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
|
||||
{
|
||||
[DbContext(typeof(PostgreSqlContext))]
|
||||
[Migration("20250318222207_init")]
|
||||
[Migration("20250319010930_init")]
|
||||
partial class init
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
@ -1541,6 +1541,41 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
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 =>
|
||||
{
|
||||
b.Property<int>("Id")
|
|
@ -550,6 +550,21 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
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(
|
||||
name: "logsettings",
|
||||
columns: table => new
|
||||
|
@ -1984,6 +1999,17 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
column: "channelid",
|
||||
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(
|
||||
name: "ix_logsettings_guildid",
|
||||
table: "logsettings",
|
||||
|
@ -2475,6 +2501,9 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
migrationBuilder.DropTable(
|
||||
name: "imageonlychannels");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "livechannelconfig");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "musicplayersettings");
|
||||
|
|
@ -1538,6 +1538,41 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
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 =>
|
||||
{
|
||||
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
|
||||
{
|
||||
[DbContext(typeof(SqliteContext))]
|
||||
[Migration("20250318222152_init")]
|
||||
[Migration("20250319010920_init")]
|
||||
partial class init
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
@ -1151,6 +1151,32 @@ namespace EllieBot.Migrations.Sqlite
|
|||
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 =>
|
||||
{
|
||||
b.Property<int>("Id")
|
|
@ -550,6 +550,21 @@ namespace EllieBot.Migrations.Sqlite
|
|||
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(
|
||||
name: "LogSettings",
|
||||
columns: table => new
|
||||
|
@ -1986,6 +2001,17 @@ namespace EllieBot.Migrations.Sqlite
|
|||
column: "ChannelId",
|
||||
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(
|
||||
name: "IX_LogSettings_GuildId",
|
||||
table: "LogSettings",
|
||||
|
@ -2477,6 +2503,9 @@ namespace EllieBot.Migrations.Sqlite
|
|||
migrationBuilder.DropTable(
|
||||
name: "ImageOnlyChannels");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "LiveChannelConfig");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "MusicPlayerSettings");
|
||||
|
|
@ -1148,6 +1148,32 @@ namespace EllieBot.Migrations.Sqlite
|
|||
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 =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
|
|
@ -85,8 +85,7 @@ public sealed class GuildTimezoneService : ITimezoneService, IReadyExecutor, IES
|
|||
to = GetTimeZoneOrDefault(g.Id) ?? TimeZoneInfo.Local;
|
||||
}
|
||||
|
||||
return TimeZoneInfo.ConvertTime(DateTime.UtcNow, TimeZoneInfo.Utc, to).ToString("HH:mm ")
|
||||
+ to.StandardName.GetInitials();
|
||||
return TimeZoneInfo.ConvertTime(DateTime.UtcNow, TimeZoneInfo.Utc, to).ToShortTimeString();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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 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": [
|
||||
".sar exclusive 1"
|
||||
],
|
||||
|
@ -2617,6 +2617,21 @@
|
|||
"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": [
|
||||
".massban"
|
||||
|
@ -2717,7 +2732,7 @@
|
|||
".exas",
|
||||
".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": [
|
||||
".expradds \"hello\" Hi there %user.mention%"
|
||||
],
|
||||
|
@ -2735,7 +2750,7 @@
|
|||
".exa",
|
||||
".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": [
|
||||
".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": [
|
||||
{
|
||||
"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": [
|
||||
{
|
||||
"Aliases": [
|
||||
|
@ -6898,7 +7016,7 @@
|
|||
"Aliases": [
|
||||
".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": [
|
||||
".savechat 150"
|
||||
],
|
||||
|
@ -6906,7 +7024,7 @@
|
|||
"Module": "Utility",
|
||||
"Options": null,
|
||||
"Requirements": [
|
||||
"Bot Owner Only"
|
||||
"Administrator Server Permission"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -8513,6 +8631,41 @@
|
|||
"Options": null,
|
||||
"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": [
|
||||
".xprate"
|
||||
|
|
|
@ -1635,3 +1635,16 @@ xpexclusion:
|
|||
xpexclude:
|
||||
- xpexclude
|
||||
- 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"
|
||||
itemId:
|
||||
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_title": "XP Exclusions",
|
||||
"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