don't track EFCore objects that don't need to be updated
This commit is contained in:
parent
d42e73699b
commit
f7f88ff98f
24 changed files with 50 additions and 33 deletions
|
|
@ -40,7 +40,7 @@ public partial class GuildsController
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(guildId.Value);
|
var guildConfig = await db.GetGuildAsync(guildId.Value, false);
|
||||||
var logChannelId =
|
var logChannelId =
|
||||||
webhookExecutor.GetLogChannel(guildConfig, LogChannelType.GuildUpdate)
|
webhookExecutor.GetLogChannel(guildConfig, LogChannelType.GuildUpdate)
|
||||||
?? webhookExecutor.GetLogChannel(guildConfig, LogChannelType.GuildMemberRemove);
|
?? webhookExecutor.GetLogChannel(guildConfig, LogChannelType.GuildMemberRemove);
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ public partial class GuildsController(
|
||||||
{
|
{
|
||||||
var (guildId, guild) = await ParseGuildAsync(id);
|
var (guildId, guild) = await ParseGuildAsync(id);
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(guildId.Value);
|
var guildConfig = await db.GetGuildAsync(guildId.Value, false);
|
||||||
|
|
||||||
var channels = channelCache
|
var channels = channelCache
|
||||||
.GuildChannels(guildId)
|
.GuildChannels(guildId)
|
||||||
|
|
|
||||||
|
|
@ -227,7 +227,7 @@ public class MetaCommands(
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var query = HttpUtility.UrlEncode("delta(catalogger_received_messages[5m])");
|
var query = HttpUtility.UrlEncode("increase(catalogger_received_messages[5m])");
|
||||||
var resp = await _client.GetAsync($"http://localhost:9090/api/v1/query?query={query}");
|
var resp = await _client.GetAsync($"http://localhost:9090/api/v1/query?query={query}");
|
||||||
resp.EnsureSuccessStatusCode();
|
resp.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ public class ChannelCreateResponder(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(ch.GuildID.Value, ct);
|
var guildConfig = await db.GetGuildAsync(ch.GuildID.Value, false, ct);
|
||||||
webhookExecutor.QueueLog(
|
webhookExecutor.QueueLog(
|
||||||
guildConfig,
|
guildConfig,
|
||||||
LogChannelType.ChannelCreate,
|
LogChannelType.ChannelCreate,
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ public class ChannelDeleteResponder(
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(evt.GuildID.Value, ct);
|
var guildConfig = await db.GetGuildAsync(evt.GuildID.Value, false, ct);
|
||||||
var embed = new EmbedBuilder()
|
var embed = new EmbedBuilder()
|
||||||
.WithTitle("Channel deleted")
|
.WithTitle("Channel deleted")
|
||||||
.WithColour(DiscordUtils.Red)
|
.WithColour(DiscordUtils.Red)
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ public class ChannelUpdateResponder(
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(evt.GuildID.Value, ct);
|
var guildConfig = await db.GetGuildAsync(evt.GuildID.Value, false, ct);
|
||||||
|
|
||||||
var builder = new EmbedBuilder()
|
var builder = new EmbedBuilder()
|
||||||
.WithTitle(
|
.WithTitle(
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ public class GuildBanAddResponder(
|
||||||
|
|
||||||
public async Task<Result> RespondAsync(IGuildBanAdd evt, CancellationToken ct = default)
|
public async Task<Result> RespondAsync(IGuildBanAdd evt, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var guildConfig = await db.GetGuildAsync(evt.GuildID, ct);
|
var guildConfig = await db.GetGuildAsync(evt.GuildID, true, ct);
|
||||||
|
|
||||||
// Delay 2 seconds for the audit log
|
// Delay 2 seconds for the audit log
|
||||||
await Task.Delay(2000, ct);
|
await Task.Delay(2000, ct);
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ public class GuildBanRemoveResponder(
|
||||||
|
|
||||||
public async Task<Result> RespondAsync(IGuildBanRemove evt, CancellationToken ct = default)
|
public async Task<Result> RespondAsync(IGuildBanRemove evt, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var guildConfig = await db.GetGuildAsync(evt.GuildID, ct);
|
var guildConfig = await db.GetGuildAsync(evt.GuildID, false, ct);
|
||||||
|
|
||||||
// Delay 2 seconds for the audit log
|
// Delay 2 seconds for the audit log
|
||||||
await Task.Delay(2000, ct);
|
await Task.Delay(2000, ct);
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ public class GuildEmojisUpdateResponder(
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(evt.GuildID, ct);
|
var guildConfig = await db.GetGuildAsync(evt.GuildID, false, ct);
|
||||||
webhookExecutor.QueueLog(guildConfig, LogChannelType.GuildEmojisUpdate, embed);
|
webhookExecutor.QueueLog(guildConfig, LogChannelType.GuildEmojisUpdate, embed);
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ public class GuildUpdateResponder(
|
||||||
|
|
||||||
if (embed.Fields.Count != 0)
|
if (embed.Fields.Count != 0)
|
||||||
{
|
{
|
||||||
var guildConfig = await db.GetGuildAsync(evt.ID, ct);
|
var guildConfig = await db.GetGuildAsync(evt.ID, false, ct);
|
||||||
webhookExecutor.QueueLog(
|
webhookExecutor.QueueLog(
|
||||||
guildConfig,
|
guildConfig,
|
||||||
LogChannelType.GuildUpdate,
|
LogChannelType.GuildUpdate,
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ public class InviteCreateResponder(
|
||||||
inline: true
|
inline: true
|
||||||
);
|
);
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(guildId, ct);
|
var guildConfig = await db.GetGuildAsync(guildId, false, ct);
|
||||||
webhookExecutor.QueueLog(
|
webhookExecutor.QueueLog(
|
||||||
guildConfig,
|
guildConfig,
|
||||||
LogChannelType.InviteCreate,
|
LogChannelType.InviteCreate,
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ public class InviteDeleteResponder(
|
||||||
inline: true
|
inline: true
|
||||||
);
|
);
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(guildId, ct);
|
var guildConfig = await db.GetGuildAsync(guildId, false, ct);
|
||||||
webhookExecutor.QueueLog(
|
webhookExecutor.QueueLog(
|
||||||
guildConfig,
|
guildConfig,
|
||||||
LogChannelType.InviteDelete,
|
LogChannelType.InviteDelete,
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ public class GuildMemberAddResponder(
|
||||||
.WithCurrentTimestamp()
|
.WithCurrentTimestamp()
|
||||||
.WithFooter($"ID: {user.ID}");
|
.WithFooter($"ID: {user.ID}");
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(member.GuildID, ct);
|
var guildConfig = await db.GetGuildAsync(member.GuildID, false, ct);
|
||||||
var guildRes = await guildApi.GetGuildAsync(member.GuildID, withCounts: true, ct);
|
var guildRes = await guildApi.GetGuildAsync(member.GuildID, withCounts: true, ct);
|
||||||
if (guildRes.IsSuccess && guildRes.Entity.ApproximateMemberCount.IsDefined())
|
if (guildRes.IsSuccess && guildRes.Entity.ApproximateMemberCount.IsDefined())
|
||||||
builder.Description +=
|
builder.Description +=
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ public class GuildMemberRemoveResponder(
|
||||||
.WithFooter($"ID: {evt.User.ID}")
|
.WithFooter($"ID: {evt.User.ID}")
|
||||||
.WithCurrentTimestamp();
|
.WithCurrentTimestamp();
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(evt.GuildID, ct);
|
var guildConfig = await db.GetGuildAsync(evt.GuildID, false, ct);
|
||||||
|
|
||||||
var member = await memberCache.TryGetAsync(evt.GuildID, evt.User.ID);
|
var member = await memberCache.TryGetAsync(evt.GuildID, evt.User.ID);
|
||||||
if (member == null)
|
if (member == null)
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@ public class GuildMemberUpdateResponder(
|
||||||
.GetOrThrow();
|
.GetOrThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(newMember.GuildID, ct);
|
var guildConfig = await db.GetGuildAsync(newMember.GuildID, false, ct);
|
||||||
webhookExecutor.QueueLog(guildConfig, LogChannelType.GuildMemberAvatarUpdate, embed);
|
webhookExecutor.QueueLog(guildConfig, LogChannelType.GuildMemberAvatarUpdate, embed);
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
@ -204,7 +204,7 @@ public class GuildMemberUpdateResponder(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(newMember.GuildID, ct);
|
var guildConfig = await db.GetGuildAsync(newMember.GuildID, false, ct);
|
||||||
webhookExecutor.QueueLog(
|
webhookExecutor.QueueLog(
|
||||||
guildConfig,
|
guildConfig,
|
||||||
LogChannelType.GuildMemberNickUpdate,
|
LogChannelType.GuildMemberNickUpdate,
|
||||||
|
|
@ -253,7 +253,7 @@ public class GuildMemberUpdateResponder(
|
||||||
embed.AddField("Reason", "*(unknown)*");
|
embed.AddField("Reason", "*(unknown)*");
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(member.GuildID, ct);
|
var guildConfig = await db.GetGuildAsync(member.GuildID, false, ct);
|
||||||
webhookExecutor.QueueLog(
|
webhookExecutor.QueueLog(
|
||||||
guildConfig,
|
guildConfig,
|
||||||
LogChannelType.GuildMemberTimeout,
|
LogChannelType.GuildMemberTimeout,
|
||||||
|
|
@ -268,7 +268,7 @@ public class GuildMemberUpdateResponder(
|
||||||
CancellationToken ct = default
|
CancellationToken ct = default
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var guildConfig = await db.GetGuildAsync(member.GuildID, ct);
|
var guildConfig = await db.GetGuildAsync(member.GuildID, false, ct);
|
||||||
var guildRoles = roleCache.GuildRoles(member.GuildID).ToList();
|
var guildRoles = roleCache.GuildRoles(member.GuildID).ToList();
|
||||||
|
|
||||||
var keyRoleUpdate = new EmbedBuilder()
|
var keyRoleUpdate = new EmbedBuilder()
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ public class MessageCreateResponder(
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
var guild = await db.GetGuildAsync(msg.GuildID, ct);
|
var guild = await db.GetGuildAsync(msg.GuildID, false, ct);
|
||||||
// The guild needs to have enabled at least one of the message logging events,
|
// The guild needs to have enabled at least one of the message logging events,
|
||||||
// and the channel must not be ignored, to store the message.
|
// and the channel must not be ignored, to store the message.
|
||||||
if (guild.IsMessageIgnored(msg.ChannelID, msg.Author.ID))
|
if (guild.IsMessageIgnored(msg.ChannelID, msg.Author.ID))
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ public class MessageDeleteBulkResponder(
|
||||||
|
|
||||||
public async Task<Result> RespondAsync(IMessageDeleteBulk evt, CancellationToken ct = default)
|
public async Task<Result> RespondAsync(IMessageDeleteBulk evt, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var guild = await db.GetGuildAsync(evt.GuildID, ct);
|
var guild = await db.GetGuildAsync(evt.GuildID, false, ct);
|
||||||
if (guild.IsMessageIgnored(evt.ChannelID, null))
|
if (guild.IsMessageIgnored(evt.ChannelID, null))
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ public class MessageDeleteResponder(
|
||||||
if (await messageRepository.IsMessageIgnoredAsync(evt.ID.Value, ct))
|
if (await messageRepository.IsMessageIgnoredAsync(evt.ID.Value, ct))
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
|
|
||||||
var guild = await db.GetGuildAsync(evt.GuildID, ct);
|
var guild = await db.GetGuildAsync(evt.GuildID, false, ct);
|
||||||
if (guild.IsMessageIgnored(evt.ChannelID, evt.ID))
|
if (guild.IsMessageIgnored(evt.ChannelID, evt.ID))
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ public class MessageUpdateResponder(
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(msg.GuildID.Value, ct);
|
var guildConfig = await db.GetGuildAsync(msg.GuildID.Value, false, ct);
|
||||||
|
|
||||||
if (await messageRepository.IsMessageIgnoredAsync(msg.ID.Value, ct))
|
if (await messageRepository.IsMessageIgnoredAsync(msg.ID.Value, ct))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ public class RoleCreateResponder(
|
||||||
_logger.Debug("Received new role {RoleId} in guild {GuildId}", evt.Role.ID, evt.GuildID);
|
_logger.Debug("Received new role {RoleId} in guild {GuildId}", evt.Role.ID, evt.GuildID);
|
||||||
roleCache.Set(evt.Role, evt.GuildID);
|
roleCache.Set(evt.Role, evt.GuildID);
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(evt.GuildID, ct);
|
var guildConfig = await db.GetGuildAsync(evt.GuildID, false, ct);
|
||||||
|
|
||||||
var embed = new EmbedBuilder()
|
var embed = new EmbedBuilder()
|
||||||
.WithTitle("Role created")
|
.WithTitle("Role created")
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ public class RoleDeleteResponder(
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(evt.GuildID, ct);
|
var guildConfig = await db.GetGuildAsync(evt.GuildID, false, ct);
|
||||||
|
|
||||||
var embed = new EmbedBuilder()
|
var embed = new EmbedBuilder()
|
||||||
.WithTitle($"Role \"{role.Name}\" deleted")
|
.WithTitle($"Role \"{role.Name}\" deleted")
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ public class RoleUpdateResponder(
|
||||||
if (embed.Fields.Count == 0)
|
if (embed.Fields.Count == 0)
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(evt.GuildID, ct);
|
var guildConfig = await db.GetGuildAsync(evt.GuildID, false, ct);
|
||||||
webhookExecutor.QueueLog(
|
webhookExecutor.QueueLog(
|
||||||
guildConfig,
|
guildConfig,
|
||||||
LogChannelType.GuildRoleUpdate,
|
LogChannelType.GuildRoleUpdate,
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,10 @@ public class MessageRepository(
|
||||||
msg.Attachments.Select(a => new Attachment(a.Filename, a.Size, a.ContentType.Value))
|
msg.Attachments.Select(a => new Attachment(a.Filename, a.Size, a.ContentType.Value))
|
||||||
);
|
);
|
||||||
|
|
||||||
var dbMsg = await db.Messages.FindAsync(msg.ID.Value);
|
var dbMsg = await db.Messages.FindAsync(
|
||||||
|
new object?[] { msg.ID.Value },
|
||||||
|
cancellationToken: ct
|
||||||
|
);
|
||||||
if (dbMsg == null)
|
if (dbMsg == null)
|
||||||
throw new CataloggerError(
|
throw new CataloggerError(
|
||||||
"Message was null despite HasProxyInfoAsync returning true"
|
"Message was null despite HasProxyInfoAsync returning true"
|
||||||
|
|
@ -127,7 +130,7 @@ public class MessageRepository(
|
||||||
{
|
{
|
||||||
_logger.Debug("Retrieving message {MessageId}", id);
|
_logger.Debug("Retrieving message {MessageId}", id);
|
||||||
|
|
||||||
var dbMsg = await db.Messages.FindAsync(id);
|
var dbMsg = await db.Messages.AsNoTracking().FirstOrDefaultAsync(m => m.Id == id, ct);
|
||||||
if (dbMsg == null)
|
if (dbMsg == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
|
@ -159,7 +162,8 @@ public class MessageRepository(
|
||||||
_logger.Debug("Checking if message {MessageId} has proxy information", id);
|
_logger.Debug("Checking if message {MessageId} has proxy information", id);
|
||||||
|
|
||||||
var msg = await db
|
var msg = await db
|
||||||
.Messages.Select(m => new { m.Id, m.OriginalId })
|
.Messages.AsNoTracking()
|
||||||
|
.Select(m => new { m.Id, m.OriginalId })
|
||||||
.FirstOrDefaultAsync(m => m.Id == id);
|
.FirstOrDefaultAsync(m => m.Id == id);
|
||||||
return (msg != null, msg?.OriginalId != null);
|
return (msg != null, msg?.OriginalId != null);
|
||||||
}
|
}
|
||||||
|
|
@ -195,7 +199,8 @@ public class MessageRepository(
|
||||||
public async Task<bool> IsMessageIgnoredAsync(ulong id, CancellationToken ct = default)
|
public async Task<bool> IsMessageIgnoredAsync(ulong id, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
_logger.Debug("Checking if message {MessageId} is ignored", id);
|
_logger.Debug("Checking if message {MessageId} is ignored", id);
|
||||||
return await db.IgnoredMessages.FirstOrDefaultAsync(m => m.Id == id, ct) != null;
|
return await db.IgnoredMessages.AsNoTracking().FirstOrDefaultAsync(m => m.Id == id, ct)
|
||||||
|
!= null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public const int MaxMessageAgeDays = 15;
|
public const int MaxMessageAgeDays = 15;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
using Catalogger.Backend.Database.Models;
|
using Catalogger.Backend.Database.Models;
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Remora.Rest.Core;
|
using Remora.Rest.Core;
|
||||||
|
|
||||||
namespace Catalogger.Backend.Database.Queries;
|
namespace Catalogger.Backend.Database.Queries;
|
||||||
|
|
@ -24,22 +25,30 @@ public static class QueryExtensions
|
||||||
public static async ValueTask<Guild> GetGuildAsync(
|
public static async ValueTask<Guild> GetGuildAsync(
|
||||||
this DatabaseContext db,
|
this DatabaseContext db,
|
||||||
Snowflake id,
|
Snowflake id,
|
||||||
|
bool shouldTrack = true,
|
||||||
CancellationToken ct = default
|
CancellationToken ct = default
|
||||||
) => await db.GetGuildAsync(id.ToUlong(), ct);
|
) => await db.GetGuildAsync(id.ToUlong(), shouldTrack, ct);
|
||||||
|
|
||||||
public static async ValueTask<Guild> GetGuildAsync(
|
public static async ValueTask<Guild> GetGuildAsync(
|
||||||
this DatabaseContext db,
|
this DatabaseContext db,
|
||||||
Optional<Snowflake> id,
|
Optional<Snowflake> id,
|
||||||
|
bool shouldTrack = true,
|
||||||
CancellationToken ct = default
|
CancellationToken ct = default
|
||||||
) => await db.GetGuildAsync(id.ToUlong(), ct);
|
) => await db.GetGuildAsync(id.ToUlong(), shouldTrack, ct);
|
||||||
|
|
||||||
public static async ValueTask<Guild> GetGuildAsync(
|
public static async ValueTask<Guild> GetGuildAsync(
|
||||||
this DatabaseContext db,
|
this DatabaseContext db,
|
||||||
ulong id,
|
ulong id,
|
||||||
|
bool shouldTrack = true,
|
||||||
CancellationToken ct = default
|
CancellationToken ct = default
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var guild = await db.Guilds.FindAsync([id], ct);
|
Guild? guild;
|
||||||
|
if (shouldTrack)
|
||||||
|
guild = await db.Guilds.FindAsync([id], ct);
|
||||||
|
else
|
||||||
|
guild = await db.Guilds.AsNoTracking().FirstOrDefaultAsync(g => g.Id == id, ct);
|
||||||
|
|
||||||
if (guild == null)
|
if (guild == null)
|
||||||
throw new CataloggerError("Guild not found, was not initialized during guild create");
|
throw new CataloggerError("Guild not found, was not initialized during guild create");
|
||||||
return guild;
|
return guild;
|
||||||
|
|
@ -50,5 +59,8 @@ public static class QueryExtensions
|
||||||
Snowflake guildId,
|
Snowflake guildId,
|
||||||
Snowflake userId,
|
Snowflake userId,
|
||||||
CancellationToken ct = default
|
CancellationToken ct = default
|
||||||
) => await db.Watchlists.FindAsync([guildId.Value, userId.Value], ct);
|
) =>
|
||||||
|
await db
|
||||||
|
.Watchlists.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(w => w.GuildId == guildId.Value && w.UserId == userId.Value, ct);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue