forked from EllieBotDevs/elliebot
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": [
|
||||
|
@ -4537,7 +4607,7 @@
|
|||
".marmaladeunload",
|
||||
".maunload"
|
||||
],
|
||||
"Description": "Unloads the previously loaded marmalade.\nProvide no name to see the list of unloadable marmalades. \nRead about the marmalade system [here](https://docs.elliebot.net/ellie/marmalade/creating-a-marmalade/)",
|
||||
"Description": "Unloads the previously loaded marmalade.\nProvide no name to see the list of unloadable marmalades.\nRead about the marmalade system [here](https://docs.elliebot.net/ellie/marmalade/creating-a-marmalade/)",
|
||||
"Usage": [
|
||||
".marmaladeunload mycoolmarmalade",
|
||||
".marmaladeunload"
|
||||
|
@ -4572,7 +4642,7 @@
|
|||
".marmaladeinfo",
|
||||
".mainfo"
|
||||
],
|
||||
"Description": "Shows information about the specified marmalade such as the author, name, description, list of canaries, number of commands etc.\nProvide no name to see the basic information about all loaded marmalades. \nRead about the marmalade system [here](https://docs.elliebot.net/ellie/marmalade/creating-a-marmalade/)",
|
||||
"Description": "Shows information about the specified marmalade such as the author, name, description, list of canaries, number of commands etc.\nProvide no name to see the basic information about all loaded marmalades.\nRead about the marmalade system [here](https://docs.elliebot.net/ellie/marmalade/creating-a-marmalade/)",
|
||||
"Usage": [
|
||||
".marmaladeinfo mycoolmarmalade",
|
||||
".marmaladeinfo"
|
||||
|
@ -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"
|
||||
|
|
|
@ -1634,4 +1634,17 @@ xpexclusion:
|
|||
- xpexl
|
||||
xpexclude:
|
||||
- xpexclude
|
||||
- xpex
|
||||
- xpex
|
||||
livechadd:
|
||||
- livechadd
|
||||
- lcha
|
||||
- lchadd
|
||||
livechlist:
|
||||
- livechlist
|
||||
- lchl
|
||||
- lchli
|
||||
- lchlist
|
||||
livechremove:
|
||||
- livechremove
|
||||
- lchd
|
||||
- lchrm
|
|
@ -5128,4 +5128,31 @@ xpexclude:
|
|||
- type:
|
||||
desc: "Type of the item to exclude: role or user"
|
||||
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_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