mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
Added giveaway commands .ga start/end/cancel/reroll/list
This commit is contained in:
@@ -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
|
- `.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
|
- 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.
|
- `.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 <id>` prematurely ends the giveaway and selects a winner
|
||||||
|
- `.ga cancel <id>` cancels the giveaway and doesn't select the winner
|
||||||
|
- `.ga list` lists active giveaways on the current server
|
||||||
|
- `.ga reroll <id>` 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
|
## [4.3.22] - 23.04.2023
|
||||||
|
|
||||||
|
@@ -5,6 +5,7 @@ public sealed class GiveawayModel
|
|||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public ulong GuildId { get; set; }
|
public ulong GuildId { get; set; }
|
||||||
public ulong MessageId { get; set; }
|
public ulong MessageId { get; set; }
|
||||||
|
public ulong ChannelId { get; set; }
|
||||||
public string Message { get; set; }
|
public string Message { get; set; }
|
||||||
|
|
||||||
public IList<GiveawayUser> Participants { get; set; } = new List<GiveawayUser>();
|
public IList<GiveawayUser> Participants { get; set; } = new List<GiveawayUser>();
|
||||||
|
@@ -36,8 +36,9 @@ public partial class Utility
|
|||||||
}
|
}
|
||||||
|
|
||||||
eb
|
eb
|
||||||
|
.WithOkColor()
|
||||||
.WithTitle(GetText(strs.giveaway_started))
|
.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.AddReactionAsync(new Emoji(GiveawayService.GiveawayEmoji));
|
||||||
await startingMsg.ModifyAsync(x => x.Embed = eb.Build());
|
await startingMsg.ModifyAsync(x => x.Embed = eb.Build());
|
||||||
@@ -47,30 +48,32 @@ public partial class Utility
|
|||||||
[UserPerm(GuildPerm.ManageMessages)]
|
[UserPerm(GuildPerm.ManageMessages)]
|
||||||
public async Task GiveawayEnd(kwum id)
|
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)
|
if(!success)
|
||||||
return;
|
{
|
||||||
|
await ReplyErrorLocalizedAsync(strs.giveaway_not_found);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var eb = _eb.Create(ctx)
|
await ctx.OkAsync();
|
||||||
.WithOkColor()
|
_ = ctx.Message.DeleteAfter(5);
|
||||||
.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[UserPerm(GuildPerm.ManageMessages)]
|
[UserPerm(GuildPerm.ManageMessages)]
|
||||||
public async Task GiveawayReroll(kwum id)
|
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]
|
[Cmd]
|
||||||
@@ -101,11 +104,12 @@ public partial class Utility
|
|||||||
}
|
}
|
||||||
|
|
||||||
var eb = _eb.Create(ctx)
|
var eb = _eb.Create(ctx)
|
||||||
|
.WithTitle(GetText(strs.giveaway_list))
|
||||||
.WithOkColor();
|
.WithOkColor();
|
||||||
|
|
||||||
foreach (var g in giveaways)
|
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);
|
await ctx.Channel.EmbedAsync(eb);
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using NadekoBot.Common.ModuleBehaviors;
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
using NadekoBot.Db.Models;
|
using NadekoBot.Db.Models;
|
||||||
|
|
||||||
@@ -12,14 +13,24 @@ public sealed class GiveawayService : INService, IReadyExecutor
|
|||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
private readonly IBotCredentials _creds;
|
private readonly IBotCredentials _creds;
|
||||||
private readonly DiscordSocketClient _client;
|
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<GiveawayModel> _giveawayCache = new SortedSet<GiveawayModel>();
|
||||||
private readonly NadekoRandom _rng;
|
private readonly NadekoRandom _rng;
|
||||||
|
private readonly ConcurrentDictionary<int, GiveawayRerollData> _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;
|
_db = db;
|
||||||
_creds = creds;
|
_creds = creds;
|
||||||
_client = client;
|
_client = client;
|
||||||
|
_eb = eb;
|
||||||
|
_strings = strings;
|
||||||
|
_localization = localization;
|
||||||
|
_cache = cache;
|
||||||
_rng = new NadekoRandom();
|
_rng = new NadekoRandom();
|
||||||
|
|
||||||
|
|
||||||
@@ -33,12 +44,12 @@ public sealed class GiveawayService : INService, IReadyExecutor
|
|||||||
{
|
{
|
||||||
if (!r.User.IsSpecified)
|
if (!r.User.IsSpecified)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var user = r.User.Value;
|
var user = r.User.Value;
|
||||||
|
|
||||||
if (user.IsBot || user.IsWebhook)
|
if (user.IsBot || user.IsWebhook)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (r.Emote is Emoji e && e.Name == GiveawayEmoji)
|
if (r.Emote is Emoji e && e.Name == GiveawayEmoji)
|
||||||
{
|
{
|
||||||
await LeaveGivawayAsync(msg.Id, user.Id);
|
await LeaveGivawayAsync(msg.Id, user.Id);
|
||||||
@@ -73,10 +84,40 @@ public sealed class GiveawayService : INService, IReadyExecutor
|
|||||||
// load giveaways for this shard from the database
|
// load giveaways for this shard from the database
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
|
|
||||||
_shardGiveaways = await ctx
|
var gas = await ctx
|
||||||
.GetTable<GiveawayModel>()
|
.GetTable<GiveawayModel>()
|
||||||
.Where(x => Linq2DbExpressions.GuildOnShard(x.GuildId, _creds.TotalShards, _client.ShardId))
|
.Where(x => Linq2DbExpressions.GuildOnShard(x.GuildId, _creds.TotalShards, _client.ShardId))
|
||||||
.ToArrayAsync();
|
.ToArrayAsync();
|
||||||
|
|
||||||
|
lock (_giveawayCache)
|
||||||
|
{
|
||||||
|
_giveawayCache = new(gas, Comparer<GiveawayModel>.Create((x, y) => x.EndsAt.CompareTo(y.EndsAt)));
|
||||||
|
}
|
||||||
|
|
||||||
|
var timer = new PeriodicTimer(TimeSpan.FromMinutes(1));
|
||||||
|
|
||||||
|
while (await timer.WaitForNextTickAsync())
|
||||||
|
{
|
||||||
|
IEnumerable<GiveawayModel> 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<int?> StartGiveawayAsync(ulong guildId, ulong channelId, ulong messageId, TimeSpan duration,
|
public async Task<int?> StartGiveawayAsync(ulong guildId, ulong channelId, ulong messageId, TimeSpan duration,
|
||||||
@@ -93,20 +134,26 @@ public sealed class GiveawayService : INService, IReadyExecutor
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
var endsAt = DateTime.UtcNow + duration;
|
var endsAt = DateTime.UtcNow + duration;
|
||||||
var output = await ctx.GetTable<GiveawayModel>()
|
var ga = await ctx.GetTable<GiveawayModel>()
|
||||||
.InsertWithOutputAsync(() => new GiveawayModel
|
.InsertWithOutputAsync(() => new GiveawayModel
|
||||||
{
|
{
|
||||||
GuildId = guildId,
|
GuildId = guildId,
|
||||||
MessageId = messageId,
|
MessageId = messageId,
|
||||||
|
ChannelId = channelId,
|
||||||
Message = message,
|
Message = message,
|
||||||
EndsAt = endsAt,
|
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<bool> EndGiveawayAsync(ulong guildId, int id)
|
||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
|
|
||||||
@@ -117,38 +164,78 @@ public sealed class GiveawayService : INService, IReadyExecutor
|
|||||||
.FirstOrDefaultAsyncLinqToDB();
|
.FirstOrDefaultAsyncLinqToDB();
|
||||||
|
|
||||||
if (giveaway is null)
|
if (giveaway is null)
|
||||||
return default;
|
return false;
|
||||||
|
|
||||||
await ctx
|
await ctx
|
||||||
.GetTable<GiveawayModel>()
|
.GetTable<GiveawayModel>()
|
||||||
.Where(x => x.Id == id)
|
.Where(x => x.Id == id)
|
||||||
.DeleteAsync();
|
.DeleteAsync();
|
||||||
|
|
||||||
if (giveaway.Participants.Count == 0)
|
lock (_giveawayCache)
|
||||||
return default;
|
{
|
||||||
|
_giveawayCache.Remove(giveaway);
|
||||||
if (giveaway.Participants.Count == 1)
|
}
|
||||||
return (giveaway, giveaway.Participants[0]);
|
|
||||||
|
|
||||||
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<bool> RerollGiveawayAsync(ulong guildId, int giveawayId)
|
||||||
|
{
|
||||||
|
var rerollModel = _cache.Get<GiveawayRerollData>("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<bool> CancelGiveawayAsync(ulong guildId, int id)
|
public async Task<bool> CancelGiveawayAsync(ulong guildId, int id)
|
||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
|
|
||||||
var count = await ctx
|
var ga = await ctx
|
||||||
.GetTable<GiveawayModel>()
|
.GetTable<GiveawayModel>()
|
||||||
.Where(x => x.GuildId == guildId && x.Id == id)
|
.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<IReadOnlyCollection<GiveawayModel>> GetGiveawaysAsync(ulong guildId)
|
public async Task<IReadOnlyCollection<GiveawayModel>> GetGiveawaysAsync(ulong guildId)
|
||||||
@@ -190,20 +277,85 @@ public sealed class GiveawayService : INService, IReadyExecutor
|
|||||||
public async Task<bool> LeaveGivawayAsync(ulong messageId, ulong userId)
|
public async Task<bool> LeaveGivawayAsync(ulong messageId, ulong userId)
|
||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
|
|
||||||
var giveaway = await ctx
|
var giveaway = await ctx
|
||||||
.GetTable<GiveawayModel>()
|
.GetTable<GiveawayModel>()
|
||||||
.Where(x => x.MessageId == messageId)
|
.Where(x => x.MessageId == messageId)
|
||||||
.FirstOrDefaultAsyncLinqToDB();
|
.FirstOrDefaultAsyncLinqToDB();
|
||||||
|
|
||||||
if (giveaway is null)
|
if (giveaway is null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
await ctx
|
await ctx
|
||||||
.GetTable<GiveawayUser>()
|
.GetTable<GiveawayUser>()
|
||||||
.Where(x => x.UserId == userId && x.GiveawayId == giveaway.Id)
|
.Where(x => x.UserId == userId && x.GiveawayId == giveaway.Id)
|
||||||
.DeleteAsync();
|
.DeleteAsync();
|
||||||
|
|
||||||
return true;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
@@ -12,7 +12,7 @@ using Nadeko.Bot.Db;
|
|||||||
namespace NadekoBot.Db.Migrations.Mysql
|
namespace NadekoBot.Db.Migrations.Mysql
|
||||||
{
|
{
|
||||||
[DbContext(typeof(MysqlContext))]
|
[DbContext(typeof(MysqlContext))]
|
||||||
[Migration("20240424152958_giveaway")]
|
[Migration("20240424203922_giveaway")]
|
||||||
partial class giveaway
|
partial class giveaway
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -3085,6 +3085,10 @@ namespace NadekoBot.Db.Migrations.Mysql
|
|||||||
|
|
||||||
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
|
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<ulong>("ChannelId")
|
||||||
|
.HasColumnType("bigint unsigned")
|
||||||
|
.HasColumnName("channelid");
|
||||||
|
|
||||||
b.Property<DateTime>("EndsAt")
|
b.Property<DateTime>("EndsAt")
|
||||||
.HasColumnType("datetime(6)")
|
.HasColumnType("datetime(6)")
|
||||||
.HasColumnName("endsat");
|
.HasColumnName("endsat");
|
@@ -20,6 +20,7 @@ namespace NadekoBot.Db.Migrations.Mysql
|
|||||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
guildid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
|
guildid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
|
||||||
messageid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
|
messageid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
|
||||||
|
channelid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
|
||||||
message = table.Column<string>(type: "longtext", nullable: false)
|
message = table.Column<string>(type: "longtext", nullable: false)
|
||||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
endsat = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
endsat = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
@@ -3082,6 +3082,10 @@ namespace NadekoBot.Db.Migrations.Mysql
|
|||||||
|
|
||||||
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
|
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<ulong>("ChannelId")
|
||||||
|
.HasColumnType("bigint unsigned")
|
||||||
|
.HasColumnName("channelid");
|
||||||
|
|
||||||
b.Property<DateTime>("EndsAt")
|
b.Property<DateTime>("EndsAt")
|
||||||
.HasColumnType("datetime(6)")
|
.HasColumnType("datetime(6)")
|
||||||
.HasColumnName("endsat");
|
.HasColumnName("endsat");
|
||||||
|
@@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|||||||
namespace NadekoBot.Db.Migrations
|
namespace NadekoBot.Db.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PostgreSqlContext))]
|
[DbContext(typeof(PostgreSqlContext))]
|
||||||
[Migration("20240424152951_giveaway")]
|
[Migration("20240424203915_giveaway")]
|
||||||
partial class giveaway
|
partial class giveaway
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -3084,6 +3084,10 @@ namespace NadekoBot.Db.Migrations
|
|||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("ChannelId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("channelid");
|
||||||
|
|
||||||
b.Property<DateTime>("EndsAt")
|
b.Property<DateTime>("EndsAt")
|
||||||
.HasColumnType("timestamp without time zone")
|
.HasColumnType("timestamp without time zone")
|
||||||
.HasColumnName("endsat");
|
.HasColumnName("endsat");
|
@@ -20,6 +20,7 @@ namespace NadekoBot.Db.Migrations
|
|||||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||||
messageid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
messageid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||||
|
channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||||
message = table.Column<string>(type: "text", nullable: false),
|
message = table.Column<string>(type: "text", nullable: false),
|
||||||
endsat = table.Column<DateTime>(type: "timestamp without time zone", nullable: false)
|
endsat = table.Column<DateTime>(type: "timestamp without time zone", nullable: false)
|
||||||
},
|
},
|
@@ -3081,6 +3081,10 @@ namespace NadekoBot.Db.Migrations
|
|||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("ChannelId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("channelid");
|
||||||
|
|
||||||
b.Property<DateTime>("EndsAt")
|
b.Property<DateTime>("EndsAt")
|
||||||
.HasColumnType("timestamp without time zone")
|
.HasColumnType("timestamp without time zone")
|
||||||
.HasColumnName("endsat");
|
.HasColumnName("endsat");
|
||||||
|
@@ -11,7 +11,7 @@ using Nadeko.Bot.Db;
|
|||||||
namespace NadekoBot.Db.Migrations.Sqlite
|
namespace NadekoBot.Db.Migrations.Sqlite
|
||||||
{
|
{
|
||||||
[DbContext(typeof(SqliteContext))]
|
[DbContext(typeof(SqliteContext))]
|
||||||
[Migration("20240424152943_giveaway")]
|
[Migration("20240424203909_giveaway")]
|
||||||
partial class giveaway
|
partial class giveaway
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -2289,6 +2289,9 @@ namespace NadekoBot.Db.Migrations.Sqlite
|
|||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ulong>("ChannelId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<DateTime>("EndsAt")
|
b.Property<DateTime>("EndsAt")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
@@ -19,6 +19,7 @@ namespace NadekoBot.Db.Migrations.Sqlite
|
|||||||
.Annotation("Sqlite:Autoincrement", true),
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||||
MessageId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
MessageId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||||
|
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||||
Message = table.Column<string>(type: "TEXT", nullable: false),
|
Message = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
EndsAt = table.Column<DateTime>(type: "TEXT", nullable: false)
|
EndsAt = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||||
},
|
},
|
@@ -2286,6 +2286,9 @@ namespace NadekoBot.Db.Migrations.Sqlite
|
|||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ulong>("ChannelId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<DateTime>("EndsAt")
|
b.Property<DateTime>("EndsAt")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
@@ -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"
|
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:
|
args:
|
||||||
- "\"find this long text\""
|
- "\"find this long text\""
|
||||||
- "AuthorName"
|
- "AuthorName"
|
||||||
- "keyword some text"
|
- "keyword some text"
|
||||||
- "keyword AuthorName"
|
- "keyword AuthorName"
|
||||||
quoteid:
|
quoteid:
|
||||||
@@ -2006,7 +2006,7 @@ ytuploadnotif:
|
|||||||
Shortcut for `.feed https://www.youtube.com/feeds/videos.xml?channel_id=%3Cyoutube_channel_id`
|
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.
|
You can optionally specify a message which will be posted with an update.
|
||||||
args:
|
args:
|
||||||
- "https://www.youtube.com/channel/UCSJ4gkVC6NrvII8umztf0Ow"
|
- "https://www.youtube.com/channel/UCSJ4gkVC6NrvII8umztf0Ow"
|
||||||
- "https://www.youtube.com/channel/UCSJ4gkVC6NrvII8umztf0Ow New video is posted"
|
- "https://www.youtube.com/channel/UCSJ4gkVC6NrvII8umztf0Ow New video is posted"
|
||||||
feed:
|
feed:
|
||||||
desc: |-
|
desc: |-
|
||||||
@@ -2266,7 +2266,7 @@ deleteemptyservers:
|
|||||||
args:
|
args:
|
||||||
- ""
|
- ""
|
||||||
medusaload:
|
medusaload:
|
||||||
desc: |-
|
desc: |-
|
||||||
Loads a medusa with the specified name from the data/medusae/ folder.
|
Loads a medusa with the specified name from the data/medusae/ folder.
|
||||||
Provide no name to see the list of loadable medusae.
|
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/)
|
Read about the medusa system [here](https://nadekobot.readthedocs.io/en/latest/medusa/creating-a-medusa/)
|
||||||
@@ -2289,7 +2289,7 @@ medusainfo:
|
|||||||
args:
|
args:
|
||||||
- "mycoolmedusa"
|
- "mycoolmedusa"
|
||||||
- ""
|
- ""
|
||||||
medusalist:
|
medusalist:
|
||||||
desc: |-
|
desc: |-
|
||||||
Lists all loaded and unloaded medusae.
|
Lists all loaded and unloaded medusae.
|
||||||
Read about the medusa system [here](https://nadekobot.readthedocs.io/en/latest/medusa/creating-a-medusa/)
|
Read about the medusa system [here](https://nadekobot.readthedocs.io/en/latest/medusa/creating-a-medusa/)
|
||||||
@@ -2329,7 +2329,7 @@ patronmessage:
|
|||||||
args:
|
args:
|
||||||
- "x hello"
|
- "x hello"
|
||||||
eval:
|
eval:
|
||||||
desc: |-
|
desc: |-
|
||||||
Execute arbitrary C# code and (optionally) return a result. Several namespaces are included by default.
|
Execute arbitrary C# code and (optionally) return a result. Several namespaces are included by default.
|
||||||
Special variables available:
|
Special variables available:
|
||||||
`self` - Instance of the command group executing the command (this)
|
`self` - Instance of the command group executing the command (this)
|
||||||
@@ -2397,15 +2397,15 @@ giveawaystart:
|
|||||||
- "15m Quick giveaway for a free course!"
|
- "15m Quick giveaway for a free course!"
|
||||||
- "1d Join to win 1000$!"
|
- "1d Join to win 1000$!"
|
||||||
giveawayend:
|
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:
|
args:
|
||||||
- "ab3"
|
- "ab3"
|
||||||
giveawaycancel:
|
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:
|
args:
|
||||||
- "ab3"
|
- "ab3"
|
||||||
giveawayreroll:
|
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:
|
args:
|
||||||
- "cd3"
|
- "cd3"
|
||||||
giveawaylist:
|
giveawaylist:
|
||||||
|
@@ -1075,5 +1075,6 @@
|
|||||||
"no_givaways": "There are no active giveaways on this server.",
|
"no_givaways": "There are no active giveaways on this server.",
|
||||||
"giveaway_cancelled": "Giveaway cancelled.",
|
"giveaway_cancelled": "Giveaway cancelled.",
|
||||||
"giveaway_starting": "Starting giveaway...",
|
"giveaway_starting": "Starting giveaway...",
|
||||||
"winner": "Winner"
|
"winner": "Winner",
|
||||||
|
"giveaway_list": "List of active giveways",
|
||||||
}
|
}
|
||||||
|
@@ -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 PostgreSqlContext -f
|
||||||
dotnet ef migrations remove -c MysqlContext -f
|
dotnet ef migrations remove -c MysqlContext -f
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user