feat: watchlist repository, remove ef core from all bot code
This commit is contained in:
parent
da4dfae27c
commit
f0511a560c
19 changed files with 155 additions and 97 deletions
|
|
@ -19,6 +19,7 @@ using Catalogger.Backend.Bot;
|
||||||
using Catalogger.Backend.Database.Queries;
|
using Catalogger.Backend.Database.Queries;
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
|
using Dapper;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Remora.Discord.Extensions.Embeds;
|
using Remora.Discord.Extensions.Embeds;
|
||||||
|
|
@ -40,7 +41,7 @@ public partial class GuildsController
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(guildId.Value, false);
|
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||||
var logChannelId =
|
var logChannelId =
|
||||||
webhookExecutor.GetLogChannel(guildConfig, LogChannelType.GuildUpdate)
|
webhookExecutor.GetLogChannel(guildConfig, LogChannelType.GuildUpdate)
|
||||||
?? webhookExecutor.GetLogChannel(guildConfig, LogChannelType.GuildMemberRemove);
|
?? webhookExecutor.GetLogChannel(guildConfig, LogChannelType.GuildMemberRemove);
|
||||||
|
|
@ -80,17 +81,32 @@ public partial class GuildsController
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await using var tx = await db.Database.BeginTransactionAsync();
|
await using var tx = await dbConn.BeginTransactionAsync();
|
||||||
var inviteCount = await db
|
|
||||||
.Invites.Where(i => i.GuildId == guildId.Value)
|
var inviteCount = await dbConn.ExecuteAsync(
|
||||||
.ExecuteDeleteAsync();
|
"delete from invites where guild_id = @GuildId",
|
||||||
var watchlistCount = await db
|
new { GuildId = guildId.Value },
|
||||||
.Watchlists.Where(w => w.GuildId == guildId.Value)
|
tx
|
||||||
.ExecuteDeleteAsync();
|
);
|
||||||
var messageCount = await db
|
|
||||||
.Messages.Where(m => m.GuildId == guildId.Value)
|
var watchlistCount = await dbConn.ExecuteAsync(
|
||||||
.ExecuteDeleteAsync();
|
"delete from watchlists where guild_id = @GuildId",
|
||||||
await db.Guilds.Where(g => g.Id == guildId.Value).ExecuteDeleteAsync();
|
new { GuildId = guildId.Value },
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
var messageCount = await dbConn.ExecuteAsync(
|
||||||
|
"delete from messages where guild_id = @GuildId",
|
||||||
|
new { GuildId = guildId.Value },
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
await dbConn.ExecuteAsync(
|
||||||
|
"delete from guilds where id = @GuildId",
|
||||||
|
new { GuildId = guildId.Value },
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
await tx.CommitAsync();
|
await tx.CommitAsync();
|
||||||
|
|
||||||
_logger.Information(
|
_logger.Information(
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ using Catalogger.Backend.Api.Middleware;
|
||||||
using Catalogger.Backend.Cache;
|
using Catalogger.Backend.Cache;
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database;
|
||||||
using Catalogger.Backend.Database.Queries;
|
using Catalogger.Backend.Database.Dapper;
|
||||||
using Catalogger.Backend.Database.Redis;
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Remora.Discord.API;
|
using Remora.Discord.API;
|
||||||
|
|
@ -33,6 +33,8 @@ namespace Catalogger.Backend.Api;
|
||||||
public partial class GuildsController(
|
public partial class GuildsController(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DatabaseContext db,
|
DatabaseContext db,
|
||||||
|
DatabaseConnection dbConn,
|
||||||
|
GuildRepository guildRepository,
|
||||||
GuildCache guildCache,
|
GuildCache guildCache,
|
||||||
EmojiCache emojiCache,
|
EmojiCache emojiCache,
|
||||||
ChannelCache channelCache,
|
ChannelCache channelCache,
|
||||||
|
|
@ -70,7 +72,7 @@ public partial class GuildsController(
|
||||||
{
|
{
|
||||||
var (guildId, guild) = await ParseGuildAsync(id);
|
var (guildId, guild) = await ParseGuildAsync(id);
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(guildId.Value, false);
|
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||||
|
|
||||||
var channels = channelCache
|
var channels = channelCache
|
||||||
.GuildChannels(guildId)
|
.GuildChannels(guildId)
|
||||||
|
|
@ -134,12 +136,12 @@ public partial class GuildsController(
|
||||||
[ProducesResponseType<Database.Models.Guild.ChannelConfig>(statusCode: StatusCodes.Status200OK)]
|
[ProducesResponseType<Database.Models.Guild.ChannelConfig>(statusCode: StatusCodes.Status200OK)]
|
||||||
public async Task<IActionResult> PatchGuildAsync(string id, [FromBody] ChannelRequest req)
|
public async Task<IActionResult> PatchGuildAsync(string id, [FromBody] ChannelRequest req)
|
||||||
{
|
{
|
||||||
var (guildId, guild) = await ParseGuildAsync(id);
|
var (guildId, _) = await ParseGuildAsync(id);
|
||||||
var guildChannels = channelCache
|
var guildChannels = channelCache
|
||||||
.GuildChannels(guildId)
|
.GuildChannels(guildId)
|
||||||
.Where(c => c.Type is ChannelType.GuildText)
|
.Where(c => c.Type is ChannelType.GuildText)
|
||||||
.ToList();
|
.ToList();
|
||||||
var guildConfig = await db.GetGuildAsync(guildId);
|
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||||
|
|
||||||
if (req.IgnoredChannels != null)
|
if (req.IgnoredChannels != null)
|
||||||
{
|
{
|
||||||
|
|
@ -316,9 +318,7 @@ public partial class GuildsController(
|
||||||
)
|
)
|
||||||
guildConfig.Channels.MessageDeleteBulk = req.MessageDeleteBulk ?? 0;
|
guildConfig.Channels.MessageDeleteBulk = req.MessageDeleteBulk ?? 0;
|
||||||
|
|
||||||
db.Update(guildConfig);
|
await guildRepository.UpdateChannelConfigAsync(guildId, guildConfig.Channels);
|
||||||
await db.SaveChangesAsync();
|
|
||||||
|
|
||||||
return Ok(guildConfig.Channels);
|
return Ok(guildConfig.Channels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ using System.ComponentModel;
|
||||||
using Catalogger.Backend.Cache;
|
using Catalogger.Backend.Cache;
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database;
|
||||||
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
using Catalogger.Backend.Database.Queries;
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
|
|
@ -42,7 +43,7 @@ namespace Catalogger.Backend.Bot.Commands;
|
||||||
public class ChannelCommands(
|
public class ChannelCommands(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
Config config,
|
Config config,
|
||||||
DatabaseContext db,
|
GuildRepository guildRepository,
|
||||||
GuildCache guildCache,
|
GuildCache guildCache,
|
||||||
ChannelCache channelCache,
|
ChannelCache channelCache,
|
||||||
IMemberCache memberCache,
|
IMemberCache memberCache,
|
||||||
|
|
@ -207,7 +208,7 @@ public class ChannelCommands(
|
||||||
if (!guildCache.TryGet(guildId, out var guild))
|
if (!guildCache.TryGet(guildId, out var guild))
|
||||||
throw new CataloggerError("Guild not in cache");
|
throw new CataloggerError("Guild not in cache");
|
||||||
var guildChannels = channelCache.GuildChannels(guildId).ToList();
|
var guildChannels = channelCache.GuildChannels(guildId).ToList();
|
||||||
var guildConfig = await db.GetGuildAsync(guildId);
|
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||||
|
|
||||||
var (embeds, components) = BuildRootMenu(guildChannels, guild, guildConfig);
|
var (embeds, components) = BuildRootMenu(guildChannels, guild, guildConfig);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,7 @@
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
using Remora.Discord.API.Abstractions.Objects;
|
using Remora.Discord.API.Abstractions.Objects;
|
||||||
|
|
@ -24,7 +23,6 @@ using Remora.Discord.API.Objects;
|
||||||
using Remora.Discord.Commands.Attributes;
|
using Remora.Discord.Commands.Attributes;
|
||||||
using Remora.Discord.Commands.Contexts;
|
using Remora.Discord.Commands.Contexts;
|
||||||
using Remora.Discord.Commands.Extensions;
|
using Remora.Discord.Commands.Extensions;
|
||||||
using Remora.Discord.Commands.Feedback.Messages;
|
|
||||||
using Remora.Discord.Commands.Feedback.Services;
|
using Remora.Discord.Commands.Feedback.Services;
|
||||||
using Remora.Discord.Commands.Services;
|
using Remora.Discord.Commands.Services;
|
||||||
using Remora.Discord.Interactivity;
|
using Remora.Discord.Interactivity;
|
||||||
|
|
@ -36,7 +34,7 @@ namespace Catalogger.Backend.Bot.Commands;
|
||||||
|
|
||||||
public class ChannelCommandsComponents(
|
public class ChannelCommandsComponents(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DatabaseContext db,
|
GuildRepository guildRepository,
|
||||||
GuildCache guildCache,
|
GuildCache guildCache,
|
||||||
ChannelCache channelCache,
|
ChannelCache channelCache,
|
||||||
ContextInjectionService contextInjection,
|
ContextInjectionService contextInjection,
|
||||||
|
|
@ -62,7 +60,7 @@ public class ChannelCommandsComponents(
|
||||||
if (!guildCache.TryGet(guildId, out var guild))
|
if (!guildCache.TryGet(guildId, out var guild))
|
||||||
throw new CataloggerError("Guild not in cache");
|
throw new CataloggerError("Guild not in cache");
|
||||||
var guildChannels = channelCache.GuildChannels(guildId).ToList();
|
var guildChannels = channelCache.GuildChannels(guildId).ToList();
|
||||||
var guildConfig = await db.GetGuildAsync(guildId);
|
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||||
|
|
||||||
var result = await dataService.LeaseDataAsync(msg.ID);
|
var result = await dataService.LeaseDataAsync(msg.ID);
|
||||||
await using var lease = result.GetOrThrow();
|
await using var lease = result.GetOrThrow();
|
||||||
|
|
@ -166,8 +164,7 @@ public class ChannelCommandsComponents(
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
|
|
||||||
db.Update(guildConfig);
|
await guildRepository.UpdateChannelConfigAsync(guildId, guildConfig.Channels);
|
||||||
await db.SaveChangesAsync();
|
|
||||||
goto case "return";
|
goto case "return";
|
||||||
case "return":
|
case "return":
|
||||||
var (e, c) = ChannelCommands.BuildRootMenu(guildChannels, guild, guildConfig);
|
var (e, c) = ChannelCommands.BuildRootMenu(guildChannels, guild, guildConfig);
|
||||||
|
|
@ -260,7 +257,7 @@ public class ChannelCommandsComponents(
|
||||||
throw new CataloggerError("No guild ID in context");
|
throw new CataloggerError("No guild ID in context");
|
||||||
if (!guildCache.TryGet(guildId, out var guild))
|
if (!guildCache.TryGet(guildId, out var guild))
|
||||||
throw new CataloggerError("Guild not in cache");
|
throw new CataloggerError("Guild not in cache");
|
||||||
var guildConfig = await db.GetGuildAsync(guildId);
|
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||||
var channelId = channels[0].ID.ToUlong();
|
var channelId = channels[0].ID.ToUlong();
|
||||||
|
|
||||||
var result = await dataService.LeaseDataAsync(msg.ID);
|
var result = await dataService.LeaseDataAsync(msg.ID);
|
||||||
|
|
@ -354,8 +351,7 @@ public class ChannelCommandsComponents(
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
|
|
||||||
db.Update(guildConfig);
|
await guildRepository.UpdateChannelConfigAsync(guildId, guildConfig.Channels);
|
||||||
await db.SaveChangesAsync();
|
|
||||||
|
|
||||||
List<IEmbed> embeds =
|
List<IEmbed> embeds =
|
||||||
[
|
[
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,7 @@
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using Catalogger.Backend.Cache;
|
using Catalogger.Backend.Cache;
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
using Remora.Commands.Attributes;
|
using Remora.Commands.Attributes;
|
||||||
|
|
@ -38,7 +37,7 @@ namespace Catalogger.Backend.Bot.Commands;
|
||||||
[DiscordDefaultMemberPermissions(DiscordPermission.ManageGuild)]
|
[DiscordDefaultMemberPermissions(DiscordPermission.ManageGuild)]
|
||||||
public class IgnoreChannelCommands(
|
public class IgnoreChannelCommands(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DatabaseContext db,
|
GuildRepository guildRepository,
|
||||||
IMemberCache memberCache,
|
IMemberCache memberCache,
|
||||||
GuildCache guildCache,
|
GuildCache guildCache,
|
||||||
ChannelCache channelCache,
|
ChannelCache channelCache,
|
||||||
|
|
@ -66,7 +65,7 @@ public class IgnoreChannelCommands(
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var (_, guildId) = contextInjection.GetUserAndGuild();
|
var (_, guildId) = contextInjection.GetUserAndGuild();
|
||||||
var guildConfig = await db.GetGuildAsync(guildId);
|
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||||
|
|
||||||
if (guildConfig.Channels.IgnoredChannels.Contains(channel.ID.Value))
|
if (guildConfig.Channels.IgnoredChannels.Contains(channel.ID.Value))
|
||||||
return await feedbackService.ReplyAsync(
|
return await feedbackService.ReplyAsync(
|
||||||
|
|
@ -75,8 +74,7 @@ public class IgnoreChannelCommands(
|
||||||
);
|
);
|
||||||
|
|
||||||
guildConfig.Channels.IgnoredChannels.Add(channel.ID.Value);
|
guildConfig.Channels.IgnoredChannels.Add(channel.ID.Value);
|
||||||
db.Update(guildConfig);
|
await guildRepository.UpdateChannelConfigAsync(guildId, guildConfig.Channels);
|
||||||
await db.SaveChangesAsync();
|
|
||||||
|
|
||||||
return await feedbackService.ReplyAsync(
|
return await feedbackService.ReplyAsync(
|
||||||
$"Successfully added {(channel.Type == ChannelType.GuildCategory ? channel.Name : $"<#{channel.ID}>")} to the list of ignored channels."
|
$"Successfully added {(channel.Type == ChannelType.GuildCategory ? channel.Name : $"<#{channel.ID}>")} to the list of ignored channels."
|
||||||
|
|
@ -90,7 +88,7 @@ public class IgnoreChannelCommands(
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var (_, guildId) = contextInjection.GetUserAndGuild();
|
var (_, guildId) = contextInjection.GetUserAndGuild();
|
||||||
var guildConfig = await db.GetGuildAsync(guildId);
|
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||||
|
|
||||||
if (!guildConfig.Channels.IgnoredChannels.Contains(channel.ID.Value))
|
if (!guildConfig.Channels.IgnoredChannels.Contains(channel.ID.Value))
|
||||||
return await feedbackService.ReplyAsync(
|
return await feedbackService.ReplyAsync(
|
||||||
|
|
@ -99,8 +97,7 @@ public class IgnoreChannelCommands(
|
||||||
);
|
);
|
||||||
|
|
||||||
guildConfig.Channels.IgnoredChannels.Remove(channel.ID.Value);
|
guildConfig.Channels.IgnoredChannels.Remove(channel.ID.Value);
|
||||||
db.Update(guildConfig);
|
await guildRepository.UpdateChannelConfigAsync(guildId, guildConfig.Channels);
|
||||||
await db.SaveChangesAsync();
|
|
||||||
|
|
||||||
return await feedbackService.ReplyAsync(
|
return await feedbackService.ReplyAsync(
|
||||||
$"Successfully removed {(channel.Type == ChannelType.GuildCategory ? channel.Name : $"<#{channel.ID}>")} from the list of ignored channels."
|
$"Successfully removed {(channel.Type == ChannelType.GuildCategory ? channel.Name : $"<#{channel.ID}>")} from the list of ignored channels."
|
||||||
|
|
@ -116,7 +113,7 @@ public class IgnoreChannelCommands(
|
||||||
throw new CataloggerError("Guild not in cache");
|
throw new CataloggerError("Guild not in cache");
|
||||||
|
|
||||||
var guildChannels = channelCache.GuildChannels(guildId).ToList();
|
var guildChannels = channelCache.GuildChannels(guildId).ToList();
|
||||||
var guildConfig = await db.GetGuildAsync(guildId);
|
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||||
|
|
||||||
var member = await memberCache.TryGetAsync(guildId, userId);
|
var member = await memberCache.TryGetAsync(guildId, userId);
|
||||||
if (member == null)
|
if (member == null)
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,7 @@
|
||||||
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Remora.Commands.Attributes;
|
using Remora.Commands.Attributes;
|
||||||
using Remora.Commands.Groups;
|
using Remora.Commands.Groups;
|
||||||
|
|
@ -33,7 +32,7 @@ namespace Catalogger.Backend.Bot.Commands;
|
||||||
[Group("key-roles")]
|
[Group("key-roles")]
|
||||||
[DiscordDefaultMemberPermissions(DiscordPermission.ManageGuild)]
|
[DiscordDefaultMemberPermissions(DiscordPermission.ManageGuild)]
|
||||||
public class KeyRoleCommands(
|
public class KeyRoleCommands(
|
||||||
DatabaseContext db,
|
GuildRepository guildRepository,
|
||||||
ContextInjectionService contextInjection,
|
ContextInjectionService contextInjection,
|
||||||
IFeedbackService feedbackService,
|
IFeedbackService feedbackService,
|
||||||
GuildCache guildCache,
|
GuildCache guildCache,
|
||||||
|
|
@ -48,7 +47,7 @@ public class KeyRoleCommands(
|
||||||
if (!guildCache.TryGet(guildId, out var guild))
|
if (!guildCache.TryGet(guildId, out var guild))
|
||||||
throw new CataloggerError("Guild not in cache");
|
throw new CataloggerError("Guild not in cache");
|
||||||
var guildRoles = roleCache.GuildRoles(guildId).ToList();
|
var guildRoles = roleCache.GuildRoles(guildId).ToList();
|
||||||
var guildConfig = await db.GetGuildAsync(guildId);
|
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||||
|
|
||||||
if (guildConfig.KeyRoles.Length == 0)
|
if (guildConfig.KeyRoles.Length == 0)
|
||||||
return await feedbackService.ReplyAsync(
|
return await feedbackService.ReplyAsync(
|
||||||
|
|
@ -85,17 +84,14 @@ public class KeyRoleCommands(
|
||||||
if (role == null)
|
if (role == null)
|
||||||
throw new CataloggerError("Role is not cached");
|
throw new CataloggerError("Role is not cached");
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(guildId);
|
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||||
if (guildConfig.KeyRoles.Any(id => role.ID == id))
|
if (guildConfig.KeyRoles.Any(id => role.ID.Value == id))
|
||||||
return await feedbackService.ReplyAsync(
|
return await feedbackService.ReplyAsync(
|
||||||
$"{role.Name} is already a key role.",
|
$"{role.Name} is already a key role.",
|
||||||
isEphemeral: true
|
isEphemeral: true
|
||||||
);
|
);
|
||||||
|
|
||||||
guildConfig.KeyRoles = guildConfig.KeyRoles.Append(role.ID.Value).ToArray();
|
await guildRepository.AddKeyRoleAsync(guildId, role.ID);
|
||||||
db.Update(guildConfig);
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
|
|
||||||
return await feedbackService.ReplyAsync($"Added {role.Name} to this server's key roles!");
|
return await feedbackService.ReplyAsync($"Added {role.Name} to this server's key roles!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,17 +106,14 @@ public class KeyRoleCommands(
|
||||||
if (role == null)
|
if (role == null)
|
||||||
throw new CataloggerError("Role is not cached");
|
throw new CataloggerError("Role is not cached");
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(guildId);
|
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||||
if (guildConfig.KeyRoles.All(id => role.ID != id))
|
if (guildConfig.KeyRoles.All(id => role.ID != id))
|
||||||
return await feedbackService.ReplyAsync(
|
return await feedbackService.ReplyAsync(
|
||||||
$"{role.Name} is already not a key role.",
|
$"{role.Name} is already not a key role.",
|
||||||
isEphemeral: true
|
isEphemeral: true
|
||||||
);
|
);
|
||||||
|
|
||||||
guildConfig.KeyRoles = guildConfig.KeyRoles.Except([role.ID.Value]).ToArray();
|
await guildRepository.RemoveKeyRoleAsync(guildId, role.ID);
|
||||||
db.Update(guildConfig);
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
|
|
||||||
return await feedbackService.ReplyAsync(
|
return await feedbackService.ReplyAsync(
|
||||||
$"Removed {role.Name} from this server's key roles!"
|
$"Removed {role.Name} from this server's key roles!"
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,7 @@
|
||||||
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Remora.Commands.Attributes;
|
using Remora.Commands.Attributes;
|
||||||
using Remora.Commands.Groups;
|
using Remora.Commands.Groups;
|
||||||
|
|
@ -34,7 +33,7 @@ namespace Catalogger.Backend.Bot.Commands;
|
||||||
[Description("Commands for configuring log redirects.")]
|
[Description("Commands for configuring log redirects.")]
|
||||||
[DiscordDefaultMemberPermissions(DiscordPermission.ManageGuild)]
|
[DiscordDefaultMemberPermissions(DiscordPermission.ManageGuild)]
|
||||||
public class RedirectCommands(
|
public class RedirectCommands(
|
||||||
DatabaseContext db,
|
GuildRepository guildRepository,
|
||||||
GuildCache guildCache,
|
GuildCache guildCache,
|
||||||
ChannelCache channelCache,
|
ChannelCache channelCache,
|
||||||
ContextInjectionService contextInjectionService,
|
ContextInjectionService contextInjectionService,
|
||||||
|
|
@ -60,10 +59,9 @@ public class RedirectCommands(
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var (_, guildId) = contextInjectionService.GetUserAndGuild();
|
var (_, guildId) = contextInjectionService.GetUserAndGuild();
|
||||||
var guildConfig = await db.GetGuildAsync(guildId);
|
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||||
guildConfig.Channels.Redirects[source.ID.Value] = target.ID.Value;
|
guildConfig.Channels.Redirects[source.ID.Value] = target.ID.Value;
|
||||||
db.Update(guildConfig);
|
await guildRepository.UpdateChannelConfigAsync(guildId, guildConfig.Channels);
|
||||||
await db.SaveChangesAsync();
|
|
||||||
|
|
||||||
var output =
|
var output =
|
||||||
$"Success! Edited and deleted messages from {FormatChannel(source)} will now be redirected to <#{target.ID}>.";
|
$"Success! Edited and deleted messages from {FormatChannel(source)} will now be redirected to <#{target.ID}>.";
|
||||||
|
|
@ -100,9 +98,11 @@ public class RedirectCommands(
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var (_, guildId) = contextInjectionService.GetUserAndGuild();
|
var (_, guildId) = contextInjectionService.GetUserAndGuild();
|
||||||
var guildConfig = await db.GetGuildAsync(guildId);
|
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||||
|
|
||||||
var wasSet = guildConfig.Channels.Redirects.Remove(source.ID.Value);
|
var wasSet = guildConfig.Channels.Redirects.Remove(source.ID.Value);
|
||||||
|
await guildRepository.UpdateChannelConfigAsync(guildId, guildConfig.Channels);
|
||||||
|
|
||||||
var output = wasSet
|
var output = wasSet
|
||||||
? $"Removed the redirect for {FormatChannel(source)}! Message logs from"
|
? $"Removed the redirect for {FormatChannel(source)}! Message logs from"
|
||||||
+ $"{(source.Type == ChannelType.GuildCategory ? "that category's channels" : "that channel")}"
|
+ $"{(source.Type == ChannelType.GuildCategory ? "that category's channels" : "that channel")}"
|
||||||
|
|
@ -132,9 +132,6 @@ public class RedirectCommands(
|
||||||
$"\nHowever, all channels in the {parentChannelName} category are being redirected to <#{parentRedirect}>.";
|
$"\nHowever, all channels in the {parentChannelName} category are being redirected to <#{parentRedirect}>.";
|
||||||
}
|
}
|
||||||
|
|
||||||
db.Update(guildConfig);
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
|
|
||||||
return await feedbackService.ReplyAsync(output);
|
return await feedbackService.ReplyAsync(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,7 +143,7 @@ public class RedirectCommands(
|
||||||
if (!guildCache.TryGet(guildId, out var guild))
|
if (!guildCache.TryGet(guildId, out var guild))
|
||||||
throw new CataloggerError("Guild was not cached");
|
throw new CataloggerError("Guild was not cached");
|
||||||
var guildChannels = channelCache.GuildChannels(guildId).ToList();
|
var guildChannels = channelCache.GuildChannels(guildId).ToList();
|
||||||
var guildConfig = await db.GetGuildAsync(guildId);
|
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||||
|
|
||||||
var fields = new List<IEmbedField>();
|
var fields = new List<IEmbedField>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,9 @@
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
using Catalogger.Backend.Cache;
|
using Catalogger.Backend.Cache;
|
||||||
using Catalogger.Backend.Database;
|
|
||||||
using Catalogger.Backend.Database.Dapper;
|
|
||||||
using Catalogger.Backend.Database.Dapper.Repositories;
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Remora.Discord.API.Abstractions.Gateway.Events;
|
using Remora.Discord.API.Abstractions.Gateway.Events;
|
||||||
using Remora.Discord.API.Abstractions.Rest;
|
using Remora.Discord.API.Abstractions.Rest;
|
||||||
using Remora.Discord.Extensions.Embeds;
|
using Remora.Discord.Extensions.Embeds;
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,10 @@
|
||||||
|
|
||||||
using Catalogger.Backend.Cache;
|
using Catalogger.Backend.Cache;
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
|
||||||
using Catalogger.Backend.Database.Dapper.Repositories;
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Remora.Discord.API;
|
using Remora.Discord.API;
|
||||||
using Remora.Discord.API.Abstractions.Gateway.Events;
|
using Remora.Discord.API.Abstractions.Gateway.Events;
|
||||||
using Remora.Discord.API.Abstractions.Objects;
|
using Remora.Discord.API.Abstractions.Objects;
|
||||||
|
|
@ -35,9 +32,9 @@ namespace Catalogger.Backend.Bot.Responders.Members;
|
||||||
|
|
||||||
public class GuildMemberAddResponder(
|
public class GuildMemberAddResponder(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DatabaseContext db,
|
|
||||||
InviteRepository inviteRepository,
|
InviteRepository inviteRepository,
|
||||||
GuildRepository guildRepository,
|
GuildRepository guildRepository,
|
||||||
|
WatchlistRepository watchlistRepository,
|
||||||
IMemberCache memberCache,
|
IMemberCache memberCache,
|
||||||
IInviteCache inviteCache,
|
IInviteCache inviteCache,
|
||||||
UserCache userCache,
|
UserCache userCache,
|
||||||
|
|
@ -159,7 +156,7 @@ public class GuildMemberAddResponder(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var watchlist = await db.GetWatchlistEntryAsync(member.GuildID, user.ID, ct);
|
var watchlist = await watchlistRepository.GetWatchlistEntryAsync(member.GuildID, user.ID);
|
||||||
if (watchlist != null)
|
if (watchlist != null)
|
||||||
{
|
{
|
||||||
var moderator = await userCache.GetUserAsync(
|
var moderator = await userCache.GetUserAsync(
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,7 @@
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
|
||||||
using Catalogger.Backend.Database.Dapper.Repositories;
|
using Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
using Catalogger.Backend.Database.Queries;
|
|
||||||
using Catalogger.Backend.Extensions;
|
using Catalogger.Backend.Extensions;
|
||||||
using Catalogger.Backend.Services;
|
using Catalogger.Backend.Services;
|
||||||
using Remora.Discord.API;
|
using Remora.Discord.API;
|
||||||
|
|
@ -32,7 +30,7 @@ namespace Catalogger.Backend.Bot.Responders.Messages;
|
||||||
|
|
||||||
public class MessageUpdateResponder(
|
public class MessageUpdateResponder(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
DatabaseContext db,
|
GuildRepository guildRepository,
|
||||||
ChannelCache channelCache,
|
ChannelCache channelCache,
|
||||||
UserCache userCache,
|
UserCache userCache,
|
||||||
MessageRepository messageRepository,
|
MessageRepository messageRepository,
|
||||||
|
|
@ -57,7 +55,7 @@ public class MessageUpdateResponder(
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(msg.GuildID.Value, false, ct);
|
var guildConfig = await guildRepository.GetAsync(msg.GuildID);
|
||||||
|
|
||||||
if (await messageRepository.IsMessageIgnoredAsync(msg.ID.Value))
|
if (await messageRepository.IsMessageIgnoredAsync(msg.ID.Value))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ public class DatabaseConnection(Guid id, ILogger logger, NpgsqlConnection inner)
|
||||||
|
|
||||||
DatabasePool.DecrementConnections();
|
DatabasePool.DecrementConnections();
|
||||||
var openFor = DateTimeOffset.UtcNow - _openTime;
|
var openFor = DateTimeOffset.UtcNow - _openTime;
|
||||||
_logger.Debug("Closing connection {ConnId}, open for {OpenFor}", ConnectionId, openFor);
|
_logger.Verbose("Closing connection {ConnId}, open for {OpenFor}", ConnectionId, openFor);
|
||||||
_hasClosed = true;
|
_hasClosed = true;
|
||||||
await inner.CloseAsync();
|
await inner.CloseAsync();
|
||||||
}
|
}
|
||||||
|
|
@ -53,7 +53,7 @@ public class DatabaseConnection(Guid id, ILogger logger, NpgsqlConnection inner)
|
||||||
CancellationToken cancellationToken
|
CancellationToken cancellationToken
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_logger.Debug("Beginning transaction on connection {ConnId}", ConnectionId);
|
_logger.Verbose("Beginning transaction on connection {ConnId}", ConnectionId);
|
||||||
return await inner.BeginTransactionAsync(isolationLevel, cancellationToken);
|
return await inner.BeginTransactionAsync(isolationLevel, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ public class DatabasePool
|
||||||
private Guid LogOpen()
|
private Guid LogOpen()
|
||||||
{
|
{
|
||||||
var connId = Guid.NewGuid();
|
var connId = Guid.NewGuid();
|
||||||
_logger.Debug("Opening database connection {ConnId}", connId);
|
_logger.Verbose("Opening database connection {ConnId}", connId);
|
||||||
IncrementConnections();
|
IncrementConnections();
|
||||||
return connId;
|
return connId;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,24 @@ public class GuildRepository(ILogger logger, DatabaseConnection conn)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
public async Task AddKeyRoleAsync(Snowflake guildId, Snowflake roleId) =>
|
||||||
|
await conn.ExecuteAsync(
|
||||||
|
"update guilds set key_roles = array_append(key_roles, @RoleId) where id = @GuildId",
|
||||||
|
new { GuildId = guildId.Value, RoleId = roleId.Value }
|
||||||
|
);
|
||||||
|
|
||||||
|
public async Task RemoveKeyRoleAsync(Snowflake guildId, Snowflake roleId) =>
|
||||||
|
await conn.ExecuteAsync(
|
||||||
|
"update guilds set key_roles = array_remove(key_roles, @RoleId) where id = @GuildId",
|
||||||
|
new { GuildId = guildId.Value, RoleId = roleId.Value }
|
||||||
|
);
|
||||||
|
|
||||||
|
public async Task UpdateChannelConfigAsync(Snowflake id, Guild.ChannelConfig config) =>
|
||||||
|
await conn.ExecuteAsync(
|
||||||
|
"update guilds set channels = @Channels where id = @Id",
|
||||||
|
new { Id = id, Channels = config }
|
||||||
|
);
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
conn.Dispose();
|
conn.Dispose();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
// Copyright (C) 2021-present sam (starshines.gay)
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published
|
||||||
|
// by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using Catalogger.Backend.Database.Models;
|
||||||
|
using Dapper;
|
||||||
|
using Remora.Rest.Core;
|
||||||
|
|
||||||
|
namespace Catalogger.Backend.Database.Dapper.Repositories;
|
||||||
|
|
||||||
|
public class WatchlistRepository(ILogger logger, DatabaseConnection conn)
|
||||||
|
: IDisposable,
|
||||||
|
IAsyncDisposable
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger = logger.ForContext<WatchlistRepository>();
|
||||||
|
|
||||||
|
public async Task<List<Watchlist>> GetGuildWatchlistAsync(Snowflake guildId) =>
|
||||||
|
(
|
||||||
|
await conn.QueryAsync<Watchlist>(
|
||||||
|
"select * from watchlists where guild_id = @GuildId",
|
||||||
|
new { GuildId = guildId.Value }
|
||||||
|
)
|
||||||
|
).ToList();
|
||||||
|
|
||||||
|
public async Task<Watchlist?> GetWatchlistEntryAsync(Snowflake guildId, Snowflake userId) =>
|
||||||
|
await conn.QueryFirstOrDefaultAsync<Watchlist>(
|
||||||
|
"select * from watchlists where guild_id = @GuildId and user_id = @UserId",
|
||||||
|
new { GuildId = guildId.Value, UserId = userId.Value }
|
||||||
|
);
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
conn.Dispose();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
await conn.DisposeAsync();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -53,14 +53,4 @@ public static class QueryExtensions
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<Watchlist?> GetWatchlistEntryAsync(
|
|
||||||
this DatabaseContext db,
|
|
||||||
Snowflake guildId,
|
|
||||||
Snowflake userId,
|
|
||||||
CancellationToken ct = default
|
|
||||||
) =>
|
|
||||||
await db
|
|
||||||
.Watchlists.AsNoTracking()
|
|
||||||
.FirstOrDefaultAsync(w => w.GuildId == guildId.Value && w.UserId == userId.Value, ct);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,7 @@ public static class StartupExtensions
|
||||||
.AddScoped<MessageRepository>()
|
.AddScoped<MessageRepository>()
|
||||||
.AddScoped<GuildRepository>()
|
.AddScoped<GuildRepository>()
|
||||||
.AddScoped<InviteRepository>()
|
.AddScoped<InviteRepository>()
|
||||||
|
.AddScoped<WatchlistRepository>()
|
||||||
.AddSingleton<GuildCache>()
|
.AddSingleton<GuildCache>()
|
||||||
.AddSingleton<RoleCache>()
|
.AddSingleton<RoleCache>()
|
||||||
.AddSingleton<ChannelCache>()
|
.AddSingleton<ChannelCache>()
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Catalogger.Backend.Cache.InMemoryCache;
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||||||
using Catalogger.Backend.Database;
|
using Catalogger.Backend.Database;
|
||||||
|
using Catalogger.Backend.Database.Dapper;
|
||||||
|
using Dapper;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Prometheus;
|
using Prometheus;
|
||||||
|
|
@ -39,8 +41,9 @@ public class MetricsCollectionService(
|
||||||
|
|
||||||
await using var scope = services.CreateAsyncScope();
|
await using var scope = services.CreateAsyncScope();
|
||||||
await using var db = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
|
await using var db = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
|
||||||
|
await using var conn = scope.ServiceProvider.GetRequiredService<DatabaseConnection>();
|
||||||
|
|
||||||
var messageCount = await db.Messages.CountAsync(ct);
|
var messageCount = await conn.ExecuteScalarAsync<int>("select count(id) from messages");
|
||||||
|
|
||||||
CataloggerMetrics.GuildsCached.Set(guildCache.Size);
|
CataloggerMetrics.GuildsCached.Set(guildCache.Size);
|
||||||
CataloggerMetrics.ChannelsCached.Set(channelCache.Size);
|
CataloggerMetrics.ChannelsCached.Set(channelCache.Size);
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ public class WebhookExecutorService(
|
||||||
public void QueueLog(Guild guildConfig, LogChannelType logChannelType, IEmbed embed)
|
public void QueueLog(Guild guildConfig, LogChannelType logChannelType, IEmbed embed)
|
||||||
{
|
{
|
||||||
var logChannel = GetLogChannel(guildConfig, logChannelType, channelId: null, userId: null);
|
var logChannel = GetLogChannel(guildConfig, logChannelType, channelId: null, userId: null);
|
||||||
|
_logger.Debug("Channel to log {Type} to: {LogChannel}", logChannelType, logChannel);
|
||||||
if (logChannel == null)
|
if (logChannel == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -72,6 +73,7 @@ public class WebhookExecutorService(
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void QueueLog(ulong channelId, IEmbed embed)
|
public void QueueLog(ulong channelId, IEmbed embed)
|
||||||
{
|
{
|
||||||
|
_logger.Debug("Channel to log to: {LogChannel}", channelId);
|
||||||
if (channelId == 0)
|
if (channelId == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -93,8 +93,8 @@ public static class GuildImport
|
||||||
var dbGuild = new Guild
|
var dbGuild = new Guild
|
||||||
{
|
{
|
||||||
Id = guild.Id,
|
Id = guild.Id,
|
||||||
BannedSystems = guild.BannedSystems.ToList(),
|
BannedSystems = guild.BannedSystems,
|
||||||
KeyRoles = guild.KeyRoles.ToList(),
|
KeyRoles = guild.KeyRoles,
|
||||||
Channels = channels,
|
Channels = channels,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue