added live channel commands which will make the bot update channel name with a template every 10 minutes
This commit is contained in:
parent
c179ea6c07
commit
134e5a8c92
17 changed files with 759 additions and 14 deletions
src/EllieBot
Migrations
PostgreSql
20250319010757_livechannels.sql20250319010930_init.Designer.cs20250319010930_init.csPostgreSqlContextModelSnapshot.cs
Sqlite
Modules
Administration/Timezone
Utility/LiveChannel
Xp/XpRate
data
strings
|
@ -0,0 +1,18 @@
|
||||||
|
START TRANSACTION;
|
||||||
|
CREATE TABLE livechannelconfig (
|
||||||
|
id integer GENERATED BY DEFAULT AS IDENTITY,
|
||||||
|
guildid numeric(20,0) NOT NULL,
|
||||||
|
channelid numeric(20,0) NOT NULL,
|
||||||
|
template text NOT NULL,
|
||||||
|
CONSTRAINT pk_livechannelconfig PRIMARY KEY (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX ix_livechannelconfig_guildid ON livechannelconfig (guildid);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX ix_livechannelconfig_guildid_channelid ON livechannelconfig (guildid, channelid);
|
||||||
|
|
||||||
|
INSERT INTO "__EFMigrationsHistory" (migrationid, productversion)
|
||||||
|
VALUES ('20250319010757_livechannels', '9.0.1');
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
|
@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
namespace EllieBot.Migrations.PostgreSql
|
namespace EllieBot.Migrations.PostgreSql
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PostgreSqlContext))]
|
[DbContext(typeof(PostgreSqlContext))]
|
||||||
[Migration("20250318222207_init")]
|
[Migration("20250319010930_init")]
|
||||||
partial class init
|
partial class init
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -1541,6 +1541,41 @@ namespace EllieBot.Migrations.PostgreSql
|
||||||
b.ToTable("imageonlychannels", (string)null);
|
b.ToTable("imageonlychannels", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EllieBot.Db.Models.LiveChannelConfig", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("ChannelId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("channelid");
|
||||||
|
|
||||||
|
b.Property<decimal>("GuildId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("guildid");
|
||||||
|
|
||||||
|
b.Property<string>("Template")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("template");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_livechannelconfig");
|
||||||
|
|
||||||
|
b.HasIndex("GuildId")
|
||||||
|
.HasDatabaseName("ix_livechannelconfig_guildid");
|
||||||
|
|
||||||
|
b.HasIndex("GuildId", "ChannelId")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("ix_livechannelconfig_guildid_channelid");
|
||||||
|
|
||||||
|
b.ToTable("livechannelconfig", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Db.Models.LogSetting", b =>
|
modelBuilder.Entity("EllieBot.Db.Models.LogSetting", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
|
@ -550,6 +550,21 @@ namespace EllieBot.Migrations.PostgreSql
|
||||||
table.PrimaryKey("pk_imageonlychannels", x => x.id);
|
table.PrimaryKey("pk_imageonlychannels", x => x.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "livechannelconfig",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||||
|
channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||||
|
template = table.Column<string>(type: "text", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_livechannelconfig", x => x.id);
|
||||||
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "logsettings",
|
name: "logsettings",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
|
@ -1984,6 +1999,17 @@ namespace EllieBot.Migrations.PostgreSql
|
||||||
column: "channelid",
|
column: "channelid",
|
||||||
unique: true);
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_livechannelconfig_guildid",
|
||||||
|
table: "livechannelconfig",
|
||||||
|
column: "guildid");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_livechannelconfig_guildid_channelid",
|
||||||
|
table: "livechannelconfig",
|
||||||
|
columns: new[] { "guildid", "channelid" },
|
||||||
|
unique: true);
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "ix_logsettings_guildid",
|
name: "ix_logsettings_guildid",
|
||||||
table: "logsettings",
|
table: "logsettings",
|
||||||
|
@ -2475,6 +2501,9 @@ namespace EllieBot.Migrations.PostgreSql
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "imageonlychannels");
|
name: "imageonlychannels");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "livechannelconfig");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "musicplayersettings");
|
name: "musicplayersettings");
|
||||||
|
|
|
@ -1538,6 +1538,41 @@ namespace EllieBot.Migrations.PostgreSql
|
||||||
b.ToTable("imageonlychannels", (string)null);
|
b.ToTable("imageonlychannels", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EllieBot.Db.Models.LiveChannelConfig", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("ChannelId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("channelid");
|
||||||
|
|
||||||
|
b.Property<decimal>("GuildId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("guildid");
|
||||||
|
|
||||||
|
b.Property<string>("Template")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("template");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_livechannelconfig");
|
||||||
|
|
||||||
|
b.HasIndex("GuildId")
|
||||||
|
.HasDatabaseName("ix_livechannelconfig_guildid");
|
||||||
|
|
||||||
|
b.HasIndex("GuildId", "ChannelId")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("ix_livechannelconfig_guildid_channelid");
|
||||||
|
|
||||||
|
b.ToTable("livechannelconfig", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Db.Models.LogSetting", b =>
|
modelBuilder.Entity("EllieBot.Db.Models.LogSetting", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
CREATE TABLE "LiveChannelConfig" (
|
||||||
|
"Id" INTEGER NOT NULL CONSTRAINT "PK_LiveChannelConfig" PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"GuildId" INTEGER NOT NULL,
|
||||||
|
"ChannelId" INTEGER NOT NULL,
|
||||||
|
"Template" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX "IX_LiveChannelConfig_GuildId" ON "LiveChannelConfig" ("GuildId");
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX "IX_LiveChannelConfig_GuildId_ChannelId" ON "LiveChannelConfig" ("GuildId", "ChannelId");
|
||||||
|
|
||||||
|
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
|
||||||
|
VALUES ('20250319010745_livechannels', '9.0.1');
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
|
@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
namespace EllieBot.Migrations.Sqlite
|
namespace EllieBot.Migrations.Sqlite
|
||||||
{
|
{
|
||||||
[DbContext(typeof(SqliteContext))]
|
[DbContext(typeof(SqliteContext))]
|
||||||
[Migration("20250318222152_init")]
|
[Migration("20250319010920_init")]
|
||||||
partial class init
|
partial class init
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -1151,6 +1151,32 @@ namespace EllieBot.Migrations.Sqlite
|
||||||
b.ToTable("ImageOnlyChannels");
|
b.ToTable("ImageOnlyChannels");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EllieBot.Db.Models.LiveChannelConfig", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ulong>("ChannelId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ulong>("GuildId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Template")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("GuildId");
|
||||||
|
|
||||||
|
b.HasIndex("GuildId", "ChannelId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("LiveChannelConfig");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Db.Models.LogSetting", b =>
|
modelBuilder.Entity("EllieBot.Db.Models.LogSetting", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
|
@ -550,6 +550,21 @@ namespace EllieBot.Migrations.Sqlite
|
||||||
table.PrimaryKey("PK_ImageOnlyChannels", x => x.Id);
|
table.PrimaryKey("PK_ImageOnlyChannels", x => x.Id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "LiveChannelConfig",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||||
|
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||||
|
Template = table.Column<string>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_LiveChannelConfig", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "LogSettings",
|
name: "LogSettings",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
|
@ -1986,6 +2001,17 @@ namespace EllieBot.Migrations.Sqlite
|
||||||
column: "ChannelId",
|
column: "ChannelId",
|
||||||
unique: true);
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_LiveChannelConfig_GuildId",
|
||||||
|
table: "LiveChannelConfig",
|
||||||
|
column: "GuildId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_LiveChannelConfig_GuildId_ChannelId",
|
||||||
|
table: "LiveChannelConfig",
|
||||||
|
columns: new[] { "GuildId", "ChannelId" },
|
||||||
|
unique: true);
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_LogSettings_GuildId",
|
name: "IX_LogSettings_GuildId",
|
||||||
table: "LogSettings",
|
table: "LogSettings",
|
||||||
|
@ -2477,6 +2503,9 @@ namespace EllieBot.Migrations.Sqlite
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "ImageOnlyChannels");
|
name: "ImageOnlyChannels");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "LiveChannelConfig");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "MusicPlayerSettings");
|
name: "MusicPlayerSettings");
|
||||||
|
|
|
@ -1148,6 +1148,32 @@ namespace EllieBot.Migrations.Sqlite
|
||||||
b.ToTable("ImageOnlyChannels");
|
b.ToTable("ImageOnlyChannels");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EllieBot.Db.Models.LiveChannelConfig", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ulong>("ChannelId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ulong>("GuildId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Template")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("GuildId");
|
||||||
|
|
||||||
|
b.HasIndex("GuildId", "ChannelId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("LiveChannelConfig");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Db.Models.LogSetting", b =>
|
modelBuilder.Entity("EllieBot.Db.Models.LogSetting", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
|
|
@ -85,8 +85,7 @@ public sealed class GuildTimezoneService : ITimezoneService, IReadyExecutor, IES
|
||||||
to = GetTimeZoneOrDefault(g.Id) ?? TimeZoneInfo.Local;
|
to = GetTimeZoneOrDefault(g.Id) ?? TimeZoneInfo.Local;
|
||||||
}
|
}
|
||||||
|
|
||||||
return TimeZoneInfo.ConvertTime(DateTime.UtcNow, TimeZoneInfo.Utc, to).ToString("HH:mm ")
|
return TimeZoneInfo.ConvertTime(DateTime.UtcNow, TimeZoneInfo.Utc, to).ToShortTimeString();
|
||||||
+ to.StandardName.GetInitials();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
namespace EllieBot.Modules.Utility.LiveChannel;
|
||||||
|
|
||||||
|
public partial class Utility
|
||||||
|
{
|
||||||
|
[Group]
|
||||||
|
public class LiveChannelCommands(LiveChannelService svc) : EllieModule
|
||||||
|
{
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[UserPerm(GuildPerm.ManageChannels)]
|
||||||
|
[BotPerm(GuildPerm.ManageChannels)]
|
||||||
|
public async Task LiveChAdd(IChannel channel, [Leftover] string template)
|
||||||
|
{
|
||||||
|
if (!await svc.AddLiveChannelAsync(ctx.Guild.Id, channel, template))
|
||||||
|
{
|
||||||
|
await Response()
|
||||||
|
.Error(strs.livechannel_limit(LiveChannelService.MAX_LIVECHANNELS))
|
||||||
|
.SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var eb = CreateEmbed()
|
||||||
|
.WithOkColor()
|
||||||
|
.WithDescription(GetText(strs.livechannel_added(channel.Name)))
|
||||||
|
.AddField(GetText(strs.template), template)
|
||||||
|
.AddField(GetText(strs.preview),
|
||||||
|
await repSvc.ReplaceAsync(template,
|
||||||
|
new(
|
||||||
|
client: ctx.Client as DiscordSocketClient,
|
||||||
|
guild: ctx.Guild
|
||||||
|
)));
|
||||||
|
await Response()
|
||||||
|
.Confirm(strs.livechannel_added(channel.Name))
|
||||||
|
.SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[UserPerm(GuildPerm.ManageChannels)]
|
||||||
|
[BotPerm(GuildPerm.ManageChannels)]
|
||||||
|
public async Task LiveChList()
|
||||||
|
{
|
||||||
|
var liveChannels = await svc.GetLiveChannelsAsync(ctx.Guild.Id);
|
||||||
|
|
||||||
|
if (liveChannels.Count == 0)
|
||||||
|
{
|
||||||
|
await Response().Pending(strs.livechannel_list_empty).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var embed = CreateEmbed()
|
||||||
|
.WithTitle(GetText(strs.livechannel_list_title(ctx.Guild.Name)));
|
||||||
|
|
||||||
|
foreach (var config in liveChannels)
|
||||||
|
{
|
||||||
|
var channelName = await ctx.Guild.GetChannelAsync(config.ChannelId)
|
||||||
|
.Fmap(x => x?.Name ?? config.ChannelId.ToString());
|
||||||
|
|
||||||
|
embed.AddField(channelName, config.Template);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Response().Embed(embed).SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[UserPerm(GuildPerm.ManageChannels)]
|
||||||
|
[BotPerm(GuildPerm.ManageChannels)]
|
||||||
|
public Task LiveChRemove(IChannel channel)
|
||||||
|
=> LiveChRemove(channel.Id);
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[UserPerm(GuildPerm.ManageChannels)]
|
||||||
|
[BotPerm(GuildPerm.ManageChannels)]
|
||||||
|
public async Task LiveChRemove(ulong channelId)
|
||||||
|
{
|
||||||
|
if (await svc.RemoveLiveChannelAsync(ctx.Guild.Id, channelId))
|
||||||
|
{
|
||||||
|
await Response()
|
||||||
|
.Confirm(strs.livechannel_removed(((SocketGuild)ctx.Guild).GetChannel(channelId)?.Name ??
|
||||||
|
channelId.ToString())).SendAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Response().Error(strs.livechannel_not_found).SendAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
195
src/EllieBot/Modules/Utility/LiveChannel/LiveChannelService.cs
Normal file
195
src/EllieBot/Modules/Utility/LiveChannel/LiveChannelService.cs
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
using System.Net;
|
||||||
|
using LinqToDB;
|
||||||
|
using LinqToDB.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
|
using EllieBot.Db.Models;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Utility.LiveChannel;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Service for managing live channels.
|
||||||
|
/// </summary>
|
||||||
|
public class LiveChannelService(
|
||||||
|
DbService db,
|
||||||
|
DiscordSocketClient client,
|
||||||
|
IReplacementService repSvc,
|
||||||
|
ShardData shardData) : IReadyExecutor, IEService
|
||||||
|
{
|
||||||
|
public const int MAX_LIVECHANNELS = 5;
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, LiveChannelConfig>> _liveChannels = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes data when bot is ready
|
||||||
|
/// </summary>
|
||||||
|
public async Task OnReadyAsync()
|
||||||
|
{
|
||||||
|
// Load all existing live channels into memory
|
||||||
|
await using var uow = db.GetDbContext();
|
||||||
|
var configs = await uow.GetTable<LiveChannelConfig>()
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(x => Queries.GuildOnShard(x.GuildId, shardData.TotalShards, shardData.ShardId))
|
||||||
|
.ToListAsyncLinqToDB();
|
||||||
|
|
||||||
|
foreach (var config in configs)
|
||||||
|
{
|
||||||
|
var guildDict = _liveChannels.GetOrAdd(
|
||||||
|
config.GuildId,
|
||||||
|
_ => new());
|
||||||
|
|
||||||
|
guildDict[config.ChannelId] = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(10));
|
||||||
|
while (await timer.WaitForNextTickAsync())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// get all live channels from cache
|
||||||
|
var channels = new List<LiveChannelConfig>(_liveChannels.Count * 2);
|
||||||
|
|
||||||
|
foreach (var (_, vals) in _liveChannels)
|
||||||
|
{
|
||||||
|
foreach (var (_, config) in vals)
|
||||||
|
{
|
||||||
|
channels.Add(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var config in channels)
|
||||||
|
{
|
||||||
|
var guild = client.GetGuild(config.GuildId);
|
||||||
|
var channel = guild?.GetChannel(config.ChannelId);
|
||||||
|
|
||||||
|
if (channel is null)
|
||||||
|
{
|
||||||
|
await RemoveLiveChannelAsync(config.GuildId, config.ChannelId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var repCtx = new ReplacementContext(
|
||||||
|
user: null,
|
||||||
|
guild: guild,
|
||||||
|
client: client
|
||||||
|
);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var text = await repSvc.ReplaceAsync(config.Template, repCtx);
|
||||||
|
|
||||||
|
// only update if needed
|
||||||
|
if (channel.Name != text)
|
||||||
|
await channel.ModifyAsync(x => x.Name = text);
|
||||||
|
}
|
||||||
|
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden)
|
||||||
|
{
|
||||||
|
await RemoveLiveChannelAsync(config.GuildId, config.ChannelId);
|
||||||
|
Log.Warning(
|
||||||
|
"Channel {ChannelId} in guild {GuildId} is not accessible. Live channel will be removed",
|
||||||
|
config.ChannelId,
|
||||||
|
config.GuildId);
|
||||||
|
}
|
||||||
|
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.TooManyRequests ||
|
||||||
|
ex.DiscordCode == DiscordErrorCode.ChannelWriteRatelimit)
|
||||||
|
{
|
||||||
|
Log.Warning(ex, "LiveChannel hit a ratelimit. Sleeping for 2 minutes: {Message}", ex.Message);
|
||||||
|
await Task.Delay(2.Minutes());
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Error in live channel service: {ErrorMessage}", e.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for half a second to reduce the chance of global ratelimits
|
||||||
|
await Task.Delay(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Error in live channel service: {ErrorMessage}", ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new live channel configuration to the specified guild.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="guildId">ID of the guild</param>
|
||||||
|
/// <param name="channel">Channel to set as live</param>
|
||||||
|
/// <param name="template">Template text to use for the channel</param>
|
||||||
|
/// <returns>True if successfully added, false otherwise</returns>
|
||||||
|
public async Task<bool> AddLiveChannelAsync(ulong guildId, IChannel channel, string template)
|
||||||
|
{
|
||||||
|
var guildDict = _liveChannels.GetOrAdd(
|
||||||
|
guildId,
|
||||||
|
_ => new());
|
||||||
|
|
||||||
|
if (guildDict.Count >= MAX_LIVECHANNELS)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
await using var uow = db.GetDbContext();
|
||||||
|
await uow.GetTable<LiveChannelConfig>()
|
||||||
|
.InsertOrUpdateAsync(() => new()
|
||||||
|
{
|
||||||
|
GuildId = guildId,
|
||||||
|
ChannelId = channel.Id,
|
||||||
|
Template = template
|
||||||
|
},
|
||||||
|
(_) => new()
|
||||||
|
{
|
||||||
|
Template = template
|
||||||
|
},
|
||||||
|
() => new()
|
||||||
|
{
|
||||||
|
GuildId = guildId,
|
||||||
|
ChannelId = channel.Id
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add to in-memory cache
|
||||||
|
var newConfig = new LiveChannelConfig
|
||||||
|
{
|
||||||
|
GuildId = guildId,
|
||||||
|
ChannelId = channel.Id,
|
||||||
|
Template = template
|
||||||
|
};
|
||||||
|
|
||||||
|
guildDict[channel.Id] = newConfig;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a live channel configuration from the specified guild.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="guildId">ID of the guild</param>
|
||||||
|
/// <param name="channelId">ID of the channel to remove as live</param>
|
||||||
|
/// <returns>True if successfully removed, false otherwise</returns>
|
||||||
|
public async Task<bool> RemoveLiveChannelAsync(ulong guildId, ulong channelId)
|
||||||
|
{
|
||||||
|
if (!_liveChannels.TryGetValue(guildId, out var guildDict) ||
|
||||||
|
!guildDict.TryRemove(channelId, out _))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
await using var uow = db.GetDbContext();
|
||||||
|
await uow.GetTable<LiveChannelConfig>()
|
||||||
|
.Where(x => x.GuildId == guildId && x.ChannelId == channelId)
|
||||||
|
.DeleteAsync();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all live channels for a guild.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="guildId">ID of the guild</param>
|
||||||
|
/// <returns>List of live channel configurations</returns>
|
||||||
|
public async Task<List<LiveChannelConfig>> GetLiveChannelsAsync(ulong guildId)
|
||||||
|
{
|
||||||
|
await using var uow = db.GetDbContext();
|
||||||
|
return await uow.GetTable<LiveChannelConfig>()
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(x => x.GuildId == guildId)
|
||||||
|
.ToListAsyncLinqToDB();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
using EllieBot.Common.TypeReaders.Models;
|
||||||
|
using EllieBot.Db;
|
||||||
|
using EllieBot.Db.Models;
|
||||||
|
|
||||||
|
namespace EllieBot.Db.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration for a live channel.
|
||||||
|
/// </summary>
|
||||||
|
public class LiveChannelConfig
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ID of the server this live channel belongs to.
|
||||||
|
/// </summary>
|
||||||
|
public ulong GuildId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ID of the channel that is configured as a live channel.
|
||||||
|
/// </summary>
|
||||||
|
public ulong ChannelId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Text template to be used for the live channel.
|
||||||
|
/// </summary>
|
||||||
|
public string Template { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LiveChannelConfigDbEntityTypeConfiguration : IEntityTypeConfiguration<LiveChannelConfig>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<LiveChannelConfig> builder)
|
||||||
|
{
|
||||||
|
builder.HasIndex(x => x.GuildId);
|
||||||
|
builder.HasIndex(x => new { x.GuildId, x.ChannelId }).IsUnique();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1731,7 +1731,7 @@
|
||||||
".sar excl",
|
".sar excl",
|
||||||
".sar tesar"
|
".sar tesar"
|
||||||
],
|
],
|
||||||
"Description": "Toggles whether self-assigned roles are exclusive.\nWhile enabled, users can only have one self-assignable role per group.",
|
"Description": "Toggles the sar group as exclusive.\nWhile enabled, users can only have one self-assignable role from that group.",
|
||||||
"Usage": [
|
"Usage": [
|
||||||
".sar exclusive 1"
|
".sar exclusive 1"
|
||||||
],
|
],
|
||||||
|
@ -2617,6 +2617,21 @@
|
||||||
"ModerateMembers Server Permission"
|
"ModerateMembers Server Permission"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".masskick"
|
||||||
|
],
|
||||||
|
"Description": "Kicks multiple users at once. Specify a space separated list of IDs of users who you wish to kick.",
|
||||||
|
"Usage": [
|
||||||
|
".masskick 123123123 3333333333 444444444"
|
||||||
|
],
|
||||||
|
"Submodule": "UserPunishCommands",
|
||||||
|
"Module": "Administration",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": [
|
||||||
|
"KickMembers Server Permission"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".massban"
|
".massban"
|
||||||
|
@ -2717,7 +2732,7 @@
|
||||||
".exas",
|
".exas",
|
||||||
".expraddserver"
|
".expraddserver"
|
||||||
],
|
],
|
||||||
"Description": "Add an expression with a trigger and a response in this server. Bot will post a response whenever someone types the trigger word. This command is useful if you want to lower the permission requirement for managing expressions by using `.dpo`. Guide [here](<https://docs.elliebot.net/ellie/features/expressions/>).",
|
"Description": "Add an expression with a trigger and a response in this server. Bot will post a response whenever someone types the trigger word. This command is useful if you want to lower the permission requirement for managing expressions by using `.dpo`.",
|
||||||
"Usage": [
|
"Usage": [
|
||||||
".expradds \"hello\" Hi there %user.mention%"
|
".expradds \"hello\" Hi there %user.mention%"
|
||||||
],
|
],
|
||||||
|
@ -2735,7 +2750,7 @@
|
||||||
".exa",
|
".exa",
|
||||||
".acr"
|
".acr"
|
||||||
],
|
],
|
||||||
"Description": "Add an expression with a trigger and a response. Bot will post a response whenever someone types the trigger word. Running this command in a server requires the Administrator permission. Running this command in DM is Bot Owner only and adds a new global expression. Guide [here](<https://docs.elliebot.net/ellie/features/expressions/>)",
|
"Description": "Add an expression with a trigger and a response.\nBot will post a response whenever someone types the trigger word.\nRunning this command in a server requires the Administrator permission.\nRunning this command in DM is Bot Owner only and adds a new global expression.",
|
||||||
"Usage": [
|
"Usage": [
|
||||||
".expradd \"hello\" Hi there %user.mention%"
|
".expradd \"hello\" Hi there %user.mention%"
|
||||||
],
|
],
|
||||||
|
@ -4513,6 +4528,61 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"LiveChannelCommands": [
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".livechadd",
|
||||||
|
".lcha",
|
||||||
|
".lchadd"
|
||||||
|
],
|
||||||
|
"Description": "Adds a channel as a live channel with the specified template.\nYou can see a full list of placeholders with `.phs` command.",
|
||||||
|
"Usage": [
|
||||||
|
".livechadd #general Time: %server.time%",
|
||||||
|
".livechadd #general -- %server.members% --"
|
||||||
|
],
|
||||||
|
"Submodule": "LiveChannelCommands",
|
||||||
|
"Module": "LiveChannelCommands",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": [
|
||||||
|
"ManageChannels Server Permission"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".livechlist",
|
||||||
|
".lchl",
|
||||||
|
".lchli",
|
||||||
|
".lchlist"
|
||||||
|
],
|
||||||
|
"Description": "Lists all live channels in the server.",
|
||||||
|
"Usage": [
|
||||||
|
".livechlist"
|
||||||
|
],
|
||||||
|
"Submodule": "LiveChannelCommands",
|
||||||
|
"Module": "LiveChannelCommands",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": [
|
||||||
|
"ManageChannels Server Permission"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".livechremove",
|
||||||
|
".lchd",
|
||||||
|
".lchrm"
|
||||||
|
],
|
||||||
|
"Description": "Removes a live channel.",
|
||||||
|
"Usage": [
|
||||||
|
".livechremove #general"
|
||||||
|
],
|
||||||
|
"Submodule": "LiveChannelCommands",
|
||||||
|
"Module": "LiveChannelCommands",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": [
|
||||||
|
"ManageChannels Server Permission"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"Marmalade": [
|
"Marmalade": [
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
|
@ -5766,6 +5836,54 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"ScheduledCommands": [
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".schedulelist",
|
||||||
|
".schl",
|
||||||
|
".schli"
|
||||||
|
],
|
||||||
|
"Description": "Lists your scheduled commands in the current server.",
|
||||||
|
"Usage": [
|
||||||
|
".schedulelist"
|
||||||
|
],
|
||||||
|
"Submodule": "ScheduledCommands",
|
||||||
|
"Module": "ScheduledCommands",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".scheduledelete",
|
||||||
|
".schd",
|
||||||
|
".schdel"
|
||||||
|
],
|
||||||
|
"Description": "Deletes one of your scheduled commands by its ID.",
|
||||||
|
"Usage": [
|
||||||
|
".scheduledelete 5"
|
||||||
|
],
|
||||||
|
"Submodule": "ScheduledCommands",
|
||||||
|
"Module": "ScheduledCommands",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".scheduleadd",
|
||||||
|
".scha",
|
||||||
|
".schadd"
|
||||||
|
],
|
||||||
|
"Description": "Schedules a command to be executed after the specified amount of time.\nYou can schedule up to 5 commands at a time.",
|
||||||
|
"Usage": [
|
||||||
|
".scheduleadd 1h5m .say Hello after 1 hour and 5 minutes",
|
||||||
|
".scheduleadd 3h .br all"
|
||||||
|
],
|
||||||
|
"Submodule": "ScheduledCommands",
|
||||||
|
"Module": "ScheduledCommands",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"Searches": [
|
"Searches": [
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
|
@ -6898,7 +7016,7 @@
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".savechat"
|
".savechat"
|
||||||
],
|
],
|
||||||
"Description": "Saves a number of messages to a text file and sends it to you.",
|
"Description": "Saves a number of messages to a text file and sends it to you.\nMax is 1000, unless you're the bot owner. ",
|
||||||
"Usage": [
|
"Usage": [
|
||||||
".savechat 150"
|
".savechat 150"
|
||||||
],
|
],
|
||||||
|
@ -6906,7 +7024,7 @@
|
||||||
"Module": "Utility",
|
"Module": "Utility",
|
||||||
"Options": null,
|
"Options": null,
|
||||||
"Requirements": [
|
"Requirements": [
|
||||||
"Bot Owner Only"
|
"Administrator Server Permission"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -8513,6 +8631,41 @@
|
||||||
"Options": null,
|
"Options": null,
|
||||||
"Requirements": []
|
"Requirements": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".xpexclusion",
|
||||||
|
".xpexl"
|
||||||
|
],
|
||||||
|
"Description": "Shows a list of all XP exclusions in the server.",
|
||||||
|
"Usage": [
|
||||||
|
".xpexclusion"
|
||||||
|
],
|
||||||
|
"Submodule": "XpExclusionCommands",
|
||||||
|
"Module": "Xp",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": [
|
||||||
|
"Administrator Server Permission"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".xpexclude",
|
||||||
|
".xpex"
|
||||||
|
],
|
||||||
|
"Description": "Toggles XP gain exclusion for a specified item.\nItem types can be Role or User.",
|
||||||
|
"Usage": [
|
||||||
|
".xpexclude @CoolRole",
|
||||||
|
".xpexclude @User",
|
||||||
|
".xpexclude role 123123123",
|
||||||
|
".xpexclude user 123123123"
|
||||||
|
],
|
||||||
|
"Submodule": "XpExclusionCommands",
|
||||||
|
"Module": "Xp",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": [
|
||||||
|
"Administrator Server Permission"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".xprate"
|
".xprate"
|
||||||
|
|
|
@ -1635,3 +1635,16 @@ xpexclusion:
|
||||||
xpexclude:
|
xpexclude:
|
||||||
- xpexclude
|
- xpexclude
|
||||||
- xpex
|
- xpex
|
||||||
|
livechadd:
|
||||||
|
- livechadd
|
||||||
|
- lcha
|
||||||
|
- lchadd
|
||||||
|
livechlist:
|
||||||
|
- livechlist
|
||||||
|
- lchl
|
||||||
|
- lchli
|
||||||
|
- lchlist
|
||||||
|
livechremove:
|
||||||
|
- livechremove
|
||||||
|
- lchd
|
||||||
|
- lchrm
|
|
@ -5129,3 +5129,30 @@ xpexclude:
|
||||||
desc: "Type of the item to exclude: role or user"
|
desc: "Type of the item to exclude: role or user"
|
||||||
itemId:
|
itemId:
|
||||||
desc: "ID or mention of the item to exclude."
|
desc: "ID or mention of the item to exclude."
|
||||||
|
livechadd:
|
||||||
|
desc: |-
|
||||||
|
Adds a channel as a live channel with the specified template.
|
||||||
|
You can see a full list of placeholders with `{0}phs` command.
|
||||||
|
ex:
|
||||||
|
- '#general Time: %server.time%'
|
||||||
|
- '#general -- %server.members% --'
|
||||||
|
params:
|
||||||
|
- channel:
|
||||||
|
desc: "The channel to configure as a live channel."
|
||||||
|
- template:
|
||||||
|
desc: "The text template to use as a name for the live channel."
|
||||||
|
livechlist:
|
||||||
|
desc: |-
|
||||||
|
Lists all live channels in the server.
|
||||||
|
ex:
|
||||||
|
- ''
|
||||||
|
params:
|
||||||
|
- { }
|
||||||
|
livechremove:
|
||||||
|
desc: |-
|
||||||
|
Removes a live channel.
|
||||||
|
ex:
|
||||||
|
- '#general'
|
||||||
|
params:
|
||||||
|
- channel:
|
||||||
|
desc: "The channel to remove from live channels."
|
|
@ -1221,5 +1221,15 @@
|
||||||
"xp_exclusion_none": "There are no exclusions set for this server.",
|
"xp_exclusion_none": "There are no exclusions set for this server.",
|
||||||
"xp_exclusion_title": "XP Exclusions",
|
"xp_exclusion_title": "XP Exclusions",
|
||||||
"xp_exclude_added": "{0}: {1} has been excluded from the XP system.",
|
"xp_exclude_added": "{0}: {1} has been excluded from the XP system.",
|
||||||
"xp_exclude_removed": "{0}: {1} is no longer excluded from the XP system."
|
"xp_exclude_removed": "{0}: {1} is no longer excluded from the XP system.",
|
||||||
|
"livechannel_added": "Successfully added {0} as a live channel.",
|
||||||
|
"livechannel_template": "Template: `{0}`",
|
||||||
|
"livechannel_limit": "You can have at most {0} live channels per server.",
|
||||||
|
"livechannel_exists": "This channel is already configured as a live channel.",
|
||||||
|
"livechannel_removed": "Successfully removed {0} from live channels.",
|
||||||
|
"livechannel_not_found": "Channel was not found in the live channels list.",
|
||||||
|
"livechannel_list_title": "Live Channels in {0}",
|
||||||
|
"livechannel_list_empty": "No live channels configured for this server.",
|
||||||
|
"template": "Template",
|
||||||
|
"preview": "Preview"
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue