.notify now lets you not specify a channel in which case the event message will be sent to the channel from which the event originated - but only if that event has an origin channel.
This commit is contained in:
parent
6f64a15cd4
commit
c5442f9144
19 changed files with 261 additions and 150 deletions
src/EllieBot
Db/Models
Migrations
PostgreSql
20250228044141_notify-allow-origin-channel.sql20250228044209_init.Designer.cs20250228044209_init.csPostgreSqlContextModelSnapshot.cs
Sqlite
Modules
data
strings/commands
|
@ -8,7 +8,7 @@ public class Notify
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
public ulong GuildId { get; set; }
|
public ulong GuildId { get; set; }
|
||||||
public ulong ChannelId { get; set; }
|
public ulong? ChannelId { get; set; }
|
||||||
public NotifyType Type { get; set; }
|
public NotifyType Type { get; set; }
|
||||||
|
|
||||||
[MaxLength(10_000)]
|
[MaxLength(10_000)]
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
START TRANSACTION;
|
||||||
|
ALTER TABLE notify ALTER COLUMN channelid DROP NOT NULL;
|
||||||
|
|
||||||
|
INSERT INTO "__EFMigrationsHistory" (migrationid, productversion)
|
||||||
|
VALUES ('20250228044141_notify-allow-origin-channel', '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("20250226215222_init")]
|
[Migration("20250228044209_init")]
|
||||||
partial class init
|
partial class init
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -1809,7 +1809,7 @@ namespace EllieBot.Migrations.PostgreSql
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
b.Property<decimal>("ChannelId")
|
b.Property<decimal?>("ChannelId")
|
||||||
.HasColumnType("numeric(20,0)")
|
.HasColumnType("numeric(20,0)")
|
||||||
.HasColumnName("channelid");
|
.HasColumnName("channelid");
|
||||||
|
|
|
@ -658,7 +658,7 @@ namespace EllieBot.Migrations.PostgreSql
|
||||||
id = table.Column<int>(type: "integer", nullable: false)
|
id = table.Column<int>(type: "integer", nullable: false)
|
||||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||||
channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: true),
|
||||||
type = table.Column<int>(type: "integer", nullable: false),
|
type = table.Column<int>(type: "integer", nullable: false),
|
||||||
message = table.Column<string>(type: "character varying(10000)", maxLength: 10000, nullable: false)
|
message = table.Column<string>(type: "character varying(10000)", maxLength: 10000, nullable: false)
|
||||||
},
|
},
|
|
@ -1806,7 +1806,7 @@ namespace EllieBot.Migrations.PostgreSql
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
b.Property<decimal>("ChannelId")
|
b.Property<decimal?>("ChannelId")
|
||||||
.HasColumnType("numeric(20,0)")
|
.HasColumnType("numeric(20,0)")
|
||||||
.HasColumnName("channelid");
|
.HasColumnName("channelid");
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
CREATE TABLE "ef_temp_Notify" (
|
||||||
|
"Id" INTEGER NOT NULL CONSTRAINT "PK_Notify" PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"ChannelId" INTEGER NULL,
|
||||||
|
"GuildId" INTEGER NOT NULL,
|
||||||
|
"Message" TEXT NOT NULL,
|
||||||
|
"Type" INTEGER NOT NULL,
|
||||||
|
CONSTRAINT "AK_Notify_GuildId_Type" UNIQUE ("GuildId", "Type")
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO "ef_temp_Notify" ("Id", "ChannelId", "GuildId", "Message", "Type")
|
||||||
|
SELECT "Id", "ChannelId", "GuildId", "Message", "Type"
|
||||||
|
FROM "Notify";
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
PRAGMA foreign_keys = 0;
|
||||||
|
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
DROP TABLE "Notify";
|
||||||
|
|
||||||
|
ALTER TABLE "ef_temp_Notify" RENAME TO "Notify";
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
PRAGMA foreign_keys = 1;
|
||||||
|
|
||||||
|
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
|
||||||
|
VALUES ('20250228044138_notify-allow-origin-channel', '9.0.1');
|
|
@ -11,7 +11,7 @@ using EllieBot.Db;
|
||||||
namespace EllieBot.Migrations.Sqlite
|
namespace EllieBot.Migrations.Sqlite
|
||||||
{
|
{
|
||||||
[DbContext(typeof(SqliteContext))]
|
[DbContext(typeof(SqliteContext))]
|
||||||
[Migration("20250226215220_init")]
|
[Migration("20250228044206_init")]
|
||||||
partial class init
|
partial class init
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -1351,7 +1351,7 @@ namespace EllieBot.Migrations.Sqlite
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<ulong>("ChannelId")
|
b.Property<ulong?>("ChannelId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<ulong>("GuildId")
|
b.Property<ulong>("GuildId")
|
|
@ -658,7 +658,7 @@ namespace EllieBot.Migrations.Sqlite
|
||||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
.Annotation("Sqlite:Autoincrement", true),
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||||
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: true),
|
||||||
Type = table.Column<int>(type: "INTEGER", nullable: false),
|
Type = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
Message = table.Column<string>(type: "TEXT", maxLength: 10000, nullable: false)
|
Message = table.Column<string>(type: "TEXT", maxLength: 10000, nullable: false)
|
||||||
},
|
},
|
|
@ -1348,7 +1348,7 @@ namespace EllieBot.Migrations.Sqlite
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<ulong>("ChannelId")
|
b.Property<ulong?>("ChannelId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<ulong>("GuildId")
|
b.Property<ulong>("GuildId")
|
||||||
|
|
|
@ -3,15 +3,25 @@
|
||||||
namespace EllieBot.Modules.Administration;
|
namespace EllieBot.Modules.Administration;
|
||||||
|
|
||||||
public interface INotifyModel<T>
|
public interface INotifyModel<T>
|
||||||
where T: struct, INotifyModel<T>
|
where T : struct, INotifyModel<T>
|
||||||
{
|
{
|
||||||
static abstract string KeyName { get; }
|
static abstract string KeyName { get; }
|
||||||
static abstract NotifyType NotifyType { get; }
|
static abstract NotifyType NotifyType { get; }
|
||||||
static abstract IReadOnlyList<NotifyModelPlaceholderData<T>> GetReplacements();
|
static abstract IReadOnlyList<NotifyModelPlaceholderData<T>> GetReplacements();
|
||||||
|
|
||||||
|
static virtual bool SupportsOriginTarget
|
||||||
|
=> false;
|
||||||
|
|
||||||
public virtual bool TryGetGuildId(out ulong guildId)
|
public virtual bool TryGetGuildId(out ulong guildId)
|
||||||
{
|
{
|
||||||
guildId = 0;
|
guildId = 0;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool TryGetChannelId(out ulong channelId)
|
||||||
|
{
|
||||||
|
channelId = 0;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,4 +13,7 @@ public interface INotifySubscriber
|
||||||
NotifyModelData GetRegisteredModel(NotifyType nType);
|
NotifyModelData GetRegisteredModel(NotifyType nType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly record struct NotifyModelData(NotifyType Type, IReadOnlyList<string> Replacements);
|
public readonly record struct NotifyModelData(
|
||||||
|
NotifyType Type,
|
||||||
|
bool SupportsOriginTarget,
|
||||||
|
IReadOnlyList<string> Replacements);
|
|
@ -3,7 +3,12 @@ using EllieBot.Modules.Administration;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Xp.Services;
|
namespace EllieBot.Modules.Xp.Services;
|
||||||
|
|
||||||
public record struct AddRoleRewardNotifyModel(ulong GuildId, ulong RoleId, ulong UserId, long Level) : INotifyModel<AddRoleRewardNotifyModel>
|
public record struct AddRoleRewardNotifyModel(
|
||||||
|
ulong GuildId,
|
||||||
|
ulong RoleId,
|
||||||
|
ulong UserId,
|
||||||
|
long Level)
|
||||||
|
: INotifyModel<AddRoleRewardNotifyModel>
|
||||||
{
|
{
|
||||||
public static string KeyName
|
public static string KeyName
|
||||||
=> "notify.reward.addrole";
|
=> "notify.reward.addrole";
|
||||||
|
@ -18,9 +23,9 @@ public record struct AddRoleRewardNotifyModel(ulong GuildId, ulong RoleId, ulong
|
||||||
public static IReadOnlyList<NotifyModelPlaceholderData<AddRoleRewardNotifyModel>> GetReplacements()
|
public static IReadOnlyList<NotifyModelPlaceholderData<AddRoleRewardNotifyModel>> GetReplacements()
|
||||||
=>
|
=>
|
||||||
[
|
[
|
||||||
new(PH_LEVEL, static (data, g) => data.Level.ToString() ),
|
new(PH_LEVEL, static (data, g) => data.Level.ToString()),
|
||||||
new(PH_USER, static (data, g) => g.GetUser(data.UserId)?.ToString() ?? data.UserId.ToString() ),
|
new(PH_USER, static (data, g) => g.GetUser(data.UserId)?.ToString() ?? data.UserId.ToString()),
|
||||||
new(PH_ROLE, static (data, g) => g.GetRole(data.RoleId)?.ToString() ?? data.RoleId.ToString() )
|
new(PH_ROLE, static (data, g) => g.GetRole(data.RoleId)?.ToString() ?? data.RoleId.ToString())
|
||||||
];
|
];
|
||||||
|
|
||||||
public bool TryGetUserId(out ulong userId)
|
public bool TryGetUserId(out ulong userId)
|
||||||
|
|
|
@ -4,7 +4,7 @@ namespace EllieBot.Modules.Administration;
|
||||||
|
|
||||||
public readonly record struct LevelUpNotifyModel(
|
public readonly record struct LevelUpNotifyModel(
|
||||||
ulong GuildId,
|
ulong GuildId,
|
||||||
ulong ChannelId,
|
ulong? ChannelId,
|
||||||
ulong UserId,
|
ulong UserId,
|
||||||
long Level) : INotifyModel<LevelUpNotifyModel>
|
long Level) : INotifyModel<LevelUpNotifyModel>
|
||||||
{
|
{
|
||||||
|
@ -21,17 +21,32 @@ public readonly record struct LevelUpNotifyModel(
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
[
|
[
|
||||||
new(PH_LEVEL, static (data, g) => data.Level.ToString() ),
|
new(PH_LEVEL, static (data, g) => data.Level.ToString()),
|
||||||
new(PH_USER, static (data, g) => g.GetUser(data.UserId)?.ToString() ?? data.UserId.ToString() )
|
new(PH_USER, static (data, g) => g.GetUser(data.UserId)?.ToString() ?? data.UserId.ToString())
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool SupportsOriginTarget
|
||||||
|
=> true;
|
||||||
|
|
||||||
public readonly bool TryGetGuildId(out ulong guildId)
|
public readonly bool TryGetGuildId(out ulong guildId)
|
||||||
{
|
{
|
||||||
guildId = GuildId;
|
guildId = GuildId;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public readonly bool TryGetChannelId(out ulong channelId)
|
||||||
|
{
|
||||||
|
if (ChannelId is ulong cid)
|
||||||
|
{
|
||||||
|
channelId = cid;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
channelId = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public readonly bool TryGetUserId(out ulong userId)
|
public readonly bool TryGetUserId(out ulong userId)
|
||||||
{
|
{
|
||||||
userId = UserId;
|
userId = UserId;
|
||||||
|
|
|
@ -12,23 +12,23 @@ public partial class Administration
|
||||||
public async Task Notify()
|
public async Task Notify()
|
||||||
{
|
{
|
||||||
await Response()
|
await Response()
|
||||||
.Paginated()
|
.Paginated()
|
||||||
.Items(Enum.GetValues<NotifyType>().DistinctBy(x => (int)x).ToList())
|
.Items(Enum.GetValues<NotifyType>().DistinctBy(x => (int)x).ToList())
|
||||||
.PageSize(5)
|
.PageSize(5)
|
||||||
.Page((items, page) =>
|
.Page((items, page) =>
|
||||||
{
|
{
|
||||||
var eb = CreateEmbed()
|
var eb = CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithTitle(GetText(strs.notify_available));
|
.WithTitle(GetText(strs.notify_available));
|
||||||
|
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
eb.AddField(item.ToString(), GetText(GetDescription(item)), false);
|
eb.AddField(item.ToString(), GetText(GetDescription(item)), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return eb;
|
return eb;
|
||||||
})
|
})
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private LocStr GetDescription(NotifyType item)
|
private LocStr GetDescription(NotifyType item)
|
||||||
|
@ -43,36 +43,56 @@ public partial class Administration
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[UserPerm(GuildPerm.Administrator)]
|
[UserPerm(GuildPerm.Administrator)]
|
||||||
public async Task Notify(NotifyType nType, [Leftover] string? message = null)
|
public async Task Notify(NotifyType nType)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(message))
|
// show msg
|
||||||
|
var conf = await _service.GetNotifyAsync(ctx.Guild.Id, nType);
|
||||||
|
if (conf is null)
|
||||||
{
|
{
|
||||||
// show msg
|
await Response().Confirm(strs.notify_msg_not_set).SendAsync();
|
||||||
var conf = await _service.GetNotifyAsync(ctx.Guild.Id, nType);
|
|
||||||
if (conf is null)
|
|
||||||
{
|
|
||||||
await Response().Confirm(strs.notify_msg_not_set).SendAsync();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var eb = CreateEmbed()
|
|
||||||
.WithOkColor()
|
|
||||||
.WithTitle(GetText(strs.notify_msg))
|
|
||||||
.WithDescription(conf.Message.TrimTo(2048))
|
|
||||||
.AddField(GetText(strs.notify_type), conf.Type.ToString(), true)
|
|
||||||
.AddField(GetText(strs.channel),
|
|
||||||
$"""
|
|
||||||
<#{conf.ChannelId}>
|
|
||||||
`{conf.ChannelId}`
|
|
||||||
""",
|
|
||||||
true);
|
|
||||||
|
|
||||||
await Response().Embed(eb).SendAsync();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _service.EnableAsync(ctx.Guild.Id, ctx.Channel.Id, nType, message);
|
var outChannel = conf.ChannelId is null
|
||||||
await Response().Confirm(strs.notify_on($"<#{ctx.Channel.Id}>", Format.Bold(nType.ToString()))).SendAsync();
|
? """
|
||||||
|
from which the event originated
|
||||||
|
`origin`
|
||||||
|
"""
|
||||||
|
: $"""
|
||||||
|
<#{conf.ChannelId}>
|
||||||
|
`{conf.ChannelId}`
|
||||||
|
""";
|
||||||
|
var eb = CreateEmbed()
|
||||||
|
.WithOkColor()
|
||||||
|
.WithTitle(GetText(strs.notify_msg))
|
||||||
|
.WithDescription(conf.Message.TrimTo(2048))
|
||||||
|
.AddField(GetText(strs.notify_type), conf.Type.ToString(), true)
|
||||||
|
.AddField(GetText(strs.channel),
|
||||||
|
outChannel,
|
||||||
|
true);
|
||||||
|
|
||||||
|
await Response().Embed(eb).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[UserPerm(GuildPerm.Administrator)]
|
||||||
|
public async Task Notify(NotifyType nType, [Leftover] string message)
|
||||||
|
=> await NotifyInternalAsync(nType, null, message);
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[UserPerm(GuildPerm.Administrator)]
|
||||||
|
public async Task Notify(NotifyType nType, IMessageChannel channel, [Leftover] string message)
|
||||||
|
=> await NotifyInternalAsync(nType, channel, message);
|
||||||
|
|
||||||
|
private async Task NotifyInternalAsync(NotifyType nType, IMessageChannel? channel, [Leftover] string message)
|
||||||
|
{
|
||||||
|
var result = await _service.EnableAsync(ctx.Guild.Id, channel?.Id, nType, message);
|
||||||
|
|
||||||
|
var outChannel = channel is null ? "origin" : $"<#{channel.Id}>";
|
||||||
|
await Response()
|
||||||
|
.Confirm(strs.notify_on(outChannel, Format.Bold(nType.ToString())))
|
||||||
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
|
@ -82,13 +102,12 @@ public partial class Administration
|
||||||
var data = _service.GetRegisteredModel(nType);
|
var data = _service.GetRegisteredModel(nType);
|
||||||
|
|
||||||
var eb = CreateEmbed()
|
var eb = CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithTitle(GetText(strs.notify_placeholders(nType.ToString().ToLower())));
|
.WithTitle(GetText(strs.notify_placeholders(nType.ToString().ToLower())));
|
||||||
|
|
||||||
eb.WithDescription(data.Replacements.Join("\n---\n", x => $"`%event.{x}%`"));
|
eb.WithDescription(data.Replacements.Join("\n---\n", x => $"`%event.{x}%`"));
|
||||||
|
|
||||||
await Response().Embed(eb).SendAsync();
|
await Response().Embed(eb).SendAsync();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
|
@ -115,8 +134,8 @@ public partial class Administration
|
||||||
sb.AppendLine(GetText(strs.notify_none));
|
sb.AppendLine(GetText(strs.notify_none));
|
||||||
|
|
||||||
await Response()
|
await Response()
|
||||||
.Confirm(GetText(strs.notify_list), text: sb.ToString())
|
.Confirm(GetText(strs.notify_list), text: sb.ToString())
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
|
|
|
@ -16,6 +16,7 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService
|
||||||
private readonly IBotCreds _creds;
|
private readonly IBotCreds _creds;
|
||||||
private readonly IReplacementService _repSvc;
|
private readonly IReplacementService _repSvc;
|
||||||
private readonly IPubSub _pubSub;
|
private readonly IPubSub _pubSub;
|
||||||
|
|
||||||
private ConcurrentDictionary<NotifyType, ConcurrentDictionary<ulong, Notify>> _events = new();
|
private ConcurrentDictionary<NotifyType, ConcurrentDictionary<ulong, Notify>> _events = new();
|
||||||
|
|
||||||
public NotifyService(
|
public NotifyService(
|
||||||
|
@ -36,12 +37,10 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService
|
||||||
|
|
||||||
private void RegisterModels()
|
private void RegisterModels()
|
||||||
{
|
{
|
||||||
|
|
||||||
RegisterModel<LevelUpNotifyModel>();
|
RegisterModel<LevelUpNotifyModel>();
|
||||||
RegisterModel<ProtectionNotifyModel>();
|
RegisterModel<ProtectionNotifyModel>();
|
||||||
RegisterModel<AddRoleRewardNotifyModel>();
|
RegisterModel<AddRoleRewardNotifyModel>();
|
||||||
RegisterModel<RemoveRoleRewardNotifyModel>();
|
RegisterModel<RemoveRoleRewardNotifyModel>();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnReadyAsync()
|
public async Task OnReadyAsync()
|
||||||
|
@ -84,7 +83,7 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Warning(ex,
|
Log.Warning(ex,
|
||||||
"Unknown error occurred while trying to triger {NotifyEvent} for {NotifyModel}",
|
"Unknown error occurred while trying to trigger {NotifyEvent} for {NotifyModel}",
|
||||||
T.KeyName,
|
T.KeyName,
|
||||||
data);
|
data);
|
||||||
}
|
}
|
||||||
|
@ -93,36 +92,39 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService
|
||||||
private async Task OnEvent<T>(T model)
|
private async Task OnEvent<T>(T model)
|
||||||
where T : struct, INotifyModel<T>
|
where T : struct, INotifyModel<T>
|
||||||
{
|
{
|
||||||
if (_events.TryGetValue(T.NotifyType, out var subs))
|
if (!_events.TryGetValue(T.NotifyType, out var subs))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// make sure the event is consumed
|
||||||
|
// only in the guild it was meant for
|
||||||
|
if (model.TryGetGuildId(out var gid))
|
||||||
{
|
{
|
||||||
if (model.TryGetGuildId(out var gid))
|
if (!subs.TryGetValue(gid, out var conf))
|
||||||
{
|
|
||||||
if (!subs.TryGetValue(gid, out var conf))
|
|
||||||
return;
|
|
||||||
|
|
||||||
await HandleNotifyEvent(conf, model);
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var key in subs.Keys.ToArray())
|
await HandleNotifyEvent(conf, model);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo optimize this
|
||||||
|
foreach (var key in subs.Keys)
|
||||||
|
{
|
||||||
|
if (subs.TryGetValue(key, out var notif))
|
||||||
{
|
{
|
||||||
if (subs.TryGetValue(key, out var notif))
|
try
|
||||||
{
|
{
|
||||||
try
|
await HandleNotifyEvent(notif, model);
|
||||||
{
|
|
||||||
await HandleNotifyEvent(notif, model);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex,
|
|
||||||
"Error occured while sending notification {NotifyEvent} to guild {GuildId}: {ErrorMessage}",
|
|
||||||
T.NotifyType,
|
|
||||||
key,
|
|
||||||
ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.Delay(500);
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex,
|
||||||
|
"Error occured while sending notification {NotifyEvent} to guild {GuildId}: {ErrorMessage}",
|
||||||
|
T.NotifyType,
|
||||||
|
key,
|
||||||
|
ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,9 +133,27 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService
|
||||||
where T : struct, INotifyModel<T>
|
where T : struct, INotifyModel<T>
|
||||||
{
|
{
|
||||||
var guild = _client.GetGuild(conf.GuildId);
|
var guild = _client.GetGuild(conf.GuildId);
|
||||||
var channel = guild?.GetTextChannel(conf.ChannelId);
|
|
||||||
|
|
||||||
if (guild is null || channel is null)
|
// bot probably left the guild, cleanup?
|
||||||
|
if (guild is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
IMessageChannel? channel;
|
||||||
|
// if notify channel is specified for this event, send the event to that channel
|
||||||
|
if (conf.ChannelId is ulong confCid)
|
||||||
|
{
|
||||||
|
channel = guild.GetTextChannel(confCid);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// otherwise get the origin channel of the event
|
||||||
|
if (!model.TryGetChannelId(out var cid))
|
||||||
|
return;
|
||||||
|
|
||||||
|
channel = guild.GetChannel(cid) as IMessageChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channel is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
IUser? user = null;
|
IUser? user = null;
|
||||||
|
@ -165,14 +185,24 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetPhToken(string name) => $"%event.{name}%";
|
private static string GetPhToken(string name)
|
||||||
|
=> $"%event.{name}%";
|
||||||
|
|
||||||
public async Task EnableAsync(
|
public async Task<bool> EnableAsync(
|
||||||
ulong guildId,
|
ulong guildId,
|
||||||
ulong channelId,
|
ulong? channelId,
|
||||||
NotifyType nType,
|
NotifyType nType,
|
||||||
string message)
|
string message)
|
||||||
{
|
{
|
||||||
|
// check if the notify type model supports null channel
|
||||||
|
if (channelId is null)
|
||||||
|
{
|
||||||
|
var model = GetRegisteredModel(nType);
|
||||||
|
if (!model.SupportsOriginTarget)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
await uow.GetTable<Notify>()
|
await uow.GetTable<Notify>()
|
||||||
.InsertOrUpdateAsync(() => new()
|
.InsertOrUpdateAsync(() => new()
|
||||||
|
@ -201,6 +231,8 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService
|
||||||
Type = nType,
|
Type = nType,
|
||||||
Message = message
|
Message = message
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DisableAsync(ulong guildId, NotifyType nType)
|
public async Task DisableAsync(ulong guildId, NotifyType nType)
|
||||||
|
@ -244,11 +276,15 @@ public sealed class NotifyService : IReadyExecutor, INotifySubscriber, IEService
|
||||||
|
|
||||||
// messed up big time, it was supposed to be fully extensible, but it's stored as an enum in the database already...
|
// messed up big time, it was supposed to be fully extensible, but it's stored as an enum in the database already...
|
||||||
private readonly ConcurrentDictionary<NotifyType, NotifyModelData> _models = new();
|
private readonly ConcurrentDictionary<NotifyType, NotifyModelData> _models = new();
|
||||||
|
|
||||||
public void RegisterModel<T>() where T : struct, INotifyModel<T>
|
public void RegisterModel<T>() where T : struct, INotifyModel<T>
|
||||||
{
|
{
|
||||||
var data = new NotifyModelData(T.NotifyType, T.GetReplacements().Map(x => x.Name));
|
var data = new NotifyModelData(T.NotifyType,
|
||||||
|
T.SupportsOriginTarget,
|
||||||
|
T.GetReplacements().Map(x => x.Name));
|
||||||
_models[T.NotifyType] = data;
|
_models[T.NotifyType] = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NotifyModelData GetRegisteredModel(NotifyType nType) => _models[nType];
|
public NotifyModelData GetRegisteredModel(NotifyType nType)
|
||||||
}
|
=> _models[nType];
|
||||||
|
}
|
|
@ -85,6 +85,8 @@ public class GuildConfigXpService(DbService db, ShardData shardData, XpConfigSer
|
||||||
GuildId = guildId,
|
GuildId = guildId,
|
||||||
RateType = type,
|
RateType = type,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_guildRates[(type, guildId)] = new XpRate(type, amount, cooldown);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetChannelXpRateAsync(ulong guildId,
|
public async Task SetChannelXpRateAsync(ulong guildId,
|
||||||
|
@ -119,6 +121,9 @@ public class GuildConfigXpService(DbService db, ShardData shardData, XpConfigSer
|
||||||
ChannelId = channelId,
|
ChannelId = channelId,
|
||||||
RateType = type,
|
RateType = type,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_channelRates.GetOrAdd(guildId, _ => new())
|
||||||
|
[(type, channelId)] = new XpRate(type, amount, cooldown);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
@ -137,6 +142,9 @@ public class GuildConfigXpService(DbService db, ShardData shardData, XpConfigSer
|
||||||
var deleted = await uow.GetTable<GuildXpConfig>()
|
var deleted = await uow.GetTable<GuildXpConfig>()
|
||||||
.Where(x => x.GuildId == guildId)
|
.Where(x => x.GuildId == guildId)
|
||||||
.DeleteAsync();
|
.DeleteAsync();
|
||||||
|
|
||||||
|
_guildRates.TryRemove((XpRateType.Text, guildId), out _);
|
||||||
|
|
||||||
return deleted > 0;
|
return deleted > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,6 +154,10 @@ public class GuildConfigXpService(DbService db, ShardData shardData, XpConfigSer
|
||||||
var deleted = await uow.GetTable<ChannelXpConfig>()
|
var deleted = await uow.GetTable<ChannelXpConfig>()
|
||||||
.Where(x => x.GuildId == guildId && x.ChannelId == channelId)
|
.Where(x => x.GuildId == guildId && x.ChannelId == channelId)
|
||||||
.DeleteAsync();
|
.DeleteAsync();
|
||||||
|
|
||||||
|
if (_channelRates.TryGetValue(guildId, out var channelRates))
|
||||||
|
channelRates.TryRemove((XpRateType.Text, channelId), out _);
|
||||||
|
|
||||||
return deleted > 0;
|
return deleted > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -155,7 +155,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
|
|
||||||
if (oldBatch.Contains(u))
|
if (oldBatch.Contains(u))
|
||||||
{
|
{
|
||||||
validUsers.Add(new(u, rate.Amount));
|
validUsers.Add(new(u, rate.Amount, vc.Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
_voiceXpBatch.Add(u);
|
_voiceXpBatch.Add(u);
|
||||||
|
@ -218,13 +218,13 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
(batch, stats) => stats)
|
(batch, stats) => stats)
|
||||||
.ToListAsyncLinqToDB();
|
.ToListAsyncLinqToDB();
|
||||||
|
|
||||||
var userToXp = currentBatch.ToDictionary(x => x.User.Id, x => x.Xp);
|
var userToXp = currentBatch.ToDictionary(x => x.User.Id, x => x);
|
||||||
foreach (var u in updated)
|
foreach (var u in updated)
|
||||||
{
|
{
|
||||||
if (!userToXp.TryGetValue(u.UserId, out var xpGained))
|
if (!userToXp.TryGetValue(u.UserId, out var data))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var oldStats = new LevelStats(u.Xp - xpGained);
|
var oldStats = new LevelStats(u.Xp - data.Xp);
|
||||||
var newStats = new LevelStats(u.Xp);
|
var newStats = new LevelStats(u.Xp);
|
||||||
|
|
||||||
Log.Information("User {User} xp updated from {OldLevel} to {NewLevel}",
|
Log.Information("User {User} xp updated from {OldLevel} to {NewLevel}",
|
||||||
|
@ -235,9 +235,8 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
if (oldStats.Level < newStats.Level)
|
if (oldStats.Level < newStats.Level)
|
||||||
{
|
{
|
||||||
await _levelUpQueue.EnqueueAsync(NotifyUser(u.GuildId,
|
await _levelUpQueue.EnqueueAsync(NotifyUser(u.GuildId,
|
||||||
0,
|
data.ChannelId,
|
||||||
u.UserId,
|
u.UserId,
|
||||||
true,
|
|
||||||
oldStats.Level,
|
oldStats.Level,
|
||||||
newStats.Level));
|
newStats.Level));
|
||||||
}
|
}
|
||||||
|
@ -246,19 +245,15 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
|
|
||||||
private Func<Task> NotifyUser(
|
private Func<Task> NotifyUser(
|
||||||
ulong guildId,
|
ulong guildId,
|
||||||
ulong channelId,
|
ulong? channelId,
|
||||||
ulong userId,
|
ulong userId,
|
||||||
bool isServer,
|
|
||||||
long oldLevel,
|
long oldLevel,
|
||||||
long newLevel)
|
long newLevel)
|
||||||
=> async () =>
|
=> async () =>
|
||||||
{
|
{
|
||||||
if (isServer)
|
await HandleRewardsInternalAsync(guildId, userId, oldLevel, newLevel);
|
||||||
{
|
|
||||||
await HandleRewardsInternalAsync(guildId, userId, oldLevel, newLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
await HandleNotifyInternalAsync(guildId, channelId, userId, isServer, newLevel);
|
await HandleNotifyInternalAsync(guildId, channelId, userId, newLevel);
|
||||||
};
|
};
|
||||||
|
|
||||||
private async Task HandleRewardsInternalAsync(
|
private async Task HandleRewardsInternalAsync(
|
||||||
|
@ -338,9 +333,8 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
|
|
||||||
private async Task HandleNotifyInternalAsync(
|
private async Task HandleNotifyInternalAsync(
|
||||||
ulong guildId,
|
ulong guildId,
|
||||||
ulong channelId,
|
ulong? channelId,
|
||||||
ulong userId,
|
ulong userId,
|
||||||
bool isServer,
|
|
||||||
long newLevel)
|
long newLevel)
|
||||||
{
|
{
|
||||||
var guild = _client.GetGuild(guildId);
|
var guild = _client.GetGuild(guildId);
|
||||||
|
@ -349,18 +343,15 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
if (guild is null || user is null)
|
if (guild is null || user is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (isServer)
|
var model = new LevelUpNotifyModel()
|
||||||
{
|
{
|
||||||
var model = new LevelUpNotifyModel()
|
GuildId = guildId,
|
||||||
{
|
UserId = userId,
|
||||||
GuildId = guildId,
|
ChannelId = channelId,
|
||||||
UserId = userId,
|
Level = newLevel
|
||||||
ChannelId = channelId,
|
};
|
||||||
Level = newLevel
|
await _notifySub.NotifyAsync(model, true);
|
||||||
};
|
return;
|
||||||
await _notifySub.NotifyAsync(model, true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetCurrencyReward(ulong guildId, int level, int amount)
|
public async Task SetCurrencyReward(ulong guildId, int level, int amount)
|
||||||
|
@ -552,7 +543,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
if (!await TryAddUserGainedXpAsync(user.Id, rate.Cooldown))
|
if (!await TryAddUserGainedXpAsync(user.Id, rate.Cooldown))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_usersBatch.Add(new(user, rate.Amount));
|
_usersBatch.Add(new(user, rate.Amount, gc.Id));
|
||||||
});
|
});
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
@ -1169,7 +1160,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly record struct XpQueueEntry(IGuildUser User, long Xp)
|
public readonly record struct XpQueueEntry(IGuildUser User, long Xp, ulong? ChannelId)
|
||||||
{
|
{
|
||||||
public bool Equals(XpQueueEntry? other)
|
public bool Equals(XpQueueEntry? other)
|
||||||
=> other?.User == User;
|
=> other?.User == User;
|
||||||
|
|
|
@ -4,16 +4,6 @@ version: 3
|
||||||
isEnabled: false
|
isEnabled: false
|
||||||
# Who can do how much of what
|
# Who can do how much of what
|
||||||
limits:
|
limits:
|
||||||
100:
|
|
||||||
ChatBot:
|
|
||||||
quota: 50000000
|
|
||||||
quotaPeriod: PerMonth
|
|
||||||
ReactionRole:
|
|
||||||
quota: -1
|
|
||||||
quotaPeriod: Total
|
|
||||||
Prune:
|
|
||||||
quota: -1
|
|
||||||
quotaPeriod: PerDay
|
|
||||||
50:
|
50:
|
||||||
ChatBot:
|
ChatBot:
|
||||||
quota: 20000000
|
quota: 20000000
|
||||||
|
@ -24,16 +14,6 @@ limits:
|
||||||
Prune:
|
Prune:
|
||||||
quota: -1
|
quota: -1
|
||||||
quotaPeriod: PerDay
|
quotaPeriod: PerDay
|
||||||
20:
|
|
||||||
ChatBot:
|
|
||||||
quota: 6500000
|
|
||||||
quotaPeriod: PerMonth
|
|
||||||
ReactionRole:
|
|
||||||
quota: -1
|
|
||||||
quotaPeriod: Total
|
|
||||||
Prune:
|
|
||||||
quota: 20
|
|
||||||
quotaPeriod: PerDay
|
|
||||||
10:
|
10:
|
||||||
ChatBot:
|
ChatBot:
|
||||||
quota: 2500000
|
quota: 2500000
|
||||||
|
@ -44,7 +24,7 @@ limits:
|
||||||
Prune:
|
Prune:
|
||||||
quota: 5
|
quota: 5
|
||||||
quotaPeriod: PerDay
|
quotaPeriod: PerDay
|
||||||
5:
|
2:
|
||||||
ChatBot:
|
ChatBot:
|
||||||
quota: 1000000
|
quota: 1000000
|
||||||
quotaPeriod: PerMonth
|
quotaPeriod: PerMonth
|
||||||
|
@ -53,4 +33,4 @@ limits:
|
||||||
quotaPeriod: Total
|
quotaPeriod: Total
|
||||||
Prune:
|
Prune:
|
||||||
quota: 2
|
quota: 2
|
||||||
quotaPeriod: PerDay
|
quotaPeriod: PerDay
|
|
@ -4862,14 +4862,18 @@ minesweeper:
|
||||||
desc: "The number of mines to create."
|
desc: "The number of mines to create."
|
||||||
notify:
|
notify:
|
||||||
desc: |-
|
desc: |-
|
||||||
Sends a message to the current channel once the specified event occurs.
|
Sends a message to the specified channel once the specified event occurs.
|
||||||
|
|
||||||
|
If no channel is specified, the message will be sent to the channel from which the event originated.
|
||||||
|
*note: this is only possible for events that have an origin channel (for example `levelup`)*
|
||||||
|
|
||||||
Provide no parameters to see all available events.
|
Provide no parameters to see all available events.
|
||||||
ex:
|
ex:
|
||||||
- 'levelup Congratulations to user %user.name% for reaching level %event.level%'
|
- 'levelup Congratulations to user %user.name% for reaching level %event.level%'
|
||||||
params:
|
params:
|
||||||
- { }
|
- { }
|
||||||
- event:
|
- event:
|
||||||
desc: "The event to notify on."
|
desc: "The event for which to see the current message."
|
||||||
- event:
|
- event:
|
||||||
desc: "The event to notify on."
|
desc: "The event to notify on."
|
||||||
message:
|
message:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue