diff --git a/Myriad/Builders/EmbedBuilder.cs b/Myriad/Builders/EmbedBuilder.cs index ecfc3524..00e6c3de 100644 --- a/Myriad/Builders/EmbedBuilder.cs +++ b/Myriad/Builders/EmbedBuilder.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Myriad.Types; @@ -11,66 +11,71 @@ namespace Myriad.Builders public EmbedBuilder Title(string? title) { - _embed = _embed with {Title = title}; + _embed = _embed with { Title = title }; return this; } public EmbedBuilder Description(string? description) { - _embed = _embed with { Description = description}; + _embed = _embed with { Description = description }; return this; } public EmbedBuilder Url(string? url) { - _embed = _embed with {Url = url}; + _embed = _embed with { Url = url }; return this; } public EmbedBuilder Color(uint? color) { - _embed = _embed with {Color = color}; + _embed = _embed with { Color = color }; return this; } public EmbedBuilder Footer(Embed.EmbedFooter? footer) { - _embed = _embed with { + _embed = _embed with + { Footer = footer - }; + }; return this; } public EmbedBuilder Image(Embed.EmbedImage? image) { - _embed = _embed with { + _embed = _embed with + { Image = image - }; + }; return this; } public EmbedBuilder Thumbnail(Embed.EmbedThumbnail? thumbnail) { - _embed = _embed with { + _embed = _embed with + { Thumbnail = thumbnail - }; + }; return this; } public EmbedBuilder Author(Embed.EmbedAuthor? author) { - _embed = _embed with { + _embed = _embed with + { Author = author - }; + }; return this; } public EmbedBuilder Timestamp(string? timestamp) { - _embed = _embed with { + _embed = _embed with + { Timestamp = timestamp - }; + }; return this; } diff --git a/Myriad/Cache/DiscordCacheExtensions.cs b/Myriad/Cache/DiscordCacheExtensions.cs index 91c9f268..6b864b27 100644 --- a/Myriad/Cache/DiscordCacheExtensions.cs +++ b/Myriad/Cache/DiscordCacheExtensions.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Myriad.Gateway; @@ -69,9 +69,9 @@ namespace Myriad.Cache private static async ValueTask SaveMessageCreate(this IDiscordCache cache, MessageCreateEvent evt) { await cache.TrySaveDmChannelStub(evt.GuildId, evt.ChannelId); - + await cache.SaveUser(evt.Author); - foreach (var mention in evt.Mentions) + foreach (var mention in evt.Mentions) await cache.SaveUser(mention); } @@ -84,7 +84,7 @@ namespace Myriad.Cache private static async ValueTask SaveThreadListSync(this IDiscordCache cache, ThreadListSyncEvent evt) { - foreach (var thread in evt.Threads) + foreach (var thread in evt.Threads) await cache.SaveChannel(thread); } } diff --git a/Myriad/Cache/IDiscordCache.cs b/Myriad/Cache/IDiscordCache.cs index 34207d74..5ba90e9b 100644 --- a/Myriad/Cache/IDiscordCache.cs +++ b/Myriad/Cache/IDiscordCache.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; using Myriad.Types; diff --git a/Myriad/Cache/MemoryDiscordCache.cs b/Myriad/Cache/MemoryDiscordCache.cs index 3174870b..4369b72c 100644 --- a/Myriad/Cache/MemoryDiscordCache.cs +++ b/Myriad/Cache/MemoryDiscordCache.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -15,7 +15,7 @@ namespace Myriad.Cache private readonly ConcurrentDictionary _guilds = new(); private readonly ConcurrentDictionary _roles = new(); private readonly ConcurrentDictionary _users = new(); - + public ValueTask SaveGuild(Guild guild) { SaveGuildRaw(guild); @@ -61,18 +61,20 @@ namespace Myriad.Cache var found = false; for (var i = 0; i < guild.Guild.Roles.Length; i++) { - if (guild.Guild.Roles[i].Id != role.Id) + if (guild.Guild.Roles[i].Id != role.Id) continue; - + guild.Guild.Roles[i] = role; found = true; } if (!found) { - _guilds[guildId] = guild with { - Guild = guild.Guild with { - Roles = guild.Guild.Roles.Concat(new[] { role}).ToArray() + _guilds[guildId] = guild with + { + Guild = guild.Guild with + { + Roles = guild.Guild.Roles.Concat(new[] { role }).ToArray() } }; } @@ -87,7 +89,7 @@ namespace Myriad.Cache // We may get a message create before channel create and we want to have it saved _channels.GetOrAdd(channelId, id => new Channel { - Id = id, + Id = id, Type = Channel.ChannelType.Dm }); return default; @@ -134,7 +136,7 @@ namespace Myriad.Cache return false; } - public bool TryGetChannel(ulong channelId, out Channel channel) => + public bool TryGetChannel(ulong channelId, out Channel channel) => _channels.TryGetValue(channelId, out channel!); public bool TryGetDmChannel(ulong userId, out Channel channel) @@ -143,7 +145,7 @@ namespace Myriad.Cache if (!_dmChannels.TryGetValue(userId, out var channelId)) return false; return TryGetChannel(channelId, out channel); - } + } public bool TryGetUser(ulong userId, out User user) => _users.TryGetValue(userId, out user!); diff --git a/Myriad/Extensions/CacheExtensions.cs b/Myriad/Extensions/CacheExtensions.cs index 5be4df99..c72da3fb 100644 --- a/Myriad/Extensions/CacheExtensions.cs +++ b/Myriad/Extensions/CacheExtensions.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; using Myriad.Cache; @@ -15,7 +15,7 @@ namespace Myriad.Extensions throw new KeyNotFoundException($"Guild {guildId} not found in cache"); return guild; } - + public static Channel GetChannel(this IDiscordCache cache, ulong channelId) { if (!cache.TryGetChannel(channelId, out var channel)) @@ -36,14 +36,14 @@ namespace Myriad.Extensions throw new KeyNotFoundException($"User {userId} not found in cache"); return user; } - + public static Role GetRole(this IDiscordCache cache, ulong roleId) { if (!cache.TryGetRole(roleId, out var role)) throw new KeyNotFoundException($"User {roleId} not found in cache"); return role; } - + public static async ValueTask GetOrFetchUser(this IDiscordCache cache, DiscordApiClient rest, ulong userId) { if (cache.TryGetUser(userId, out var cacheUser)) @@ -54,7 +54,7 @@ namespace Myriad.Extensions await cache.SaveUser(restUser); return restUser; } - + public static async ValueTask GetOrFetchChannel(this IDiscordCache cache, DiscordApiClient rest, ulong channelId) { if (cache.TryGetChannel(channelId, out var cacheChannel)) @@ -79,9 +79,9 @@ namespace Myriad.Extensions public static Channel GetRootChannel(this IDiscordCache cache, ulong channelOrThread) { var channel = cache.GetChannel(channelOrThread); - if (!channel.IsThread()) + if (!channel.IsThread()) return channel; - + var parent = cache.GetChannel(channel.ParentId!.Value); return parent; } diff --git a/Myriad/Extensions/ChannelExtensions.cs b/Myriad/Extensions/ChannelExtensions.cs index b511b970..0af2323c 100644 --- a/Myriad/Extensions/ChannelExtensions.cs +++ b/Myriad/Extensions/ChannelExtensions.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; namespace Myriad.Extensions { diff --git a/Myriad/Extensions/GuildExtensions.cs b/Myriad/Extensions/GuildExtensions.cs index 1e95b8bc..2f97aa56 100644 --- a/Myriad/Extensions/GuildExtensions.cs +++ b/Myriad/Extensions/GuildExtensions.cs @@ -1,7 +1,7 @@ -namespace Myriad.Extensions +namespace Myriad.Extensions { public static class GuildExtensions { - + } } \ No newline at end of file diff --git a/Myriad/Extensions/MessageExtensions.cs b/Myriad/Extensions/MessageExtensions.cs index 60adb532..8faac26e 100644 --- a/Myriad/Extensions/MessageExtensions.cs +++ b/Myriad/Extensions/MessageExtensions.cs @@ -1,4 +1,4 @@ -using Myriad.Gateway; +using Myriad.Gateway; using Myriad.Types; namespace Myriad.Extensions @@ -7,7 +7,7 @@ namespace Myriad.Extensions { public static string JumpLink(this Message msg) => $"https://discord.com/channels/{msg.GuildId}/{msg.ChannelId}/{msg.Id}"; - + public static string JumpLink(this MessageReactionAddEvent msg) => $"https://discord.com/channels/{msg.GuildId}/{msg.ChannelId}/{msg.MessageId}"; } diff --git a/Myriad/Extensions/PermissionExtensions.cs b/Myriad/Extensions/PermissionExtensions.cs index 567c32b5..fa614ba3 100644 --- a/Myriad/Extensions/PermissionExtensions.cs +++ b/Myriad/Extensions/PermissionExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; @@ -21,12 +21,12 @@ namespace Myriad.Extensions if (!cache.TryGetChannel(channelId, out var channel)) // todo: handle channel not found better return PermissionSet.Dm; - + if (channel.GuildId == null) return PermissionSet.Dm; var rootChannel = cache.GetRootChannel(channelId); - + var guild = cache.GetGuild(channel.GuildId.Value); if (isWebhook) @@ -34,10 +34,10 @@ namespace Myriad.Extensions return PermissionsFor(guild, rootChannel, userId, member); } - + public static PermissionSet EveryonePermissions(this Guild guild) => guild.Roles.FirstOrDefault(r => r.Id == guild.Id)?.Permissions ?? PermissionSet.Dm; - + public static PermissionSet EveryonePermissions(this IDiscordCache cache, Channel channel) { if (channel.Type == Channel.ChannelType.Dm) @@ -62,7 +62,7 @@ namespace Myriad.Extensions { if (channel.Type == Channel.ChannelType.Dm) return PermissionSet.Dm; - + if (member == null) // this happens with system (Discord platform-owned) users - they're not actually in the guild, so there is no member object. return EveryonePermissions(guild); diff --git a/Myriad/Extensions/SnowflakeExtensions.cs b/Myriad/Extensions/SnowflakeExtensions.cs index 71446138..57f00211 100644 --- a/Myriad/Extensions/SnowflakeExtensions.cs +++ b/Myriad/Extensions/SnowflakeExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using Myriad.Types; @@ -7,7 +7,7 @@ namespace Myriad.Extensions public static class SnowflakeExtensions { public static readonly DateTimeOffset DiscordEpoch = new(2015, 1, 1, 0, 0, 0, TimeSpan.Zero); - + public static DateTimeOffset SnowflakeToTimestamp(ulong snowflake) => DiscordEpoch + TimeSpan.FromMilliseconds(snowflake >> 22); diff --git a/Myriad/Extensions/UserExtensions.cs b/Myriad/Extensions/UserExtensions.cs index 81d16706..4a5d7ec6 100644 --- a/Myriad/Extensions/UserExtensions.cs +++ b/Myriad/Extensions/UserExtensions.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; namespace Myriad.Extensions { @@ -6,7 +6,7 @@ namespace Myriad.Extensions { public static string Mention(this User user) => $"<@{user.Id}>"; - public static string AvatarUrl(this User user, string? format = "png", int? size = 128) => + public static string AvatarUrl(this User user, string? format = "png", int? size = 128) => $"https://cdn.discordapp.com/avatars/{user.Id}/{user.Avatar}.{format}?size={size}"; } } \ No newline at end of file diff --git a/Myriad/Gateway/Cluster.cs b/Myriad/Gateway/Cluster.cs index b41d8c19..91ef1b0b 100644 --- a/Myriad/Gateway/Cluster.cs +++ b/Myriad/Gateway/Cluster.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -30,7 +30,7 @@ namespace Myriad.Gateway public IReadOnlyDictionary Shards => _shards; public User? User => _shards.Values.Select(s => s.User).FirstOrDefault(s => s != null); public ApplicationPartial? Application => _shards.Values.Select(s => s.Application).FirstOrDefault(s => s != null); - + public async Task Start(GatewayInfo.Bot info) { await Start(info.Url, 0, info.Shards - 1, info.Shards, info.SessionStartLimit.MaxConcurrency); @@ -39,7 +39,7 @@ namespace Myriad.Gateway public async Task Start(string url, int shardMin, int shardMax, int shardTotal, int recommendedConcurrency) { _ratelimiter = GetRateLimiter(recommendedConcurrency); - + var shardCount = shardMax - shardMin + 1; _logger.Information("Starting {ShardCount} of {ShardTotal} shards (#{ShardMin}-#{ShardMax}) at {Url}", shardCount, shardTotal, shardMin, shardMax, url); @@ -51,7 +51,7 @@ namespace Myriad.Gateway private async Task StartShards() { _logger.Information("Connecting shards..."); - foreach (var shard in _shards.Values) + foreach (var shard in _shards.Values) await shard.Start(); } @@ -60,7 +60,7 @@ namespace Myriad.Gateway var shard = new Shard(_gatewaySettings, shardInfo, _ratelimiter!, url, _logger); shard.OnEventReceived += evt => OnShardEventReceived(shard, evt); _shards[shardInfo.ShardId] = shard; - + ShardCreated?.Invoke(shard); } @@ -69,12 +69,12 @@ namespace Myriad.Gateway if (EventReceived != null) await EventReceived(shard, evt); } - + private int GetActualShardConcurrency(int recommendedConcurrency) { if (_gatewaySettings.MaxShardConcurrency == null) return recommendedConcurrency; - + return Math.Min(_gatewaySettings.MaxShardConcurrency.Value, recommendedConcurrency); } @@ -84,7 +84,7 @@ namespace Myriad.Gateway { return new TwilightGatewayRatelimiter(_logger, _gatewaySettings.GatewayQueueUrl); } - + var concurrency = GetActualShardConcurrency(recommendedConcurrency); return new LocalGatewayRatelimiter(_logger, concurrency); } diff --git a/Myriad/Gateway/Events/ChannelCreateEvent.cs b/Myriad/Gateway/Events/ChannelCreateEvent.cs index 08c7f4aa..df4186af 100644 --- a/Myriad/Gateway/Events/ChannelCreateEvent.cs +++ b/Myriad/Gateway/Events/ChannelCreateEvent.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; namespace Myriad.Gateway { diff --git a/Myriad/Gateway/Events/ChannelDeleteEvent.cs b/Myriad/Gateway/Events/ChannelDeleteEvent.cs index 7a3907b9..b30ec0cf 100644 --- a/Myriad/Gateway/Events/ChannelDeleteEvent.cs +++ b/Myriad/Gateway/Events/ChannelDeleteEvent.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; namespace Myriad.Gateway { diff --git a/Myriad/Gateway/Events/ChannelUpdateEvent.cs b/Myriad/Gateway/Events/ChannelUpdateEvent.cs index 95b675ac..1230b5b1 100644 --- a/Myriad/Gateway/Events/ChannelUpdateEvent.cs +++ b/Myriad/Gateway/Events/ChannelUpdateEvent.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; namespace Myriad.Gateway { diff --git a/Myriad/Gateway/Events/GuildCreateEvent.cs b/Myriad/Gateway/Events/GuildCreateEvent.cs index 6b3cfb5e..1d211be2 100644 --- a/Myriad/Gateway/Events/GuildCreateEvent.cs +++ b/Myriad/Gateway/Events/GuildCreateEvent.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; namespace Myriad.Gateway { diff --git a/Myriad/Gateway/Events/GuildDeleteEvent.cs b/Myriad/Gateway/Events/GuildDeleteEvent.cs index a46be10b..8e52097c 100644 --- a/Myriad/Gateway/Events/GuildDeleteEvent.cs +++ b/Myriad/Gateway/Events/GuildDeleteEvent.cs @@ -1,4 +1,4 @@ -namespace Myriad.Gateway +namespace Myriad.Gateway { public record GuildDeleteEvent(ulong Id, bool Unavailable): IGatewayEvent; } \ No newline at end of file diff --git a/Myriad/Gateway/Events/GuildMemberAddEvent.cs b/Myriad/Gateway/Events/GuildMemberAddEvent.cs index 33bcb057..a4f2fe7f 100644 --- a/Myriad/Gateway/Events/GuildMemberAddEvent.cs +++ b/Myriad/Gateway/Events/GuildMemberAddEvent.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; namespace Myriad.Gateway { diff --git a/Myriad/Gateway/Events/GuildMemberRemoveEvent.cs b/Myriad/Gateway/Events/GuildMemberRemoveEvent.cs index 713dd85a..a0506872 100644 --- a/Myriad/Gateway/Events/GuildMemberRemoveEvent.cs +++ b/Myriad/Gateway/Events/GuildMemberRemoveEvent.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; namespace Myriad.Gateway { diff --git a/Myriad/Gateway/Events/GuildMemberUpdateEvent.cs b/Myriad/Gateway/Events/GuildMemberUpdateEvent.cs index 61f5b828..aac1cf7f 100644 --- a/Myriad/Gateway/Events/GuildMemberUpdateEvent.cs +++ b/Myriad/Gateway/Events/GuildMemberUpdateEvent.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; namespace Myriad.Gateway { diff --git a/Myriad/Gateway/Events/GuildRoleCreateEvent.cs b/Myriad/Gateway/Events/GuildRoleCreateEvent.cs index 4c5079fc..fddb0a30 100644 --- a/Myriad/Gateway/Events/GuildRoleCreateEvent.cs +++ b/Myriad/Gateway/Events/GuildRoleCreateEvent.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; namespace Myriad.Gateway { diff --git a/Myriad/Gateway/Events/GuildRoleDeleteEvent.cs b/Myriad/Gateway/Events/GuildRoleDeleteEvent.cs index 082c56df..860eebda 100644 --- a/Myriad/Gateway/Events/GuildRoleDeleteEvent.cs +++ b/Myriad/Gateway/Events/GuildRoleDeleteEvent.cs @@ -1,4 +1,4 @@ -namespace Myriad.Gateway +namespace Myriad.Gateway { public record GuildRoleDeleteEvent(ulong GuildId, ulong RoleId): IGatewayEvent; } \ No newline at end of file diff --git a/Myriad/Gateway/Events/GuildRoleUpdateEvent.cs b/Myriad/Gateway/Events/GuildRoleUpdateEvent.cs index 298769ca..0f17cf12 100644 --- a/Myriad/Gateway/Events/GuildRoleUpdateEvent.cs +++ b/Myriad/Gateway/Events/GuildRoleUpdateEvent.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; namespace Myriad.Gateway { diff --git a/Myriad/Gateway/Events/GuildUpdateEvent.cs b/Myriad/Gateway/Events/GuildUpdateEvent.cs index 5d4695db..1da14092 100644 --- a/Myriad/Gateway/Events/GuildUpdateEvent.cs +++ b/Myriad/Gateway/Events/GuildUpdateEvent.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; namespace Myriad.Gateway { diff --git a/Myriad/Gateway/Events/IGatewayEvent.cs b/Myriad/Gateway/Events/IGatewayEvent.cs index 679ff2e4..a4ae61ad 100644 --- a/Myriad/Gateway/Events/IGatewayEvent.cs +++ b/Myriad/Gateway/Events/IGatewayEvent.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; namespace Myriad.Gateway @@ -7,33 +7,33 @@ namespace Myriad.Gateway { public static readonly Dictionary EventTypes = new() { - {"READY", typeof(ReadyEvent)}, - {"RESUMED", typeof(ResumedEvent)}, - {"GUILD_CREATE", typeof(GuildCreateEvent)}, - {"GUILD_UPDATE", typeof(GuildUpdateEvent)}, - {"GUILD_DELETE", typeof(GuildDeleteEvent)}, - {"GUILD_MEMBER_ADD", typeof(GuildMemberAddEvent)}, - {"GUILD_MEMBER_REMOVE", typeof(GuildMemberRemoveEvent)}, - {"GUILD_MEMBER_UPDATE", typeof(GuildMemberUpdateEvent)}, - {"GUILD_ROLE_CREATE", typeof(GuildRoleCreateEvent)}, - {"GUILD_ROLE_UPDATE", typeof(GuildRoleUpdateEvent)}, - {"GUILD_ROLE_DELETE", typeof(GuildRoleDeleteEvent)}, - {"CHANNEL_CREATE", typeof(ChannelCreateEvent)}, - {"CHANNEL_UPDATE", typeof(ChannelUpdateEvent)}, - {"CHANNEL_DELETE", typeof(ChannelDeleteEvent)}, - {"THREAD_CREATE", typeof(ThreadCreateEvent)}, - {"THREAD_UPDATE", typeof(ThreadUpdateEvent)}, - {"THREAD_DELETE", typeof(ThreadDeleteEvent)}, - {"THREAD_LIST_SYNC", typeof(ThreadListSyncEvent)}, - {"MESSAGE_CREATE", typeof(MessageCreateEvent)}, - {"MESSAGE_UPDATE", typeof(MessageUpdateEvent)}, - {"MESSAGE_DELETE", typeof(MessageDeleteEvent)}, - {"MESSAGE_DELETE_BULK", typeof(MessageDeleteBulkEvent)}, - {"MESSAGE_REACTION_ADD", typeof(MessageReactionAddEvent)}, - {"MESSAGE_REACTION_REMOVE", typeof(MessageReactionRemoveEvent)}, - {"MESSAGE_REACTION_REMOVE_ALL", typeof(MessageReactionRemoveAllEvent)}, - {"MESSAGE_REACTION_REMOVE_EMOJI", typeof(MessageReactionRemoveEmojiEvent)}, - {"INTERACTION_CREATE", typeof(InteractionCreateEvent)} + { "READY", typeof(ReadyEvent) }, + { "RESUMED", typeof(ResumedEvent) }, + { "GUILD_CREATE", typeof(GuildCreateEvent) }, + { "GUILD_UPDATE", typeof(GuildUpdateEvent) }, + { "GUILD_DELETE", typeof(GuildDeleteEvent) }, + { "GUILD_MEMBER_ADD", typeof(GuildMemberAddEvent) }, + { "GUILD_MEMBER_REMOVE", typeof(GuildMemberRemoveEvent) }, + { "GUILD_MEMBER_UPDATE", typeof(GuildMemberUpdateEvent) }, + { "GUILD_ROLE_CREATE", typeof(GuildRoleCreateEvent) }, + { "GUILD_ROLE_UPDATE", typeof(GuildRoleUpdateEvent) }, + { "GUILD_ROLE_DELETE", typeof(GuildRoleDeleteEvent) }, + { "CHANNEL_CREATE", typeof(ChannelCreateEvent) }, + { "CHANNEL_UPDATE", typeof(ChannelUpdateEvent) }, + { "CHANNEL_DELETE", typeof(ChannelDeleteEvent) }, + { "THREAD_CREATE", typeof(ThreadCreateEvent) }, + { "THREAD_UPDATE", typeof(ThreadUpdateEvent) }, + { "THREAD_DELETE", typeof(ThreadDeleteEvent) }, + { "THREAD_LIST_SYNC", typeof(ThreadListSyncEvent) }, + { "MESSAGE_CREATE", typeof(MessageCreateEvent) }, + { "MESSAGE_UPDATE", typeof(MessageUpdateEvent) }, + { "MESSAGE_DELETE", typeof(MessageDeleteEvent) }, + { "MESSAGE_DELETE_BULK", typeof(MessageDeleteBulkEvent) }, + { "MESSAGE_REACTION_ADD", typeof(MessageReactionAddEvent) }, + { "MESSAGE_REACTION_REMOVE", typeof(MessageReactionRemoveEvent) }, + { "MESSAGE_REACTION_REMOVE_ALL", typeof(MessageReactionRemoveAllEvent) }, + { "MESSAGE_REACTION_REMOVE_EMOJI", typeof(MessageReactionRemoveEmojiEvent) }, + { "INTERACTION_CREATE", typeof(InteractionCreateEvent) } }; } } \ No newline at end of file diff --git a/Myriad/Gateway/Events/InteractionCreateEvent.cs b/Myriad/Gateway/Events/InteractionCreateEvent.cs index 5ffccabc..05156d9f 100644 --- a/Myriad/Gateway/Events/InteractionCreateEvent.cs +++ b/Myriad/Gateway/Events/InteractionCreateEvent.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; namespace Myriad.Gateway { diff --git a/Myriad/Gateway/Events/MessageCreateEvent.cs b/Myriad/Gateway/Events/MessageCreateEvent.cs index 6df58ad4..02a88712 100644 --- a/Myriad/Gateway/Events/MessageCreateEvent.cs +++ b/Myriad/Gateway/Events/MessageCreateEvent.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; namespace Myriad.Gateway { diff --git a/Myriad/Gateway/Events/MessageDeleteBulkEvent.cs b/Myriad/Gateway/Events/MessageDeleteBulkEvent.cs index b4b88601..96268129 100644 --- a/Myriad/Gateway/Events/MessageDeleteBulkEvent.cs +++ b/Myriad/Gateway/Events/MessageDeleteBulkEvent.cs @@ -1,4 +1,4 @@ -namespace Myriad.Gateway +namespace Myriad.Gateway { public record MessageDeleteBulkEvent(ulong[] Ids, ulong ChannelId, ulong? GuildId): IGatewayEvent; } \ No newline at end of file diff --git a/Myriad/Gateway/Events/MessageDeleteEvent.cs b/Myriad/Gateway/Events/MessageDeleteEvent.cs index e7a05e04..a5f533d5 100644 --- a/Myriad/Gateway/Events/MessageDeleteEvent.cs +++ b/Myriad/Gateway/Events/MessageDeleteEvent.cs @@ -1,4 +1,4 @@ -namespace Myriad.Gateway +namespace Myriad.Gateway { public record MessageDeleteEvent(ulong Id, ulong ChannelId, ulong? GuildId): IGatewayEvent; } \ No newline at end of file diff --git a/Myriad/Gateway/Events/MessageReactionAddEvent.cs b/Myriad/Gateway/Events/MessageReactionAddEvent.cs index c7545bea..da05fc52 100644 --- a/Myriad/Gateway/Events/MessageReactionAddEvent.cs +++ b/Myriad/Gateway/Events/MessageReactionAddEvent.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; namespace Myriad.Gateway { diff --git a/Myriad/Gateway/Events/MessageReactionRemoveAllEvent.cs b/Myriad/Gateway/Events/MessageReactionRemoveAllEvent.cs index 1ef0dab0..972537b1 100644 --- a/Myriad/Gateway/Events/MessageReactionRemoveAllEvent.cs +++ b/Myriad/Gateway/Events/MessageReactionRemoveAllEvent.cs @@ -1,4 +1,4 @@ -namespace Myriad.Gateway +namespace Myriad.Gateway { public record MessageReactionRemoveAllEvent(ulong ChannelId, ulong MessageId, ulong? GuildId): IGatewayEvent; } \ No newline at end of file diff --git a/Myriad/Gateway/Events/MessageReactionRemoveEmojiEvent.cs b/Myriad/Gateway/Events/MessageReactionRemoveEmojiEvent.cs index ff4a5dad..1bad7f44 100644 --- a/Myriad/Gateway/Events/MessageReactionRemoveEmojiEvent.cs +++ b/Myriad/Gateway/Events/MessageReactionRemoveEmojiEvent.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; namespace Myriad.Gateway { diff --git a/Myriad/Gateway/Events/MessageReactionRemoveEvent.cs b/Myriad/Gateway/Events/MessageReactionRemoveEvent.cs index 392e2cf9..37b186d8 100644 --- a/Myriad/Gateway/Events/MessageReactionRemoveEvent.cs +++ b/Myriad/Gateway/Events/MessageReactionRemoveEvent.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; namespace Myriad.Gateway { diff --git a/Myriad/Gateway/Events/MessageUpdateEvent.cs b/Myriad/Gateway/Events/MessageUpdateEvent.cs index 09ef4316..2123ede3 100644 --- a/Myriad/Gateway/Events/MessageUpdateEvent.cs +++ b/Myriad/Gateway/Events/MessageUpdateEvent.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; using Myriad.Utils; namespace Myriad.Gateway diff --git a/Myriad/Gateway/Events/ReadyEvent.cs b/Myriad/Gateway/Events/ReadyEvent.cs index 7dad1ee7..cb0c2dbd 100644 --- a/Myriad/Gateway/Events/ReadyEvent.cs +++ b/Myriad/Gateway/Events/ReadyEvent.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; using Myriad.Types; diff --git a/Myriad/Gateway/Events/ResumedEvent.cs b/Myriad/Gateway/Events/ResumedEvent.cs index de8ecfe1..2cb216a9 100644 --- a/Myriad/Gateway/Events/ResumedEvent.cs +++ b/Myriad/Gateway/Events/ResumedEvent.cs @@ -1,4 +1,4 @@ -namespace Myriad.Gateway +namespace Myriad.Gateway { public record ResumedEvent: IGatewayEvent; } \ No newline at end of file diff --git a/Myriad/Gateway/Events/ThreadCreateEvent.cs b/Myriad/Gateway/Events/ThreadCreateEvent.cs index ee204188..832e3fc1 100644 --- a/Myriad/Gateway/Events/ThreadCreateEvent.cs +++ b/Myriad/Gateway/Events/ThreadCreateEvent.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; namespace Myriad.Gateway { diff --git a/Myriad/Gateway/Events/ThreadDeleteEvent.cs b/Myriad/Gateway/Events/ThreadDeleteEvent.cs index 694ae56b..09fe2fda 100644 --- a/Myriad/Gateway/Events/ThreadDeleteEvent.cs +++ b/Myriad/Gateway/Events/ThreadDeleteEvent.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; namespace Myriad.Gateway { diff --git a/Myriad/Gateway/Events/ThreadListSyncEvent.cs b/Myriad/Gateway/Events/ThreadListSyncEvent.cs index 56ff7426..07ec3356 100644 --- a/Myriad/Gateway/Events/ThreadListSyncEvent.cs +++ b/Myriad/Gateway/Events/ThreadListSyncEvent.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; namespace Myriad.Gateway { diff --git a/Myriad/Gateway/Events/ThreadUpdateEvent.cs b/Myriad/Gateway/Events/ThreadUpdateEvent.cs index 68cc3afb..6f8f6c7a 100644 --- a/Myriad/Gateway/Events/ThreadUpdateEvent.cs +++ b/Myriad/Gateway/Events/ThreadUpdateEvent.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; namespace Myriad.Gateway { diff --git a/Myriad/Gateway/GatewayCloseException.cs b/Myriad/Gateway/GatewayCloseException.cs index 02f83159..d6be8a2b 100644 --- a/Myriad/Gateway/GatewayCloseException.cs +++ b/Myriad/Gateway/GatewayCloseException.cs @@ -1,11 +1,11 @@ -using System; +using System; namespace Myriad.Gateway { // TODO: unused? public class GatewayCloseException: Exception { - public GatewayCloseException(int closeCode, string closeReason): base($"{closeCode}: {closeReason}") + public GatewayCloseException(int closeCode, string closeReason) : base($"{closeCode}: {closeReason}") { CloseCode = closeCode; CloseReason = closeReason; diff --git a/Myriad/Gateway/GatewayIntent.cs b/Myriad/Gateway/GatewayIntent.cs index 1a2c7c7d..fc1a1cda 100644 --- a/Myriad/Gateway/GatewayIntent.cs +++ b/Myriad/Gateway/GatewayIntent.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Myriad.Gateway { diff --git a/Myriad/Gateway/GatewayPacket.cs b/Myriad/Gateway/GatewayPacket.cs index 1cf7e26d..50a2b502 100644 --- a/Myriad/Gateway/GatewayPacket.cs +++ b/Myriad/Gateway/GatewayPacket.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Myriad.Gateway { @@ -7,10 +7,12 @@ namespace Myriad.Gateway [JsonPropertyName("op")] public GatewayOpcode Opcode { get; init; } [JsonPropertyName("d")] public object? Payload { get; init; } - [JsonPropertyName("s")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("s")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public int? Sequence { get; init; } - [JsonPropertyName("t")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("t")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? EventType { get; init; } } diff --git a/Myriad/Gateway/GatewaySettings.cs b/Myriad/Gateway/GatewaySettings.cs index a0e6a59e..4690a4ab 100644 --- a/Myriad/Gateway/GatewaySettings.cs +++ b/Myriad/Gateway/GatewaySettings.cs @@ -1,4 +1,4 @@ -namespace Myriad.Gateway +namespace Myriad.Gateway { public record GatewaySettings { diff --git a/Myriad/Gateway/Limit/IGatewayRatelimiter.cs b/Myriad/Gateway/Limit/IGatewayRatelimiter.cs index b5552e25..7b8096b7 100644 --- a/Myriad/Gateway/Limit/IGatewayRatelimiter.cs +++ b/Myriad/Gateway/Limit/IGatewayRatelimiter.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; namespace Myriad.Gateway.Limit { diff --git a/Myriad/Gateway/Limit/LocalGatewayRatelimiter.cs b/Myriad/Gateway/Limit/LocalGatewayRatelimiter.cs index 1661dffc..c85e4251 100644 --- a/Myriad/Gateway/Limit/LocalGatewayRatelimiter.cs +++ b/Myriad/Gateway/Limit/LocalGatewayRatelimiter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Threading.Tasks; diff --git a/Myriad/Gateway/Limit/TwilightGatewayRatelimiter.cs b/Myriad/Gateway/Limit/TwilightGatewayRatelimiter.cs index 51841717..8707751d 100644 --- a/Myriad/Gateway/Limit/TwilightGatewayRatelimiter.cs +++ b/Myriad/Gateway/Limit/TwilightGatewayRatelimiter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net.Http; using System.Threading.Tasks; @@ -14,7 +14,7 @@ namespace Myriad.Gateway.Limit { Timeout = TimeSpan.FromSeconds(60) }; - + public TwilightGatewayRatelimiter(ILogger logger, string url) { _url = url; diff --git a/Myriad/Gateway/Payloads/GatewayHello.cs b/Myriad/Gateway/Payloads/GatewayHello.cs index f8593bb9..775a9b31 100644 --- a/Myriad/Gateway/Payloads/GatewayHello.cs +++ b/Myriad/Gateway/Payloads/GatewayHello.cs @@ -1,4 +1,4 @@ -namespace Myriad.Gateway +namespace Myriad.Gateway { public record GatewayHello(int HeartbeatInterval); } \ No newline at end of file diff --git a/Myriad/Gateway/Payloads/GatewayIdentify.cs b/Myriad/Gateway/Payloads/GatewayIdentify.cs index bc6d1931..2357f533 100644 --- a/Myriad/Gateway/Payloads/GatewayIdentify.cs +++ b/Myriad/Gateway/Payloads/GatewayIdentify.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Myriad.Gateway { diff --git a/Myriad/Gateway/Payloads/GatewayResume.cs b/Myriad/Gateway/Payloads/GatewayResume.cs index fd386889..83d561a1 100644 --- a/Myriad/Gateway/Payloads/GatewayResume.cs +++ b/Myriad/Gateway/Payloads/GatewayResume.cs @@ -1,4 +1,4 @@ -namespace Myriad.Gateway +namespace Myriad.Gateway { public record GatewayResume(string Token, string SessionId, int Seq); } \ No newline at end of file diff --git a/Myriad/Gateway/Payloads/GatewayStatusUpdate.cs b/Myriad/Gateway/Payloads/GatewayStatusUpdate.cs index 7ca57090..55f7e8ef 100644 --- a/Myriad/Gateway/Payloads/GatewayStatusUpdate.cs +++ b/Myriad/Gateway/Payloads/GatewayStatusUpdate.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; using Myriad.Serialization; using Myriad.Types; diff --git a/Myriad/Gateway/Shard.cs b/Myriad/Gateway/Shard.cs index 65aa3019..4bbc6226 100644 --- a/Myriad/Gateway/Shard.cs +++ b/Myriad/Gateway/Shard.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net.WebSockets; using System.Text.Json; using System.Threading.Tasks; @@ -68,18 +68,18 @@ namespace Myriad.Gateway _conn = new ShardConnection(_jsonSerializerOptions, _logger); } - + private async Task ShardLoop() { // may be superfluous but this adds shard id to ambient context which is nice using var _ = LogContext.PushProperty("ShardId", _info.ShardId); - + while (true) { try { await ConnectInner(); - + await HandleConnectionOpened(); while (_conn.State == WebSocketState.Open) @@ -90,7 +90,7 @@ namespace Myriad.Gateway await _stateManager.HandlePacketReceived(packet); } - + await HandleConnectionClosed(_conn.CloseStatus, _conn.CloseStatusDescription); _logger.Information("Shard {ShardId}: Reconnecting after delay {ReconnectDelay}", @@ -103,20 +103,20 @@ namespace Myriad.Gateway catch (Exception e) { _logger.Error(e, "Shard {ShardId}: Error in main shard loop, reconnecting in 5 seconds...", _info.ShardId); - + // todo: exponential backoff here? this should never happen, ideally... await Task.Delay(TimeSpan.FromSeconds(5)); } } } - + public async Task Start() { if (_worker == null) _worker = ShardLoop(); // we can probably TCS this instead of spin loop but w/e - while (State != ShardState.Identifying) + while (State != ShardState.Identifying) await Task.Delay(100); } @@ -128,7 +128,7 @@ namespace Myriad.Gateway Payload = payload }); } - + private async Task ConnectInner() { while (true) @@ -148,12 +148,12 @@ namespace Myriad.Gateway } } } - + private async Task DisconnectInner(WebSocketCloseStatus closeStatus) { await _conn.Disconnect(closeStatus, null); } - + private async Task SendIdentify() { await _conn.Send(new GatewayPacket @@ -165,8 +165,8 @@ namespace Myriad.Gateway Intents = _settings.Intents, Properties = new GatewayIdentify.ConnectionProperties { - Browser = LibraryName, - Device = LibraryName, + Browser = LibraryName, + Device = LibraryName, Os = Environment.OSVersion.ToString() }, Shard = _info, @@ -175,7 +175,7 @@ namespace Myriad.Gateway } }); } - + private async Task SendResume((string SessionId, int? LastSeq) arg) { await _conn.Send(new GatewayPacket @@ -184,12 +184,12 @@ namespace Myriad.Gateway Payload = new GatewayResume(_settings.Token, arg.SessionId, arg.LastSeq ?? 0) }); } - + private async Task SendHeartbeat(int? lastSeq) { - await _conn.Send(new GatewayPacket {Opcode = GatewayOpcode.Heartbeat, Payload = lastSeq}); + await _conn.Send(new GatewayPacket { Opcode = GatewayOpcode.Heartbeat, Payload = lastSeq }); } - + private async Task Reconnect(WebSocketCloseStatus closeStatus, TimeSpan delay) { _reconnectDelay = delay; @@ -202,7 +202,7 @@ namespace Myriad.Gateway Ready?.Invoke(); if (arg is ResumedEvent) Resumed?.Invoke(); - + await (OnEventReceived?.Invoke(arg) ?? Task.CompletedTask); } @@ -215,7 +215,7 @@ namespace Myriad.Gateway private async Task HandleConnectionClosed(WebSocketCloseStatus? closeStatus, string? description) { - _logger.Information("Shard {ShardId}: Connection closed ({CloseStatus}/{Description})", + _logger.Information("Shard {ShardId}: Connection closed ({CloseStatus}/{Description})", _info.ShardId, closeStatus, description ?? ""); await _stateManager.HandleConnectionClosed(); SocketClosed?.Invoke(closeStatus, description); diff --git a/Myriad/Gateway/ShardConnection.cs b/Myriad/Gateway/ShardConnection.cs index 03e84792..23733fcc 100644 --- a/Myriad/Gateway/ShardConnection.cs +++ b/Myriad/Gateway/ShardConnection.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net.WebSockets; using System.Text.Json; using System.Threading; @@ -13,11 +13,11 @@ namespace Myriad.Gateway private ClientWebSocket? _client; private readonly ILogger _logger; private readonly ShardPacketSerializer _serializer; - + public WebSocketState State => _client?.State ?? WebSocketState.Closed; public WebSocketCloseStatus? CloseStatus => _client?.CloseStatus; public string? CloseStatusDescription => _client?.CloseStatusDescription; - + public ShardConnection(JsonSerializerOptions jsonSerializerOptions, ILogger logger) { _logger = logger.ForContext(); @@ -28,7 +28,7 @@ namespace Myriad.Gateway { _client?.Dispose(); _client = new ClientWebSocket(); - + await _client.ConnectAsync(GetConnectionUri(url), ct); } @@ -40,7 +40,7 @@ namespace Myriad.Gateway public async Task Send(GatewayPacket packet) { // from `ManagedWebSocket.s_validSendStates` - if (_client is not {State: WebSocketState.Open or WebSocketState.CloseReceived}) + if (_client is not { State: WebSocketState.Open or WebSocketState.CloseReceived }) return; try @@ -62,7 +62,7 @@ namespace Myriad.Gateway public async Task Read() { // from `ManagedWebSocket.s_validReceiveStates` - if (_client is not {State: WebSocketState.Open or WebSocketState.CloseSent}) + if (_client is not { State: WebSocketState.Open or WebSocketState.CloseSent }) return null; try @@ -79,7 +79,7 @@ namespace Myriad.Gateway return null; } - + private Uri GetConnectionUri(string baseUri) => new UriBuilder(baseUri) { Query = "v=9&encoding=json" @@ -89,10 +89,10 @@ namespace Myriad.Gateway { if (_client == null) return; - + var client = _client; _client = null; - + // from `ManagedWebSocket.s_validCloseStates` if (client.State is WebSocketState.Open or WebSocketState.CloseReceived or WebSocketState.CloseSent) { diff --git a/Myriad/Gateway/ShardInfo.cs b/Myriad/Gateway/ShardInfo.cs index 07a096f6..8547f66d 100644 --- a/Myriad/Gateway/ShardInfo.cs +++ b/Myriad/Gateway/ShardInfo.cs @@ -1,4 +1,4 @@ -namespace Myriad.Gateway +namespace Myriad.Gateway { public record ShardInfo(int ShardId, int NumShards); } \ No newline at end of file diff --git a/Myriad/Gateway/ShardPacketSerializer.cs b/Myriad/Gateway/ShardPacketSerializer.cs index 133ec960..4d87bf0f 100644 --- a/Myriad/Gateway/ShardPacketSerializer.cs +++ b/Myriad/Gateway/ShardPacketSerializer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Buffers; using System.IO; using System.Net.WebSockets; @@ -10,7 +10,7 @@ namespace Myriad.Gateway public class ShardPacketSerializer { private const int BufferSize = 64 * 1024; - + private readonly JsonSerializerOptions _jsonSerializerOptions; public ShardPacketSerializer(JsonSerializerOptions jsonSerializerOptions) @@ -21,15 +21,15 @@ namespace Myriad.Gateway public async ValueTask<(WebSocketMessageType type, GatewayPacket? packet)> ReadPacket(ClientWebSocket socket) { using var buf = MemoryPool.Shared.Rent(BufferSize); - + var res = await socket.ReceiveAsync(buf.Memory, default); if (res.MessageType == WebSocketMessageType.Close) return (res.MessageType, null); - + if (res.EndOfMessage) // Entire packet fits within one buffer, deserialize directly return DeserializeSingleBuffer(buf, res); - + // Otherwise copy to stream buffer and deserialize from there return await DeserializeMultipleBuffer(socket, buf, res); } @@ -51,7 +51,7 @@ namespace Myriad.Gateway stream.Write(buf.Memory.Span.Slice(0, res.Count)); } - return DeserializeObject(res, stream.GetBuffer().AsSpan(0, (int) stream.Length)); + return DeserializeObject(res, stream.GetBuffer().AsSpan(0, (int)stream.Length)); } private (WebSocketMessageType type, GatewayPacket packet) DeserializeSingleBuffer( diff --git a/Myriad/Gateway/State/HeartbeatWorker.cs b/Myriad/Gateway/State/HeartbeatWorker.cs index 794cfc2c..f1762bf7 100644 --- a/Myriad/Gateway/State/HeartbeatWorker.cs +++ b/Myriad/Gateway/State/HeartbeatWorker.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; @@ -8,7 +8,7 @@ namespace Myriad.Gateway.State { private Task? _worker; private CancellationTokenSource? _workerCts; - + public TimeSpan? CurrentHeartbeatInterval { get; private set; } public async ValueTask Start(TimeSpan heartbeatInterval, Func callback) @@ -23,9 +23,9 @@ namespace Myriad.Gateway.State public async ValueTask Stop() { - if (_worker == null) + if (_worker == null) return; - + _workerCts?.Cancel(); try { @@ -38,12 +38,12 @@ namespace Myriad.Gateway.State _worker = null; CurrentHeartbeatInterval = null; } - + private async Task Worker(TimeSpan heartbeatInterval, Func callback, CancellationToken ct) { var initialDelay = GetInitialHeartbeatDelay(heartbeatInterval); await Task.Delay(initialDelay, ct); - + while (!ct.IsCancellationRequested) { await callback(); diff --git a/Myriad/Gateway/State/ShardState.cs b/Myriad/Gateway/State/ShardState.cs index a3bacaee..f7843ec2 100644 --- a/Myriad/Gateway/State/ShardState.cs +++ b/Myriad/Gateway/State/ShardState.cs @@ -1,4 +1,4 @@ -namespace Myriad.Gateway.State +namespace Myriad.Gateway.State { public enum ShardState { diff --git a/Myriad/Gateway/State/ShardStateManager.cs b/Myriad/Gateway/State/ShardStateManager.cs index 2345a0d4..04b77a40 100644 --- a/Myriad/Gateway/State/ShardStateManager.cs +++ b/Myriad/Gateway/State/ShardStateManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net.WebSockets; using System.Text.Json; using System.Threading.Tasks; @@ -18,7 +18,7 @@ namespace Myriad.Gateway private readonly ShardInfo _info; private readonly JsonSerializerOptions _jsonSerializerOptions; private ShardState _state = ShardState.Disconnected; - + private DateTimeOffset? _lastHeartbeatSent; private TimeSpan? _latency; private bool _hasReceivedHeartbeatAck; @@ -30,7 +30,7 @@ namespace Myriad.Gateway public TimeSpan? Latency => _latency; public User? User { get; private set; } public ApplicationPartial? Application { get; private set; } - + public Func SendIdentify { get; init; } public Func<(string SessionId, int? LastSeq), Task> SendResume { get; init; } public Func SendHeartbeat { get; init; } @@ -52,7 +52,7 @@ namespace Myriad.Gateway _state = ShardState.Handshaking; return Task.CompletedTask; } - + public async Task HandleConnectionClosed() { _latency = null; @@ -71,36 +71,36 @@ namespace Myriad.Gateway case GatewayOpcode.Heartbeat: await HandleHeartbeatRequest(); break; - + case GatewayOpcode.HeartbeatAck: await HandleHeartbeatAck(); break; - + case GatewayOpcode.Reconnect: - { - await HandleReconnect(); - break; - } + { + await HandleReconnect(); + break; + } case GatewayOpcode.InvalidSession: - { - var canResume = DeserializePayload(packet); - await HandleInvalidSession(canResume); - break; - } + { + var canResume = DeserializePayload(packet); + await HandleInvalidSession(canResume); + break; + } - case GatewayOpcode.Dispatch: + case GatewayOpcode.Dispatch: _lastSeq = packet.Sequence; - var evt = DeserializeEvent(packet.EventType!, (JsonElement) packet.Payload!); + var evt = DeserializeEvent(packet.EventType!, (JsonElement)packet.Payload!); if (evt != null) { - if (evt is ReadyEvent ready) + if (evt is ReadyEvent ready) await HandleReady(ready); - - if (evt is ResumedEvent) + + if (evt is ResumedEvent) await HandleResumed(); - + await HandleEvent(evt); } break; @@ -110,7 +110,7 @@ namespace Myriad.Gateway private async Task HandleHello(GatewayHello hello) { var interval = TimeSpan.FromMilliseconds(hello.HeartbeatInterval); - + _hasReceivedHeartbeatAck = true; await _heartbeatWorker.Start(interval, HandleHeartbeatTimer); await IdentifyOrResume(); @@ -152,14 +152,14 @@ namespace Myriad.Gateway _sessionId = null; _lastSeq = null; } - + _logger.Information("Shard {ShardId}: Received Invalid Session (can resume? {CanResume})", _info.ShardId, canResume); var delay = TimeSpan.FromMilliseconds(new Random().Next(1000, 5000)); await DoReconnect(WebSocketCloseStatus.NormalClosure, delay); } - + private async Task HandleReconnect() { _logger.Information("Shard {ShardId}: Received Reconnect", _info.ShardId); @@ -167,7 +167,7 @@ namespace Myriad.Gateway // we use 1005 (no error specified) instead await DoReconnect(WebSocketCloseStatus.Empty, TimeSpan.FromSeconds(1)); } - + private Task HandleReady(ReadyEvent ready) { _logger.Information("Shard {ShardId}: Received Ready", _info.ShardId); @@ -218,10 +218,10 @@ namespace Myriad.Gateway private T DeserializePayload(GatewayPacket packet) { - var packetPayload = (JsonElement) packet.Payload!; + var packetPayload = (JsonElement)packet.Payload!; return JsonSerializer.Deserialize(packetPayload.GetRawText(), _jsonSerializerOptions)!; } - + private IGatewayEvent? DeserializeEvent(string eventType, JsonElement payload) { if (!IGatewayEvent.EventTypes.TryGetValue(eventType, out var clrType)) diff --git a/Myriad/Rest/BaseRestClient.cs b/Myriad/Rest/BaseRestClient.cs index 923a6f5a..d86a3e71 100644 --- a/Myriad/Rest/BaseRestClient.cs +++ b/Myriad/Rest/BaseRestClient.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Net; @@ -52,7 +52,7 @@ namespace Myriad.Rest var waitPolicy = Policy .Handle() .WaitAndRetryAsync(3, - (_, e, _) => ((RatelimitBucketExhaustedException) e).RetryAfter, + (_, e, _) => ((RatelimitBucketExhaustedException)e).RetryAfter, (_, _, _, _) => Task.CompletedTask) .AsAsyncPolicy(); @@ -68,7 +68,7 @@ namespace Myriad.Rest return default; } - public async Task Get(string path, (string endpointName, ulong major) ratelimitParams) where T: class + public async Task Get(string path, (string endpointName, ulong major) ratelimitParams) where T : class { using var response = await Send(() => new HttpRequestMessage(HttpMethod.Get, ApiBaseUrl + path), ratelimitParams, true); @@ -81,7 +81,7 @@ namespace Myriad.Rest } public async Task Post(string path, (string endpointName, ulong major) ratelimitParams, object? body) - where T: class + where T : class { using var response = await Send(() => { @@ -91,9 +91,9 @@ namespace Myriad.Rest }, ratelimitParams); return await ReadResponse(response); } - + public async Task PostMultipart(string path, (string endpointName, ulong major) ratelimitParams, object? payload, MultipartFile[]? files) - where T: class + where T : class { using var response = await Send(() => { @@ -105,7 +105,7 @@ namespace Myriad.Rest } public async Task Patch(string path, (string endpointName, ulong major) ratelimitParams, object? body) - where T: class + where T : class { using var response = await Send(() => { @@ -117,7 +117,7 @@ namespace Myriad.Rest } public async Task Put(string path, (string endpointName, ulong major) ratelimitParams, object? body) - where T: class + where T : class { using var response = await Send(() => { @@ -160,7 +160,7 @@ namespace Myriad.Rest request.Content = mfd; } - private async Task ReadResponse(HttpResponseMessage response) where T: class + private async Task ReadResponse(HttpResponseMessage response) where T : class { if (response.StatusCode == HttpStatusCode.NoContent) return null; @@ -174,7 +174,7 @@ namespace Myriad.Rest return await _retryPolicy.ExecuteAsync(async _ => { using var __ = LogContext.PushProperty("EndpointName", ratelimitParams.endpointName); - + var request = createRequest(); _logger.Debug("Request: {RequestMethod} {RequestPath}", request.Method, request.RequestUri); @@ -189,7 +189,7 @@ namespace Myriad.Rest _logger.Debug( "Response: {RequestMethod} {RequestPath} -> {StatusCode} {ReasonPhrase} (in {ResponseDurationMs} ms)", - request.Method, request.RequestUri, (int) response.StatusCode, response.ReasonPhrase, stopwatch.ElapsedMilliseconds); + request.Method, request.RequestUri, (int)response.StatusCode, response.ReasonPhrase, stopwatch.ElapsedMilliseconds); await HandleApiError(response, ignoreNotFound); @@ -211,7 +211,7 @@ namespace Myriad.Rest return; var body = await response.Content.ReadAsStringAsync(); - var apiError = TryParseApiError(body); + var apiError = TryParseApiError(body); if (apiError != null) _logger.Warning("Discord API error: {DiscordErrorCode} {DiscordErrorMessage}", apiError.Code, apiError.Message); diff --git a/Myriad/Rest/DiscordApiClient.cs b/Myriad/Rest/DiscordApiClient.cs index 7b1edeaf..3b37fc79 100644 --- a/Myriad/Rest/DiscordApiClient.cs +++ b/Myriad/Rest/DiscordApiClient.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net; using System.Threading.Tasks; @@ -34,7 +34,7 @@ namespace Myriad.Rest public Task GetGuild(ulong id) => _client.Get($"/guilds/{id}", ("GetGuild", id)); - + public Task GetGuildChannels(ulong id) => _client.Get($"/guilds/{id}/channels", ("GetGuildChannels", id))!; @@ -108,7 +108,7 @@ namespace Myriad.Rest public Task CreateWebhook(ulong channelId, CreateWebhookRequest request) => _client.Post($"/channels/{channelId}/webhooks", ("CreateWebhook", channelId), request)!; - + public Task GetWebhook(ulong webhookId) => _client.Get($"/webhooks/{webhookId}/webhooks", ("GetWebhook", webhookId))!; @@ -121,7 +121,7 @@ namespace Myriad.Rest var url = $"/webhooks/{webhookId}/{webhookToken}?wait=true"; if (threadId != null) url += $"&thread_id={threadId}"; - + return _client.PostMultipart(url, ("ExecuteWebhook", webhookId), request, files)!; } diff --git a/Myriad/Rest/DiscordApiError.cs b/Myriad/Rest/DiscordApiError.cs index 59cce46b..55d6640a 100644 --- a/Myriad/Rest/DiscordApiError.cs +++ b/Myriad/Rest/DiscordApiError.cs @@ -1,4 +1,4 @@ -using System.Text.Json; +using System.Text.Json; namespace Myriad.Rest { diff --git a/Myriad/Rest/Exceptions/DiscordRequestException.cs b/Myriad/Rest/Exceptions/DiscordRequestException.cs index 0aa94d98..3a803081 100644 --- a/Myriad/Rest/Exceptions/DiscordRequestException.cs +++ b/Myriad/Rest/Exceptions/DiscordRequestException.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net; using System.Net.Http; @@ -29,43 +29,49 @@ namespace Myriad.Rest.Exceptions public class NotFoundException: DiscordRequestException { - public NotFoundException(HttpResponseMessage response, string responseBody, DiscordApiError? apiError): base( - response, responseBody, apiError) { } + public NotFoundException(HttpResponseMessage response, string responseBody, DiscordApiError? apiError) : base( + response, responseBody, apiError) + { } } public class UnauthorizedException: DiscordRequestException { - public UnauthorizedException(HttpResponseMessage response, string responseBody, DiscordApiError? apiError): base( - response, responseBody, apiError) { } + public UnauthorizedException(HttpResponseMessage response, string responseBody, DiscordApiError? apiError) : base( + response, responseBody, apiError) + { } } public class ForbiddenException: DiscordRequestException { - public ForbiddenException(HttpResponseMessage response, string responseBody, DiscordApiError? apiError): base( - response, responseBody, apiError) { } + public ForbiddenException(HttpResponseMessage response, string responseBody, DiscordApiError? apiError) : base( + response, responseBody, apiError) + { } } public class ConflictException: DiscordRequestException { - public ConflictException(HttpResponseMessage response, string responseBody, DiscordApiError? apiError): base( - response, responseBody, apiError) { } + public ConflictException(HttpResponseMessage response, string responseBody, DiscordApiError? apiError) : base( + response, responseBody, apiError) + { } } public class BadRequestException: DiscordRequestException { - public BadRequestException(HttpResponseMessage response, string responseBody, DiscordApiError? apiError): base( - response, responseBody, apiError) { } + public BadRequestException(HttpResponseMessage response, string responseBody, DiscordApiError? apiError) : base( + response, responseBody, apiError) + { } } public class TooManyRequestsException: DiscordRequestException { - public TooManyRequestsException(HttpResponseMessage response, string responseBody, DiscordApiError? apiError): - base(response, responseBody, apiError) { } + public TooManyRequestsException(HttpResponseMessage response, string responseBody, DiscordApiError? apiError) : + base(response, responseBody, apiError) + { } } public class UnknownDiscordRequestException: DiscordRequestException { public UnknownDiscordRequestException(HttpResponseMessage response, string responseBody, - DiscordApiError? apiError): base(response, responseBody, apiError) { } + DiscordApiError? apiError) : base(response, responseBody, apiError) { } } } \ No newline at end of file diff --git a/Myriad/Rest/Exceptions/RatelimitException.cs b/Myriad/Rest/Exceptions/RatelimitException.cs index 780f45ea..0c613150 100644 --- a/Myriad/Rest/Exceptions/RatelimitException.cs +++ b/Myriad/Rest/Exceptions/RatelimitException.cs @@ -1,4 +1,4 @@ -using System; +using System; using Myriad.Rest.Ratelimit; @@ -6,12 +6,12 @@ namespace Myriad.Rest.Exceptions { public class RatelimitException: Exception { - public RatelimitException(string? message): base(message) { } + public RatelimitException(string? message) : base(message) { } } public class RatelimitBucketExhaustedException: RatelimitException { - public RatelimitBucketExhaustedException(Bucket bucket, TimeSpan retryAfter): base( + public RatelimitBucketExhaustedException(Bucket bucket, TimeSpan retryAfter) : base( "Rate limit bucket exhausted, request blocked") { Bucket = bucket; @@ -24,6 +24,6 @@ namespace Myriad.Rest.Exceptions public class GloballyRatelimitedException: RatelimitException { - public GloballyRatelimitedException(): base("Global rate limit hit") { } + public GloballyRatelimitedException() : base("Global rate limit hit") { } } } \ No newline at end of file diff --git a/Myriad/Rest/Ratelimit/Bucket.cs b/Myriad/Rest/Ratelimit/Bucket.cs index da11dc2b..1acb6d7e 100644 --- a/Myriad/Rest/Ratelimit/Bucket.cs +++ b/Myriad/Rest/Ratelimit/Bucket.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading; using Serilog; @@ -54,7 +54,7 @@ namespace Myriad.Rest.Ratelimit "{BucketKey}/{BucketMajor}: Bucket has [{BucketRemaining}/{BucketLimit} left], allowing through", Key, Major, Remaining, Limit); Remaining--; - + return true; } @@ -73,7 +73,7 @@ namespace Myriad.Rest.Ratelimit try { _semaphore.Wait(); - + _logger.Verbose("{BucketKey}/{BucketMajor}: Received rate limit headers: {@RateLimitHeaders}", Key, Major, headers); @@ -90,7 +90,7 @@ namespace Myriad.Rest.Ratelimit } } - if (headers.Limit != null) + if (headers.Limit != null) Limit = headers.Limit.Value; if (headers.Remaining != null && !_hasReceivedHeaders) @@ -98,7 +98,7 @@ namespace Myriad.Rest.Ratelimit var oldRemaining = Remaining; Remaining = Math.Min(headers.Remaining.Value, Remaining); - _logger.Debug("{BucketKey}/{BucketMajor}: Received first remaining of {HeaderRemaining}, previous local remaining is {LocalRemaining}, new local remaining is {Remaining}", + _logger.Debug("{BucketKey}/{BucketMajor}: Received first remaining of {HeaderRemaining}, previous local remaining is {LocalRemaining}, new local remaining is {Remaining}", Key, Major, headers.Remaining.Value, oldRemaining, Remaining); _hasReceivedHeaders = true; } @@ -114,7 +114,7 @@ namespace Myriad.Rest.Ratelimit try { _semaphore.Wait(); - + // If we don't have any reset data, "snap" it to now // This happens before first request and at this point the reset is invalid anyway, so it's fine // but it ensures the stale timeout doesn't trigger early by using `default` value diff --git a/Myriad/Rest/Ratelimit/BucketManager.cs b/Myriad/Rest/Ratelimit/BucketManager.cs index 0f8ccf0e..9a465c6d 100644 --- a/Myriad/Rest/Ratelimit/BucketManager.cs +++ b/Myriad/Rest/Ratelimit/BucketManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; @@ -72,7 +72,7 @@ namespace Myriad.Rest.Ratelimit { if (now - bucket.LastUsed <= StaleBucketTimeout) continue; - + _logger.Debug("Pruning unused bucket {BucketKey}/{BucketMajor} (last used at {BucketLastUsed})", bucket.Key, bucket.Major, bucket.LastUsed); _buckets.TryRemove(key, out _); diff --git a/Myriad/Rest/Ratelimit/DiscordRateLimitPolicy.cs b/Myriad/Rest/Ratelimit/DiscordRateLimitPolicy.cs index 0ec340e1..9faaa0ad 100644 --- a/Myriad/Rest/Ratelimit/DiscordRateLimitPolicy.cs +++ b/Myriad/Rest/Ratelimit/DiscordRateLimitPolicy.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net.Http; using System.Threading; using System.Threading.Tasks; diff --git a/Myriad/Rest/Ratelimit/RatelimitHeaders.cs b/Myriad/Rest/Ratelimit/RatelimitHeaders.cs index 581b360e..70e453bb 100644 --- a/Myriad/Rest/Ratelimit/RatelimitHeaders.cs +++ b/Myriad/Rest/Ratelimit/RatelimitHeaders.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.Linq; using System.Net.Http; @@ -13,7 +13,7 @@ namespace Myriad.Rest.Ratelimit private const string ResetAfterHeader = "X-RateLimit-Reset-After"; private const string BucketHeader = "X-RateLimit-Bucket"; private const string GlobalHeader = "X-RateLimit-Global"; - + public bool Global { get; private set; } public int? Limit { get; private set; } public int? Remaining { get; private set; } @@ -25,7 +25,7 @@ namespace Myriad.Rest.Ratelimit public bool HasRatelimitInfo => Limit != null && Remaining != null && Reset != null && ResetAfter != null && Bucket != null; - + public RatelimitHeaders() { } public static RatelimitHeaders Parse(HttpResponseMessage response) @@ -41,12 +41,12 @@ namespace Myriad.Rest.Ratelimit var resetTimestamp = TryGetDouble(response, ResetHeader); if (resetTimestamp != null) - headers.Reset = DateTimeOffset.FromUnixTimeMilliseconds((long) (resetTimestamp.Value * 1000)); + headers.Reset = DateTimeOffset.FromUnixTimeMilliseconds((long)(resetTimestamp.Value * 1000)); var resetAfterSeconds = TryGetDouble(response, ResetAfterHeader); if (resetAfterSeconds != null) headers.ResetAfter = TimeSpan.FromSeconds(resetAfterSeconds.Value); - + var global = TryGetHeader(response, GlobalHeader); if (global != null && bool.TryParse(global, out var globalBool)) headers.Global = globalBool; @@ -58,27 +58,27 @@ namespace Myriad.Rest.Ratelimit { if (!response.Headers.TryGetValues(headerName, out var values)) return null; - + return values.FirstOrDefault(); } private static int? TryGetInt(HttpResponseMessage response, string headerName) { var valueString = TryGetHeader(response, headerName); - + if (!int.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) return null; - + return value; } private static double? TryGetDouble(HttpResponseMessage response, string headerName) { var valueString = TryGetHeader(response, headerName); - + if (!double.TryParse(valueString, NumberStyles.Float, CultureInfo.InvariantCulture, out var value)) return null; - + return value; } } diff --git a/Myriad/Rest/Ratelimit/Ratelimiter.cs b/Myriad/Rest/Ratelimit/Ratelimiter.cs index 089656f1..52613ba3 100644 --- a/Myriad/Rest/Ratelimit/Ratelimiter.cs +++ b/Myriad/Rest/Ratelimit/Ratelimiter.cs @@ -1,4 +1,4 @@ -using System; +using System; using Myriad.Rest.Exceptions; diff --git a/Myriad/Rest/Types/AllowedMentions.cs b/Myriad/Rest/Types/AllowedMentions.cs index 2c663ffc..cd26e7cf 100644 --- a/Myriad/Rest/Types/AllowedMentions.cs +++ b/Myriad/Rest/Types/AllowedMentions.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; using Myriad.Serialization; diff --git a/Myriad/Rest/Types/MultipartFile.cs b/Myriad/Rest/Types/MultipartFile.cs index e5a488d6..ba36375f 100644 --- a/Myriad/Rest/Types/MultipartFile.cs +++ b/Myriad/Rest/Types/MultipartFile.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; namespace Myriad.Rest.Types { diff --git a/Myriad/Rest/Types/Requests/CommandRequest.cs b/Myriad/Rest/Types/Requests/CommandRequest.cs index 3958f44b..81fb1133 100644 --- a/Myriad/Rest/Types/Requests/CommandRequest.cs +++ b/Myriad/Rest/Types/Requests/CommandRequest.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Myriad.Types; diff --git a/Myriad/Rest/Types/Requests/CreateDmRequest.cs b/Myriad/Rest/Types/Requests/CreateDmRequest.cs index f28b2fe2..2d17bc29 100644 --- a/Myriad/Rest/Types/Requests/CreateDmRequest.cs +++ b/Myriad/Rest/Types/Requests/CreateDmRequest.cs @@ -1,4 +1,4 @@ -namespace Myriad.Rest.Types.Requests +namespace Myriad.Rest.Types.Requests { public record CreateDmRequest(ulong RecipientId); } \ No newline at end of file diff --git a/Myriad/Rest/Types/Requests/CreateWebhookRequest.cs b/Myriad/Rest/Types/Requests/CreateWebhookRequest.cs index cd38f67d..5cbbf714 100644 --- a/Myriad/Rest/Types/Requests/CreateWebhookRequest.cs +++ b/Myriad/Rest/Types/Requests/CreateWebhookRequest.cs @@ -1,4 +1,4 @@ -namespace Myriad.Rest.Types.Requests +namespace Myriad.Rest.Types.Requests { public record CreateWebhookRequest(string Name); } \ No newline at end of file diff --git a/Myriad/Rest/Types/Requests/ExecuteWebhookRequest.cs b/Myriad/Rest/Types/Requests/ExecuteWebhookRequest.cs index dcd19a35..c4733fa8 100644 --- a/Myriad/Rest/Types/Requests/ExecuteWebhookRequest.cs +++ b/Myriad/Rest/Types/Requests/ExecuteWebhookRequest.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; namespace Myriad.Rest.Types.Requests { diff --git a/Myriad/Rest/Types/Requests/MessageEditRequest.cs b/Myriad/Rest/Types/Requests/MessageEditRequest.cs index 3e74f7f9..93f88bb6 100644 --- a/Myriad/Rest/Types/Requests/MessageEditRequest.cs +++ b/Myriad/Rest/Types/Requests/MessageEditRequest.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; using Myriad.Types; using Myriad.Utils; @@ -8,14 +8,14 @@ namespace Myriad.Rest.Types.Requests public record MessageEditRequest { [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public Optional Content { get; init; } - + public Optional Content { get; init; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public Optional Embed { get; init; } - + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public Optional Flags { get; init; } - + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public Optional AllowedMentions { get; init; } diff --git a/Myriad/Rest/Types/Requests/MessageRequest.cs b/Myriad/Rest/Types/Requests/MessageRequest.cs index 57f49e24..0ea8ddc4 100644 --- a/Myriad/Rest/Types/Requests/MessageRequest.cs +++ b/Myriad/Rest/Types/Requests/MessageRequest.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; namespace Myriad.Rest.Types.Requests { diff --git a/Myriad/Rest/Types/Requests/ModifyGuildMemberRequest.cs b/Myriad/Rest/Types/Requests/ModifyGuildMemberRequest.cs index 6fcd8fc0..a843c755 100644 --- a/Myriad/Rest/Types/Requests/ModifyGuildMemberRequest.cs +++ b/Myriad/Rest/Types/Requests/ModifyGuildMemberRequest.cs @@ -1,4 +1,4 @@ -namespace Myriad.Rest.Types +namespace Myriad.Rest.Types { public record ModifyGuildMemberRequest { diff --git a/Myriad/Rest/Types/Requests/WebhookMessageEditRequest.cs b/Myriad/Rest/Types/Requests/WebhookMessageEditRequest.cs index 039ac625..7d73e669 100644 --- a/Myriad/Rest/Types/Requests/WebhookMessageEditRequest.cs +++ b/Myriad/Rest/Types/Requests/WebhookMessageEditRequest.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; using Myriad.Utils; @@ -7,8 +7,8 @@ namespace Myriad.Rest.Types.Requests public record WebhookMessageEditRequest { [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public Optional Content { get; init; } - + public Optional Content { get; init; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public Optional AllowedMentions { get; init; } } diff --git a/Myriad/Serialization/JsonSerializerOptionsExtensions.cs b/Myriad/Serialization/JsonSerializerOptionsExtensions.cs index 50b1192f..5d3aac26 100644 --- a/Myriad/Serialization/JsonSerializerOptionsExtensions.cs +++ b/Myriad/Serialization/JsonSerializerOptionsExtensions.cs @@ -1,4 +1,4 @@ -using System.Text.Json; +using System.Text.Json; using System.Text.Json.Serialization; namespace Myriad.Serialization diff --git a/Myriad/Serialization/JsonSnakeCaseNamingPolicy.cs b/Myriad/Serialization/JsonSnakeCaseNamingPolicy.cs index 4a09d8f0..f07e5c33 100644 --- a/Myriad/Serialization/JsonSnakeCaseNamingPolicy.cs +++ b/Myriad/Serialization/JsonSnakeCaseNamingPolicy.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Text; using System.Text.Json; diff --git a/Myriad/Serialization/JsonSnakeCaseStringEnumConverter.cs b/Myriad/Serialization/JsonSnakeCaseStringEnumConverter.cs index 5ff76513..840fc03e 100644 --- a/Myriad/Serialization/JsonSnakeCaseStringEnumConverter.cs +++ b/Myriad/Serialization/JsonSnakeCaseStringEnumConverter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/Myriad/Serialization/JsonStringConverter.cs b/Myriad/Serialization/JsonStringConverter.cs index 975da967..23b8bd6d 100644 --- a/Myriad/Serialization/JsonStringConverter.cs +++ b/Myriad/Serialization/JsonStringConverter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/Myriad/Serialization/OptionalConverter.cs b/Myriad/Serialization/OptionalConverter.cs index af7149f3..aad482fa 100644 --- a/Myriad/Serialization/OptionalConverter.cs +++ b/Myriad/Serialization/OptionalConverter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; @@ -22,11 +22,11 @@ namespace Myriad.Serialization JsonSerializer.Serialize(writer, value.HasValue ? value.GetValue() : default, typeof(T), options); } } - + public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) { var innerType = typeToConvert.GetGenericArguments()[0]; - return (JsonConverter?) Activator.CreateInstance( + return (JsonConverter?)Activator.CreateInstance( typeof(Inner<>).MakeGenericType(innerType), BindingFlags.Instance | BindingFlags.Public, null, diff --git a/Myriad/Serialization/PermissionSetJsonConverter.cs b/Myriad/Serialization/PermissionSetJsonConverter.cs index 02fc313b..91dc9bed 100644 --- a/Myriad/Serialization/PermissionSetJsonConverter.cs +++ b/Myriad/Serialization/PermissionSetJsonConverter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Text.Json; using System.Text.Json.Serialization; @@ -13,12 +13,12 @@ namespace Myriad.Serialization var str = reader.GetString(); if (str == null) return default; - return (PermissionSet) ulong.Parse(str); + return (PermissionSet)ulong.Parse(str); } public override void Write(Utf8JsonWriter writer, PermissionSet value, JsonSerializerOptions options) { - writer.WriteStringValue(((ulong) value).ToString()); + writer.WriteStringValue(((ulong)value).ToString()); } } } \ No newline at end of file diff --git a/Myriad/Serialization/ShardInfoJsonConverter.cs b/Myriad/Serialization/ShardInfoJsonConverter.cs index a504d1b3..c2d58281 100644 --- a/Myriad/Serialization/ShardInfoJsonConverter.cs +++ b/Myriad/Serialization/ShardInfoJsonConverter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/Myriad/Types/Activity.cs b/Myriad/Types/Activity.cs index 261a6c66..e5977c8c 100644 --- a/Myriad/Types/Activity.cs +++ b/Myriad/Types/Activity.cs @@ -1,16 +1,16 @@ -namespace Myriad.Types +namespace Myriad.Types { public record Activity: ActivityPartial { } - + public record ActivityPartial { public string Name { get; init; } public ActivityType Type { get; init; } public string? Url { get; init; } } - + public enum ActivityType { Game = 0, diff --git a/Myriad/Types/Application/Application.cs b/Myriad/Types/Application/Application.cs index e277e946..8e4204f6 100644 --- a/Myriad/Types/Application/Application.cs +++ b/Myriad/Types/Application/Application.cs @@ -1,4 +1,4 @@ -namespace Myriad.Types +namespace Myriad.Types { public record Application: ApplicationPartial { @@ -16,7 +16,7 @@ public string? Slug { get; init; } public string? CoverImage { get; init; } } - + public record ApplicationPartial { public ulong Id { get; init; } diff --git a/Myriad/Types/Application/ApplicationCommand.cs b/Myriad/Types/Application/ApplicationCommand.cs index 53f88dd6..af12b02f 100644 --- a/Myriad/Types/Application/ApplicationCommand.cs +++ b/Myriad/Types/Application/ApplicationCommand.cs @@ -1,4 +1,4 @@ -namespace Myriad.Types +namespace Myriad.Types { public record ApplicationCommand { diff --git a/Myriad/Types/Application/ApplicationCommandInteractionData.cs b/Myriad/Types/Application/ApplicationCommandInteractionData.cs index 768f9e85..7936f94a 100644 --- a/Myriad/Types/Application/ApplicationCommandInteractionData.cs +++ b/Myriad/Types/Application/ApplicationCommandInteractionData.cs @@ -1,4 +1,4 @@ -namespace Myriad.Types +namespace Myriad.Types { public record ApplicationCommandInteractionData { diff --git a/Myriad/Types/Application/ApplicationCommandInteractionDataOption.cs b/Myriad/Types/Application/ApplicationCommandInteractionDataOption.cs index 0f5a2730..1ca3c6c8 100644 --- a/Myriad/Types/Application/ApplicationCommandInteractionDataOption.cs +++ b/Myriad/Types/Application/ApplicationCommandInteractionDataOption.cs @@ -1,4 +1,4 @@ -namespace Myriad.Types +namespace Myriad.Types { public record ApplicationCommandInteractionDataOption { diff --git a/Myriad/Types/Application/ApplicationCommandOption.cs b/Myriad/Types/Application/ApplicationCommandOption.cs index 09ba945f..d99649e3 100644 --- a/Myriad/Types/Application/ApplicationCommandOption.cs +++ b/Myriad/Types/Application/ApplicationCommandOption.cs @@ -1,4 +1,4 @@ -namespace Myriad.Types +namespace Myriad.Types { public record ApplicationCommandOption(ApplicationCommandOption.OptionType Type, string Name, string Description) { diff --git a/Myriad/Types/Application/Interaction.cs b/Myriad/Types/Application/Interaction.cs index 237a9540..06c5614e 100644 --- a/Myriad/Types/Application/Interaction.cs +++ b/Myriad/Types/Application/Interaction.cs @@ -1,4 +1,4 @@ -namespace Myriad.Types +namespace Myriad.Types { public record Interaction { diff --git a/Myriad/Types/Application/InteractionApplicationCommandCallbackData.cs b/Myriad/Types/Application/InteractionApplicationCommandCallbackData.cs index 68d46aa3..894559f0 100644 --- a/Myriad/Types/Application/InteractionApplicationCommandCallbackData.cs +++ b/Myriad/Types/Application/InteractionApplicationCommandCallbackData.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; using Myriad.Rest.Types; using Myriad.Utils; @@ -9,19 +9,19 @@ namespace Myriad.Types { [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public Optional Tts { get; init; } - + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public Optional Content { get; init; } - + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public Optional Embeds { get; init; } - + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public Optional AllowedMentions { get; init; } - + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public Optional Flags { get; init; } - + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public Optional Components { get; init; } } diff --git a/Myriad/Types/Application/InteractionResponse.cs b/Myriad/Types/Application/InteractionResponse.cs index 9f07e3ec..1b54e3f0 100644 --- a/Myriad/Types/Application/InteractionResponse.cs +++ b/Myriad/Types/Application/InteractionResponse.cs @@ -1,4 +1,4 @@ -namespace Myriad.Types +namespace Myriad.Types { public record InteractionResponse { diff --git a/Myriad/Types/Channel.cs b/Myriad/Types/Channel.cs index e58ec59c..0c9758db 100644 --- a/Myriad/Types/Channel.cs +++ b/Myriad/Types/Channel.cs @@ -1,4 +1,4 @@ -namespace Myriad.Types +namespace Myriad.Types { public record Channel { diff --git a/Myriad/Types/Embed.cs b/Myriad/Types/Embed.cs index 7aa09e17..230d55d0 100644 --- a/Myriad/Types/Embed.cs +++ b/Myriad/Types/Embed.cs @@ -1,4 +1,4 @@ -namespace Myriad.Types +namespace Myriad.Types { public record Embed { @@ -16,44 +16,44 @@ public EmbedAuthor? Author { get; init; } public Field[]? Fields { get; init; } - public record EmbedFooter ( + public record EmbedFooter( string Text, string? IconUrl = null, string? ProxyIconUrl = null ); - public record EmbedImage ( + public record EmbedImage( string? Url, uint? Width = null, uint? Height = null ); - public record EmbedThumbnail ( + public record EmbedThumbnail( string? Url, string? ProxyUrl = null, uint? Width = null, uint? Height = null ); - public record EmbedVideo ( + public record EmbedVideo( string? Url, uint? Width = null, uint? Height = null ); - public record EmbedProvider ( + public record EmbedProvider( string? Name, string? Url ); - public record EmbedAuthor ( + public record EmbedAuthor( string? Name = null, string? Url = null, string? IconUrl = null, string? ProxyIconUrl = null ); - public record Field ( + public record Field( string Name, string Value, bool Inline = false diff --git a/Myriad/Types/Emoji.cs b/Myriad/Types/Emoji.cs index 415d42a1..9ce76381 100644 --- a/Myriad/Types/Emoji.cs +++ b/Myriad/Types/Emoji.cs @@ -1,4 +1,4 @@ -namespace Myriad.Types +namespace Myriad.Types { public record Emoji { diff --git a/Myriad/Types/Gateway/GatewayInfo.cs b/Myriad/Types/Gateway/GatewayInfo.cs index 055681cb..2f3ff2c1 100644 --- a/Myriad/Types/Gateway/GatewayInfo.cs +++ b/Myriad/Types/Gateway/GatewayInfo.cs @@ -1,9 +1,9 @@ -namespace Myriad.Types +namespace Myriad.Types { public record GatewayInfo { public string Url { get; init; } - + public record Bot: GatewayInfo { public int Shards { get; init; } diff --git a/Myriad/Types/Gateway/SessionStartLimit.cs b/Myriad/Types/Gateway/SessionStartLimit.cs index b5da4770..954d695e 100644 --- a/Myriad/Types/Gateway/SessionStartLimit.cs +++ b/Myriad/Types/Gateway/SessionStartLimit.cs @@ -1,4 +1,4 @@ -namespace Myriad.Types +namespace Myriad.Types { public record SessionStartLimit { diff --git a/Myriad/Types/Guild.cs b/Myriad/Types/Guild.cs index 5bb0abf8..fe39d0ff 100644 --- a/Myriad/Types/Guild.cs +++ b/Myriad/Types/Guild.cs @@ -1,4 +1,4 @@ -namespace Myriad.Types +namespace Myriad.Types { public record Guild { diff --git a/Myriad/Types/GuildMember.cs b/Myriad/Types/GuildMember.cs index e47578a2..0e610f24 100644 --- a/Myriad/Types/GuildMember.cs +++ b/Myriad/Types/GuildMember.cs @@ -1,10 +1,10 @@ -namespace Myriad.Types +namespace Myriad.Types { public record GuildMember: GuildMemberPartial { public User User { get; init; } } - + public record GuildMemberPartial { public string? Avatar { get; init; } diff --git a/Myriad/Types/Message.cs b/Myriad/Types/Message.cs index 101d37f3..7d95dd66 100644 --- a/Myriad/Types/Message.cs +++ b/Myriad/Types/Message.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Text.Json.Serialization; using Myriad.Utils; @@ -61,7 +61,7 @@ namespace Myriad.Types public MessageType Type { get; init; } public Reference? MessageReference { get; set; } public MessageFlags Flags { get; init; } - + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public Optional ReferencedMessage { get; init; } public MessageComponent[]? Components { get; init; } diff --git a/Myriad/Types/PermissionSet.cs b/Myriad/Types/PermissionSet.cs index ce4c3a06..036aeb2e 100644 --- a/Myriad/Types/PermissionSet.cs +++ b/Myriad/Types/PermissionSet.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Myriad.Types { diff --git a/Myriad/Types/Permissions.cs b/Myriad/Types/Permissions.cs index 423b7ae8..c0f602a7 100644 --- a/Myriad/Types/Permissions.cs +++ b/Myriad/Types/Permissions.cs @@ -1,4 +1,4 @@ -namespace Myriad.Types +namespace Myriad.Types { public static class Permissions { diff --git a/Myriad/Types/Role.cs b/Myriad/Types/Role.cs index 2e77f2ac..215dc320 100644 --- a/Myriad/Types/Role.cs +++ b/Myriad/Types/Role.cs @@ -1,4 +1,4 @@ -namespace Myriad.Types +namespace Myriad.Types { public record Role { diff --git a/Myriad/Types/User.cs b/Myriad/Types/User.cs index ad6a4795..81426213 100644 --- a/Myriad/Types/User.cs +++ b/Myriad/Types/User.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Myriad.Types { diff --git a/Myriad/Types/Webhook.cs b/Myriad/Types/Webhook.cs index d5ac3c03..4b16906f 100644 --- a/Myriad/Types/Webhook.cs +++ b/Myriad/Types/Webhook.cs @@ -1,4 +1,4 @@ -namespace Myriad.Types +namespace Myriad.Types { public record Webhook { diff --git a/Myriad/Utils/Optional.cs b/Myriad/Utils/Optional.cs index 881a53ce..7742e4f9 100644 --- a/Myriad/Utils/Optional.cs +++ b/Myriad/Utils/Optional.cs @@ -1,10 +1,10 @@ -namespace Myriad.Utils +namespace Myriad.Utils { public interface IOptional { object? GetValue(); } - + public readonly struct Optional: IOptional { public Optional(T value) diff --git a/PluralKit.API/ApiConfig.cs b/PluralKit.API/ApiConfig.cs index 0568fe54..6a7825e4 100644 --- a/PluralKit.API/ApiConfig.cs +++ b/PluralKit.API/ApiConfig.cs @@ -1,4 +1,4 @@ -namespace PluralKit.API +namespace PluralKit.API { public class ApiConfig { diff --git a/PluralKit.API/Authentication/AuthExt.cs b/PluralKit.API/Authentication/AuthExt.cs index 1d259eec..88389524 100644 --- a/PluralKit.API/Authentication/AuthExt.cs +++ b/PluralKit.API/Authentication/AuthExt.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Security.Claims; using PluralKit.Core; @@ -11,12 +11,12 @@ namespace PluralKit.API { var claim = user.FindFirst(PKClaims.SystemId); if (claim == null) throw new ArgumentException("User is unauthorized"); - + if (int.TryParse(claim.Value, out var id)) return new SystemId(id); throw new ArgumentException("User has non-integer system ID claim"); } - + public static LookupContext ContextFor(this ClaimsPrincipal user, PKSystem system) { if (!user.Identity.IsAuthenticated) return LookupContext.API; diff --git a/PluralKit.API/Authentication/PKClaims.cs b/PluralKit.API/Authentication/PKClaims.cs index 2ab31e1a..1991e3ba 100644 --- a/PluralKit.API/Authentication/PKClaims.cs +++ b/PluralKit.API/Authentication/PKClaims.cs @@ -1,4 +1,4 @@ -namespace PluralKit.API +namespace PluralKit.API { public class PKClaims { diff --git a/PluralKit.API/Authentication/SystemTokenAuthenticationHandler.cs b/PluralKit.API/Authentication/SystemTokenAuthenticationHandler.cs index 8080f60a..c843eb7e 100644 --- a/PluralKit.API/Authentication/SystemTokenAuthenticationHandler.cs +++ b/PluralKit.API/Authentication/SystemTokenAuthenticationHandler.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Security.Claims; using System.Text.Encodings.Web; @@ -17,8 +17,8 @@ namespace PluralKit.API public class SystemTokenAuthenticationHandler: AuthenticationHandler { private readonly IDatabase _db; - - public SystemTokenAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IDatabase db): base(options, logger, encoder, clock) + + public SystemTokenAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IDatabase db) : base(options, logger, encoder, clock) { _db = db; } @@ -32,7 +32,7 @@ namespace PluralKit.API var systemId = await _db.Execute(c => c.QuerySingleOrDefaultAsync("select id from systems where token = @token", new { token })); if (systemId == null) return AuthenticateResult.Fail("Invalid system token"); - var claims = new[] {new Claim(PKClaims.SystemId, systemId.Value.Value.ToString())}; + var claims = new[] { new Claim(PKClaims.SystemId, systemId.Value.Value.ToString()) }; var identity = new ClaimsIdentity(claims, Scheme.Name); var principal = new ClaimsPrincipal(identity); var ticket = new AuthenticationTicket(principal, Scheme.Name); @@ -43,7 +43,7 @@ namespace PluralKit.API public class Opts: AuthenticationSchemeOptions { - + } } } \ No newline at end of file diff --git a/PluralKit.API/Authorization/MemberOwnerHandler.cs b/PluralKit.API/Authorization/MemberOwnerHandler.cs index a212ad2c..8a524352 100644 --- a/PluralKit.API/Authorization/MemberOwnerHandler.cs +++ b/PluralKit.API/Authorization/MemberOwnerHandler.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; @@ -6,7 +6,8 @@ using PluralKit.Core; namespace PluralKit.API { - public class MemberOwnerHandler: AuthorizationHandler { + public class MemberOwnerHandler: AuthorizationHandler + { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, OwnSystemRequirement requirement, PKMember resource) { diff --git a/PluralKit.API/Authorization/MemberPrivacyHandler.cs b/PluralKit.API/Authorization/MemberPrivacyHandler.cs index 41437ed6..32872d3d 100644 --- a/PluralKit.API/Authorization/MemberPrivacyHandler.cs +++ b/PluralKit.API/Authorization/MemberPrivacyHandler.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; diff --git a/PluralKit.API/Authorization/OwnSystemRequirement.cs b/PluralKit.API/Authorization/OwnSystemRequirement.cs index e292db75..036d7d83 100644 --- a/PluralKit.API/Authorization/OwnSystemRequirement.cs +++ b/PluralKit.API/Authorization/OwnSystemRequirement.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; namespace PluralKit.API { diff --git a/PluralKit.API/Authorization/PrivacyRequirement.cs b/PluralKit.API/Authorization/PrivacyRequirement.cs index ef9312e1..32e7b5ff 100644 --- a/PluralKit.API/Authorization/PrivacyRequirement.cs +++ b/PluralKit.API/Authorization/PrivacyRequirement.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.AspNetCore.Authorization; @@ -9,7 +9,7 @@ namespace PluralKit.API public class PrivacyRequirement: IAuthorizationRequirement { public readonly Func Mapper; - + public PrivacyRequirement(Func mapper) { Mapper = mapper; diff --git a/PluralKit.API/Authorization/SystemOwnerHandler.cs b/PluralKit.API/Authorization/SystemOwnerHandler.cs index 72cfede7..03e4cb7a 100644 --- a/PluralKit.API/Authorization/SystemOwnerHandler.cs +++ b/PluralKit.API/Authorization/SystemOwnerHandler.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; @@ -12,7 +12,7 @@ namespace PluralKit.API OwnSystemRequirement requirement, PKSystem resource) { if (!context.User.Identity.IsAuthenticated) return Task.CompletedTask; - if (resource.Id == context.User.CurrentSystem()) + if (resource.Id == context.User.CurrentSystem()) context.Succeed(requirement); return Task.CompletedTask; } diff --git a/PluralKit.API/Authorization/SystemPrivacyHandler.cs b/PluralKit.API/Authorization/SystemPrivacyHandler.cs index 469324b1..5661dc7e 100644 --- a/PluralKit.API/Authorization/SystemPrivacyHandler.cs +++ b/PluralKit.API/Authorization/SystemPrivacyHandler.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; diff --git a/PluralKit.API/Controllers/v1/AccountController.cs b/PluralKit.API/Controllers/v1/AccountController.cs index edb3114a..948e6dbf 100644 --- a/PluralKit.API/Controllers/v1/AccountController.cs +++ b/PluralKit.API/Controllers/v1/AccountController.cs @@ -10,7 +10,7 @@ namespace PluralKit.API { [ApiController] [ApiVersion("1.0")] - [Route( "v{version:apiVersion}/a" )] + [Route("v{version:apiVersion}/a")] public class AccountController: ControllerBase { private readonly IDatabase _db; @@ -27,7 +27,7 @@ namespace PluralKit.API var system = await _db.Execute(c => _repo.GetSystemByAccount(c, aid)); if (system == null) return NotFound("Account not found."); - + return Ok(system.ToJson(User.ContextFor(system))); } } diff --git a/PluralKit.API/Controllers/v1/MemberController.cs b/PluralKit.API/Controllers/v1/MemberController.cs index 8052120e..050c8674 100644 --- a/PluralKit.API/Controllers/v1/MemberController.cs +++ b/PluralKit.API/Controllers/v1/MemberController.cs @@ -14,7 +14,7 @@ namespace PluralKit.API { [ApiController] [ApiVersion("1.0")] - [Route( "v{version:apiVersion}/m" )] + [Route("v{version:apiVersion}/m")] public class MemberController: ControllerBase { private readonly IDatabase _db; @@ -43,14 +43,14 @@ namespace PluralKit.API { if (!properties.ContainsKey("name")) return BadRequest("Member name must be specified."); - + var systemId = User.CurrentSystem(); await using var conn = await _db.Obtain(); var systemData = await _repo.GetSystem(conn, systemId); // Enforce per-system member limit - var memberCount = await conn.QuerySingleAsync("select count(*) from members where system = @System", new {System = systemId}); + var memberCount = await conn.QuerySingleAsync("select count(*) from members where system = @System", new { System = systemId }); var memberLimit = systemData?.MemberLimitOverride ?? Limits.MaxMemberCount; if (memberCount >= memberLimit) return BadRequest($"Member limit reached ({memberCount} / {memberLimit})."); @@ -74,7 +74,7 @@ namespace PluralKit.API await tx.RollbackAsync(); return BadRequest($"Request field '{e.Message}' is invalid."); } - + member = await _repo.UpdateMember(conn, member.Id, patch, transaction: tx); await tx.CommitAsync(); return Ok(member.ToJson(User.ContextFor(member), needsLegacyProxyTags: true)); @@ -88,7 +88,7 @@ namespace PluralKit.API var member = await _repo.GetMemberByHid(conn, hid); if (member == null) return NotFound("Member not found."); - + var res = await _auth.AuthorizeAsync(User, member, "EditMember"); if (!res.Succeeded) return Unauthorized($"Member '{hid}' is not part of your system."); @@ -106,11 +106,11 @@ namespace PluralKit.API { return BadRequest($"Request field '{e.Message}' is invalid."); } - + var newMember = await _repo.UpdateMember(conn, member.Id, patch); return Ok(newMember.ToJson(User.ContextFor(newMember), needsLegacyProxyTags: true)); } - + [HttpDelete("{hid}")] [Authorize] public async Task DeleteMember(string hid) @@ -119,7 +119,7 @@ namespace PluralKit.API var member = await _repo.GetMemberByHid(conn, hid); if (member == null) return NotFound("Member not found."); - + var res = await _auth.AuthorizeAsync(User, member, "EditMember"); if (!res.Succeeded) return Unauthorized($"Member '{hid}' is not part of your system."); @@ -127,4 +127,4 @@ namespace PluralKit.API return Ok(); } } -} +} \ No newline at end of file diff --git a/PluralKit.API/Controllers/v1/MessageController.cs b/PluralKit.API/Controllers/v1/MessageController.cs index a5f09ce1..bc5ad4fe 100644 --- a/PluralKit.API/Controllers/v1/MessageController.cs +++ b/PluralKit.API/Controllers/v1/MessageController.cs @@ -22,10 +22,10 @@ namespace PluralKit.API [JsonProperty("system")] public JObject System; [JsonProperty("member")] public JObject Member; } - + [ApiController] [ApiVersion("1.0")] - [Route( "v{version:apiVersion}/msg" )] + [Route("v{version:apiVersion}/msg")] public class MessageController: ControllerBase { private readonly IDatabase _db; @@ -45,7 +45,7 @@ namespace PluralKit.API return new MessageReturn { - Timestamp = Instant.FromUnixTimeMilliseconds((long) (msg.Message.Mid >> 22) + 1420070400000), + Timestamp = Instant.FromUnixTimeMilliseconds((long)(msg.Message.Mid >> 22) + 1420070400000), Id = msg.Message.Mid.ToString(), Channel = msg.Message.Channel.ToString(), Sender = msg.Message.Sender.ToString(), @@ -53,6 +53,6 @@ namespace PluralKit.API System = msg.System.ToJson(User.ContextFor(msg.System)), Original = msg.Message.OriginalMid?.ToString() }; - } + } } } \ No newline at end of file diff --git a/PluralKit.API/Controllers/v1/MetaController.cs b/PluralKit.API/Controllers/v1/MetaController.cs index bad8b396..098bd8a8 100644 --- a/PluralKit.API/Controllers/v1/MetaController.cs +++ b/PluralKit.API/Controllers/v1/MetaController.cs @@ -13,8 +13,8 @@ namespace PluralKit.API { [ApiController] [ApiVersion("1.0")] - [Route( "v{version:apiVersion}" )] - public class MetaController : ControllerBase + [Route("v{version:apiVersion}")] + public class MetaController: ControllerBase { private readonly IDatabase _db; private readonly ModelRepository _repo; @@ -33,7 +33,7 @@ namespace PluralKit.API var o = new JObject(); o.Add("shards", shards.ToJSON()); o.Add("version", BuildInfoService.Version); - + return Ok(o); } } @@ -53,7 +53,7 @@ namespace PluralKit.API s.Add("status", "down"); else s.Add("status", "up"); - + s.Add("ping", shard.Ping); s.Add("last_heartbeat", shard.LastHeartbeat.ToString()); s.Add("last_connection", shard.LastConnection.ToString()); diff --git a/PluralKit.API/Controllers/v1/SystemController.cs b/PluralKit.API/Controllers/v1/SystemController.cs index fd82551d..496c402d 100644 --- a/PluralKit.API/Controllers/v1/SystemController.cs +++ b/PluralKit.API/Controllers/v1/SystemController.cs @@ -37,8 +37,8 @@ namespace PluralKit.API [ApiController] [ApiVersion("1.0")] - [Route( "v{version:apiVersion}/s" )] - public class SystemController : ControllerBase + [Route("v{version:apiVersion}/s")] + public class SystemController: ControllerBase { private readonly IDatabase _db; private readonly ModelRepository _repo; @@ -88,9 +88,9 @@ namespace PluralKit.API public async Task>> GetSwitches(string hid, [FromQuery(Name = "before")] Instant? before) { if (before == null) before = SystemClock.Instance.GetCurrentInstant(); - + await using var conn = await _db.Obtain(); - + var system = await _repo.GetSystemByHid(conn, hid); if (system == null) return NotFound("System not found."); @@ -104,7 +104,7 @@ namespace PluralKit.API ) as members from switches where switches.system = @System and switches.timestamp < @Before order by switches.timestamp desc - limit 100;", new {System = system.Id, Before = before}); + limit 100;", new { System = system.Id, Before = before }); return Ok(res); } @@ -112,16 +112,16 @@ namespace PluralKit.API public async Task> GetFronters(string hid) { await using var conn = await _db.Obtain(); - + var system = await _repo.GetSystemByHid(conn, hid); if (system == null) return NotFound("System not found."); - + var auth = await _auth.AuthorizeAsync(User, system, "ViewFront"); if (!auth.Succeeded) return StatusCode(StatusCodes.Status403Forbidden, "Unauthorized to view fronter."); - + var sw = await _repo.GetLatestSwitch(conn, system.Id); - if (sw == null) return NotFound("System has no registered switches."); - + if (sw == null) return NotFound("System has no registered switches."); + var members = _repo.GetSwitchMembers(conn, sw.Id); return Ok(new FrontersReturn { @@ -162,7 +162,7 @@ namespace PluralKit.API { if (param.Members.Distinct().Count() != param.Members.Count) return BadRequest("Duplicate members in member list."); - + await using var conn = await _db.Obtain(); // We get the current switch, if it exists @@ -177,8 +177,8 @@ namespace PluralKit.API } // Resolve member objects for all given IDs - var membersList = (await conn.QueryAsync("select * from members where hid = any(@Hids)", new {Hids = param.Members})).ToList(); - + var membersList = (await conn.QueryAsync("select * from members where hid = any(@Hids)", new { Hids = param.Members })).ToList(); + foreach (var member in membersList) if (member.System != User.CurrentSystem()) return BadRequest($"Cannot switch to member '{member.Hid}' not in system."); @@ -186,12 +186,12 @@ namespace PluralKit.API // membersList is in DB order, and we want it in actual input order // so we go through a dict and map the original input appropriately var membersDict = membersList.ToDictionary(m => m.Hid); - + var membersInOrder = new List(); // We do this without .Select() since we want to have the early return bail if it doesn't find the member foreach (var givenMemberId in param.Members) { - if (!membersDict.TryGetValue(givenMemberId, out var member)) + if (!membersDict.TryGetValue(givenMemberId, out var member)) return BadRequest($"Member '{givenMemberId}' not found."); membersInOrder.Add(member); } diff --git a/PluralKit.API/Program.cs b/PluralKit.API/Program.cs index 67860041..a4f3c2ed 100644 --- a/PluralKit.API/Program.cs +++ b/PluralKit.API/Program.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Autofac.Extensions.DependencyInjection; diff --git a/PluralKit.API/Startup.cs b/PluralKit.API/Startup.cs index 0549b312..db755045 100644 --- a/PluralKit.API/Startup.cs +++ b/PluralKit.API/Startup.cs @@ -34,12 +34,12 @@ namespace PluralKit.API services.AddCors(); services.AddAuthentication("SystemToken") .AddScheme("SystemToken", null); - + services.AddAuthorization(options => { options.AddPolicy("EditSystem", p => p.RequireAuthenticatedUser().AddRequirements(new OwnSystemRequirement())); options.AddPolicy("EditMember", p => p.RequireAuthenticatedUser().AddRequirements(new OwnSystemRequirement())); - + options.AddPolicy("ViewMembers", p => p.AddRequirements(new PrivacyRequirement(s => s.MemberListPrivacy))); options.AddPolicy("ViewFront", p => p.AddRequirements(new PrivacyRequirement(s => s.FrontPrivacy))); options.AddPolicy("ViewFrontHistory", p => p.AddRequirements(new PrivacyRequirement(s => s.FrontHistoryPrivacy))); @@ -48,35 +48,35 @@ namespace PluralKit.API services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - + services.AddControllers() .SetCompatibilityVersion(CompatibilityVersion.Latest) .AddNewtonsoftJson(); // sorry MS, this just does *more* services.AddApiVersioning(); - + services.AddVersionedApiExplorer(c => { c.GroupNameFormat = "'v'VV"; c.ApiVersionParameterSource = new UrlSegmentApiVersionReader(); c.SubstituteApiVersionInUrl = true; }); - + services.AddSwaggerGen(c => { - c.SwaggerDoc("v1.0", new OpenApiInfo {Title = "PluralKit", Version = "1.0"}); - + c.SwaggerDoc("v1.0", new OpenApiInfo { Title = "PluralKit", Version = "1.0" }); + c.EnableAnnotations(); c.AddSecurityDefinition("TokenAuth", - new OpenApiSecurityScheme {Name = "Authorization", Type = SecuritySchemeType.ApiKey}); - + new OpenApiSecurityScheme { Name = "Authorization", Type = SecuritySchemeType.ApiKey }); + // Exclude routes without a version, then fall back to group name matching (default behavior) c.DocInclusionPredicate((docName, apiDesc) => { if (!apiDesc.RelativePath.StartsWith("v1/")) return false; return apiDesc.GroupName == docName; }); - + // Set the comments path for the Swagger JSON and UI. // https://docs.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-3.1&tabs=visual-studio#customize-and-extend var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; @@ -103,7 +103,7 @@ namespace PluralKit.API if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); - + // Only enable Swagger stuff when ASPNETCORE_ENVIRONMENT=Development (for now) app.UseSwagger(); app.UseSwaggerUI(c => @@ -119,7 +119,7 @@ namespace PluralKit.API //app.UseHttpsRedirection(); app.UseCors(opts => opts.AllowAnyMethod().AllowAnyOrigin().WithHeaders("Content-Type", "Authorization")); - + app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); diff --git a/PluralKit.Bot/Bot.cs b/PluralKit.Bot/Bot.cs index 64e731b0..f5297c91 100644 --- a/PluralKit.Bot/Bot.cs +++ b/PluralKit.Bot/Bot.cs @@ -30,7 +30,7 @@ namespace PluralKit.Bot public class Bot { private readonly ConcurrentDictionary _guildMembers = new(); - + private readonly Cluster _cluster; private readonly DiscordApiClient _rest; private readonly ILogger _logger; @@ -44,7 +44,7 @@ namespace PluralKit.Bot private bool _hasReceivedReady = false; private Timer _periodicTask; // Never read, just kept here for GC reasons - public Bot(ILifetimeScope services, ILogger logger, PeriodicStatCollector collector, IMetrics metrics, + public Bot(ILifetimeScope services, ILogger logger, PeriodicStatCollector collector, IMetrics metrics, ErrorMessageService errorMessageService, CommandMessageService commandMessageService, Cluster cluster, DiscordApiClient rest, IDiscordCache cache) { _logger = logger.ForContext(); @@ -61,7 +61,7 @@ namespace PluralKit.Bot public void Init() { _cluster.EventReceived += OnEventReceived; - + // Init the shard stuff _services.Resolve().Init(); @@ -72,7 +72,7 @@ namespace PluralKit.Bot var timeTillNextWholeMinute = TimeSpan.FromMilliseconds(60000 - timeNow.ToUnixTimeMilliseconds() % 60000 + 250); _periodicTask = new Timer(_ => { - var __ = UpdatePeriodic(); + var __ = UpdatePeriodic(); }, null, timeTillNextWholeMinute, TimeSpan.FromMinutes(1)); } @@ -88,7 +88,7 @@ namespace PluralKit.Bot return PermissionSet.Dm; } - + private async Task OnEventReceived(Shard shard, IGatewayEvent evt) { await _cache.HandleGatewayEvent(evt); @@ -158,7 +158,7 @@ namespace PluralKit.Bot { new ActivityPartial { - Name = "Restarting... (please wait)", + Name = "Restarting... (please wait)", Type = ActivityType.Game } }, @@ -167,7 +167,7 @@ namespace PluralKit.Bot } } - private Task HandleEvent(Shard shard, T evt) where T: IGatewayEvent + private Task HandleEvent(Shard shard, T evt) where T : IGatewayEvent { // We don't want to stall the event pipeline, so we'll "fork" inside here var _ = HandleEventInner(); @@ -203,13 +203,13 @@ namespace PluralKit.Bot var sentryEnricher = serviceScope.ResolveOptional>(); sentryEnricher?.Enrich(serviceScope.Resolve(), shard, evt); - using var timer = _metrics.Measure.Timer.Time(BotMetrics.EventsHandled, + using var timer = _metrics.Measure.Timer.Time(BotMetrics.EventsHandled, new MetricTags("event", typeof(T).Name.Replace("Event", ""))); // Delegate to the queue to see if it wants to handle this event // the TryHandle call returns true if it's handled the event // Usually it won't, so just pass it on to the main handler - if (queue == null || !await queue.TryHandle(evt)) + if (queue == null || !await queue.TryHandle(evt)) await handler.Handle(shard, evt); } catch (Exception exc) @@ -218,12 +218,12 @@ namespace PluralKit.Bot } } } - + private async Task HandleError(Shard shard, IEventHandler handler, T evt, ILifetimeScope serviceScope, Exception exc) - where T: IGatewayEvent + where T : IGatewayEvent { _metrics.Measure.Meter.Mark(BotMetrics.BotErrors, exc.GetType().FullName); - + // Make this beforehand so we can access the event ID for logging var sentryEvent = new SentryEvent(exc); @@ -239,7 +239,7 @@ namespace PluralKit.Bot // Report error to Sentry // This will just no-op if there's no URL set var sentryScope = serviceScope.Resolve(); - + // Add some specific info about Discord error responses, as a breadcrumb // TODO: headers to dict // if (exc is BadRequestException bre) @@ -248,7 +248,7 @@ namespace PluralKit.Bot // sentryScope.AddBreadcrumb(nfe.Response, "response.error", data: new Dictionary(nfe.Response.Headers)); // if (exc is UnauthorizedException ue) // sentryScope.AddBreadcrumb(ue.Response, "response.error", data: new Dictionary(ue.Response.Headers)); - + SentrySdk.CaptureEvent(sentryEvent, sentryScope); // Once we've sent it to Sentry, report it to the user (if we have permission to) @@ -261,7 +261,7 @@ namespace PluralKit.Bot } } } - + private async Task UpdatePeriodic() { _logger.Debug("Running once-per-minute scheduled tasks"); @@ -273,7 +273,7 @@ namespace PluralKit.Bot // Collect some stats, submit them to the metrics backend await _collector.CollectStats(); - await Task.WhenAll(((IMetricsRoot) _metrics).ReportRunner.RunAllAsync()); + await Task.WhenAll(((IMetricsRoot)_metrics).ReportRunner.RunAllAsync()); _logger.Debug("Submitted metrics to backend"); } diff --git a/PluralKit.Bot/BotConfig.cs b/PluralKit.Bot/BotConfig.cs index eeae52cd..aeb94f5f 100644 --- a/PluralKit.Bot/BotConfig.cs +++ b/PluralKit.Bot/BotConfig.cs @@ -2,22 +2,22 @@ namespace PluralKit.Bot { public class BotConfig { - public static readonly string[] DefaultPrefixes = {"pk;", "pk!"}; + public static readonly string[] DefaultPrefixes = { "pk;", "pk!" }; public string Token { get; set; } public ulong? ClientId { get; set; } - + // ASP.NET configuration merges arrays with defaults, so we leave this field nullable // and fall back to the separate default array at the use site :) // This does bind [] as null (therefore default) instead of an empty array, but I can live w/ that. public string[] Prefixes { get; set; } - + public int? MaxShardConcurrency { get; set; } - + public ulong? AdminRole { get; set; } - + public ClusterSettings? Cluster { get; set; } - + public string? GatewayQueueUrl { get; set; } public record ClusterSettings diff --git a/PluralKit.Bot/BotMetrics.cs b/PluralKit.Bot/BotMetrics.cs index 6d423ffa..20625c37 100644 --- a/PluralKit.Bot/BotMetrics.cs +++ b/PluralKit.Bot/BotMetrics.cs @@ -7,23 +7,23 @@ namespace PluralKit.Bot { public static class BotMetrics { - public static MeterOptions MessagesReceived => new MeterOptions {Name = "Messages processed", MeasurementUnit = Unit.Events, RateUnit = TimeUnit.Seconds, Context = "Bot"}; - public static MeterOptions MessagesProxied => new MeterOptions {Name = "Messages proxied", MeasurementUnit = Unit.Events, RateUnit = TimeUnit.Seconds, Context = "Bot"}; - public static MeterOptions CommandsRun => new MeterOptions {Name = "Commands run", MeasurementUnit = Unit.Commands, RateUnit = TimeUnit.Seconds, Context = "Bot"}; - public static GaugeOptions MembersTotal => new GaugeOptions {Name = "Members total", MeasurementUnit = Unit.None, Context = "Bot"}; - public static GaugeOptions MembersOnline => new GaugeOptions {Name = "Members online", MeasurementUnit = Unit.None, Context = "Bot"}; - public static GaugeOptions Guilds => new GaugeOptions {Name = "Guilds", MeasurementUnit = Unit.None, Context = "Bot"}; - public static GaugeOptions Channels => new GaugeOptions {Name = "Channels", MeasurementUnit = Unit.None, Context = "Bot"}; - public static GaugeOptions ShardLatency => new GaugeOptions { Name = "Shard Latency", Context = "Bot" }; + public static MeterOptions MessagesReceived => new MeterOptions { Name = "Messages processed", MeasurementUnit = Unit.Events, RateUnit = TimeUnit.Seconds, Context = "Bot" }; + public static MeterOptions MessagesProxied => new MeterOptions { Name = "Messages proxied", MeasurementUnit = Unit.Events, RateUnit = TimeUnit.Seconds, Context = "Bot" }; + public static MeterOptions CommandsRun => new MeterOptions { Name = "Commands run", MeasurementUnit = Unit.Commands, RateUnit = TimeUnit.Seconds, Context = "Bot" }; + public static GaugeOptions MembersTotal => new GaugeOptions { Name = "Members total", MeasurementUnit = Unit.None, Context = "Bot" }; + public static GaugeOptions MembersOnline => new GaugeOptions { Name = "Members online", MeasurementUnit = Unit.None, Context = "Bot" }; + public static GaugeOptions Guilds => new GaugeOptions { Name = "Guilds", MeasurementUnit = Unit.None, Context = "Bot" }; + public static GaugeOptions Channels => new GaugeOptions { Name = "Channels", MeasurementUnit = Unit.None, Context = "Bot" }; + public static GaugeOptions ShardLatency => new GaugeOptions { Name = "Shard Latency", Context = "Bot" }; public static GaugeOptions ShardsConnected => new GaugeOptions { Name = "Shards Connected", Context = "Bot", MeasurementUnit = Unit.Connections }; public static MeterOptions WebhookCacheMisses => new MeterOptions { Name = "Webhook cache misses", Context = "Bot", MeasurementUnit = Unit.Calls }; public static GaugeOptions WebhookCacheSize => new GaugeOptions { Name = "Webhook Cache Size", Context = "Bot", MeasurementUnit = Unit.Items }; public static TimerOptions WebhookResponseTime => new TimerOptions { Name = "Webhook Response Time", Context = "Bot", RateUnit = TimeUnit.Seconds, MeasurementUnit = Unit.Requests, DurationUnit = TimeUnit.Seconds }; public static TimerOptions MessageContextQueryTime => new TimerOptions { Name = "Message context query duration", Context = "Bot", RateUnit = TimeUnit.Seconds, DurationUnit = TimeUnit.Seconds, MeasurementUnit = Unit.Calls }; public static TimerOptions ProxyMembersQueryTime => new TimerOptions { Name = "Proxy member query duration", Context = "Bot", RateUnit = TimeUnit.Seconds, DurationUnit = TimeUnit.Seconds, MeasurementUnit = Unit.Calls }; - public static TimerOptions DiscordApiRequests => new TimerOptions { Name = "Discord API requests", MeasurementUnit = Unit.Requests, DurationUnit = TimeUnit.Milliseconds, Context = "Bot"}; - public static MeterOptions BotErrors => new MeterOptions { Name = "Bot errors", MeasurementUnit = Unit.Errors, RateUnit = TimeUnit.Seconds, Context = "Bot"}; - public static MeterOptions ErrorMessagesSent => new MeterOptions { Name = "Error messages sent", MeasurementUnit = Unit.Errors, RateUnit = TimeUnit.Seconds, Context = "Bot"}; - public static TimerOptions EventsHandled => new TimerOptions { Name = "Events handled", MeasurementUnit = Unit.Errors, RateUnit = TimeUnit.Seconds, DurationUnit = TimeUnit.Seconds, Context = "Bot"}; + public static TimerOptions DiscordApiRequests => new TimerOptions { Name = "Discord API requests", MeasurementUnit = Unit.Requests, DurationUnit = TimeUnit.Milliseconds, Context = "Bot" }; + public static MeterOptions BotErrors => new MeterOptions { Name = "Bot errors", MeasurementUnit = Unit.Errors, RateUnit = TimeUnit.Seconds, Context = "Bot" }; + public static MeterOptions ErrorMessagesSent => new MeterOptions { Name = "Error messages sent", MeasurementUnit = Unit.Errors, RateUnit = TimeUnit.Seconds, Context = "Bot" }; + public static TimerOptions EventsHandled => new TimerOptions { Name = "Events handled", MeasurementUnit = Unit.Errors, RateUnit = TimeUnit.Seconds, DurationUnit = TimeUnit.Seconds, Context = "Bot" }; } } \ No newline at end of file diff --git a/PluralKit.Bot/CommandSystem/Command.cs b/PluralKit.Bot/CommandSystem/Command.cs index 30e6596d..a1d1061e 100644 --- a/PluralKit.Bot/CommandSystem/Command.cs +++ b/PluralKit.Bot/CommandSystem/Command.cs @@ -5,7 +5,7 @@ namespace PluralKit.Bot public string Key { get; } public string Usage { get; } public string Description { get; } - + public Command(string key, string usage, string description) { Key = key; diff --git a/PluralKit.Bot/CommandSystem/CommandGroup.cs b/PluralKit.Bot/CommandSystem/CommandGroup.cs index 62f3a060..dcad926f 100644 --- a/PluralKit.Bot/CommandSystem/CommandGroup.cs +++ b/PluralKit.Bot/CommandSystem/CommandGroup.cs @@ -6,7 +6,7 @@ namespace PluralKit.Bot { public string Key { get; } public string Description { get; } - + public ICollection Children { get; } public CommandGroup(string key, string description, ICollection children) diff --git a/PluralKit.Bot/CommandSystem/Context.cs b/PluralKit.Bot/CommandSystem/Context.cs index adcb8642..954c532b 100644 --- a/PluralKit.Bot/CommandSystem/Context.cs +++ b/PluralKit.Bot/CommandSystem/Context.cs @@ -77,7 +77,7 @@ namespace PluralKit.Bot public DiscordApiClient Rest => _rest; public PKSystem System => _senderSystem; - + public Parameters Parameters => _parameters; internal IDatabase Database => _db; @@ -109,7 +109,7 @@ namespace PluralKit.Bot return msg; } - + public async Task Execute(Command commandDef, Func handler) { _currentCommand = commandDef; @@ -134,15 +134,15 @@ namespace PluralKit.Bot } } - public LookupContext LookupContextFor(PKSystem target) => + public LookupContext LookupContextFor(PKSystem target) => System?.Id == target.Id ? LookupContext.ByOwner : LookupContext.ByNonOwner; - - public LookupContext LookupContextFor(SystemId systemId) => + + public LookupContext LookupContextFor(SystemId systemId) => System?.Id == systemId ? LookupContext.ByOwner : LookupContext.ByNonOwner; public LookupContext LookupContextFor(PKMember target) => System?.Id == target.System ? LookupContext.ByOwner : LookupContext.ByNonOwner; - + public IComponentContext Services => _provider; } } \ No newline at end of file diff --git a/PluralKit.Bot/CommandSystem/ContextArgumentsExt.cs b/PluralKit.Bot/CommandSystem/ContextArgumentsExt.cs index bc5f5960..480d2814 100644 --- a/PluralKit.Bot/CommandSystem/ContextArgumentsExt.cs +++ b/PluralKit.Bot/CommandSystem/ContextArgumentsExt.cs @@ -14,8 +14,8 @@ namespace PluralKit.Bot { public static string PopArgument(this Context ctx) => ctx.Parameters.Pop(); - - public static string PeekArgument(this Context ctx) => + + public static string PeekArgument(this Context ctx) => ctx.Parameters.Peek(); public static string RemainderOrNull(this Context ctx, bool skipFlags = true) => @@ -23,10 +23,10 @@ namespace PluralKit.Bot public static bool HasNext(this Context ctx, bool skipFlags = true) => ctx.RemainderOrNull(skipFlags) != null; - - public static string FullCommand(this Context ctx) => + + public static string FullCommand(this Context ctx) => ctx.Parameters.FullCommand; - + /// /// Checks if the next parameter is equal to one of the given keywords. Case-insensitive. /// @@ -58,7 +58,7 @@ namespace PluralKit.Bot { // Flags are *ALWAYS PARSED LOWERCASE*. This means we skip out on a "ToLower" call here. // Can assume the caller array only contains lowercase *and* the set below only contains lowercase - + var flags = ctx.Parameters.Flags(); return potentialMatches.Any(potentialMatch => flags.Contains(potentialMatch)); } @@ -66,7 +66,7 @@ namespace PluralKit.Bot public static async Task MatchClear(this Context ctx, string toClear = null) { var matched = ctx.Match("clear", "reset") || ctx.MatchFlag("c", "clear"); - if (matched && toClear != null) + if (matched && toClear != null) return await ctx.ConfirmClear(toClear); return matched; } @@ -82,11 +82,11 @@ namespace PluralKit.Bot if (parseRawMessageId && ulong.TryParse(word, out var mid)) return (mid, null); - + var match = Regex.Match(word, "https://(?:\\w+.)?discord(?:app)?.com/channels/\\d+/(\\d+)/(\\d+)"); if (!match.Success) return (null, null); - + var channelId = ulong.Parse(match.Groups[1].Value); var messageId = ulong.Parse(match.Groups[2].Value); ctx.PopArgument(); @@ -108,11 +108,11 @@ namespace PluralKit.Bot if (restrictToSystem != null && member.System != restrictToSystem) throw Errors.NotOwnMemberError; // TODO: name *which* member? - + members.Add(member); // Then add to the final output list } if (members.Count == 0) throw new PKSyntaxError($"You must input at least one member."); - + return members; } @@ -131,13 +131,13 @@ namespace PluralKit.Bot if (restrictToSystem != null && group.System != restrictToSystem) throw Errors.NotOwnGroupError; // TODO: name *which* group? - + groups.Add(group); // Then add to the final output list } - + if (groups.Count == 0) throw new PKSyntaxError($"You must input at least one group."); return groups; } } -} +} \ No newline at end of file diff --git a/PluralKit.Bot/CommandSystem/ContextChecksExt.cs b/PluralKit.Bot/CommandSystem/ContextChecksExt.cs index e09b816e..5a41908a 100644 --- a/PluralKit.Bot/CommandSystem/ContextChecksExt.cs +++ b/PluralKit.Bot/CommandSystem/ContextChecksExt.cs @@ -1,4 +1,4 @@ -using Myriad.Types; +using Myriad.Types; using PluralKit.Core; @@ -11,27 +11,27 @@ namespace PluralKit.Bot if (ctx.Channel.GuildId != null) return ctx; throw new PKError("This command can not be run in a DM."); } - + public static Context CheckSystemPrivacy(this Context ctx, PKSystem target, PrivacyLevel level) { if (level.CanAccess(ctx.LookupContextFor(target))) return ctx; throw new PKError("You do not have permission to access this information."); } - + public static Context CheckOwnMember(this Context ctx, PKMember member) { if (member.System != ctx.System?.Id) throw Errors.NotOwnMemberError; return ctx; } - + public static Context CheckOwnGroup(this Context ctx, PKGroup group) { if (group.System != ctx.System?.Id) throw Errors.NotOwnGroupError; return ctx; } - + public static Context CheckSystem(this Context ctx) { if (ctx.System == null) @@ -45,7 +45,7 @@ namespace PluralKit.Bot throw Errors.ExistingSystemError; return ctx; } - + public static Context CheckAuthorPermission(this Context ctx, PermissionSet neededPerms, string permissionName) { if ((ctx.UserPermissions & neededPerms) != neededPerms) diff --git a/PluralKit.Bot/CommandSystem/ContextEntityArgumentsExt.cs b/PluralKit.Bot/CommandSystem/ContextEntityArgumentsExt.cs index 2bd93da4..f3fcd343 100644 --- a/PluralKit.Bot/CommandSystem/ContextEntityArgumentsExt.cs +++ b/PluralKit.Bot/CommandSystem/ContextEntityArgumentsExt.cs @@ -23,14 +23,14 @@ namespace PluralKit.Bot public static bool MatchUserRaw(this Context ctx, out ulong id) { id = 0; - + var text = ctx.PeekArgument(); if (text.TryParseMention(out var mentionId)) id = mentionId; return id != 0; } - + public static Task PeekSystem(this Context ctx) => ctx.MatchSystemInner(); public static async Task MatchSystem(this Context ctx) @@ -50,7 +50,7 @@ namespace PluralKit.Bot // - A system hid await using var conn = await ctx.Database.Obtain(); - + // Direct IDs and mentions are both handled by the below method: if (input.TryParseMention(out var id)) return await ctx.Repository.GetSystemByAccount(conn, id); @@ -82,7 +82,7 @@ namespace PluralKit.Bot // And if that again fails, we try finding a member with a display name matching the argument from the system if (ctx.System != null && await ctx.Repository.GetMemberByDisplayName(conn, ctx.System.Id, input) is PKMember memberByDisplayName) return memberByDisplayName; - + // We didn't find anything, so we return null. return null; } @@ -102,17 +102,17 @@ namespace PluralKit.Bot // Finally, we return the member value. return member; } - + public static async Task PeekGroup(this Context ctx) { var input = ctx.PeekArgument(); await using var conn = await ctx.Database.Obtain(); - if (ctx.System != null && await ctx.Repository.GetGroupByName(conn, ctx.System.Id, input) is {} byName) + if (ctx.System != null && await ctx.Repository.GetGroupByName(conn, ctx.System.Id, input) is { } byName) return byName; - if (await ctx.Repository.GetGroupByHid(conn, input) is {} byHid) + if (await ctx.Repository.GetGroupByHid(conn, input) is { } byHid) return byHid; - if (await ctx.Repository.GetGroupByDisplayName(conn, ctx.System.Id, input) is {} byDisplayName) + if (await ctx.Repository.GetGroupByDisplayName(conn, ctx.System.Id, input) is { } byDisplayName) return byDisplayName; return null; @@ -139,7 +139,7 @@ namespace PluralKit.Bot return $"Member with name \"{input}\" not found. Note that a member ID is 5 characters long."; return $"Member not found. Note that a member ID is 5 characters long."; } - + public static string CreateGroupNotFoundError(this Context ctx, string input) { // TODO: does this belong here? @@ -154,18 +154,18 @@ namespace PluralKit.Bot return $"Group with name \"{input}\" not found. Note that a group ID is 5 characters long."; return $"Group not found. Note that a group ID is 5 characters long."; } - + public static Task MatchChannel(this Context ctx) { - if (!MentionUtils.TryParseChannel(ctx.PeekArgument(), out var id)) + if (!MentionUtils.TryParseChannel(ctx.PeekArgument(), out var id)) return Task.FromResult(null); if (!ctx.Cache.TryGetChannel(id, out var channel)) return Task.FromResult(null); - + if (!DiscordUtils.IsValidGuildChannel(channel)) return Task.FromResult(null); - + ctx.PopArgument(); return Task.FromResult(channel); } diff --git a/PluralKit.Bot/CommandSystem/Parameters.cs b/PluralKit.Bot/CommandSystem/Parameters.cs index 8dc229af..a9eb73fa 100644 --- a/PluralKit.Bot/CommandSystem/Parameters.cs +++ b/PluralKit.Bot/CommandSystem/Parameters.cs @@ -37,10 +37,10 @@ namespace PluralKit.Bot { // Start of the word internal readonly int startPos; - + // End of the word internal readonly int endPos; - + // How much to advance word pointer afterwards to point at the start of the *next* word internal readonly int advanceAfterWord; @@ -66,15 +66,15 @@ namespace PluralKit.Bot private void ParseFlags() { _flags = new HashSet(); - + var ptr = 0; while (NextWordPosition(ptr) is { } wp) { ptr = wp.endPos + wp.advanceAfterWord; - + // Is this word a *flag* (as in, starts with a - AND is not quoted) if (_cmd[wp.startPos] != '-' || wp.wasQuoted) continue; // (if not, carry on w/ next word) - + // Find the *end* of the flag start (technically allowing arbitrary amounts of dashes) var flagNameStart = wp.startPos; while (flagNameStart < _cmd.Length && _cmd[flagNameStart] == '-') @@ -125,7 +125,7 @@ namespace PluralKit.Bot if (skipFlags) { // Skip all *leading* flags when taking the remainder - while (NextWordPosition(_ptr) is {} wp) + while (NextWordPosition(_ptr) is { } wp) { if (_cmd[wp.startPos] != '-' || wp.wasQuoted) break; _ptr = wp.endPos + wp.advanceAfterWord; @@ -135,7 +135,7 @@ namespace PluralKit.Bot // *Then* get the remainder return _cmd.Substring(Math.Min(_ptr, _cmd.Length)).Trim(); } - + public string FullCommand => _cmd; private WordPosition? NextWordPosition(int position) @@ -163,7 +163,7 @@ namespace PluralKit.Bot // Not a quoted word, just find the next space and return if it's the end of the command var wordEnd = _cmd.IndexOf(' ', position + 1); - + return wordEnd == -1 ? new WordPosition(position, _cmd.Length, 0, false) : new WordPosition(position, wordEnd, 1, false); @@ -179,7 +179,7 @@ namespace PluralKit.Bot return true; } } - + correspondingRightQuotes = null; return false; } diff --git a/PluralKit.Bot/Commands/Admin.cs b/PluralKit.Bot/Commands/Admin.cs index c947037e..9a5ad56e 100644 --- a/PluralKit.Bot/Commands/Admin.cs +++ b/PluralKit.Bot/Commands/Admin.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -22,7 +22,7 @@ namespace PluralKit.Bot public async Task UpdateSystemId(Context ctx) { AssertBotAdmin(ctx); - + var target = await ctx.MatchSystem(); if (target == null) throw new PKError("Unknown system."); @@ -38,14 +38,14 @@ namespace PluralKit.Bot if (!await ctx.PromptYesNo($"Change system ID of `{target.Hid}` to `{newHid}`?", "Change")) throw new PKError("ID change cancelled."); - await _db.Execute(c => _repo.UpdateSystem(c, target.Id, new SystemPatch {Hid = newHid})); + await _db.Execute(c => _repo.UpdateSystem(c, target.Id, new SystemPatch { Hid = newHid })); await ctx.Reply($"{Emojis.Success} System ID updated (`{target.Hid}` -> `{newHid}`)."); } - + public async Task UpdateMemberId(Context ctx) { AssertBotAdmin(ctx); - + var target = await ctx.MatchMember(); if (target == null) throw new PKError("Unknown member."); @@ -60,8 +60,8 @@ namespace PluralKit.Bot if (!await ctx.PromptYesNo($"Change member ID of **{target.NameFor(LookupContext.ByNonOwner)}** (`{target.Hid}`) to `{newHid}`?", "Change")) throw new PKError("ID change cancelled."); - - await _db.Execute(c => _repo.UpdateMember(c, target.Id, new MemberPatch {Hid = newHid})); + + await _db.Execute(c => _repo.UpdateMember(c, target.Id, new MemberPatch { Hid = newHid })); await ctx.Reply($"{Emojis.Success} Member ID updated (`{target.Hid}` -> `{newHid}`)."); } @@ -84,7 +84,7 @@ namespace PluralKit.Bot if (!await ctx.PromptYesNo($"Change group ID of **{target.Name}** (`{target.Hid}`) to `{newHid}`?", "Change")) throw new PKError("ID change cancelled."); - await _db.Execute(c => _repo.UpdateGroup(c, target.Id, new GroupPatch {Hid = newHid})); + await _db.Execute(c => _repo.UpdateGroup(c, target.Id, new GroupPatch { Hid = newHid })); await ctx.Reply($"{Emojis.Success} Group ID updated (`{target.Hid}` -> `{newHid}`)."); } @@ -109,7 +109,7 @@ namespace PluralKit.Bot if (!await ctx.PromptYesNo($"Update member limit from **{currentLimit}** to **{newLimit}**?", "Update")) throw new PKError("Member limit change cancelled."); - + await using var conn = await _db.Obtain(); await _repo.UpdateSystem(conn, target.Id, new SystemPatch { diff --git a/PluralKit.Bot/Commands/Autoproxy.cs b/PluralKit.Bot/Commands/Autoproxy.cs index 7b9adf48..0d094937 100644 --- a/PluralKit.Bot/Commands/Autoproxy.cs +++ b/PluralKit.Bot/Commands/Autoproxy.cs @@ -27,7 +27,7 @@ namespace PluralKit.Bot { // no need to check account here, it's already done at CommandTree ctx.CheckGuildContext(); - + if (ctx.Match("off", "stop", "cancel", "no", "disable", "remove")) await AutoproxyOff(ctx); else if (ctx.Match("latch", "last", "proxy", "stick", "sticky")) @@ -90,7 +90,7 @@ namespace PluralKit.Bot var commandList = "**pk;autoproxy latch** - Autoproxies as last-proxied member\n**pk;autoproxy front** - Autoproxies as current (first) fronter\n**pk;autoproxy ** - Autoproxies as a specific member"; var eb = new EmbedBuilder() .Title($"Current autoproxy status (for {ctx.Guild.Name.EscapeMarkdown()})"); - + var fronters = ctx.MessageContext.LastSwitchMembers; var relevantMember = ctx.MessageContext.AutoproxyMode switch { @@ -99,36 +99,38 @@ namespace PluralKit.Bot _ => null }; - switch (ctx.MessageContext.AutoproxyMode) { + switch (ctx.MessageContext.AutoproxyMode) + { case AutoproxyMode.Off: eb.Description($"Autoproxy is currently **off** in this server. To enable it, use one of the following commands:\n{commandList}"); break; case AutoproxyMode.Front: - { - if (fronters.Length == 0) - eb.Description("Autoproxy is currently set to **front mode** in this server, but there are currently no fronters registered. Use the `pk;switch` command to log a switch."); - else { - if (relevantMember == null) - throw new ArgumentException("Attempted to print member autoproxy status, but the linked member ID wasn't found in the database. Should be handled appropriately."); - eb.Description($"Autoproxy is currently set to **front mode** in this server. The current (first) fronter is **{relevantMember.NameFor(ctx).EscapeMarkdown()}** (`{relevantMember.Hid}`). To disable, type `pk;autoproxy off`."); - } + if (fronters.Length == 0) + eb.Description("Autoproxy is currently set to **front mode** in this server, but there are currently no fronters registered. Use the `pk;switch` command to log a switch."); + else + { + if (relevantMember == null) + throw new ArgumentException("Attempted to print member autoproxy status, but the linked member ID wasn't found in the database. Should be handled appropriately."); + eb.Description($"Autoproxy is currently set to **front mode** in this server. The current (first) fronter is **{relevantMember.NameFor(ctx).EscapeMarkdown()}** (`{relevantMember.Hid}`). To disable, type `pk;autoproxy off`."); + } - break; - } + break; + } // AutoproxyMember is never null if Mode is Member, this is just to make the compiler shut up - case AutoproxyMode.Member when relevantMember != null: { - eb.Description($"Autoproxy is active for member **{relevantMember.NameFor(ctx)}** (`{relevantMember.Hid}`) in this server. To disable, type `pk;autoproxy off`."); - break; - } + case AutoproxyMode.Member when relevantMember != null: + { + eb.Description($"Autoproxy is active for member **{relevantMember.NameFor(ctx)}** (`{relevantMember.Hid}`) in this server. To disable, type `pk;autoproxy off`."); + break; + } case AutoproxyMode.Latch: eb.Description("Autoproxy is currently set to **latch mode**, meaning the *last-proxied member* will be autoproxied. To disable, type `pk;autoproxy off`."); break; - + default: throw new ArgumentOutOfRangeException(); } - if (!ctx.MessageContext.AllowAutoproxy) + if (!ctx.MessageContext.AllowAutoproxy) eb.Field(new("\u200b", $"{Emojis.Note} Autoproxy is currently **disabled** for your account (<@{ctx.Author.Id}>). To enable it, use `pk;autoproxy account enable`.")); return eb.Build(); @@ -139,9 +141,9 @@ namespace PluralKit.Bot if (!ctx.HasNext()) { var timeout = ctx.System.LatchTimeout.HasValue - ? Duration.FromSeconds(ctx.System.LatchTimeout.Value) - : (Duration?) null; - + ? Duration.FromSeconds(ctx.System.LatchTimeout.Value) + : (Duration?)null; + if (timeout == null) await ctx.Reply($"You do not have a custom autoproxy timeout duration set. The default latch timeout duration is {ProxyMatcher.DefaultLatchExpiryTime.ToTimeSpan().Humanize(4)}."); else if (timeout == Duration.Zero) @@ -169,9 +171,9 @@ namespace PluralKit.Bot else newTimeout = timeoutPeriod; } - await _db.Execute(conn => _repo.UpdateSystem(conn, ctx.System.Id, - new SystemPatch { LatchTimeout = (int?) newTimeout?.TotalSeconds })); - + await _db.Execute(conn => _repo.UpdateSystem(conn, ctx.System.Id, + new SystemPatch { LatchTimeout = (int?)newTimeout?.TotalSeconds })); + if (newTimeout == null) await ctx.Reply($"{Emojis.Success} Latch timeout reset to default ({ProxyMatcher.DefaultLatchExpiryTime.ToTimeSpan().Humanize(4)})."); else if (newTimeout == Duration.Zero && overflow != Duration.Zero) @@ -213,7 +215,7 @@ namespace PluralKit.Bot private Task UpdateAutoproxy(Context ctx, AutoproxyMode autoproxyMode, MemberId? autoproxyMember) { - var patch = new SystemGuildPatch {AutoproxyMode = autoproxyMode, AutoproxyMember = autoproxyMember}; + var patch = new SystemGuildPatch { AutoproxyMode = autoproxyMode, AutoproxyMember = autoproxyMember }; return _db.Execute(conn => _repo.UpsertSystemGuild(conn, ctx.System.Id, ctx.Guild.Id, patch)); } } diff --git a/PluralKit.Bot/Commands/Avatars/ContextAvatarExt.cs b/PluralKit.Bot/Commands/Avatars/ContextAvatarExt.cs index 850681e6..b924cdf9 100644 --- a/PluralKit.Bot/Commands/Avatars/ContextAvatarExt.cs +++ b/PluralKit.Bot/Commands/Avatars/ContextAvatarExt.cs @@ -16,9 +16,9 @@ namespace PluralKit.Bot if (await ctx.MatchUser() is { } user) { var url = user.AvatarUrl("png", 256); - return new ParsedImage {Url = url, Source = AvatarSource.User, SourceUser = user}; + return new ParsedImage { Url = url, Source = AvatarSource.User, SourceUser = user }; } - + // If we have a positional argument, try to parse it as a URL var arg = ctx.RemainderOrNull(); if (arg != null) @@ -26,24 +26,24 @@ namespace PluralKit.Bot // Allow surrounding the URL with to "de-embed" if (arg.StartsWith("<") && arg.EndsWith(">")) arg = arg.Substring(1, arg.Length - 2); - + if (!Uri.TryCreate(arg, UriKind.Absolute, out var uri)) throw Errors.InvalidUrl(arg); if (uri.Scheme != "http" && uri.Scheme != "https") throw Errors.InvalidUrl(arg); - + // ToString URL-decodes, which breaks URLs to spaces; AbsoluteUri doesn't - return new ParsedImage {Url = uri.AbsoluteUri, Source = AvatarSource.Url}; + return new ParsedImage { Url = uri.AbsoluteUri, Source = AvatarSource.Url }; } - + // If we have an attachment, use that - if (ctx.Message.Attachments.FirstOrDefault() is {} attachment) + if (ctx.Message.Attachments.FirstOrDefault() is { } attachment) { var url = attachment.ProxyUrl; - return new ParsedImage {Url = url, Source = AvatarSource.Attachment}; + return new ParsedImage { Url = url, Source = AvatarSource.Attachment }; } - + // We should only get here if there are no arguments (which would get parsed as URL + throw if error) // and if there are no attachments (which would have been caught just before) return null; @@ -63,4 +63,4 @@ namespace PluralKit.Bot User, Attachment } -} +} \ No newline at end of file diff --git a/PluralKit.Bot/Commands/Checks.cs b/PluralKit.Bot/Commands/Checks.cs index 363b854c..826696f3 100644 --- a/PluralKit.Bot/Commands/Checks.cs +++ b/PluralKit.Bot/Commands/Checks.cs @@ -55,19 +55,22 @@ namespace PluralKit.Bot if (!ulong.TryParse(guildIdStr, out var guildId)) throw new PKSyntaxError($"Could not parse {guildIdStr.AsCode()} as an ID."); - try { + try + { guild = await _rest.GetGuild(guildId); - } catch (Myriad.Rest.Exceptions.ForbiddenException) { + } + catch (Myriad.Rest.Exceptions.ForbiddenException) + { throw Errors.GuildNotFound(guildId); } - - if (guild != null) + + if (guild != null) senderGuildUser = await _rest.GetGuildMember(guildId, ctx.Author.Id); - if (guild == null || senderGuildUser == null) + if (guild == null || senderGuildUser == null) throw Errors.GuildNotFound(guildId); } - var requiredPermissions = new [] + var requiredPermissions = new[] { PermissionSet.ViewChannel, PermissionSet.SendMessages, @@ -87,7 +90,7 @@ namespace PluralKit.Bot var botPermissions = _bot.PermissionsIn(channel.Id); var webhookPermissions = _cache.EveryonePermissions(channel); var userPermissions = PermissionExtensions.PermissionsFor(guild, channel, ctx.Author.Id, senderGuildUser); - + if ((userPermissions & PermissionSet.ViewChannel) == 0) { // If the user can't see this channel, don't calculate permissions for it @@ -100,14 +103,14 @@ namespace PluralKit.Bot // We use a bitfield so we can set individual permission bits in the loop // TODO: Rewrite with proper bitfield math ulong missingPermissionField = 0; - + foreach (var requiredPermission in requiredPermissions) if ((botPermissions & requiredPermission) == 0) - missingPermissionField |= (ulong) requiredPermission; + missingPermissionField |= (ulong)requiredPermission; if ((webhookPermissions & PermissionSet.UseExternalEmojis) == 0) { - missingPermissionField |= (ulong) PermissionSet.UseExternalEmojis; + missingPermissionField |= (ulong)PermissionSet.UseExternalEmojis; missingEmojiPermissions = true; } @@ -119,7 +122,7 @@ namespace PluralKit.Bot permissionsMissing[missingPermissionField].Add(channel); } } - + // Generate the output embed var eb = new EmbedBuilder() .Title($"Permission check for **{guild.Name}**"); @@ -134,7 +137,7 @@ namespace PluralKit.Bot { // Each missing permission field can have multiple missing channels // so we extract them all and generate a comma-separated list - var missingPermissionNames = ((PermissionSet) missingPermissionField).ToPermissionString(); + var missingPermissionNames = ((PermissionSet)missingPermissionField).ToPermissionString(); var channelsList = string.Join("\n", channels .OrderBy(c => c.Position) @@ -155,7 +158,7 @@ namespace PluralKit.Bot if (footer.Length > 0) eb.Footer(new(footer)); - + // Send! :) await ctx.Reply(embed: eb.Build()); } @@ -164,7 +167,7 @@ namespace PluralKit.Bot { if (!ctx.HasNext() && ctx.Message.MessageReference == null) throw new PKError("You need to specify a message."); - + var failedToGetMessage = "Could not find a valid message to check, was not able to fetch the message, or the message was not sent by you."; var (messageId, channelId) = ctx.MatchMessage(false); @@ -182,7 +185,7 @@ namespace PluralKit.Bot // get the message info var msg = ctx.Message; - try + try { msg = await _rest.GetMessage(channelId.Value, messageId.Value); } @@ -212,13 +215,14 @@ namespace PluralKit.Bot var members = (await _repo.GetProxyMembers(conn, msg.Author.Id, channel.GuildId.Value)).ToList(); // Run everything through the checks, catch the ProxyCheckFailedException, and reply with the error message. - try - { + try + { _proxy.ShouldProxy(channel, msg, context); _matcher.TryMatch(context, members, out var match, msg.Content, msg.Attachments.Length > 0, context.AllowAutoproxy); await ctx.Reply("I'm not sure why this message was not proxied, sorry."); - } catch (ProxyService.ProxyChecksFailedException e) + } + catch (ProxyService.ProxyChecksFailedException e) { await ctx.Reply($"{e.Message}"); } diff --git a/PluralKit.Bot/Commands/CommandTree.cs b/PluralKit.Bot/Commands/CommandTree.cs index eb5134ee..bed7961f 100644 --- a/PluralKit.Bot/Commands/CommandTree.cs +++ b/PluralKit.Bot/Commands/CommandTree.cs @@ -120,13 +120,13 @@ namespace PluralKit.Bot GroupDelete, GroupMemberRandom, GroupFrontPercent }; - public static Command[] SwitchCommands = {Switch, SwitchOut, SwitchMove, SwitchDelete, SwitchDeleteAll}; + public static Command[] SwitchCommands = { Switch, SwitchOut, SwitchMove, SwitchDelete, SwitchDeleteAll }; - public static Command[] AutoproxyCommands = {AutoproxySet, AutoproxyTimeout, AutoproxyAccount}; - - public static Command[] LogCommands = {LogChannel, LogChannelClear, LogEnable, LogDisable}; + public static Command[] AutoproxyCommands = { AutoproxySet, AutoproxyTimeout, AutoproxyAccount }; - public static Command[] BlacklistCommands = {BlacklistAdd, BlacklistRemove, BlacklistShow}; + public static Command[] LogCommands = { LogChannel, LogChannelClear, LogEnable, LogDisable }; + + public static Command[] BlacklistCommands = { BlacklistAdd, BlacklistRemove, BlacklistShow }; public Task ExecuteCommand(Context ctx) { @@ -214,9 +214,9 @@ namespace PluralKit.Bot return HandleAdminCommand(ctx); if (ctx.Match("random", "r")) if (ctx.Match("group", "g") || ctx.MatchFlag("group", "g")) - return ctx.Execute(GroupRandom, r => r.Group(ctx)); - else - return ctx.Execute(MemberRandom, m => m.Member(ctx)); + return ctx.Execute(GroupRandom, r => r.Group(ctx)); + else + return ctx.Execute(MemberRandom, m => m.Member(ctx)); // remove compiler warning return ctx.Reply( @@ -348,7 +348,7 @@ namespace PluralKit.Bot await PrintCommandNotFoundError(ctx, SystemList, SystemFronter, SystemFrontHistory, SystemFrontPercent, SystemInfo); } - + private async Task HandleMemberCommand(Context ctx) { if (ctx.Match("new", "n", "add", "create", "register")) @@ -392,7 +392,7 @@ namespace PluralKit.Bot await ctx.Execute(MemberGroupAdd, m => m.AddRemove(ctx, target, Groups.AddRemoveOperation.Add)); else if (ctx.Match("remove", "rem")) await ctx.Execute(MemberGroupRemove, m => m.AddRemove(ctx, target, Groups.AddRemoveOperation.Remove)); - else + else await ctx.Execute(MemberGroups, m => m.List(ctx, target)); else if (ctx.Match("serveravatar", "servericon", "serverimage", "serverpfp", "serverpic", "savatar", "spic", "guildavatar", "guildpic", "guildicon", "sicon")) await ctx.Execute(MemberServerAvatar, m => m.ServerAvatar(ctx, target)); @@ -414,8 +414,8 @@ namespace PluralKit.Bot await ctx.Execute(MemberInfo, m => m.Soulscream(ctx, target)); else if (!ctx.HasNext()) // Bare command await ctx.Execute(MemberInfo, m => m.ViewMember(ctx, target)); - else - await PrintCommandNotFoundError(ctx, MemberInfo, MemberRename, MemberDisplayName, MemberServerName ,MemberDesc, MemberPronouns, MemberColor, MemberBirthday, MemberProxy, MemberDelete, MemberAvatar, SystemList); + else + await PrintCommandNotFoundError(ctx, MemberInfo, MemberRename, MemberDisplayName, MemberServerName, MemberDesc, MemberPronouns, MemberColor, MemberBirthday, MemberProxy, MemberDelete, MemberAvatar, SystemList); } private async Task HandleGroupCommand(Context ctx) @@ -427,7 +427,7 @@ namespace PluralKit.Bot await ctx.Execute(GroupList, g => g.ListSystemGroups(ctx, null)); else if (ctx.Match("commands", "help")) await PrintCommandList(ctx, "groups", GroupCommands); - else if (await ctx.MatchGroup() is {} target) + else if (await ctx.MatchGroup() is { } target) { // Commands with group argument if (ctx.Match("rename", "name", "changename", "setname")) @@ -437,7 +437,7 @@ namespace PluralKit.Bot else if (ctx.Match("description", "info", "bio", "text", "desc")) await ctx.Execute(GroupDesc, g => g.GroupDescription(ctx, target)); else if (ctx.Match("add", "a")) - await ctx.Execute(GroupAdd,g => g.AddRemoveMembers(ctx, target, Groups.AddRemoveOperation.Add)); + await ctx.Execute(GroupAdd, g => g.AddRemoveMembers(ctx, target, Groups.AddRemoveOperation.Add)); else if (ctx.Match("remove", "rem", "r")) await ctx.Execute(GroupRemove, g => g.AddRemoveMembers(ctx, target, Groups.AddRemoveOperation.Remove)); else if (ctx.Match("members", "list", "ms", "l")) @@ -488,14 +488,15 @@ namespace PluralKit.Bot } private async Task CommandHelpRoot(Context ctx) - { + { if (!ctx.HasNext()) { await ctx.Reply($"{Emojis.Error} You need to pass a target command.\nAvailable command help targets: `system`, `member`, `group`, `switch`, `log`, `blacklist`.\nFor the full list of commands, see the website: "); return; } - switch (ctx.PeekArgument()) { + switch (ctx.PeekArgument()) + { case "system": case "systems": case "s": @@ -561,14 +562,14 @@ namespace PluralKit.Bot await ctx.Reply( $"{Emojis.Error} Unknown command `pk;{ctx.FullCommand().Truncate(100)}`. Perhaps you meant to use one of the following commands?\n{commandListStr}\n\nFor a full list of possible commands, see ."); } - + private async Task PrintCommandExpectedError(Context ctx, params Command[] potentialCommands) { var commandListStr = CreatePotentialCommandList(potentialCommands); await ctx.Reply( $"{Emojis.Error} You need to pass a command. Perhaps you meant to use one of the following commands?\n{commandListStr}\n\nFor a full list of possible commands, see ."); } - + private static string CreatePotentialCommandList(params Command[] potentialCommands) { return string.Join("\n", potentialCommands.Select(cmd => $"- **pk;{cmd.Usage}** - *{cmd.Description}*")); @@ -597,4 +598,4 @@ namespace PluralKit.Bot return $"System with ID {input.AsCode()} not found."; } } -} +} \ No newline at end of file diff --git a/PluralKit.Bot/Commands/Fun.cs b/PluralKit.Bot/Commands/Fun.cs index 6ec22bb2..70f144ff 100644 --- a/PluralKit.Bot/Commands/Fun.cs +++ b/PluralKit.Bot/Commands/Fun.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; namespace PluralKit.Bot { diff --git a/PluralKit.Bot/Commands/Groups.cs b/PluralKit.Bot/Commands/Groups.cs index a10cdb86..95c634b6 100644 --- a/PluralKit.Bot/Commands/Groups.cs +++ b/PluralKit.Bot/Commands/Groups.cs @@ -36,14 +36,14 @@ namespace PluralKit.Bot public async Task CreateGroup(Context ctx) { ctx.CheckSystem(); - + // Check group name length var groupName = ctx.RemainderOrNull() ?? throw new PKSyntaxError("You must pass a group name."); if (groupName.Length > Limits.MaxGroupNameLength) throw new PKError($"Group name too long ({groupName.Length}/{Limits.MaxGroupNameLength} characters)."); - + await using var conn = await _db.Obtain(); - + // Check group cap var existingGroupCount = await conn.QuerySingleAsync("select count(*) from groups where system = @System", new { System = ctx.System.Id }); var groupLimit = ctx.System.GroupLimitOverride ?? Limits.MaxGroupCount; @@ -52,14 +52,15 @@ namespace PluralKit.Bot // Warn if there's already a group by this name var existingGroup = await _repo.GetGroupByName(conn, ctx.System.Id, groupName); - if (existingGroup != null) { + if (existingGroup != null) + { var msg = $"{Emojis.Warn} You already have a group in your system with the name \"{existingGroup.Name}\" (with ID `{existingGroup.Hid}`). Do you want to create another group with the same name?"; if (!await ctx.PromptYesNo(msg, "Create")) throw new PKError("Group creation cancelled."); } - + var newGroup = await _repo.CreateGroup(conn, ctx.System.Id, groupName); - + var eb = new EmbedBuilder() .Description($"Your new group, **{groupName}**, has been created, with the group ID **`{newGroup.Hid}`**.\nBelow are a couple of useful commands:") .Field(new("View the group card", $"> pk;group **{newGroup.Reference()}**")) @@ -72,23 +73,24 @@ namespace PluralKit.Bot public async Task RenameGroup(Context ctx, PKGroup target) { ctx.CheckOwnGroup(target); - + // Check group name length var newName = ctx.RemainderOrNull() ?? throw new PKSyntaxError("You must pass a new group name."); if (newName.Length > Limits.MaxGroupNameLength) throw new PKError($"New group name too long ({newName.Length}/{Limits.MaxMemberNameLength} characters)."); - + await using var conn = await _db.Obtain(); - + // Warn if there's already a group by this name var existingGroup = await _repo.GetGroupByName(conn, ctx.System.Id, newName); - if (existingGroup != null && existingGroup.Id != target.Id) { + if (existingGroup != null && existingGroup.Id != target.Id) + { var msg = $"{Emojis.Warn} You already have a group in your system with the name \"{existingGroup.Name}\" (with ID `{existingGroup.Hid}`). Do you want to rename this group to that name too?"; if (!await ctx.PromptYesNo(msg, "Rename")) throw new PKError("Group rename cancelled."); } - await _repo.UpdateGroup(conn, target.Id, new GroupPatch {Name = newName}); + await _repo.UpdateGroup(conn, target.Id, new GroupPatch { Name = newName }); await ctx.Reply($"{Emojis.Success} Group name changed from **{target.Name}** to **{newName}**."); } @@ -98,8 +100,8 @@ namespace PluralKit.Bot if (await ctx.MatchClear("this group's display name")) { ctx.CheckOwnGroup(target); - - var patch = new GroupPatch {DisplayName = Partial.Null()}; + + var patch = new GroupPatch { DisplayName = Partial.Null() }; await _db.Execute(conn => _repo.UpdateGroup(conn, target.Id, patch)); await ctx.Reply($"{Emojis.Success} Group display name cleared."); @@ -124,40 +126,40 @@ namespace PluralKit.Bot var eb = new EmbedBuilder() .Field(new("Name", target.Name)) .Field(new("Display Name", target.DisplayName ?? "*(none)*")); - + if (ctx.System?.Id == target.System) eb.Description($"To change display name, type `pk;group {target.Reference()} displayname `.\nTo clear it, type `pk;group {target.Reference()} displayname -clear`.\nTo print the raw display name, type `pk;group {target.Reference()} displayname -raw`."); - + await ctx.Reply(embed: eb.Build()); } } else { ctx.CheckOwnGroup(target); - + var newDisplayName = ctx.RemainderOrNull(); - - var patch = new GroupPatch {DisplayName = Partial.Present(newDisplayName)}; + + var patch = new GroupPatch { DisplayName = Partial.Present(newDisplayName) }; await _db.Execute(conn => _repo.UpdateGroup(conn, target.Id, patch)); await ctx.Reply($"{Emojis.Success} Group display name changed."); } } - + public async Task GroupDescription(Context ctx, PKGroup target) { if (await ctx.MatchClear("this group's description")) { ctx.CheckOwnGroup(target); - var patch = new GroupPatch {Description = Partial.Null()}; + var patch = new GroupPatch { Description = Partial.Null() }; await _db.Execute(conn => _repo.UpdateGroup(conn, target.Id, patch)); await ctx.Reply($"{Emojis.Success} Group description cleared."); - } + } else if (!ctx.HasNext()) { if (!target.DescriptionPrivacy.CanAccess(ctx.LookupContextFor(target.System))) - throw Errors.LookupNotAllowed; + throw Errors.LookupNotAllowed; if (target.Description == null) if (ctx.System?.Id == target.System) @@ -170,7 +172,7 @@ namespace PluralKit.Bot await ctx.Reply(embed: new EmbedBuilder() .Title("Group description") .Description(target.Description) - .Field(new("\u200B", $"To print the description with formatting, type `pk;group {target.Reference()} description -raw`." + .Field(new("\u200B", $"To print the description with formatting, type `pk;group {target.Reference()} description -raw`." + (ctx.System?.Id == target.System ? $" To clear it, type `pk;group {target.Reference()} description -clear`." : ""))) .Build()); } @@ -181,10 +183,10 @@ namespace PluralKit.Bot var description = ctx.RemainderOrNull().NormalizeLineEndSpacing(); if (description.IsLongerThan(Limits.MaxDescriptionLength)) throw Errors.DescriptionTooLongError(description.Length); - - var patch = new GroupPatch {Description = Partial.Present(description)}; + + var patch = new GroupPatch { Description = Partial.Present(description) }; await _db.Execute(conn => _repo.UpdateGroup(conn, target.Id, patch)); - + await ctx.Reply($"{Emojis.Success} Group description changed."); } } @@ -194,19 +196,19 @@ namespace PluralKit.Bot async Task ClearIcon() { ctx.CheckOwnGroup(target); - - await _db.Execute(c => _repo.UpdateGroup(c, target.Id, new GroupPatch {Icon = null})); + + await _db.Execute(c => _repo.UpdateGroup(c, target.Id, new GroupPatch { Icon = null })); await ctx.Reply($"{Emojis.Success} Group icon cleared."); } async Task SetIcon(ParsedImage img) { ctx.CheckOwnGroup(target); - + await AvatarUtils.VerifyAvatarOrThrow(_client, img.Url); - await _db.Execute(c => _repo.UpdateGroup(c, target.Id, new GroupPatch {Icon = img.Url})); - + await _db.Execute(c => _repo.UpdateGroup(c, target.Id, new GroupPatch { Icon = img.Url })); + var msg = img.Source switch { AvatarSource.User => $"{Emojis.Success} Group icon changed to {img.SourceUser?.Username}'s avatar!\n{Emojis.Warn} If {img.SourceUser?.Username} changes their avatar, the group icon will need to be re-set.", @@ -214,11 +216,11 @@ namespace PluralKit.Bot AvatarSource.Attachment => $"{Emojis.Success} Group icon changed to attached image.\n{Emojis.Warn} If you delete the message containing the attachment, the group icon will stop working.", _ => throw new ArgumentOutOfRangeException() }; - + // The attachment's already right there, no need to preview it. var hasEmbed = img.Source != AvatarSource.Attachment; - await (hasEmbed - ? ctx.Reply(msg, embed: new EmbedBuilder().Image(new(img.Url)).Build()) + await (hasEmbed + ? ctx.Reply(msg, embed: new EmbedBuilder().Image(new(img.Url)).Build()) : ctx.Reply(msg)); } @@ -231,7 +233,7 @@ namespace PluralKit.Bot var eb = new EmbedBuilder() .Title("Group icon") .Image(new(target.Icon.TryGetCleanCdnUrl())); - + if (target.System == ctx.System?.Id) { eb.Description($"To clear, use `pk;group {target.Reference()} icon -clear`."); @@ -245,7 +247,7 @@ namespace PluralKit.Bot if (await ctx.MatchClear("this group's icon")) await ClearIcon(); - else if (await ctx.MatchImage() is {} img) + else if (await ctx.MatchImage() is { } img) await SetIcon(img); else await ShowIcon(); @@ -257,7 +259,7 @@ namespace PluralKit.Bot { ctx.CheckOwnGroup(target); - await _db.Execute(c => _repo.UpdateGroup(c, target.Id, new GroupPatch {BannerImage = null})); + await _db.Execute(c => _repo.UpdateGroup(c, target.Id, new GroupPatch { BannerImage = null })); await ctx.Reply($"{Emojis.Success} Group banner image cleared."); } @@ -267,7 +269,7 @@ namespace PluralKit.Bot await AvatarUtils.VerifyAvatarOrThrow(_client, img.Url, isFullSizeImage: true); - await _db.Execute(c => _repo.UpdateGroup(c, target.Id, new GroupPatch {BannerImage = img.Url})); + await _db.Execute(c => _repo.UpdateGroup(c, target.Id, new GroupPatch { BannerImage = img.Url })); var msg = img.Source switch { @@ -279,8 +281,8 @@ namespace PluralKit.Bot // The attachment's already right there, no need to preview it. var hasEmbed = img.Source != AvatarSource.Attachment; - await (hasEmbed - ? ctx.Reply(msg, embed: new EmbedBuilder().Image(new(img.Url)).Build()) + await (hasEmbed + ? ctx.Reply(msg, embed: new EmbedBuilder().Image(new(img.Url)).Build()) : ctx.Reply(msg)); } @@ -307,7 +309,7 @@ namespace PluralKit.Bot if (await ctx.MatchClear("this group's banner image")) await ClearBannerImage(); - else if (await ctx.MatchImage() is {} img) + else if (await ctx.MatchImage() is { } img) await SetBannerImage(img); else await ShowBannerImage(); @@ -319,10 +321,10 @@ namespace PluralKit.Bot if (await ctx.MatchClear()) { ctx.CheckOwnGroup(target); - - var patch = new GroupPatch {Color = Partial.Null()}; + + var patch = new GroupPatch { Color = Partial.Null() }; await _db.Execute(conn => _repo.UpdateGroup(conn, target.Id, patch)); - + await ctx.Reply($"{Emojis.Success} Group color cleared."); } else if (!ctx.HasNext()) @@ -349,8 +351,8 @@ namespace PluralKit.Bot if (color.StartsWith("#")) color = color.Substring(1); if (!Regex.IsMatch(color, "^[0-9a-fA-F]{6}$")) throw Errors.InvalidColorError(color); - - var patch = new GroupPatch {Color = Partial.Present(color.ToLowerInvariant())}; + + var patch = new GroupPatch { Color = Partial.Present(color.ToLowerInvariant()) }; await _db.Execute(conn => _repo.UpdateGroup(conn, target.Id, patch)); await ctx.Reply(embed: new EmbedBuilder() @@ -368,9 +370,9 @@ namespace PluralKit.Bot ctx.CheckSystem(); system = ctx.System; } - + ctx.CheckSystemPrivacy(system, system.GroupListPrivacy); - + // TODO: integrate with the normal "search" system await using var conn = await _db.Obtain(); @@ -382,25 +384,25 @@ namespace PluralKit.Bot else throw new PKError("You do not have permission to access this information."); } - + var groups = (await conn.QueryGroupList(system.Id)) .Where(g => g.Visibility.CanAccess(pctx)) .OrderBy(g => g.Name, StringComparer.InvariantCultureIgnoreCase) .ToList(); - + if (groups.Count == 0) { if (system.Id == ctx.System?.Id) await ctx.Reply("This system has no groups. To create one, use the command `pk;group new `."); else await ctx.Reply("This system has no groups."); - + return; } var title = system.Name != null ? $"Groups of {system.Name} (`{system.Hid}`)" : $"Groups of `{system.Hid}`"; await ctx.Paginate(groups.ToAsyncEnumerable(), groups.Count, 25, title, ctx.System.Color, Renderer); - + Task Renderer(EmbedBuilder eb, IEnumerable page) { eb.WithSimpleLineContent(page.Select(g => @@ -430,15 +432,15 @@ namespace PluralKit.Bot .Select(m => m.Id) .Distinct() .ToList(); - + await using var conn = await _db.Obtain(); - + var existingMembersInGroup = (await conn.QueryMemberList(target.System, - new DatabaseViewsExt.MemberListQueryOptions {GroupFilter = target.Id})) + new DatabaseViewsExt.MemberListQueryOptions { GroupFilter = target.Id })) .Select(m => m.Id.Value) .Distinct() .ToHashSet(); - + List toAction; if (op == AddRemoveOperation.Add) @@ -463,21 +465,21 @@ namespace PluralKit.Bot public async Task ListGroupMembers(Context ctx, PKGroup target) { await using var conn = await _db.Obtain(); - + var targetSystem = await GetGroupSystem(ctx, target, conn); ctx.CheckSystemPrivacy(targetSystem, target.ListPrivacy); - + var opts = ctx.ParseMemberListOptions(ctx.LookupContextFor(target.System)); opts.GroupFilter = target.Id; var title = new StringBuilder($"Members of {target.DisplayName ?? target.Name} (`{target.Hid}`) in "); - if (targetSystem.Name != null) + if (targetSystem.Name != null) title.Append($"{targetSystem.Name} (`{targetSystem.Hid}`)"); else title.Append($"`{targetSystem.Hid}`"); - if (opts.Search != null) + if (opts.Search != null) title.Append($" matching **{opts.Search}**"); - + await ctx.RenderMemberList(ctx.LookupContextFor(target.System), _db, target.System, title.ToString(), target.Color, opts); } @@ -495,29 +497,29 @@ namespace PluralKit.Bot { await ctx.Reply(embed: new EmbedBuilder() .Title($"Current privacy settings for {target.Name}") - .Field(new("Description", target.DescriptionPrivacy.Explanation()) ) + .Field(new("Description", target.DescriptionPrivacy.Explanation())) .Field(new("Icon", target.IconPrivacy.Explanation())) .Field(new("Member list", target.ListPrivacy.Explanation())) .Field(new("Visibility", target.Visibility.Explanation())) .Description($"To edit privacy settings, use the command:\n> pk;group **{target.Reference()}** privacy **** ****\n\n- `subject` is one of `description`, `icon`, `members`, `visibility`, or `all`\n- `level` is either `public` or `private`.") - .Build()); + .Build()); return; } async Task SetAll(PrivacyLevel level) { await _db.Execute(c => _repo.UpdateGroup(c, target.Id, new GroupPatch().WithAllPrivacy(level))); - + if (level == PrivacyLevel.Private) await ctx.Reply($"{Emojis.Success} All {target.Name}'s privacy settings have been set to **{level.LevelName()}**. Other accounts will now see nothing on the group card."); - else + else await ctx.Reply($"{Emojis.Success} All {target.Name}'s privacy settings have been set to **{level.LevelName()}**. Other accounts will now see everything on the group card."); } async Task SetLevel(GroupPrivacySubject subject, PrivacyLevel level) { await _db.Execute(c => _repo.UpdateGroup(c, target.Id, new GroupPatch().WithPrivacy(subject, level))); - + var subjectName = subject switch { GroupPrivacySubject.Description => "description privacy", @@ -526,22 +528,22 @@ namespace PluralKit.Bot GroupPrivacySubject.Visibility => "visibility", _ => throw new ArgumentOutOfRangeException($"Unknown privacy subject {subject}") }; - + var explanation = (subject, level) switch { (GroupPrivacySubject.Description, PrivacyLevel.Private) => "This group's description is now hidden from other systems.", (GroupPrivacySubject.Icon, PrivacyLevel.Private) => "This group's icon is now hidden from other systems.", (GroupPrivacySubject.Visibility, PrivacyLevel.Private) => "This group is now hidden from group lists and member cards.", (GroupPrivacySubject.List, PrivacyLevel.Private) => "This group's member list is now hidden from other systems.", - + (GroupPrivacySubject.Description, PrivacyLevel.Public) => "This group's description is no longer hidden from other systems.", (GroupPrivacySubject.Icon, PrivacyLevel.Public) => "This group's icon is no longer hidden from other systems.", (GroupPrivacySubject.Visibility, PrivacyLevel.Public) => "This group is no longer hidden from group lists and member cards.", (GroupPrivacySubject.List, PrivacyLevel.Public) => "This group's member list is no longer hidden from other systems.", - + _ => throw new InvalidOperationException($"Invalid subject/level tuple ({subject}, {level})") }; - + await ctx.Reply($"{Emojis.Success} {target.Name}'s **{subjectName}** has been set to **{level.LevelName()}**. {explanation}"); } @@ -560,14 +562,14 @@ namespace PluralKit.Bot throw new PKError($"Group deletion cancelled. Note that you must reply with your group ID (`{target.Hid}`) *verbatim*."); await _db.Execute(conn => _repo.DeleteGroup(conn, target.Id)); - + await ctx.Reply($"{Emojis.Success} Group deleted."); } - public async Task GroupFrontPercent(Context ctx, PKGroup target) + public async Task GroupFrontPercent(Context ctx, PKGroup target) { await using var conn = await _db.Obtain(); - + var targetSystem = await GetGroupSystem(ctx, target, conn); ctx.CheckSystemPrivacy(targetSystem, targetSystem.FrontHistoryPrivacy); @@ -575,7 +577,7 @@ namespace PluralKit.Bot if (totalSwitches == 0) throw Errors.NoRegisteredSwitches; string durationStr = ctx.RemainderOrNull() ?? "30d"; - + var now = SystemClock.Instance.GetCurrentInstant(); var rangeStart = DateUtils.ParseDateTime(durationStr, true, targetSystem.Zone); @@ -583,7 +585,7 @@ namespace PluralKit.Bot if (rangeStart.Value.ToInstant() > now) throw Errors.FrontPercentTimeInFuture; var title = new StringBuilder($"Frontpercent of {target.DisplayName ?? target.Name} (`{target.Hid}`) in "); - if (targetSystem.Name != null) + if (targetSystem.Name != null) title.Append($"{targetSystem.Name} (`{targetSystem.Hid}`)"); else title.Append($"`{targetSystem.Hid}`"); diff --git a/PluralKit.Bot/Commands/Help.cs b/PluralKit.Bot/Commands/Help.cs index d4bd9e9f..542025bc 100644 --- a/PluralKit.Bot/Commands/Help.cs +++ b/PluralKit.Bot/Commands/Help.cs @@ -15,7 +15,7 @@ namespace PluralKit.Bot .Description("PluralKit is a bot designed for plural communities on Discord. It allows you to register systems, maintain system information, set up message proxying, log switches, and more.") .Field(new("What is this for? What are systems?", "This bot detects messages with certain tags associated with a profile, then replaces that message under a \"pseudo-account\" of that profile using webhooks. This is useful for multiple people sharing one body (aka \"systems\"), people who wish to roleplay as different characters without having several accounts, or anyone else who may want to post messages as a different person from the same account.")) .Field(new("Why are people's names saying [BOT] next to them?", "These people are not actually bots, this is just a Discord limitation. See [the documentation](https://pluralkit.me/guide#proxying) for an in-depth explanation.")) - .Field(new("How do I get started?", "To get started using PluralKit, try running the following commands (of course replacing the relevant names with your own):\n**1**. `pk;system new` - Create a system (if you haven't already)\n**2**. `pk;member add John` - Add a new member to your system\n**3**. `pk;member John proxy [text]` - Set up [square brackets] as proxy tags\n**4**. You're done! You can now type [a message in brackets] and it'll be proxied appropriately.\n**5**. Optionally, you may set an avatar from the URL of an image with `pk;member John avatar [link to image]`, or from a file by typing `pk;member John avatar` and sending the message with an attached image.\n\nSee [the Getting Started guide](https://pluralkit.me/start) for more information.")) + .Field(new("How do I get started?", "To get started using PluralKit, try running the following commands (of course replacing the relevant names with your own):\n**1**. `pk;system new` - Create a system (if you haven't already)\n**2**. `pk;member add John` - Add a new member to your system\n**3**. `pk;member John proxy [text]` - Set up [square brackets] as proxy tags\n**4**. You're done! You can now type [a message in brackets] and it'll be proxied appropriately.\n**5**. Optionally, you may set an avatar from the URL of an image with `pk;member John avatar [link to image]`, or from a file by typing `pk;member John avatar` and sending the message with an attached image.\n\nSee [the Getting Started guide](https://pluralkit.me/start) for more information.")) .Field(new("Useful tips", $"React with {Emojis.Error} on a proxied message to delete it (only if you sent it!)\nReact with {Emojis.RedQuestion} on a proxied message to look up information about it (like who sent it)\nReact with {Emojis.Bell} on a proxied message to \"ping\" the sender\nType **`pk;invite`** to get a link to invite this bot to your own server!")) .Field(new("More information", "For a full list of commands, see [the command list](https://pluralkit.me/commands).\nFor a more in-depth explanation of message proxying, see [the documentation](https://pluralkit.me/guide#proxying).\nIf you're an existing user of Tupperbox, type `pk;import` and attach a Tupperbox export file (from `tul!export`) to import your data from there.")) .Field(new("Support server", "We also have a Discord server for support, discussion, suggestions, announcements, etc: https://discord.gg/PczBt78")) diff --git a/PluralKit.Bot/Commands/ImportExport.cs b/PluralKit.Bot/Commands/ImportExport.cs index c671bfe0..ed6d53eb 100644 --- a/PluralKit.Bot/Commands/ImportExport.cs +++ b/PluralKit.Bot/Commands/ImportExport.cs @@ -28,7 +28,7 @@ namespace PluralKit.Bot // Otherwise it'll mess up/reformat the ISO strings for ???some??? reason >.> DateParseHandling = DateParseHandling.None }; - + public ImportExport(DataFileService dataFiles, HttpClient client) { _dataFiles = dataFiles; @@ -41,7 +41,7 @@ namespace PluralKit.Bot if (url == null) throw Errors.NoImportFilePassed; await ctx.BusyIndicator(async () => - { + { JObject data; try { @@ -68,10 +68,10 @@ namespace PluralKit.Bot if (!await ctx.PromptYesNo(msg, "Proceed")) throw Errors.ImportCancelled; } - + if (data.ContainsKey("accounts") && data.Value("accounts").Type != JTokenType.Null - && data.Value("accounts").Contains((JToken) ctx.Author.Id.ToString())) + && data.Value("accounts").Contains((JToken)ctx.Author.Id.ToString())) { var msg = $"{Emojis.Warn} You seem to importing a system profile belonging to another account. Are you sure you want to proceed?"; if (!await ctx.PromptYesNo(msg, "Import")) throw Errors.ImportCancelled; @@ -95,15 +95,15 @@ namespace PluralKit.Bot public async Task Export(Context ctx) { ctx.CheckSystem(); - + var json = await ctx.BusyIndicator(async () => { // Make the actual data file var data = await _dataFiles.ExportSystem(ctx.System); return JsonConvert.SerializeObject(data, Formatting.None); }); - - + + // Send it as a Discord attachment *in DMs* var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); @@ -112,10 +112,10 @@ namespace PluralKit.Bot var dm = await ctx.Cache.GetOrCreateDmChannel(ctx.Rest, ctx.Author.Id); var msg = await ctx.Rest.CreateMessage(dm.Id, - new MessageRequest {Content = $"{Emojis.Success} Here you go!"}, - new[] {new MultipartFile("system.json", stream)}); + new MessageRequest { Content = $"{Emojis.Success} Here you go!" }, + new[] { new MultipartFile("system.json", stream) }); await ctx.Rest.CreateMessage(dm.Id, new MessageRequest { Content = $"<{msg.Attachments[0].Url}>" }); - + // If the original message wasn't posted in DMs, send a public reminder if (ctx.Channel.Type != Channel.ChannelType.Dm) await ctx.Reply($"{Emojis.Success} Check your DMs!"); @@ -128,4 +128,4 @@ namespace PluralKit.Bot } } } -} +} \ No newline at end of file diff --git a/PluralKit.Bot/Commands/Lists/ContextListExt.cs b/PluralKit.Bot/Commands/Lists/ContextListExt.cs index 7c0e2265..f3f5aca1 100644 --- a/PluralKit.Bot/Commands/Lists/ContextListExt.cs +++ b/PluralKit.Bot/Commands/Lists/ContextListExt.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -18,15 +18,15 @@ namespace PluralKit.Bot public static MemberListOptions ParseMemberListOptions(this Context ctx, LookupContext lookupCtx) { var p = new MemberListOptions(); - + // Short or long list? (parse this first, as it can potentially take a positional argument) var isFull = ctx.Match("f", "full", "big", "details", "long") || ctx.MatchFlag("f", "full"); p.Type = isFull ? ListType.Long : ListType.Short; - + // Search query if (ctx.HasNext()) p.Search = ctx.RemainderOrNull(); - + // Include description in search? if (ctx.MatchFlag("search-description", "filter-description", "in-description", "sd", "description", "desc")) p.SearchDescription = true; @@ -47,7 +47,7 @@ namespace PluralKit.Bot p.Reverse = true; // Privacy filter (default is public only) - if (ctx.MatchFlag("a", "all")) p.PrivacyFilter = null; + if (ctx.MatchFlag("a", "all")) p.PrivacyFilter = null; if (ctx.MatchFlag("private-only", "private", "priv")) p.PrivacyFilter = PrivacyLevel.Private; if (ctx.MatchFlag("public-only", "public", "pub")) p.PrivacyFilter = PrivacyLevel.Public; @@ -55,7 +55,7 @@ namespace PluralKit.Bot if (p.PrivacyFilter != PrivacyLevel.Public && lookupCtx != LookupContext.ByOwner) // TODO: should this just return null instead of throwing or something? >.> throw new PKError("You cannot look up private members of another system."); - + // Additional fields to include in the search results if (ctx.MatchFlag("with-last-switch", "with-last-fronted", "with-last-front", "wls", "wlf")) p.IncludeLastSwitch = true; @@ -69,13 +69,13 @@ namespace PluralKit.Bot p.IncludeAvatar = true; if (ctx.MatchFlag("with-pronouns", "wp")) p.IncludePronouns = true; - + // Always show the sort property, too if (p.SortProperty == SortProperty.LastSwitch) p.IncludeLastSwitch = true; - if (p.SortProperty == SortProperty.LastMessage) p.IncludeLastMessage= true; + if (p.SortProperty == SortProperty.LastMessage) p.IncludeLastMessage = true; if (p.SortProperty == SortProperty.MessageCount) p.IncludeMessageCount = true; if (p.SortProperty == SortProperty.CreationDate) p.IncludeCreated = true; - + // Done! return p; } @@ -96,119 +96,126 @@ namespace PluralKit.Bot { // Add a global footer with the filter/sort string + result count eb.Footer(new($"{opts.CreateFilterString()}. {"result".ToQuantity(members.Count)}.")); - + // Then call the specific renderers if (opts.Type == ListType.Short) ShortRenderer(eb, page); else LongRenderer(eb, page); - + return Task.CompletedTask; } void ShortRenderer(EmbedBuilder eb, IEnumerable page) - { + { // We may end up over the description character limit // so run it through a helper that "makes it work" :) eb.WithSimpleLineContent(page.Select(m => { var ret = $"[`{m.Hid}`] **{m.NameFor(ctx)}** "; - switch (opts.SortProperty) { - case SortProperty.Birthdate: { - var birthday = m.BirthdayFor(lookupCtx); - if (birthday != null) - ret += $"(birthday: {m.BirthdayString})"; - break; - } - case SortProperty.MessageCount: { - if (m.MessageCountFor(lookupCtx) is {} count) - ret += $"({count} messages)"; - break; - } - case SortProperty.LastSwitch: { - if (m.MetadataPrivacy.TryGet(lookupCtx, m.LastSwitchTime, out var lastSw)) - ret += $"(last switched in: )"; - break; - } - case SortProperty.LastMessage: { - if (m.MetadataPrivacy.TryGet(lookupCtx, m.LastMessage, out var lastMsg)) - ret += $"(last message: )"; - break; - } - case SortProperty.CreationDate: { - if (m.MetadataPrivacy.TryGet(lookupCtx, m.Created, out var created)) - ret += $"(created at )"; - break; - } - default: { - if (opts.IncludeMessageCount && m.MessageCountFor(lookupCtx) is {} count) - ret += $"({count} messages)"; - else if (opts.IncludeLastSwitch && m.MetadataPrivacy.TryGet(lookupCtx, m.LastSwitchTime, out var lastSw)) - ret += $"(last switched in: )"; - else if (opts.IncludeLastMessage && m.MetadataPrivacy.TryGet(lookupCtx, m.LastMessage, out var lastMsg)) - ret += $"(last message: )"; - else if (opts.IncludeCreated && m.MetadataPrivacy.TryGet(lookupCtx, m.Created, out var created)) - ret += $"(created at )"; - else if (opts.IncludePronouns && m.PronounsFor(lookupCtx) is {} pronouns) - ret += $"({pronouns})"; - else if (m.HasProxyTags) + switch (opts.SortProperty) + { + case SortProperty.Birthdate: { - var proxyTagsString = m.ProxyTagsString(); - if (proxyTagsString.Length > 100) // arbitrary threshold for now, tweak? - proxyTagsString = "tags too long, see member card"; - ret += $"*(*{proxyTagsString}*)*"; + var birthday = m.BirthdayFor(lookupCtx); + if (birthday != null) + ret += $"(birthday: {m.BirthdayString})"; + break; + } + case SortProperty.MessageCount: + { + if (m.MessageCountFor(lookupCtx) is { } count) + ret += $"({count} messages)"; + break; + } + case SortProperty.LastSwitch: + { + if (m.MetadataPrivacy.TryGet(lookupCtx, m.LastSwitchTime, out var lastSw)) + ret += $"(last switched in: )"; + break; + } + case SortProperty.LastMessage: + { + if (m.MetadataPrivacy.TryGet(lookupCtx, m.LastMessage, out var lastMsg)) + ret += $"(last message: )"; + break; + } + case SortProperty.CreationDate: + { + if (m.MetadataPrivacy.TryGet(lookupCtx, m.Created, out var created)) + ret += $"(created at )"; + break; + } + default: + { + if (opts.IncludeMessageCount && m.MessageCountFor(lookupCtx) is { } count) + ret += $"({count} messages)"; + else if (opts.IncludeLastSwitch && m.MetadataPrivacy.TryGet(lookupCtx, m.LastSwitchTime, out var lastSw)) + ret += $"(last switched in: )"; + else if (opts.IncludeLastMessage && m.MetadataPrivacy.TryGet(lookupCtx, m.LastMessage, out var lastMsg)) + ret += $"(last message: )"; + else if (opts.IncludeCreated && m.MetadataPrivacy.TryGet(lookupCtx, m.Created, out var created)) + ret += $"(created at )"; + else if (opts.IncludePronouns && m.PronounsFor(lookupCtx) is { } pronouns) + ret += $"({pronouns})"; + else if (m.HasProxyTags) + { + var proxyTagsString = m.ProxyTagsString(); + if (proxyTagsString.Length > 100) // arbitrary threshold for now, tweak? + proxyTagsString = "tags too long, see member card"; + ret += $"*(*{proxyTagsString}*)*"; + } + break; } - break; - } } return ret; })); } - + void LongRenderer(EmbedBuilder eb, IEnumerable page) { var zone = ctx.System?.Zone ?? DateTimeZone.Utc; foreach (var m in page) { var profile = new StringBuilder($"**ID**: {m.Hid}"); - + if (m.DisplayName != null && m.NamePrivacy.CanAccess(lookupCtx)) profile.Append($"\n**Display name**: {m.DisplayName}"); - - if (m.PronounsFor(lookupCtx) is {} pronouns) + + if (m.PronounsFor(lookupCtx) is { } pronouns) profile.Append($"\n**Pronouns**: {pronouns}"); - - if (m.BirthdayFor(lookupCtx) != null) + + if (m.BirthdayFor(lookupCtx) != null) profile.Append($"\n**Birthdate**: {m.BirthdayString}"); - - if (m.ProxyTags.Count > 0) + + if (m.ProxyTags.Count > 0) profile.Append($"\n**Proxy tags**: {m.ProxyTagsString()}"); - - if ((opts.IncludeMessageCount || opts.SortProperty == SortProperty.MessageCount) && m.MessageCountFor(lookupCtx) is {} count && count > 0) + + if ((opts.IncludeMessageCount || opts.SortProperty == SortProperty.MessageCount) && m.MessageCountFor(lookupCtx) is { } count && count > 0) profile.Append($"\n**Message count:** {count}"); - + if ((opts.IncludeLastMessage || opts.SortProperty == SortProperty.LastMessage) && m.MetadataPrivacy.TryGet(lookupCtx, m.LastMessage, out var lastMsg)) profile.Append($"\n**Last message:** {DiscordUtils.SnowflakeToInstant(lastMsg.Value).FormatZoned(zone)}"); - + if ((opts.IncludeLastSwitch || opts.SortProperty == SortProperty.LastSwitch) && m.MetadataPrivacy.TryGet(lookupCtx, m.LastSwitchTime, out var lastSw)) profile.Append($"\n**Last switched in:** {lastSw.Value.FormatZoned(zone)}"); if ((opts.IncludeCreated || opts.SortProperty == SortProperty.CreationDate) && m.MetadataPrivacy.TryGet(lookupCtx, m.Created, out var created)) profile.Append($"\n**Created on:** {created.FormatZoned(zone)}"); - - if (opts.IncludeAvatar && m.AvatarFor(lookupCtx) is {} avatar) + + if (opts.IncludeAvatar && m.AvatarFor(lookupCtx) is { } avatar) profile.Append($"\n**Avatar URL:** {avatar.TryGetCleanCdnUrl()}"); - if (m.DescriptionFor(lookupCtx) is {} desc) + if (m.DescriptionFor(lookupCtx) is { } desc) profile.Append($"\n\n{desc}"); - + if (m.MemberVisibility == PrivacyLevel.Private) profile.Append("\n*(this member is hidden)*"); - + eb.Field(new(m.NameFor(ctx), profile.ToString().Truncate(1024))); } } } } -} +} \ No newline at end of file diff --git a/PluralKit.Bot/Commands/Lists/MemberListOptions.cs b/PluralKit.Bot/Commands/Lists/MemberListOptions.cs index 4ae69552..e77aece6 100644 --- a/PluralKit.Bot/Commands/Lists/MemberListOptions.cs +++ b/PluralKit.Bot/Commands/Lists/MemberListOptions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -19,7 +19,7 @@ namespace PluralKit.Bot public GroupId? GroupFilter { get; set; } public string? Search { get; set; } public bool SearchDescription { get; set; } - + public ListType Type { get; set; } public bool IncludeMessageCount { get; set; } public bool IncludeLastSwitch { get; set; } @@ -27,7 +27,7 @@ namespace PluralKit.Bot public bool IncludeCreated { get; set; } public bool IncludeAvatar { get; set; } public bool IncludePronouns { get; set; } - + public string CreateFilterString() { var str = new StringBuilder(); @@ -46,7 +46,7 @@ namespace PluralKit.Bot SortProperty.Random => "randomly", _ => new ArgumentOutOfRangeException($"Couldn't find readable string for sort property {SortProperty}") }); - + if (Search != null) { str.Append($", searching for \"{Search}\""); @@ -67,7 +67,7 @@ namespace PluralKit.Bot public DatabaseViewsExt.MemberListQueryOptions ToQueryOptions() => new DatabaseViewsExt.MemberListQueryOptions { - PrivacyFilter = PrivacyFilter, + PrivacyFilter = PrivacyFilter, GroupFilter = GroupFilter, Search = Search, SearchDescription = SearchDescription @@ -80,7 +80,7 @@ namespace PluralKit.Bot { IComparer ReverseMaybe(IComparer c) => opts.Reverse ? Comparer.Create((a, b) => c.Compare(b, a)) : c; - + var randGen = new global::System.Random(); var culture = StringComparer.InvariantCultureIgnoreCase; @@ -112,7 +112,7 @@ namespace PluralKit.Bot .ThenBy(m => m.NameFor(ctx), culture); } } - + public enum SortProperty { Name, diff --git a/PluralKit.Bot/Commands/Member.cs b/PluralKit.Bot/Commands/Member.cs index 620463a2..5b248ee0 100644 --- a/PluralKit.Bot/Commands/Member.cs +++ b/PluralKit.Bot/Commands/Member.cs @@ -21,7 +21,7 @@ namespace PluralKit.Bot private readonly ModelRepository _repo; private readonly EmbedService _embeds; private readonly HttpClient _client; - + public Member(EmbedService embeds, IDatabase db, ModelRepository repo, HttpClient client) { _embeds = embeds; @@ -30,16 +30,18 @@ namespace PluralKit.Bot _client = client; } - public async Task NewMember(Context ctx) { + public async Task NewMember(Context ctx) + { if (ctx.System == null) throw Errors.NoSystemError; var memberName = ctx.RemainderOrNull() ?? throw new PKSyntaxError("You must pass a member name."); - + // Hard name length cap if (memberName.Length > Limits.MaxMemberNameLength) throw Errors.MemberNameTooLongError(memberName.Length); // Warn if there's already a member by this name var existingMember = await _db.Execute(c => _repo.GetMemberByName(c, ctx.System.Id, memberName)); - if (existingMember != null) { + if (existingMember != null) + { var msg = $"{Emojis.Warn} You already have a member in your system with the name \"{existingMember.NameFor(ctx)}\" (with ID `{existingMember.Hid}`). Do you want to create another member with the same name?"; if (!await ctx.PromptYesNo(msg, "Create")) throw new PKError("Member creation cancelled."); } @@ -61,10 +63,13 @@ namespace PluralKit.Bot Exception imageMatchError = null; if (avatarArg != null) { - try { + try + { await AvatarUtils.VerifyAvatarOrThrow(_client, avatarArg.Url); await _db.Execute(conn => _repo.UpdateMember(conn, member.Id, new MemberPatch { AvatarUrl = avatarArg.Url })); - } catch (Exception e) { + } + catch (Exception e) + { imageMatchError = e; } } @@ -72,7 +77,7 @@ namespace PluralKit.Bot // Send confirmation and space hint await ctx.Reply($"{Emojis.Success} Member \"{memberName}\" (`{member.Hid}`) registered! Check out the getting started page for how to get a member up and running: https://pluralkit.me/start#create-a-member"); if (await _db.Execute(conn => conn.QuerySingleAsync("select has_private_members(@System)", - new {System = ctx.System.Id}))) //if has private members + new { System = ctx.System.Id }))) //if has private members await ctx.Reply($"{Emojis.Warn} This member is currently **public**. To change this, use `pk;member {member.Hid} private`."); if (avatarArg != null) if (imageMatchError == null) @@ -86,7 +91,7 @@ namespace PluralKit.Bot else if (memberCount >= Limits.MaxMembersWarnThreshold(memberLimit)) await ctx.Reply($"{Emojis.Warn} You are approaching the per-system member limit ({memberCount} / {memberLimit} members). Please review your member list for unused or duplicate members."); } - + public async Task ViewMember(Context ctx, PKMember target) { var system = await _db.Execute(c => _repo.GetSystem(c, target.System)); @@ -96,10 +101,10 @@ namespace PluralKit.Bot public async Task Soulscream(Context ctx, PKMember target) { // this is for a meme, please don't take this code seriously. :) - + var name = target.NameFor(ctx.LookupContextFor(target)); var encoded = HttpUtility.UrlEncode(name); - + var resp = await _client.GetAsync($"https://onomancer.sibr.dev/api/generateStats2?name={encoded}"); if (resp.StatusCode != HttpStatusCode.OK) // lol @@ -116,4 +121,4 @@ namespace PluralKit.Bot await ctx.Reply(embed: eb.Build()); } } -} +} \ No newline at end of file diff --git a/PluralKit.Bot/Commands/MemberAvatar.cs b/PluralKit.Bot/Commands/MemberAvatar.cs index 094d04a2..bb4c0f2c 100644 --- a/PluralKit.Bot/Commands/MemberAvatar.cs +++ b/PluralKit.Bot/Commands/MemberAvatar.cs @@ -21,7 +21,7 @@ namespace PluralKit.Bot _repo = repo; _client = client; } - + private async Task AvatarClear(AvatarLocation location, Context ctx, PKMember target, MemberGuildSettings? mgs) { await UpdateAvatar(location, ctx, target, null); @@ -36,7 +36,7 @@ namespace PluralKit.Bot { if (mgs?.AvatarUrl != null) await ctx.Reply($"{Emojis.Success} Member avatar cleared. Note that this member has a server-specific avatar set here, type `pk;member {target.Reference()} serveravatar clear` if you wish to clear that too."); - else + else await ctx.Reply($"{Emojis.Success} Member avatar cleared."); } } @@ -57,10 +57,10 @@ namespace PluralKit.Bot if (location == AvatarLocation.Server) throw new PKError($"This member does not have a server avatar set. Type `pk;member {target.Reference()} avatar` to see their global avatar."); } - + var field = location == AvatarLocation.Server ? $"server avatar (for {ctx.Guild.Name})" : "avatar"; var cmd = location == AvatarLocation.Server ? "serveravatar" : "avatar"; - + var eb = new EmbedBuilder() .Title($"{target.NameFor(ctx)}'s {field}") .Image(new(currentValue?.TryGetCleanCdnUrl())); @@ -75,7 +75,7 @@ namespace PluralKit.Bot var guildData = await _db.Execute(c => _repo.GetMemberGuild(c, ctx.Guild.Id, target.Id)); await AvatarCommandTree(AvatarLocation.Server, ctx, target, guildData); } - + public async Task Avatar(Context ctx, PKMember target) { var guildData = ctx.Guild != null ? @@ -119,7 +119,7 @@ namespace PluralKit.Bot AvatarLocation.Member => "avatar", _ => throw new ArgumentOutOfRangeException(nameof(location)) }; - + var serverFrag = location switch { AvatarLocation.Server => $" This avatar will now be used when proxying in this server (**{ctx.Guild.Name}**).", @@ -137,8 +137,8 @@ namespace PluralKit.Bot // The attachment's already right there, no need to preview it. var hasEmbed = avatar.Source != AvatarSource.Attachment; - return hasEmbed - ? ctx.Reply(msg, embed: new EmbedBuilder().Image(new(avatar.Url)).Build()) + return hasEmbed + ? ctx.Reply(msg, embed: new EmbedBuilder().Image(new(avatar.Url)).Build()) : ctx.Reply(msg); } diff --git a/PluralKit.Bot/Commands/MemberEdit.cs b/PluralKit.Bot/Commands/MemberEdit.cs index 53de9d75..8953d616 100644 --- a/PluralKit.Bot/Commands/MemberEdit.cs +++ b/PluralKit.Bot/Commands/MemberEdit.cs @@ -24,10 +24,10 @@ namespace PluralKit.Bot _client = client; } - public async Task Name(Context ctx, PKMember target) + public async Task Name(Context ctx, PKMember target) { - ctx.CheckSystem().CheckOwnMember(target); - + ctx.CheckSystem().CheckOwnMember(target); + var newName = ctx.RemainderOrNull() ?? throw new PKSyntaxError("You must pass a new name for the member."); // Hard name length cap @@ -35,14 +35,14 @@ namespace PluralKit.Bot // Warn if there's already a member by this name var existingMember = await _db.Execute(conn => _repo.GetMemberByName(conn, ctx.System.Id, newName)); - if (existingMember != null && existingMember.Id != target.Id) + if (existingMember != null && existingMember.Id != target.Id) { var msg = $"{Emojis.Warn} You already have a member in your system with the name \"{existingMember.NameFor(ctx)}\" (`{existingMember.Hid}`). Do you want to rename this member to that name too?"; if (!await ctx.PromptYesNo(msg, "Rename")) throw new PKError("Member renaming cancelled."); } // Rename the member - var patch = new MemberPatch {Name = Partial.Present(newName)}; + var patch = new MemberPatch { Name = Partial.Present(newName) }; await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch)); await ctx.Reply($"{Emojis.Success} Member renamed."); @@ -57,15 +57,16 @@ namespace PluralKit.Bot } } - public async Task Description(Context ctx, PKMember target) { + public async Task Description(Context ctx, PKMember target) + { if (await ctx.MatchClear("this member's description")) { ctx.CheckOwnMember(target); - var patch = new MemberPatch {Description = Partial.Null()}; + var patch = new MemberPatch { Description = Partial.Null() }; await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch)); await ctx.Reply($"{Emojis.Success} Member description cleared."); - } + } else if (!ctx.HasNext()) { if (!target.DescriptionPrivacy.CanAccess(ctx.LookupContextFor(target.System))) @@ -81,7 +82,7 @@ namespace PluralKit.Bot await ctx.Reply(embed: new EmbedBuilder() .Title("Member description") .Description(target.Description) - .Field(new("\u200B", $"To print the description with formatting, type `pk;member {target.Reference()} description -raw`." + .Field(new("\u200B", $"To print the description with formatting, type `pk;member {target.Reference()} description -raw`." + (ctx.System?.Id == target.System ? $" To clear it, type `pk;member {target.Reference()} description -clear`." : ""))) .Build()); } @@ -92,23 +93,24 @@ namespace PluralKit.Bot var description = ctx.RemainderOrNull().NormalizeLineEndSpacing(); if (description.IsLongerThan(Limits.MaxDescriptionLength)) throw Errors.DescriptionTooLongError(description.Length); - - var patch = new MemberPatch {Description = Partial.Present(description)}; + + var patch = new MemberPatch { Description = Partial.Present(description) }; await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch)); - + await ctx.Reply($"{Emojis.Success} Member description changed."); } } - - public async Task Pronouns(Context ctx, PKMember target) { + + public async Task Pronouns(Context ctx, PKMember target) + { if (await ctx.MatchClear("this member's pronouns")) { ctx.CheckOwnMember(target); - var patch = new MemberPatch {Pronouns = Partial.Null()}; + var patch = new MemberPatch { Pronouns = Partial.Null() }; await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch)); await ctx.Reply($"{Emojis.Success} Member pronouns cleared."); - } + } else if (!ctx.HasNext()) { if (!target.PronounPrivacy.CanAccess(ctx.LookupContextFor(target.System))) @@ -131,10 +133,10 @@ namespace PluralKit.Bot var pronouns = ctx.RemainderOrNull().NormalizeLineEndSpacing(); if (pronouns.IsLongerThan(Limits.MaxPronounsLength)) throw Errors.MemberPronounsTooLongError(pronouns.Length); - - var patch = new MemberPatch {Pronouns = Partial.Present(pronouns)}; + + var patch = new MemberPatch { Pronouns = Partial.Present(pronouns) }; await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch)); - + await ctx.Reply($"{Emojis.Success} Member pronouns changed."); } } @@ -145,7 +147,7 @@ namespace PluralKit.Bot async Task ClearBannerImage() { - await _db.Execute(c => _repo.UpdateMember(c, target.Id, new MemberPatch {BannerImage = null})); + await _db.Execute(c => _repo.UpdateMember(c, target.Id, new MemberPatch { BannerImage = null })); await ctx.Reply($"{Emojis.Success} Member banner image cleared."); } @@ -153,7 +155,7 @@ namespace PluralKit.Bot { await AvatarUtils.VerifyAvatarOrThrow(_client, img.Url, isFullSizeImage: true); - await _db.Execute(c => _repo.UpdateMember(c, target.Id, new MemberPatch {BannerImage = img.Url})); + await _db.Execute(c => _repo.UpdateMember(c, target.Id, new MemberPatch { BannerImage = img.Url })); var msg = img.Source switch { @@ -165,8 +167,8 @@ namespace PluralKit.Bot // The attachment's already right there, no need to preview it. var hasEmbed = img.Source != AvatarSource.Attachment; - await (hasEmbed - ? ctx.Reply(msg, embed: new EmbedBuilder().Image(new(img.Url)).Build()) + await (hasEmbed + ? ctx.Reply(msg, embed: new EmbedBuilder().Image(new(img.Url)).Build()) : ctx.Reply(msg)); } @@ -186,7 +188,7 @@ namespace PluralKit.Bot if (await ctx.MatchClear("this member's banner image")) await ClearBannerImage(); - else if (await ctx.MatchImage() is {} img) + else if (await ctx.MatchImage() is { } img) await SetBannerImage(img); else await ShowBannerImage(); @@ -198,10 +200,10 @@ namespace PluralKit.Bot if (await ctx.MatchClear()) { ctx.CheckOwnMember(target); - - var patch = new MemberPatch {Color = Partial.Null()}; + + var patch = new MemberPatch { Color = Partial.Null() }; await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch)); - + await ctx.Reply($"{Emojis.Success} Member color cleared."); } else if (!ctx.HasNext()) @@ -230,8 +232,8 @@ namespace PluralKit.Bot if (color.StartsWith("#")) color = color.Substring(1); if (!Regex.IsMatch(color, "^[0-9a-fA-F]{6}$")) throw Errors.InvalidColorError(color); - - var patch = new MemberPatch {Color = Partial.Present(color.ToLowerInvariant())}; + + var patch = new MemberPatch { Color = Partial.Present(color.ToLowerInvariant()) }; await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch)); await ctx.Reply(embed: new EmbedBuilder() @@ -246,17 +248,17 @@ namespace PluralKit.Bot if (await ctx.MatchClear("this member's birthday")) { ctx.CheckOwnMember(target); - - var patch = new MemberPatch {Birthday = Partial.Null()}; + + var patch = new MemberPatch { Birthday = Partial.Null() }; await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch)); await ctx.Reply($"{Emojis.Success} Member birthdate cleared."); - } + } else if (!ctx.HasNext()) { if (!target.BirthdayPrivacy.CanAccess(ctx.LookupContextFor(target.System))) throw Errors.LookupNotAllowed; - + if (target.Birthday == null) await ctx.Reply("This member does not have a birthdate set." + (ctx.System?.Id == target.System ? $" To set one, type `pk;member {target.Reference()} birthdate `." : "")); @@ -267,22 +269,22 @@ namespace PluralKit.Bot else { ctx.CheckOwnMember(target); - + var birthdayStr = ctx.RemainderOrNull(); var birthday = DateUtils.ParseDate(birthdayStr, true); if (birthday == null) throw Errors.BirthdayParseError(birthdayStr); - - var patch = new MemberPatch {Birthday = Partial.Present(birthday)}; + + var patch = new MemberPatch { Birthday = Partial.Present(birthday) }; await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch)); await ctx.Reply($"{Emojis.Success} Member birthdate changed."); } } - + private async Task CreateMemberNameInfoEmbed(Context ctx, PKMember target) { var lcx = ctx.LookupContextFor(target); - + MemberGuildSettings memberGuildConfig = null; if (ctx.Guild != null) memberGuildConfig = await _db.Execute(c => _repo.GetMemberGuild(c, ctx.Guild.Id, target.Id)); @@ -329,12 +331,12 @@ namespace PluralKit.Bot await ctx.Reply(successStr); } - + if (await ctx.MatchClear("this member's display name")) { ctx.CheckOwnMember(target); - - var patch = new MemberPatch {DisplayName = Partial.Null()}; + + var patch = new MemberPatch { DisplayName = Partial.Null() }; await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch)); await PrintSuccess($"{Emojis.Success} Member display name cleared. This member will now be proxied using their member name \"{target.NameFor(ctx)}\"."); @@ -365,25 +367,25 @@ namespace PluralKit.Bot else { ctx.CheckOwnMember(target); - + var newDisplayName = ctx.RemainderOrNull(); - - var patch = new MemberPatch {DisplayName = Partial.Present(newDisplayName)}; + + var patch = new MemberPatch { DisplayName = Partial.Present(newDisplayName) }; await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch)); await PrintSuccess($"{Emojis.Success} Member display name changed. This member will now be proxied using the name \"{newDisplayName}\"."); } } - + public async Task ServerName(Context ctx, PKMember target) { ctx.CheckGuildContext(); - + if (await ctx.MatchClear("this member's server name")) { ctx.CheckOwnMember(target); - var patch = new MemberGuildPatch {DisplayName = null}; + var patch = new MemberGuildPatch { DisplayName = null }; await _db.Execute(conn => _repo.UpsertMemberGuild(conn, target.Id, ctx.Guild.Id, patch)); if (target.DisplayName != null) @@ -419,16 +421,16 @@ namespace PluralKit.Bot else { ctx.CheckOwnMember(target); - + var newServerName = ctx.RemainderOrNull(); - - var patch = new MemberGuildPatch {DisplayName = newServerName}; + + var patch = new MemberGuildPatch { DisplayName = newServerName }; await _db.Execute(conn => _repo.UpsertMemberGuild(conn, target.Id, ctx.Guild.Id, patch)); await ctx.Reply($"{Emojis.Success} Member server name changed. This member will now be proxied using the name \"{newServerName}\" in this server ({ctx.Guild.Name})."); } } - + public async Task KeepProxy(Context ctx, PKMember target) { ctx.CheckSystem().CheckOwnMember(target); @@ -446,9 +448,9 @@ namespace PluralKit.Bot return; }; - var patch = new MemberPatch {KeepProxy = Partial.Present(newValue)}; + var patch = new MemberPatch { KeepProxy = Partial.Present(newValue) }; await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch)); - + if (newValue) await ctx.Reply($"{Emojis.Success} Member proxy tags will now be included in the resulting message when proxying."); else @@ -473,7 +475,7 @@ namespace PluralKit.Bot return; }; - var patch = new MemberPatch {AllowAutoproxy = Partial.Present(newValue)}; + var patch = new MemberPatch { AllowAutoproxy = Partial.Present(newValue) }; await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch)); if (newValue) @@ -491,18 +493,18 @@ namespace PluralKit.Bot { await ctx.Reply(embed: new EmbedBuilder() .Title($"Current privacy settings for {target.NameFor(ctx)}") - .Field(new("Name (replaces name with display name if member has one)",target.NamePrivacy.Explanation())) + .Field(new("Name (replaces name with display name if member has one)", target.NamePrivacy.Explanation())) .Field(new("Description", target.DescriptionPrivacy.Explanation())) .Field(new("Avatar", target.AvatarPrivacy.Explanation())) .Field(new("Birthday", target.BirthdayPrivacy.Explanation())) .Field(new("Pronouns", target.PronounPrivacy.Explanation())) - .Field(new("Meta (message count, last front, last message)",target.MetadataPrivacy.Explanation())) + .Field(new("Meta (message count, last front, last message)", target.MetadataPrivacy.Explanation())) .Field(new("Visibility", target.MemberVisibility.Explanation())) .Description("To edit privacy settings, use the command:\n`pk;member privacy `\n\n- `subject` is one of `name`, `description`, `avatar`, `birthday`, `pronouns`, `created`, `messages`, `visibility`, or `all`\n- `level` is either `public` or `private`.") - .Build()); + .Build()); return; } - + // Get guild settings (mostly for warnings and such) MemberGuildSettings guildSettings = null; if (ctx.Guild != null) @@ -511,17 +513,17 @@ namespace PluralKit.Bot async Task SetAll(PrivacyLevel level) { await _db.Execute(c => _repo.UpdateMember(c, target.Id, new MemberPatch().WithAllPrivacy(level))); - + if (level == PrivacyLevel.Private) await ctx.Reply($"{Emojis.Success} All {target.NameFor(ctx)}'s privacy settings have been set to **{level.LevelName()}**. Other accounts will now see nothing on the member card."); - else + else await ctx.Reply($"{Emojis.Success} All {target.NameFor(ctx)}'s privacy settings have been set to **{level.LevelName()}**. Other accounts will now see everything on the member card."); } async Task SetLevel(MemberPrivacySubject subject, PrivacyLevel level) { await _db.Execute(c => _repo.UpdateMember(c, target.Id, new MemberPatch().WithPrivacy(subject, level))); - + var subjectName = subject switch { MemberPrivacySubject.Name => "name privacy", @@ -533,7 +535,7 @@ namespace PluralKit.Bot MemberPrivacySubject.Visibility => "visibility", _ => throw new ArgumentOutOfRangeException($"Unknown privacy subject {subject}") }; - + var explanation = (subject, level) switch { (MemberPrivacySubject.Name, PrivacyLevel.Private) => "This member's name is now hidden from other systems, and will be replaced by the member's display name.", @@ -543,7 +545,7 @@ namespace PluralKit.Bot (MemberPrivacySubject.Pronouns, PrivacyLevel.Private) => "This member's pronouns are now hidden from other systems.", (MemberPrivacySubject.Metadata, PrivacyLevel.Private) => "This member's metadata (eg. created timestamp, message count, etc) is now hidden from other systems.", (MemberPrivacySubject.Visibility, PrivacyLevel.Private) => "This member is now hidden from member lists.", - + (MemberPrivacySubject.Name, PrivacyLevel.Public) => "This member's name is no longer hidden from other systems.", (MemberPrivacySubject.Description, PrivacyLevel.Public) => "This member's description is no longer hidden from other systems.", (MemberPrivacySubject.Avatar, PrivacyLevel.Public) => "This member's avatar is no longer hidden from other systems.", @@ -551,16 +553,16 @@ namespace PluralKit.Bot (MemberPrivacySubject.Pronouns, PrivacyLevel.Public) => "This member's pronouns are no longer hidden other systems.", (MemberPrivacySubject.Metadata, PrivacyLevel.Public) => "This member's metadata (eg. created timestamp, message count, etc) is no longer hidden from other systems.", (MemberPrivacySubject.Visibility, PrivacyLevel.Public) => "This member is no longer hidden from member lists.", - + _ => throw new InvalidOperationException($"Invalid subject/level tuple ({subject}, {level})") }; - + await ctx.Reply($"{Emojis.Success} {target.NameFor(ctx)}'s **{subjectName}** has been set to **{level.LevelName()}**. {explanation}"); - + // Name privacy only works given a display name if (subject == MemberPrivacySubject.Name && level == PrivacyLevel.Private && target.DisplayName == null) await ctx.Reply($"{Emojis.Warn} This member does not have a display name set, and name privacy **will not take effect**."); - + // Avatar privacy doesn't apply when proxying if no server avatar is set if (subject == MemberPrivacySubject.Avatar && level == PrivacyLevel.Private && guildSettings?.AvatarUrl == null) await ctx.Reply($"{Emojis.Warn} This member does not have a server avatar set, so *proxying* will **still show the member avatar**. If you want to hide your avatar when proxying here, set a server avatar: `pk;member {target.Reference()} serveravatar`"); @@ -571,17 +573,17 @@ namespace PluralKit.Bot else await SetLevel(ctx.PopMemberPrivacySubject(), ctx.PopPrivacyLevel()); } - + public async Task Delete(Context ctx, PKMember target) { ctx.CheckSystem().CheckOwnMember(target); - + await ctx.Reply($"{Emojis.Warn} Are you sure you want to delete \"{target.NameFor(ctx)}\"? If so, reply to this message with the member's ID (`{target.Hid}`). __***This cannot be undone!***__"); if (!await ctx.ConfirmWithReply(target.Hid)) throw Errors.MemberDeleteCancelled; - + await _db.Execute(conn => _repo.DeleteMember(conn, target.Id)); - + await ctx.Reply($"{Emojis.Success} Member deleted."); } } -} +} \ No newline at end of file diff --git a/PluralKit.Bot/Commands/MemberGroup.cs b/PluralKit.Bot/Commands/MemberGroup.cs index 1c67a30f..e324f233 100644 --- a/PluralKit.Bot/Commands/MemberGroup.cs +++ b/PluralKit.Bot/Commands/MemberGroup.cs @@ -76,14 +76,14 @@ namespace PluralKit.Bot description = "This member has no groups."; else description = string.Join("\n", groups.Select(g => $"[`{g.Hid}`] **{g.DisplayName ?? g.Name}**")); - + if (pctx == LookupContext.ByOwner) { msg += $"\n\nTo add this member to one or more groups, use `pk;m {target.Reference()} group add [group 2] [group 3...]`"; if (groups.Count > 0) msg += $"\nTo remove this member from one or more groups, use `pk;m {target.Reference()} group remove [group 2] [group 3...]`"; } - + await ctx.Reply(msg, (new EmbedBuilder().Title($"{target.Name}'s groups").Description(description)).Build()); } } diff --git a/PluralKit.Bot/Commands/MemberProxy.cs b/PluralKit.Bot/Commands/MemberProxy.cs index bd0b86e3..e46f2bb9 100644 --- a/PluralKit.Bot/Commands/MemberProxy.cs +++ b/PluralKit.Bot/Commands/MemberProxy.cs @@ -11,7 +11,7 @@ namespace PluralKit.Bot { private readonly IDatabase _db; private readonly ModelRepository _repo; - + public MemberProxy(IDatabase db, ModelRepository repo) { _db = db; @@ -31,20 +31,20 @@ namespace PluralKit.Bot if (prefixAndSuffix.Length > 2) throw Errors.ProxyMultipleText; return new ProxyTag(prefixAndSuffix[0], prefixAndSuffix[1]); } - + async Task WarnOnConflict(ProxyTag newTag) { var query = "select * from (select *, (unnest(proxy_tags)).prefix as prefix, (unnest(proxy_tags)).suffix as suffix from members where system = @System) as _ where prefix is not distinct from @Prefix and suffix is not distinct from @Suffix and id != @Existing"; var conflicts = (await _db.Execute(conn => conn.QueryAsync(query, - new {Prefix = newTag.Prefix, Suffix = newTag.Suffix, Existing = target.Id, system = target.System}))).ToList(); - + new { Prefix = newTag.Prefix, Suffix = newTag.Suffix, Existing = target.Id, system = target.System }))).ToList(); + if (conflicts.Count <= 0) return true; var conflictList = conflicts.Select(m => $"- **{m.NameFor(ctx)}**"); var msg = $"{Emojis.Warn} The following members have conflicting proxy tags:\n{string.Join('\n', conflictList)}\nDo you want to proceed anyway?"; return await ctx.PromptYesNo(msg, "Proceed"); } - + // "Sub"command: clear flag if (await ctx.MatchClear()) { @@ -55,10 +55,10 @@ namespace PluralKit.Bot if (!await ctx.PromptYesNo(msg, "Clear")) throw Errors.GenericCancelled(); } - - var patch = new MemberPatch {ProxyTags = Partial.Present(new ProxyTag[0])}; + + var patch = new MemberPatch { ProxyTags = Partial.Present(new ProxyTag[0]) }; await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch)); - + await ctx.Reply($"{Emojis.Success} Proxy tags cleared."); } // "Sub"command: no arguments; will print proxy tags @@ -73,20 +73,20 @@ namespace PluralKit.Bot else if (ctx.Match("add", "append")) { if (!ctx.HasNext(skipFlags: false)) throw new PKSyntaxError("You must pass an example proxy to add (eg. `[text]` or `J:text`)."); - + var tagToAdd = ParseProxyTags(ctx.RemainderOrNull(skipFlags: false)); if (tagToAdd.IsEmpty) throw Errors.EmptyProxyTags(target); if (target.ProxyTags.Contains(tagToAdd)) throw Errors.ProxyTagAlreadyExists(tagToAdd, target); if (tagToAdd.ProxyString.Length > Limits.MaxProxyTagLength) throw new PKError($"Proxy tag too long ({tagToAdd.ProxyString.Length} > {Limits.MaxProxyTagLength} characters)."); - + if (!await WarnOnConflict(tagToAdd)) throw Errors.GenericCancelled(); var newTags = target.ProxyTags.ToList(); newTags.Add(tagToAdd); - var patch = new MemberPatch {ProxyTags = Partial.Present(newTags.ToArray())}; + var patch = new MemberPatch { ProxyTags = Partial.Present(newTags.ToArray()) }; await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch)); await ctx.Reply($"{Emojis.Success} Added proxy tags {tagToAdd.ProxyString.AsCode()}."); @@ -103,7 +103,7 @@ namespace PluralKit.Bot var newTags = target.ProxyTags.ToList(); newTags.Remove(tagToRemove); - var patch = new MemberPatch {ProxyTags = Partial.Present(newTags.ToArray())}; + var patch = new MemberPatch { ProxyTags = Partial.Present(newTags.ToArray()) }; await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch)); await ctx.Reply($"{Emojis.Success} Removed proxy tags {tagToRemove.ProxyString.AsCode()}."); @@ -122,14 +122,14 @@ namespace PluralKit.Bot if (!await ctx.PromptYesNo(msg, "Replace")) throw Errors.GenericCancelled(); } - + if (!await WarnOnConflict(requestedTag)) throw Errors.GenericCancelled(); - var newTags = new[] {requestedTag}; - var patch = new MemberPatch {ProxyTags = Partial.Present(newTags)}; + var newTags = new[] { requestedTag }; + var patch = new MemberPatch { ProxyTags = Partial.Present(newTags) }; await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch)); - + await ctx.Reply($"{Emojis.Success} Member proxy tags set to {requestedTag.ProxyString.AsCode()}."); } } diff --git a/PluralKit.Bot/Commands/Message.cs b/PluralKit.Bot/Commands/Message.cs index 6adbbaee..ddaea6b0 100644 --- a/PluralKit.Bot/Commands/Message.cs +++ b/PluralKit.Bot/Commands/Message.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System.Threading.Tasks; using Myriad.Builders; @@ -17,7 +17,7 @@ namespace PluralKit.Bot public class ProxiedMessage { private static readonly Duration EditTimeout = Duration.FromMinutes(10); - + private readonly IDatabase _db; private readonly ModelRepository _repo; private readonly EmbedService _embeds; @@ -48,7 +48,7 @@ namespace PluralKit.Bot if (ctx.System.Id != msg.System.Id) throw new PKError("Can't edit a message sent by a different system."); - + if (_cache.GetRootChannel(msg.Message.Channel).Id != msg.Message.Channel) throw new PKError("PluralKit cannot edit messages in threads."); @@ -61,7 +61,7 @@ namespace PluralKit.Bot try { var editedMsg = await _webhookExecutor.EditWebhookMessage(msg.Message.Channel, msg.Message.Mid, newContent); - + if (ctx.Guild == null) await _rest.CreateReaction(ctx.Channel.Id, ctx.Message.Id, new() { Name = Emojis.Success }); @@ -75,7 +75,7 @@ namespace PluralKit.Bot throw new PKError("Could not edit message."); } } - + private async Task GetMessageToEdit(Context ctx) { await using var conn = await _db.Obtain(); @@ -112,14 +112,14 @@ namespace PluralKit.Bot var lastMessage = await _repo.GetLastMessage(conn, ctx.Guild.Id, ctx.Channel.Id, ctx.Author.Id); if (lastMessage == null) return null; - + var timestamp = DiscordUtils.SnowflakeToInstant(lastMessage.Mid); if (_clock.GetCurrentInstant() - timestamp > EditTimeout) return null; return lastMessage; } - + public async Task GetMessage(Context ctx) { var (messageId, _) = ctx.MatchMessage(true); @@ -129,7 +129,7 @@ namespace PluralKit.Bot throw new PKSyntaxError("You must pass a message ID or link."); throw new PKSyntaxError($"Could not parse {ctx.PeekArgument().AsCode()} as a message ID or link."); } - + var message = await _db.Execute(c => _repo.GetMessage(c, messageId.Value)); if (message == null) throw Errors.MessageNotFound(messageId.Value); @@ -155,4 +155,4 @@ namespace PluralKit.Bot await ctx.Reply(embed: await _embeds.CreateMessageInfoEmbed(message)); } } -} +} \ No newline at end of file diff --git a/PluralKit.Bot/Commands/Misc.cs b/PluralKit.Bot/Commands/Misc.cs index ea257049..ed12d6fc 100644 --- a/PluralKit.Bot/Commands/Misc.cs +++ b/PluralKit.Bot/Commands/Misc.cs @@ -21,7 +21,8 @@ using Myriad.Rest.Exceptions; using Myriad.Rest.Types.Requests; using Myriad.Types; -namespace PluralKit.Bot { +namespace PluralKit.Bot +{ public class Misc { private readonly BotConfig _botConfig; @@ -55,31 +56,31 @@ namespace PluralKit.Bot { _proxy = proxy; _matcher = matcher; } - + public async Task Invite(Context ctx) { var clientId = _botConfig.ClientId ?? _cluster.Application?.Id; - var permissions = + var permissions = PermissionSet.AddReactions | - PermissionSet.AttachFiles | + PermissionSet.AttachFiles | PermissionSet.EmbedLinks | PermissionSet.ManageMessages | PermissionSet.ManageWebhooks | - PermissionSet.ReadMessageHistory | + PermissionSet.ReadMessageHistory | PermissionSet.SendMessages; - + var invite = $"https://discord.com/oauth2/authorize?client_id={clientId}&scope=bot%20applications.commands&permissions={(ulong)permissions}"; await ctx.Reply($"{Emojis.Success} Use this link to add PluralKit to your server:\n<{invite}>"); } - + public async Task Stats(Context ctx) { var timeBefore = SystemClock.Instance.GetCurrentInstant(); var msg = await ctx.Reply($"..."); var timeAfter = SystemClock.Instance.GetCurrentInstant(); var apiLatency = timeAfter - timeBefore; - + var messagesReceived = _metrics.Snapshot.GetForContext("Bot").Meters.FirstOrDefault(m => m.MultidimensionalName == BotMetrics.MessagesReceived.Name)?.Value; var messagesProxied = _metrics.Snapshot.GetForContext("Bot").Meters.FirstOrDefault(m => m.MultidimensionalName == BotMetrics.MessagesProxied.Name)?.Value; var commandsRun = _metrics.Snapshot.GetForContext("Bot").Meters.FirstOrDefault(m => m.MultidimensionalName == BotMetrics.CommandsRun.Name)?.Value; @@ -94,7 +95,7 @@ namespace PluralKit.Bot { var shardTotal = ctx.Cluster.Shards.Count; var shardUpTotal = _shards.Shards.Where(x => x.Connected).Count(); var shardInfo = _shards.GetShardInfo(ctx.Shard); - + var process = Process.GetCurrentProcess(); var memoryUsage = process.WorkingSet64; @@ -102,7 +103,7 @@ namespace PluralKit.Bot { var shardUptime = now - shardInfo.LastConnectionTime; var embed = new EmbedBuilder(); - if (messagesReceived != null) embed.Field(new("Messages processed",$"{messagesReceived.OneMinuteRate * 60:F1}/m ({messagesReceived.FifteenMinuteRate * 60:F1}/m over 15m)", true)); + if (messagesReceived != null) embed.Field(new("Messages processed", $"{messagesReceived.OneMinuteRate * 60:F1}/m ({messagesReceived.FifteenMinuteRate * 60:F1}/m over 15m)", true)); if (messagesProxied != null) embed.Field(new("Messages proxied", $"{messagesProxied.OneMinuteRate * 60:F1}/m ({messagesProxied.FifteenMinuteRate * 60:F1}/m over 15m)", true)); if (commandsRun != null) embed.Field(new("Commands executed", $"{commandsRun.OneMinuteRate * 60:F1}/m ({commandsRun.FifteenMinuteRate * 60:F1}/m over 15m)", true)); @@ -114,10 +115,10 @@ namespace PluralKit.Bot { .Field(new("Latency", $"API: {apiLatency.TotalMilliseconds:F0} ms, shard: {shardInfo.ShardLatency.Milliseconds} ms", true)) .Field(new("Total numbers", $"{totalSystems:N0} systems, {totalMembers:N0} members, {totalGroups:N0} groups, {totalSwitches:N0} switches, {totalMessages:N0} messages")) .Timestamp(now.ToDateTimeOffset().ToString("O")) - .Footer(new($"PluralKit {BuildInfoService.Version} • https://github.com/xSke/PluralKit"));; + .Footer(new($"PluralKit {BuildInfoService.Version} • https://github.com/xSke/PluralKit")); ; await ctx.Rest.EditMessage(msg.ChannelId, msg.Id, - new MessageEditRequest {Content = "", Embed = embed.Build()}); + new MessageEditRequest { Content = "", Embed = embed.Build() }); } } -} +} \ No newline at end of file diff --git a/PluralKit.Bot/Commands/Privacy/ContextPrivacyExt.cs b/PluralKit.Bot/Commands/Privacy/ContextPrivacyExt.cs index cabd82b4..99ebe745 100644 --- a/PluralKit.Bot/Commands/Privacy/ContextPrivacyExt.cs +++ b/PluralKit.Bot/Commands/Privacy/ContextPrivacyExt.cs @@ -1,4 +1,4 @@ -using PluralKit.Core; +using PluralKit.Core; namespace PluralKit.Bot { @@ -14,7 +14,7 @@ namespace PluralKit.Bot if (!ctx.HasNext()) throw new PKSyntaxError("You must pass a privacy level (`public` or `private`)"); - + throw new PKSyntaxError($"Invalid privacy level {ctx.PopArgument().AsCode()} (must be `public` or `private`)."); } @@ -22,25 +22,25 @@ namespace PluralKit.Bot { if (!SystemPrivacyUtils.TryParseSystemPrivacy(ctx.PeekArgument(), out var subject)) throw new PKSyntaxError($"Invalid privacy subject {ctx.PopArgument().AsCode()} (must be `description`, `members`, `front`, `fronthistory`, `groups`, or `all`)."); - + ctx.PopArgument(); return subject; } - + public static MemberPrivacySubject PopMemberPrivacySubject(this Context ctx) { if (!MemberPrivacyUtils.TryParseMemberPrivacy(ctx.PeekArgument(), out var subject)) throw new PKSyntaxError($"Invalid privacy subject {ctx.PopArgument().AsCode()} (must be `name`, `description`, `avatar`, `birthday`, `pronouns`, `metadata`, `visibility`, or `all`)."); - + ctx.PopArgument(); return subject; } - + public static GroupPrivacySubject PopGroupPrivacySubject(this Context ctx) { if (!GroupPrivacyUtils.TryParseGroupPrivacy(ctx.PeekArgument(), out var subject)) throw new PKSyntaxError($"Invalid privacy subject {ctx.PopArgument().AsCode()} (must be `description`, `icon`, `visibility`, or `all`)."); - + ctx.PopArgument(); return subject; } @@ -51,7 +51,7 @@ namespace PluralKit.Bot if (ctx.MatchFlag("a", "all")) privacy = false; if (pctx == LookupContext.ByNonOwner && !privacy) throw Errors.LookupNotAllowed; - return privacy; + return privacy; } } -} +} \ No newline at end of file diff --git a/PluralKit.Bot/Commands/Random.cs b/PluralKit.Bot/Commands/Random.cs index 6c154cbc..79afe3ba 100644 --- a/PluralKit.Bot/Commands/Random.cs +++ b/PluralKit.Bot/Commands/Random.cs @@ -33,7 +33,7 @@ namespace PluralKit.Bot return _repo.GetSystemMembers(c, ctx.System.Id) .Where(m => m.MemberVisibility == PrivacyLevel.Public); }).ToListAsync(); - + if (members == null || !members.Any()) throw new PKError("Your system has no members! Please create at least one member before using this command."); diff --git a/PluralKit.Bot/Commands/ServerConfig.cs b/PluralKit.Bot/Commands/ServerConfig.cs index 12641288..f8d70733 100644 --- a/PluralKit.Bot/Commands/ServerConfig.cs +++ b/PluralKit.Bot/Commands/ServerConfig.cs @@ -29,23 +29,23 @@ namespace PluralKit.Bot public async Task SetLogChannel(Context ctx) { ctx.CheckGuildContext().CheckAuthorPermission(PermissionSet.ManageGuild, "Manage Server"); - + if (await ctx.MatchClear("the server log channel")) { - await _db.Execute(conn => _repo.UpsertGuild(conn, ctx.Guild.Id, new GuildPatch {LogChannel = null})); + await _db.Execute(conn => _repo.UpsertGuild(conn, ctx.Guild.Id, new GuildPatch { LogChannel = null })); await ctx.Reply($"{Emojis.Success} Proxy logging channel cleared."); return; } - + if (!ctx.HasNext()) throw new PKSyntaxError("You must pass a #channel to set, or `clear` to clear it."); - + Channel channel = null; var channelString = ctx.PeekArgument(); channel = await ctx.MatchChannel(); if (channel == null || channel.GuildId != ctx.Guild.Id) throw Errors.ChannelNotFound(channelString); - var patch = new GuildPatch {LogChannel = channel.Id}; + var patch = new GuildPatch { LogChannel = channel.Id }; await _db.Execute(conn => _repo.UpsertGuild(conn, ctx.Guild.Id, patch)); await ctx.Reply($"{Emojis.Success} Proxy logging channel set to #{channel.Name}."); } @@ -59,12 +59,12 @@ namespace PluralKit.Bot affectedChannels = _cache.GetGuildChannels(ctx.Guild.Id).Where(x => x.Type == Channel.ChannelType.GuildText).ToList(); else if (!ctx.HasNext()) throw new PKSyntaxError("You must pass one or more #channels."); else while (ctx.HasNext()) - { - var channelString = ctx.PeekArgument(); - var channel = await ctx.MatchChannel(); - if (channel == null || channel.GuildId != ctx.Guild.Id) throw Errors.ChannelNotFound(channelString); - affectedChannels.Add(channel); - } + { + var channelString = ctx.PeekArgument(); + var channel = await ctx.MatchChannel(); + if (channel == null || channel.GuildId != ctx.Guild.Id) throw Errors.ChannelNotFound(channelString); + affectedChannels.Add(channel); + } ulong? logChannel = null; await using (var conn = await _db.Obtain()) @@ -76,8 +76,8 @@ namespace PluralKit.Bot blacklist.ExceptWith(affectedChannels.Select(c => c.Id)); else blacklist.UnionWith(affectedChannels.Select(c => c.Id)); - - var patch = new GuildPatch {LogBlacklist = blacklist.ToArray()}; + + var patch = new GuildPatch { LogBlacklist = blacklist.ToArray() }; await _repo.UpsertGuild(conn, ctx.Guild.Id, patch); } @@ -91,7 +91,7 @@ namespace PluralKit.Bot ctx.CheckGuildContext().CheckAuthorPermission(PermissionSet.ManageGuild, "Manage Server"); var blacklist = await _db.Execute(c => _repo.GetGuild(c, ctx.Guild.Id)); - + // Resolve all channels from the cache and order by position var channels = blacklist.Blacklist .Select(id => _cache.GetChannelOrNull(id)) @@ -112,7 +112,7 @@ namespace PluralKit.Bot { string CategoryName(ulong? id) => id != null ? _cache.GetChannel(id.Value).Name : "(no category)"; - + ulong? lastCategory = null; var fieldValue = new StringBuilder(); @@ -144,13 +144,13 @@ namespace PluralKit.Bot affectedChannels = _cache.GetGuildChannels(ctx.Guild.Id).Where(x => x.Type == Channel.ChannelType.GuildText).ToList(); else if (!ctx.HasNext()) throw new PKSyntaxError("You must pass one or more #channels."); else while (ctx.HasNext()) - { - var channelString = ctx.PeekArgument(); - var channel = await ctx.MatchChannel(); - if (channel == null || channel.GuildId != ctx.Guild.Id) throw Errors.ChannelNotFound(channelString); - affectedChannels.Add(channel); - } - + { + var channelString = ctx.PeekArgument(); + var channel = await ctx.MatchChannel(); + if (channel == null || channel.GuildId != ctx.Guild.Id) throw Errors.ChannelNotFound(channelString); + affectedChannels.Add(channel); + } + await using (var conn = await _db.Obtain()) { var guild = await _repo.GetGuild(conn, ctx.Guild.Id); @@ -159,8 +159,8 @@ namespace PluralKit.Bot blacklist.UnionWith(affectedChannels.Select(c => c.Id)); else blacklist.ExceptWith(affectedChannels.Select(c => c.Id)); - - var patch = new GuildPatch {Blacklist = blacklist.ToArray()}; + + var patch = new GuildPatch { Blacklist = blacklist.ToArray() }; await _repo.UpsertGuild(conn, ctx.Guild.Id, patch); } @@ -186,14 +186,14 @@ namespace PluralKit.Bot var guildCfg = await _db.Execute(c => _repo.GetGuild(c, ctx.Guild.Id)); if (guildCfg.LogCleanupEnabled) - eb.Description("Log cleanup is currently **on** for this server. To disable it, type `pk;logclean off`."); - else + eb.Description("Log cleanup is currently **on** for this server. To disable it, type `pk;logclean off`."); + else eb.Description("Log cleanup is currently **off** for this server. To enable it, type `pk;logclean on`."); await ctx.Reply(embed: eb.Build()); return; } - var patch = new GuildPatch {LogCleanupEnabled = newValue}; + var patch = new GuildPatch { LogCleanupEnabled = newValue }; await _db.Execute(conn => _repo.UpsertGuild(conn, ctx.Guild.Id, patch)); if (newValue) diff --git a/PluralKit.Bot/Commands/Switch.cs b/PluralKit.Bot/Commands/Switch.cs index 80093217..fe6673e0 100644 --- a/PluralKit.Bot/Commands/Switch.cs +++ b/PluralKit.Bot/Commands/Switch.cs @@ -30,7 +30,7 @@ namespace PluralKit.Bot public async Task SwitchOut(Context ctx) { ctx.CheckSystem(); - + // Switch with no members = switch-out await DoSwitchCommand(ctx, new PKMember[] { }); } @@ -61,35 +61,35 @@ namespace PluralKit.Bot else await ctx.Reply($"{Emojis.Success} Switch registered. Current fronter is now {string.Join(", ", members.Select(m => m.NameFor(ctx)))}."); } - + public async Task SwitchMove(Context ctx) { ctx.CheckSystem(); - + var timeToMove = ctx.RemainderOrNull() ?? throw new PKSyntaxError("Must pass a date or time to move the switch to."); var tz = TzdbDateTimeZoneSource.Default.ForId(ctx.System.UiTz ?? "UTC"); - + var result = DateUtils.ParseDateTime(timeToMove, true, tz); if (result == null) throw Errors.InvalidDateTime(timeToMove); await using var conn = await _db.Obtain(); - + var time = result.Value; if (time.ToInstant() > SystemClock.Instance.GetCurrentInstant()) throw Errors.SwitchTimeInFuture; // Fetch the last two switches for the system to do bounds checking on var lastTwoSwitches = await _repo.GetSwitches(conn, ctx.System.Id).Take(2).ToListAsync(); - + // If we don't have a switch to move, don't bother if (lastTwoSwitches.Count == 0) throw Errors.NoRegisteredSwitches; - + // If there's a switch *behind* the one we move, we check to make srue we're not moving the time further back than that if (lastTwoSwitches.Count == 2) { if (lastTwoSwitches[1].Timestamp > time.ToInstant()) throw Errors.SwitchMoveBeforeSecondLast(lastTwoSwitches[1].Timestamp.InZone(tz)); } - + // Now we can actually do the move, yay! // But, we do a prompt to confirm. var lastSwitchMembers = _repo.GetSwitchMembers(conn, lastTwoSwitches[0].Id); @@ -98,16 +98,16 @@ namespace PluralKit.Bot var lastSwitchDeltaStr = (SystemClock.Instance.GetCurrentInstant() - lastTwoSwitches[0].Timestamp).FormatDuration(); var newSwitchTime = time.ToInstant().ToUnixTimeSeconds(); var newSwitchDeltaStr = (SystemClock.Instance.GetCurrentInstant() - time.ToInstant()).FormatDuration(); - + // yeet var msg = $"{Emojis.Warn} This will move the latest switch ({lastSwitchMemberStr}) from ({lastSwitchDeltaStr} ago) to ({newSwitchDeltaStr} ago). Is this OK?"; if (!await ctx.PromptYesNo(msg, "Move Switch")) throw Errors.SwitchMoveCancelled; - + // aaaand *now* we do the move await _repo.MoveSwitch(conn, lastTwoSwitches[0].Id, time.ToInstant()); await ctx.Reply($"{Emojis.Success} Switch moved to ({newSwitchDeltaStr} ago)."); } - + public async Task SwitchDelete(Context ctx) { ctx.CheckSystem(); @@ -122,7 +122,7 @@ namespace PluralKit.Bot await ctx.Reply($"{Emojis.Success} Cleared system switches!"); return; } - + await using var conn = await _db.Obtain(); // Fetch the last two switches for the system to do bounds checking on @@ -148,8 +148,8 @@ namespace PluralKit.Bot if (!await ctx.PromptYesNo(msg, "Delete Switch")) throw Errors.SwitchDeleteCancelled; await _repo.DeleteSwitch(conn, lastTwoSwitches[0].Id); - + await ctx.Reply($"{Emojis.Success} Switch deleted."); } } -} +} \ No newline at end of file diff --git a/PluralKit.Bot/Commands/System.cs b/PluralKit.Bot/Commands/System.cs index d531196d..2336d256 100644 --- a/PluralKit.Bot/Commands/System.cs +++ b/PluralKit.Bot/Commands/System.cs @@ -9,15 +9,16 @@ namespace PluralKit.Bot private readonly EmbedService _embeds; private readonly IDatabase _db; private readonly ModelRepository _repo; - + public System(EmbedService embeds, IDatabase db, ModelRepository repo) { _embeds = embeds; _db = db; _repo = repo; } - - public async Task Query(Context ctx, PKSystem system) { + + public async Task Query(Context ctx, PKSystem system) + { if (system == null) throw Errors.NoSystemError; await ctx.Reply(embed: await _embeds.CreateSystemEmbed(ctx, system, ctx.LookupContextFor(system))); @@ -37,9 +38,9 @@ namespace PluralKit.Bot await _repo.AddAccount(c, system.Id, ctx.Author.Id); return system; }); - + // TODO: better message, perhaps embed like in groups? await ctx.Reply($"{Emojis.Success} Your system has been created. Type `pk;system` to view it, and type `pk;system help` for more information about commands you can use now. Now that you have that set up, check out the getting started guide on setting up members and proxies: "); } } -} +} \ No newline at end of file diff --git a/PluralKit.Bot/Commands/SystemEdit.cs b/PluralKit.Bot/Commands/SystemEdit.cs index 508028cd..6e6633e4 100644 --- a/PluralKit.Bot/Commands/SystemEdit.cs +++ b/PluralKit.Bot/Commands/SystemEdit.cs @@ -33,7 +33,7 @@ namespace PluralKit.Bot if (await ctx.MatchClear("your system's name")) { - var clearPatch = new SystemPatch {Name = null}; + var clearPatch = new SystemPatch { Name = null }; await _db.Execute(conn => _repo.UpdateSystem(conn, ctx.System.Id, clearPatch)); await ctx.Reply($"{Emojis.Success} System name cleared."); @@ -49,28 +49,29 @@ namespace PluralKit.Bot await ctx.Reply("Your system currently does not have a name. Type `pk;system name ` to set one."); return; } - + if (newSystemName != null && newSystemName.Length > Limits.MaxSystemNameLength) throw Errors.SystemNameTooLongError(newSystemName.Length); - - var patch = new SystemPatch {Name = newSystemName}; + + var patch = new SystemPatch { Name = newSystemName }; await _db.Execute(conn => _repo.UpdateSystem(conn, ctx.System.Id, patch)); - + await ctx.Reply($"{Emojis.Success} System name changed."); } - - public async Task Description(Context ctx) { + + public async Task Description(Context ctx) + { ctx.CheckSystem(); if (await ctx.MatchClear("your system's description")) { - var patch = new SystemPatch {Description = null}; + var patch = new SystemPatch { Description = null }; await _db.Execute(conn => _repo.UpdateSystem(conn, ctx.System.Id, patch)); - + await ctx.Reply($"{Emojis.Success} System description cleared."); return; } - + var newDescription = ctx.RemainderOrNull()?.NormalizeLineEndSpacing(); if (newDescription == null) { @@ -88,27 +89,28 @@ namespace PluralKit.Bot else { if (newDescription.Length > Limits.MaxDescriptionLength) throw Errors.DescriptionTooLongError(newDescription.Length); - - var patch = new SystemPatch {Description = newDescription}; + + var patch = new SystemPatch { Description = newDescription }; await _db.Execute(conn => _repo.UpdateSystem(conn, ctx.System.Id, patch)); - + await ctx.Reply($"{Emojis.Success} System description changed."); } } - public async Task Color(Context ctx) { + public async Task Color(Context ctx) + { ctx.CheckSystem(); if (await ctx.MatchClear()) { - var patch = new SystemPatch {Color = Partial.Null()}; + var patch = new SystemPatch { Color = Partial.Null() }; await _db.Execute(conn => _repo.UpdateSystem(conn, ctx.System.Id, patch)); await ctx.Reply($"{Emojis.Success} System color cleared."); } - else if (!ctx.HasNext()) + else if (!ctx.HasNext()) { - if (ctx.System.Color == null) + if (ctx.System.Color == null) await ctx.Reply( $"Your system does not have a color set. To set one, type `pk;system color `."); else @@ -126,7 +128,7 @@ namespace PluralKit.Bot if (color.StartsWith("#")) color = color.Substring(1); if (!Regex.IsMatch(color, "^[0-9a-fA-F]{6}$")) throw Errors.InvalidColorError(color); - var patch = new SystemPatch {Color = Partial.Present(color.ToLowerInvariant())}; + var patch = new SystemPatch { Color = Partial.Present(color.ToLowerInvariant()) }; await _db.Execute(conn => _repo.UpdateSystem(conn, ctx.System.Id, patch)); await ctx.Reply(embed: new EmbedBuilder() @@ -136,18 +138,19 @@ namespace PluralKit.Bot .Build()); } } - + public async Task Tag(Context ctx) { ctx.CheckSystem(); if (await ctx.MatchClear("your system's tag")) { - var patch = new SystemPatch {Tag = null}; + var patch = new SystemPatch { Tag = null }; await _db.Execute(conn => _repo.UpdateSystem(conn, ctx.System.Id, patch)); - + await ctx.Reply($"{Emojis.Success} System tag cleared."); - } else if (!ctx.HasNext(skipFlags: false)) + } + else if (!ctx.HasNext(skipFlags: false)) { if (ctx.System.Tag == null) await ctx.Reply($"You currently have no system tag. To set one, type `pk;s tag `."); @@ -160,10 +163,10 @@ namespace PluralKit.Bot if (newTag != null) if (newTag.Length > Limits.MaxSystemTagLength) throw Errors.SystemTagTooLongError(newTag.Length); - - var patch = new SystemPatch {Tag = newTag}; + + var patch = new SystemPatch { Tag = newTag }; await _db.Execute(conn => _repo.UpdateSystem(conn, ctx.System.Id, patch)); - + await ctx.Reply($"{Emojis.Success} System tag changed. Member names will now end with {newTag.AsCode()} when proxied."); } } @@ -198,9 +201,9 @@ namespace PluralKit.Bot { var newTag = ctx.RemainderOrNull(skipFlags: false); if (newTag != null && newTag.Length > Limits.MaxSystemTagLength) - throw Errors.SystemTagTooLongError(newTag.Length); + throw Errors.SystemTagTooLongError(newTag.Length); - var patch = new SystemGuildPatch {Tag = newTag}; + var patch = new SystemGuildPatch { Tag = newTag }; await _db.Execute(conn => _repo.UpsertSystemGuild(conn, ctx.System.Id, ctx.Guild.Id, patch)); await ctx.Reply($"{Emojis.Success} System server tag changed. Member names will now end with {newTag.AsCode()} when proxied in the current server '{ctx.Guild.Name}'."); @@ -211,7 +214,7 @@ namespace PluralKit.Bot async Task Clear() { - var patch = new SystemGuildPatch {Tag = null}; + var patch = new SystemGuildPatch { Tag = null }; await _db.Execute(conn => _repo.UpsertSystemGuild(conn, ctx.System.Id, ctx.Guild.Id, patch)); await ctx.Reply($"{Emojis.Success} System server tag cleared. Member names will now end with the global system tag, if there is one set."); @@ -222,7 +225,7 @@ namespace PluralKit.Bot async Task EnableDisable(bool newValue) { - var patch = new SystemGuildPatch {TagEnabled = newValue}; + var patch = new SystemGuildPatch { TagEnabled = newValue }; await _db.Execute(conn => _repo.UpsertSystemGuild(conn, ctx.System.Id, ctx.Guild.Id, patch)); await ctx.Reply(PrintEnableDisableResult(newValue, newValue != ctx.MessageContext.TagEnabled)); @@ -253,7 +256,7 @@ namespace PluralKit.Bot str += $" Member names will now end with the global system tag when proxied in the current server, if there is one set."; } } - + return str; } @@ -268,14 +271,14 @@ namespace PluralKit.Bot else await Set(); } - + public async Task Avatar(Context ctx) { ctx.CheckSystem(); async Task ClearIcon() { - await _db.Execute(c => _repo.UpdateSystem(c, ctx.System.Id, new SystemPatch {AvatarUrl = null})); + await _db.Execute(c => _repo.UpdateSystem(c, ctx.System.Id, new SystemPatch { AvatarUrl = null })); await ctx.Reply($"{Emojis.Success} System icon cleared."); } @@ -283,8 +286,8 @@ namespace PluralKit.Bot { await AvatarUtils.VerifyAvatarOrThrow(_client, img.Url); - await _db.Execute(c => _repo.UpdateSystem(c, ctx.System.Id, new SystemPatch {AvatarUrl = img.Url})); - + await _db.Execute(c => _repo.UpdateSystem(c, ctx.System.Id, new SystemPatch { AvatarUrl = img.Url })); + var msg = img.Source switch { AvatarSource.User => $"{Emojis.Success} System icon changed to {img.SourceUser?.Username}'s avatar!\n{Emojis.Warn} If {img.SourceUser?.Username} changes their avatar, the system icon will need to be re-set.", @@ -292,11 +295,11 @@ namespace PluralKit.Bot AvatarSource.Attachment => $"{Emojis.Success} System icon changed to attached image.\n{Emojis.Warn} If you delete the message containing the attachment, the system icon will stop working.", _ => throw new ArgumentOutOfRangeException() }; - + // The attachment's already right there, no need to preview it. var hasEmbed = img.Source != AvatarSource.Attachment; - await (hasEmbed - ? ctx.Reply(msg, embed: new EmbedBuilder().Image(new(img.Url)).Build()) + await (hasEmbed + ? ctx.Reply(msg, embed: new EmbedBuilder().Image(new(img.Url)).Build()) : ctx.Reply(msg)); } @@ -316,7 +319,7 @@ namespace PluralKit.Bot if (await ctx.MatchClear("your system's icon")) await ClearIcon(); - else if (await ctx.MatchImage() is {} img) + else if (await ctx.MatchImage() is { } img) await SetIcon(img); else await ShowIcon(); @@ -328,7 +331,7 @@ namespace PluralKit.Bot async Task ClearImage() { - await _db.Execute(c => _repo.UpdateSystem(c, ctx.System.Id, new SystemPatch {BannerImage = null})); + await _db.Execute(c => _repo.UpdateSystem(c, ctx.System.Id, new SystemPatch { BannerImage = null })); await ctx.Reply($"{Emojis.Success} System banner image cleared."); } @@ -336,7 +339,7 @@ namespace PluralKit.Bot { await AvatarUtils.VerifyAvatarOrThrow(_client, img.Url, isFullSizeImage: true); - await _db.Execute(c => _repo.UpdateSystem(c, ctx.System.Id, new SystemPatch {BannerImage = img.Url})); + await _db.Execute(c => _repo.UpdateSystem(c, ctx.System.Id, new SystemPatch { BannerImage = img.Url })); var msg = img.Source switch { @@ -348,8 +351,8 @@ namespace PluralKit.Bot // The attachment's already right there, no need to preview it. var hasEmbed = img.Source != AvatarSource.Attachment; - await (hasEmbed - ? ctx.Reply(msg, embed: new EmbedBuilder().Image(new(img.Url)).Build()) + await (hasEmbed + ? ctx.Reply(msg, embed: new EmbedBuilder().Image(new(img.Url)).Build()) : ctx.Reply(msg)); } @@ -369,13 +372,14 @@ namespace PluralKit.Bot if (await ctx.MatchClear("your system's banner image")) await ClearImage(); - else if (await ctx.MatchImage() is {} img) + else if (await ctx.MatchImage() is { } img) await SetImage(img); else await ShowImage(); } - public async Task Delete(Context ctx) { + public async Task Delete(Context ctx) + { ctx.CheckSystem(); await ctx.Reply($"{Emojis.Warn} Are you sure you want to delete your system? If so, reply to this message with your system's ID (`{ctx.System.Hid}`).\n**Note: this action is permanent.**"); @@ -383,10 +387,10 @@ namespace PluralKit.Bot throw new PKError($"System deletion cancelled. Note that you must reply with your system ID (`{ctx.System.Hid}`) *verbatim*."); await _db.Execute(conn => _repo.DeleteSystem(conn, ctx.System.Id)); - + await ctx.Reply($"{Emojis.Success} System deleted."); } - + public async Task SystemProxy(Context ctx) { ctx.CheckSystem(); @@ -415,7 +419,7 @@ namespace PluralKit.Bot return; } - var patch = new SystemGuildPatch {ProxyEnabled = newValue}; + var patch = new SystemGuildPatch { ProxyEnabled = newValue }; await _db.Execute(conn => _repo.UpsertSystemGuild(conn, ctx.System.Id, guild.Id, patch)); if (newValue) @@ -423,20 +427,20 @@ namespace PluralKit.Bot else await ctx.Reply($"Message proxying in {serverText} is now **disabled** for your system."); } - - public async Task SystemTimezone(Context ctx) + + public async Task SystemTimezone(Context ctx) { if (ctx.System == null) throw Errors.NoSystemError; if (await ctx.MatchClear()) { - var clearPatch = new SystemPatch {UiTz = "UTC"}; + var clearPatch = new SystemPatch { UiTz = "UTC" }; await _db.Execute(conn => _repo.UpdateSystem(conn, ctx.System.Id, clearPatch)); await ctx.Reply($"{Emojis.Success} System time zone cleared (set to UTC)."); return; } - + var zoneStr = ctx.RemainderOrNull(); if (zoneStr == null) { @@ -451,8 +455,8 @@ namespace PluralKit.Bot var currentTime = SystemClock.Instance.GetCurrentInstant().InZone(zone); var msg = $"This will change the system time zone to **{zone.Id}**. The current time is **{currentTime.FormatZoned()}**. Is this correct?"; if (!await ctx.PromptYesNo(msg, "Change Timezone")) throw Errors.TimezoneChangeCancelled; - - var patch = new SystemPatch {UiTz = zone.Id}; + + var patch = new SystemPatch { UiTz = zone.Id }; await _db.Execute(conn => _repo.UpdateSystem(conn, ctx.System.Id, patch)); await ctx.Reply($"System time zone changed to **{zone.Id}**."); @@ -522,45 +526,49 @@ namespace PluralKit.Bot await SetLevel(ctx.PopSystemPrivacySubject(), ctx.PopPrivacyLevel()); } - public async Task SystemPing(Context ctx) - { - ctx.CheckSystem(); + public async Task SystemPing(Context ctx) + { + ctx.CheckSystem(); - if (!ctx.HasNext()) - { - if (ctx.System.PingsEnabled) {await ctx.Reply("Reaction pings are currently **enabled** for your system. To disable reaction pings, type `pk;s ping disable`.");} - else {await ctx.Reply("Reaction pings are currently **disabled** for your system. To enable reaction pings, type `pk;s ping enable`.");} - } - else { - if (ctx.Match("on", "enable")) { - var patch = new SystemPatch {PingsEnabled = true}; + if (!ctx.HasNext()) + { + if (ctx.System.PingsEnabled) { await ctx.Reply("Reaction pings are currently **enabled** for your system. To disable reaction pings, type `pk;s ping disable`."); } + else { await ctx.Reply("Reaction pings are currently **disabled** for your system. To enable reaction pings, type `pk;s ping enable`."); } + } + else + { + if (ctx.Match("on", "enable")) + { + var patch = new SystemPatch { PingsEnabled = true }; await _db.Execute(conn => _repo.UpdateSystem(conn, ctx.System.Id, patch)); - + await ctx.Reply("Reaction pings have now been enabled."); } - if (ctx.Match("off", "disable")) { - var patch = new SystemPatch {PingsEnabled = false}; + if (ctx.Match("off", "disable")) + { + var patch = new SystemPatch { PingsEnabled = false }; await _db.Execute(conn => _repo.UpdateSystem(conn, ctx.System.Id, patch)); - + await ctx.Reply("Reaction pings have now been disabled."); } } - } + } - public async Task FindTimeZone(Context ctx, string zoneStr) { + public async Task FindTimeZone(Context ctx, string zoneStr) + { // First, if we're given a flag emoji, we extract the flag emoji code from it. zoneStr = Core.StringUtils.ExtractCountryFlag(zoneStr) ?? zoneStr; - + // Then, we find all *locations* matching either the given country code or the country name. var locations = TzdbDateTimeZoneSource.Default.Zone1970Locations; var matchingLocations = locations.Where(l => l.Countries.Any(c => string.Equals(c.Code, zoneStr, StringComparison.InvariantCultureIgnoreCase) || string.Equals(c.Name, zoneStr, StringComparison.InvariantCultureIgnoreCase))); - + // Then, we find all (unique) time zone IDs that match. var matchingZones = matchingLocations.Select(l => DateTimeZoneProviders.Tzdb.GetZoneOrNull(l.ZoneId)) .Distinct().ToList(); - + // If the set of matching zones is empty (ie. we didn't find anything), we try a few other things. if (matchingZones.Count == 0) { @@ -587,13 +595,13 @@ namespace PluralKit.Bot matchingZones = allZones.Select(z => DateTimeZoneProviders.Tzdb.GetZoneOrNull(z)) .Where(z => z.GetUtcOffset(SystemClock.Instance.GetCurrentInstant()) == offset).ToList(); } - + // If we have a list of viable time zones, we ask the user which is correct. - + // If we only have one, return that one. if (matchingZones.Count == 1) return matchingZones.First(); - + // Otherwise, prompt and return! return await ctx.Choose("There were multiple matches for your time zone query. Please select the region that matches you the closest:", matchingZones, z => @@ -605,4 +613,4 @@ namespace PluralKit.Bot }); } } -} +} \ No newline at end of file diff --git a/PluralKit.Bot/Commands/SystemFront.cs b/PluralKit.Bot/Commands/SystemFront.cs index eaef310c..beb5da6a 100644 --- a/PluralKit.Bot/Commands/SystemFront.cs +++ b/PluralKit.Bot/Commands/SystemFront.cs @@ -21,7 +21,7 @@ namespace PluralKit.Bot _db = db; _repo = repo; } - + struct FrontHistoryEntry { public readonly Instant? LastTime; @@ -40,10 +40,10 @@ namespace PluralKit.Bot ctx.CheckSystemPrivacy(system, system.FrontPrivacy); await using var conn = await _db.Obtain(); - + var sw = await _repo.GetLatestSwitch(conn, system.Id); if (sw == null) throw Errors.NoRegisteredSwitches; - + await ctx.Reply(embed: await _embeds.CreateFronterEmbed(sw, system.Zone, ctx.LookupContextFor(system))); } @@ -54,7 +54,7 @@ namespace PluralKit.Bot // Gotta be careful here: if we dispose of the connection while the IAE is alive, boom await using var conn = await _db.Obtain(); - + var totalSwitches = await _repo.GetSwitchCount(conn, system.Id); if (totalSwitches == 0) throw Errors.NoRegisteredSwitches; @@ -78,10 +78,10 @@ namespace PluralKit.Bot var lastSw = entry.LastTime; var sw = entry.ThisSwitch; - + // Fetch member list and format await using var conn = await _db.Obtain(); - + var members = await _db.Execute(c => _repo.GetSwitchMembers(c, sw.Id)).ToListAsync(); var membersStr = members.Any() ? string.Join(", ", members.Select(m => m.NameFor(ctx))) : "no fronter"; @@ -111,7 +111,7 @@ namespace PluralKit.Bot } ); } - + public async Task SystemFrontPercent(Context ctx, PKSystem system) { if (system == null) throw Errors.NoSystemError; @@ -121,7 +121,7 @@ namespace PluralKit.Bot if (totalSwitches == 0) throw Errors.NoRegisteredSwitches; string durationStr = ctx.RemainderOrNull() ?? "30d"; - + var now = SystemClock.Instance.GetCurrentInstant(); var rangeStart = DateUtils.ParseDateTime(durationStr, true, system.Zone); @@ -129,7 +129,7 @@ namespace PluralKit.Bot if (rangeStart.Value.ToInstant() > now) throw Errors.FrontPercentTimeInFuture; var title = new StringBuilder($"Frontpercent of "); - if (system.Name != null) + if (system.Name != null) title.Append($"{system.Name} (`{system.Hid}`)"); else title.Append($"`{system.Hid}`"); diff --git a/PluralKit.Bot/Commands/SystemLink.cs b/PluralKit.Bot/Commands/SystemLink.cs index cc8736ba..c2348f84 100644 --- a/PluralKit.Bot/Commands/SystemLink.cs +++ b/PluralKit.Bot/Commands/SystemLink.cs @@ -18,13 +18,13 @@ namespace PluralKit.Bot _db = db; _repo = repo; } - + public async Task LinkSystem(Context ctx) { ctx.CheckSystem(); await using var conn = await _db.Obtain(); - + var account = await ctx.MatchUser() ?? throw new PKSyntaxError("You must pass an account to link with (either ID or @mention)."); var accountIds = await _repo.GetSystemAccounts(conn, ctx.System.Id); if (accountIds.Contains(account.Id)) @@ -32,7 +32,7 @@ namespace PluralKit.Bot var existingAccount = await _repo.GetSystemByAccount(conn, account.Id); if (existingAccount != null) - throw Errors.AccountInOtherSystem(existingAccount); + throw Errors.AccountInOtherSystem(existingAccount); var msg = $"{account.Mention()}, please confirm the link."; if (!await ctx.PromptYesNo(msg, "Confirm", user: account, matchFlag: false)) throw Errors.MemberLinkCancelled; @@ -43,7 +43,7 @@ namespace PluralKit.Bot public async Task UnlinkAccount(Context ctx) { ctx.CheckSystem(); - + await using var conn = await _db.Obtain(); ulong id; @@ -55,7 +55,7 @@ namespace PluralKit.Bot var accountIds = (await _repo.GetSystemAccounts(conn, ctx.System.Id)).ToList(); if (!accountIds.Contains(id)) throw Errors.AccountNotLinked; if (accountIds.Count == 1) throw Errors.UnlinkingLastAccount; - + var msg = $"Are you sure you want to unlink <@{id}> from your system?"; if (!await ctx.PromptYesNo(msg, "Unlink")) throw Errors.MemberUnlinkCancelled; diff --git a/PluralKit.Bot/Commands/SystemList.cs b/PluralKit.Bot/Commands/SystemList.cs index 8a33e552..349e1667 100644 --- a/PluralKit.Bot/Commands/SystemList.cs +++ b/PluralKit.Bot/Commands/SystemList.cs @@ -8,7 +8,7 @@ namespace PluralKit.Bot public class SystemList { private readonly IDatabase _db; - + public SystemList(IDatabase db) { _db = db; @@ -26,15 +26,15 @@ namespace PluralKit.Bot private string GetEmbedTitle(PKSystem target, MemberListOptions opts) { var title = new StringBuilder("Members of "); - - if (target.Name != null) + + if (target.Name != null) title.Append($"{target.Name} (`{target.Hid}`)"); - else + else title.Append($"`{target.Hid}`"); - + if (opts.Search != null) title.Append($" matching **{opts.Search}**"); - + return title.ToString(); } } diff --git a/PluralKit.Bot/Commands/Token.cs b/PluralKit.Bot/Commands/Token.cs index f861b8fb..f46baf6d 100644 --- a/PluralKit.Bot/Commands/Token.cs +++ b/PluralKit.Bot/Commands/Token.cs @@ -25,7 +25,7 @@ namespace PluralKit.Bot // Get or make a token var token = ctx.System.Token ?? await MakeAndSetNewToken(ctx.System); - + try { // DM the user a security disclaimer, and then the token in a separate message (for easy copying on mobile) @@ -34,7 +34,7 @@ namespace PluralKit.Bot { Content = $"{Emojis.Warn} Please note that this grants access to modify (and delete!) all your system data, so keep it safe and secure. If it leaks or you need a new one, you can invalidate this one with `pk;token refresh`.\n\nYour token is below:" }); - await ctx.Rest.CreateMessage(dm.Id, new MessageRequest {Content = token}); + await ctx.Rest.CreateMessage(dm.Id, new MessageRequest { Content = token }); // If we're not already in a DM, reply with a reminder to check if (ctx.Channel.Type != Channel.ChannelType.Dm) @@ -50,15 +50,15 @@ namespace PluralKit.Bot private async Task MakeAndSetNewToken(PKSystem system) { - var patch = new SystemPatch {Token = StringUtils.GenerateToken()}; + var patch = new SystemPatch { Token = StringUtils.GenerateToken() }; system = await _db.Execute(conn => _repo.UpdateSystem(conn, system.Id, patch)); return system.Token; } - + public async Task RefreshToken(Context ctx) { ctx.CheckSystem(); - + if (ctx.System.Token == null) { // If we don't have a token, call the other method instead @@ -67,19 +67,20 @@ namespace PluralKit.Bot return; } - try { + try + { // DM the user an invalidation disclaimer, and then the token in a separate message (for easy copying on mobile) var dm = await ctx.Cache.GetOrCreateDmChannel(ctx.Rest, ctx.Author.Id); await ctx.Rest.CreateMessage(dm.Id, new MessageRequest { Content = $"{Emojis.Warn} Your previous API token has been invalidated. You will need to change it anywhere it's currently used.\n\nYour token is below:" }); - + // Make the new token after sending the first DM; this ensures if we can't DM, we also don't end up // breaking their existing token as a side effect :) var token = await MakeAndSetNewToken(ctx.System); await ctx.Rest.CreateMessage(dm.Id, new MessageRequest { Content = token }); - + // If we're not already in a DM, reply with a reminder to check if (ctx.Channel.Type != Channel.ChannelType.Dm) await ctx.Reply($"{Emojis.Success} Check your DMs!"); diff --git a/PluralKit.Bot/Errors.cs b/PluralKit.Bot/Errors.cs index a3a2abf1..3a5ad547 100644 --- a/PluralKit.Bot/Errors.cs +++ b/PluralKit.Bot/Errors.cs @@ -6,11 +6,12 @@ using Humanizer; using NodaTime; using PluralKit.Core; -namespace PluralKit.Bot { +namespace PluralKit.Bot +{ /// /// An exception class representing user-facing errors caused when parsing and executing commands. /// - public class PKError : Exception + public class PKError: Exception { public PKError(string message) : base(message) { @@ -21,14 +22,15 @@ namespace PluralKit.Bot { /// A subclass of that represent command syntax errors, meaning they'll have their command /// usages printed in the message. /// - public class PKSyntaxError : PKError + public class PKSyntaxError: PKError { public PKSyntaxError(string message) : base(message) { } } - - public static class Errors { + + public static class Errors + { // TODO: is returning constructed errors and throwing them at call site a good idea, or should these be methods that insta-throw instead? // or should we just like... go back to inlining them? at least for the one-time-use commands @@ -50,9 +52,9 @@ namespace PluralKit.Bot { public static PKError BirthdayParseError(string birthday) => new PKError($"\"{birthday}\" could not be parsed as a valid date. Try a format like \"2016-12-24\" or \"May 3 1996\"."); public static PKError ProxyMustHaveText => new PKSyntaxError("Example proxy message must contain the string 'text'."); public static PKError ProxyMultipleText => new PKSyntaxError("Example proxy message must contain the string 'text' exactly once."); - + public static PKError MemberDeleteCancelled => new PKError($"Member deletion cancelled. Stay safe! {Emojis.ThumbsUp}"); - public static PKError AvatarServerError(HttpStatusCode statusCode) => new PKError($"Server responded with status code {(int) statusCode}, are you sure your link is working?"); + public static PKError AvatarServerError(HttpStatusCode statusCode) => new PKError($"Server responded with status code {(int)statusCode}, are you sure your link is working?"); public static PKError AvatarFileSizeLimit(long size) => new PKError($"File size too large ({size.Bytes().ToString("#.#")} > {Limits.AvatarFileSizeLimit.Bytes().ToString("#.#")}), try shrinking or compressing the image."); public static PKError AvatarNotAnImage(string mimeType) => new PKError($"The given link does not point to an image{(mimeType != null ? $" ({mimeType})" : "")}. Make sure you're using a direct link (ending in .jpg, .png, .gif)."); public static PKError AvatarDimensionsTooLarge(int width, int height) => new PKError($"Image too large ({width}x{height} > {Limits.AvatarDimensionLimit}x{Limits.AvatarDimensionLimit}), try resizing the image."); @@ -60,7 +62,7 @@ namespace PluralKit.Bot { public static PKError UserHasNoAvatar => new PKError("The given user has no avatar set."); public static PKError InvalidUrl(string url) => new PKError($"The given URL is invalid."); public static PKError UrlTooLong(string url) => new PKError($"The given URL is too long ({url.Length}/{Limits.MaxUriLength} characters)."); - + public static PKError AccountAlreadyLinked => new PKError("That account is already linked to your system."); public static PKError AccountNotLinked => new PKError("That account isn't linked to your system."); public static PKError AccountInOtherSystem(PKSystem system) => new PKError($"The mentioned account is already linked to another system (see `pk;system {system.Hid}`)."); @@ -94,7 +96,7 @@ namespace PluralKit.Bot { public static PKError InvalidImportFile => new PKError("Imported data file invalid. Make sure this is a .json file directly exported from PluralKit or Tupperbox."); public static PKError ImportCancelled => new PKError("Import cancelled."); public static PKError MessageNotFound(ulong id) => new PKError($"Message with ID '{id}' not found. Are you sure it's a message proxied by PluralKit?"); - + public static PKError DurationParseError(string durationStr) => new PKError($"Could not parse {durationStr.AsCode()} as a valid duration. Try a format such as `30d`, `1d3h` or `20m30s`."); public static PKError FrontPercentTimeInFuture => new PKError("Cannot get the front percent between now and a time in the future."); diff --git a/PluralKit.Bot/Handlers/IEventHandler.cs b/PluralKit.Bot/Handlers/IEventHandler.cs index 4a086706..6f5f5549 100644 --- a/PluralKit.Bot/Handlers/IEventHandler.cs +++ b/PluralKit.Bot/Handlers/IEventHandler.cs @@ -4,7 +4,7 @@ using Myriad.Gateway; namespace PluralKit.Bot { - public interface IEventHandler where T: IGatewayEvent + public interface IEventHandler where T : IGatewayEvent { Task Handle(Shard shard, T evt); diff --git a/PluralKit.Bot/Handlers/InteractionCreated.cs b/PluralKit.Bot/Handlers/InteractionCreated.cs index 4ac75910..24fa37c1 100644 --- a/PluralKit.Bot/Handlers/InteractionCreated.cs +++ b/PluralKit.Bot/Handlers/InteractionCreated.cs @@ -11,7 +11,7 @@ namespace PluralKit.Bot { private readonly InteractionDispatchService _interactionDispatch; private readonly ILifetimeScope _services; - + public InteractionCreated(InteractionDispatchService interactionDispatch, ILifetimeScope services) { _interactionDispatch = interactionDispatch; diff --git a/PluralKit.Bot/Handlers/MessageCreated.cs b/PluralKit.Bot/Handlers/MessageCreated.cs index 82da5d03..a10bcdc2 100644 --- a/PluralKit.Bot/Handlers/MessageCreated.cs +++ b/PluralKit.Bot/Handlers/MessageCreated.cs @@ -64,25 +64,25 @@ namespace PluralKit.Bot var guild = evt.GuildId != null ? _cache.GetGuild(evt.GuildId.Value) : null; var channel = _cache.GetChannel(evt.ChannelId); var rootChannel = _cache.GetRootChannel(evt.ChannelId); - + // Log metrics and message info _metrics.Measure.Meter.Mark(BotMetrics.MessagesReceived); _lastMessageCache.AddMessage(evt); - + // Get message context from DB (tracking w/ metrics) MessageContext ctx; await using (var conn = await _db.Obtain()) using (_metrics.Measure.Timer.Time(BotMetrics.MessageContextQueryTime)) ctx = await _repo.GetMessageContext(conn, evt.Author.Id, evt.GuildId ?? default, rootChannel.Id); - + // Try each handler until we find one that succeeds - if (await TryHandleLogClean(evt, ctx)) + if (await TryHandleLogClean(evt, ctx)) return; - + // Only do command/proxy handling if it's a user account - if (evt.Author.Bot || evt.WebhookId != null || evt.Author.System == true) + if (evt.Author.Bot || evt.WebhookId != null || evt.Author.System == true) return; - + if (await TryHandleCommand(shard, evt, guild, channel, ctx)) return; await TryHandleProxy(shard, evt, guild, channel, ctx); @@ -133,11 +133,11 @@ namespace PluralKit.Bot foreach (var prefix in prefixes) { if (!message.StartsWith(prefix, StringComparison.InvariantCultureIgnoreCase)) continue; - + argPos = prefix.Length; return true; } - + // Then, check mention prefix (must be the bot user, ofc) argPos = -1; if (DiscordUtils.HasMentionPrefix(message, ref argPos, out var id)) @@ -156,7 +156,7 @@ namespace PluralKit.Bot } // Catch any failed proxy checks so they get ignored in the global error handler - catch (ProxyService.ProxyChecksFailedException) {} + catch (ProxyService.ProxyChecksFailedException) { } catch (PKError e) { @@ -164,7 +164,7 @@ namespace PluralKit.Bot if (botPermissions.HasFlag(PermissionSet.SendMessages)) { await _rest.CreateMessage(evt.ChannelId, - new MessageRequest {Content = $"{Emojis.Error} {e.Message}"}); + new MessageRequest { Content = $"{Emojis.Error} {e.Message}" }); } } diff --git a/PluralKit.Bot/Handlers/MessageDeleted.cs b/PluralKit.Bot/Handlers/MessageDeleted.cs index f0d5dd6d..b575b482 100644 --- a/PluralKit.Bot/Handlers/MessageDeleted.cs +++ b/PluralKit.Bot/Handlers/MessageDeleted.cs @@ -14,7 +14,7 @@ namespace PluralKit.Bot public class MessageDeleted: IEventHandler, IEventHandler { private static readonly TimeSpan MessageDeleteDelay = TimeSpan.FromSeconds(15); - + private readonly IDatabase _db; private readonly ModelRepository _repo; private readonly ILogger _logger; @@ -27,7 +27,7 @@ namespace PluralKit.Bot _lastMessage = lastMessage; _logger = logger.ForContext(); } - + public Task Handle(Shard shard, MessageDeleteEvent evt) { // Delete deleted webhook messages from the data store @@ -54,11 +54,11 @@ namespace PluralKit.Bot { await Task.Delay(MessageDeleteDelay); - _logger.Information("Bulk deleting {Count} messages in channel {Channel}", + _logger.Information("Bulk deleting {Count} messages in channel {Channel}", evt.Ids.Length, evt.ChannelId); await _db.Execute(c => _repo.DeleteMessagesBulk(c, evt.Ids)); } - + _lastMessage.HandleMessageDeletion(evt.ChannelId, evt.Ids.ToList()); _ = Inner(); return Task.CompletedTask; diff --git a/PluralKit.Bot/Handlers/MessageEdited.cs b/PluralKit.Bot/Handlers/MessageEdited.cs index 3a4a99d4..f22acfc6 100644 --- a/PluralKit.Bot/Handlers/MessageEdited.cs +++ b/PluralKit.Bot/Handlers/MessageEdited.cs @@ -28,7 +28,7 @@ namespace PluralKit.Bot private readonly Bot _bot; private readonly DiscordApiClient _rest; private readonly ILogger _logger; - + public MessageEdited(LastMessageCacheService lastMessageCache, ProxyService proxy, IDatabase db, IMetrics metrics, ModelRepository repo, Cluster client, IDiscordCache cache, Bot bot, DiscordApiClient rest, ILogger logger) { _lastMessageCache = lastMessageCache; @@ -46,11 +46,11 @@ namespace PluralKit.Bot public async Task Handle(Shard shard, MessageUpdateEvent evt) { if (evt.Author.Value?.Id == _client.User?.Id) return; - + // Edit message events sometimes arrive with missing data; double-check it's all there - if (!evt.Content.HasValue || !evt.Author.HasValue || !evt.Member.HasValue) + if (!evt.Content.HasValue || !evt.Author.HasValue || !evt.Member.HasValue) return; - + var channel = _cache.GetChannel(evt.ChannelId); if (!DiscordUtils.IsValidGuildChannel(channel)) return; @@ -60,7 +60,7 @@ namespace PluralKit.Bot // Only react to the last message in the channel if (lastMessage?.Id != evt.Id) return; - + // Just run the normal message handling code, with a flag to disable autoproxying MessageContext ctx; await using (var conn = await _db.Obtain()) @@ -76,7 +76,7 @@ namespace PluralKit.Bot channel: channel, botPermissions: botPermissions); } // Catch any failed proxy checks so they get ignored in the global error handler - catch (ProxyService.ProxyChecksFailedException) {} + catch (ProxyService.ProxyChecksFailedException) { } } private async Task GetMessageCreateEvent(MessageUpdateEvent evt, CachedMessage lastMessage, Channel channel) @@ -86,11 +86,11 @@ namespace PluralKit.Bot var messageReference = lastMessage.ReferencedMessage != null ? new Message.Reference(channel.GuildId, evt.ChannelId, lastMessage.ReferencedMessage.Value) : null; - - var messageType = lastMessage.ReferencedMessage != null - ? Message.MessageType.Reply + + var messageType = lastMessage.ReferencedMessage != null + ? Message.MessageType.Reply : Message.MessageType.Default; - + // TODO: is this missing anything? var equivalentEvt = new MessageCreateEvent { @@ -112,7 +112,7 @@ namespace PluralKit.Bot { if (referencedMessageId == null) return null; - + var botPermissions = _bot.PermissionsIn(channelId); if (!botPermissions.HasFlag(PermissionSet.ReadMessageHistory)) { diff --git a/PluralKit.Bot/Handlers/ReactionAdded.cs b/PluralKit.Bot/Handlers/ReactionAdded.cs index f7f0f900..579bf876 100644 --- a/PluralKit.Bot/Handlers/ReactionAdded.cs +++ b/PluralKit.Bot/Handlers/ReactionAdded.cs @@ -40,7 +40,7 @@ namespace PluralKit.Bot } public async Task Handle(Shard shard, MessageReactionAddEvent evt) - { + { await TryHandleProxyMessageReactions(evt); } @@ -71,42 +71,42 @@ namespace PluralKit.Bot // Ignore reactions from bots (we can't DM them anyway) if (user.Bot) return; - + switch (evt.Emoji.Name) { // Message deletion case "\u274C": // Red X - { - await using var conn = await _db.Obtain(); - var msg = await _repo.GetMessage(conn, evt.MessageId); - if (msg != null) - await HandleProxyDeleteReaction(evt, msg); - - break; - } + { + await using var conn = await _db.Obtain(); + var msg = await _repo.GetMessage(conn, evt.MessageId); + if (msg != null) + await HandleProxyDeleteReaction(evt, msg); + + break; + } case "\u2753": // Red question mark case "\u2754": // White question mark - { - await using var conn = await _db.Obtain(); - var msg = await _repo.GetMessage(conn, evt.MessageId); - if (msg != null) - await HandleQueryReaction(evt, msg); - - break; - } + { + await using var conn = await _db.Obtain(); + var msg = await _repo.GetMessage(conn, evt.MessageId); + if (msg != null) + await HandleQueryReaction(evt, msg); + + break; + } case "\U0001F514": // Bell case "\U0001F6CE": // Bellhop bell case "\U0001F3D3": // Ping pong paddle (lol) case "\u23F0": // Alarm clock case "\u2757": // Exclamation mark - { - await using var conn = await _db.Obtain(); - var msg = await _repo.GetMessage(conn, evt.MessageId); - if (msg != null) - await HandlePingReaction(evt, msg); - break; - } + { + await using var conn = await _db.Obtain(); + var msg = await _repo.GetMessage(conn, evt.MessageId); + if (msg != null) + await HandlePingReaction(evt, msg); + break; + } } } @@ -114,7 +114,7 @@ namespace PluralKit.Bot { if (!_bot.PermissionsIn(evt.ChannelId).HasFlag(PermissionSet.ManageMessages)) return; - + using var conn = await _db.Obtain(); var system = await _repo.GetSystemByAccount(conn, evt.UserId); @@ -136,7 +136,7 @@ namespace PluralKit.Bot private async ValueTask HandleCommandDeleteReaction(MessageReactionAddEvent evt, CommandMessage msg) { // Can only delete your own message - if (msg.AuthorId != evt.UserId) + if (msg.AuthorId != evt.UserId) return; try @@ -154,7 +154,7 @@ namespace PluralKit.Bot private async ValueTask HandleQueryReaction(MessageReactionAddEvent evt, FullMessage msg) { var guild = _cache.GetGuild(evt.GuildId!.Value); - + // Try to DM the user info about the message try { @@ -163,14 +163,14 @@ namespace PluralKit.Bot { Embed = await _embeds.CreateMemberEmbed(msg.System, msg.Member, guild, LookupContext.ByNonOwner) }); - + await _rest.CreateMessage(dm.Id, new MessageRequest { Embed = await _embeds.CreateMessageInfoEmbed(msg) }); } catch (ForbiddenException) { } // No permissions to DM, can't check for this :( - + await TryRemoveOriginalReaction(evt); } @@ -178,20 +178,20 @@ namespace PluralKit.Bot { if (!_bot.PermissionsIn(evt.ChannelId).HasFlag(PermissionSet.ManageMessages)) return; - + // Check if the "pinger" has permission to send messages in this channel // (if not, PK shouldn't send messages on their behalf) var member = await _rest.GetGuildMember(evt.GuildId!.Value, evt.UserId); var requiredPerms = PermissionSet.ViewChannel | PermissionSet.SendMessages; if (member == null || !_cache.PermissionsFor(evt.ChannelId, member).HasFlag(requiredPerms)) return; - + if (msg.System.PingsEnabled) { // If the system has pings enabled, go ahead await _rest.CreateMessage(evt.ChannelId, new() { Content = $"Psst, **{msg.Member.DisplayName()}** (<@{msg.Message.Sender}>), you have been pinged by <@{evt.UserId}>.", - Components = new [] + Components = new[] { new MessageComponent { @@ -208,7 +208,7 @@ namespace PluralKit.Bot } } }, - AllowedMentions = new AllowedMentions {Users = new[] {msg.Message.Sender}} + AllowedMentions = new AllowedMentions { Users = new[] { msg.Message.Sender } } }); } else @@ -221,7 +221,7 @@ namespace PluralKit.Bot { Content = $"{Emojis.Error} {msg.Member.DisplayName()}'s system has disabled reaction pings. If you want to mention them anyway, you can copy/paste the following message:" }); - await _rest.CreateMessage(dm.Id, new MessageRequest {Content = $"<@{msg.Message.Sender}>".AsCode()}); + await _rest.CreateMessage(dm.Id, new MessageRequest { Content = $"<@{msg.Message.Sender}>".AsCode() }); } catch (ForbiddenException) { } } diff --git a/PluralKit.Bot/Init.cs b/PluralKit.Bot/Init.cs index a93ed6a3..52502542 100644 --- a/PluralKit.Bot/Init.cs +++ b/PluralKit.Bot/Init.cs @@ -24,29 +24,29 @@ namespace PluralKit.Bot // Load configuration and run global init stuff var config = InitUtils.BuildConfiguration(args).Build(); InitUtils.InitStatic(); - + // Set up DI container and modules var services = BuildContainer(config); - + return RunWrapper(services, async ct => { // init version service await BuildInfoService.LoadVersion(); var logger = services.Resolve().ForContext(); - + // Initialize Sentry SDK, and make sure it gets dropped at the end using var _ = Sentry.SentrySdk.Init(services.Resolve().SentryUrl); // "Connect to the database" (ie. set off database migrations and ensure state) logger.Information("Connecting to database"); await services.Resolve().ApplyMigrations(); - + // Init the bot instance itself, register handlers and such to the client before beginning to connect logger.Information("Initializing bot"); var bot = services.Resolve(); bot.Init(); - + // Install observer for request/responses DiscordRequestObserver.Install(services); @@ -80,14 +80,14 @@ namespace PluralKit.Bot var shutdown = new TaskCompletionSource(); var gracefulShutdownCts = new CancellationTokenSource(); - + Console.CancelKeyPress += delegate { // ReSharper disable once AccessToDisposedClosure (will only be hit before the below disposal) logger.Information("Received SIGINT/Ctrl-C, attempting graceful shutdown..."); gracefulShutdownCts.Cancel(); }; - + AppDomain.CurrentDomain.ProcessExit += (_, __) => { // This callback is fired on a SIGKILL is sent. @@ -109,9 +109,9 @@ namespace PluralKit.Bot { logger.Fatal(e, "Error while running bot"); } - + // Allow the log buffer to flush properly before exiting - ((Logger) logger).Dispose(); + ((Logger)logger).Dispose(); await Task.Delay(500); shutdown.SetResult(null); } @@ -138,18 +138,18 @@ namespace PluralKit.Bot var cluster = services.Resolve(); var config = services.Resolve(); - + if (config.Cluster != null) { // For multi-instance deployments, calculate the "span" of shards this node is responsible for var totalNodes = config.Cluster.TotalNodes; var totalShards = config.Cluster.TotalShards; var nodeIndex = ExtractNodeIndex(config.Cluster.NodeName); - + // Should evenly distribute shards even with an uneven amount of nodes - var shardMin = (int) Math.Round(totalShards * (float) nodeIndex / totalNodes); - var shardMax = (int) Math.Round(totalShards * (float) (nodeIndex + 1) / totalNodes) - 1; - + var shardMin = (int)Math.Round(totalShards * (float)nodeIndex / totalNodes); + var shardMax = (int)Math.Round(totalShards * (float)(nodeIndex + 1) / totalNodes) - 1; + await cluster.Start(info.Url, shardMin, shardMax, totalShards, info.SessionStartLimit.MaxConcurrency); } else diff --git a/PluralKit.Bot/Interactive/BaseInteractive.cs b/PluralKit.Bot/Interactive/BaseInteractive.cs index e5917887..53377a8f 100644 --- a/PluralKit.Bot/Interactive/BaseInteractive.cs +++ b/PluralKit.Bot/Interactive/BaseInteractive.cs @@ -21,7 +21,7 @@ namespace PluralKit.Bot.Interactive protected readonly TaskCompletionSource _tcs = new(); protected Message _message { get; private set; } protected bool _running; - + protected BaseInteractive(Context ctx) { _ctx = ctx; @@ -33,7 +33,7 @@ namespace PluralKit.Bot.Interactive { var dispatch = _ctx.Services.Resolve(); var customId = dispatch.Register(handler, Timeout); - + var button = new Button { Label = label, @@ -56,16 +56,17 @@ namespace PluralKit.Bot.Interactive protected async Task Finish(InteractionContext? ctx = null) { - foreach (var button in _buttons) + foreach (var button in _buttons) button.Disabled = true; if (ctx != null) await Update(ctx); - else - await _ctx.Rest.EditMessage(_message.ChannelId, _message.Id, new MessageEditRequest { + else + await _ctx.Rest.EditMessage(_message.ChannelId, _message.Id, new MessageEditRequest + { Components = GetComponents() }); - + _tcs.TrySetResult(); } @@ -95,12 +96,12 @@ namespace PluralKit.Bot.Interactive public void Setup(Context ctx) { var dispatch = ctx.Services.Resolve(); - foreach (var button in _buttons) + foreach (var button in _buttons) button.CustomId = dispatch.Register(button.Handler, Timeout); } public abstract Task Start(); - + public async Task Run() { if (_running) @@ -108,7 +109,7 @@ namespace PluralKit.Bot.Interactive _running = true; await Start(); - + var cts = new CancellationTokenSource(Timeout.ToTimeSpan()); cts.Token.Register(() => _tcs.TrySetException(new TimeoutException("Action timed out"))); @@ -125,7 +126,7 @@ namespace PluralKit.Bot.Interactive protected void Cleanup() { var dispatch = _ctx.Services.Resolve(); - foreach (var button in _buttons) + foreach (var button in _buttons) dispatch.Unregister(button.CustomId!); } } diff --git a/PluralKit.Bot/Interactive/Button.cs b/PluralKit.Bot/Interactive/Button.cs index a2c6d4e5..4c9b9223 100644 --- a/PluralKit.Bot/Interactive/Button.cs +++ b/PluralKit.Bot/Interactive/Button.cs @@ -15,9 +15,9 @@ namespace PluralKit.Bot.Interactive public MessageComponent ToMessageComponent() => new() { - Type = ComponentType.Button, - Label = Label, - Style = Style, + Type = ComponentType.Button, + Label = Label, + Style = Style, CustomId = CustomId, Disabled = Disabled }; diff --git a/PluralKit.Bot/Interactive/YesNoPrompt.cs b/PluralKit.Bot/Interactive/YesNoPrompt.cs index 8530dcd1..d4d6dd67 100644 --- a/PluralKit.Bot/Interactive/YesNoPrompt.cs +++ b/PluralKit.Bot/Interactive/YesNoPrompt.cs @@ -23,7 +23,7 @@ namespace PluralKit.Bot.Interactive public string CancelLabel { get; set; } = "Cancel"; public ButtonStyle CancelStyle { get; set; } = ButtonStyle.Secondary; - + public override async Task Start() { AddButton(ctx => OnButtonClick(ctx, true), AcceptLabel, AcceptStyle); @@ -32,7 +32,7 @@ namespace PluralKit.Bot.Interactive AllowedMentions mentions = null; if (User != _ctx.Author.Id) - mentions = new AllowedMentions {Users = new[] {User!.Value}}; + mentions = new AllowedMentions { Users = new[] { User!.Value } }; await Send(Message, mentions: mentions); } @@ -44,7 +44,7 @@ namespace PluralKit.Bot.Interactive await Update(ctx); return; } - + Result = result; await Finish(ctx); } @@ -82,11 +82,11 @@ namespace PluralKit.Bot.Interactive _running = true; var queue = _ctx.Services.Resolve>(); - + var messageDispatch = queue.WaitFor(MessagePredicate, Timeout, cts.Token); await Start(); - + cts.Token.Register(() => _tcs.TrySetException(new TimeoutException("Action timed out"))); try @@ -101,7 +101,7 @@ namespace PluralKit.Bot.Interactive } } - public YesNoPrompt(Context ctx): base(ctx) + public YesNoPrompt(Context ctx) : base(ctx) { User = ctx.Author.Id; } diff --git a/PluralKit.Bot/Modules.cs b/PluralKit.Bot/Modules.cs index a84a1331..10c908ed 100644 --- a/PluralKit.Bot/Modules.cs +++ b/PluralKit.Bot/Modules.cs @@ -69,7 +69,7 @@ namespace PluralKit.Bot builder.RegisterType().AsSelf(); builder.RegisterType().AsSelf(); builder.RegisterType().AsSelf(); - + // Bot core builder.RegisterType().AsSelf().SingleInstance(); builder.RegisterType().As>(); @@ -77,11 +77,11 @@ namespace PluralKit.Bot builder.RegisterType().As>(); builder.RegisterType().As>(); builder.RegisterType().As>(); - + // Event handler queue builder.RegisterType>().AsSelf().SingleInstance(); builder.RegisterType>().AsSelf().SingleInstance(); - + // Bot services builder.RegisterType().AsSelf().SingleInstance(); builder.RegisterType().AsSelf().SingleInstance(); @@ -97,7 +97,7 @@ namespace PluralKit.Bot builder.RegisterType().AsSelf().SingleInstance(); builder.RegisterType().AsSelf().SingleInstance(); builder.RegisterType().AsSelf().SingleInstance(); - + // Sentry stuff builder.Register(_ => new Scope(null)).AsSelf().InstancePerLifetimeScope(); builder.RegisterType() @@ -107,7 +107,7 @@ namespace PluralKit.Bot .As>() .As>() .SingleInstance(); - + // Proxy stuff builder.RegisterType().AsSelf().SingleInstance(); builder.RegisterType().AsSelf().SingleInstance(); @@ -116,7 +116,7 @@ namespace PluralKit.Bot builder.Register(c => new HttpClient { Timeout = TimeSpan.FromSeconds(5), - DefaultRequestHeaders = {{"User-Agent", DiscordApiClient.UserAgent}} + DefaultRequestHeaders = { { "User-Agent", DiscordApiClient.UserAgent } } }).AsSelf().SingleInstance(); builder.RegisterInstance(SystemClock.Instance).As(); builder.RegisterType().AsSelf().SingleInstance(); diff --git a/PluralKit.Bot/Proxy/ProxyMatch.cs b/PluralKit.Bot/Proxy/ProxyMatch.cs index d3578248..3d908be9 100644 --- a/PluralKit.Bot/Proxy/ProxyMatch.cs +++ b/PluralKit.Bot/Proxy/ProxyMatch.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using PluralKit.Core; namespace PluralKit.Bot @@ -8,7 +8,7 @@ namespace PluralKit.Bot public ProxyMember Member; public string? Content; public ProxyTag? ProxyTags; - + public string? ProxyContent { get diff --git a/PluralKit.Bot/Proxy/ProxyMatcher.cs b/PluralKit.Bot/Proxy/ProxyMatcher.cs index 653aa4b8..5bc0ce4e 100644 --- a/PluralKit.Bot/Proxy/ProxyMatcher.cs +++ b/PluralKit.Bot/Proxy/ProxyMatcher.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using NodaTime; @@ -32,7 +32,7 @@ namespace PluralKit.Bot private bool TryMatchTags(IReadOnlyCollection members, string messageContent, bool hasAttachments, out ProxyMatch match) { if (!_parser.TryMatch(members, messageContent, out match)) return false; - + // Edge case: If we got a match with blank inner text, we'd normally just send w/ attachments // However, if there are no attachments, the user probably intended something else, so we "un-match" and proceed to autoproxy return hasAttachments || match.Content.Trim().Length > 0; @@ -50,19 +50,19 @@ namespace PluralKit.Bot // Find the member we should autoproxy (null if none) var member = ctx.AutoproxyMode switch { - AutoproxyMode.Member when ctx.AutoproxyMember != null => + AutoproxyMode.Member when ctx.AutoproxyMember != null => members.FirstOrDefault(m => m.Id == ctx.AutoproxyMember), - AutoproxyMode.Front when ctx.LastSwitchMembers.Length > 0 => + AutoproxyMode.Front when ctx.LastSwitchMembers.Length > 0 => members.FirstOrDefault(m => m.Id == ctx.LastSwitchMembers[0]), - + AutoproxyMode.Latch when ctx.LastMessageMember != null => members.FirstOrDefault(m => m.Id == ctx.LastMessageMember.Value), - + _ => null }; // Throw an error if the member is null, message varies depending on autoproxy mode - if (member == null) + if (member == null) { if (ctx.AutoproxyMode == AutoproxyMode.Front) throw new ProxyService.ProxyChecksFailedException("You are using autoproxy front, but no members are currently registered as fronting. Please use `pk;switch ` to log a new switch."); @@ -78,13 +78,13 @@ namespace PluralKit.Bot // Moved the IsLatchExpired() check to here, so that an expired latch and a latch without any previous messages throw different errors if (ctx.AutoproxyMode == AutoproxyMode.Latch && IsLatchExpired(ctx)) - throw new ProxyService.ProxyChecksFailedException("Latch-mode autoproxy has timed out. Please send a new message using proxy tags."); + throw new ProxyService.ProxyChecksFailedException("Latch-mode autoproxy has timed out. Please send a new message using proxy tags."); match = new ProxyMatch { Content = messageContent, Member = member, - + // We're autoproxying, so not using any proxy tags here // we just find the first pair of tags (if any), otherwise null ProxyTags = member.ProxyTags.FirstOrDefault() @@ -98,7 +98,7 @@ namespace PluralKit.Bot if (ctx.LatchTimeout == 0) return false; var timeout = ctx.LatchTimeout.HasValue - ? Duration.FromSeconds(ctx.LatchTimeout.Value) + ? Duration.FromSeconds(ctx.LatchTimeout.Value) : DefaultLatchExpiryTime; var timestamp = DiscordUtils.SnowflakeToInstant(ctx.LastMessage.Value); diff --git a/PluralKit.Bot/Proxy/ProxyService.cs b/PluralKit.Bot/Proxy/ProxyService.cs index 1ae7c189..d20eef8d 100644 --- a/PluralKit.Bot/Proxy/ProxyService.cs +++ b/PluralKit.Bot/Proxy/ProxyService.cs @@ -53,7 +53,7 @@ namespace PluralKit.Bot public async Task HandleIncomingMessage(Shard shard, MessageCreateEvent message, MessageContext ctx, Guild guild, Channel channel, bool allowAutoproxy, PermissionSet botPermissions) { - if (!ShouldProxy(channel, message, ctx)) + if (!ShouldProxy(channel, message, ctx)) return false; // Fetch members and try to match to a specific member @@ -64,7 +64,7 @@ namespace PluralKit.Bot List members; using (_metrics.Measure.Timer.Time(BotMetrics.ProxyMembersQueryTime)) members = (await _repo.GetProxyMembers(conn, message.Author.Id, message.GuildId!.Value)).ToList(); - + if (!_matcher.TryMatch(ctx, members, out var match, message.Content, message.Attachments.Length > 0, allowAutoproxy)) return false; @@ -72,12 +72,12 @@ namespace PluralKit.Bot if (message.Content != null && message.Content.Length > 2000) throw new PKError("PluralKit cannot proxy messages over 2000 characters in length."); // Permission check after proxy match so we don't get spammed when not actually proxying - if (!await CheckBotPermissionsOrError(botPermissions, rootChannel.Id)) + if (!await CheckBotPermissionsOrError(botPermissions, rootChannel.Id)) return false; // this method throws, so no need to wrap it in an if statement CheckProxyNameBoundsOrError(match.Member.ProxyName(ctx)); - + // Check if the sender account can mention everyone/here + embed links // we need to "mirror" these permissions when proxying to prevent exploits var senderPermissions = PermissionExtensions.PermissionsFor(guild, rootChannel, message); @@ -94,17 +94,17 @@ namespace PluralKit.Bot // Make sure author has a system if (ctx.SystemId == null) throw new ProxyChecksFailedException(Errors.NoSystemError.Message); - + // Make sure channel is a guild text channel and this is a normal message if (!DiscordUtils.IsValidGuildChannel(channel)) throw new ProxyChecksFailedException("This channel is not a text channel."); if (msg.Type != Message.MessageType.Default && msg.Type != Message.MessageType.Reply) throw new ProxyChecksFailedException("This message is not a normal message."); - + // Make sure author is a normal user if (msg.Author.System == true || msg.Author.Bot || msg.WebhookId != null) throw new ProxyChecksFailedException("This message was not sent by a normal user."); - + // Make sure proxying is enabled here if (ctx.InBlacklist) throw new ProxyChecksFailedException($"Proxying was disabled in this channel by a server administrator (via the proxy blacklist)."); @@ -112,12 +112,12 @@ namespace PluralKit.Bot // Make sure the system has proxying enabled in the server if (!ctx.ProxyEnabled) throw new ProxyChecksFailedException("Your system has proxying disabled in this server. Type `pk;proxy on` to enable it."); - + // Make sure we have either an attachment or message content var isMessageBlank = msg.Content == null || msg.Content.Trim().Length == 0; if (isMessageBlank && msg.Attachments.Length == 0) throw new ProxyChecksFailedException("Message cannot be blank."); - + // All good! return true; } @@ -137,17 +137,17 @@ namespace PluralKit.Bot if (embed != null) embeds.Add(embed); } - + // TODO: have a clean error for when message can't be fetched instead of just being silent } - + // Send the webhook var content = match.ProxyContent; if (!allowEmbeds) content = content.BreakLinkEmbeds(); var messageChannel = _cache.GetChannel(trigger.ChannelId); var rootChannel = _cache.GetRootChannel(trigger.ChannelId); - var threadId = messageChannel.IsThread() ? messageChannel.Id : (ulong?)null; + var threadId = messageChannel.IsThread() ? messageChannel.Id : (ulong?)null; var proxyMessage = await _webhookExecutor.ExecuteWebhook(new ProxyRequest { @@ -186,7 +186,7 @@ namespace PluralKit.Bot { // repliedTo doesn't have a GuildId field :/ var jumpLink = $"https://discord.com/channels/{trigger.GuildId}/{repliedTo.ChannelId}/{repliedTo.Id}"; - + var content = new StringBuilder(); var hasContent = !string.IsNullOrWhiteSpace(repliedTo.Content); @@ -211,7 +211,7 @@ namespace PluralKit.Bot var urlTail = repliedTo.Content.Substring(100).Split(" ")[0]; msg += urlTail + " "; } - + var spoilersInOriginalString = Regex.Matches(repliedTo.Content, @"\|\|").Count; var spoilersInTruncatedString = Regex.Matches(msg, @"\|\|").Count; if (spoilersInTruncatedString % 2 == 1 && spoilersInOriginalString % 2 == 0) @@ -219,7 +219,7 @@ namespace PluralKit.Bot if (msg != repliedTo.Content) msg += "…"; } - + content.Append($"**[Reply to:]({jumpLink})** "); content.Append(msg); if (repliedTo.Attachments.Length > 0) @@ -229,7 +229,7 @@ namespace PluralKit.Bot { content.Append($"*[(click to see attachment)]({jumpLink})*"); } - + var username = nickname ?? repliedTo.Author.Username; var avatarUrl = avatar != null ? $"https://cdn.discordapp.com/guilds/{trigger.GuildId}/users/{repliedTo.Author.Id}/{avatar}.png" @@ -247,12 +247,12 @@ namespace PluralKit.Bot private async Task FixSameName(ulong channelId, MessageContext ctx, ProxyMember member) { var proxyName = member.ProxyName(ctx); - + var lastMessage = _lastMessage.GetLastMessage(channelId)?.Previous; if (lastMessage == null) // cache is out of date or channel is empty. return proxyName; - + await using var conn = await _db.Obtain(); var pkMessage = await _repo.GetMessage(conn, lastMessage.Id); @@ -294,9 +294,9 @@ namespace PluralKit.Bot }; Task SaveMessageInDatabase() => _repo.AddMessage(conn, sentMessage); - + Task LogMessageToChannel() => _logChannel.LogMessage(ctx, sentMessage, triggerMessage, proxyMessage).AsTask(); - + async Task DeleteProxyTriggerMessage() { // Wait a second or so before deleting the original message @@ -307,13 +307,13 @@ namespace PluralKit.Bot } catch (NotFoundException) { - _logger.Debug("Trigger message {TriggerMessageId} was already deleted when we attempted to; deleting proxy message {ProxyMessageId} also", + _logger.Debug("Trigger message {TriggerMessageId} was already deleted when we attempted to; deleting proxy message {ProxyMessageId} also", triggerMessage.Id, proxyMessage.Id); await HandleTriggerAlreadyDeleted(proxyMessage); // Swallow the exception, we don't need it } } - + // Run post-proxy actions (simultaneously; order doesn't matter) // Note that only AddMessage is using our passed-in connection, careful not to pass it elsewhere and run into conflicts await Task.WhenAll( @@ -341,7 +341,7 @@ namespace PluralKit.Bot { // If we can't send messages at all, just bail immediately. // 2020-04-22: Manage Messages does *not* override a lack of Send Messages. - if (!permissions.HasFlag(PermissionSet.SendMessages)) + if (!permissions.HasFlag(PermissionSet.SendMessages)) return false; if (!permissions.HasFlag(PermissionSet.ManageWebhooks)) @@ -370,9 +370,9 @@ namespace PluralKit.Bot { if (proxyName.Length > Limits.MaxProxyNameLength) throw Errors.ProxyNameTooLong(proxyName); } - public class ProxyChecksFailedException : Exception + public class ProxyChecksFailedException: Exception { - public ProxyChecksFailedException(string message) : base(message) {} + public ProxyChecksFailedException(string message) : base(message) { } } } -} +} \ No newline at end of file diff --git a/PluralKit.Bot/Proxy/ProxyTagParser.cs b/PluralKit.Bot/Proxy/ProxyTagParser.cs index 6ff39215..0b5a44c1 100644 --- a/PluralKit.Bot/Proxy/ProxyTagParser.cs +++ b/PluralKit.Bot/Proxy/ProxyTagParser.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System.Collections.Generic; using System.Linq; @@ -11,10 +11,10 @@ namespace PluralKit.Bot public bool TryMatch(IEnumerable members, string? input, out ProxyMatch result) { result = default; - + // Null input is valid and is equivalent to empty string if (input == null) return false; - + // If the message starts with a @mention, and then proceeds to have proxy tags, // extract the mention and place it inside the inner message // eg. @Ske [text] => [@Ske text] @@ -26,13 +26,13 @@ namespace PluralKit.Bot var tags = members .SelectMany(member => member.ProxyTags.Select(tag => (tag, member))) .OrderByDescending(p => p.tag.ProxyString.Length); - + // Iterate now-ordered list of tags and try matching each one foreach (var (tag, member) in tags) { result.ProxyTags = tag; result.Member = member; - + // Skip blank tags (shouldn't ever happen in practice) if (tag.Prefix == null && tag.Suffix == null) continue; @@ -43,10 +43,10 @@ namespace PluralKit.Bot if (leadingMention != null) result.Content = $"{leadingMention} {result.Content}"; return true; } - + // (if not, keep going) } - + // We couldn't match anything :( return false; } @@ -54,25 +54,25 @@ namespace PluralKit.Bot private bool TryMatchTagsInner(string input, ProxyTag tag, out string inner) { inner = ""; - + // Normalize null tags to empty strings var prefix = tag.Prefix ?? ""; var suffix = tag.Suffix ?? ""; - + // Check if our input starts/ends with the tags - var isMatch = input.Length >= prefix.Length + suffix.Length + var isMatch = input.Length >= prefix.Length + suffix.Length && input.StartsWith(prefix) && input.EndsWith(suffix); - + // Special case: image-only proxies + proxy tags with spaces // Trim everything, then see if we have a "contentless tag pair" (normally disallowed, but OK if we have an attachment) // Note `input` is still "", even if there are spaces between if (!isMatch && input.Trim() == prefix.TrimEnd() + suffix.TrimStart()) return true; - if (!isMatch) return false; - + if (!isMatch) return false; + // We got a match, extract inner text inner = input.Substring(prefix.Length, input.Length - prefix.Length - suffix.Length); - + // (see https://github.com/xSke/PluralKit/pull/181) return inner.Trim() != "\U0000fe0f"; } @@ -81,7 +81,7 @@ namespace PluralKit.Bot { var mentionPos = 0; if (!DiscordUtils.HasMentionPrefix(input, ref mentionPos, out _)) return null; - + var leadingMention = input.Substring(0, mentionPos); input = input.Substring(mentionPos); return leadingMention; diff --git a/PluralKit.Bot/Services/CommandMessageService.cs b/PluralKit.Bot/Services/CommandMessageService.cs index b666fd0b..80a985ad 100644 --- a/PluralKit.Bot/Services/CommandMessageService.cs +++ b/PluralKit.Bot/Services/CommandMessageService.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using NodaTime; @@ -11,12 +11,12 @@ namespace PluralKit.Bot public class CommandMessageService { private static readonly Duration CommandMessageRetention = Duration.FromHours(2); - + private readonly IDatabase _db; private readonly ModelRepository _repo; private readonly IClock _clock; private readonly ILogger _logger; - + public CommandMessageService(IDatabase db, ModelRepository repo, IClock clock, ILogger logger) { _db = db; @@ -42,7 +42,7 @@ namespace PluralKit.Bot var deleteThresholdSnowflake = DiscordUtils.InstantToSnowflake(deleteThresholdInstant); var deletedRows = await _db.Execute(conn => _repo.DeleteCommandMessagesBefore(conn, deleteThresholdSnowflake)); - + _logger.Information("Pruned {DeletedRows} command messages older than retention {Retention} (older than {DeleteThresholdInstant} / {DeleteThresholdSnowflake})", deletedRows, CommandMessageRetention, deleteThresholdInstant, deleteThresholdSnowflake); } diff --git a/PluralKit.Bot/Services/CpuStatService.cs b/PluralKit.Bot/Services/CpuStatService.cs index de161be7..cf6c0418 100644 --- a/PluralKit.Bot/Services/CpuStatService.cs +++ b/PluralKit.Bot/Services/CpuStatService.cs @@ -8,7 +8,7 @@ namespace PluralKit.Bot public class CpuStatService { private readonly ILogger _logger; - + public double LastCpuMeasure { get; private set; } public CpuStatService(ILogger logger) @@ -23,15 +23,15 @@ namespace PluralKit.Bot { // We get the current processor time, wait 5 seconds, then compare // https://medium.com/@jackwild/getting-cpu-usage-in-net-core-7ef825831b8b - + _logger.Debug("Estimating CPU usage..."); var stopwatch = new Stopwatch(); - + stopwatch.Start(); var cpuTimeBefore = Process.GetCurrentProcess().TotalProcessorTime; - + await Task.Delay(5000); - + stopwatch.Stop(); var cpuTimeAfter = Process.GetCurrentProcess().TotalProcessorTime; diff --git a/PluralKit.Bot/Services/EmbedService.cs b/PluralKit.Bot/Services/EmbedService.cs index 12e1a951..7b6a18d9 100644 --- a/PluralKit.Bot/Services/EmbedService.cs +++ b/PluralKit.Bot/Services/EmbedService.cs @@ -16,7 +16,8 @@ using NodaTime; using PluralKit.Core; -namespace PluralKit.Bot { +namespace PluralKit.Bot +{ public class EmbedService { private readonly IDatabase _db; @@ -42,11 +43,11 @@ namespace PluralKit.Bot { return Task.WhenAll(ids.Select(Inner)); } - + public async Task CreateSystemEmbed(Context cctx, PKSystem system, LookupContext ctx) { await using var conn = await _db.Obtain(); - + // Fetch/render info for all accounts simultaneously var accounts = await _repo.GetSystemAccounts(conn, system.Id); var users = (await GetUsers(accounts)).Select(x => x.User?.NameAndMention() ?? $"(deleted account {x.Id})"); @@ -81,13 +82,13 @@ namespace PluralKit.Bot { eb.Field(new("Fronter".ToQuantity(switchMembers.Count, ShowQuantityAs.None), string.Join(", ", switchMembers.Select(m => m.NameFor(ctx))))); } - if (system.Tag != null) + if (system.Tag != null) eb.Field(new("Tag", system.Tag.EscapeMarkdown(), true)); if (cctx.Guild != null) { var guildSettings = await _repo.GetSystemGuild(conn, cctx.Guild.Id, system.Id); - + if (guildSettings.Tag != null && guildSettings.TagEnabled) eb.Field(new($"Tag (in server '{cctx.Guild.Name}')", guildSettings.Tag .EscapeMarkdown(), true)); @@ -114,7 +115,8 @@ namespace PluralKit.Bot { return eb.Build(); } - public Embed CreateLoggedMessageEmbed(Message triggerMessage, Message proxiedMessage, string systemHid, PKMember member, string channelName, string oldContent = null) { + public Embed CreateLoggedMessageEmbed(Message triggerMessage, Message proxiedMessage, string systemHid, PKMember member, string channelName, string oldContent = null) + { // TODO: pronouns in ?-reacted response using this card var timestamp = DiscordUtils.SnowflakeToInstant(proxiedMessage.Id); var name = proxiedMessage.Author.Username; @@ -129,7 +131,7 @@ namespace PluralKit.Bot { if (oldContent != null) embed.Field(new("Old message", oldContent?.NormalizeLineEndSpacing().Truncate(1000))); - + return embed.Build(); } @@ -155,7 +157,7 @@ namespace PluralKit.Bot { } await using var conn = await _db.Obtain(); - + var guildSettings = guild != null ? await _repo.GetMemberGuild(conn, guild.Id, member.Id) : null; var guildDisplayName = guildSettings?.DisplayName; var avatar = guildSettings?.AvatarUrl ?? member.AvatarFor(ctx); @@ -179,19 +181,19 @@ namespace PluralKit.Bot { var description = ""; if (member.MemberVisibility == PrivacyLevel.Private) description += "*(this member is hidden)*\n"; if (guildSettings?.AvatarUrl != null) - if (member.AvatarFor(ctx) != null) + if (member.AvatarFor(ctx) != null) description += $"*(this member has a server-specific avatar set; [click here]({member.AvatarUrl.TryGetCleanCdnUrl()}) to see the global avatar)*\n"; else description += "*(this member has a server-specific avatar set)*\n"; if (description != "") eb.Description(description); - + if (avatar != null) eb.Thumbnail(new(avatar.TryGetCleanCdnUrl())); if (!member.DisplayName.EmptyOrNull() && member.NamePrivacy.CanAccess(ctx)) eb.Field(new("Display Name", member.DisplayName.Truncate(1024), true)); if (guild != null && guildDisplayName != null) eb.Field(new($"Server Nickname (for {guild.Name})", guildDisplayName.Truncate(1024), true)); if (member.BirthdayFor(ctx) != null) eb.Field(new("Birthdate", member.BirthdayString, true)); - if (member.PronounsFor(ctx) is {} pronouns && !string.IsNullOrWhiteSpace(pronouns)) eb.Field(new("Pronouns", pronouns.Truncate(1024), true)); - if (member.MessageCountFor(ctx) is {} count && count > 0) eb.Field(new("Message Count", member.MessageCount.ToString(), true)); + if (member.PronounsFor(ctx) is { } pronouns && !string.IsNullOrWhiteSpace(pronouns)) eb.Field(new("Pronouns", pronouns.Truncate(1024), true)); + if (member.MessageCountFor(ctx) is { } count && count > 0) eb.Field(new("Message Count", member.MessageCount.ToString(), true)); if (member.HasProxyTags) eb.Field(new("Proxy Tags", member.ProxyTagsString("\n").Truncate(1024), true)); // --- For when this gets added to the member object itself or however they get added // if (member.LastMessage != null && member.MetadataPrivacy.CanAccess(ctx)) eb.AddField("Last message:" FormatTimestamp(DiscordUtils.SnowflakeToInstant(m.LastMessage.Value))); @@ -208,7 +210,7 @@ namespace PluralKit.Bot { eb.Field(new($"Groups ({groups.Count})", content.Truncate(1000))); } - if (member.DescriptionFor(ctx) is {} desc) + if (member.DescriptionFor(ctx) is { } desc) eb.Field(new("Description", member.Description.NormalizeLineEndSpacing(), false)); return eb.Build(); @@ -217,7 +219,7 @@ namespace PluralKit.Bot { public async Task CreateGroupEmbed(Context ctx, PKSystem system, PKGroup target) { await using var conn = await _db.Obtain(); - + var pctx = ctx.LookupContextFor(system); var memberCount = ctx.MatchPrivateFlag(pctx) ? await _repo.GetGroupMemberCount(conn, target.Id, PrivacyLevel.Public) : await _repo.GetGroupMemberCount(conn, target.Id); @@ -246,7 +248,7 @@ namespace PluralKit.Bot { if (target.DisplayName != null) eb.Field(new("Display Name", target.DisplayName, true)); - + if (!target.Color.EmptyOrNull()) eb.Field(new("Color", $"#{target.Color}", true)); if (target.ListPrivacy.CanAccess(pctx)) @@ -261,7 +263,7 @@ namespace PluralKit.Bot { if (target.DescriptionFor(pctx) is { } desc) eb.Field(new("Description", desc)); - if (target.IconFor(pctx) is {} icon) + if (target.IconFor(pctx) is { } icon) eb.Thumbnail(new(icon.TryGetCleanCdnUrl())); return eb.Build(); @@ -349,14 +351,14 @@ namespace PluralKit.Bot { .Select(role => role.Name)); eb.Field(new($"Account roles ({roles.Count})", rolesString.Truncate(1024))); } - + return eb.Build(); } public Task CreateFrontPercentEmbed(FrontBreakdown breakdown, PKSystem system, PKGroup group, DateTimeZone tz, LookupContext ctx, string embedTitle, bool ignoreNoFronters, bool showFlat) { string color = system.Color; - if (group != null) + if (group != null) { color = group.Color; } @@ -408,7 +410,7 @@ namespace PluralKit.Bot { foreach (var pair in membersOrdered) { var frac = pair.Value / period; - eb.Field(new(pair.Key?.NameFor(ctx) ?? "*(no fronter)*", $"{frac*100:F0}% ({pair.Value.FormatDuration()})")); + eb.Field(new(pair.Key?.NameFor(ctx) ?? "*(no fronter)*", $"{frac * 100:F0}% ({pair.Value.FormatDuration()})")); } if (membersOrdered.Count > maxEntriesToDisplay) @@ -422,4 +424,4 @@ namespace PluralKit.Bot { return Task.FromResult(eb.Build()); } } -} +} \ No newline at end of file diff --git a/PluralKit.Bot/Services/ErrorMessageService.cs b/PluralKit.Bot/Services/ErrorMessageService.cs index bd4a6581..d7720b9e 100644 --- a/PluralKit.Bot/Services/ErrorMessageService.cs +++ b/PluralKit.Bot/Services/ErrorMessageService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Threading.Tasks; @@ -17,11 +17,11 @@ namespace PluralKit.Bot { private static readonly Duration MinErrorInterval = Duration.FromSeconds(10); private readonly ConcurrentDictionary _lastErrorInChannel = new ConcurrentDictionary(); - + private readonly IMetrics _metrics; private readonly ILogger _logger; private readonly DiscordApiClient _rest; - + public ErrorMessageService(IMetrics metrics, ILogger logger, DiscordApiClient rest) { _metrics = metrics; @@ -53,7 +53,7 @@ namespace PluralKit.Bot Content = $"> **Error code:** `{errorId}`", Embed = embed.Build() }); - + _logger.Information("Sent error message to {ChannelId} with error code {ErrorId}", channelId, errorId); _metrics.Measure.Meter.Mark(BotMetrics.ErrorMessagesSent, "sent"); } diff --git a/PluralKit.Bot/Services/GroupAddRemoveResponseService.cs b/PluralKit.Bot/Services/GroupAddRemoveResponseService.cs index ceb20aba..be91d5f0 100644 --- a/PluralKit.Bot/Services/GroupAddRemoveResponseService.cs +++ b/PluralKit.Bot/Services/GroupAddRemoveResponseService.cs @@ -12,14 +12,14 @@ namespace PluralKit.Bot private class Response { private readonly Groups.AddRemoveOperation _op; - + private readonly string _actionStr; private readonly string _containStr; private readonly string _emojiStr; private readonly bool _memberPlural; private readonly bool _groupPlural; - + private readonly int _actionedOn; private readonly int _notActionedOn; @@ -27,18 +27,18 @@ namespace PluralKit.Bot int notActionedOn) { _op = action; - + _actionStr = action == Groups.AddRemoveOperation.Add ? "added to" : "removed from"; _containStr = action == Groups.AddRemoveOperation.Add ? "in" : "not in"; _emojiStr = actionedOn > 0 ? Emojis.Success : Emojis.Error; _memberPlural = memberCount > 1; _groupPlural = groupCount > 1; - + // sanity checking: we can't add multiple groups to multiple members (at least for now) if (_memberPlural && _groupPlural) throw new ArgumentOutOfRangeException(); - + // sanity checking: we can't act/not act on a different number of entities than we have if (_memberPlural && (actionedOn + notActionedOn) != memberCount) throw new ArgumentOutOfRangeException(); @@ -48,7 +48,7 @@ namespace PluralKit.Bot _actionedOn = actionedOn; _notActionedOn = notActionedOn; } - + // name generators private string MemberString(bool capitalize = false) => capitalize @@ -59,14 +59,14 @@ namespace PluralKit.Bot => capitalize ? (count == 1 ? "Member" : "Members") : (count == 1 ? "member" : "members"); - + private string GroupString() => _groupPlural ? "groups" : "group"; private string GroupString(int count) => count == 1 ? "group" : "groups"; // string generators - + private string ResponseString() { if (_actionedOn > 0 && _notActionedOn > 0 && _memberPlural) @@ -100,9 +100,9 @@ namespace PluralKit.Bot return $" ({msg})"; } - + public string ToString() => $"{_emojiStr} {ResponseString()}{InfoMessage()}."; - + // | } } diff --git a/PluralKit.Bot/Services/InteractionDispatchService.cs b/PluralKit.Bot/Services/InteractionDispatchService.cs index 76a2d322..71902417 100644 --- a/PluralKit.Bot/Services/InteractionDispatchService.cs +++ b/PluralKit.Bot/Services/InteractionDispatchService.cs @@ -18,7 +18,7 @@ namespace PluralKit.Bot private readonly IClock _clock; private readonly ILogger _logger; private readonly Task _cleanupWorker; - + public InteractionDispatchService(IClock clock, ILogger logger) { _clock = clock; @@ -31,7 +31,7 @@ namespace PluralKit.Bot { if (!Guid.TryParse(customId, out var customIdGuid)) return false; - + if (!_handlers.TryGetValue(customIdGuid, out var handler)) return false; @@ -52,10 +52,10 @@ namespace PluralKit.Bot var key = Guid.NewGuid(); var handler = new RegisteredInteraction { - Callback = callback, + Callback = callback, Expiry = _clock.GetCurrentInstant() + (expiry ?? DefaultExpiry) }; - + _handlers[key] = handler; return key.ToString(); } @@ -93,7 +93,7 @@ namespace PluralKit.Bot public void Dispose() { - _cts.Cancel(); + _cts.Cancel(); _cts.Dispose(); } } diff --git a/PluralKit.Bot/Services/LastMessageCacheService.cs b/PluralKit.Bot/Services/LastMessageCacheService.cs index b5ec3ca4..acc873de 100644 --- a/PluralKit.Bot/Services/LastMessageCacheService.cs +++ b/PluralKit.Bot/Services/LastMessageCacheService.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System.Collections.Concurrent; using System.Collections.Generic; @@ -18,7 +18,7 @@ namespace PluralKit.Bot _cache[msg.ChannelId] = new(current, previous?.Current); } - private CachedMessage ToCachedMessage(Message msg) => + private CachedMessage ToCachedMessage(Message msg) => new(msg.Id, msg.ReferencedMessage.Value?.Id, msg.Author.Username); public CacheEntry? GetLastMessage(ulong channel) @@ -73,4 +73,4 @@ namespace PluralKit.Bot public record CacheEntry(CachedMessage Current, CachedMessage? Previous); public record CachedMessage(ulong Id, ulong? ReferencedMessage, string AuthorUsername); -} +} \ No newline at end of file diff --git a/PluralKit.Bot/Services/LogChannelService.cs b/PluralKit.Bot/Services/LogChannelService.cs index 64b9039e..3648bc84 100644 --- a/PluralKit.Bot/Services/LogChannelService.cs +++ b/PluralKit.Bot/Services/LogChannelService.cs @@ -12,8 +12,10 @@ using PluralKit.Core; using Serilog; -namespace PluralKit.Bot { - public class LogChannelService { +namespace PluralKit.Bot +{ + public class LogChannelService + { private readonly EmbedService _embed; private readonly IDatabase _db; private readonly ModelRepository _repo; @@ -40,7 +42,7 @@ namespace PluralKit.Bot { return; var triggerChannel = _cache.GetChannel(proxiedMessage.Channel); - + await using var conn = await _db.Obtain(); var system = await _repo.GetSystem(conn, ctx.SystemId.Value); var member = await _repo.GetMember(conn, proxiedMessage.Member); @@ -48,7 +50,7 @@ namespace PluralKit.Bot { // Send embed! var embed = _embed.CreateLoggedMessageEmbed(trigger, hookMessage, system.Hid, member, triggerChannel.Name, oldContent); var url = $"https://discord.com/channels/{proxiedMessage.Guild.Value}/{proxiedMessage.Channel}/{proxiedMessage.Mid}"; - await _rest.CreateMessage(logChannel.Id, new() {Content = url, Embed = embed}); + await _rest.CreateMessage(logChannel.Id, new() { Content = url, Embed = embed }); } private async Task GetAndCheckLogChannel(MessageContext ctx, Message trigger, PKMessage proxiedMessage) @@ -71,17 +73,17 @@ namespace PluralKit.Bot { } if (ctx.SystemId == null || logChannelId == null || isBlacklisted) return null; - + // Find log channel and check if valid var logChannel = await FindLogChannel(guildId, logChannelId.Value); if (logChannel == null || logChannel.Type != Channel.ChannelType.GuildText) return null; - + // Check bot permissions var perms = _bot.PermissionsIn(logChannel.Id); if (!perms.HasFlag(PermissionSet.SendMessages | PermissionSet.EmbedLinks)) { _logger.Information( - "Does not have permission to log proxy, ignoring (channel: {ChannelId}, guild: {GuildId}, bot permissions: {BotPermissions})", + "Does not have permission to log proxy, ignoring (channel: {ChannelId}, guild: {GuildId}, bot permissions: {BotPermissions})", ctx.LogChannel.Value, trigger.GuildId!.Value, perms); return null; } @@ -94,12 +96,12 @@ namespace PluralKit.Bot { // TODO: fetch it directly on cache miss? if (_cache.TryGetChannel(channelId, out var channel)) return channel; - + // Channel doesn't exist or we don't have permission to access it, let's remove it from the database too _logger.Warning("Attempted to fetch missing log channel {LogChannel} for guild {Guild}, removing from database", channelId, guildId); await using var conn = await _db.Obtain(); await conn.ExecuteAsync("update servers set log_channel = null where id = @Guild", - new {Guild = guildId}); + new { Guild = guildId }); return null; } diff --git a/PluralKit.Bot/Services/LoggerCleanService.cs b/PluralKit.Bot/Services/LoggerCleanService.cs index 0806f1f5..bb4d310a 100644 --- a/PluralKit.Bot/Services/LoggerCleanService.cs +++ b/PluralKit.Bot/Services/LoggerCleanService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; @@ -48,15 +48,15 @@ namespace PluralKit.Bot // There are two "Logger"s. They seem to be entirely unrelated. Don't ask. new LoggerBot("Logger#6088", 298822483060981760 , ExtractLoggerA, webhookName: "Logger"), - new LoggerBot("Logger#6278", 327424261180620801, ExtractLoggerB), - + new LoggerBot("Logger#6278", 327424261180620801, ExtractLoggerB), + new LoggerBot("Dyno", 155149108183695360, ExtractDyno, webhookName: "Dyno"), new LoggerBot("Auttaja", 242730576195354624, ExtractAuttaja, webhookName: "Auttaja"), - new LoggerBot("GenericBot", 295329346590343168, ExtractGenericBot), - new LoggerBot("blargbot", 134133271750639616, ExtractBlargBot), - new LoggerBot("Mantaro", 213466096718708737, ExtractMantaro), - new LoggerBot("UnbelievaBoat", 292953664492929025, ExtractUnbelievaBoat, webhookName: "UnbelievaBoat"), - new LoggerBot("Vanessa", 310261055060443136, fuzzyExtractFunc: ExtractVanessa), + new LoggerBot("GenericBot", 295329346590343168, ExtractGenericBot), + new LoggerBot("blargbot", 134133271750639616, ExtractBlargBot), + new LoggerBot("Mantaro", 213466096718708737, ExtractMantaro), + new LoggerBot("UnbelievaBoat", 292953664492929025, ExtractUnbelievaBoat, webhookName: "UnbelievaBoat"), + new LoggerBot("Vanessa", 310261055060443136, fuzzyExtractFunc: ExtractVanessa), new LoggerBot("SafetyAtLast", 401549924199694338, fuzzyExtractFunc: ExtractSAL), new LoggerBot("GearBot", 349977940198555660, fuzzyExtractFunc: ExtractGearBot), new LoggerBot("GiselleBot", 356831787445387285, ExtractGiselleBot), @@ -72,7 +72,7 @@ namespace PluralKit.Bot private readonly IDiscordCache _cache; private readonly Bot _bot; // todo: get rid of this nasty private readonly ILogger _logger; - + public LoggerCleanService(IDatabase db, DiscordApiClient client, IDiscordCache cache, Bot bot, ILogger logger) { _db = db; @@ -87,17 +87,17 @@ namespace PluralKit.Bot public async ValueTask HandleLoggerBotCleanup(Message msg) { var channel = _cache.GetChannel(msg.ChannelId); - + if (channel.Type != Channel.ChannelType.GuildText) return; if (!_bot.PermissionsIn(channel.Id).HasFlag(PermissionSet.ManageMessages)) return; - + // If this message is from a *webhook*, check if the name matches one of the bots we know // TODO: do we need to do a deeper webhook origin check, or would that be too hard on the rate limit? // If it's from a *bot*, check the bot ID to see if we know it. LoggerBot bot = null; if (msg.WebhookId != null) _botsByWebhookName.TryGetValue(msg.Author.Username, out bot); else if (msg.Author.Bot) _bots.TryGetValue(msg.Author.Id, out bot); - + // If we didn't find anything before, or what we found is an unsupported bot, bail if (bot == null) return; @@ -113,10 +113,10 @@ namespace PluralKit.Bot // either way but shouldn't be too much, given it's constrained by user ID and guild. var fuzzy = bot.FuzzyExtractFunc(msg); if (fuzzy == null) return; - + _logger.Debug("Fuzzy logclean for {BotName} on {MessageId}: {@FuzzyExtractResult}", bot.Name, msg.Id, fuzzy); - + var mid = await _db.Execute(conn => conn.QuerySingleOrDefaultAsync( "select mid from messages where sender = @User and mid > @ApproxID and guild = @Guild limit 1", @@ -127,11 +127,11 @@ namespace PluralKit.Bot ApproxId = DiscordUtils.InstantToSnowflake( fuzzy.Value.ApproxTimestamp - Duration.FromSeconds(3)) })); - + // If we didn't find a corresponding message, bail - if (mid == null) - return; - + if (mid == null) + return; + // Otherwise, we can *reasonably assume* that this is a logged deletion, so delete the log message. await _client.DeleteMessage(msg.ChannelId, msg.Id); } @@ -140,12 +140,12 @@ namespace PluralKit.Bot // Other bots give us the message ID itself, and we can just extract that from the database directly. var extractedId = bot.ExtractFunc(msg); if (extractedId == null) return; // If we didn't find anything, bail. - + _logger.Debug("Pure logclean for {BotName} on {MessageId}: {@FuzzyExtractResult}", bot.Name, msg.Id, extractedId); var mid = await _db.Execute(conn => conn.QuerySingleOrDefaultAsync( - "select mid from messages where original_mid = @Mid", new {Mid = extractedId.Value})); + "select mid from messages where original_mid = @Mid", new { Mid = extractedId.Value })); if (mid == null) return; // If we've gotten this far, we found a logged deletion of a trigger message. Just yeet it! @@ -167,9 +167,9 @@ namespace PluralKit.Bot // Regex also checks that this is a deletion. var stringWithId = msg.Embeds?.FirstOrDefault()?.Description ?? msg.Content; if (stringWithId == null) return null; - + var match = _auttajaRegex.Match(stringWithId); - return match.Success ? ulong.Parse(match.Groups[1].Value) : (ulong?) null; + return match.Success ? ulong.Parse(match.Groups[1].Value) : (ulong?)null; } private static ulong? ExtractDyno(Message msg) @@ -178,7 +178,7 @@ namespace PluralKit.Bot var embed = msg.Embeds?.FirstOrDefault(); if (embed?.Footer == null || !(embed.Description?.Contains("deleted in") ?? false)) return null; var match = _dynoRegex.Match(embed.Footer.Text ?? ""); - return match.Success ? ulong.Parse(match.Groups[1].Value) : (ulong?) null; + return match.Success ? ulong.Parse(match.Groups[1].Value) : (ulong?)null; } private static ulong? ExtractLoggerA(Message msg) @@ -188,11 +188,11 @@ namespace PluralKit.Bot var embed = msg.Embeds?.FirstOrDefault(); if (embed == null) return null; if (!embed.Description.StartsWith("Message deleted in")) return null; - + var idField = embed.Fields.FirstOrDefault(f => f.Name == "ID"); if (idField.Value == null) return null; // "OrDefault" = all-null object var match = _loggerARegex.Match(idField.Value); - return match.Success ? ulong.Parse(match.Groups[1].Value) : (ulong?) null; + return match.Success ? ulong.Parse(match.Groups[1].Value) : (ulong?)null; } private static ulong? ExtractLoggerB(Message msg) @@ -202,7 +202,7 @@ namespace PluralKit.Bot var embed = msg.Embeds?.FirstOrDefault(); if (embed?.Footer == null || !(embed.Title?.EndsWith("A Message Was Deleted!") ?? false)) return null; var match = _loggerBRegex.Match(embed.Footer.Text ?? ""); - return match.Success ? ulong.Parse(match.Groups[1].Value) : (ulong?) null; + return match.Success ? ulong.Parse(match.Groups[1].Value) : (ulong?)null; } private static ulong? ExtractGenericBot(Message msg) @@ -211,7 +211,7 @@ namespace PluralKit.Bot var embed = msg.Embeds?.FirstOrDefault(); if (embed?.Footer == null || !(embed.Title?.Contains("Message Deleted") ?? false)) return null; var match = _basicRegex.Match(embed.Footer.Text ?? ""); - return match.Success ? ulong.Parse(match.Groups[1].Value) : (ulong?) null; + return match.Success ? ulong.Parse(match.Groups[1].Value) : (ulong?)null; } private static ulong? ExtractBlargBot(Message msg) @@ -221,7 +221,7 @@ namespace PluralKit.Bot if (embed == null || !(embed.Title?.EndsWith("Message Deleted") ?? false)) return null; var field = embed.Fields.FirstOrDefault(f => f.Name == "Message ID"); var match = _basicRegex.Match(field.Value ?? ""); - return match.Success ? ulong.Parse(match.Groups[1].Value) : (ulong?) null; + return match.Success ? ulong.Parse(match.Groups[1].Value) : (ulong?)null; } private static ulong? ExtractMantaro(Message msg) @@ -229,7 +229,7 @@ namespace PluralKit.Bot // Plain message, "Message (ID: [id]) created by [user] (ID: [id]) in channel [channel] was deleted. if (!(msg.Content?.Contains("was deleted.") ?? false)) return null; var match = _mantaroRegex.Match(msg.Content); - return match.Success ? ulong.Parse(match.Groups[1].Value) : (ulong?) null; + return match.Success ? ulong.Parse(match.Groups[1].Value) : (ulong?)null; } private static FuzzyExtractResult? ExtractCarlBot(Message msg) @@ -239,13 +239,13 @@ namespace PluralKit.Bot var embed = msg.Embeds?.FirstOrDefault(); if (embed?.Footer == null || embed.Timestamp == null || !(embed.Title?.StartsWith("Message deleted in") ?? false)) return null; var match = _carlRegex.Match(embed.Footer.Text ?? ""); - return match.Success + return match.Success ? new FuzzyExtractResult { - User = ulong.Parse(match.Groups[1].Value), + User = ulong.Parse(match.Groups[1].Value), ApproxTimestamp = OffsetDateTimePattern.Rfc3339.Parse(embed.Timestamp).GetValueOrThrow().ToInstant() } - : (FuzzyExtractResult?) null; + : (FuzzyExtractResult?)null; } private static FuzzyExtractResult? ExtractCircle(Message msg) @@ -261,16 +261,17 @@ namespace PluralKit.Bot var field = embed.Fields.FirstOrDefault(f => f.Name == "Message Author"); if (field.Value == null) return null; stringWithId = field.Value; - } + } if (stringWithId == null) return null; - + var match = _circleRegex.Match(stringWithId); - return match.Success - ? new FuzzyExtractResult { + return match.Success + ? new FuzzyExtractResult + { User = ulong.Parse(match.Groups[1].Value), ApproxTimestamp = msg.Timestamp().ToInstant() } - : (FuzzyExtractResult?) null; + : (FuzzyExtractResult?)null; } private static FuzzyExtractResult? ExtractPancake(Message msg) @@ -286,18 +287,18 @@ namespace PluralKit.Bot User = ulong.Parse(match.Groups[1].Value), ApproxTimestamp = msg.Timestamp().ToInstant() } - : (FuzzyExtractResult?) null; + : (FuzzyExtractResult?)null; } - + private static ulong? ExtractUnbelievaBoat(Message msg) { // Embed author is "Message Deleted", footer contains message ID per regex var embed = msg.Embeds?.FirstOrDefault(); if (embed?.Footer == null || embed.Author?.Name != "Message Deleted") return null; var match = _unbelievaboatRegex.Match(embed.Footer.Text ?? ""); - return match.Success ? ulong.Parse(match.Groups[1].Value) : (ulong?) null; + return match.Success ? ulong.Parse(match.Groups[1].Value) : (ulong?)null; } - + private static FuzzyExtractResult? ExtractVanessa(Message msg) { // Title is "Message Deleted", embed description contains mention @@ -310,9 +311,9 @@ namespace PluralKit.Bot User = ulong.Parse(match.Groups[1].Value), ApproxTimestamp = msg.Timestamp().ToInstant() } - : (FuzzyExtractResult?) null; + : (FuzzyExtractResult?)null; } - + private static FuzzyExtractResult? ExtractSAL(Message msg) { // Title is "Message Deleted!", field "Message Author" contains ID @@ -327,7 +328,7 @@ namespace PluralKit.Bot User = ulong.Parse(match.Groups[1].Value), ApproxTimestamp = msg.Timestamp().ToInstant() } - : (FuzzyExtractResult?) null; + : (FuzzyExtractResult?)null; } private static FuzzyExtractResult? ExtractGearBot(Message msg) @@ -342,7 +343,7 @@ namespace PluralKit.Bot User = ulong.Parse(match.Groups[1].Value), ApproxTimestamp = msg.Timestamp().ToInstant() } - : (FuzzyExtractResult?) null; + : (FuzzyExtractResult?)null; } private static ulong? ExtractGiselleBot(Message msg) @@ -350,7 +351,7 @@ namespace PluralKit.Bot var embed = msg.Embeds?.FirstOrDefault(); if (embed?.Title == null || embed.Title != "🗑 Message Deleted") return null; var match = _GiselleRegex.Match(embed?.Description); - return match.Success ? ulong.Parse(match.Groups[1].Value) : (ulong?) null; + return match.Success ? ulong.Parse(match.Groups[1].Value) : (ulong?)null; } private static FuzzyExtractResult? ExtractVortex(Message msg) diff --git a/PluralKit.Bot/Services/PeriodicStatCollector.cs b/PluralKit.Bot/Services/PeriodicStatCollector.cs index cd9c96d5..8deff8b4 100644 --- a/PluralKit.Bot/Services/PeriodicStatCollector.cs +++ b/PluralKit.Bot/Services/PeriodicStatCollector.cs @@ -43,11 +43,11 @@ namespace PluralKit.Bot { var stopwatch = new Stopwatch(); stopwatch.Start(); - + // Aggregate guild/channel stats var guildCount = 0; var channelCount = 0; - + // No LINQ today, sorry await foreach (var guild in _cache.GetAllGuilds()) { @@ -58,10 +58,10 @@ namespace PluralKit.Bot channelCount++; } } - + _metrics.Measure.Gauge.SetValue(BotMetrics.Guilds, guildCount); _metrics.Measure.Gauge.SetValue(BotMetrics.Channels, channelCount); - + // Aggregate DB stats var counts = await _db.Execute(c => c.QueryFirstAsync("select (select count(*) from systems) as systems, (select count(*) from members) as members, (select count(*) from switches) as switches, (select count(*) from messages) as messages, (select count(*) from groups) as groups")); _metrics.Measure.Gauge.SetValue(CoreMetrics.SystemCount, counts.Systems); @@ -69,7 +69,7 @@ namespace PluralKit.Bot _metrics.Measure.Gauge.SetValue(CoreMetrics.SwitchCount, counts.Switches); _metrics.Measure.Gauge.SetValue(CoreMetrics.MessageCount, counts.Messages); _metrics.Measure.Gauge.SetValue(CoreMetrics.GroupCount, counts.Groups); - + // Process info var process = Process.GetCurrentProcess(); _metrics.Measure.Gauge.SetValue(CoreMetrics.ProcessPhysicalMemory, process.WorkingSet64); @@ -78,10 +78,10 @@ namespace PluralKit.Bot _metrics.Measure.Gauge.SetValue(CoreMetrics.ProcessThreads, process.Threads.Count); _metrics.Measure.Gauge.SetValue(CoreMetrics.ProcessHandles, process.HandleCount); _metrics.Measure.Gauge.SetValue(CoreMetrics.CpuUsage, await _cpu.EstimateCpuUsage()); - + // Database info _metrics.Measure.Gauge.SetValue(CoreMetrics.DatabaseConnections, _countHolder.ConnectionCount); - + // Other shiz _metrics.Measure.Gauge.SetValue(BotMetrics.WebhookCacheSize, _webhookCache.CacheSize); @@ -92,7 +92,7 @@ namespace PluralKit.Bot public class Counts { public int Systems { get; } - public int Members { get; } + public int Members { get; } public int Switches { get; } public int Messages { get; } public int Groups { get; } diff --git a/PluralKit.Bot/Services/ShardInfoService.cs b/PluralKit.Bot/Services/ShardInfoService.cs index 7ec05562..7e182581 100644 --- a/PluralKit.Bot/Services/ShardInfoService.cs +++ b/PluralKit.Bot/Services/ShardInfoService.cs @@ -38,7 +38,7 @@ namespace PluralKit.Bot private readonly IDatabase _db; private readonly ModelRepository _repo; - + public ShardInfoService(ILogger logger, Cluster client, IMetrics metrics, IDatabase db, ModelRepository repo) { _client = client; @@ -68,18 +68,19 @@ namespace PluralKit.Bot if (_shardInfo.TryGetValue(shard.ShardId, out var info)) { // Skip adding listeners if we've seen this shard & already added listeners to it - if (info.HasAttachedListeners) + if (info.HasAttachedListeners) return; - } else _shardInfo[shard.ShardId] = info = new ShardInfo(); - + } + else _shardInfo[shard.ShardId] = info = new ShardInfo(); + // Call our own SocketOpened listener manually (and then attach the listener properly) - + // Register listeners for new shards shard.Resumed += () => ReadyOrResumed(shard); shard.Ready += () => ReadyOrResumed(shard); shard.SocketClosed += (closeStatus, message) => SocketClosed(shard, closeStatus, message); shard.HeartbeatReceived += latency => Heartbeated(shard, latency); - + // Register that we've seen it info.HasAttachedListeners = true; } @@ -100,7 +101,7 @@ namespace PluralKit.Bot info.LastConnectionTime = SystemClock.Instance.GetCurrentInstant(); info.Connected = true; ReportShardStatus(); - + _ = ExecuteWithDatabase(async c => { await _repo.SetShardStatus(c, shard.ShardId, PKShardInfo.ShardStatus.Up); @@ -114,7 +115,7 @@ namespace PluralKit.Bot info.DisconnectionCount++; info.Connected = false; ReportShardStatus(); - + _ = ExecuteWithDatabase(c => _repo.SetShardStatus(c, shard.ShardId, PKShardInfo.ShardStatus.Down)); } @@ -125,7 +126,7 @@ namespace PluralKit.Bot info.LastHeartbeatTime = SystemClock.Instance.GetCurrentInstant(); info.Connected = true; info.ShardLatency = latency.ToDuration(); - + _ = ExecuteWithDatabase(c => _repo.RegisterShardHeartbeat(c, shard.ShardId, latency.ToDuration())); } diff --git a/PluralKit.Bot/Services/WebhookCacheService.cs b/PluralKit.Bot/Services/WebhookCacheService.cs index 7d0f6239..e8c26714 100644 --- a/PluralKit.Bot/Services/WebhookCacheService.cs +++ b/PluralKit.Bot/Services/WebhookCacheService.cs @@ -34,7 +34,7 @@ namespace PluralKit.Bot _logger = logger.ForContext(); _webhooks = new ConcurrentDictionary>>(); } - + public async Task GetWebhook(ulong channelId) { // We cache the webhook through a Lazy>, this way we make sure to only create one webhook per channel @@ -46,14 +46,14 @@ namespace PluralKit.Bot return _webhooks.GetOrAdd(channelId, new Lazy>(Factory)); } var lazyWebhookValue = GetWebhookTaskInner(); - + // If we've cached a failed Task, we need to clear it and try again // This is so errors don't become "sticky" and *they* in turn get cached (not good) // although, keep in mind this block gets hit the call *after* the task failed (since we only await it below) if (lazyWebhookValue.IsValueCreated && lazyWebhookValue.Value.IsFaulted) { _logger.Warning(lazyWebhookValue.Value.Exception, "Cached webhook task for {Channel} faulted with below exception", channelId); - + // Specifically don't recurse here so we don't infinite-loop - if this one errors too, it'll "stick" // until next time this function gets hit (which is okay, probably). _webhooks.TryRemove(channelId, out _); @@ -72,7 +72,7 @@ namespace PluralKit.Bot { // note: webhook.ChannelId may not be the same as channelId >.> _logger.Debug("Refreshing webhook for channel {Channel}", webhook.ChannelId); - + _webhooks.TryRemove(webhook.ChannelId, out _); return await GetWebhook(channelId); } @@ -81,7 +81,7 @@ namespace PluralKit.Bot { _logger.Debug("Webhook for channel {Channel} not found in cache, trying to fetch", channelId); _metrics.Measure.Meter.Mark(BotMetrics.WebhookCacheMisses); - + _logger.Debug("Finding webhook for channel {Channel}", channelId); var webhooks = await FetchChannelWebhooks(channelId); @@ -89,12 +89,12 @@ namespace PluralKit.Bot var ourWebhook = webhooks.FirstOrDefault(IsWebhookMine); if (ourWebhook != null) return ourWebhook; - + // We don't have one, so we gotta create a new one // but first, make sure we haven't hit the webhook cap yet... if (webhooks.Length >= 10) throw new PKError("This channel has the maximum amount of possible webhooks (10) already created. A server admin must delete one or more webhooks so PluralKit can create one for proxying."); - + return await DoCreateWebhook(channelId); } @@ -113,7 +113,7 @@ namespace PluralKit.Bot return new Webhook[0]; } } - + private async Task DoCreateWebhook(ulong channelId) { _logger.Information("Creating new webhook for channel {Channel}", channelId); diff --git a/PluralKit.Bot/Services/WebhookExecutorService.cs b/PluralKit.Bot/Services/WebhookExecutorService.cs index 1c2701e1..c19fc5f8 100644 --- a/PluralKit.Bot/Services/WebhookExecutorService.cs +++ b/PluralKit.Bot/Services/WebhookExecutorService.cs @@ -21,10 +21,12 @@ using Serilog; namespace PluralKit.Bot { - public class WebhookExecutionErrorOnDiscordsEnd: Exception { + public class WebhookExecutionErrorOnDiscordsEnd: Exception + { } - - public class WebhookRateLimited: WebhookExecutionErrorOnDiscordsEnd { + + public class WebhookRateLimited: WebhookExecutionErrorOnDiscordsEnd + { // Exceptions for control flow? don't mind if I do // TODO: rewrite both of these as a normal exceptional return value (0?) in case of error to be discarded by caller } @@ -41,7 +43,7 @@ namespace PluralKit.Bot public Embed[] Embeds { get; init; } public bool AllowEveryone { get; init; } } - + public class WebhookExecutorService { private readonly IDiscordCache _cache; @@ -64,31 +66,32 @@ namespace PluralKit.Bot public async Task ExecuteWebhook(ProxyRequest req) { _logger.Verbose("Invoking webhook in channel {Channel}", req.ChannelId); - + // Get a webhook, execute it var webhook = await _webhookCache.GetWebhook(req.ChannelId); var webhookMessage = await ExecuteWebhookInner(webhook, req); - + // Log the relevant metrics _metrics.Measure.Meter.Mark(BotMetrics.MessagesProxied); _logger.Information("Invoked webhook {Webhook} in channel {Channel} (thread {ThreadId})", webhook.Id, req.ChannelId, req.ThreadId); - + return webhookMessage; } public async Task EditWebhookMessage(ulong channelId, ulong messageId, string newContent) { var webhook = await _webhookCache.GetWebhook(channelId); - var allowedMentions = newContent.ParseMentions() with { + var allowedMentions = newContent.ParseMentions() with + { Roles = Array.Empty(), Parse = Array.Empty() }; return await _rest.EditWebhookMessage(webhook.Id, webhook.Token, messageId, - new WebhookMessageEditRequest {Content = newContent, AllowedMentions = allowedMentions}); + new WebhookMessageEditRequest { Content = newContent, AllowedMentions = allowedMentions }); } - + private async Task ExecuteWebhookInner(Webhook webhook, ProxyRequest req, bool hasRetried = false) { var guild = _cache.GetGuild(req.GuildId); @@ -96,7 +99,8 @@ namespace PluralKit.Bot var allowedMentions = content.ParseMentions(); if (!req.AllowEveryone) - allowedMentions = allowedMentions.RemoveUnmentionableRoles(guild) with { + allowedMentions = allowedMentions.RemoveUnmentionableRoles(guild) with + { // also clear @everyones Parse = Array.Empty() }; @@ -109,18 +113,19 @@ namespace PluralKit.Bot AvatarUrl = !string.IsNullOrWhiteSpace(req.AvatarUrl) ? req.AvatarUrl : null, Embeds = req.Embeds }; - + MultipartFile[] files = null; var attachmentChunks = ChunkAttachmentsOrThrow(req.Attachments, 8 * 1024 * 1024); if (attachmentChunks.Count > 0) { - _logger.Information("Invoking webhook with {AttachmentCount} attachments totalling {AttachmentSize} MiB in {AttachmentChunks} chunks", + _logger.Information("Invoking webhook with {AttachmentCount} attachments totalling {AttachmentSize} MiB in {AttachmentChunks} chunks", req.Attachments.Length, req.Attachments.Select(a => a.Size).Sum() / 1024 / 1024, attachmentChunks.Count); files = await GetAttachmentFiles(attachmentChunks[0]); } - + Message webhookMessage; - using (_metrics.Measure.Timer.Time(BotMetrics.WebhookResponseTime)) { + using (_metrics.Measure.Timer.Time(BotMetrics.WebhookResponseTime)) + { try { webhookMessage = await _rest.ExecuteWebhook(webhook.Id, webhook.Token, webhookReq, files, req.ThreadId); @@ -139,14 +144,14 @@ namespace PluralKit.Bot // but is still in our cache. Invalidate, refresh, try again _logger.Warning("Error invoking webhook {Webhook} in channel {Channel} (thread {ThreadId})", webhook.Id, webhook.ChannelId, req.ThreadId); - + var newWebhook = await _webhookCache.InvalidateAndRefreshWebhook(req.ChannelId, webhook); return await ExecuteWebhookInner(newWebhook, req, hasRetried: true); } throw; } - } + } // We don't care about whether the sending succeeds, and we don't want to *wait* for it, so we just fork it off var _ = TrySendRemainingAttachments(webhook, req.Name, req.AvatarUrl, attachmentChunks, req.ThreadId); @@ -161,11 +166,11 @@ namespace PluralKit.Bot for (var i = 1; i < attachmentChunks.Count; i++) { var files = await GetAttachmentFiles(attachmentChunks[i]); - var req = new ExecuteWebhookRequest {Username = name, AvatarUrl = avatarUrl}; + var req = new ExecuteWebhookRequest { Username = name, AvatarUrl = avatarUrl }; await _rest.ExecuteWebhook(webhook.Id, webhook.Token!, req, files, threadId); } } - + private async Task GetAttachmentFiles(IReadOnlyCollection attachments) { async Task GetStream(Message.Attachment attachment) @@ -184,7 +189,7 @@ namespace PluralKit.Bot // If any individual attachment is larger than 8MB, will throw an error var chunks = new List>(); var list = new List(); - + foreach (var attachment in attachments) { if (attachment.Size >= sizeThreshold) throw Errors.AttachmentTooLarge; @@ -194,7 +199,7 @@ namespace PluralKit.Bot chunks.Add(list); list = new List(); } - + list.Add(attachment); } @@ -212,7 +217,7 @@ namespace PluralKit.Bot // since Discord blocks webhooks containing the word "Clyde"... for some reason. /shrug return Regex.Replace(name, "(c)(lyde)", Replacement, RegexOptions.IgnoreCase); } - + private string FixSingleCharacterName(string proxyName) { if (proxyName.Length == 1) diff --git a/PluralKit.Bot/Tracing/DiscordRequestObserver.cs b/PluralKit.Bot/Tracing/DiscordRequestObserver.cs index 7a50156b..444d8162 100644 --- a/PluralKit.Bot/Tracing/DiscordRequestObserver.cs +++ b/PluralKit.Bot/Tracing/DiscordRequestObserver.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -30,7 +30,7 @@ namespace PluralKit.Bot _metrics = metrics; _logger = logger.ForContext(); } - + public void OnCompleted() { } public void OnError(Exception error) { } @@ -53,10 +53,10 @@ namespace PluralKit.Bot url = Regex.Replace(url, @"/reactions/[^{/]+/\d+", "/reactions/{emoji}/{user_id}"); url = Regex.Replace(url, @"/reactions/[^{/]+", "/reactions/{emoji}"); url = Regex.Replace(url, @"/invites/[^{/]+", "/invites/{invite_code}"); - + // catch-all for missed IDs url = Regex.Replace(url, @"\d{17,19}", "{snowflake}"); - + return url; } @@ -66,7 +66,7 @@ namespace PluralKit.Bot var routePath = NormalizeRoutePath(localPath); return $"{req.Method} {routePath}"; } - + private void HandleException(Exception exc, HttpRequestMessage req) { _logger @@ -109,8 +109,8 @@ namespace PluralKit.Bot if (IsDiscordApiRequest(response)) { var timer = _metrics.Provider.Timer.Instance(BotMetrics.DiscordApiRequests, new MetricTags( - new[] {"endpoint", "status_code"}, - new[] {endpoint, ((int) response.StatusCode).ToString()} + new[] { "endpoint", "status_code" }, + new[] { endpoint, ((int)response.StatusCode).ToString() } )); timer.Record(activity.Duration.Ticks / 10, TimeUnit.Microseconds); } @@ -131,19 +131,19 @@ namespace PluralKit.Bot switch (value.Key) { case "System.Net.Http.HttpRequestOut.Stop": - { - var data = Unsafe.As(value.Value); - if (data.Response != null) - HandleResponse(data.Response, Activity.Current); + { + var data = Unsafe.As(value.Value); + if (data.Response != null) + HandleResponse(data.Response, Activity.Current); - break; - } + break; + } case "System.Net.Http.Exception": - { - var data = Unsafe.As(value.Value); - HandleException(data.Exception, data.Request); - break; - } + { + var data = Unsafe.As(value.Value); + HandleException(data.Exception, data.Request); + break; + } } } @@ -151,7 +151,7 @@ namespace PluralKit.Bot { DiagnosticListener.AllListeners.Subscribe(new ListenerObserver(services)); } - + #pragma warning disable 649 private class ActivityStopData { @@ -160,7 +160,7 @@ namespace PluralKit.Bot public HttpRequestMessage Request; public TaskStatus RequestTaskStatus; } - + private class ExceptionData { // Field order here matters! diff --git a/PluralKit.Bot/Utils/AvatarUtils.cs b/PluralKit.Bot/Utils/AvatarUtils.cs index f644a6b2..c6262b30 100644 --- a/PluralKit.Bot/Utils/AvatarUtils.cs +++ b/PluralKit.Bot/Utils/AvatarUtils.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Net.Http; using System.Text.RegularExpressions; @@ -8,11 +8,13 @@ using PluralKit.Core; using SixLabors.ImageSharp; -namespace PluralKit.Bot { - public static class AvatarUtils { +namespace PluralKit.Bot +{ + public static class AvatarUtils + { public static async Task VerifyAvatarOrThrow(HttpClient client, string url, bool isFullSizeImage = false) { - if (url.Length > Limits.MaxUriLength) + if (url.Length > Limits.MaxUriLength) throw Errors.UrlTooLong(url); // List of MIME types we consider acceptable diff --git a/PluralKit.Bot/Utils/ContextUtils.cs b/PluralKit.Bot/Utils/ContextUtils.cs index 9f4d1cef..9f11dee1 100644 --- a/PluralKit.Bot/Utils/ContextUtils.cs +++ b/PluralKit.Bot/Utils/ContextUtils.cs @@ -17,8 +17,10 @@ using NodaTime; using PluralKit.Bot.Interactive; using PluralKit.Core; -namespace PluralKit.Bot { - public static class ContextUtils { +namespace PluralKit.Bot +{ + public static class ContextUtils + { public static async Task ConfirmClear(this Context ctx, string toClear) { if (!(await ctx.PromptYesNo($"{Emojis.Warn} Are you sure you want to clear {toClear}?", "Clear"))) throw Errors.GenericCancelled(); @@ -50,7 +52,7 @@ namespace PluralKit.Bot { if (predicate != null && !predicate.Invoke(evt)) return false; // Check predicate return true; } - + return await ctx.Services.Resolve>().WaitFor(ReactionPredicate, timeout); } @@ -58,20 +60,21 @@ namespace PluralKit.Bot { { bool Predicate(MessageCreateEvent e) => e.Author.Id == ctx.Author.Id && e.ChannelId == ctx.Channel.Id; - + var msg = await ctx.Services.Resolve>() .WaitFor(Predicate, Duration.FromMinutes(1)); - + return string.Equals(msg.Content, expectedReply, StringComparison.InvariantCultureIgnoreCase); } - public static async Task Paginate(this Context ctx, IAsyncEnumerable items, int totalCount, int itemsPerPage, string title, string color, Func, Task> renderer) { + public static async Task Paginate(this Context ctx, IAsyncEnumerable items, int totalCount, int itemsPerPage, string title, string color, Func, Task> renderer) + { // TODO: make this generic enough we can use it in Choose below var buffer = new List(); await using var enumerator = items.GetAsyncEnumerator(); - var pageCount = (int) Math.Ceiling(totalCount / (double) itemsPerPage); + var pageCount = (int)Math.Ceiling(totalCount / (double)itemsPerPage); async Task MakeEmbedForPage(int page) { var bufferedItemsNeeded = (page + 1) * itemsPerPage; @@ -79,10 +82,10 @@ namespace PluralKit.Bot { buffer.Add(enumerator.Current); var eb = new EmbedBuilder(); - eb.Title(pageCount > 1 ? $"[{page+1}/{pageCount}] {title}" : title); + eb.Title(pageCount > 1 ? $"[{page + 1}/{pageCount}] {title}" : title); if (color != null) eb.Color(color.ToDiscordColor()); - await renderer(eb, buffer.Skip(page*itemsPerPage).Take(itemsPerPage)); + await renderer(eb, buffer.Skip(page * itemsPerPage).Take(itemsPerPage)); return eb.Build(); } @@ -94,9 +97,11 @@ namespace PluralKit.Bot { var _ = ctx.Rest.CreateReactionsBulk(msg, botEmojis); // Again, "fork" - try { + try + { var currentPage = 0; - while (true) { + while (true) + { var reaction = await ctx.AwaitReaction(msg, ctx.Author, timeout: Duration.FromMinutes(5)); // Increment/decrement page counter based on which reaction was clicked @@ -105,19 +110,21 @@ namespace PluralKit.Bot { if (reaction.Emoji.Name == "\u27A1") currentPage = (currentPage + 1) % pageCount; // > if (reaction.Emoji.Name == "\u23E9") currentPage = pageCount - 1; // >> if (reaction.Emoji.Name == Emojis.Error) break; // X - + // C#'s % operator is dumb and wrong, so we fix negative numbers if (currentPage < 0) currentPage += pageCount; - + // If we can, remove the user's reaction (so they can press again quickly) if (ctx.BotPermissions.HasFlag(PermissionSet.ManageMessages)) await ctx.Rest.DeleteUserReaction(msg.ChannelId, msg.Id, reaction.Emoji, reaction.UserId); - + // Edit the embed with the new page var embed = await MakeEmbedForPage(currentPage); - await ctx.Rest.EditMessage(msg.ChannelId, msg.Id, new MessageEditRequest {Embed = embed}); + await ctx.Rest.EditMessage(msg.ChannelId, msg.Id, new MessageEditRequest { Embed = embed }); } - } catch (TimeoutException) { + } + catch (TimeoutException) + { // "escape hatch", clean up as if we hit X } @@ -132,7 +139,7 @@ namespace PluralKit.Bot { // either way, nothing to do here catch (ForbiddenException) { } } - + public static async Task Choose(this Context ctx, string description, IList items, Func display = null) { // Generate a list of :regional_indicator_?: emoji surrogate pairs (starting at codepoint 0x1F1E6) @@ -157,27 +164,27 @@ namespace PluralKit.Bot { if (items.Count > pageSize) { var currPage = 0; - var pageCount = (items.Count-1) / pageSize + 1; - + var pageCount = (items.Count - 1) / pageSize + 1; + // Send the original message var msg = await ctx.Reply($"**[Page {currPage + 1}/{pageCount}]**\n{description}\n{MakeOptionList(currPage)}"); - + // Add back/forward reactions and the actual indicator emojis async Task AddEmojis() { await ctx.Rest.CreateReaction(msg.ChannelId, msg.Id, new() { Name = "\u2B05" }); await ctx.Rest.CreateReaction(msg.ChannelId, msg.Id, new() { Name = "\u27A1" }); - for (int i = 0; i < items.Count; i++) + for (int i = 0; i < items.Count; i++) await ctx.Rest.CreateReaction(msg.ChannelId, msg.Id, new() { Name = indicators[i] }); } var _ = AddEmojis(); // Not concerned about awaiting - + while (true) { // Wait for a reaction var reaction = await ctx.AwaitReaction(msg, ctx.Author); - + // If it's a movement reaction, inc/dec the page index if (reaction.Emoji.Name == "\u2B05") currPage -= 1; // < if (reaction.Emoji.Name == "\u27A1") currPage += 1; // > @@ -210,17 +217,17 @@ namespace PluralKit.Bot { async Task AddEmojis() { for (int i = 0; i < items.Count; i++) - await ctx.Rest.CreateReaction(msg.ChannelId, msg.Id, new() {Name = indicators[i]}); + await ctx.Rest.CreateReaction(msg.ChannelId, msg.Id, new() { Name = indicators[i] }); } var _ = AddEmojis(); // Then wait for a reaction and return whichever one we found - var reaction = await ctx.AwaitReaction(msg, ctx.Author,rx => indicators.Contains(rx.Emoji.Name)); + var reaction = await ctx.AwaitReaction(msg, ctx.Author, rx => indicators.Contains(rx.Emoji.Name)); return items[Array.IndexOf(indicators, reaction.Emoji.Name)]; } } - + public static async Task BusyIndicator(this Context ctx, Func f, string emoji = "\u23f3" /* hourglass */) { await ctx.BusyIndicator(async () => @@ -239,13 +246,13 @@ namespace PluralKit.Bot { try { - await Task.WhenAll(ctx.Rest.CreateReaction(ctx.Message.ChannelId, ctx.Message.Id, new() {Name = emoji}), task); + await Task.WhenAll(ctx.Rest.CreateReaction(ctx.Message.ChannelId, ctx.Message.Id, new() { Name = emoji }), task); return await task; } finally { var _ = ctx.Rest.DeleteOwnReaction(ctx.Message.ChannelId, ctx.Message.Id, new() { Name = emoji }); } - } + } } } \ No newline at end of file diff --git a/PluralKit.Bot/Utils/DiscordUtils.cs b/PluralKit.Bot/Utils/DiscordUtils.cs index 80686ea1..aa87e0a0 100644 --- a/PluralKit.Bot/Utils/DiscordUtils.cs +++ b/PluralKit.Bot/Utils/DiscordUtils.cs @@ -24,7 +24,7 @@ namespace PluralKit.Bot public const uint Green = 0x00cc78; public const uint Red = 0xef4b3d; public const uint Gray = 0x979c9f; - + private static readonly Regex USER_MENTION = new Regex("<@!?(\\d{17,19})>"); private static readonly Regex ROLE_MENTION = new Regex("<@&(\\d{17,19})>"); private static readonly Regex EVERYONE_HERE_MENTION = new Regex("@(everyone|here)"); @@ -36,23 +36,23 @@ namespace PluralKit.Bot // corresponding to: https://github.com/Khan/simple-markdown/blob/master/src/index.js#L1489 // I added ? at the start/end; they need to be handled specially later... private static readonly Regex UNBROKEN_LINK_REGEX = new Regex("?"); - + public static string NameAndMention(this User user) { return $"{user.Username}#{user.Discriminator} ({user.Mention()})"; } - + public static Instant SnowflakeToInstant(ulong snowflake) => Instant.FromUtc(2015, 1, 1, 0, 0, 0) + Duration.FromMilliseconds(snowflake >> 22); public static ulong InstantToSnowflake(Instant time) => - (ulong) (time - Instant.FromUtc(2015, 1, 1, 0, 0, 0)).TotalMilliseconds << 22; - + (ulong)(time - Instant.FromUtc(2015, 1, 1, 0, 0, 0)).TotalMilliseconds << 22; + public static async Task CreateReactionsBulk(this DiscordApiClient rest, Message msg, string[] reactions) { foreach (var reaction in reactions) { - await rest.CreateReaction(msg.ChannelId, msg.Id, new() {Name = reaction}); + await rest.CreateReaction(msg.ChannelId, msg.Id, new() { Name = reaction }); } } @@ -97,22 +97,23 @@ namespace PluralKit.Bot var users = USER_MENTION.Matches(input).Select(x => ulong.Parse(x.Groups[1].Value)); var roles = ROLE_MENTION.Matches(input).Select(x => ulong.Parse(x.Groups[1].Value)); var everyone = EVERYONE_HERE_MENTION.IsMatch(input); - + return new AllowedMentions { Users = users.Distinct().ToArray(), Roles = roles.Distinct().ToArray(), - Parse = everyone ? new[] {AllowedMentions.ParseType.Everyone} : null + Parse = everyone ? new[] { AllowedMentions.ParseType.Everyone } : null }; } public static AllowedMentions RemoveUnmentionableRoles(this AllowedMentions mentions, Guild guild) { - return mentions with { + return mentions with + { Roles = mentions.Roles ?.Where(id => guild.Roles.FirstOrDefault(r => r.Id == id)?.Mentionable == true) .ToArray() - }; + }; } public static string EscapeMarkdown(this string input) @@ -146,7 +147,7 @@ namespace PluralKit.Bot // So, surrounding with two backticks, then escaping all backtick pairs makes it impossible(!) to "break out" return $"``{EscapeBacktickPair(input)}``"; } - + public static EmbedBuilder WithSimpleLineContent(this EmbedBuilder eb, IEnumerable lines) { static int CharacterLimit(int pageNumber) => @@ -178,7 +179,7 @@ namespace PluralKit.Bot return $"<{match.Value}>"; }); - public static string EventType(this IGatewayEvent evt) => + public static string EventType(this IGatewayEvent evt) => evt.GetType().Name.Replace("Event", ""); public static bool HasReactionPermissions(Context ctx) @@ -188,11 +189,11 @@ namespace PluralKit.Bot } public static bool IsValidGuildChannel(Channel channel) => - channel.Type is + channel.Type is Channel.ChannelType.GuildText or - Channel.ChannelType.GuildNews or + Channel.ChannelType.GuildNews or Channel.ChannelType.GuildPublicThread or Channel.ChannelType.GuildPrivateThread or Channel.ChannelType.GuildNewsThread; } -} +} \ No newline at end of file diff --git a/PluralKit.Bot/Utils/InteractionContext.cs b/PluralKit.Bot/Utils/InteractionContext.cs index 0a87407c..01944c82 100644 --- a/PluralKit.Bot/Utils/InteractionContext.cs +++ b/PluralKit.Bot/Utils/InteractionContext.cs @@ -13,7 +13,7 @@ namespace PluralKit.Bot { private readonly InteractionCreateEvent _evt; private readonly ILifetimeScope _services; - + public InteractionContext(InteractionCreateEvent evt, ILifetimeScope services) { _evt = evt; @@ -55,9 +55,9 @@ namespace PluralKit.Bot } public async Task Respond(InteractionResponse.ResponseType type, InteractionApplicationCommandCallbackData? data) - { + { var rest = _services.Resolve(); - await rest.CreateInteractionResponse(_evt.Id, _evt.Token, new InteractionResponse {Type = type, Data = data}); + await rest.CreateInteractionResponse(_evt.Id, _evt.Token, new InteractionResponse { Type = type, Data = data }); } } } \ No newline at end of file diff --git a/PluralKit.Bot/Utils/MentionUtils.cs b/PluralKit.Bot/Utils/MentionUtils.cs index 73444ce0..3ecb3c79 100644 --- a/PluralKit.Bot/Utils/MentionUtils.cs +++ b/PluralKit.Bot/Utils/MentionUtils.cs @@ -5,7 +5,7 @@ namespace PluralKit.Bot.Utils { // PK note: class is wholesale copied from Discord.NET (MIT-licensed) // https://github.com/discord-net/Discord.Net/blob/ff0fea98a65d907fbce07856f1a9ef4aebb9108b/src/Discord.Net.Core/Utils/MentionUtils.cs - + /// /// Provides a series of helper methods for parsing mentions. /// diff --git a/PluralKit.Bot/Utils/MiscUtils.cs b/PluralKit.Bot/Utils/MiscUtils.cs index 377e22bf..11e1a2f0 100644 --- a/PluralKit.Bot/Utils/MiscUtils.cs +++ b/PluralKit.Bot/Utils/MiscUtils.cs @@ -16,10 +16,11 @@ using Polly.Timeout; namespace PluralKit.Bot { - public static class MiscUtils { - public static string ProxyTagsString(this PKMember member, string separator = ", ") => + public static class MiscUtils + { + public static string ProxyTagsString(this PKMember member, string separator = ", ") => string.Join(separator, member.ProxyTags.Select(t => t.ProxyString.AsCode())); - + public static bool IsOurProblem(this Exception e) { // This function filters out sporadic errors out of our control from being reported to Sentry @@ -40,24 +41,24 @@ namespace PluralKit.Bot if (e is TimeoutRejectedException) return false; // 5xxs? also not our problem :^) - if (e is UnknownDiscordRequestException udre && (int) udre.StatusCode >= 500) return false; + if (e is UnknownDiscordRequestException udre && (int)udre.StatusCode >= 500) return false; // Webhook server errors are also *not our problem* // (this includes rate limit errors, WebhookRateLimited is a subclass) if (e is WebhookExecutionErrorOnDiscordsEnd) return false; - + // Socket errors are *not our problem* if (e.GetBaseException() is SocketException) return false; - + // Tasks being cancelled for whatver reason are, you guessed it, also not our problem. if (e is TaskCanceledException) return false; // Sometimes Discord just times everything out. if (e is TimeoutException) return false; - + // Ignore "Database is shutting down" error if (e is PostgresException pe && pe.SqlState == "57P03") return false; - + // Ignore thread pool exhaustion errors if (e is NpgsqlException npe && npe.Message.Contains("The connection pool has been exhausted")) return false; diff --git a/PluralKit.Bot/Utils/ModelUtils.cs b/PluralKit.Bot/Utils/ModelUtils.cs index 2429c09d..4771aab0 100644 --- a/PluralKit.Bot/Utils/ModelUtils.cs +++ b/PluralKit.Bot/Utils/ModelUtils.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using System.Text.RegularExpressions; using PluralKit.Core; @@ -24,21 +24,21 @@ namespace PluralKit.Bot bool IsSimple(string s) => // No spaces, no symbols, allow single quote but not at the start Regex.IsMatch(s, "^[\\w\\d\\-_'?]+$") && !s.StartsWith("'"); - + // If it's very long (>25 chars), always use hid if (name.Length >= 25) return hid; // If name is "simple" just use that - if (IsSimple(name)) + if (IsSimple(name)) return name; - + // If three or fewer "words" and they're all simple individually, quote them var words = name.Split(' '); if (words.Length <= 3 && words.All(w => w.Length > 0 && IsSimple(w))) // Words with double quotes are never "simple" so we're safe to naive-quote here return $"\"{name}\""; - + // Otherwise, just use hid return hid; } diff --git a/PluralKit.Bot/Utils/SentryUtils.cs b/PluralKit.Bot/Utils/SentryUtils.cs index a43bfad9..ac63b3d7 100644 --- a/PluralKit.Bot/Utils/SentryUtils.cs +++ b/PluralKit.Bot/Utils/SentryUtils.cs @@ -7,7 +7,7 @@ using Sentry; namespace PluralKit.Bot { - public interface ISentryEnricher where T: IGatewayEvent + public interface ISentryEnricher where T : IGatewayEvent { void Enrich(Scope scope, Shard shard, T evt); } @@ -25,10 +25,10 @@ namespace PluralKit.Bot { _bot = bot; } - + // TODO: should this class take the Scope by dependency injection instead? // Would allow us to create a centralized "chain of handlers" where this class could just be registered as an entry in - + public void Enrich(Scope scope, Shard shard, MessageCreateEvent evt) { scope.AddBreadcrumb(evt.Content, "event.message", data: new Dictionary diff --git a/PluralKit.Bot/Utils/SerilogGatewayEnricherFactory.cs b/PluralKit.Bot/Utils/SerilogGatewayEnricherFactory.cs index 9409ba3e..a3845505 100644 --- a/PluralKit.Bot/Utils/SerilogGatewayEnricherFactory.cs +++ b/PluralKit.Bot/Utils/SerilogGatewayEnricherFactory.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Myriad.Cache; using Myriad.Extensions; @@ -26,14 +26,14 @@ namespace PluralKit.Bot { new("ShardId", new ScalarValue(shard.ShardId)), }; - + var (guild, channel) = GetGuildChannelId(evt); var user = GetUserId(evt); var message = GetMessageId(evt); - + if (guild != null) props.Add(new("GuildId", new ScalarValue(guild.Value))); - + if (channel != null) { props.Add(new("ChannelId", new ScalarValue(channel.Value))); @@ -44,10 +44,10 @@ namespace PluralKit.Bot props.Add(new("BotPermissions", new ScalarValue(botPermissions))); } } - + if (message != null) props.Add(new("MessageId", new ScalarValue(message.Value))); - + if (user != null) props.Add(new("UserId", new ScalarValue(user.Value))); @@ -56,7 +56,7 @@ namespace PluralKit.Bot return new Inner(props); } - + private (ulong?, ulong?) GetGuildChannelId(IGatewayEvent evt) => evt switch { ChannelCreateEvent e => (e.GuildId, e.Id), @@ -101,7 +101,7 @@ namespace PluralKit.Bot { public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) { - foreach (var prop in Properties) + foreach (var prop in Properties) logEvent.AddPropertyIfAbsent(prop); } } diff --git a/PluralKit.Core/CoreMetrics.cs b/PluralKit.Core/CoreMetrics.cs index c4a56a24..773b0c1f 100644 --- a/PluralKit.Core/CoreMetrics.cs +++ b/PluralKit.Core/CoreMetrics.cs @@ -12,15 +12,15 @@ namespace PluralKit.Core public static GaugeOptions MessageCount => new GaugeOptions { Name = "Messages", MeasurementUnit = Unit.Items }; public static GaugeOptions SwitchCount => new GaugeOptions { Name = "Switches", MeasurementUnit = Unit.Items }; public static GaugeOptions GroupCount => new GaugeOptions { Name = "Groups", MeasurementUnit = Unit.Items }; - + public static GaugeOptions ProcessPhysicalMemory => new GaugeOptions { Name = "Process Physical Memory", MeasurementUnit = Unit.Bytes, Context = "Process" }; public static GaugeOptions ProcessVirtualMemory => new GaugeOptions { Name = "Process Virtual Memory", MeasurementUnit = Unit.Bytes, Context = "Process" }; public static GaugeOptions ProcessPrivateMemory => new GaugeOptions { Name = "Process Private Memory", MeasurementUnit = Unit.Bytes, Context = "Process" }; public static GaugeOptions ProcessThreads => new GaugeOptions { Name = "Process Thread Count", MeasurementUnit = Unit.Threads, Context = "Process" }; public static GaugeOptions ProcessHandles => new GaugeOptions { Name = "Process Handle Count", MeasurementUnit = Unit.Items, Context = "Process" }; public static GaugeOptions CpuUsage => new GaugeOptions { Name = "CPU Usage", MeasurementUnit = Unit.Percent, Context = "Process" }; - - public static MeterOptions DatabaseRequests => new MeterOptions() { Name = "Database Requests", MeasurementUnit = Unit.Requests, Context = "Database", RateUnit = TimeUnit.Seconds}; + + public static MeterOptions DatabaseRequests => new MeterOptions() { Name = "Database Requests", MeasurementUnit = Unit.Requests, Context = "Database", RateUnit = TimeUnit.Seconds }; public static TimerOptions DatabaseQuery => new TimerOptions() { Name = "Database Query", MeasurementUnit = Unit.Requests, DurationUnit = TimeUnit.Seconds, RateUnit = TimeUnit.Seconds, Context = "Database" }; public static GaugeOptions DatabaseConnections => new GaugeOptions() { Name = "Database Connections", MeasurementUnit = Unit.Connections, Context = "Database" }; } diff --git a/PluralKit.Core/Database/Database.cs b/PluralKit.Core/Database/Database.cs index ca96a500..2ede1507 100644 --- a/PluralKit.Core/Database/Database.cs +++ b/PluralKit.Core/Database/Database.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Data; using System.IO; @@ -18,7 +18,7 @@ namespace PluralKit.Core { internal class Database: IDatabase { - + private readonly CoreConfig _config; private readonly ILogger _logger; private readonly IMetrics _metrics; @@ -34,20 +34,22 @@ namespace PluralKit.Core _metrics = metrics; _migrator = migrator; _logger = logger.ForContext(); - + _connectionString = new NpgsqlConnectionStringBuilder(_config.Database) { - Pooling = true, Enlist = false, NoResetOnClose = true, - + Pooling = true, + Enlist = false, + NoResetOnClose = true, + // Lower timeout than default (15s -> 2s), should ideally fail-fast instead of hanging Timeout = 2 }.ConnectionString; } - + public static void InitStatic() { DefaultTypeMap.MatchNamesWithUnderscores = true; - + // Dapper by default tries to pass ulongs to Npgsql, which rejects them since PostgreSQL technically // doesn't support unsigned types on its own. // Instead we add a custom mapper to encode them as signed integers instead, converting them back and forth. @@ -61,7 +63,7 @@ namespace PluralKit.Core // So we add a custom type handler that literally just passes the type through to Npgsql SqlMapper.AddTypeHandler(new PassthroughTypeHandler()); SqlMapper.AddTypeHandler(new PassthroughTypeHandler()); - + // Add ID types to Dapper SqlMapper.AddTypeHandler(new NumericIdHandler(i => new SystemId(i))); SqlMapper.AddTypeHandler(new NumericIdHandler(i => new MemberId(i))); @@ -71,25 +73,25 @@ namespace PluralKit.Core SqlMapper.AddTypeHandler(new NumericIdArrayHandler(i => new MemberId(i))); SqlMapper.AddTypeHandler(new NumericIdArrayHandler(i => new SwitchId(i))); SqlMapper.AddTypeHandler(new NumericIdArrayHandler(i => new GroupId(i))); - + // Register our custom types to Npgsql // Without these it'll still *work* but break at the first launch + probably cause other small issues NpgsqlConnection.GlobalTypeMapper.MapComposite("proxy_tag"); NpgsqlConnection.GlobalTypeMapper.MapEnum("privacy_level"); } - + public async Task Obtain() { // Mark the request (for a handle, I guess) in the metrics _metrics.Measure.Meter.Mark(CoreMetrics.DatabaseRequests); - + // Create a connection and open it // We wrap it in PKConnection for tracing purposes var conn = new PKConnection(new NpgsqlConnection(_connectionString), _countHolder, _logger, _metrics); await conn.OpenAsync(); return conn; } - + public async Task ApplyMigrations() { using var conn = await Obtain(); @@ -99,28 +101,28 @@ namespace PluralKit.Core private class PassthroughTypeHandler: SqlMapper.TypeHandler { public override void SetValue(IDbDataParameter parameter, T value) => parameter.Value = value; - public override T Parse(object value) => (T) value; + public override T Parse(object value) => (T)value; } private class UlongEncodeAsLongHandler: SqlMapper.TypeHandler { public override ulong Parse(object value) => // Cast to long to unbox, then to ulong (???) - (ulong) (long) value; + (ulong)(long)value; - public override void SetValue(IDbDataParameter parameter, ulong value) => parameter.Value = (long) value; + public override void SetValue(IDbDataParameter parameter, ulong value) => parameter.Value = (long)value; } private class UlongArrayHandler: SqlMapper.TypeHandler { - public override void SetValue(IDbDataParameter parameter, ulong[] value) => parameter.Value = Array.ConvertAll(value, i => (long) i); + public override void SetValue(IDbDataParameter parameter, ulong[] value) => parameter.Value = Array.ConvertAll(value, i => (long)i); - public override ulong[] Parse(object value) => Array.ConvertAll((long[]) value, i => (ulong) i); + public override ulong[] Parse(object value) => Array.ConvertAll((long[])value, i => (ulong)i); } private class NumericIdHandler: SqlMapper.TypeHandler - where T: INumericId - where TInner: IEquatable, IComparable + where T : INumericId + where TInner : IEquatable, IComparable { private readonly Func _factory; @@ -131,12 +133,12 @@ namespace PluralKit.Core public override void SetValue(IDbDataParameter parameter, T value) => parameter.Value = value.Value; - public override T Parse(object value) => _factory((TInner) value); + public override T Parse(object value) => _factory((TInner)value); } private class NumericIdArrayHandler: SqlMapper.TypeHandler - where T: INumericId - where TInner: IEquatable, IComparable + where T : INumericId + where TInner : IEquatable, IComparable { private readonly Func _factory; @@ -147,10 +149,10 @@ namespace PluralKit.Core public override void SetValue(IDbDataParameter parameter, T[] value) => parameter.Value = Array.ConvertAll(value, v => v.Value); - public override T[] Parse(object value) => Array.ConvertAll((TInner[]) value, v => _factory(v)); + public override T[] Parse(object value) => Array.ConvertAll((TInner[])value, v => _factory(v)); } } - + public static class DatabaseExt { public static async Task Execute(this IDatabase db, Func func) @@ -164,11 +166,11 @@ namespace PluralKit.Core await using var conn = await db.Obtain(); return await func(conn); } - + public static async IAsyncEnumerable Execute(this IDatabase db, Func> func) { await using var conn = await db.Obtain(); - + await foreach (var val in func(conn)) yield return val; } diff --git a/PluralKit.Core/Database/Functions/MessageContext.cs b/PluralKit.Core/Database/Functions/MessageContext.cs index aee600c7..299e90c2 100644 --- a/PluralKit.Core/Database/Functions/MessageContext.cs +++ b/PluralKit.Core/Database/Functions/MessageContext.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using NodaTime; diff --git a/PluralKit.Core/Database/Functions/ProxyMember.cs b/PluralKit.Core/Database/Functions/ProxyMember.cs index 072821cd..6e5fa6c8 100644 --- a/PluralKit.Core/Database/Functions/ProxyMember.cs +++ b/PluralKit.Core/Database/Functions/ProxyMember.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System.Collections.Generic; namespace PluralKit.Core @@ -11,11 +11,11 @@ namespace PluralKit.Core public MemberId Id { get; } public IReadOnlyCollection ProxyTags { get; } = new ProxyTag[0]; public bool KeepProxy { get; } - + public string? ServerName { get; } public string? DisplayName { get; } public string Name { get; } = ""; - + public string? ServerAvatar { get; } public string? Avatar { get; } diff --git a/PluralKit.Core/Database/IDatabase.cs b/PluralKit.Core/Database/IDatabase.cs index 2ac84d5e..f6789e94 100644 --- a/PluralKit.Core/Database/IDatabase.cs +++ b/PluralKit.Core/Database/IDatabase.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; namespace PluralKit.Core { diff --git a/PluralKit.Core/Database/Repository/ModelRepository.CommandMessage.cs b/PluralKit.Core/Database/Repository/ModelRepository.CommandMessage.cs index 69222313..17743169 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.CommandMessage.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.CommandMessage.cs @@ -5,23 +5,23 @@ using Dapper; namespace PluralKit.Core { public partial class ModelRepository - { - public Task SaveCommandMessage(IPKConnection conn, ulong messageId, ulong authorId) => - conn.QueryAsync("insert into command_messages (message_id, author_id) values (@Message, @Author)", - new {Message = messageId, Author = authorId }); + { + public Task SaveCommandMessage(IPKConnection conn, ulong messageId, ulong authorId) => + conn.QueryAsync("insert into command_messages (message_id, author_id) values (@Message, @Author)", + new { Message = messageId, Author = authorId }); - public Task GetCommandMessage(IPKConnection conn, ulong messageId) => - conn.QuerySingleOrDefaultAsync("select * from command_messages where message_id = @Message", - new {Message = messageId}); + public Task GetCommandMessage(IPKConnection conn, ulong messageId) => + conn.QuerySingleOrDefaultAsync("select * from command_messages where message_id = @Message", + new { Message = messageId }); public Task DeleteCommandMessagesBefore(IPKConnection conn, ulong messageIdThreshold) => conn.ExecuteAsync("delete from command_messages where message_id < @Threshold", - new {Threshold = messageIdThreshold}); + new { Threshold = messageIdThreshold }); } - public class CommandMessage - { + public class CommandMessage + { public ulong AuthorId { get; set; } public ulong MessageId { get; set; } - } + } } \ No newline at end of file diff --git a/PluralKit.Core/Database/Repository/ModelRepository.Context.cs b/PluralKit.Core/Database/Repository/ModelRepository.Context.cs index 0a4d330a..589ce2ec 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.Context.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.Context.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Data; using System.Threading.Tasks; @@ -10,16 +10,16 @@ namespace PluralKit.Core { public Task GetMessageContext(IPKConnection conn, ulong account, ulong guild, ulong channel) { - return conn.QueryFirstAsync("message_context", - new { account_id = account, guild_id = guild, channel_id = channel }, + return conn.QueryFirstAsync("message_context", + new { account_id = account, guild_id = guild, channel_id = channel }, commandType: CommandType.StoredProcedure); - } - + } + public Task> GetProxyMembers(IPKConnection conn, ulong account, ulong guild) { - return conn.QueryAsync("proxy_members", - new { account_id = account, guild_id = guild }, + return conn.QueryAsync("proxy_members", + new { account_id = account, guild_id = guild }, commandType: CommandType.StoredProcedure); - } + } } } \ No newline at end of file diff --git a/PluralKit.Core/Database/Repository/ModelRepository.Group.cs b/PluralKit.Core/Database/Repository/ModelRepository.Group.cs index 2afb43eb..24d1e17c 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.Group.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.Group.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System.Collections.Generic; using System.Data; using System.Linq; @@ -12,14 +12,14 @@ namespace PluralKit.Core public partial class ModelRepository { public Task GetGroupByName(IPKConnection conn, SystemId system, string name) => - conn.QueryFirstOrDefaultAsync("select * from groups where system = @System and lower(Name) = lower(@Name)", new {System = system, Name = name}); - + conn.QueryFirstOrDefaultAsync("select * from groups where system = @System and lower(Name) = lower(@Name)", new { System = system, Name = name }); + public Task GetGroupByDisplayName(IPKConnection conn, SystemId system, string display_name) => - conn.QueryFirstOrDefaultAsync("select * from groups where system = @System and lower(display_name) = lower(@Name)", new {System = system, Name = display_name}); - + conn.QueryFirstOrDefaultAsync("select * from groups where system = @System and lower(display_name) = lower(@Name)", new { System = system, Name = display_name }); + public Task GetGroupByHid(IPKConnection conn, string hid) => - conn.QueryFirstOrDefaultAsync("select * from groups where hid = @hid", new {hid = hid.ToLowerInvariant()}); - + conn.QueryFirstOrDefaultAsync("select * from groups where hid = @hid", new { hid = hid.ToLowerInvariant() }); + public Task GetGroupMemberCount(IPKConnection conn, GroupId id, PrivacyLevel? privacyFilter = null) { var query = new StringBuilder("select count(*) from group_members"); @@ -28,14 +28,14 @@ namespace PluralKit.Core query.Append(" where group_members.group_id = @Id"); if (privacyFilter != null) query.Append(" and members.member_visibility = @PrivacyFilter"); - return conn.QuerySingleOrDefaultAsync(query.ToString(), new {Id = id, PrivacyFilter = privacyFilter}); + return conn.QuerySingleOrDefaultAsync(query.ToString(), new { Id = id, PrivacyFilter = privacyFilter }); } - + public async Task CreateGroup(IPKConnection conn, SystemId system, string name, IDbTransaction? transaction = null) { var group = await conn.QueryFirstAsync( "insert into groups (hid, system, name) values (find_free_group_hid(), @System, @Name) returning *", - new {System = system, Name = name}, transaction); + new { System = system, Name = name }, transaction); _logger.Information("Created group {GroupId} in system {SystemId}: {GroupName}", group.Id, system, name); return group; } @@ -52,7 +52,7 @@ namespace PluralKit.Core public Task DeleteGroup(IPKConnection conn, GroupId group) { _logger.Information("Deleted {GroupId}", group); - return conn.ExecuteAsync("delete from groups where id = @Id", new {Id = @group}); + return conn.ExecuteAsync("delete from groups where id = @Id", new { Id = @group }); } public async Task AddMembersToGroup(IPKConnection conn, GroupId group, @@ -76,7 +76,7 @@ namespace PluralKit.Core { _logger.Information("Removed members from {GroupId}: {MemberIds}", group, members); return conn.ExecuteAsync("delete from group_members where group_id = @Group and member_id = any(@Members)", - new {Group = @group, Members = members.ToArray()}); + new { Group = @group, Members = members.ToArray() }); } } } \ No newline at end of file diff --git a/PluralKit.Core/Database/Repository/ModelRepository.GroupMember.cs b/PluralKit.Core/Database/Repository/ModelRepository.GroupMember.cs index 7ac0d65c..a3b82149 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.GroupMember.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.GroupMember.cs @@ -7,12 +7,12 @@ using Dapper; namespace PluralKit.Core { public partial class ModelRepository - { + { public IAsyncEnumerable GetMemberGroups(IPKConnection conn, MemberId id) => conn.QueryStreamAsync( "select groups.* from group_members inner join groups on group_members.group_id = groups.id where group_members.member_id = @Id", - new {Id = id}); - + new { Id = id }); + public async Task AddGroupsToMember(IPKConnection conn, MemberId member, IReadOnlyCollection groups) { @@ -33,7 +33,7 @@ namespace PluralKit.Core { _logger.Information("Removed groups from {MemberId}: {GroupIds}", member, groups); return conn.ExecuteAsync("delete from group_members where member_id = @Member and group_id = any(@Groups)", - new {Member = @member, Groups = groups.ToArray() }); + new { Member = @member, Groups = groups.ToArray() }); } } diff --git a/PluralKit.Core/Database/Repository/ModelRepository.Guild.cs b/PluralKit.Core/Database/Repository/ModelRepository.Guild.cs index 8be75949..43027049 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.Guild.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.Guild.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Dapper; @@ -14,7 +14,7 @@ namespace PluralKit.Core .Build(); return conn.ExecuteAsync(query, pms); } - + public Task UpsertSystemGuild(IPKConnection conn, SystemId system, ulong guild, SystemGuildPatch patch) { @@ -36,18 +36,18 @@ namespace PluralKit.Core .Build(); return conn.ExecuteAsync(query, pms); } - + public Task GetGuild(IPKConnection conn, ulong guild) => - conn.QueryFirstAsync("insert into servers (id) values (@guild) on conflict (id) do update set id = @guild returning *", new {guild}); + conn.QueryFirstAsync("insert into servers (id) values (@guild) on conflict (id) do update set id = @guild returning *", new { guild }); public Task GetSystemGuild(IPKConnection conn, ulong guild, SystemId system) => conn.QueryFirstAsync( - "insert into system_guild (guild, system) values (@guild, @system) on conflict (guild, system) do update set guild = @guild, system = @system returning *", - new {guild, system}); + "insert into system_guild (guild, system) values (@guild, @system) on conflict (guild, system) do update set guild = @guild, system = @system returning *", + new { guild, system }); public Task GetMemberGuild(IPKConnection conn, ulong guild, MemberId member) => conn.QueryFirstAsync( "insert into member_guild (guild, member) values (@guild, @member) on conflict (guild, member) do update set guild = @guild, member = @member returning *", - new {guild, member}); + new { guild, member }); } } \ No newline at end of file diff --git a/PluralKit.Core/Database/Repository/ModelRepository.Member.cs b/PluralKit.Core/Database/Repository/ModelRepository.Member.cs index c7dfa34c..1ab4fc60 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.Member.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.Member.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System.Data; using System.Threading.Tasks; @@ -9,22 +9,22 @@ namespace PluralKit.Core public partial class ModelRepository { public Task GetMember(IPKConnection conn, MemberId id) => - conn.QueryFirstOrDefaultAsync("select * from members where id = @id", new {id}); - - public Task GetMemberByHid(IPKConnection conn, string hid) => + conn.QueryFirstOrDefaultAsync("select * from members where id = @id", new { id }); + + public Task GetMemberByHid(IPKConnection conn, string hid) => conn.QuerySingleOrDefaultAsync("select * from members where hid = @Hid", new { Hid = hid.ToLower() }); - public Task GetMemberByName(IPKConnection conn, SystemId system, string name) => + public Task GetMemberByName(IPKConnection conn, SystemId system, string name) => conn.QueryFirstOrDefaultAsync("select * from members where lower(name) = lower(@Name) and system = @SystemID", new { Name = name, SystemID = system }); - public Task GetMemberByDisplayName(IPKConnection conn, SystemId system, string name) => + public Task GetMemberByDisplayName(IPKConnection conn, SystemId system, string name) => conn.QueryFirstOrDefaultAsync("select * from members where lower(display_name) = lower(@Name) and system = @SystemID", new { Name = name, SystemID = system }); public async Task CreateMember(IPKConnection conn, SystemId id, string memberName, IDbTransaction? transaction = null) { var member = await conn.QueryFirstAsync( "insert into members (hid, system, name) values (find_free_member_hid(), @SystemId, @Name) returning *", - new {SystemId = id, Name = memberName}, transaction); + new { SystemId = id, Name = memberName }, transaction); _logger.Information("Created {MemberId} in {SystemId}: {MemberName}", member.Id, id, memberName); return member; @@ -42,7 +42,7 @@ namespace PluralKit.Core public Task DeleteMember(IPKConnection conn, MemberId id) { _logger.Information("Deleted {MemberId}", id); - return conn.ExecuteAsync("delete from members where id = @Id", new {Id = id}); + return conn.ExecuteAsync("delete from members where id = @Id", new { Id = id }); } } } \ No newline at end of file diff --git a/PluralKit.Core/Database/Repository/ModelRepository.Message.cs b/PluralKit.Core/Database/Repository/ModelRepository.Message.cs index 4e64bab5..38780bdc 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.Message.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.Message.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -8,26 +8,27 @@ namespace PluralKit.Core { public partial class ModelRepository { - public async Task AddMessage(IPKConnection conn, PKMessage msg) { + public async Task AddMessage(IPKConnection conn, PKMessage msg) + { // "on conflict do nothing" in the (pretty rare) case of duplicate events coming in from Discord, which would lead to a DB error before await conn.ExecuteAsync("insert into messages(mid, guild, channel, member, sender, original_mid) values(@Mid, @Guild, @Channel, @Member, @Sender, @OriginalMid) on conflict do nothing", msg); _logger.Debug("Stored message {@StoredMessage} in channel {Channel}", msg, msg.Channel); } - + public async Task GetMessage(IPKConnection conn, ulong id) { FullMessage Mapper(PKMessage msg, PKMember member, PKSystem system) => - new FullMessage {Message = msg, System = system, Member = member}; + new FullMessage { Message = msg, System = system, Member = member }; var result = await conn.QueryAsync( "select messages.*, members.*, systems.* from messages, members, systems where (mid = @Id or original_mid = @Id) and messages.member = members.id and systems.id = members.system", - Mapper, new {Id = id}); + Mapper, new { Id = id }); return result.FirstOrDefault(); } public async Task DeleteMessage(IPKConnection conn, ulong id) { - var rowCount = await conn.ExecuteAsync("delete from messages where mid = @Id", new {Id = id}); + var rowCount = await conn.ExecuteAsync("delete from messages where mid = @Id", new { Id = id }); if (rowCount > 0) _logger.Information("Deleted message {MessageId} from database", id); } @@ -37,7 +38,7 @@ namespace PluralKit.Core // Npgsql doesn't support ulongs in general - we hacked around it for plain ulongs but tbh not worth it for collections of ulong // Hence we map them to single longs, which *are* supported (this is ok since they're Technically (tm) stored as signed longs in the db anyway) var rowCount = await conn.ExecuteAsync("delete from messages where mid = any(@Ids)", - new {Ids = ids.Select(id => (long) id).ToArray()}); + new { Ids = ids.Select(id => (long)id).ToArray() }); if (rowCount > 0) _logger.Information("Bulk deleted messages ({FoundCount} found) from database: {MessageIds}", rowCount, ids); @@ -65,7 +66,7 @@ namespace PluralKit.Core public ulong Sender { get; set; } public ulong? OriginalMid { get; set; } } - + public class FullMessage { public PKMessage Message; diff --git a/PluralKit.Core/Database/Repository/ModelRepository.Shards.cs b/PluralKit.Core/Database/Repository/ModelRepository.Shards.cs index 832bb4aa..d0ac6584 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.Shards.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.Shards.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; using Dapper; @@ -15,16 +15,16 @@ namespace PluralKit.Core public Task SetShardStatus(IPKConnection conn, int shard, PKShardInfo.ShardStatus status) => conn.ExecuteAsync( "insert into shards (id, status) values (@Id, @Status) on conflict (id) do update set status = @Status", - new {Id = shard, Status = status}); + new { Id = shard, Status = status }); public Task RegisterShardHeartbeat(IPKConnection conn, int shard, Duration ping) => conn.ExecuteAsync( "insert into shards (id, last_heartbeat, ping) values (@Id, now(), @Ping) on conflict (id) do update set last_heartbeat = now(), ping = @Ping", - new {Id = shard, Ping = ping.TotalSeconds}); + new { Id = shard, Ping = ping.TotalSeconds }); public Task RegisterShardConnection(IPKConnection conn, int shard) => conn.ExecuteAsync( "insert into shards (id, last_connection) values (@Id, now()) on conflict (id) do update set last_connection = now()", - new {Id = shard}); + new { Id = shard }); } } \ No newline at end of file diff --git a/PluralKit.Core/Database/Repository/ModelRepository.Switch.cs b/PluralKit.Core/Database/Repository/ModelRepository.Switch.cs index d8633acf..5665be4c 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.Switch.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.Switch.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -19,7 +19,7 @@ namespace PluralKit.Core // First, we insert the switch itself var sw = await conn.QuerySingleAsync("insert into switches(system) values (@System) returning *", - new {System = system}); + new { System = system }); // Then we insert each member in the switch in the switch_members table await using (var w = conn.BeginBinaryImport("copy switch_members (switch, member) from stdin (format binary)")) @@ -43,20 +43,20 @@ namespace PluralKit.Core public async Task MoveSwitch(IPKConnection conn, SwitchId id, Instant time) { await conn.ExecuteAsync("update switches set timestamp = @Time where id = @Id", - new {Time = time, Id = id}); + new { Time = time, Id = id }); _logger.Information("Updated {SwitchId} timestamp: {SwitchTimestamp}", id, time); } public async Task DeleteSwitch(IPKConnection conn, SwitchId id) { - await conn.ExecuteAsync("delete from switches where id = @Id", new {Id = id}); + await conn.ExecuteAsync("delete from switches where id = @Id", new { Id = id }); _logger.Information("Deleted {Switch}", id); } public async Task DeleteAllSwitches(IPKConnection conn, SystemId system) { - await conn.ExecuteAsync("delete from switches where system = @Id", new {Id = system}); + await conn.ExecuteAsync("delete from switches where system = @Id", new { Id = system }); _logger.Information("Deleted all switches in {SystemId}", system); } @@ -65,7 +65,7 @@ namespace PluralKit.Core // TODO: refactor the PKSwitch data structure to somehow include a hydrated member list return conn.QueryStreamAsync( "select * from switches where system = @System order by timestamp desc", - new {System = system}); + new { System = system }); } public async Task GetSwitchCount(IPKConnection conn, SystemId system) @@ -86,7 +86,7 @@ namespace PluralKit.Core FROM switches WHERE switches.system = @System AND switches.timestamp < @Start", - new {System = system, Start = start}); + new { System = system, Start = start }); // Then collect the time and members of all switches that overlap the range var switchMembersEntries = conn.QueryStreamAsync( @@ -101,7 +101,7 @@ namespace PluralKit.Core ) AND switches.timestamp < @End ORDER BY switches.timestamp DESC", - new {System = system, Start = start, End = end, LastSwitch = lastSwitch}); + new { System = system, Start = start, End = end, LastSwitch = lastSwitch }); // Yield each value here await foreach (var entry in switchMembersEntries) @@ -114,7 +114,7 @@ namespace PluralKit.Core { return conn.QueryStreamAsync( "select * from switch_members, members where switch_members.member = members.id and switch_members.switch = @Switch order by switch_members.id", - new {Switch = sw}); + new { Switch = sw }); } public async Task GetLatestSwitch(IPKConnection conn, SystemId system) => @@ -136,13 +136,13 @@ namespace PluralKit.Core // key used in GetPerMemberSwitchDuration below var membersList = await conn.QueryAsync( "select * from members where id = any(@Switches)", // lol postgres specific `= any()` syntax - new {Switches = switchMembers.Select(m => m.Member.Value).Distinct().ToList()}); + new { Switches = switchMembers.Select(m => m.Member.Value).Distinct().ToList() }); var memberObjects = membersList.ToDictionary(m => m.Id); // check if a group ID is provided. if so, query DB for all members of said group, otherwise use membersList var groupMembersList = group != null ? await conn.QueryAsync( "select * from members inner join group_members on members.id = group_members.member_id where group_id = @id", - new {id = group}) : membersList; + new { id = group }) : membersList; var groupMemberObjects = groupMembersList.ToDictionary(m => m.Id); // Initialize entries - still need to loop to determine the TimespanEnd below @@ -154,7 +154,7 @@ namespace PluralKit.Core select new SwitchListEntry { TimespanStart = g.Key, - Members = g.Where(x => x.Member != default(MemberId) && groupMemberObjects.Any(m => x.Member == m.Key) ).Select(x => memberObjects[x.Member]) + Members = g.Where(x => x.Member != default(MemberId) && groupMemberObjects.Any(m => x.Member == m.Key)).Select(x => memberObjects[x.Member]) .ToList() }; @@ -171,7 +171,9 @@ namespace PluralKit.Core outList.Add(new SwitchListEntry { - Members = e.Members, TimespanStart = switchStartClamped, TimespanEnd = endTime + Members = e.Members, + TimespanStart = switchStartClamped, + TimespanEnd = endTime }); // next switch's end is this switch's start (we're working backward in time) @@ -219,7 +221,7 @@ namespace PluralKit.Core }; } } - + public struct SwitchListEntry { public ICollection Members; @@ -234,7 +236,7 @@ namespace PluralKit.Core public Instant RangeStart; public Instant RangeEnd; } - + public struct SwitchMembersListEntry { public MemberId Member; diff --git a/PluralKit.Core/Database/Repository/ModelRepository.System.cs b/PluralKit.Core/Database/Repository/ModelRepository.System.cs index f2375d88..102722ec 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.System.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.System.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System.Collections.Generic; using System.Text; using System.Threading.Tasks; @@ -10,36 +10,36 @@ namespace PluralKit.Core public partial class ModelRepository { public Task GetSystem(IPKConnection conn, SystemId id) => - conn.QueryFirstOrDefaultAsync("select * from systems where id = @id", new {id}); + conn.QueryFirstOrDefaultAsync("select * from systems where id = @id", new { id }); public Task GetSystemByAccount(IPKConnection conn, ulong accountId) => conn.QuerySingleOrDefaultAsync( "select systems.* from systems, accounts where accounts.system = systems.id and accounts.uid = @Id", - new {Id = accountId}); + new { Id = accountId }); public Task GetSystemByHid(IPKConnection conn, string hid) => conn.QuerySingleOrDefaultAsync("select * from systems where systems.hid = @Hid", - new {Hid = hid.ToLower()}); + new { Hid = hid.ToLower() }); public Task> GetSystemAccounts(IPKConnection conn, SystemId system) => - conn.QueryAsync("select uid from accounts where system = @Id", new {Id = system}); + conn.QueryAsync("select uid from accounts where system = @Id", new { Id = system }); public IAsyncEnumerable GetSystemMembers(IPKConnection conn, SystemId system) => - conn.QueryStreamAsync("select * from members where system = @SystemID", new {SystemID = system}); + conn.QueryStreamAsync("select * from members where system = @SystemID", new { SystemID = system }); public Task GetSystemMemberCount(IPKConnection conn, SystemId id, PrivacyLevel? privacyFilter = null) { var query = new StringBuilder("select count(*) from members where system = @Id"); if (privacyFilter != null) - query.Append($" and member_visibility = {(int) privacyFilter.Value}"); - return conn.QuerySingleAsync(query.ToString(), new {Id = id}); + query.Append($" and member_visibility = {(int)privacyFilter.Value}"); + return conn.QuerySingleAsync(query.ToString(), new { Id = id }); } public async Task CreateSystem(IPKConnection conn, string? systemName = null, IPKTransaction? tx = null) { var system = await conn.QuerySingleAsync( "insert into systems (hid, name) values (find_free_system_hid(), @Name) returning *", - new {Name = systemName}, + new { Name = systemName }, transaction: tx); _logger.Information("Created {SystemId}", system.Id); return system; @@ -59,21 +59,21 @@ namespace PluralKit.Core // We have "on conflict do nothing" since linking an account when it's already linked to the same system is idempotent // This is used in import/export, although the pk;link command checks for this case beforehand await conn.ExecuteAsync("insert into accounts (uid, system) values (@Id, @SystemId) on conflict do nothing", - new {Id = accountId, SystemId = system}); + new { Id = accountId, SystemId = system }); _logger.Information("Linked account {UserId} to {SystemId}", accountId, system); } public async Task RemoveAccount(IPKConnection conn, SystemId system, ulong accountId) { await conn.ExecuteAsync("delete from accounts where uid = @Id and system = @SystemId", - new {Id = accountId, SystemId = system}); + new { Id = accountId, SystemId = system }); _logger.Information("Unlinked account {UserId} from {SystemId}", accountId, system); } public Task DeleteSystem(IPKConnection conn, SystemId id) { _logger.Information("Deleted {SystemId}", id); - return conn.ExecuteAsync("delete from systems where id = @Id", new {Id = id}); + return conn.ExecuteAsync("delete from systems where id = @Id", new { Id = id }); } } } \ No newline at end of file diff --git a/PluralKit.Core/Database/Repository/ModelRepository.cs b/PluralKit.Core/Database/Repository/ModelRepository.cs index 40bca720..6a2a324e 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.cs @@ -1,4 +1,4 @@ -using Serilog; +using Serilog; namespace PluralKit.Core { diff --git a/PluralKit.Core/Database/Utils/ConnectionUtils.cs b/PluralKit.Core/Database/Utils/ConnectionUtils.cs index 766295bb..ece98a56 100644 --- a/PluralKit.Core/Database/Utils/ConnectionUtils.cs +++ b/PluralKit.Core/Database/Utils/ConnectionUtils.cs @@ -1,18 +1,19 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Data.Common; using Dapper; -namespace PluralKit.Core { +namespace PluralKit.Core +{ public static class ConnectionUtils { public static async IAsyncEnumerable QueryStreamAsync(this IPKConnection conn, string sql, object param) { - await using var reader = (DbDataReader) await conn.ExecuteReaderAsync(sql, param); + await using var reader = (DbDataReader)await conn.ExecuteReaderAsync(sql, param); var parser = reader.GetRowParser(); - + while (await reader.ReadAsync()) - yield return parser(reader); + yield return parser(reader); } } } \ No newline at end of file diff --git a/PluralKit.Core/Database/Utils/DatabaseMigrator.cs b/PluralKit.Core/Database/Utils/DatabaseMigrator.cs index ea9353ea..d174812f 100644 --- a/PluralKit.Core/Database/Utils/DatabaseMigrator.cs +++ b/PluralKit.Core/Database/Utils/DatabaseMigrator.cs @@ -62,7 +62,7 @@ namespace PluralKit.Core // If the above creates new enum/composite types, we must tell Npgsql to reload the internal type caches // This will propagate to every other connection as well, since it marks the global type mapper collection dirty. - ((PKConnection) conn).ReloadTypes(); + ((PKConnection)conn).ReloadTypes(); } private async Task GetCurrentDatabaseVersion(IPKConnection conn) @@ -82,4 +82,4 @@ namespace PluralKit.Core return -1; } } -} +} \ No newline at end of file diff --git a/PluralKit.Core/Database/Utils/DbConnectionCountHolder.cs b/PluralKit.Core/Database/Utils/DbConnectionCountHolder.cs index 7d3ef751..79e16306 100644 --- a/PluralKit.Core/Database/Utils/DbConnectionCountHolder.cs +++ b/PluralKit.Core/Database/Utils/DbConnectionCountHolder.cs @@ -1,4 +1,4 @@ -using System.Threading; +using System.Threading; namespace PluralKit.Core { diff --git a/PluralKit.Core/Database/Utils/QueryBuilder.cs b/PluralKit.Core/Database/Utils/QueryBuilder.cs index 2b423754..b612da4d 100644 --- a/PluralKit.Core/Database/Utils/QueryBuilder.cs +++ b/PluralKit.Core/Database/Utils/QueryBuilder.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System; using System.Text; @@ -23,32 +23,32 @@ namespace PluralKit.Core _conflictField = conflictField; _condition = condition; } - - public static QueryBuilder Insert(string table) => new QueryBuilder(QueryType.Insert, table, null, null); + + public static QueryBuilder Insert(string table) => new QueryBuilder(QueryType.Insert, table, null, null); public static QueryBuilder Update(string table, string condition) => new QueryBuilder(QueryType.Update, table, null, condition); public static QueryBuilder Upsert(string table, string conflictField) => new QueryBuilder(QueryType.Upsert, table, conflictField, null); public QueryBuilder Constant(string fieldName, string paramName) { if (_firstInsert) _firstInsert = false; - else + else { _insertFragment.Append(", "); _valuesFragment.Append(", "); } - + _insertFragment.Append(fieldName); _valuesFragment.Append(paramName); return this; } - + public QueryBuilder Variable(string fieldName, string paramName) { Constant(fieldName, paramName); - + if (_firstUpdate) _firstUpdate = false; else _updateFragment.Append(", "); - + _updateFragment.Append(fieldName); _updateFragment.Append(" = "); _updateFragment.Append(paramName); @@ -59,7 +59,7 @@ namespace PluralKit.Core { if (_firstInsert) throw new ArgumentException("No fields have been added to the query."); - + StringBuilder query = new StringBuilder(Type switch { QueryType.Insert => $"insert into {Table} ({_insertFragment}) values ({_valuesFragment})", @@ -70,7 +70,7 @@ namespace PluralKit.Core if (Type == QueryType.Update && _condition != null) query.Append($" where {_condition}"); - + if (!string.IsNullOrEmpty(suffix)) query.Append($" {suffix}"); query.Append(";"); diff --git a/PluralKit.Core/Database/Utils/UpdateQueryBuilder.cs b/PluralKit.Core/Database/Utils/UpdateQueryBuilder.cs index 805a4712..a3491e27 100644 --- a/PluralKit.Core/Database/Utils/UpdateQueryBuilder.cs +++ b/PluralKit.Core/Database/Utils/UpdateQueryBuilder.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; using Dapper; @@ -13,7 +13,7 @@ namespace PluralKit.Core { _qb = qb; } - + public static UpdateQueryBuilder Insert(string table) => new UpdateQueryBuilder(QueryBuilder.Insert(table)); public static UpdateQueryBuilder Update(string table, string condition) => new UpdateQueryBuilder(QueryBuilder.Update(table, condition)); public static UpdateQueryBuilder Upsert(string table, string conflictField) => new UpdateQueryBuilder(QueryBuilder.Upsert(table, conflictField)); diff --git a/PluralKit.Core/Database/Views/DatabaseViewsExt.cs b/PluralKit.Core/Database/Views/DatabaseViewsExt.cs index 0171900b..3d43a037 100644 --- a/PluralKit.Core/Database/Views/DatabaseViewsExt.cs +++ b/PluralKit.Core/Database/Views/DatabaseViewsExt.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System.Collections.Generic; using System.Text; using System.Threading.Tasks; @@ -10,11 +10,11 @@ namespace PluralKit.Core public static class DatabaseViewsExt { public static Task> QueryCurrentFronters(this IPKConnection conn, SystemId system) => - conn.QueryAsync("select * from system_fronters where system = @system", new {system}); + conn.QueryAsync("select * from system_fronters where system = @system", new { system }); public static Task> QueryGroupList(this IPKConnection conn, SystemId system) => - conn.QueryAsync("select * from group_list where system = @System", new {System = system}); - + conn.QueryAsync("select * from group_list where system = @System", new { System = system }); + public static Task> QueryMemberList(this IPKConnection conn, SystemId system, MemberListQueryOptions opts) { StringBuilder query; @@ -24,11 +24,11 @@ namespace PluralKit.Core query = new StringBuilder("select member_list.* from group_members inner join member_list on member_list.id = group_members.member_id where group_id = @groupFilter"); if (opts.PrivacyFilter != null) - query.Append($" and member_visibility = {(int) opts.PrivacyFilter}"); + query.Append($" and member_visibility = {(int)opts.PrivacyFilter}"); if (opts.Search != null) { - static string Filter(string column) => $"(position(lower(@filter) in lower(coalesce({column}, ''))) > 0)"; + static string Filter(string column) => $"(position(lower(@filter) in lower(coalesce({column}, ''))) > 0)"; query.Append($" and ({Filter("name")} or {Filter("display_name")}"); if (opts.SearchDescription) @@ -41,10 +41,10 @@ namespace PluralKit.Core } query.Append(")"); } - - return conn.QueryAsync(query.ToString(), new {system, filter = opts.Search, groupFilter = opts.GroupFilter}); + + return conn.QueryAsync(query.ToString(), new { system, filter = opts.Search, groupFilter = opts.GroupFilter }); } - + public struct MemberListQueryOptions { public PrivacyLevel? PrivacyFilter; diff --git a/PluralKit.Core/Database/Views/ListedGroup.cs b/PluralKit.Core/Database/Views/ListedGroup.cs index fcfbc184..adc627f1 100644 --- a/PluralKit.Core/Database/Views/ListedGroup.cs +++ b/PluralKit.Core/Database/Views/ListedGroup.cs @@ -1,6 +1,6 @@ -namespace PluralKit.Core +namespace PluralKit.Core { - public class ListedGroup : PKGroup + public class ListedGroup: PKGroup { public int MemberCount { get; } } diff --git a/PluralKit.Core/Database/Views/ListedMember.cs b/PluralKit.Core/Database/Views/ListedMember.cs index 47a68439..3d1646b6 100644 --- a/PluralKit.Core/Database/Views/ListedMember.cs +++ b/PluralKit.Core/Database/Views/ListedMember.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using NodaTime; namespace PluralKit.Core @@ -12,6 +12,6 @@ namespace PluralKit.Core public AnnualDate? AnnualBirthday => Birthday != null ? new AnnualDate(Birthday.Value.Month, Birthday.Value.Day) - : (AnnualDate?) null; + : (AnnualDate?)null; } } \ No newline at end of file diff --git a/PluralKit.Core/Database/Views/SystemFronter.cs b/PluralKit.Core/Database/Views/SystemFronter.cs index 4983b720..86e1954f 100644 --- a/PluralKit.Core/Database/Views/SystemFronter.cs +++ b/PluralKit.Core/Database/Views/SystemFronter.cs @@ -1,4 +1,4 @@ -using NodaTime; +using NodaTime; namespace PluralKit.Core { diff --git a/PluralKit.Core/Database/Wrappers/IPKCommand.cs b/PluralKit.Core/Database/Wrappers/IPKCommand.cs index affdf72d..bebcf480 100644 --- a/PluralKit.Core/Database/Wrappers/IPKCommand.cs +++ b/PluralKit.Core/Database/Wrappers/IPKCommand.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Data; using System.Data.Common; using System.Threading; diff --git a/PluralKit.Core/Database/Wrappers/IPKConnection.cs b/PluralKit.Core/Database/Wrappers/IPKConnection.cs index 1de9c9b5..5b15b889 100644 --- a/PluralKit.Core/Database/Wrappers/IPKConnection.cs +++ b/PluralKit.Core/Database/Wrappers/IPKConnection.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Data; using System.Threading; using System.Threading.Tasks; @@ -10,18 +10,18 @@ namespace PluralKit.Core public interface IPKConnection: IDbConnection, IAsyncDisposable { public Guid ConnectionId { get; } - + public Task OpenAsync(CancellationToken cancellationToken = default); public Task CloseAsync(); public Task ChangeDatabaseAsync(string databaseName, CancellationToken ct = default); - + public ValueTask BeginTransactionAsync(CancellationToken ct = default) => BeginTransactionAsync(IsolationLevel.Unspecified, ct); public ValueTask BeginTransactionAsync(IsolationLevel level, CancellationToken ct = default); - + public NpgsqlBinaryImporter BeginBinaryImport(string copyFromCommand); public NpgsqlBinaryExporter BeginBinaryExport(string copyToCommand); - + [Obsolete] new void Open(); [Obsolete] new void Close(); diff --git a/PluralKit.Core/Database/Wrappers/IPKTransaction.cs b/PluralKit.Core/Database/Wrappers/IPKTransaction.cs index 324450f7..0491c59c 100644 --- a/PluralKit.Core/Database/Wrappers/IPKTransaction.cs +++ b/PluralKit.Core/Database/Wrappers/IPKTransaction.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Data; using System.Threading; using System.Threading.Tasks; diff --git a/PluralKit.Core/Database/Wrappers/PKCommand.cs b/PluralKit.Core/Database/Wrappers/PKCommand.cs index c6a5282a..42e68707 100644 --- a/PluralKit.Core/Database/Wrappers/PKCommand.cs +++ b/PluralKit.Core/Database/Wrappers/PKCommand.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System; using System.Data; using System.Data.Common; @@ -19,11 +19,11 @@ namespace PluralKit.Core internal class PKCommand: DbCommand, IPKCommand { private NpgsqlCommand Inner { get; } - + private readonly PKConnection _ourConnection; private readonly ILogger _logger; private readonly IMetrics _metrics; - + public PKCommand(NpgsqlCommand inner, PKConnection ourConnection, ILogger logger, IMetrics metrics) { Inner = inner; @@ -111,16 +111,16 @@ namespace PluralKit.Core { var end = SystemClock.Instance.GetCurrentInstant(); var elapsed = end - start; - + _logger.Verbose("Executed query {Query} in {ElapsedTime} on connection {ConnectionId}", CommandText, elapsed, _ourConnection.ConnectionId); - + // One "BCL compatible tick" is 100 nanoseconds var micros = elapsed.BclCompatibleTicks / 10; _metrics.Provider.Timer.Instance(CoreMetrics.DatabaseQuery, new MetricTags("query", CommandText)) .Record(micros, TimeUnit.Microseconds, CommandText); } } - + private static Exception SyncError(string caller) => throw new Exception($"Executed synchronous IDbCommand function {caller}!"); } } \ No newline at end of file diff --git a/PluralKit.Core/Database/Wrappers/PKConnection.cs b/PluralKit.Core/Database/Wrappers/PKConnection.cs index fe04948c..f21862a0 100644 --- a/PluralKit.Core/Database/Wrappers/PKConnection.cs +++ b/PluralKit.Core/Database/Wrappers/PKConnection.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System; using System.Data; using System.Data.Common; @@ -37,28 +37,28 @@ namespace PluralKit.Core _logger = logger.ForContext(); _metrics = metrics; } - + public override Task OpenAsync(CancellationToken ct) { if (_hasOpened) return Inner.OpenAsync(ct); _countHolder.Increment(); _hasOpened = true; _openTime = SystemClock.Instance.GetCurrentInstant(); - _logger.Verbose("Opened database connection {ConnectionId}, new connection count {ConnectionCount}", ConnectionId, _countHolder.ConnectionCount); + _logger.Verbose("Opened database connection {ConnectionId}, new connection count {ConnectionCount}", ConnectionId, _countHolder.ConnectionCount); return Inner.OpenAsync(ct); } public override Task CloseAsync() => Inner.CloseAsync(); protected override DbCommand CreateDbCommand() => new PKCommand(Inner.CreateCommand(), this, _logger, _metrics); - + public void ReloadTypes() => Inner.ReloadTypes(); public new async ValueTask BeginTransactionAsync(IsolationLevel level, CancellationToken ct = default) => new PKTransaction(await Inner.BeginTransactionAsync(level, ct)); public NpgsqlBinaryImporter BeginBinaryImport(string copyFromCommand) => Inner.BeginBinaryImport(copyFromCommand); public NpgsqlBinaryExporter BeginBinaryExport(string copyToCommand) => Inner.BeginBinaryExport(copyToCommand); - + public override void ChangeDatabase(string databaseName) => Inner.ChangeDatabase(databaseName); public override Task ChangeDatabaseAsync(string databaseName, CancellationToken ct = default) => Inner.ChangeDatabaseAsync(databaseName, ct); protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) => throw SyncError(nameof(BeginDbTransaction)); @@ -85,12 +85,12 @@ namespace PluralKit.Core public override ConnectionState State => Inner.State; public override string DataSource => Inner.DataSource; public override string ServerVersion => Inner.ServerVersion; - + protected override void Dispose(bool disposing) - { + { Inner.Dispose(); if (_hasClosed) return; - + LogClose(); } @@ -100,12 +100,12 @@ namespace PluralKit.Core LogClose(); return Inner.DisposeAsync(); } - + private void LogClose() { _countHolder.Decrement(); _hasClosed = true; - + var duration = SystemClock.Instance.GetCurrentInstant() - _openTime; _logger.Verbose("Closed database connection {ConnectionId} (open for {ConnectionDuration}), new connection count {ConnectionCount}", ConnectionId, duration, _countHolder.ConnectionCount); } diff --git a/PluralKit.Core/Database/Wrappers/PKTransaction.cs b/PluralKit.Core/Database/Wrappers/PKTransaction.cs index 50b83119..4fd0ef39 100644 --- a/PluralKit.Core/Database/Wrappers/PKTransaction.cs +++ b/PluralKit.Core/Database/Wrappers/PKTransaction.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Data; using System.Data.Common; using System.Threading; @@ -11,7 +11,7 @@ namespace PluralKit.Core internal class PKTransaction: DbTransaction, IPKTransaction { public NpgsqlTransaction Inner { get; } - + public PKTransaction(NpgsqlTransaction inner) { Inner = inner; @@ -25,7 +25,7 @@ namespace PluralKit.Core protected override DbConnection DbConnection => Inner.Connection; public override IsolationLevel IsolationLevel => Inner.IsolationLevel; - + private static Exception SyncError(string caller) => throw new Exception($"Executed synchronous IDbTransaction function {caller}!"); } } \ No newline at end of file diff --git a/PluralKit.Core/Logging/PatchObjectDestructuring.cs b/PluralKit.Core/Logging/PatchObjectDestructuring.cs index 7436e933..ee823a19 100644 --- a/PluralKit.Core/Logging/PatchObjectDestructuring.cs +++ b/PluralKit.Core/Logging/PatchObjectDestructuring.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Serilog.Core; using Serilog.Events; @@ -12,7 +12,7 @@ namespace PluralKit.Core { result = null; if (!(value is PatchObject po)) return false; - + var propList = new List(); foreach (var props in po.GetType().GetProperties()) { diff --git a/PluralKit.Core/Logging/ScalarFormatting.cs b/PluralKit.Core/Logging/ScalarFormatting.cs index 394c3c15..93ac680b 100644 --- a/PluralKit.Core/Logging/ScalarFormatting.cs +++ b/PluralKit.Core/Logging/ScalarFormatting.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using Serilog.Formatting.Elasticsearch; @@ -30,7 +30,7 @@ namespace PluralKit.Core public Elasticsearch(bool omitEnclosingObject = false, string closingDelimiter = null, bool renderMessage = true, IFormatProvider formatProvider = null, ISerializer serializer = null, bool inlineFields = false, - bool renderMessageTemplate = true, bool formatStackTraceAsArray = false): base( + bool renderMessageTemplate = true, bool formatStackTraceAsArray = false) : base( omitEnclosingObject, closingDelimiter, renderMessage, formatProvider, serializer, inlineFields, renderMessageTemplate, formatStackTraceAsArray) { diff --git a/PluralKit.Core/Models/GuildConfig.cs b/PluralKit.Core/Models/GuildConfig.cs index 9b112de5..ed7b3747 100644 --- a/PluralKit.Core/Models/GuildConfig.cs +++ b/PluralKit.Core/Models/GuildConfig.cs @@ -1,4 +1,4 @@ -namespace PluralKit.Core +namespace PluralKit.Core { public class GuildConfig { diff --git a/PluralKit.Core/Models/MemberGuildSettings.cs b/PluralKit.Core/Models/MemberGuildSettings.cs index 7a3102cb..3347275e 100644 --- a/PluralKit.Core/Models/MemberGuildSettings.cs +++ b/PluralKit.Core/Models/MemberGuildSettings.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable namespace PluralKit.Core { public class MemberGuildSettings diff --git a/PluralKit.Core/Models/ModelTypes/INumericId.cs b/PluralKit.Core/Models/ModelTypes/INumericId.cs index 25d1eac5..7be3c096 100644 --- a/PluralKit.Core/Models/ModelTypes/INumericId.cs +++ b/PluralKit.Core/Models/ModelTypes/INumericId.cs @@ -1,10 +1,10 @@ -using System; +using System; namespace PluralKit.Core { public interface INumericId: IEquatable, IComparable - where T: INumericId - where TInner: IEquatable, IComparable + where T : INumericId + where TInner : IEquatable, IComparable { public TInner Value { get; } } diff --git a/PluralKit.Core/Models/ModelTypes/Partial.cs b/PluralKit.Core/Models/ModelTypes/Partial.cs index e64b2738..93377006 100644 --- a/PluralKit.Core/Models/ModelTypes/Partial.cs +++ b/PluralKit.Core/Models/ModelTypes/Partial.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System; using System.Collections; using System.Collections.Generic; @@ -26,7 +26,7 @@ namespace PluralKit.Core public static Partial Present(T obj) => new Partial(true, obj); public static Partial Absent = new Partial(false, default!); - public IEnumerable ToArray() => IsPresent ? new[] {Value} : new T[0]; + public IEnumerable ToArray() => IsPresent ? new[] { Value } : new T[0]; public IEnumerator GetEnumerator() => ToArray().GetEnumerator(); @@ -52,7 +52,7 @@ namespace PluralKit.Core return typeof(Partial<>) .MakeGenericType(innerType) .GetMethod(nameof(Partial.Present))! - .Invoke(null, new[] {innerValue}); + .Invoke(null, new[] { innerValue }); } public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) => diff --git a/PluralKit.Core/Models/PKGroup.cs b/PluralKit.Core/Models/PKGroup.cs index 742d9acf..50baa216 100644 --- a/PluralKit.Core/Models/PKGroup.cs +++ b/PluralKit.Core/Models/PKGroup.cs @@ -1,4 +1,4 @@ -using NodaTime; +using NodaTime; @@ -24,7 +24,7 @@ namespace PluralKit.Core public static bool operator !=(GroupId left, GroupId right) => !left.Equals(right); public int CompareTo(GroupId other) => Value.CompareTo(other.Value); - + public override string ToString() => $"Group #{Value}"; } @@ -46,7 +46,7 @@ namespace PluralKit.Core public PrivacyLevel IconPrivacy { get; private set; } public PrivacyLevel ListPrivacy { get; private set; } public PrivacyLevel Visibility { get; private set; } - + public Instant Created { get; private set; } } @@ -54,7 +54,7 @@ namespace PluralKit.Core { public static string? DescriptionFor(this PKGroup group, LookupContext ctx) => group.DescriptionPrivacy.Get(ctx, group.Description); - + public static string? IconFor(this PKGroup group, LookupContext ctx) => group.IconPrivacy.Get(ctx, group.Icon?.TryGetCleanCdnUrl()); } diff --git a/PluralKit.Core/Models/PKMember.cs b/PluralKit.Core/Models/PKMember.cs index b93a0e5d..3e1e9777 100644 --- a/PluralKit.Core/Models/PKMember.cs +++ b/PluralKit.Core/Models/PKMember.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; @@ -7,7 +7,8 @@ using Newtonsoft.Json.Linq; using NodaTime; using NodaTime.Text; -namespace PluralKit.Core { +namespace PluralKit.Core +{ public readonly struct MemberId: INumericId { public int Value { get; } @@ -28,7 +29,7 @@ namespace PluralKit.Core { public static bool operator !=(MemberId left, MemberId right) => !left.Equals(right); public int CompareTo(MemberId other) => Value.CompareTo(other.Value); - + public override string ToString() => $"Member #{Value}"; } @@ -61,10 +62,11 @@ namespace PluralKit.Core { public PrivacyLevel PronounPrivacy { get; private set; } public PrivacyLevel MetadataPrivacy { get; private set; } // public PrivacyLevel ColorPrivacy { get; private set; } - + /// Returns a formatted string representing the member's birthday, taking into account that a year of "0001" or "0004" is hidden /// Before Feb 10 2020, the sentinel year was 0001, now it is 0004. - [JsonIgnore] public string BirthdayString + [JsonIgnore] + public string BirthdayString { get { @@ -97,7 +99,7 @@ namespace PluralKit.Core { member.PronounPrivacy.Get(ctx, member.Pronouns); public static Instant? CreatedFor(this PKMember member, LookupContext ctx) => - member.MetadataPrivacy.Get(ctx, (Instant?) member.Created); + member.MetadataPrivacy.Get(ctx, (Instant?)member.Created); public static int MessageCountFor(this PKMember member, LookupContext ctx) => member.MetadataPrivacy.Get(ctx, member.MessageCount); @@ -105,7 +107,7 @@ namespace PluralKit.Core { public static JObject ToJson(this PKMember member, LookupContext ctx, bool needsLegacyProxyTags = false) { var includePrivacy = ctx == LookupContext.ByOwner; - + var o = new JObject(); o.Add("id", member.Hid); o.Add("name", member.NameFor(ctx)); @@ -117,14 +119,14 @@ namespace PluralKit.Core { o.Add("avatar_url", member.AvatarFor(ctx).TryGetCleanCdnUrl()); o.Add("banner", member.DescriptionPrivacy.Get(ctx, member.BannerImage).TryGetCleanCdnUrl()); o.Add("description", member.DescriptionFor(ctx)); - + var tagArray = new JArray(); - foreach (var tag in member.ProxyTags) - tagArray.Add(new JObject {{"prefix", tag.Prefix}, {"suffix", tag.Suffix}}); + foreach (var tag in member.ProxyTags) + tagArray.Add(new JObject { { "prefix", tag.Prefix }, { "suffix", tag.Suffix } }); o.Add("proxy_tags", tagArray); o.Add("keep_proxy", member.KeepProxy); - + o.Add("privacy", includePrivacy ? (member.MemberVisibility.LevelName()) : null); o.Add("visibility", includePrivacy ? (member.MemberVisibility.LevelName()) : null); diff --git a/PluralKit.Core/Models/PKShardInfo.cs b/PluralKit.Core/Models/PKShardInfo.cs index fc456e76..042dc8e0 100644 --- a/PluralKit.Core/Models/PKShardInfo.cs +++ b/PluralKit.Core/Models/PKShardInfo.cs @@ -1,4 +1,4 @@ -using NodaTime; +using NodaTime; namespace PluralKit.Core { diff --git a/PluralKit.Core/Models/PKSwitch.cs b/PluralKit.Core/Models/PKSwitch.cs index c73c030b..e2209f6c 100644 --- a/PluralKit.Core/Models/PKSwitch.cs +++ b/PluralKit.Core/Models/PKSwitch.cs @@ -1,8 +1,9 @@ -using NodaTime; +using NodaTime; -namespace PluralKit.Core { +namespace PluralKit.Core +{ public readonly struct SwitchId: INumericId { @@ -24,7 +25,7 @@ namespace PluralKit.Core { public static bool operator !=(SwitchId left, SwitchId right) => !left.Equals(right); public int CompareTo(SwitchId other) => Value.CompareTo(other.Value); - + public override string ToString() => $"Switch #{Value}"; } diff --git a/PluralKit.Core/Models/PKSystem.cs b/PluralKit.Core/Models/PKSystem.cs index 5ce0e9e2..edf0b7dc 100644 --- a/PluralKit.Core/Models/PKSystem.cs +++ b/PluralKit.Core/Models/PKSystem.cs @@ -1,11 +1,12 @@ -using Dapper.Contrib.Extensions; +using Dapper.Contrib.Extensions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NodaTime; -namespace PluralKit.Core { +namespace PluralKit.Core +{ public readonly struct SystemId: INumericId { @@ -46,14 +47,14 @@ namespace PluralKit.Core { public string UiTz { get; set; } public bool PingsEnabled { get; } public int? LatchTimeout { get; } - public PrivacyLevel DescriptionPrivacy { get; } - public PrivacyLevel MemberListPrivacy { get;} + public PrivacyLevel DescriptionPrivacy { get; } + public PrivacyLevel MemberListPrivacy { get; } public PrivacyLevel FrontPrivacy { get; } public PrivacyLevel FrontHistoryPrivacy { get; } public PrivacyLevel GroupListPrivacy { get; } public int? MemberLimitOverride { get; } public int? GroupLimitOverride { get; } - + [JsonIgnore] public DateTimeZone Zone => DateTimeZoneProviders.Tzdb.GetZoneOrNull(UiTz); } @@ -82,4 +83,4 @@ namespace PluralKit.Core { return o; } } -} +} \ No newline at end of file diff --git a/PluralKit.Core/Models/Patch/AccountPatch.cs b/PluralKit.Core/Models/Patch/AccountPatch.cs index 4614d4b8..c4059d5e 100644 --- a/PluralKit.Core/Models/Patch/AccountPatch.cs +++ b/PluralKit.Core/Models/Patch/AccountPatch.cs @@ -1,10 +1,10 @@ namespace PluralKit.Core { - public class AccountPatch: PatchObject - { - public Partial AllowAutoproxy { get; set; } + public class AccountPatch: PatchObject + { + public Partial AllowAutoproxy { get; set; } - public override UpdateQueryBuilder Apply(UpdateQueryBuilder b) => b - .With("allow_autoproxy", AllowAutoproxy); - } -} \ No newline at end of file + public override UpdateQueryBuilder Apply(UpdateQueryBuilder b) => b + .With("allow_autoproxy", AllowAutoproxy); + } +} \ No newline at end of file diff --git a/PluralKit.Core/Models/Patch/GroupPatch.cs b/PluralKit.Core/Models/Patch/GroupPatch.cs index daa45047..95f0116e 100644 --- a/PluralKit.Core/Models/Patch/GroupPatch.cs +++ b/PluralKit.Core/Models/Patch/GroupPatch.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System.Text.RegularExpressions; namespace PluralKit.Core @@ -12,7 +12,7 @@ namespace PluralKit.Core public Partial Icon { get; set; } public Partial BannerImage { get; set; } public Partial Color { get; set; } - + public Partial DescriptionPrivacy { get; set; } public Partial IconPrivacy { get; set; } public Partial ListPrivacy { get; set; } diff --git a/PluralKit.Core/Models/Patch/GuildPatch.cs b/PluralKit.Core/Models/Patch/GuildPatch.cs index 2e9849e2..721f889f 100644 --- a/PluralKit.Core/Models/Patch/GuildPatch.cs +++ b/PluralKit.Core/Models/Patch/GuildPatch.cs @@ -1,4 +1,4 @@ -namespace PluralKit.Core +namespace PluralKit.Core { public class GuildPatch: PatchObject { diff --git a/PluralKit.Core/Models/Patch/MemberGuildPatch.cs b/PluralKit.Core/Models/Patch/MemberGuildPatch.cs index 39b7dabd..5daf7e49 100644 --- a/PluralKit.Core/Models/Patch/MemberGuildPatch.cs +++ b/PluralKit.Core/Models/Patch/MemberGuildPatch.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable namespace PluralKit.Core { public class MemberGuildPatch: PatchObject diff --git a/PluralKit.Core/Models/Patch/MemberPatch.cs b/PluralKit.Core/Models/Patch/MemberPatch.cs index c30eef20..0d06c9f0 100644 --- a/PluralKit.Core/Models/Patch/MemberPatch.cs +++ b/PluralKit.Core/Models/Patch/MemberPatch.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System.Linq; using System.Text.RegularExpressions; @@ -83,9 +83,9 @@ namespace PluralKit.Core { var patch = new MemberPatch(); - if (o.ContainsKey("name") && o["name"].Type == JTokenType.Null) + if (o.ContainsKey("name") && o["name"].Type == JTokenType.Null) throw new ValidationError("Member name can not be set to null."); - + if (o.ContainsKey("name")) patch.Name = o.Value("name"); if (o.ContainsKey("color")) patch.Color = o.Value("color").NullIfEmpty()?.ToLower(); if (o.ContainsKey("display_name")) patch.DisplayName = o.Value("display_name").NullIfEmpty(); @@ -107,17 +107,17 @@ namespace PluralKit.Core // legacy: used in old export files and APIv1 if (o.ContainsKey("prefix") || o.ContainsKey("suffix") && !o.ContainsKey("proxy_tags")) - patch.ProxyTags = new[] {new ProxyTag(o.Value("prefix"), o.Value("suffix"))}; + patch.ProxyTags = new[] { new ProxyTag(o.Value("prefix"), o.Value("suffix")) }; else if (o.ContainsKey("proxy_tags")) patch.ProxyTags = o.Value("proxy_tags") .OfType().Select(o => new ProxyTag(o.Value("prefix"), o.Value("suffix"))) .Where(p => p.Valid) .ToArray(); - - if(o.ContainsKey("privacy")) //TODO: Deprecate this completely in api v2 + + if (o.ContainsKey("privacy")) //TODO: Deprecate this completely in api v2 { var plevel = o.ParsePrivacy("privacy"); - + patch.Visibility = plevel; patch.NamePrivacy = plevel; patch.AvatarPrivacy = plevel; diff --git a/PluralKit.Core/Models/Patch/PatchObject.cs b/PluralKit.Core/Models/Patch/PatchObject.cs index 7f2f5c9e..d7e626fd 100644 --- a/PluralKit.Core/Models/Patch/PatchObject.cs +++ b/PluralKit.Core/Models/Patch/PatchObject.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Text.RegularExpressions; namespace PluralKit.Core @@ -7,7 +7,7 @@ namespace PluralKit.Core { public abstract UpdateQueryBuilder Apply(UpdateQueryBuilder b); - public void AssertIsValid() {} + public void AssertIsValid() { } protected bool AssertValid(string input, string name, int maxLength, Func? validate = null) { @@ -28,7 +28,7 @@ namespace PluralKit.Core public class ValidationError: Exception { - public ValidationError(string message): base(message) { } + public ValidationError(string message) : base(message) { } } public class FieldTooLongError: ValidationError @@ -37,7 +37,7 @@ namespace PluralKit.Core public int MaxLength; public int ActualLength; - public FieldTooLongError(string name, int maxLength, int actualLength): + public FieldTooLongError(string name, int maxLength, int actualLength) : base($"{name} too long ({actualLength} > {maxLength})") { Name = name; diff --git a/PluralKit.Core/Models/Patch/SystemGuildPatch.cs b/PluralKit.Core/Models/Patch/SystemGuildPatch.cs index eb2cf499..e07e15e1 100644 --- a/PluralKit.Core/Models/Patch/SystemGuildPatch.cs +++ b/PluralKit.Core/Models/Patch/SystemGuildPatch.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable namespace PluralKit.Core { public class SystemGuildPatch: PatchObject diff --git a/PluralKit.Core/Models/Patch/SystemPatch.cs b/PluralKit.Core/Models/Patch/SystemPatch.cs index e1f2b840..199da232 100644 --- a/PluralKit.Core/Models/Patch/SystemPatch.cs +++ b/PluralKit.Core/Models/Patch/SystemPatch.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System; using System.Text.RegularExpressions; @@ -82,7 +82,7 @@ namespace PluralKit.Core // legacy: APIv1 uses "tz" instead of "timezone" // todo: remove in APIv2 if (o.ContainsKey("tz")) patch.UiTz = o.Value("tz") ?? "UTC"; - + if (o.ContainsKey("description_privacy")) patch.DescriptionPrivacy = o.ParsePrivacy("description_privacy"); if (o.ContainsKey("member_list_privacy")) patch.MemberListPrivacy = o.ParsePrivacy("member_list_privacy"); if (o.ContainsKey("front_privacy")) patch.FrontPrivacy = o.ParsePrivacy("front_privacy"); diff --git a/PluralKit.Core/Models/Privacy/GroupPrivacySubject.cs b/PluralKit.Core/Models/Privacy/GroupPrivacySubject.cs index 90691d48..5463244e 100644 --- a/PluralKit.Core/Models/Privacy/GroupPrivacySubject.cs +++ b/PluralKit.Core/Models/Privacy/GroupPrivacySubject.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace PluralKit.Core { @@ -9,7 +9,7 @@ namespace PluralKit.Core List, Visibility } - + public static class GroupPrivacyUtils { public static GroupPatch WithPrivacy(this GroupPatch group, GroupPrivacySubject subject, PrivacyLevel level) @@ -23,14 +23,14 @@ namespace PluralKit.Core GroupPrivacySubject.Visibility => group.Visibility = level, _ => throw new ArgumentOutOfRangeException($"Unknown privacy subject {subject}") }; - + return group; } public static GroupPatch WithAllPrivacy(this GroupPatch member, PrivacyLevel level) { foreach (var subject in Enum.GetValues(typeof(GroupPrivacySubject))) - member.WithPrivacy((GroupPrivacySubject) subject, level); + member.WithPrivacy((GroupPrivacySubject)subject, level); return member; } diff --git a/PluralKit.Core/Models/Privacy/LookupContext.cs b/PluralKit.Core/Models/Privacy/LookupContext.cs index 5ebdd323..40c7ff41 100644 --- a/PluralKit.Core/Models/Privacy/LookupContext.cs +++ b/PluralKit.Core/Models/Privacy/LookupContext.cs @@ -1,4 +1,4 @@ -namespace PluralKit.Core +namespace PluralKit.Core { public enum LookupContext { diff --git a/PluralKit.Core/Models/Privacy/MemberPrivacySubject.cs b/PluralKit.Core/Models/Privacy/MemberPrivacySubject.cs index a92819e2..817cb734 100644 --- a/PluralKit.Core/Models/Privacy/MemberPrivacySubject.cs +++ b/PluralKit.Core/Models/Privacy/MemberPrivacySubject.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace PluralKit.Core { @@ -29,14 +29,14 @@ namespace PluralKit.Core MemberPrivacySubject.Visibility => member.Visibility = level, _ => throw new ArgumentOutOfRangeException($"Unknown privacy subject {subject}") }; - + return member; } public static MemberPatch WithAllPrivacy(this MemberPatch member, PrivacyLevel level) { foreach (var subject in Enum.GetValues(typeof(MemberPrivacySubject))) - member.WithPrivacy((MemberPrivacySubject) subject, level); + member.WithPrivacy((MemberPrivacySubject)subject, level); return member; } diff --git a/PluralKit.Core/Models/Privacy/PrivacyLevel.cs b/PluralKit.Core/Models/Privacy/PrivacyLevel.cs index 0e7466a3..c04cf74c 100644 --- a/PluralKit.Core/Models/Privacy/PrivacyLevel.cs +++ b/PluralKit.Core/Models/Privacy/PrivacyLevel.cs @@ -1,4 +1,4 @@ -using System; +using System; using Newtonsoft.Json.Linq; @@ -9,18 +9,18 @@ namespace PluralKit.Core Public = 1, Private = 2 } - + public static class PrivacyLevelExt { public static bool CanAccess(this PrivacyLevel level, LookupContext ctx) => level == PrivacyLevel.Public || ctx == LookupContext.ByOwner; - public static string LevelName(this PrivacyLevel level) => + public static string LevelName(this PrivacyLevel level) => level == PrivacyLevel.Public ? "public" : "private"; public static T Get(this PrivacyLevel level, LookupContext ctx, T input, T fallback = default) => level.CanAccess(ctx) ? input : fallback; - + public static string Explanation(this PrivacyLevel level) => level switch { @@ -40,7 +40,7 @@ namespace PluralKit.Core output = input; return true; } - + public static string ToJsonString(this PrivacyLevel level) => level.LevelName(); public static PrivacyLevel ParsePrivacy(this JObject o, string propertyName) diff --git a/PluralKit.Core/Models/Privacy/SystemPrivacySubject.cs b/PluralKit.Core/Models/Privacy/SystemPrivacySubject.cs index 8fcf3efe..7da22106 100644 --- a/PluralKit.Core/Models/Privacy/SystemPrivacySubject.cs +++ b/PluralKit.Core/Models/Privacy/SystemPrivacySubject.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace PluralKit.Core { @@ -25,17 +25,17 @@ namespace PluralKit.Core SystemPrivacySubject.GroupList => system.GroupListPrivacy = level, _ => throw new ArgumentOutOfRangeException($"Unknown privacy subject {subject}") }; - + return system; } public static SystemPatch WithAllPrivacy(this SystemPatch system, PrivacyLevel level) { foreach (var subject in Enum.GetValues(typeof(SystemPrivacySubject))) - WithPrivacy(system, (SystemPrivacySubject) subject, level); + WithPrivacy(system, (SystemPrivacySubject)subject, level); return system; } - + public static bool TryParseSystemPrivacy(string input, out SystemPrivacySubject subject) { switch (input.ToLowerInvariant()) @@ -45,7 +45,7 @@ namespace PluralKit.Core case "text": case "info": subject = SystemPrivacySubject.Description; - break; + break; case "members": case "memberlist": case "list": @@ -72,6 +72,6 @@ namespace PluralKit.Core return false; } return true; - } + } } } \ No newline at end of file diff --git a/PluralKit.Core/Models/ProxyTag.cs b/PluralKit.Core/Models/ProxyTag.cs index 9abde10a..90dbcdda 100644 --- a/PluralKit.Core/Models/ProxyTag.cs +++ b/PluralKit.Core/Models/ProxyTag.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; namespace PluralKit.Core { @@ -14,7 +14,8 @@ namespace PluralKit.Core [JsonProperty("prefix")] public string Prefix { get; set; } [JsonProperty("suffix")] public string Suffix { get; set; } - [JsonIgnore] public bool Valid => + [JsonIgnore] + public bool Valid => Prefix != null || Suffix != null && ProxyString.Length <= Limits.MaxProxyTagLength; @@ -35,4 +36,4 @@ namespace PluralKit.Core } } } -} +} \ No newline at end of file diff --git a/PluralKit.Core/Models/SystemGuildSettings.cs b/PluralKit.Core/Models/SystemGuildSettings.cs index 4b69bf8f..83b6a2cd 100644 --- a/PluralKit.Core/Models/SystemGuildSettings.cs +++ b/PluralKit.Core/Models/SystemGuildSettings.cs @@ -1,4 +1,4 @@ -namespace PluralKit.Core +namespace PluralKit.Core { public enum AutoproxyMode { @@ -7,7 +7,7 @@ Latch = 3, Member = 4 } - + public class SystemGuildSettings { public ulong Guild { get; } @@ -16,8 +16,8 @@ public AutoproxyMode AutoproxyMode { get; } = AutoproxyMode.Off; public MemberId? AutoproxyMember { get; } - + public string? Tag { get; } - public bool TagEnabled { get; } + public bool TagEnabled { get; } } } \ No newline at end of file diff --git a/PluralKit.Core/Modules/ConfigModule.cs b/PluralKit.Core/Modules/ConfigModule.cs index 1ba7fa77..993939b9 100644 --- a/PluralKit.Core/Modules/ConfigModule.cs +++ b/PluralKit.Core/Modules/ConfigModule.cs @@ -1,10 +1,10 @@ -using Autofac; +using Autofac; using Microsoft.Extensions.Configuration; namespace PluralKit.Core { - public class ConfigModule: Module where T: new() + public class ConfigModule: Module where T : new() { private readonly string _submodule; diff --git a/PluralKit.Core/Modules/DataStoreModule.cs b/PluralKit.Core/Modules/DataStoreModule.cs index 5e1c8922..1daf5150 100644 --- a/PluralKit.Core/Modules/DataStoreModule.cs +++ b/PluralKit.Core/Modules/DataStoreModule.cs @@ -1,4 +1,4 @@ -using Autofac; +using Autofac; using Autofac.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection; @@ -13,7 +13,7 @@ namespace PluralKit.Core builder.RegisterType().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().AsSelf().SingleInstance(); - + builder.Populate(new ServiceCollection().AddMemoryCache()); } } diff --git a/PluralKit.Core/Modules/LoggingModule.cs b/PluralKit.Core/Modules/LoggingModule.cs index 33d7e584..adab7624 100644 --- a/PluralKit.Core/Modules/LoggingModule.cs +++ b/PluralKit.Core/Modules/LoggingModule.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using Autofac; @@ -43,7 +43,7 @@ namespace PluralKit.Core { var consoleTemplate = "[{Timestamp:HH:mm:ss.fff}] {Level:u3} {Message:lj}{NewLine}{Exception}"; var outputTemplate = "[{Timestamp:yyyy-MM-dd HH:mm:ss.ffffff}] {Level:u3} {Message:lj}{NewLine}{Exception}"; - + var logCfg = new LoggerConfiguration() .Enrich.FromLogContext() .ConfigureForNodaTime(DateTimeZoneProviders.Tzdb) @@ -52,7 +52,7 @@ namespace PluralKit.Core // Don't want App.Metrics/D#+ spam .MinimumLevel.Override("App.Metrics", LogEventLevel.Information) - + // Actual formatting for these is handled in ScalarFormatting .Destructure.AsScalar() .Destructure.AsScalar() @@ -60,7 +60,7 @@ namespace PluralKit.Core .Destructure.AsScalar() .Destructure.ByTransforming(t => new { t.Prefix, t.Suffix }) .Destructure.With() - + .WriteTo.Async(a => { // Both the same output, except one is raw compact JSON and one is plain text. @@ -109,7 +109,7 @@ namespace PluralKit.Core return Log.Logger = logCfg.CreateLogger(); } } - + // Serilog why is this necessary for such a simple thing >.> public class UTCTimestampFormatProvider: IFormatProvider { diff --git a/PluralKit.Core/Modules/MetricsModule.cs b/PluralKit.Core/Modules/MetricsModule.cs index cb650b3f..89deda97 100644 --- a/PluralKit.Core/Modules/MetricsModule.cs +++ b/PluralKit.Core/Modules/MetricsModule.cs @@ -1,4 +1,4 @@ -using App.Metrics; +using App.Metrics; using Autofac; diff --git a/PluralKit.Core/Services/DataFileService.cs b/PluralKit.Core/Services/DataFileService.cs index 39ab962b..0d5f3ff0 100644 --- a/PluralKit.Core/Services/DataFileService.cs +++ b/PluralKit.Core/Services/DataFileService.cs @@ -17,14 +17,14 @@ namespace PluralKit.Core private readonly IDatabase _db; private readonly ModelRepository _repo; private readonly ILogger _logger; - + public DataFileService(IDatabase db, ModelRepository repo, ILogger logger) { _db = db; _repo = repo; _logger = logger; } - + public async Task ExportSystem(PKSystem system) { await using var conn = await _db.Obtain(); @@ -40,8 +40,8 @@ namespace PluralKit.Core o.Add("timezone", system.UiTz); o.Add("created", system.Created.FormatExport()); o.Add("accounts", new JArray((await _repo.GetSystemAccounts(conn, system.Id)).ToList())); - o.Add("members", new JArray((await _repo.GetSystemMembers(conn, system.Id).ToListAsync()).Select(m => m.ToJson(LookupContext.ByOwner)))); - + o.Add("members", new JArray((await _repo.GetSystemMembers(conn, system.Id).ToListAsync()).Select(m => m.ToJson(LookupContext.ByOwner)))); + var switches = new JArray(); var switchList = await _repo.GetPeriodFronters(conn, system.Id, null, Instant.FromDateTimeUtc(DateTime.MinValue.ToUniversalTime()), SystemClock.Instance.GetCurrentInstant()); @@ -53,15 +53,15 @@ namespace PluralKit.Core switches.Add(s); } o.Add("switches", switches); - + return o; } - + public async Task ImportSystem(ulong userId, PKSystem? system, JObject importFile, Func confirmFunc) { await using var conn = await _db.Obtain(); await using var tx = await conn.BeginTransactionAsync(); - + return await BulkImporter.PerformImport(conn, tx, _repo, _logger, userId, system, importFile, confirmFunc); } } diff --git a/PluralKit.Core/Utils/BulkImporter/BulkImporter.cs b/PluralKit.Core/Utils/BulkImporter/BulkImporter.cs index e4ad7bbc..3055d669 100644 --- a/PluralKit.Core/Utils/BulkImporter/BulkImporter.cs +++ b/PluralKit.Core/Utils/BulkImporter/BulkImporter.cs @@ -12,10 +12,10 @@ using Serilog; namespace PluralKit.Core { - public partial class BulkImporter : IAsyncDisposable + public partial class BulkImporter: IAsyncDisposable { private ILogger _logger { get; init; } - private ModelRepository _repo { get; init; } + private ModelRepository _repo { get; init; } private PKSystem _system { get; set; } private IPKConnection _conn { get; init; } @@ -41,16 +41,17 @@ namespace PluralKit.Core _confirmFunc = confirmFunc, }; - if (system == null) { + if (system == null) + { system = await repo.CreateSystem(conn, null, tx); await repo.AddAccount(conn, system.Id, userId); importer._result.CreatedSystem = system.Hid; importer._system = system; } - + // Fetch all members in the system and log their names and hids var members = await conn.QueryAsync("select id, hid, name from members where system = @System", - new {System = system.Id}); + new { System = system.Id }); foreach (var m in members) { importer._existingMemberHids[m.Hid] = m.Id; @@ -105,11 +106,12 @@ namespace PluralKit.Core { await _tx.RollbackAsync(); } - catch (InvalidOperationException) {} + catch (InvalidOperationException) { } } - private class ImportException : Exception { - public ImportException(string Message) : base(Message) {} + private class ImportException: Exception + { + public ImportException(string Message) : base(Message) { } } } diff --git a/PluralKit.Core/Utils/BulkImporter/PluralKitImport.cs b/PluralKit.Core/Utils/BulkImporter/PluralKitImport.cs index e7ba5855..09c90b0d 100644 --- a/PluralKit.Core/Utils/BulkImporter/PluralKitImport.cs +++ b/PluralKit.Core/Utils/BulkImporter/PluralKitImport.cs @@ -33,9 +33,10 @@ namespace PluralKit.Core var members = importFile.Value("members"); var switches = importFile.Value("switches"); - - var newMembers = members.Count(m => { - var (found, _) = TryGetExistingMember(m.Value("id"), m.Value("name")); + + var newMembers = members.Count(m => + { + var (found, _) = TryGetExistingMember(m.Value("id"), m.Value("name")); return found == null; }); await AssertLimitNotReached(newMembers); @@ -43,7 +44,7 @@ namespace PluralKit.Core foreach (JObject member in members) await ImportMember(member); - if (switches.Any(sw => sw.Value("members").Any(m => !_knownIdentifiers.ContainsKey((string) m)))) + if (switches.Any(sw => sw.Value("members").Any(m => !_knownIdentifiers.ContainsKey((string)m)))) throw new ImportException("One or more switches include members that haven't been imported."); await ImportSwitches(switches); @@ -99,9 +100,9 @@ namespace PluralKit.Core private async Task ImportSwitches(JArray switches) { - var existingSwitches = (await _conn.QueryAsync("select * from switches where system = @System", new {System = _system.Id})).ToList(); + var existingSwitches = (await _conn.QueryAsync("select * from switches where system = @System", new { System = _system.Id })).ToList(); var existingTimestamps = existingSwitches.Select(sw => sw.Timestamp).ToImmutableHashSet(); - var lastSwitchId = existingSwitches.Count != 0 ? existingSwitches.Select(sw => sw.Id).Max() : (SwitchId?) null; + var lastSwitchId = existingSwitches.Count != 0 ? existingSwitches.Select(sw => sw.Id).Max() : (SwitchId?)null; if (switches.Count > 10000) throw new ImportException($"Too many switches present in import file."); @@ -115,10 +116,10 @@ namespace PluralKit.Core var timestampString = sw.Value("timestamp"); var timestamp = DateTimeFormats.TimestampExportFormat.Parse(timestampString); if (!timestamp.Success) throw new ImportException($"Switch timestamp {timestampString} is not an valid timestamp."); - + // Don't import duplicate switches if (existingTimestamps.Contains(timestamp.Value)) continue; - + // Otherwise, write to importer await importer.StartRowAsync(); await importer.WriteAsync(_system.Id.Value, NpgsqlDbType.Integer); @@ -127,7 +128,7 @@ namespace PluralKit.Core var members = sw.Value("members"); if (members.Count > Limits.MaxSwitchMemberCount) throw new ImportException($"Switch with timestamp {timestampString} contains too many members ({members.Count} > 100)."); - + // Note that we've imported a switch with this timestamp importedSwitches[timestamp.Value] = sw.Value("members"); } @@ -135,13 +136,13 @@ namespace PluralKit.Core // Commit the import await importer.CompleteAsync(); } - + // Now, fetch all the switches we just added (so, now we get their IDs too) // IDs are sequential, so any ID in this system, with a switch ID > the last max, will be one we just added var justAddedSwitches = await _conn.QueryAsync( "select * from switches where system = @System and id > @LastSwitchId", - new {System = _system.Id, LastSwitchId = lastSwitchId?.Value ?? -1}); - + new { System = _system.Id, LastSwitchId = lastSwitchId?.Value ?? -1 }); + // Lastly, import the switch members await using (var importer = _conn.BeginBinaryImport("copy switch_members (switch, member) from stdin (format binary)")) { @@ -149,13 +150,13 @@ namespace PluralKit.Core { if (!importedSwitches.TryGetValue(justAddedSwitch.Timestamp, out var switchMembers)) throw new Exception($"Found 'just-added' switch (by ID) with timestamp {justAddedSwitch.Timestamp}, but this did not correspond to a timestamp we just added a switch entry of! :/"); - + // We still assume timestamps are unique and non-duplicate, so: foreach (var memberIdentifier in switchMembers) { - if (!_knownIdentifiers.TryGetValue((string) memberIdentifier, out var memberId)) + if (!_knownIdentifiers.TryGetValue((string)memberIdentifier, out var memberId)) throw new Exception($"Attempted to import switch with member identifier {memberIdentifier} but could not find an entry in the id map for this! :/"); - + await importer.StartRowAsync(); await importer.WriteAsync(justAddedSwitch.Id.Value, NpgsqlDbType.Integer); await importer.WriteAsync(memberId.Value, NpgsqlDbType.Integer); diff --git a/PluralKit.Core/Utils/BulkImporter/TupperboxImport.cs b/PluralKit.Core/Utils/BulkImporter/TupperboxImport.cs index 53df6338..2023c7d4 100644 --- a/PluralKit.Core/Utils/BulkImporter/TupperboxImport.cs +++ b/PluralKit.Core/Utils/BulkImporter/TupperboxImport.cs @@ -49,7 +49,7 @@ namespace PluralKit.Core var hasGroup = tupper.ContainsKey("group_id") && tupper["group_id"].Type != JTokenType.Null; var multipleTags = false; - + var name = tupper.Value("name"); var patch = new MemberPatch(); @@ -61,8 +61,8 @@ namespace PluralKit.Core if (brackets.Count % 2 != 0) throw new ImportException($"Field 'brackets' in tupper {name} is invalid."); var tags = new List(); - for (var i = 0; i < brackets.Count / 2; i++) - tags.Add(new ProxyTag((string) brackets[i * 2], (string) brackets[i * 2 + 1])); + for (var i = 0; i < brackets.Count / 2; i++) + tags.Add(new ProxyTag((string)brackets[i * 2], (string)brackets[i * 2 + 1])); patch.ProxyTags = tags.ToArray(); } // todo: && if is new member @@ -97,7 +97,7 @@ namespace PluralKit.Core } else _result.Modified++; - + _logger.Debug("Importing member with identifier {FileId} to system {System} (is creating new member? {IsCreatingNewMember})", name, _system.Id, isNewMember); @@ -113,9 +113,9 @@ namespace PluralKit.Core { throw new ImportException($"Field {e.Message} in tupper {name} is invalid."); } - + await _repo.UpdateMember(_conn, memberId, patch, _tx); - + return (lastSetTag, multipleTags, hasGroup); } } diff --git a/PluralKit.Core/Utils/DateTimeFormats.cs b/PluralKit.Core/Utils/DateTimeFormats.cs index 68a2b624..abeb5bbe 100644 --- a/PluralKit.Core/Utils/DateTimeFormats.cs +++ b/PluralKit.Core/Utils/DateTimeFormats.cs @@ -1,12 +1,13 @@ -using NodaTime; +using NodaTime; using NodaTime.Text; -namespace PluralKit.Core { +namespace PluralKit.Core +{ public static class DateTimeFormats { public static IPattern TimestampExportFormat = InstantPattern.ExtendedIso; public static IPattern DateExportFormat = LocalDatePattern.CreateWithInvariantCulture("yyyy-MM-dd"); - + // We create a composite pattern that only shows the two most significant things // eg. if we have something with nonzero day component, we show d h, but if it's // a smaller duration we may only bother with showing h m or m s @@ -17,7 +18,7 @@ namespace PluralKit.Core { {DurationPattern.CreateWithInvariantCulture("H'h' m'm'"), d => d.Hours > 0}, {DurationPattern.CreateWithInvariantCulture("D'd' h'h'"), d => d.Days > 0} }.Build(); - + public static IPattern LocalDateTimeFormat = LocalDateTimePattern.CreateWithInvariantCulture("yyyy-MM-dd HH:mm:ss"); public static IPattern ZonedDateTimeFormat = ZonedDateTimePattern.CreateWithInvariantCulture("yyyy-MM-dd HH:mm:ss x", DateTimeZoneProviders.Tzdb); diff --git a/PluralKit.Core/Utils/DateUtils.cs b/PluralKit.Core/Utils/DateUtils.cs index 84c323da..3c051f3b 100644 --- a/PluralKit.Core/Utils/DateUtils.cs +++ b/PluralKit.Core/Utils/DateUtils.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using System.Text.RegularExpressions; using NodaTime; @@ -11,7 +11,7 @@ namespace PluralKit.Core public static Duration? ParsePeriod(string str) { Duration d = Duration.Zero; - + foreach (Match match in Regex.Matches(str, "(\\d{1,6})(\\w)")) { var amount = int.Parse(match.Groups[1].Value); @@ -34,7 +34,7 @@ namespace PluralKit.Core // NodaTime can't parse constructs like "1st" and "2nd" so we quietly replace those away // Gotta make sure to do the regex otherwise we'll catch things like the "st" in "August" too str = Regex.Replace(str, "(\\d+)(st|nd|rd|th)", "$1"); - + var patterns = new[] { "MMM d yyyy", // Jan 1 2019 @@ -72,12 +72,12 @@ namespace PluralKit.Core public static ZonedDateTime? ParseDateTime(string str, bool nudgeToPast = false, DateTimeZone zone = null) { if (zone == null) zone = DateTimeZone.Utc; - + // Find the current timestamp in the given zone, find the (naive) midnight timestamp, then put that into the same zone (and make it naive again) // Should yield a 12:00:00 AM. var now = SystemClock.Instance.GetCurrentInstant().InZone(zone).LocalDateTime; var midnight = now.Date.AtMidnight(); - + // First we try to parse the string as a relative time using the period parser var relResult = ParsePeriod(str); if (relResult != null) @@ -119,7 +119,7 @@ namespace PluralKit.Core "MM dd", // 01 01 "MM/dd" // 01-01 }; - + // First, we try all the timestamps that only have a time foreach (var timePattern in timePatterns) { @@ -130,17 +130,17 @@ namespace PluralKit.Core // If we have a successful match and we need a time in the past, we try to shove a future-time a date before // Example: "4:30 pm" at 3:30 pm likely refers to 4:30 pm the previous day var val = result.Value; - + // If we need to nudge, we just subtract a day. This only occurs when we're parsing specifically *just time*, so // we know we won't nudge it by more than a day since we use today's midnight timestamp as a date template. - + // Since this is a naive datetime, this ensures we're actually moving by one calendar day even if // DST changes occur, since they'll be resolved later wrt. the right side of the boundary if (val > now && nudgeToPast) val = val.PlusDays(-1); return val.InZoneLeniently(zone); } } - + // Then we try specific date+time combinations, both date first and time first, with and without commas foreach (var timePattern in timePatterns) { @@ -158,7 +158,7 @@ namespace PluralKit.Core } } } - + // Finally, just date patterns, still using midnight as the template foreach (var datePattern in datePatterns) { diff --git a/PluralKit.Core/Utils/Emojis.cs b/PluralKit.Core/Utils/Emojis.cs index 4382cc50..bdce40e8 100644 --- a/PluralKit.Core/Utils/Emojis.cs +++ b/PluralKit.Core/Utils/Emojis.cs @@ -1,5 +1,7 @@ -namespace PluralKit.Core { - public static class Emojis { +namespace PluralKit.Core +{ + public static class Emojis + { public static readonly string Warn = "\u26A0"; public static readonly string Success = "\u2705"; public static readonly string Error = "\u274C"; diff --git a/PluralKit.Core/Utils/HandlerQueue.cs b/PluralKit.Core/Utils/HandlerQueue.cs index b114e679..a0df25eb 100644 --- a/PluralKit.Core/Utils/HandlerQueue.cs +++ b/PluralKit.Core/Utils/HandlerQueue.cs @@ -23,7 +23,7 @@ namespace PluralKit.Core return default; } - var entry = new HandlerEntry {Predicate = predicate, Handler = Handler}; + var entry = new HandlerEntry { Predicate = predicate, Handler = Handler }; _handlers[Interlocked.Increment(ref _seq)] = entry; // Wait for either the event task or the timeout task diff --git a/PluralKit.Core/Utils/InitUtils.cs b/PluralKit.Core/Utils/InitUtils.cs index 38ef82f6..65832bd6 100644 --- a/PluralKit.Core/Utils/InitUtils.cs +++ b/PluralKit.Core/Utils/InitUtils.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; using Microsoft.Extensions.Configuration; @@ -7,20 +7,21 @@ using Newtonsoft.Json; using NodaTime; using NodaTime.Serialization.JsonNet; -namespace PluralKit.Core { +namespace PluralKit.Core +{ public static class InitUtils { public static void InitStatic() { Database.InitStatic(); } - + public static IConfigurationBuilder BuildConfiguration(string[] args) => new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("pluralkit.conf", true) .AddEnvironmentVariables() .AddCommandLine(args); - + public static JsonSerializerSettings BuildSerializerSettings() => new JsonSerializerSettings().BuildSerializerSettings(); public static JsonSerializerSettings BuildSerializerSettings(this JsonSerializerSettings settings) diff --git a/PluralKit.Core/Utils/Limits.cs b/PluralKit.Core/Utils/Limits.cs index ab419e2e..5c4e01fa 100644 --- a/PluralKit.Core/Utils/Limits.cs +++ b/PluralKit.Core/Utils/Limits.cs @@ -1,4 +1,5 @@ -namespace PluralKit.Core { +namespace PluralKit.Core +{ public static class Limits { public static readonly int MaxProxyNameLength = 80; @@ -6,7 +7,7 @@ namespace PluralKit.Core { public static readonly int MaxSystemNameLength = 100; public static readonly int MaxSystemTagLength = MaxProxyNameLength - 1; public static readonly int MaxMemberCount = 1000; - public static int MaxMembersWarnThreshold (int memberLimit) => memberLimit - 50; + public static int MaxMembersWarnThreshold(int memberLimit) => memberLimit - 50; public static readonly int MaxGroupCount = 250; public static readonly int MaxDescriptionLength = 1000; public static readonly int MaxProxyTagLength = 100; diff --git a/PluralKit.Core/Utils/MiscUtils.cs b/PluralKit.Core/Utils/MiscUtils.cs index e0352c70..a88c613a 100644 --- a/PluralKit.Core/Utils/MiscUtils.cs +++ b/PluralKit.Core/Utils/MiscUtils.cs @@ -10,7 +10,7 @@ namespace PluralKit.Core try { uri = new Uri(input); - if (!uri.IsAbsoluteUri || (uri.Scheme != "http" && uri.Scheme != "https")) + if (!uri.IsAbsoluteUri || (uri.Scheme != "http" && uri.Scheme != "https")) return false; } catch (UriFormatException) diff --git a/PluralKit.Core/Utils/StringUtils.cs b/PluralKit.Core/Utils/StringUtils.cs index a98c5a36..871f351e 100644 --- a/PluralKit.Core/Utils/StringUtils.cs +++ b/PluralKit.Core/Utils/StringUtils.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Security.Cryptography; using System.Text; @@ -20,7 +20,7 @@ namespace PluralKit.Core if (str != null) return str.Length > length; return false; } - + public static string ExtractCountryFlag(string flag) { if (flag.Length != 4) return null; @@ -30,14 +30,14 @@ namespace PluralKit.Core var cp2 = char.ConvertToUtf32(flag, 2); if (cp1 < 0x1F1E6 || cp1 > 0x1F1FF) return null; if (cp2 < 0x1F1E6 || cp2 > 0x1F1FF) return null; - return $"{(char) (cp1 - 0x1F1E6 + 'A')}{(char) (cp2 - 0x1F1E6 + 'A')}"; + return $"{(char)(cp1 - 0x1F1E6 + 'A')}{(char)(cp2 - 0x1F1E6 + 'A')}"; } catch (ArgumentException) { return null; } } - + public static string NullIfEmpty(this string input) { if (input == null) return null; @@ -70,7 +70,7 @@ namespace PluralKit.Core foreach (var s in input) { var limit = characterLimitByPage.Invoke(output.Count); - + // Would adding this string put us over the limit? // (note: don't roll over if the buffer's already empty; this means an individual section is above the character limit. todo: truncate, then?) if (buf.Length > 0 && buf.Length + s.Length > limit) @@ -82,12 +82,12 @@ namespace PluralKit.Core buf.Append(s); } - + // We most likely have something left over, so add that in too if (buf.Length > 0) output.Add(buf.ToString()); return output; - } + } } } \ No newline at end of file diff --git a/PluralKit.Core/Utils/TaskUtils.cs b/PluralKit.Core/Utils/TaskUtils.cs index 9ae836bd..e7ea9c92 100644 --- a/PluralKit.Core/Utils/TaskUtils.cs +++ b/PluralKit.Core/Utils/TaskUtils.cs @@ -2,24 +2,35 @@ using System; using System.Threading; using System.Threading.Tasks; -namespace PluralKit.Core { - public static class TaskUtils { - public static async Task CatchException(this Task task, Action handler) { - try { +namespace PluralKit.Core +{ + public static class TaskUtils + { + public static async Task CatchException(this Task task, Action handler) + { + try + { await task; - } catch (Exception e) { + } + catch (Exception e) + { handler(e); } } - - public static async Task TimeoutAfter(this Task task, TimeSpan? timeout) { + + public static async Task TimeoutAfter(this Task task, TimeSpan? timeout) + { // https://stackoverflow.com/a/22078975 - using (var timeoutCancellationTokenSource = new CancellationTokenSource()) { + using (var timeoutCancellationTokenSource = new CancellationTokenSource()) + { var completedTask = await Task.WhenAny(task, Task.Delay(timeout ?? TimeSpan.FromMilliseconds(-1), timeoutCancellationTokenSource.Token)); - if (completedTask == task) { + if (completedTask == task) + { timeoutCancellationTokenSource.Cancel(); return await task; // Very important in order to propagate exceptions - } else { + } + else + { throw new TimeoutException(); } } diff --git a/PluralKit.Tests/GroupAddRemoveResponseTests.cs b/PluralKit.Tests/GroupAddRemoveResponseTests.cs index e4dcc892..3b56f345 100644 --- a/PluralKit.Tests/GroupAddRemoveResponseTests.cs +++ b/PluralKit.Tests/GroupAddRemoveResponseTests.cs @@ -57,7 +57,7 @@ namespace PluralKit.Tests $"{success} Member added to 1 group (member already in 2 groups).", func(addOp, 1, 3, 1, 2) ); - + [Fact] public void Failure() => Assert.Equal( @@ -88,7 +88,7 @@ namespace PluralKit.Tests $"{success} 1 member added to group (2 members already in group).", func(addOp, 3, 1, 1, 2) ); - + [Fact] public void Failure() => Assert.Equal( @@ -140,7 +140,7 @@ namespace PluralKit.Tests $"{success} Member removed from 2 groups (member already not in 1 group).", func(removeOp, 1, 3, 2, 1) ); - + [Fact] public void Failure() => Assert.Equal( @@ -171,7 +171,7 @@ namespace PluralKit.Tests $"{success} 2 members removed from group (1 member already not in group).", func(removeOp, 3, 1, 2, 1) ); - + [Fact] public void Failure() => Assert.Equal( @@ -180,6 +180,6 @@ namespace PluralKit.Tests ); } - } + } } } \ No newline at end of file diff --git a/PluralKit.Tests/ProxyTagParserTests.cs b/PluralKit.Tests/ProxyTagParserTests.cs index 21b0a3f7..a1d9fe22 100644 --- a/PluralKit.Tests/ProxyTagParserTests.cs +++ b/PluralKit.Tests/ProxyTagParserTests.cs @@ -16,7 +16,7 @@ namespace PluralKit.Tests new ProxyMember("John", new ProxyTag("[", "]")), new ProxyMember("Bob", new ProxyTag("{", "}"), new ProxyTag("<", ">")), new ProxyMember("Prefixed", new ProxyTag("A:", "")), - new ProxyMember("Tagless") + new ProxyMember("Tagless") }; [Fact] @@ -57,7 +57,7 @@ namespace PluralKit.Tests [InlineData("something A:prefix")] public void TagsOnlyMatchAtTheStartAndEnd(string input) => AssertNoMatch(members, input); - + [Theory] [InlineData("[ text ]", " text ")] [InlineData("A: text", " text")] @@ -69,15 +69,15 @@ namespace PluralKit.Tests { private ProxyMember[] members = { new ProxyMember("John", new ProxyTag("[", "]")), - new ProxyMember("Suffix only", new ProxyTag("", "-Q")), + new ProxyMember("Suffix only", new ProxyTag("", "-Q")), }; public void MentionAtStartGetsMovedIntoTags() => AssertMatch(members, "<@466378653216014359>[some text]", content: "some text"); - + public void SpacesBetweenMentionAndTagsAreAllowed() => AssertMatch(members, "<@466378653216014359> [some text]", content: "some text"); - + public void MentionMovingTakesPrecedenceOverTagMatching() => // (as opposed to content: "<@466378653216014359> some text") // which would also be valid, but the tags should be moved first @@ -102,21 +102,21 @@ namespace PluralKit.Tests [InlineData("[[[way too deep]]]", "Level Three")] [InlineData("[[unmatched brackets]]]", "Level Two")] [InlineData("[more unmatched brackets]]]]]]", "Level One")] - public void MostSpecificTagsAreMatched(string input, string expectedName) => + public void MostSpecificTagsAreMatched(string input, string expectedName) => AssertMatch(members, input, name: expectedName); } - + public class EmptyInput { - private ProxyMember[] members = {new ProxyMember("Something", new ProxyTag("[", "]"))}; - + private ProxyMember[] members = { new ProxyMember("Something", new ProxyTag("[", "]")) }; + [Theory] [InlineData("")] [InlineData("some text")] [InlineData("{bogus tags, idk}")] - public void NoMembersMatchNothing(string input) => - AssertNoMatch(new ProxyMember[]{}, input); - + public void NoMembersMatchNothing(string input) => + AssertNoMatch(new ProxyMember[] { }, input); + [Fact] public void EmptyStringMatchesNothing() => AssertNoMatch(members, ""); @@ -148,11 +148,11 @@ namespace PluralKit.Tests { AssertMatch(members, "{ spaces in here }"); AssertNoMatch(members, "{no spaces}"); - + AssertMatch(members, "A: text here"); AssertNoMatch(members, "A:same text without spaces"); } - + [Fact] public void SpacesBeforePrefixOrAfterSuffixAlsoCount() { @@ -167,7 +167,7 @@ namespace PluralKit.Tests AssertMatch(members, "{}"); } } - + internal static ProxyMatch AssertMatch(IEnumerable members, string input, string? name = null, string? prefix = null, string? suffix = null, string? content = null) { Assert.True(new ProxyTagParser().TryMatch(members, input, out var result));