diff --git a/Cargo.toml b/Cargo.toml index 270d00a6..444bcee6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ reqwest = { version = "0.12.7" , default-features = false, features = ["rustls-t sentry = { version = "0.36.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" -sqlx = { version = "0.8.2", features = ["runtime-tokio", "postgres", "time", "macros", "uuid"] } +sqlx = { version = "0.8.2", features = ["runtime-tokio", "postgres", "time", "chrono", "macros", "uuid"] } tokio = { version = "1.36.0", features = ["full"] } tracing = "0.1" tracing-subscriber = { version = "0.3.16", features = ["env-filter", "json"] } diff --git a/PluralKit.Bot/CommandMeta/CommandTree.cs b/PluralKit.Bot/CommandMeta/CommandTree.cs index 8d111ef4..67379fe1 100644 --- a/PluralKit.Bot/CommandMeta/CommandTree.cs +++ b/PluralKit.Bot/CommandMeta/CommandTree.cs @@ -592,6 +592,8 @@ public partial class CommandTree return ctx.Execute(null, m => m.HidDisplayCaps(ctx)); if (ctx.MatchMultiple(new[] { "pad" }, new[] { "id", "ids" }) || ctx.MatchMultiple(new[] { "id" }, new[] { "pad", "padding" }) || ctx.Match("idpad", "padid", "padids")) return ctx.Execute(null, m => m.HidListPadding(ctx)); + if (ctx.MatchMultiple(new[] { "show" }, new[] { "color", "colour", "colors", "colours" }) || ctx.Match("showcolor", "showcolour", "showcolors", "showcolours", "colorcode", "colorhex")) + return ctx.Execute(null, m => m.CardShowColorHex(ctx)); if (ctx.MatchMultiple(new[] { "name" }, new[] { "format" }) || ctx.Match("nameformat", "nf")) return ctx.Execute(null, m => m.NameFormat(ctx)); if (ctx.MatchMultiple(new[] { "member", "group" }, new[] { "limit" }) || ctx.Match("limit")) diff --git a/PluralKit.Bot/Commands/Config.cs b/PluralKit.Bot/Commands/Config.cs index 1b8efab1..1feb7c37 100644 --- a/PluralKit.Bot/Commands/Config.cs +++ b/PluralKit.Bot/Commands/Config.cs @@ -123,6 +123,13 @@ public class Config "off" )); + items.Add(new( + "show color", + "Whether to show color codes in system/member/group cards", + EnabledDisabled(ctx.Config.CardShowColorHex), + "disabled" + )); + items.Add(new( "Proxy Switch", "Switching behavior when proxy tags are used", @@ -570,6 +577,20 @@ public class Config else throw new PKError(badInputError); } + public async Task CardShowColorHex(Context ctx) + { + if (!ctx.HasNext()) + { + var msg = $"Showing color codes on system/member/group cards is currently **{EnabledDisabled(ctx.Config.CardShowColorHex)}**."; + await ctx.Reply(msg); + return; + } + + var newVal = ctx.MatchToggle(false); + await ctx.Repository.UpdateSystemConfig(ctx.System.Id, new() { CardShowColorHex = newVal }); + await ctx.Reply($"Showing color codes on system/member/group cards is now {EnabledDisabled(newVal)}."); + } + public async Task ProxySwitch(Context ctx) { if (!ctx.HasNext()) diff --git a/PluralKit.Bot/Services/EmbedService.cs b/PluralKit.Bot/Services/EmbedService.cs index 5a229ad6..39e85a37 100644 --- a/PluralKit.Bot/Services/EmbedService.cs +++ b/PluralKit.Bot/Services/EmbedService.cs @@ -75,12 +75,15 @@ public class EmbedService if (system.Tag != null) headerText += $"\n**Tag:** {system.Tag.EscapeMarkdown()}"; + if (cctx.Config.CardShowColorHex && !system.Color.EmptyOrNull()) + headerText += $"\n**Color:** #{system.Color}"; + if (cctx.Guild != null) { if (guildSettings.Tag != null && guildSettings.TagEnabled) - headerText += $"**Tag (in server '{cctx.Guild.Name}'):** {guildSettings.Tag.EscapeMarkdown()}"; + headerText += $"\n**Tag (in server '{cctx.Guild.Name}'):** {guildSettings.Tag.EscapeMarkdown()}"; if (!guildSettings.TagEnabled) - headerText += $"**Tag (in server '{cctx.Guild.Name}'):** *(tag is disabled in this server)*"; + headerText += $"\n**Tag (in server '{cctx.Guild.Name}'):** *(tag is disabled in this server)*"; } if (system.MemberListPrivacy.CanAccess(ctx)) @@ -358,6 +361,8 @@ public class EmbedService headerText += $"\n**Display name:** {member.DisplayName.Truncate(1024)}"; if (guild != null && guildDisplayName != null) headerText += $"\n**Server nickname (for '{guild.Name}'):** {guildDisplayName.Truncate(1024)}"; + if (ccfg.CardShowColorHex && !member.Color.EmptyOrNull()) + headerText += $"\n**Color:** #{member.Color}"; if (member.PronounsFor(ctx) is { } pronouns && !string.IsNullOrWhiteSpace(pronouns)) headerText += $"\n**Pronouns:** {pronouns}"; if (member.BirthdayFor(ctx) != null) @@ -577,6 +582,9 @@ public class EmbedService if (target.NamePrivacy.CanAccess(pctx) && target.DisplayName != null) headerText += $"\n**Display name:** {target.DisplayName}"; + if (ctx.Config.CardShowColorHex && !target.Color.EmptyOrNull()) + headerText += $"\n**Color:** #{target.Color}"; + if (target.ListPrivacy.CanAccess(pctx)) { headerText += $"\n**Members:** {memberCount}"; diff --git a/PluralKit.Bot/Services/ErrorMessageService.cs b/PluralKit.Bot/Services/ErrorMessageService.cs index 50f47d1c..efab9d02 100644 --- a/PluralKit.Bot/Services/ErrorMessageService.cs +++ b/PluralKit.Bot/Services/ErrorMessageService.cs @@ -116,7 +116,7 @@ public class ErrorMessageService return new EmbedBuilder() .Color(0xE74C3C) .Title("Internal error occurred") - .Description($"For support, please send the error code above as text in {channelInfo} with a description of what you were doing at the time.") + .Description($"**If you need support,** please send/forward the error code above **as text** in {channelInfo} with a description of what you were doing at the time.") .Footer(new Embed.EmbedFooter(errorId)) .Timestamp(now.ToDateTimeOffset().ToString("O")) .Build(); diff --git a/PluralKit.Core/Models/Patch/SystemConfigPatch.cs b/PluralKit.Core/Models/Patch/SystemConfigPatch.cs index d3d80428..c29cfae8 100644 --- a/PluralKit.Core/Models/Patch/SystemConfigPatch.cs +++ b/PluralKit.Core/Models/Patch/SystemConfigPatch.cs @@ -22,6 +22,7 @@ public class SystemConfigPatch: PatchObject public Partial ProxyErrorMessageEnabled { get; set; } public Partial HidDisplaySplit { get; set; } public Partial HidDisplayCaps { get; set; } + public Partial CardShowColorHex { get; set; } public Partial NameFormat { get; set; } public Partial HidListPadding { get; set; } public Partial ProxySwitch { get; set; } @@ -41,6 +42,7 @@ public class SystemConfigPatch: PatchObject .With("hid_display_split", HidDisplaySplit) .With("hid_display_caps", HidDisplayCaps) .With("hid_list_padding", HidListPadding) + .With("card_show_color_hex", CardShowColorHex) .With("proxy_switch", ProxySwitch) .With("name_format", NameFormat) ); @@ -107,6 +109,9 @@ public class SystemConfigPatch: PatchObject if (HidListPadding.IsPresent) o.Add("hid_list_padding", HidListPadding.Value.ToUserString()); + if (CardShowColorHex.IsPresent) + o.Add("card_show_color_hex", CardShowColorHex.Value); + if (ProxySwitch.IsPresent) o.Add("proxy_switch", ProxySwitch.Value.ToUserString()); @@ -150,6 +155,9 @@ public class SystemConfigPatch: PatchObject if (o.ContainsKey("hid_display_caps")) patch.HidDisplayCaps = o.Value("hid_display_caps"); + if (o.ContainsKey("card_show_color_hex")) + patch.CardShowColorHex = o.Value("card_show_color_hex"); + if (o.ContainsKey("proxy_switch")) patch.ProxySwitch = o.Value("proxy_switch") switch { diff --git a/PluralKit.Core/Models/SystemConfig.cs b/PluralKit.Core/Models/SystemConfig.cs index b4148211..dac7965f 100644 --- a/PluralKit.Core/Models/SystemConfig.cs +++ b/PluralKit.Core/Models/SystemConfig.cs @@ -23,6 +23,7 @@ public class SystemConfig public bool ProxyErrorMessageEnabled { get; } public bool HidDisplaySplit { get; } public bool HidDisplayCaps { get; } + public bool CardShowColorHex { get; } public HidPadFormat HidListPadding { get; } public ProxySwitchAction ProxySwitch { get; } public string NameFormat { get; } @@ -60,6 +61,7 @@ public static class SystemConfigExt o.Add("hid_display_split", cfg.HidDisplaySplit); o.Add("hid_display_caps", cfg.HidDisplayCaps); o.Add("hid_list_padding", cfg.HidListPadding.ToUserString()); + o.Add("card_show_color_hex", cfg.CardShowColorHex); o.Add("proxy_switch", cfg.ProxySwitch.ToUserString()); o.Add("name_format", cfg.NameFormat); diff --git a/crates/gateway/src/discord/gateway.rs b/crates/gateway/src/discord/gateway.rs index 6d16bfd1..8210e06e 100644 --- a/crates/gateway/src/discord/gateway.rs +++ b/crates/gateway/src/discord/gateway.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use tokio::sync::mpsc::Sender; use tracing::{error, info, warn}; use twilight_gateway::{ - create_iterator, ConfigBuilder, Event, EventTypeFlags, Message, Shard, ShardId, + create_iterator, CloseFrame, ConfigBuilder, Event, EventTypeFlags, Message, Shard, ShardId, }; use twilight_model::gateway::{ payload::outgoing::update_presence::UpdatePresencePayload, @@ -116,7 +116,11 @@ pub async fn runner( let raw_event = match item { Ok(evt) => match evt { Message::Close(frame) => { + let mut state_event = ShardStateEvent::Closed; let close_code = if let Some(close) = frame { + if close == CloseFrame::RESUME { + state_event = ShardStateEvent::Reconnect; + } close.code.to_string() } else { "unknown".to_string() @@ -131,9 +135,7 @@ pub async fn runner( ) .increment(1); - if let Err(error) = - tx_state.try_send((shard.id(), ShardStateEvent::Closed, None, None)) - { + if let Err(error) = tx_state.try_send((shard.id(), state_event, None, None)) { error!("failed to update shard state for socket closure: {error}"); } diff --git a/crates/gateway/src/discord/shard_state.rs b/crates/gateway/src/discord/shard_state.rs index c85e02c8..847b14c6 100644 --- a/crates/gateway/src/discord/shard_state.rs +++ b/crates/gateway/src/discord/shard_state.rs @@ -86,7 +86,7 @@ impl ShardStateManager { Ok(()) } - pub async fn socket_closed(&self, shard_id: u32) -> anyhow::Result<()> { + pub async fn socket_closed(&self, shard_id: u32, reconnect: bool) -> anyhow::Result<()> { gauge!("pluralkit_gateway_shard_up").decrement(1); let mut info = self @@ -97,6 +97,9 @@ impl ShardStateManager { info.shard_id = shard_id as i32; info.cluster_id = Some(cluster_config().node_id as i32); info.up = false; + if reconnect { + info.last_reconnect = chrono::offset::Utc::now().timestamp() as i32 + } info.disconnection_count += 1; self.save_shard(shard_id, info).await?; diff --git a/crates/gateway/src/main.rs b/crates/gateway/src/main.rs index e61c3445..12db76b5 100644 --- a/crates/gateway/src/main.rs +++ b/crates/gateway/src/main.rs @@ -109,8 +109,16 @@ async fn main() -> anyhow::Result<()> { }; } ShardStateEvent::Closed => { - if let Err(error) = shard_state.socket_closed(shard_id.number()).await { - error!("failed to update shard state for heartbeat: {error}") + if let Err(error) = + shard_state.socket_closed(shard_id.number(), false).await + { + error!("failed to update shard state for closed: {error}") + }; + } + ShardStateEvent::Reconnect => { + if let Err(error) = shard_state.socket_closed(shard_id.number(), true).await + { + error!("failed to update shard state for reconnect: {error}") }; } ShardStateEvent::Other => { @@ -121,7 +129,7 @@ async fn main() -> anyhow::Result<()> { ) .await { - error!("failed to update shard state for heartbeat: {error}") + error!("failed to update shard state for other evt: {error}") }; } } diff --git a/crates/libpk/src/state.rs b/crates/libpk/src/state.rs index df44ea1d..3ea6342a 100644 --- a/crates/libpk/src/state.rs +++ b/crates/libpk/src/state.rs @@ -8,11 +8,13 @@ pub struct ShardState { /// unix timestamp pub last_heartbeat: i32, pub last_connection: i32, + pub last_reconnect: i32, pub cluster_id: Option, } pub enum ShardStateEvent { Closed, Heartbeat, + Reconnect, Other, } diff --git a/crates/migrate/data/migrations/53.sql b/crates/migrate/data/migrations/53.sql new file mode 100644 index 00000000..3bfa3582 --- /dev/null +++ b/crates/migrate/data/migrations/53.sql @@ -0,0 +1,6 @@ +-- database version 53 +-- add toggle for showing color codes on cv2 cards + +alter table system_config add column card_show_color_hex bool default false; + +update info set schema_version = 53; \ No newline at end of file diff --git a/dashboard/main.go b/dashboard/main.go index e657c740..9a0bc993 100644 --- a/dashboard/main.go +++ b/dashboard/main.go @@ -64,6 +64,10 @@ func main() { createEmbed(rw, r) }) + r.Get("/status", func(rw http.ResponseWriter, r *http.Request) { + http.Redirect(rw, r, "https://status.pluralkit.me/", http.StatusMovedPermanently) + }) + http.ListenAndServe(":8080", r) } diff --git a/dashboard/src/components/common/Navigation.svelte b/dashboard/src/components/common/Navigation.svelte index daf9db1d..498d6470 100644 --- a/dashboard/src/components/common/Navigation.svelte +++ b/dashboard/src/components/common/Navigation.svelte @@ -53,7 +53,7 @@ Public - Bot status + Bot status diff --git a/docs/content/.vuepress/config.js b/docs/content/.vuepress/config.js index 5bdc945e..b66c56e0 100644 --- a/docs/content/.vuepress/config.js +++ b/docs/content/.vuepress/config.js @@ -18,7 +18,7 @@ module.exports = { }, themeConfig: { - repo: 'PluralKit/PluralKit', + repo: false, docsDir: 'docs/content/', docsBranch: 'main', editLinks: true, @@ -29,7 +29,8 @@ module.exports = { nav: [ { text: "Web dashboard", link: "https://dash.pluralkit.me" }, { text: "Support server", link: "https://discord.gg/PczBt78" }, - { text: "Invite bot", link: "https://discord.com/oauth2/authorize?client_id=466378653216014359&scope=bot%20applications.commands&permissions=536995904" } + { text: "Invite bot", link: "https://discord.com/oauth2/authorize?client_id=466378653216014359&scope=bot%20applications.commands&permissions=536995904" }, + { text: "Bot status", link: "https://status.pluralkit.me/" } ], sidebar: [ "/", diff --git a/flake.lock b/flake.lock index 7298bd18..4024bfd9 100644 --- a/flake.lock +++ b/flake.lock @@ -297,16 +297,16 @@ }, "systems": { "locked": { - "lastModified": 1680978846, - "narHash": "sha256-Gtqg8b/v49BFDpDetjclCYXm8mAnTrUzR0JnE2nv5aw=", + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", "owner": "nix-systems", - "repo": "x86_64-linux", - "rev": "2ecfcac5e15790ba6ce360ceccddb15ad16d08a8", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", "type": "github" }, "original": { "owner": "nix-systems", - "repo": "x86_64-linux", + "repo": "default", "type": "github" } }, diff --git a/flake.nix b/flake.nix index 8fd2ed6b..85793415 100644 --- a/flake.nix +++ b/flake.nix @@ -4,7 +4,7 @@ inputs = { nixpkgs.url = "nixpkgs/nixpkgs-unstable"; parts.url = "github:hercules-ci/flake-parts"; - systems.url = "github:nix-systems/x86_64-linux"; + systems.url = "github:nix-systems/default"; # process compose process-compose.url = "github:Platonic-Systems/process-compose-flake"; services.url = "github:juspay/services-flake";