forked from EllieBotDevs/elliebot
added scheduled commands, .scha, .schd and .schl
This commit is contained in:
parent
83701a9e0b
commit
ca46786c5e
17 changed files with 14143 additions and 13540 deletions
src/EllieBot
Migrations
PostgreSql
20250315225539_init.Designer.cs20250317063129_scheduled-commands.sql20250317063309_init.Designer.cs20250317063309_init.csPostgreSqlContextModelSnapshot.cs
Sqlite
Modules/Utility/Scheduled
_common
strings
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,23 @@
|
|||
START TRANSACTION;
|
||||
CREATE TABLE scheduledcommand (
|
||||
id integer GENERATED BY DEFAULT AS IDENTITY,
|
||||
userid numeric(20,0) NOT NULL,
|
||||
channelid numeric(20,0) NOT NULL,
|
||||
guildid numeric(20,0) NOT NULL,
|
||||
messageid numeric(20,0) NOT NULL,
|
||||
text text NOT NULL,
|
||||
"when" timestamp without time zone NOT NULL,
|
||||
CONSTRAINT pk_scheduledcommand PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE INDEX ix_scheduledcommand_guildid ON scheduledcommand (guildid);
|
||||
|
||||
CREATE INDEX ix_scheduledcommand_userid ON scheduledcommand (userid);
|
||||
|
||||
CREATE INDEX ix_scheduledcommand_when ON scheduledcommand ("when");
|
||||
|
||||
INSERT INTO "__EFMigrationsHistory" (migrationid, productversion)
|
||||
VALUES ('20250317063129_scheduled-commands', '9.0.1');
|
||||
|
||||
COMMIT;
|
||||
|
4086
src/EllieBot/Migrations/PostgreSql/20250317063309_init.Designer.cs
generated
Normal file
4086
src/EllieBot/Migrations/PostgreSql/20250317063309_init.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -855,6 +855,24 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
table.UniqueConstraint("ak_sargroup_guildid_groupnumber", x => new { x.guildid, x.groupnumber });
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "scheduledcommand",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
userid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
channelid = 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),
|
||||
text = table.Column<string>(type: "text", nullable: false),
|
||||
when = table.Column<DateTime>(type: "timestamp without time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_scheduledcommand", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "shopentry",
|
||||
columns: table => new
|
||||
|
@ -2048,6 +2066,21 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
column: "guildid",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_scheduledcommand_guildid",
|
||||
table: "scheduledcommand",
|
||||
column: "guildid");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_scheduledcommand_userid",
|
||||
table: "scheduledcommand",
|
||||
column: "userid");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_scheduledcommand_when",
|
||||
table: "scheduledcommand",
|
||||
column: "when");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_shopentry_guildid_index",
|
||||
table: "shopentry",
|
||||
|
@ -2472,6 +2505,9 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
migrationBuilder.DropTable(
|
||||
name: "sarautodelete");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "scheduledcommand");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "shopentryitem");
|
||||
|
||||
|
@ -2590,4 +2626,4 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
name: "discorduser");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,22 @@
|
|||
BEGIN TRANSACTION;
|
||||
CREATE TABLE "ScheduledCommand" (
|
||||
"Id" INTEGER NOT NULL CONSTRAINT "PK_ScheduledCommand" PRIMARY KEY AUTOINCREMENT,
|
||||
"UserId" INTEGER NOT NULL,
|
||||
"ChannelId" INTEGER NOT NULL,
|
||||
"GuildId" INTEGER NOT NULL,
|
||||
"MessageId" INTEGER NOT NULL,
|
||||
"Text" TEXT NOT NULL,
|
||||
"When" TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX "IX_ScheduledCommand_GuildId" ON "ScheduledCommand" ("GuildId");
|
||||
|
||||
CREATE INDEX "IX_ScheduledCommand_UserId" ON "ScheduledCommand" ("UserId");
|
||||
|
||||
CREATE INDEX "IX_ScheduledCommand_When" ON "ScheduledCommand" ("When");
|
||||
|
||||
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
|
||||
VALUES ('20250317063119_scheduled-commands', '9.0.1');
|
||||
|
||||
COMMIT;
|
||||
|
3109
src/EllieBot/Migrations/Sqlite/20250317063300_init.Designer.cs
generated
Normal file
3109
src/EllieBot/Migrations/Sqlite/20250317063300_init.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -857,6 +857,24 @@ namespace EllieBot.Migrations.Sqlite
|
|||
table.UniqueConstraint("AK_SarGroup_GuildId_GroupNumber", x => new { x.GuildId, x.GroupNumber });
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ScheduledCommand",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
UserId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
MessageId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
Text = table.Column<string>(type: "TEXT", nullable: false),
|
||||
When = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ScheduledCommand", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ShopEntry",
|
||||
columns: table => new
|
||||
|
@ -2050,6 +2068,21 @@ namespace EllieBot.Migrations.Sqlite
|
|||
column: "GuildId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ScheduledCommand_GuildId",
|
||||
table: "ScheduledCommand",
|
||||
column: "GuildId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ScheduledCommand_UserId",
|
||||
table: "ScheduledCommand",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ScheduledCommand_When",
|
||||
table: "ScheduledCommand",
|
||||
column: "When");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ShopEntry_GuildId_Index",
|
||||
table: "ShopEntry",
|
||||
|
@ -2474,6 +2507,9 @@ namespace EllieBot.Migrations.Sqlite
|
|||
migrationBuilder.DropTable(
|
||||
name: "SarAutoDelete");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ScheduledCommand");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ShopEntryItem");
|
||||
|
||||
|
@ -2592,4 +2628,4 @@ namespace EllieBot.Migrations.Sqlite
|
|||
name: "DiscordUser");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
150
src/EllieBot/Modules/Utility/Scheduled/ScheduleCommandService.cs
Normal file
150
src/EllieBot/Modules/Utility/Scheduled/ScheduleCommandService.cs
Normal file
|
@ -0,0 +1,150 @@
|
|||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using EllieBot.Common.ModuleBehaviors;
|
||||
using EllieBot.Modules.Administration;
|
||||
|
||||
namespace EllieBot.Modules.Utility.Scheduled;
|
||||
|
||||
public sealed class ScheduleCommandService(
|
||||
DbService db,
|
||||
ICommandHandler cmdHandler,
|
||||
DiscordSocketClient client,
|
||||
ShardData shardData) : IEService, IReadyExecutor
|
||||
{
|
||||
private TaskCompletionSource _tcs = new();
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
_tcs = new();
|
||||
|
||||
// get the next scheduled command
|
||||
var scheduledCommand = await db.GetDbContext()
|
||||
.GetTable<ScheduledCommand>()
|
||||
.Where(x => Queries.GuildOnShard(x.GuildId, shardData.TotalShards, shardData.ShardId))
|
||||
.OrderBy(x => x.When)
|
||||
.FirstOrDefaultAsyncLinqToDB();
|
||||
|
||||
if (scheduledCommand is null)
|
||||
{
|
||||
await _tcs.Task;
|
||||
continue;
|
||||
}
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
if (scheduledCommand.When > now)
|
||||
{
|
||||
var diff = scheduledCommand.When - now;
|
||||
await Task.WhenAny(Task.Delay(diff), _tcs.Task);
|
||||
continue;
|
||||
}
|
||||
|
||||
await db.GetDbContext()
|
||||
.GetTable<ScheduledCommand>()
|
||||
.Where(x => x.Id == scheduledCommand.Id)
|
||||
.DeleteAsync();
|
||||
|
||||
var guild = client.GetGuild(scheduledCommand.GuildId);
|
||||
var channel = guild?.GetChannel(scheduledCommand.ChannelId) as ISocketMessageChannel;
|
||||
|
||||
if (guild is null || channel is null)
|
||||
continue;
|
||||
|
||||
var message = await channel.GetMessageAsync(scheduledCommand.MessageId) as IUserMessage;
|
||||
var user = await (guild as IGuild).GetUserAsync(scheduledCommand.UserId);
|
||||
|
||||
if (message is null || user is null)
|
||||
continue;
|
||||
|
||||
_ = Task.Run(async ()
|
||||
=> await cmdHandler.TryRunCommand(guild,
|
||||
channel,
|
||||
new DoAsUserMessage(message, user, scheduledCommand.Text)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a scheduled command to be executed after the specified time
|
||||
/// </summary>
|
||||
/// <param name="guildId">ID of the guild</param>
|
||||
/// <param name="channelId">ID of the channel where the command was issued</param>
|
||||
/// <param name="messageId">ID of the message that triggered this command</param>
|
||||
/// <param name="userId">ID of the user who scheduled the command</param>
|
||||
/// <param name="commandText">The command text to execute</param>
|
||||
/// <param name="when">Time span after which the command will be executed</param>
|
||||
/// <returns>True if command was added, false if user reached the limit</returns>
|
||||
public async Task<bool> AddScheduledCommandAsync(
|
||||
ulong guildId,
|
||||
ulong channelId,
|
||||
ulong messageId,
|
||||
ulong userId,
|
||||
string commandText,
|
||||
TimeSpan when)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(commandText, nameof(commandText));
|
||||
|
||||
await using var uow = db.GetDbContext();
|
||||
|
||||
var count = await uow.GetTable<ScheduledCommand>()
|
||||
.Where(x => x.GuildId == guildId && x.UserId == userId)
|
||||
.CountAsyncLinqToDB();
|
||||
|
||||
if (count >= 5)
|
||||
return false;
|
||||
|
||||
await uow.GetTable<ScheduledCommand>()
|
||||
.InsertAsync(() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
UserId = userId,
|
||||
Text = commandText,
|
||||
When = DateTime.UtcNow + when,
|
||||
ChannelId = channelId,
|
||||
MessageId = messageId
|
||||
});
|
||||
|
||||
_tcs.TrySetResult();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all scheduled commands for a specific user in a guild
|
||||
/// </summary>
|
||||
/// <param name="guildId">Guild ID</param>
|
||||
/// <param name="userId">User ID</param>
|
||||
/// <returns>List of scheduled commands</returns>
|
||||
public async Task<List<ScheduledCommand>> GetUserScheduledCommandsAsync(ulong guildId, ulong userId)
|
||||
{
|
||||
await using var uow = db.GetDbContext();
|
||||
|
||||
return await uow.GetTable<ScheduledCommand>()
|
||||
.Where(x => x.GuildId == guildId && x.UserId == userId)
|
||||
.OrderBy(x => x.When)
|
||||
.AsNoTracking()
|
||||
.ToListAsyncLinqToDB();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a scheduled command by its ID
|
||||
/// </summary>
|
||||
/// <param name="id">ID of the scheduled command</param>
|
||||
/// <param name="guildId">Guild ID</param>
|
||||
/// <param name="userId">User ID</param>
|
||||
/// <returns>True if command was deleted, false otherwise</returns>
|
||||
public async Task<bool> DeleteScheduledCommandAsync(int id, ulong guildId, ulong userId)
|
||||
{
|
||||
await using var uow = db.GetDbContext();
|
||||
|
||||
var result = await uow.GetTable<ScheduledCommand>()
|
||||
.Where(x => x.Id == id && x.GuildId == guildId && x.UserId == userId)
|
||||
.DeleteAsync();
|
||||
|
||||
if (result > 0)
|
||||
_tcs.TrySetResult();
|
||||
|
||||
return result > 0;
|
||||
}
|
||||
}
|
28
src/EllieBot/Modules/Utility/Scheduled/ScheduledCommand.cs
Normal file
28
src/EllieBot/Modules/Utility/Scheduled/ScheduledCommand.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace EllieBot.Modules.Utility.Scheduled;
|
||||
|
||||
public sealed class ScheduledCommand
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
public ulong UserId { get; set; }
|
||||
public ulong ChannelId { get; set; }
|
||||
public ulong GuildId { get; set; }
|
||||
public ulong MessageId { get; set; }
|
||||
public string Text { get; set; } = string.Empty;
|
||||
public DateTime When { get; set; }
|
||||
}
|
||||
|
||||
public sealed class ScheduledCommandEntityConfiguration : IEntityTypeConfiguration<ScheduledCommand>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ScheduledCommand> builder)
|
||||
{
|
||||
builder.HasIndex(x => x.UserId);
|
||||
builder.HasIndex(x => x.GuildId);
|
||||
builder.HasIndex(x => x.When);
|
||||
}
|
||||
}
|
89
src/EllieBot/Modules/Utility/Scheduled/ScheduledCommands.cs
Normal file
89
src/EllieBot/Modules/Utility/Scheduled/ScheduledCommands.cs
Normal file
|
@ -0,0 +1,89 @@
|
|||
using EllieBot.Common.TypeReaders.Models;
|
||||
|
||||
namespace EllieBot.Modules.Utility.Scheduled;
|
||||
|
||||
public partial class Utility
|
||||
{
|
||||
[Group]
|
||||
public class ScheduledCommands(ScheduleCommandService scs) : EllieModule
|
||||
{
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ScheduleList()
|
||||
{
|
||||
var scheduledCommands = await scs.GetUserScheduledCommandsAsync(ctx.Guild.Id, ctx.User.Id);
|
||||
|
||||
if (scheduledCommands.Count == 0)
|
||||
{
|
||||
await Response().Error(strs.schedule_list_none).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
await Response()
|
||||
.Paginated()
|
||||
.Items(scheduledCommands)
|
||||
.PageSize(5)
|
||||
.Page((pageCommands, _) =>
|
||||
{
|
||||
var eb = CreateEmbed()
|
||||
.WithTitle(GetText(strs.schedule_list_title))
|
||||
.WithAuthor(ctx.User)
|
||||
.WithOkColor();
|
||||
|
||||
foreach (var cmd in pageCommands)
|
||||
{
|
||||
eb.AddField(
|
||||
$"`{GetText(strs.schedule_id)}:` {cmd.Id}",
|
||||
$"""
|
||||
`{GetText(strs.schedule_command)}:` {cmd.Text}
|
||||
`{GetText(strs.schedule_when)}:` {TimestampTag.FromDateTime(cmd.When, TimestampTagStyles.Relative)}
|
||||
""");
|
||||
}
|
||||
|
||||
return eb;
|
||||
})
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ScheduleDelete([Leftover] int id)
|
||||
{
|
||||
var success = await scs.DeleteScheduledCommandAsync(id, ctx.Guild.Id, ctx.User.Id);
|
||||
|
||||
if (success)
|
||||
{
|
||||
await Response().Confirm(strs.schedule_deleted(id)).SendAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await Response().Error(strs.schedule_delete_error).SendAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ScheduleAdd(ParsedTimespan timeString, [Leftover] string commandText)
|
||||
{
|
||||
if (timeString.Time < TimeSpan.FromMinutes(1))
|
||||
return;
|
||||
|
||||
var success = await scs.AddScheduledCommandAsync(
|
||||
ctx.Guild.Id,
|
||||
ctx.Channel.Id,
|
||||
ctx.Message.Id,
|
||||
ctx.User.Id,
|
||||
commandText,
|
||||
timeString.Time);
|
||||
|
||||
if (success)
|
||||
{
|
||||
await Response().Confirm(strs.schedule_add_success).SendAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await Response().Error(strs.schedule_add_error).SendAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ public sealed class DoAsUserMessage : IUserMessage
|
|||
private IUserMessage _msg;
|
||||
private readonly IUser _user;
|
||||
|
||||
public DoAsUserMessage(SocketUserMessage msg, IUser user, string message)
|
||||
public DoAsUserMessage(IUserMessage msg, IUser user, string message)
|
||||
{
|
||||
_msg = msg;
|
||||
_user = user;
|
||||
|
|
|
@ -1614,4 +1614,16 @@ userrolename:
|
|||
- urn
|
||||
userroleicon:
|
||||
- userroleicon
|
||||
- uri
|
||||
- uri
|
||||
schedulelist:
|
||||
- schedulelist
|
||||
- schl
|
||||
- schli
|
||||
scheduledelete:
|
||||
- scheduledelete
|
||||
- schd
|
||||
- schdel
|
||||
scheduleadd:
|
||||
- scheduleadd
|
||||
- scha
|
||||
- schadd
|
|
@ -5073,4 +5073,31 @@ userrolename:
|
|||
- role:
|
||||
desc: 'The assigned role to rename.'
|
||||
name:
|
||||
desc: 'The new name for the role.'
|
||||
desc: 'The new name for the role.'
|
||||
schedulelist:
|
||||
desc: |-
|
||||
Lists your scheduled commands in the current server.
|
||||
ex:
|
||||
- ''
|
||||
params:
|
||||
- { }
|
||||
scheduledelete:
|
||||
desc: |-
|
||||
Deletes one of your scheduled commands by its ID.
|
||||
ex:
|
||||
- '5'
|
||||
params:
|
||||
- id:
|
||||
desc: "The ID of the scheduled command to delete."
|
||||
scheduleadd:
|
||||
desc: |-
|
||||
Schedules a command to be executed after the specified amount of time.
|
||||
You can schedule up to 5 commands at a time.
|
||||
ex:
|
||||
- '1h5m .say Hello after 1 hour and 5 minutes'
|
||||
- '3h .br all'
|
||||
params:
|
||||
- time:
|
||||
desc: "How long it takes for the command to execute. Example: 1h30m = 1 hour and 30 minutes"
|
||||
- command:
|
||||
desc: "Command that will be executed after the specified time has elapsed"
|
|
@ -1207,5 +1207,15 @@
|
|||
"userrole_icon_invalid": "The role icon cannot be empty.",
|
||||
"userrole_hierarchy_error": "You can't assign or modify roles that are higher than or equal to your, or bots highest role.",
|
||||
"userrole_role_not_exists": "That role doesn't exist.",
|
||||
"whos_playing_game": "{0} users are playing {1}"
|
||||
}
|
||||
"whos_playing_game": "{0} users are playing {1}",
|
||||
"schedule_list_title": "Scheduled Commands",
|
||||
"schedule_list_none": "You don't have any scheduled commands.",
|
||||
"schedule_list_for_user": "Scheduled Commands",
|
||||
"schedule_deleted": "Scheduled command #{0} successfully deleted.",
|
||||
"schedule_delete_error": "Error deleting scheduled command.",
|
||||
"schedule_id": "ID",
|
||||
"schedule_command": "Command",
|
||||
"schedule_when": "Executes At",
|
||||
"schedule_add_success": "Scheduled command successfully added.",
|
||||
"schedule_add_error": "You already have 5 scheduled commands. Please delete some before adding more."
|
||||
}
|
Loading…
Add table
Reference in a new issue