Compare commits
4 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f3dfc74d6 | |||
| a4a6fb5d31 | |||
| 24f6aee57d | |||
| 8a4e3ff184 |
16 changed files with 216 additions and 146 deletions
|
|
@ -43,6 +43,7 @@ public class ChannelCommands(
|
|||
Config config,
|
||||
GuildRepository guildRepository,
|
||||
GuildCache guildCache,
|
||||
GuildFetchService guildFetchService,
|
||||
ChannelCache channelCache,
|
||||
IMemberCache memberCache,
|
||||
IFeedbackService feedbackService,
|
||||
|
|
@ -68,8 +69,11 @@ public class ChannelCommands(
|
|||
public async Task<IResult> CheckPermissionsAsync()
|
||||
{
|
||||
var (userId, guildId) = contextInjection.GetUserAndGuild();
|
||||
|
||||
if (!guildCache.TryGet(guildId, out var guild))
|
||||
throw new CataloggerError("Guild not in cache");
|
||||
{
|
||||
return CataloggerError.Result($"Guild {guildId} not in cache");
|
||||
}
|
||||
|
||||
var embed = new EmbedBuilder().WithTitle($"Permission check for {guild.Name}");
|
||||
|
||||
|
|
@ -78,8 +82,18 @@ public class ChannelCommands(
|
|||
DiscordSnowflake.New(config.Discord.ApplicationId)
|
||||
);
|
||||
var currentUser = await memberCache.TryGetAsync(guildId, userId);
|
||||
|
||||
if (botUser == null || currentUser == null)
|
||||
throw new CataloggerError("Bot member or invoking member not found in cache");
|
||||
{
|
||||
// If this happens, something has gone wrong when fetching members. Refetch the guild's members.
|
||||
guildFetchService.EnqueueGuild(guildId);
|
||||
_logger.Error(
|
||||
"Either our own user {BotId} or the invoking user {UserId} is not in cache, aborting permission check",
|
||||
config.Discord.ApplicationId,
|
||||
userId
|
||||
);
|
||||
return CataloggerError.Result("Bot member or invoking member not found in cache");
|
||||
}
|
||||
|
||||
// We don't want to check categories or threads
|
||||
var guildChannels = channelCache
|
||||
|
|
@ -204,7 +218,7 @@ public class ChannelCommands(
|
|||
{
|
||||
var (userId, guildId) = contextInjection.GetUserAndGuild();
|
||||
if (!guildCache.TryGet(guildId, out var guild))
|
||||
throw new CataloggerError("Guild not in cache");
|
||||
return CataloggerError.Result("Guild not in cache");
|
||||
var guildChannels = channelCache.GuildChannels(guildId).ToList();
|
||||
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||
|
||||
|
|
|
|||
|
|
@ -50,15 +50,15 @@ public class ChannelCommandsComponents(
|
|||
public async Task<Result> OnMenuSelectionAsync(IReadOnlyList<string> values)
|
||||
{
|
||||
if (contextInjection.Context is not IInteractionCommandContext ctx)
|
||||
throw new CataloggerError("No context");
|
||||
return CataloggerError.Result("No context");
|
||||
if (!ctx.TryGetUserID(out var userId))
|
||||
throw new CataloggerError("No user ID in context");
|
||||
return CataloggerError.Result("No user ID in context");
|
||||
if (!ctx.Interaction.Message.TryGet(out var msg))
|
||||
throw new CataloggerError("No message ID in context");
|
||||
return CataloggerError.Result("No message ID in context");
|
||||
if (!ctx.TryGetGuildID(out var guildId))
|
||||
throw new CataloggerError("No guild ID in context");
|
||||
return CataloggerError.Result("No guild ID in context");
|
||||
if (!guildCache.TryGet(guildId, out var guild))
|
||||
throw new CataloggerError("Guild not in cache");
|
||||
return CataloggerError.Result("Guild not in cache");
|
||||
var guildChannels = channelCache.GuildChannels(guildId).ToList();
|
||||
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ public class ChannelCommandsComponents(
|
|||
var state = values[0];
|
||||
|
||||
if (!Enum.TryParse<LogChannelType>(state, out var logChannelType))
|
||||
throw new CataloggerError($"Invalid config-channels state {state}");
|
||||
return CataloggerError.Result($"Invalid config-channels state {state}");
|
||||
|
||||
var channelId = WebhookExecutorService.GetDefaultLogChannel(guildConfig, logChannelType);
|
||||
string? channelMention;
|
||||
|
|
@ -147,15 +147,15 @@ public class ChannelCommandsComponents(
|
|||
public async Task<Result> OnButtonPressedAsync(string state)
|
||||
{
|
||||
if (contextInjection.Context is not IInteractionCommandContext ctx)
|
||||
throw new CataloggerError("No context");
|
||||
return CataloggerError.Result("No context");
|
||||
if (!ctx.TryGetUserID(out var userId))
|
||||
throw new CataloggerError("No user ID in context");
|
||||
return CataloggerError.Result("No user ID in context");
|
||||
if (!ctx.Interaction.Message.TryGet(out var msg))
|
||||
throw new CataloggerError("No message ID in context");
|
||||
return CataloggerError.Result("No message ID in context");
|
||||
if (!ctx.TryGetGuildID(out var guildId))
|
||||
throw new CataloggerError("No guild ID in context");
|
||||
return CataloggerError.Result("No guild ID in context");
|
||||
if (!guildCache.TryGet(guildId, out var guild))
|
||||
throw new CataloggerError("Guild not in cache");
|
||||
return CataloggerError.Result("Guild not in cache");
|
||||
var guildChannels = channelCache.GuildChannels(guildId).ToList();
|
||||
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||
|
||||
|
|
@ -179,9 +179,9 @@ public class ChannelCommandsComponents(
|
|||
);
|
||||
case "reset":
|
||||
if (lease.Data.CurrentPage == null)
|
||||
throw new CataloggerError("CurrentPage was null in reset button callback");
|
||||
return CataloggerError.Result("CurrentPage was null in reset button callback");
|
||||
if (!Enum.TryParse<LogChannelType>(lease.Data.CurrentPage, out var channelType))
|
||||
throw new CataloggerError(
|
||||
return CataloggerError.Result(
|
||||
$"Invalid config-channels CurrentPage: '{lease.Data.CurrentPage}'"
|
||||
);
|
||||
|
||||
|
|
@ -281,15 +281,15 @@ public class ChannelCommandsComponents(
|
|||
public async Task<Result> OnMenuSelectionAsync(IReadOnlyList<IPartialChannel> channels)
|
||||
{
|
||||
if (contextInjection.Context is not IInteractionCommandContext ctx)
|
||||
throw new CataloggerError("No context");
|
||||
return CataloggerError.Result("No context");
|
||||
if (!ctx.TryGetUserID(out var userId))
|
||||
throw new CataloggerError("No user ID in context");
|
||||
return CataloggerError.Result("No user ID in context");
|
||||
if (!ctx.Interaction.Message.TryGet(out var msg))
|
||||
throw new CataloggerError("No message ID in context");
|
||||
return CataloggerError.Result("No message ID in context");
|
||||
if (!ctx.TryGetGuildID(out var guildId))
|
||||
throw new CataloggerError("No guild ID in context");
|
||||
return CataloggerError.Result("No guild ID in context");
|
||||
if (!guildCache.TryGet(guildId, out var guild))
|
||||
throw new CataloggerError("Guild not in cache");
|
||||
return CataloggerError.Result("Guild not in cache");
|
||||
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||
var channelId = channels[0].ID.ToUlong();
|
||||
|
||||
|
|
@ -305,7 +305,7 @@ public class ChannelCommandsComponents(
|
|||
}
|
||||
|
||||
if (!Enum.TryParse<LogChannelType>(lease.Data.CurrentPage, out var channelType))
|
||||
throw new CataloggerError(
|
||||
return CataloggerError.Result(
|
||||
$"Invalid config-channels CurrentPage '{lease.Data.CurrentPage}'"
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ public class IgnoreEntitiesCommands : CommandGroup
|
|||
{
|
||||
var (_, guildId) = contextInjection.GetUserAndGuild();
|
||||
if (!guildCache.TryGet(guildId, out var guild))
|
||||
throw new CataloggerError("Guild not in cache");
|
||||
return CataloggerError.Result("Guild not in cache");
|
||||
|
||||
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||
|
||||
|
|
@ -201,14 +201,14 @@ public class IgnoreEntitiesCommands : CommandGroup
|
|||
{
|
||||
var (userId, guildId) = contextInjection.GetUserAndGuild();
|
||||
if (!guildCache.TryGet(guildId, out var guild))
|
||||
throw new CataloggerError("Guild not in cache");
|
||||
return CataloggerError.Result("Guild not in cache");
|
||||
|
||||
var guildChannels = channelCache.GuildChannels(guildId).ToList();
|
||||
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||
|
||||
var member = await memberCache.TryGetAsync(guildId, userId);
|
||||
if (member == null)
|
||||
throw new CataloggerError("Executing member not found");
|
||||
return CataloggerError.Result("Executing member not found");
|
||||
|
||||
var ignoredChannels = guildConfig
|
||||
.IgnoredChannels.Select(id =>
|
||||
|
|
|
|||
|
|
@ -110,14 +110,14 @@ public partial class IgnoreMessageCommands : CommandGroup
|
|||
{
|
||||
var (userId, guildId) = contextInjection.GetUserAndGuild();
|
||||
if (!guildCache.TryGet(guildId, out var guild))
|
||||
throw new CataloggerError("Guild not in cache");
|
||||
return CataloggerError.Result("Guild not in cache");
|
||||
|
||||
var guildChannels = channelCache.GuildChannels(guildId).ToList();
|
||||
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||
|
||||
var member = await memberCache.TryGetAsync(guildId, userId);
|
||||
if (member == null)
|
||||
throw new CataloggerError("Executing member not found");
|
||||
return CataloggerError.Result("Executing member not found");
|
||||
|
||||
var ignoredChannels = guildConfig
|
||||
.Messages.IgnoredChannels.Select(id =>
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ public partial class IgnoreMessageCommands
|
|||
{
|
||||
var (_, guildId) = contextInjection.GetUserAndGuild();
|
||||
if (!guildCache.TryGet(guildId, out var guild))
|
||||
throw new CataloggerError("Guild not in cache");
|
||||
return CataloggerError.Result("Guild not in cache");
|
||||
|
||||
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ public partial class IgnoreMessageCommands
|
|||
{
|
||||
var (userId, guildId) = contextInjection.GetUserAndGuild();
|
||||
if (!guildCache.TryGet(guildId, out var guild))
|
||||
throw new CataloggerError("Guild was not cached");
|
||||
return CataloggerError.Result("Guild not in cache");
|
||||
|
||||
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ public class InviteCommands(
|
|||
var (userId, guildId) = contextInjection.GetUserAndGuild();
|
||||
var guildInvites = await guildApi.GetGuildInvitesAsync(guildId).GetOrThrow();
|
||||
if (!guildCache.TryGet(guildId, out var guild))
|
||||
throw new CataloggerError("Guild not in cache");
|
||||
return CataloggerError.Result("Guild not in cache");
|
||||
|
||||
var dbInvites = await inviteRepository.GetGuildInvitesAsync(guildId);
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ public class KeyRoleCommands(
|
|||
{
|
||||
var (_, guildId) = contextInjection.GetUserAndGuild();
|
||||
if (!guildCache.TryGet(guildId, out var guild))
|
||||
throw new CataloggerError("Guild not in cache");
|
||||
return CataloggerError.Result("Guild not in cache");
|
||||
var guildRoles = roleCache.GuildRoles(guildId).ToList();
|
||||
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ public class KeyRoleCommands(
|
|||
var (_, guildId) = contextInjection.GetUserAndGuild();
|
||||
var role = roleCache.GuildRoles(guildId).FirstOrDefault(r => r.ID == roleId);
|
||||
if (role == null)
|
||||
throw new CataloggerError("Role is not cached");
|
||||
return CataloggerError.Result("Role is not cached");
|
||||
|
||||
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||
if (guildConfig.KeyRoles.Any(id => role.ID.Value == id))
|
||||
|
|
@ -111,7 +111,7 @@ public class KeyRoleCommands(
|
|||
var (_, guildId) = contextInjection.GetUserAndGuild();
|
||||
var role = roleCache.GuildRoles(guildId).FirstOrDefault(r => r.ID == roleId);
|
||||
if (role == null)
|
||||
throw new CataloggerError("Role is not cached");
|
||||
return CataloggerError.Result("Role is not cached");
|
||||
|
||||
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||
if (guildConfig.KeyRoles.All(id => role.ID != id))
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ public class MetaCommands(
|
|||
await channelApi.EditMessageAsync(msg.ChannelID, msg.ID, content: "", embeds: embeds);
|
||||
}
|
||||
|
||||
// TODO: add more checks around response format, configurable prometheus endpoint
|
||||
// TODO: add more checks around response format
|
||||
private async Task<double?> MessagesRate()
|
||||
{
|
||||
if (!config.Logging.EnableMetrics)
|
||||
|
|
@ -227,7 +227,8 @@ public class MetaCommands(
|
|||
try
|
||||
{
|
||||
var query = HttpUtility.UrlEncode("increase(catalogger_received_messages[5m])");
|
||||
var resp = await _client.GetAsync($"http://localhost:9090/api/v1/query?query={query}");
|
||||
var prometheusUrl = config.Logging.PrometheusUrl ?? "http://localhost:9090";
|
||||
var resp = await _client.GetAsync($"{prometheusUrl}/api/v1/query?query={query}");
|
||||
resp.EnsureSuccessStatusCode();
|
||||
|
||||
var data = await resp.Content.ReadFromJsonAsync<PrometheusResponse>();
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ public class RedirectCommands(
|
|||
{
|
||||
var (userId, guildId) = contextInjectionService.GetUserAndGuild();
|
||||
if (!guildCache.TryGet(guildId, out var guild))
|
||||
throw new CataloggerError("Guild was not cached");
|
||||
return CataloggerError.Result("Guild not in cache");
|
||||
var guildChannels = channelCache.GuildChannels(guildId).ToList();
|
||||
var guildConfig = await guildRepository.GetAsync(guildId);
|
||||
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ public class WatchlistCommands(
|
|||
{
|
||||
var (userId, guildId) = contextInjectionService.GetUserAndGuild();
|
||||
if (!guildCache.TryGet(guildId, out var guild))
|
||||
throw new CataloggerError("Guild was not cached");
|
||||
return CataloggerError.Result("Guild not in cache");
|
||||
|
||||
var watchlist = await watchlistRepository.GetGuildWatchlistAsync(guildId);
|
||||
if (watchlist.Count == 0)
|
||||
|
|
|
|||
|
|
@ -19,17 +19,21 @@ using Remora.Commands.Services;
|
|||
using Remora.Commands.Tokenization;
|
||||
using Remora.Commands.Trees;
|
||||
using Remora.Discord.API.Abstractions.Gateway.Events;
|
||||
using Remora.Discord.API.Abstractions.Objects;
|
||||
using Remora.Discord.API.Abstractions.Rest;
|
||||
using Remora.Discord.API.Objects;
|
||||
using Remora.Discord.Commands.Responders;
|
||||
using Remora.Discord.Commands.Services;
|
||||
using Remora.Discord.Gateway.Responders;
|
||||
using Remora.Rest.Core;
|
||||
using Remora.Results;
|
||||
using Serilog.Context;
|
||||
|
||||
namespace Catalogger.Backend.Bot.Responders;
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for Remora.Discord's default interaction responder, that ignores all events if test mode is enabled.
|
||||
/// Wrapper for Remora.Discord's default interaction responder, that ignores all events if test mode is enabled,
|
||||
/// and handles <see cref="CataloggerError" /> results returned by commands.
|
||||
/// </summary>
|
||||
public class CustomInteractionResponder(
|
||||
Config config,
|
||||
|
|
@ -87,6 +91,38 @@ public class CustomInteractionResponder(
|
|||
true
|
||||
);
|
||||
|
||||
return await _inner.RespondAsync(evt, ct);
|
||||
var result = await _inner.RespondAsync(evt, ct);
|
||||
if (result.Error is not CataloggerError cataloggerError)
|
||||
return result;
|
||||
|
||||
return await interactionAPI.CreateInteractionResponseAsync(
|
||||
evt.ID,
|
||||
evt.Token,
|
||||
new InteractionResponse(
|
||||
Type: InteractionCallbackType.ChannelMessageWithSource,
|
||||
Data: new Optional<OneOf.OneOf<
|
||||
IInteractionMessageCallbackData,
|
||||
IInteractionAutocompleteCallbackData,
|
||||
IInteractionModalCallbackData
|
||||
>>(
|
||||
new InteractionMessageCallbackData(
|
||||
Embeds: new Optional<IReadOnlyList<IEmbed>>(
|
||||
[
|
||||
new Embed(
|
||||
Colour: DiscordUtils.Red,
|
||||
Title: "Something went wrong",
|
||||
Description: $"""
|
||||
Something went wrong while running this command.
|
||||
> {cataloggerError.Message}
|
||||
Please try again later.
|
||||
"""
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
ct: ct
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,13 @@
|
|||
// 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 Remora.Results;
|
||||
using RemoraResult = Remora.Results.Result;
|
||||
|
||||
namespace Catalogger.Backend;
|
||||
|
||||
public class CataloggerError(string message) : Exception(message) { }
|
||||
public class CataloggerError(string message) : Exception(message), IResultError
|
||||
{
|
||||
public static RemoraResult Result(string message) =>
|
||||
RemoraResult.FromError(new CataloggerError(message));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ public class Config
|
|||
public bool EnableMetrics { get; init; } = true;
|
||||
|
||||
public string? SeqLogUrl { get; init; }
|
||||
public string? PrometheusUrl { get; init; }
|
||||
}
|
||||
|
||||
public class DatabaseConfig
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ public class WebhookExecutorService(
|
|||
private readonly ILogger _logger = logger.ForContext<WebhookExecutorService>();
|
||||
private readonly Snowflake _applicationId = DiscordSnowflake.New(config.Discord.ApplicationId);
|
||||
private readonly ConcurrentDictionary<ulong, ConcurrentQueue<IEmbed>> _cache = new();
|
||||
private readonly ConcurrentDictionary<ulong, object> _locks = new();
|
||||
private readonly ConcurrentDictionary<ulong, Lock> _locks = new();
|
||||
private readonly ConcurrentDictionary<ulong, Timer> _timers = new();
|
||||
private IUser? _selfUser;
|
||||
|
||||
|
|
@ -189,7 +189,7 @@ public class WebhookExecutorService(
|
|||
private List<IEmbed> TakeFromQueue(ulong channelId)
|
||||
{
|
||||
var queue = _cache.GetOrAdd(channelId, []);
|
||||
var channelLock = _locks.GetOrAdd(channelId, channelId);
|
||||
var channelLock = _locks.GetOrAdd(channelId, new Lock());
|
||||
lock (channelLock)
|
||||
{
|
||||
var totalContentLength = 0;
|
||||
|
|
@ -293,10 +293,10 @@ public class WebhookExecutorService(
|
|||
roleIds != null && logChannelType is LogChannelType.GuildMemberUpdate;
|
||||
|
||||
if (isMessageLog)
|
||||
return GetMessageLogChannel(guild, logChannelType, channelId, userId);
|
||||
return GetLogChannelForMessageEvent(guild, logChannelType, channelId, userId);
|
||||
|
||||
if (isChannelLog)
|
||||
return GetChannelLogChannel(guild, logChannelType, channelId!.Value);
|
||||
return GetLogChannelForChannelEvent(guild, logChannelType, channelId!.Value);
|
||||
|
||||
if (isRoleLog && guild.IgnoredRoles.Contains(roleId!.Value.Value))
|
||||
return null;
|
||||
|
|
@ -305,77 +305,11 @@ public class WebhookExecutorService(
|
|||
if (isMemberRoleUpdateLog && roleIds!.All(r => guild.IgnoredRoles.Contains(r.Value)))
|
||||
return null;
|
||||
|
||||
// If nothing is ignored, return the correct log channel!
|
||||
// If nothing is ignored, and this isn't a message or channel event, return the default log channel.
|
||||
return GetDefaultLogChannel(guild, logChannelType);
|
||||
}
|
||||
|
||||
private ulong? GetChannelLogChannel(
|
||||
Guild guild,
|
||||
LogChannelType logChannelType,
|
||||
Snowflake channelId
|
||||
)
|
||||
{
|
||||
_logger.Verbose(
|
||||
"Getting log channel for event {Event} in guild {GuildId} and channel {ChannelId}",
|
||||
logChannelType,
|
||||
guild.Id,
|
||||
channelId
|
||||
);
|
||||
|
||||
if (!channelCache.TryGet(channelId, out var channel))
|
||||
{
|
||||
_logger.Verbose(
|
||||
"Channel with ID {ChannelId} is not cached, returning default log channel",
|
||||
channelId
|
||||
);
|
||||
return GetDefaultLogChannel(guild, logChannelType);
|
||||
}
|
||||
|
||||
Snowflake? categoryId;
|
||||
if (
|
||||
channel.Type
|
||||
is ChannelType.AnnouncementThread
|
||||
or ChannelType.PrivateThread
|
||||
or ChannelType.PublicThread
|
||||
)
|
||||
{
|
||||
// parent_id should always have a value for threads
|
||||
channelId = channel.ParentID.Value!.Value;
|
||||
if (!channelCache.TryGet(channelId, out var parentChannel))
|
||||
{
|
||||
_logger.Verbose(
|
||||
"Parent channel for thread {ChannelId} is not in cache, returning the default log channel",
|
||||
channelId
|
||||
);
|
||||
return GetDefaultLogChannel(guild, logChannelType);
|
||||
}
|
||||
categoryId = parentChannel.ParentID.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
channelId = channel.ID;
|
||||
categoryId = channel.ParentID.Value;
|
||||
}
|
||||
|
||||
// Check if the channel or its category is ignored
|
||||
if (
|
||||
guild.IgnoredChannels.Contains(channelId.Value)
|
||||
|| (categoryId != null && guild.IgnoredChannels.Contains(categoryId.Value.Value))
|
||||
)
|
||||
{
|
||||
_logger.Verbose(
|
||||
"Channel {ChannelId} or its parent {CategoryId} is ignored",
|
||||
channelId,
|
||||
categoryId
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
_logger.Verbose("Returning default log channel for {EventType}", logChannelType);
|
||||
return GetDefaultLogChannel(guild, logChannelType);
|
||||
}
|
||||
|
||||
private ulong? GetMessageLogChannel(
|
||||
private ulong? GetLogChannelForMessageEvent(
|
||||
Guild guild,
|
||||
LogChannelType logChannelType,
|
||||
Snowflake? channelId = null,
|
||||
|
|
@ -415,41 +349,24 @@ public class WebhookExecutorService(
|
|||
return GetDefaultLogChannel(guild, logChannelType);
|
||||
}
|
||||
|
||||
Snowflake? categoryId;
|
||||
if (
|
||||
channel.Type
|
||||
is ChannelType.AnnouncementThread
|
||||
or ChannelType.PrivateThread
|
||||
or ChannelType.PublicThread
|
||||
)
|
||||
{
|
||||
// parent_id should always have a value for threads
|
||||
channelId = channel.ParentID.Value!.Value;
|
||||
if (!channelCache.TryGet(channelId.Value, out var parentChannel))
|
||||
if (!GetChannelAndParentId(channel, out var actualChannelId, out var categoryId))
|
||||
{
|
||||
_logger.Verbose(
|
||||
"Parent channel for thread {ChannelId} is not in cache, returning the default log channel",
|
||||
"Could not get root channel and category ID for channel {ChannelId}, returning default log channel",
|
||||
channelId
|
||||
);
|
||||
return GetDefaultLogChannel(guild, logChannelType);
|
||||
}
|
||||
categoryId = parentChannel.ParentID.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
channelId = channel.ID;
|
||||
categoryId = channel.ParentID.Value;
|
||||
}
|
||||
|
||||
// Check if the channel or its category is ignored
|
||||
if (
|
||||
guild.Messages.IgnoredChannels.Contains(channelId.Value.Value)
|
||||
guild.Messages.IgnoredChannels.Contains(actualChannelId.Value)
|
||||
|| categoryId != null && guild.Messages.IgnoredChannels.Contains(categoryId.Value.Value)
|
||||
)
|
||||
{
|
||||
_logger.Verbose(
|
||||
"Channel {ChannelId} or its parent {CategoryId} is ignored",
|
||||
channelId,
|
||||
actualChannelId,
|
||||
categoryId
|
||||
);
|
||||
return null;
|
||||
|
|
@ -459,8 +376,10 @@ public class WebhookExecutorService(
|
|||
{
|
||||
// Check the channel-local and category-local ignored users
|
||||
var channelIgnoredUsers =
|
||||
guild.Messages.IgnoredUsersPerChannel.GetValueOrDefault(channelId.Value.Value)
|
||||
guild.Messages.IgnoredUsersPerChannel.GetValueOrDefault(actualChannelId.Value)
|
||||
?? [];
|
||||
|
||||
// Obviously, we can only check for category-level ignored users if we actually got a category ID.
|
||||
var categoryIgnoredUsers =
|
||||
(
|
||||
categoryId != null
|
||||
|
|
@ -469,6 +388,8 @@ public class WebhookExecutorService(
|
|||
)
|
||||
: []
|
||||
) ?? [];
|
||||
|
||||
// Combine the ignored users in the channel and category, then check if the user is in there.
|
||||
if (channelIgnoredUsers.Concat(categoryIgnoredUsers).Contains(userId.Value))
|
||||
{
|
||||
_logger.Verbose(
|
||||
|
|
@ -482,7 +403,7 @@ public class WebhookExecutorService(
|
|||
}
|
||||
|
||||
// These three events can be redirected to other channels. Redirects can be on a channel or category level.
|
||||
// The events are only redirected if they're supposed to be logged in the first place.
|
||||
// The events are only redirected if they're supposed to be logged in the first place (i.e. GetDefaultLogChannel doesn't return 0)
|
||||
if (GetDefaultLogChannel(guild, logChannelType) == 0)
|
||||
{
|
||||
_logger.Verbose(
|
||||
|
|
@ -492,21 +413,21 @@ public class WebhookExecutorService(
|
|||
return null;
|
||||
}
|
||||
|
||||
var categoryRedirect =
|
||||
categoryId != null
|
||||
? guild.Channels.Redirects.GetValueOrDefault(categoryId.Value.Value)
|
||||
: 0;
|
||||
|
||||
if (guild.Channels.Redirects.TryGetValue(channelId.Value.Value, out var channelRedirect))
|
||||
if (guild.Channels.Redirects.TryGetValue(actualChannelId.Value, out var channelRedirect))
|
||||
{
|
||||
_logger.Verbose(
|
||||
"Messages from channel {ChannelId} should be redirected to {RedirectId}",
|
||||
channelId,
|
||||
actualChannelId,
|
||||
channelRedirect
|
||||
);
|
||||
return channelRedirect;
|
||||
}
|
||||
|
||||
var categoryRedirect =
|
||||
categoryId != null
|
||||
? guild.Channels.Redirects.GetValueOrDefault(categoryId.Value.Value)
|
||||
: 0;
|
||||
|
||||
if (categoryRedirect != 0)
|
||||
{
|
||||
_logger.Verbose(
|
||||
|
|
@ -514,6 +435,7 @@ public class WebhookExecutorService(
|
|||
categoryId,
|
||||
categoryRedirect
|
||||
);
|
||||
return categoryRedirect;
|
||||
}
|
||||
|
||||
_logger.Verbose(
|
||||
|
|
@ -523,6 +445,92 @@ public class WebhookExecutorService(
|
|||
return GetDefaultLogChannel(guild, logChannelType);
|
||||
}
|
||||
|
||||
private ulong? GetLogChannelForChannelEvent(
|
||||
Guild guild,
|
||||
LogChannelType logChannelType,
|
||||
Snowflake channelId
|
||||
)
|
||||
{
|
||||
_logger.Verbose(
|
||||
"Getting log channel for event {Event} in guild {GuildId} and channel {ChannelId}",
|
||||
logChannelType,
|
||||
guild.Id,
|
||||
channelId
|
||||
);
|
||||
|
||||
if (!channelCache.TryGet(channelId, out var channel))
|
||||
{
|
||||
_logger.Verbose(
|
||||
"Channel with ID {ChannelId} is not cached, returning default log channel",
|
||||
channelId
|
||||
);
|
||||
return GetDefaultLogChannel(guild, logChannelType);
|
||||
}
|
||||
|
||||
if (!GetChannelAndParentId(channel, out channelId, out var categoryId))
|
||||
{
|
||||
_logger.Verbose(
|
||||
"Could not get root channel and category ID for channel {ChannelId}, returning default log channel",
|
||||
channelId
|
||||
);
|
||||
return GetDefaultLogChannel(guild, logChannelType);
|
||||
}
|
||||
|
||||
// Check if the channel or its category is ignored
|
||||
if (
|
||||
guild.IgnoredChannels.Contains(channelId.Value)
|
||||
|| (categoryId != null && guild.IgnoredChannels.Contains(categoryId.Value.Value))
|
||||
)
|
||||
{
|
||||
_logger.Verbose(
|
||||
"Channel {ChannelId} or its parent {CategoryId} is ignored",
|
||||
channelId,
|
||||
categoryId
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
_logger.Verbose("Returning default log channel for {EventType}", logChannelType);
|
||||
return GetDefaultLogChannel(guild, logChannelType);
|
||||
}
|
||||
|
||||
private bool GetChannelAndParentId(
|
||||
IChannel channel,
|
||||
out Snowflake channelId,
|
||||
out Snowflake? categoryId
|
||||
)
|
||||
{
|
||||
if (
|
||||
channel.Type
|
||||
is ChannelType.AnnouncementThread
|
||||
or ChannelType.PrivateThread
|
||||
or ChannelType.PublicThread
|
||||
)
|
||||
{
|
||||
// parent_id should always have a value for threads
|
||||
channelId = channel.ParentID.Value!.Value;
|
||||
if (!channelCache.TryGet(channelId, out var parentChannel))
|
||||
{
|
||||
_logger.Verbose(
|
||||
"Parent channel for thread {ChannelId} is not in cache, returning the default log channel",
|
||||
channelId
|
||||
);
|
||||
|
||||
channelId = Snowflake.CreateTimestampSnowflake();
|
||||
categoryId = null;
|
||||
return false;
|
||||
}
|
||||
categoryId = parentChannel.ParentID.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
channelId = channel.ID;
|
||||
categoryId = channel.ParentID.Value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static ulong GetDefaultLogChannel(Guild guild, LogChannelType logChannelType) =>
|
||||
logChannelType switch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ LogQueries = false
|
|||
SeqLogUrl = http://localhost:5341
|
||||
# Whether to enable Prometheus metrics. If disabled, Catalogger will update metrics manually every so often.
|
||||
EnableMetrics = false
|
||||
# The URL for the Prometheus server. Used for message rate if metrics are enabled.
|
||||
# Defaults to http://localhost:9090, should be changed if Prometheus is on another server.
|
||||
PrometheusUrl = http://localhost:9090
|
||||
|
||||
[Database]
|
||||
Url = Host=localhost;Database=postgres;Username=postgres;Password=postgres
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue