From 49563ea6d4231c115de76cfe4c324a8b755d8f46 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Wed, 24 Apr 2024 22:31:45 +0000 Subject: [PATCH] Added giveaway commands .ga start/end/cancel/reroll/list --- CHANGELOG.md | 7 + .../Models/giveaway/GiveawayModel.cs | 1 + .../Giveaway/GiveawayCommands.cs | 42 ++-- .../Giveaway/GiveawayService.cs | 202 +++++++++++++++--- ...cs => 20240424203922_giveaway.Designer.cs} | 6 +- ...giveaway.cs => 20240424203922_giveaway.cs} | 1 + .../Mysql/MysqlContextModelSnapshot.cs | 4 + ...cs => 20240424203915_giveaway.Designer.cs} | 6 +- ...giveaway.cs => 20240424203915_giveaway.cs} | 1 + .../PostgreSqlContextModelSnapshot.cs | 4 + ...cs => 20240424203909_giveaway.Designer.cs} | 5 +- ...giveaway.cs => 20240424203909_giveaway.cs} | 1 + .../Sqlite/SqliteContextModelSnapshot.cs | 3 + .../data/strings/commands/commands.en-US.yml | 16 +- .../strings/responses/responses.en-US.json | 3 +- src/NadekoBot/remove-migration.ps1 | 2 +- 16 files changed, 247 insertions(+), 57 deletions(-) rename src/NadekoBot/Migrations/Mysql/{20240424152958_giveaway.Designer.cs => 20240424203922_giveaway.Designer.cs} (99%) rename src/NadekoBot/Migrations/Mysql/{20240424152958_giveaway.cs => 20240424203922_giveaway.cs} (96%) rename src/NadekoBot/Migrations/Postgresql/{20240424152951_giveaway.Designer.cs => 20240424203915_giveaway.Designer.cs} (99%) rename src/NadekoBot/Migrations/Postgresql/{20240424152951_giveaway.cs => 20240424203915_giveaway.cs} (96%) rename src/NadekoBot/Migrations/Sqlite/{20240424152943_giveaway.Designer.cs => 20240424203909_giveaway.Designer.cs} (99%) rename src/NadekoBot/Migrations/Sqlite/{20240424152943_giveaway.cs => 20240424203909_giveaway.cs} (96%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1142da5dd..2d22a43fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,13 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog. - `.shopadd command` You can now sell commands in the shop. The command will execute as if you were the one running it when someone buys it - type `.h .shopadd` for more info - `.stickyroles` Users leaving the server will have their roles saved to the database and reapplied if they rejoin within 30 days. +- Giveaway commands + - `.ga start` starts the giveway with the specified duration and message (prize). You may have up to 5 giveaways on the server at once + - `.ga end ` prematurely ends the giveaway and selects a winner + - `.ga cancel ` cancels the giveaway and doesn't select the winner + - `.ga list` lists active giveaways on the current server + - `.ga reroll ` rerolls the winner on the completed giveaway. This only works for 24 hours after the giveaway has ended, or until the bot restarts. + - After the giveaway has started, user join the giveaway by adding a :tada: reaction ## [4.3.22] - 23.04.2023 diff --git a/src/Nadeko.Bot.Db/Models/giveaway/GiveawayModel.cs b/src/Nadeko.Bot.Db/Models/giveaway/GiveawayModel.cs index 2ce735bcb..208bbe946 100644 --- a/src/Nadeko.Bot.Db/Models/giveaway/GiveawayModel.cs +++ b/src/Nadeko.Bot.Db/Models/giveaway/GiveawayModel.cs @@ -5,6 +5,7 @@ public sealed class GiveawayModel public int Id { get; set; } public ulong GuildId { get; set; } public ulong MessageId { get; set; } + public ulong ChannelId { get; set; } public string Message { get; set; } public IList Participants { get; set; } = new List(); diff --git a/src/Nadeko.Bot.Modules.Utility/Giveaway/GiveawayCommands.cs b/src/Nadeko.Bot.Modules.Utility/Giveaway/GiveawayCommands.cs index 4ed420726..2147e6ad4 100644 --- a/src/Nadeko.Bot.Modules.Utility/Giveaway/GiveawayCommands.cs +++ b/src/Nadeko.Bot.Modules.Utility/Giveaway/GiveawayCommands.cs @@ -36,8 +36,9 @@ public partial class Utility } eb + .WithOkColor() .WithTitle(GetText(strs.giveaway_started)) - .WithFooter($"id: {new kwum(id).ToString()}"); + .WithFooter($"id: {new kwum(id).ToString()}"); await startingMsg.AddReactionAsync(new Emoji(GiveawayService.GiveawayEmoji)); await startingMsg.ModifyAsync(x => x.Embed = eb.Build()); @@ -47,30 +48,32 @@ public partial class Utility [UserPerm(GuildPerm.ManageMessages)] public async Task GiveawayEnd(kwum id) { - var (giveaway, winner) = await _service.EndGiveawayAsync(ctx.Guild.Id, id); + var success = await _service.EndGiveawayAsync(ctx.Guild.Id, id); - if (winner is null || giveaway is null) - return; + if(!success) + { + await ReplyErrorLocalizedAsync(strs.giveaway_not_found); + return; + } - var eb = _eb.Create(ctx) - .WithOkColor() - .WithTitle(GetText(strs.giveaway_ended)) - .WithDescription(giveaway.Message) - .AddField(GetText(strs.winner), - $""" - {winner.Name} - <@{winner.UserId}> - {Format.Code(winner.UserId.ToString())} - """, - true); - - await ctx.Channel.EmbedAsync(eb); + await ctx.OkAsync(); + _ = ctx.Message.DeleteAfter(5); } [Cmd] [UserPerm(GuildPerm.ManageMessages)] public async Task GiveawayReroll(kwum id) - { + { + var success = await _service.RerollGiveawayAsync(ctx.Guild.Id, id); + if (!success) + { + await ReplyErrorLocalizedAsync(strs.giveaway_not_found); + return; + } + + + await ctx.OkAsync(); + _ = ctx.Message.DeleteAfter(5); } [Cmd] @@ -101,11 +104,12 @@ public partial class Utility } var eb = _eb.Create(ctx) + .WithTitle(GetText(strs.giveaway_list)) .WithOkColor(); foreach (var g in giveaways) { - eb.AddField($"id: {new kwum(g.Id)}", g.Message, true); + eb.AddField($"id: {new kwum(g.Id)}", g.Message, true); } await ctx.Channel.EmbedAsync(eb); diff --git a/src/Nadeko.Bot.Modules.Utility/Giveaway/GiveawayService.cs b/src/Nadeko.Bot.Modules.Utility/Giveaway/GiveawayService.cs index 0970ab2b9..a97294625 100644 --- a/src/Nadeko.Bot.Modules.Utility/Giveaway/GiveawayService.cs +++ b/src/Nadeko.Bot.Modules.Utility/Giveaway/GiveawayService.cs @@ -1,5 +1,6 @@ using LinqToDB; using LinqToDB.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Memory; using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Db.Models; @@ -12,14 +13,24 @@ public sealed class GiveawayService : INService, IReadyExecutor private readonly DbService _db; private readonly IBotCredentials _creds; private readonly DiscordSocketClient _client; - private GiveawayModel[] _shardGiveaways; + private readonly IEmbedBuilderService _eb; + private readonly IBotStrings _strings; + private readonly ILocalization _localization; + private readonly IMemoryCache _cache; + private SortedSet _giveawayCache = new SortedSet(); private readonly NadekoRandom _rng; + private readonly ConcurrentDictionary _rerolls = new(); - public GiveawayService(DbService db, IBotCredentials creds, DiscordSocketClient client) + public GiveawayService(DbService db, IBotCredentials creds, DiscordSocketClient client, + IEmbedBuilderService eb, IBotStrings strings, ILocalization localization, IMemoryCache cache) { _db = db; _creds = creds; _client = client; + _eb = eb; + _strings = strings; + _localization = localization; + _cache = cache; _rng = new NadekoRandom(); @@ -33,12 +44,12 @@ public sealed class GiveawayService : INService, IReadyExecutor { if (!r.User.IsSpecified) return; - + var user = r.User.Value; - + if (user.IsBot || user.IsWebhook) return; - + if (r.Emote is Emoji e && e.Name == GiveawayEmoji) { await LeaveGivawayAsync(msg.Id, user.Id); @@ -73,10 +84,40 @@ public sealed class GiveawayService : INService, IReadyExecutor // load giveaways for this shard from the database await using var ctx = _db.GetDbContext(); - _shardGiveaways = await ctx + var gas = await ctx .GetTable() .Where(x => Linq2DbExpressions.GuildOnShard(x.GuildId, _creds.TotalShards, _client.ShardId)) .ToArrayAsync(); + + lock (_giveawayCache) + { + _giveawayCache = new(gas, Comparer.Create((x, y) => x.EndsAt.CompareTo(y.EndsAt))); + } + + var timer = new PeriodicTimer(TimeSpan.FromMinutes(1)); + + while (await timer.WaitForNextTickAsync()) + { + IEnumerable toEnd; + lock (_giveawayCache) + { + toEnd = _giveawayCache.TakeWhile( + x => x.EndsAt <= DateTime.UtcNow.AddSeconds(15)) + .ToArray(); + } + + foreach (var ga in toEnd) + { + try + { + await EndGiveawayAsync(ga.GuildId, ga.Id); + } + catch + { + Log.Warning("Failed to end the giveaway with id {Id}", ga.Id); + } + } + } } public async Task StartGiveawayAsync(ulong guildId, ulong channelId, ulong messageId, TimeSpan duration, @@ -93,20 +134,26 @@ public sealed class GiveawayService : INService, IReadyExecutor return null; var endsAt = DateTime.UtcNow + duration; - var output = await ctx.GetTable() + var ga = await ctx.GetTable() .InsertWithOutputAsync(() => new GiveawayModel { GuildId = guildId, MessageId = messageId, + ChannelId = channelId, Message = message, EndsAt = endsAt, }); - return output.Id; + lock (_giveawayCache) + { + _giveawayCache.Add(ga); + } + + return ga.Id; } - public async Task<(GiveawayModel? giveaway, GiveawayUser? winner)> EndGiveawayAsync(ulong guildId, int id) + public async Task EndGiveawayAsync(ulong guildId, int id) { await using var ctx = _db.GetDbContext(); @@ -117,38 +164,78 @@ public sealed class GiveawayService : INService, IReadyExecutor .FirstOrDefaultAsyncLinqToDB(); if (giveaway is null) - return default; + return false; await ctx .GetTable() .Where(x => x.Id == id) .DeleteAsync(); - if (giveaway.Participants.Count == 0) - return default; - - if (giveaway.Participants.Count == 1) - return (giveaway, giveaway.Participants[0]); + lock (_giveawayCache) + { + _giveawayCache.Remove(giveaway); + } - return (giveaway, giveaway.Participants[_rng.Next(0, giveaway.Participants.Count - 1)]); + var winner = PickWinner(giveaway); + + await OnGiveawayEnded(giveaway, winner); + + return true; } - public async Task RerollGiveawayAsync(ulong guildId, int id) + private GiveawayUser? PickWinner(GiveawayModel giveaway) { + if (giveaway.Participants.Count == 0) + return default; + + if (giveaway.Participants.Count == 1) + { + // as this is the last participant, rerolls no longer possible + _cache.Remove($"reroll:{giveaway.Id}"); + return giveaway.Participants[0]; + } + + var winner = giveaway.Participants[_rng.Next(0, giveaway.Participants.Count - 1)]; + + HandleWinnerSelection(giveaway, winner); + return winner; + } + + public async Task RerollGiveawayAsync(ulong guildId, int giveawayId) + { + var rerollModel = _cache.Get("reroll:" + giveawayId); + + if (rerollModel is null) + return false; + + var winner = PickWinner(rerollModel.Giveaway); + + if (winner is not null) + { + await OnGiveawayEnded(rerollModel.Giveaway, winner); + } + + return true; } public async Task CancelGiveawayAsync(ulong guildId, int id) { await using var ctx = _db.GetDbContext(); - var count = await ctx + var ga = await ctx .GetTable() .Where(x => x.GuildId == guildId && x.Id == id) - .DeleteAsync(); + .DeleteWithOutputAsync(); - // todo clear cache + if (ga is not { Length: > 0 }) + return false; - return count > 0; + lock (_giveawayCache) + { + _giveawayCache.Remove(ga[0]); + } + + return true; } public async Task> GetGiveawaysAsync(ulong guildId) @@ -190,20 +277,85 @@ public sealed class GiveawayService : INService, IReadyExecutor public async Task LeaveGivawayAsync(ulong messageId, ulong userId) { await using var ctx = _db.GetDbContext(); - + var giveaway = await ctx .GetTable() .Where(x => x.MessageId == messageId) .FirstOrDefaultAsyncLinqToDB(); - + if (giveaway is null) return false; - + await ctx .GetTable() .Where(x => x.UserId == userId && x.GiveawayId == giveaway.Id) .DeleteAsync(); - + return true; } + + public async Task OnGiveawayEnded(GiveawayModel ga, GiveawayUser? winner) + { + var culture = _localization.GetCultureInfo(ga.GuildId); + + string GetText(LocStr str) + => _strings.GetText(str, culture); + + var ch = _client.GetChannel(ga.ChannelId) as ITextChannel; + if (ch is null) + return; + + var msg = await ch.GetMessageAsync(ga.MessageId) as IUserMessage; + if (msg is null) + return; + + var winnerStr = winner is null + ? "-" + : $""" + {winner.Name} + <@{winner.UserId}> + {Format.Code(winner.UserId.ToString())} + """; + + var eb = _eb + .Create() + .WithOkColor() + .WithTitle(GetText(strs.giveaway_ended)) + .WithDescription(ga.Message) + .WithFooter($"id: {new kwum(ga.Id).ToString()}") + .AddField(GetText(strs.winner), + winnerStr, + true); + + try + { + await msg.ModifyAsync(x => x.Embed = eb.Build()); + } + catch + { + _ = msg.DeleteAsync(); + await ch.EmbedAsync(eb); + } + } + + private void HandleWinnerSelection(GiveawayModel ga, GiveawayUser winner) + { + ga.Participants = ga.Participants.Where(x => x.UserId != winner.UserId).ToList(); + + var rerollData = new GiveawayRerollData(ga); + _cache.Set($"reroll:{ga.Id}", rerollData, TimeSpan.FromDays(1)); + } +} + +public sealed class GiveawayRerollData +{ + public GiveawayModel Giveaway { get; init; } + public DateTime ExpiresAt { get; init; } + + public GiveawayRerollData(GiveawayModel ga) + { + Giveaway = ga; + + ExpiresAt = DateTime.UtcNow.AddDays(1); + } } \ No newline at end of file diff --git a/src/NadekoBot/Migrations/Mysql/20240424152958_giveaway.Designer.cs b/src/NadekoBot/Migrations/Mysql/20240424203922_giveaway.Designer.cs similarity index 99% rename from src/NadekoBot/Migrations/Mysql/20240424152958_giveaway.Designer.cs rename to src/NadekoBot/Migrations/Mysql/20240424203922_giveaway.Designer.cs index 1a87a9cd6..408b12712 100644 --- a/src/NadekoBot/Migrations/Mysql/20240424152958_giveaway.Designer.cs +++ b/src/NadekoBot/Migrations/Mysql/20240424203922_giveaway.Designer.cs @@ -12,7 +12,7 @@ using Nadeko.Bot.Db; namespace NadekoBot.Db.Migrations.Mysql { [DbContext(typeof(MysqlContext))] - [Migration("20240424152958_giveaway")] + [Migration("20240424203922_giveaway")] partial class giveaway { /// @@ -3085,6 +3085,10 @@ namespace NadekoBot.Db.Migrations.Mysql MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("ChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("channelid"); + b.Property("EndsAt") .HasColumnType("datetime(6)") .HasColumnName("endsat"); diff --git a/src/NadekoBot/Migrations/Mysql/20240424152958_giveaway.cs b/src/NadekoBot/Migrations/Mysql/20240424203922_giveaway.cs similarity index 96% rename from src/NadekoBot/Migrations/Mysql/20240424152958_giveaway.cs rename to src/NadekoBot/Migrations/Mysql/20240424203922_giveaway.cs index e6155ff42..b4dab4d60 100644 --- a/src/NadekoBot/Migrations/Mysql/20240424152958_giveaway.cs +++ b/src/NadekoBot/Migrations/Mysql/20240424203922_giveaway.cs @@ -20,6 +20,7 @@ namespace NadekoBot.Db.Migrations.Mysql .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), guildid = table.Column(type: "bigint unsigned", nullable: false), messageid = table.Column(type: "bigint unsigned", nullable: false), + channelid = table.Column(type: "bigint unsigned", nullable: false), message = table.Column(type: "longtext", nullable: false) .Annotation("MySql:CharSet", "utf8mb4"), endsat = table.Column(type: "datetime(6)", nullable: false) diff --git a/src/NadekoBot/Migrations/Mysql/MysqlContextModelSnapshot.cs b/src/NadekoBot/Migrations/Mysql/MysqlContextModelSnapshot.cs index c027c4f3f..1832e2177 100644 --- a/src/NadekoBot/Migrations/Mysql/MysqlContextModelSnapshot.cs +++ b/src/NadekoBot/Migrations/Mysql/MysqlContextModelSnapshot.cs @@ -3082,6 +3082,10 @@ namespace NadekoBot.Db.Migrations.Mysql MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("ChannelId") + .HasColumnType("bigint unsigned") + .HasColumnName("channelid"); + b.Property("EndsAt") .HasColumnType("datetime(6)") .HasColumnName("endsat"); diff --git a/src/NadekoBot/Migrations/Postgresql/20240424152951_giveaway.Designer.cs b/src/NadekoBot/Migrations/Postgresql/20240424203915_giveaway.Designer.cs similarity index 99% rename from src/NadekoBot/Migrations/Postgresql/20240424152951_giveaway.Designer.cs rename to src/NadekoBot/Migrations/Postgresql/20240424203915_giveaway.Designer.cs index 2d1407745..465f1c555 100644 --- a/src/NadekoBot/Migrations/Postgresql/20240424152951_giveaway.Designer.cs +++ b/src/NadekoBot/Migrations/Postgresql/20240424203915_giveaway.Designer.cs @@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace NadekoBot.Db.Migrations { [DbContext(typeof(PostgreSqlContext))] - [Migration("20240424152951_giveaway")] + [Migration("20240424203915_giveaway")] partial class giveaway { /// @@ -3084,6 +3084,10 @@ namespace NadekoBot.Db.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("ChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelid"); + b.Property("EndsAt") .HasColumnType("timestamp without time zone") .HasColumnName("endsat"); diff --git a/src/NadekoBot/Migrations/Postgresql/20240424152951_giveaway.cs b/src/NadekoBot/Migrations/Postgresql/20240424203915_giveaway.cs similarity index 96% rename from src/NadekoBot/Migrations/Postgresql/20240424152951_giveaway.cs rename to src/NadekoBot/Migrations/Postgresql/20240424203915_giveaway.cs index a0834356a..8796c8596 100644 --- a/src/NadekoBot/Migrations/Postgresql/20240424152951_giveaway.cs +++ b/src/NadekoBot/Migrations/Postgresql/20240424203915_giveaway.cs @@ -20,6 +20,7 @@ namespace NadekoBot.Db.Migrations .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), guildid = table.Column(type: "numeric(20,0)", nullable: false), messageid = table.Column(type: "numeric(20,0)", nullable: false), + channelid = table.Column(type: "numeric(20,0)", nullable: false), message = table.Column(type: "text", nullable: false), endsat = table.Column(type: "timestamp without time zone", nullable: false) }, diff --git a/src/NadekoBot/Migrations/Postgresql/PostgreSqlContextModelSnapshot.cs b/src/NadekoBot/Migrations/Postgresql/PostgreSqlContextModelSnapshot.cs index 8d5cff49b..d305d44c1 100644 --- a/src/NadekoBot/Migrations/Postgresql/PostgreSqlContextModelSnapshot.cs +++ b/src/NadekoBot/Migrations/Postgresql/PostgreSqlContextModelSnapshot.cs @@ -3081,6 +3081,10 @@ namespace NadekoBot.Db.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("ChannelId") + .HasColumnType("numeric(20,0)") + .HasColumnName("channelid"); + b.Property("EndsAt") .HasColumnType("timestamp without time zone") .HasColumnName("endsat"); diff --git a/src/NadekoBot/Migrations/Sqlite/20240424152943_giveaway.Designer.cs b/src/NadekoBot/Migrations/Sqlite/20240424203909_giveaway.Designer.cs similarity index 99% rename from src/NadekoBot/Migrations/Sqlite/20240424152943_giveaway.Designer.cs rename to src/NadekoBot/Migrations/Sqlite/20240424203909_giveaway.Designer.cs index 481962636..7b906ac56 100644 --- a/src/NadekoBot/Migrations/Sqlite/20240424152943_giveaway.Designer.cs +++ b/src/NadekoBot/Migrations/Sqlite/20240424203909_giveaway.Designer.cs @@ -11,7 +11,7 @@ using Nadeko.Bot.Db; namespace NadekoBot.Db.Migrations.Sqlite { [DbContext(typeof(SqliteContext))] - [Migration("20240424152943_giveaway")] + [Migration("20240424203909_giveaway")] partial class giveaway { /// @@ -2289,6 +2289,9 @@ namespace NadekoBot.Db.Migrations.Sqlite .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("ChannelId") + .HasColumnType("INTEGER"); + b.Property("EndsAt") .HasColumnType("TEXT"); diff --git a/src/NadekoBot/Migrations/Sqlite/20240424152943_giveaway.cs b/src/NadekoBot/Migrations/Sqlite/20240424203909_giveaway.cs similarity index 96% rename from src/NadekoBot/Migrations/Sqlite/20240424152943_giveaway.cs rename to src/NadekoBot/Migrations/Sqlite/20240424203909_giveaway.cs index 056194565..22f6a3e2c 100644 --- a/src/NadekoBot/Migrations/Sqlite/20240424152943_giveaway.cs +++ b/src/NadekoBot/Migrations/Sqlite/20240424203909_giveaway.cs @@ -19,6 +19,7 @@ namespace NadekoBot.Db.Migrations.Sqlite .Annotation("Sqlite:Autoincrement", true), GuildId = table.Column(type: "INTEGER", nullable: false), MessageId = table.Column(type: "INTEGER", nullable: false), + ChannelId = table.Column(type: "INTEGER", nullable: false), Message = table.Column(type: "TEXT", nullable: false), EndsAt = table.Column(type: "TEXT", nullable: false) }, diff --git a/src/NadekoBot/Migrations/Sqlite/SqliteContextModelSnapshot.cs b/src/NadekoBot/Migrations/Sqlite/SqliteContextModelSnapshot.cs index cb29aa420..c9a825dde 100644 --- a/src/NadekoBot/Migrations/Sqlite/SqliteContextModelSnapshot.cs +++ b/src/NadekoBot/Migrations/Sqlite/SqliteContextModelSnapshot.cs @@ -2286,6 +2286,9 @@ namespace NadekoBot.Db.Migrations.Sqlite .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("ChannelId") + .HasColumnType("INTEGER"); + b.Property("EndsAt") .HasColumnType("TEXT"); diff --git a/src/NadekoBot/data/strings/commands/commands.en-US.yml b/src/NadekoBot/data/strings/commands/commands.en-US.yml index 7f9d8cbf8..b2a445827 100644 --- a/src/NadekoBot/data/strings/commands/commands.en-US.yml +++ b/src/NadekoBot/data/strings/commands/commands.en-US.yml @@ -633,7 +633,7 @@ quotesearch: desc: "Shows a random quote given a search query. Partially matches in several ways: 1) Only content of any quote, 2) only by author, 3) keyword and content, 3) or keyword and author" args: - "\"find this long text\"" - - "AuthorName" + - "AuthorName" - "keyword some text" - "keyword AuthorName" quoteid: @@ -2006,7 +2006,7 @@ ytuploadnotif: Shortcut for `.feed https://www.youtube.com/feeds/videos.xml?channel_id=%3Cyoutube_channel_id` You can optionally specify a message which will be posted with an update. args: - - "https://www.youtube.com/channel/UCSJ4gkVC6NrvII8umztf0Ow" + - "https://www.youtube.com/channel/UCSJ4gkVC6NrvII8umztf0Ow" - "https://www.youtube.com/channel/UCSJ4gkVC6NrvII8umztf0Ow New video is posted" feed: desc: |- @@ -2266,7 +2266,7 @@ deleteemptyservers: args: - "" medusaload: - desc: |- + desc: |- Loads a medusa with the specified name from the data/medusae/ folder. Provide no name to see the list of loadable medusae. Read about the medusa system [here](https://nadekobot.readthedocs.io/en/latest/medusa/creating-a-medusa/) @@ -2289,7 +2289,7 @@ medusainfo: args: - "mycoolmedusa" - "" -medusalist: +medusalist: desc: |- Lists all loaded and unloaded medusae. Read about the medusa system [here](https://nadekobot.readthedocs.io/en/latest/medusa/creating-a-medusa/) @@ -2329,7 +2329,7 @@ patronmessage: args: - "x hello" eval: - desc: |- + desc: |- Execute arbitrary C# code and (optionally) return a result. Several namespaces are included by default. Special variables available: `self` - Instance of the command group executing the command (this) @@ -2397,15 +2397,15 @@ giveawaystart: - "15m Quick giveaway for a free course!" - "1d Join to win 1000$!" giveawayend: - desc: "Prematurely ends a giveaway and chooses a winner. Specify the ID of the giveaway to end." + desc: "Prematurely ends a giveaway and selects a winner. Specify the ID of the giveaway to end." args: - "ab3" giveawaycancel: - desc: "Cancels a giveaway. Specify the ID of the giveaway to cancel." + desc: "Cancels a giveaway. Specify the ID of the giveaway to cancel. The winner will not be chosen." args: - "ab3" giveawayreroll: - desc: "Rerolls a giveaway. Specify the ID of the giveaway to reroll. The winner will not be chosen." + desc: "Rerolls a giveaway. Specify the ID of the giveaway to reroll. This is only active within 24h after the giveaway has ended or until the bot restarts." args: - "cd3" giveawaylist: diff --git a/src/NadekoBot/data/strings/responses/responses.en-US.json b/src/NadekoBot/data/strings/responses/responses.en-US.json index c17d06fb6..9e4a509bf 100644 --- a/src/NadekoBot/data/strings/responses/responses.en-US.json +++ b/src/NadekoBot/data/strings/responses/responses.en-US.json @@ -1075,5 +1075,6 @@ "no_givaways": "There are no active giveaways on this server.", "giveaway_cancelled": "Giveaway cancelled.", "giveaway_starting": "Starting giveaway...", - "winner": "Winner" + "winner": "Winner", + "giveaway_list": "List of active giveways", } diff --git a/src/NadekoBot/remove-migration.ps1 b/src/NadekoBot/remove-migration.ps1 index 1db1277b3..877458391 100644 --- a/src/NadekoBot/remove-migration.ps1 +++ b/src/NadekoBot/remove-migration.ps1 @@ -1,4 +1,4 @@ -dotnet ef migrations remove -c SqliteContext +dotnet ef migrations remove -c SqliteContext -f dotnet ef migrations remove -c PostgreSqlContext -f dotnet ef migrations remove -c MysqlContext -f