From 9d0c022c6838ca30ac68491bc63cb800726c3a83 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 18 Mar 2024 01:01:46 +0000 Subject: [PATCH] Many fixes and tweaks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix tags (again?) * Make tag names more consistent * Remove prefix commands (not implemented well and not worth fixing) * Allow '-'s in tags * Fix /joke * Fix /members * Fix intel_hd issue match * Fix log analysis reply * Clearer log analysis messages It's weird to say the process failed when no issues were found. * Clippy * Final doc cleanup * Fix link expanding * Fix duplicate event filtering The other code simply does not work. ChannelId does have a method to grab members but I'm not sure whether it would work either. * Remove message resolution It's surprisingly hard to create an bug-free implementation. * Fix pluralkit detection * simplify tag codegen * commands: improve error handling in members unwrap() bad!!!11!! * events: use debug logs for pk checks * Revert "Remove message resolution" This reverts commit 0d9f224a81917212adafdeb2213f3cc11b44cf88. * Bring back prefix commands with "." (it's easier to type) * Add help * Fix messsage resolution * utils: factor out message resolution * Improve tag message * Disable VC support for message resolution for now * Improve prefix command usage Update on edit, display additional tip with wrong usage. * Check invoke_on_edit to display tip * Add defer in commands which make http requests * Apply tag sorting to slash commands too * handlers::error: `+=` -> `writeln!` * handlers::event: ignore own new messages * help: remove unneeded format! * optimize for size in release builds * nix: cleanup deployment expressions * nix: use treefmt * nix: update flake.lock Flake lock file updates: • Updated input 'fenix': 'github:nix-community/fenix/eb683549b7d76b12d1a009f888b91b70ed34485f' (2024-01-27) → 'github:nix-community/fenix/c53bb4a32f2fce7acf4e8e160a54779c4460ffdb' (2024-03-17) • Updated input 'fenix/rust-analyzer-src': 'github:rust-lang/rust-analyzer/596e5c77cf5b2b660b3ac2ce732fa0596c246d9b' (2024-01-26) → 'github:rust-lang/rust-analyzer/5ecace48f693afaa6adf8cb23086b651db3aec96' (2024-03-16) • Updated input 'nixpkgs': 'github:nixos/nixpkgs/4fddc9be4eaf195d631333908f2a454b03628ee5' (2024-01-25) → 'github:nixos/nixpkgs/34ad8c9f29a18b4dd97a9ad40ceb16954f24afe7' (2024-03-17) • Updated input 'pre-commit-hooks': 'github:cachix/pre-commit-hooks.nix/f56597d53fd174f796b5a7d3ee0b494f9e2285cc' (2024-01-20) → 'github:cachix/pre-commit-hooks.nix/5df5a70ad7575f6601d91f0efec95dd9bc619431' (2024-02-15) • Updated input 'procfile-nix': 'github:getchoo/procfile-nix/31a33e4264e5c6214844993c5b508fb3500ef5cd' (2024-01-27) → 'github:getchoo/procfile-nix/7a0ab379a4ab71c9deccaca9fb463e9aaea363d8' (2024-03-14) --------- Co-authored-by: seth --- Cargo.toml | 6 + build.rs | 63 +++--- flake.lock | 95 +++++---- flake.nix | 12 +- nix/deployment.nix | 41 ++-- nix/derivation.nix | 12 +- nix/dev.nix | 56 +++-- nix/module.nix | 16 +- nix/packages.nix | 8 - src/api/dadjoke.rs | 5 +- src/commands/general/help.rs | 23 +++ src/commands/general/joke.rs | 7 +- src/commands/general/members.rs | 30 ++- src/commands/general/mod.rs | 2 + src/commands/general/ping.rs | 4 +- src/commands/general/rory.rs | 5 +- src/commands/general/say.rs | 25 ++- src/commands/general/stars.rs | 4 +- src/commands/general/tag.rs | 30 ++- src/commands/mod.rs | 1 + src/handlers/error.rs | 46 +++++ src/handlers/event/analyze_logs/issues.rs | 4 +- src/handlers/event/analyze_logs/mod.rs | 11 +- src/handlers/event/expand_link.rs | 5 +- src/handlers/event/mod.rs | 10 +- src/handlers/event/pluralkit.rs | 36 ++-- src/handlers/event/support_onboard.rs | 27 +-- src/main.rs | 5 +- src/tags.rs | 2 +- src/utils/mod.rs | 4 +- src/utils/resolve_message.rs | 193 +++++++++++------- tags/{always-offline.md => always_offline.md} | 0 tags/{binary-search.md => binary_search.md} | 0 tags/{FTB.md => ftb.md} | 0 ...avaforgebugfix.md => java_forge_bugfix.md} | 0 ...egacyjavafixer.md => legacy_java_fixer.md} | 0 tags/{macosarmjava.md => macos_arm_java.md} | 0 tags/{whyjava8.md => why_java_8.md} | 0 38 files changed, 492 insertions(+), 296 deletions(-) create mode 100644 src/commands/general/help.rs rename tags/{always-offline.md => always_offline.md} (100%) rename tags/{binary-search.md => binary_search.md} (100%) rename tags/{FTB.md => ftb.md} (100%) rename tags/{javaforgebugfix.md => java_forge_bugfix.md} (100%) rename tags/{legacyjavafixer.md => legacy_java_fixer.md} (100%) rename tags/{macosarmjava.md => macos_arm_java.md} (100%) rename tags/{whyjava8.md => why_java_8.md} (100%) diff --git a/Cargo.toml b/Cargo.toml index 13040b8..ea5bc06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,3 +41,9 @@ tokio = { version = "1.35.1", features = [ "signal", ] } url = { version = "2.5.0", features = ["serde"] } + +[profile.release] +codegen-units = 1 +opt-level = "s" +panic = "abort" +strip = "symbols" diff --git a/build.rs b/build.rs index 9331c6d..9925fb4 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,4 @@ +use std::io::Write; use std::path::Path; use std::{env, fs}; @@ -9,14 +10,15 @@ include!("src/tags.rs"); #[allow(dead_code)] fn main() { let out_dir = env::var_os("OUT_DIR").unwrap(); - let generated = Path::new(&out_dir).join("generated.rs"); + let dest_file = Path::new(&out_dir).join("generated.rs"); + let mut file = fs::File::create(dest_file).unwrap(); let tag_files: Vec = fs::read_dir(TAG_DIR) .unwrap() .map(|f| f.unwrap().file_name().to_string_lossy().to_string()) .collect(); - let tags: Vec = tag_files + let mut tags: Vec = tag_files .clone() .into_iter() .map(|name| { @@ -39,50 +41,45 @@ fn main() { Tag { content, - file_name: name, + id: name.trim_end_matches(".md").to_string(), frontmatter: data, } }) .collect(); - let formatted_names: Vec = tags + tags.sort_by(|t1, t2| t1.id.cmp(&t2.id)); + + let tag_names: Vec = tags.iter().map(|t| format!("{},", t.id)).collect(); + + let tag_matches: Vec = tags .iter() - .map(|t| t.file_name.replace(".md", "").replace('-', "_")) + .map(|t| format!("Self::{} => \"{}\",", t.id, t.id)) .collect(); - let tag_choice = format!( - r#" - #[allow(non_camel_case_types, clippy::upper_case_acronyms)] - #[derive(Clone, Debug, poise::ChoiceParameter)] - pub enum Choice {{ + writeln!( + file, + r#"#[allow(non_camel_case_types, clippy::upper_case_acronyms)] +#[derive(Clone, Debug, poise::ChoiceParameter)] +pub enum Choice {{ {} - }}"#, - formatted_names.join(",\n") - ); +}}"#, + tag_names.join("\n") + ) + .unwrap(); - let to_str = format!( - r#" - impl Choice {{ - fn as_str(&self) -> &str {{ + writeln!( + file, + r#"impl Choice {{ + fn as_str(&self) -> &str {{ match &self {{ - {} + {} }} - }} - }} - "#, - formatted_names - .iter() - .map(|n| { - let file_name = n.replace('_', "-") + ".md"; - format!("Self::{n} => \"{file_name}\",") - }) - .collect::>() - .join("\n") - ); + }} +}}"#, + tag_matches.join("\n") + ) + .unwrap(); - let contents = Vec::from([tag_choice, to_str]).join("\n\n"); - - fs::write(generated, contents).unwrap(); println!( "cargo:rustc-env=TAGS={}", // make sure we can deserialize with env! at runtime diff --git a/flake.lock b/flake.lock index 6393c20..f88b7db 100644 --- a/flake.lock +++ b/flake.lock @@ -8,11 +8,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1706336364, - "narHash": "sha256-mJ5i2YIVKv6jTN2+l3oOUUej2NUVjJX/H3bAq6019ks=", + "lastModified": 1710656467, + "narHash": "sha256-4Plj0vNP+ckWVNi6EtVojL9YV2dwSH7H4UMFCV40VE8=", "owner": "nix-community", "repo": "fenix", - "rev": "eb683549b7d76b12d1a009f888b91b70ed34485f", + "rev": "c53bb4a32f2fce7acf4e8e160a54779c4460ffdb", "type": "github" }, "original": { @@ -37,6 +37,26 @@ "type": "github" } }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709336216, + "narHash": "sha256-Dt/wOWeW6Sqm11Yh+2+t0dfEWxoMxGBvv3JpIocFl9E=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "f7b3c975cf067e56e7cda6cb098ebe3fb4d74ca2", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, "flake-utils": { "inputs": { "systems": "systems" @@ -98,11 +118,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1706173671, - "narHash": "sha256-lciR7kQUK2FCAYuszyd7zyRRmTaXVeoZsCyK6QFpGdk=", + "lastModified": 1710701596, + "narHash": "sha256-v4lsAi3vE/sEWg0G8AydMjs3NTHlsNw8K01xw06cKLg=", "owner": "nixos", "repo": "nixpkgs", - "rev": "4fddc9be4eaf195d631333908f2a454b03628ee5", + "rev": "34ad8c9f29a18b4dd97a9ad40ceb16954f24afe7", "type": "github" }, "original": { @@ -128,26 +148,6 @@ "type": "github" } }, - "parts": { - "inputs": { - "nixpkgs-lib": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1704982712, - "narHash": "sha256-2Ptt+9h8dczgle2Oo6z5ni5rt/uLMG47UFTR1ry/wgg=", - "owner": "hercules-ci", - "repo": "flake-parts", - "rev": "07f6395285469419cf9d078f59b5b49993198c00", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "flake-parts", - "type": "github" - } - }, "pre-commit-hooks": { "inputs": { "flake-compat": "flake-compat", @@ -159,11 +159,11 @@ "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1705757126, - "narHash": "sha256-Eksr+n4Q8EYZKAN0Scef5JK4H6FcHc+TKNHb95CWm+c=", + "lastModified": 1708018599, + "narHash": "sha256-M+Ng6+SePmA8g06CmUZWi1AjG2tFBX9WCXElBHEKnyM=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "f56597d53fd174f796b5a7d3ee0b494f9e2285cc", + "rev": "5df5a70ad7575f6601d91f0efec95dd9bc619431", "type": "github" }, "original": { @@ -179,11 +179,11 @@ ] }, "locked": { - "lastModified": 1706387387, - "narHash": "sha256-7C3HncC25yK1kvLp+/9KoBa1Iz5Ly2JtICqmCz2nvio=", + "lastModified": 1710407041, + "narHash": "sha256-rCHklFHPzrq341KoTgXNdknNZbjOJ+VmalqX5s5YdGM=", "owner": "getchoo", "repo": "procfile-nix", - "rev": "31a33e4264e5c6214844993c5b508fb3500ef5cd", + "rev": "7a0ab379a4ab71c9deccaca9fb463e9aaea363d8", "type": "github" }, "original": { @@ -195,21 +195,22 @@ "root": { "inputs": { "fenix": "fenix", + "flake-parts": "flake-parts", "naersk": "naersk", "nixpkgs": "nixpkgs", - "parts": "parts", "pre-commit-hooks": "pre-commit-hooks", - "procfile-nix": "procfile-nix" + "procfile-nix": "procfile-nix", + "treefmt-nix": "treefmt-nix" } }, "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1706295183, - "narHash": "sha256-VSyMaUsXfjb31B8/uT5cM5qXC1VOHLVsCi/bQuo3O/g=", + "lastModified": 1710610549, + "narHash": "sha256-xFIGLn5u+msUazlLbdjZ3gQgXrt7Lrlhq+XXUH0XU/0=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "596e5c77cf5b2b660b3ac2ce732fa0596c246d9b", + "rev": "5ecace48f693afaa6adf8cb23086b651db3aec96", "type": "github" }, "original": { @@ -233,6 +234,26 @@ "repo": "default", "type": "github" } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1710278050, + "narHash": "sha256-Oc6BP7soXqb8itlHI8UKkdf3V9GeJpa1S39SR5+HJys=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "35791f76524086ab4b785a33e4abbedfda64bd22", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 31f6c29..47a0194 100644 --- a/flake.nix +++ b/flake.nix @@ -3,7 +3,7 @@ inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; - parts = { + flake-parts = { url = "github:hercules-ci/flake-parts"; inputs.nixpkgs-lib.follows = "nixpkgs"; }; @@ -27,10 +27,15 @@ url = "github:getchoo/procfile-nix"; inputs.nixpkgs.follows = "nixpkgs"; }; + + treefmt-nix = { + url = "github:numtide/treefmt-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; - outputs = {parts, ...} @ inputs: - parts.lib.mkFlake {inherit inputs;} { + outputs = {flake-parts, ...} @ inputs: + flake-parts.lib.mkFlake {inherit inputs;} { imports = [ ./nix/dev.nix ./nix/packages.nix @@ -38,6 +43,7 @@ inputs.pre-commit-hooks.flakeModule inputs.procfile-nix.flakeModule + inputs.treefmt-nix.flakeModule ]; systems = [ diff --git a/nix/deployment.nix b/nix/deployment.nix index f22a9fe..115318b 100644 --- a/nix/deployment.nix +++ b/nix/deployment.nix @@ -1,9 +1,12 @@ { inputs, - self, + flake-parts-lib, + withSystem, ... }: { - flake.nixosModules.default = import ./module.nix self; + flake.nixosModules.default = flake-parts-lib.importApply ./module.nix { + inherit withSystem; + }; perSystem = { lib, @@ -13,8 +16,8 @@ inputs', ... }: let - crossPkgsFor = - { + crossPkgs = + rec { x86_64-linux = { x86_64 = pkgs.pkgsStatic; aarch64 = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic; @@ -30,11 +33,13 @@ aarch64 = pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic; }; - aarch64-darwin = crossPkgsFor.x86_64-darwin; + aarch64-darwin = x86_64-darwin; } .${system}; - exeFor = arch: let + refractionFor = arch: let + inherit (crossPkgs.${arch}.stdenv) cc; + target = "${arch}-unknown-linux-musl"; target' = builtins.replaceStrings ["-"] ["_"] target; targetUpper = lib.toUpper target'; @@ -52,8 +57,8 @@ }; refraction = self'.packages.refraction.override { + lto = true; naersk = naersk'; - optimizeSize = true; }; newAttrs = { @@ -62,26 +67,26 @@ "CARGO_TARGET_${targetUpper}_RUSTFLAGS" = "-C target-feature=+crt-static"; "CARGO_TARGET_${targetUpper}_LINKER" = newAttrs."CC_${target'}"; }; - - inherit (crossPkgsFor.${arch}.stdenv) cc; in - lib.getExe ( - refraction.overrideAttrs (lib.const newAttrs) - ); + refraction.overrideAttrs newAttrs; containerFor = arch: pkgs.dockerTools.buildLayeredImage { name = "refraction"; tag = "latest-${arch}"; contents = [pkgs.dockerTools.caCertificates]; - config.Cmd = [(exeFor arch)]; + config.Cmd = [ + (lib.getExe (refractionFor arch)) + ]; - architecture = crossPkgsFor.${arch}.go.GOARCH; + architecture = crossPkgs.${arch}.go.GOARCH; }; - in { - legacyPackages = { - container-x86_64 = containerFor "x86_64"; - container-aarch64 = containerFor "aarch64"; + + mkPackagesFor = arch: { + "refraction-static-${arch}" = refractionFor arch; + "container-${arch}" = containerFor arch; }; + in { + legacyPackages = lib.attrsets.mergeAttrsList (map mkPackagesFor ["x86_64" "aarch64"]); }; } diff --git a/nix/derivation.nix b/nix/derivation.nix index 6553dda..9e37e05 100644 --- a/nix/derivation.nix +++ b/nix/derivation.nix @@ -2,11 +2,9 @@ lib, stdenv, naersk, - CoreFoundation, - Security, - SystemConfiguration, + darwin, version, - optimizeSize ? false, + lto ? false, }: naersk.buildPackage { pname = "refraction"; @@ -23,13 +21,13 @@ naersk.buildPackage { ]; }; - buildInputs = lib.optionals stdenv.hostPlatform.isDarwin [ + buildInputs = lib.optionals stdenv.hostPlatform.isDarwin (with darwin.apple_sdk.frameworks; [ CoreFoundation Security SystemConfiguration - ]; + ]); - cargoBuildFlags = lib.optionals optimizeSize ["-C" "codegen-units=1" "-C" "strip=symbols" "-C" "opt-level=z"]; + cargoBuildFlags = lib.optionals lto ["-C" "lto=thin" "-C" "embed-bitcode=yes" "-Zdylib-lto"]; meta = with lib; { mainProgram = "refraction"; diff --git a/nix/dev.nix b/nix/dev.nix index 7d1a47e..b9bdf2f 100644 --- a/nix/dev.nix +++ b/nix/dev.nix @@ -5,24 +5,9 @@ config, self', ... - }: { - pre-commit.settings.hooks = { - actionlint.enable = true; - alejandra.enable = true; - deadnix.enable = true; - rustfmt.enable = true; - statix.enable = true; - nil.enable = true; - prettier = { - enable = true; - excludes = ["flake.lock"]; - }; - }; - - procfiles.daemons.processes = { - redis = lib.getExe' pkgs.redis "redis-server"; - }; - + }: let + enableAll = lib.flip lib.genAttrs (lib.const {enable = true;}); + in { devShells.default = pkgs.mkShell { shellHook = '' ${config.pre-commit.installationScript} @@ -31,6 +16,7 @@ packages = with pkgs; [ # general actionlint + nodePackages.prettier config.procfiles.daemons.package # rust @@ -50,6 +36,38 @@ RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; }; - formatter = pkgs.alejandra; + treefmt = { + projectRootFile = "flake.nix"; + + programs = enableAll [ + "alejandra" + "deadnix" + "prettier" + "rustfmt" + ]; + + settings.global = { + excludes = [ + "./target" + "./flake.lock" + "./Cargo.lock" + ]; + }; + }; + + pre-commit.settings = { + settings.treefmt.package = config.treefmt.build.wrapper; + + hooks = enableAll [ + "actionlint" + "nil" + "statix" + "treefmt" + ]; + }; + + procfiles.daemons.processes = { + redis = lib.getExe' pkgs.redis "redis-server"; + }; }; } diff --git a/nix/module.nix b/nix/module.nix index baee5fe..eccdbc2 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -1,4 +1,4 @@ -self: { +{withSystem, ...}: { config, lib, pkgs, @@ -22,7 +22,9 @@ self: { in { options.services.refraction = { enable = mkEnableOption "refraction"; - package = mkPackageOption self.packages.${pkgs.stdenv.hostPlatform.system} "refraction" {}; + package = mkPackageOption ( + withSystem pkgs.stdenv.hostPlatform.system ({pkgs, ...}: pkgs) + ) "refraction" {}; user = mkOption { description = mdDoc '' @@ -102,7 +104,7 @@ in { serviceConfig = { Type = "simple"; - Restart = "always"; + Restart = "on-failure"; EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile; @@ -122,8 +124,14 @@ in { ProtectKernelModules = true; ProtectKernelTunables = true; ProtectSystem = "strict"; - RestrictNamespaces = "uts ipc pid user cgroup"; + RestrictNamespaces = true; RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@resources" + "~@privileged" + ]; }; }; diff --git a/nix/packages.nix b/nix/packages.nix index a7d4333..19f5730 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -12,14 +12,6 @@ packages = { refraction = pkgs.callPackage ./derivation.nix { version = builtins.substring 0 7 self.rev or "dirty"; - - inherit - (pkgs.darwin.apple_sdk.frameworks) - CoreFoundation - Security - SystemConfiguration - ; - naersk = inputs.naersk.lib.${system}; }; diff --git a/src/api/dadjoke.rs b/src/api/dadjoke.rs index b80d5ec..e1e7962 100644 --- a/src/api/dadjoke.rs +++ b/src/api/dadjoke.rs @@ -7,7 +7,10 @@ use reqwest::StatusCode; const DADJOKE: &str = "https://icanhazdadjoke.com"; pub async fn get_joke() -> Result { - let req = REQWEST_CLIENT.get(DADJOKE).build()?; + let req = REQWEST_CLIENT + .get(DADJOKE) + .header("Accept", "text/plain") + .build()?; debug!("Making request to {}", req.url()); let resp = REQWEST_CLIENT.execute(req).await?; diff --git a/src/commands/general/help.rs b/src/commands/general/help.rs new file mode 100644 index 0000000..bdf3047 --- /dev/null +++ b/src/commands/general/help.rs @@ -0,0 +1,23 @@ +use eyre::Result; +use poise::{builtins, samples::HelpConfiguration}; + +use crate::Context; + +/// View the help menu +#[poise::command(slash_command, prefix_command, track_edits = true)] +pub async fn help( + ctx: Context<'_>, + #[description = "provide information about a specific command"] command: Option, +) -> Result<()> { + builtins::help( + ctx, + command.as_deref(), + HelpConfiguration { + extra_text_at_bottom: "Use /help for more information about a specific command!", + ..HelpConfiguration::default() + }, + ) + .await?; + + Ok(()) +} diff --git a/src/commands/general/joke.rs b/src/commands/general/joke.rs index b6b2c60..fb39d20 100644 --- a/src/commands/general/joke.rs +++ b/src/commands/general/joke.rs @@ -5,11 +5,14 @@ use eyre::Result; use log::trace; /// It's a joke -#[poise::command(slash_command, prefix_command)] +#[poise::command(slash_command, prefix_command, track_edits = true)] pub async fn joke(ctx: Context<'_>) -> Result<()> { trace!("Running joke command"); + + ctx.defer().await?; + let joke = dadjoke::get_joke().await?; - ctx.reply(joke).await?; + ctx.say(joke).await?; Ok(()) } diff --git a/src/commands/general/members.rs b/src/commands/general/members.rs index 39da469..94204cc 100644 --- a/src/commands/general/members.rs +++ b/src/commands/general/members.rs @@ -1,26 +1,34 @@ use crate::{consts, Context}; -use eyre::{OptionExt, Result}; +use eyre::{eyre, Context as _, OptionExt, Result}; use log::trace; use poise::serenity_prelude::CreateEmbed; use poise::CreateReply; /// Returns the number of members in the server -#[poise::command(slash_command, prefix_command)] +#[poise::command(slash_command, prefix_command, guild_only = true, track_edits = true)] pub async fn members(ctx: Context<'_>) -> Result<()> { trace!("Running members command"); - let guild = ctx.guild().ok_or_eyre("Couldn't fetch guild!")?.to_owned(); - let count = guild.member_count; - let online = if let Some(count) = guild.approximate_presence_count { - count.to_string() - } else { - "Undefined".to_string() - }; + ctx.defer().await?; + + let guild_id = ctx.guild_id().ok_or_eyre("Couldn't get guild ID!")?; + let guild = ctx + .http() + .get_guild_with_counts(guild_id) + .await + .wrap_err_with(|| format!("Couldn't fetch guild {guild_id} with counts!"))?; + + let member_count = guild + .approximate_member_count + .ok_or_else(|| eyre!("Couldn't get member count for guild {guild_id}!"))?; + let online_count = guild + .approximate_presence_count + .ok_or_else(|| eyre!("Couldn't get online count for guild {guild_id}!"))?; let embed = CreateEmbed::new() - .title(format!("{count} total members!")) - .description(format!("{online} online members")) + .title(format!("{member_count} total members!",)) + .description(format!("{online_count} online members",)) .color(consts::COLORS["blue"]); let reply = CreateReply::default().embed(embed); diff --git a/src/commands/general/mod.rs b/src/commands/general/mod.rs index e514e39..050caff 100644 --- a/src/commands/general/mod.rs +++ b/src/commands/general/mod.rs @@ -1,3 +1,4 @@ +mod help; mod joke; mod members; mod ping; @@ -6,6 +7,7 @@ mod say; mod stars; mod tag; +pub use help::help; pub use joke::joke; pub use members::members; pub use ping::ping; diff --git a/src/commands/general/ping.rs b/src/commands/general/ping.rs index f21a7c0..90fc53f 100644 --- a/src/commands/general/ping.rs +++ b/src/commands/general/ping.rs @@ -4,9 +4,9 @@ use eyre::Result; use log::trace; /// Replies with pong! -#[poise::command(slash_command, prefix_command, ephemeral)] +#[poise::command(slash_command, prefix_command, track_edits = true, ephemeral)] pub async fn ping(ctx: Context<'_>) -> Result<()> { trace!("Running ping command!"); - ctx.reply("Pong!").await?; + ctx.say("Pong!").await?; Ok(()) } diff --git a/src/commands/general/rory.rs b/src/commands/general/rory.rs index ea2b0a1..6bdc6f5 100644 --- a/src/commands/general/rory.rs +++ b/src/commands/general/rory.rs @@ -7,12 +7,15 @@ use poise::serenity_prelude::{CreateEmbed, CreateEmbedFooter}; use poise::CreateReply; /// Gets a Rory photo! -#[poise::command(slash_command, prefix_command)] +#[poise::command(slash_command, prefix_command, track_edits = true)] pub async fn rory( ctx: Context<'_>, #[description = "specify a Rory ID"] id: Option, ) -> Result<()> { trace!("Running rory command"); + + ctx.defer().await?; + let rory = rory::get(id).await?; let embed = { diff --git a/src/commands/general/say.rs b/src/commands/general/say.rs index eeb2e9b..afeee0d 100644 --- a/src/commands/general/say.rs +++ b/src/commands/general/say.rs @@ -9,18 +9,30 @@ use poise::serenity_prelude::{CreateEmbed, CreateEmbedAuthor, CreateMessage}; prefix_command, ephemeral, default_member_permissions = "MODERATE_MEMBERS", - required_permissions = "MODERATE_MEMBERS" + required_permissions = "MODERATE_MEMBERS", + guild_only = true )] -pub async fn say(ctx: Context<'_>, #[description = "Just content?"] content: String) -> Result<()> { +pub async fn say( + ctx: Context<'_>, + #[description = "the message content"] content: String, +) -> Result<()> { let guild = ctx.guild().ok_or_eyre("Couldn't get guild!")?.to_owned(); let channel = ctx .guild_channel() .await .ok_or_eyre("Couldn't get channel!")?; + if let Context::Prefix(prefix) = ctx { + // ignore error, we might not have perm + let _ = prefix.msg.delete(ctx).await; + } + ctx.defer_ephemeral().await?; channel.say(ctx, &content).await?; - ctx.say("I said what you said!").await?; + + if let Context::Application(_) = ctx { + ctx.say("I said what you said!").await?; + } if let Some(channel_id) = ctx.data().config.discord.channels().say_log_channel_id() { let log_channel = guild @@ -29,8 +41,11 @@ pub async fn say(ctx: Context<'_>, #[description = "Just content?"] content: Str .find(|c| c.0 == &channel_id) .ok_or_eyre("Couldn't get log channel from guild!")?; - let author = CreateEmbedAuthor::new(ctx.author().tag()) - .icon_url(ctx.author().avatar_url().unwrap_or("Undefined".to_string())); + let author = CreateEmbedAuthor::new(ctx.author().tag()).icon_url( + ctx.author() + .avatar_url() + .unwrap_or_else(|| ctx.author().default_avatar_url()), + ); let embed = CreateEmbed::default() .title("Say command used!") diff --git a/src/commands/general/stars.rs b/src/commands/general/stars.rs index 200a562..7cbbd9b 100644 --- a/src/commands/general/stars.rs +++ b/src/commands/general/stars.rs @@ -6,10 +6,12 @@ use poise::serenity_prelude::CreateEmbed; use poise::CreateReply; /// Returns GitHub stargazer count -#[poise::command(slash_command, prefix_command)] +#[poise::command(slash_command, prefix_command, track_edits = true)] pub async fn stars(ctx: Context<'_>) -> Result<()> { trace!("Running stars command"); + ctx.defer().await?; + let prismlauncher = ctx .data() .octocrab diff --git a/src/commands/general/tag.rs b/src/commands/general/tag.rs index 5d95e28..43fa7a8 100644 --- a/src/commands/general/tag.rs +++ b/src/commands/general/tag.rs @@ -13,19 +13,24 @@ include!(concat!(env!("OUT_DIR"), "/generated.rs")); static TAGS: Lazy> = Lazy::new(|| serde_json::from_str(env!("TAGS")).unwrap()); /// Send a tag -#[poise::command(slash_command, prefix_command)] +#[poise::command( + slash_command, + prefix_command, + track_edits = true, + help_text_fn = help +)] pub async fn tag( ctx: Context<'_>, - #[description = "the copypasta you want to send"] name: Choice, - user: Option, + #[description = "the tag to send"] name: Choice, + #[description = "a user to mention"] user: Option, ) -> Result<()> { trace!("Running tag command"); - let tag_file = name.as_str(); + let tag_id = name.as_str(); let tag = TAGS .iter() - .find(|t| t.file_name == tag_file) - .ok_or_else(|| eyre!("Tried to get non-existent tag: {tag_file}"))?; + .find(|t| t.id == tag_id) + .ok_or_else(|| eyre!("Tried to get non-existent tag: {tag_id}"))?; let frontmatter = &tag.frontmatter; @@ -49,6 +54,9 @@ pub async fn tag( } } + e = e.title(&frontmatter.title); + e = e.description(&tag.content); + e }; @@ -66,3 +74,13 @@ pub async fn tag( Ok(()) } + +fn help() -> String { + let tag_list = TAGS + .iter() + .map(|tag| format!("`{}`", tag.id)) + .collect::>() + .join(", "); + + format!("Available tags: {tag_list}") +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 00130a7..0d44cbe 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -14,5 +14,6 @@ pub fn get() -> Vec> { general::say(), general::stars(), general::tag(), + general::help(), ] } diff --git a/src/handlers/error.rs b/src/handlers/error.rs index 4dce9b5..b0e8462 100644 --- a/src/handlers/error.rs +++ b/src/handlers/error.rs @@ -1,11 +1,21 @@ use crate::consts; use crate::Data; +use std::fmt::Write; use eyre::Report; use log::error; use poise::serenity_prelude::{CreateEmbed, Timestamp}; use poise::{CreateReply, FrameworkError}; +// getchoo: i like writeln! and don't like +macro_rules! writelne { + ($dst:expr, $($arg:tt)*) => { + if let Err(why) = writeln!($dst, $($arg)*) { + error!("We somehow cannot write to what should be on the heap. What are you using this macro with? Anyways, here's the error:\n{why:#?}"); + } + } +} + pub async fn handle(error: FrameworkError<'_, Data, Report>) { match error { FrameworkError::Setup { @@ -44,6 +54,42 @@ pub async fn handle(error: FrameworkError<'_, Data, Report>) { ); } + FrameworkError::ArgumentParse { + error, input, ctx, .. + } => { + let mut response = String::new(); + + if let Some(input) = input { + writelne!( + &mut response, + "**Cannot parse `{input}` as argument: {error}**\n" + ); + } else { + writelne!(&mut response, "**{error}**\n"); + } + + if let Some(help_text) = ctx.command().help_text.as_ref() { + writelne!(&mut response, "{help_text}\n"); + } + + if ctx.command().invoke_on_edit { + writelne!( + &mut response, + "**Tip:** Edit your message to update the response." + ); + } + + writelne!( + &mut response, + "For more information, refer to /help {}.", + ctx.command().name + ); + + if let Err(why) = ctx.say(response).await { + error!("Unhandled error displaying ArgumentParse error\n{why:#?}"); + } + } + error => { if let Err(e) = poise::builtins::on_error(error).await { error!("Unhandled error occurred:\n{e:#?}"); diff --git a/src/handlers/event/analyze_logs/issues.rs b/src/handlers/event/analyze_logs/issues.rs index 11208c7..575be42 100644 --- a/src/handlers/event/analyze_logs/issues.rs +++ b/src/handlers/event/analyze_logs/issues.rs @@ -95,7 +95,7 @@ fn intel_hd(log: &str) -> Issue { See https://prismlauncher.org/wiki/getting-started/installing-java/#a-note-about-intel-hd-20003000-on-windows-10 for more info".to_string() ); - let found = log.contains("java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier.(Ljava/util/jar/Manifest;)V"); + let found = log.contains("org.lwjgl.LWJGLException: Pixel format not accelerated"); found.then_some(issue) } @@ -204,7 +204,7 @@ async fn outdated_launcher(log: &str, data: &Data) -> Result { if version_from_log < latest_version { let issue = ( - "Outdated Prism Launcher".to_string(), + "Outdated Prism Launcher".to_string(), format!("Your installed version is {version_from_log}, while the newest version is {latest_version}.\nPlease update, for more info see https://prismlauncher.org/download/") ); diff --git a/src/handlers/event/analyze_logs/mod.rs b/src/handlers/event/analyze_logs/mod.rs index 15df9eb..1d5481d 100644 --- a/src/handlers/event/analyze_logs/mod.rs +++ b/src/handlers/event/analyze_logs/mod.rs @@ -19,7 +19,7 @@ pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()> if log.is_err() { let embed = CreateEmbed::new() - .title("Analyze failed!") + .title("Analysis failed!") .description("Couldn't download log"); let allowed_mentions = CreateAllowedMentions::new().replied_user(true); let our_message = CreateMessage::new() @@ -43,11 +43,9 @@ pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()> let mut e = CreateEmbed::new().title("Log analysis"); if issues.is_empty() { - e = e.color(COLORS["green"]).field( - "Analyze failed!", - "No issues found automatically", - false, - ); + e = e + .color(COLORS["green"]) + .description("No issues found automatically"); } else { e = e.color(COLORS["red"]); @@ -61,6 +59,7 @@ pub async fn handle(ctx: &Context, message: &Message, data: &Data) -> Result<()> let allowed_mentions = CreateAllowedMentions::new().replied_user(true); let message = CreateMessage::new() + .reference_message(message) .allowed_mentions(allowed_mentions) .embed(embed); diff --git a/src/handlers/event/expand_link.rs b/src/handlers/event/expand_link.rs index 05f6be0..ae73b56 100644 --- a/src/handlers/event/expand_link.rs +++ b/src/handlers/event/expand_link.rs @@ -4,13 +4,12 @@ use poise::serenity_prelude::{Context, CreateAllowedMentions, CreateMessage, Mes use crate::utils; pub async fn handle(ctx: &Context, message: &Message) -> Result<()> { - let embeds = utils::resolve_message(ctx, message).await?; + let embeds = utils::resolve_message::from_message(ctx, message).await?; - // TODO getchoo: actually reply to user - // ...not sure why Message doesn't give me a builder in reply() or equivalents if !embeds.is_empty() { let allowed_mentions = CreateAllowedMentions::new().replied_user(false); let reply = CreateMessage::new() + .reference_message(message) .embeds(embeds) .allowed_mentions(allowed_mentions); diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index 83cb7bf..53b2952 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -15,7 +15,7 @@ mod support_onboard; pub async fn handle( ctx: &Context, event: &FullEvent, - framework: FrameworkContext<'_, Data, Report>, + _framework: FrameworkContext<'_, Data, Report>, data: &Data, ) -> Result<()> { match event { @@ -31,8 +31,10 @@ pub async fn handle( FullEvent::Message { new_message } => { // ignore new messages from bots - // NOTE: the webhook_id check allows us to still respond to PK users - if new_message.author.bot && new_message.webhook_id.is_none() { + // note: the webhook_id check allows us to still respond to PK users + if (new_message.author.bot && new_message.webhook_id.is_none()) + || new_message.is_own(ctx) + { trace!("Ignoring message {} from bot", new_message.id); return Ok(()); } @@ -57,7 +59,7 @@ pub async fn handle( } FullEvent::ThreadCreate { thread } => { - support_onboard::handle(ctx, thread, framework).await?; + support_onboard::handle(ctx, thread).await?; } _ => {} diff --git a/src/handlers/event/pluralkit.rs b/src/handlers/event/pluralkit.rs index e37b79a..ee8977a 100644 --- a/src/handlers/event/pluralkit.rs +++ b/src/handlers/event/pluralkit.rs @@ -2,18 +2,18 @@ use crate::{api, Data}; use std::time::Duration; use eyre::Result; -use log::trace; +use log::{debug, trace}; use poise::serenity_prelude::{Context, Message}; use tokio::time::sleep; -const PK_DELAY_SEC: Duration = Duration::from_secs(1000); +const PK_DELAY: Duration = Duration::from_secs(1); pub async fn is_message_proxied(message: &Message) -> Result { trace!( "Waiting on PluralKit API for {} seconds", - PK_DELAY_SEC.as_secs() + PK_DELAY.as_secs() ); - sleep(PK_DELAY_SEC).await; + sleep(PK_DELAY).await; let proxied = api::pluralkit::get_sender(message.id).await.is_ok(); @@ -21,21 +21,23 @@ pub async fn is_message_proxied(message: &Message) -> Result { } pub async fn handle(_: &Context, msg: &Message, data: &Data) -> Result<()> { - if msg.webhook_id.is_some() { - trace!( - "Message {} has a webhook ID. Checking if it was sent through PluralKit", - msg.id - ); + if msg.webhook_id.is_none() { + return Ok(()); + } - trace!( - "Waiting on PluralKit API for {} seconds", - PK_DELAY_SEC.as_secs() - ); - sleep(PK_DELAY_SEC).await; + debug!( + "Message {} has a webhook ID. Checking if it was sent through PluralKit", + msg.id + ); - if let Ok(sender) = api::pluralkit::get_sender(msg.id).await { - data.storage.store_user_plurality(sender).await?; - } + trace!( + "Waiting on PluralKit API for {} seconds", + PK_DELAY.as_secs() + ); + sleep(PK_DELAY).await; + + if let Ok(sender) = api::pluralkit::get_sender(msg.id).await { + data.storage.store_user_plurality(sender).await?; } Ok(()) diff --git a/src/handlers/event/support_onboard.rs b/src/handlers/event/support_onboard.rs index f3aa6b1..f2d2d5f 100644 --- a/src/handlers/event/support_onboard.rs +++ b/src/handlers/event/support_onboard.rs @@ -1,36 +1,17 @@ -use crate::Data; - -use eyre::{eyre, Context as _, OptionExt, Report, Result}; +use eyre::{eyre, OptionExt, Result}; use log::{debug, trace}; use poise::serenity_prelude::{ ChannelType, Context, CreateAllowedMentions, CreateMessage, GuildChannel, }; -use poise::FrameworkContext; -pub async fn handle( - ctx: &Context, - thread: &GuildChannel, - framework: FrameworkContext<'_, Data, Report>, -) -> Result<()> { +pub async fn handle(ctx: &Context, thread: &GuildChannel) -> Result<()> { if thread.kind != ChannelType::PublicThread { trace!("Not doing support onboard in non-public thread channel"); return Ok(()); } - // TODO @getchoo: it seems like we can get multiple ThreadCreate events - // should probably figure out a better way to not repeat ourselves here - if thread - .members(ctx) - .wrap_err_with(|| { - format!( - "Couldn't fetch members from thread {}! Not sending a support onboard message.", - thread.id - ) - })? - .iter() - .any(|member| member.user.id == framework.bot_id) - { - debug!("Not sending support onboard message...I think i've been here before :p"); + if thread.last_message_id.is_some() { + debug!("Ignoring duplicate thread creation event"); return Ok(()); } diff --git a/src/main.rs b/src/main.rs index 94b35e6..93d7e5b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,8 +2,7 @@ #![allow(clippy::missing_errors_doc)] #![forbid(unsafe_code)] -use std::sync::Arc; -use std::time::Duration; +use std::{sync::Arc, time::Duration}; use eyre::{eyre, Context as _, Report, Result}; use log::{info, trace, warn}; @@ -111,7 +110,7 @@ async fn main() -> Result<()> { }, prefix_options: PrefixFrameworkOptions { - prefix: Some("r".into()), + prefix: Some(".".into()), edit_tracker: Some(Arc::from(EditTracker::for_timespan(Duration::from_secs( 3600, )))), diff --git a/src/tags.rs b/src/tags.rs index ab77b1e..2d6336d 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -15,6 +15,6 @@ pub struct TagFrontmatter { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Tag { pub content: String, - pub file_name: String, + pub id: String, pub frontmatter: TagFrontmatter, } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 8cfb89f..3df2c9a 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,3 +1 @@ -mod resolve_message; - -pub use resolve_message::resolve as resolve_message; +pub mod resolve_message; diff --git a/src/utils/resolve_message.rs b/src/utils/resolve_message.rs index 4e4d9b5..c237eda 100644 --- a/src/utils/resolve_message.rs +++ b/src/utils/resolve_message.rs @@ -4,17 +4,14 @@ use eyre::{eyre, Context as _, Result}; use log::{debug, trace}; use once_cell::sync::Lazy; use poise::serenity_prelude::{ - ChannelId, ChannelType, Colour, Context, CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter, - Message, MessageId, + Cache, CacheHttp, ChannelId, ChannelType, Colour, Context, CreateEmbed, CreateEmbedAuthor, + CreateEmbedFooter, GuildChannel, Member, Message, MessageId, Permissions, }; use regex::Regex; -static MESSAGE_PATTERN: Lazy = Lazy::new(|| { - Regex::new(r"/(https?:\/\/)?(?:canary\.|ptb\.)?discord(?:app)?\.com\/channels\/(?\d+)\/(?\d+)\/(?\d+)/g;").unwrap() -}); - -fn find_first_image(msg: &Message) -> Option { - msg.attachments +fn find_first_image(message: &Message) -> Option { + message + .attachments .iter() .find(|a| { a.content_type @@ -25,86 +22,130 @@ fn find_first_image(msg: &Message) -> Option { .map(|res| res.url.clone()) } -pub async fn resolve(ctx: &Context, msg: &Message) -> Result> { +async fn member_can_view_channel( + ctx: impl CacheHttp + AsRef, + member: &Member, + channel: &GuildChannel, +) -> Result { + static REQUIRED_PERMISSIONS: Lazy = + Lazy::new(|| Permissions::VIEW_CHANNEL | Permissions::READ_MESSAGE_HISTORY); + + let guild = ctx.http().get_guild(channel.guild_id).await?; + + let channel_to_check = match &channel.kind { + ChannelType::PublicThread => { + let parent_id = channel + .parent_id + .ok_or_else(|| eyre!("Couldn't get parent of thread {}", channel.id))?; + parent_id + .to_channel(ctx) + .await? + .guild() + .ok_or_else(|| eyre!("Couldn't get GuildChannel from ChannelID {parent_id}!"))? + } + + ChannelType::Text | ChannelType::News => channel.to_owned(), + + _ => return Ok(false), + }; + + let can_view = guild + .user_permissions_in(&channel_to_check, member) + .contains(*REQUIRED_PERMISSIONS); + Ok(can_view) +} + +pub async fn to_embed( + ctx: impl CacheHttp + AsRef, + message: &Message, +) -> Result { + let author = CreateEmbedAuthor::new(message.author.tag()).icon_url( + message + .author + .avatar_url() + .unwrap_or_else(|| message.author.default_avatar_url()), + ); + + let footer = CreateEmbedFooter::new(format!( + "#{}", + message.channel(ctx).await?.guild().unwrap_or_default().name + )); + + let mut embed = CreateEmbed::new() + .author(author) + .color(Colour::BLITZ_BLUE) + .timestamp(message.timestamp) + .footer(footer) + .description(format!( + "{}\n\n[Jump to original message]({})", + message.content, + message.link() + )); + + if !message.attachments.is_empty() { + embed = embed.fields(message.attachments.iter().map(|a| { + ( + "Attachments".to_string(), + format!("[{}]({})", a.filename, a.url), + false, + ) + })); + + if let Some(image) = find_first_image(message) { + embed = embed.image(image); + } + } + + Ok(embed) +} + +pub async fn from_message(ctx: &Context, msg: &Message) -> Result> { + static MESSAGE_PATTERN: Lazy = Lazy::new(|| { + Regex::new(r"(?:https?:\/\/)?(?:canary\.|ptb\.)?discord(?:app)?\.com\/channels\/(?\d+)\/(?\d+)\/(?\d+)").unwrap() + }); + + let Some(guild_id) = msg.guild_id else { + debug!("Not resolving message in DM"); + return Ok(Vec::new()); + }; + let author = guild_id.member(ctx, msg.author.id).await?; + let matches = MESSAGE_PATTERN .captures_iter(&msg.content) .map(|capture| capture.extract()); let mut embeds: Vec = vec![]; - for (url, [_server_id, channel_id, message_id]) in matches { - trace!("Attempting to resolve message {message_id} from URL {url}"); - - let channel = ChannelId::from_str(channel_id) - .wrap_err_with(|| format!("Couldn't parse channel ID {channel_id}!"))? - .to_channel_cached(ctx.as_ref()) - .ok_or_else(|| eyre!("Couldn't find Guild Channel from {channel_id}!"))? - .to_owned(); - - let author_can_view = if channel.kind == ChannelType::PublicThread - || channel.kind == ChannelType::PrivateThread - { - let thread_members = channel - .id - .get_thread_members(ctx) - .await - .wrap_err("Couldn't get members from thread!")?; - - thread_members - .iter() - .any(|member| member.user_id == msg.author.id) - } else { - channel - .members(ctx) - .wrap_err_with(|| format!("Couldn't get members for channel {channel_id}!"))? - .iter() - .any(|member| member.user.id == msg.author.id) - }; - - if !author_can_view { - debug!("Not resolving message for author who can't see it"); + for (url, [target_guild_id, target_channel_id, target_message_id]) in matches { + if target_guild_id != guild_id.to_string() { + debug!("Not resolving message from other server"); + continue; } + trace!("Attempting to resolve message {target_message_id} from URL {url}"); - let original_message = channel - .message( - ctx, - MessageId::from_str(message_id) - .wrap_err_with(|| format!("Couldn't parse message ID {message_id}!"))?, - ) - .await - .wrap_err_with(|| { - format!("Couldn't get message from ID {message_id} in channel {channel_id}!") + let target_channel = ChannelId::from_str(target_channel_id)? + .to_channel(ctx) + .await? + .guild() + .ok_or_else(|| { + eyre!("Couldn't find GuildChannel from ChannelId {target_channel_id}!") })?; - let author = CreateEmbedAuthor::new(original_message.author.tag()) - .icon_url(original_message.author.default_avatar_url()); - let footer = CreateEmbedFooter::new(format!("#{}", channel.name)); - - let mut embed = CreateEmbed::new() - .author(author) - .color(Colour::BLITZ_BLUE) - .timestamp(original_message.timestamp) - .footer(footer) - .description(format!( - "{}\n\n[Jump to original message]({})", - original_message.content, - original_message.link() - )); - - if !original_message.attachments.is_empty() { - embed = embed.fields(original_message.attachments.iter().map(|a| { - ( - "Attachments".to_string(), - format!("[{}]({})", a.filename, a.url), - false, - ) - })); - - if let Some(image) = find_first_image(msg) { - embed = embed.image(image); - } + if !member_can_view_channel(ctx, &author, &target_channel).await? { + debug!("Not resolving message for author who can't see it"); + continue; } + let target_message_id = MessageId::from_str(target_message_id)?; + let target_message = target_channel + .message(ctx, target_message_id) + .await + .wrap_err_with(|| { + eyre!("Couldn't find channel message from ID {target_message_id}!") + })?; + + let embed = to_embed(ctx, &target_message).await?; + embeds.push(embed); } diff --git a/tags/always-offline.md b/tags/always_offline.md similarity index 100% rename from tags/always-offline.md rename to tags/always_offline.md diff --git a/tags/binary-search.md b/tags/binary_search.md similarity index 100% rename from tags/binary-search.md rename to tags/binary_search.md diff --git a/tags/FTB.md b/tags/ftb.md similarity index 100% rename from tags/FTB.md rename to tags/ftb.md diff --git a/tags/javaforgebugfix.md b/tags/java_forge_bugfix.md similarity index 100% rename from tags/javaforgebugfix.md rename to tags/java_forge_bugfix.md diff --git a/tags/legacyjavafixer.md b/tags/legacy_java_fixer.md similarity index 100% rename from tags/legacyjavafixer.md rename to tags/legacy_java_fixer.md diff --git a/tags/macosarmjava.md b/tags/macos_arm_java.md similarity index 100% rename from tags/macosarmjava.md rename to tags/macos_arm_java.md diff --git a/tags/whyjava8.md b/tags/why_java_8.md similarity index 100% rename from tags/whyjava8.md rename to tags/why_java_8.md