From 12c3726e4414914fd4ab6b14aae45bac098513f8 Mon Sep 17 00:00:00 2001 From: rladenson Date: Tue, 12 Nov 2024 15:10:29 -0700 Subject: [PATCH 01/37] fix(docs): fix typo in case sensitivity section of user guide --- docs/content/user-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/user-guide.md b/docs/content/user-guide.md index 0f624dc8..fdc4d126 100644 --- a/docs/content/user-guide.md +++ b/docs/content/user-guide.md @@ -382,7 +382,7 @@ You can now set some proxy tags: pk;member John proxy John:text -Now, oth of the following will work without needing to add multiple versions of the proxy tag: +Now, both of the following will work without needing to add multiple versions of the proxy tag: John: Hello! JOHN: Hello! From 681aca860dca25f61c862d8397495571ab6343d9 Mon Sep 17 00:00:00 2001 From: rladenson Date: Tue, 12 Nov 2024 15:19:21 -0700 Subject: [PATCH 02/37] fix(bot): fix typo in member no pronouns set response --- PluralKit.Bot/Commands/MemberEdit.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PluralKit.Bot/Commands/MemberEdit.cs b/PluralKit.Bot/Commands/MemberEdit.cs index c9fc943b..b57f1bae 100644 --- a/PluralKit.Bot/Commands/MemberEdit.cs +++ b/PluralKit.Bot/Commands/MemberEdit.cs @@ -133,7 +133,7 @@ public class MemberEdit { var noPronounsSetMessage = "This member does not have pronouns set."; if (ctx.System?.Id == target.System) - noPronounsSetMessage += $"To set some, type `pk;member {target.Reference(ctx)} pronouns `."; + noPronounsSetMessage += $" To set some, type `pk;member {target.Reference(ctx)} pronouns `."; ctx.CheckSystemPrivacy(target.System, target.PronounPrivacy); From 1594b12546c6da170c95c6d93f849d3be29f9238 Mon Sep 17 00:00:00 2001 From: alyssa Date: Thu, 14 Nov 2024 10:30:10 +0900 Subject: [PATCH 03/37] feat(scheduled_tasks): use env variables instead of consul to find gateway instances --- go.work | 8 ++--- go.work.sum | 3 +- services/scheduled_tasks/go.mod | 2 +- services/scheduled_tasks/repo.go | 53 ++++++-------------------------- 4 files changed, 17 insertions(+), 49 deletions(-) diff --git a/go.work b/go.work index fb60983f..a9e9d1ec 100644 --- a/go.work +++ b/go.work @@ -1,5 +1,5 @@ -go 1.19 +go 1.23 -use ( - ./services/scheduled_tasks -) +toolchain go1.23.2 + +use ./services/scheduled_tasks diff --git a/go.work.sum b/go.work.sum index 20bb40cf..72bcf761 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,3 +1,4 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= +github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= diff --git a/services/scheduled_tasks/go.mod b/services/scheduled_tasks/go.mod index 7cea5024..74020db5 100644 --- a/services/scheduled_tasks/go.mod +++ b/services/scheduled_tasks/go.mod @@ -1,6 +1,6 @@ module scheduled_tasks -go 1.18 +go 1.23 require ( github.com/getsentry/sentry-go v0.15.0 diff --git a/services/scheduled_tasks/repo.go b/services/scheduled_tasks/repo.go index ece7e0dd..31554850 100644 --- a/services/scheduled_tasks/repo.go +++ b/services/scheduled_tasks/repo.go @@ -20,59 +20,26 @@ type httpstats struct { func query_http_cache() []httpstats { var values []httpstats - url := os.Getenv("CONSUL_URL") - if url == "" { - panic("missing CONSUL_URL in environment") + http_cache_url := os.Getenv("HTTP_CACHE_URL") + if http_cache_url == "" { + panic("missing HTTP_CACHE_URL in environment") } - expected_gateway_count, err := strconv.Atoi(os.Getenv("EXPECTED_GATEWAY_COUNT")) + cluster_count, err := strconv.Atoi(os.Getenv("CLUSTER_COUNT")) if err != nil { - panic(fmt.Sprintf("missing or invalid EXPECTED_GATEWAY_COUNT in environment")) + panic(fmt.Sprintf("missing or invalid CLUSTER_COUNT in environment")) } - resp, err := http.Get(fmt.Sprintf("%v/v1/health/service/pluralkit-gateway", url)) - if err != nil { - panic(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - panic(fmt.Sprintf("got status %v trying to query consul for all_gateway_instances", resp.Status)) - } - - var ips []string - - data, err := io.ReadAll(resp.Body) - if err != nil { - panic(err) - } - var cs []any - err = json.Unmarshal(data, &cs) - if err != nil { - panic(err) - } - - if len(cs) != expected_gateway_count { - panic(fmt.Sprintf("got unexpected number of gateway instances from consul (expected %v, got %v)", expected_gateway_count, len(cs))) - } - - for idx, itm := range cs { - if ip, ok := itm.(map[string]any)["Service"].(map[string]any)["Address"].(string); ok { - ips = append(ips, ip) - } else { - panic(fmt.Sprintf("got bad data from consul for all_gateway_instances, at index %v", idx)) - } - } - - log.Printf("querying %v gateway clusters for discord stats\n", len(ips)) - - for _, ip := range ips { - resp, err := http.Get("http://"+ip+":5000/stats") + for i := range cluster_count { + log.Printf("querying gateway cluster %v for discord stats\n", i) + url := fmt.Sprintf("http://cluster%v.%s:5000/stats", i, http_cache_url) + resp, err := http.Get(url) if err != nil { panic(err) } defer resp.Body.Close() if resp.StatusCode != http.StatusFound { - panic(fmt.Sprintf("got status %v trying to query %v:5000", resp.Status, ip)) + panic(fmt.Sprintf("got status %v trying to query %v.%s:5000", resp.Status, i, http_cache_url)) } var s httpstats data, err := io.ReadAll(resp.Body) From c2da18be8e7a4526bb7e0f18c3b6f7e55ec764c7 Mon Sep 17 00:00:00 2001 From: alyssa Date: Thu, 14 Nov 2024 11:15:51 +0900 Subject: [PATCH 04/37] feat(bot): add banner credit to pk;help --- PluralKit.Bot/Commands/Help.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PluralKit.Bot/Commands/Help.cs b/PluralKit.Bot/Commands/Help.cs index 47d18944..3894c3f5 100644 --- a/PluralKit.Bot/Commands/Help.cs +++ b/PluralKit.Bot/Commands/Help.cs @@ -11,7 +11,7 @@ public class Help { Title = "PluralKit", Description = "PluralKit is a bot designed for plural communities on Discord, and is open for anyone to use. It allows you to register systems, maintain system information, set up message proxying, log switches, and more.", - Footer = new("By @ske | Myriad design by @layl, art by @tedkalashnikov | GitHub: https://github.com/PluralKit/PluralKit/ | Website: https://pluralkit.me/"), + Footer = new("By @ske | Myriad design by @layl, icon by @tedkalashnikov, banner by @fulmine | GitHub: https://github.com/PluralKit/PluralKit/ | Website: https://pluralkit.me/"), Color = DiscordUtils.Blue, }; From 487cdd482219b15bc6032f86f9d1beb4e9599858 Mon Sep 17 00:00:00 2001 From: alyssa Date: Thu, 14 Nov 2024 11:35:02 +0900 Subject: [PATCH 05/37] fix(bot): update messages for pk;serverconfig log blacklist --- PluralKit.Bot/Commands/ServerConfig.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/PluralKit.Bot/Commands/ServerConfig.cs b/PluralKit.Bot/Commands/ServerConfig.cs index 16cf31ee..922adcf7 100644 --- a/PluralKit.Bot/Commands/ServerConfig.cs +++ b/PluralKit.Bot/Commands/ServerConfig.cs @@ -181,7 +181,7 @@ public class ServerConfig await ctx.Reply( $"{Emojis.Success} Message logging for the given channels {(enable ? "enabled" : "disabled")}." + (logChannel == null - ? $"\n{Emojis.Warn} Please note that no logging channel is set, so there is nowhere to log messages to. You can set a logging channel using `pk;log channel #your-log-channel`." + ? $"\n{Emojis.Warn} Please note that no logging channel is set, so there is nowhere to log messages to. You can set a logging channel using `pk;serverconfig log channel #your-log-channel`." : "")); } @@ -351,7 +351,10 @@ public class ServerConfig await ctx.Repository.UpdateGuild(ctx.Guild.Id, new GuildPatch { LogBlacklist = blacklist.ToArray() }); await ctx.Reply( - $"{Emojis.Success} Channels {(shouldAdd ? "added to" : "removed from")} the logging blacklist."); + $"{Emojis.Success} Channels {(shouldAdd ? "added to" : "removed from")} the logging blacklist." + + (guild.LogChannel == null + ? $"\n{Emojis.Warn} Please note that no logging channel is set, so there is nowhere to log messages to. You can set a logging channel using `pk;serverconfig log channel #your-log-channel`." + : "")); } public async Task SetLogCleanup(Context ctx) From dfd3cba6dd8edd119b56f08ec927ec17fa5635b3 Mon Sep 17 00:00:00 2001 From: alyssa Date: Thu, 14 Nov 2024 11:43:32 +0900 Subject: [PATCH 06/37] fix(bot): set correct guild/channel values for messages reproxied in DMs --- PluralKit.Bot/Proxy/ProxyService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PluralKit.Bot/Proxy/ProxyService.cs b/PluralKit.Bot/Proxy/ProxyService.cs index 85b7dd7d..ebdb0fe3 100644 --- a/PluralKit.Bot/Proxy/ProxyService.cs +++ b/PluralKit.Bot/Proxy/ProxyService.cs @@ -496,10 +496,10 @@ public class ProxyService async Task SaveMessageInRedis() { // logclean info - await _redis.SetLogCleanup(triggerMessage.Author.Id, triggerMessage.GuildId.Value); + await _redis.SetLogCleanup(triggerMessage.Author.Id, proxyMessage.GuildId!.Value); // last message info (edit/reproxy) - await _redis.SetLastMessage(triggerMessage.Author.Id, triggerMessage.ChannelId, sentMessage.Mid); + await _redis.SetLastMessage(triggerMessage.Author.Id, proxyMessage.ChannelId, sentMessage.Mid); // "by original mid" lookup await _redis.SetOriginalMid(triggerMessage.Id, proxyMessage.Id); From e4f04b5bd539fde7f952202b0229142443422dbb Mon Sep 17 00:00:00 2001 From: alyssa Date: Thu, 14 Nov 2024 11:45:40 +0900 Subject: [PATCH 07/37] fix(bot): throw better errors for (some) failed cache lookups --- Myriad/Extensions/CacheExtensions.cs | 3 ++- PluralKit.Bot/Handlers/MessageEdited.cs | 16 +++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Myriad/Extensions/CacheExtensions.cs b/Myriad/Extensions/CacheExtensions.cs index 0f6fb931..84045784 100644 --- a/Myriad/Extensions/CacheExtensions.cs +++ b/Myriad/Extensions/CacheExtensions.cs @@ -50,7 +50,8 @@ public static class CacheExtensions if (!channel.IsThread()) return channel; - var parent = await cache.GetChannel(guildId, channel.ParentId!.Value); + var parent = await cache.TryGetChannel(guildId, channel.ParentId!.Value); + if (parent == null) throw new Exception($"failed to find parent channel for thread {channelOrThread} in cache"); return parent; } } \ No newline at end of file diff --git a/PluralKit.Bot/Handlers/MessageEdited.cs b/PluralKit.Bot/Handlers/MessageEdited.cs index 468a8654..168feecb 100644 --- a/PluralKit.Bot/Handlers/MessageEdited.cs +++ b/PluralKit.Bot/Handlers/MessageEdited.cs @@ -52,13 +52,19 @@ public class MessageEdited: IEventHandler if (!evt.Content.HasValue || !evt.Author.HasValue || !evt.Member.HasValue) return; - var guildIdMaybe = evt.GuildId.HasValue ? evt.GuildId.Value ?? 0 : 0; + // we only use message edit event for proxying, so ignore messages from DMs + if (!evt.GuildId.HasValue || evt.GuildId.Value == null) return; + ulong guildId = evt.GuildId!.Value!.Value; - var channel = await _cache.GetChannel(guildIdMaybe, evt.ChannelId); // todo: is this correct for message update? + var channel = await _cache.TryGetChannel(guildId, evt.ChannelId); // todo: is this correct for message update? + if (channel == null) + throw new Exception("could not find self channel in MessageEdited event"); if (!DiscordUtils.IsValidGuildChannel(channel)) return; - var rootChannel = await _cache.GetRootChannel(guildIdMaybe, channel.Id); - var guild = await _cache.GetGuild(channel.GuildId!.Value); + var rootChannel = await _cache.GetRootChannel(guildId, channel.Id); + var guild = await _cache.TryGetGuild(channel.GuildId!.Value); + if (guild == null) + throw new Exception("could not find self guild in MessageEdited event"); var lastMessage = _lastMessageCache.GetLastMessage(evt.ChannelId)?.Current; // Only react to the last message in the channel @@ -73,7 +79,7 @@ public class MessageEdited: IEventHandler return; var equivalentEvt = await GetMessageCreateEvent(evt, lastMessage, channel); - var botPermissions = await _cache.BotPermissionsIn(guildIdMaybe, channel.Id); + var botPermissions = await _cache.BotPermissionsIn(guildId, channel.Id); try { From 7edc432e0f72386d973774aea124acf0d7504df4 Mon Sep 17 00:00:00 2001 From: alyssa Date: Thu, 14 Nov 2024 12:23:52 +0900 Subject: [PATCH 08/37] chore(gateway): update redis.hset for Rust 2024 https://github.com/rust-lang/rust/issues/123748 --- services/gateway/src/discord/shard_state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/gateway/src/discord/shard_state.rs b/services/gateway/src/discord/shard_state.rs index a301f04b..1a4b57ef 100644 --- a/services/gateway/src/discord/shard_state.rs +++ b/services/gateway/src/discord/shard_state.rs @@ -42,7 +42,7 @@ impl ShardStateManager { async fn save_shard(&self, shard_id: u32, info: ShardState) -> anyhow::Result<()> { self.redis - .hset( + .hset::<(), &str, (String, Bytes)>( "pluralkit:shardstatus", ( shard_id.to_string(), From 8831e8fabef6ab2afd192342d8031dee92aff10c Mon Sep 17 00:00:00 2001 From: alyssa Date: Thu, 14 Nov 2024 12:29:53 +0900 Subject: [PATCH 09/37] fix(gateway): add minutely loop to joinset --- services/gateway/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/gateway/src/main.rs b/services/gateway/src/main.rs index 8ac380b1..3b995dbd 100644 --- a/services/gateway/src/main.rs +++ b/services/gateway/src/main.rs @@ -69,7 +69,7 @@ async fn main() -> anyhow::Result<()> { let mut signals = Signals::new(&[SIGINT, SIGTERM])?; - tokio::spawn(async move { + set.spawn(tokio::spawn(async move { for sig in signals.forever() { info!("received signal {:?}", sig); @@ -86,7 +86,7 @@ async fn main() -> anyhow::Result<()> { let _ = shutdown_tx.send(()); break; } - }); + })); let _ = shutdown_rx.recv(); From 93f8786da19e8de3dc5b1e8f076e55a7678a18b2 Mon Sep 17 00:00:00 2001 From: alyssa Date: Thu, 14 Nov 2024 13:39:47 +0900 Subject: [PATCH 10/37] feat: add basic api/gateway metrics --- Cargo.lock | 70 ++++++++++++--------- services/api/src/main.rs | 2 +- services/api/src/middleware/authnz.rs | 12 +++- services/api/src/middleware/logger.rs | 56 +++++++++++++---- services/gateway/Cargo.toml | 2 + services/gateway/src/discord/gateway.rs | 7 +++ services/gateway/src/discord/shard_state.rs | 9 +++ services/gateway/src/logger.rs | 36 ++++++++--- 8 files changed, 141 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b103416c..de637542 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,7 +89,7 @@ dependencies = [ "anyhow", "axum 0.7.5", "fred", - "hyper 1.3.1", + "hyper 1.5.0", "hyper-util", "lazy_static", "libpk", @@ -256,7 +256,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.0", "http-body-util", - "hyper 1.3.1", + "hyper 1.5.0", "hyper-util", "itoa", "matchit", @@ -398,9 +398,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "bytes-utils" @@ -1022,6 +1022,7 @@ dependencies = [ "metrics", "prost", "serde_json", + "serde_variant", "signal-hook", "tokio", "tracing", @@ -1350,9 +1351,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.3.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" dependencies = [ "bytes", "futures-channel", @@ -1391,7 +1392,7 @@ checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.3.1", + "hyper 1.5.0", "hyper-util", "rustls 0.22.4", "rustls-native-certs", @@ -1409,7 +1410,7 @@ checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.3.1", + "hyper 1.5.0", "hyper-util", "rustls 0.23.10", "rustls-pki-types", @@ -1421,20 +1422,19 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.5" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", "futures-util", "http 1.1.0", "http-body 1.0.0", - "hyper 1.3.1", + "hyper 1.5.0", "pin-project-lite", "socket2 0.5.7", "tokio", - "tower", "tower-service", "tracing", ] @@ -1753,7 +1753,7 @@ checksum = "b4f0c8427b39666bf970460908b213ec09b3b350f20c0c2eabcbba51704a08e6" dependencies = [ "base64 0.22.1", "http-body-util", - "hyper 1.3.1", + "hyper 1.5.0", "hyper-util", "indexmap", "ipnet", @@ -2523,7 +2523,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.0", "http-body-util", - "hyper 1.3.1", + "hyper 1.5.0", "hyper-rustls 0.27.3", "hyper-util", "ipnet", @@ -2963,6 +2963,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_variant" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a0068df419f9d9b6488fdded3f1c818522cdea328e02ce9d9f147380265a432" +dependencies = [ + "serde", +] + [[package]] name = "sha-1" version = "0.10.1" @@ -3584,9 +3593,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.11" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", @@ -3764,12 +3773,12 @@ dependencies = [ [[package]] name = "tracing-log" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "lazy_static", "log", + "once_cell", "tracing-core", ] @@ -3785,9 +3794,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.16" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", "nu-ansi-term", @@ -3813,11 +3822,12 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "twilight-cache-inmemory" version = "0.16.0-rc.1" -source = "git+https://github.com/pluralkit/twilight#41a71cc74441b54ed10e813e76ad040ce4c366e2" +source = "git+https://github.com/pluralkit/twilight#b346757b3054d461f5652b5ba0148a70eda5697e" dependencies = [ "bitflags 2.5.0", "dashmap", "serde", + "tracing", "twilight-model", "twilight-util", ] @@ -3825,7 +3835,7 @@ dependencies = [ [[package]] name = "twilight-gateway" version = "0.16.0-rc.1" -source = "git+https://github.com/pluralkit/twilight#41a71cc74441b54ed10e813e76ad040ce4c366e2" +source = "git+https://github.com/pluralkit/twilight#b346757b3054d461f5652b5ba0148a70eda5697e" dependencies = [ "bitflags 2.5.0", "fastrand", @@ -3845,7 +3855,7 @@ dependencies = [ [[package]] name = "twilight-gateway-queue" version = "0.16.0-rc.1" -source = "git+https://github.com/pluralkit/twilight#41a71cc74441b54ed10e813e76ad040ce4c366e2" +source = "git+https://github.com/pluralkit/twilight#b346757b3054d461f5652b5ba0148a70eda5697e" dependencies = [ "tokio", "tracing", @@ -3854,12 +3864,12 @@ dependencies = [ [[package]] name = "twilight-http" version = "0.16.0-rc.1" -source = "git+https://github.com/pluralkit/twilight#41a71cc74441b54ed10e813e76ad040ce4c366e2" +source = "git+https://github.com/pluralkit/twilight#b346757b3054d461f5652b5ba0148a70eda5697e" dependencies = [ "fastrand", "http 1.1.0", "http-body-util", - "hyper 1.3.1", + "hyper 1.5.0", "hyper-rustls 0.26.0", "hyper-util", "percent-encoding", @@ -3875,7 +3885,7 @@ dependencies = [ [[package]] name = "twilight-http-ratelimiting" version = "0.16.0-rc.1" -source = "git+https://github.com/pluralkit/twilight#41a71cc74441b54ed10e813e76ad040ce4c366e2" +source = "git+https://github.com/pluralkit/twilight#b346757b3054d461f5652b5ba0148a70eda5697e" dependencies = [ "tokio", "tracing", @@ -3884,7 +3894,7 @@ dependencies = [ [[package]] name = "twilight-model" version = "0.16.0-rc.1" -source = "git+https://github.com/pluralkit/twilight#41a71cc74441b54ed10e813e76ad040ce4c366e2" +source = "git+https://github.com/pluralkit/twilight#b346757b3054d461f5652b5ba0148a70eda5697e" dependencies = [ "bitflags 2.5.0", "serde", @@ -3896,7 +3906,7 @@ dependencies = [ [[package]] name = "twilight-util" version = "0.16.0-rc.1" -source = "git+https://github.com/pluralkit/twilight#41a71cc74441b54ed10e813e76ad040ce4c366e2" +source = "git+https://github.com/pluralkit/twilight#b346757b3054d461f5652b5ba0148a70eda5697e" dependencies = [ "twilight-model", ] @@ -3904,7 +3914,7 @@ dependencies = [ [[package]] name = "twilight-validate" version = "0.16.0-rc.1" -source = "git+https://github.com/pluralkit/twilight#41a71cc74441b54ed10e813e76ad040ce4c366e2" +source = "git+https://github.com/pluralkit/twilight#b346757b3054d461f5652b5ba0148a70eda5697e" dependencies = [ "twilight-model", ] diff --git a/services/api/src/main.rs b/services/api/src/main.rs index dbd2d46f..e9e89815 100644 --- a/services/api/src/main.rs +++ b/services/api/src/main.rs @@ -133,11 +133,11 @@ async fn main() -> anyhow::Result<()> { .route("/v2/members/:member_id/oembed.json", get(rproxy)) .route("/v2/groups/:group_id/oembed.json", get(rproxy)) - .layer(axum::middleware::from_fn(middleware::logger)) .layer(middleware::ratelimit::ratelimiter(middleware::ratelimit::do_request_ratelimited)) // this sucks .layer(axum::middleware::from_fn_with_state(ctx.clone(), middleware::authnz)) .layer(axum::middleware::from_fn(middleware::ignore_invalid_routes)) .layer(axum::middleware::from_fn(middleware::cors)) + .layer(axum::middleware::from_fn(middleware::logger)) .layer(tower_http::catch_panic::CatchPanicLayer::custom(util::handle_panic)) diff --git a/services/api/src/middleware/authnz.rs b/services/api/src/middleware/authnz.rs index e1b44941..4544e6bf 100644 --- a/services/api/src/middleware/authnz.rs +++ b/services/api/src/middleware/authnz.rs @@ -8,6 +8,8 @@ use tracing::error; use crate::ApiContext; +use super::logger::DID_AUTHENTICATE_HEADER; + pub async fn authnz(State(ctx): State, mut request: Request, next: Next) -> Response { let headers = request.headers_mut(); headers.remove("x-pluralkit-systemid"); @@ -15,6 +17,7 @@ pub async fn authnz(State(ctx): State, mut request: Request, next: N .get("authorization") .map(|h| h.to_str().ok()) .flatten(); + let mut authenticated = false; if let Some(auth_header) = auth_header { if let Some(system_id) = match libpk::db::repository::legacy_token_auth(&ctx.db, auth_header).await { @@ -29,7 +32,14 @@ pub async fn authnz(State(ctx): State, mut request: Request, next: N "x-pluralkit-systemid", HeaderValue::from_str(format!("{system_id}").as_str()).unwrap(), ); + authenticated = true; } } - next.run(request).await + let mut response = next.run(request).await; + if authenticated { + response + .headers_mut() + .insert(DID_AUTHENTICATE_HEADER, HeaderValue::from_static("1")); + } + response } diff --git a/services/api/src/middleware/logger.rs b/services/api/src/middleware/logger.rs index 7250f225..cfb84b4a 100644 --- a/services/api/src/middleware/logger.rs +++ b/services/api/src/middleware/logger.rs @@ -1,7 +1,7 @@ use std::time::Instant; use axum::{extract::MatchedPath, extract::Request, middleware::Next, response::Response}; -use metrics::histogram; +use metrics::{counter, histogram}; use tracing::{info, span, warn, Instrument, Level}; use crate::util::header_or_unknown; @@ -10,11 +10,12 @@ use crate::util::header_or_unknown; // todo: change as necessary const MIN_LOG_TIME: u128 = 2_000; +pub const DID_AUTHENTICATE_HEADER: &'static str = "x-pluralkit-didauthenticate"; + pub async fn logger(request: Request, next: Next) -> Response { let method = request.method().clone(); - let request_id = header_or_unknown(request.headers().get("Fly-Request-Id")); - let remote_ip = header_or_unknown(request.headers().get("Fly-Client-IP")); + let remote_ip = header_or_unknown(request.headers().get("X-PluralKit-Client-IP")); let user_agent = header_or_unknown(request.headers().get("User-Agent")); let endpoint = request @@ -26,10 +27,9 @@ pub async fn logger(request: Request, next: Next) -> Response { let uri = request.uri().clone(); - let request_id_span = span!( + let request_span = span!( Level::INFO, "request", - request_id, remote_ip, method = method.as_str(), endpoint = endpoint.clone(), @@ -37,9 +37,37 @@ pub async fn logger(request: Request, next: Next) -> Response { ); let start = Instant::now(); - let response = next.run(request).instrument(request_id_span).await; + let mut response = next.run(request).instrument(request_span).await; let elapsed = start.elapsed().as_millis(); + let authenticated = { + let headers = response.headers_mut(); + println!("{:#?}", headers.keys()); + if headers.contains_key(DID_AUTHENTICATE_HEADER) { + headers.remove(DID_AUTHENTICATE_HEADER); + true + } else { + false + } + }; + + counter!( + "pluralkit_api_requests", + "method" => method.to_string(), + "endpoint" => endpoint.clone(), + "status" => response.status().to_string(), + "authenticated" => authenticated.to_string(), + ) + .increment(1); + histogram!( + "pluralkit_api_requests_bucket", + "method" => method.to_string(), + "endpoint" => endpoint.clone(), + "status" => response.status().to_string(), + "authenticated" => authenticated.to_string(), + ) + .record(elapsed as f64 / 1_000_f64); + info!( "{} handled request for {} {} in {}ms", response.status(), @@ -47,15 +75,17 @@ pub async fn logger(request: Request, next: Next) -> Response { endpoint, elapsed ); - histogram!( - "pk_http_requests", - "method" => method.to_string(), - "route" => endpoint.clone(), - "status" => response.status().to_string() - ) - .record((elapsed as f64) / 1_000_f64); if elapsed > MIN_LOG_TIME { + counter!( + "pluralkit_api_slow_requests_count", + "method" => method.to_string(), + "endpoint" => endpoint.clone(), + "status" => response.status().to_string(), + "authenticated" => authenticated.to_string(), + ) + .increment(1); + warn!( "request to {} full path {} (endpoint {}) took a long time ({}ms)!", method, diff --git a/services/gateway/Cargo.toml b/services/gateway/Cargo.toml index b1a2daa7..6b9f3e25 100644 --- a/services/gateway/Cargo.toml +++ b/services/gateway/Cargo.toml @@ -24,3 +24,5 @@ twilight-cache-inmemory = { workspace = true } twilight-util = { workspace = true } twilight-model = { workspace = true } twilight-http = { workspace = true } + +serde_variant = "0.1.3" diff --git a/services/gateway/src/discord/gateway.rs b/services/gateway/src/discord/gateway.rs index 15d5ac80..40ed9bf7 100644 --- a/services/gateway/src/discord/gateway.rs +++ b/services/gateway/src/discord/gateway.rs @@ -1,4 +1,5 @@ use libpk::_config::ClusterSettings; +use metrics::counter; use std::sync::{mpsc::Sender, Arc}; use tracing::{info, warn}; use twilight_gateway::{ @@ -85,6 +86,12 @@ pub async fn runner( while let Some(item) = shard.next_event(EventTypeFlags::all()).await { match item { Ok(event) => { + counter!( + "pluralkit_gateway_events", + "shard_id" => shard.id().number().to_string(), + "event_type" => serde_variant::to_variant_name(&event.kind()).unwrap(), + ) + .increment(1); if let Err(error) = shard_state .handle_event(shard.id().number(), event.clone()) .await diff --git a/services/gateway/src/discord/shard_state.rs b/services/gateway/src/discord/shard_state.rs index 1a4b57ef..f92ff866 100644 --- a/services/gateway/src/discord/shard_state.rs +++ b/services/gateway/src/discord/shard_state.rs @@ -1,5 +1,6 @@ use bytes::Bytes; use fred::{clients::RedisPool, interfaces::HashesInterface}; +use metrics::{counter, gauge}; use prost::Message; use tracing::info; use twilight_gateway::Event; @@ -59,6 +60,13 @@ impl ShardStateManager { shard_id, if resumed { "resumed" } else { "ready" } ); + counter!( + "pluralkit_gateway_shard_reconnect", + "shard_id" => shard_id.to_string(), + "resumed" => resumed.to_string(), + ) + .increment(1); + gauge!("pluralkit_gateway_shard_up").increment(1); let mut info = self.get_shard(shard_id).await?; info.last_connection = chrono::offset::Utc::now().timestamp() as i32; info.up = true; @@ -68,6 +76,7 @@ impl ShardStateManager { async fn socket_closed(&self, shard_id: u32) -> anyhow::Result<()> { info!("shard {} closed", shard_id); + gauge!("pluralkit_gateway_shard_up").decrement(1); let mut info = self.get_shard(shard_id).await?; info.up = false; info.disconnection_count += 1; diff --git a/services/gateway/src/logger.rs b/services/gateway/src/logger.rs index aa65bc67..459aef31 100644 --- a/services/gateway/src/logger.rs +++ b/services/gateway/src/logger.rs @@ -1,6 +1,9 @@ use std::time::Instant; -use axum::{extract::MatchedPath, extract::Request, middleware::Next, response::Response}; +use axum::{ + extract::MatchedPath, extract::Request, http::StatusCode, middleware::Next, response::Response, +}; +use metrics::{counter, histogram}; use tracing::{info, span, warn, Instrument, Level}; // log any requests that take longer than 2 seconds @@ -30,13 +33,30 @@ pub async fn logger(request: Request, next: Next) -> Response { let response = next.run(request).instrument(request_id_span).await; let elapsed = start.elapsed().as_millis(); - info!( - "{} handled request for {} {} in {}ms", - response.status(), - method, - uri.path(), - elapsed - ); + counter!( + "pluralkit_gateway_cache_api_requests", + "method" => method.to_string(), + "endpoint" => endpoint.clone(), + "status" => response.status().to_string(), + ) + .increment(1); + histogram!( + "pluralkit_gateway_cache_api_requests_bucket", + "method" => method.to_string(), + "endpoint" => endpoint.clone(), + "status" => response.status().to_string(), + ) + .record(elapsed as f64 / 1_000_f64); + + if response.status() != StatusCode::FOUND { + info!( + "{} handled request for {} {} in {}ms", + response.status(), + method, + uri.path(), + elapsed + ); + } if elapsed > MIN_LOG_TIME { warn!( From a2432a6640abeeae1462e4b13f3f119ff62f4c5d Mon Sep 17 00:00:00 2001 From: alyssa Date: Thu, 14 Nov 2024 14:18:37 +0900 Subject: [PATCH 11/37] fix: update Go version in scheduled_tasks docker build --- services/scheduled_tasks/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/scheduled_tasks/Dockerfile b/services/scheduled_tasks/Dockerfile index 9e23b7af..8f790bd1 100644 --- a/services/scheduled_tasks/Dockerfile +++ b/services/scheduled_tasks/Dockerfile @@ -6,7 +6,7 @@ WORKDIR /build COPY ./ /build -RUN go build . +RUN GOTOOLCHAIN=auto go build . FROM alpine:latest From 1c9b7fae99102029817b7d307f7380675fece6b0 Mon Sep 17 00:00:00 2001 From: alyssa Date: Thu, 14 Nov 2024 14:19:07 +0900 Subject: [PATCH 12/37] chore: remove unused dockerfile for avatars service --- services/avatars/Dockerfile | 88 ------------------------------------- 1 file changed, 88 deletions(-) delete mode 100644 services/avatars/Dockerfile diff --git a/services/avatars/Dockerfile b/services/avatars/Dockerfile deleted file mode 100644 index 21864cc2..00000000 --- a/services/avatars/Dockerfile +++ /dev/null @@ -1,88 +0,0 @@ -# syntax=docker/dockerfile:1 - -# Comments are provided throughout this file to help you get started. -# If you need more help, visit the Dockerfile reference guide at -# https://docs.docker.com/go/dockerfile-reference/ - -ARG RUST_VERSION=1.75.0 -ARG APP_NAME=pluralkit-avatars - -################################################################################ -# xx is a helper for cross-compilation. -# See https://github.com/tonistiigi/xx/ for more information. -FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.3.0 AS xx - -################################################################################ -# Create a stage for building the application. -FROM --platform=$BUILDPLATFORM rust:${RUST_VERSION}-alpine AS build -ARG APP_NAME -WORKDIR /app - -# Copy cross compilation utilities from the xx stage. -COPY --from=xx / / - -# Install host build dependencies. -RUN apk add --no-cache clang lld musl-dev git file - -# This is the architecture you’re building for, which is passed in by the builder. -# Placing it here allows the previous steps to be cached across architectures. -ARG TARGETPLATFORM - -# Install cross compilation build dependencies. -RUN xx-apk add --no-cache musl-dev gcc - -# Build the application. -# Leverage a cache mount to /usr/local/cargo/registry/ -# for downloaded dependencies, a cache mount to /usr/local/cargo/git/db -# for git repository dependencies, and a cache mount to /app/target/ for -# compiled dependencies which will speed up subsequent builds. -# Leverage a bind mount to the src directory to avoid having to copy the -# source code into the container. Once built, copy the executable to an -# output directory before the cache mounted /app/target is unmounted. -# XXX: removed `id` from target mount, see: https://github.com/reproducible-containers/buildkit-cache-dance/issues/12 -RUN --mount=type=bind,source=src,target=src \ - --mount=type=bind,source=Cargo.toml,target=Cargo.toml \ - --mount=type=bind,source=Cargo.lock,target=Cargo.lock \ - --mount=type=cache,target=/app/target/$TARGETPLATFORM/ \ - --mount=type=cache,target=/usr/local/cargo/git/db \ - --mount=type=cache,target=/usr/local/cargo/registry/ \ - < Date: Sat, 16 Nov 2024 22:01:57 +0100 Subject: [PATCH 13/37] feat(bot): add -clear-attachments flag to pk;edit (#700) --- Myriad/Rest/Types/Requests/WebhookMessageEditRequest.cs | 3 +++ PluralKit.Bot/Commands/Message.cs | 5 +++-- PluralKit.Bot/Services/WebhookExecutorService.cs | 9 ++++++--- docs/content/tips-and-tricks.md | 1 + 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Myriad/Rest/Types/Requests/WebhookMessageEditRequest.cs b/Myriad/Rest/Types/Requests/WebhookMessageEditRequest.cs index e1765a6f..512464d3 100644 --- a/Myriad/Rest/Types/Requests/WebhookMessageEditRequest.cs +++ b/Myriad/Rest/Types/Requests/WebhookMessageEditRequest.cs @@ -15,4 +15,7 @@ public record WebhookMessageEditRequest [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public Optional Embeds { get; init; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public Optional Attachments { get; init; } } \ No newline at end of file diff --git a/PluralKit.Bot/Commands/Message.cs b/PluralKit.Bot/Commands/Message.cs index 6fc271a1..9a3b31fb 100644 --- a/PluralKit.Bot/Commands/Message.cs +++ b/PluralKit.Bot/Commands/Message.cs @@ -118,7 +118,8 @@ public class ProxiedMessage // Should we clear embeds? var clearEmbeds = ctx.MatchFlag("clear-embed", "ce"); - if (clearEmbeds && newContent == null) + var clearAttachments = ctx.MatchFlag("clear-attachments", "ca"); + if ((clearEmbeds || clearAttachments) && newContent == null) newContent = originalMsg.Content!; if (newContent == null) @@ -218,7 +219,7 @@ public class ProxiedMessage try { var editedMsg = - await _webhookExecutor.EditWebhookMessage(msg.Guild ?? 0, msg.Channel, msg.Mid, newContent, clearEmbeds); + await _webhookExecutor.EditWebhookMessage(msg.Guild ?? 0, msg.Channel, msg.Mid, newContent, clearEmbeds, clearAttachments); if (ctx.Guild == null) await _rest.CreateReaction(ctx.Channel.Id, ctx.Message.Id, new Emoji { Name = Emojis.Success }); diff --git a/PluralKit.Bot/Services/WebhookExecutorService.cs b/PluralKit.Bot/Services/WebhookExecutorService.cs index fc23388c..999920d6 100644 --- a/PluralKit.Bot/Services/WebhookExecutorService.cs +++ b/PluralKit.Bot/Services/WebhookExecutorService.cs @@ -19,7 +19,6 @@ using Newtonsoft.Json.Linq; using Serilog; -using PluralKit.Core; using Myriad.Utils; namespace PluralKit.Bot; @@ -87,7 +86,8 @@ public class WebhookExecutorService return webhookMessage; } - public async Task EditWebhookMessage(ulong guildId, ulong channelId, ulong messageId, string newContent, bool clearEmbeds = false) + public async Task EditWebhookMessage(ulong guildId, ulong channelId, ulong messageId, string newContent, + bool clearEmbeds = false, bool clearAttachments = false) { var allowedMentions = newContent.ParseMentions() with { @@ -108,7 +108,10 @@ public class WebhookExecutorService { Content = newContent, AllowedMentions = allowedMentions, - Embeds = (clearEmbeds == true ? Optional.Some(new Embed[] { }) : Optional.None()), + Embeds = (clearEmbeds ? Optional.Some(new Embed[] { }) : Optional.None()), + Attachments = (clearAttachments + ? Optional.Some(new Message.Attachment[] { }) + : Optional.None()) }; return await _rest.EditWebhookMessage(webhook.Id, webhook.Token, messageId, editReq, threadId); diff --git a/docs/content/tips-and-tricks.md b/docs/content/tips-and-tricks.md index 2385ab70..7acba69a 100644 --- a/docs/content/tips-and-tricks.md +++ b/docs/content/tips-and-tricks.md @@ -82,6 +82,7 @@ You cannot look up private members or groups of another system. |pk;edit|-prepend|-p|Prepend the new content to the old message instead of overwriting it| |pk;edit|-nospace|-ns|Append/prepend without adding a space| |pk;edit|-clear-embed|-ce|Remove embeds from a message| +|pk;edit|-clear-attachments|-ca|Remove attachments from a message| |pk;edit|-regex|-x|Edit using a C# Regex formatted like s\|X\|Y or s\|X\|Y\|F, where \| is any character, X is a Regex, Y is a substitution string, and F is a set of Regex flags| |pk;switch edit and pk;switch add|-append|-a|Append members to the current switch or make a new switch with members appended| |pk;switch edit and pk;switch add|-prepend|-p|Prepend members to the current switch or make a new switch with members prepended| From f3e006034b19ef8bc5c45bc45d13e37ac0d812c2 Mon Sep 17 00:00:00 2001 From: alyssa Date: Thu, 21 Nov 2024 06:50:55 +0900 Subject: [PATCH 14/37] feat(bot): add opensearch log configuration --- PluralKit.API/packages.lock.json | 43 +++++++++++++++++++++---- PluralKit.Bot/packages.lock.json | 43 +++++++++++++++++++++---- PluralKit.Core/Modules/LoggingModule.cs | 19 +++++------ PluralKit.Core/PluralKit.Core.csproj | 1 + PluralKit.Core/packages.lock.json | 43 +++++++++++++++++++++---- PluralKit.Tests/packages.lock.json | 43 +++++++++++++++++++++---- 6 files changed, 157 insertions(+), 35 deletions(-) diff --git a/PluralKit.API/packages.lock.json b/PluralKit.API/packages.lock.json index 0d41f905..5418e85c 100644 --- a/PluralKit.API/packages.lock.json +++ b/PluralKit.API/packages.lock.json @@ -131,6 +131,15 @@ "App.Metrics.Formatters.InfluxDB": "4.1.0" } }, + "AppFact.SerilogOpenSearchSink": { + "type": "Transitive", + "resolved": "0.0.8", + "contentHash": "RI3lfmvAwhqrYwy5KPqsBT/tB/opSzeoVH2WUfvGKNBpl6ILCw/5wE8+19L+XMzBFVqgZ5QmkQ2PqTzG9I/ckA==", + "dependencies": { + "OpenSearch.Client": "1.4.0", + "Serilog": "2.12.0" + } + }, "Autofac": { "type": "Transitive", "resolved": "6.0.0", @@ -514,6 +523,24 @@ "Npgsql": "4.1.5" } }, + "OpenSearch.Client": { + "type": "Transitive", + "resolved": "1.4.0", + "contentHash": "91TXm+I8PzxT0yxkf0q5Quee5stVcIFys56pU8+M3+Kiakx+aiaTAlZJHfA3Oy6dMJndNkVm37IPKYCqN3dS4g==", + "dependencies": { + "OpenSearch.Net": "1.4.0" + } + }, + "OpenSearch.Net": { + "type": "Transitive", + "resolved": "1.4.0", + "contentHash": "xCM6m3aArN9gXIl2DvWXaDlbpjOBbgMeRPsAM7s1eDbWhu8wKNyfPNKSfep4JlYXkZ7N6Oi/+lmi+G3/SpcqlQ==", + "dependencies": { + "Microsoft.CSharp": "4.7.0", + "System.Buffers": "4.5.1", + "System.Diagnostics.DiagnosticSource": "6.0.1" + } + }, "Pipelines.Sockets.Unofficial": { "type": "Transitive", "resolved": "2.2.8", @@ -797,8 +824,8 @@ }, "System.Buffers": { "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "pL2ChpaRRWI/p4LXyy4RgeWlYF2sgfj/pnVMvBqwNFr5cXg7CXNnWZWxrOONLg8VGdFB8oB+EG2Qw4MLgTOe+A==" + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" }, "System.Collections": { "type": "Transitive", @@ -851,8 +878,11 @@ }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", - "resolved": "4.7.1", - "contentHash": "j81Lovt90PDAq8kLpaJfJKV/rWdWuEk6jfV+MBkee33vzYLEUsy4gXK8laa9V2nZlLM9VM9yA/OOQxxPEJKAMw==" + "resolved": "6.0.1", + "contentHash": "KiLYDu2k2J82Q9BJpWiuQqCkFjRBWVq4jDzKKWawVi9KWzyD0XG3cmfX0vqTQlL14Wi9EufJrbL0+KCLTbqWiQ==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } }, "System.Diagnostics.Tools": { "type": "Transitive", @@ -1216,8 +1246,8 @@ }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", - "resolved": "4.7.1", - "contentHash": "zOHkQmzPCn5zm/BH+cxC1XbUS3P4Yoi3xzW7eRgVpDR2tPGSzyMZ17Ig1iRkfJuY0nhxkQQde8pgePNiA7z7TQ==" + "resolved": "6.0.0", + "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" }, "System.Runtime.Extensions": { "type": "Transitive", @@ -1540,6 +1570,7 @@ "dependencies": { "App.Metrics": "[4.1.0, )", "App.Metrics.Reporting.InfluxDB": "[4.1.0, )", + "AppFact.SerilogOpenSearchSink": "[0.0.8, )", "Autofac": "[6.0.0, )", "Autofac.Extensions.DependencyInjection": "[7.1.0, )", "Dapper": "[2.0.35, )", diff --git a/PluralKit.Bot/packages.lock.json b/PluralKit.Bot/packages.lock.json index 6ef93128..c74411cc 100644 --- a/PluralKit.Bot/packages.lock.json +++ b/PluralKit.Bot/packages.lock.json @@ -116,6 +116,15 @@ "App.Metrics.Formatters.InfluxDB": "4.1.0" } }, + "AppFact.SerilogOpenSearchSink": { + "type": "Transitive", + "resolved": "0.0.8", + "contentHash": "RI3lfmvAwhqrYwy5KPqsBT/tB/opSzeoVH2WUfvGKNBpl6ILCw/5wE8+19L+XMzBFVqgZ5QmkQ2PqTzG9I/ckA==", + "dependencies": { + "OpenSearch.Client": "1.4.0", + "Serilog": "2.12.0" + } + }, "Autofac": { "type": "Transitive", "resolved": "6.0.0", @@ -464,6 +473,24 @@ "Npgsql": "4.1.5" } }, + "OpenSearch.Client": { + "type": "Transitive", + "resolved": "1.4.0", + "contentHash": "91TXm+I8PzxT0yxkf0q5Quee5stVcIFys56pU8+M3+Kiakx+aiaTAlZJHfA3Oy6dMJndNkVm37IPKYCqN3dS4g==", + "dependencies": { + "OpenSearch.Net": "1.4.0" + } + }, + "OpenSearch.Net": { + "type": "Transitive", + "resolved": "1.4.0", + "contentHash": "xCM6m3aArN9gXIl2DvWXaDlbpjOBbgMeRPsAM7s1eDbWhu8wKNyfPNKSfep4JlYXkZ7N6Oi/+lmi+G3/SpcqlQ==", + "dependencies": { + "Microsoft.CSharp": "4.7.0", + "System.Buffers": "4.5.1", + "System.Diagnostics.DiagnosticSource": "6.0.1" + } + }, "Pipelines.Sockets.Unofficial": { "type": "Transitive", "resolved": "2.2.8", @@ -726,8 +753,8 @@ }, "System.Buffers": { "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "pL2ChpaRRWI/p4LXyy4RgeWlYF2sgfj/pnVMvBqwNFr5cXg7CXNnWZWxrOONLg8VGdFB8oB+EG2Qw4MLgTOe+A==" + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" }, "System.Collections": { "type": "Transitive", @@ -780,8 +807,11 @@ }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", - "resolved": "4.7.1", - "contentHash": "j81Lovt90PDAq8kLpaJfJKV/rWdWuEk6jfV+MBkee33vzYLEUsy4gXK8laa9V2nZlLM9VM9yA/OOQxxPEJKAMw==" + "resolved": "6.0.1", + "contentHash": "KiLYDu2k2J82Q9BJpWiuQqCkFjRBWVq4jDzKKWawVi9KWzyD0XG3cmfX0vqTQlL14Wi9EufJrbL0+KCLTbqWiQ==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } }, "System.Diagnostics.Tools": { "type": "Transitive", @@ -1123,8 +1153,8 @@ }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", - "resolved": "4.7.1", - "contentHash": "zOHkQmzPCn5zm/BH+cxC1XbUS3P4Yoi3xzW7eRgVpDR2tPGSzyMZ17Ig1iRkfJuY0nhxkQQde8pgePNiA7z7TQ==" + "resolved": "6.0.0", + "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" }, "System.Runtime.Extensions": { "type": "Transitive", @@ -1459,6 +1489,7 @@ "dependencies": { "App.Metrics": "[4.1.0, )", "App.Metrics.Reporting.InfluxDB": "[4.1.0, )", + "AppFact.SerilogOpenSearchSink": "[0.0.8, )", "Autofac": "[6.0.0, )", "Autofac.Extensions.DependencyInjection": "[7.1.0, )", "Dapper": "[2.0.35, )", diff --git a/PluralKit.Core/Modules/LoggingModule.cs b/PluralKit.Core/Modules/LoggingModule.cs index dd026207..18ffa58b 100644 --- a/PluralKit.Core/Modules/LoggingModule.cs +++ b/PluralKit.Core/Modules/LoggingModule.cs @@ -2,6 +2,8 @@ using System.Globalization; using Autofac; +using AppFact.SerilogOpenSearchSink; + using Microsoft.Extensions.Logging; using NodaTime; @@ -9,7 +11,6 @@ using NodaTime; using Serilog; using Serilog.Events; using Serilog.Formatting.Compact; -using Serilog.Sinks.Elasticsearch; using Serilog.Sinks.Seq; using Serilog.Sinks.SystemConsole.Themes; @@ -104,16 +105,12 @@ public class LoggingModule: Module if (config.ElasticUrl != null) { - var elasticConfig = new ElasticsearchSinkOptions(new Uri(config.ElasticUrl)) - { - AutoRegisterTemplate = true, - AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv7, - MinimumLogEventLevel = config.ElasticLogLevel, - IndexFormat = "pluralkit-logs-{0:yyyy.MM.dd}", - CustomFormatter = new ScalarFormatting.Elasticsearch() - }; - - logCfg.WriteTo.Elasticsearch(elasticConfig); + logCfg.WriteTo.OpenSearch( + uri: config.ElasticUrl, + index: "dotnet-logs", + basicAuthUser: "unused", + basicAuthPassword: "unused" + ); } if (config.SeqLogUrl != null) diff --git a/PluralKit.Core/PluralKit.Core.csproj b/PluralKit.Core/PluralKit.Core.csproj index a11d9333..3ff78105 100644 --- a/PluralKit.Core/PluralKit.Core.csproj +++ b/PluralKit.Core/PluralKit.Core.csproj @@ -18,6 +18,7 @@ + diff --git a/PluralKit.Core/packages.lock.json b/PluralKit.Core/packages.lock.json index 7f9fef36..e9ca102e 100644 --- a/PluralKit.Core/packages.lock.json +++ b/PluralKit.Core/packages.lock.json @@ -22,6 +22,16 @@ "App.Metrics.Formatters.InfluxDB": "4.1.0" } }, + "AppFact.SerilogOpenSearchSink": { + "type": "Direct", + "requested": "[0.0.8, )", + "resolved": "0.0.8", + "contentHash": "RI3lfmvAwhqrYwy5KPqsBT/tB/opSzeoVH2WUfvGKNBpl6ILCw/5wE8+19L+XMzBFVqgZ5QmkQ2PqTzG9I/ckA==", + "dependencies": { + "OpenSearch.Client": "1.4.0", + "Serilog": "2.12.0" + } + }, "Autofac": { "type": "Direct", "requested": "[6.0.0, )", @@ -551,6 +561,24 @@ "System.Xml.XDocument": "4.3.0" } }, + "OpenSearch.Client": { + "type": "Transitive", + "resolved": "1.4.0", + "contentHash": "91TXm+I8PzxT0yxkf0q5Quee5stVcIFys56pU8+M3+Kiakx+aiaTAlZJHfA3Oy6dMJndNkVm37IPKYCqN3dS4g==", + "dependencies": { + "OpenSearch.Net": "1.4.0" + } + }, + "OpenSearch.Net": { + "type": "Transitive", + "resolved": "1.4.0", + "contentHash": "xCM6m3aArN9gXIl2DvWXaDlbpjOBbgMeRPsAM7s1eDbWhu8wKNyfPNKSfep4JlYXkZ7N6Oi/+lmi+G3/SpcqlQ==", + "dependencies": { + "Microsoft.CSharp": "4.7.0", + "System.Buffers": "4.5.1", + "System.Diagnostics.DiagnosticSource": "6.0.1" + } + }, "Pipelines.Sockets.Unofficial": { "type": "Transitive", "resolved": "2.2.8", @@ -692,8 +720,8 @@ }, "System.Buffers": { "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "pL2ChpaRRWI/p4LXyy4RgeWlYF2sgfj/pnVMvBqwNFr5cXg7CXNnWZWxrOONLg8VGdFB8oB+EG2Qw4MLgTOe+A==" + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" }, "System.Collections": { "type": "Transitive", @@ -746,8 +774,11 @@ }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", - "resolved": "4.7.1", - "contentHash": "j81Lovt90PDAq8kLpaJfJKV/rWdWuEk6jfV+MBkee33vzYLEUsy4gXK8laa9V2nZlLM9VM9yA/OOQxxPEJKAMw==" + "resolved": "6.0.1", + "contentHash": "KiLYDu2k2J82Q9BJpWiuQqCkFjRBWVq4jDzKKWawVi9KWzyD0XG3cmfX0vqTQlL14Wi9EufJrbL0+KCLTbqWiQ==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } }, "System.Diagnostics.Tools": { "type": "Transitive", @@ -1081,8 +1112,8 @@ }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", - "resolved": "4.7.1", - "contentHash": "zOHkQmzPCn5zm/BH+cxC1XbUS3P4Yoi3xzW7eRgVpDR2tPGSzyMZ17Ig1iRkfJuY0nhxkQQde8pgePNiA7z7TQ==" + "resolved": "6.0.0", + "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" }, "System.Runtime.Extensions": { "type": "Transitive", diff --git a/PluralKit.Tests/packages.lock.json b/PluralKit.Tests/packages.lock.json index 122f7381..3feb41de 100644 --- a/PluralKit.Tests/packages.lock.json +++ b/PluralKit.Tests/packages.lock.json @@ -108,6 +108,15 @@ "App.Metrics.Formatters.InfluxDB": "4.1.0" } }, + "AppFact.SerilogOpenSearchSink": { + "type": "Transitive", + "resolved": "0.0.8", + "contentHash": "RI3lfmvAwhqrYwy5KPqsBT/tB/opSzeoVH2WUfvGKNBpl6ILCw/5wE8+19L+XMzBFVqgZ5QmkQ2PqTzG9I/ckA==", + "dependencies": { + "OpenSearch.Client": "1.4.0", + "Serilog": "2.12.0" + } + }, "Autofac": { "type": "Transitive", "resolved": "6.0.0", @@ -595,6 +604,24 @@ "resolved": "5.0.0", "contentHash": "c5JVjuVAm4f7E9Vj+v09Z9s2ZsqFDjBpcsyS3M9xRo0bEdm/LVZSzLxxNvfvAwRiiE8nwe1h2G4OwiwlzFKXlA==" }, + "OpenSearch.Client": { + "type": "Transitive", + "resolved": "1.4.0", + "contentHash": "91TXm+I8PzxT0yxkf0q5Quee5stVcIFys56pU8+M3+Kiakx+aiaTAlZJHfA3Oy6dMJndNkVm37IPKYCqN3dS4g==", + "dependencies": { + "OpenSearch.Net": "1.4.0" + } + }, + "OpenSearch.Net": { + "type": "Transitive", + "resolved": "1.4.0", + "contentHash": "xCM6m3aArN9gXIl2DvWXaDlbpjOBbgMeRPsAM7s1eDbWhu8wKNyfPNKSfep4JlYXkZ7N6Oi/+lmi+G3/SpcqlQ==", + "dependencies": { + "Microsoft.CSharp": "4.7.0", + "System.Buffers": "4.5.1", + "System.Diagnostics.DiagnosticSource": "6.0.1" + } + }, "Pipelines.Sockets.Unofficial": { "type": "Transitive", "resolved": "2.2.8", @@ -914,8 +941,8 @@ }, "System.Buffers": { "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "pL2ChpaRRWI/p4LXyy4RgeWlYF2sgfj/pnVMvBqwNFr5cXg7CXNnWZWxrOONLg8VGdFB8oB+EG2Qw4MLgTOe+A==" + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" }, "System.Collections": { "type": "Transitive", @@ -968,8 +995,11 @@ }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", - "resolved": "4.7.1", - "contentHash": "j81Lovt90PDAq8kLpaJfJKV/rWdWuEk6jfV+MBkee33vzYLEUsy4gXK8laa9V2nZlLM9VM9yA/OOQxxPEJKAMw==" + "resolved": "6.0.1", + "contentHash": "KiLYDu2k2J82Q9BJpWiuQqCkFjRBWVq4jDzKKWawVi9KWzyD0XG3cmfX0vqTQlL14Wi9EufJrbL0+KCLTbqWiQ==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } }, "System.Diagnostics.Tools": { "type": "Transitive", @@ -1333,8 +1363,8 @@ }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", - "resolved": "4.7.1", - "contentHash": "zOHkQmzPCn5zm/BH+cxC1XbUS3P4Yoi3xzW7eRgVpDR2tPGSzyMZ17Ig1iRkfJuY0nhxkQQde8pgePNiA7z7TQ==" + "resolved": "6.0.0", + "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" }, "System.Runtime.Extensions": { "type": "Transitive", @@ -1737,6 +1767,7 @@ "dependencies": { "App.Metrics": "[4.1.0, )", "App.Metrics.Reporting.InfluxDB": "[4.1.0, )", + "AppFact.SerilogOpenSearchSink": "[0.0.8, )", "Autofac": "[6.0.0, )", "Autofac.Extensions.DependencyInjection": "[7.1.0, )", "Dapper": "[2.0.35, )", From 0600ae00fff55749db1ae321dd3c180042846433 Mon Sep 17 00:00:00 2001 From: alyssa Date: Thu, 21 Nov 2024 10:40:33 +0900 Subject: [PATCH 15/37] fix(gateway): remove shard_id from pluralkit_gateway_events metric --- services/gateway/src/discord/gateway.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/services/gateway/src/discord/gateway.rs b/services/gateway/src/discord/gateway.rs index 40ed9bf7..a50fda8e 100644 --- a/services/gateway/src/discord/gateway.rs +++ b/services/gateway/src/discord/gateway.rs @@ -86,12 +86,18 @@ pub async fn runner( while let Some(item) = shard.next_event(EventTypeFlags::all()).await { match item { Ok(event) => { + // event_type * shard_id is too many labels and prometheus fails to query it + // so we split it into two metrics counter!( - "pluralkit_gateway_events", - "shard_id" => shard.id().number().to_string(), + "pluralkit_gateway_events_type", "event_type" => serde_variant::to_variant_name(&event.kind()).unwrap(), ) .increment(1); + counter!( + "pluralkit_gateway_events_shard", + "shard_id" => shard.id().number().to_string(), + ) + .increment(1); if let Err(error) = shard_state .handle_event(shard.id().number(), event.clone()) .await From 701bafdf97349fef13ee2ba4651fe5b3a5fc80cc Mon Sep 17 00:00:00 2001 From: alyssa Date: Thu, 21 Nov 2024 10:45:03 +0900 Subject: [PATCH 16/37] feat: add global component tag to rust json logs, add sentry, some cleanup --- Cargo.lock | 219 ++++++++++++++++++++++++++++++ Cargo.toml | 1 + lib/libpk/Cargo.toml | 3 + lib/libpk/src/_config.rs | 3 + lib/libpk/src/lib.rs | 54 +++++++- services/api/src/main.rs | 61 +++++---- services/avatars/src/cleanup.rs | 10 +- services/avatars/src/main.rs | 8 +- services/dispatch/src/main.rs | 2 + services/gateway/src/cache_api.rs | 1 - services/gateway/src/main.rs | 8 +- 11 files changed, 321 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de637542..cb904814 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -637,6 +637,16 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "serde", + "uuid", +] + [[package]] name = "der" version = "0.7.9" @@ -815,6 +825,18 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "findshlibs" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" +dependencies = [ + "cc", + "lazy_static", + "libc", + "winapi", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1251,6 +1273,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "hostname" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" +dependencies = [ + "cfg-if", + "libc", + "windows", +] + [[package]] name = "http" version = "0.2.8" @@ -1553,6 +1586,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json-subscriber" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91d0a86fd2fba3a8721e7086b2c9fceb0994f71cdbd64ad2dfc1b202a5c062b4" +dependencies = [ + "serde", + "serde_json", + "tracing", + "tracing-core", + "tracing-serde", + "tracing-subscriber", + "uuid", +] + [[package]] name = "json5" version = "0.4.1" @@ -1592,13 +1640,16 @@ dependencies = [ "anyhow", "config", "fred", + "json-subscriber", "lazy_static", "metrics", "metrics-exporter-prometheus", "prost", "prost-build", "prost-types", + "sentry", "serde", + "serde_json", "sqlx", "time", "tokio", @@ -1971,6 +2022,17 @@ dependencies = [ "hashbrown 0.13.2", ] +[[package]] +name = "os_info" +version = "3.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" +dependencies = [ + "log", + "serde", + "windows-sys 0.52.0", +] + [[package]] name = "overload" version = "0.1.1" @@ -2518,6 +2580,7 @@ checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ "base64 0.22.1", "bytes", + "futures-channel", "futures-core", "futures-util", "http 1.1.0", @@ -2692,6 +2755,15 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.34" @@ -2735,6 +2807,7 @@ version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ + "log", "ring 0.17.8", "rustls-pki-types", "rustls-webpki 0.102.4", @@ -2748,6 +2821,7 @@ version = "0.23.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" dependencies = [ + "log", "once_cell", "ring 0.17.8", "rustls-pki-types", @@ -2881,6 +2955,115 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" +[[package]] +name = "sentry" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5484316556650182f03b43d4c746ce0e3e48074a21e2f51244b648b6542e1066" +dependencies = [ + "httpdate", + "reqwest 0.12.8", + "rustls 0.22.4", + "sentry-backtrace", + "sentry-contexts", + "sentry-core", + "sentry-debug-images", + "sentry-panic", + "sentry-tracing", + "tokio", + "ureq", + "webpki-roots 0.26.6", +] + +[[package]] +name = "sentry-backtrace" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40aa225bb41e2ec9d7c90886834367f560efc1af028f1c5478a6cce6a59c463a" +dependencies = [ + "backtrace", + "once_cell", + "regex", + "sentry-core", +] + +[[package]] +name = "sentry-contexts" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a8dd746da3d16cb8c39751619cefd4fcdbd6df9610f3310fd646b55f6e39910" +dependencies = [ + "hostname", + "libc", + "os_info", + "rustc_version", + "sentry-core", + "uname", +] + +[[package]] +name = "sentry-core" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "161283cfe8e99c8f6f236a402b9ccf726b201f365988b5bb637ebca0abbd4a30" +dependencies = [ + "once_cell", + "rand", + "sentry-types", + "serde", + "serde_json", +] + +[[package]] +name = "sentry-debug-images" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc6b25e945fcaa5e97c43faee0267eebda9f18d4b09a251775d8fef1086238a" +dependencies = [ + "findshlibs", + "once_cell", + "sentry-core", +] + +[[package]] +name = "sentry-panic" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc74f229c7186dd971a9491ffcbe7883544aa064d1589bd30b83fb856cd22d63" +dependencies = [ + "sentry-backtrace", + "sentry-core", +] + +[[package]] +name = "sentry-tracing" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3c5faf2103cd01eeda779ea439b68c4ee15adcdb16600836e97feafab362ec" +dependencies = [ + "sentry-backtrace", + "sentry-core", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "sentry-types" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d68cdf6bc41b8ff3ae2a9c4671e97426dcdd154cc1d4b6b72813f285d6b163f" +dependencies = [ + "debugid", + "hex", + "rand", + "serde", + "serde_json", + "thiserror", + "time", + "url", + "uuid", +] + [[package]] name = "serde" version = "1.0.203" @@ -3931,6 +4114,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +[[package]] +name = "uname" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8" +dependencies = [ + "libc", +] + [[package]] name = "unicode-bidi" version = "0.3.10" @@ -3982,6 +4174,21 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" +dependencies = [ + "base64 0.22.1", + "log", + "once_cell", + "rustls 0.23.10", + "rustls-pki-types", + "url", + "webpki-roots 0.26.6", +] + [[package]] name = "url" version = "2.5.2" @@ -3991,6 +4198,7 @@ dependencies = [ "form_urlencoded", "idna 0.5.0", "percent-encoding", + "serde", ] [[package]] @@ -4005,6 +4213,7 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ + "getrandom", "serde", ] @@ -4219,6 +4428,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index 314c0cee..2c7aa9de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ futures = "0.3.30" lazy_static = "1.4.0" metrics = "0.23.0" reqwest = { version = "0.12.7" , default-features = false, features = ["rustls-tls", "trust-dns"]} +sentry = { version = "0.34.0", default-features = false, features = ["backtrace", "contexts", "panic", "debug-images", "reqwest", "rustls"] } # replace native-tls with rustls serde = { version = "1.0.196", features = ["derive"] } serde_json = "1.0.117" signal-hook = "0.3.17" diff --git a/lib/libpk/Cargo.toml b/lib/libpk/Cargo.toml index 5a4b8eb5..a21bf55f 100644 --- a/lib/libpk/Cargo.toml +++ b/lib/libpk/Cargo.toml @@ -10,7 +10,9 @@ lazy_static = { workspace = true } metrics = { workspace = true } prost = { workspace = true } prost-types = { workspace = true } +sentry = { workspace = true } serde = { workspace = true } +serde_json = { workspace = true } sqlx = { workspace = true } time = { workspace = true } tokio = { workspace = true } @@ -20,6 +22,7 @@ twilight-model = { workspace = true } uuid = { workspace = true } config = "0.14.0" +json-subscriber = { version = "0.2.2", features = ["env-filter"] } metrics-exporter-prometheus = { version = "0.15.3", default-features = false, features = ["tokio", "http-listener", "tracing"] } [build-dependencies] diff --git a/lib/libpk/src/_config.rs b/lib/libpk/src/_config.rs index 2249b4aa..6f69399b 100644 --- a/lib/libpk/src/_config.rs +++ b/lib/libpk/src/_config.rs @@ -102,6 +102,9 @@ pub struct PKConfig { #[serde(default = "_json_log_default")] pub(crate) json_log: bool, + + #[serde(default)] + pub sentry_url: Option, } impl PKConfig { diff --git a/lib/libpk/src/lib.rs b/lib/libpk/src/lib.rs index e1a9561c..03806359 100644 --- a/lib/libpk/src/lib.rs +++ b/lib/libpk/src/lib.rs @@ -1,6 +1,7 @@ #![feature(let_chains)] use metrics_exporter_prometheus::PrometheusBuilder; -use tracing_subscriber::EnvFilter; +use sentry::IntoDsn; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; pub mod db; pub mod proto; @@ -9,12 +10,18 @@ pub mod util; pub mod _config; pub use crate::_config::CONFIG as config; +// functions in this file are only used by the main function below + pub fn init_logging(component: &str) -> anyhow::Result<()> { - // todo: fix component if config.json_log { - tracing_subscriber::fmt() - .json() - .with_env_filter(EnvFilter::from_default_env()) + let mut layer = json_subscriber::layer(); + layer.inner_layer_mut().add_static_field( + "component", + serde_json::Value::String(component.to_string()), + ); + tracing_subscriber::registry() + .with(layer) + .with(EnvFilter::from_default_env()) .init(); } else { tracing_subscriber::fmt() @@ -33,3 +40,40 @@ pub fn init_metrics() -> anyhow::Result<()> { } Ok(()) } + +pub fn init_sentry() -> sentry::ClientInitGuard { + sentry::init(sentry::ClientOptions { + dsn: config + .sentry_url + .clone() + .map(|u| u.into_dsn().unwrap()) + .flatten(), + release: sentry::release_name!(), + ..Default::default() + }) +} + +#[macro_export] +macro_rules! main { + ($component:expr) => { + fn main() -> anyhow::Result<()> { + let _sentry_guard = libpk::init_sentry(); + // we might also be able to use env!("CARGO_CRATE_NAME") here + libpk::init_logging($component)?; + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() + .block_on(async { + if let Err(err) = libpk::init_metrics() { + tracing::error!("failed to init metrics collector: {err}"); + }; + tracing::info!("hello world"); + if let Err(err) = real_main().await { + tracing::error!("failed to run service: {err}"); + }; + }); + Ok(()) + } + }; +} diff --git a/services/api/src/main.rs b/services/api/src/main.rs index e9e89815..bbf3c6ee 100644 --- a/services/api/src/main.rs +++ b/services/api/src/main.rs @@ -54,29 +54,9 @@ async fn rproxy( // this function is manually formatted for easier legibility of route_services #[rustfmt::skip] -#[tokio::main] -async fn main() -> anyhow::Result<()> { - libpk::init_logging("api")?; - libpk::init_metrics()?; - info!("hello world"); - - let db = libpk::db::init_data_db().await?; - let redis = libpk::db::init_redis().await?; - - let rproxy_uri = Uri::from_static(&libpk::config.api.as_ref().expect("missing api config").remote_url).to_string(); - let rproxy_client = hyper_util::client::legacy::Client::<(), ()>::builder(TokioExecutor::new()) - .build(HttpConnector::new()); - - let ctx = ApiContext { - db, - redis, - - rproxy_uri: rproxy_uri[..rproxy_uri.len() - 1].to_string(), - rproxy_client, - }; - +fn router(ctx: ApiContext) -> Router { // processed upside down (???) so we have to put middleware at the end - let app = Router::new() + Router::new() .route("/v2/systems/:system_id", get(rproxy)) .route("/v2/systems/:system_id", patch(rproxy)) .route("/v2/systems/:system_id/settings", get(rproxy)) @@ -143,9 +123,42 @@ async fn main() -> anyhow::Result<()> { .with_state(ctx) - .route("/", get(|| async { axum::response::Redirect::to("https://pluralkit.me/api") })); + .route("/", get(|| async { axum::response::Redirect::to("https://pluralkit.me/api") })) +} + +libpk::main!("api"); +async fn real_main() -> anyhow::Result<()> { + let db = libpk::db::init_data_db().await?; + let redis = libpk::db::init_redis().await?; + + let rproxy_uri = Uri::from_static( + &libpk::config + .api + .as_ref() + .expect("missing api config") + .remote_url, + ) + .to_string(); + let rproxy_client = hyper_util::client::legacy::Client::<(), ()>::builder(TokioExecutor::new()) + .build(HttpConnector::new()); + + let ctx = ApiContext { + db, + redis, + + rproxy_uri: rproxy_uri[..rproxy_uri.len() - 1].to_string(), + rproxy_client, + }; + + let app = router(ctx); + + let addr: &str = libpk::config + .api + .as_ref() + .expect("missing api config") + .addr + .as_ref(); - let addr: &str = libpk::config.api.as_ref().expect("missing api config").addr.as_ref(); let listener = tokio::net::TcpListener::bind(addr).await?; info!("listening on {}", addr); axum::serve(listener, app).await?; diff --git a/services/avatars/src/cleanup.rs b/services/avatars/src/cleanup.rs index eaa29527..be21ff6c 100644 --- a/services/avatars/src/cleanup.rs +++ b/services/avatars/src/cleanup.rs @@ -4,12 +4,8 @@ use sqlx::prelude::FromRow; use std::{sync::Arc, time::Duration}; use tracing::{error, info}; -#[tokio::main] -async fn main() -> anyhow::Result<()> { - libpk::init_logging("avatar_cleanup")?; - libpk::init_metrics()?; - info!("hello world"); - +libpk::main!("avatar_cleanup"); +async fn real_main() -> anyhow::Result<()> { let config = libpk::config .avatars .as_ref() @@ -129,7 +125,7 @@ async fn cleanup_job(pool: sqlx::PgPool, bucket: Arc) -> anyhow::Res } _ => { let status = cf_resp.status(); - println!("{:#?}", cf_resp.text().await?); + tracing::info!("raw response from cloudflare: {:#?}", cf_resp.text().await?); anyhow::bail!("cloudflare returned bad error code {}", status); } } diff --git a/services/avatars/src/main.rs b/services/avatars/src/main.rs index 44bbfe69..6a294f2d 100644 --- a/services/avatars/src/main.rs +++ b/services/avatars/src/main.rs @@ -142,12 +142,8 @@ pub struct AppState { config: Arc, } -#[tokio::main] -async fn main() -> anyhow::Result<()> { - libpk::init_logging("avatars")?; - libpk::init_metrics()?; - info!("hello world"); - +libpk::main!("avatars"); +async fn real_main() -> anyhow::Result<()> { let config = libpk::config .avatars .as_ref() diff --git a/services/dispatch/src/main.rs b/services/dispatch/src/main.rs index 1e28e7aa..a9d6d2af 100644 --- a/services/dispatch/src/main.rs +++ b/services/dispatch/src/main.rs @@ -19,6 +19,8 @@ use axum::{extract::State, http::Uri, routing::post, Json, Router}; mod logger; +// this package does not currently use libpk + #[tokio::main] async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt() diff --git a/services/gateway/src/cache_api.rs b/services/gateway/src/cache_api.rs index efbca0fc..df189a31 100644 --- a/services/gateway/src/cache_api.rs +++ b/services/gateway/src/cache_api.rs @@ -47,7 +47,6 @@ pub async fn run_server(cache: Arc) -> anyhow::Result<()> { get(|State(cache): State>, Path(guild_id): Path| async move { match cache.guild_permissions(Id::new(guild_id), libpk::config.discord.as_ref().expect("missing discord config").client_id).await { Ok(val) => { - println!("hh {}", Permissions::all().bits()); status_code(StatusCode::FOUND, to_string(&val.bits()).unwrap()) }, Err(err) => { diff --git a/services/gateway/src/main.rs b/services/gateway/src/main.rs index 3b995dbd..ae9e840e 100644 --- a/services/gateway/src/main.rs +++ b/services/gateway/src/main.rs @@ -18,12 +18,8 @@ mod cache_api; mod discord; mod logger; -#[tokio::main] -async fn main() -> anyhow::Result<()> { - libpk::init_logging("gateway")?; - libpk::init_metrics()?; - info!("hello world"); - +libpk::main!("gateway"); +async fn real_main() -> anyhow::Result<()> { let (shutdown_tx, shutdown_rx) = channel::<()>(); let shutdown_tx = Arc::new(shutdown_tx); From 1ebb7decdeba93a4e4520e1a21eb1ee767436e83 Mon Sep 17 00:00:00 2001 From: alyssa Date: Thu, 28 Nov 2024 10:10:17 +0900 Subject: [PATCH 17/37] fix: bind prometheus listener to ipv6 --- lib/libpk/src/lib.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/libpk/src/lib.rs b/lib/libpk/src/lib.rs index 03806359..19d4e1ee 100644 --- a/lib/libpk/src/lib.rs +++ b/lib/libpk/src/lib.rs @@ -1,4 +1,6 @@ #![feature(let_chains)] +use std::net::SocketAddr; + use metrics_exporter_prometheus::PrometheusBuilder; use sentry::IntoDsn; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; @@ -34,9 +36,9 @@ pub fn init_logging(component: &str) -> anyhow::Result<()> { pub fn init_metrics() -> anyhow::Result<()> { if config.run_metrics_server { - // automatically spawns a http listener at :9000 - let builder = PrometheusBuilder::new(); - builder.install()?; + PrometheusBuilder::new() + .with_http_listener("[::]:9000".parse::().unwrap()) + .install()?; } Ok(()) } From 4355a92d29eee10f5c6238a7e18b30b6ef19c9fe Mon Sep 17 00:00:00 2001 From: alyssa Date: Fri, 6 Dec 2024 06:00:46 +0900 Subject: [PATCH 18/37] feat(docs): update donation links in faq --- docs/content/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/faq.md b/docs/content/faq.md index 7e486d59..27b1ef3a 100644 --- a/docs/content/faq.md +++ b/docs/content/faq.md @@ -24,7 +24,7 @@ You can suggest features in the [support server](https://discord.gg/PczBt78)'s ` We also track feature requests through [Github Issues](https://github.com/PluralKit/PluralKit/issues). Feel free to open issue reports or feature requests there as well. ### How can I support the bot's development? -I (the bot author, [Ske](https://twitter.com/floofstrid)) have a Patreon. The income from there goes towards server hosting, domains, infrastructure, my Monster Energy addiction, et cetera. There are no benefits. There might never be any. But nevertheless, it can be found here: [https://www.patreon.com/floofstrid](https://www.patreon.com/floofstrid) +We accept donations on [Patreon](https://patreon.com/pluralkit/) (recurring) and [Buy Me A Coffee](https://buymeacoffee.com/pluralkit/) (one-time). Any funds donated here will be used to pay for server hosting and (if anything is left over) development work. ### Can I recover my system if I lose access to my Discord account? Yes, through one of two methods. Both require you to do preparations **before** you lose the account. From 2d17582e0588922a18ae912c1c080f111a2eca3c Mon Sep 17 00:00:00 2001 From: rladenson Date: Mon, 18 Nov 2024 00:53:33 -0700 Subject: [PATCH 19/37] fix: include "pronouns" in system privacy blurb --- PluralKit.Bot/Commands/SystemEdit.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PluralKit.Bot/Commands/SystemEdit.cs b/PluralKit.Bot/Commands/SystemEdit.cs index 264761ec..047901e9 100644 --- a/PluralKit.Bot/Commands/SystemEdit.cs +++ b/PluralKit.Bot/Commands/SystemEdit.cs @@ -842,7 +842,7 @@ public class SystemEdit .Field(new Embed.Field("Current fronter(s)", target.FrontPrivacy.Explanation())) .Field(new Embed.Field("Front/switch history", target.FrontHistoryPrivacy.Explanation())) .Description( - "To edit privacy settings, use the command:\n`pk;system privacy `\n\n- `subject` is one of `name`, `avatar`, `description`, `banner`, `list`, `front`, `fronthistory`, `groups`, or `all` \n- `level` is either `public` or `private`."); + "To edit privacy settings, use the command:\n`pk;system privacy `\n\n- `subject` is one of `name`, `avatar`, `description`, `banner`, `pronouns`, `list`, `front`, `fronthistory`, `groups`, or `all` \n- `level` is either `public` or `private`."); return ctx.Reply(embed: eb.Build()); } From 2c4dea29be3f84d708b421b636c82d41ae133fdf Mon Sep 17 00:00:00 2001 From: rladenson Date: Mon, 18 Nov 2024 18:45:13 -0700 Subject: [PATCH 20/37] feat: add raw and plaintext formats to member avatar, proxy avatar, and guild avatar --- .../Context/ContextArgumentsExt.cs | 9 ++++++ .../CommandSystem/Context/ContextAvatarExt.cs | 4 +++ PluralKit.Bot/Commands/MemberAvatar.cs | 28 +++++++++++++++---- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/PluralKit.Bot/CommandSystem/Context/ContextArgumentsExt.cs b/PluralKit.Bot/CommandSystem/Context/ContextArgumentsExt.cs index 1a958c8e..982eec77 100644 --- a/PluralKit.Bot/CommandSystem/Context/ContextArgumentsExt.cs +++ b/PluralKit.Bot/CommandSystem/Context/ContextArgumentsExt.cs @@ -98,6 +98,15 @@ public static class ContextArgumentsExt return ReplyFormat.Standard; } + public static ReplyFormat PeekMatchFormat(this Context ctx) + { + int ptr1 = ctx.Parameters._ptr; + int ptr2 = ctx.Parameters._ptr; + if (ctx.PeekMatch(ref ptr1, new[] { "r", "raw" }) || ctx.MatchFlag("r", "raw")) return ReplyFormat.Raw; + if (ctx.PeekMatch(ref ptr2, new[] { "pt", "plaintext" }) || ctx.MatchFlag("pt", "plaintext")) return ReplyFormat.Plaintext; + return ReplyFormat.Standard; + } + public static bool MatchToggle(this Context ctx, bool? defaultValue = null) { var value = ctx.MatchToggleOrNull(defaultValue); diff --git a/PluralKit.Bot/CommandSystem/Context/ContextAvatarExt.cs b/PluralKit.Bot/CommandSystem/Context/ContextAvatarExt.cs index ca2ff912..3501b53b 100644 --- a/PluralKit.Bot/CommandSystem/Context/ContextAvatarExt.cs +++ b/PluralKit.Bot/CommandSystem/Context/ContextAvatarExt.cs @@ -15,6 +15,10 @@ public static class ContextAvatarExt return new ParsedImage { Url = url, Source = AvatarSource.User, SourceUser = user }; } + // If we have raw or plaintext, don't try to parse as a URL + if (ctx.PeekMatchFormat() != ReplyFormat.Standard) + return null; + // If we have a positional argument, try to parse it as a URL var arg = ctx.RemainderOrNull(); if (arg != null) diff --git a/PluralKit.Bot/Commands/MemberAvatar.cs b/PluralKit.Bot/Commands/MemberAvatar.cs index 66c17de1..b81c4754 100644 --- a/PluralKit.Bot/Commands/MemberAvatar.cs +++ b/PluralKit.Bot/Commands/MemberAvatar.cs @@ -86,12 +86,28 @@ public class MemberAvatar if (location == MemberAvatarLocation.Server) field += $" (for {ctx.Guild.Name})"; - var eb = new EmbedBuilder() - .Title($"{target.NameFor(ctx)}'s {field}") - .Image(new Embed.EmbedImage(currentValue?.TryGetCleanCdnUrl())); - if (target.System == ctx.System?.Id) - eb.Description($"To clear, use `pk;member {target.Reference(ctx)} {location.Command()} clear`."); - await ctx.Reply(embed: eb.Build()); + var format = ctx.MatchFormat(); + if (format == ReplyFormat.Raw) + { + await ctx.Reply($"`{currentValue?.TryGetCleanCdnUrl()}`"); + } + else if (format == ReplyFormat.Plaintext) + { + var eb = new EmbedBuilder() + .Description($"Showing {field} link for member {target.NameFor(ctx)} (`{target.DisplayHid(ctx.Config)}`)"); + await ctx.Reply($"<{currentValue?.TryGetCleanCdnUrl()}>", embed: eb.Build()); + return; + } + else if (format == ReplyFormat.Standard) + { + var eb = new EmbedBuilder() + .Title($"{target.NameFor(ctx)}'s {field}") + .Image(new Embed.EmbedImage(currentValue?.TryGetCleanCdnUrl())); + if (target.System == ctx.System?.Id) + eb.Description($"To clear, use `pk;member {target.Reference(ctx)} {location.Command()} clear`."); + await ctx.Reply(embed: eb.Build()); + } + else throw new PKError("Format Not Recognized"); } public async Task ServerAvatar(Context ctx, PKMember target) From cd32518c7308bd8bdc4bda0619ed9a55151729f2 Mon Sep 17 00:00:00 2001 From: rladenson Date: Mon, 18 Nov 2024 20:11:12 -0700 Subject: [PATCH 21/37] feat: add raw and plaintext formats to system pfp --- PluralKit.Bot/Commands/SystemEdit.cs | 29 ++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/PluralKit.Bot/Commands/SystemEdit.cs b/PluralKit.Bot/Commands/SystemEdit.cs index 047901e9..1cb7ca36 100644 --- a/PluralKit.Bot/Commands/SystemEdit.cs +++ b/PluralKit.Bot/Commands/SystemEdit.cs @@ -565,19 +565,28 @@ public class SystemEdit async Task ShowIcon() { if ((target.AvatarUrl?.Trim() ?? "").Length > 0) - { - var eb = new EmbedBuilder() - .Title("System icon") - .Image(new Embed.EmbedImage(target.AvatarUrl.TryGetCleanCdnUrl())); - if (target.Id == ctx.System?.Id) - eb.Description("To clear, use `pk;system icon clear`."); - await ctx.Reply(embed: eb.Build()); - } + switch (ctx.MatchFormat()) + { + case ReplyFormat.Raw: + await ctx.Reply($"`{target.AvatarUrl.TryGetCleanCdnUrl()}`"); + break; + case ReplyFormat.Plaintext: + var ebP = new EmbedBuilder() + .Description($"Showing icon for system {target.NameFor(ctx)}"); + await ctx.Reply(text: $"<{target.AvatarUrl.TryGetCleanCdnUrl()}>", embed: ebP.Build()); + break; + default: + var ebS = new EmbedBuilder() + .Title("System icon") + .Image(new Embed.EmbedImage(target.AvatarUrl.TryGetCleanCdnUrl())); + if (target.Id == ctx.System?.Id) + ebS.Description("To clear, use `pk;system icon clear`."); + await ctx.Reply(embed: ebS.Build()); + break; + } else - { throw new PKSyntaxError( "This system does not have an icon set. Set one by attaching an image to this command, or by passing an image URL or @mention."); - } } if (target != null && target?.Id != ctx.System?.Id) From 1ae6c8ff54b4ed8adbbd1eefd981fa62d4636ca7 Mon Sep 17 00:00:00 2001 From: rladenson Date: Mon, 18 Nov 2024 22:44:13 -0700 Subject: [PATCH 22/37] feat: add raw and pt format to group icon --- PluralKit.Bot/Commands/Groups.cs | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/PluralKit.Bot/Commands/Groups.cs b/PluralKit.Bot/Commands/Groups.cs index a6105275..75d94ad5 100644 --- a/PluralKit.Bot/Commands/Groups.cs +++ b/PluralKit.Bot/Commands/Groups.cs @@ -312,21 +312,28 @@ public class Groups ctx.CheckSystemPrivacy(target.System, target.IconPrivacy); if ((target.Icon?.Trim() ?? "").Length > 0) - { - var eb = new EmbedBuilder() - .Title("Group icon") - .Image(new Embed.EmbedImage(target.Icon.TryGetCleanCdnUrl())); - - if (target.System == ctx.System?.Id) - eb.Description($"To clear, use `pk;group {target.Reference(ctx)} icon -clear`."); - - await ctx.Reply(embed: eb.Build()); - } + switch (ctx.MatchFormat()) + { + case ReplyFormat.Raw: + await ctx.Reply($"`{target.Icon.TryGetCleanCdnUrl()}`"); + break; + case ReplyFormat.Plaintext: + var ebP = new EmbedBuilder() + .Description($"Showing avatar for group {target.NameFor(ctx)}"); + await ctx.Reply(text: $"<{target.Icon.TryGetCleanCdnUrl()}>", embed: ebP.Build()); + break; + default: + var ebS = new EmbedBuilder() + .Title("Group icon") + .Image(new Embed.EmbedImage(target.Icon.TryGetCleanCdnUrl())); + if (target.System == ctx.System?.Id) + ebS.Description($"To clear, use `pk;group {target.Reference(ctx)} icon -clear`."); + await ctx.Reply(embed: ebS.Build()); + break; + } else - { throw new PKSyntaxError( "This group does not have an avatar set. Set one by attaching an image to this command, or by passing an image URL or @mention."); - } } if (ctx.MatchClear()) From b972a0238ce7b1cf62d886db6f9a2e99473b80a0 Mon Sep 17 00:00:00 2001 From: rladenson Date: Mon, 18 Nov 2024 22:58:02 -0700 Subject: [PATCH 23/37] feat: add raw and pt formats to group pfp and banner --- PluralKit.Bot/Commands/Groups.cs | 35 +++++++++++++++++++------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/PluralKit.Bot/Commands/Groups.cs b/PluralKit.Bot/Commands/Groups.cs index 75d94ad5..e1c5986a 100644 --- a/PluralKit.Bot/Commands/Groups.cs +++ b/PluralKit.Bot/Commands/Groups.cs @@ -385,22 +385,29 @@ public class Groups { ctx.CheckSystemPrivacy(target.System, target.BannerPrivacy); - if ((target.BannerImage?.Trim() ?? "").Length > 0) - { - var eb = new EmbedBuilder() - .Title("Group banner image") - .Image(new Embed.EmbedImage(target.BannerImage)); - - if (target.System == ctx.System?.Id) - eb.Description($"To clear, use `pk;group {target.Reference(ctx)} banner clear`."); - - await ctx.Reply(embed: eb.Build()); - } + if ((target.Icon?.Trim() ?? "").Length > 0) + switch (ctx.MatchFormat()) + { + case ReplyFormat.Raw: + await ctx.Reply($"`{target.BannerImage.TryGetCleanCdnUrl()}`"); + break; + case ReplyFormat.Plaintext: + var ebP = new EmbedBuilder() + .Description($"Showing banner for group {target.NameFor(ctx)}"); + await ctx.Reply(text: $"<{target.BannerImage.TryGetCleanCdnUrl()}>", embed: ebP.Build()); + break; + default: + var ebS = new EmbedBuilder() + .Title("Group banner image") + .Image(new Embed.EmbedImage(target.BannerImage.TryGetCleanCdnUrl())); + if (target.System == ctx.System?.Id) + ebS.Description($"To clear, use `pk;group {target.Reference(ctx)} banner clear`."); + await ctx.Reply(embed: ebS.Build()); + break; + } else - { throw new PKSyntaxError( - "This group does not have a banner image set. Set one by attaching an image to this command, or by passing an image URL."); - } + "This group does not have a banner image set. Set one by attaching an image to this command, or by passing an image URL or @mention."); } if (ctx.MatchClear()) From f6819d5caf5e85c9542bcab1431fb6960784f99e Mon Sep 17 00:00:00 2001 From: rladenson Date: Mon, 18 Nov 2024 23:18:27 -0700 Subject: [PATCH 24/37] feat: add raw and pt format to system servericon --- PluralKit.Bot/Commands/SystemEdit.cs | 29 ++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/PluralKit.Bot/Commands/SystemEdit.cs b/PluralKit.Bot/Commands/SystemEdit.cs index 1cb7ca36..5d151d5a 100644 --- a/PluralKit.Bot/Commands/SystemEdit.cs +++ b/PluralKit.Bot/Commands/SystemEdit.cs @@ -648,19 +648,28 @@ public class SystemEdit var settings = await ctx.Repository.GetSystemGuild(ctx.Guild.Id, target.Id); if ((settings.AvatarUrl?.Trim() ?? "").Length > 0) - { - var eb = new EmbedBuilder() - .Title("System server icon") - .Image(new Embed.EmbedImage(settings.AvatarUrl.TryGetCleanCdnUrl())); - if (target.Id == ctx.System?.Id) - eb.Description("To clear, use `pk;system servericon clear`."); - await ctx.Reply(embed: eb.Build()); - } + switch (ctx.MatchFormat()) + { + case ReplyFormat.Raw: + await ctx.Reply($"`{settings.AvatarUrl.TryGetCleanCdnUrl()}`"); + break; + case ReplyFormat.Plaintext: + var ebP = new EmbedBuilder() + .Description($"Showing icon for system {target.NameFor(ctx)}"); + await ctx.Reply(text: $"<{settings.AvatarUrl.TryGetCleanCdnUrl()}>", embed: ebP.Build()); + break; + default: + var ebS = new EmbedBuilder() + .Title("System server icon") + .Image(new Embed.EmbedImage(settings.AvatarUrl.TryGetCleanCdnUrl())); + if (target.Id == ctx.System?.Id) + ebS.Description("To clear, use `pk;system servericon clear`."); + await ctx.Reply(embed: ebS.Build()); + break; + } else - { throw new PKSyntaxError( "This system does not have a icon specific to this server. Set one by attaching an image to this command, or by passing an image URL or @mention."); - } } ctx.CheckGuildContext(); From 8db785f98acd65dd85844ccae02113ffea6985d5 Mon Sep 17 00:00:00 2001 From: rladenson Date: Mon, 18 Nov 2024 23:27:44 -0700 Subject: [PATCH 25/37] feat: add raw and pt formats to system banner --- PluralKit.Bot/Commands/SystemEdit.cs | 33 +++++++++++++++++----------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/PluralKit.Bot/Commands/SystemEdit.cs b/PluralKit.Bot/Commands/SystemEdit.cs index 5d151d5a..e8b244e2 100644 --- a/PluralKit.Bot/Commands/SystemEdit.cs +++ b/PluralKit.Bot/Commands/SystemEdit.cs @@ -694,24 +694,31 @@ public class SystemEdit var isOwnSystem = target.Id == ctx.System?.Id; - if (!ctx.HasNext() && ctx.Message.Attachments.Length == 0) + if ((!ctx.HasNext() && ctx.Message.Attachments.Length == 0) || ctx.PeekMatchFormat() != ReplyFormat.Standard) { if ((target.BannerImage?.Trim() ?? "").Length > 0) - { - var eb = new EmbedBuilder() - .Title("System banner image") - .Image(new Embed.EmbedImage(target.BannerImage)); - - if (isOwnSystem) - eb.Description("To clear, use `pk;system banner clear`."); - - await ctx.Reply(embed: eb.Build()); - } + switch (ctx.MatchFormat()) + { + case ReplyFormat.Raw: + await ctx.Reply($"`{target.BannerImage.TryGetCleanCdnUrl()}`"); + break; + case ReplyFormat.Plaintext: + var ebP = new EmbedBuilder() + .Description($"Showing banner for system {target.NameFor(ctx)}"); + await ctx.Reply(text: $"<{target.BannerImage.TryGetCleanCdnUrl()}>", embed: ebP.Build()); + break; + default: + var ebS = new EmbedBuilder() + .Title("System banner image") + .Image(new Embed.EmbedImage(target.BannerImage.TryGetCleanCdnUrl())); + if (target.Id == ctx.System?.Id) + ebS.Description("To clear, use `pk;system banner clear`."); + await ctx.Reply(embed: ebS.Build()); + break; + } else - { throw new PKSyntaxError("This system does not have a banner image set." + (isOwnSystem ? "Set one by attaching an image to this command, or by passing an image URL or @mention." : "")); - } return; } From ba279612ca1754655868f632d4306b0a32a42f8f Mon Sep 17 00:00:00 2001 From: rladenson Date: Mon, 18 Nov 2024 23:43:04 -0700 Subject: [PATCH 26/37] feat: add raw and pt formats to member banners --- PluralKit.Bot/Commands/MemberEdit.cs | 30 ++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/PluralKit.Bot/Commands/MemberEdit.cs b/PluralKit.Bot/Commands/MemberEdit.cs index b57f1bae..22e60573 100644 --- a/PluralKit.Bot/Commands/MemberEdit.cs +++ b/PluralKit.Bot/Commands/MemberEdit.cs @@ -229,18 +229,28 @@ public class MemberEdit async Task ShowBannerImage() { if ((target.BannerImage?.Trim() ?? "").Length > 0) - { - var eb = new EmbedBuilder() - .Title($"{target.NameFor(ctx)}'s banner image") - .Image(new Embed.EmbedImage(target.BannerImage)) - .Description($"To clear, use `pk;member {target.Reference(ctx)} banner clear`."); - await ctx.Reply(embed: eb.Build()); - } + switch (ctx.MatchFormat()) + { + case ReplyFormat.Raw: + await ctx.Reply($"`{target.BannerImage.TryGetCleanCdnUrl()}`"); + break; + case ReplyFormat.Plaintext: + var ebP = new EmbedBuilder() + .Description($"Showing banner for member {target.NameFor(ctx)} (`{target.Id}`)"); + await ctx.Reply(text: $"<{target.BannerImage.TryGetCleanCdnUrl()}>", embed: ebP.Build()); + break; + default: + var ebS = new EmbedBuilder() + .Title($"{target.NameFor(ctx)}'s banner image") + .Image(new Embed.EmbedImage(target.BannerImage.TryGetCleanCdnUrl())); + if (target.System == ctx.System?.Id) + ebS.Description($"To clear, use `pk;member {target.Reference(ctx)} banner clear`."); + await ctx.Reply(embed: ebS.Build()); + break; + } else - { throw new PKSyntaxError( - "This member does not have a banner image set. Set one by attaching an image to this command, or by passing an image URL."); - } + "This member does not have a banner image set." + ((target.System == ctx.System?.Id) ? " Set one by attaching an image to this command, or by passing an image URL." : "")); } if (ctx.MatchClear() && await ctx.ConfirmClear("this member's banner image")) From 3bf120323497bfe05de0ebd21e3f2c465a5f31be Mon Sep 17 00:00:00 2001 From: rladenson Date: Mon, 18 Nov 2024 23:45:37 -0700 Subject: [PATCH 27/37] fix: show ids in pt embeds for image links --- PluralKit.Bot/Commands/Groups.cs | 4 ++-- PluralKit.Bot/Commands/MemberEdit.cs | 2 +- PluralKit.Bot/Commands/SystemEdit.cs | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/PluralKit.Bot/Commands/Groups.cs b/PluralKit.Bot/Commands/Groups.cs index e1c5986a..0716a7c6 100644 --- a/PluralKit.Bot/Commands/Groups.cs +++ b/PluralKit.Bot/Commands/Groups.cs @@ -319,7 +319,7 @@ public class Groups break; case ReplyFormat.Plaintext: var ebP = new EmbedBuilder() - .Description($"Showing avatar for group {target.NameFor(ctx)}"); + .Description($"Showing avatar for group {target.NameFor(ctx)} (`{target.DisplayHid(ctx.Config)}`)"); await ctx.Reply(text: $"<{target.Icon.TryGetCleanCdnUrl()}>", embed: ebP.Build()); break; default: @@ -393,7 +393,7 @@ public class Groups break; case ReplyFormat.Plaintext: var ebP = new EmbedBuilder() - .Description($"Showing banner for group {target.NameFor(ctx)}"); + .Description($"Showing banner for group {target.NameFor(ctx)} (`{target.DisplayHid(ctx.Config)}`)"); await ctx.Reply(text: $"<{target.BannerImage.TryGetCleanCdnUrl()}>", embed: ebP.Build()); break; default: diff --git a/PluralKit.Bot/Commands/MemberEdit.cs b/PluralKit.Bot/Commands/MemberEdit.cs index 22e60573..dc8439ff 100644 --- a/PluralKit.Bot/Commands/MemberEdit.cs +++ b/PluralKit.Bot/Commands/MemberEdit.cs @@ -236,7 +236,7 @@ public class MemberEdit break; case ReplyFormat.Plaintext: var ebP = new EmbedBuilder() - .Description($"Showing banner for member {target.NameFor(ctx)} (`{target.Id}`)"); + .Description($"Showing banner for member {target.NameFor(ctx)} (`{target.DisplayHid(ctx.Config)}`)"); await ctx.Reply(text: $"<{target.BannerImage.TryGetCleanCdnUrl()}>", embed: ebP.Build()); break; default: diff --git a/PluralKit.Bot/Commands/SystemEdit.cs b/PluralKit.Bot/Commands/SystemEdit.cs index e8b244e2..51fa3e50 100644 --- a/PluralKit.Bot/Commands/SystemEdit.cs +++ b/PluralKit.Bot/Commands/SystemEdit.cs @@ -572,7 +572,7 @@ public class SystemEdit break; case ReplyFormat.Plaintext: var ebP = new EmbedBuilder() - .Description($"Showing icon for system {target.NameFor(ctx)}"); + .Description($"Showing icon for system {target.NameFor(ctx)} (`{target.DisplayHid(ctx.Config)}`)"); await ctx.Reply(text: $"<{target.AvatarUrl.TryGetCleanCdnUrl()}>", embed: ebP.Build()); break; default: @@ -655,7 +655,7 @@ public class SystemEdit break; case ReplyFormat.Plaintext: var ebP = new EmbedBuilder() - .Description($"Showing icon for system {target.NameFor(ctx)}"); + .Description($"Showing icon for system {target.NameFor(ctx)} (`{target.DisplayHid(ctx.Config)}`)"); await ctx.Reply(text: $"<{settings.AvatarUrl.TryGetCleanCdnUrl()}>", embed: ebP.Build()); break; default: @@ -704,7 +704,7 @@ public class SystemEdit break; case ReplyFormat.Plaintext: var ebP = new EmbedBuilder() - .Description($"Showing banner for system {target.NameFor(ctx)}"); + .Description($"Showing banner for system {target.NameFor(ctx)} (`{target.DisplayHid(ctx.Config)}`)"); await ctx.Reply(text: $"<{target.BannerImage.TryGetCleanCdnUrl()}>", embed: ebP.Build()); break; default: From ee43ad8f83f5e008eb8e0163b6634e4defb7a779 Mon Sep 17 00:00:00 2001 From: Petal Ladenson Date: Tue, 10 Oct 2023 22:03:36 -0600 Subject: [PATCH 28/37] feat: logclean for annabelle's embed log format --- PluralKit.Bot/Services/LoggerCleanService.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/PluralKit.Bot/Services/LoggerCleanService.cs b/PluralKit.Bot/Services/LoggerCleanService.cs index fc5855c9..d4fc3aa5 100644 --- a/PluralKit.Bot/Services/LoggerCleanService.cs +++ b/PluralKit.Bot/Services/LoggerCleanService.cs @@ -42,6 +42,7 @@ public class LoggerCleanService private static readonly Regex _ProBotRegex = new("\\*\\*Message sent by <@(\\d{17,19})> deleted in <#\\d{17,19}>.\\*\\*"); private static readonly Regex _DozerRegex = new("Message ID: (\\d{17,19}) - (\\d{17,19})\nUserID: (\\d{17,19})"); private static readonly Regex _SkyraRegex = new("https://discord.com/channels/(\\d{17,19})/(\\d{17,19})/(\\d{17,19})"); + private static readonly Regex _AnnabelleRegex = new("```\n(\\d{17,19})\n```"); private static readonly Regex _VortexRegex = new("`\\[(\\d\\d:\\d\\d:\\d\\d)\\]` .* \\(ID:(\\d{17,19})\\).* <#\\d{17,19}>:"); @@ -79,6 +80,7 @@ public class LoggerCleanService new LoggerBot("ProBot Prime", 567703512763334685, fuzzyExtractFunc: ExtractProBot), // webhook (?) new LoggerBot("Dozer", 356535250932858885, ExtractDozer), new LoggerBot("Skyra", 266624760782258186, ExtractSkyra), + new LoggerBot("Annabelle", 231241068383961088, ExtractAnnabelle), }.ToDictionary(b => b.Id); private static Dictionary _botsByApplicationId @@ -407,6 +409,14 @@ public class LoggerCleanService return match.Success ? ulong.Parse(match.Groups[3].Value) : null; } + private static ulong? ExtractAnnabelle(Message msg) + { + var embed = msg.Embeds?.FirstOrDefault(); + if (embed?.Author?.Name == null || !embed.Author.Name.EndsWith("Deleted Message")) return null; + var match = _AnnabelleRegex.Match(embed.Fields[2].Value); + return match.Success ? ulong.Parse(match.Groups[1].Value) : null; + } + public class LoggerBot { public ulong Id; From 79ea22f52703b0194fd699f6358ba8f08e854ec8 Mon Sep 17 00:00:00 2001 From: Petal Ladenson Date: Tue, 10 Oct 2023 23:19:41 -0600 Subject: [PATCH 29/37] feat: let logbots have both fuzzy and precise matching --- PluralKit.Bot/Services/LoggerCleanService.cs | 32 +++++++++++--------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/PluralKit.Bot/Services/LoggerCleanService.cs b/PluralKit.Bot/Services/LoggerCleanService.cs index d4fc3aa5..b67c054a 100644 --- a/PluralKit.Bot/Services/LoggerCleanService.cs +++ b/PluralKit.Bot/Services/LoggerCleanService.cs @@ -80,7 +80,7 @@ public class LoggerCleanService new LoggerBot("ProBot Prime", 567703512763334685, fuzzyExtractFunc: ExtractProBot), // webhook (?) new LoggerBot("Dozer", 356535250932858885, ExtractDozer), new LoggerBot("Skyra", 266624760782258186, ExtractSkyra), - new LoggerBot("Annabelle", 231241068383961088, ExtractAnnabelle), + new LoggerBot("Annabelle", 231241068383961088, ExtractAnnabelle), }.ToDictionary(b => b.Id); private static Dictionary _botsByApplicationId @@ -121,6 +121,7 @@ public class LoggerCleanService try { // We try two ways of extracting the actual message, depending on the bots + // Some bots have different log formats so we check for both types of extract function if (bot.FuzzyExtractFunc != null) { // Some bots (Carl, Circle, etc) only give us a user ID and a rough timestamp, so we try our best to @@ -129,20 +130,22 @@ public class LoggerCleanService // delete event timestamp, which is... good enough, I think? Potential for false positives and negatives // 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; + if (fuzzy != null) + { - _logger.Debug("Fuzzy logclean for {BotName} on {MessageId}: {@FuzzyExtractResult}", - bot.Name, msg.Id, fuzzy); + _logger.Debug("Fuzzy logclean for {BotName} on {MessageId}: {@FuzzyExtractResult}", + bot.Name, msg.Id, fuzzy); - var exists = await _redis.HasLogCleanup(fuzzy.Value.User, msg.GuildId.Value); + var exists = await _redis.HasLogCleanup(fuzzy.Value.User, msg.GuildId.Value); - // If we didn't find a corresponding message, bail - if (!exists) return; + // If we didn't find a corresponding message, bail + if (!exists) return; - // Otherwise, we can *reasonably assume* that this is a logged deletion, so delete the log message. - await _client.DeleteMessage(msg.ChannelId, msg.Id); + // Otherwise, we can *reasonably assume* that this is a logged deletion, so delete the log message. + await _client.DeleteMessage(msg.ChannelId, msg.Id); + } } - else if (bot.ExtractFunc != null) + if (bot.ExtractFunc != null) { // Other bots give us the message ID itself, and we can just extract that from the database directly. var extractedId = bot.ExtractFunc(msg); @@ -152,10 +155,11 @@ public class LoggerCleanService bot.Name, msg.Id, extractedId); var mid = await _redis.GetOriginalMid(extractedId.Value); - if (mid == null) return; - - // If we've gotten this far, we found a logged deletion of a trigger message. Just yeet it! - await _client.DeleteMessage(msg.ChannelId, msg.Id); + if (mid != null) + { + // If we've gotten this far, we found a logged deletion of a trigger message. Just yeet it! + await _client.DeleteMessage(msg.ChannelId, msg.Id); + } } // else should not happen, but idk, it might } catch (NotFoundException) From d8a2d052ed38142a5584e2b5f6a0742f92536b1b Mon Sep 17 00:00:00 2001 From: Petal Ladenson Date: Tue, 10 Oct 2023 23:24:36 -0600 Subject: [PATCH 30/37] feat: logclean for annabelle's nonembed log format --- PluralKit.Bot/Services/LoggerCleanService.cs | 22 +++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/PluralKit.Bot/Services/LoggerCleanService.cs b/PluralKit.Bot/Services/LoggerCleanService.cs index b67c054a..6810ee24 100644 --- a/PluralKit.Bot/Services/LoggerCleanService.cs +++ b/PluralKit.Bot/Services/LoggerCleanService.cs @@ -43,6 +43,7 @@ public class LoggerCleanService private static readonly Regex _DozerRegex = new("Message ID: (\\d{17,19}) - (\\d{17,19})\nUserID: (\\d{17,19})"); private static readonly Regex _SkyraRegex = new("https://discord.com/channels/(\\d{17,19})/(\\d{17,19})/(\\d{17,19})"); private static readonly Regex _AnnabelleRegex = new("```\n(\\d{17,19})\n```"); + private static readonly Regex _AnnabelleRegexFuzzy = new("A message from \\*\\*[\\w.]{2,32}\\*\\* \\(`(\\d{17,19})`\\) was deleted in <#\\d{17,19}>"); private static readonly Regex _VortexRegex = new("`\\[(\\d\\d:\\d\\d:\\d\\d)\\]` .* \\(ID:(\\d{17,19})\\).* <#\\d{17,19}>:"); @@ -80,7 +81,7 @@ public class LoggerCleanService new LoggerBot("ProBot Prime", 567703512763334685, fuzzyExtractFunc: ExtractProBot), // webhook (?) new LoggerBot("Dozer", 356535250932858885, ExtractDozer), new LoggerBot("Skyra", 266624760782258186, ExtractSkyra), - new LoggerBot("Annabelle", 231241068383961088, ExtractAnnabelle), + new LoggerBot("Annabelle", 231241068383961088, ExtractAnnabelle, fuzzyExtractFunc: ExtractAnnabelleFuzzy), }.ToDictionary(b => b.Id); private static Dictionary _botsByApplicationId @@ -143,6 +144,7 @@ public class LoggerCleanService // Otherwise, we can *reasonably assume* that this is a logged deletion, so delete the log message. await _client.DeleteMessage(msg.ChannelId, msg.Id); + } } if (bot.ExtractFunc != null) @@ -415,12 +417,30 @@ public class LoggerCleanService private static ulong? ExtractAnnabelle(Message msg) { + // this bot has both an embed and a non-embed log format + // the embed is precise matching (this), the non-embed is fuzzy (below) var embed = msg.Embeds?.FirstOrDefault(); if (embed?.Author?.Name == null || !embed.Author.Name.EndsWith("Deleted Message")) return null; var match = _AnnabelleRegex.Match(embed.Fields[2].Value); return match.Success ? ulong.Parse(match.Groups[1].Value) : null; } + private static FuzzyExtractResult? ExtractAnnabelleFuzzy(Message msg) + { + // matching for annabelle's non-precise non-embed format + // it has a discord (unix) timestamp but I (Petalss) can't get that to convert to rfc3339 so + // we use the message timestamp + if (msg.Embeds.Length != 0) return null; + var match = _AnnabelleRegexFuzzy.Match(msg.Content); + return match.Success + ? new FuzzyExtractResult + { + User = ulong.Parse(match.Groups[1].Value), + ApproxTimestamp = msg.Timestamp().ToInstant() + } + : null; + } + public class LoggerBot { public ulong Id; From 31934b3b7ad1f9fe9f15a5efcc629e89ed6fce4f Mon Sep 17 00:00:00 2001 From: rladenson Date: Tue, 19 Nov 2024 01:51:43 -0700 Subject: [PATCH 31/37] feat: change annabelle logclean to use the timestamp in the message --- PluralKit.Bot/Services/LoggerCleanService.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/PluralKit.Bot/Services/LoggerCleanService.cs b/PluralKit.Bot/Services/LoggerCleanService.cs index 6810ee24..a08382c3 100644 --- a/PluralKit.Bot/Services/LoggerCleanService.cs +++ b/PluralKit.Bot/Services/LoggerCleanService.cs @@ -43,7 +43,7 @@ public class LoggerCleanService private static readonly Regex _DozerRegex = new("Message ID: (\\d{17,19}) - (\\d{17,19})\nUserID: (\\d{17,19})"); private static readonly Regex _SkyraRegex = new("https://discord.com/channels/(\\d{17,19})/(\\d{17,19})/(\\d{17,19})"); private static readonly Regex _AnnabelleRegex = new("```\n(\\d{17,19})\n```"); - private static readonly Regex _AnnabelleRegexFuzzy = new("A message from \\*\\*[\\w.]{2,32}\\*\\* \\(`(\\d{17,19})`\\) was deleted in <#\\d{17,19}>"); + private static readonly Regex _AnnabelleRegexFuzzy = new("\\ A message from \\*\\*[\\w.]{2,32}\\*\\* \\(`(\\d{17,19})`\\) was deleted in <#\\d{17,19}>"); private static readonly Regex _VortexRegex = new("`\\[(\\d\\d:\\d\\d:\\d\\d)\\]` .* \\(ID:(\\d{17,19})\\).* <#\\d{17,19}>:"); @@ -81,7 +81,7 @@ public class LoggerCleanService new LoggerBot("ProBot Prime", 567703512763334685, fuzzyExtractFunc: ExtractProBot), // webhook (?) new LoggerBot("Dozer", 356535250932858885, ExtractDozer), new LoggerBot("Skyra", 266624760782258186, ExtractSkyra), - new LoggerBot("Annabelle", 231241068383961088, ExtractAnnabelle, fuzzyExtractFunc: ExtractAnnabelleFuzzy), + new LoggerBot("Annabelle", 231241068383961088, fuzzyExtractFunc: ExtractAnnabelleFuzzy), }.ToDictionary(b => b.Id); private static Dictionary _botsByApplicationId @@ -138,6 +138,7 @@ public class LoggerCleanService bot.Name, msg.Id, fuzzy); var exists = await _redis.HasLogCleanup(fuzzy.Value.User, msg.GuildId.Value); + _logger.Debug(exists.ToString()); // If we didn't find a corresponding message, bail if (!exists) return; @@ -428,15 +429,14 @@ public class LoggerCleanService private static FuzzyExtractResult? ExtractAnnabelleFuzzy(Message msg) { // matching for annabelle's non-precise non-embed format - // it has a discord (unix) timestamp but I (Petalss) can't get that to convert to rfc3339 so - // we use the message timestamp + // it has a discord (unix) timestamp for the message so we use that if (msg.Embeds.Length != 0) return null; var match = _AnnabelleRegexFuzzy.Match(msg.Content); return match.Success ? new FuzzyExtractResult { - User = ulong.Parse(match.Groups[1].Value), - ApproxTimestamp = msg.Timestamp().ToInstant() + User = ulong.Parse(match.Groups[2].Value), + ApproxTimestamp = DateTimeOffset.FromUnixTimeSeconds(long.Parse(match.Groups[1].Value)).ToInstant() } : null; } From 3f6d9e5a10ec11efc8cfb9b01b1200dcf882a940 Mon Sep 17 00:00:00 2001 From: Petal Ladenson Date: Thu, 5 Dec 2024 17:12:33 -0700 Subject: [PATCH 32/37] fix: remove unused logclean paramter ApproxTimestamp --- PluralKit.Bot/Services/LoggerCleanService.cs | 43 +++++++------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/PluralKit.Bot/Services/LoggerCleanService.cs b/PluralKit.Bot/Services/LoggerCleanService.cs index a08382c3..37193591 100644 --- a/PluralKit.Bot/Services/LoggerCleanService.cs +++ b/PluralKit.Bot/Services/LoggerCleanService.cs @@ -125,10 +125,10 @@ public class LoggerCleanService // Some bots have different log formats so we check for both types of extract function if (bot.FuzzyExtractFunc != null) { - // Some bots (Carl, Circle, etc) only give us a user ID and a rough timestamp, so we try our best to + // Some bots (Carl, Circle, etc) only give us a user ID, so we try our best to // "cross-reference" those with the message DB. We know the deletion event happens *after* the message // was sent, so we're checking for any messages sent in the same guild within 3 seconds before the - // delete event timestamp, which is... good enough, I think? Potential for false positives and negatives + // delete event log, which is... good enough, I think? Potential for false positives and negatives // 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) @@ -267,8 +267,8 @@ public class LoggerCleanService private static FuzzyExtractResult? ExtractCircle(Message msg) { // Like Auttaja, Circle has both embed and compact modes, but the regex works for both. - // Compact: "Message from [user] ([id]) deleted in [channel]", no timestamp (use message time) - // Embed: Message Author field: "[user] ([id])", then an embed timestamp + // Compact: "Message from [user] ([id]) deleted in [channel]" + // Embed: Message Author field: "[user] ([id])" var stringWithId = msg.Content; if (msg.Embeds?.Length > 0) { @@ -285,24 +285,21 @@ public class LoggerCleanService return match.Success ? new FuzzyExtractResult { - User = ulong.Parse(match.Groups[1].Value), - ApproxTimestamp = msg.Timestamp().ToInstant() + User = ulong.Parse(match.Groups[1].Value) } : null; } private static FuzzyExtractResult? ExtractPancake(Message msg) { - // Embed, author is "Message Deleted", description includes a mention, timestamp is *message send time* (but no ID) - // so we use the message timestamp to get somewhere *after* the message was proxied + // Embed, author is "Message Deleted", description includes a mention var embed = msg.Embeds?.FirstOrDefault(); if (embed?.Description == null || embed.Author?.Name != "Message Deleted") return null; var match = _pancakeRegex.Match(embed.Description); return match.Success ? new FuzzyExtractResult { - User = ulong.Parse(match.Groups[1].Value), - ApproxTimestamp = msg.Timestamp().ToInstant() + User = ulong.Parse(match.Groups[1].Value) } : null; } @@ -325,8 +322,7 @@ public class LoggerCleanService return match.Success ? new FuzzyExtractResult { - User = ulong.Parse(match.Groups[1].Value), - ApproxTimestamp = msg.Timestamp().ToInstant() + User = ulong.Parse(match.Groups[1].Value) } : null; } @@ -342,8 +338,7 @@ public class LoggerCleanService return match.Success ? new FuzzyExtractResult { - User = ulong.Parse(match.Groups[1].Value), - ApproxTimestamp = msg.Timestamp().ToInstant() + User = ulong.Parse(match.Groups[1].Value) } : null; } @@ -351,14 +346,12 @@ public class LoggerCleanService private static FuzzyExtractResult? ExtractGearBot(Message msg) { // Simple text based message log. - // No message ID, but we have timestamp and author ID. - // Not using timestamp here though (seems to be same as message timestamp), might be worth implementing in the future. + // No message ID, but we have author ID. var match = _GearBotRegex.Match(msg.Content); return match.Success ? new FuzzyExtractResult { - User = ulong.Parse(match.Groups[1].Value), - ApproxTimestamp = msg.Timestamp().ToInstant() + User = ulong.Parse(match.Groups[1].Value) } : null; } @@ -373,14 +366,11 @@ public class LoggerCleanService private static FuzzyExtractResult? ExtractVortex(Message msg) { - // timestamp is HH:MM:SS - // however, that can be set to the user's timezone, so we just use the message timestamp var match = _VortexRegex.Match(msg.Content); return match.Success ? new FuzzyExtractResult { - User = ulong.Parse(match.Groups[2].Value), - ApproxTimestamp = msg.Timestamp().ToInstant() + User = ulong.Parse(match.Groups[2].Value) } : null; } @@ -388,15 +378,12 @@ public class LoggerCleanService private static FuzzyExtractResult? ExtractProBot(Message msg) { // user ID and channel ID are in the embed description (we don't use channel ID) - // timestamp is in the embed footer if (msg.Embeds.Length == 0 || msg.Embeds[0].Description == null) return null; var match = _ProBotRegex.Match(msg.Embeds[0].Description); return match.Success ? new FuzzyExtractResult { - User = ulong.Parse(match.Groups[1].Value), - ApproxTimestamp = OffsetDateTimePattern.Rfc3339 - .Parse(msg.Embeds[0].Timestamp).GetValueOrThrow().ToInstant() + User = ulong.Parse(match.Groups[1].Value) } : null; } @@ -435,8 +422,7 @@ public class LoggerCleanService return match.Success ? new FuzzyExtractResult { - User = ulong.Parse(match.Groups[2].Value), - ApproxTimestamp = DateTimeOffset.FromUnixTimeSeconds(long.Parse(match.Groups[1].Value)).ToInstant() + User = ulong.Parse(match.Groups[2].Value) } : null; } @@ -465,6 +451,5 @@ public class LoggerCleanService public struct FuzzyExtractResult { public ulong User { get; set; } - public Instant ApproxTimestamp { get; set; } } } \ No newline at end of file From da4c05d4ed2e9d3debab57f0799963cbd4662e9c Mon Sep 17 00:00:00 2001 From: rladenson Date: Thu, 5 Dec 2024 16:59:30 -0700 Subject: [PATCH 33/37] docs: add sapphire and annabelle to logclean docs --- docs/content/staff/compatibility.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/content/staff/compatibility.md b/docs/content/staff/compatibility.md index c40fb817..5c3bcabe 100644 --- a/docs/content/staff/compatibility.md +++ b/docs/content/staff/compatibility.md @@ -21,6 +21,7 @@ This requires you to have the *Manage Server* permission on the server. ### Supported bots At the moment, log cleanup works with the following bots: +- Annabelle (precise in embed format, fuzzy in inline format) - [Auttaja](https://auttaja.io/) (precise) - [blargbot](https://blargbot.xyz/) (precise) - [Carl-bot](https://carl.gg/) (precise) @@ -34,6 +35,7 @@ At the moment, log cleanup works with the following bots: - [Mantaro](https://mantaro.site/) (precise) - [Pancake](https://pancake.gg/) (fuzzy) - [SafetyAtLast](https://www.safetyatlast.net/) (fuzzy) +- [Sapphire](https://sapph.xyz/) (precise, only in default format) - [Skyra](https://www.skyra.pw/) (precise) - [UnbelievaBoat](https://unbelievaboat.com/) (precise) - Vanessa (fuzzy) From ec6cbb2a645d4d29d12b9309908f1b29a6fb53b3 Mon Sep 17 00:00:00 2001 From: rladenson Date: Tue, 19 Nov 2024 17:35:09 -0700 Subject: [PATCH 34/37] feat: guild-specific name format --- PluralKit.Bot/CommandMeta/CommandHelp.cs | 3 +- PluralKit.Bot/CommandMeta/CommandTree.cs | 2 + PluralKit.Bot/Commands/Config.cs | 50 ++++++++++++++++++- .../Database/Functions/MessageContext.cs | 1 + .../Database/Functions/MessageContextExt.cs | 2 +- .../Database/Functions/ProxyMember.cs | 2 +- .../Database/Functions/functions.sql | 2 + PluralKit.Core/Database/Migrations/49.sql | 6 +++ .../Database/Utils/DatabaseMigrator.cs | 2 +- .../Models/Patch/SystemGuildPatch.cs | 8 +++ PluralKit.Core/Models/SystemGuildSettings.cs | 2 + 11 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 PluralKit.Core/Database/Migrations/49.sql diff --git a/PluralKit.Bot/CommandMeta/CommandHelp.cs b/PluralKit.Bot/CommandMeta/CommandHelp.cs index c4a87f60..56b545a3 100644 --- a/PluralKit.Bot/CommandMeta/CommandHelp.cs +++ b/PluralKit.Bot/CommandMeta/CommandHelp.cs @@ -32,6 +32,7 @@ public partial class CommandTree public static Command ConfigGroupDefaultPrivacy = new("config private group", "config private group [on|off]", "Sets whether group privacy is automatically set to private when creating a new group"); public static Command ConfigProxySwitch = new Command("config proxyswitch", "config proxyswitch [on|off]", "Sets whether to log a switch every time a proxy tag is used"); public static Command ConfigNameFormat = new Command("config nameformat", "config nameformat [format]", "Changes your system's username formatting"); + public static Command ConfigServerNameFormat = new Command("config servernameformat", "config servernameformat [format]", "Changes your system's username formatting in the current server"); public static Command AutoproxySet = new Command("autoproxy", "autoproxy [off|front|latch|member]", "Sets your system's autoproxy mode for the current server"); public static Command AutoproxyOff = new Command("autoproxy off", "autoproxy off", "Disables autoproxying for your system in the current server"); public static Command AutoproxyFront = new Command("autoproxy front", "autoproxy front", "Sets your system's autoproxy in this server to proxy the first member currently registered as front"); @@ -150,7 +151,7 @@ public partial class CommandTree { ConfigAutoproxyAccount, ConfigAutoproxyTimeout, ConfigTimezone, ConfigPing, ConfigMemberDefaultPrivacy, ConfigGroupDefaultPrivacy, ConfigShowPrivate, - ConfigProxySwitch, ConfigNameFormat + ConfigProxySwitch, ConfigNameFormat, ConfigServerNameFormat }; public static Command[] ServerConfigCommands = diff --git a/PluralKit.Bot/CommandMeta/CommandTree.cs b/PluralKit.Bot/CommandMeta/CommandTree.cs index a458e1d4..51f8f449 100644 --- a/PluralKit.Bot/CommandMeta/CommandTree.cs +++ b/PluralKit.Bot/CommandMeta/CommandTree.cs @@ -596,6 +596,8 @@ public partial class CommandTree return ctx.Execute(null, m => m.LimitUpdate(ctx)); if (ctx.MatchMultiple(new[] { "proxy" }, new[] { "switch" }) || ctx.Match("proxyswitch", "ps")) return ctx.Execute(null, m => m.ProxySwitch(ctx)); + if (ctx.MatchMultiple(new[] { "server" }, new[] { "name" }, new[] { "format" }) || ctx.MatchMultiple(new[] { "server", "servername" }, new[] { "format", "nameformat", "nf" }) || ctx.Match("snf", "servernf", "servernameformat", "snameformat")) + return ctx.Execute(null, m => m.ServerNameFormat(ctx)); // todo: maybe add the list of configuration keys here? return ctx.Reply($"{Emojis.Error} Could not find a setting with that name. Please see `pk;commands config` for the list of possible config settings."); diff --git a/PluralKit.Bot/Commands/Config.cs b/PluralKit.Bot/Commands/Config.cs index fd01bb6a..cedc6c66 100644 --- a/PluralKit.Bot/Commands/Config.cs +++ b/PluralKit.Bot/Commands/Config.cs @@ -1,7 +1,7 @@ using System.Text; using Humanizer; - +using Myriad.Builders; using NodaTime; using NodaTime.Text; using NodaTime.TimeZones; @@ -137,6 +137,13 @@ public class Config ProxyMember.DefaultFormat )); + items.Add(new( + "Server Name Format", + "Format string used to display a member's name in the current server", + (await ctx.Repository.GetSystemGuild(ctx.Guild.Id, ctx.System.Id)).NameFormat ?? "none set", + "none set" + )); + await ctx.Paginate( items.ToAsyncEnumerable(), items.Count, @@ -584,6 +591,47 @@ public class Config await ctx.Reply($"Member names are now formatted as `{formatString}`"); } + public async Task ServerNameFormat(Context ctx) + { + var clearFlag = ctx.MatchClear(); + var format = ctx.MatchFormat(); + + // if there's nothing next or what's next is raw/plaintext and we're not clearing, it's a query + if ((!ctx.HasNext() || format != ReplyFormat.Standard) && !clearFlag) + { + var guildCfg = await ctx.Repository.GetSystemGuild(ctx.Guild.Id, ctx.System.Id); + if (guildCfg.NameFormat == null) + await ctx.Reply("You do not have a specific name format set for this server and member names are formatted with your global name format."); + else + switch (format) + { + case ReplyFormat.Raw: + await ctx.Reply($"`{guildCfg.NameFormat}`"); + break; + case ReplyFormat.Plaintext: + var eb = new EmbedBuilder() + .Description($"Showing guild Name Format for system {ctx.System.DisplayHid(ctx.Config)}"); + await ctx.Reply(guildCfg.NameFormat, eb.Build()); + break; + default: + await ctx.Reply($"Your member names in this server are currently formatted as `{guildCfg.NameFormat}`"); + break; + } + return; + } + + string? formatString = null; + if (!clearFlag) + { + formatString = ctx.RemainderOrNull(); + } + await ctx.Repository.UpdateSystemGuild(ctx.System.Id, ctx.Guild.Id, new() { NameFormat = formatString }); + if (formatString == null) + await ctx.Reply($"Member names are now formatted with your global name format in this server."); + else + await ctx.Reply($"Member names are now formatted as `{formatString}` in this server."); + } + public Task LimitUpdate(Context ctx) { throw new PKError("You cannot update your own member or group limits. If you need a limit update, please join the " + diff --git a/PluralKit.Core/Database/Functions/MessageContext.cs b/PluralKit.Core/Database/Functions/MessageContext.cs index 7f767b26..1d110eeb 100644 --- a/PluralKit.Core/Database/Functions/MessageContext.cs +++ b/PluralKit.Core/Database/Functions/MessageContext.cs @@ -27,6 +27,7 @@ public class MessageContext public string? SystemGuildTag { get; } public bool TagEnabled { get; } public string? NameFormat { get; } + public string? GuildNameFormat { get; } public string? SystemAvatar { get; } public string? SystemGuildAvatar { get; } public bool AllowAutoproxy { get; } diff --git a/PluralKit.Core/Database/Functions/MessageContextExt.cs b/PluralKit.Core/Database/Functions/MessageContextExt.cs index bca26b2d..de6d2c04 100644 --- a/PluralKit.Core/Database/Functions/MessageContextExt.cs +++ b/PluralKit.Core/Database/Functions/MessageContextExt.cs @@ -9,7 +9,7 @@ public static class MessageContextExt if (!ctx.TagEnabled || tag == null) return false; - var format = ctx.NameFormat ?? ProxyMember.DefaultFormat; + var format = ctx.GuildNameFormat ?? ctx.NameFormat ?? ProxyMember.DefaultFormat; if (!format.Contains("{tag}")) return false; diff --git a/PluralKit.Core/Database/Functions/ProxyMember.cs b/PluralKit.Core/Database/Functions/ProxyMember.cs index 0df7cf35..2c97468f 100644 --- a/PluralKit.Core/Database/Functions/ProxyMember.cs +++ b/PluralKit.Core/Database/Functions/ProxyMember.cs @@ -45,7 +45,7 @@ public class ProxyMember var tag = ctx.SystemGuildTag ?? ctx.SystemTag; if (!ctx.TagEnabled) tag = null; - return FormatTag(ctx.NameFormat ?? DefaultFormat, tag, memberName); + return FormatTag(ctx.GuildNameFormat ?? ctx.NameFormat ?? DefaultFormat, tag, memberName); } public string? ProxyAvatar(MessageContext ctx) => ServerAvatar ?? WebhookAvatar ?? Avatar ?? ctx.SystemGuildAvatar ?? ctx.SystemAvatar; diff --git a/PluralKit.Core/Database/Functions/functions.sql b/PluralKit.Core/Database/Functions/functions.sql index 693dff5f..29eccbca 100644 --- a/PluralKit.Core/Database/Functions/functions.sql +++ b/PluralKit.Core/Database/Functions/functions.sql @@ -16,6 +16,7 @@ create function message_context(account_id bigint, guild_id bigint, channel_id b proxy_enabled bool, system_guild_tag text, system_guild_avatar text, + guild_name_format text, last_switch int, last_switch_members int[], @@ -51,6 +52,7 @@ as $$ coalesce(system_guild.proxy_enabled, true) as proxy_enabled, system_guild.tag as system_guild_tag, system_guild.avatar_url as system_guild_avatar, + system_guild.name_format as guild_name_format, -- system_last_switch view system_last_switch.switch as last_switch, diff --git a/PluralKit.Core/Database/Migrations/49.sql b/PluralKit.Core/Database/Migrations/49.sql new file mode 100644 index 00000000..837c9e0b --- /dev/null +++ b/PluralKit.Core/Database/Migrations/49.sql @@ -0,0 +1,6 @@ +-- database version 49 +-- add guild name format + +alter table system_guild add column name_format text; + +update info set schema_version = 49; \ No newline at end of file diff --git a/PluralKit.Core/Database/Utils/DatabaseMigrator.cs b/PluralKit.Core/Database/Utils/DatabaseMigrator.cs index 0aea679a..4e212d46 100644 --- a/PluralKit.Core/Database/Utils/DatabaseMigrator.cs +++ b/PluralKit.Core/Database/Utils/DatabaseMigrator.cs @@ -9,7 +9,7 @@ namespace PluralKit.Core; internal class DatabaseMigrator { private const string RootPath = "PluralKit.Core.Database"; // "resource path" root for SQL files - private const int TargetSchemaVersion = 48; + private const int TargetSchemaVersion = 49; private readonly ILogger _logger; public DatabaseMigrator(ILogger logger) diff --git a/PluralKit.Core/Models/Patch/SystemGuildPatch.cs b/PluralKit.Core/Models/Patch/SystemGuildPatch.cs index f19ff00f..25cd8271 100644 --- a/PluralKit.Core/Models/Patch/SystemGuildPatch.cs +++ b/PluralKit.Core/Models/Patch/SystemGuildPatch.cs @@ -13,6 +13,7 @@ public class SystemGuildPatch: PatchObject public Partial TagEnabled { get; set; } public Partial AvatarUrl { get; set; } public Partial DisplayName { get; set; } + public Partial NameFormat { get; set; } public override Query Apply(Query q) => q.ApplyPatch(wrapper => wrapper .With("proxy_enabled", ProxyEnabled) @@ -20,6 +21,7 @@ public class SystemGuildPatch: PatchObject .With("tag_enabled", TagEnabled) .With("avatar_url", AvatarUrl) .With("display_name", DisplayName) + .With("name_format", NameFormat) ); public new void AssertIsValid() @@ -53,6 +55,9 @@ public class SystemGuildPatch: PatchObject if (o.ContainsKey("display_name")) patch.DisplayName = o.Value("display_name").NullIfEmpty(); + if (o.ContainsKey("name_format")) + patch.NameFormat = o.Value("name_format").NullIfEmpty(); + return patch; } @@ -77,6 +82,9 @@ public class SystemGuildPatch: PatchObject if (DisplayName.IsPresent) o.Add("display_name", DisplayName.Value); + if (NameFormat.IsPresent) + o.Add("name_format", NameFormat.Value); + return o; } } \ No newline at end of file diff --git a/PluralKit.Core/Models/SystemGuildSettings.cs b/PluralKit.Core/Models/SystemGuildSettings.cs index 439712e0..231d08a3 100644 --- a/PluralKit.Core/Models/SystemGuildSettings.cs +++ b/PluralKit.Core/Models/SystemGuildSettings.cs @@ -11,6 +11,7 @@ public class SystemGuildSettings public bool TagEnabled { get; } public string? AvatarUrl { get; } public string? DisplayName { get; } + public string? NameFormat { get; } } public static class SystemGuildExt @@ -24,6 +25,7 @@ public static class SystemGuildExt o.Add("tag_enabled", settings.TagEnabled); o.Add("avatar_url", settings.AvatarUrl); o.Add("display_name", settings.DisplayName); + o.Add("name_format", settings.NameFormat); return o; } From c0bc2163f57936abc3e1e740d2e1ce5353eb03ca Mon Sep 17 00:00:00 2001 From: rladenson Date: Wed, 20 Nov 2024 16:28:42 -0700 Subject: [PATCH 35/37] fix: allow servernameformat to be run in DMs --- PluralKit.Bot/Commands/Config.cs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/PluralKit.Bot/Commands/Config.cs b/PluralKit.Bot/Commands/Config.cs index cedc6c66..a6783fb7 100644 --- a/PluralKit.Bot/Commands/Config.cs +++ b/PluralKit.Bot/Commands/Config.cs @@ -137,12 +137,24 @@ public class Config ProxyMember.DefaultFormat )); - items.Add(new( - "Server Name Format", - "Format string used to display a member's name in the current server", - (await ctx.Repository.GetSystemGuild(ctx.Guild.Id, ctx.System.Id)).NameFormat ?? "none set", - "none set" - )); + if (ctx.Guild == null) + { + items.Add(new( + "Server Name Format", + "Format string used to display a member's name in the current server", + "only available in servers", + "only available in servers" + )); + } + else + { + items.Add(new( + "Server Name Format", + "Format string used to display a member's name in the current server", + (await ctx.Repository.GetSystemGuild(ctx.Guild.Id, ctx.System.Id)).NameFormat ?? "none set", + "none set" + )); + } await ctx.Paginate( items.ToAsyncEnumerable(), @@ -593,6 +605,7 @@ public class Config public async Task ServerNameFormat(Context ctx) { + ctx.CheckGuildContext(); var clearFlag = ctx.MatchClear(); var format = ctx.MatchFormat(); From ceeac242965fb081bfb8013e3b597b06b510ef3c Mon Sep 17 00:00:00 2001 From: rladenson Date: Thu, 5 Dec 2024 17:23:43 -0700 Subject: [PATCH 36/37] docs: add servernameformat to docs --- docs/content/command-list.md | 1 + docs/content/user-guide.md | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/docs/content/command-list.md b/docs/content/command-list.md index bc8178cb..b89d6312 100644 --- a/docs/content/command-list.md +++ b/docs/content/command-list.md @@ -150,6 +150,7 @@ You can have a space after `pk;`, e.g. `pk;system` and `pk; system` will do the - `pk;config pad IDs [left|right|off]` - Toggles whether to pad (add a space) 5-character IDs in lists. - `pk;config proxy switch [on|off]` - Toggles whether to log a switch whenever you proxy as a different member. - `pk;config name format [format]` - Changes your system's username formatting. +- `pk;config server name format [format]` - Changes your system's username formatting for the current server. ## Server owner commands *(all commands here require Manage Server permission)* diff --git a/docs/content/user-guide.md b/docs/content/user-guide.md index fdc4d126..cdf55ae2 100644 --- a/docs/content/user-guide.md +++ b/docs/content/user-guide.md @@ -394,6 +394,10 @@ The default proxy username formatting is "{name} {tag}", but you can customize t pk;config nameformat {tag} {name} pk;config nameformat {name}@{tag} +You can also do this on a per-server basis: + + pk;config servernameformat {tag} {name} + pk;config servernameformat {name}@{tag} ## Interacting with proxied messages From 562afcdb8afc4b4dd1fb6dd424be5c235f42b56a Mon Sep 17 00:00:00 2001 From: rladenson Date: Mon, 18 Nov 2024 00:43:49 -0700 Subject: [PATCH 37/37] fix: inconsistent behavior with non-owned banner lookups --- PluralKit.Bot/Commands/MemberEdit.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/PluralKit.Bot/Commands/MemberEdit.cs b/PluralKit.Bot/Commands/MemberEdit.cs index dc8439ff..b3c2e837 100644 --- a/PluralKit.Bot/Commands/MemberEdit.cs +++ b/PluralKit.Bot/Commands/MemberEdit.cs @@ -194,16 +194,18 @@ public class MemberEdit public async Task BannerImage(Context ctx, PKMember target) { - ctx.CheckOwnMember(target); - async Task ClearBannerImage() { + ctx.CheckOwnMember(target); + await ctx.ConfirmClear("this member's banner image"); + await ctx.Repository.UpdateMember(target.Id, new MemberPatch { BannerImage = null }); await ctx.Reply($"{Emojis.Success} Member banner image cleared."); } async Task SetBannerImage(ParsedImage img) { + ctx.CheckOwnMember(target); img = await _avatarHosting.TryRehostImage(img, AvatarHostingService.RehostedImageType.Banner, ctx.Author.Id, ctx.System); await AvatarUtils.VerifyAvatarOrThrow(_client, img.Url, true); @@ -253,7 +255,7 @@ public class MemberEdit "This member does not have a banner image set." + ((target.System == ctx.System?.Id) ? " Set one by attaching an image to this command, or by passing an image URL." : "")); } - if (ctx.MatchClear() && await ctx.ConfirmClear("this member's banner image")) + if (ctx.MatchClear()) await ClearBannerImage(); else if (await ctx.MatchImage() is { } img) await SetBannerImage(img);