From 2e0d9a2ad094b89d00f0564eefe3d77acb7d377a Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Thu, 1 Aug 2024 10:36:45 +0900 Subject: [PATCH 001/104] Refactor file structure --- Cargo.lock | 24 +++++----- Cargo.toml | 2 +- src/features/auth.rs | 87 ++++++++++++++++++++++++++++++++++++ src/features/logging.rs | 8 ++++ src/features/mod.rs | 5 +++ src/main.rs | 97 ++++------------------------------------- src/utils.rs | 7 +++ 7 files changed, 129 insertions(+), 101 deletions(-) create mode 100644 src/features/auth.rs create mode 100644 src/features/logging.rs create mode 100644 src/features/mod.rs create mode 100644 src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index a445066..6cb45e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1421,18 +1421,6 @@ dependencies = [ "libc", ] -[[package]] -name = "simple_auth_bot" -version = "0.1.0" -dependencies = [ - "dotenvy", - "regex", - "serenity", - "tokio", - "tracing", - "tracing-subscriber", -] - [[package]] name = "skeptic" version = "0.13.7" @@ -1920,6 +1908,18 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0" +[[package]] +name = "valine_bot" +version = "0.1.0" +dependencies = [ + "dotenvy", + "regex", + "serenity", + "tokio", + "tracing", + "tracing-subscriber", +] + [[package]] name = "valuable" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index b1cc88f..28d117c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "simple_auth_bot" +name = "valine_bot" version = "0.1.0" edition = "2021" diff --git a/src/features/auth.rs b/src/features/auth.rs new file mode 100644 index 0000000..6dcb201 --- /dev/null +++ b/src/features/auth.rs @@ -0,0 +1,87 @@ +use std::env; +use std::sync::LazyLock; + +use regex::Regex; +use serenity::all::{ChannelId, GuildId, MessageUpdateEvent, RoleId}; +use serenity::async_trait; +use serenity::model::channel::Message; +use serenity::prelude::*; +use tracing::error; + +use crate::utils::create_message; + +static TRIGGER_REGEX: LazyLock = + LazyLock::new(|| Regex::new(&env::var("TRIGGER_REGEX").unwrap()).unwrap()); +static CHANNEL_ID: LazyLock = + LazyLock::new(|| ChannelId::new(env::var("CHANNEL_ID").unwrap().parse().unwrap())); +static LOG_CHANNEL_ID: LazyLock = + LazyLock::new(|| ChannelId::new(env::var("LOG_CHANNEL_ID").unwrap().parse().unwrap())); +static ROLE_ID: LazyLock = + LazyLock::new(|| RoleId::new(env::var("ROLE_ID").unwrap().parse().unwrap())); + +pub struct Handler; + +impl Handler { + async fn handle_message(&self, ctx: &Context, guild_id: GuildId, msg: Message) { + if !TRIGGER_REGEX.is_match(&msg.content) { + return; + } + + if msg.channel_id != *CHANNEL_ID { + return; + } + + let member = match guild_id.member(&ctx.http, msg.author.id).await { + Ok(member) => member, + Err(why) => return error!("Failed to get member: {:?}", why), + }; + + if member.roles.contains(&ROLE_ID) { + error!("{} already has the role", member.user.name); + return; + } + + if let Err(why) = member.add_role(&ctx.http, *ROLE_ID).await { + let log = create_message(format!( + "{} にロールを追加できませんでした。\n```\n{}```", + member.mention(), + why + )); + if let Err(why) = LOG_CHANNEL_ID.send_message(&ctx.http, log).await { + error!("Error sending message: {:?}", why) + } + return error!("Failed to add role: {:?}", why); + } + + let log = create_message(format!("{} にロールを追加しました。", member.mention())); + if let Err(why) = LOG_CHANNEL_ID.send_message(&ctx.http, log).await { + error!("Error sending message: {:?}", why) + } + } +} + +#[async_trait] +impl EventHandler for Handler { + async fn message(&self, ctx: Context, msg: Message) { + let Some(guild_id) = msg.guild_id else { + return error!("Failed to get guild id: {:?}", msg); + }; + self.handle_message(&ctx, guild_id, msg).await; + } + + async fn message_update( + &self, + ctx: Context, + _: Option, + _: Option, + event: MessageUpdateEvent, + ) { + let Some(guild_id) = event.guild_id else { + return error!("Failed to get guild id: {:?}", event); + }; + match event.channel_id.message(&ctx.http, event.id).await { + Ok(msg) => self.handle_message(&ctx, guild_id, msg).await, + Err(why) => error!("Failed to get message: {:?}", why), + } + } +} diff --git a/src/features/logging.rs b/src/features/logging.rs new file mode 100644 index 0000000..306c47b --- /dev/null +++ b/src/features/logging.rs @@ -0,0 +1,8 @@ +use serenity::{all::EventHandler, async_trait}; + +pub struct Handler; + + +#[async_trait] +impl EventHandler for Handler { +} diff --git a/src/features/mod.rs b/src/features/mod.rs new file mode 100644 index 0000000..e343d0a --- /dev/null +++ b/src/features/mod.rs @@ -0,0 +1,5 @@ +mod auth; +mod logging; + +pub use auth::Handler as AuthHandler; +pub use logging::Handler as LoggingHandler; diff --git a/src/main.rs b/src/main.rs index 3a567d0..2b61ddb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,96 +1,15 @@ +mod features; +mod utils; + use std::env; -use std::sync::LazyLock; -use regex::Regex; -use serenity::all::{ - ChannelId, CreateAllowedMentions, CreateMessage, GuildId, MessageUpdateEvent, Ready, RoleId, -}; -use serenity::async_trait; -use serenity::model::channel::Message; -use serenity::prelude::*; +use serenity::{all::Ready, async_trait, prelude::*}; use tracing::{error, info}; -static TRIGGER_REGEX: LazyLock = - LazyLock::new(|| Regex::new(&env::var("TRIGGER_REGEX").unwrap()).unwrap()); -static CHANNEL_ID: LazyLock = - LazyLock::new(|| ChannelId::new(env::var("CHANNEL_ID").unwrap().parse().unwrap())); -static LOG_CHANNEL_ID: LazyLock = - LazyLock::new(|| ChannelId::new(env::var("LOG_CHANNEL_ID").unwrap().parse().unwrap())); -static ROLE_ID: LazyLock = - LazyLock::new(|| RoleId::new(env::var("ROLE_ID").unwrap().parse().unwrap())); - -fn create_message(content: String) -> CreateMessage { - CreateMessage::new() - .content(content) - .allowed_mentions(CreateAllowedMentions::new().all_users(false)) -} - -struct Handler; - -impl Handler { - async fn handle_message(&self, ctx: &Context, guild_id: GuildId, msg: Message) { - if !TRIGGER_REGEX.is_match(&msg.content) { - return; - } - - if msg.channel_id != *CHANNEL_ID { - return; - } - - let member = match guild_id.member(&ctx.http, msg.author.id).await { - Ok(member) => member, - Err(why) => return error!("Failed to get member: {:?}", why), - }; - - if member.roles.contains(&ROLE_ID) { - error!("{} already has the role", member.user.name); - return; - } - - if let Err(why) = member.add_role(&ctx.http, *ROLE_ID).await { - let log = create_message(format!( - "{} にロールを追加できませんでした。\n```\n{}```", - member.mention(), - why - )); - if let Err(why) = LOG_CHANNEL_ID.send_message(&ctx.http, log).await { - error!("Error sending message: {:?}", why) - } - return error!("Failed to add role: {:?}", why); - } - - let log = create_message(format!("{} にロールを追加しました。", member.mention())); - if let Err(why) = LOG_CHANNEL_ID.send_message(&ctx.http, log).await { - error!("Error sending message: {:?}", why) - } - } -} +struct MainHandler; #[async_trait] -impl EventHandler for Handler { - async fn message(&self, ctx: Context, msg: Message) { - let Some(guild_id) = msg.guild_id else { - return error!("Failed to get guild id: {:?}", msg); - }; - self.handle_message(&ctx, guild_id, msg).await; - } - - async fn message_update( - &self, - ctx: Context, - _: Option, - _: Option, - event: MessageUpdateEvent, - ) { - let Some(guild_id) = event.guild_id else { - return error!("Failed to get guild id: {:?}", event); - }; - match event.channel_id.message(&ctx.http, event.id).await { - Ok(msg) => self.handle_message(&ctx, guild_id, msg).await, - Err(why) => error!("Failed to get message: {:?}", why), - } - } - +impl EventHandler for MainHandler { async fn ready(&self, _: Context, ready: Ready) { info!("{} is connected!", ready.user.name); } @@ -105,7 +24,9 @@ async fn main() { let token = env::var("TOKEN").expect("Expected a TOKEN in the environment"); let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT; let mut client = Client::builder(&token, intents) - .event_handler(Handler) + .event_handler(MainHandler) + .event_handler(features::AuthHandler) + .event_handler(features::LoggingHandler) .await .expect("Err creating client"); diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..781a918 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,7 @@ +use serenity::all::{CreateAllowedMentions, CreateMessage}; + +pub fn create_message(content: String) -> CreateMessage { + CreateMessage::new() + .content(content) + .allowed_mentions(CreateAllowedMentions::new().all_users(false)) +} From b46cfedd8f4f594c108aadc7ba0ba4efd53581bd Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Fri, 18 Oct 2024 03:41:48 +0900 Subject: [PATCH 002/104] Add message cache --- Cargo.lock | 84 +++++++++++++++++--------- Cargo.toml | 9 ++- src/features/auth.rs | 34 ++++++++--- src/features/logging.rs | 76 ++++++++++++++++++++++- src/features/message_cache.rs | 110 ++++++++++++++++++++++++++++++++++ src/features/mod.rs | 4 ++ src/main.rs | 21 +++++-- src/utils.rs | 26 +++++++- 8 files changed, 317 insertions(+), 47 deletions(-) create mode 100644 src/features/message_cache.rs diff --git a/Cargo.lock b/Cargo.lock index 6cb45e5..acbc69b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -279,6 +279,20 @@ dependencies = [ "serde", ] +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "data-encoding" version = "2.6.0" @@ -322,6 +336,12 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "encoding_rs" version = "0.8.34" @@ -789,6 +809,15 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -880,7 +909,7 @@ checksum = "c325dfab65f261f386debee8b0969da215b3fa0037e74c8a1234db7ba986d803" dependencies = [ "crossbeam-channel", "crossbeam-utils", - "dashmap", + "dashmap 5.5.3", "skeptic", "smallvec", "tagptr", @@ -898,13 +927,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.11" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ + "hermit-abi", "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -932,16 +962,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "object" version = "0.36.0" @@ -1086,9 +1106,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.5" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", @@ -1098,9 +1118,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -1109,9 +1129,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" @@ -1368,7 +1388,7 @@ dependencies = [ "bytes", "chrono", "command_attr", - "dashmap", + "dashmap 5.5.3", "flate2", "futures", "fxhash", @@ -1421,6 +1441,12 @@ dependencies = [ "libc", ] +[[package]] +name = "similar" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" + [[package]] name = "skeptic" version = "0.13.7" @@ -1636,27 +1662,26 @@ dependencies = [ [[package]] name = "tokio" -version = "1.38.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", @@ -1829,7 +1854,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb704842c709bc76f63e99e704cb208beeccca2abbabd0d9aec02e48ca1cee0f" dependencies = [ "chrono", - "dashmap", + "dashmap 5.5.3", "hashbrown", "mini-moka", "parking_lot", @@ -1912,9 +1937,12 @@ checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0" name = "valine_bot" version = "0.1.0" dependencies = [ + "dashmap 6.1.0", "dotenvy", + "itertools", "regex", "serenity", + "similar", "tokio", "tracing", "tracing-subscriber", diff --git a/Cargo.toml b/Cargo.toml index 28d117c..43b64da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,9 +7,12 @@ edition = "2021" [dependencies] dotenvy = "0.15.7" -regex = "1.10.5" -serenity = "0.12.2" -tokio = { version = "1.38.0", features = ["rt-multi-thread", "macros", "signal"] } +regex = "1.11.0" +similar = "2.6.0" +dashmap = "6.1.0" +itertools = "0.13.0" +serenity = { version = "0.12.2" } +tokio = { version = "1.40.0", features = ["rt-multi-thread", "macros", "signal"] } tracing = "0.1.40" tracing-subscriber = "0.3.18" diff --git a/src/features/auth.rs b/src/features/auth.rs index 6dcb201..9ea822b 100644 --- a/src/features/auth.rs +++ b/src/features/auth.rs @@ -2,13 +2,13 @@ use std::env; use std::sync::LazyLock; use regex::Regex; -use serenity::all::{ChannelId, GuildId, MessageUpdateEvent, RoleId}; +use serenity::all::{ChannelId, GuildId, MessageUpdateEvent, RoleId, User}; use serenity::async_trait; use serenity::model::channel::Message; use serenity::prelude::*; use tracing::error; -use crate::utils::create_message; +use crate::utils::{create_message, get_message}; static TRIGGER_REGEX: LazyLock = LazyLock::new(|| Regex::new(&env::var("TRIGGER_REGEX").unwrap()).unwrap()); @@ -22,16 +22,23 @@ static ROLE_ID: LazyLock = pub struct Handler; impl Handler { - async fn handle_message(&self, ctx: &Context, guild_id: GuildId, msg: Message) { - if !TRIGGER_REGEX.is_match(&msg.content) { + async fn handle_message( + &self, + ctx: &Context, + guild_id: GuildId, + channel_id: ChannelId, + author: User, + content: String, + ) { + if !TRIGGER_REGEX.is_match(&content) { return; } - if msg.channel_id != *CHANNEL_ID { + if channel_id != *CHANNEL_ID { return; } - let member = match guild_id.member(&ctx.http, msg.author.id).await { + let member = match guild_id.member(&ctx.http, author.id).await { Ok(member) => member, Err(why) => return error!("Failed to get member: {:?}", why), }; @@ -66,7 +73,8 @@ impl EventHandler for Handler { let Some(guild_id) = msg.guild_id else { return error!("Failed to get guild id: {:?}", msg); }; - self.handle_message(&ctx, guild_id, msg).await; + + self.handle_message(&ctx, guild_id, msg.channel_id, msg.author, msg.content).await; } async fn message_update( @@ -79,8 +87,16 @@ impl EventHandler for Handler { let Some(guild_id) = event.guild_id else { return error!("Failed to get guild id: {:?}", event); }; - match event.channel_id.message(&ctx.http, event.id).await { - Ok(msg) => self.handle_message(&ctx, guild_id, msg).await, + let Some(author) = event.author else { + return error!("Failed to get author: {:?}", event); + }; + if let Some(content) = event.content { + self.handle_message(&ctx, guild_id, event.channel_id, author, content).await; + return; + } + + match get_message(&ctx, event.channel_id, event.id).await { + Ok(m) => self.handle_message(&ctx, guild_id, event.channel_id, author, m.content).await, Err(why) => error!("Failed to get message: {:?}", why), } } diff --git a/src/features/logging.rs b/src/features/logging.rs index 306c47b..d4695d9 100644 --- a/src/features/logging.rs +++ b/src/features/logging.rs @@ -1,8 +1,80 @@ -use serenity::{all::EventHandler, async_trait}; +use std::{env, sync::LazyLock}; -pub struct Handler; +use serenity::{ + all::{ + ChannelId, Context, EventHandler, GuildId, Mentionable, Message, MessageId, MessageUpdateEvent + }, + async_trait, +}; +use similar::TextDiff; +use tracing::{error, info}; + +use crate::utils::{create_message, get_cached_message}; + +use super::message_cache::MessageCacheType; +static LOG_CHANNEL_ID: LazyLock = + LazyLock::new(|| ChannelId::new(env::var("MESSAGE_LOG_CHANNEL_ID").unwrap().parse().unwrap())); + +pub struct Handler; #[async_trait] impl EventHandler for Handler { + // async fn message_update( + // &self, + // _: Context, + // old: Option, + // new: Option, + // event: MessageUpdateEvent, + // ) { + // if old.is_none() || new.is_none() { + // return error!("Failed to get old or new message: {:?}", event); + // } + + // let (old, new) = match (old, new) { + // (Some(old), Some(new)) => (old, new), + // _ => return error!("Failed to get old or new message: {:?}", event), + // }; + // } + + async fn message_delete( + &self, + ctx: Context, + channel_id: ChannelId, + deleted_message_id: MessageId, + _: Option, + ) { + { + let data = ctx.data.read().await; + let cache = data.get::().unwrap(); + let c = ChannelId::new(1098136613059567737); + let messages = match cache.get_messages(c) { + Some(m) => m.clone(), + None => Vec::new(), + }; + messages.iter().for_each(|m| { + info!("Message: {:?}", m); + }); + } + + let message = match get_cached_message(&ctx, channel_id, deleted_message_id).await { + Some(m) => m.clone(), + None => return error!("Failed to get message: {:?}", deleted_message_id), + }; + + let log = create_message(format!( + "{} はメッセージを削除しました。\n```diff\n{}```", + message.author.mention(), + message + .content + .lines() + .map(|line| format!("- {}", line)) + .collect::>() + .join("\n") + )); + + if let Err(why) = LOG_CHANNEL_ID.send_message(&ctx.http, log).await { + error!("Error sending message: {:?}", why) + } + } } diff --git a/src/features/message_cache.rs b/src/features/message_cache.rs new file mode 100644 index 0000000..9821207 --- /dev/null +++ b/src/features/message_cache.rs @@ -0,0 +1,110 @@ +use std::{ + collections::HashMap, + env, + sync::{Arc, LazyLock}, +}; + +use dashmap::DashMap; +use itertools::Itertools; +use serenity::{ + all::{ChannelId, Context, EventHandler, GetMessages, GuildId, Message, MessageId}, + async_trait, + prelude::TypeMapKey, +}; +use tracing::info; + +static MESSAGE_CACHE_GUILD_ID: LazyLock = + LazyLock::new(|| GuildId::new(env::var("MESSAGE_CACHE_GUILD_ID").unwrap().parse().unwrap())); + +pub struct MessageCache { + cache: DashMap>, +} + +impl MessageCache { + pub fn new() -> Self { + Self { + cache: DashMap::new(), + } + } + + pub fn extend(&self, iter: impl IntoIterator) { + iter.into_iter() + .into_group_map_by(|(channel_id, _, _)| *channel_id) + .into_iter() + .map(|(channel_id, messages)| { + ( + channel_id, + messages + .into_iter() + .map(|(_, message_id, message)| (message_id, message)) + .collect::>(), + ) + }) + .for_each(|(channel_id, messages)| { + let mut map = self.cache.entry(channel_id).or_insert(HashMap::new()); + map.extend(messages); + }); + } + + pub fn extend_messages(&self, iter: impl IntoIterator) { + self.extend( + iter.into_iter() + .map(|message| (message.channel_id, message.id, message)), + ); + } + + pub fn get(&self, channel_id: ChannelId, message_id: MessageId) -> Option { + let map = self.cache.get(&channel_id)?; + map.get(&message_id).cloned() + } + + pub fn get_messages(&self, channel_id: ChannelId) -> Option> { + self.cache + .get(&channel_id) + .map(|map| map.values().cloned().collect()) + } +} + +impl Default for MessageCache { + fn default() -> Self { + Self::new() + } +} + +pub struct MessageCacheType; + +impl TypeMapKey for MessageCacheType { + type Value = Arc; +} + +pub struct Handler; + +#[async_trait] +impl EventHandler for Handler { + async fn cache_ready(&self, ctx: Context, _: Vec) { + let channels = MESSAGE_CACHE_GUILD_ID.channels(&ctx.http).await.unwrap(); + for (id, channel) in channels.iter() { + if !channel.is_text_based() { + continue; + } + + let Ok(messages) = channel + .messages(&ctx.http, GetMessages::new().limit(100)) + .await + else { + info!("Failed to get messages for channel: {:?}", channel.name); + continue; + }; + + let mut data = ctx.data.write().await; + let cache = data.get_mut::().unwrap(); + let len = messages.len(); + cache.extend_messages(messages); + info!( + "Cached {} messages for channel: {} ({})", + len, channel.name, id + ); + } + info!("Cache ready!"); + } +} diff --git a/src/features/mod.rs b/src/features/mod.rs index e343d0a..c4c87c5 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -1,5 +1,9 @@ mod auth; mod logging; +mod message_cache; pub use auth::Handler as AuthHandler; pub use logging::Handler as LoggingHandler; +pub use message_cache::Handler as MessageCacheHandler; + +pub use message_cache::{MessageCache, MessageCacheType}; diff --git a/src/main.rs b/src/main.rs index 2b61ddb..3d0a804 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,18 @@ mod features; mod utils; -use std::env; - -use serenity::{all::Ready, async_trait, prelude::*}; +use std::{env, sync::Arc}; + +use features::{MessageCache, MessageCacheType}; +use serenity::{ + all::Ready, + async_trait, + cache::Settings as CacheSettings, + prelude::*, +}; use tracing::{error, info}; + struct MainHandler; #[async_trait] @@ -22,11 +29,17 @@ async fn main() { let _ = dotenvy::dotenv(); let token = env::var("TOKEN").expect("Expected a TOKEN in the environment"); - let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT; + let intents = + GatewayIntents::GUILD_MESSAGES | GatewayIntents::GUILDS | GatewayIntents::MESSAGE_CONTENT; + let mut settings = CacheSettings::default(); + settings.max_messages = 1_000_000; let mut client = Client::builder(&token, intents) .event_handler(MainHandler) .event_handler(features::AuthHandler) .event_handler(features::LoggingHandler) + .event_handler(features::MessageCacheHandler) + .cache_settings(settings) + .type_map_insert::(Arc::new(MessageCache::new())) .await .expect("Err creating client"); diff --git a/src/utils.rs b/src/utils.rs index 781a918..a61784e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,7 +1,31 @@ -use serenity::all::{CreateAllowedMentions, CreateMessage}; +use serenity::{all::{ChannelId, Context, CreateAllowedMentions, CreateMessage, Message, MessageId}, Result}; + +use crate::MessageCacheType; pub fn create_message(content: String) -> CreateMessage { CreateMessage::new() .content(content) .allowed_mentions(CreateAllowedMentions::new().all_users(false)) } + +pub async fn get_cached_message(ctx: &Context, channel_id: ChannelId, message_id: MessageId) -> Option { + if let Some(m) = ctx.cache.message(channel_id, message_id) { + return Some(m.clone()); + } + + let data = ctx.data.read().await; + let cache = data.get::().unwrap(); + if let Some(m) = cache.get(channel_id, message_id) { + return Some(m); + } + + None +} + +pub async fn get_message(ctx: &Context, channel_id: ChannelId, message_id: MessageId) -> Result { + if let Some(cached) = get_cached_message(ctx, channel_id, message_id).await { + return Ok(cached); + } + + channel_id.message(&ctx.http, message_id).await +} From df9914ba475fd446d3cd55a14478f83ae7a6d8b3 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 19 Oct 2024 05:14:41 +0900 Subject: [PATCH 003/104] Add message edit/delete log --- .env.example | 10 +- .vscode/settings.json | 3 + Cargo.toml | 6 +- rustfmt.toml | 2 + src/features/auth.rs | 35 +++--- src/features/logging.rs | 209 ++++++++++++++++++++++++++-------- src/features/message_cache.rs | 37 +++--- src/main.rs | 13 +-- src/utils.rs | 26 ++++- 9 files changed, 237 insertions(+), 104 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 rustfmt.toml diff --git a/.env.example b/.env.example index 17fda83..f6a3764 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,14 @@ +# トークン TOKEN="TOKEN" - +# キーワードを検知するチャンネルID CHANNEL_ID=100000000000000000000 +# キーワード認証ログを送信するチャンネルID LOG_CHANNEL_ID=100000000000000000000 +# キーワード認証後に付与するロールID ROLE_ID=100000000000000000000 +# キーワードの正規表現 TRIGGER_REGEX="[おオオ][のノノ][れレレ][GgGg][RrRr][EeEe][GgGg]" +# メッセージ編集・削除ログを送信するチャンネルID +MESSAGE_LOG_CHANNEL_ID=1296160534680305804 +# メッセージキャッシュを保持するギルドID +MESSAGE_CACHE_GUILD_ID=525995782592266250 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..23fd35f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.formatOnSave": true +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 43b64da..25ad0f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,11 @@ similar = "2.6.0" dashmap = "6.1.0" itertools = "0.13.0" serenity = { version = "0.12.2" } -tokio = { version = "1.40.0", features = ["rt-multi-thread", "macros", "signal"] } +tokio = { version = "1.40.0", features = [ + "rt-multi-thread", + "macros", + "signal", +] } tracing = "0.1.40" tracing-subscriber = "0.3.18" diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..53f860a --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,2 @@ +max_width = 120 +use_field_init_shorthand = true diff --git a/src/features/auth.rs b/src/features/auth.rs index 9ea822b..9e3dbe8 100644 --- a/src/features/auth.rs +++ b/src/features/auth.rs @@ -10,14 +10,14 @@ use tracing::error; use crate::utils::{create_message, get_message}; -static TRIGGER_REGEX: LazyLock = - LazyLock::new(|| Regex::new(&env::var("TRIGGER_REGEX").unwrap()).unwrap()); -static CHANNEL_ID: LazyLock = - LazyLock::new(|| ChannelId::new(env::var("CHANNEL_ID").unwrap().parse().unwrap())); -static LOG_CHANNEL_ID: LazyLock = - LazyLock::new(|| ChannelId::new(env::var("LOG_CHANNEL_ID").unwrap().parse().unwrap())); -static ROLE_ID: LazyLock = - LazyLock::new(|| RoleId::new(env::var("ROLE_ID").unwrap().parse().unwrap())); +#[rustfmt::skip] +static TRIGGER_REGEX: LazyLock = LazyLock::new(|| Regex::new(&env::var("TRIGGER_REGEX").unwrap()).unwrap()); +#[rustfmt::skip] +static CHANNEL_ID: LazyLock = LazyLock::new(|| ChannelId::new(env::var("CHANNEL_ID").unwrap().parse().unwrap())); +#[rustfmt::skip] +static LOG_CHANNEL_ID: LazyLock = LazyLock::new(|| ChannelId::new(env::var("LOG_CHANNEL_ID").unwrap().parse().unwrap())); +#[rustfmt::skip] +static ROLE_ID: LazyLock = LazyLock::new(|| RoleId::new(env::var("ROLE_ID").unwrap().parse().unwrap())); pub struct Handler; @@ -74,16 +74,11 @@ impl EventHandler for Handler { return error!("Failed to get guild id: {:?}", msg); }; - self.handle_message(&ctx, guild_id, msg.channel_id, msg.author, msg.content).await; + self.handle_message(&ctx, guild_id, msg.channel_id, msg.author, msg.content) + .await; } - async fn message_update( - &self, - ctx: Context, - _: Option, - _: Option, - event: MessageUpdateEvent, - ) { + async fn message_update(&self, ctx: Context, _: Option, _: Option, event: MessageUpdateEvent) { let Some(guild_id) = event.guild_id else { return error!("Failed to get guild id: {:?}", event); }; @@ -91,12 +86,16 @@ impl EventHandler for Handler { return error!("Failed to get author: {:?}", event); }; if let Some(content) = event.content { - self.handle_message(&ctx, guild_id, event.channel_id, author, content).await; + self.handle_message(&ctx, guild_id, event.channel_id, author, content) + .await; return; } match get_message(&ctx, event.channel_id, event.id).await { - Ok(m) => self.handle_message(&ctx, guild_id, event.channel_id, author, m.content).await, + Ok(m) => { + self.handle_message(&ctx, guild_id, event.channel_id, author, m.content) + .await + } Err(why) => error!("Failed to get message: {:?}", why), } } diff --git a/src/features/logging.rs b/src/features/logging.rs index d4695d9..e33e3ca 100644 --- a/src/features/logging.rs +++ b/src/features/logging.rs @@ -1,41 +1,148 @@ use std::{env, sync::LazyLock}; +use itertools::enumerate; use serenity::{ all::{ - ChannelId, Context, EventHandler, GuildId, Mentionable, Message, MessageId, MessageUpdateEvent + ChannelId, Color, Context, CreateEmbed, EmbedMessageBuilding, EventHandler, FormattedTimestamp, + FormattedTimestampStyle, GuildId, Mentionable, Message, MessageBuilder, MessageId, MessageUpdateEvent, + Timestamp, }, async_trait, }; -use similar::TextDiff; -use tracing::{error, info}; +use tracing::error; -use crate::utils::{create_message, get_cached_message}; - -use super::message_cache::MessageCacheType; +use crate::{ + features::MessageCacheType, + utils::{create_diff_lines_text, create_safe_message, get_cached_message}, +}; -static LOG_CHANNEL_ID: LazyLock = - LazyLock::new(|| ChannelId::new(env::var("MESSAGE_LOG_CHANNEL_ID").unwrap().parse().unwrap())); +#[rustfmt::skip] +static LOG_CHANNEL_ID: LazyLock = LazyLock::new(|| ChannelId::new(env::var("MESSAGE_LOG_CHANNEL_ID").unwrap().parse().unwrap())); pub struct Handler; +impl Handler { + pub fn build_embed(&self, message: &Message, new_content: String, mut embed: CreateEmbed) -> CreateEmbed { + if let Some(message_reference) = &message.message_reference { + let id = message_reference.message_id.unwrap_or(MessageId::default()); + + let mut builder = MessageBuilder::new(); + builder + .push_bold_safe("元メッセージ: ") + .push_safe(id.link(message_reference.channel_id, message_reference.guild_id)) + .push_safe(" ") + .push_mono_line_safe(id.to_string()); + + embed = embed.field("__**転送**__", builder.build(), false); + } + + if let Some(poll) = &message.poll { + let mut builder = MessageBuilder::new(); + builder + .push_bold_safe("タイトル: ") + .push_line_safe(poll.question.text.clone().unwrap_or("<不明なタイトル>".to_string())) + .push_bold_line_safe("回答:"); + + let results = &poll.results; + + for (i, answer) in enumerate(&poll.answers) { + builder.push_safe(&format!( + "- {}", + answer.poll_media.text.clone().unwrap_or("<不明な回答>".to_string()) + )); + + match results { + Some(results) => builder.push_line_safe(&format!(": {}票", results.answer_counts[i].count)), + None => builder.push_safe("\n"), + }; + } + + if let Some(expiry) = poll.expiry { + let formatted = FormattedTimestamp::new(expiry, Some(FormattedTimestampStyle::LongDateTime)); + builder + .push_bold_safe("有効期限:") + .push_line_safe(formatted.to_string()); + } + + embed = embed.field("__**投票**__", builder.build(), false); + } + + if !message.content.is_empty() { + let mut changed = MessageBuilder::new(); + changed.push_codeblock_safe(create_diff_lines_text(&message.content, &new_content), Some("diff")); + + embed = embed.field("__**テキスト差分**__", changed.build(), false); + } + + if !message.attachments.is_empty() { + let mut changed = MessageBuilder::new(); + for attachment in &message.attachments { + changed + .push_safe("- ") + .push_named_link_safe(&attachment.filename, &attachment.url) + .push_safe("\n"); + } + embed = embed.field("__**添付ファイル**__", changed.build(), false); + } + embed + } + + async fn update_cache(&self, ctx: &Context, message: &Message) { + let mut data = ctx.data.write().await; + let cache = data.get_mut::().unwrap(); + cache.insert(message.clone()); + } +} + #[async_trait] impl EventHandler for Handler { - // async fn message_update( - // &self, - // _: Context, - // old: Option, - // new: Option, - // event: MessageUpdateEvent, - // ) { - // if old.is_none() || new.is_none() { - // return error!("Failed to get old or new message: {:?}", event); - // } - - // let (old, new) = match (old, new) { - // (Some(old), Some(new)) => (old, new), - // _ => return error!("Failed to get old or new message: {:?}", event), - // }; - // } + async fn message_update(&self, ctx: Context, old: Option, _: Option, event: MessageUpdateEvent) { + let Some(message) = get_cached_message(&ctx, event.channel_id, event.id).await else { + return error!("Failed to get message: {:?}", event.id); + }; + let message = old.unwrap_or(message); + + let Ok(new_message) = event.channel_id.message(&ctx.http, event.id).await else { + return error!("Failed to get new message: {:?}", event.id); + }; + + if message.content == new_message.content { + return; + } + + self.update_cache(&ctx, &new_message).await; + + let timestamp = FormattedTimestamp::new(Timestamp::now(), Some(FormattedTimestampStyle::LongDateTime)); + let description = MessageBuilder::new() + .push_bold_safe("メンバー: ") + .mention(&message.author.mention()) + .push_safe(" ") + .push_mono_line_safe(message.author.id.to_string()) + .push_bold_safe("メッセージ: ") + .push_safe(message.id.link(message.channel_id, message.guild_id)) + .push_safe(" ") + .push_mono_line_safe(message.id.to_string()) + .push_bold_safe("時刻: ") + .push_line_safe(timestamp.to_string()) + .build(); + + let mut embed = CreateEmbed::new() + .title("メッセージ編集ログ") + .description(description) + .color(Color::new(0xff8800)) + .thumbnail( + message + .author + .avatar_url() + .unwrap_or("https://cdn.discordapp.com/embed/avatars/0.png".to_string()), + ); + + embed = self.build_embed(&message, new_message.content, embed); + let log = create_safe_message().add_embed(embed); + if let Err(why) = LOG_CHANNEL_ID.send_message(&ctx.http, log).await { + error!("Error sending message: {:?}", why) + } + } async fn message_delete( &self, @@ -44,35 +151,37 @@ impl EventHandler for Handler { deleted_message_id: MessageId, _: Option, ) { - { - let data = ctx.data.read().await; - let cache = data.get::().unwrap(); - let c = ChannelId::new(1098136613059567737); - let messages = match cache.get_messages(c) { - Some(m) => m.clone(), - None => Vec::new(), - }; - messages.iter().for_each(|m| { - info!("Message: {:?}", m); - }); - } - - let message = match get_cached_message(&ctx, channel_id, deleted_message_id).await { - Some(m) => m.clone(), - None => return error!("Failed to get message: {:?}", deleted_message_id), + let Some(message) = get_cached_message(&ctx, channel_id, deleted_message_id).await else { + return error!("Failed to get message: {:?}", deleted_message_id); }; - let log = create_message(format!( - "{} はメッセージを削除しました。\n```diff\n{}```", - message.author.mention(), - message - .content - .lines() - .map(|line| format!("- {}", line)) - .collect::>() - .join("\n") - )); + let timestamp = FormattedTimestamp::new(Timestamp::now(), Some(FormattedTimestampStyle::LongDateTime)); + let description = MessageBuilder::new() + .push_bold_safe("メンバー: ") + .mention(&message.author.mention()) + .push_safe(" ") + .push_mono_line_safe(message.author.id.to_string()) + .push_bold_safe("チャンネル: ") + .mention(&message.channel_id.mention()) + .push_safe(" ") + .push_mono_line_safe(message.id.to_string()) + .push_bold_safe("時刻: ") + .push_line_safe(timestamp.to_string()) + .build(); + + let mut embed = CreateEmbed::new() + .title("メッセージ削除ログ") + .description(description) + .color(Color::new(0xf00000)) + .thumbnail( + message + .author + .avatar_url() + .unwrap_or("https://cdn.discordapp.com/embed/avatars/0.png".to_string()), + ); + embed = self.build_embed(&message, "".to_string(), embed); + let log = create_safe_message().add_embed(embed); if let Err(why) = LOG_CHANNEL_ID.send_message(&ctx.http, log).await { error!("Error sending message: {:?}", why) } diff --git a/src/features/message_cache.rs b/src/features/message_cache.rs index 9821207..7b132ec 100644 --- a/src/features/message_cache.rs +++ b/src/features/message_cache.rs @@ -13,8 +13,8 @@ use serenity::{ }; use tracing::info; -static MESSAGE_CACHE_GUILD_ID: LazyLock = - LazyLock::new(|| GuildId::new(env::var("MESSAGE_CACHE_GUILD_ID").unwrap().parse().unwrap())); +#[rustfmt::skip] +static MESSAGE_CACHE_GUILD_ID: LazyLock = LazyLock::new(|| GuildId::new(env::var("MESSAGE_CACHE_GUILD_ID").unwrap().parse().unwrap())); pub struct MessageCache { cache: DashMap>, @@ -22,9 +22,12 @@ pub struct MessageCache { impl MessageCache { pub fn new() -> Self { - Self { - cache: DashMap::new(), - } + Self { cache: DashMap::new() } + } + + pub fn insert(&self, message: Message) { + let mut map = self.cache.entry(message.channel_id).or_insert(HashMap::new()); + map.insert(message.id, message); } pub fn extend(&self, iter: impl IntoIterator) { @@ -57,12 +60,6 @@ impl MessageCache { let map = self.cache.get(&channel_id)?; map.get(&message_id).cloned() } - - pub fn get_messages(&self, channel_id: ChannelId) -> Option> { - self.cache - .get(&channel_id) - .map(|map| map.values().cloned().collect()) - } } impl Default for MessageCache { @@ -77,21 +74,24 @@ impl TypeMapKey for MessageCacheType { type Value = Arc; } -pub struct Handler; +pub struct Handler { + pub disabled: bool, +} #[async_trait] impl EventHandler for Handler { async fn cache_ready(&self, ctx: Context, _: Vec) { + if self.disabled { + return; + } + let channels = MESSAGE_CACHE_GUILD_ID.channels(&ctx.http).await.unwrap(); for (id, channel) in channels.iter() { if !channel.is_text_based() { continue; } - let Ok(messages) = channel - .messages(&ctx.http, GetMessages::new().limit(100)) - .await - else { + let Ok(messages) = channel.messages(&ctx.http, GetMessages::new().limit(100)).await else { info!("Failed to get messages for channel: {:?}", channel.name); continue; }; @@ -100,10 +100,7 @@ impl EventHandler for Handler { let cache = data.get_mut::().unwrap(); let len = messages.len(); cache.extend_messages(messages); - info!( - "Cached {} messages for channel: {} ({})", - len, channel.name, id - ); + info!("Cached {} messages for channel: {} ({})", len, channel.name, id); } info!("Cache ready!"); } diff --git a/src/main.rs b/src/main.rs index 3d0a804..1e16c99 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,15 +4,9 @@ mod utils; use std::{env, sync::Arc}; use features::{MessageCache, MessageCacheType}; -use serenity::{ - all::Ready, - async_trait, - cache::Settings as CacheSettings, - prelude::*, -}; +use serenity::{all::Ready, async_trait, cache::Settings as CacheSettings, prelude::*}; use tracing::{error, info}; - struct MainHandler; #[async_trait] @@ -29,15 +23,14 @@ async fn main() { let _ = dotenvy::dotenv(); let token = env::var("TOKEN").expect("Expected a TOKEN in the environment"); - let intents = - GatewayIntents::GUILD_MESSAGES | GatewayIntents::GUILDS | GatewayIntents::MESSAGE_CONTENT; + let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::GUILDS | GatewayIntents::MESSAGE_CONTENT; let mut settings = CacheSettings::default(); settings.max_messages = 1_000_000; let mut client = Client::builder(&token, intents) .event_handler(MainHandler) .event_handler(features::AuthHandler) .event_handler(features::LoggingHandler) - .event_handler(features::MessageCacheHandler) + .event_handler(features::MessageCacheHandler { disabled: false }) .cache_settings(settings) .type_map_insert::(Arc::new(MessageCache::new())) .await diff --git a/src/utils.rs b/src/utils.rs index a61784e..6d7fb16 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,11 +1,18 @@ -use serenity::{all::{ChannelId, Context, CreateAllowedMentions, CreateMessage, Message, MessageId}, Result}; +use itertools::Itertools; +use serenity::{ + all::{ChannelId, Context, CreateAllowedMentions, CreateMessage, Message, MessageId}, + Result, +}; +use similar::{Algorithm, ChangeTag, TextDiff}; use crate::MessageCacheType; +pub fn create_safe_message() -> CreateMessage { + CreateMessage::new().allowed_mentions(CreateAllowedMentions::new().all_users(false)) +} + pub fn create_message(content: String) -> CreateMessage { - CreateMessage::new() - .content(content) - .allowed_mentions(CreateAllowedMentions::new().all_users(false)) + create_safe_message().content(content) } pub async fn get_cached_message(ctx: &Context, channel_id: ChannelId, message_id: MessageId) -> Option { @@ -29,3 +36,14 @@ pub async fn get_message(ctx: &Context, channel_id: ChannelId, message_id: Messa channel_id.message(&ctx.http, message_id).await } + +pub fn create_diff_lines_text(old: &str, new: &str) -> String { + let diff = TextDiff::configure().algorithm(Algorithm::Myers).diff_lines(old, new); + diff.iter_all_changes() + .map(|c| match c.tag() { + ChangeTag::Delete => format!("- {}", c), + ChangeTag::Insert => format!("+ {}", c), + ChangeTag::Equal => c.to_string(), + }) + .join("") +} From 8c5561da3c92a7d85c7a0b4580537c6ba0bd43b7 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sun, 3 Nov 2024 00:23:14 +0900 Subject: [PATCH 004/104] React when authenticated --- .env.example | 4 ++++ src/features/auth.rs | 19 +++++++++++++------ src/main.rs | 9 ++++++++- src/utils.rs | 11 ++++++++++- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/.env.example b/.env.example index f6a3764..4ad9878 100644 --- a/.env.example +++ b/.env.example @@ -6,9 +6,13 @@ CHANNEL_ID=100000000000000000000 LOG_CHANNEL_ID=100000000000000000000 # キーワード認証後に付与するロールID ROLE_ID=100000000000000000000 +# 認証完了時に付与するリアクション +AUTHENTICATED_REACTION="<:verified:100000000000000000000>" # キーワードの正規表現 TRIGGER_REGEX="[おオオ][のノノ][れレレ][GgGg][RrRr][EeEe][GgGg]" # メッセージ編集・削除ログを送信するチャンネルID MESSAGE_LOG_CHANNEL_ID=1296160534680305804 # メッセージキャッシュを保持するギルドID MESSAGE_CACHE_GUILD_ID=525995782592266250 +# メッセージキャッシュを無効化するか +CACHE_DISABLED=false diff --git a/src/features/auth.rs b/src/features/auth.rs index 9e3dbe8..26161aa 100644 --- a/src/features/auth.rs +++ b/src/features/auth.rs @@ -2,13 +2,13 @@ use std::env; use std::sync::LazyLock; use regex::Regex; -use serenity::all::{ChannelId, GuildId, MessageUpdateEvent, RoleId, User}; -use serenity::async_trait; +use serenity::all::{ChannelId, GuildId, MessageId, MessageUpdateEvent, ReactionType, RoleId, User}; use serenity::model::channel::Message; use serenity::prelude::*; +use serenity::{async_trait, utils}; use tracing::error; -use crate::utils::{create_message, get_message}; +use crate::utils::{create_message, get_message, react_from_id}; #[rustfmt::skip] static TRIGGER_REGEX: LazyLock = LazyLock::new(|| Regex::new(&env::var("TRIGGER_REGEX").unwrap()).unwrap()); @@ -18,6 +18,8 @@ static CHANNEL_ID: LazyLock = LazyLock::new(|| ChannelId::new(env::va static LOG_CHANNEL_ID: LazyLock = LazyLock::new(|| ChannelId::new(env::var("LOG_CHANNEL_ID").unwrap().parse().unwrap())); #[rustfmt::skip] static ROLE_ID: LazyLock = LazyLock::new(|| RoleId::new(env::var("ROLE_ID").unwrap().parse().unwrap())); +#[rustfmt::skip] +static AUTHENTICATED_REACTION: LazyLock = LazyLock::new(|| utils::parse_emoji(env::var("AUTHENTICATED_REACTION").unwrap()).unwrap().into()); pub struct Handler; @@ -27,6 +29,7 @@ impl Handler { ctx: &Context, guild_id: GuildId, channel_id: ChannelId, + message_id: MessageId, author: User, content: String, ) { @@ -60,6 +63,10 @@ impl Handler { return error!("Failed to add role: {:?}", why); } + if let Err(why) = react_from_id(ctx, channel_id, message_id, &AUTHENTICATED_REACTION).await { + error!("Failed to react to message: {:?}", why); + } + let log = create_message(format!("{} にロールを追加しました。", member.mention())); if let Err(why) = LOG_CHANNEL_ID.send_message(&ctx.http, log).await { error!("Error sending message: {:?}", why) @@ -74,7 +81,7 @@ impl EventHandler for Handler { return error!("Failed to get guild id: {:?}", msg); }; - self.handle_message(&ctx, guild_id, msg.channel_id, msg.author, msg.content) + self.handle_message(&ctx, guild_id, msg.channel_id, msg.id, msg.author, msg.content) .await; } @@ -86,14 +93,14 @@ impl EventHandler for Handler { return error!("Failed to get author: {:?}", event); }; if let Some(content) = event.content { - self.handle_message(&ctx, guild_id, event.channel_id, author, content) + self.handle_message(&ctx, guild_id, event.channel_id, event.id, author, content) .await; return; } match get_message(&ctx, event.channel_id, event.id).await { Ok(m) => { - self.handle_message(&ctx, guild_id, event.channel_id, author, m.content) + self.handle_message(&ctx, guild_id, event.channel_id, event.id, author, m.content) .await } Err(why) => error!("Failed to get message: {:?}", why), diff --git a/src/main.rs b/src/main.rs index 1e16c99..82947bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,6 +23,11 @@ async fn main() { let _ = dotenvy::dotenv(); let token = env::var("TOKEN").expect("Expected a TOKEN in the environment"); + let cache_disabled = env::var("CACHE_DISABLED") + .expect("Expected a CACHE_DISABLED in the environment") + .parse::() + .expect("CACHE_DISABLED must be a boolean"); + let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::GUILDS | GatewayIntents::MESSAGE_CONTENT; let mut settings = CacheSettings::default(); settings.max_messages = 1_000_000; @@ -30,7 +35,9 @@ async fn main() { .event_handler(MainHandler) .event_handler(features::AuthHandler) .event_handler(features::LoggingHandler) - .event_handler(features::MessageCacheHandler { disabled: false }) + .event_handler(features::MessageCacheHandler { + disabled: cache_disabled, + }) .cache_settings(settings) .type_map_insert::(Arc::new(MessageCache::new())) .await diff --git a/src/utils.rs b/src/utils.rs index 6d7fb16..20cee45 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,6 @@ use itertools::Itertools; use serenity::{ - all::{ChannelId, Context, CreateAllowedMentions, CreateMessage, Message, MessageId}, + all::{CacheHttp, ChannelId, Context, CreateAllowedMentions, CreateMessage, Message, MessageId, ReactionType}, Result, }; use similar::{Algorithm, ChangeTag, TextDiff}; @@ -37,6 +37,15 @@ pub async fn get_message(ctx: &Context, channel_id: ChannelId, message_id: Messa channel_id.message(&ctx.http, message_id).await } +pub async fn react_from_id( + ctx: &Context, + channel_id: ChannelId, + message_id: MessageId, + reaction_type: &ReactionType, +) -> Result<()> { + ctx.http().create_reaction(channel_id, message_id, reaction_type).await +} + pub fn create_diff_lines_text(old: &str, new: &str) -> String { let diff = TextDiff::configure().algorithm(Algorithm::Myers).diff_lines(old, new); diff.iter_all_changes() From 778cbcfd10c26a1cfd4bb788c425a083aeba633e Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sun, 3 Nov 2024 00:43:25 +0900 Subject: [PATCH 005/104] Update workflow --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9ae26bf..64d480f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,7 +36,7 @@ jobs: # zigをインストール - name: Install Zig - uses: goto-bus-stop/setup-zig@v2 + uses: mlugg/setup-zig@v1 # rustをインストール - name: Install Rust toolchain From 814e8709c68ded3ddcac55d645b972cc71cfeaf4 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Thu, 7 Nov 2024 01:45:14 +0900 Subject: [PATCH 006/104] =?UTF-8?q?.env=E3=81=8B=E3=82=89=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=81=AB=E4=BB=A5?= =?UTF-8?q?=E9=99=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 18 --- .gitignore | 1 + Cargo.lock | 213 +++++++++++++++++++++++++++++----- Cargo.toml | 16 ++- config.sample.toml | 28 +++++ src/config.rs | 55 +++++++++ src/features/auth.rs | 44 +++---- src/features/logging.rs | 24 ++-- src/features/message_cache.rs | 44 +++---- src/main.rs | 23 ++-- src/utils.rs | 22 ++-- 11 files changed, 346 insertions(+), 142 deletions(-) delete mode 100644 .env.example create mode 100644 config.sample.toml create mode 100644 src/config.rs diff --git a/.env.example b/.env.example deleted file mode 100644 index 4ad9878..0000000 --- a/.env.example +++ /dev/null @@ -1,18 +0,0 @@ -# トークン -TOKEN="TOKEN" -# キーワードを検知するチャンネルID -CHANNEL_ID=100000000000000000000 -# キーワード認証ログを送信するチャンネルID -LOG_CHANNEL_ID=100000000000000000000 -# キーワード認証後に付与するロールID -ROLE_ID=100000000000000000000 -# 認証完了時に付与するリアクション -AUTHENTICATED_REACTION="<:verified:100000000000000000000>" -# キーワードの正規表現 -TRIGGER_REGEX="[おオオ][のノノ][れレレ][GgGg][RrRr][EeEe][GgGg]" -# メッセージ編集・削除ログを送信するチャンネルID -MESSAGE_LOG_CHANNEL_ID=1296160534680305804 -# メッセージキャッシュを保持するギルドID -MESSAGE_CACHE_GUILD_ID=525995782592266250 -# メッセージキャッシュを無効化するか -CACHE_DISABLED=false diff --git a/.gitignore b/.gitignore index 83a00ee..963ff02 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target .env +config.toml diff --git a/Cargo.lock b/Cargo.lock index acbc69b..7ac9d00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -265,6 +265,41 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.66", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.66", +] + [[package]] name = "dashmap" version = "5.5.3" @@ -272,7 +307,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown", + "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", @@ -287,7 +322,7 @@ checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", "crossbeam-utils", - "hashbrown", + "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", @@ -330,12 +365,6 @@ dependencies = [ "syn 2.0.66", ] -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - [[package]] name = "either" version = "1.13.0" @@ -409,9 +438,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -423,9 +452,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -433,21 +462,21 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", @@ -456,21 +485,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -538,13 +567,19 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.5" @@ -557,6 +592,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "http" version = "0.2.12" @@ -781,6 +822,12 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.0.0" @@ -793,6 +840,17 @@ dependencies = [ "utf8_iter", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.2.6" @@ -800,7 +858,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.5", + "serde", ] [[package]] @@ -1106,9 +1165,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -1363,6 +1422,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1375,6 +1443,36 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.2.6", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "serenity" version = "0.12.2" @@ -1505,6 +1603,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.5.0" @@ -1662,9 +1766,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.40.0" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" dependencies = [ "backtrace", "bytes", @@ -1738,6 +1842,40 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +dependencies = [ + "indexmap 2.2.6", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower-service" version = "0.3.2" @@ -1855,7 +1993,7 @@ checksum = "eb704842c709bc76f63e99e704cb208beeccca2abbabd0d9aec02e48ca1cee0f" dependencies = [ "chrono", "dashmap 5.5.3", - "hashbrown", + "hashbrown 0.14.5", "mini-moka", "parking_lot", "secrecy", @@ -1938,12 +2076,14 @@ name = "valine_bot" version = "0.1.0" dependencies = [ "dashmap 6.1.0", - "dotenvy", "itertools", "regex", + "serde", + "serde_with", "serenity", "similar", "tokio", + "toml", "tracing", "tracing-subscriber", ] @@ -2268,6 +2408,15 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" diff --git a/Cargo.toml b/Cargo.toml index 25ad0f3..41a032b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,19 +6,17 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dotenvy = "0.15.7" -regex = "1.11.0" +regex = "1.0" similar = "2.6.0" dashmap = "6.1.0" -itertools = "0.13.0" +itertools = "0.13" serenity = { version = "0.12.2" } -tokio = { version = "1.40.0", features = [ - "rt-multi-thread", - "macros", - "signal", -] } +tokio = { version = "1.0", features = ["rt-multi-thread", "macros", "signal"] } tracing = "0.1.40" -tracing-subscriber = "0.3.18" +tracing-subscriber = "0.3" +serde = { version = "1.0", features = ["derive"] } +serde_with = "3" +toml = "0.8" [profile.release] panic = "abort" diff --git a/config.sample.toml b/config.sample.toml new file mode 100644 index 0000000..e9d347c --- /dev/null +++ b/config.sample.toml @@ -0,0 +1,28 @@ + +[bot] +token = "TOKEN" + + +[auth] +# 認証に使用するチャンネルID +channel_id = "000000000000000000" +# 認証の結果を通知するチャンネルID +log_channel_id = "000000000000000000" +# 認証後に付与するロールID +role_id = "000000000000000000" +# 認証に成功したメッセージに付与するリアクション +authenticated_reaction = "<:verified:000000000000000000>" +# マッチしたら認証成功とする正規表現 +trigger_regex = "[おオオ][のノノ][れレレ][GgGg][RrRr][EeEe][GgGg]" + + +[message_logging] +# メッセージの削除・編集のログを残すチャンネルID +channel_id = "000000000000000000" + + +[message_cache] +# キャッシュを無効化するかどうか +disabled = true +# 過去のメッセージのキャッシュを取るギルドIDのリスト +target_guild_ids = ["000000000000000000"] diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..5460733 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,55 @@ +use std::sync::Arc; + +use regex::Regex; +use serde::Deserialize; +use serde_with::{serde_as, DisplayFromStr}; +use serenity::{ + all::{ChannelId, Context, GuildId, ReactionType, RoleId}, + prelude::TypeMapKey, +}; + +pub async fn get_config(ctx: &Context) -> Arc { + let data = ctx.data.read().await; + let config = data.get::().expect("Expected MessageCount in TypeMap."); + config.clone() +} + +#[derive(Debug, Deserialize)] +pub struct Config { + pub bot: BotConfig, + pub auth: AuthConfig, + pub message_logging: MessageLoggingConfig, + pub message_cache: MessageCacheConfig, +} + +impl TypeMapKey for Config { + type Value = Arc; +} + +#[derive(Debug, Deserialize)] +pub struct BotConfig { + pub token: String, +} + +#[serde_as] +#[derive(Debug, Deserialize)] +pub struct AuthConfig { + pub channel_id: ChannelId, + pub log_channel_id: ChannelId, + pub role_id: RoleId, + #[serde_as(as = "DisplayFromStr")] + pub authenticated_reaction: ReactionType, + #[serde_as(as = "DisplayFromStr")] + pub trigger_regex: Regex, +} + +#[derive(Debug, Deserialize)] +pub struct MessageLoggingConfig { + pub channel_id: ChannelId, +} + +#[derive(Debug, Deserialize)] +pub struct MessageCacheConfig { + pub disabled: bool, + pub target_guild_ids: Vec, +} diff --git a/src/features/auth.rs b/src/features/auth.rs index 26161aa..4af586e 100644 --- a/src/features/auth.rs +++ b/src/features/auth.rs @@ -1,25 +1,11 @@ -use std::env; -use std::sync::LazyLock; - -use regex::Regex; -use serenity::all::{ChannelId, GuildId, MessageId, MessageUpdateEvent, ReactionType, RoleId, User}; +use serenity::all::{ChannelId, GuildId, MessageId, MessageUpdateEvent, User}; +use serenity::async_trait; use serenity::model::channel::Message; use serenity::prelude::*; -use serenity::{async_trait, utils}; use tracing::error; -use crate::utils::{create_message, get_message, react_from_id}; - -#[rustfmt::skip] -static TRIGGER_REGEX: LazyLock = LazyLock::new(|| Regex::new(&env::var("TRIGGER_REGEX").unwrap()).unwrap()); -#[rustfmt::skip] -static CHANNEL_ID: LazyLock = LazyLock::new(|| ChannelId::new(env::var("CHANNEL_ID").unwrap().parse().unwrap())); -#[rustfmt::skip] -static LOG_CHANNEL_ID: LazyLock = LazyLock::new(|| ChannelId::new(env::var("LOG_CHANNEL_ID").unwrap().parse().unwrap())); -#[rustfmt::skip] -static ROLE_ID: LazyLock = LazyLock::new(|| RoleId::new(env::var("ROLE_ID").unwrap().parse().unwrap())); -#[rustfmt::skip] -static AUTHENTICATED_REACTION: LazyLock = LazyLock::new(|| utils::parse_emoji(env::var("AUTHENTICATED_REACTION").unwrap()).unwrap().into()); +use crate::config::get_config; +use crate::utils::{create_message, get_message, react_from_id, send_message}; pub struct Handler; @@ -33,11 +19,13 @@ impl Handler { author: User, content: String, ) { - if !TRIGGER_REGEX.is_match(&content) { + let config = &get_config(ctx).await.auth; + + if !config.trigger_regex.is_match(&content) { return; } - if channel_id != *CHANNEL_ID { + if channel_id != config.channel_id { return; } @@ -46,31 +34,25 @@ impl Handler { Err(why) => return error!("Failed to get member: {:?}", why), }; - if member.roles.contains(&ROLE_ID) { + if member.roles.contains(&config.role_id) { error!("{} already has the role", member.user.name); return; } - if let Err(why) = member.add_role(&ctx.http, *ROLE_ID).await { + if let Err(why) = member.add_role(&ctx.http, config.role_id).await { let log = create_message(format!( "{} にロールを追加できませんでした。\n```\n{}```", member.mention(), why )); - if let Err(why) = LOG_CHANNEL_ID.send_message(&ctx.http, log).await { - error!("Error sending message: {:?}", why) - } + let _ = send_message(ctx, &config.log_channel_id, log).await; return error!("Failed to add role: {:?}", why); } - if let Err(why) = react_from_id(ctx, channel_id, message_id, &AUTHENTICATED_REACTION).await { - error!("Failed to react to message: {:?}", why); - } + react_from_id(ctx, channel_id, message_id, &config.authenticated_reaction).await; let log = create_message(format!("{} にロールを追加しました。", member.mention())); - if let Err(why) = LOG_CHANNEL_ID.send_message(&ctx.http, log).await { - error!("Error sending message: {:?}", why) - } + let _ = send_message(ctx, &config.log_channel_id, log).await; } } diff --git a/src/features/logging.rs b/src/features/logging.rs index e33e3ca..7379ee1 100644 --- a/src/features/logging.rs +++ b/src/features/logging.rs @@ -1,5 +1,3 @@ -use std::{env, sync::LazyLock}; - use itertools::enumerate; use serenity::{ all::{ @@ -12,13 +10,11 @@ use serenity::{ use tracing::error; use crate::{ + config::get_config, features::MessageCacheType, - utils::{create_diff_lines_text, create_safe_message, get_cached_message}, + utils::{create_diff_lines_text, create_safe_message, get_cached_message, send_message}, }; -#[rustfmt::skip] -static LOG_CHANNEL_ID: LazyLock = LazyLock::new(|| ChannelId::new(env::var("MESSAGE_LOG_CHANNEL_ID").unwrap().parse().unwrap())); - pub struct Handler; impl Handler { @@ -92,6 +88,12 @@ impl Handler { let cache = data.get_mut::().unwrap(); cache.insert(message.clone()); } + + async fn send_log(&self, ctx: &Context, embed: CreateEmbed) { + let config = get_config(ctx).await; + let log = create_safe_message().add_embed(embed); + let _ = send_message(ctx, &config.message_logging.channel_id, log).await; + } } #[async_trait] @@ -138,10 +140,7 @@ impl EventHandler for Handler { ); embed = self.build_embed(&message, new_message.content, embed); - let log = create_safe_message().add_embed(embed); - if let Err(why) = LOG_CHANNEL_ID.send_message(&ctx.http, log).await { - error!("Error sending message: {:?}", why) - } + self.send_log(&ctx, embed).await; } async fn message_delete( @@ -181,9 +180,6 @@ impl EventHandler for Handler { ); embed = self.build_embed(&message, "".to_string(), embed); - let log = create_safe_message().add_embed(embed); - if let Err(why) = LOG_CHANNEL_ID.send_message(&ctx.http, log).await { - error!("Error sending message: {:?}", why) - } + self.send_log(&ctx, embed).await; } } diff --git a/src/features/message_cache.rs b/src/features/message_cache.rs index 7b132ec..6ec6449 100644 --- a/src/features/message_cache.rs +++ b/src/features/message_cache.rs @@ -1,8 +1,4 @@ -use std::{ - collections::HashMap, - env, - sync::{Arc, LazyLock}, -}; +use std::{collections::HashMap, sync::Arc}; use dashmap::DashMap; use itertools::Itertools; @@ -11,10 +7,9 @@ use serenity::{ async_trait, prelude::TypeMapKey, }; -use tracing::info; +use tracing::{error, info}; -#[rustfmt::skip] -static MESSAGE_CACHE_GUILD_ID: LazyLock = LazyLock::new(|| GuildId::new(env::var("MESSAGE_CACHE_GUILD_ID").unwrap().parse().unwrap())); +use crate::config::get_config; pub struct MessageCache { cache: DashMap>, @@ -85,22 +80,29 @@ impl EventHandler for Handler { return; } - let channels = MESSAGE_CACHE_GUILD_ID.channels(&ctx.http).await.unwrap(); - for (id, channel) in channels.iter() { - if !channel.is_text_based() { - continue; - } - - let Ok(messages) = channel.messages(&ctx.http, GetMessages::new().limit(100)).await else { - info!("Failed to get messages for channel: {:?}", channel.name); + let config = get_config(&ctx).await; + for guild in &config.message_cache.target_guild_ids { + let Ok(channels) = guild.channels(&ctx.http).await else { + error!("Failed to get channels for guild: {:?}", guild); continue; }; - let mut data = ctx.data.write().await; - let cache = data.get_mut::().unwrap(); - let len = messages.len(); - cache.extend_messages(messages); - info!("Cached {} messages for channel: {} ({})", len, channel.name, id); + for (id, channel) in channels { + if !channel.is_text_based() { + continue; + } + + let Ok(messages) = channel.messages(&ctx.http, GetMessages::new().limit(100)).await else { + info!("Failed to get messages for channel: {:?}", channel.name); + continue; + }; + + let mut data = ctx.data.write().await; + let cache = data.get_mut::().unwrap(); + let len = messages.len(); + cache.extend_messages(messages); + info!("Cached {} messages for channel: {} ({})", len, channel.name, id); + } } info!("Cache ready!"); } diff --git a/src/main.rs b/src/main.rs index 82947bf..ff3346e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,10 @@ +mod config; mod features; mod utils; -use std::{env, sync::Arc}; +use std::{fs::read_to_string, sync::Arc}; +use config::Config; use features::{MessageCache, MessageCacheType}; use serenity::{all::Ready, async_trait, cache::Settings as CacheSettings, prelude::*}; use tracing::{error, info}; @@ -20,26 +22,27 @@ impl EventHandler for MainHandler { async fn main() { tracing_subscriber::fmt::init(); - let _ = dotenvy::dotenv(); - - let token = env::var("TOKEN").expect("Expected a TOKEN in the environment"); - let cache_disabled = env::var("CACHE_DISABLED") - .expect("Expected a CACHE_DISABLED in the environment") - .parse::() - .expect("CACHE_DISABLED must be a boolean"); + let config = read_to_string("config.toml").expect("Failed to read config.toml"); + let config = match toml::from_str::(&config) { + Ok(config) => config, + Err(e) => { + panic!("Failed to parse config.toml: {}", e); + } + }; let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::GUILDS | GatewayIntents::MESSAGE_CONTENT; let mut settings = CacheSettings::default(); settings.max_messages = 1_000_000; - let mut client = Client::builder(&token, intents) + let mut client = Client::builder(&config.bot.token, intents) .event_handler(MainHandler) .event_handler(features::AuthHandler) .event_handler(features::LoggingHandler) .event_handler(features::MessageCacheHandler { - disabled: cache_disabled, + disabled: config.message_cache.disabled, }) .cache_settings(settings) .type_map_insert::(Arc::new(MessageCache::new())) + .type_map_insert::(Arc::new(config)) .await .expect("Err creating client"); diff --git a/src/utils.rs b/src/utils.rs index 20cee45..ee49517 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,6 +4,7 @@ use serenity::{ Result, }; use similar::{Algorithm, ChangeTag, TextDiff}; +use tracing::error; use crate::MessageCacheType; @@ -15,6 +16,16 @@ pub fn create_message(content: String) -> CreateMessage { create_safe_message().content(content) } +pub async fn send_message(ctx: &Context, channel_id: &ChannelId, builder: CreateMessage) -> Result { + match channel_id.send_message(&ctx.http, builder).await { + Ok(m) => Ok(m), + Err(why) => { + error!("Error sending message: {:?}", why); + Err(why) + } + } +} + pub async fn get_cached_message(ctx: &Context, channel_id: ChannelId, message_id: MessageId) -> Option { if let Some(m) = ctx.cache.message(channel_id, message_id) { return Some(m.clone()); @@ -37,13 +48,10 @@ pub async fn get_message(ctx: &Context, channel_id: ChannelId, message_id: Messa channel_id.message(&ctx.http, message_id).await } -pub async fn react_from_id( - ctx: &Context, - channel_id: ChannelId, - message_id: MessageId, - reaction_type: &ReactionType, -) -> Result<()> { - ctx.http().create_reaction(channel_id, message_id, reaction_type).await +pub async fn react_from_id(ctx: &Context, channel_id: ChannelId, message_id: MessageId, reaction_type: &ReactionType) { + if let Err(why) = ctx.http().create_reaction(channel_id, message_id, reaction_type).await { + error!("Failed to react to message: {:?}", why); + } } pub fn create_diff_lines_text(old: &str, new: &str) -> String { From c3978b9419bb76d8ce21abe749e3744ed4c6cd78 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Thu, 7 Nov 2024 02:50:36 +0900 Subject: [PATCH 007/104] =?UTF-8?q?=E3=82=B9=E3=83=AC=E3=83=83=E3=83=89?= =?UTF-8?q?=E3=81=AE=E9=96=8B=E5=A7=8B=E6=99=82=E3=81=AB=E3=83=A1=E3=83=83?= =?UTF-8?q?=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92=E9=80=81=E4=BF=A1=E3=81=99?= =?UTF-8?q?=E3=82=8B=E6=A9=9F=E8=83=BD=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- Cargo.toml | 2 +- config.sample.toml | 12 +++++++++ src/config.rs | 12 +++++++++ src/features/mod.rs | 2 ++ src/features/thread_channel_startup.rs | 35 ++++++++++++++++++++++++++ src/main.rs | 1 + 7 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 src/features/thread_channel_startup.rs diff --git a/Cargo.lock b/Cargo.lock index 7ac9d00..fa847b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2073,7 +2073,7 @@ checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0" [[package]] name = "valine_bot" -version = "0.1.0" +version = "1.2.0" dependencies = [ "dashmap 6.1.0", "itertools", diff --git a/Cargo.toml b/Cargo.toml index 41a032b..1a260c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "valine_bot" -version = "0.1.0" +version = "1.2.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/config.sample.toml b/config.sample.toml index e9d347c..0619c84 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -26,3 +26,15 @@ channel_id = "000000000000000000" disabled = true # 過去のメッセージのキャッシュを取るギルドIDのリスト target_guild_ids = ["000000000000000000"] + + +[thread_channel_startup] +# 空にする方法 +# threads = [] + +[[thread_channel_startup.threads]] +# 初期メッセージを投稿するスレッドチャンネルのID +channel_id = "000000000000000000" +# 初期メッセージの内容 +startup_message = """スレッドの始まりだぁ! +まずはこのテンプレートを読んでね!""" diff --git a/src/config.rs b/src/config.rs index 5460733..e564c20 100644 --- a/src/config.rs +++ b/src/config.rs @@ -20,6 +20,7 @@ pub struct Config { pub auth: AuthConfig, pub message_logging: MessageLoggingConfig, pub message_cache: MessageCacheConfig, + pub thread_channel_startup: ThreadChannelStartupConfig, } impl TypeMapKey for Config { @@ -53,3 +54,14 @@ pub struct MessageCacheConfig { pub disabled: bool, pub target_guild_ids: Vec, } + +#[derive(Debug, Deserialize)] +pub struct ThreadChannelStartupConfig { + pub threads: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct ThreadStartupConfig { + pub channel_id: ChannelId, + pub startup_message: String, +} diff --git a/src/features/mod.rs b/src/features/mod.rs index c4c87c5..3018c3f 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -1,9 +1,11 @@ mod auth; mod logging; mod message_cache; +mod thread_channel_startup; pub use auth::Handler as AuthHandler; pub use logging::Handler as LoggingHandler; pub use message_cache::Handler as MessageCacheHandler; +pub use thread_channel_startup::Handler as ThreadChannelStartupHandler; pub use message_cache::{MessageCache, MessageCacheType}; diff --git a/src/features/thread_channel_startup.rs b/src/features/thread_channel_startup.rs new file mode 100644 index 0000000..295b3ed --- /dev/null +++ b/src/features/thread_channel_startup.rs @@ -0,0 +1,35 @@ +use serenity::{ + all::{Context, EventHandler, GuildChannel}, + async_trait, +}; +use tracing::error; + +use crate::{ + config::get_config, + utils::{create_message, send_message}, +}; + +pub struct Handler; + +#[async_trait] +impl EventHandler for Handler { + async fn thread_create(&self, ctx: Context, thread: GuildChannel) { + // スレッドの作成時とスレッドの初期メッセージ送信後にイベントが発火するので、初期メッセージ送信後は何もしない + if thread.last_message_id.is_some() { + return; + } + + let config = &get_config(&ctx).await.thread_channel_startup; + let Some(parent_id) = thread.parent_id else { + return error!("Failed to get parent id: {:?}", thread); + }; + for thread_config in &config.threads { + if parent_id != thread_config.channel_id { + continue; + } + + let log = create_message(thread_config.startup_message.clone()); + let _ = send_message(&ctx, &thread.id, log).await; + } + } +} diff --git a/src/main.rs b/src/main.rs index ff3346e..cfeefdd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,6 +37,7 @@ async fn main() { .event_handler(MainHandler) .event_handler(features::AuthHandler) .event_handler(features::LoggingHandler) + .event_handler(features::ThreadChannelStartupHandler) .event_handler(features::MessageCacheHandler { disabled: config.message_cache.disabled, }) From a42a3111ef35a43a69e65b10ad0260bdd53227c3 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 11 Nov 2024 11:51:25 +0900 Subject: [PATCH 008/104] =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=95=E3=82=A3?= =?UTF-8?q?=E3=82=B0=E3=81=AE=E3=83=81=E3=82=A7=E3=83=83=E3=82=AF=E6=A9=9F?= =?UTF-8?q?=E8=83=BD=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 23 ++++++++++++++++++++++- Cargo.toml | 7 ++++--- rust-toolchain.toml | 2 +- src/main.rs | 15 +++++++++++++++ 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fa847b3..9ad5c57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,6 +115,26 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bpaf" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50fd5174866dc2fa2ddc96e8fb800852d37f064f32a45c7b7c2f8fa2c64c77fa" +dependencies = [ + "bpaf_derive", +] + +[[package]] +name = "bpaf_derive" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf95d9c7e6aba67f8fc07761091e93254677f4db9e27197adecebc7039a58722" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -2073,8 +2093,9 @@ checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0" [[package]] name = "valine_bot" -version = "1.2.0" +version = "1.3.0" dependencies = [ + "bpaf", "dashmap 6.1.0", "itertools", "regex", diff --git a/Cargo.toml b/Cargo.toml index 1a260c6..e5259ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "valine_bot" -version = "1.2.0" +version = "1.3.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -11,12 +11,13 @@ similar = "2.6.0" dashmap = "6.1.0" itertools = "0.13" serenity = { version = "0.12.2" } -tokio = { version = "1.0", features = ["rt-multi-thread", "macros", "signal"] } tracing = "0.1.40" tracing-subscriber = "0.3" -serde = { version = "1.0", features = ["derive"] } serde_with = "3" toml = "0.8" +tokio = { version = "1.0", features = ["rt-multi-thread", "macros", "signal"] } +serde = { version = "1.0", features = ["derive"] } +bpaf = { version = "0.9", features = ["derive"] } [profile.release] panic = "abort" diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 8cca5be..76fcadb 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.80" +channel = "1.82" diff --git a/src/main.rs b/src/main.rs index cfeefdd..79d0cb8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ mod utils; use std::{fs::read_to_string, sync::Arc}; +use bpaf::Bpaf; use config::Config; use features::{MessageCache, MessageCacheType}; use serenity::{all::Ready, async_trait, cache::Settings as CacheSettings, prelude::*}; @@ -18,6 +19,13 @@ impl EventHandler for MainHandler { } } +#[derive(Clone, Debug, Bpaf)] +#[bpaf(options, version)] +struct Options { + #[bpaf(short, long)] + check_config: bool, +} + #[tokio::main] async fn main() { tracing_subscriber::fmt::init(); @@ -30,6 +38,13 @@ async fn main() { } }; + let options = options().run(); + + if options.check_config { + println!("Config is valid"); + return; + } + let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::GUILDS | GatewayIntents::MESSAGE_CONTENT; let mut settings = CacheSettings::default(); settings.max_messages = 1_000_000; From 6fa15cfdac4dc8ea28bb151fb4625c58cba7d910 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Tue, 10 Dec 2024 11:14:32 +0900 Subject: [PATCH 009/104] Fix #2 --- src/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.rs b/src/utils.rs index ee49517..a9488b9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -60,7 +60,7 @@ pub fn create_diff_lines_text(old: &str, new: &str) -> String { .map(|c| match c.tag() { ChangeTag::Delete => format!("- {}", c), ChangeTag::Insert => format!("+ {}", c), - ChangeTag::Equal => c.to_string(), + ChangeTag::Equal => format!(" {}", c), }) .join("") } From ea78279eb6c9eec82a9bcc81f80e6860589390ed Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Tue, 10 Dec 2024 11:19:07 +0900 Subject: [PATCH 010/104] Fix #1 --- src/features/auth.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/auth.rs b/src/features/auth.rs index 4af586e..b84a814 100644 --- a/src/features/auth.rs +++ b/src/features/auth.rs @@ -34,6 +34,8 @@ impl Handler { Err(why) => return error!("Failed to get member: {:?}", why), }; + react_from_id(ctx, channel_id, message_id, &config.authenticated_reaction).await; + if member.roles.contains(&config.role_id) { error!("{} already has the role", member.user.name); return; @@ -49,8 +51,6 @@ impl Handler { return error!("Failed to add role: {:?}", why); } - react_from_id(ctx, channel_id, message_id, &config.authenticated_reaction).await; - let log = create_message(format!("{} にロールを追加しました。", member.mention())); let _ = send_message(ctx, &config.log_channel_id, log).await; } From df43993af65825e77b24e88a1c21d1c34116ea96 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Tue, 10 Dec 2024 11:34:39 +0900 Subject: [PATCH 011/104] Fix #3 --- src/features/thread_channel_startup.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/thread_channel_startup.rs b/src/features/thread_channel_startup.rs index 295b3ed..0fd74dd 100644 --- a/src/features/thread_channel_startup.rs +++ b/src/features/thread_channel_startup.rs @@ -14,8 +14,8 @@ pub struct Handler; #[async_trait] impl EventHandler for Handler { async fn thread_create(&self, ctx: Context, thread: GuildChannel) { - // スレッドの作成時とスレッドの初期メッセージ送信後にイベントが発火するので、初期メッセージ送信後は何もしない - if thread.last_message_id.is_some() { + // スレッドの作成時とスレッドの初期メッセージ送信後にイベントが発火するので、スレッド作成時は無視する + if thread.last_message_id.is_none() { return; } From 0704e1290b5675fefd03cb62ca56e4e41fa30c89 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Fri, 13 Dec 2024 03:11:10 +0900 Subject: [PATCH 012/104] =?UTF-8?q?=E8=BB=A2=E9=80=81=E3=81=A8=E8=BF=94?= =?UTF-8?q?=E4=BF=A1=E3=82=92=E5=8C=BA=E5=88=A5=E3=81=99=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 523 +++++++++++++++++++++++----------------- Cargo.toml | 5 +- rust-toolchain.toml | 2 +- src/features/logging.rs | 36 +-- 4 files changed, 331 insertions(+), 235 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9ad5c57..5ef1118 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,21 +1,21 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aho-corasick" @@ -43,43 +43,43 @@ dependencies = [ [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" dependencies = [ "serde", ] [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -102,9 +102,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "block-buffer" @@ -132,7 +132,7 @@ checksum = "cf95d9c7e6aba67f8fc07761091e93254677f4db9e27197adecebc7039a58722" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -155,15 +155,15 @@ 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 = "camino" -version = "1.1.7" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" dependencies = [ "serde", ] @@ -192,9 +192,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.99" +version = "1.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +checksum = "40545c26d092346d8a8dab71ee48e7685a7a9cba76e634790c215b41a4a7b4cf" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -212,14 +215,14 @@ dependencies = [ "iana-time-zone", "num-traits", "serde", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] name = "command_attr" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88da8d7e9fe6f30d8e3fcf72d0f84102b49de70fece952633e8439e89bdc7631" +checksum = "6fcc89439e1bb4e19050a9586a767781a3060000d2f3296fd2a40597ad9421c5" dependencies = [ "proc-macro2", "quote", @@ -238,15 +241,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -306,7 +309,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -317,7 +320,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -364,6 +367,17 @@ dependencies = [ "serde", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "digest" version = "0.10.7" @@ -376,13 +390,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -393,9 +407,9 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -427,15 +441,15 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "flate2" -version = "1.0.30" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", "miniz_oxide", @@ -500,7 +514,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -565,9 +579,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" @@ -587,7 +601,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.2.6", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -606,6 +620,12 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "hashbrown" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -653,9 +673,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -665,9 +685,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.29" +version = "0.14.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" +checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" dependencies = [ "bytes", "futures-channel", @@ -703,9 +723,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -795,9 +815,9 @@ checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" [[package]] name = "icu_properties" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f8ac670d7422d7f76b32e17a5db556510825b29ec9154f235977c9caba61036" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ "displaydoc", "icu_collections", @@ -839,7 +859,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -850,16 +870,25 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4716a3a0933a1d01c2f72450e89596eb51dd34ef3c211ccd875acdf1f8fe47ed" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "icu_normalizer", - "icu_properties", + "idna_adapter", "smallvec", "utf8_iter", ] +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -873,20 +902,20 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.1", "serde", ] [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "itertools" @@ -905,18 +934,18 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "levenshtein" @@ -926,9 +955,9 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.162" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" [[package]] name = "linux-raw-sys" @@ -954,9 +983,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" @@ -972,9 +1001,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", @@ -997,11 +1026,11 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.7.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] @@ -1043,18 +1072,18 @@ dependencies = [ [[package]] name = "object" -version = "0.36.0" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "overload" @@ -1082,7 +1111,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -1093,9 +1122,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -1103,6 +1132,35 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "poise" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1819d5a45e3590ef33754abce46432570c54a120798bdbf893112b4211fa09a6" +dependencies = [ + "async-trait", + "derivative", + "futures-util", + "parking_lot", + "poise_macros", + "regex", + "serenity", + "tokio", + "tracing", +] + +[[package]] +name = "poise_macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fa2c123c961e78315cd3deac7663177f12be4460f5440dbf62a7ed37b1effea" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1111,15 +1169,18 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "proc-macro2" -version = "1.0.85" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] @@ -1130,16 +1191,16 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "memchr", "unicase", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -1176,11 +1237,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.2" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", ] [[package]] @@ -1279,11 +1340,11 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -1311,7 +1372,7 @@ dependencies = [ "log", "ring", "rustls-pki-types", - "rustls-webpki 0.102.4", + "rustls-webpki 0.102.8", "subtle", "zeroize", ] @@ -1327,9 +1388,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" [[package]] name = "rustls-webpki" @@ -1343,9 +1404,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.4" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", @@ -1404,40 +1465,41 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.203" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] [[package]] name = "serde_cow" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e84ce5596a72f0c4c60759a10ff8c22d5eaf227b0dc2789c8746193309058b" +checksum = "1e7bbbec7196bfde255ab54b65e34087c0849629280028238e67ee25d6a4b7da" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -1473,7 +1535,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.2.6", + "indexmap 2.6.0", "serde", "serde_derive", "serde_json", @@ -1490,19 +1552,19 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] name = "serenity" -version = "0.12.2" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "880a04106592d0a8f5bdacb1d935889bfbccb4a14f7074984d9cd857235d34ac" +checksum = "3d72ec4323681bf9a3cabe40fd080abc2435859b502a1b5aa9bf693f125bfa76" dependencies = [ "arrayvec", "async-trait", "base64 0.22.1", - "bitflags 2.5.0", + "bitflags 2.6.0", "bytes", "chrono", "command_attr", @@ -1550,11 +1612,17 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -1631,9 +1699,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -1648,9 +1716,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.66" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -1671,7 +1739,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -1703,34 +1771,35 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tempfile" -version = "3.10.1" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", + "once_cell", "rustix", "windows-sys 0.52.0", ] [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -1786,9 +1855,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.41.0" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", "bytes", @@ -1809,7 +1878,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -1846,14 +1915,14 @@ dependencies = [ "tokio", "tokio-rustls 0.25.0", "tungstenite", - "webpki-roots 0.26.2", + "webpki-roots 0.26.6", ] [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -1885,11 +1954,11 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.20" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", @@ -1898,9 +1967,9 @@ dependencies = [ [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -1922,7 +1991,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -1962,9 +2031,9 @@ dependencies = [ [[package]] name = "triomphe" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b2cb4fbb9995eeb36ac86fadf24031ccd58f99d6b4b2d7b911db70bddb80d90" +checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" [[package]] name = "try-lock" @@ -2007,9 +2076,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "typesize" -version = "0.1.7" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb704842c709bc76f63e99e704cb208beeccca2abbabd0d9aec02e48ca1cee0f" +checksum = "549e54551d85ba6718a95333d9bc4367f69793d7aba638de30f8d25a1f554a1d" dependencies = [ "chrono", "dashmap 5.5.3", @@ -2025,29 +2094,26 @@ dependencies = [ [[package]] name = "typesize-derive" -version = "0.1.7" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "905e88c2a4cc27686bd57e495121d451f027e441388a67f773be729ad4be1ea8" +checksum = "fd9fc0ad9e03a2b0c2e2a0eafaecccef2121829e1ab6ce9c9d790e6c6766bd1c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] name = "unicase" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] +checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "untrusted" @@ -2057,9 +2123,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.1" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c25da092f0a868cdf09e8674cd3b7ef3a7d92a24253e663a2fb85e2496de56" +checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" dependencies = [ "form_urlencoded", "idna", @@ -2098,6 +2164,7 @@ dependencies = [ "bpaf", "dashmap 6.1.0", "itertools", + "poise", "regex", "serde", "serde_with", @@ -2117,9 +2184,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" @@ -2148,34 +2215,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", @@ -2185,9 +2253,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2195,28 +2263,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "wasm-streams" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ "futures-util", "js-sys", @@ -2227,9 +2295,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", @@ -2243,9 +2311,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "0.26.2" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c452ad30530b54a4d8e71952716a212b08efd0f3562baa66c29a618b07da7c3" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" dependencies = [ "rustls-pki-types", ] @@ -2268,9 +2336,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys 0.52.0", ] @@ -2287,7 +2355,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -2305,7 +2373,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -2325,18 +2393,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -2347,9 +2415,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -2359,9 +2427,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -2371,15 +2439,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -2389,9 +2457,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -2401,9 +2469,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -2413,9 +2481,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -2425,9 +2493,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" @@ -2480,10 +2548,31 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "zerofrom" version = "0.1.4" @@ -2501,7 +2590,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", "synstructure", ] @@ -2513,9 +2602,9 @@ checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] name = "zerovec" -version = "0.10.2" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2cc8827d6c0994478a15c53f374f46fbd41bea663d809b14744bc42e6b109c" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ "yoke", "zerofrom", @@ -2524,11 +2613,11 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97cf56601ee5052b4417d90c8755c6683473c926039908196cf35d99f893ebe7" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] diff --git a/Cargo.toml b/Cargo.toml index e5259ba..65d7368 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,12 +6,13 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +serenity = "0.12.4" +poise = "0.6" regex = "1.0" similar = "2.6.0" dashmap = "6.1.0" itertools = "0.13" -serenity = { version = "0.12.2" } -tracing = "0.1.40" +tracing = "0.1" tracing-subscriber = "0.3" serde_with = "3" toml = "0.8" diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 76fcadb..a718dc2 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.82" +channel = "1.83" diff --git a/src/features/logging.rs b/src/features/logging.rs index 7379ee1..4d61b1f 100644 --- a/src/features/logging.rs +++ b/src/features/logging.rs @@ -2,8 +2,8 @@ use itertools::enumerate; use serenity::{ all::{ ChannelId, Color, Context, CreateEmbed, EmbedMessageBuilding, EventHandler, FormattedTimestamp, - FormattedTimestampStyle, GuildId, Mentionable, Message, MessageBuilder, MessageId, MessageUpdateEvent, - Timestamp, + FormattedTimestampStyle, GuildId, Mentionable, Message, MessageBuilder, MessageId, MessageReferenceKind, + MessageUpdateEvent, Timestamp, }, async_trait, }; @@ -19,17 +19,23 @@ pub struct Handler; impl Handler { pub fn build_embed(&self, message: &Message, new_content: String, mut embed: CreateEmbed) -> CreateEmbed { - if let Some(message_reference) = &message.message_reference { - let id = message_reference.message_id.unwrap_or(MessageId::default()); - - let mut builder = MessageBuilder::new(); - builder - .push_bold_safe("元メッセージ: ") - .push_safe(id.link(message_reference.channel_id, message_reference.guild_id)) - .push_safe(" ") - .push_mono_line_safe(id.to_string()); - - embed = embed.field("__**転送**__", builder.build(), false); + if let Some(m_ref) = &message.message_reference { + let id = m_ref.message_id.unwrap_or(MessageId::default()); + let (name, content) = match m_ref.kind { + MessageReferenceKind::Default => ("__**返信**__", "返信先: "), + MessageReferenceKind::Forward => ("__**転送**__", "転送元: "), + _ => ("__**不明**__", "不明な対象メッセージ: "), + }; + embed = embed.field( + name, + MessageBuilder::new() + .push_bold_safe(content) + .push_safe(id.link(m_ref.channel_id, m_ref.guild_id)) + .push_safe(" ") + .push_mono_line_safe(id.to_string()) + .build(), + false, + ) } if let Some(poll) = &message.poll { @@ -42,13 +48,13 @@ impl Handler { let results = &poll.results; for (i, answer) in enumerate(&poll.answers) { - builder.push_safe(&format!( + builder.push_safe(format!( "- {}", answer.poll_media.text.clone().unwrap_or("<不明な回答>".to_string()) )); match results { - Some(results) => builder.push_line_safe(&format!(": {}票", results.answer_counts[i].count)), + Some(results) => builder.push_line_safe(format!(": {}票", results.answer_counts[i].count)), None => builder.push_safe("\n"), }; } From 2bb722751b1fa49dcf543b144cd1252caabd5f95 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Fri, 13 Dec 2024 03:14:47 +0900 Subject: [PATCH 013/104] Fix #4 --- src/features/logging.rs | 246 +++++++++++++++++++++------------------- 1 file changed, 132 insertions(+), 114 deletions(-) diff --git a/src/features/logging.rs b/src/features/logging.rs index 4d61b1f..511d010 100644 --- a/src/features/logging.rs +++ b/src/features/logging.rs @@ -1,7 +1,9 @@ -use itertools::enumerate; +use std::ops::Not; + +use itertools::{enumerate, Itertools}; use serenity::{ all::{ - ChannelId, Color, Context, CreateEmbed, EmbedMessageBuilding, EventHandler, FormattedTimestamp, + ChannelId, Context, CreateEmbed, EmbedMessageBuilding, EventHandler, FormattedTimestamp, FormattedTimestampStyle, GuildId, Mentionable, Message, MessageBuilder, MessageId, MessageReferenceKind, MessageUpdateEvent, Timestamp, }, @@ -18,77 +20,138 @@ use crate::{ pub struct Handler; impl Handler { - pub fn build_embed(&self, message: &Message, new_content: String, mut embed: CreateEmbed) -> CreateEmbed { - if let Some(m_ref) = &message.message_reference { - let id = m_ref.message_id.unwrap_or(MessageId::default()); - let (name, content) = match m_ref.kind { - MessageReferenceKind::Default => ("__**返信**__", "返信先: "), - MessageReferenceKind::Forward => ("__**転送**__", "転送元: "), - _ => ("__**不明**__", "不明な対象メッセージ: "), - }; - embed = embed.field( - name, - MessageBuilder::new() - .push_bold_safe(content) - .push_safe(id.link(m_ref.channel_id, m_ref.guild_id)) - .push_safe(" ") - .push_mono_line_safe(id.to_string()) - .build(), - false, - ) - } + fn build_reply_field(embed: CreateEmbed, message: &Message) -> CreateEmbed { + let Some(m_ref) = &message.message_reference else { + return embed; + }; + let id = m_ref.message_id.unwrap_or(MessageId::default()); + let (name, content) = match m_ref.kind { + MessageReferenceKind::Default => ("__**返信**__", "返信先: "), + MessageReferenceKind::Forward => ("__**転送**__", "転送元: "), + _ => ("__**不明**__", "不明な対象メッセージ: "), + }; + embed.field( + name, + MessageBuilder::new() + .push_bold_safe(content) + .push_safe(id.link(m_ref.channel_id, m_ref.guild_id)) + .push_safe(" ") + .push_mono_line_safe(id.to_string()) + .build(), + false, + ) + } - if let Some(poll) = &message.poll { - let mut builder = MessageBuilder::new(); - builder - .push_bold_safe("タイトル: ") - .push_line_safe(poll.question.text.clone().unwrap_or("<不明なタイトル>".to_string())) - .push_bold_line_safe("回答:"); - - let results = &poll.results; - - for (i, answer) in enumerate(&poll.answers) { - builder.push_safe(format!( - "- {}", - answer.poll_media.text.clone().unwrap_or("<不明な回答>".to_string()) - )); - - match results { - Some(results) => builder.push_line_safe(format!(": {}票", results.answer_counts[i].count)), - None => builder.push_safe("\n"), - }; - } + fn build_poll_field(embed: CreateEmbed, message: &Message) -> CreateEmbed { + let Some(poll) = &message.poll else { + return embed; + }; - if let Some(expiry) = poll.expiry { - let formatted = FormattedTimestamp::new(expiry, Some(FormattedTimestampStyle::LongDateTime)); - builder - .push_bold_safe("有効期限:") - .push_line_safe(formatted.to_string()); + let mut builder = MessageBuilder::new(); + builder + .push_bold_safe("タイトル: ") + .push_line_safe(poll.question.text.clone().unwrap_or("<不明なタイトル>".to_string())) + .push_bold_line_safe("回答:"); + + for (i, answer) in enumerate(&poll.answers) { + let answer_text = answer.poll_media.text.clone().unwrap_or("<不明な回答>".to_string()); + builder.push_safe(format!("- {}", answer_text)); + if let Some(results) = &poll.results { + builder.push_line_safe(format!(": {}票", results.answer_counts[i].count)); + } else { + builder.push_safe("\n"); } + } - embed = embed.field("__**投票**__", builder.build(), false); + if let Some(expiry) = poll.expiry { + builder.push_bold_safe("有効期限: ").push_line_safe( + FormattedTimestamp::new(expiry, Some(FormattedTimestampStyle::LongDateTime)).to_string(), + ); } - if !message.content.is_empty() { - let mut changed = MessageBuilder::new(); - changed.push_codeblock_safe(create_diff_lines_text(&message.content, &new_content), Some("diff")); + embed.field("__**投票**__", builder.build(), false) + } - embed = embed.field("__**テキスト差分**__", changed.build(), false); + fn build_diff_field(mut embed: CreateEmbed, old_content: &str, new_content: &str) -> CreateEmbed { + if old_content.is_empty() { + return embed; } - if !message.attachments.is_empty() { - let mut changed = MessageBuilder::new(); - for attachment in &message.attachments { - changed - .push_safe("- ") - .push_named_link_safe(&attachment.filename, &attachment.url) - .push_safe("\n"); + let diff = create_diff_lines_text(old_content, new_content); + let chunks = diff.lines().peekable().batching(|lines| { + let mut str = String::new(); + while let Some(line) = lines.next_if(|&l| str.len() + l.len() <= 1000) { + str.push_str(line); + str.push('\n'); } - embed = embed.field("__**添付ファイル**__", changed.build(), false); + str.is_empty().not().then_some(str) + }); + + for (i, chunk) in enumerate(chunks) { + let changed = MessageBuilder::new().push_codeblock_safe(chunk, Some("diff")).build(); + embed = embed.field(if i == 0 { "__**テキスト差分**__" } else { "" }, changed, false) } embed } + fn build_attachments_field(embed: CreateEmbed, message: &Message) -> CreateEmbed { + if message.attachments.is_empty() { + return embed; + } + let mut builder = MessageBuilder::new(); + for attachment in &message.attachments { + builder + .push_safe("- ") + .push_named_link_safe(&attachment.filename, &attachment.url) + .push_safe("\n"); + } + embed.field("__**添付ファイル**__", builder.build(), false) + } + + fn build_embed(&self, message: &Message, new_content: String, mut embed: CreateEmbed) -> CreateEmbed { + embed = Self::build_reply_field(embed, message); + embed = Self::build_poll_field(embed, message); + embed = Self::build_diff_field(embed, &message.content, &new_content); + Self::build_attachments_field(embed, message) + } + + async fn create_and_send_log( + &self, + ctx: &Context, + message: &Message, + title: &str, + color: i32, + new_content: Option, + ) { + let timestamp = FormattedTimestamp::new(Timestamp::now(), Some(FormattedTimestampStyle::LongDateTime)); + let description = MessageBuilder::new() + .push_bold_safe("メンバー: ") + .mention(&message.author.mention()) + .push_safe(" ") + .push_mono_line_safe(message.author.id.to_string()) + .push_bold_safe("メッセージ: ") + .push_safe(message.id.link(message.channel_id, message.guild_id)) + .push_safe(" ") + .push_mono_line_safe(message.id.to_string()) + .push_bold_safe("時刻: ") + .push_line_safe(timestamp.to_string()) + .build(); + + let mut embed = CreateEmbed::new() + .title(title) + .description(description) + .color(color) + .thumbnail( + message + .author + .avatar_url() + .unwrap_or("https://cdn.discordapp.com/embed/avatars/0.png".to_string()), + ); + + embed = self.build_embed(message, new_content.unwrap_or("".to_string()), embed); + self.send_log(ctx, embed).await; + } + async fn update_cache(&self, ctx: &Context, message: &Message) { let mut data = ctx.data.write().await; let cache = data.get_mut::().unwrap(); @@ -119,34 +182,14 @@ impl EventHandler for Handler { } self.update_cache(&ctx, &new_message).await; - - let timestamp = FormattedTimestamp::new(Timestamp::now(), Some(FormattedTimestampStyle::LongDateTime)); - let description = MessageBuilder::new() - .push_bold_safe("メンバー: ") - .mention(&message.author.mention()) - .push_safe(" ") - .push_mono_line_safe(message.author.id.to_string()) - .push_bold_safe("メッセージ: ") - .push_safe(message.id.link(message.channel_id, message.guild_id)) - .push_safe(" ") - .push_mono_line_safe(message.id.to_string()) - .push_bold_safe("時刻: ") - .push_line_safe(timestamp.to_string()) - .build(); - - let mut embed = CreateEmbed::new() - .title("メッセージ編集ログ") - .description(description) - .color(Color::new(0xff8800)) - .thumbnail( - message - .author - .avatar_url() - .unwrap_or("https://cdn.discordapp.com/embed/avatars/0.png".to_string()), - ); - - embed = self.build_embed(&message, new_message.content, embed); - self.send_log(&ctx, embed).await; + self.create_and_send_log( + &ctx, + &message, + "メッセージ編集ログ", + 0xff8800, + Some(new_message.content), + ) + .await; } async fn message_delete( @@ -160,32 +203,7 @@ impl EventHandler for Handler { return error!("Failed to get message: {:?}", deleted_message_id); }; - let timestamp = FormattedTimestamp::new(Timestamp::now(), Some(FormattedTimestampStyle::LongDateTime)); - let description = MessageBuilder::new() - .push_bold_safe("メンバー: ") - .mention(&message.author.mention()) - .push_safe(" ") - .push_mono_line_safe(message.author.id.to_string()) - .push_bold_safe("チャンネル: ") - .mention(&message.channel_id.mention()) - .push_safe(" ") - .push_mono_line_safe(message.id.to_string()) - .push_bold_safe("時刻: ") - .push_line_safe(timestamp.to_string()) - .build(); - - let mut embed = CreateEmbed::new() - .title("メッセージ削除ログ") - .description(description) - .color(Color::new(0xf00000)) - .thumbnail( - message - .author - .avatar_url() - .unwrap_or("https://cdn.discordapp.com/embed/avatars/0.png".to_string()), - ); - - embed = self.build_embed(&message, "".to_string(), embed); - self.send_log(&ctx, embed).await; + self.create_and_send_log(&ctx, &message, "メッセージ削除ログ", 0xf00000, None) + .await; } } From d03bf34cc817606ff40c5255cc0b4e51bcbd2372 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Fri, 13 Dec 2024 04:50:41 +0900 Subject: [PATCH 014/104] =?UTF-8?q?=E3=82=B9=E3=83=AC=E3=83=83=E3=83=89?= =?UTF-8?q?=E3=81=AE=E9=96=8B=E5=A7=8B=E6=99=82=E3=81=AB=E4=BA=8C=E5=BA=A6?= =?UTF-8?q?=E5=88=9D=E6=9C=9F=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8?= =?UTF-8?q?=E3=81=8C=E9=80=81=E3=82=89=E3=82=8C=E3=82=8B=E3=83=90=E3=82=B0?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/thread_channel_startup.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/features/thread_channel_startup.rs b/src/features/thread_channel_startup.rs index 0fd74dd..076418f 100644 --- a/src/features/thread_channel_startup.rs +++ b/src/features/thread_channel_startup.rs @@ -15,7 +15,8 @@ pub struct Handler; impl EventHandler for Handler { async fn thread_create(&self, ctx: Context, thread: GuildChannel) { // スレッドの作成時とスレッドの初期メッセージ送信後にイベントが発火するので、スレッド作成時は無視する - if thread.last_message_id.is_none() { + // スレッドとフォーラム共通で初期メッセージが送信されるまで、メンバーが存在しないためこれを利用して判定する + if thread.member.is_some() { return; } From 889297b5de6d9e48a6ce6786335e534beb87b2b7 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 16 Dec 2024 23:16:29 +0900 Subject: [PATCH 015/104] =?UTF-8?q?=E5=88=9D=E6=9C=9F=E3=83=A1=E3=83=83?= =?UTF-8?q?=E3=82=BB=E3=83=BC=E3=82=B8=E3=81=AE=E6=8A=95=E7=A8=BF=E3=82=92?= =?UTF-8?q?=E5=BE=85=E3=81=A4=E3=82=88=E3=81=86=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 初期メッセージ投稿後のイベントはBotがメッセージを送信したあとに発火するという仕様(バグ?)があったため、最初のイベントで初期メッセージの投稿を待つように --- src/features/thread_channel_startup.rs | 18 ++++++++++++++---- src/utils.rs | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/features/thread_channel_startup.rs b/src/features/thread_channel_startup.rs index 076418f..680cbe7 100644 --- a/src/features/thread_channel_startup.rs +++ b/src/features/thread_channel_startup.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use serenity::{ all::{Context, EventHandler, GuildChannel}, async_trait, @@ -14,22 +16,30 @@ pub struct Handler; #[async_trait] impl EventHandler for Handler { async fn thread_create(&self, ctx: Context, thread: GuildChannel) { - // スレッドの作成時とスレッドの初期メッセージ送信後にイベントが発火するので、スレッド作成時は無視する - // スレッドとフォーラム共通で初期メッセージが送信されるまで、メンバーが存在しないためこれを利用して判定する - if thread.member.is_some() { + // Botがメッセージを送信すると二度イベントが発火するので、初期メッセージ送信後のイベントは無視する + if thread.last_message_id.is_some() { return; } + // 初期メッセージが送信されるか、1秒経つまで待機 + let _ = thread + .await_reply(&ctx.shard) + .author_id(thread.owner_id.unwrap()) + .timeout(Duration::from_secs(1)) + .next() + .await; + let config = &get_config(&ctx).await.thread_channel_startup; let Some(parent_id) = thread.parent_id else { return error!("Failed to get parent id: {:?}", thread); }; + for thread_config in &config.threads { if parent_id != thread_config.channel_id { continue; } - let log = create_message(thread_config.startup_message.clone()); + let log = create_message(&thread_config.startup_message); let _ = send_message(&ctx, &thread.id, log).await; } } diff --git a/src/utils.rs b/src/utils.rs index a9488b9..7979cf2 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -12,7 +12,7 @@ pub fn create_safe_message() -> CreateMessage { CreateMessage::new().allowed_mentions(CreateAllowedMentions::new().all_users(false)) } -pub fn create_message(content: String) -> CreateMessage { +pub fn create_message(content: impl Into) -> CreateMessage { create_safe_message().content(content) } From 30d49bd8f085fa1c5b559b84f1a4813b90d4215c Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Fri, 20 Dec 2024 00:09:10 +0900 Subject: [PATCH 016/104] =?UTF-8?q?=E3=82=B9=E3=83=AC=E3=83=83=E3=83=89?= =?UTF-8?q?=E4=B8=BB=E3=81=8C=E3=83=94=E3=83=B3=E7=95=99=E3=82=81=E3=81=A7?= =?UTF-8?q?=E3=81=8D=E3=82=8B=E6=A9=9F=E8=83=BD=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.sample.toml | 1 + src/config.rs | 3 +- src/features/logging.rs | 4 ++ src/features/mod.rs | 33 ++++++++++++ src/features/thread_channel_startup.rs | 6 +-- src/features/thread_pin.rs | 69 ++++++++++++++++++++++++++ src/main.rs | 61 ++++++++++++++++++++++- 7 files changed, 171 insertions(+), 6 deletions(-) create mode 100644 src/features/thread_pin.rs diff --git a/config.sample.toml b/config.sample.toml index 0619c84..ca4ae6e 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -1,6 +1,7 @@ [bot] token = "TOKEN" +application_id = "000000000000000000" [auth] diff --git a/src/config.rs b/src/config.rs index e564c20..ef15144 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,7 +4,7 @@ use regex::Regex; use serde::Deserialize; use serde_with::{serde_as, DisplayFromStr}; use serenity::{ - all::{ChannelId, Context, GuildId, ReactionType, RoleId}, + all::{ChannelId, Context, GuildId, ReactionType, RoleId, UserId}, prelude::TypeMapKey, }; @@ -30,6 +30,7 @@ impl TypeMapKey for Config { #[derive(Debug, Deserialize)] pub struct BotConfig { pub token: String, + pub application_id: UserId, } #[serde_as] diff --git a/src/features/logging.rs b/src/features/logging.rs index 511d010..54869ae 100644 --- a/src/features/logging.rs +++ b/src/features/logging.rs @@ -123,6 +123,10 @@ impl Handler { color: i32, new_content: Option, ) { + if message.author.bot { + return; + } + let timestamp = FormattedTimestamp::new(Timestamp::now(), Some(FormattedTimestampStyle::LongDateTime)); let description = MessageBuilder::new() .push_bold_safe("メンバー: ") diff --git a/src/features/mod.rs b/src/features/mod.rs index 3018c3f..2b3a0c0 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -2,6 +2,7 @@ mod auth; mod logging; mod message_cache; mod thread_channel_startup; +mod thread_pin; pub use auth::Handler as AuthHandler; pub use logging::Handler as LoggingHandler; @@ -9,3 +10,35 @@ pub use message_cache::Handler as MessageCacheHandler; pub use thread_channel_startup::Handler as ThreadChannelStartupHandler; pub use message_cache::{MessageCache, MessageCacheType}; + +pub type PError = Box; +pub type PContext<'a> = poise::Context<'a, (), PError>; +pub type PCommand = poise::Command<(), PError>; + +pub fn commands() -> Vec { + build_commands(vec![thread_pin::pin]) +} + +fn alias_command(base: fn() -> PCommand, name: String) -> PCommand { + let mut command = base(); + command.name = name; + command.aliases.clear(); + command.context_menu_action = None; + command.context_menu_name = None; + command +} + +fn build_commands(commands: Vec PCommand>) -> Vec { + commands + .into_iter() + .flat_map(|cmd| { + let base = cmd(); + let aliases = base + .aliases + .clone() + .into_iter() + .map(move |alias| alias_command(cmd, alias)); + std::iter::once(base).chain(aliases).collect::>() + }) + .collect() +} diff --git a/src/features/thread_channel_startup.rs b/src/features/thread_channel_startup.rs index 680cbe7..a94fdd9 100644 --- a/src/features/thread_channel_startup.rs +++ b/src/features/thread_channel_startup.rs @@ -21,12 +21,12 @@ impl EventHandler for Handler { return; } - // 初期メッセージが送信されるか、1秒経つまで待機 + // 初期メッセージが送信されるか、5秒経つまで待機 let _ = thread .await_reply(&ctx.shard) + .channel_id(thread.id) .author_id(thread.owner_id.unwrap()) - .timeout(Duration::from_secs(1)) - .next() + .timeout(Duration::from_secs(5)) .await; let config = &get_config(&ctx).await.thread_channel_startup; diff --git a/src/features/thread_pin.rs b/src/features/thread_pin.rs new file mode 100644 index 0000000..872eaee --- /dev/null +++ b/src/features/thread_pin.rs @@ -0,0 +1,69 @@ +use std::time::Duration; + +use poise::{say_reply, FrameworkError}; +use serenity::{ + all::{Message, MessageType}, + futures::StreamExt, +}; + +use super::PContext; +use crate::{config::get_config, on_error, PError}; + +async fn pin_on_error(error: FrameworkError<'_, (), PError>) { + match error { + FrameworkError::Command { ctx, error, .. } => { + let _ = say_reply(ctx, format!("ピン留めに失敗しました: {}", error)).await; + } + error => { + let _ = on_error(error).await; + } + } +} + +/// スレッド主限定でメッセージをピン留めします。 +#[poise::command( + context_menu_command = "ピン留め", + slash_command, + ephemeral, + aliases("ピン留め"), + on_error = "pin_on_error", + required_bot_permissions = "MANAGE_MESSAGES" +)] +pub async fn pin( + ctx: PContext<'_>, + #[description = "ピン留めするメッセージ (リンクかID)"] msg: Message, +) -> Result<(), PError> { + let channel = ctx.guild_channel().await.unwrap(); + let Some(owner) = channel.owner_id else { + say_reply(ctx, "スレッド以外のチャンネルでは使用出来ません。").await?; + return Ok(()); + }; + + if ctx.author().id != owner { + say_reply(ctx, "スレッド主のみがピン留めできます。").await?; + return Ok(()); + } + + let bot_id = get_config(ctx.serenity_context()).await.bot.application_id; + let mut stream = channel + .await_reply(&ctx.serenity_context().shard) + .timeout(Duration::from_secs(5)) + .channel_id(channel.id) + .author_id(bot_id) + .filter(|r| r.kind == MessageType::PinsAdd) + .stream(); + + if msg.pinned { + msg.unpin(ctx).await?; + say_reply(ctx, "ピン留めを解除しました。").await?; + } else { + msg.pin(ctx).await?; + say_reply(ctx, "ピン留めしました。").await?; + } + + if let Some(msg) = stream.next().await { + let _ = msg.delete(ctx.http()).await; + } + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 79d0cb8..540fe8f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,8 +6,14 @@ use std::{fs::read_to_string, sync::Arc}; use bpaf::Bpaf; use config::Config; -use features::{MessageCache, MessageCacheType}; -use serenity::{all::Ready, async_trait, cache::Settings as CacheSettings, prelude::*}; +use features::{commands, MessageCache, MessageCacheType, PError}; +use poise::{say_reply, Framework, FrameworkError, FrameworkOptions}; +use serenity::{ + all::{MessageParseError, Ready}, + async_trait, + cache::Settings as CacheSettings, + prelude::*, +}; use tracing::{error, info}; struct MainHandler; @@ -26,6 +32,42 @@ struct Options { check_config: bool, } +pub async fn on_error(error: FrameworkError<'_, (), PError>) { + match error { + FrameworkError::ArgumentParse { ctx, input, error, .. } => { + let Some(input) = input else { + return error!("Error parsing input: {:?}", error); + }; + + let error = match error.downcast_ref::() { + Some(MessageParseError::Malformed) => { + "メッセージとして解析できませんでした。\nメッセージID、メッセージURL形式で入力してください。" + } + Some(MessageParseError::Http(_)) => "メッセージを取得できませんでした。", + _ => &error.to_string(), + }; + + let _ = say_reply(ctx, format!("入力 `{}` の解析に失敗しました。\n{}", input, error)).await; + } + FrameworkError::MissingBotPermissions { + missing_permissions, + ctx, + .. + } => { + let msg = format!( + "ボットに権限が無いためコマンドを実行できません: {}", + missing_permissions, + ); + let _ = say_reply(ctx, msg).await; + } + error => { + if let Err(e) = poise::builtins::on_error(error).await { + println!("Error while handling error: {}", e) + } + } + } +} + #[tokio::main] async fn main() { tracing_subscriber::fmt::init(); @@ -45,10 +87,25 @@ async fn main() { return; } + let framework = Framework::builder() + .options(FrameworkOptions { + commands: commands(), + on_error: |error| Box::pin(on_error(error)), + ..Default::default() + }) + .setup(|ctx, _ready, framework| { + Box::pin(async move { + poise::builtins::register_globally(ctx, &framework.options().commands).await?; + Ok(()) + }) + }) + .build(); + let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::GUILDS | GatewayIntents::MESSAGE_CONTENT; let mut settings = CacheSettings::default(); settings.max_messages = 1_000_000; let mut client = Client::builder(&config.bot.token, intents) + .framework(framework) .event_handler(MainHandler) .event_handler(features::AuthHandler) .event_handler(features::LoggingHandler) From 1f37a2a226c61d413bd78d7ef497fde91d1ff2ba Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Fri, 20 Dec 2024 00:25:11 +0900 Subject: [PATCH 017/104] =?UTF-8?q?=E3=83=94=E3=83=B3=E7=95=99=E3=82=81?= =?UTF-8?q?=E3=82=92DM=E3=81=8B=E3=82=89=E5=AE=9F=E8=A1=8C=E3=81=A7?= =?UTF-8?q?=E3=81=8D=E3=81=AA=E3=81=84=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/thread_pin.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/features/thread_pin.rs b/src/features/thread_pin.rs index 872eaee..c591665 100644 --- a/src/features/thread_pin.rs +++ b/src/features/thread_pin.rs @@ -25,6 +25,7 @@ async fn pin_on_error(error: FrameworkError<'_, (), PError>) { context_menu_command = "ピン留め", slash_command, ephemeral, + guild_only, aliases("ピン留め"), on_error = "pin_on_error", required_bot_permissions = "MANAGE_MESSAGES" From 2b55a504022e522295c83fd0f8f0b56e44568c6f Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Fri, 20 Dec 2024 01:05:09 +0900 Subject: [PATCH 018/104] =?UTF-8?q?=E5=90=88=E8=A8=80=E8=91=89=E3=82=92?= =?UTF-8?q?=E3=82=B9=E3=83=A9=E3=83=83=E3=82=B7=E3=83=A5=E3=82=B3=E3=83=9E?= =?UTF-8?q?=E3=83=B3=E3=83=89=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.sample.toml | 4 -- src/config.rs | 5 +- src/features/auth.rs | 127 +++++++++++++++---------------------- src/features/mod.rs | 3 +- src/features/thread_pin.rs | 4 +- src/main.rs | 5 +- src/utils.rs | 16 +---- 7 files changed, 62 insertions(+), 102 deletions(-) diff --git a/config.sample.toml b/config.sample.toml index ca4ae6e..f9156e4 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -5,14 +5,10 @@ application_id = "000000000000000000" [auth] -# 認証に使用するチャンネルID -channel_id = "000000000000000000" # 認証の結果を通知するチャンネルID log_channel_id = "000000000000000000" # 認証後に付与するロールID role_id = "000000000000000000" -# 認証に成功したメッセージに付与するリアクション -authenticated_reaction = "<:verified:000000000000000000>" # マッチしたら認証成功とする正規表現 trigger_regex = "[おオオ][のノノ][れレレ][GgGg][RrRr][EeEe][GgGg]" diff --git a/src/config.rs b/src/config.rs index ef15144..56e3e01 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,7 +4,7 @@ use regex::Regex; use serde::Deserialize; use serde_with::{serde_as, DisplayFromStr}; use serenity::{ - all::{ChannelId, Context, GuildId, ReactionType, RoleId, UserId}, + all::{ChannelId, Context, GuildId, RoleId, UserId}, prelude::TypeMapKey, }; @@ -36,12 +36,9 @@ pub struct BotConfig { #[serde_as] #[derive(Debug, Deserialize)] pub struct AuthConfig { - pub channel_id: ChannelId, pub log_channel_id: ChannelId, pub role_id: RoleId, #[serde_as(as = "DisplayFromStr")] - pub authenticated_reaction: ReactionType, - #[serde_as(as = "DisplayFromStr")] pub trigger_regex: Regex, } diff --git a/src/features/auth.rs b/src/features/auth.rs index b84a814..a300085 100644 --- a/src/features/auth.rs +++ b/src/features/auth.rs @@ -1,91 +1,68 @@ -use serenity::all::{ChannelId, GuildId, MessageId, MessageUpdateEvent, User}; -use serenity::async_trait; -use serenity::model::channel::Message; -use serenity::prelude::*; +use poise::{say_reply, FrameworkError}; +use serenity::all::Mentionable; use tracing::error; use crate::config::get_config; -use crate::utils::{create_message, get_message, react_from_id, send_message}; +use crate::features::PError; +use crate::on_error; +use crate::utils::{create_message, send_message}; -pub struct Handler; +use super::PContext; -impl Handler { - async fn handle_message( - &self, - ctx: &Context, - guild_id: GuildId, - channel_id: ChannelId, - message_id: MessageId, - author: User, - content: String, - ) { - let config = &get_config(ctx).await.auth; - - if !config.trigger_regex.is_match(&content) { - return; - } - - if channel_id != config.channel_id { - return; - } - - let member = match guild_id.member(&ctx.http, author.id).await { - Ok(member) => member, - Err(why) => return error!("Failed to get member: {:?}", why), - }; - - react_from_id(ctx, channel_id, message_id, &config.authenticated_reaction).await; - - if member.roles.contains(&config.role_id) { - error!("{} already has the role", member.user.name); - return; +async fn keyword_on_error(error: FrameworkError<'_, (), PError>) { + match error { + FrameworkError::Command { ctx, error, .. } => { + let _ = say_reply(ctx, "合言葉の確認中にエラーが発生しました。").await; + error!("Command error: {}", error); } - - if let Err(why) = member.add_role(&ctx.http, config.role_id).await { - let log = create_message(format!( - "{} にロールを追加できませんでした。\n```\n{}```", - member.mention(), - why - )); - let _ = send_message(ctx, &config.log_channel_id, log).await; - return error!("Failed to add role: {:?}", why); + error => { + let _ = on_error(error).await; } - - let log = create_message(format!("{} にロールを追加しました。", member.mention())); - let _ = send_message(ctx, &config.log_channel_id, log).await; } } -#[async_trait] -impl EventHandler for Handler { - async fn message(&self, ctx: Context, msg: Message) { - let Some(guild_id) = msg.guild_id else { - return error!("Failed to get guild id: {:?}", msg); - }; +/// 合言葉を入力してください。 +#[poise::command( + slash_command, + ephemeral, + guild_only, + aliases("合言葉"), + on_error = "keyword_on_error", + required_bot_permissions = "MANAGE_ROLES" +)] +pub async fn keyword(ctx: PContext<'_>, #[description = "合言葉"] keyword: String) -> Result<(), PError> { + let config = &get_config(ctx.serenity_context()).await.auth; - self.handle_message(&ctx, guild_id, msg.channel_id, msg.id, msg.author, msg.content) - .await; + if !config.trigger_regex.is_match(&keyword) { + say_reply(ctx, "合言葉が間違っています。").await?; + return Ok(()); } - async fn message_update(&self, ctx: Context, _: Option, _: Option, event: MessageUpdateEvent) { - let Some(guild_id) = event.guild_id else { - return error!("Failed to get guild id: {:?}", event); - }; - let Some(author) = event.author else { - return error!("Failed to get author: {:?}", event); - }; - if let Some(content) = event.content { - self.handle_message(&ctx, guild_id, event.channel_id, event.id, author, content) - .await; - return; - } + let member = ctx.author_member().await.unwrap(); - match get_message(&ctx, event.channel_id, event.id).await { - Ok(m) => { - self.handle_message(&ctx, guild_id, event.channel_id, event.id, author, m.content) - .await - } - Err(why) => error!("Failed to get message: {:?}", why), - } + if member.roles.contains(&config.role_id) { + error!("{} already has the role", member.user.name); + say_reply(ctx, "すでにロールを持っています。").await?; + return Ok(()); } + + if let Err(why) = member.add_role(&ctx.http(), config.role_id).await { + let log = create_message(format!( + "{} にロールを追加できませんでした。\n```\n{}```", + member.mention(), + why + )); + let _ = send_message(ctx.serenity_context(), &config.log_channel_id, log).await; + error!("Failed to add role: {:?}", why); + return Ok(()); + } + + let log = create_message(format!("{} にロールを追加しました。", member.mention())); + let _ = send_message(ctx.serenity_context(), &config.log_channel_id, log).await; + say_reply( + ctx, + "合言葉を確認しました。\nチャンネルが表示されない場合、アプリの再起動や再読み込み(Ctrl + R)をお試しください。", + ) + .await?; + Ok(()) } diff --git a/src/features/mod.rs b/src/features/mod.rs index 2b3a0c0..0a00b6c 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -4,7 +4,6 @@ mod message_cache; mod thread_channel_startup; mod thread_pin; -pub use auth::Handler as AuthHandler; pub use logging::Handler as LoggingHandler; pub use message_cache::Handler as MessageCacheHandler; pub use thread_channel_startup::Handler as ThreadChannelStartupHandler; @@ -16,7 +15,7 @@ pub type PContext<'a> = poise::Context<'a, (), PError>; pub type PCommand = poise::Command<(), PError>; pub fn commands() -> Vec { - build_commands(vec![thread_pin::pin]) + build_commands(vec![auth::keyword, thread_pin::pin]) } fn alias_command(base: fn() -> PCommand, name: String) -> PCommand { diff --git a/src/features/thread_pin.rs b/src/features/thread_pin.rs index c591665..9274b53 100644 --- a/src/features/thread_pin.rs +++ b/src/features/thread_pin.rs @@ -5,6 +5,7 @@ use serenity::{ all::{Message, MessageType}, futures::StreamExt, }; +use tracing::error; use super::PContext; use crate::{config::get_config, on_error, PError}; @@ -12,7 +13,8 @@ use crate::{config::get_config, on_error, PError}; async fn pin_on_error(error: FrameworkError<'_, (), PError>) { match error { FrameworkError::Command { ctx, error, .. } => { - let _ = say_reply(ctx, format!("ピン留めに失敗しました: {}", error)).await; + let _ = say_reply(ctx, "ピン留め中にエラーが発生しました。").await; + error!("Command error: {}", error); } error => { let _ = on_error(error).await; diff --git a/src/main.rs b/src/main.rs index 540fe8f..45ce6ee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,6 +34,10 @@ struct Options { pub async fn on_error(error: FrameworkError<'_, (), PError>) { match error { + FrameworkError::Command { error, ctx, .. } => { + let _ = say_reply(ctx, "コマンド実行中にエラーが発生しました。").await; + error!("Command error: {}", error); + } FrameworkError::ArgumentParse { ctx, input, error, .. } => { let Some(input) = input else { return error!("Error parsing input: {:?}", error); @@ -107,7 +111,6 @@ async fn main() { let mut client = Client::builder(&config.bot.token, intents) .framework(framework) .event_handler(MainHandler) - .event_handler(features::AuthHandler) .event_handler(features::LoggingHandler) .event_handler(features::ThreadChannelStartupHandler) .event_handler(features::MessageCacheHandler { diff --git a/src/utils.rs b/src/utils.rs index 7979cf2..2097ce2 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,6 @@ use itertools::Itertools; use serenity::{ - all::{CacheHttp, ChannelId, Context, CreateAllowedMentions, CreateMessage, Message, MessageId, ReactionType}, + all::{ChannelId, Context, CreateAllowedMentions, CreateMessage, Message, MessageId}, Result, }; use similar::{Algorithm, ChangeTag, TextDiff}; @@ -40,20 +40,6 @@ pub async fn get_cached_message(ctx: &Context, channel_id: ChannelId, message_id None } -pub async fn get_message(ctx: &Context, channel_id: ChannelId, message_id: MessageId) -> Result { - if let Some(cached) = get_cached_message(ctx, channel_id, message_id).await { - return Ok(cached); - } - - channel_id.message(&ctx.http, message_id).await -} - -pub async fn react_from_id(ctx: &Context, channel_id: ChannelId, message_id: MessageId, reaction_type: &ReactionType) { - if let Err(why) = ctx.http().create_reaction(channel_id, message_id, reaction_type).await { - error!("Failed to react to message: {:?}", why); - } -} - pub fn create_diff_lines_text(old: &str, new: &str) -> String { let diff = TextDiff::configure().algorithm(Algorithm::Myers).diff_lines(old, new); diff.iter_all_changes() From 125f61c66bcd2d0b601e75e281b3193eee1b3d0d Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 21 Dec 2024 06:34:30 +0900 Subject: [PATCH 019/104] =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=95=E3=82=A3?= =?UTF-8?q?=E3=82=B0=E3=81=AE=E5=86=8D=E8=AA=AD=E3=81=BF=E8=BE=BC=E3=81=BF?= =?UTF-8?q?=E3=82=92=E8=A1=8C=E3=81=86=E3=82=B3=E3=83=9E=E3=83=B3=E3=83=89?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/admin.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/features/admin.rs diff --git a/src/features/admin.rs b/src/features/admin.rs new file mode 100644 index 0000000..23eda30 --- /dev/null +++ b/src/features/admin.rs @@ -0,0 +1,27 @@ +use std::sync::Arc; + +use poise::say_reply; +use tokio::fs::read_to_string; + +use crate::{config::Config, features::PError}; + +use super::PContext; + +/// コンフィグを再読み込み +#[poise::command(slash_command, ephemeral, owners_only, dm_only)] +pub async fn reload_config(ctx: PContext<'_>) -> Result<(), PError> { + let Ok(config) = read_to_string("config.toml").await else { + return Err("Failed to read config.toml".into()); + }; + + let Ok(config) = toml::from_str::(&config) else { + return Err("Failed to parse config.toml".into()); + }; + + let mut data = ctx.serenity_context().data.write().await; + data.insert::(Arc::new(config)); + + say_reply(ctx, "Config reloaded").await?; + + Ok(()) +} From adccd2787a0d3351b8d23c7f642fd5180c20ee20 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 21 Dec 2024 06:39:02 +0900 Subject: [PATCH 020/104] =?UTF-8?q?=E5=90=88=E8=A8=80=E8=91=89=E3=81=AB?= =?UTF-8?q?=E3=83=80=E3=83=9F=E3=83=BC=E3=81=AE=E3=82=AA=E3=83=BC=E3=83=88?= =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=97=E3=83=AA=E3=83=BC=E3=83=88=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.sample.toml | 3 +++ src/config.rs | 4 +++- src/features/auth.rs | 22 ++++++++++++++++++---- src/features/mod.rs | 8 +++++--- src/features/thread_pin.rs | 6 +++--- src/main.rs | 12 +++++++----- 6 files changed, 39 insertions(+), 16 deletions(-) diff --git a/config.sample.toml b/config.sample.toml index f9156e4..f0b0a96 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -2,6 +2,7 @@ [bot] token = "TOKEN" application_id = "000000000000000000" +owners = ["000000000000000000"] [auth] @@ -11,6 +12,8 @@ log_channel_id = "000000000000000000" role_id = "000000000000000000" # マッチしたら認証成功とする正規表現 trigger_regex = "[おオオ][のノノ][れレレ][GgGg][RrRr][EeEe][GgGg]" +# オートコンプリートに表示されるダミーのキーワード +dummy_keywords = ["ダミー", "dummy"] [message_logging] diff --git a/src/config.rs b/src/config.rs index 56e3e01..b23163c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{collections::HashSet, sync::Arc}; use regex::Regex; use serde::Deserialize; @@ -30,6 +30,7 @@ impl TypeMapKey for Config { #[derive(Debug, Deserialize)] pub struct BotConfig { pub token: String, + pub owners: HashSet, pub application_id: UserId, } @@ -40,6 +41,7 @@ pub struct AuthConfig { pub role_id: RoleId, #[serde_as(as = "DisplayFromStr")] pub trigger_regex: Regex, + pub dummy_keywords: Vec, } #[derive(Debug, Deserialize)] diff --git a/src/features/auth.rs b/src/features/auth.rs index a300085..589943d 100644 --- a/src/features/auth.rs +++ b/src/features/auth.rs @@ -1,5 +1,6 @@ use poise::{say_reply, FrameworkError}; use serenity::all::Mentionable; +use serenity::futures::{self, Stream, StreamExt}; use tracing::error; use crate::config::get_config; @@ -7,13 +8,13 @@ use crate::features::PError; use crate::on_error; use crate::utils::{create_message, send_message}; -use super::PContext; +use super::{CommandData, PContext}; -async fn keyword_on_error(error: FrameworkError<'_, (), PError>) { +async fn keyword_on_error(error: FrameworkError<'_, CommandData, PError>) { match error { FrameworkError::Command { ctx, error, .. } => { let _ = say_reply(ctx, "合言葉の確認中にエラーが発生しました。").await; - error!("Command error: {}", error); + error!("Command error: {:?}", error); } error => { let _ = on_error(error).await; @@ -21,16 +22,29 @@ async fn keyword_on_error(error: FrameworkError<'_, (), PError>) { } } +async fn autocomplete_keyword<'a>(ctx: PContext<'_>, partial: &'a str) -> impl Stream + 'a { + let config = &get_config(ctx.serenity_context()).await.auth; + futures::stream::iter(config.dummy_keywords.clone()) + .filter(move |name| futures::future::ready(name.starts_with(partial))) + .map(|name| name.to_string()) +} + /// 合言葉を入力してください。 #[poise::command( slash_command, ephemeral, guild_only, aliases("合言葉"), + member_cooldown = 60, on_error = "keyword_on_error", required_bot_permissions = "MANAGE_ROLES" )] -pub async fn keyword(ctx: PContext<'_>, #[description = "合言葉"] keyword: String) -> Result<(), PError> { +pub async fn keyword( + ctx: PContext<'_>, + #[autocomplete = "autocomplete_keyword"] + #[description = "合言葉"] + keyword: String, +) -> Result<(), PError> { let config = &get_config(ctx.serenity_context()).await.auth; if !config.trigger_regex.is_match(&keyword) { diff --git a/src/features/mod.rs b/src/features/mod.rs index 0a00b6c..7d98963 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -1,3 +1,4 @@ +mod admin; mod auth; mod logging; mod message_cache; @@ -11,11 +12,12 @@ pub use thread_channel_startup::Handler as ThreadChannelStartupHandler; pub use message_cache::{MessageCache, MessageCacheType}; pub type PError = Box; -pub type PContext<'a> = poise::Context<'a, (), PError>; -pub type PCommand = poise::Command<(), PError>; +pub struct CommandData {} +pub type PContext<'a> = poise::Context<'a, CommandData, PError>; +pub type PCommand = poise::Command; pub fn commands() -> Vec { - build_commands(vec![auth::keyword, thread_pin::pin]) + build_commands(vec![auth::keyword, thread_pin::pin, admin::reload_config]) } fn alias_command(base: fn() -> PCommand, name: String) -> PCommand { diff --git a/src/features/thread_pin.rs b/src/features/thread_pin.rs index 9274b53..2fd5634 100644 --- a/src/features/thread_pin.rs +++ b/src/features/thread_pin.rs @@ -8,13 +8,13 @@ use serenity::{ use tracing::error; use super::PContext; -use crate::{config::get_config, on_error, PError}; +use crate::{config::get_config, features::CommandData, on_error, PError}; -async fn pin_on_error(error: FrameworkError<'_, (), PError>) { +async fn pin_on_error(error: FrameworkError<'_, CommandData, PError>) { match error { FrameworkError::Command { ctx, error, .. } => { let _ = say_reply(ctx, "ピン留め中にエラーが発生しました。").await; - error!("Command error: {}", error); + error!("Command error: {:?}", error); } error => { let _ = on_error(error).await; diff --git a/src/main.rs b/src/main.rs index 45ce6ee..d8f707a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use std::{fs::read_to_string, sync::Arc}; use bpaf::Bpaf; use config::Config; -use features::{commands, MessageCache, MessageCacheType, PError}; +use features::{commands, CommandData, MessageCache, MessageCacheType, PError}; use poise::{say_reply, Framework, FrameworkError, FrameworkOptions}; use serenity::{ all::{MessageParseError, Ready}, @@ -32,11 +32,11 @@ struct Options { check_config: bool, } -pub async fn on_error(error: FrameworkError<'_, (), PError>) { +pub async fn on_error(error: FrameworkError<'_, CommandData, PError>) { match error { FrameworkError::Command { error, ctx, .. } => { let _ = say_reply(ctx, "コマンド実行中にエラーが発生しました。").await; - error!("Command error: {}", error); + error!("Command error: {:?}", error); } FrameworkError::ArgumentParse { ctx, input, error, .. } => { let Some(input) = input else { @@ -95,12 +95,14 @@ async fn main() { .options(FrameworkOptions { commands: commands(), on_error: |error| Box::pin(on_error(error)), + skip_checks_for_owners: true, + owners: config.bot.owners.clone(), ..Default::default() }) - .setup(|ctx, _ready, framework| { + .setup(move |ctx, _ready, framework| { Box::pin(async move { poise::builtins::register_globally(ctx, &framework.options().commands).await?; - Ok(()) + Ok(CommandData {}) }) }) .build(); From 342aaad4b8b102da422b6f93227175a8348decf3 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sun, 22 Dec 2024 04:14:51 +0900 Subject: [PATCH 021/104] =?UTF-8?q?=E8=B3=AA=E5=95=8F=E3=82=B9=E3=83=AC?= =?UTF-8?q?=E3=83=83=E3=83=89=E3=82=92=E4=BD=9C=E6=88=90=E3=81=99=E3=82=8B?= =?UTF-8?q?=E6=A9=9F=E8=83=BD=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.sample.toml | 8 + src/config.rs | 10 +- src/features/auth.rs | 18 +- src/features/mod.rs | 9 +- src/features/question/command.rs | 199 ++++++++++++++++++ src/features/question/mod.rs | 119 +++++++++++ src/features/question/modal.rs | 77 +++++++ .../question/question_creation_handler.rs | 179 ++++++++++++++++ src/features/thread_pin.rs | 18 +- src/main.rs | 3 +- 10 files changed, 605 insertions(+), 35 deletions(-) create mode 100644 src/features/question/command.rs create mode 100644 src/features/question/mod.rs create mode 100644 src/features/question/modal.rs create mode 100644 src/features/question/question_creation_handler.rs diff --git a/config.sample.toml b/config.sample.toml index f0b0a96..162b650 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -38,3 +38,11 @@ channel_id = "000000000000000000" # 初期メッセージの内容 startup_message = """スレッドの始まりだぁ! まずはこのテンプレートを読んでね!""" + +[question] +# 質問の投稿先フォーラムのID +forum_id = "000000000000000000" +# 解決済みのタグID +solved_tag = "000000000000000000" +# 除外するタグIDのリスト +exclude_tags = ["000000000000000000"] diff --git a/src/config.rs b/src/config.rs index b23163c..62b6713 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,7 +4,7 @@ use regex::Regex; use serde::Deserialize; use serde_with::{serde_as, DisplayFromStr}; use serenity::{ - all::{ChannelId, Context, GuildId, RoleId, UserId}, + all::{ChannelId, Context, ForumTagId, GuildId, RoleId, UserId}, prelude::TypeMapKey, }; @@ -21,6 +21,7 @@ pub struct Config { pub message_logging: MessageLoggingConfig, pub message_cache: MessageCacheConfig, pub thread_channel_startup: ThreadChannelStartupConfig, + pub question: QuestionConfig, } impl TypeMapKey for Config { @@ -65,3 +66,10 @@ pub struct ThreadStartupConfig { pub channel_id: ChannelId, pub startup_message: String, } + +#[derive(Debug, Deserialize)] +pub struct QuestionConfig { + pub forum_id: ChannelId, + pub exclude_tags: Vec, + pub solved_tag: ForumTagId, +} diff --git a/src/features/auth.rs b/src/features/auth.rs index 589943d..8299e3d 100644 --- a/src/features/auth.rs +++ b/src/features/auth.rs @@ -1,26 +1,13 @@ -use poise::{say_reply, FrameworkError}; +use poise::say_reply; use serenity::all::Mentionable; use serenity::futures::{self, Stream, StreamExt}; use tracing::error; use crate::config::get_config; use crate::features::PError; -use crate::on_error; use crate::utils::{create_message, send_message}; -use super::{CommandData, PContext}; - -async fn keyword_on_error(error: FrameworkError<'_, CommandData, PError>) { - match error { - FrameworkError::Command { ctx, error, .. } => { - let _ = say_reply(ctx, "合言葉の確認中にエラーが発生しました。").await; - error!("Command error: {:?}", error); - } - error => { - let _ = on_error(error).await; - } - } -} +use super::PContext; async fn autocomplete_keyword<'a>(ctx: PContext<'_>, partial: &'a str) -> impl Stream + 'a { let config = &get_config(ctx.serenity_context()).await.auth; @@ -36,7 +23,6 @@ async fn autocomplete_keyword<'a>(ctx: PContext<'_>, partial: &'a str) -> impl S guild_only, aliases("合言葉"), member_cooldown = 60, - on_error = "keyword_on_error", required_bot_permissions = "MANAGE_ROLES" )] pub async fn keyword( diff --git a/src/features/mod.rs b/src/features/mod.rs index 7d98963..5a9b37b 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -2,11 +2,13 @@ mod admin; mod auth; mod logging; mod message_cache; +mod question; mod thread_channel_startup; mod thread_pin; pub use logging::Handler as LoggingHandler; pub use message_cache::Handler as MessageCacheHandler; +pub use question::Handler as QuestionHandler; pub use thread_channel_startup::Handler as ThreadChannelStartupHandler; pub use message_cache::{MessageCache, MessageCacheType}; @@ -17,7 +19,12 @@ pub type PContext<'a> = poise::Context<'a, CommandData, PError>; pub type PCommand = poise::Command; pub fn commands() -> Vec { - build_commands(vec![auth::keyword, thread_pin::pin, admin::reload_config]) + build_commands(vec![ + auth::keyword, + question::question, + thread_pin::pin, + admin::reload_config, + ]) } fn alias_command(base: fn() -> PCommand, name: String) -> PCommand { diff --git a/src/features/question/command.rs b/src/features/question/command.rs new file mode 100644 index 0000000..5f71ca4 --- /dev/null +++ b/src/features/question/command.rs @@ -0,0 +1,199 @@ +use poise::{ApplicationContext, CreateReply}; +use serenity::all::{ + ButtonStyle, Channel, CreateActionRow, CreateButton, CreateForumPost, CreateMessage, CreateSelectMenu, + CreateSelectMenuKind, CreateSelectMenuOption, ForumEmoji, ForumTag, ForumTagId, MessageBuilder, ReactionType, +}; +use serenity::futures::lock::Mutex; +use tokio::sync::mpsc; +use tracing::debug; + +use std::sync::Arc; +use std::vec; + +use crate::config::get_config; +use crate::features::question::modal::{BasicQuestionData, DetailedQuestionData}; +use crate::features::question::question_creation_handler::{CustomIds, QuestionCreationHandler}; +use crate::features::question::QUESTION_CLOSE_PREFIX; +use crate::features::{CommandData, PError}; + +fn reaction_from_forum_emoji(emoji: &ForumEmoji) -> Option { + match emoji.clone() { + ForumEmoji::Id(emoji) => Some(emoji.into()), + ForumEmoji::Name(emoji) => Some(emoji.try_into().unwrap()), + _ => None, + } +} + +fn create_select_menu( + custom_id: impl Into, + available_tags: &[ForumTag], + exclude_tags: &[ForumTagId], + selected_tags: &[ForumTagId], +) -> CreateActionRow { + let options = available_tags + .iter() + .filter(|x| !exclude_tags.contains(&x.id)) + .map(|x| { + let opt = CreateSelectMenuOption::new(x.name.clone(), x.id.to_string()) + .default_selection(selected_tags.contains(&x.id)); + match &x.emoji { + Some(emoji) => opt.emoji(reaction_from_forum_emoji(emoji).unwrap()), + None => opt, + } + }) + .collect::>(); + + let select_menu = CreateSelectMenu::new( + custom_id, + CreateSelectMenuKind::String { + options: options.clone(), + }, + ) + .min_values(1) + .max_values(options.len().try_into().unwrap()) + .placeholder("タグを選択してください"); + + CreateActionRow::SelectMenu(select_menu) +} + +/// Modに関する質問を行うためのフォーラムを作成します。 +#[poise::command( + slash_command, + ephemeral, + guild_only, + aliases("質問開始"), + member_cooldown = 60, + required_bot_permissions = "CREATE_PUBLIC_THREADS" +)] +pub async fn question(ctx: ApplicationContext<'_, CommandData, PError>) -> Result<(), PError> { + let custom_ids = Arc::new(CustomIds::new(ctx.id())); + + ctx.defer_ephemeral().await?; + + let submit_button = CreateButton::new(&custom_ids.submit) + .label("質問を送信") + .style(ButtonStyle::Success); + + let config = &get_config(ctx.serenity_context()).await.question; + let Ok(Channel::Guild(channel)) = config.forum_id.to_channel(ctx.serenity_context()).await else { + return Err("Failed to create forum channel".into()); + }; + + let buttons = vec![ + CreateButton::new(&custom_ids.basic) + .label("質問の基本情報を入力") + .style(ButtonStyle::Primary), + CreateButton::new(&custom_ids.detailed) + .label("質問の詳細情報を入力") + .style(ButtonStyle::Primary), + submit_button.clone().disabled(true), + ]; + + const PROMPT: &str = "ボタンをクリックしてすべての情報を入力してください。\nセレクトボックスからタグを設定してください。\nまた、再度ボタンをクリックすると入力内容を編集することができます。"; + let message = ctx + .send(CreateReply::default().content(PROMPT).components(vec![ + create_select_menu( + &custom_ids.select_tag, + &channel.available_tags, + &config.exclude_tags, + &[], + ), + CreateActionRow::Buttons(buttons.clone()), + ])) + .await?; + + let basic_data = Arc::new(Mutex::new(None::)); + let detailed_data = Arc::new(Mutex::new(None::)); + let forum_tag_ids = Arc::new(Mutex::new(Vec::::new())); + + let (submit_tx, mut submit_rx) = mpsc::channel::<()>(1); + let (inputted_tx, mut inputted_rx) = mpsc::channel::<()>(1); + + { + let handler = QuestionCreationHandler { + ctx: ctx.serenity_context().clone(), + interaction: ctx.interaction.clone(), + custom_ids: custom_ids.clone(), + basic_data: basic_data.clone(), + detailed_data: detailed_data.clone(), + forum_tag_ids: forum_tag_ids.clone(), + submit_tx, + inputted_tx, + }; + + handler.handle_component_interaction(); + handler.handle_modal_interaction(); + } + + if inputted_rx.recv().await.is_none() { + debug!("inputted_rx closed"); + return Ok(()); + } + + const CONFIRM: &str = "情報が入力されました、内容を確認し問題なければ「質問を送信」ボタンをクリックしてください。\n### この機能で作成されるフォームは編集出来ません、間違いが無いように気をつけてください。"; + message + .edit( + ctx.into(), + CreateReply::default() + .content(format!("{}\n{}", PROMPT, CONFIRM)) + .components(vec![ + create_select_menu( + &custom_ids.select_tag, + &channel.available_tags, + &config.exclude_tags, + &forum_tag_ids.lock().await.clone(), + ), + CreateActionRow::Buttons({ + let mut c = buttons.clone(); + c[2] = submit_button.clone(); + c + }), + ]), + ) + .await?; + + if submit_rx.recv().await.is_none() { + debug!("submit_rx closed"); + return Ok(()); + } + + let basic_data = basic_data.lock().await.clone().unwrap(); + let detailed_data = detailed_data.lock().await.clone().unwrap(); + let forum_tag_ids = forum_tag_ids.lock().await.clone(); + + let msg = MessageBuilder::new() + .push_line(basic_data.to_string()) + .push_line(detailed_data.to_string()) + .push("\n質問者: ") + .mention(&ctx.interaction.user) + .build(); + + let forum_channel = channel + .create_forum_post( + ctx.http(), + CreateForumPost::new( + &basic_data.title, + CreateMessage::default() + .content(msg) + .components(vec![CreateActionRow::Buttons(vec![CreateButton::new(format!( + "{}:{}", + QUESTION_CLOSE_PREFIX, ctx.interaction.user.id + )) + .label("質問を解決済みにする") + .style(ButtonStyle::Danger)])]), + ) + .set_applied_tags(forum_tag_ids), + ) + .await?; + + let msg = MessageBuilder::new() + .push_line_safe("質問フォーラムを開始しました。") + .mention(&forum_channel) + .build(); + + message + .edit(ctx.into(), CreateReply::default().content(msg).components(vec![])) + .await?; + + Ok(()) +} diff --git a/src/features/question/mod.rs b/src/features/question/mod.rs new file mode 100644 index 0000000..d41912e --- /dev/null +++ b/src/features/question/mod.rs @@ -0,0 +1,119 @@ +mod command; +mod modal; +mod question_creation_handler; + +use std::{str::FromStr, time::Duration}; + +pub use command::question; +use serenity::{ + all::{ + ButtonStyle, CacheHttp, Channel, ComponentInteractionCollector, ComponentInteractionDataKind, Context, + CreateActionRow, CreateButton, CreateInteractionResponse, CreateInteractionResponseMessage, + EditInteractionResponse, EditThread, EventHandler, Interaction, UserId, + }, + async_trait, +}; +use tracing::error; + +use crate::config::get_config; + +pub static QUESTION_CLOSE_PREFIX: &str = "close_question_forum"; + +pub struct Handler; + +#[async_trait] +impl EventHandler for Handler { + async fn interaction_create(&self, ctx: Context, interaction: Interaction) { + let Interaction::Component(interaction) = interaction else { + return; + }; + let ComponentInteractionDataKind::Button = interaction.data.kind else { + return; + }; + let custom_id = &interaction.data.custom_id; + if !custom_id.starts_with(QUESTION_CLOSE_PREFIX) { + return; + } + + let (_, author_id) = custom_id.split_at(QUESTION_CLOSE_PREFIX.len() + 1); + let author_id = UserId::from_str(author_id).unwrap(); + if author_id != interaction.user.id { + return; + } + + let config = &get_config(&ctx).await.question; + let Ok(Channel::Guild(channel)) = interaction.channel_id.to_channel(&ctx).await else { + return error!("Failed to get channel: {:?}", interaction.channel_id); + }; + if channel.applied_tags.contains(&config.solved_tag) { + let _ = interaction + .create_response( + ctx.http(), + CreateInteractionResponse::Message( + CreateInteractionResponseMessage::new() + .content("既に解決済みです。") + .ephemeral(true), + ), + ) + .await; + } + + let confirm_custom_id = format!("close_question_confirm:{}", interaction.id); + let cancel_custom_id = format!("close_question_cancel:{}", interaction.id); + + let _ = interaction + .create_response( + ctx.http(), + CreateInteractionResponse::Message( + CreateInteractionResponseMessage::new() + .content("本当に質問を終了しますか?") + .ephemeral(true) + .components( + [CreateActionRow::Buttons( + [ + CreateButton::new(&confirm_custom_id) + .label("はい") + .emoji('✅') + .style(ButtonStyle::Danger), + CreateButton::new(&cancel_custom_id) + .label("いいえ") + .emoji('❎') + .style(ButtonStyle::Success), + ] + .to_vec(), + )] + .to_vec(), + ), + ), + ) + .await; + + let res = ComponentInteractionCollector::new(&ctx.shard) + .custom_ids(vec![confirm_custom_id.clone(), cancel_custom_id.clone()]) + .timeout(Duration::from_secs(60)) + .await; + + let (confirmed, text) = match res { + Some(i) if i.data.custom_id == confirm_custom_id => (true, "質問を解決済みにしました。"), + _ => (false, "キャンセルしました。"), + }; + + if confirmed { + let mut applied_tags = channel.applied_tags.clone(); + applied_tags.push(config.solved_tag); + + interaction + .channel_id + .edit_thread(ctx.http(), EditThread::new().applied_tags(applied_tags)) + .await + .unwrap(); + } + + let _ = interaction + .edit_response( + ctx.http(), + EditInteractionResponse::new().content(text).components(vec![]), + ) + .await; + } +} diff --git a/src/features/question/modal.rs b/src/features/question/modal.rs new file mode 100644 index 0000000..c5d357a --- /dev/null +++ b/src/features/question/modal.rs @@ -0,0 +1,77 @@ +use std::fmt::Display; + +#[derive(Debug, Clone, poise::Modal)] +pub struct BasicQuestionData { + #[name = "質問のタイトル (わかりやすいように質問内容を要約してください)"] + #[placeholder = "質問のタイトルを入力してください"] + #[min_length = 10] + #[max_length = 100] + pub title: String, + #[name = "Minecraftのバージョン"] + #[placeholder = "Minecraftのバージョンを入力してください"] + #[min_length = 3] + #[max_length = 20] + pub mc_version: String, + #[name = "Modローダー (Forge, Fabric, NeoForge, Quilt, その他)"] + #[placeholder = "使用しているModローダーを選択してください"] + #[min_length = 3] + #[max_length = 20] + pub loader: String, + #[name = "Modローダーのバージョン"] + #[placeholder = "使用しているModローダーのバージョンを入力してください"] + #[min_length = 3] + #[max_length = 20] + pub loader_version: String, +} + +impl Display for BasicQuestionData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "### 基本情報\n- Minecraftバージョン: {}\n- Modローダー: {}\n- Modローダーバージョン: {}", + self.mc_version, self.loader, self.loader_version + ) + } +} + +#[derive(Debug, Clone, poise::Modal)] +pub struct DetailedQuestionData { + #[name = "質問の内容 (詳細な質問内容を入力してください)"] + #[placeholder = "質問の内容を入力してください"] + #[min_length = 20] + #[max_length = 1000] + #[paragraph] + pub content: String, + #[name = "問題解決の達成基準"] + #[placeholder = "問題解決の達成基準を入力してください"] + #[min_length = 20] + #[max_length = 1000] + #[paragraph] + pub content2: String, + #[name = "試したこと・調べたこと"] + #[placeholder = "質問を行う前に試したことや調べたことを入力してください"] + #[min_length = 20] + #[max_length = 1000] + #[paragraph] + pub content3: String, +} + +impl Display for DetailedQuestionData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "### 質問内容\n- 質問内容:\n{}\n- 問題解決の達成基準:\n{}\n- 試したこと・調べたこと:\n{}", + self.content, self.content2, self.content3 + ) + } +} + +impl Default for DetailedQuestionData { + fn default() -> Self { + Self { + content: "例:クラッシュした, 変な挙動をする, modの扱い方がわからない".to_string(), + content2: "例: クラッシュから抜け出したい, このような挙動にしたい, このmodでこのようなことがしたい".to_string(), + content3: "例:○○というサイトに掲載されてた対処法を試した\n推奨:mclo.gs にて変換したクラッシュレポート、lastet.logのリンクを貼る".to_string(), + } + } +} diff --git a/src/features/question/question_creation_handler.rs b/src/features/question/question_creation_handler.rs new file mode 100644 index 0000000..e4ee30a --- /dev/null +++ b/src/features/question/question_creation_handler.rs @@ -0,0 +1,179 @@ +use std::{str::FromStr, sync::Arc, time::Duration}; + +use poise::Modal; +use serenity::{ + all::{ + CacheHttp, CommandInteraction, ComponentInteraction, ComponentInteractionCollector, + ComponentInteractionDataKind, Context, ForumTagId, ModalInteractionCollector, ModalInteractionData, + }, + futures::{lock::Mutex, StreamExt}, +}; +use tokio::sync::mpsc; +use tracing::{debug, error}; + +use super::modal::{BasicQuestionData, DetailedQuestionData}; + +pub struct CustomIds { + pub basic: String, + pub detailed: String, + pub select_tag: String, + pub submit: String, +} + +impl CustomIds { + pub fn new(id: u64) -> Self { + Self { + basic: format!("open_basic_question_modal:{}", id), + detailed: format!("open_detailed_question_modal:{}", id), + select_tag: format!("question_select_tag:{}", id), + submit: format!("question_submit:{}", id), + } + } + + pub fn to_vec(&self) -> Vec { + vec![ + self.basic.clone(), + self.detailed.clone(), + self.select_tag.clone(), + self.submit.clone(), + ] + } +} + +static TIMEOUT: Duration = Duration::from_secs(3600); + +#[derive(Clone)] +pub struct QuestionCreationHandler { + pub ctx: Context, + pub interaction: CommandInteraction, + pub custom_ids: Arc, + pub basic_data: Arc>>, + pub detailed_data: Arc>>, + pub forum_tag_ids: Arc>>, + pub submit_tx: mpsc::Sender<()>, + pub inputted_tx: mpsc::Sender<()>, +} + +impl QuestionCreationHandler { + async fn enable_button(&self) { + let has_basic_data = self.basic_data.lock().await.is_some(); + let has_detailed_data = self.detailed_data.lock().await.is_some(); + let has_forum_tag_ids = !self.forum_tag_ids.lock().await.is_empty(); + + if has_basic_data && has_detailed_data && has_forum_tag_ids && !self.inputted_tx.is_closed() { + self.inputted_tx.send(()).await.unwrap(); + } + } + + async fn send_modal(&self, interaction: &ComponentInteraction, default: Option, custom_id: &str) { + let modal = M::create(default, custom_id.to_owned()); + let Ok(_) = interaction.create_response(self.ctx.http(), modal.clone()).await else { + error!("Failed to create response: {:?}", modal); + return; + }; + } + + async fn parse_response(&self, data: &ModalInteractionData) -> Option { + match M::parse(data.clone()) { + Ok(data) => Some(data), + Err(e) => { + error!("Failed to parse modal data: {:?}, {:?}", e, data); + None + } + } + } + + pub fn handle_component_interaction(&self) { + let self_clone = self.clone(); + tokio::spawn(async move { + self_clone._handle_component_interaction().await; + debug!("component interaction end"); + }); + } + + async fn _handle_component_interaction(self) { + let mut stream = ComponentInteractionCollector::new(self.ctx.shard.clone()) + .custom_ids(self.custom_ids.to_vec()) + .timeout(TIMEOUT) + .stream(); + let http = self.ctx.http(); + loop { + let Some(interaction) = stream.next().await else { + break; + }; + + match interaction.data.custom_id { + ref x if x == &self.custom_ids.basic => { + self.send_modal::( + &interaction, + self.basic_data.lock().await.clone(), + &self.custom_ids.basic, + ) + .await + } + ref x if x == &self.custom_ids.detailed => { + self.send_modal::( + &interaction, + Some(self.detailed_data.lock().await.clone().unwrap_or_default()), + &self.custom_ids.detailed, + ) + .await + } + ref x if x == &self.custom_ids.select_tag => { + if let ComponentInteractionDataKind::StringSelect { ref values } = interaction.data.kind { + let mut tags = self.forum_tag_ids.lock().await; + tags.clear(); + tags.extend(values.iter().map(|x| ForumTagId::from_str(x).unwrap())); + interaction.defer(http).await.unwrap(); + }; + } + ref x if x == &self.custom_ids.submit => { + self.submit_tx.send(()).await.unwrap(); + interaction.defer(http).await.unwrap(); + return; + } + _ => {} + } + + // データが入力されたら送信ボタンを有効化する + self.enable_button().await; + } + + let _ = self.interaction.delete_response(http).await; + } + + pub fn handle_modal_interaction(&self) { + let self_clone = self.clone(); + tokio::spawn(async move { + self_clone._handle_modal_interaction().await; + debug!("modal interaction end"); + }); + } + + async fn _handle_modal_interaction(self) { + let mut stream = ModalInteractionCollector::new(self.ctx.shard.clone()) + .custom_ids(self.custom_ids.to_vec()) + .timeout(TIMEOUT) + .stream(); + while let Some(res) = tokio::select! { + res = stream.next() => res, + _ = self.submit_tx.closed() => None, + } { + match res.data.custom_id { + ref x if x == &self.custom_ids.basic => { + let mut data = self.basic_data.lock().await; + *data = self.parse_response::(&res.data).await; + } + ref x if x == &self.custom_ids.detailed => { + let mut data = self.detailed_data.lock().await; + *data = self.parse_response::(&res.data).await; + } + _ => {} + } + + let _ = res.defer(self.ctx.http()).await; + + self.enable_button().await; + } + } +} diff --git a/src/features/thread_pin.rs b/src/features/thread_pin.rs index 2fd5634..8a90aa1 100644 --- a/src/features/thread_pin.rs +++ b/src/features/thread_pin.rs @@ -1,26 +1,13 @@ use std::time::Duration; -use poise::{say_reply, FrameworkError}; +use poise::say_reply; use serenity::{ all::{Message, MessageType}, futures::StreamExt, }; -use tracing::error; use super::PContext; -use crate::{config::get_config, features::CommandData, on_error, PError}; - -async fn pin_on_error(error: FrameworkError<'_, CommandData, PError>) { - match error { - FrameworkError::Command { ctx, error, .. } => { - let _ = say_reply(ctx, "ピン留め中にエラーが発生しました。").await; - error!("Command error: {:?}", error); - } - error => { - let _ = on_error(error).await; - } - } -} +use crate::{config::get_config, PError}; /// スレッド主限定でメッセージをピン留めします。 #[poise::command( @@ -29,7 +16,6 @@ async fn pin_on_error(error: FrameworkError<'_, CommandData, PError>) { ephemeral, guild_only, aliases("ピン留め"), - on_error = "pin_on_error", required_bot_permissions = "MANAGE_MESSAGES" )] pub async fn pin( diff --git a/src/main.rs b/src/main.rs index d8f707a..827817e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,7 +36,7 @@ pub async fn on_error(error: FrameworkError<'_, CommandData, PError>) { match error { FrameworkError::Command { error, ctx, .. } => { let _ = say_reply(ctx, "コマンド実行中にエラーが発生しました。").await; - error!("Command error: {:?}", error); + error!("Command error: Command: {:?}, Error: {:?}", ctx.command(), error); } FrameworkError::ArgumentParse { ctx, input, error, .. } => { let Some(input) = input else { @@ -115,6 +115,7 @@ async fn main() { .event_handler(MainHandler) .event_handler(features::LoggingHandler) .event_handler(features::ThreadChannelStartupHandler) + .event_handler(features::QuestionHandler) .event_handler(features::MessageCacheHandler { disabled: config.message_cache.disabled, }) From bd0f9a20318a2908a484fac9e22ca0f829e3bf11 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sun, 22 Dec 2024 05:18:50 +0900 Subject: [PATCH 022/104] =?UTF-8?q?=E8=B3=AA=E5=95=8F=E4=BD=9C=E6=88=90?= =?UTF-8?q?=E6=99=82=E3=81=AB=E3=83=8F=E3=83=B3=E3=82=B0=E3=81=99=E3=82=8B?= =?UTF-8?q?=E4=BA=8B=E3=81=8C=E3=81=82=E3=82=8B=E5=95=8F=E9=A1=8C=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit inputted_rx が閉じられていなかったのが原因 --- src/features/question/command.rs | 21 +++++---- .../question/question_creation_handler.rs | 47 +++++++++---------- 2 files changed, 33 insertions(+), 35 deletions(-) diff --git a/src/features/question/command.rs b/src/features/question/command.rs index 5f71ca4..d26669f 100644 --- a/src/features/question/command.rs +++ b/src/features/question/command.rs @@ -3,8 +3,7 @@ use serenity::all::{ ButtonStyle, Channel, CreateActionRow, CreateButton, CreateForumPost, CreateMessage, CreateSelectMenu, CreateSelectMenuKind, CreateSelectMenuOption, ForumEmoji, ForumTag, ForumTagId, MessageBuilder, ReactionType, }; -use serenity::futures::lock::Mutex; -use tokio::sync::mpsc; +use tokio::sync::{mpsc, RwLock}; use tracing::debug; use std::sync::Arc; @@ -102,9 +101,9 @@ pub async fn question(ctx: ApplicationContext<'_, CommandData, PError>) -> Resul ])) .await?; - let basic_data = Arc::new(Mutex::new(None::)); - let detailed_data = Arc::new(Mutex::new(None::)); - let forum_tag_ids = Arc::new(Mutex::new(Vec::::new())); + let basic_data = Arc::new(RwLock::new(None::)); + let detailed_data = Arc::new(RwLock::new(None::)); + let forum_tag_ids = Arc::new(RwLock::new(Vec::::new())); let (submit_tx, mut submit_rx) = mpsc::channel::<()>(1); let (inputted_tx, mut inputted_rx) = mpsc::channel::<()>(1); @@ -129,6 +128,7 @@ pub async fn question(ctx: ApplicationContext<'_, CommandData, PError>) -> Resul debug!("inputted_rx closed"); return Ok(()); } + inputted_rx.close(); const CONFIRM: &str = "情報が入力されました、内容を確認し問題なければ「質問を送信」ボタンをクリックしてください。\n### この機能で作成されるフォームは編集出来ません、間違いが無いように気をつけてください。"; message @@ -141,7 +141,7 @@ pub async fn question(ctx: ApplicationContext<'_, CommandData, PError>) -> Resul &custom_ids.select_tag, &channel.available_tags, &config.exclude_tags, - &forum_tag_ids.lock().await.clone(), + &forum_tag_ids.read().await, ), CreateActionRow::Buttons({ let mut c = buttons.clone(); @@ -156,10 +156,11 @@ pub async fn question(ctx: ApplicationContext<'_, CommandData, PError>) -> Resul debug!("submit_rx closed"); return Ok(()); } + submit_rx.close(); - let basic_data = basic_data.lock().await.clone().unwrap(); - let detailed_data = detailed_data.lock().await.clone().unwrap(); - let forum_tag_ids = forum_tag_ids.lock().await.clone(); + let basic_data = basic_data.read().await.clone().unwrap(); + let detailed_data = detailed_data.read().await.clone().unwrap(); + let forum_tag_ids = forum_tag_ids.read().await; let msg = MessageBuilder::new() .push_line(basic_data.to_string()) @@ -182,7 +183,7 @@ pub async fn question(ctx: ApplicationContext<'_, CommandData, PError>) -> Resul .label("質問を解決済みにする") .style(ButtonStyle::Danger)])]), ) - .set_applied_tags(forum_tag_ids), + .set_applied_tags(&*forum_tag_ids), ) .await?; diff --git a/src/features/question/question_creation_handler.rs b/src/features/question/question_creation_handler.rs index e4ee30a..f6d9c7d 100644 --- a/src/features/question/question_creation_handler.rs +++ b/src/features/question/question_creation_handler.rs @@ -6,10 +6,10 @@ use serenity::{ CacheHttp, CommandInteraction, ComponentInteraction, ComponentInteractionCollector, ComponentInteractionDataKind, Context, ForumTagId, ModalInteractionCollector, ModalInteractionData, }, - futures::{lock::Mutex, StreamExt}, + futures::StreamExt, }; -use tokio::sync::mpsc; -use tracing::{debug, error}; +use tokio::sync::{mpsc, RwLock}; +use tracing::error; use super::modal::{BasicQuestionData, DetailedQuestionData}; @@ -47,20 +47,24 @@ pub struct QuestionCreationHandler { pub ctx: Context, pub interaction: CommandInteraction, pub custom_ids: Arc, - pub basic_data: Arc>>, - pub detailed_data: Arc>>, - pub forum_tag_ids: Arc>>, + pub basic_data: Arc>>, + pub detailed_data: Arc>>, + pub forum_tag_ids: Arc>>, pub submit_tx: mpsc::Sender<()>, pub inputted_tx: mpsc::Sender<()>, } impl QuestionCreationHandler { async fn enable_button(&self) { - let has_basic_data = self.basic_data.lock().await.is_some(); - let has_detailed_data = self.detailed_data.lock().await.is_some(); - let has_forum_tag_ids = !self.forum_tag_ids.lock().await.is_empty(); + if self.inputted_tx.is_closed() { + return; + } + + let has_basic_data = self.basic_data.read().await.is_some(); + let has_detailed_data = self.detailed_data.read().await.is_some(); + let has_forum_tag_ids = !self.forum_tag_ids.try_read().unwrap().is_empty(); - if has_basic_data && has_detailed_data && has_forum_tag_ids && !self.inputted_tx.is_closed() { + if has_basic_data && has_detailed_data && has_forum_tag_ids { self.inputted_tx.send(()).await.unwrap(); } } @@ -87,7 +91,6 @@ impl QuestionCreationHandler { let self_clone = self.clone(); tokio::spawn(async move { self_clone._handle_component_interaction().await; - debug!("component interaction end"); }); } @@ -97,16 +100,12 @@ impl QuestionCreationHandler { .timeout(TIMEOUT) .stream(); let http = self.ctx.http(); - loop { - let Some(interaction) = stream.next().await else { - break; - }; - + while let Some(interaction) = stream.next().await { match interaction.data.custom_id { ref x if x == &self.custom_ids.basic => { self.send_modal::( &interaction, - self.basic_data.lock().await.clone(), + self.basic_data.read().await.clone(), &self.custom_ids.basic, ) .await @@ -114,16 +113,15 @@ impl QuestionCreationHandler { ref x if x == &self.custom_ids.detailed => { self.send_modal::( &interaction, - Some(self.detailed_data.lock().await.clone().unwrap_or_default()), + Some(self.detailed_data.read().await.clone().unwrap_or_default()), &self.custom_ids.detailed, ) - .await + .await; } ref x if x == &self.custom_ids.select_tag => { if let ComponentInteractionDataKind::StringSelect { ref values } = interaction.data.kind { - let mut tags = self.forum_tag_ids.lock().await; - tags.clear(); - tags.extend(values.iter().map(|x| ForumTagId::from_str(x).unwrap())); + let mut tags = self.forum_tag_ids.write().await; + *tags = values.iter().map(|x| ForumTagId::from_str(x).unwrap()).collect(); interaction.defer(http).await.unwrap(); }; } @@ -146,7 +144,6 @@ impl QuestionCreationHandler { let self_clone = self.clone(); tokio::spawn(async move { self_clone._handle_modal_interaction().await; - debug!("modal interaction end"); }); } @@ -161,11 +158,11 @@ impl QuestionCreationHandler { } { match res.data.custom_id { ref x if x == &self.custom_ids.basic => { - let mut data = self.basic_data.lock().await; + let mut data = self.basic_data.write().await; *data = self.parse_response::(&res.data).await; } ref x if x == &self.custom_ids.detailed => { - let mut data = self.detailed_data.lock().await; + let mut data = self.detailed_data.write().await; *data = self.parse_response::(&res.data).await; } _ => {} From ef2ceb9cd2f4670940b3eb10500920158172b340 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sun, 22 Dec 2024 05:34:03 +0900 Subject: [PATCH 023/104] =?UTF-8?q?=E3=83=A2=E3=83=BC=E3=83=80=E3=83=AB?= =?UTF-8?q?=E3=81=AE=E3=82=BF=E3=82=A4=E3=83=88=E3=83=AB=E3=82=92=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/question/modal.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/features/question/modal.rs b/src/features/question/modal.rs index c5d357a..e663b84 100644 --- a/src/features/question/modal.rs +++ b/src/features/question/modal.rs @@ -1,6 +1,7 @@ use std::fmt::Display; #[derive(Debug, Clone, poise::Modal)] +#[name = "質問の基本情報を入力"] pub struct BasicQuestionData { #[name = "質問のタイトル (わかりやすいように質問内容を要約してください)"] #[placeholder = "質問のタイトルを入力してください"] @@ -35,6 +36,7 @@ impl Display for BasicQuestionData { } #[derive(Debug, Clone, poise::Modal)] +#[name = "質問の詳細情報を入力"] pub struct DetailedQuestionData { #[name = "質問の内容 (詳細な質問内容を入力してください)"] #[placeholder = "質問の内容を入力してください"] From 87a6acd1411fe6fd3630b0cca92d72fe3679fb6c Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sun, 22 Dec 2024 05:39:07 +0900 Subject: [PATCH 024/104] =?UTF-8?q?=E3=82=AA=E3=83=BC=E3=83=8A=E5=B0=82?= =?UTF-8?q?=E7=94=A8=E3=82=B3=E3=83=9E=E3=83=B3=E3=83=89=E3=82=92=E4=B8=80?= =?UTF-8?q?=E8=88=AC=E3=83=A6=E3=83=BC=E3=82=B6=E3=81=8C=E5=AE=9F=E8=A1=8C?= =?UTF-8?q?=E3=81=97=E3=81=9F=E9=9A=9B=E3=81=AE=E3=82=A8=E3=83=A9=E3=83=BC?= =?UTF-8?q?=E3=82=92=E6=97=A5=E6=9C=AC=E8=AA=9E=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main.rs b/src/main.rs index 827817e..3ddfb3d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -64,6 +64,9 @@ pub async fn on_error(error: FrameworkError<'_, CommandData, PError>) { ); let _ = say_reply(ctx, msg).await; } + FrameworkError::NotAnOwner { ctx, .. } => { + let _ = say_reply(ctx, "このコマンドはボットのオーナーのみ実行できます。").await; + } error => { if let Err(e) = poise::builtins::on_error(error).await { println!("Error while handling error: {}", e) From 11f9f9c00d67d832f19657a449a8ef96b15ab769 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sun, 22 Dec 2024 05:57:19 +0900 Subject: [PATCH 025/104] =?UTF-8?q?thread=5Fcreate=20=E3=81=A7=E3=81=AE?= =?UTF-8?q?=E5=88=9D=E6=9C=9F=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8?= =?UTF-8?q?=E9=80=81=E4=BF=A1=E3=82=92=E5=BE=85=E3=81=A4=E5=87=A6=E7=90=86?= =?UTF-8?q?=E3=82=92=E9=96=A2=E6=95=B0=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/thread_channel_startup.rs | 15 ++-------- src/utils.rs | 39 +++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/features/thread_channel_startup.rs b/src/features/thread_channel_startup.rs index a94fdd9..508136a 100644 --- a/src/features/thread_channel_startup.rs +++ b/src/features/thread_channel_startup.rs @@ -1,5 +1,3 @@ -use std::time::Duration; - use serenity::{ all::{Context, EventHandler, GuildChannel}, async_trait, @@ -8,7 +6,7 @@ use tracing::error; use crate::{ config::get_config, - utils::{create_message, send_message}, + utils::{await_initial_message, create_message, send_message}, }; pub struct Handler; @@ -16,19 +14,10 @@ pub struct Handler; #[async_trait] impl EventHandler for Handler { async fn thread_create(&self, ctx: Context, thread: GuildChannel) { - // Botがメッセージを送信すると二度イベントが発火するので、初期メッセージ送信後のイベントは無視する - if thread.last_message_id.is_some() { + if await_initial_message(&ctx, &thread).await { return; } - // 初期メッセージが送信されるか、5秒経つまで待機 - let _ = thread - .await_reply(&ctx.shard) - .channel_id(thread.id) - .author_id(thread.owner_id.unwrap()) - .timeout(Duration::from_secs(5)) - .await; - let config = &get_config(&ctx).await.thread_channel_startup; let Some(parent_id) = thread.parent_id else { return error!("Failed to get parent id: {:?}", thread); diff --git a/src/utils.rs b/src/utils.rs index 2097ce2..67f5245 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,8 @@ +use std::time::Duration; + use itertools::Itertools; use serenity::{ - all::{ChannelId, Context, CreateAllowedMentions, CreateMessage, Message, MessageId}, + all::{ChannelId, Context, CreateAllowedMentions, CreateMessage, GuildChannel, Message, MessageId}, Result, }; use similar::{Algorithm, ChangeTag, TextDiff}; @@ -16,6 +18,41 @@ pub fn create_message(content: impl Into) -> CreateMessage { create_safe_message().content(content) } +/** +thread_create イベントにおいて、初期メッセージが送信されるか5秒経過するまで待機する + +初期メッセージが送信されると、falseを返し、既に初期メッセージが存在する場合、true を返す +```rs +pub struct Handler; + +#[async_trait] +impl EventHandler for Handler { + async fn thread_create(&self, ctx: Context, thread: GuildChannel) { + if await_thread_create(&ctx, &thread).await { + return; + } + + // 処理 + } +} +``` + */ +pub async fn await_initial_message(ctx: &Context, thread: &GuildChannel) -> bool { + // Botがメッセージを送信すると二度イベントが発火するので、初期メッセージ送信後のイベントは無視する + if thread.last_message_id.is_some() { + return true; + } + + // 初期メッセージが送信されるか、5秒経つまで待機 + thread + .await_reply(&ctx.shard) + .channel_id(thread.id) + .author_id(thread.owner_id.unwrap()) + .timeout(Duration::from_secs(5)) + .await; + false +} + pub async fn send_message(ctx: &Context, channel_id: &ChannelId, builder: CreateMessage) -> Result { match channel_id.send_message(&ctx.http, builder).await { Ok(m) => Ok(m), From cd9e598fec944fe0993b4e7857ce5bcc6fe9a9a9 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sun, 22 Dec 2024 06:20:32 +0900 Subject: [PATCH 026/104] =?UTF-8?q?=E3=82=B9=E3=83=AC=E3=83=83=E3=83=89?= =?UTF-8?q?=E3=81=AE=E8=87=AA=E5=8B=95=E6=8B=9B=E5=BE=85=E3=82=92=E5=AE=9F?= =?UTF-8?q?=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.sample.toml | 4 ++++ src/config.rs | 6 +++++ src/features/mod.rs | 2 ++ src/features/thread_auto_invite.rs | 36 ++++++++++++++++++++++++++++++ src/main.rs | 1 + 5 files changed, 49 insertions(+) create mode 100644 src/features/thread_auto_invite.rs diff --git a/config.sample.toml b/config.sample.toml index 162b650..53c7d65 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -39,6 +39,10 @@ channel_id = "000000000000000000" startup_message = """スレッドの始まりだぁ! まずはこのテンプレートを読んでね!""" +[thread_auto_invite] +# 自動で招待するロールのID +role_id = "000000000000000000" + [question] # 質問の投稿先フォーラムのID forum_id = "000000000000000000" diff --git a/src/config.rs b/src/config.rs index 62b6713..b6cac01 100644 --- a/src/config.rs +++ b/src/config.rs @@ -21,6 +21,7 @@ pub struct Config { pub message_logging: MessageLoggingConfig, pub message_cache: MessageCacheConfig, pub thread_channel_startup: ThreadChannelStartupConfig, + pub thread_auto_invite: ThreadAutoInviteConfig, pub question: QuestionConfig, } @@ -61,6 +62,11 @@ pub struct ThreadChannelStartupConfig { pub threads: Vec, } +#[derive(Debug, Deserialize)] +pub struct ThreadAutoInviteConfig { + pub role_id: RoleId, +} + #[derive(Debug, Deserialize)] pub struct ThreadStartupConfig { pub channel_id: ChannelId, diff --git a/src/features/mod.rs b/src/features/mod.rs index 5a9b37b..34c1f26 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -3,12 +3,14 @@ mod auth; mod logging; mod message_cache; mod question; +mod thread_auto_invite; mod thread_channel_startup; mod thread_pin; pub use logging::Handler as LoggingHandler; pub use message_cache::Handler as MessageCacheHandler; pub use question::Handler as QuestionHandler; +pub use thread_auto_invite::Handler as ThreadAutoInviteHandler; pub use thread_channel_startup::Handler as ThreadChannelStartupHandler; pub use message_cache::{MessageCache, MessageCacheType}; diff --git a/src/features/thread_auto_invite.rs b/src/features/thread_auto_invite.rs new file mode 100644 index 0000000..434e61f --- /dev/null +++ b/src/features/thread_auto_invite.rs @@ -0,0 +1,36 @@ +use serenity::{ + all::{Context, EditMessage, EventHandler, GuildChannel, Mentionable}, + async_trait, +}; +use tracing::error; + +use crate::{ + config::get_config, + utils::{await_initial_message, create_message}, +}; + +pub struct Handler; + +#[async_trait] +impl EventHandler for Handler { + async fn thread_create(&self, ctx: Context, thread: GuildChannel) { + if await_initial_message(&ctx, &thread).await { + return; + } + + let mut message = { + let msg = create_message("スレッド自動招待用メッセージ"); + match thread.send_message(&ctx, msg).await { + Ok(m) => m, + Err(why) => return error!("Error sending message: {:?}", why), + } + }; + + let config = &get_config(&ctx).await.thread_auto_invite; + let _ = message + .edit(&ctx, EditMessage::new().content(config.role_id.mention().to_string())) + .await; + + let _ = message.delete(&ctx).await; + } +} diff --git a/src/main.rs b/src/main.rs index 3ddfb3d..b9ef1ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -117,6 +117,7 @@ async fn main() { .framework(framework) .event_handler(MainHandler) .event_handler(features::LoggingHandler) + .event_handler(features::ThreadAutoInviteHandler) .event_handler(features::ThreadChannelStartupHandler) .event_handler(features::QuestionHandler) .event_handler(features::MessageCacheHandler { From 0df5908328ab8a2f6cc820b9724b7f605f0e609b Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sun, 22 Dec 2024 06:25:27 +0900 Subject: [PATCH 027/104] =?UTF-8?q?=E3=82=B3=E3=83=9E=E3=83=B3=E3=83=89?= =?UTF-8?q?=E3=81=AE=E3=82=AF=E3=83=BC=E3=83=AB=E3=83=80=E3=82=A6=E3=83=B3?= =?UTF-8?q?=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92=E6=97=A5?= =?UTF-8?q?=E6=9C=AC=E8=AA=9E=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/logging.rs | 6 +++--- src/main.rs | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/features/logging.rs b/src/features/logging.rs index 54869ae..f66c85e 100644 --- a/src/features/logging.rs +++ b/src/features/logging.rs @@ -173,12 +173,12 @@ impl Handler { impl EventHandler for Handler { async fn message_update(&self, ctx: Context, old: Option, _: Option, event: MessageUpdateEvent) { let Some(message) = get_cached_message(&ctx, event.channel_id, event.id).await else { - return error!("Failed to get message: {:?}", event.id); + return error!("Failed to get message: {}", event.id); }; let message = old.unwrap_or(message); let Ok(new_message) = event.channel_id.message(&ctx.http, event.id).await else { - return error!("Failed to get new message: {:?}", event.id); + return error!("Failed to get new message: {}", event.id); }; if message.content == new_message.content { @@ -204,7 +204,7 @@ impl EventHandler for Handler { _: Option, ) { let Some(message) = get_cached_message(&ctx, channel_id, deleted_message_id).await else { - return error!("Failed to get message: {:?}", deleted_message_id); + return error!("Failed to get message: {}", deleted_message_id); }; self.create_and_send_log(&ctx, &message, "メッセージ削除ログ", 0xf00000, None) diff --git a/src/main.rs b/src/main.rs index b9ef1ea..3a5bb44 100644 --- a/src/main.rs +++ b/src/main.rs @@ -67,6 +67,20 @@ pub async fn on_error(error: FrameworkError<'_, CommandData, PError>) { FrameworkError::NotAnOwner { ctx, .. } => { let _ = say_reply(ctx, "このコマンドはボットのオーナーのみ実行できます。").await; } + FrameworkError::CooldownHit { + remaining_cooldown, + ctx, + .. + } => { + let _ = say_reply( + ctx, + format!( + "このコマンドはクールダウン中です。残り時間: {}秒", + remaining_cooldown.as_secs() + ), + ) + .await; + } error => { if let Err(e) = poise::builtins::on_error(error).await { println!("Error while handling error: {}", e) From 7e904b164ab55a65adfe420bedd3eeabf3b28957 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sun, 22 Dec 2024 06:57:57 +0900 Subject: [PATCH 028/104] =?UTF-8?q?dm=5Fonly=E3=81=AB=E5=AF=BE=E5=BF=9C?= =?UTF-8?q?=E3=81=95=E3=81=9B=E3=82=8B=E3=81=9F=E3=82=81=E3=80=81poise?= =?UTF-8?q?=E3=82=92=E3=82=A2=E3=83=83=E3=83=97=E3=83=87=E3=83=BC=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 14 ++++++++++---- Cargo.toml | 3 ++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5ef1118..808bf2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1135,25 +1135,25 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "poise" version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1819d5a45e3590ef33754abce46432570c54a120798bdbf893112b4211fa09a6" +source = "git+https://github.com/serenity-rs/poise?rev=db10b126c8b07f7e1924cba1672f04ff24ed4ec7#db10b126c8b07f7e1924cba1672f04ff24ed4ec7" dependencies = [ "async-trait", "derivative", "futures-util", + "indexmap 2.6.0", "parking_lot", "poise_macros", "regex", "serenity", "tokio", "tracing", + "trim-in-place", ] [[package]] name = "poise_macros" version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fa2c123c961e78315cd3deac7663177f12be4460f5440dbf62a7ed37b1effea" +source = "git+https://github.com/serenity-rs/poise?rev=db10b126c8b07f7e1924cba1672f04ff24ed4ec7#db10b126c8b07f7e1924cba1672f04ff24ed4ec7" dependencies = [ "darling", "proc-macro2", @@ -2029,6 +2029,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "trim-in-place" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" + [[package]] name = "triomphe" version = "0.1.14" diff --git a/Cargo.toml b/Cargo.toml index 65d7368..c8f1ec5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,8 @@ edition = "2021" [dependencies] serenity = "0.12.4" -poise = "0.6" +# Git 上では修正されているが、crates.io にはまだ反映されていないため、git で指定 +poise = { git = "https://github.com/serenity-rs/poise", rev = "db10b126c8b07f7e1924cba1672f04ff24ed4ec7" } regex = "1.0" similar = "2.6.0" dashmap = "6.1.0" From da757490f1e7ef1f6b5edd7d5d99fbb010edcb5f Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sun, 22 Dec 2024 08:23:44 +0900 Subject: [PATCH 029/104] =?UTF-8?q?=E3=82=B3=E3=83=9E=E3=83=B3=E3=83=89?= =?UTF-8?q?=E5=AE=9F=E8=A1=8C=E6=99=82=E3=81=AB=E3=83=AD=E3=83=BC=E3=83=AB?= =?UTF-8?q?=E3=81=AE=E6=9C=89=E7=84=A1=E3=82=92=E3=83=81=E3=82=A7=E3=83=83?= =?UTF-8?q?=E3=82=AF=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 25 +++++++++++++++++++++++-- Cargo.toml | 1 + src/error.rs | 7 +++++++ src/features/admin.rs | 4 +--- src/features/auth.rs | 4 +--- src/features/mod.rs | 5 +---- src/features/question/command.rs | 6 ++++-- src/features/thread_pin.rs | 7 ++++--- src/main.rs | 15 ++++++++++++++- src/utils.rs | 18 +++++++++++++++++- 10 files changed, 73 insertions(+), 19 deletions(-) create mode 100644 src/error.rs diff --git a/Cargo.lock b/Cargo.lock index 808bf2d..c0ae162 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1788,7 +1788,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +dependencies = [ + "thiserror-impl 2.0.9", ] [[package]] @@ -1802,6 +1811,17 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "thiserror-impl" +version = "2.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -2063,7 +2083,7 @@ dependencies = [ "rustls 0.22.4", "rustls-pki-types", "sha1", - "thiserror", + "thiserror 1.0.69", "url", "utf-8", ] @@ -2176,6 +2196,7 @@ dependencies = [ "serde_with", "serenity", "similar", + "thiserror 2.0.9", "tokio", "toml", "tracing", diff --git a/Cargo.toml b/Cargo.toml index c8f1ec5..2d65bd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ tracing = "0.1" tracing-subscriber = "0.3" serde_with = "3" toml = "0.8" +thiserror = "2" tokio = { version = "1.0", features = ["rt-multi-thread", "macros", "signal"] } serde = { version = "1.0", features = ["derive"] } bpaf = { version = "0.9", features = ["derive"] } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..c58266e --- /dev/null +++ b/src/error.rs @@ -0,0 +1,7 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum BotError { + #[error("このコマンドの実行に必要なロールがありません。")] + HasNoRole, +} diff --git a/src/features/admin.rs b/src/features/admin.rs index 23eda30..a987420 100644 --- a/src/features/admin.rs +++ b/src/features/admin.rs @@ -3,9 +3,7 @@ use std::sync::Arc; use poise::say_reply; use tokio::fs::read_to_string; -use crate::{config::Config, features::PError}; - -use super::PContext; +use crate::{config::Config, PContext, PError}; /// コンフィグを再読み込み #[poise::command(slash_command, ephemeral, owners_only, dm_only)] diff --git a/src/features/auth.rs b/src/features/auth.rs index 8299e3d..ea59b2a 100644 --- a/src/features/auth.rs +++ b/src/features/auth.rs @@ -4,10 +4,8 @@ use serenity::futures::{self, Stream, StreamExt}; use tracing::error; use crate::config::get_config; -use crate::features::PError; use crate::utils::{create_message, send_message}; - -use super::PContext; +use crate::{PContext, PError}; async fn autocomplete_keyword<'a>(ctx: PContext<'_>, partial: &'a str) -> impl Stream + 'a { let config = &get_config(ctx.serenity_context()).await.auth; diff --git a/src/features/mod.rs b/src/features/mod.rs index 34c1f26..3b8aef8 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -15,10 +15,7 @@ pub use thread_channel_startup::Handler as ThreadChannelStartupHandler; pub use message_cache::{MessageCache, MessageCacheType}; -pub type PError = Box; -pub struct CommandData {} -pub type PContext<'a> = poise::Context<'a, CommandData, PError>; -pub type PCommand = poise::Command; +use crate::PCommand; pub fn commands() -> Vec { build_commands(vec![ diff --git a/src/features/question/command.rs b/src/features/question/command.rs index d26669f..62198f5 100644 --- a/src/features/question/command.rs +++ b/src/features/question/command.rs @@ -13,7 +13,8 @@ use crate::config::get_config; use crate::features::question::modal::{BasicQuestionData, DetailedQuestionData}; use crate::features::question::question_creation_handler::{CustomIds, QuestionCreationHandler}; use crate::features::question::QUESTION_CLOSE_PREFIX; -use crate::features::{CommandData, PError}; +use crate::utils::has_authed_role; +use crate::{CommandData, PError}; fn reaction_from_forum_emoji(emoji: &ForumEmoji) -> Option { match emoji.clone() { @@ -62,7 +63,8 @@ fn create_select_menu( guild_only, aliases("質問開始"), member_cooldown = 60, - required_bot_permissions = "CREATE_PUBLIC_THREADS" + required_bot_permissions = "CREATE_PUBLIC_THREADS", + check = "has_authed_role" )] pub async fn question(ctx: ApplicationContext<'_, CommandData, PError>) -> Result<(), PError> { let custom_ids = Arc::new(CustomIds::new(ctx.id())); diff --git a/src/features/thread_pin.rs b/src/features/thread_pin.rs index 8a90aa1..4fe9897 100644 --- a/src/features/thread_pin.rs +++ b/src/features/thread_pin.rs @@ -6,8 +6,8 @@ use serenity::{ futures::StreamExt, }; -use super::PContext; -use crate::{config::get_config, PError}; +use crate::utils::has_authed_role; +use crate::{config::get_config, PContext, PError}; /// スレッド主限定でメッセージをピン留めします。 #[poise::command( @@ -16,7 +16,8 @@ use crate::{config::get_config, PError}; ephemeral, guild_only, aliases("ピン留め"), - required_bot_permissions = "MANAGE_MESSAGES" + required_bot_permissions = "MANAGE_MESSAGES", + check = "has_authed_role" )] pub async fn pin( ctx: PContext<'_>, diff --git a/src/main.rs b/src/main.rs index 3a5bb44..476b3fd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ mod config; +mod error; mod features; mod utils; @@ -6,7 +7,7 @@ use std::{fs::read_to_string, sync::Arc}; use bpaf::Bpaf; use config::Config; -use features::{commands, CommandData, MessageCache, MessageCacheType, PError}; +use features::{commands, MessageCache, MessageCacheType}; use poise::{say_reply, Framework, FrameworkError, FrameworkOptions}; use serenity::{ all::{MessageParseError, Ready}, @@ -16,6 +17,11 @@ use serenity::{ }; use tracing::{error, info}; +pub type PError = Box; +pub struct CommandData {} +pub type PContext<'a> = poise::Context<'a, CommandData, PError>; +pub type PCommand = poise::Command; + struct MainHandler; #[async_trait] @@ -81,6 +87,13 @@ pub async fn on_error(error: FrameworkError<'_, CommandData, PError>) { ) .await; } + FrameworkError::CommandCheckFailed { ctx, error, .. } => { + let error = match error { + Some(error) => error.to_string(), + None => "コマンドの実行条件を満たしていません。".to_string(), + }; + let _ = say_reply(ctx, error).await; + } error => { if let Err(e) = poise::builtins::on_error(error).await { println!("Error while handling error: {}", e) diff --git a/src/utils.rs b/src/utils.rs index 67f5245..da7ec2c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -8,7 +8,7 @@ use serenity::{ use similar::{Algorithm, ChangeTag, TextDiff}; use tracing::error; -use crate::MessageCacheType; +use crate::{config::get_config, error::BotError, MessageCacheType, PContext, PError}; pub fn create_safe_message() -> CreateMessage { CreateMessage::new().allowed_mentions(CreateAllowedMentions::new().all_users(false)) @@ -53,6 +53,22 @@ pub async fn await_initial_message(ctx: &Context, thread: &GuildChannel) -> bool false } +/* +認証済みロールを持っているかどうかを確認します。 +*/ +pub async fn has_authed_role(ctx: PContext<'_>) -> Result { + let Some(member) = ctx.author_member().await else { + return Ok(false); + }; + + let config = &get_config(ctx.serenity_context()).await.auth; + if !member.roles.contains(&config.role_id) { + Err(BotError::HasNoRole.into()) + } else { + Ok(true) + } +} + pub async fn send_message(ctx: &Context, channel_id: &ChannelId, builder: CreateMessage) -> Result { match channel_id.send_message(&ctx.http, builder).await { Ok(m) => Ok(m), From d0ff0710b144b0cfc16cb32de2de42e0ea35508d Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Fri, 27 Dec 2024 01:38:49 +0900 Subject: [PATCH 030/104] =?UTF-8?q?=E8=B3=AA=E5=95=8F=E3=82=B9=E3=83=AC?= =?UTF-8?q?=E3=83=83=E3=83=89=E3=81=A7=E3=82=82=E3=83=94=E3=83=B3=E7=95=99?= =?UTF-8?q?=E3=82=81=E3=82=B3=E3=83=9E=E3=83=B3=E3=83=89=E3=81=8C=E5=88=A9?= =?UTF-8?q?=E7=94=A8=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/thread_pin.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/features/thread_pin.rs b/src/features/thread_pin.rs index 4fe9897..96193f9 100644 --- a/src/features/thread_pin.rs +++ b/src/features/thread_pin.rs @@ -24,9 +24,20 @@ pub async fn pin( #[description = "ピン留めするメッセージ (リンクかID)"] msg: Message, ) -> Result<(), PError> { let channel = ctx.guild_channel().await.unwrap(); - let Some(owner) = channel.owner_id else { - say_reply(ctx, "スレッド以外のチャンネルでは使用出来ません。").await?; - return Ok(()); + let config = get_config(ctx.serenity_context()).await; + + let owner = match ( + channel.owner_id, + channel.parent_id.unwrap_or_default() == config.question.forum_id, + ) { + // 質問フォーラムの場合、初期メッセージのメンションからスレッド主を取得 + // スレッドの初期メッセージのIDはスレッドのIDと同じ + (_, true) => channel.message(ctx, channel.id.get()).await?.mentions[0].id, + (Some(owner), false) => owner, + (None, _) => { + say_reply(ctx, "スレッド以外のチャンネルでは使用出来ません。").await?; + return Ok(()); + } }; if ctx.author().id != owner { @@ -34,12 +45,11 @@ pub async fn pin( return Ok(()); } - let bot_id = get_config(ctx.serenity_context()).await.bot.application_id; let mut stream = channel .await_reply(&ctx.serenity_context().shard) .timeout(Duration::from_secs(5)) .channel_id(channel.id) - .author_id(bot_id) + .author_id(config.bot.application_id) .filter(|r| r.kind == MessageType::PinsAdd) .stream(); From a21c5e8d651229dd148f5e9553c40b6905f8681c Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Wed, 12 Feb 2025 10:12:09 +0900 Subject: [PATCH 031/104] =?UTF-8?q?=E9=81=8E=E5=8E=BB=E3=83=A1=E3=83=83?= =?UTF-8?q?=E3=82=BB=E3=83=BC=E3=82=B8=E3=81=AE=E3=82=AD=E3=83=A3=E3=83=83?= =?UTF-8?q?=E3=82=B7=E3=83=A5=E6=A9=9F=E8=83=BD=E3=82=92=E3=82=B9=E3=83=AC?= =?UTF-8?q?=E3=83=83=E3=83=89=E3=81=AB=E5=AF=BE=E5=BF=9C=E3=81=95=E3=81=9B?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 520 ++++++++++-------- Cargo.toml | 10 +- config.sample.toml | 9 +- src/config.rs | 2 + .../cache.rs} | 45 +- src/features/message_cache/mod.rs | 112 ++++ src/main.rs | 6 +- src/utils.rs | 71 ++- 8 files changed, 503 insertions(+), 272 deletions(-) rename src/features/{message_cache.rs => message_cache/cache.rs} (56%) create mode 100644 src/features/message_cache/mod.rs diff --git a/Cargo.lock b/Cargo.lock index c0ae162..1003a0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,15 +50,37 @@ dependencies = [ "serde", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.98", ] [[package]] @@ -102,9 +124,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] name = "block-buffer" @@ -117,29 +139,29 @@ dependencies = [ [[package]] name = "bpaf" -version = "0.9.15" +version = "0.9.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50fd5174866dc2fa2ddc96e8fb800852d37f064f32a45c7b7c2f8fa2c64c77fa" +checksum = "913d667d4716acd286a0dc58824a4c0ec8ce58eeca95cfb58172d17a9ec01035" dependencies = [ "bpaf_derive", ] [[package]] name = "bpaf_derive" -version = "0.5.13" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf95d9c7e6aba67f8fc07761091e93254677f4db9e27197adecebc7039a58722" +checksum = "34d8a24f809c4cda0832689019daa067d0ae927d801429196b238a3e8cb0cd3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.98", ] [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bytecount" @@ -155,9 +177,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" [[package]] name = "camino" @@ -170,9 +192,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] @@ -192,9 +214,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.37" +version = "1.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40545c26d092346d8a8dab71ee48e7685a7a9cba76e634790c215b41a4a7b4cf" +checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda" dependencies = [ "shlex", ] @@ -207,9 +229,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -247,9 +269,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -265,18 +287,18 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crypto-common" @@ -309,7 +331,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.87", + "syn 2.0.98", ] [[package]] @@ -320,7 +342,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.87", + "syn 2.0.98", ] [[package]] @@ -353,9 +375,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" [[package]] name = "deranged" @@ -396,7 +418,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.98", ] [[package]] @@ -422,12 +444,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -441,15 +463,15 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "flate2" -version = "1.0.34" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", @@ -478,6 +500,7 @@ checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -500,6 +523,17 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.31" @@ -514,7 +548,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.98", ] [[package]] @@ -574,7 +608,19 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.6", ] [[package]] @@ -585,9 +631,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "h2" @@ -601,7 +647,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.6.0", + "indexmap 2.7.1", "slab", "tokio", "tokio-util", @@ -622,15 +668,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" - -[[package]] -name = "hermit-abi" -version = "0.3.9" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "hex" @@ -651,9 +691,9 @@ dependencies = [ [[package]] name = "http" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -673,9 +713,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.5" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" [[package]] name = "httpdate" @@ -685,9 +725,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.31" +version = "0.14.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" dependencies = [ "bytes", "futures-channel", @@ -859,7 +899,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.98", ] [[package]] @@ -902,42 +942,43 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", - "hashbrown 0.15.1", + "hashbrown 0.15.2", "serde", ] [[package]] name = "ipnet" -version = "2.10.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "itertools" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -955,21 +996,21 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.162" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "litemap" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "lock_api" @@ -983,9 +1024,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "memchr" @@ -1026,22 +1067,21 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ "adler2", ] [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -1072,18 +1112,18 @@ dependencies = [ [[package]] name = "object" -version = "0.36.5" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.20.2" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "overload" @@ -1122,9 +1162,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -1140,7 +1180,7 @@ dependencies = [ "async-trait", "derivative", "futures-util", - "indexmap 2.6.0", + "indexmap 2.7.1", "parking_lot", "poise_macros", "regex", @@ -1158,7 +1198,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.98", ] [[package]] @@ -1178,9 +1218,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -1191,16 +1231,16 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "memchr", "unicase", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -1232,16 +1272,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", ] [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", ] [[package]] @@ -1258,9 +1298,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -1325,7 +1365,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.15", "libc", "spin", "untrusted", @@ -1340,15 +1380,15 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.40" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1388,9 +1428,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] name = "rustls-webpki" @@ -1413,11 +1453,17 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "same-file" @@ -1456,18 +1502,18 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.23" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.214" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] @@ -1483,20 +1529,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.98", ] [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" dependencies = [ "itoa", "memchr", @@ -1527,15 +1573,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.11.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" +checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.6.0", + "indexmap 2.7.1", "serde", "serde_derive", "serde_json", @@ -1545,14 +1591,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.11.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" +checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.98", ] [[package]] @@ -1564,7 +1610,7 @@ dependencies = [ "arrayvec", "async-trait", "base64 0.22.1", - "bitflags 2.6.0", + "bitflags 2.8.0", "bytes", "chrono", "command_attr", @@ -1629,9 +1675,9 @@ dependencies = [ [[package]] name = "similar" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "skeptic" @@ -1665,9 +1711,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1716,9 +1762,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.87" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", @@ -1739,7 +1785,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.98", ] [[package]] @@ -1771,15 +1817,16 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tempfile" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" dependencies = [ "cfg-if", "fastrand", + "getrandom 0.3.1", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1793,11 +1840,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.9" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ - "thiserror-impl 2.0.9", + "thiserror-impl 2.0.11", ] [[package]] @@ -1808,18 +1855,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.98", ] [[package]] name = "thiserror-impl" -version = "2.0.9" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.98", ] [[package]] @@ -1834,9 +1881,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -1855,9 +1902,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -1875,9 +1922,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.41.1" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", "bytes", @@ -1892,13 +1939,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.98", ] [[package]] @@ -1935,14 +1982,14 @@ dependencies = [ "tokio", "tokio-rustls 0.25.0", "tungstenite", - "webpki-roots 0.26.6", + "webpki-roots 0.26.8", ] [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -1953,9 +2000,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", "serde_spanned", @@ -1974,11 +2021,11 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.1", "serde", "serde_spanned", "toml_datetime", @@ -1993,9 +2040,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -2005,20 +2052,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.98", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -2037,9 +2084,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "nu-ansi-term", "sharded-slab", @@ -2076,7 +2123,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.1.0", + "http 1.2.0", "httparse", "log", "rand", @@ -2102,9 +2149,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "typesize" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "549e54551d85ba6718a95333d9bc4367f69793d7aba638de30f8d25a1f554a1d" +checksum = "20304d891be0766f52123746c721d1190b953e874f9eccf29067a64c1a0ae16c" dependencies = [ "chrono", "dashmap 5.5.3", @@ -2120,26 +2167,26 @@ dependencies = [ [[package]] name = "typesize-derive" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd9fc0ad9e03a2b0c2e2a0eafaecccef2121829e1ab6ce9c9d790e6c6766bd1c" +checksum = "536b6812192bda8551cfa0e52524e328c6a951b48e66529ee4522d6c721243d6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.98", ] [[package]] name = "unicase" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "untrusted" @@ -2149,9 +2196,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.3" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -2185,10 +2232,12 @@ checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0" [[package]] name = "valine_bot" -version = "1.3.0" +version = "2.2.0" dependencies = [ + "async-stream", "bpaf", "dashmap 6.1.0", + "futures", "itertools", "poise", "regex", @@ -2196,7 +2245,7 @@ dependencies = [ "serde_with", "serenity", "similar", - "thiserror 2.0.9", + "thiserror 2.0.11", "tokio", "toml", "tracing", @@ -2205,9 +2254,9 @@ dependencies = [ [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "version_check" @@ -2240,49 +2289,59 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.98", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2290,22 +2349,25 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.98", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-streams" @@ -2322,9 +2384,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -2338,9 +2400,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "0.26.6" +version = "0.26.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" +checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" dependencies = [ "rustls-pki-types", ] @@ -2367,7 +2429,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2403,6 +2465,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -2526,9 +2597,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.20" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603" dependencies = [ "memchr", ] @@ -2543,6 +2614,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.8.0", +] + [[package]] name = "write16" version = "1.0.0" @@ -2557,9 +2637,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "yoke" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", @@ -2569,13 +2649,13 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.98", "synstructure", ] @@ -2597,27 +2677,27 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.98", ] [[package]] name = "zerofrom" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.98", "synstructure", ] @@ -2646,5 +2726,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.98", ] diff --git a/Cargo.toml b/Cargo.toml index 2d65bd8..a0d3e46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "valine_bot" -version = "1.3.0" +version = "2.2.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -10,14 +10,16 @@ serenity = "0.12.4" # Git 上では修正されているが、crates.io にはまだ反映されていないため、git で指定 poise = { git = "https://github.com/serenity-rs/poise", rev = "db10b126c8b07f7e1924cba1672f04ff24ed4ec7" } regex = "1.0" -similar = "2.6.0" -dashmap = "6.1.0" -itertools = "0.13" +similar = "2.7" +dashmap = "6.1" +itertools = "0.14" tracing = "0.1" tracing-subscriber = "0.3" serde_with = "3" toml = "0.8" thiserror = "2" +async-stream = "0.3" +futures = "0.3" tokio = { version = "1.0", features = ["rt-multi-thread", "macros", "signal"] } serde = { version = "1.0", features = ["derive"] } bpaf = { version = "0.9", features = ["derive"] } diff --git a/config.sample.toml b/config.sample.toml index 53c7d65..aab0c74 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -22,10 +22,15 @@ channel_id = "000000000000000000" [message_cache] -# キャッシュを無効化するかどうか -disabled = true +# 過去メッセージのキャッシュを無効化するかどうか +disabled = false +# キャッシュする過去メッセージの最大数 +limit = 1000 # 過去のメッセージのキャッシュを取るギルドIDのリスト +# target_guild_ids = ["651286346861641735"] target_guild_ids = ["000000000000000000"] +# 過去メッセージのキャッシュ取らないチャンネルIDのリスト、親チャンネルを指定すると子のチャンネルも対象 (孫は対象外) +ignore_channel_ids = ["000000000000000000"] [thread_channel_startup] diff --git a/src/config.rs b/src/config.rs index b6cac01..f29f345 100644 --- a/src/config.rs +++ b/src/config.rs @@ -54,7 +54,9 @@ pub struct MessageLoggingConfig { #[derive(Debug, Deserialize)] pub struct MessageCacheConfig { pub disabled: bool, + pub limit: usize, pub target_guild_ids: Vec, + pub ignore_channel_ids: Vec, } #[derive(Debug, Deserialize)] diff --git a/src/features/message_cache.rs b/src/features/message_cache/cache.rs similarity index 56% rename from src/features/message_cache.rs rename to src/features/message_cache/cache.rs index 6ec6449..9299847 100644 --- a/src/features/message_cache.rs +++ b/src/features/message_cache/cache.rs @@ -3,13 +3,9 @@ use std::{collections::HashMap, sync::Arc}; use dashmap::DashMap; use itertools::Itertools; use serenity::{ - all::{ChannelId, Context, EventHandler, GetMessages, GuildId, Message, MessageId}, - async_trait, + all::{ChannelId, Message, MessageId}, prelude::TypeMapKey, }; -use tracing::{error, info}; - -use crate::config::get_config; pub struct MessageCache { cache: DashMap>, @@ -68,42 +64,3 @@ pub struct MessageCacheType; impl TypeMapKey for MessageCacheType { type Value = Arc; } - -pub struct Handler { - pub disabled: bool, -} - -#[async_trait] -impl EventHandler for Handler { - async fn cache_ready(&self, ctx: Context, _: Vec) { - if self.disabled { - return; - } - - let config = get_config(&ctx).await; - for guild in &config.message_cache.target_guild_ids { - let Ok(channels) = guild.channels(&ctx.http).await else { - error!("Failed to get channels for guild: {:?}", guild); - continue; - }; - - for (id, channel) in channels { - if !channel.is_text_based() { - continue; - } - - let Ok(messages) = channel.messages(&ctx.http, GetMessages::new().limit(100)).await else { - info!("Failed to get messages for channel: {:?}", channel.name); - continue; - }; - - let mut data = ctx.data.write().await; - let cache = data.get_mut::().unwrap(); - let len = messages.len(); - cache.extend_messages(messages); - info!("Cached {} messages for channel: {} ({})", len, channel.name, id); - } - } - info!("Cache ready!"); - } -} diff --git a/src/features/message_cache/mod.rs b/src/features/message_cache/mod.rs new file mode 100644 index 0000000..e8b2563 --- /dev/null +++ b/src/features/message_cache/mod.rs @@ -0,0 +1,112 @@ +mod cache; + +pub use cache::{MessageCache, MessageCacheType}; + +use async_stream::stream; +use futures::StreamExt; +use serenity::{ + all::{Context, EventHandler, Guild, GuildChannel, GuildId, Member}, + async_trait, +}; +use tracing::{error, info}; + +use crate::{ + config::{get_config, Config}, + utils::fetch_all_archived_public_thread, +}; + +pub struct Handler { + pub disabled: bool, +} + +impl Handler { + async fn cache_channel_message( + &self, + ctx: &Context, + config: &Config, + channel: GuildChannel, + guild: &Guild, + bot_member: &Member, + ) { + if !channel.is_text_based() { + return; + } + + let is_ignored = std::iter::once(channel.id) + .chain(channel.parent_id.into_iter()) + .any(|id| config.message_cache.ignore_channel_ids.contains(&id)); + if is_ignored { + return; + } + + if !guild.user_permissions_in(&channel, bot_member).read_message_history() { + return; + } + + let messages = channel + .id + .messages_iter(&ctx) + .take(config.message_cache.limit) + .filter_map(|x| async { x.ok() }) + .collect::>() + .await; + + let mut data = ctx.data.write().await; + let cache = data.get_mut::().unwrap(); + let len = messages.len(); + cache.extend_messages(messages); + info!("Cached {} messages for channel: {} ({})", len, channel.name, channel.id); + } +} + +#[async_trait] +impl EventHandler for Handler { + async fn cache_ready(&self, ctx: Context, _: Vec) { + if self.disabled { + return; + } + + let ctx_ref = &ctx; + let config = get_config(ctx_ref).await; + + for guild_id in &config.message_cache.target_guild_ids { + let guild = match guild_id.to_guild_cached(ctx_ref) { + Some(guild) => guild.clone(), + None => { + error!("Failed to get guild: {:?}", guild_id); + continue; + } + }; + + let Ok(bot_member) = guild.member(ctx_ref, config.bot.application_id).await else { + error!("Failed to get bot member for guild: {:?}", guild_id); + continue; + }; + + let Ok(channels) = guild_id.channels(ctx_ref).await else { + error!("Failed to get channels for guild: {:?}", guild_id); + continue; + }; + + let active_threads = guild.threads.clone(); + let _ = stream! { + for thread in active_threads { + yield thread; + } + + for (id, channel) in channels { + yield channel; + + for await thread in fetch_all_archived_public_thread(ctx_ref, id, None).await { + yield thread; + } + } + } + .map(|channel| self.cache_channel_message(ctx_ref, &config, channel, &guild, &bot_member)) + .buffered(10) + .collect::>() + .await; + } + info!("Cache ready!"); + } +} diff --git a/src/main.rs b/src/main.rs index 476b3fd..c34c233 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,7 @@ use config::Config; use features::{commands, MessageCache, MessageCacheType}; use poise::{say_reply, Framework, FrameworkError, FrameworkOptions}; use serenity::{ - all::{MessageParseError, Ready}, + all::{MessageParseError, RatelimitInfo, Ready}, async_trait, cache::Settings as CacheSettings, prelude::*, @@ -29,6 +29,10 @@ impl EventHandler for MainHandler { async fn ready(&self, _: Context, ready: Ready) { info!("{} is connected!", ready.user.name); } + + async fn ratelimit(&self, data: RatelimitInfo) { + info!("Ratelimited {}: {}s", data.path, data.timeout.as_secs()); + } } #[derive(Clone, Debug, Bpaf)] diff --git a/src/utils.rs b/src/utils.rs index da7ec2c..7a538ad 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,8 +1,13 @@ use std::time::Duration; +use async_stream::stream; +use futures::Stream; use itertools::Itertools; use serenity::{ - all::{ChannelId, Context, CreateAllowedMentions, CreateMessage, GuildChannel, Message, MessageId}, + all::{ + ChannelId, Context, CreateAllowedMentions, CreateMessage, GuildChannel, Http, LightMethod, Message, MessageId, + Request, Route, ThreadsData, Timestamp, + }, Result, }; use similar::{Algorithm, ChangeTag, TextDiff}; @@ -93,6 +98,70 @@ pub async fn get_cached_message(ctx: &Context, channel_id: ChannelId, message_id None } +/** +指定されたチャンネルのアーカイブされたパブリックスレッドを取得します。 +- `ChannelId::get_archived_public_threads`は Discord の要求に従っていないので、自前で実装しています。 + - Serenity の next ブランチで修正済み +*/ +pub async fn fetch_channel_archived_public_threads( + http: impl AsRef, + channel_id: ChannelId, + before: Option, + limit: Option, +) -> Result { + let mut params = vec![]; + if let Some(before) = before { + params.push(("before", before.to_string())); + } + if let Some(limit) = limit { + params.push(("limit", limit.to_string())); + } + + http.as_ref() + .fire(Request::new(Route::ChannelArchivedPublicThreads { channel_id }, LightMethod::Get).params(Some(params))) + .await +} + +/** +指定されたチャンネルのアーカイブされたパブリックスレッドをすべて取得します。 + */ +pub async fn fetch_all_archived_public_thread( + ctx: &Context, + channel_id: ChannelId, + max_retries: Option, +) -> impl Stream + '_ { + let max_retries = max_retries.unwrap_or(5); + Box::pin(stream! { + let mut retries_left = max_retries; + let mut before = None; + loop { + let thread_data = match fetch_channel_archived_public_threads(&ctx, channel_id, before, Some(100)).await { + Ok(data) => data, + Err(_) => { + if retries_left == 0 { + break; + } else { + retries_left -= 1; + continue; + } + } + }; + + before = thread_data.threads + .last() + .and_then(|last| last.thread_metadata.unwrap().archive_timestamp); + + for channel in thread_data.threads { + yield channel; + } + + if !thread_data.has_more || before.is_none() { + break; + } + } + }) +} + pub fn create_diff_lines_text(old: &str, new: &str) -> String { let diff = TextDiff::configure().algorithm(Algorithm::Myers).diff_lines(old, new); diff.iter_all_changes() From b67923730586691e711e6185ab92fc4dab115637 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Thu, 13 Feb 2025 08:36:12 +0900 Subject: [PATCH 032/104] =?UTF-8?q?=E5=90=88=E8=A8=80=E8=91=89=E3=81=AE?= =?UTF-8?q?=E5=85=A5=E5=8A=9B=E3=82=92=E3=83=A2=E3=83=BC=E3=83=80=E3=83=AB?= =?UTF-8?q?=E3=81=A7=E8=A1=8C=E3=81=86=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 64 ++++++++- Cargo.toml | 1 + src/error.rs | 69 ++++++++++ src/features/auth.rs | 220 +++++++++++++++++++++++------- src/features/message_cache/mod.rs | 6 +- src/features/mod.rs | 3 +- src/features/question/mod.rs | 50 +++---- src/main.rs | 74 +--------- src/utils.rs | 21 ++- 9 files changed, 350 insertions(+), 158 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1003a0c..1a70b6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1213,7 +1213,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -1252,8 +1252,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.0", + "zerocopy 0.8.17", ] [[package]] @@ -1263,7 +1274,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.0", ] [[package]] @@ -1275,6 +1296,16 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +dependencies = [ + "getrandom 0.3.1", + "zerocopy 0.8.17", +] + [[package]] name = "redox_syscall" version = "0.5.8" @@ -2126,7 +2157,7 @@ dependencies = [ "http 1.2.0", "httparse", "log", - "rand", + "rand 0.8.5", "rustls 0.22.4", "rustls-pki-types", "sha1", @@ -2240,6 +2271,7 @@ dependencies = [ "futures", "itertools", "poise", + "rand 0.9.0", "regex", "serde", "serde_with", @@ -2666,7 +2698,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa91407dacce3a68c56de03abe2760159582b846c6a4acd2f456618087f12713" +dependencies = [ + "zerocopy-derive 0.8.17", ] [[package]] @@ -2680,6 +2721,17 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06718a168365cad3d5ff0bb133aad346959a2074bd4a85c121255a11304a8626" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "zerofrom" version = "0.1.5" diff --git a/Cargo.toml b/Cargo.toml index a0d3e46..7f202a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" serenity = "0.12.4" # Git 上では修正されているが、crates.io にはまだ反映されていないため、git で指定 poise = { git = "https://github.com/serenity-rs/poise", rev = "db10b126c8b07f7e1924cba1672f04ff24ed4ec7" } +rand = "0.9" regex = "1.0" similar = "2.7" dashmap = "6.1" diff --git a/src/error.rs b/src/error.rs index c58266e..d449b6a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,76 @@ +use poise::{say_reply, FrameworkError}; +use serenity::all::MessageParseError; use thiserror::Error; +use tracing::error; + +use crate::{CommandData, PError}; #[derive(Error, Debug)] pub enum BotError { #[error("このコマンドの実行に必要なロールがありません。")] HasNoRole, } + +pub async fn on_error(error: FrameworkError<'_, CommandData, PError>) { + match error { + FrameworkError::Command { error, ctx, .. } => { + let _ = say_reply(ctx, "コマンド実行中にエラーが発生しました。").await; + error!("Command error: Command: {:?}, Error: {:?}", ctx.command(), error); + } + FrameworkError::ArgumentParse { ctx, input, error, .. } => { + let Some(input) = input else { + return error!("Error parsing input: {:?}", error); + }; + + let error = match error.downcast_ref::() { + Some(MessageParseError::Malformed) => { + "メッセージとして解析できませんでした。\nメッセージID、メッセージURL形式で入力してください。" + } + Some(MessageParseError::Http(_)) => "メッセージを取得できませんでした。", + _ => &error.to_string(), + }; + + let _ = say_reply(ctx, format!("入力 `{}` の解析に失敗しました。\n{}", input, error)).await; + } + FrameworkError::MissingBotPermissions { + missing_permissions, + ctx, + .. + } => { + let msg = format!( + "ボットに権限が無いためコマンドを実行できません: {}", + missing_permissions, + ); + let _ = say_reply(ctx, msg).await; + } + FrameworkError::NotAnOwner { ctx, .. } => { + let _ = say_reply(ctx, "このコマンドはボットのオーナーのみ実行できます。").await; + } + FrameworkError::CooldownHit { + remaining_cooldown, + ctx, + .. + } => { + let _ = say_reply( + ctx, + format!( + "このコマンドはクールダウン中です。残り時間: {}秒", + remaining_cooldown.as_secs() + ), + ) + .await; + } + FrameworkError::CommandCheckFailed { ctx, error, .. } => { + let error = match error { + Some(error) => error.to_string(), + None => "コマンドの実行条件を満たしていません。".to_string(), + }; + let _ = say_reply(ctx, error).await; + } + error => { + if let Err(e) = poise::builtins::on_error(error).await { + println!("Error while handling error: {}", e) + } + } + } +} diff --git a/src/features/auth.rs b/src/features/auth.rs index ea59b2a..40c5ff9 100644 --- a/src/features/auth.rs +++ b/src/features/auth.rs @@ -1,66 +1,190 @@ +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use dashmap::DashMap; use poise::say_reply; -use serenity::all::Mentionable; -use serenity::futures::{self, Stream, StreamExt}; +use rand::seq::IndexedRandom; +use serenity::all::{ + ActionRowComponent, ButtonStyle, ComponentInteractionDataKind, Context, CreateActionRow, CreateButton, + CreateInputText, CreateInteractionResponse, CreateInteractionResponseFollowup, CreateModal, EventHandler, + InputTextStyle, Interaction, Mentionable, ModalInteractionCollector, Ready, UserId, +}; +use serenity::async_trait; use tracing::error; use crate::config::get_config; -use crate::utils::{create_message, send_message}; +use crate::utils::{create_interaction_message, create_message, send_message}; use crate::{PContext, PError}; -async fn autocomplete_keyword<'a>(ctx: PContext<'_>, partial: &'a str) -> impl Stream + 'a { - let config = &get_config(ctx.serenity_context()).await.auth; - futures::stream::iter(config.dummy_keywords.clone()) - .filter(move |name| futures::future::ready(name.starts_with(partial))) - .map(|name| name.to_string()) +static KEYWORD_INPUT_BUTTON: &str = "keyword_input:button"; +static AUTH_COOLDOWN: Duration = Duration::from_secs(60); + +pub struct Handler { + cooldown: Arc>, } -/// 合言葉を入力してください。 -#[poise::command( - slash_command, - ephemeral, - guild_only, - aliases("合言葉"), - member_cooldown = 60, - required_bot_permissions = "MANAGE_ROLES" -)] -pub async fn keyword( - ctx: PContext<'_>, - #[autocomplete = "autocomplete_keyword"] - #[description = "合言葉"] - keyword: String, -) -> Result<(), PError> { - let config = &get_config(ctx.serenity_context()).await.auth; +impl Handler { + pub fn new() -> Self { + Self { + cooldown: Arc::new(DashMap::new()), + } + } + + fn remaining_cooldown(&self, user_id: UserId) -> Option { + if let Some(instant) = self.cooldown.get(&user_id) { + let remaining = (AUTH_COOLDOWN - instant.elapsed()).as_secs(); + if remaining > 0 { + return Some(remaining); + } + } + None + } - if !config.trigger_regex.is_match(&keyword) { - say_reply(ctx, "合言葉が間違っています。").await?; - return Ok(()); + fn start_cooldown(&self, user_id: UserId) { + self.cooldown.insert(user_id, Instant::now()); } +} + +#[async_trait] +impl EventHandler for Handler { + async fn interaction_create(&self, ctx: Context, interaction: Interaction) { + let Interaction::Component(interaction) = interaction else { + return; + }; + let ComponentInteractionDataKind::Button = interaction.data.kind else { + return; + }; + if interaction.data.custom_id != KEYWORD_INPUT_BUTTON { + return; + } + + let config = &get_config(&ctx).await.auth; + let member = interaction.member.as_ref().unwrap(); + + if member.roles.contains(&config.role_id) { + let _ = interaction + .create_response(&ctx, create_interaction_message("既に認証済みです。", true, None)) + .await; + return; + } + + if let Some(remaining) = self.remaining_cooldown(interaction.user.id) { + let _ = interaction + .create_response( + &ctx, + create_interaction_message( + format!("クールダウン中です。\n{}秒後に再度お試しください。", remaining), + true, + None, + ), + ) + .await; + return; + } + + let mut input_text = CreateInputText::new(InputTextStyle::Short, "合言葉", "keyword") + .required(true) + .placeholder("合言葉を入力してください。"); - let member = ctx.author_member().await.unwrap(); + if let Some(value) = config.dummy_keywords.choose(&mut rand::rng()) { + input_text = input_text.value(value); + } - if member.roles.contains(&config.role_id) { - error!("{} already has the role", member.user.name); - say_reply(ctx, "すでにロールを持っています。").await?; - return Ok(()); + let custom_id = interaction.id.to_string(); + + let _ = interaction + .create_response( + &ctx, + CreateInteractionResponse::Modal( + CreateModal::new(&custom_id, "合言葉を入力してください。") + .components([CreateActionRow::InputText(input_text)].to_vec()), + ), + ) + .await; + + let Some(interaction) = ModalInteractionCollector::new(&ctx.shard) + .custom_ids(vec![custom_id]) + .timeout(Duration::from_secs(60)) + .await + else { + let _ = interaction + .create_followup( + &ctx, + CreateInteractionResponseFollowup::new() + .content("時間切れです。もう一度お試しください。") + .ephemeral(true), + ) + .await; + return; + }; + + let keyword = match interaction.data.components.first().unwrap().components.first() { + Some(ActionRowComponent::InputText(text)) => text.value.clone().unwrap(), + _ => return error!("Invalid modal interaction: {:?}", interaction), + }; + + if !config.trigger_regex.is_match(&keyword) { + let _ = interaction + .create_response(&ctx, create_interaction_message("合言葉が間違っています。", true, None)) + .await; + self.start_cooldown(interaction.user.id); + return; + } + + if let Err(why) = member.add_role(&ctx, config.role_id).await { + let log = create_message(format!( + "{} にロールを追加できませんでした。\n```\n{}```", + member.mention(), + why + )); + let _ = send_message(&ctx, &config.log_channel_id, log).await; + return error!("Failed to add role: {:?}", why); + } + + let log = create_message(format!("{} にロールを追加しました。", member.mention())); + let _ = send_message(&ctx, &config.log_channel_id, log).await; + + const AUTH_SUCCESS_MESSAGE: &str = "合言葉を確認しました。\nチャンネルが表示されない場合、アプリの再起動や再読み込み(Ctrl + R)をお試しください。"; + let _ = interaction + .create_response(&ctx, create_interaction_message(AUTH_SUCCESS_MESSAGE, true, None)) + .await; } - if let Err(why) = member.add_role(&ctx.http(), config.role_id).await { - let log = create_message(format!( - "{} にロールを追加できませんでした。\n```\n{}```", - member.mention(), - why - )); - let _ = send_message(ctx.serenity_context(), &config.log_channel_id, log).await; - error!("Failed to add role: {:?}", why); - return Ok(()); + async fn ready(&self, _: Context, _: Ready) { + let cooldown = self.cooldown.clone(); + tokio::spawn(async move { + loop { + cooldown.retain(|_, instant| instant.elapsed() < AUTH_COOLDOWN); + tokio::time::sleep(Duration::from_secs(3600)).await; + } + }); } +} + +/// 合言葉を入力するボタンを作成します。 +#[poise::command(slash_command, ephemeral, guild_only, default_member_permissions = "ADMINISTRATOR")] +pub async fn create_keyword_button( + ctx: PContext<'_>, + #[description = "ボタンの表示名"] button: String, + #[description = "メッセージ内容"] content: String, +) -> Result<(), PError> { + say_reply(ctx, "ボタンを作成しました。").await?; + + let _ = ctx + .channel_id() + .send_message( + ctx, + create_message(content).components( + [CreateActionRow::Buttons( + [CreateButton::new(KEYWORD_INPUT_BUTTON) + .label(button) + .style(ButtonStyle::Primary)] + .to_vec(), + )] + .to_vec(), + ), + ) + .await?; - let log = create_message(format!("{} にロールを追加しました。", member.mention())); - let _ = send_message(ctx.serenity_context(), &config.log_channel_id, log).await; - say_reply( - ctx, - "合言葉を確認しました。\nチャンネルが表示されない場合、アプリの再起動や再読み込み(Ctrl + R)をお試しください。", - ) - .await?; Ok(()) } diff --git a/src/features/message_cache/mod.rs b/src/features/message_cache/mod.rs index e8b2563..9167ba3 100644 --- a/src/features/message_cache/mod.rs +++ b/src/features/message_cache/mod.rs @@ -16,10 +16,14 @@ use crate::{ }; pub struct Handler { - pub disabled: bool, + disabled: bool, } impl Handler { + pub fn new(disabled: bool) -> Self { + Self { disabled } + } + async fn cache_channel_message( &self, ctx: &Context, diff --git a/src/features/mod.rs b/src/features/mod.rs index 3b8aef8..da57c2e 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -7,6 +7,7 @@ mod thread_auto_invite; mod thread_channel_startup; mod thread_pin; +pub use auth::Handler as AuthHandler; pub use logging::Handler as LoggingHandler; pub use message_cache::Handler as MessageCacheHandler; pub use question::Handler as QuestionHandler; @@ -19,7 +20,7 @@ use crate::PCommand; pub fn commands() -> Vec { build_commands(vec![ - auth::keyword, + auth::create_keyword_button, question::question, thread_pin::pin, admin::reload_config, diff --git a/src/features/question/mod.rs b/src/features/question/mod.rs index d41912e..cf03423 100644 --- a/src/features/question/mod.rs +++ b/src/features/question/mod.rs @@ -8,14 +8,13 @@ pub use command::question; use serenity::{ all::{ ButtonStyle, CacheHttp, Channel, ComponentInteractionCollector, ComponentInteractionDataKind, Context, - CreateActionRow, CreateButton, CreateInteractionResponse, CreateInteractionResponseMessage, - EditInteractionResponse, EditThread, EventHandler, Interaction, UserId, + CreateActionRow, CreateButton, EditInteractionResponse, EditThread, EventHandler, Interaction, UserId, }, async_trait, }; use tracing::error; -use crate::config::get_config; +use crate::{config::get_config, utils::create_interaction_message}; pub static QUESTION_CLOSE_PREFIX: &str = "close_question_forum"; @@ -47,14 +46,7 @@ impl EventHandler for Handler { }; if channel.applied_tags.contains(&config.solved_tag) { let _ = interaction - .create_response( - ctx.http(), - CreateInteractionResponse::Message( - CreateInteractionResponseMessage::new() - .content("既に解決済みです。") - .ephemeral(true), - ), - ) + .create_response(ctx.http(), create_interaction_message("既に解決済みです。", true, None)) .await; } @@ -64,26 +56,22 @@ impl EventHandler for Handler { let _ = interaction .create_response( ctx.http(), - CreateInteractionResponse::Message( - CreateInteractionResponseMessage::new() - .content("本当に質問を終了しますか?") - .ephemeral(true) - .components( - [CreateActionRow::Buttons( - [ - CreateButton::new(&confirm_custom_id) - .label("はい") - .emoji('✅') - .style(ButtonStyle::Danger), - CreateButton::new(&cancel_custom_id) - .label("いいえ") - .emoji('❎') - .style(ButtonStyle::Success), - ] - .to_vec(), - )] - .to_vec(), - ), + create_interaction_message( + "本当に質問を終了しますか?", + true, + Some(vec![CreateActionRow::Buttons( + [ + CreateButton::new(&confirm_custom_id) + .label("はい") + .emoji('✅') + .style(ButtonStyle::Danger), + CreateButton::new(&cancel_custom_id) + .label("いいえ") + .emoji('❎') + .style(ButtonStyle::Success), + ] + .to_vec(), + )]), ), ) .await; diff --git a/src/main.rs b/src/main.rs index c34c233..4a438a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,10 +7,11 @@ use std::{fs::read_to_string, sync::Arc}; use bpaf::Bpaf; use config::Config; +use error::on_error; use features::{commands, MessageCache, MessageCacheType}; -use poise::{say_reply, Framework, FrameworkError, FrameworkOptions}; +use poise::{Framework, FrameworkOptions}; use serenity::{ - all::{MessageParseError, RatelimitInfo, Ready}, + all::{RatelimitInfo, Ready}, async_trait, cache::Settings as CacheSettings, prelude::*, @@ -42,70 +43,6 @@ struct Options { check_config: bool, } -pub async fn on_error(error: FrameworkError<'_, CommandData, PError>) { - match error { - FrameworkError::Command { error, ctx, .. } => { - let _ = say_reply(ctx, "コマンド実行中にエラーが発生しました。").await; - error!("Command error: Command: {:?}, Error: {:?}", ctx.command(), error); - } - FrameworkError::ArgumentParse { ctx, input, error, .. } => { - let Some(input) = input else { - return error!("Error parsing input: {:?}", error); - }; - - let error = match error.downcast_ref::() { - Some(MessageParseError::Malformed) => { - "メッセージとして解析できませんでした。\nメッセージID、メッセージURL形式で入力してください。" - } - Some(MessageParseError::Http(_)) => "メッセージを取得できませんでした。", - _ => &error.to_string(), - }; - - let _ = say_reply(ctx, format!("入力 `{}` の解析に失敗しました。\n{}", input, error)).await; - } - FrameworkError::MissingBotPermissions { - missing_permissions, - ctx, - .. - } => { - let msg = format!( - "ボットに権限が無いためコマンドを実行できません: {}", - missing_permissions, - ); - let _ = say_reply(ctx, msg).await; - } - FrameworkError::NotAnOwner { ctx, .. } => { - let _ = say_reply(ctx, "このコマンドはボットのオーナーのみ実行できます。").await; - } - FrameworkError::CooldownHit { - remaining_cooldown, - ctx, - .. - } => { - let _ = say_reply( - ctx, - format!( - "このコマンドはクールダウン中です。残り時間: {}秒", - remaining_cooldown.as_secs() - ), - ) - .await; - } - FrameworkError::CommandCheckFailed { ctx, error, .. } => { - let error = match error { - Some(error) => error.to_string(), - None => "コマンドの実行条件を満たしていません。".to_string(), - }; - let _ = say_reply(ctx, error).await; - } - error => { - if let Err(e) = poise::builtins::on_error(error).await { - println!("Error while handling error: {}", e) - } - } - } -} - #[tokio::main] async fn main() { tracing_subscriber::fmt::init(); @@ -147,13 +84,12 @@ async fn main() { let mut client = Client::builder(&config.bot.token, intents) .framework(framework) .event_handler(MainHandler) + .event_handler(features::AuthHandler::new()) .event_handler(features::LoggingHandler) .event_handler(features::ThreadAutoInviteHandler) .event_handler(features::ThreadChannelStartupHandler) .event_handler(features::QuestionHandler) - .event_handler(features::MessageCacheHandler { - disabled: config.message_cache.disabled, - }) + .event_handler(features::MessageCacheHandler::new(config.message_cache.disabled)) .cache_settings(settings) .type_map_insert::(Arc::new(MessageCache::new())) .type_map_insert::(Arc::new(config)) diff --git a/src/utils.rs b/src/utils.rs index 7a538ad..2c92954 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -5,8 +5,9 @@ use futures::Stream; use itertools::Itertools; use serenity::{ all::{ - ChannelId, Context, CreateAllowedMentions, CreateMessage, GuildChannel, Http, LightMethod, Message, MessageId, - Request, Route, ThreadsData, Timestamp, + ChannelId, Context, CreateActionRow, CreateAllowedMentions, CreateInteractionResponse, + CreateInteractionResponseMessage, CreateMessage, GuildChannel, Http, LightMethod, Message, MessageId, Request, + Route, ThreadsData, Timestamp, }, Result, }; @@ -23,6 +24,22 @@ pub fn create_message(content: impl Into) -> CreateMessage { create_safe_message().content(content) } +pub fn create_interaction_message( + content: impl Into, + ephemeral: bool, + components: Option>, +) -> CreateInteractionResponse { + let mut msg = CreateInteractionResponseMessage::new() + .content(content) + .ephemeral(ephemeral); + + if let Some(components) = components { + msg = msg.components(components); + } + + CreateInteractionResponse::Message(msg) +} + /** thread_create イベントにおいて、初期メッセージが送信されるか5秒経過するまで待機する From be2576f40157ec72239583e8d4ca27a1e7fad0eb Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Thu, 13 Feb 2025 10:36:32 +0900 Subject: [PATCH 033/104] =?UTF-8?q?=E8=87=AA=E5=8B=95=E3=82=AD=E3=83=83?= =?UTF-8?q?=E3=82=AF=E6=A9=9F=E8=83=BD=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 39 ++++++++++++++++++++- Cargo.toml | 2 ++ config.sample.toml | 14 +++++++- src/config.rs | 11 ++++++ src/features/auth.rs | 16 +++++++-- src/features/auto_kick.rs | 73 +++++++++++++++++++++++++++++++++++++++ src/features/mod.rs | 2 ++ src/main.rs | 1 + 8 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 src/features/auto_kick.rs diff --git a/Cargo.lock b/Cargo.lock index 1a70b6e..1eb560f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -235,8 +235,10 @@ checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-targets 0.52.6", ] @@ -421,6 +423,20 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "duration-str" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ad6b66883f70e2f38f1ee99e3797b9d7e7b7fb051ed2e23e027c81753056c8" +dependencies = [ + "chrono", + "rust_decimal", + "serde", + "thiserror 2.0.11", + "time", + "winnow 0.6.26", +] + [[package]] name = "either" version = "1.13.0" @@ -1403,6 +1419,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rust_decimal" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" +dependencies = [ + "arrayvec", + "num-traits", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -2060,7 +2086,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.7.2", ] [[package]] @@ -2267,7 +2293,9 @@ version = "2.2.0" dependencies = [ "async-stream", "bpaf", + "chrono", "dashmap 6.1.0", + "duration-str", "futures", "itertools", "poise", @@ -2627,6 +2655,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.6.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28" +dependencies = [ + "memchr", +] + [[package]] name = "winnow" version = "0.7.2" diff --git a/Cargo.toml b/Cargo.toml index 7f202a7..82e2021 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,12 +11,14 @@ serenity = "0.12.4" poise = { git = "https://github.com/serenity-rs/poise", rev = "db10b126c8b07f7e1924cba1672f04ff24ed4ec7" } rand = "0.9" regex = "1.0" +chrono = "0.4" similar = "2.7" dashmap = "6.1" itertools = "0.14" tracing = "0.1" tracing-subscriber = "0.3" serde_with = "3" +duration-str = "0.12" toml = "0.8" thiserror = "2" async-stream = "0.3" diff --git a/config.sample.toml b/config.sample.toml index aab0c74..be0341d 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -12,10 +12,22 @@ log_channel_id = "000000000000000000" role_id = "000000000000000000" # マッチしたら認証成功とする正規表現 trigger_regex = "[おオオ][のノノ][れレレ][GgGg][RrRr][EeEe][GgGg]" -# オートコンプリートに表示されるダミーのキーワード +# モーダルのデフォルト値にランダムに選択させるダミーのキーワード郡 dummy_keywords = ["ダミー", "dummy"] +[auto_kick] +# キック対象のギルドID +guild_id = "000000000000000000" +# 参加してからキックされるまでの猶予時間 (例: "12h" なら12時間) (1時間に一度チェックされる) +grace_period = "12h" +# キックされた際にDMへ送信するメッセージ +kick_message = """てすとサーバー にて合言葉の入力が確認できなかったため、自動的にキックされました。 +再度参加する場合は、以下のリンクから再度参加してください。 +https://example.com/ +""" + + [message_logging] # メッセージの削除・編集のログを残すチャンネルID channel_id = "000000000000000000" diff --git a/src/config.rs b/src/config.rs index f29f345..2e860a3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,7 @@ use std::{collections::HashSet, sync::Arc}; +use chrono::Duration; +use duration_str::deserialize_duration_chrono; use regex::Regex; use serde::Deserialize; use serde_with::{serde_as, DisplayFromStr}; @@ -18,6 +20,7 @@ pub async fn get_config(ctx: &Context) -> Arc { pub struct Config { pub bot: BotConfig, pub auth: AuthConfig, + pub auto_kick: AutoKickConfig, pub message_logging: MessageLoggingConfig, pub message_cache: MessageCacheConfig, pub thread_channel_startup: ThreadChannelStartupConfig, @@ -81,3 +84,11 @@ pub struct QuestionConfig { pub exclude_tags: Vec, pub solved_tag: ForumTagId, } + +#[derive(Debug, Deserialize)] +pub struct AutoKickConfig { + pub guild_id: GuildId, + #[serde(deserialize_with = "deserialize_duration_chrono")] + pub grace_period: Duration, + pub kick_message: String, +} diff --git a/src/features/auth.rs b/src/features/auth.rs index 40c5ff9..e986f82 100644 --- a/src/features/auth.rs +++ b/src/features/auth.rs @@ -6,8 +6,8 @@ use poise::say_reply; use rand::seq::IndexedRandom; use serenity::all::{ ActionRowComponent, ButtonStyle, ComponentInteractionDataKind, Context, CreateActionRow, CreateButton, - CreateInputText, CreateInteractionResponse, CreateInteractionResponseFollowup, CreateModal, EventHandler, - InputTextStyle, Interaction, Mentionable, ModalInteractionCollector, Ready, UserId, + CreateInputText, CreateInteractionResponse, CreateInteractionResponseFollowup, CreateModal, EmbedMessageBuilding, + EventHandler, InputTextStyle, Interaction, Mentionable, MessageBuilder, ModalInteractionCollector, Ready, UserId, }; use serenity::async_trait; use tracing::error; @@ -141,7 +141,17 @@ impl EventHandler for Handler { return error!("Failed to add role: {:?}", why); } - let log = create_message(format!("{} にロールを追加しました。", member.mention())); + let log = create_message( + MessageBuilder::new() + .push_named_link_safe( + member.display_name(), + format!("", member.user.id), + ) + .push(" (") + .push_mono(member.user.id.to_string()) + .push(") にロールを追加しました。") + .build(), + ); let _ = send_message(&ctx, &config.log_channel_id, log).await; const AUTH_SUCCESS_MESSAGE: &str = "合言葉を確認しました。\nチャンネルが表示されない場合、アプリの再起動や再読み込み(Ctrl + R)をお試しください。"; diff --git a/src/features/auto_kick.rs b/src/features/auto_kick.rs new file mode 100644 index 0000000..786df82 --- /dev/null +++ b/src/features/auto_kick.rs @@ -0,0 +1,73 @@ +use std::time::Duration; + +use futures::StreamExt; +use serenity::{ + all::{Context, EventHandler, GuildId, MessageBuilder}, + async_trait, +}; +use tokio::pin; +use tracing::error; + +use crate::{ + config::get_config, + utils::{create_message, send_message}, +}; + +pub struct Handler; + +#[async_trait] +impl EventHandler for Handler { + async fn cache_ready(&self, ctx: Context, _: Vec) { + tokio::spawn(async move { + let config = get_config(&ctx).await; + + loop { + let members = config.auto_kick.guild_id.members_iter(&ctx); + pin!(members); + + while let Some(Ok(member)) = members.next().await { + if member.user.bot { + continue; + } + + if member.roles.contains(&config.auth.role_id) { + continue; + } + + let joined = match member.joined_at { + Some(time) => *time, + None => continue, + }; + + if chrono::Utc::now().signed_duration_since(joined) < config.auto_kick.grace_period { + continue; + } + + if let Err(e) = member.kick(&ctx).await { + error!("Failed to kick user: {:?}", e); + continue; + }; + + let dm_message = member + .user + .id + .direct_message(&ctx, create_message(&config.auto_kick.kick_message)) + .await; + + let log = create_message( + MessageBuilder::new() + .push_safe(member.display_name()) + .push(" (") + .push_mono(member.user.id.to_string()) + .push(") をキックしました。") + .push(dm_message.map(|_| "").unwrap_or_else(|_| "DMの送信に失敗しました。")) + .build(), + ); + let _ = send_message(&ctx, &config.auth.log_channel_id, log).await; + } + + tokio::time::sleep(Duration::from_secs(3600)).await; + } + }); + } +} diff --git a/src/features/mod.rs b/src/features/mod.rs index da57c2e..852e84d 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -1,5 +1,6 @@ mod admin; mod auth; +mod auto_kick; mod logging; mod message_cache; mod question; @@ -8,6 +9,7 @@ mod thread_channel_startup; mod thread_pin; pub use auth::Handler as AuthHandler; +pub use auto_kick::Handler as AutoKickHandler; pub use logging::Handler as LoggingHandler; pub use message_cache::Handler as MessageCacheHandler; pub use question::Handler as QuestionHandler; diff --git a/src/main.rs b/src/main.rs index 4a438a5..0e365eb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -85,6 +85,7 @@ async fn main() { .framework(framework) .event_handler(MainHandler) .event_handler(features::AuthHandler::new()) + .event_handler(features::AutoKickHandler) .event_handler(features::LoggingHandler) .event_handler(features::ThreadAutoInviteHandler) .event_handler(features::ThreadChannelStartupHandler) From 72d9decbd82725e6752281ac9183d4c73e1bb9dc Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Fri, 14 Feb 2025 04:32:08 +0900 Subject: [PATCH 034/104] =?UTF-8?q?toml=20=E3=81=AE=E3=83=95=E3=82=A9?= =?UTF-8?q?=E3=83=BC=E3=83=9E=E3=83=83=E3=83=88=E8=A8=AD=E5=AE=9A=E3=82=92?= =?UTF-8?q?=E6=95=B4=E3=81=88=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 11 ++++++++++- Cargo.toml | 26 +++++++++++++------------- config.sample.toml | 10 +++++----- taplo.toml | 17 +++++++++++++++++ 4 files changed, 45 insertions(+), 19 deletions(-) create mode 100644 taplo.toml diff --git a/.vscode/settings.json b/.vscode/settings.json index 23fd35f..5dec177 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,12 @@ { - "editor.formatOnSave": true + "editor.formatOnSave": true, + "[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer" + }, + "[toml]": { + "editor.defaultFormatter": "tamasfe.even-better-toml", + }, + "evenBetterToml.formatter.compactArrays": false, + "evenBetterToml.formatter.compactInlineTables": false, + "evenBetterToml.formatter.compactEntries": false } \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 82e2021..634b83f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,26 +6,26 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -serenity = "0.12.4" +async-stream = "0.3" +bpaf = { version = "0.9", features = [ "derive" ] } +chrono = "0.4" +dashmap = "6.1" +duration-str = "0.12.0" +futures = "0.3" +itertools = "0.14" # Git 上では修正されているが、crates.io にはまだ反映されていないため、git で指定 poise = { git = "https://github.com/serenity-rs/poise", rev = "db10b126c8b07f7e1924cba1672f04ff24ed4ec7" } rand = "0.9" regex = "1.0" -chrono = "0.4" +serde = { version = "1.0", features = [ "derive" ] } +serde_with = "3" +serenity = "0.12.4" similar = "2.7" -dashmap = "6.1" -itertools = "0.14" +thiserror = "2" +tokio = { version = "1.0", features = [ "rt-multi-thread", "macros", "signal" ] } +toml = "0.8" tracing = "0.1" tracing-subscriber = "0.3" -serde_with = "3" -duration-str = "0.12" -toml = "0.8" -thiserror = "2" -async-stream = "0.3" -futures = "0.3" -tokio = { version = "1.0", features = ["rt-multi-thread", "macros", "signal"] } -serde = { version = "1.0", features = ["derive"] } -bpaf = { version = "0.9", features = ["derive"] } [profile.release] panic = "abort" diff --git a/config.sample.toml b/config.sample.toml index be0341d..fc0156e 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -2,7 +2,7 @@ [bot] token = "TOKEN" application_id = "000000000000000000" -owners = ["000000000000000000"] +owners = [ "000000000000000000" ] [auth] @@ -13,7 +13,7 @@ role_id = "000000000000000000" # マッチしたら認証成功とする正規表現 trigger_regex = "[おオオ][のノノ][れレレ][GgGg][RrRr][EeEe][GgGg]" # モーダルのデフォルト値にランダムに選択させるダミーのキーワード郡 -dummy_keywords = ["ダミー", "dummy"] +dummy_keywords = [ "ダミー", "dummy" ] [auto_kick] @@ -40,9 +40,9 @@ disabled = false limit = 1000 # 過去のメッセージのキャッシュを取るギルドIDのリスト # target_guild_ids = ["651286346861641735"] -target_guild_ids = ["000000000000000000"] +target_guild_ids = [ "000000000000000000" ] # 過去メッセージのキャッシュ取らないチャンネルIDのリスト、親チャンネルを指定すると子のチャンネルも対象 (孫は対象外) -ignore_channel_ids = ["000000000000000000"] +ignore_channel_ids = [ "000000000000000000" ] [thread_channel_startup] @@ -66,4 +66,4 @@ forum_id = "000000000000000000" # 解決済みのタグID solved_tag = "000000000000000000" # 除外するタグIDのリスト -exclude_tags = ["000000000000000000"] +exclude_tags = [ "000000000000000000" ] diff --git a/taplo.toml b/taplo.toml new file mode 100644 index 0000000..acfbcec --- /dev/null +++ b/taplo.toml @@ -0,0 +1,17 @@ +[formatting] +column_width = 120 +compact_arrays = false +compact_entries = false +compact_inline_tables = false +reorder_keys = false + + +[[rule]] +include = [ "**/Cargo.toml" ] +keys = [ "dependencies" ] + +[[rule]] +include = [ "taplo.toml" ] + +[rule.formatting] +reorder_keys = true From ddedb5ea3b718b003ca3c23f2ee58904d89e37bf Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Fri, 14 Feb 2025 15:05:40 +0900 Subject: [PATCH 035/104] =?UTF-8?q?=E3=83=94=E3=83=B3=E7=95=99=E3=82=81?= =?UTF-8?q?=E3=82=B3=E3=83=9E=E3=83=B3=E3=83=89=E3=82=92=E6=94=B9=E8=89=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 指定のチャンネル内で指定のユーザーが使用できるように --- config.sample.toml | 11 +++- src/config.rs | 55 ++++++++++++++------ src/features/message_cache/mod.rs | 3 +- src/features/mod.rs | 4 +- src/features/pin.rs | 83 +++++++++++++++++++++++++++++++ src/features/thread_pin.rs | 69 ------------------------- 6 files changed, 136 insertions(+), 89 deletions(-) create mode 100644 src/features/pin.rs delete mode 100644 src/features/thread_pin.rs diff --git a/config.sample.toml b/config.sample.toml index fc0156e..5cbfe01 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -1,7 +1,5 @@ - [bot] token = "TOKEN" -application_id = "000000000000000000" owners = [ "000000000000000000" ] @@ -45,6 +43,13 @@ target_guild_ids = [ "000000000000000000" ] ignore_channel_ids = [ "000000000000000000" ] +[pin] +# チャンネルのオーナーを指定する +channels = [ + { id = "1318964564095930419", owner = "422735871410700308" }, # テスト +] + + [thread_channel_startup] # 空にする方法 # threads = [] @@ -56,10 +61,12 @@ channel_id = "000000000000000000" startup_message = """スレッドの始まりだぁ! まずはこのテンプレートを読んでね!""" + [thread_auto_invite] # 自動で招待するロールのID role_id = "000000000000000000" + [question] # 質問の投稿先フォーラムのID forum_id = "000000000000000000" diff --git a/src/config.rs b/src/config.rs index 2e860a3..9e8f40e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,9 +1,12 @@ -use std::{collections::HashSet, sync::Arc}; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; use chrono::Duration; use duration_str::deserialize_duration_chrono; use regex::Regex; -use serde::Deserialize; +use serde::{Deserialize, Deserializer}; use serde_with::{serde_as, DisplayFromStr}; use serenity::{ all::{ChannelId, Context, ForumTagId, GuildId, RoleId, UserId}, @@ -23,6 +26,7 @@ pub struct Config { pub auto_kick: AutoKickConfig, pub message_logging: MessageLoggingConfig, pub message_cache: MessageCacheConfig, + pub pin: PinConfig, pub thread_channel_startup: ThreadChannelStartupConfig, pub thread_auto_invite: ThreadAutoInviteConfig, pub question: QuestionConfig, @@ -36,7 +40,6 @@ impl TypeMapKey for Config { pub struct BotConfig { pub token: String, pub owners: HashSet, - pub application_id: UserId, } #[serde_as] @@ -49,6 +52,14 @@ pub struct AuthConfig { pub dummy_keywords: Vec, } +#[derive(Debug, Deserialize)] +pub struct AutoKickConfig { + pub guild_id: GuildId, + #[serde(deserialize_with = "deserialize_duration_chrono")] + pub grace_period: Duration, + pub kick_message: String, +} + #[derive(Debug, Deserialize)] pub struct MessageLoggingConfig { pub channel_id: ChannelId, @@ -63,13 +74,30 @@ pub struct MessageCacheConfig { } #[derive(Debug, Deserialize)] -pub struct ThreadChannelStartupConfig { - pub threads: Vec, +pub struct PinConfig { + #[serde(deserialize_with = "to_pin_channels")] + pub channels: HashMap, +} + +fn to_pin_channels<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Debug, Deserialize)] + struct Temp { + id: ChannelId, + owner: UserId, + } + + Ok(Vec::deserialize(deserializer)? + .into_iter() + .map(|i: Temp| (i.id, i.owner)) + .collect()) } #[derive(Debug, Deserialize)] -pub struct ThreadAutoInviteConfig { - pub role_id: RoleId, +pub struct ThreadChannelStartupConfig { + pub threads: Vec, } #[derive(Debug, Deserialize)] @@ -78,17 +106,14 @@ pub struct ThreadStartupConfig { pub startup_message: String, } +#[derive(Debug, Deserialize)] +pub struct ThreadAutoInviteConfig { + pub role_id: RoleId, +} + #[derive(Debug, Deserialize)] pub struct QuestionConfig { pub forum_id: ChannelId, pub exclude_tags: Vec, pub solved_tag: ForumTagId, } - -#[derive(Debug, Deserialize)] -pub struct AutoKickConfig { - pub guild_id: GuildId, - #[serde(deserialize_with = "deserialize_duration_chrono")] - pub grace_period: Duration, - pub kick_message: String, -} diff --git a/src/features/message_cache/mod.rs b/src/features/message_cache/mod.rs index 9167ba3..3c16f6e 100644 --- a/src/features/message_cache/mod.rs +++ b/src/features/message_cache/mod.rs @@ -82,7 +82,8 @@ impl EventHandler for Handler { } }; - let Ok(bot_member) = guild.member(ctx_ref, config.bot.application_id).await else { + let bot_id = ctx_ref.cache.current_user().id; + let Ok(bot_member) = guild.member(ctx_ref, bot_id).await else { error!("Failed to get bot member for guild: {:?}", guild_id); continue; }; diff --git a/src/features/mod.rs b/src/features/mod.rs index 852e84d..8f8eff3 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -3,10 +3,10 @@ mod auth; mod auto_kick; mod logging; mod message_cache; +mod pin; mod question; mod thread_auto_invite; mod thread_channel_startup; -mod thread_pin; pub use auth::Handler as AuthHandler; pub use auto_kick::Handler as AutoKickHandler; @@ -24,7 +24,7 @@ pub fn commands() -> Vec { build_commands(vec![ auth::create_keyword_button, question::question, - thread_pin::pin, + pin::pin, admin::reload_config, ]) } diff --git a/src/features/pin.rs b/src/features/pin.rs new file mode 100644 index 0000000..820b3e2 --- /dev/null +++ b/src/features/pin.rs @@ -0,0 +1,83 @@ +use std::time::Duration; + +use futures::StreamExt; +use poise::say_reply; +use serenity::all::{GuildChannel, Message, MessageType}; + +use crate::config::Config; +use crate::utils::has_authed_role; +use crate::{config::get_config, PContext, PError}; + +async fn check_owner(ctx: PContext<'_>, config: &Config, channel: &GuildChannel) -> bool { + let author_id = ctx.author().id; + + if channel.owner_id == Some(author_id) { + return true; + } + + // コンフィグで設定されたオーナーかどうか + if config.pin.channels.get(&channel.id) == Some(&author_id) { + return true; + } + + // 質問フォーラムの場合、初期メッセージのメンションからスレッド主を取得 + // スレッドの初期メッセージのIDはスレッドのIDと同じ + if channel.parent_id == Some(config.question.forum_id) { + let Ok(msg) = channel.message(ctx, channel.id.get()).await else { + // メッセージが取得できない場合はスレッドオーナーではない判定 + return false; + }; + + if msg.mentions.iter().any(|m| m.id == author_id) { + return true; + } + } + + false +} + +/// スレッド主限定でメッセージをピン留めします。 +#[poise::command( + context_menu_command = "ピン留め", + slash_command, + ephemeral, + guild_only, + aliases("ピン留め"), + required_bot_permissions = "MANAGE_MESSAGES", + check = "has_authed_role" +)] +pub async fn pin( + ctx: PContext<'_>, + #[description = "ピン留めするメッセージ (リンクかID)"] msg: Message, +) -> Result<(), PError> { + let config = get_config(ctx.serenity_context()).await; + let channel = ctx.guild_channel().await.unwrap(); + + if !check_owner(ctx, &config, &channel).await { + say_reply(ctx, "あなたはこのチャンネルでピン留めできません。").await?; + return Ok(()); + } + + // ストリームを取得することでイベントの受信を開始させる + let mut stream = channel + .await_reply(&ctx.serenity_context().shard) + .timeout(Duration::from_secs(5)) + .channel_id(msg.channel_id) + .author_id(ctx.serenity_context().cache.current_user().id) + .filter(|r| r.kind == MessageType::PinsAdd) + .stream(); + + if msg.pinned { + msg.unpin(ctx).await?; + say_reply(ctx, "ピン留めを解除しました。").await?; + } else { + msg.pin(ctx).await?; + say_reply(ctx, "ピン留めしました。").await?; + } + + if let Some(msg) = stream.next().await { + let _ = msg.delete(ctx.http()).await; + } + + Ok(()) +} diff --git a/src/features/thread_pin.rs b/src/features/thread_pin.rs deleted file mode 100644 index 96193f9..0000000 --- a/src/features/thread_pin.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::time::Duration; - -use poise::say_reply; -use serenity::{ - all::{Message, MessageType}, - futures::StreamExt, -}; - -use crate::utils::has_authed_role; -use crate::{config::get_config, PContext, PError}; - -/// スレッド主限定でメッセージをピン留めします。 -#[poise::command( - context_menu_command = "ピン留め", - slash_command, - ephemeral, - guild_only, - aliases("ピン留め"), - required_bot_permissions = "MANAGE_MESSAGES", - check = "has_authed_role" -)] -pub async fn pin( - ctx: PContext<'_>, - #[description = "ピン留めするメッセージ (リンクかID)"] msg: Message, -) -> Result<(), PError> { - let channel = ctx.guild_channel().await.unwrap(); - let config = get_config(ctx.serenity_context()).await; - - let owner = match ( - channel.owner_id, - channel.parent_id.unwrap_or_default() == config.question.forum_id, - ) { - // 質問フォーラムの場合、初期メッセージのメンションからスレッド主を取得 - // スレッドの初期メッセージのIDはスレッドのIDと同じ - (_, true) => channel.message(ctx, channel.id.get()).await?.mentions[0].id, - (Some(owner), false) => owner, - (None, _) => { - say_reply(ctx, "スレッド以外のチャンネルでは使用出来ません。").await?; - return Ok(()); - } - }; - - if ctx.author().id != owner { - say_reply(ctx, "スレッド主のみがピン留めできます。").await?; - return Ok(()); - } - - let mut stream = channel - .await_reply(&ctx.serenity_context().shard) - .timeout(Duration::from_secs(5)) - .channel_id(channel.id) - .author_id(config.bot.application_id) - .filter(|r| r.kind == MessageType::PinsAdd) - .stream(); - - if msg.pinned { - msg.unpin(ctx).await?; - say_reply(ctx, "ピン留めを解除しました。").await?; - } else { - msg.pin(ctx).await?; - say_reply(ctx, "ピン留めしました。").await?; - } - - if let Some(msg) = stream.next().await { - let _ = msg.delete(ctx.http()).await; - } - - Ok(()) -} From 4d1f22154dec2e8434433754c1ec30f9c3d3a1b4 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 15 Feb 2025 03:19:28 +0900 Subject: [PATCH 036/104] =?UTF-8?q?=E3=82=AD=E3=83=A3=E3=83=83=E3=82=B7?= =?UTF-8?q?=E3=83=A5=E5=8F=96=E5=BE=97=E3=81=AE=E4=B8=A6=E5=88=97=E5=8C=96?= =?UTF-8?q?=E3=82=92=E3=82=84=E3=82=81=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/message_cache/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/features/message_cache/mod.rs b/src/features/message_cache/mod.rs index 3c16f6e..fb0d00b 100644 --- a/src/features/message_cache/mod.rs +++ b/src/features/message_cache/mod.rs @@ -107,8 +107,7 @@ impl EventHandler for Handler { } } } - .map(|channel| self.cache_channel_message(ctx_ref, &config, channel, &guild, &bot_member)) - .buffered(10) + .then(|c| self.cache_channel_message(ctx_ref, &config, c, &guild, &bot_member)) .collect::>() .await; } From 8c8201e34a9cf193bce73e088d52e2c2b7fbdab0 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 15 Feb 2025 03:29:51 +0900 Subject: [PATCH 037/104] =?UTF-8?q?DM=E3=81=8C=E9=80=81=E4=BF=A1=E3=81=95?= =?UTF-8?q?=E3=82=8C=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/auto_kick.rs | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/features/auto_kick.rs b/src/features/auto_kick.rs index 786df82..ce5342b 100644 --- a/src/features/auto_kick.rs +++ b/src/features/auto_kick.rs @@ -1,4 +1,7 @@ -use std::time::Duration; +use std::{ + sync::atomic::{AtomicBool, Ordering}, + time::Duration, +}; use futures::StreamExt; use serenity::{ @@ -13,11 +16,25 @@ use crate::{ utils::{create_message, send_message}, }; -pub struct Handler; +pub struct Handler { + task_started: AtomicBool, +} + +impl Handler { + pub fn new() -> Self { + Self { + task_started: AtomicBool::new(false), + } + } +} #[async_trait] impl EventHandler for Handler { async fn cache_ready(&self, ctx: Context, _: Vec) { + if self.task_started.swap(true, Ordering::Relaxed) { + return; + } + tokio::spawn(async move { let config = get_config(&ctx).await; @@ -43,24 +60,24 @@ impl EventHandler for Handler { continue; } - if let Err(e) = member.kick(&ctx).await { - error!("Failed to kick user: {:?}", e); - continue; - }; - let dm_message = member .user .id .direct_message(&ctx, create_message(&config.auto_kick.kick_message)) .await; + if let Err(e) = member.kick(&ctx).await { + error!("Failed to kick user: {:?}", e); + continue; + }; + let log = create_message( MessageBuilder::new() .push_safe(member.display_name()) .push(" (") .push_mono(member.user.id.to_string()) .push(") をキックしました。") - .push(dm_message.map(|_| "").unwrap_or_else(|_| "DMの送信に失敗しました。")) + .push(dm_message.map_or("DMの送信に失敗しました。", |_| "")) .build(), ); let _ = send_message(&ctx, &config.auth.log_channel_id, log).await; From fb0a4c0410ec8eaafd7095fe1c8127d6a8fa37d5 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 15 Feb 2025 03:32:09 +0900 Subject: [PATCH 038/104] =?UTF-8?q?=E3=83=A1=E3=83=A2=E3=83=AA=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E9=87=8F=E3=82=92=E3=82=A2=E3=82=AF=E3=83=86=E3=82=A3?= =?UTF-8?q?=E3=83=93=E3=83=86=E3=82=A3=E3=81=AB=E8=A1=A8=E7=A4=BA=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 128 ++++++++++++++++++++++++++++-- Cargo.toml | 3 +- src/features/message_cache/mod.rs | 10 ++- src/main.rs | 59 ++++++++++++-- taplo.toml | 2 - 5 files changed, 185 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1eb560f..5925e96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -296,6 +296,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -425,16 +444,16 @@ dependencies = [ [[package]] name = "duration-str" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ad6b66883f70e2f38f1ee99e3797b9d7e7b7fb051ed2e23e027c81753056c8" +checksum = "99b55e40ba8fc1ef074c9f9031b4cb88bb1f30c946f80a9305df44973c0b9a2d" dependencies = [ "chrono", "rust_decimal", "serde", "thiserror 2.0.11", "time", - "winnow 0.6.26", + "winnow 0.6.8", ] [[package]] @@ -788,7 +807,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows-core 0.52.0", ] [[package]] @@ -1101,6 +1120,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1322,6 +1350,26 @@ dependencies = [ "zerocopy 0.8.17", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.8" @@ -1845,6 +1893,20 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "sysinfo" +version = "0.31.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -2305,6 +2367,7 @@ dependencies = [ "serde_with", "serenity", "similar", + "sysinfo", "thiserror 2.0.11", "tokio", "toml", @@ -2498,6 +2561,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -2507,6 +2580,49 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -2657,9 +2773,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.26" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28" +checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 634b83f..19487fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,10 @@ async-stream = "0.3" bpaf = { version = "0.9", features = [ "derive" ] } chrono = "0.4" dashmap = "6.1" -duration-str = "0.12.0" +duration-str = "0.13.0" futures = "0.3" itertools = "0.14" +sysinfo = "0.31.0" # Git 上では修正されているが、crates.io にはまだ反映されていないため、git で指定 poise = { git = "https://github.com/serenity-rs/poise", rev = "db10b126c8b07f7e1924cba1672f04ff24ed4ec7" } rand = "0.9" diff --git a/src/features/message_cache/mod.rs b/src/features/message_cache/mod.rs index fb0d00b..a5dba78 100644 --- a/src/features/message_cache/mod.rs +++ b/src/features/message_cache/mod.rs @@ -1,5 +1,7 @@ mod cache; +use std::sync::atomic::{AtomicBool, Ordering}; + pub use cache::{MessageCache, MessageCacheType}; use async_stream::stream; @@ -17,11 +19,15 @@ use crate::{ pub struct Handler { disabled: bool, + collected: AtomicBool, } impl Handler { pub fn new(disabled: bool) -> Self { - Self { disabled } + Self { + disabled, + collected: AtomicBool::new(false), + } } async fn cache_channel_message( @@ -66,7 +72,7 @@ impl Handler { #[async_trait] impl EventHandler for Handler { async fn cache_ready(&self, ctx: Context, _: Vec) { - if self.disabled { + if self.disabled || self.collected.swap(true, Ordering::Relaxed) { return; } diff --git a/src/main.rs b/src/main.rs index 0e365eb..b13c1af 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,14 @@ mod error; mod features; mod utils; -use std::{fs::read_to_string, sync::Arc}; +use std::{ + fs::read_to_string, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + time::Duration, +}; use bpaf::Bpaf; use config::Config; @@ -11,11 +18,12 @@ use error::on_error; use features::{commands, MessageCache, MessageCacheType}; use poise::{Framework, FrameworkOptions}; use serenity::{ - all::{RatelimitInfo, Ready}, + all::{ActivityData, GuildId, RatelimitInfo, Ready}, async_trait, cache::Settings as CacheSettings, prelude::*, }; +use sysinfo::{Pid, System}; use tracing::{error, info}; pub type PError = Box; @@ -23,7 +31,17 @@ pub struct CommandData {} pub type PContext<'a> = poise::Context<'a, CommandData, PError>; pub type PCommand = poise::Command; -struct MainHandler; +struct MainHandler { + task_started: AtomicBool, +} + +impl MainHandler { + pub fn new() -> Self { + Self { + task_started: AtomicBool::new(false), + } + } +} #[async_trait] impl EventHandler for MainHandler { @@ -31,8 +49,37 @@ impl EventHandler for MainHandler { info!("{} is connected!", ready.user.name); } + async fn cache_ready(&self, ctx: Context, _: Vec) { + if self.task_started.swap(true, Ordering::Relaxed) { + return; + } + + tokio::spawn(async move { + let mut system = System::new_all(); + let pid = Pid::from_u32(std::process::id()); + + loop { + system.refresh_all(); + + let Some(memory) = system.process(pid).map(|p| p.memory() as f64 / 1024.0 / 1024.0) else { + error!("Failed to get process info"); + continue; + }; + + ctx.set_activity(Some(ActivityData::custom(format!("メモリ使用量: {:.1}MB", memory)))); + + tokio::time::sleep(Duration::from_secs(60)).await; + } + }); + } + async fn ratelimit(&self, data: RatelimitInfo) { - info!("Ratelimited {}: {}s", data.path, data.timeout.as_secs()); + info!( + "Ratelimited {} {}: {}s", + data.method.reqwest_method(), + data.path, + data.timeout.as_secs() + ); } } @@ -83,9 +130,9 @@ async fn main() { settings.max_messages = 1_000_000; let mut client = Client::builder(&config.bot.token, intents) .framework(framework) - .event_handler(MainHandler) + .event_handler(MainHandler::new()) .event_handler(features::AuthHandler::new()) - .event_handler(features::AutoKickHandler) + .event_handler(features::AutoKickHandler::new()) .event_handler(features::LoggingHandler) .event_handler(features::ThreadAutoInviteHandler) .event_handler(features::ThreadChannelStartupHandler) diff --git a/taplo.toml b/taplo.toml index acfbcec..4332056 100644 --- a/taplo.toml +++ b/taplo.toml @@ -10,8 +10,6 @@ reorder_keys = false include = [ "**/Cargo.toml" ] keys = [ "dependencies" ] -[[rule]] -include = [ "taplo.toml" ] [rule.formatting] reorder_keys = true From 02ce304e79f2e8c9bed1eaae9f6f8748a8e85f0c Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 15 Feb 2025 05:13:10 +0900 Subject: [PATCH 039/104] =?UTF-8?q?=E3=82=AD=E3=83=A3=E3=83=83=E3=82=B7?= =?UTF-8?q?=E3=83=A5=E5=8F=96=E5=BE=97=E3=82=92=E5=86=8D=E5=BA=A6=E4=B8=A6?= =?UTF-8?q?=E5=88=97=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/message_cache/mod.rs | 8 +++++--- src/main.rs | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/features/message_cache/mod.rs b/src/features/message_cache/mod.rs index a5dba78..6ab2bed 100644 --- a/src/features/message_cache/mod.rs +++ b/src/features/message_cache/mod.rs @@ -5,7 +5,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; pub use cache::{MessageCache, MessageCacheType}; use async_stream::stream; -use futures::StreamExt; +use futures::{future, StreamExt}; use serenity::{ all::{Context, EventHandler, Guild, GuildChannel, GuildId, Member}, async_trait, @@ -57,7 +57,8 @@ impl Handler { .id .messages_iter(&ctx) .take(config.message_cache.limit) - .filter_map(|x| async { x.ok() }) + .take_while(|x| future::ready(x.is_ok())) + .filter_map(|x| future::ready(x.ok())) .collect::>() .await; @@ -113,7 +114,8 @@ impl EventHandler for Handler { } } } - .then(|c| self.cache_channel_message(ctx_ref, &config, c, &guild, &bot_member)) + .map(|c| self.cache_channel_message(ctx_ref, &config, c, &guild, &bot_member)) + .buffer_unordered(20) .collect::>() .await; } diff --git a/src/main.rs b/src/main.rs index b13c1af..7ab4e4e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,7 +24,7 @@ use serenity::{ prelude::*, }; use sysinfo::{Pid, System}; -use tracing::{error, info}; +use tracing::{error, info, warn}; pub type PError = Box; pub struct CommandData {} @@ -74,7 +74,7 @@ impl EventHandler for MainHandler { } async fn ratelimit(&self, data: RatelimitInfo) { - info!( + warn!( "Ratelimited {} {}: {}s", data.method.reqwest_method(), data.path, From 8576e842411d27276a100a35c1746f615f46cd5b Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 15 Feb 2025 06:34:16 +0900 Subject: [PATCH 040/104] =?UTF-8?q?invite=5Fthread=20=E3=82=B3=E3=83=9E?= =?UTF-8?q?=E3=83=B3=E3=83=89=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/error.rs | 8 +++-- src/features/mod.rs | 1 + src/features/thread_auto_invite.rs | 53 +++++++++++++++++++++--------- src/main.rs | 2 +- src/utils.rs | 33 ++++++++++++++++++- 5 files changed, 77 insertions(+), 20 deletions(-) diff --git a/src/error.rs b/src/error.rs index d449b6a..a8f5ab5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,12 +3,14 @@ use serenity::all::MessageParseError; use thiserror::Error; use tracing::error; -use crate::{CommandData, PError}; +use crate::{utils::format_duration, CommandData, PError}; #[derive(Error, Debug)] pub enum BotError { #[error("このコマンドの実行に必要なロールがありません。")] HasNoRole, + #[error("スレッドでのみ実行できるコマンドです。")] + IsNotInThread, } pub async fn on_error(error: FrameworkError<'_, CommandData, PError>) { @@ -54,8 +56,8 @@ pub async fn on_error(error: FrameworkError<'_, CommandData, PError>) { let _ = say_reply( ctx, format!( - "このコマンドはクールダウン中です。残り時間: {}秒", - remaining_cooldown.as_secs() + "このコマンドはクールダウン中です。残り時間: {}", + format_duration(remaining_cooldown, 2), ), ) .await; diff --git a/src/features/mod.rs b/src/features/mod.rs index 8f8eff3..2d8e363 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -26,6 +26,7 @@ pub fn commands() -> Vec { question::question, pin::pin, admin::reload_config, + thread_auto_invite::invite_thread, ]) } diff --git a/src/features/thread_auto_invite.rs b/src/features/thread_auto_invite.rs index 434e61f..85eb952 100644 --- a/src/features/thread_auto_invite.rs +++ b/src/features/thread_auto_invite.rs @@ -1,14 +1,32 @@ +use poise::{say_reply, ApplicationContext}; use serenity::{ - all::{Context, EditMessage, EventHandler, GuildChannel, Mentionable}, + all::{ChannelId, Context, EditMessage, EventHandler, GuildChannel, Mentionable, RoleId}, async_trait, }; use tracing::error; use crate::{ config::get_config, - utils::{await_initial_message, create_message}, + utils::{await_initial_message, create_message, has_authed_role, is_in_thread}, + CommandData, PError, }; +async fn invite_thread_by_role(ctx: &Context, thread_id: ChannelId, role_id: RoleId) { + let mut message = { + let msg = create_message("スレッド自動招待用メッセージ"); + match thread_id.send_message(&ctx, msg).await { + Ok(m) => m, + Err(why) => return error!("Error sending message: {:?}", why), + } + }; + + let _ = message + .edit(&ctx, EditMessage::new().content(role_id.mention().to_string())) + .await; + + let _ = message.delete(&ctx).await; +} + pub struct Handler; #[async_trait] @@ -18,19 +36,24 @@ impl EventHandler for Handler { return; } - let mut message = { - let msg = create_message("スレッド自動招待用メッセージ"); - match thread.send_message(&ctx, msg).await { - Ok(m) => m, - Err(why) => return error!("Error sending message: {:?}", why), - } - }; - let config = &get_config(&ctx).await.thread_auto_invite; - let _ = message - .edit(&ctx, EditMessage::new().content(config.role_id.mention().to_string())) - .await; - - let _ = message.delete(&ctx).await; + invite_thread_by_role(&ctx, thread.id, config.role_id).await; } } + +/// 招待用ロールを持ったメンバーを実行したスレッドに招待します。 +#[poise::command( + slash_command, + ephemeral, + guild_only, + aliases("スレッドに招待"), + channel_cooldown = 86400, // 24 時間 + check = "has_authed_role", + check = "is_in_thread" +)] +pub async fn invite_thread(ctx: ApplicationContext<'_, CommandData, PError>) -> Result<(), PError> { + let config = &get_config(ctx.serenity_context()).await.thread_auto_invite; + invite_thread_by_role(ctx.serenity_context(), ctx.channel_id(), config.role_id).await; + say_reply(ctx.into(), "スレッドに招待しました。").await?; + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 7ab4e4e..9198e35 100644 --- a/src/main.rs +++ b/src/main.rs @@ -113,7 +113,7 @@ async fn main() { .options(FrameworkOptions { commands: commands(), on_error: |error| Box::pin(on_error(error)), - skip_checks_for_owners: true, + skip_checks_for_owners: false, owners: config.bot.owners.clone(), ..Default::default() }) diff --git a/src/utils.rs b/src/utils.rs index 2c92954..694ea12 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -5,7 +5,7 @@ use futures::Stream; use itertools::Itertools; use serenity::{ all::{ - ChannelId, Context, CreateActionRow, CreateAllowedMentions, CreateInteractionResponse, + ChannelId, ChannelType, Context, CreateActionRow, CreateAllowedMentions, CreateInteractionResponse, CreateInteractionResponseMessage, CreateMessage, GuildChannel, Http, LightMethod, Message, MessageId, Request, Route, ThreadsData, Timestamp, }, @@ -91,6 +91,37 @@ pub async fn has_authed_role(ctx: PContext<'_>) -> Result { } } +/* +実行した場所がスレッドであるかどうかを確認します。 +*/ +pub async fn is_in_thread(ctx: PContext<'_>) -> Result { + let channel = ctx.guild_channel().await.ok_or(BotError::IsNotInThread)?; + match channel.kind { + ChannelType::PublicThread | ChannelType::PrivateThread | ChannelType::NewsThread => Ok(true), + _ => Err(BotError::IsNotInThread.into()), + } +} + +const UNITS: [(u64, &str); 4] = [(86400, "日"), (3600, "時間"), (60, "分"), (1, "秒")]; + +pub fn format_duration(duration: Duration, mut count: usize) -> String { + let mut remaining = duration.as_secs(); + let mut parts = Vec::new(); + + for (unit, label) in UNITS { + if remaining >= unit && count > 0 { + let value = remaining / unit; + if value > 0 { + parts.push(format!("{}{}", value, label)); + remaining %= unit; + count -= 1; + } + } + } + + parts.join(" ") +} + pub async fn send_message(ctx: &Context, channel_id: &ChannelId, builder: CreateMessage) -> Result { match channel_id.send_message(&ctx.http, builder).await { Ok(m) => Ok(m), From 0d1295cf767f930cc5de55a2c07f66ff79f6ca8e Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 15 Feb 2025 06:34:41 +0900 Subject: [PATCH 041/104] bump version --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5925e96..ebf1a18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2351,7 +2351,7 @@ checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0" [[package]] name = "valine_bot" -version = "2.2.0" +version = "2.3.0" dependencies = [ "async-stream", "bpaf", diff --git a/Cargo.toml b/Cargo.toml index 19487fb..0864702 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "valine_bot" -version = "2.2.0" +version = "2.3.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 8e973572979405208701a33bd1997a3dfa59e3e8 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 15 Feb 2025 06:37:44 +0900 Subject: [PATCH 042/104] =?UTF-8?q?Actions=20=E3=81=A7=E3=81=AE=E3=83=93?= =?UTF-8?q?=E3=83=AB=E3=83=89=E3=81=ABMold=20=E3=82=92=E4=BD=BF=E3=81=86?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 64d480f..111e0ab 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,9 +4,9 @@ permissions: contents: write on: - push: - tags: - - v* + release: + types: + - published jobs: build: @@ -45,6 +45,9 @@ jobs: targets: ${{ matrix.target }} bins: cargo-zigbuild + # moldをインストール + - uses: rui314/setup-mold@v1 + # ビルド - name: Build with glibc version if: matrix.glibc != -1 From 598ed497c7c269035b58654f522ffcfb2141e97e Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 15 Feb 2025 07:33:19 +0900 Subject: [PATCH 043/104] =?UTF-8?q?invite=5Fthread=20=E3=81=8C=E5=A4=B1?= =?UTF-8?q?=E6=95=97=E3=81=97=E3=81=9F=E3=81=A8=E8=A1=A8=E7=A4=BA=E3=81=95?= =?UTF-8?q?=E3=82=8C=E3=82=8B=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/features/thread_auto_invite.rs | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ebf1a18..480b0cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2351,7 +2351,7 @@ checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0" [[package]] name = "valine_bot" -version = "2.3.0" +version = "2.3.1" dependencies = [ "async-stream", "bpaf", diff --git a/Cargo.toml b/Cargo.toml index 0864702..7111622 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "valine_bot" -version = "2.3.0" +version = "2.3.1" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/features/thread_auto_invite.rs b/src/features/thread_auto_invite.rs index 85eb952..7d0e4a1 100644 --- a/src/features/thread_auto_invite.rs +++ b/src/features/thread_auto_invite.rs @@ -53,6 +53,7 @@ impl EventHandler for Handler { )] pub async fn invite_thread(ctx: ApplicationContext<'_, CommandData, PError>) -> Result<(), PError> { let config = &get_config(ctx.serenity_context()).await.thread_auto_invite; + ctx.defer_ephemeral().await?; invite_thread_by_role(ctx.serenity_context(), ctx.channel_id(), config.role_id).await; say_reply(ctx.into(), "スレッドに招待しました。").await?; Ok(()) From 296ac7b3d21e815aef2ad602677271ec1b43296d Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 8 Mar 2025 00:15:20 +0900 Subject: [PATCH 044/104] =?UTF-8?q?=E3=82=AA=E3=83=BC=E3=83=90=E3=83=BC?= =?UTF-8?q?=E3=83=95=E3=83=AD=E3=83=BC=E3=81=A7=E3=82=AF=E3=83=A9=E3=83=83?= =?UTF-8?q?=E3=82=B7=E3=83=A5=E3=81=99=E3=82=8B=E5=95=8F=E9=A1=8C=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/auth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/auth.rs b/src/features/auth.rs index e986f82..33d4305 100644 --- a/src/features/auth.rs +++ b/src/features/auth.rs @@ -32,7 +32,7 @@ impl Handler { fn remaining_cooldown(&self, user_id: UserId) -> Option { if let Some(instant) = self.cooldown.get(&user_id) { - let remaining = (AUTH_COOLDOWN - instant.elapsed()).as_secs(); + let remaining = AUTH_COOLDOWN.checked_sub(instant.elapsed())?.as_secs(); if remaining > 0 { return Some(remaining); } From 258d298e8496f4dbc6421cdd8266a6692c6909e1 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 8 Mar 2025 00:17:41 +0900 Subject: [PATCH 045/104] bump version --- Cargo.lock | 280 ++++++++++++++++++++++---------------------- Cargo.toml | 4 +- rust-toolchain.toml | 2 +- 3 files changed, 142 insertions(+), 144 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 480b0cf..abd843b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,18 +69,18 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] name = "async-trait" -version = "0.1.86" +version = "0.1.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" +checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -124,9 +124,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "block-buffer" @@ -139,22 +139,22 @@ dependencies = [ [[package]] name = "bpaf" -version = "0.9.16" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "913d667d4716acd286a0dc58824a4c0ec8ce58eeca95cfb58172d17a9ec01035" +checksum = "4de4d74c5891642753c67ab88f58d971a68dd98673b69689a8c24ce3ec78a412" dependencies = [ "bpaf_derive", ] [[package]] name = "bpaf_derive" -version = "0.5.16" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34d8a24f809c4cda0832689019daa067d0ae927d801429196b238a3e8cb0cd3e" +checksum = "fefb4feeec9a091705938922f26081aad77c64cd2e76cd1c4a9ece8e42e1618a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -177,9 +177,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "camino" @@ -214,9 +214,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.13" +version = "1.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda" +checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" dependencies = [ "shlex", ] @@ -229,9 +229,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.39" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" dependencies = [ "android-tzdata", "iana-time-zone", @@ -239,7 +239,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -352,7 +352,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -363,7 +363,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -439,7 +439,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -451,16 +451,16 @@ dependencies = [ "chrono", "rust_decimal", "serde", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "winnow 0.6.8", ] [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "encoding_rs" @@ -473,9 +473,9 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" @@ -504,9 +504,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "flate2" -version = "1.0.35" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" dependencies = [ "crc32fast", "miniz_oxide", @@ -583,7 +583,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -748,9 +748,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" @@ -934,7 +934,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -1003,9 +1003,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" @@ -1031,21 +1031,21 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.170" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" [[package]] name = "linux-raw-sys" -version = "0.4.15" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" [[package]] name = "litemap" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "lock_api" @@ -1059,9 +1059,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.25" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "memchr" @@ -1102,9 +1102,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" dependencies = [ "adler2", ] @@ -1242,7 +1242,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -1262,9 +1262,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] @@ -1275,16 +1275,16 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "memchr", "unicase", ] [[package]] name = "quote" -version = "1.0.38" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" dependencies = [ "proc-macro2", ] @@ -1307,8 +1307,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.0", - "zerocopy 0.8.17", + "rand_core 0.9.3", + "zerocopy 0.8.23", ] [[package]] @@ -1328,7 +1328,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -1342,12 +1342,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom 0.3.1", - "zerocopy 0.8.17", ] [[package]] @@ -1372,11 +1371,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", ] [[package]] @@ -1454,15 +1453,14 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.8" +version = "0.17.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee" dependencies = [ "cc", "cfg-if", "getrandom 0.2.15", "libc", - "spin", "untrusted", "windows-sys 0.52.0", ] @@ -1485,11 +1483,11 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.44" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "errno", "libc", "linux-raw-sys", @@ -1560,15 +1558,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "ryu" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -1607,18 +1605,18 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.217" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" dependencies = [ "serde_derive", ] @@ -1634,20 +1632,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] name = "serde_json" -version = "1.0.138" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -1703,7 +1701,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -1715,7 +1713,7 @@ dependencies = [ "arrayvec", "async-trait", "base64 0.22.1", - "bitflags 2.8.0", + "bitflags 2.9.0", "bytes", "chrono", "command_attr", @@ -1810,9 +1808,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" [[package]] name = "socket2" @@ -1824,12 +1822,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -1867,9 +1859,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.98" +version = "2.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" dependencies = [ "proc-macro2", "quote", @@ -1890,14 +1882,14 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] name = "sysinfo" -version = "0.31.4" +version = "0.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be" +checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" dependencies = [ "core-foundation-sys", "libc", @@ -1936,9 +1928,9 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tempfile" -version = "3.16.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" +checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567" dependencies = [ "cfg-if", "fastrand", @@ -1959,11 +1951,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.11", + "thiserror-impl 2.0.12", ] [[package]] @@ -1974,18 +1966,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -2000,9 +1992,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" dependencies = [ "deranged", "itoa", @@ -2015,15 +2007,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c" dependencies = [ "num-conv", "time-core", @@ -2064,7 +2056,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -2148,7 +2140,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow 0.7.2", + "winnow 0.7.3", ] [[package]] @@ -2177,7 +2169,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -2262,15 +2254,15 @@ checksum = "74b08b0c1257381af16a5c3605254d529d3e7e109f3c62befc5d168968192998" [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "typesize" -version = "0.1.11" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20304d891be0766f52123746c721d1190b953e874f9eccf29067a64c1a0ae16c" +checksum = "e29e4cac0f1acdbbe7b4deb46876a04246dc6abf60b6f2587bef8ae327cd134c" dependencies = [ "chrono", "dashmap 5.5.3", @@ -2292,7 +2284,7 @@ checksum = "536b6812192bda8551cfa0e52524e328c6a951b48e66529ee4522d6c721243d6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -2303,9 +2295,9 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "untrusted" @@ -2351,7 +2343,7 @@ checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0" [[package]] name = "valine_bot" -version = "2.3.1" +version = "2.3.2" dependencies = [ "async-stream", "bpaf", @@ -2368,7 +2360,7 @@ dependencies = [ "serenity", "similar", "sysinfo", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "toml", "tracing", @@ -2443,7 +2435,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", "wasm-bindgen-shared", ] @@ -2478,7 +2470,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2600,7 +2592,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -2611,9 +2603,15 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] +[[package]] +name = "windows-link" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" + [[package]] name = "windows-result" version = "0.1.2" @@ -2782,9 +2780,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603" +checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" dependencies = [ "memchr", ] @@ -2805,7 +2803,7 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", ] [[package]] @@ -2840,7 +2838,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", "synstructure", ] @@ -2856,11 +2854,11 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.17" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa91407dacce3a68c56de03abe2760159582b846c6a4acd2f456618087f12713" +checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" dependencies = [ - "zerocopy-derive 0.8.17", + "zerocopy-derive 0.8.23", ] [[package]] @@ -2871,38 +2869,38 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] name = "zerocopy-derive" -version = "0.8.17" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06718a168365cad3d5ff0bb133aad346959a2074bd4a85c121255a11304a8626" +checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] name = "zerofrom" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", "synstructure", ] @@ -2931,5 +2929,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] diff --git a/Cargo.toml b/Cargo.toml index 7111622..c4875c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "valine_bot" -version = "2.3.1" +version = "2.3.2" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,7 +13,7 @@ dashmap = "6.1" duration-str = "0.13.0" futures = "0.3" itertools = "0.14" -sysinfo = "0.31.0" +sysinfo = "0.33" # Git 上では修正されているが、crates.io にはまだ反映されていないため、git で指定 poise = { git = "https://github.com/serenity-rs/poise", rev = "db10b126c8b07f7e1924cba1672f04ff24ed4ec7" } rand = "0.9" diff --git a/rust-toolchain.toml b/rust-toolchain.toml index a718dc2..c5794a6 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.83" +channel = "1.85" From 2334cdb97de1be97d35cd6a3edef3a497687a3d1 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 8 Mar 2025 00:32:28 +0900 Subject: [PATCH 046/104] =?UTF-8?q?=E3=82=A8=E3=83=87=E3=82=A3=E3=82=B7?= =?UTF-8?q?=E3=83=A7=E3=83=B3=E3=82=922024=E3=81=AB=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 2 +- src/config.rs | 2 +- src/error.rs | 4 ++-- src/features/admin.rs | 2 +- src/features/logging.rs | 2 +- src/features/message_cache/mod.rs | 4 ++-- src/features/pin.rs | 2 +- src/features/question/command.rs | 15 +++++++-------- .../question/question_creation_handler.rs | 2 +- src/main.rs | 4 ++-- src/utils.rs | 4 ++-- 11 files changed, 21 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c4875c0..3fdd85f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "valine_bot" version = "2.3.2" -edition = "2021" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/config.rs b/src/config.rs index 9e8f40e..62ed9ad 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,7 +7,7 @@ use chrono::Duration; use duration_str::deserialize_duration_chrono; use regex::Regex; use serde::{Deserialize, Deserializer}; -use serde_with::{serde_as, DisplayFromStr}; +use serde_with::{DisplayFromStr, serde_as}; use serenity::{ all::{ChannelId, Context, ForumTagId, GuildId, RoleId, UserId}, prelude::TypeMapKey, diff --git a/src/error.rs b/src/error.rs index a8f5ab5..11abd0e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,9 +1,9 @@ -use poise::{say_reply, FrameworkError}; +use poise::{FrameworkError, say_reply}; use serenity::all::MessageParseError; use thiserror::Error; use tracing::error; -use crate::{utils::format_duration, CommandData, PError}; +use crate::{CommandData, PError, utils::format_duration}; #[derive(Error, Debug)] pub enum BotError { diff --git a/src/features/admin.rs b/src/features/admin.rs index a987420..471311e 100644 --- a/src/features/admin.rs +++ b/src/features/admin.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use poise::say_reply; use tokio::fs::read_to_string; -use crate::{config::Config, PContext, PError}; +use crate::{PContext, PError, config::Config}; /// コンフィグを再読み込み #[poise::command(slash_command, ephemeral, owners_only, dm_only)] diff --git a/src/features/logging.rs b/src/features/logging.rs index f66c85e..ca115a5 100644 --- a/src/features/logging.rs +++ b/src/features/logging.rs @@ -1,6 +1,6 @@ use std::ops::Not; -use itertools::{enumerate, Itertools}; +use itertools::{Itertools, enumerate}; use serenity::{ all::{ ChannelId, Context, CreateEmbed, EmbedMessageBuilding, EventHandler, FormattedTimestamp, diff --git a/src/features/message_cache/mod.rs b/src/features/message_cache/mod.rs index 6ab2bed..7169360 100644 --- a/src/features/message_cache/mod.rs +++ b/src/features/message_cache/mod.rs @@ -5,7 +5,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; pub use cache::{MessageCache, MessageCacheType}; use async_stream::stream; -use futures::{future, StreamExt}; +use futures::{StreamExt, future}; use serenity::{ all::{Context, EventHandler, Guild, GuildChannel, GuildId, Member}, async_trait, @@ -13,7 +13,7 @@ use serenity::{ use tracing::{error, info}; use crate::{ - config::{get_config, Config}, + config::{Config, get_config}, utils::fetch_all_archived_public_thread, }; diff --git a/src/features/pin.rs b/src/features/pin.rs index 820b3e2..47b3b5d 100644 --- a/src/features/pin.rs +++ b/src/features/pin.rs @@ -6,7 +6,7 @@ use serenity::all::{GuildChannel, Message, MessageType}; use crate::config::Config; use crate::utils::has_authed_role; -use crate::{config::get_config, PContext, PError}; +use crate::{PContext, PError, config::get_config}; async fn check_owner(ctx: PContext<'_>, config: &Config, channel: &GuildChannel) -> bool { let author_id = ctx.author().id; diff --git a/src/features/question/command.rs b/src/features/question/command.rs index 62198f5..943acfb 100644 --- a/src/features/question/command.rs +++ b/src/features/question/command.rs @@ -3,16 +3,16 @@ use serenity::all::{ ButtonStyle, Channel, CreateActionRow, CreateButton, CreateForumPost, CreateMessage, CreateSelectMenu, CreateSelectMenuKind, CreateSelectMenuOption, ForumEmoji, ForumTag, ForumTagId, MessageBuilder, ReactionType, }; -use tokio::sync::{mpsc, RwLock}; +use tokio::sync::{RwLock, mpsc}; use tracing::debug; use std::sync::Arc; use std::vec; use crate::config::get_config; +use crate::features::question::QUESTION_CLOSE_PREFIX; use crate::features::question::modal::{BasicQuestionData, DetailedQuestionData}; use crate::features::question::question_creation_handler::{CustomIds, QuestionCreationHandler}; -use crate::features::question::QUESTION_CLOSE_PREFIX; use crate::utils::has_authed_role; use crate::{CommandData, PError}; @@ -178,12 +178,11 @@ pub async fn question(ctx: ApplicationContext<'_, CommandData, PError>) -> Resul &basic_data.title, CreateMessage::default() .content(msg) - .components(vec![CreateActionRow::Buttons(vec![CreateButton::new(format!( - "{}:{}", - QUESTION_CLOSE_PREFIX, ctx.interaction.user.id - )) - .label("質問を解決済みにする") - .style(ButtonStyle::Danger)])]), + .components(vec![CreateActionRow::Buttons(vec![ + CreateButton::new(format!("{}:{}", QUESTION_CLOSE_PREFIX, ctx.interaction.user.id)) + .label("質問を解決済みにする") + .style(ButtonStyle::Danger), + ])]), ) .set_applied_tags(&*forum_tag_ids), ) diff --git a/src/features/question/question_creation_handler.rs b/src/features/question/question_creation_handler.rs index f6d9c7d..61d0cfd 100644 --- a/src/features/question/question_creation_handler.rs +++ b/src/features/question/question_creation_handler.rs @@ -8,7 +8,7 @@ use serenity::{ }, futures::StreamExt, }; -use tokio::sync::{mpsc, RwLock}; +use tokio::sync::{RwLock, mpsc}; use tracing::error; use super::modal::{BasicQuestionData, DetailedQuestionData}; diff --git a/src/main.rs b/src/main.rs index 9198e35..0b5af80 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,8 +6,8 @@ mod utils; use std::{ fs::read_to_string, sync::{ - atomic::{AtomicBool, Ordering}, Arc, + atomic::{AtomicBool, Ordering}, }, time::Duration, }; @@ -15,7 +15,7 @@ use std::{ use bpaf::Bpaf; use config::Config; use error::on_error; -use features::{commands, MessageCache, MessageCacheType}; +use features::{MessageCache, MessageCacheType, commands}; use poise::{Framework, FrameworkOptions}; use serenity::{ all::{ActivityData, GuildId, RatelimitInfo, Ready}, diff --git a/src/utils.rs b/src/utils.rs index 694ea12..352bb6a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,17 +4,17 @@ use async_stream::stream; use futures::Stream; use itertools::Itertools; use serenity::{ + Result, all::{ ChannelId, ChannelType, Context, CreateActionRow, CreateAllowedMentions, CreateInteractionResponse, CreateInteractionResponseMessage, CreateMessage, GuildChannel, Http, LightMethod, Message, MessageId, Request, Route, ThreadsData, Timestamp, }, - Result, }; use similar::{Algorithm, ChangeTag, TextDiff}; use tracing::error; -use crate::{config::get_config, error::BotError, MessageCacheType, PContext, PError}; +use crate::{MessageCacheType, PContext, PError, config::get_config, error::BotError}; pub fn create_safe_message() -> CreateMessage { CreateMessage::new().allowed_mentions(CreateAllowedMentions::new().all_users(false)) From fa1a79b7656b703a98419a6133fb03b38f7a502e Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 8 Mar 2025 00:43:53 +0900 Subject: [PATCH 047/104] =?UTF-8?q?=E3=82=B9=E3=83=AC=E3=83=83=E3=83=89?= =?UTF-8?q?=E3=81=AE=E6=8B=9B=E5=BE=85=E6=A9=9F=E8=83=BD=E3=82=92=E5=85=AC?= =?UTF-8?q?=E9=96=8B=E3=82=B9=E3=83=AC=E3=83=83=E3=83=89=E3=81=AE=E3=81=BF?= =?UTF-8?q?=E3=81=AB=E9=99=90=E5=AE=9A=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/error.rs | 2 ++ src/features/thread_auto_invite.rs | 14 +++++++++----- src/utils.rs | 7 ++++--- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/error.rs b/src/error.rs index 11abd0e..6c9e620 100644 --- a/src/error.rs +++ b/src/error.rs @@ -11,6 +11,8 @@ pub enum BotError { HasNoRole, #[error("スレッドでのみ実行できるコマンドです。")] IsNotInThread, + #[error("プライベートスレッドでは実行できません。")] + IsPrivateThread, } pub async fn on_error(error: FrameworkError<'_, CommandData, PError>) { diff --git a/src/features/thread_auto_invite.rs b/src/features/thread_auto_invite.rs index 7d0e4a1..e07dde2 100644 --- a/src/features/thread_auto_invite.rs +++ b/src/features/thread_auto_invite.rs @@ -1,14 +1,14 @@ -use poise::{say_reply, ApplicationContext}; +use poise::{ApplicationContext, say_reply}; use serenity::{ - all::{ChannelId, Context, EditMessage, EventHandler, GuildChannel, Mentionable, RoleId}, + all::{ChannelId, ChannelType, Context, EditMessage, EventHandler, GuildChannel, Mentionable, RoleId}, async_trait, }; use tracing::error; use crate::{ - config::get_config, - utils::{await_initial_message, create_message, has_authed_role, is_in_thread}, CommandData, PError, + config::get_config, + utils::{await_initial_message, create_message, has_authed_role, is_in_public_thread}, }; async fn invite_thread_by_role(ctx: &Context, thread_id: ChannelId, role_id: RoleId) { @@ -32,6 +32,10 @@ pub struct Handler; #[async_trait] impl EventHandler for Handler { async fn thread_create(&self, ctx: Context, thread: GuildChannel) { + if thread.kind == ChannelType::PrivateThread { + return; + } + if await_initial_message(&ctx, &thread).await { return; } @@ -49,7 +53,7 @@ impl EventHandler for Handler { aliases("スレッドに招待"), channel_cooldown = 86400, // 24 時間 check = "has_authed_role", - check = "is_in_thread" + check = "is_in_public_thread" )] pub async fn invite_thread(ctx: ApplicationContext<'_, CommandData, PError>) -> Result<(), PError> { let config = &get_config(ctx.serenity_context()).await.thread_auto_invite; diff --git a/src/utils.rs b/src/utils.rs index 352bb6a..b0318eb 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -92,12 +92,13 @@ pub async fn has_authed_role(ctx: PContext<'_>) -> Result { } /* -実行した場所がスレッドであるかどうかを確認します。 +実行した場所がパブリックスレッドであるかどうかを確認します。 */ -pub async fn is_in_thread(ctx: PContext<'_>) -> Result { +pub async fn is_in_public_thread(ctx: PContext<'_>) -> Result { let channel = ctx.guild_channel().await.ok_or(BotError::IsNotInThread)?; match channel.kind { - ChannelType::PublicThread | ChannelType::PrivateThread | ChannelType::NewsThread => Ok(true), + ChannelType::PublicThread | ChannelType::NewsThread => Ok(true), + ChannelType::PrivateThread => Err(BotError::IsPrivateThread.into()), _ => Err(BotError::IsNotInThread.into()), } } From acd86bbf776f198f8797b7bc0dd006507a1cfb4b Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 8 Mar 2025 00:44:26 +0900 Subject: [PATCH 048/104] bump version --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index abd843b..f823c84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2343,7 +2343,7 @@ checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0" [[package]] name = "valine_bot" -version = "2.3.2" +version = "2.3.3" dependencies = [ "async-stream", "bpaf", diff --git a/Cargo.toml b/Cargo.toml index 3fdd85f..127c496 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "valine_bot" -version = "2.3.2" +version = "2.3.3" edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 46dc9180b38212da9935d3f812f0d13f0f691b4a Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 14 Apr 2025 02:38:58 +0900 Subject: [PATCH 049/104] =?UTF-8?q?=E3=82=B9=E3=83=AC=E3=83=83=E3=83=89?= =?UTF-8?q?=E3=81=AE=E8=87=AA=E5=8B=95=E6=8B=9B=E5=BE=85=E3=81=8CDiscord?= =?UTF-8?q?=E3=81=AE=E3=83=AD=E3=83=BC=E3=83=AB=E3=81=82=E3=81=9F=E3=82=8A?= =?UTF-8?q?=E3=81=AE=E5=88=B6=E9=99=90=E3=82=92=E5=9B=9E=E9=81=BF=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E5=A4=89=E6=9B=B4=E3=81=99?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 427 +++++++++--------- Cargo.toml | 6 +- config.sample.toml | 8 +- src/config.rs | 4 +- src/features/mod.rs | 6 +- src/features/thread_auto_invite.rs | 64 --- src/features/thread_auto_invite/command.rs | 83 ++++ src/features/thread_auto_invite/handler.rs | 136 ++++++ .../thread_auto_invite/member_cache.rs | 150 ++++++ src/features/thread_auto_invite/mod.rs | 9 + .../thread_auto_invite/role_count_cache.rs | 82 ++++ src/main.rs | 16 +- 12 files changed, 698 insertions(+), 293 deletions(-) delete mode 100644 src/features/thread_auto_invite.rs create mode 100644 src/features/thread_auto_invite/command.rs create mode 100644 src/features/thread_auto_invite/handler.rs create mode 100644 src/features/thread_auto_invite/member_cache.rs create mode 100644 src/features/thread_auto_invite/mod.rs create mode 100644 src/features/thread_auto_invite/role_count_cache.rs diff --git a/Cargo.lock b/Cargo.lock index f823c84..ef45ca7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,18 +69,18 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] name = "async-trait" -version = "0.1.87" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -139,9 +139,9 @@ dependencies = [ [[package]] name = "bpaf" -version = "0.9.18" +version = "0.9.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de4d74c5891642753c67ab88f58d971a68dd98673b69689a8c24ce3ec78a412" +checksum = "4848ed5727d39a7573551c205bcb1ccd88c8cad4ed2c80f62e2316f208196b8d" dependencies = [ "bpaf_derive", ] @@ -154,7 +154,7 @@ checksum = "fefb4feeec9a091705938922f26081aad77c64cd2e76cd1c4a9ece8e42e1618a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -214,9 +214,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.16" +version = "1.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" dependencies = [ "shlex", ] @@ -289,28 +289,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] @@ -333,9 +314,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core", "darling_macro", @@ -343,27 +324,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -396,15 +377,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", "serde", @@ -439,21 +420,21 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] name = "duration-str" -version = "0.13.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b55e40ba8fc1ef074c9f9031b4cb88bb1f30c946f80a9305df44973c0b9a2d" +checksum = "9add086174f60bcbcfde7175e71dcfd99da24dfd12f611d0faf74f4f26e15a06" dependencies = [ "chrono", "rust_decimal", "serde", "thiserror 2.0.12", "time", - "winnow 0.6.8", + "winnow", ] [[package]] @@ -479,9 +460,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", "windows-sys 0.59.0", @@ -504,9 +485,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "flate2" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" dependencies = [ "crc32fast", "miniz_oxide", @@ -583,7 +564,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -648,14 +629,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] @@ -682,7 +663,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.7.1", + "indexmap 2.9.0", "slab", "tokio", "tokio-util", @@ -726,9 +707,9 @@ dependencies = [ [[package]] name = "http" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", @@ -798,16 +779,17 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", - "windows-core 0.52.0", + "windows-core 0.61.0", ] [[package]] @@ -860,9 +842,9 @@ dependencies = [ [[package]] name = "icu_locid_transform_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" [[package]] name = "icu_normalizer" @@ -884,9 +866,9 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" [[package]] name = "icu_properties" @@ -905,9 +887,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] name = "icu_provider" @@ -934,7 +916,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -977,9 +959,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -1031,15 +1013,15 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.170" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "linux-raw-sys" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" @@ -1059,9 +1041,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" @@ -1102,9 +1084,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", ] @@ -1154,6 +1136,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "objc2-core-foundation" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daeaf60f25471d26948a1c2f840e3f7d86f4109e3af4e8e4b5cd70c39690d925" +dependencies = [ + "bitflags 2.9.0", +] + [[package]] name = "object" version = "0.36.7" @@ -1165,9 +1156,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "overload" @@ -1224,7 +1215,7 @@ dependencies = [ "async-trait", "derivative", "futures-util", - "indexmap 2.7.1", + "indexmap 2.9.0", "parking_lot", "poise_macros", "regex", @@ -1242,7 +1233,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -1253,11 +1244,11 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -1282,13 +1273,19 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "rand" version = "0.8.5" @@ -1308,7 +1305,7 @@ checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", - "zerocopy 0.8.23", + "zerocopy", ] [[package]] @@ -1346,34 +1343,14 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.1", -] - -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", + "getrandom 0.3.2", ] [[package]] name = "redox_syscall" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" dependencies = [ "bitflags 2.9.0", ] @@ -1453,9 +1430,9 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.13" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", @@ -1467,9 +1444,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.36.0" +version = "1.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" +checksum = "faa7de2ba56ac291bd90c6b9bece784a52ae1411f9506544b3eae36dd2356d50" dependencies = [ "arrayvec", "num-traits", @@ -1483,9 +1460,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "1.0.1" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657" +checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" dependencies = [ "bitflags 2.9.0", "errno", @@ -1614,9 +1591,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] @@ -1632,13 +1609,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -1684,7 +1661,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.7.1", + "indexmap 2.9.0", "serde", "serde_derive", "serde_json", @@ -1701,7 +1678,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -1808,15 +1785,15 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1859,9 +1836,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.99" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -1882,20 +1859,19 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] name = "sysinfo" -version = "0.33.1" +version = "0.34.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" +checksum = "a4b93974b3d3aeaa036504b8eefd4c039dced109171c1ae973f1dc63b2c7e4b2" dependencies = [ - "core-foundation-sys", "libc", "memchr", "ntapi", - "rayon", + "objc2-core-foundation", "windows", ] @@ -1928,13 +1904,12 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tempfile" -version = "3.18.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ - "cfg-if", "fastrand", - "getrandom 0.3.1", + "getrandom 0.3.2", "once_cell", "rustix", "windows-sys 0.59.0", @@ -1966,7 +1941,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -1977,7 +1952,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -1992,9 +1967,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.39" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -2007,15 +1982,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -2033,9 +2008,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.43.0" +version = "1.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" dependencies = [ "backtrace", "bytes", @@ -2056,7 +2031,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -2098,9 +2073,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" dependencies = [ "bytes", "futures-core", @@ -2136,11 +2111,11 @@ version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.9.0", "serde", "serde_spanned", "toml_datetime", - "winnow 0.7.3", + "winnow", ] [[package]] @@ -2169,7 +2144,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -2234,7 +2209,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.2.0", + "http 1.3.1", "httparse", "log", "rand 0.8.5", @@ -2260,9 +2235,9 @@ checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "typesize" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e29e4cac0f1acdbbe7b4deb46876a04246dc6abf60b6f2587bef8ae327cd134c" +checksum = "7da66c62c5b7017a2787e77373c03e6a5aafde77a73bff1ff96e91cd2e128179" dependencies = [ "chrono", "dashmap 5.5.3", @@ -2284,7 +2259,7 @@ checksum = "536b6812192bda8551cfa0e52524e328c6a951b48e66529ee4522d6c721243d6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -2343,7 +2318,7 @@ checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0" [[package]] name = "valine_bot" -version = "2.3.3" +version = "2.4.0" dependencies = [ "async-stream", "bpaf", @@ -2406,9 +2381,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] @@ -2435,7 +2410,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "wasm-bindgen-shared", ] @@ -2470,7 +2445,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2565,23 +2540,27 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.52.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", "windows-targets 0.52.6", ] [[package]] name = "windows-core" -version = "0.57.0" +version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" dependencies = [ - "windows-implement", - "windows-interface", - "windows-result", - "windows-targets 0.52.6", + "windows-implement 0.60.0", + "windows-interface 0.59.1", + "windows-link", + "windows-result 0.3.2", + "windows-strings", ] [[package]] @@ -2592,7 +2571,18 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] @@ -2603,14 +2593,25 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] name = "windows-link" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-result" @@ -2621,6 +2622,24 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -2771,18 +2790,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.7.3" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" +checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" dependencies = [ "memchr", ] @@ -2799,9 +2809,9 @@ dependencies = [ [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags 2.9.0", ] @@ -2838,49 +2848,28 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "synstructure", ] [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" dependencies = [ - "byteorder", - "zerocopy-derive 0.7.35", -] - -[[package]] -name = "zerocopy" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" -dependencies = [ - "zerocopy-derive 0.8.23", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.99", + "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.23" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] [[package]] @@ -2900,7 +2889,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", "synstructure", ] @@ -2929,5 +2918,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.100", ] diff --git a/Cargo.toml b/Cargo.toml index 127c496..aa7b0e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "valine_bot" -version = "2.3.3" +version = "2.4.0" edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -10,10 +10,10 @@ async-stream = "0.3" bpaf = { version = "0.9", features = [ "derive" ] } chrono = "0.4" dashmap = "6.1" -duration-str = "0.13.0" +duration-str = "0.17.0" futures = "0.3" itertools = "0.14" -sysinfo = "0.33" +sysinfo = "0.34" # Git 上では修正されているが、crates.io にはまだ反映されていないため、git で指定 poise = { git = "https://github.com/serenity-rs/poise", rev = "db10b126c8b07f7e1924cba1672f04ff24ed4ec7" } rand = "0.9" diff --git a/config.sample.toml b/config.sample.toml index 5cbfe01..dc5819d 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -63,8 +63,12 @@ startup_message = """スレッドの始まりだぁ! [thread_auto_invite] -# 自動で招待するロールのID -role_id = "000000000000000000" +# ロールの追加削除のトリガーとなるロールID +display_role_id = "000000000000000000" +# それぞれのロールを付与できるメンバーの数 (2500人のサーバでのテストの結果、250人以上にすると招待されない) +min_member_count = 250 +# 実際に呼び出されるロールのID +role_ids = [ "000000000000000000", "000000000000000000" ] [question] diff --git a/src/config.rs b/src/config.rs index 62ed9ad..3657f92 100644 --- a/src/config.rs +++ b/src/config.rs @@ -108,7 +108,9 @@ pub struct ThreadStartupConfig { #[derive(Debug, Deserialize)] pub struct ThreadAutoInviteConfig { - pub role_id: RoleId, + pub display_role_id: RoleId, + pub role_ids: Vec, + pub min_member_count: usize, } #[derive(Debug, Deserialize)] diff --git a/src/features/mod.rs b/src/features/mod.rs index 2d8e363..beeb81b 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -13,10 +13,12 @@ pub use auto_kick::Handler as AutoKickHandler; pub use logging::Handler as LoggingHandler; pub use message_cache::Handler as MessageCacheHandler; pub use question::Handler as QuestionHandler; -pub use thread_auto_invite::Handler as ThreadAutoInviteHandler; +pub use thread_auto_invite::MemberCacheHandler; +pub use thread_auto_invite::ThreadAutoInviteHandler; pub use thread_channel_startup::Handler as ThreadChannelStartupHandler; pub use message_cache::{MessageCache, MessageCacheType}; +pub use thread_auto_invite::{MemberCache, MemberCacheType, RoleCountCache, RoleCountCacheType}; use crate::PCommand; @@ -27,6 +29,8 @@ pub fn commands() -> Vec { pin::pin, admin::reload_config, thread_auto_invite::invite_thread, + thread_auto_invite::add_invite_role, + thread_auto_invite::remove_invite_role, ]) } diff --git a/src/features/thread_auto_invite.rs b/src/features/thread_auto_invite.rs deleted file mode 100644 index e07dde2..0000000 --- a/src/features/thread_auto_invite.rs +++ /dev/null @@ -1,64 +0,0 @@ -use poise::{ApplicationContext, say_reply}; -use serenity::{ - all::{ChannelId, ChannelType, Context, EditMessage, EventHandler, GuildChannel, Mentionable, RoleId}, - async_trait, -}; -use tracing::error; - -use crate::{ - CommandData, PError, - config::get_config, - utils::{await_initial_message, create_message, has_authed_role, is_in_public_thread}, -}; - -async fn invite_thread_by_role(ctx: &Context, thread_id: ChannelId, role_id: RoleId) { - let mut message = { - let msg = create_message("スレッド自動招待用メッセージ"); - match thread_id.send_message(&ctx, msg).await { - Ok(m) => m, - Err(why) => return error!("Error sending message: {:?}", why), - } - }; - - let _ = message - .edit(&ctx, EditMessage::new().content(role_id.mention().to_string())) - .await; - - let _ = message.delete(&ctx).await; -} - -pub struct Handler; - -#[async_trait] -impl EventHandler for Handler { - async fn thread_create(&self, ctx: Context, thread: GuildChannel) { - if thread.kind == ChannelType::PrivateThread { - return; - } - - if await_initial_message(&ctx, &thread).await { - return; - } - - let config = &get_config(&ctx).await.thread_auto_invite; - invite_thread_by_role(&ctx, thread.id, config.role_id).await; - } -} - -/// 招待用ロールを持ったメンバーを実行したスレッドに招待します。 -#[poise::command( - slash_command, - ephemeral, - guild_only, - aliases("スレッドに招待"), - channel_cooldown = 86400, // 24 時間 - check = "has_authed_role", - check = "is_in_public_thread" -)] -pub async fn invite_thread(ctx: ApplicationContext<'_, CommandData, PError>) -> Result<(), PError> { - let config = &get_config(ctx.serenity_context()).await.thread_auto_invite; - ctx.defer_ephemeral().await?; - invite_thread_by_role(ctx.serenity_context(), ctx.channel_id(), config.role_id).await; - say_reply(ctx.into(), "スレッドに招待しました。").await?; - Ok(()) -} diff --git a/src/features/thread_auto_invite/command.rs b/src/features/thread_auto_invite/command.rs new file mode 100644 index 0000000..a7fd0f0 --- /dev/null +++ b/src/features/thread_auto_invite/command.rs @@ -0,0 +1,83 @@ +use poise::{ApplicationContext, say_reply}; + +use crate::{ + CommandData, PError, + config::get_config, + features::thread_auto_invite::handler::invite_thread_by_roles, + utils::{has_authed_role, is_in_public_thread}, +}; + +use super::{handler::Handler, member_cache::MemberCache}; + +/// 招待用ロールを持ったメンバーを実行したスレッドに招待します。 +#[poise::command( + slash_command, + ephemeral, + guild_only, + aliases("スレッドに招待"), + channel_cooldown = 86400, // 24 時間 + check = "has_authed_role", + check = "is_in_public_thread" +)] +pub async fn invite_thread(ctx: ApplicationContext<'_, CommandData, PError>) -> Result<(), PError> { + let config = &get_config(ctx.serenity_context()).await.thread_auto_invite; + ctx.defer_ephemeral().await?; + invite_thread_by_roles(ctx.serenity_context(), ctx.channel_id(), &config.role_ids).await; + say_reply(ctx.into(), "スレッドに招待しました。").await?; + Ok(()) +} + +/// 表示用のロールを持ったメンバーに呼び出し用のロールを付与します +#[poise::command(slash_command, guild_only, default_member_permissions = "MANAGE_ROLES")] +pub async fn add_invite_role(ctx: ApplicationContext<'_, CommandData, PError>) -> Result<(), PError> { + let members = MemberCache::get_all_members(ctx.serenity_context(), ctx.guild_id().unwrap()).await; + + let config = &get_config(ctx.serenity_context()).await.thread_auto_invite; + + ctx.defer().await?; + + let mut role_count = 0; + + for member in members { + if config.role_ids.iter().any(|r| member.roles.contains(r)) { + Handler::role_removed(ctx.serenity_context(), &member, config).await; + } + + if member.roles.contains(&config.display_role_id) { + Handler::role_added(ctx.serenity_context(), &member, config).await; + role_count += 1; + continue; + } + } + + say_reply(ctx.into(), format!("{} 人に招待用ロールを付与しました。", role_count)).await?; + Ok(()) +} + +/// 表示用のロールを持ったメンバーに呼び出し用のロールを削除 +#[poise::command(slash_command, guild_only, default_member_permissions = "MANAGE_ROLES")] +pub async fn remove_invite_role(ctx: ApplicationContext<'_, CommandData, PError>) -> Result<(), PError> { + let members = MemberCache::get_all_members(ctx.serenity_context(), ctx.guild_id().unwrap()).await; + + let config = &get_config(ctx.serenity_context()).await.thread_auto_invite; + + ctx.defer().await?; + + let mut role_count = 0; + + for member in members { + if !config.role_ids.iter().any(|r| member.roles.contains(r)) { + continue; + } + + Handler::role_removed(ctx.serenity_context(), &member, config).await; + role_count += 1; + } + + say_reply( + ctx.into(), + format!("{} 人から招待用ロールを全て削除しました。", role_count), + ) + .await?; + Ok(()) +} diff --git a/src/features/thread_auto_invite/handler.rs b/src/features/thread_auto_invite/handler.rs new file mode 100644 index 0000000..5722d08 --- /dev/null +++ b/src/features/thread_auto_invite/handler.rs @@ -0,0 +1,136 @@ +use itertools::Itertools; +use serenity::{ + all::{ + ChannelId, ChannelType, Context, EditMessage, EventHandler, GuildChannel, GuildId, GuildMemberUpdateEvent, + Member, Mentionable, RoleId, User, + }, + async_trait, +}; +use tracing::{error, info}; + +use crate::{ + config::{ThreadAutoInviteConfig, get_config}, + utils::{await_initial_message, create_message}, +}; + +use super::{RoleCountCache, RoleCountCacheType, member_cache::MemberCache, role_count_cache::find_role}; + +pub struct Handler; + +impl Handler { + pub fn new() -> Self { + Self + } + + pub(super) async fn role_added(ctx: &Context, new: &Member, config: &ThreadAutoInviteConfig) { + let Some(role) = find_role(ctx, new.guild_id, config).await else { + error!("No role found with count less than {}", config.min_member_count); + return; + }; + + let result = new + .add_role(ctx, role) + .await + .map_err(|_| error!("Failed to add role {} to member {}", role, new.user.id)) + .is_ok(); + + if result { + RoleCountCache::increment_count(ctx, role).await; + } + } + + pub(super) async fn role_removed(ctx: &Context, old: &Member, config: &ThreadAutoInviteConfig) { + let roles = old + .roles + .iter() + .filter(|r| config.role_ids.contains(r)) + .collect::>(); + + let mut data = ctx.data.write().await; + let cache = data.get_mut::().unwrap(); + + for role_id in roles { + let result = old + .remove_role(&ctx, role_id) + .await + .map_err(|_| error!("Failed to remove role {} from member {}", role_id, old.user.id)) + .is_ok(); + if result { + cache.decrement(*role_id); + break; + } + } + } +} + +pub async fn invite_thread_by_roles(ctx: &Context, thread_id: ChannelId, role_ids: &[RoleId]) { + let mut message = { + let msg = create_message("スレッド自動招待用メッセージ"); + match thread_id.send_message(&ctx, msg).await { + Ok(m) => m, + Err(why) => return error!("Error sending message: {:?}", why), + } + }; + + let content = role_ids.iter().map(|r| r.mention().to_string()).join(" "); + let _ = message.edit(&ctx, EditMessage::new().content(content)).await; + + let _ = message.delete(&ctx).await; +} + +#[async_trait] +impl EventHandler for Handler { + async fn thread_create(&self, ctx: Context, thread: GuildChannel) { + if thread.kind == ChannelType::PrivateThread { + return; + } + + if await_initial_message(&ctx, &thread).await { + return; + } + + let config = &get_config(&ctx).await.thread_auto_invite; + invite_thread_by_roles(&ctx, thread.id, &config.role_ids).await; + } + + async fn guild_member_addition(&self, ctx: Context, member: Member) { + MemberCache::insert_member(&ctx, &member).await; + } + + async fn guild_member_removal(&self, ctx: Context, guild_id: GuildId, user: User, _: Option) { + MemberCache::remove_member(&ctx, guild_id, user.id).await; + } + + async fn guild_member_update( + &self, + ctx: Context, + _: Option, + new: Option, + event: GuildMemberUpdateEvent, + ) { + let Some(new) = new else { + error!("Member update event with no new member"); + return; + }; + + let Some(old) = MemberCache::get_member(&ctx, event.guild_id, new.user.id).await else { + error!("Member update event with no old member"); + return; + }; + + MemberCache::insert_member(&ctx, &new).await; + + let config = &get_config(&ctx).await.thread_auto_invite; + + let has_new_role = new.roles.contains(&config.display_role_id); + let has_old_role = old.roles.contains(&config.display_role_id); + + if has_new_role && !has_old_role { + Handler::role_added(&ctx, &new, config).await; + info!("added role to {}", new.user.name); + } else if has_old_role && !has_new_role { + Handler::role_removed(&ctx, &old, config).await; + info!("removed role from {}", new.user.name); + } + } +} diff --git a/src/features/thread_auto_invite/member_cache.rs b/src/features/thread_auto_invite/member_cache.rs new file mode 100644 index 0000000..e49eb2c --- /dev/null +++ b/src/features/thread_auto_invite/member_cache.rs @@ -0,0 +1,150 @@ +use std::{ + collections::HashMap, + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }, +}; + +use dashmap::DashMap; +use futures::StreamExt; +use itertools::Itertools; +use serenity::{ + all::{Context, EventHandler, GuildId, Member, UserId}, + async_trait, + prelude::TypeMapKey, +}; +use tracing::{error, info}; + +pub struct Handler { + collected: AtomicBool, +} + +impl Handler { + pub fn new() -> Self { + Self { + collected: AtomicBool::new(false), + } + } +} + +impl Default for Handler { + fn default() -> Self { + Self::new() + } +} + +#[async_trait] +impl EventHandler for Handler { + async fn cache_ready(&self, ctx: Context, guild_ids: Vec) { + if self.collected.swap(true, Ordering::Relaxed) { + return; + } + + let mut total_members = 0; + + let mut data = ctx.data.write().await; + let cache = data.get_mut::().unwrap(); + + for guild_id in guild_ids { + let members = guild_id + .members_iter(&ctx) + .filter_map(async move |m| m.map_err(|e| error!("Error fetching member: {:?}", e)).ok()) + .collect::>() + .await; + total_members += members.len(); + cache.extend_members(members); + } + info!("Member cache is ready. Total members cached: {}", total_members); + } +} + +pub struct MemberCache { + cache: DashMap>, +} + +impl MemberCache { + pub fn new() -> Self { + Self { cache: DashMap::new() } + } + + pub fn insert(&self, member: &Member) { + self.cache + .entry(member.guild_id) + .or_default() + .insert(member.user.id, member.clone()); + } + + pub fn extend(&self, iter: impl IntoIterator) { + iter.into_iter() + .into_group_map_by(|(guild_id, _, _)| *guild_id) + .into_iter() + .map(|(guild_id, members)| { + ( + guild_id, + members + .into_iter() + .map(|(_, user_id, member)| (user_id, member)) + .collect::>(), + ) + }) + .for_each(|(guild_id, members)| { + let mut map = self.cache.entry(guild_id).or_default(); + map.extend(members); + }); + } + + pub fn extend_members(&self, iter: impl IntoIterator) { + self.extend(iter.into_iter().map(|member| (member.guild_id, member.user.id, member))); + } + + pub fn remove(&self, guild_id: GuildId, user_id: UserId) { + if let Some(mut map) = self.cache.get_mut(&guild_id) { + map.remove(&user_id); + } + } + + pub fn get(&self, guild_id: GuildId, user_id: UserId) -> Option { + self.cache.get(&guild_id)?.get(&user_id).cloned() + } + + pub fn get_all(&self, guild_id: GuildId) -> Vec { + self.cache.entry(guild_id).or_default().values().cloned().collect() + } + + pub async fn get_member(ctx: &Context, guild_id: GuildId, user_id: UserId) -> Option { + let data = ctx.data.read().await; + let cache = data.get::().unwrap(); + cache.get(guild_id, user_id) + } + + pub async fn get_all_members(ctx: &Context, guild_id: GuildId) -> Vec { + let data = ctx.data.read().await; + let cache = data.get::().unwrap(); + cache.get_all(guild_id) + } + + pub async fn insert_member(ctx: &Context, member: &Member) { + let mut data = ctx.data.write().await; + let cache = data.get_mut::().unwrap(); + cache.insert(member); + } + + pub async fn remove_member(ctx: &Context, guild_id: GuildId, user_id: UserId) { + let mut data = ctx.data.write().await; + let cache = data.get_mut::().unwrap(); + cache.remove(guild_id, user_id); + } +} + +impl Default for MemberCache { + fn default() -> Self { + Self::new() + } +} + +pub struct MemberCacheType; + +impl TypeMapKey for MemberCacheType { + type Value = Arc; +} diff --git a/src/features/thread_auto_invite/mod.rs b/src/features/thread_auto_invite/mod.rs new file mode 100644 index 0000000..7bba4fc --- /dev/null +++ b/src/features/thread_auto_invite/mod.rs @@ -0,0 +1,9 @@ +mod command; +mod handler; +mod member_cache; +mod role_count_cache; + +pub use command::{add_invite_role, invite_thread, remove_invite_role}; +pub use handler::Handler as ThreadAutoInviteHandler; +pub use member_cache::{Handler as MemberCacheHandler, MemberCache, MemberCacheType}; +pub use role_count_cache::{RoleCountCache, RoleCountCacheType}; diff --git a/src/features/thread_auto_invite/role_count_cache.rs b/src/features/thread_auto_invite/role_count_cache.rs new file mode 100644 index 0000000..282752e --- /dev/null +++ b/src/features/thread_auto_invite/role_count_cache.rs @@ -0,0 +1,82 @@ +use std::sync::Arc; + +use dashmap::DashMap; +use serenity::{ + all::{Context, GuildId, Member, RoleId}, + prelude::TypeMapKey, +}; +use tracing::info; + +use crate::config::ThreadAutoInviteConfig; + +use super::MemberCache; + +#[derive(Debug, Clone)] +pub struct RoleCountCache { + cache: DashMap, +} + +/** + * 今のところ、スレッド招待用ロールのカウントしか更新されない + */ +impl RoleCountCache { + pub fn new() -> Self { + Self { cache: DashMap::new() } + } + + pub fn init(&self, members: &[Member]) { + for member in members { + for role in &member.roles { + info!("{} has role {}", member.user.name, role); + self.increment(*role); + } + } + } + + pub fn increment(&self, role_id: RoleId) { + self.cache.entry(role_id).and_modify(|count| *count += 1).or_insert(1); + } + + pub fn decrement(&self, role_id: RoleId) { + self.cache.entry(role_id).and_modify(|count| *count -= 1).or_insert(0); + } + + pub fn get(&self, role_id: RoleId) -> Option { + self.cache.get(&role_id).map(|count| *count) + } + + pub async fn is_empty(ctx: &Context) -> bool { + let data = ctx.data.read().await; + let cache = data.get::().unwrap(); + cache.cache.is_empty() + } + + pub async fn increment_count(ctx: &Context, role_id: RoleId) { + let mut data = ctx.data.write().await; + let cache = data.get_mut::().unwrap(); + cache.increment(role_id); + } +} + +pub struct RoleCountCacheType; + +impl TypeMapKey for RoleCountCacheType { + type Value = Arc; +} + +pub async fn find_role(ctx: &Context, guild_id: GuildId, config: &ThreadAutoInviteConfig) -> Option { + if RoleCountCache::is_empty(ctx).await { + let members = MemberCache::get_all_members(ctx, guild_id).await; + let mut data = ctx.data.write().await; + let cache = data.get_mut::().unwrap(); + cache.init(&members); + } + + let data = ctx.data.read().await; + let cache = data.get::().unwrap(); + config + .role_ids + .iter() + .find(|r| cache.get(**r).unwrap_or(0) < config.min_member_count) + .cloned() +} diff --git a/src/main.rs b/src/main.rs index 0b5af80..850d5e6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,9 @@ use std::{ use bpaf::Bpaf; use config::Config; use error::on_error; -use features::{MessageCache, MessageCacheType, commands}; +use features::{ + MemberCache, MemberCacheType, MessageCache, MessageCacheType, RoleCountCache, RoleCountCacheType, commands, +}; use poise::{Framework, FrameworkOptions}; use serenity::{ all::{ActivityData, GuildId, RatelimitInfo, Ready}, @@ -125,21 +127,29 @@ async fn main() { }) .build(); - let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::GUILDS | GatewayIntents::MESSAGE_CONTENT; + let intents = GatewayIntents::GUILD_MESSAGES + | GatewayIntents::GUILDS + | GatewayIntents::GUILD_MEMBERS + | GatewayIntents::MESSAGE_CONTENT; + let mut settings = CacheSettings::default(); settings.max_messages = 1_000_000; + let mut client = Client::builder(&config.bot.token, intents) .framework(framework) .event_handler(MainHandler::new()) .event_handler(features::AuthHandler::new()) .event_handler(features::AutoKickHandler::new()) .event_handler(features::LoggingHandler) - .event_handler(features::ThreadAutoInviteHandler) + .event_handler(features::MemberCacheHandler::new()) + .event_handler(features::ThreadAutoInviteHandler::new()) .event_handler(features::ThreadChannelStartupHandler) .event_handler(features::QuestionHandler) .event_handler(features::MessageCacheHandler::new(config.message_cache.disabled)) .cache_settings(settings) .type_map_insert::(Arc::new(MessageCache::new())) + .type_map_insert::(Arc::new(MemberCache::new())) + .type_map_insert::(Arc::new(RoleCountCache::new())) .type_map_insert::(Arc::new(config)) .await .expect("Err creating client"); From afbd74224c2762ce49164d76c7fffc149369ca73 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 5 May 2025 23:26:07 +0900 Subject: [PATCH 050/104] Create LICENSE (#7) --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1102a3c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Lapis256 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 47b10e8e8294255d27f6bf6b29f82f649f3e09b2 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 2 Jun 2025 00:40:34 +0900 Subject: [PATCH 051/104] =?UTF-8?q?=E3=83=A1=E3=83=B3=E3=83=90=E3=83=BC?= =?UTF-8?q?=E3=81=8C=E6=8A=9C=E3=81=91=E3=81=9F=E3=81=A8=E3=81=8D=E3=81=AB?= =?UTF-8?q?=E3=83=AD=E3=83=BC=E3=83=AB=E3=82=AD=E3=83=A3=E3=83=83=E3=82=B7?= =?UTF-8?q?=E3=83=A5=E3=82=92=E6=9B=B4=E6=96=B0=E3=81=99=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/thread_auto_invite/handler.rs | 14 ++++++++++++++ src/features/thread_auto_invite/member_cache.rs | 1 + 2 files changed, 15 insertions(+) diff --git a/src/features/thread_auto_invite/handler.rs b/src/features/thread_auto_invite/handler.rs index 5722d08..f8bb7e7 100644 --- a/src/features/thread_auto_invite/handler.rs +++ b/src/features/thread_auto_invite/handler.rs @@ -98,6 +98,20 @@ impl EventHandler for Handler { } async fn guild_member_removal(&self, ctx: Context, guild_id: GuildId, user: User, _: Option) { + let Some(old) = MemberCache::get_member(&ctx, guild_id, user.id).await else { + error!("Member update event with no old member"); + return; + }; + + let config = &get_config(&ctx).await.thread_auto_invite; + + let mut data = ctx.data.write().await; + let cache = data.get_mut::().unwrap(); + old.roles + .iter() + .filter(|r| config.role_ids.contains(r)) + .for_each(|r| cache.decrement(*r)); + MemberCache::remove_member(&ctx, guild_id, user.id).await; } diff --git a/src/features/thread_auto_invite/member_cache.rs b/src/features/thread_auto_invite/member_cache.rs index e49eb2c..3f6d1e0 100644 --- a/src/features/thread_auto_invite/member_cache.rs +++ b/src/features/thread_auto_invite/member_cache.rs @@ -59,6 +59,7 @@ impl EventHandler for Handler { } } +#[derive(Clone, Debug)] pub struct MemberCache { cache: DashMap>, } From 1e4bd830b3855efb656c030f2faef0d27dad26d8 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 2 Jun 2025 00:40:53 +0900 Subject: [PATCH 052/104] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 472 ++++++++++++++++++++++---------------------- Cargo.toml | 2 +- rust-toolchain.toml | 2 +- src/main.rs | 7 +- 4 files changed, 241 insertions(+), 242 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ef45ca7..e398fd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,7 +69,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -80,7 +80,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -91,9 +91,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -124,9 +124,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "block-buffer" @@ -139,9 +139,9 @@ dependencies = [ [[package]] name = "bpaf" -version = "0.9.19" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4848ed5727d39a7573551c205bcb1ccd88c8cad4ed2c80f62e2316f208196b8d" +checksum = "473976d7a8620bb1e06dcdd184407c2363fe4fec8e983ee03ed9197222634a31" dependencies = [ "bpaf_derive", ] @@ -154,7 +154,7 @@ checksum = "fefb4feeec9a091705938922f26081aad77c64cd2e76cd1c4a9ece8e42e1618a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -214,9 +214,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.19" +version = "1.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" +checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" dependencies = [ "shlex", ] @@ -229,9 +229,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", @@ -333,7 +333,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -344,7 +344,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -420,7 +420,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -460,9 +460,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", "windows-sys 0.59.0", @@ -564,7 +564,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -618,9 +618,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", @@ -629,9 +629,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", @@ -684,9 +684,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" [[package]] name = "hex" @@ -789,7 +789,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.0", + "windows-core", ] [[package]] @@ -803,21 +803,22 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", @@ -826,31 +827,11 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", @@ -858,67 +839,54 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -938,9 +906,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -964,7 +932,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.3", "serde", ] @@ -1013,9 +981,9 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.171" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "linux-raw-sys" @@ -1025,15 +993,15 @@ checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -1093,13 +1061,13 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1138,11 +1106,21 @@ dependencies = [ [[package]] name = "objc2-core-foundation" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daeaf60f25471d26948a1c2f840e3f7d86f4109e3af4e8e4b5cd70c39690d925" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a" +dependencies = [ + "libc", + "objc2-core-foundation", ] [[package]] @@ -1168,9 +1146,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -1178,9 +1156,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", @@ -1233,7 +1211,16 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", +] + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", ] [[package]] @@ -1253,9 +1240,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -1266,7 +1253,7 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "memchr", "unicase", ] @@ -1299,13 +1286,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", - "zerocopy", ] [[package]] @@ -1334,7 +1320,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] @@ -1343,16 +1329,16 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", ] [[package]] name = "redox_syscall" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -1436,7 +1422,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.15", + "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", @@ -1460,11 +1446,11 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys", @@ -1508,9 +1494,12 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] [[package]] name = "rustls-webpki" @@ -1535,9 +1524,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "ryu" @@ -1615,7 +1604,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1678,7 +1667,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1690,7 +1679,7 @@ dependencies = [ "arrayvec", "async-trait", "base64 0.22.1", - "bitflags 2.9.0", + "bitflags 2.9.1", "bytes", "chrono", "command_attr", @@ -1746,9 +1735,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] @@ -1791,9 +1780,9 @@ checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "socket2" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1836,9 +1825,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -1853,25 +1842,26 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] name = "sysinfo" -version = "0.34.2" +version = "0.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b93974b3d3aeaa036504b8eefd4c039dced109171c1ae973f1dc63b2c7e4b2" +checksum = "79251336d17c72d9762b8b54be4befe38d2db56fbbc0241396d70f173c39d47a" dependencies = [ "libc", "memchr", "ntapi", "objc2-core-foundation", + "objc2-io-kit", "windows", ] @@ -1904,12 +1894,12 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tempfile" -version = "3.19.1" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", - "getrandom 0.3.2", + "getrandom 0.3.3", "once_cell", "rustix", "windows-sys 0.59.0", @@ -1941,7 +1931,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1952,7 +1942,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1998,9 +1988,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -2008,9 +1998,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.44.2" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", @@ -2031,7 +2021,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2068,14 +2058,14 @@ dependencies = [ "tokio", "tokio-rustls 0.25.0", "tungstenite", - "webpki-roots 0.26.8", + "webpki-roots 0.26.11", ] [[package]] name = "tokio-util" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", @@ -2086,9 +2076,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.20" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" dependencies = [ "serde", "serde_spanned", @@ -2098,26 +2088,33 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ "indexmap 2.9.0", "serde", "serde_spanned", "toml_datetime", + "toml_write", "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" + [[package]] name = "tower-service" version = "0.3.3" @@ -2144,7 +2141,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2259,7 +2256,7 @@ checksum = "536b6812192bda8551cfa0e52524e328c6a951b48e66529ee4522d6c721243d6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2298,12 +2295,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -2328,7 +2319,7 @@ dependencies = [ "futures", "itertools", "poise", - "rand 0.9.0", + "rand 0.9.1", "regex", "serde", "serde_with", @@ -2410,7 +2401,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "wasm-bindgen-shared", ] @@ -2445,7 +2436,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2490,9 +2481,18 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "0.26.8" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.0", +] + +[[package]] +name = "webpki-roots" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" +checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" dependencies = [ "rustls-pki-types", ] @@ -2530,48 +2530,48 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.57.0" +version = "0.61.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" dependencies = [ - "windows-core 0.57.0", - "windows-targets 0.52.6", + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", ] [[package]] -name = "windows-core" -version = "0.57.0" +name = "windows-collections" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-implement 0.57.0", - "windows-interface 0.57.0", - "windows-result 0.1.2", - "windows-targets 0.52.6", + "windows-core", ] [[package]] name = "windows-core" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement 0.60.0", - "windows-interface 0.59.1", + "windows-implement", + "windows-interface", "windows-link", - "windows-result 0.3.2", + "windows-result", "windows-strings", ] [[package]] -name = "windows-implement" -version = "0.57.0" +name = "windows-future" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", + "windows-core", + "windows-link", + "windows-threading", ] [[package]] @@ -2582,18 +2582,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", -] - -[[package]] -name = "windows-interface" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2604,7 +2593,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2614,28 +2603,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] -name = "windows-result" -version = "0.1.2" +name = "windows-numerics" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-targets 0.52.6", + "windows-core", + "windows-link", ] [[package]] name = "windows-result" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] @@ -2698,6 +2688,15 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -2790,9 +2789,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.6" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] @@ -2813,26 +2812,20 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", @@ -2842,34 +2835,34 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2889,7 +2882,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "synstructure", ] @@ -2899,11 +2892,22 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", @@ -2912,11 +2916,11 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] diff --git a/Cargo.toml b/Cargo.toml index aa7b0e1..18271ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ dashmap = "6.1" duration-str = "0.17.0" futures = "0.3" itertools = "0.14" -sysinfo = "0.34" +sysinfo = "0.35" # Git 上では修正されているが、crates.io にはまだ反映されていないため、git で指定 poise = { git = "https://github.com/serenity-rs/poise", rev = "db10b126c8b07f7e1924cba1672f04ff24ed4ec7" } rand = "0.9" diff --git a/rust-toolchain.toml b/rust-toolchain.toml index c5794a6..0837c1f 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.85" +channel = "1.87" diff --git a/src/main.rs b/src/main.rs index 850d5e6..359f46b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -97,12 +97,7 @@ async fn main() { tracing_subscriber::fmt::init(); let config = read_to_string("config.toml").expect("Failed to read config.toml"); - let config = match toml::from_str::(&config) { - Ok(config) => config, - Err(e) => { - panic!("Failed to parse config.toml: {}", e); - } - }; + let config = toml::from_str::(&config).unwrap_or_else(|e| panic!("Failed to parse config.toml: {}", e)); let options = options().run(); From 53ecc45beccc259f51d6cb65cf4c5147bebc5eaa Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 2 Jun 2025 07:12:17 +0900 Subject: [PATCH 053/104] =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=86=E3=83=8A?= =?UTF-8?q?=E3=82=A4=E3=83=A1=E3=83=BC=E3=82=B8=E3=82=92=E4=BD=9C=E6=88=90?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Dockerを使うように * Fix workflow * Update release.yml * Fix release.yml * 別のジョブでDocker Imageをビルドするように * Fix release.yml --- .github/workflows/release.yml | 64 +++++++++++++++++++++++++++++++---- Dockerfile | 18 ++++++++++ 2 files changed, 75 insertions(+), 7 deletions(-) create mode 100644 Dockerfile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 111e0ab..342cac6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,6 +2,7 @@ name: Release permissions: contents: write + packages: write on: release: @@ -17,11 +18,9 @@ jobs: - os: ubuntu-latest target: x86_64-unknown-linux-gnu glibc: 2.17 - extension: "" - os: ubuntu-latest target: aarch64-unknown-linux-gnu glibc: 2.17 - extension: "" runs-on: ${{ matrix.os }} steps: @@ -36,7 +35,7 @@ jobs: # zigをインストール - name: Install Zig - uses: mlugg/setup-zig@v1 + uses: mlugg/setup-zig@v2 # rustをインストール - name: Install Rust toolchain @@ -59,11 +58,18 @@ jobs: run: | cargo build --release --target ${{ matrix.target }} - # ビルド済みバイナリをリネーム - - name: Rename artifacts + # ビルド済みバイナリをコピーしてリネーム + - name: Copy and rename artifacts shell: bash run: | - mv target/${{ matrix.target }}/release/${{ env.PROJECT_NAME }}{,-${{ github.ref_name }}-${{ matrix.target }}${{ matrix.extension }}} + mv target/${{ matrix.target }}/release/${{ env.PROJECT_NAME }} ./target/release/ + cp target/release/${{ env.PROJECT_NAME }}{,-${{ github.ref_name }}-${{ matrix.target }}} + cp target/release/${{ env.PROJECT_NAME }} target/release/${{ matrix.target }} + + - uses: actions/upload-artifact@v4 + with: + name: binary-${{ matrix.target }} + path: target/release/${{ matrix.target }} # ビルド済みバイナリをReleasesに配置 - name: Release @@ -72,4 +78,48 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: files: | - target/${{ matrix.target }}/release/${{ env.PROJECT_NAME }}-${{ github.ref_name }}-${{ matrix.target }}${{ matrix.extension }} + target/release/${{ env.PROJECT_NAME }}-${{ github.ref_name }}-${{ matrix.target }} + + docker: + runs-on: ubuntu-latest + needs: build + name: Build and push Docker image + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + path: binaries + merge-multiple: true + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=sha,enable=false + type=semver,pattern={{version}} + + - name: Build and push Docker image + id: push + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + platforms: linux/amd64,linux/arm64 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..742d9cc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM debian:bookworm-slim AS builder + +ARG TARGETPLATFORM + +WORKDIR /builder +COPY binaries binaries + +RUN case "$TARGETPLATFORM" in \ + "linux/amd64") mv binaries/x86_64-unknown-linux-gnu ./valine_bot ;; \ + "linux/arm64") mv binaries/aarch64-unknown-linux-gnu ./valine_bot ;; \ + *) echo "Unsupported TARGETPLATFORM=$TARGETPLATFORM" >&2; exit 1 ;; \ + esac && \ + chmod +x ./valine_bot + +FROM debian:bookworm-slim +WORKDIR /app +COPY --from=builder /builder/valine_bot /app/valine_bot +CMD ["./valine_bot"] From b8f432e7e1c6b38d0ca22b4754089ebd9c45df6d Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 2 Jun 2025 08:16:15 +0900 Subject: [PATCH 054/104] =?UTF-8?q?=E3=83=99=E3=83=BC=E3=82=B9=E3=82=A4?= =?UTF-8?q?=E3=83=A1=E3=83=BC=E3=82=B8=E3=82=92Distroless=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 742d9cc..7befbc4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ RUN case "$TARGETPLATFORM" in \ esac && \ chmod +x ./valine_bot -FROM debian:bookworm-slim +FROM gcr.io/distroless/cc-debian12:nonroot WORKDIR /app COPY --from=builder /builder/valine_bot /app/valine_bot CMD ["./valine_bot"] From 058708c520293eef2b811a85855c37634b614671 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 2 Jun 2025 08:19:19 +0900 Subject: [PATCH 055/104] =?UTF-8?q?CMD=E3=82=92ENTRYPOINT=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7befbc4..a523127 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,4 +15,4 @@ RUN case "$TARGETPLATFORM" in \ FROM gcr.io/distroless/cc-debian12:nonroot WORKDIR /app COPY --from=builder /builder/valine_bot /app/valine_bot -CMD ["./valine_bot"] +ENTRYPOINT ["./valine_bot"] From 4dd565e7088b069bc2e11a938d3eb3b6c594e893 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 4 May 2026 03:06:40 +0900 Subject: [PATCH 056/104] =?UTF-8?q?chore:=20Rust=20=E3=82=92=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 0837c1f..4933b3b 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.87" +channel = "1.95" From f4f7f539e6ce4bff98d6f636973a255277177c2b Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 4 May 2026 03:06:57 +0900 Subject: [PATCH 057/104] =?UTF-8?q?chore:=20=E4=BE=9D=E5=AD=98=E3=83=A9?= =?UTF-8?q?=E3=82=A4=E3=83=96=E3=83=A9=E3=83=AA=E3=82=92=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 1807 +++++++++++++++++++++++++++++++--------------------- Cargo.toml | 19 +- 2 files changed, 1090 insertions(+), 736 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e398fd6..8c79dfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,36 +2,21 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -41,6 +26,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + [[package]] name = "arrayvec" version = "0.7.6" @@ -69,46 +60,31 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] -name = "autocfg" -version = "1.4.0" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - -[[package]] -name = "base64" -version = "0.21.7" +name = "autocfg" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "base64" @@ -118,15 +94,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "1.3.2" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "block-buffer" @@ -139,35 +109,45 @@ dependencies = [ [[package]] name = "bpaf" -version = "0.9.20" +version = "0.9.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473976d7a8620bb1e06dcdd184407c2363fe4fec8e983ee03ed9197222634a31" +checksum = "0b8d4b90317451025bfa1f7d359a5fa698a5f745927ff27696429b7fbd7c0c48" dependencies = [ "bpaf_derive", ] [[package]] name = "bpaf_derive" -version = "0.5.17" +version = "0.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fefb4feeec9a091705938922f26081aad77c64cd2e76cd1c4a9ece8e42e1618a" +checksum = "549ca9c364fdc06f9f36d1356980193d930abc80db848193c2dd4d0e9a83de1b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", +] + +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "serde", ] [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytecount" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" [[package]] name = "byteorder" @@ -177,17 +157,17 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "camino" -version = "1.1.9" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -214,26 +194,43 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.25" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ + "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", @@ -244,25 +241,15 @@ dependencies = [ [[package]] name = "command_attr" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fcc89439e1bb4e19050a9586a767781a3060000d2f3296fd2a40597ad9421c5" +checksum = "8208103c5e25a091226dfa8d61d08d0561cc14f31b25691811ba37d4ec9b157b" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -278,11 +265,20 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -304,9 +300,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -318,8 +314,18 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", ] [[package]] @@ -333,7 +339,20 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.101", + "syn 2.0.117", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", ] [[package]] @@ -342,9 +361,20 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core", + "darling_core 0.20.11", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] @@ -377,18 +407,18 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", - "serde", + "serde_core", ] [[package]] @@ -420,37 +450,34 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "duration-str" -version = "0.17.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9add086174f60bcbcfde7175e71dcfd99da24dfd12f611d0faf74f4f26e15a06" +checksum = "027cd1402a609c71a9ac333c7e3d90ee042e7a131da1a83da8c60df323f12f61" dependencies = [ "chrono", "rust_decimal", "serde", - "thiserror 2.0.12", + "thiserror 2.0.18", "time", - "winnow", + "winnow 0.7.15", ] [[package]] -name = "either" -version = "1.15.0" +name = "dyn-clone" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] -name = "encoding_rs" -version = "0.8.35" +name = "either" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "equivalent" @@ -460,12 +487,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.12" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -479,15 +506,21 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" -version = "1.1.1" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", @@ -499,20 +532,26 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -525,9 +564,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -535,15 +574,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -552,38 +591,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -593,19 +632,9 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -618,57 +647,50 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", + "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", ] [[package]] -name = "gimli" -version = "0.31.1" +name = "getrandom" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasip2", + "wasip3", +] [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" - -[[package]] -name = "h2" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap 2.9.0", - "slab", - "tokio", - "tokio-util", - "tracing", -] +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "hashbrown" @@ -684,9 +706,24 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.3" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" @@ -696,34 +733,34 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" -version = "0.2.12" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] [[package]] -name = "http" -version = "1.3.1" +name = "http-body" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "fnv", - "itoa", + "http", ] [[package]] -name = "http-body" -version = "0.4.6" +name = "http-body-util" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "http 0.2.12", + "futures-core", + "http", + "http-body", "pin-project-lite", ] @@ -733,55 +770,70 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - [[package]] name = "hyper" -version = "0.14.32" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ + "atomic-waker", "bytes", "futures-channel", "futures-core", - "futures-util", - "h2", - "http 0.2.12", + "http", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", - "socket2", + "smallvec", "tokio", - "tower-service", - "tracing", "want", ] [[package]] name = "hyper-rustls" -version = "0.24.2" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls 0.23.40", + "tokio", + "tokio-rustls 0.26.4", + "tower-service", + "webpki-roots 1.0.7", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", "futures-util", - "http 0.2.12", + "http", + "http-body", "hyper", - "rustls 0.21.12", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", "tokio", - "tokio-rustls 0.24.1", + "tower-service", + "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -803,12 +855,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -816,9 +869,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -829,11 +882,10 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -844,42 +896,38 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", - "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", - "stable_deref_trait", - "tinystr", "writeable", "yoke", "zerofrom", @@ -887,6 +935,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -895,9 +949,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -906,9 +960,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -927,20 +981,31 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.15.3", + "hashbrown 0.17.0", "serde", + "serde_core", ] [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +dependencies = [ + "memchr", + "serde", +] [[package]] name = "itertools" @@ -953,16 +1018,18 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -973,6 +1040,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "levenshtein" version = "1.0.5" @@ -981,43 +1054,48 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.27" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "memchr" -version = "2.7.4" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "mime" @@ -1052,48 +1130,48 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] name = "mio" -version = "1.0.4" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] name = "ntapi" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" dependencies = [ "winapi", ] [[package]] name = "nu-ansi-term" -version = "0.46.0" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "overload", - "winapi", + "windows-sys 0.61.2", ] [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-traits" @@ -1106,49 +1184,34 @@ dependencies = [ [[package]] name = "objc2-core-foundation" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.9.1", + "bitflags", ] [[package]] name = "objc2-io-kit" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" dependencies = [ "libc", "objc2-core-foundation", ] -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "overload" -version = "0.1.1" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -1156,69 +1219,53 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link", ] [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "poise" -version = "0.6.1" -source = "git+https://github.com/serenity-rs/poise?rev=db10b126c8b07f7e1924cba1672f04ff24ed4ec7#db10b126c8b07f7e1924cba1672f04ff24ed4ec7" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711433114bca9ff68582aa58af5cbcf573e83250a8714c2dca1c36f453cad3ae" dependencies = [ "async-trait", "derivative", "futures-util", - "indexmap 2.9.0", + "indexmap 2.14.0", "parking_lot", - "poise_macros", "regex", "serenity", + "serenity_poise_macros", "tokio", "tracing", "trim-in-place", ] -[[package]] -name = "poise_macros" -version = "0.6.1" -source = "git+https://github.com/serenity-rs/poise?rev=db10b126c8b07f7e1924cba1672f04ff24ed4ec7#db10b126c8b07f7e1924cba1672f04ff24ed4ec7" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.101", -] - [[package]] name = "potential_utf" -version = "0.1.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -1238,11 +1285,21 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -1253,31 +1310,92 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" dependencies = [ - "bitflags 2.9.1", + "bitflags", "memchr", "unicase", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.40", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.4", + "ring", + "rustc-hash", + "rustls 0.23.40", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" -version = "1.0.40" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -1286,12 +1404,23 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.1" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", ] [[package]] @@ -1311,7 +1440,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -1320,32 +1449,58 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "redox_syscall" -version = "0.5.12" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ - "bitflags 2.9.1", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -1355,9 +1510,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -1366,52 +1521,50 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.11.27" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64 0.21.7", + "base64", "bytes", - "encoding_rs", "futures-core", "futures-util", - "h2", - "http 0.2.12", + "http", "http-body", + "http-body-util", "hyper", "hyper-rustls", - "ipnet", + "hyper-util", "js-sys", "log", - "mime", "mime_guess", - "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.12", - "rustls-pemfile", + "quinn", + "rustls 0.23.40", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", - "system-configuration", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls 0.26.4", "tokio-util", + "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 0.25.4", - "winreg", + "webpki-roots 1.0.7", ] [[package]] @@ -1422,7 +1575,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -1430,43 +1583,31 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.37.1" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa7de2ba56ac291bd90c6b9bece784a52ae1411f9506544b3eae36dd2356d50" +checksum = "2ce901f9a19d251159075a4c37af514c3b8ef99c22e02dd8c19161cf397ee94a" dependencies = [ "arrayvec", "num-traits", ] [[package]] -name = "rustc-demangle" -version = "0.1.24" +name = "rustc-hash" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustix" -version = "1.0.7" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.9.1", + "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", + "windows-sys 0.61.2", ] [[package]] @@ -1484,38 +1625,45 @@ dependencies = [ ] [[package]] -name = "rustls-pemfile" -version = "1.0.4" +name = "rustls" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ - "base64 0.21.7", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.103.13", + "subtle", + "zeroize", ] [[package]] name = "rustls-pki-types" -version = "1.12.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ + "web-time", "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.101.7" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", + "rustls-pki-types", "untrusted", ] [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "ring", "rustls-pki-types", @@ -1524,15 +1672,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" @@ -1544,21 +1692,35 @@ dependencies = [ ] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "schemars" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] [[package]] -name = "sct" -version = "0.7.1" +name = "schemars" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" dependencies = [ - "ring", - "untrusted", + "dyn-clone", + "ref-cast", + "serde", + "serde_json", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "secrecy" version = "0.8.0" @@ -1571,18 +1733,29 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.26" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" dependencies = [ "serde", + "serde_core", ] [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] @@ -1598,34 +1771,35 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] name = "serde_spanned" -version = "0.6.8" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -1642,17 +1816,18 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.12.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +checksum = "f05839ce67618e14a09b286535c0d9c94e85ef25469b0e13cb4f844e5593eb19" dependencies = [ - "base64 0.22.1", + "base64", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.9.0", - "serde", - "serde_derive", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", "serde_json", "serde_with_macros", "time", @@ -1660,38 +1835,38 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.12.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +checksum = "cf2ebbe86054f9b45bc3881e865683ccfaccce97b9b4cb53f3039d67f355a334" dependencies = [ - "darling", + "darling 0.23.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "serenity" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d72ec4323681bf9a3cabe40fd080abc2435859b502a1b5aa9bf693f125bfa76" +checksum = "9bde37f42765dfdc34e2a039e0c84afbf79a3101c1941763b0beb816c2f17541" dependencies = [ "arrayvec", "async-trait", - "base64 0.22.1", - "bitflags 2.9.1", + "base64", + "bitflags", "bytes", "chrono", "command_attr", "dashmap 5.5.3", "flate2", "futures", - "fxhash", "levenshtein", "mime_guess", "parking_lot", "percent-encoding", "reqwest", + "rustc-hash", "secrecy", "serde", "serde_cow", @@ -1707,6 +1882,18 @@ dependencies = [ "uwl", ] +[[package]] +name = "serenity_poise_macros" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bc8407893be0d973a7bb48325e10e0160a0db44f4099467c83179688220e5f5" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "sha1" version = "0.10.6" @@ -1714,7 +1901,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -1735,18 +1922,28 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + [[package]] name = "similar" -version = "2.7.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" +checksum = "04d93e861ede2e497b47833469b8ec9d5c07fa4c78ce7a00f6eb7dd8168b4b3f" +dependencies = [ + "bstr", +] [[package]] name = "skeptic" @@ -1765,34 +1962,31 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.9" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.10" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" @@ -1825,9 +2019,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1836,9 +2030,12 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "0.1.2" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -1848,14 +2045,14 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "sysinfo" -version = "0.35.1" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79251336d17c72d9762b8b54be4befe38d2db56fbbc0241396d70f173c39d47a" +checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f" dependencies = [ "libc", "memchr", @@ -1865,27 +2062,6 @@ dependencies = [ "windows", ] -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "tagptr" version = "0.2.0" @@ -1894,15 +2070,15 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tempfile" -version = "3.20.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1916,11 +2092,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.18", ] [[package]] @@ -1931,56 +2107,55 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] name = "time" -version = "0.3.41" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -1988,21 +2163,35 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" -version = "1.45.1" +version = "1.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ - "backtrace", "bytes", "libc", "mio", @@ -2010,38 +2199,38 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "tokio-rustls" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ - "rustls 0.21.12", + "rustls 0.22.4", + "rustls-pki-types", "tokio", ] [[package]] name = "tokio-rustls" -version = "0.25.0" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.22.4", - "rustls-pki-types", + "rustls 0.23.40", "tokio", ] @@ -2063,9 +2252,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -2076,44 +2265,81 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.22" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ - "serde", + "indexmap 2.14.0", + "serde_core", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_parser", + "toml_writer", + "winnow 1.0.2", ] [[package]] name = "toml_datetime" -version = "0.6.9" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ - "serde", + "serde_core", ] [[package]] -name = "toml_edit" -version = "0.22.26" +name = "toml_parser" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "indexmap 2.9.0", - "serde", - "serde_spanned", - "toml_datetime", - "toml_write", - "winnow", + "winnow 1.0.2", ] [[package]] -name = "toml_write" -version = "0.1.1" +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" @@ -2123,9 +2349,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -2135,20 +2361,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -2167,9 +2393,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.19" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "nu-ansi-term", "sharded-slab", @@ -2187,9 +2413,9 @@ checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" [[package]] name = "triomphe" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" +checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39" [[package]] name = "try-lock" @@ -2206,10 +2432,10 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.3.1", + "http", "httparse", "log", - "rand 0.8.5", + "rand 0.8.6", "rustls 0.22.4", "rustls-pki-types", "sha1", @@ -2226,9 +2452,9 @@ checksum = "74b08b0c1257381af16a5c3605254d529d3e7e109f3c62befc5d168968192998" [[package]] name = "typenum" -version = "1.18.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "typesize" @@ -2256,20 +2482,26 @@ checksum = "536b6812192bda8551cfa0e52524e328c6a951b48e66529ee4522d6c721243d6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "unicase" -version = "2.8.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "untrusted" @@ -2279,14 +2511,15 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -2309,7 +2542,7 @@ checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0" [[package]] name = "valine_bot" -version = "2.4.0" +version = "2.4.1" dependencies = [ "async-stream", "bpaf", @@ -2319,14 +2552,14 @@ dependencies = [ "futures", "itertools", "poise", - "rand 0.9.1", + "rand 0.10.1", "regex", "serde", "serde_with", "serenity", "similar", "sysinfo", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", "toml", "tracing", @@ -2366,63 +2599,56 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen 0.57.1", ] [[package]] -name = "wasm-bindgen" -version = "0.2.100" +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", + "wit-bindgen 0.51.0", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" +name = "wasm-bindgen" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.101", + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" dependencies = [ - "cfg-if", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2430,26 +2656,48 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.101", - "wasm-bindgen-backend", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.14.0", + "wasm-encoder", + "wasmparser", +] + [[package]] name = "wasm-streams" version = "0.4.2" @@ -2463,21 +2711,37 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap 2.14.0", + "semver", +] + [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] -name = "webpki-roots" -version = "0.25.4" +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] [[package]] name = "webpki-roots" @@ -2485,14 +2749,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.0", + "webpki-roots 1.0.7", ] [[package]] name = "webpki-roots" -version = "1.0.0" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" dependencies = [ "rustls-pki-types", ] @@ -2515,11 +2779,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2530,31 +2794,30 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.61.1" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ "windows-collections", "windows-core", "windows-future", - "windows-link", "windows-numerics", ] [[package]] name = "windows-collections" -version = "0.2.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ "windows-core", ] [[package]] name = "windows-core" -version = "0.61.2" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", @@ -2565,9 +2828,9 @@ dependencies = [ [[package]] name = "windows-future" -version = "0.2.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ "windows-core", "windows-link", @@ -2576,37 +2839,37 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "windows-link" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-numerics" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ "windows-core", "windows-link", @@ -2614,31 +2877,22 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.3.4" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -2650,26 +2904,20 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.52.6", + "windows-targets 0.53.5", ] [[package]] -name = "windows-targets" -version = "0.48.5" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-link", ] [[package]] @@ -2681,7 +2929,7 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", @@ -2689,19 +2937,30 @@ dependencies = [ ] [[package]] -name = "windows-threading" -version = "0.1.0" +name = "windows-targets" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" +name = "windows-threading" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] [[package]] name = "windows_aarch64_gnullvm" @@ -2710,10 +2969,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" +name = "windows_aarch64_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -2722,10 +2981,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] -name = "windows_i686_gnu" -version = "0.48.5" +name = "windows_aarch64_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -2733,6 +2992,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" @@ -2740,10 +3005,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] -name = "windows_i686_msvc" -version = "0.48.5" +name = "windows_i686_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -2752,10 +3017,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" +name = "windows_i686_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -2764,10 +3029,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" +name = "windows_x86_64_gnu" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -2776,10 +3041,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" +name = "windows_x86_64_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -2787,47 +3052,133 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" -version = "0.7.10" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] [[package]] -name = "winreg" -version = "0.50.0" +name = "winnow" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ - "cfg-if", - "windows-sys 0.48.0", + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.14.0", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap 2.14.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-parser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ - "bitflags 2.9.1", + "anyhow", + "id-arena", + "indexmap 2.14.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", ] [[package]] name = "writeable" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "yoke" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -2835,68 +3186,68 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -2905,9 +3256,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -2916,11 +3267,17 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.117", ] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 18271ef..ca25917 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,30 +1,27 @@ [package] name = "valine_bot" -version = "2.4.0" +version = "2.4.1" edition = "2024" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] async-stream = "0.3" bpaf = { version = "0.9", features = [ "derive" ] } chrono = "0.4" dashmap = "6.1" -duration-str = "0.17.0" +duration-str = "0.21" futures = "0.3" itertools = "0.14" -sysinfo = "0.35" -# Git 上では修正されているが、crates.io にはまだ反映されていないため、git で指定 -poise = { git = "https://github.com/serenity-rs/poise", rev = "db10b126c8b07f7e1924cba1672f04ff24ed4ec7" } -rand = "0.9" +poise = "0.6.2" +rand = "0.10" regex = "1.0" serde = { version = "1.0", features = [ "derive" ] } serde_with = "3" -serenity = "0.12.4" -similar = "2.7" +serenity = "0.12.5" +similar = "3.1" +sysinfo = "0.38" thiserror = "2" tokio = { version = "1.0", features = [ "rt-multi-thread", "macros", "signal" ] } -toml = "0.8" +toml = "1.0" tracing = "0.1" tracing-subscriber = "0.3" From bf0d6e75d204b9afad004cbd6179844606c81029 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 4 May 2026 03:19:51 +0900 Subject: [PATCH 058/104] =?UTF-8?q?chore(ci):=20=E3=82=B3=E3=83=9F?= =?UTF-8?q?=E3=83=83=E3=83=88=E6=AF=8E=E3=83=93=E3=83=AB=E3=83=89=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit close #15 --- .github/workflows/build.yml | 86 +++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..b6ab8af --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,86 @@ +name: Build + +permissions: + contents: write + packages: write + +on: + push: + tags-ignore: + - "**" + branches: + - "**" + pull_request: + workflow_dispatch: + +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + build: + name: Release binary + strategy: + matrix: + include: + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + glibc: 2.17 + - os: ubuntu-latest + target: aarch64-unknown-linux-gnu + glibc: 2.17 + + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + # Rustのpackage名を取得して環境変数に入れておく。(後のステップで使用) + - name: Extract crate information + shell: bash + run: | + echo "PROJECT_NAME=$(sed -n 's/^name = "\(.*\)"/\1/p' Cargo.toml | head -n1)" >> $GITHUB_ENV + + # zigをインストール + - name: Install Zig + uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2.2.1 + + # rustをインストール + - name: Install Rust toolchain + uses: moonrepo/setup-rust@abb2d32350334249b178c401e5ec5836e0cd88d3 # v1.3.0 + with: + targets: ${{ matrix.target }} + bins: cargo-zigbuild + + # moldをインストール + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 + + # ビルド + - name: Build with glibc version + if: matrix.glibc != -1 + run: | + cargo zigbuild --release --target ${{ matrix.target }}.${{ matrix.glibc }} + + - name: Build without glibc version + if: matrix.glibc == -1 + run: | + cargo build --release --target ${{ matrix.target }} + + # ビルド済みバイナリをコピーしてリネーム + - name: Copy and rename artifacts + shell: bash + run: | + mv target/${{ matrix.target }}/release/${{ env.PROJECT_NAME }} ./target/release/ + cp target/release/${{ env.PROJECT_NAME }}{,-${{ github.ref_name }}-${{ matrix.target }}} + cp target/release/${{ env.PROJECT_NAME }} target/release/${{ matrix.target }} + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: binary-${{ matrix.target }} + path: target/release/${{ matrix.target }} From e8b2d19a187ea566afa9aa13759964ac98be763c Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 4 May 2026 03:20:26 +0900 Subject: [PATCH 059/104] =?UTF-8?q?chore(ci):=20=E3=83=AA=E3=83=AA?= =?UTF-8?q?=E3=83=BC=E3=82=B9=E3=83=AF=E3=83=BC=E3=82=AF=E3=83=95=E3=83=AD?= =?UTF-8?q?=E3=83=BC=E3=81=AE=E4=BE=9D=E5=AD=98=E9=96=A2=E4=BF=82=E3=82=92?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot --- .github/workflows/release.yml | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 342cac6..0f57c2d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,7 +25,9 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 # Rustのpackage名を取得して環境変数に入れておく。(後のステップで使用) - name: Extract crate information @@ -35,17 +37,17 @@ jobs: # zigをインストール - name: Install Zig - uses: mlugg/setup-zig@v2 + uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2.2.1 # rustをインストール - name: Install Rust toolchain - uses: moonrepo/setup-rust@v1 + uses: moonrepo/setup-rust@abb2d32350334249b178c401e5ec5836e0cd88d3 # v1.3.0 with: targets: ${{ matrix.target }} bins: cargo-zigbuild # moldをインストール - - uses: rui314/setup-mold@v1 + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 # ビルド - name: Build with glibc version @@ -66,14 +68,14 @@ jobs: cp target/release/${{ env.PROJECT_NAME }}{,-${{ github.ref_name }}-${{ matrix.target }}} cp target/release/${{ env.PROJECT_NAME }} target/release/${{ matrix.target }} - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: binary-${{ matrix.target }} path: target/release/${{ matrix.target }} # ビルド済みバイナリをReleasesに配置 - name: Release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -86,19 +88,19 @@ jobs: name: Build and push Docker image steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Download artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: path: binaries merge-multiple: true - name: Login to GitHub Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ghcr.io username: ${{ github.actor }} @@ -106,7 +108,7 @@ jobs: - name: Extract metadata for Docker id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 with: images: ghcr.io/${{ github.repository }} tags: | @@ -115,7 +117,7 @@ jobs: - name: Build and push Docker image id: push - uses: docker/build-push-action@v6 + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 with: context: . file: ./Dockerfile From d8224802f06fd4c15bd38d29c72bd7b479a55349 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 4 May 2026 05:41:20 +0900 Subject: [PATCH 060/104] =?UTF-8?q?chore(ci):=20=E3=83=AA=E3=83=AA?= =?UTF-8?q?=E3=83=BC=E3=82=B9=E3=81=AB=E3=83=A9=E3=82=A4=E3=82=BB=E3=83=B3?= =?UTF-8?q?=E3=82=B9=E9=80=9A=E7=9F=A5=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit close #8 --- .github/workflows/build.yml | 22 ++++++++++++++----- .github/workflows/release.yml | 41 ++++++++++++++++++++++++++--------- .gitignore | 2 ++ Cargo.toml | 2 ++ Dockerfile | 20 ++++++++++++----- about.hbs | 35 ++++++++++++++++++++++++++++++ about.toml | 14 ++++++++++++ 7 files changed, 115 insertions(+), 21 deletions(-) create mode 100644 about.hbs create mode 100644 about.toml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b6ab8af..b3db9b0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -56,7 +56,9 @@ jobs: uses: moonrepo/setup-rust@abb2d32350334249b178c401e5ec5836e0cd88d3 # v1.3.0 with: targets: ${{ matrix.target }} - bins: cargo-zigbuild + bins: cargo-zigbuild,cargo-about + env: + GITHUB_TOKEN: ${{ github.token }} # moldをインストール - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 @@ -76,11 +78,19 @@ jobs: - name: Copy and rename artifacts shell: bash run: | - mv target/${{ matrix.target }}/release/${{ env.PROJECT_NAME }} ./target/release/ - cp target/release/${{ env.PROJECT_NAME }}{,-${{ github.ref_name }}-${{ matrix.target }}} - cp target/release/${{ env.PROJECT_NAME }} target/release/${{ matrix.target }} + mkdir -p dist + cp target/${{ matrix.target }}/release/${{ env.PROJECT_NAME }} dist/${{ matrix.target }} + cp LICENSE dist/LICENSE + + - name: Generate third-party license notices + run: | + cargo about generate about.hbs \ + --locked \ + --fail \ + --target ${{ matrix.target }} \ + -o dist/THIRD_PARTY_LICENSES-${{ matrix.target }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: binary-${{ matrix.target }} - path: target/release/${{ matrix.target }} + name: ${{ matrix.target }} + path: dist/* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0f57c2d..4e5f266 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,7 +44,9 @@ jobs: uses: moonrepo/setup-rust@abb2d32350334249b178c401e5ec5836e0cd88d3 # v1.3.0 with: targets: ${{ matrix.target }} - bins: cargo-zigbuild + bins: cargo-zigbuild,cargo-about + env: + GITHUB_TOKEN: ${{ github.token }} # moldをインストール - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 @@ -64,23 +66,40 @@ jobs: - name: Copy and rename artifacts shell: bash run: | - mv target/${{ matrix.target }}/release/${{ env.PROJECT_NAME }} ./target/release/ - cp target/release/${{ env.PROJECT_NAME }}{,-${{ github.ref_name }}-${{ matrix.target }}} - cp target/release/${{ env.PROJECT_NAME }} target/release/${{ matrix.target }} + mkdir -p dist + cp target/${{ matrix.target }}/release/${{ env.PROJECT_NAME }} dist/${{ env.PROJECT_NAME }}-${{ github.ref_name }}-${{ matrix.target }} + cp LICENSE dist/LICENSE + + - name: Generate third-party license notices + run: | + cargo about generate about.hbs \ + --locked \ + --fail \ + --target ${{ matrix.target }} \ + -o dist/THIRD_PARTY_LICENSES-${{ matrix.target }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: binary-${{ matrix.target }} - path: target/release/${{ matrix.target }} + name: ${{ matrix.target }} + path: dist/* - # ビルド済みバイナリをReleasesに配置 - - name: Release + # ビルド済みバイナリとライセンス通知をReleasesに配置 + - name: Release binary and target license notices uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: files: | - target/release/${{ env.PROJECT_NAME }}-${{ github.ref_name }}-${{ matrix.target }} + dist/${{ env.PROJECT_NAME }}-${{ github.ref_name }}-${{ matrix.target }} + dist/THIRD_PARTY_LICENSES-${{ matrix.target }} + + - name: Release project license + if: matrix.target == 'x86_64-unknown-linux-gnu' + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + files: dist/LICENSE docker: runs-on: ubuntu-latest @@ -89,6 +108,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 @@ -96,7 +117,7 @@ jobs: - name: Download artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - path: binaries + path: dist merge-multiple: true - name: Login to GitHub Container Registry diff --git a/.gitignore b/.gitignore index 963ff02..8e8a3e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /target +/dist +/THIRD_PARTY_LICENSES* .env config.toml diff --git a/Cargo.toml b/Cargo.toml index ca25917..1c55432 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,8 @@ name = "valine_bot" version = "2.4.1" edition = "2024" +license = "MIT" +publish = false [dependencies] async-stream = "0.3" diff --git a/Dockerfile b/Dockerfile index a523127..04b9389 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,16 +3,26 @@ FROM debian:bookworm-slim AS builder ARG TARGETPLATFORM WORKDIR /builder -COPY binaries binaries +COPY dist dist -RUN case "$TARGETPLATFORM" in \ - "linux/amd64") mv binaries/x86_64-unknown-linux-gnu ./valine_bot ;; \ - "linux/arm64") mv binaries/aarch64-unknown-linux-gnu ./valine_bot ;; \ +RUN set -eu; \ + case "$TARGETPLATFORM" in \ + "linux/amd64") \ + cp dist/*x86_64-unknown-linux-gnu ./valine_bot; \ + cp dist/THIRD_PARTY_LICENSES-x86_64-unknown-linux-gnu ./THIRD_PARTY_LICENSES \ + ;; \ + "linux/arm64") \ + cp dist/*aarch64-unknown-linux-gnu ./valine_bot; \ + cp dist/THIRD_PARTY_LICENSES-aarch64-unknown-linux-gnu ./THIRD_PARTY_LICENSES \ + ;; \ *) echo "Unsupported TARGETPLATFORM=$TARGETPLATFORM" >&2; exit 1 ;; \ - esac && \ + esac; \ + cp dist/LICENSE ./LICENSE; \ chmod +x ./valine_bot FROM gcr.io/distroless/cc-debian12:nonroot WORKDIR /app COPY --from=builder /builder/valine_bot /app/valine_bot +COPY --from=builder /builder/LICENSE /app/LICENSE +COPY --from=builder /builder/THIRD_PARTY_LICENSES /app/THIRD_PARTY_LICENSES ENTRYPOINT ["./valine_bot"] diff --git a/about.hbs b/about.hbs new file mode 100644 index 0000000..ac0d51c --- /dev/null +++ b/about.hbs @@ -0,0 +1,35 @@ +Third-party license notices +=========================== + +This file lists license notices for third-party Rust crates used by this +project. Distribute it with raw binaries published for this release. + +The license for this project itself is provided separately in LICENSE. + +Generated with: + + cargo about generate about.hbs --locked --fail --target -o THIRD_PARTY_LICENSES- + +License overview +---------------- + +{{#each overview}} +- {{{name}}} ({{count}}) +{{/each}} + +License texts +------------- + +{{#each licenses}} +================================================================================ +{{{name}}} +================================================================================ + +Used by: +{{#each used_by}} +- {{{crate.name}}} {{{crate.version}}}{{#if crate.repository}} ({{{crate.repository}}}){{else}} (https://crates.io/crates/{{{crate.name}}}){{/if}} +{{/each}} + +{{{text}}} + +{{/each}} diff --git a/about.toml b/about.toml new file mode 100644 index 0000000..00a047c --- /dev/null +++ b/about.toml @@ -0,0 +1,14 @@ +accepted = [ + "0BSD", + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "BSD-2-Clause", + "BSD-3-Clause", + "BSL-1.0", + "CDLA-Permissive-2.0", + "ISC", + "MIT", + "Unicode-3.0", + "Unlicense", + "Zlib", +] From 91cb7c91867fd76398852f6a7e91271479ca019c Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 4 May 2026 05:47:32 +0900 Subject: [PATCH 061/104] =?UTF-8?q?chore(ci):=20Rust=E3=82=AD=E3=83=A3?= =?UTF-8?q?=E3=83=83=E3=82=B7=E3=83=A5=E3=81=AE=E8=A8=AD=E5=AE=9A=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 3 +++ .github/workflows/release.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b3db9b0..bec4025 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -60,6 +60,9 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} + - name: Cache cargo registry and build artifacts + uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + # moldをインストール - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4e5f266..bc3e004 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,6 +48,9 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} + - name: Cache cargo registry and build artifacts + uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + # moldをインストール - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 From 60a5661c70e5195f383f1aea66d7d52cd4086cee Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 4 May 2026 07:11:56 +0900 Subject: [PATCH 062/104] =?UTF-8?q?refactor:=20=E7=8B=AC=E8=87=AA=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=83=90=E3=83=BC=E3=82=AD=E3=83=A3=E3=83=83=E3=82=B7?= =?UTF-8?q?=E3=83=A5=E3=82=92=20Serenity=20cache=20=E3=81=AB=E7=BD=AE?= =?UTF-8?q?=E3=81=8D=E6=8F=9B=E3=81=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot --- src/features/mod.rs | 3 +- src/features/thread_auto_invite/command.rs | 8 +- src/features/thread_auto_invite/handler.rs | 21 +-- .../thread_auto_invite/member_cache.rs | 151 ------------------ src/features/thread_auto_invite/mod.rs | 2 - .../thread_auto_invite/role_count_cache.rs | 8 +- src/main.rs | 14 +- src/utils.rs | 12 +- 8 files changed, 32 insertions(+), 187 deletions(-) delete mode 100644 src/features/thread_auto_invite/member_cache.rs diff --git a/src/features/mod.rs b/src/features/mod.rs index beeb81b..e915ca9 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -13,12 +13,11 @@ pub use auto_kick::Handler as AutoKickHandler; pub use logging::Handler as LoggingHandler; pub use message_cache::Handler as MessageCacheHandler; pub use question::Handler as QuestionHandler; -pub use thread_auto_invite::MemberCacheHandler; pub use thread_auto_invite::ThreadAutoInviteHandler; pub use thread_channel_startup::Handler as ThreadChannelStartupHandler; pub use message_cache::{MessageCache, MessageCacheType}; -pub use thread_auto_invite::{MemberCache, MemberCacheType, RoleCountCache, RoleCountCacheType}; +pub use thread_auto_invite::{RoleCountCache, RoleCountCacheType}; use crate::PCommand; diff --git a/src/features/thread_auto_invite/command.rs b/src/features/thread_auto_invite/command.rs index a7fd0f0..92bf6e4 100644 --- a/src/features/thread_auto_invite/command.rs +++ b/src/features/thread_auto_invite/command.rs @@ -4,10 +4,10 @@ use crate::{ CommandData, PError, config::get_config, features::thread_auto_invite::handler::invite_thread_by_roles, - utils::{has_authed_role, is_in_public_thread}, + utils::{get_guild_members, has_authed_role, is_in_public_thread}, }; -use super::{handler::Handler, member_cache::MemberCache}; +use super::handler::Handler; /// 招待用ロールを持ったメンバーを実行したスレッドに招待します。 #[poise::command( @@ -30,7 +30,7 @@ pub async fn invite_thread(ctx: ApplicationContext<'_, CommandData, PError>) -> /// 表示用のロールを持ったメンバーに呼び出し用のロールを付与します #[poise::command(slash_command, guild_only, default_member_permissions = "MANAGE_ROLES")] pub async fn add_invite_role(ctx: ApplicationContext<'_, CommandData, PError>) -> Result<(), PError> { - let members = MemberCache::get_all_members(ctx.serenity_context(), ctx.guild_id().unwrap()).await; + let members = get_guild_members(ctx.serenity_context(), ctx.guild_id().unwrap()); let config = &get_config(ctx.serenity_context()).await.thread_auto_invite; @@ -57,7 +57,7 @@ pub async fn add_invite_role(ctx: ApplicationContext<'_, CommandData, PError>) - /// 表示用のロールを持ったメンバーに呼び出し用のロールを削除 #[poise::command(slash_command, guild_only, default_member_permissions = "MANAGE_ROLES")] pub async fn remove_invite_role(ctx: ApplicationContext<'_, CommandData, PError>) -> Result<(), PError> { - let members = MemberCache::get_all_members(ctx.serenity_context(), ctx.guild_id().unwrap()).await; + let members = get_guild_members(ctx.serenity_context(), ctx.guild_id().unwrap()); let config = &get_config(ctx.serenity_context()).await.thread_auto_invite; diff --git a/src/features/thread_auto_invite/handler.rs b/src/features/thread_auto_invite/handler.rs index f8bb7e7..c30957a 100644 --- a/src/features/thread_auto_invite/handler.rs +++ b/src/features/thread_auto_invite/handler.rs @@ -13,7 +13,7 @@ use crate::{ utils::{await_initial_message, create_message}, }; -use super::{RoleCountCache, RoleCountCacheType, member_cache::MemberCache, role_count_cache::find_role}; +use super::{RoleCountCache, RoleCountCacheType, role_count_cache::find_role}; pub struct Handler; @@ -93,12 +93,8 @@ impl EventHandler for Handler { invite_thread_by_roles(&ctx, thread.id, &config.role_ids).await; } - async fn guild_member_addition(&self, ctx: Context, member: Member) { - MemberCache::insert_member(&ctx, &member).await; - } - - async fn guild_member_removal(&self, ctx: Context, guild_id: GuildId, user: User, _: Option) { - let Some(old) = MemberCache::get_member(&ctx, guild_id, user.id).await else { + async fn guild_member_removal(&self, ctx: Context, _: GuildId, _: User, old: Option) { + let Some(old) = old else { error!("Member update event with no old member"); return; }; @@ -111,29 +107,24 @@ impl EventHandler for Handler { .iter() .filter(|r| config.role_ids.contains(r)) .for_each(|r| cache.decrement(*r)); - - MemberCache::remove_member(&ctx, guild_id, user.id).await; } async fn guild_member_update( &self, ctx: Context, - _: Option, + old: Option, new: Option, - event: GuildMemberUpdateEvent, + _: GuildMemberUpdateEvent, ) { let Some(new) = new else { error!("Member update event with no new member"); return; }; - - let Some(old) = MemberCache::get_member(&ctx, event.guild_id, new.user.id).await else { + let Some(old) = old else { error!("Member update event with no old member"); return; }; - MemberCache::insert_member(&ctx, &new).await; - let config = &get_config(&ctx).await.thread_auto_invite; let has_new_role = new.roles.contains(&config.display_role_id); diff --git a/src/features/thread_auto_invite/member_cache.rs b/src/features/thread_auto_invite/member_cache.rs deleted file mode 100644 index 3f6d1e0..0000000 --- a/src/features/thread_auto_invite/member_cache.rs +++ /dev/null @@ -1,151 +0,0 @@ -use std::{ - collections::HashMap, - sync::{ - Arc, - atomic::{AtomicBool, Ordering}, - }, -}; - -use dashmap::DashMap; -use futures::StreamExt; -use itertools::Itertools; -use serenity::{ - all::{Context, EventHandler, GuildId, Member, UserId}, - async_trait, - prelude::TypeMapKey, -}; -use tracing::{error, info}; - -pub struct Handler { - collected: AtomicBool, -} - -impl Handler { - pub fn new() -> Self { - Self { - collected: AtomicBool::new(false), - } - } -} - -impl Default for Handler { - fn default() -> Self { - Self::new() - } -} - -#[async_trait] -impl EventHandler for Handler { - async fn cache_ready(&self, ctx: Context, guild_ids: Vec) { - if self.collected.swap(true, Ordering::Relaxed) { - return; - } - - let mut total_members = 0; - - let mut data = ctx.data.write().await; - let cache = data.get_mut::().unwrap(); - - for guild_id in guild_ids { - let members = guild_id - .members_iter(&ctx) - .filter_map(async move |m| m.map_err(|e| error!("Error fetching member: {:?}", e)).ok()) - .collect::>() - .await; - total_members += members.len(); - cache.extend_members(members); - } - info!("Member cache is ready. Total members cached: {}", total_members); - } -} - -#[derive(Clone, Debug)] -pub struct MemberCache { - cache: DashMap>, -} - -impl MemberCache { - pub fn new() -> Self { - Self { cache: DashMap::new() } - } - - pub fn insert(&self, member: &Member) { - self.cache - .entry(member.guild_id) - .or_default() - .insert(member.user.id, member.clone()); - } - - pub fn extend(&self, iter: impl IntoIterator) { - iter.into_iter() - .into_group_map_by(|(guild_id, _, _)| *guild_id) - .into_iter() - .map(|(guild_id, members)| { - ( - guild_id, - members - .into_iter() - .map(|(_, user_id, member)| (user_id, member)) - .collect::>(), - ) - }) - .for_each(|(guild_id, members)| { - let mut map = self.cache.entry(guild_id).or_default(); - map.extend(members); - }); - } - - pub fn extend_members(&self, iter: impl IntoIterator) { - self.extend(iter.into_iter().map(|member| (member.guild_id, member.user.id, member))); - } - - pub fn remove(&self, guild_id: GuildId, user_id: UserId) { - if let Some(mut map) = self.cache.get_mut(&guild_id) { - map.remove(&user_id); - } - } - - pub fn get(&self, guild_id: GuildId, user_id: UserId) -> Option { - self.cache.get(&guild_id)?.get(&user_id).cloned() - } - - pub fn get_all(&self, guild_id: GuildId) -> Vec { - self.cache.entry(guild_id).or_default().values().cloned().collect() - } - - pub async fn get_member(ctx: &Context, guild_id: GuildId, user_id: UserId) -> Option { - let data = ctx.data.read().await; - let cache = data.get::().unwrap(); - cache.get(guild_id, user_id) - } - - pub async fn get_all_members(ctx: &Context, guild_id: GuildId) -> Vec { - let data = ctx.data.read().await; - let cache = data.get::().unwrap(); - cache.get_all(guild_id) - } - - pub async fn insert_member(ctx: &Context, member: &Member) { - let mut data = ctx.data.write().await; - let cache = data.get_mut::().unwrap(); - cache.insert(member); - } - - pub async fn remove_member(ctx: &Context, guild_id: GuildId, user_id: UserId) { - let mut data = ctx.data.write().await; - let cache = data.get_mut::().unwrap(); - cache.remove(guild_id, user_id); - } -} - -impl Default for MemberCache { - fn default() -> Self { - Self::new() - } -} - -pub struct MemberCacheType; - -impl TypeMapKey for MemberCacheType { - type Value = Arc; -} diff --git a/src/features/thread_auto_invite/mod.rs b/src/features/thread_auto_invite/mod.rs index 7bba4fc..0f8273c 100644 --- a/src/features/thread_auto_invite/mod.rs +++ b/src/features/thread_auto_invite/mod.rs @@ -1,9 +1,7 @@ mod command; mod handler; -mod member_cache; mod role_count_cache; pub use command::{add_invite_role, invite_thread, remove_invite_role}; pub use handler::Handler as ThreadAutoInviteHandler; -pub use member_cache::{Handler as MemberCacheHandler, MemberCache, MemberCacheType}; pub use role_count_cache::{RoleCountCache, RoleCountCacheType}; diff --git a/src/features/thread_auto_invite/role_count_cache.rs b/src/features/thread_auto_invite/role_count_cache.rs index 282752e..298ab03 100644 --- a/src/features/thread_auto_invite/role_count_cache.rs +++ b/src/features/thread_auto_invite/role_count_cache.rs @@ -7,9 +7,7 @@ use serenity::{ }; use tracing::info; -use crate::config::ThreadAutoInviteConfig; - -use super::MemberCache; +use crate::{config::ThreadAutoInviteConfig, utils::get_guild_members}; #[derive(Debug, Clone)] pub struct RoleCountCache { @@ -66,10 +64,10 @@ impl TypeMapKey for RoleCountCacheType { pub async fn find_role(ctx: &Context, guild_id: GuildId, config: &ThreadAutoInviteConfig) -> Option { if RoleCountCache::is_empty(ctx).await { - let members = MemberCache::get_all_members(ctx, guild_id).await; + let members = get_guild_members(ctx, guild_id); let mut data = ctx.data.write().await; let cache = data.get_mut::().unwrap(); - cache.init(&members); + cache.init(members.collect::>().as_slice()); } let data = ctx.data.read().await; diff --git a/src/main.rs b/src/main.rs index 359f46b..cc103bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,12 +15,10 @@ use std::{ use bpaf::Bpaf; use config::Config; use error::on_error; -use features::{ - MemberCache, MemberCacheType, MessageCache, MessageCacheType, RoleCountCache, RoleCountCacheType, commands, -}; +use features::{MessageCache, MessageCacheType, RoleCountCache, RoleCountCacheType, commands}; use poise::{Framework, FrameworkOptions}; use serenity::{ - all::{ActivityData, GuildId, RatelimitInfo, Ready}, + all::{ActivityData, ChunkGuildFilter, Guild, GuildId, RatelimitInfo, Ready}, async_trait, cache::Settings as CacheSettings, prelude::*, @@ -75,6 +73,12 @@ impl EventHandler for MainHandler { }); } + async fn guild_create(&self, ctx: Context, guild: Guild, _: Option) { + // 全てのメンバーを取得する。結果は Serenity によって自動でキャッシュされる。 + ctx.shard + .chunk_guild(guild.id, Some(0), false, ChunkGuildFilter::None, None); + } + async fn ratelimit(&self, data: RatelimitInfo) { warn!( "Ratelimited {} {}: {}s", @@ -136,14 +140,12 @@ async fn main() { .event_handler(features::AuthHandler::new()) .event_handler(features::AutoKickHandler::new()) .event_handler(features::LoggingHandler) - .event_handler(features::MemberCacheHandler::new()) .event_handler(features::ThreadAutoInviteHandler::new()) .event_handler(features::ThreadChannelStartupHandler) .event_handler(features::QuestionHandler) .event_handler(features::MessageCacheHandler::new(config.message_cache.disabled)) .cache_settings(settings) .type_map_insert::(Arc::new(MessageCache::new())) - .type_map_insert::(Arc::new(MemberCache::new())) .type_map_insert::(Arc::new(RoleCountCache::new())) .type_map_insert::(Arc::new(config)) .await diff --git a/src/utils.rs b/src/utils.rs index b0318eb..f8c38a9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -7,8 +7,8 @@ use serenity::{ Result, all::{ ChannelId, ChannelType, Context, CreateActionRow, CreateAllowedMentions, CreateInteractionResponse, - CreateInteractionResponseMessage, CreateMessage, GuildChannel, Http, LightMethod, Message, MessageId, Request, - Route, ThreadsData, Timestamp, + CreateInteractionResponseMessage, CreateMessage, GuildChannel, GuildId, Http, LightMethod, Member, Message, + MessageId, Request, Route, ThreadsData, Timestamp, }, }; use similar::{Algorithm, ChangeTag, TextDiff}; @@ -221,3 +221,11 @@ pub fn create_diff_lines_text(old: &str, new: &str) -> String { }) .join("") } + +pub fn get_guild_members(ctx: &Context, guild_id: GuildId) -> impl Iterator { + guild_id + .to_guild_cached(ctx) + .map(|guild| guild.members.clone().into_values()) + .into_iter() + .flatten() +} From c8f9d6c0f1b79ef0c9ce36bc1527f186405281e2 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 4 May 2026 07:47:45 +0900 Subject: [PATCH 063/104] =?UTF-8?q?refactor:=20=E6=8B=9B=E5=BE=85=E3=83=AD?= =?UTF-8?q?=E3=83=BC=E3=83=AB=E9=81=B8=E6=8A=9E=E3=81=AB=20HTTP=20API=20?= =?UTF-8?q?=E3=81=AE=E3=83=AD=E3=83=BC=E3=83=AB=E4=BA=BA=E6=95=B0=E3=82=92?= =?UTF-8?q?=E4=BD=BF=E3=81=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot --- src/config.rs | 2 +- src/features/mod.rs | 1 - src/features/thread_auto_invite/command.rs | 12 +-- src/features/thread_auto_invite/handler.rs | 72 +++++++---------- src/features/thread_auto_invite/mod.rs | 2 - .../thread_auto_invite/role_count_cache.rs | 80 ------------------- src/main.rs | 3 +- 7 files changed, 36 insertions(+), 136 deletions(-) delete mode 100644 src/features/thread_auto_invite/role_count_cache.rs diff --git a/src/config.rs b/src/config.rs index 3657f92..b0413eb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -110,7 +110,7 @@ pub struct ThreadStartupConfig { pub struct ThreadAutoInviteConfig { pub display_role_id: RoleId, pub role_ids: Vec, - pub min_member_count: usize, + pub min_member_count: u32, } #[derive(Debug, Deserialize)] diff --git a/src/features/mod.rs b/src/features/mod.rs index e915ca9..b96c0e8 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -17,7 +17,6 @@ pub use thread_auto_invite::ThreadAutoInviteHandler; pub use thread_channel_startup::Handler as ThreadChannelStartupHandler; pub use message_cache::{MessageCache, MessageCacheType}; -pub use thread_auto_invite::{RoleCountCache, RoleCountCacheType}; use crate::PCommand; diff --git a/src/features/thread_auto_invite/command.rs b/src/features/thread_auto_invite/command.rs index 92bf6e4..d357b4e 100644 --- a/src/features/thread_auto_invite/command.rs +++ b/src/features/thread_auto_invite/command.rs @@ -36,21 +36,21 @@ pub async fn add_invite_role(ctx: ApplicationContext<'_, CommandData, PError>) - ctx.defer().await?; - let mut role_count = 0; + let mut added_count = 0; for member in members { if config.role_ids.iter().any(|r| member.roles.contains(r)) { - Handler::role_removed(ctx.serenity_context(), &member, config).await; + Handler::handle_role_removal(ctx.serenity_context(), &member, config).await; } if member.roles.contains(&config.display_role_id) { - Handler::role_added(ctx.serenity_context(), &member, config).await; - role_count += 1; + Handler::handle_role_assignment(ctx.serenity_context(), &member, config).await; + added_count += 1; continue; } } - say_reply(ctx.into(), format!("{} 人に招待用ロールを付与しました。", role_count)).await?; + say_reply(ctx.into(), format!("{} 人に招待用ロールを付与しました。", added_count)).await?; Ok(()) } @@ -70,7 +70,7 @@ pub async fn remove_invite_role(ctx: ApplicationContext<'_, CommandData, PError> continue; } - Handler::role_removed(ctx.serenity_context(), &member, config).await; + Handler::handle_role_removal(ctx.serenity_context(), &member, config).await; role_count += 1; } diff --git a/src/features/thread_auto_invite/handler.rs b/src/features/thread_auto_invite/handler.rs index c30957a..732ce10 100644 --- a/src/features/thread_auto_invite/handler.rs +++ b/src/features/thread_auto_invite/handler.rs @@ -2,7 +2,7 @@ use itertools::Itertools; use serenity::{ all::{ ChannelId, ChannelType, Context, EditMessage, EventHandler, GuildChannel, GuildId, GuildMemberUpdateEvent, - Member, Mentionable, RoleId, User, + Member, Mentionable, RoleId, }, async_trait, }; @@ -13,8 +13,6 @@ use crate::{ utils::{await_initial_message, create_message}, }; -use super::{RoleCountCache, RoleCountCacheType, role_count_cache::find_role}; - pub struct Handler; impl Handler { @@ -22,41 +20,45 @@ impl Handler { Self } - pub(super) async fn role_added(ctx: &Context, new: &Member, config: &ThreadAutoInviteConfig) { - let Some(role) = find_role(ctx, new.guild_id, config).await else { + async fn find_role(ctx: &Context, guild_id: GuildId, config: &ThreadAutoInviteConfig) -> Option { + let role_member_counts = ctx + .http + .get_guild_role_member_counts(guild_id) + .await + .map_err(|e| error!("Failed to get guild role member counts: {}", e)) + .ok()?; + + config.role_ids.iter().find_map(|&role_id| { + let count = *role_member_counts.get(&role_id)?; + (count < config.min_member_count).then_some(role_id) + }) + } + + pub(super) async fn handle_role_assignment(ctx: &Context, new: &Member, config: &ThreadAutoInviteConfig) { + let Some(role) = Self::find_role(ctx, new.guild_id, config).await else { error!("No role found with count less than {}", config.min_member_count); return; }; - let result = new - .add_role(ctx, role) - .await - .map_err(|_| error!("Failed to add role {} to member {}", role, new.user.id)) - .is_ok(); - - if result { - RoleCountCache::increment_count(ctx, role).await; + if let Err(e) = new.add_role(ctx, role).await { + error!("Failed to add role {} to member {}: {}", role, new.user.id, e); + } else { + info!("Added role {} to member {}", role, new.user.id); } } - pub(super) async fn role_removed(ctx: &Context, old: &Member, config: &ThreadAutoInviteConfig) { + pub(super) async fn handle_role_removal(ctx: &Context, old: &Member, config: &ThreadAutoInviteConfig) { let roles = old .roles .iter() .filter(|r| config.role_ids.contains(r)) .collect::>(); - let mut data = ctx.data.write().await; - let cache = data.get_mut::().unwrap(); - for role_id in roles { - let result = old - .remove_role(&ctx, role_id) - .await - .map_err(|_| error!("Failed to remove role {} from member {}", role_id, old.user.id)) - .is_ok(); - if result { - cache.decrement(*role_id); + if let Err(e) = old.remove_role(&ctx, role_id).await { + error!("Failed to remove role {} from member {}: {}", role_id, old.user.id, e); + } else { + info!("Removed role {} from member {}", role_id, old.user.id); break; } } @@ -93,22 +95,6 @@ impl EventHandler for Handler { invite_thread_by_roles(&ctx, thread.id, &config.role_ids).await; } - async fn guild_member_removal(&self, ctx: Context, _: GuildId, _: User, old: Option) { - let Some(old) = old else { - error!("Member update event with no old member"); - return; - }; - - let config = &get_config(&ctx).await.thread_auto_invite; - - let mut data = ctx.data.write().await; - let cache = data.get_mut::().unwrap(); - old.roles - .iter() - .filter(|r| config.role_ids.contains(r)) - .for_each(|r| cache.decrement(*r)); - } - async fn guild_member_update( &self, ctx: Context, @@ -131,11 +117,9 @@ impl EventHandler for Handler { let has_old_role = old.roles.contains(&config.display_role_id); if has_new_role && !has_old_role { - Handler::role_added(&ctx, &new, config).await; - info!("added role to {}", new.user.name); + Handler::handle_role_assignment(&ctx, &new, config).await; } else if has_old_role && !has_new_role { - Handler::role_removed(&ctx, &old, config).await; - info!("removed role from {}", new.user.name); + Handler::handle_role_removal(&ctx, &old, config).await; } } } diff --git a/src/features/thread_auto_invite/mod.rs b/src/features/thread_auto_invite/mod.rs index 0f8273c..cd208d3 100644 --- a/src/features/thread_auto_invite/mod.rs +++ b/src/features/thread_auto_invite/mod.rs @@ -1,7 +1,5 @@ mod command; mod handler; -mod role_count_cache; pub use command::{add_invite_role, invite_thread, remove_invite_role}; pub use handler::Handler as ThreadAutoInviteHandler; -pub use role_count_cache::{RoleCountCache, RoleCountCacheType}; diff --git a/src/features/thread_auto_invite/role_count_cache.rs b/src/features/thread_auto_invite/role_count_cache.rs deleted file mode 100644 index 298ab03..0000000 --- a/src/features/thread_auto_invite/role_count_cache.rs +++ /dev/null @@ -1,80 +0,0 @@ -use std::sync::Arc; - -use dashmap::DashMap; -use serenity::{ - all::{Context, GuildId, Member, RoleId}, - prelude::TypeMapKey, -}; -use tracing::info; - -use crate::{config::ThreadAutoInviteConfig, utils::get_guild_members}; - -#[derive(Debug, Clone)] -pub struct RoleCountCache { - cache: DashMap, -} - -/** - * 今のところ、スレッド招待用ロールのカウントしか更新されない - */ -impl RoleCountCache { - pub fn new() -> Self { - Self { cache: DashMap::new() } - } - - pub fn init(&self, members: &[Member]) { - for member in members { - for role in &member.roles { - info!("{} has role {}", member.user.name, role); - self.increment(*role); - } - } - } - - pub fn increment(&self, role_id: RoleId) { - self.cache.entry(role_id).and_modify(|count| *count += 1).or_insert(1); - } - - pub fn decrement(&self, role_id: RoleId) { - self.cache.entry(role_id).and_modify(|count| *count -= 1).or_insert(0); - } - - pub fn get(&self, role_id: RoleId) -> Option { - self.cache.get(&role_id).map(|count| *count) - } - - pub async fn is_empty(ctx: &Context) -> bool { - let data = ctx.data.read().await; - let cache = data.get::().unwrap(); - cache.cache.is_empty() - } - - pub async fn increment_count(ctx: &Context, role_id: RoleId) { - let mut data = ctx.data.write().await; - let cache = data.get_mut::().unwrap(); - cache.increment(role_id); - } -} - -pub struct RoleCountCacheType; - -impl TypeMapKey for RoleCountCacheType { - type Value = Arc; -} - -pub async fn find_role(ctx: &Context, guild_id: GuildId, config: &ThreadAutoInviteConfig) -> Option { - if RoleCountCache::is_empty(ctx).await { - let members = get_guild_members(ctx, guild_id); - let mut data = ctx.data.write().await; - let cache = data.get_mut::().unwrap(); - cache.init(members.collect::>().as_slice()); - } - - let data = ctx.data.read().await; - let cache = data.get::().unwrap(); - config - .role_ids - .iter() - .find(|r| cache.get(**r).unwrap_or(0) < config.min_member_count) - .cloned() -} diff --git a/src/main.rs b/src/main.rs index cc103bf..c13e137 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ use std::{ use bpaf::Bpaf; use config::Config; use error::on_error; -use features::{MessageCache, MessageCacheType, RoleCountCache, RoleCountCacheType, commands}; +use features::{MessageCache, MessageCacheType, commands}; use poise::{Framework, FrameworkOptions}; use serenity::{ all::{ActivityData, ChunkGuildFilter, Guild, GuildId, RatelimitInfo, Ready}, @@ -146,7 +146,6 @@ async fn main() { .event_handler(features::MessageCacheHandler::new(config.message_cache.disabled)) .cache_settings(settings) .type_map_insert::(Arc::new(MessageCache::new())) - .type_map_insert::(Arc::new(RoleCountCache::new())) .type_map_insert::(Arc::new(config)) .await .expect("Err creating client"); From 9c3891b90edb23809d06f4b494bd82f0ec4dc1ae Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 4 May 2026 07:51:30 +0900 Subject: [PATCH 064/104] =?UTF-8?q?chore:=20=E4=B8=8D=E8=A6=81=E3=81=AA=20?= =?UTF-8?q?into=5Fiter=20=E5=91=BC=E3=81=B3=E5=87=BA=E3=81=97=E3=82=92?= =?UTF-8?q?=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IntoIterator を受け取る引数に対する明示的な into_iter 呼び出しを省く。 --- src/features/message_cache/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/message_cache/mod.rs b/src/features/message_cache/mod.rs index 7169360..a6b9bba 100644 --- a/src/features/message_cache/mod.rs +++ b/src/features/message_cache/mod.rs @@ -43,7 +43,7 @@ impl Handler { } let is_ignored = std::iter::once(channel.id) - .chain(channel.parent_id.into_iter()) + .chain(channel.parent_id) .any(|id| config.message_cache.ignore_channel_ids.contains(&id)); if is_ignored { return; From c02bc2deecd57d5efd2a366f7b99cbbc7bf7cdfd Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 4 May 2026 09:18:06 +0900 Subject: [PATCH 065/104] =?UTF-8?q?fix(docker):=20=E3=83=90=E3=82=A4?= =?UTF-8?q?=E3=83=8A=E3=83=AA=E6=88=90=E6=9E=9C=E7=89=A9=E3=81=AE=E3=82=B3?= =?UTF-8?q?=E3=83=94=E3=83=BC=E5=AF=BE=E8=B1=A1=E3=82=92=E6=98=8E=E7=A2=BA?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 04b9389..7b2ba90 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,16 +7,12 @@ COPY dist dist RUN set -eu; \ case "$TARGETPLATFORM" in \ - "linux/amd64") \ - cp dist/*x86_64-unknown-linux-gnu ./valine_bot; \ - cp dist/THIRD_PARTY_LICENSES-x86_64-unknown-linux-gnu ./THIRD_PARTY_LICENSES \ - ;; \ - "linux/arm64") \ - cp dist/*aarch64-unknown-linux-gnu ./valine_bot; \ - cp dist/THIRD_PARTY_LICENSES-aarch64-unknown-linux-gnu ./THIRD_PARTY_LICENSES \ - ;; \ + "linux/amd64") target="x86_64-unknown-linux-gnu" ;; \ + "linux/arm64") target="aarch64-unknown-linux-gnu" ;; \ *) echo "Unsupported TARGETPLATFORM=$TARGETPLATFORM" >&2; exit 1 ;; \ esac; \ + cp dist/valine_bot-*-"$target" ./valine_bot; \ + cp "dist/THIRD_PARTY_LICENSES-$target" ./THIRD_PARTY_LICENSES; \ cp dist/LICENSE ./LICENSE; \ chmod +x ./valine_bot From 18a09367e970ec68fe42f89c04ddbfc3480b888a Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 4 May 2026 09:19:53 +0900 Subject: [PATCH 066/104] chore: bump version --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c79dfb..4806b03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2542,7 +2542,7 @@ checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0" [[package]] name = "valine_bot" -version = "2.4.1" +version = "2.4.2" dependencies = [ "async-stream", "bpaf", diff --git a/Cargo.toml b/Cargo.toml index 1c55432..fa77105 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "valine_bot" -version = "2.4.1" +version = "2.4.2" edition = "2024" license = "MIT" publish = false From 16679ee9d065a894f81e811c39e17fbc484b67d2 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Tue, 5 May 2026 03:52:09 +0900 Subject: [PATCH 067/104] =?UTF-8?q?chore(deploy):=20Podman=20Quadlet=20?= =?UTF-8?q?=E3=81=AE=E3=83=87=E3=83=97=E3=83=AD=E3=82=A4=E5=AE=9A=E7=BE=A9?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/README.md | 92 ++++++++++++++++++++++++++++++++++++ deploy/valine-bot.env.sample | 1 + deploy/valine-bot.quadlets | 55 +++++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 deploy/README.md create mode 100644 deploy/valine-bot.env.sample create mode 100644 deploy/valine-bot.quadlets diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..a916054 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,92 @@ +# Valine Bot デプロイ + +## 専用ユーザー + +専用ユーザー `valine-bot` を作り、パスワードをロックします。 + +```sh +if ! id valine-bot >/dev/null 2>&1; then + sudo useradd -m -s /bin/bash valine-bot +fi + +sudo passwd -l valine-bot +sudo loginctl enable-linger valine-bot +``` + +以降の作業は専用ユーザーに切り替えて行います。 + +```sh +sudo -iu valine-bot +``` + +## インストール + +`main` ではなく、release tag または commit SHA に固定した raw URL を使ってください。 + +```sh +podman quadlet install \ + https://raw.githubusercontent.com/Valine3gDev/valine_bot/vX.Y.Z/deploy/valine-bot.quadlets +``` + +このリポジトリの GHCR image は release workflow で `ghcr.io/valine3gdev/valine_bot:2.4.2` のような `v` なしの semver tag として発行されます。別バージョンを使う場合は、インストール後に `$HOME/.config/containers/systemd/valine-bot.container` の `Image=` を更新してください。 + +## env file + +サンプルをダウンロードして、実際のパスワードに書き換えてからインストールします。 + +```sh +curl -fsSLo valine-bot.env \ + https://raw.githubusercontent.com/Valine3gDev/valine_bot/vX.Y.Z/deploy/valine-bot.env.sample + +${EDITOR:-vi} valine-bot.env + +install -D -m 600 valine-bot.env "$HOME/.config/containers/systemd/valine-bot.env" +rm -f valine-bot.env +``` + +## 起動 + +```sh +systemctl --user daemon-reload +systemctl --user enable --now valine-bot-db.service valine-bot.service +``` + +## 状態確認 + +```sh +systemctl --user status valine-bot-db.service +systemctl --user status valine-bot.service +``` + +## ログ確認 + +```sh +journalctl --user -u valine-bot.service -f +journalctl --user -u valine-bot-db.service -f +``` + +## 停止 + +```sh +systemctl --user stop valine-bot.service valine-bot-db.service +``` + +自動起動も無効にする場合: + +```sh +systemctl --user disable valine-bot.service valine-bot-db.service +``` + +## unit 名 + +- `valine-bot-db.container` から `valine-bot-db.service` が生成されます。 +- `valine-bot.container` から `valine-bot.service` が生成されます。 +- `valine-bot.network` から network 用 unit (`valine-bot-network.service`) が生成されます。 + +## DB volume + +DB データは Podman named volume の `valine-bot-db-data` に保存されます。rootless Podman では通常、実体は `~/.local/share/containers/storage/volumes/valine-bot-db-data/_data` 配下です。 + +```sh +podman volume inspect valine-bot-db-data +``` diff --git a/deploy/valine-bot.env.sample b/deploy/valine-bot.env.sample new file mode 100644 index 0000000..ace69e8 --- /dev/null +++ b/deploy/valine-bot.env.sample @@ -0,0 +1 @@ +POSTGRES_PASSWORD=change-me diff --git a/deploy/valine-bot.quadlets b/deploy/valine-bot.quadlets new file mode 100644 index 0000000..51e89f7 --- /dev/null +++ b/deploy/valine-bot.quadlets @@ -0,0 +1,55 @@ +# FileName=valine-bot.network +[Unit] +Description=Valine Bot Podman network + +[Network] +NetworkName=valine-bot-net + +--- +# FileName=valine-bot-db.container +[Unit] +Description=Valine Bot PostgreSQL database +Requires=valine-bot.network +After=valine-bot.network + +[Container] +Image=docker.io/library/postgres:18.3 +ContainerName=valine-bot-db +Network=valine-bot.network +Volume=valine-bot-db-data:/var/lib/postgresql/data +Environment=POSTGRES_DB=valine_bot +Environment=POSTGRES_USER=valine_bot +EnvironmentFile=./valine-bot.env + +[Service] +Restart=always + +[Install] +WantedBy=default.target + +--- +# FileName=valine-bot.container +[Unit] +Description=Valine Bot application +Requires=valine-bot.network +Requires=valine-bot-db.container +After=valine-bot.network +After=valine-bot-db.container + +[Container] +Image=ghcr.io/valine3gdev/valine_bot:2.4.2 +ContainerName=valine-bot +Network=valine-bot.network +Environment=DB_HOST=valine-bot-db +Environment=DB_PORT=5432 +Environment=DB_NAME=valine_bot +Environment=DB_USER=valine_bot +EnvironmentFile=./valine-bot.env +ReadOnly=true +DropCapability=all + +[Service] +Restart=always + +[Install] +WantedBy=default.target From 89ce08136f5e7aedca44dc3a787d5a9a0791bd4d Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Tue, 5 May 2026 08:20:36 +0900 Subject: [PATCH 068/104] =?UTF-8?q?fix(deploy):=20Quadlet=20=E3=81=AE?= =?UTF-8?q?=E6=9C=AC=E7=95=AA=E9=81=8B=E7=94=A8=E6=89=8B=E9=A0=86=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/README.md | 73 +++++++++++++++++++++++++++----------- deploy/valine-bot.quadlets | 11 +++--- 2 files changed, 60 insertions(+), 24 deletions(-) diff --git a/deploy/README.md b/deploy/README.md index a916054..32998e8 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -24,12 +24,31 @@ sudo -iu valine-bot `main` ではなく、release tag または commit SHA に固定した raw URL を使ってください。 ```sh -podman quadlet install \ +podman quadlet install -r \ https://raw.githubusercontent.com/Valine3gDev/valine_bot/vX.Y.Z/deploy/valine-bot.quadlets ``` このリポジトリの GHCR image は release workflow で `ghcr.io/valine3gdev/valine_bot:2.4.2` のような `v` なしの semver tag として発行されます。別バージョンを使う場合は、インストール後に `$HOME/.config/containers/systemd/valine-bot.container` の `Image=` を更新してください。 +## 更新 + +イメージタグなどを変更する場合: + +```sh +nano $HOME/.config/containers/systemd/valine-bot.container + +systemctl --user daemon-reload +systemctl --user restart valine-bot.service +``` + +ユーザーを切り替えずに更新する場合: + +```sh +sudo -u valine-bot -H sh -lc '${EDITOR:-nano} "$HOME/.config/containers/systemd/valine-bot.container"' +sudo systemctl --user --machine=valine-bot@.host daemon-reload +sudo systemctl --user --machine=valine-bot@.host restart valine-bot.service +``` + ## env file サンプルをダウンロードして、実際のパスワードに書き換えてからインストールします。 @@ -38,17 +57,32 @@ podman quadlet install \ curl -fsSLo valine-bot.env \ https://raw.githubusercontent.com/Valine3gDev/valine_bot/vX.Y.Z/deploy/valine-bot.env.sample -${EDITOR:-vi} valine-bot.env +${EDITOR:-nano} valine-bot.env install -D -m 600 valine-bot.env "$HOME/.config/containers/systemd/valine-bot.env" rm -f valine-bot.env ``` +## config file + +`$HOME/valine-bot/config.toml` を直接参照します。このファイルはコンテナ内の `/app/config.toml` として read-only mount されます。 + +```sh +mkdir -p "$HOME/valine-bot" +chmod 700 "$HOME/valine-bot" + +curl -fsSLo "$HOME/valine-bot/config.toml" \ + https://raw.githubusercontent.com/Valine3gDev/valine_bot/vX.Y.Z/config.sample.toml + +${EDITOR:-nano} "$HOME/valine-bot/config.toml" +chmod 600 "$HOME/valine-bot/config.toml" +``` + ## 起動 ```sh systemctl --user daemon-reload -systemctl --user enable --now valine-bot-db.service valine-bot.service +systemctl --user start valine-bot-db.service valine-bot.service ``` ## 状態確認 @@ -61,32 +95,31 @@ systemctl --user status valine-bot.service ## ログ確認 ```sh -journalctl --user -u valine-bot.service -f -journalctl --user -u valine-bot-db.service -f +journalctl --user -axu valine-bot.service -f +journalctl --user -axu valine-bot-db.service -f ``` -## 停止 - +ユーザーを切り替えずに ```sh -systemctl --user stop valine-bot.service valine-bot-db.service +sudo -u valine-bot journalctl --user -axu valine-bot.service -f +sudo -u valine-bot journalctl --user -axu valine-bot-db.service -f ``` -自動起動も無効にする場合: +## 停止 ```sh -systemctl --user disable valine-bot.service valine-bot-db.service +systemctl --user stop valine-bot.service valine-bot-db.service ``` -## unit 名 - -- `valine-bot-db.container` から `valine-bot-db.service` が生成されます。 -- `valine-bot.container` から `valine-bot.service` が生成されます。 -- `valine-bot.network` から network 用 unit (`valine-bot-network.service`) が生成されます。 - -## DB volume - -DB データは Podman named volume の `valine-bot-db-data` に保存されます。rootless Podman では通常、実体は `~/.local/share/containers/storage/volumes/valine-bot-db-data/_data` 配下です。 +## コンフィグ検証 ```sh -podman volume inspect valine-bot-db-data +podman run --rm \ + --read-only \ + --cap-drop=all \ + --userns=keep-id \ + --user "$(id -u):$(id -g)" \ + --volume "$HOME/valine-bot/config.toml:/app/config.toml:ro" \ + ghcr.io/valine3gdev/valine_bot:2.4.2 \ + --check-config ``` diff --git a/deploy/valine-bot.quadlets b/deploy/valine-bot.quadlets index 51e89f7..c1f1e8d 100644 --- a/deploy/valine-bot.quadlets +++ b/deploy/valine-bot.quadlets @@ -1,4 +1,4 @@ -# FileName=valine-bot.network +# FileName=valine-bot [Unit] Description=Valine Bot Podman network @@ -6,7 +6,7 @@ Description=Valine Bot Podman network NetworkName=valine-bot-net --- -# FileName=valine-bot-db.container +# FileName=valine-bot-db [Unit] Description=Valine Bot PostgreSQL database Requires=valine-bot.network @@ -16,7 +16,7 @@ After=valine-bot.network Image=docker.io/library/postgres:18.3 ContainerName=valine-bot-db Network=valine-bot.network -Volume=valine-bot-db-data:/var/lib/postgresql/data +Volume=valine-bot-db-data:/var/lib/postgresql Environment=POSTGRES_DB=valine_bot Environment=POSTGRES_USER=valine_bot EnvironmentFile=./valine-bot.env @@ -28,7 +28,7 @@ Restart=always WantedBy=default.target --- -# FileName=valine-bot.container +# FileName=valine-bot [Unit] Description=Valine Bot application Requires=valine-bot.network @@ -40,6 +40,9 @@ After=valine-bot-db.container Image=ghcr.io/valine3gdev/valine_bot:2.4.2 ContainerName=valine-bot Network=valine-bot.network +Volume=%h/valine-bot/config.toml:/app/config.toml:ro +User=%U:%G +UserNS=keep-id Environment=DB_HOST=valine-bot-db Environment=DB_PORT=5432 Environment=DB_NAME=valine_bot From 2b213b96cb31e576b8129ee7c1756b0bcaa79141 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Tue, 26 May 2026 19:53:12 +0900 Subject: [PATCH 069/104] =?UTF-8?q?fix:=20=E3=82=B2=E3=83=BC=E3=83=88?= =?UTF-8?q?=E3=82=A6=E3=82=A7=E3=82=A4=E3=81=AE=E5=86=8D=E6=8E=A5=E7=B6=9A?= =?UTF-8?q?=E5=BE=8C=E3=81=AB=E3=82=A2=E3=82=AF=E3=83=86=E3=82=A3=E3=83=93?= =?UTF-8?q?=E3=83=86=E3=82=A3=E3=81=8C=E6=9B=B4=E6=96=B0=E3=81=A7=E3=81=8D?= =?UTF-8?q?=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.rs | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/main.rs b/src/main.rs index c13e137..518c03f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,14 +3,7 @@ mod error; mod features; mod utils; -use std::{ - fs::read_to_string, - sync::{ - Arc, - atomic::{AtomicBool, Ordering}, - }, - time::Duration, -}; +use std::{fs::read_to_string, sync::Arc, time::Duration}; use bpaf::Bpaf; use config::Config; @@ -24,6 +17,7 @@ use serenity::{ prelude::*, }; use sysinfo::{Pid, System}; +use tokio::task::JoinHandle; use tracing::{error, info, warn}; pub type PError = Box; @@ -32,13 +26,13 @@ pub type PContext<'a> = poise::Context<'a, CommandData, PError>; pub type PCommand = poise::Command; struct MainHandler { - task_started: AtomicBool, + activity_task: Mutex>>, } impl MainHandler { pub fn new() -> Self { Self { - task_started: AtomicBool::new(false), + activity_task: Mutex::new(None), } } } @@ -50,27 +44,28 @@ impl EventHandler for MainHandler { } async fn cache_ready(&self, ctx: Context, _: Vec) { - if self.task_started.swap(true, Ordering::Relaxed) { - return; + let mut task = self.activity_task.lock().await; + + if let Some(handle) = task.take() { + handle.abort(); } - tokio::spawn(async move { + *task = Some(tokio::spawn(async move { let mut system = System::new_all(); let pid = Pid::from_u32(std::process::id()); loop { system.refresh_all(); - let Some(memory) = system.process(pid).map(|p| p.memory() as f64 / 1024.0 / 1024.0) else { + if let Some(memory) = system.process(pid).map(|p| p.memory() as f64 / 1024.0 / 1024.0) { + ctx.set_activity(Some(ActivityData::custom(format!("メモリ使用量: {:.1}MB", memory)))); + } else { error!("Failed to get process info"); - continue; - }; - - ctx.set_activity(Some(ActivityData::custom(format!("メモリ使用量: {:.1}MB", memory)))); + } tokio::time::sleep(Duration::from_secs(60)).await; } - }); + })); } async fn guild_create(&self, ctx: Context, guild: Guild, _: Option) { From 59fe27b0c82d5321495c874224ffa366dcb6dc19 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Wed, 27 May 2026 03:16:12 +0900 Subject: [PATCH 070/104] =?UTF-8?q?feat:=20=E8=B3=AA=E5=95=8F=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E3=81=AE=E9=A0=85=E7=9B=AE=E3=82=92=E5=A4=89=E6=9B=B4?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 任意の mclo.gs リンクの入力欄を追加 - Modローダーのバージョンを削除 --- src/features/question/modal.rs | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/features/question/modal.rs b/src/features/question/modal.rs index e663b84..d7b2814 100644 --- a/src/features/question/modal.rs +++ b/src/features/question/modal.rs @@ -18,19 +18,14 @@ pub struct BasicQuestionData { #[min_length = 3] #[max_length = 20] pub loader: String, - #[name = "Modローダーのバージョン"] - #[placeholder = "使用しているModローダーのバージョンを入力してください"] - #[min_length = 3] - #[max_length = 20] - pub loader_version: String, } impl Display for BasicQuestionData { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "### 基本情報\n- Minecraftバージョン: {}\n- Modローダー: {}\n- Modローダーバージョン: {}", - self.mc_version, self.loader, self.loader_version + "### 基本情報\n- Minecraftバージョン: {}\n- Modローダー: {}", + self.mc_version, self.loader ) } } @@ -56,14 +51,22 @@ pub struct DetailedQuestionData { #[max_length = 1000] #[paragraph] pub content3: String, + #[name = "mclo.gs にアップロードしたログやクラッシュレポートなどのリンク (任意)"] + #[placeholder = "mclo.gs にアップロードしたログやクラッシュレポートなどのリンクを入力してください"] + #[max_length = 1000] + #[paragraph] + pub logs: Option, } impl Display for DetailedQuestionData { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "### 質問内容\n- 質問内容:\n{}\n- 問題解決の達成基準:\n{}\n- 試したこと・調べたこと:\n{}", - self.content, self.content2, self.content3 + "### 質問内容\n- 質問内容:\n{}\n- 問題解決の達成基準:\n{}\n- 試したこと・調べたこと:\n{}\n- ログやクラッシュレポートのリンク:\n{}", + self.content, + self.content2, + self.content3, + self.logs.as_deref().unwrap_or("なし") ) } } @@ -72,8 +75,11 @@ impl Default for DetailedQuestionData { fn default() -> Self { Self { content: "例:クラッシュした, 変な挙動をする, modの扱い方がわからない".to_string(), - content2: "例: クラッシュから抜け出したい, このような挙動にしたい, このmodでこのようなことがしたい".to_string(), - content3: "例:○○というサイトに掲載されてた対処法を試した\n推奨:mclo.gs にて変換したクラッシュレポート、lastet.logのリンクを貼る".to_string(), + content2: "例: クラッシュから抜け出したい, このような挙動にしたい, このmodでこのようなことがしたい" + .to_string(), + content3: "例:○○というサイトに掲載されてた対処法を試した\nAIに聞いてみてこのような回答を得られた\nなど" + .to_string(), + logs: None, } } } From ce745eac02254b5a5aad0c1d52ecb361e4271b79 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Wed, 27 May 2026 03:17:00 +0900 Subject: [PATCH 071/104] chore: bump version --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4806b03..541ccb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2542,7 +2542,7 @@ checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0" [[package]] name = "valine_bot" -version = "2.4.2" +version = "2.5.0" dependencies = [ "async-stream", "bpaf", diff --git a/Cargo.toml b/Cargo.toml index fa77105..927b3e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "valine_bot" -version = "2.4.2" +version = "2.5.0" edition = "2024" license = "MIT" publish = false From 9d37a033e4bc0d71f39eeda8084523749a901d55 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Fri, 12 Jun 2026 16:17:41 +0900 Subject: [PATCH 072/104] =?UTF-8?q?feat:=20=E3=83=A1=E3=83=83=E3=82=BB?= =?UTF-8?q?=E3=83=BC=E3=82=B8=E3=83=AD=E3=82=B0=E3=82=92=E6=94=B9=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - メッセージの一括削除に対応 - メッセージ送信日時を追加 --- src/extensions/message_builder.rs | 75 ++++++++++++++++++++++++ src/extensions/mod.rs | 3 + src/features/logging.rs | 96 ++++++++++++++++++++++--------- src/main.rs | 1 + 4 files changed, 148 insertions(+), 27 deletions(-) create mode 100644 src/extensions/message_builder.rs create mode 100644 src/extensions/mod.rs diff --git a/src/extensions/message_builder.rs b/src/extensions/message_builder.rs new file mode 100644 index 0000000..0c9cb4f --- /dev/null +++ b/src/extensions/message_builder.rs @@ -0,0 +1,75 @@ +use serenity::{ + model::Timestamp, + utils::{FormattedTimestamp, FormattedTimestampStyle, MessageBuilder}, +}; + +#[allow(dead_code)] +pub trait MessageBuilderTimestampExt { + fn _push_linebreak(&mut self) -> &mut Self; + + fn push_timestamp(&mut self, timestamp: Timestamp, style: Option) -> &mut Self; + + fn push_timestamp_line(&mut self, timestamp: Timestamp, style: Option) -> &mut Self { + self.push_timestamp(timestamp, style)._push_linebreak() + } + + fn push_timestamp_short(&mut self, timestamp: Timestamp) -> &mut Self { + self.push_timestamp(timestamp, Some(FormattedTimestampStyle::ShortTime)) + } + fn push_timestamp_short_line(&mut self, timestamp: Timestamp) -> &mut Self { + self.push_timestamp_short(timestamp)._push_linebreak() + } + + fn push_timestamp_long(&mut self, timestamp: Timestamp) -> &mut Self { + self.push_timestamp(timestamp, Some(FormattedTimestampStyle::LongTime)) + } + fn push_timestamp_long_line(&mut self, timestamp: Timestamp) -> &mut Self { + self.push_timestamp_long(timestamp)._push_linebreak() + } + + fn push_timestamp_short_date(&mut self, timestamp: Timestamp) -> &mut Self { + self.push_timestamp(timestamp, Some(FormattedTimestampStyle::ShortDate)) + } + fn push_timestamp_short_date_line(&mut self, timestamp: Timestamp) -> &mut Self { + self.push_timestamp_short_date(timestamp)._push_linebreak() + } + + fn push_timestamp_long_date(&mut self, timestamp: Timestamp) -> &mut Self { + self.push_timestamp(timestamp, Some(FormattedTimestampStyle::LongDate)) + } + fn push_timestamp_long_date_line(&mut self, timestamp: Timestamp) -> &mut Self { + self.push_timestamp_long_date(timestamp)._push_linebreak() + } + + fn push_timestamp_short_date_time(&mut self, timestamp: Timestamp) -> &mut Self { + self.push_timestamp(timestamp, Some(FormattedTimestampStyle::ShortDateTime)) + } + fn push_timestamp_short_date_time_line(&mut self, timestamp: Timestamp) -> &mut Self { + self.push_timestamp_short_date_time(timestamp)._push_linebreak() + } + + fn push_timestamp_long_date_time(&mut self, timestamp: Timestamp) -> &mut Self { + self.push_timestamp(timestamp, Some(FormattedTimestampStyle::LongDateTime)) + } + fn push_timestamp_long_date_time_line(&mut self, timestamp: Timestamp) -> &mut Self { + self.push_timestamp_long_date_time(timestamp)._push_linebreak() + } + + fn push_timestamp_relative_time(&mut self, timestamp: Timestamp) -> &mut Self { + self.push_timestamp(timestamp, Some(FormattedTimestampStyle::RelativeTime)) + } + fn push_timestamp_relative_time_line(&mut self, timestamp: Timestamp) -> &mut Self { + self.push_timestamp_relative_time(timestamp)._push_linebreak() + } +} + +impl MessageBuilderTimestampExt for MessageBuilder { + fn push_timestamp(&mut self, timestamp: Timestamp, style: Option) -> &mut Self { + let formatted = FormattedTimestamp::new(timestamp, style).to_string(); + self.push(formatted) + } + + fn _push_linebreak(&mut self) -> &mut Self { + self.push_line("") + } +} diff --git a/src/extensions/mod.rs b/src/extensions/mod.rs new file mode 100644 index 0000000..4a14c70 --- /dev/null +++ b/src/extensions/mod.rs @@ -0,0 +1,3 @@ +mod message_builder; + +pub use message_builder::MessageBuilderTimestampExt; diff --git a/src/features/logging.rs b/src/features/logging.rs index ca115a5..0f44d5a 100644 --- a/src/features/logging.rs +++ b/src/features/logging.rs @@ -3,9 +3,8 @@ use std::ops::Not; use itertools::{Itertools, enumerate}; use serenity::{ all::{ - ChannelId, Context, CreateEmbed, EmbedMessageBuilding, EventHandler, FormattedTimestamp, - FormattedTimestampStyle, GuildId, Mentionable, Message, MessageBuilder, MessageId, MessageReferenceKind, - MessageUpdateEvent, Timestamp, + ChannelId, Context, CreateEmbed, EmbedMessageBuilding, EventHandler, GuildId, Mentionable, Message, + MessageBuilder, MessageId, MessageReferenceKind, MessageUpdateEvent, Timestamp, }, async_trait, }; @@ -13,10 +12,43 @@ use tracing::error; use crate::{ config::get_config, + extensions::MessageBuilderTimestampExt, features::MessageCacheType, utils::{create_diff_lines_text, create_safe_message, get_cached_message, send_message}, }; +enum LogType { + Edit { new_content: String }, + Delete, +} + +impl LogType { + fn name(&self) -> &'static str { + match self { + LogType::Edit { .. } => "編集", + LogType::Delete => "削除", + } + } + + fn title(&self) -> String { + format!("メッセージ{}ログ", self.name()) + } + + fn color(&self) -> i32 { + match self { + LogType::Edit { .. } => 0xff8800, + LogType::Delete => 0xf00000, + } + } + + fn new_content(&self) -> Option<&str> { + match self { + LogType::Edit { new_content } => Some(new_content), + LogType::Delete => None, + } + } +} + pub struct Handler; impl Handler { @@ -64,9 +96,9 @@ impl Handler { } if let Some(expiry) = poll.expiry { - builder.push_bold_safe("有効期限: ").push_line_safe( - FormattedTimestamp::new(expiry, Some(FormattedTimestampStyle::LongDateTime)).to_string(), - ); + builder + .push_bold_safe("有効期限: ") + .push_timestamp_long_date_time_line(expiry); } embed.field("__**投票**__", builder.build(), false) @@ -108,26 +140,18 @@ impl Handler { embed.field("__**添付ファイル**__", builder.build(), false) } - fn build_embed(&self, message: &Message, new_content: String, mut embed: CreateEmbed) -> CreateEmbed { + fn build_embed(&self, message: &Message, new_content: &str, mut embed: CreateEmbed) -> CreateEmbed { embed = Self::build_reply_field(embed, message); embed = Self::build_poll_field(embed, message); - embed = Self::build_diff_field(embed, &message.content, &new_content); + embed = Self::build_diff_field(embed, &message.content, new_content); Self::build_attachments_field(embed, message) } - async fn create_and_send_log( - &self, - ctx: &Context, - message: &Message, - title: &str, - color: i32, - new_content: Option, - ) { + async fn create_and_send_log(&self, ctx: &Context, message: &Message, log_type: LogType) { if message.author.bot { return; } - let timestamp = FormattedTimestamp::new(Timestamp::now(), Some(FormattedTimestampStyle::LongDateTime)); let description = MessageBuilder::new() .push_bold_safe("メンバー: ") .mention(&message.author.mention()) @@ -137,14 +161,16 @@ impl Handler { .push_safe(message.id.link(message.channel_id, message.guild_id)) .push_safe(" ") .push_mono_line_safe(message.id.to_string()) - .push_bold_safe("時刻: ") - .push_line_safe(timestamp.to_string()) + .push_bold_safe("メッセージ送信日時: ") + .push_timestamp_long_date_time_line(message.timestamp) + .push_bold_safe(format!("{}日時: ", log_type.name())) + .push_timestamp_long_date_time_line(Timestamp::now()) .build(); let mut embed = CreateEmbed::new() - .title(title) + .title(log_type.title()) .description(description) - .color(color) + .color(log_type.color()) .thumbnail( message .author @@ -152,7 +178,8 @@ impl Handler { .unwrap_or("https://cdn.discordapp.com/embed/avatars/0.png".to_string()), ); - embed = self.build_embed(message, new_content.unwrap_or("".to_string()), embed); + let new_content = log_type.new_content().unwrap_or(""); + embed = self.build_embed(message, new_content, embed); self.send_log(ctx, embed).await; } @@ -189,9 +216,9 @@ impl EventHandler for Handler { self.create_and_send_log( &ctx, &message, - "メッセージ編集ログ", - 0xff8800, - Some(new_message.content), + LogType::Edit { + new_content: new_message.content, + }, ) .await; } @@ -207,7 +234,22 @@ impl EventHandler for Handler { return error!("Failed to get message: {}", deleted_message_id); }; - self.create_and_send_log(&ctx, &message, "メッセージ削除ログ", 0xf00000, None) - .await; + self.create_and_send_log(&ctx, &message, LogType::Delete).await; + } + + async fn message_delete_bulk( + &self, + ctx: Context, + channel_id: ChannelId, + deleted_message_ids: Vec, + _: Option, + ) { + for message_id in deleted_message_ids { + let Some(message) = get_cached_message(&ctx, channel_id, message_id).await else { + error!("Failed to get message: {}", message_id); + continue; + }; + self.create_and_send_log(&ctx, &message, LogType::Delete).await; + } } } diff --git a/src/main.rs b/src/main.rs index 518c03f..f661c4d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod config; mod error; +mod extensions; mod features; mod utils; From 57390b0054b3e63292522a2ca83aa9d14533c5cc Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Fri, 12 Jun 2026 17:11:11 +0900 Subject: [PATCH 073/104] =?UTF-8?q?feat:=20=E3=83=8F=E3=83=8B=E3=83=BC?= =?UTF-8?q?=E3=83=9D=E3=83=83=E3=83=88=E3=82=92=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.sample.toml | 11 +++ src/config.rs | 10 ++ src/features/honeypot.rs | 199 +++++++++++++++++++++++++++++++++++++++ src/features/mod.rs | 2 + src/main.rs | 1 + 5 files changed, 223 insertions(+) create mode 100644 src/features/honeypot.rs diff --git a/config.sample.toml b/config.sample.toml index dc5819d..7cbe28c 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -26,6 +26,17 @@ https://example.com/ """ +[honeypot] +# ハニーポットのチャンネルID +channel_id = "000000000000000000" +# 削除対象にする同一内容の過去メッセージの送信された期間 (例: "24h" なら24時間以内) +message_lookback = "24h" +# キックされた際にDMへ送信するメッセージ +kick_message = """アカウントが乗っ取られたとみられる行動を確認したため、「てすとサーバー」から自動的にキックされました。""" +# ログを残すチャンネルID +log_channel_id = "000000000000000000" + + [message_logging] # メッセージの削除・編集のログを残すチャンネルID channel_id = "000000000000000000" diff --git a/src/config.rs b/src/config.rs index b0413eb..4aac491 100644 --- a/src/config.rs +++ b/src/config.rs @@ -24,6 +24,7 @@ pub struct Config { pub bot: BotConfig, pub auth: AuthConfig, pub auto_kick: AutoKickConfig, + pub honeypot: HoneypotConfig, pub message_logging: MessageLoggingConfig, pub message_cache: MessageCacheConfig, pub pin: PinConfig, @@ -60,6 +61,15 @@ pub struct AutoKickConfig { pub kick_message: String, } +#[derive(Debug, Deserialize)] +pub struct HoneypotConfig { + pub channel_id: ChannelId, + #[serde(deserialize_with = "deserialize_duration_chrono")] + pub message_lookback: Duration, + pub kick_message: String, + pub log_channel_id: ChannelId, +} + #[derive(Debug, Deserialize)] pub struct MessageLoggingConfig { pub channel_id: ChannelId, diff --git a/src/features/honeypot.rs b/src/features/honeypot.rs new file mode 100644 index 0000000..7edfcdb --- /dev/null +++ b/src/features/honeypot.rs @@ -0,0 +1,199 @@ +use std::collections::HashMap; + +use chrono::Duration; +use serenity::{ + all::prelude::{Context, EventHandler}, + async_trait, + builder::CreateEmbed, + model::{ + Timestamp, + channel::Message, + id::{ChannelId, GuildId, MessageId, UserId}, + }, + utils::MessageBuilder, +}; +use tracing::error; + +use crate::{ + config::get_config, + utils::{create_message, create_safe_message, send_message}, +}; + +struct MessageFingerprint { + author_id: UserId, + content: String, + attachments: Vec, +} + +impl MessageFingerprint { + fn matches(&self, other: &MessageFingerprint) -> bool { + if self.author_id != other.author_id { + return false; + } + + if self.content != other.content { + return false; + } + + let mut self_attachments = self.attachments.to_vec(); + let mut other_attachments = other.attachments.to_vec(); + self_attachments.sort(); + other_attachments.sort(); + + self_attachments == other_attachments + } + + fn matches_message(&self, message: &Message) -> bool { + self.matches(&message.into()) + } +} + +impl From for MessageFingerprint { + fn from(message: Message) -> Self { + (&message).into() + } +} + +impl From<&Message> for MessageFingerprint { + fn from(message: &Message) -> Self { + Self { + author_id: message.author.id, + content: message.content.clone(), + attachments: message.attachments.iter().map(|a| a.filename.clone()).collect(), + } + } +} + +pub struct Handler; + +impl Handler { + /** + 指定されたメッセージと同一の内容を持ち、指定された期間内に送信されたメッセージのID一覧を、 Serenity のキャッシュ内から収集する + + スパム対策の性質上 Bot起動以前のメッセージが必要になる可能性が低いため、Serenityのキャッシュからのみ収集する実装とした + + また、同様にスレッドにメッセージが送信されないというスパムの傾向を踏まえ、スレッド内のメッセージは収集対象から除外する + */ + fn collect_message_ids( + ctx: &Context, + guild_id: GuildId, + target_message: impl Into, + message_lookback: Duration, + ) -> HashMap> { + let Some(guild) = guild_id.to_guild_cached(&ctx) else { + error!("guild {} not found in cache", guild_id); + return HashMap::new(); + }; + + let mut ids = HashMap::new(); + let cutoff = Timestamp::now().unix_timestamp() - message_lookback.num_seconds(); + let target_message = target_message.into(); + + for channel_id in guild.channels.keys() { + if let Some(messages) = ctx.cache.channel_messages(channel_id) { + let message_ids = messages + .iter() + .filter(|(_, message)| { + message.timestamp.unix_timestamp() >= cutoff && target_message.matches_message(message) + }) + .map(|(message_id, _)| *message_id) + .collect::>(); + + if !message_ids.is_empty() { + ids.insert(*channel_id, message_ids); + } + } + } + + ids + } + + async fn delete_messages(ctx: &Context, messages: &HashMap>) { + for (channel_id, message_ids) in messages { + if message_ids.len() > 2 { + for chunk in message_ids.chunks(100) { + if let Err(e) = channel_id.delete_messages(&ctx.http, chunk).await { + error!("Failed to delete messages in channel {}: {:?}", channel_id, e); + } + } + continue; + } + + if let Some(id) = message_ids.first() + && let Err(e) = channel_id.delete_message(&ctx.http, *id).await + { + error!("Failed to delete message {} in channel {}: {:?}", id, channel_id, e); + } + } + } +} + +#[async_trait] +impl EventHandler for Handler { + async fn message(&self, ctx: Context, msg: Message) { + let author = &msg.author; + + if author.bot { + return; + } + + let config = &get_config(&ctx).await; + + if config.honeypot.channel_id != msg.channel_id { + return; + } + + let dm_message = author + .direct_message(&ctx, create_message(&config.honeypot.kick_message)) + .await; + + let Ok(member) = msg.member(&ctx).await else { + error!("user {} not found", author.id); + return; + }; + + let _ = member + .kick_with_reason(&ctx, "ハニーポットにメッセージを送信したため") + .await; + + let delete_message_ids = + Self::collect_message_ids(&ctx, msg.guild_id.unwrap(), &msg, config.honeypot.message_lookback); + Self::delete_messages(&ctx, &delete_message_ids).await; + + let mut log_builder = MessageBuilder::new(); + log_builder + .push_bold("ユーザー: ") + .push_safe(member.display_name()) + .push(" ") + .push_mono_line(author.id.to_string()) + .push_line(dm_message.map_or("DMの送信に失敗しました。", |_| "")); + + log_builder.push_bold_line("削除したメッセージID:"); + for (channel_id, message_ids) in &delete_message_ids { + for message_id in message_ids { + log_builder + .push("- ") + .push(message_id.link(*channel_id, msg.guild_id)) + .push(" ") + .push_mono_line(message_id.to_string()); + } + } + + let embed = CreateEmbed::new() + .title("ハニーポット検知") + .description(log_builder.build()) + .color(0xf00000) + .thumbnail( + author + .avatar_url() + .unwrap_or("https://cdn.discordapp.com/embed/avatars/0.png".to_string()), + ); + + let _ = send_message( + &ctx, + &config.honeypot.log_channel_id, + create_safe_message().add_embed(embed), + ) + .await; + } +} diff --git a/src/features/mod.rs b/src/features/mod.rs index b96c0e8..7d487e9 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -1,6 +1,7 @@ mod admin; mod auth; mod auto_kick; +mod honeypot; mod logging; mod message_cache; mod pin; @@ -10,6 +11,7 @@ mod thread_channel_startup; pub use auth::Handler as AuthHandler; pub use auto_kick::Handler as AutoKickHandler; +pub use honeypot::Handler as HoneypotHandler; pub use logging::Handler as LoggingHandler; pub use message_cache::Handler as MessageCacheHandler; pub use question::Handler as QuestionHandler; diff --git a/src/main.rs b/src/main.rs index f661c4d..7225632 100644 --- a/src/main.rs +++ b/src/main.rs @@ -135,6 +135,7 @@ async fn main() { .event_handler(MainHandler::new()) .event_handler(features::AuthHandler::new()) .event_handler(features::AutoKickHandler::new()) + .event_handler(features::HoneypotHandler) .event_handler(features::LoggingHandler) .event_handler(features::ThreadAutoInviteHandler::new()) .event_handler(features::ThreadChannelStartupHandler) From 91be0b008e3b36030e9a0f162cb6e6003ae64f6f Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Fri, 12 Jun 2026 17:15:28 +0900 Subject: [PATCH 074/104] chore: bump version --- Cargo.lock | 205 ++++++++++++++++++++++++++--------------------------- Cargo.toml | 2 +- 2 files changed, 103 insertions(+), 104 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 541ccb3..7ed5aa1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,9 +82,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "base64" @@ -94,9 +94,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "block-buffer" @@ -109,24 +109,33 @@ dependencies = [ [[package]] name = "bpaf" -version = "0.9.25" +version = "0.9.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8d4b90317451025bfa1f7d359a5fa698a5f745927ff27696429b7fbd7c0c48" +checksum = "0b86829876e7e200161a5aa6ea688d46c32d64d70ee3100127790dde84688d6e" dependencies = [ "bpaf_derive", ] [[package]] name = "bpaf_derive" -version = "0.5.23" +version = "0.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "549ca9c364fdc06f9f36d1356980193d930abc80db848193c2dd4d0e9a83de1b" +checksum = "2f7e98cee839b19076cb3ce1afdb62bb182e04ff5f71f70188827002fae91094" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bstr" version = "1.12.1" @@ -139,9 +148,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytecount" @@ -194,9 +203,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.61" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", "shlex", @@ -227,9 +236,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "js-sys", @@ -393,9 +402,9 @@ dependencies = [ [[package]] name = "dashmap" -version = "6.1.0" +version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" dependencies = [ "cfg-if", "crossbeam-utils", @@ -444,9 +453,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -475,9 +484,9 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "equivalent" @@ -715,9 +724,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heck" @@ -733,9 +742,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -772,9 +781,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" dependencies = [ "atomic-waker", "bytes", @@ -986,7 +995,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.0", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -997,16 +1006,6 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" -[[package]] -name = "iri-string" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "itertools" version = "0.14.0" @@ -1024,13 +1023,12 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.97" +version = "0.3.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" +checksum = "f2025f20d7a4fa7785846e7b63d10a76d3f1cee98ee5cb79ea59703f95e42162" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -1081,9 +1079,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" [[package]] name = "lru-slab" @@ -1093,9 +1091,9 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "mime" @@ -1140,9 +1138,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "wasi", @@ -1169,9 +1167,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-traits" @@ -1498,9 +1496,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -1521,9 +1519,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "reqwest" @@ -1583,9 +1581,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.41.0" +version = "1.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ce901f9a19d251159075a4c37af514c3b8ef99c22e02dd8c19161cf397ee94a" +checksum = "be2a24f50780bc85f09cc6ac299bdf1424302742d77221106859c9d8b102126a" dependencies = [ "arrayvec", "num-traits", @@ -1782,9 +1780,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -1816,11 +1814,12 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.19.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05839ce67618e14a09b286535c0d9c94e85ef25469b0e13cb4f844e5593eb19" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" dependencies = [ "base64", + "bs58", "chrono", "hex", "indexmap 1.9.3", @@ -1835,9 +1834,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.19.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2ebbe86054f9b45bc3881e865683ccfaccce97b9b4cb53f3039d67f355a334" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -1916,9 +1915,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "signal-hook-registry" @@ -1938,9 +1937,9 @@ checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "similar" -version = "3.1.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04d93e861ede2e497b47833469b8ec9d5c07fa4c78ce7a00f6eb7dd8168b4b3f" +checksum = "e6505efef05804732ed8a3f2d4f279429eb485bd69d5b0cc6b19cc02005cda16" dependencies = [ "bstr", ] @@ -1968,15 +1967,15 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "socket2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", "windows-sys 0.61.2", @@ -2188,9 +2187,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.1" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -2275,7 +2274,7 @@ dependencies = [ "toml_datetime", "toml_parser", "toml_writer", - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -2293,7 +2292,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -2319,20 +2318,20 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "bitflags", "bytes", "futures-util", "http", "http-body", - "iri-string", "pin-project-lite", "tower", "tower-layer", "tower-service", + "url", ] [[package]] @@ -2452,15 +2451,15 @@ checksum = "74b08b0c1257381af16a5c3605254d529d3e7e109f3c62befc5d168968192998" [[package]] name = "typenum" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "typesize" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da66c62c5b7017a2787e77373c03e6a5aafde77a73bff1ff96e91cd2e128179" +checksum = "a313188364f7e10138257cb288c64e9b0e282510abefc91c796799559fa14762" dependencies = [ "chrono", "dashmap 5.5.3", @@ -2542,12 +2541,12 @@ checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0" [[package]] name = "valine_bot" -version = "2.5.0" +version = "2.6.0" dependencies = [ "async-stream", "bpaf", "chrono", - "dashmap 6.1.0", + "dashmap 6.2.1", "duration-str", "futures", "itertools", @@ -2623,9 +2622,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.120" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" +checksum = "a254a4b10c19a76f09a27640e7ffbf9bc30bf67e16a3bf28aaefa4920fe81563" dependencies = [ "cfg-if", "once_cell", @@ -2636,9 +2635,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.70" +version = "0.4.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" +checksum = "54568702fabf5d4849ce2b90fadfa64168a097eaf4b351ce9df8b687a0086aaf" dependencies = [ "js-sys", "wasm-bindgen", @@ -2646,9 +2645,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.120" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" +checksum = "24a40fc75b0ec6f3746ceb10d36f53a93dcd68a93b11b6445983945d79eba0dc" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2656,9 +2655,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.120" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" +checksum = "908f34bd9b9ce3d4caf07b72dfab63d61504d156856c6bd3cd87fa350cf3985b" dependencies = [ "bumpalo", "proc-macro2", @@ -2669,9 +2668,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.120" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" +checksum = "7acbf7616c27b194bbb550bf77ed0c2c3e5b7fd1260a93082b95fb7f47959b92" dependencies = [ "unicode-ident", ] @@ -2725,9 +2724,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.97" +version = "0.3.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" +checksum = "6e0871acf327f283dc6da28a1696cdc64fb355ba9f935d052021fa77f35cce69" dependencies = [ "js-sys", "wasm-bindgen", @@ -3069,9 +3068,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" [[package]] name = "wit-bindgen" @@ -3175,9 +3174,9 @@ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "yoke" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -3198,18 +3197,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", @@ -3218,9 +3217,9 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] diff --git a/Cargo.toml b/Cargo.toml index 927b3e1..def8353 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "valine_bot" -version = "2.5.0" +version = "2.6.0" edition = "2024" license = "MIT" publish = false From 102264cfdd4f6e5003bfe6374bedccf93ce7e8a2 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Fri, 12 Jun 2026 17:32:56 +0900 Subject: [PATCH 075/104] =?UTF-8?q?chore(ci):=20=E3=82=AD=E3=83=A3?= =?UTF-8?q?=E3=83=83=E3=82=B7=E3=83=A5=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 5 ++--- .github/workflows/release.yml | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bec4025..6e2fadd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,14 +55,13 @@ jobs: - name: Install Rust toolchain uses: moonrepo/setup-rust@abb2d32350334249b178c401e5ec5836e0cd88d3 # v1.3.0 with: + cache-target: release + cache-extra-identifier: ${{ matrix.target }} targets: ${{ matrix.target }} bins: cargo-zigbuild,cargo-about env: GITHUB_TOKEN: ${{ github.token }} - - name: Cache cargo registry and build artifacts - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - # moldをインストール - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bc3e004..6a6203b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,14 +43,13 @@ jobs: - name: Install Rust toolchain uses: moonrepo/setup-rust@abb2d32350334249b178c401e5ec5836e0cd88d3 # v1.3.0 with: + cache-target: release + cache-extra-identifier: ${{ matrix.target }} targets: ${{ matrix.target }} bins: cargo-zigbuild,cargo-about env: GITHUB_TOKEN: ${{ github.token }} - - name: Cache cargo registry and build artifacts - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - # moldをインストール - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 From 7720300db1786a53ed998104190ff23c4e7e23c3 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Fri, 12 Jun 2026 17:38:41 +0900 Subject: [PATCH 076/104] chore: bump version --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ed5aa1..3d6bf10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2541,7 +2541,7 @@ checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0" [[package]] name = "valine_bot" -version = "2.6.0" +version = "2.6.1" dependencies = [ "async-stream", "bpaf", diff --git a/Cargo.toml b/Cargo.toml index def8353..5c234ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "valine_bot" -version = "2.6.0" +version = "2.6.1" edition = "2024" license = "MIT" publish = false From 780a42c86965518eddb0a91043b09383a4e3ea96 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Fri, 12 Jun 2026 18:35:53 +0900 Subject: [PATCH 077/104] =?UTF-8?q?chore:=20next=20=E3=83=96=E3=83=A9?= =?UTF-8?q?=E3=83=B3=E3=83=81=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 828 +++++++++++++++++++++++++++++++---------------------- Cargo.toml | 10 +- 2 files changed, 486 insertions(+), 352 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3d6bf10..d6494bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,30 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aformat" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f387c59d52324934bdd3586fe904051338ce4583a9bb921982a3dbb060a26e6f" +dependencies = [ + "aformat-macros", + "to-arraystring", + "typenum", + "typenum_mappings", +] + +[[package]] +name = "aformat-macros" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "254adeba6d67e7e6706f01ffdf1787cdad41e361be5b7c1e3265bba54dca7d8f" +dependencies = [ + "bytestring", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -86,6 +110,28 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "aws-lc-rs" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "base64" version = "0.22.1" @@ -107,6 +153,19 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bool_to_bitflags" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c039d9bc676b768f6d59556e99f95f5e47c811b672f8b2b2b606eb28527a2f" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.117", + "to-arraystring", +] + [[package]] name = "bpaf" version = "0.9.26" @@ -152,18 +211,6 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" -[[package]] -name = "bytecount" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes" version = "1.11.1" @@ -171,34 +218,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] -name = "camino" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" -dependencies = [ - "serde_core", -] - -[[package]] -name = "cargo-platform" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.14.2" +name = "bytestring" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" +checksum = "86566c496f2f47d9b8147a4c8b02ffdb69c919fe0c2b2e7195d22cbba0e635c9" dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", + "bytes", ] [[package]] @@ -208,6 +233,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -249,14 +276,32 @@ dependencies = [ ] [[package]] -name = "command_attr" -version = "0.5.4" +name = "cmake" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8208103c5e25a091226dfa8d61d08d0561cc14f31b25691811ba37d4ec9b157b" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "cc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", ] [[package]] @@ -292,15 +337,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -386,20 +422,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", - "serde", -] - [[package]] name = "dashmap" version = "6.2.1" @@ -412,6 +434,7 @@ dependencies = [ "lock_api", "once_cell", "parking_lot_core", + "serde", ] [[package]] @@ -426,7 +449,6 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ - "powerfmt", "serde_core", ] @@ -462,6 +484,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "duration-str" version = "0.21.0" @@ -471,7 +499,7 @@ dependencies = [ "chrono", "rust_decimal", "serde", - "thiserror 2.0.18", + "thiserror", "time", "winnow 0.7.15", ] @@ -505,20 +533,15 @@ dependencies = [ ] [[package]] -name = "error-chain" -version = "0.12.4" +name = "extract_map" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +checksum = "a8855baff5d450715f5d34c1d291a8c77363bd5a20ddacf560d7d6ea2a07f2c3" dependencies = [ - "version_check", + "hashbrown 0.15.5", + "serde", ] -[[package]] -name = "fastrand" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" - [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -533,6 +556,7 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -547,6 +571,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -556,6 +586,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" version = "0.3.32" @@ -695,12 +731,6 @@ dependencies = [ "wasip3", ] -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - [[package]] name = "hashbrown" version = "0.12.3" @@ -719,7 +749,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -808,11 +838,10 @@ dependencies = [ "http", "hyper", "hyper-util", - "rustls 0.23.40", + "rustls", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls", "tower-service", - "webpki-roots 1.0.7", ] [[package]] @@ -1021,6 +1050,65 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.117", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "js-sys" version = "0.3.100" @@ -1044,24 +1132,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" -[[package]] -name = "levenshtein" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" - [[package]] name = "libc" version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - [[package]] name = "litemap" version = "0.8.2" @@ -1111,21 +1187,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "mini-moka" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c325dfab65f261f386debee8b0969da215b3fa0037e74c8a1234db7ba986d803" -dependencies = [ - "crossbeam-channel", - "crossbeam-utils", - "dashmap 5.5.3", - "skeptic", - "smallvec", - "tagptr", - "triomphe", -] - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1147,6 +1208,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" +dependencies = [ + "serde", +] + [[package]] name = "ntapi" version = "0.4.3" @@ -1205,6 +1275,12 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "parking_lot" version = "0.12.5" @@ -1241,24 +1317,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] -name = "poise" -version = "0.6.2" +name = "pkg-config" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711433114bca9ff68582aa58af5cbcf573e83250a8714c2dca1c36f453cad3ae" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "poise" +version = "0.6.1" +source = "git+https://github.com/serenity-rs/poise?rev=de8bd6fdd54de8ee5daf0242ffb6dc6eba58df69#de8bd6fdd54de8ee5daf0242ffb6dc6eba58df69" dependencies = [ "async-trait", "derivative", "futures-util", "indexmap 2.14.0", "parking_lot", + "poise_macros", "regex", - "serenity", - "serenity_poise_macros", + "serenity 0.12.5 (git+https://github.com/serenity-rs/serenity?branch=next)", "tokio", "tracing", "trim-in-place", ] +[[package]] +name = "poise_macros" +version = "0.6.1" +source = "git+https://github.com/serenity-rs/poise?rev=de8bd6fdd54de8ee5daf0242ffb6dc6eba58df69#de8bd6fdd54de8ee5daf0242ffb6dc6eba58df69" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "potential_utf" version = "0.1.5" @@ -1302,17 +1394,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "pulldown-cmark" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" -dependencies = [ - "bitflags", - "memchr", - "unicase", -] - [[package]] name = "quinn" version = "0.11.9" @@ -1325,9 +1406,9 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.40", + "rustls", "socket2", - "thiserror 2.0.18", + "thiserror", "tokio", "tracing", "web-time", @@ -1339,16 +1420,17 @@ version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ + "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", "rand 0.9.4", "ring", "rustc-hash", - "rustls 0.23.40", + "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.18", + "thiserror", "tinyvec", "tracing", "web-time", @@ -1389,24 +1471,13 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -[[package]] -name = "rand" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - [[package]] name = "rand" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ - "rand_chacha 0.9.0", + "rand_chacha", "rand_core 0.9.5", ] @@ -1421,16 +1492,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - [[package]] name = "rand_chacha" version = "0.9.0" @@ -1441,15 +1502,6 @@ dependencies = [ "rand_core 0.9.5", ] -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - [[package]] name = "rand_core" version = "0.9.5" @@ -1525,9 +1577,9 @@ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "reqwest" -version = "0.12.28" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64", "bytes", @@ -1545,14 +1597,14 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.40", + "rustls", "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", - "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls", "tokio-util", "tower", "tower-http", @@ -1562,7 +1614,6 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 1.0.7", ] [[package]] @@ -1596,44 +1647,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] -name = "rustix" -version = "1.1.4" +name = "rustc_version" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", + "semver", ] [[package]] name = "rustls" -version = "0.22.4" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ - "log", - "ring", + "aws-lc-rs", + "once_cell", "rustls-pki-types", - "rustls-webpki 0.102.8", + "rustls-webpki", "subtle", "zeroize", ] [[package]] -name = "rustls" -version = "0.23.40" +name = "rustls-native-certs" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" dependencies = [ - "once_cell", - "ring", + "openssl-probe", "rustls-pki-types", - "rustls-webpki 0.103.13", - "subtle", - "zeroize", + "schannel", + "security-framework", ] [[package]] @@ -1647,22 +1692,39 @@ dependencies = [ ] [[package]] -name = "rustls-webpki" -version = "0.102.8" +name = "rustls-platform-verifier" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", ] +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -1689,6 +1751,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "schemars" version = "0.9.0" @@ -1725,19 +1796,37 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" dependencies = [ - "serde", "zeroize", ] +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" -dependencies = [ - "serde", - "serde_core", -] [[package]] name = "serde" @@ -1800,18 +1889,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - [[package]] name = "serde_with" version = "3.21.0" @@ -1847,50 +1924,82 @@ dependencies = [ [[package]] name = "serenity" version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bde37f42765dfdc34e2a039e0c84afbf79a3101c1941763b0beb816c2f17541" +source = "git+https://github.com/serenity-rs/serenity?branch=next#30a293f0a02c63908b68a9f4fa687e964b7ff5c8" dependencies = [ + "aformat", "arrayvec", "async-trait", "base64", "bitflags", + "bool_to_bitflags", "bytes", "chrono", - "command_attr", - "dashmap 5.5.3", + "dashmap", + "extract_map", "flate2", + "foldhash 0.2.0", "futures", - "levenshtein", + "mime", "mime_guess", + "nonmax", "parking_lot", "percent-encoding", + "ref-cast", "reqwest", - "rustc-hash", - "secrecy", "serde", "serde_cow", "serde_json", - "static_assertions", + "small-fixed-array", + "strum", "time", + "to-arraystring", "tokio", "tokio-tungstenite", "tracing", - "typemap_rev", "typesize", "url", - "uwl", + "zeroize", ] [[package]] -name = "serenity_poise_macros" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bc8407893be0d973a7bb48325e10e0160a0db44f4099467c83179688220e5f5" +name = "serenity" +version = "0.12.5" +source = "git+https://github.com/serenity-rs/serenity?rev=30a293f0a02c63908b68a9f4fa687e964b7ff5c8#30a293f0a02c63908b68a9f4fa687e964b7ff5c8" dependencies = [ - "darling 0.20.11", - "proc-macro2", - "quote", - "syn 2.0.117", + "aformat", + "arrayvec", + "async-trait", + "base64", + "bitflags", + "bool_to_bitflags", + "bytes", + "chrono", + "dashmap", + "extract_map", + "flate2", + "foldhash 0.2.0", + "futures", + "mime", + "mime_guess", + "nonmax", + "parking_lot", + "percent-encoding", + "ref-cast", + "reqwest", + "serde", + "serde_cow", + "serde_json", + "small-fixed-array", + "strum", + "time", + "to-arraystring", + "tokio", + "tokio-tungstenite", + "tracing", + "typesize", + "url", + "zeroize", + "zstd", ] [[package]] @@ -1936,27 +2045,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] -name = "similar" -version = "3.1.1" +name = "simd_cesu8" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6505efef05804732ed8a3f2d4f279429eb485bd69d5b0cc6b19cc02005cda16" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" dependencies = [ - "bstr", + "rustc_version", + "simdutf8", ] [[package]] -name = "skeptic" -version = "0.13.7" +name = "simdutf8" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "similar" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6505efef05804732ed8a3f2d4f279429eb485bd69d5b0cc6b19cc02005cda16" dependencies = [ - "bytecount", - "cargo_metadata", - "error-chain", - "glob", - "pulldown-cmark", - "tempfile", - "walkdir", + "bstr", ] [[package]] @@ -1965,6 +2075,15 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "small-fixed-array" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f47eb472ef0994fb63d68ce4851eef89fa0faaf0dc4088c941b4015ce32c083f" +dependencies = [ + "serde", +] + [[package]] name = "smallvec" version = "1.15.2" @@ -1987,18 +2106,33 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "subtle" version = "2.6.1" @@ -2061,52 +2195,13 @@ dependencies = [ "windows", ] -[[package]] -name = "tagptr" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" - -[[package]] -name = "tempfile" -version = "3.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" -dependencies = [ - "fastrand", - "getrandom 0.4.2", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "thiserror-impl", ] [[package]] @@ -2131,12 +2226,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.47" +version = "0.3.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "fc1aa89044e7786ffb2ec017acb22cb7de5b0be46d0f21aea2b224b8561e5db2" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", "serde_core", @@ -2146,15 +2240,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "9d3bfe86347f0cc659f586f01e26303ccd32418f26f30c7b0309b3ca3a07d695" dependencies = [ "num-conv", "time-core", @@ -2185,6 +2279,17 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "to-arraystring" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fafaa22f176928fb926345e78eb2ec404603c878b274e6ab1f76de1f6dde1b1" +dependencies = [ + "arrayvec", + "itoa", + "ryu", +] + [[package]] name = "tokio" version = "1.52.3" @@ -2212,39 +2317,28 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "tokio-rustls" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" -dependencies = [ - "rustls 0.22.4", - "rustls-pki-types", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.40", + "rustls", "tokio", ] [[package]] name = "tokio-tungstenite" -version = "0.21.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" dependencies = [ "futures-util", "log", - "rustls 0.22.4", + "rustls", "rustls-pki-types", "tokio", - "tokio-rustls 0.25.0", + "tokio-rustls", "tungstenite", "webpki-roots 0.26.11", ] @@ -2410,12 +2504,6 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" -[[package]] -name = "triomphe" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39" - [[package]] name = "try-lock" version = "0.2.5" @@ -2424,37 +2512,42 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.21.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ - "byteorder", "bytes", "data-encoding", "http", "httparse", "log", - "rand 0.8.6", - "rustls 0.22.4", + "rand 0.9.4", + "rustls", "rustls-pki-types", "sha1", - "thiserror 1.0.69", + "thiserror", "url", "utf-8", ] -[[package]] -name = "typemap_rev" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74b08b0c1257381af16a5c3605254d529d3e7e109f3c62befc5d168968192998" - [[package]] name = "typenum" version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" +[[package]] +name = "typenum_mappings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cbc2d8952dd1e08b0164a5b51549e80631ac9da4107669d26c8ea89cb0b5545" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "to-arraystring", +] + [[package]] name = "typesize" version = "0.1.15" @@ -2462,9 +2555,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a313188364f7e10138257cb288c64e9b0e282510abefc91c796799559fa14762" dependencies = [ "chrono", - "dashmap 5.5.3", - "hashbrown 0.14.5", - "mini-moka", + "nonmax", "parking_lot", "secrecy", "serde_json", @@ -2533,12 +2624,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "uwl" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0" - [[package]] name = "valine_bot" version = "2.6.1" @@ -2546,7 +2631,7 @@ dependencies = [ "async-stream", "bpaf", "chrono", - "dashmap 6.2.1", + "dashmap", "duration-str", "futures", "itertools", @@ -2555,10 +2640,10 @@ dependencies = [ "regex", "serde", "serde_with", - "serenity", + "serenity 0.12.5 (git+https://github.com/serenity-rs/serenity?rev=30a293f0a02c63908b68a9f4fa687e964b7ff5c8)", "similar", "sysinfo", - "thiserror 2.0.18", + "thiserror", "tokio", "toml", "tracing", @@ -2699,9 +2784,9 @@ dependencies = [ [[package]] name = "wasm-streams" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" dependencies = [ "futures-util", "js-sys", @@ -2742,6 +2827,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "0.26.11" @@ -3275,8 +3369,42 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "zlib-rs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" + [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 5c234ac..9baade0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,12 +13,10 @@ dashmap = "6.1" duration-str = "0.21" futures = "0.3" itertools = "0.14" -poise = "0.6.2" rand = "0.10" regex = "1.0" serde = { version = "1.0", features = [ "derive" ] } serde_with = "3" -serenity = "0.12.5" similar = "3.1" sysinfo = "0.38" thiserror = "2" @@ -27,6 +25,14 @@ toml = "1.0" tracing = "0.1" tracing-subscriber = "0.3" +[dependencies.serenity] +git = "https://github.com/serenity-rs/serenity" +rev = "30a293f0a02c63908b68a9f4fa687e964b7ff5c8" + +[dependencies.poise] +git = "https://github.com/serenity-rs/poise" +rev = "de8bd6fdd54de8ee5daf0242ffb6dc6eba58df69" + [profile.release] panic = "abort" strip = true From fcde0e72c56a2c4ec6faadbd07495da25c635b57 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 13 Jun 2026 04:45:00 +0900 Subject: [PATCH 078/104] =?UTF-8?q?chore:=20Serenity=E3=81=A8Poise?= =?UTF-8?q?=E3=81=AE=E4=BE=9D=E5=AD=98=E3=83=90=E3=83=BC=E3=82=B8=E3=83=A7?= =?UTF-8?q?=E3=83=B3=E3=82=92=E5=9B=BA=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 63 ++++++++++++++---------------------------------------- Cargo.toml | 7 ++++++ 2 files changed, 23 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d6494bb..8d6fd48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -228,9 +228,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.63" +version = "1.2.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f" dependencies = [ "find-msvc-tools", "jobserver", @@ -449,6 +449,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ + "powerfmt", "serde_core", ] @@ -1925,41 +1926,7 @@ dependencies = [ name = "serenity" version = "0.12.5" source = "git+https://github.com/serenity-rs/serenity?branch=next#30a293f0a02c63908b68a9f4fa687e964b7ff5c8" -dependencies = [ - "aformat", - "arrayvec", - "async-trait", - "base64", - "bitflags", - "bool_to_bitflags", - "bytes", - "chrono", - "dashmap", - "extract_map", - "flate2", - "foldhash 0.2.0", - "futures", - "mime", - "mime_guess", - "nonmax", - "parking_lot", - "percent-encoding", - "ref-cast", - "reqwest", - "serde", - "serde_cow", - "serde_json", - "small-fixed-array", - "strum", - "time", - "to-arraystring", - "tokio", - "tokio-tungstenite", - "tracing", - "typesize", - "url", - "zeroize", -] +replace = "serenity 0.12.5 (git+https://github.com/serenity-rs/serenity?rev=30a293f0a02c63908b68a9f4fa687e964b7ff5c8)" [[package]] name = "serenity" @@ -2226,11 +2193,12 @@ dependencies = [ [[package]] name = "time" -version = "0.3.48" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1aa89044e7786ffb2ec017acb22cb7de5b0be46d0f21aea2b224b8561e5db2" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", + "itoa", "num-conv", "powerfmt", "serde_core", @@ -2240,15 +2208,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.9" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.28" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d3bfe86347f0cc659f586f01e26303ccd32418f26f30c7b0309b3ca3a07d695" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -2644,6 +2612,7 @@ dependencies = [ "similar", "sysinfo", "thiserror", + "time", "tokio", "toml", "tracing", @@ -2689,9 +2658,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ "wit-bindgen 0.57.1", ] @@ -3332,9 +3301,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" [[package]] name = "zerotrie" diff --git a/Cargo.toml b/Cargo.toml index 9baade0..a9135be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,8 @@ serde_with = "3" similar = "3.1" sysinfo = "0.38" thiserror = "2" +# 最新の Serenity と time 0.3.48 に互換性がない +time = "=0.3.47" tokio = { version = "1.0", features = [ "rt-multi-thread", "macros", "signal" ] } toml = "1.0" tracing = "0.1" @@ -33,6 +35,11 @@ rev = "30a293f0a02c63908b68a9f4fa687e964b7ff5c8" git = "https://github.com/serenity-rs/poise" rev = "de8bd6fdd54de8ee5daf0242ffb6dc6eba58df69" +# Poise が next ブランチを参照しているので、直接の依存関係に合わせる +[replace."https://github.com/serenity-rs/serenity#serenity:0.12.5"] +git = "https://github.com/serenity-rs/serenity" +rev = "30a293f0a02c63908b68a9f4fa687e964b7ff5c8" + [profile.release] panic = "abort" strip = true From 217329c9dd216bf0e63ad57116f309715539c334 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 13 Jun 2026 04:46:55 +0900 Subject: [PATCH 079/104] =?UTF-8?q?refactor:=20=E3=82=A4=E3=83=99=E3=83=B3?= =?UTF-8?q?=E3=83=88=E5=87=A6=E7=90=86=E5=9F=BA=E7=9B=A4=E3=82=92=E5=86=8D?= =?UTF-8?q?=E6=A7=8B=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - イベントハンドラの変更に合わせたラッパー追加 - クライアント生成とイベント配送を core へ分離 - BotData と共通型を導入 - イベントハンドラ用マクロを追加 --- Cargo.lock | 9 + Cargo.toml | 1 + macros/Cargo.toml | 12 ++ macros/src/lib.rs | 68 ++++++ src/config.rs | 17 +- src/core/client.rs | 34 +++ src/core/event_handler.rs | 34 +++ src/core/mod.rs | 5 + src/data.rs | 19 ++ src/error.rs | 21 +- src/extensions/message_builder.rs | 113 ++++++---- src/main.rs | 147 ++++--------- src/main_event_handler.rs | 88 ++++++++ src/types.rs | 5 + src/utils.rs | 333 +++++++++++++++--------------- 15 files changed, 580 insertions(+), 326 deletions(-) create mode 100644 macros/Cargo.toml create mode 100644 macros/src/lib.rs create mode 100644 src/core/client.rs create mode 100644 src/core/event_handler.rs create mode 100644 src/core/mod.rs create mode 100644 src/data.rs create mode 100644 src/main_event_handler.rs create mode 100644 src/types.rs diff --git a/Cargo.lock b/Cargo.lock index 8d6fd48..9444504 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2617,6 +2617,15 @@ dependencies = [ "toml", "tracing", "tracing-subscriber", + "valine_bot_macros", +] + +[[package]] +name = "valine_bot_macros" +version = "0.1.0" +dependencies = [ + "quote", + "syn 2.0.117", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a9135be..809a476 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ tokio = { version = "1.0", features = [ "rt-multi-thread", "macros", "signal" ] toml = "1.0" tracing = "0.1" tracing-subscriber = "0.3" +valine_bot_macros = { path = "macros" } [dependencies.serenity] git = "https://github.com/serenity-rs/serenity" diff --git a/macros/Cargo.toml b/macros/Cargo.toml new file mode 100644 index 0000000..6659fa1 --- /dev/null +++ b/macros/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "valine_bot_macros" +version = "0.1.0" +edition = "2024" +publish = false + +[lib] +proc-macro = true + +[dependencies] +quote = "1" +syn = { version = "2", features = [ "full" ] } diff --git a/macros/src/lib.rs b/macros/src/lib.rs new file mode 100644 index 0000000..46b1c94 --- /dev/null +++ b/macros/src/lib.rs @@ -0,0 +1,68 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{FnArg, ItemFn, ReturnType, parse_macro_input}; + +#[proc_macro_attribute] +pub fn simple_event_handler(_attr: TokenStream, item: TokenStream) -> TokenStream { + let function = parse_macro_input!(item as ItemFn); + + if function.sig.asyncness.is_none() { + return syn::Error::new_spanned( + &function.sig.fn_token, + "`#[simple_event_handler]` can only be used on async functions", + ) + .into_compile_error() + .into(); + } + + if !matches!(function.sig.output, ReturnType::Default) { + return syn::Error::new_spanned( + &function.sig.output, + "`#[simple_event_handler]` functions must return `()`", + ) + .into_compile_error() + .into(); + } + + if !function.sig.generics.params.is_empty() || function.sig.generics.where_clause.is_some() { + return syn::Error::new_spanned( + &function.sig.generics, + "`#[simple_event_handler]` functions cannot have generics", + ) + .into_compile_error() + .into(); + } + + if function.sig.inputs.len() != 2 + || function + .sig + .inputs + .iter() + .any(|input| !matches!(input, FnArg::Typed(_))) + { + return syn::Error::new_spanned( + &function.sig.inputs, + "`#[simple_event_handler]` functions must take `&Context` and `&FullEvent`", + ) + .into_compile_error() + .into(); + } + + let attributes = function.attrs; + let visibility = function.vis; + let handler_name = function.sig.ident; + let inputs = function.sig.inputs; + let body = function.block; + + quote! { + #(#attributes)* + #[allow(non_camel_case_types)] + #visibility struct #handler_name; + + #[::serenity::async_trait] + impl crate::BotEventHandler for #handler_name { + async fn dispatch(&self, #inputs) #body + } + } + .into() +} diff --git a/src/config.rs b/src/config.rs index 4aac491..0c80f5b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,15 +8,12 @@ use duration_str::deserialize_duration_chrono; use regex::Regex; use serde::{Deserialize, Deserializer}; use serde_with::{DisplayFromStr, serde_as}; -use serenity::{ - all::{ChannelId, Context, ForumTagId, GuildId, RoleId, UserId}, - prelude::TypeMapKey, -}; +use serenity::all::{ChannelId, Context, ForumTagId, GuildId, RoleId, Token, UserId}; + +use crate::data::{BotData, BotDataGetter}; pub async fn get_config(ctx: &Context) -> Arc { - let data = ctx.data.read().await; - let config = data.get::().expect("Expected MessageCount in TypeMap."); - config.clone() + Arc::clone(&ctx.get_bot_data().config) } #[derive(Debug, Deserialize)] @@ -33,13 +30,9 @@ pub struct Config { pub question: QuestionConfig, } -impl TypeMapKey for Config { - type Value = Arc; -} - #[derive(Debug, Deserialize)] pub struct BotConfig { - pub token: String, + pub token: Token, pub owners: HashSet, } diff --git a/src/core/client.rs b/src/core/client.rs new file mode 100644 index 0000000..0f62b8f --- /dev/null +++ b/src/core/client.rs @@ -0,0 +1,34 @@ +use std::sync::Arc; + +use serenity::{ + Client, + all::prelude::{Context, EventHandler, GatewayIntents}, + async_trait, + gateway::client::ClientBuilder, + http::RatelimitInfo, + model::event::FullEvent, + secrets::Token, +}; + +use crate::core::event_handler::BotEventHandlers; + +struct ClientEventHandler(BotEventHandlers); + +#[async_trait] +impl EventHandler for ClientEventHandler { + async fn dispatch(&self, ctx: &Context, event: &FullEvent) { + self.0.dispatch(ctx, event).await; + } + + async fn ratelimit(&self, data: RatelimitInfo) { + self.0.ratelimit(&data).await; + } +} + +pub fn create_client( + token: impl Into, + intents: GatewayIntents, + event_handlers: BotEventHandlers, +) -> ClientBuilder { + Client::builder(token.into(), intents).event_handler(Arc::new(ClientEventHandler(event_handlers))) +} diff --git a/src/core/event_handler.rs b/src/core/event_handler.rs new file mode 100644 index 0000000..43e2887 --- /dev/null +++ b/src/core/event_handler.rs @@ -0,0 +1,34 @@ +use serenity::{all::prelude::Context, async_trait, http::RatelimitInfo, model::event::FullEvent}; + +#[async_trait] +pub trait BotEventHandler: Send + Sync { + async fn dispatch(&self, ctx: &Context, event: &FullEvent); + + #[allow(unused_variables)] + async fn ratelimit(&self, data: &RatelimitInfo) {} +} + +pub struct BotEventHandlers(Vec>); + +impl BotEventHandlers { + pub fn new() -> Self { + Self(vec![]) + } + + pub async fn dispatch(&self, ctx: &Context, event: &FullEvent) { + for handler in &self.0 { + handler.dispatch(ctx, event).await + } + } + + pub async fn ratelimit(&self, data: &RatelimitInfo) { + for handler in &self.0 { + handler.ratelimit(data).await + } + } + + pub fn add(mut self, handler: B) -> Self { + self.0.push(Box::new(handler)); + self + } +} diff --git a/src/core/mod.rs b/src/core/mod.rs new file mode 100644 index 0000000..e7bc732 --- /dev/null +++ b/src/core/mod.rs @@ -0,0 +1,5 @@ +mod client; +mod event_handler; + +pub use client::create_client; +pub use event_handler::{BotEventHandler, BotEventHandlers}; diff --git a/src/data.rs b/src/data.rs new file mode 100644 index 0000000..eabfb3c --- /dev/null +++ b/src/data.rs @@ -0,0 +1,19 @@ +use std::sync::Arc; + +use serenity::all::prelude::Context; + +use crate::config::Config; + +pub struct BotData { + pub config: Arc, +} + +pub trait BotDataGetter { + fn get_bot_data(&self) -> Arc; +} + +impl BotDataGetter for Context { + fn get_bot_data(&self) -> Arc { + self.data::() + } +} diff --git a/src/error.rs b/src/error.rs index 6c9e620..78d8f3e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,9 +1,8 @@ use poise::{FrameworkError, say_reply}; -use serenity::all::MessageParseError; use thiserror::Error; use tracing::error; -use crate::{CommandData, PError, utils::format_duration}; +use crate::{data::BotData, types::PError, utils::format_duration}; #[derive(Error, Debug)] pub enum BotError { @@ -15,7 +14,7 @@ pub enum BotError { IsPrivateThread, } -pub async fn on_error(error: FrameworkError<'_, CommandData, PError>) { +pub async fn on_error(error: FrameworkError<'_, BotData, PError>) { match error { FrameworkError::Command { error, ctx, .. } => { let _ = say_reply(ctx, "コマンド実行中にエラーが発生しました。").await; @@ -26,15 +25,15 @@ pub async fn on_error(error: FrameworkError<'_, CommandData, PError>) { return error!("Error parsing input: {:?}", error); }; - let error = match error.downcast_ref::() { - Some(MessageParseError::Malformed) => { - "メッセージとして解析できませんでした。\nメッセージID、メッセージURL形式で入力してください。" - } - Some(MessageParseError::Http(_)) => "メッセージを取得できませんでした。", - _ => &error.to_string(), - }; + // let error = match error.downcast_ref::() { + // Some(MessageParseError::Malformed) => { + // "メッセージとして解析できませんでした。\nメッセージID、メッセージURL形式で入力してください。" + // } + // Some(MessageParseError::Http(_)) => "メッセージを取得できませんでした。", + // _ => &error.to_string(), + // }; - let _ = say_reply(ctx, format!("入力 `{}` の解析に失敗しました。\n{}", input, error)).await; + let _ = say_reply(ctx, format!("入力 `{}` の解析に失敗しました。\n{:?}", input, error)).await; } FrameworkError::MissingBotPermissions { missing_permissions, diff --git a/src/extensions/message_builder.rs b/src/extensions/message_builder.rs index 0c9cb4f..fa665e2 100644 --- a/src/extensions/message_builder.rs +++ b/src/extensions/message_builder.rs @@ -5,71 +5,112 @@ use serenity::{ #[allow(dead_code)] pub trait MessageBuilderTimestampExt { - fn _push_linebreak(&mut self) -> &mut Self; + fn push_timestamp(self, timestamp: Timestamp, style: Option) -> Self; + fn push_timestamp_line(self, timestamp: Timestamp, style: Option) -> Self; - fn push_timestamp(&mut self, timestamp: Timestamp, style: Option) -> &mut Self; + fn push_short_timestamp(self, timestamp: Timestamp) -> Self; + fn push_short_timestamp_line(self, timestamp: Timestamp) -> Self; - fn push_timestamp_line(&mut self, timestamp: Timestamp, style: Option) -> &mut Self { - self.push_timestamp(timestamp, style)._push_linebreak() + fn push_medium_timestamp(self, timestamp: Timestamp) -> Self; + fn push_medium_timestamp_line(self, timestamp: Timestamp) -> Self; + + fn push_short_date_timestamp(self, timestamp: Timestamp) -> Self; + fn push_short_date_timestamp_line(self, timestamp: Timestamp) -> Self; + + fn push_long_date_timestamp(self, timestamp: Timestamp) -> Self; + fn push_long_date_timestamp_line(self, timestamp: Timestamp) -> Self; + + fn push_long_date_short_timestamp(self, timestamp: Timestamp) -> Self; + fn push_long_date_short_timestamp_line(self, timestamp: Timestamp) -> Self; + + fn push_full_date_short_timestamp(self, timestamp: Timestamp) -> Self; + fn push_full_date_short_timestamp_line(self, timestamp: Timestamp) -> Self; + + fn push_short_date_short_timestamp(self, timestamp: Timestamp) -> Self; + fn push_short_date_short_timestamp_line(self, timestamp: Timestamp) -> Self; + + fn push_short_date_medium_timestamp(self, timestamp: Timestamp) -> Self; + fn push_short_date_medium_timestamp_line(self, timestamp: Timestamp) -> Self; + + fn push_relative_timestamp(self, timestamp: Timestamp) -> Self; + fn push_timestamp_relative_time_line(self, timestamp: Timestamp) -> Self; +} + +fn _push_linebreak(mut builder: MessageBuilder) -> MessageBuilder { + builder.0.push('\n'); + builder +} + +impl MessageBuilderTimestampExt for MessageBuilder { + fn push_timestamp(mut self, timestamp: Timestamp, style: Option) -> Self { + let formatted = FormattedTimestamp::new(timestamp, style).to_string(); + self.0.push_str(&formatted); + self + } + fn push_timestamp_line(self, timestamp: Timestamp, style: Option) -> Self { + _push_linebreak(self.push_timestamp(timestamp, style)) } - fn push_timestamp_short(&mut self, timestamp: Timestamp) -> &mut Self { + fn push_short_timestamp(self, timestamp: Timestamp) -> Self { self.push_timestamp(timestamp, Some(FormattedTimestampStyle::ShortTime)) } - fn push_timestamp_short_line(&mut self, timestamp: Timestamp) -> &mut Self { - self.push_timestamp_short(timestamp)._push_linebreak() + fn push_short_timestamp_line(self, timestamp: Timestamp) -> Self { + _push_linebreak(self.push_short_timestamp(timestamp)) } - fn push_timestamp_long(&mut self, timestamp: Timestamp) -> &mut Self { - self.push_timestamp(timestamp, Some(FormattedTimestampStyle::LongTime)) + fn push_medium_timestamp(self, timestamp: Timestamp) -> Self { + self.push_timestamp(timestamp, Some(FormattedTimestampStyle::MediumTime)) } - fn push_timestamp_long_line(&mut self, timestamp: Timestamp) -> &mut Self { - self.push_timestamp_long(timestamp)._push_linebreak() + fn push_medium_timestamp_line(self, timestamp: Timestamp) -> Self { + _push_linebreak(self.push_medium_timestamp(timestamp)) } - fn push_timestamp_short_date(&mut self, timestamp: Timestamp) -> &mut Self { + fn push_short_date_timestamp(self, timestamp: Timestamp) -> Self { self.push_timestamp(timestamp, Some(FormattedTimestampStyle::ShortDate)) } - fn push_timestamp_short_date_line(&mut self, timestamp: Timestamp) -> &mut Self { - self.push_timestamp_short_date(timestamp)._push_linebreak() + fn push_short_date_timestamp_line(self, timestamp: Timestamp) -> Self { + _push_linebreak(self.push_short_date_timestamp(timestamp)) } - fn push_timestamp_long_date(&mut self, timestamp: Timestamp) -> &mut Self { + fn push_long_date_timestamp(self, timestamp: Timestamp) -> Self { self.push_timestamp(timestamp, Some(FormattedTimestampStyle::LongDate)) } - fn push_timestamp_long_date_line(&mut self, timestamp: Timestamp) -> &mut Self { - self.push_timestamp_long_date(timestamp)._push_linebreak() + fn push_long_date_timestamp_line(self, timestamp: Timestamp) -> Self { + _push_linebreak(self.push_long_date_timestamp(timestamp)) } - fn push_timestamp_short_date_time(&mut self, timestamp: Timestamp) -> &mut Self { - self.push_timestamp(timestamp, Some(FormattedTimestampStyle::ShortDateTime)) + fn push_long_date_short_timestamp(self, timestamp: Timestamp) -> Self { + self.push_timestamp(timestamp, Some(FormattedTimestampStyle::LongDateShortTime)) } - fn push_timestamp_short_date_time_line(&mut self, timestamp: Timestamp) -> &mut Self { - self.push_timestamp_short_date_time(timestamp)._push_linebreak() + fn push_long_date_short_timestamp_line(self, timestamp: Timestamp) -> Self { + _push_linebreak(self.push_long_date_short_timestamp(timestamp)) } - fn push_timestamp_long_date_time(&mut self, timestamp: Timestamp) -> &mut Self { - self.push_timestamp(timestamp, Some(FormattedTimestampStyle::LongDateTime)) + fn push_full_date_short_timestamp(self, timestamp: Timestamp) -> Self { + self.push_timestamp(timestamp, Some(FormattedTimestampStyle::FullDateShortTime)) } - fn push_timestamp_long_date_time_line(&mut self, timestamp: Timestamp) -> &mut Self { - self.push_timestamp_long_date_time(timestamp)._push_linebreak() + fn push_full_date_short_timestamp_line(self, timestamp: Timestamp) -> Self { + _push_linebreak(self.push_full_date_short_timestamp(timestamp)) } - fn push_timestamp_relative_time(&mut self, timestamp: Timestamp) -> &mut Self { - self.push_timestamp(timestamp, Some(FormattedTimestampStyle::RelativeTime)) + fn push_short_date_short_timestamp(self, timestamp: Timestamp) -> Self { + self.push_timestamp(timestamp, Some(FormattedTimestampStyle::ShortDateShortTime)) } - fn push_timestamp_relative_time_line(&mut self, timestamp: Timestamp) -> &mut Self { - self.push_timestamp_relative_time(timestamp)._push_linebreak() + fn push_short_date_short_timestamp_line(self, timestamp: Timestamp) -> Self { + _push_linebreak(self.push_short_date_short_timestamp(timestamp)) } -} -impl MessageBuilderTimestampExt for MessageBuilder { - fn push_timestamp(&mut self, timestamp: Timestamp, style: Option) -> &mut Self { - let formatted = FormattedTimestamp::new(timestamp, style).to_string(); - self.push(formatted) + fn push_short_date_medium_timestamp(self, timestamp: Timestamp) -> Self { + self.push_timestamp(timestamp, Some(FormattedTimestampStyle::ShortDateMediumTime)) + } + fn push_short_date_medium_timestamp_line(self, timestamp: Timestamp) -> Self { + _push_linebreak(self.push_short_date_medium_timestamp(timestamp)) } - fn _push_linebreak(&mut self) -> &mut Self { - self.push_line("") + fn push_relative_timestamp(self, timestamp: Timestamp) -> Self { + self.push_timestamp(timestamp, Some(FormattedTimestampStyle::RelativeTime)) + } + fn push_timestamp_relative_time_line(self, timestamp: Timestamp) -> Self { + _push_linebreak(self.push_relative_timestamp(timestamp)) } } diff --git a/src/main.rs b/src/main.rs index 7225632..e243077 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,89 +1,29 @@ mod config; +mod core; +mod data; mod error; mod extensions; -mod features; +mod main_event_handler; +mod types; +// mod features; mod utils; -use std::{fs::read_to_string, sync::Arc, time::Duration}; +use std::{fs::read_to_string, sync::Arc}; use bpaf::Bpaf; use config::Config; -use error::on_error; -use features::{MessageCache, MessageCacheType, commands}; +// use error::on_error; +// use features::{MessageCache, MessageCacheType, commands}; use poise::{Framework, FrameworkOptions}; -use serenity::{ - all::{ActivityData, ChunkGuildFilter, Guild, GuildId, RatelimitInfo, Ready}, - async_trait, - cache::Settings as CacheSettings, - prelude::*, +use serenity::{cache::Settings as CacheSettings, prelude::*}; +use tracing::error; + +use crate::{ + core::{BotEventHandlers, create_client}, + data::BotData, + error::on_error, + main_event_handler::MainEventHandler, }; -use sysinfo::{Pid, System}; -use tokio::task::JoinHandle; -use tracing::{error, info, warn}; - -pub type PError = Box; -pub struct CommandData {} -pub type PContext<'a> = poise::Context<'a, CommandData, PError>; -pub type PCommand = poise::Command; - -struct MainHandler { - activity_task: Mutex>>, -} - -impl MainHandler { - pub fn new() -> Self { - Self { - activity_task: Mutex::new(None), - } - } -} - -#[async_trait] -impl EventHandler for MainHandler { - async fn ready(&self, _: Context, ready: Ready) { - info!("{} is connected!", ready.user.name); - } - - async fn cache_ready(&self, ctx: Context, _: Vec) { - let mut task = self.activity_task.lock().await; - - if let Some(handle) = task.take() { - handle.abort(); - } - - *task = Some(tokio::spawn(async move { - let mut system = System::new_all(); - let pid = Pid::from_u32(std::process::id()); - - loop { - system.refresh_all(); - - if let Some(memory) = system.process(pid).map(|p| p.memory() as f64 / 1024.0 / 1024.0) { - ctx.set_activity(Some(ActivityData::custom(format!("メモリ使用量: {:.1}MB", memory)))); - } else { - error!("Failed to get process info"); - } - - tokio::time::sleep(Duration::from_secs(60)).await; - } - })); - } - - async fn guild_create(&self, ctx: Context, guild: Guild, _: Option) { - // 全てのメンバーを取得する。結果は Serenity によって自動でキャッシュされる。 - ctx.shard - .chunk_guild(guild.id, Some(0), false, ChunkGuildFilter::None, None); - } - - async fn ratelimit(&self, data: RatelimitInfo) { - warn!( - "Ratelimited {} {}: {}s", - data.method.reqwest_method(), - data.path, - data.timeout.as_secs() - ); - } -} #[derive(Clone, Debug, Bpaf)] #[bpaf(options, version)] @@ -106,20 +46,18 @@ async fn main() { return; } + let data = Arc::new(BotData { + config: Arc::new(config), + }); + let framework = Framework::builder() .options(FrameworkOptions { - commands: commands(), + // commands: commands(), on_error: |error| Box::pin(on_error(error)), skip_checks_for_owners: false, - owners: config.bot.owners.clone(), + owners: data.config.bot.owners.clone(), ..Default::default() }) - .setup(move |ctx, _ready, framework| { - Box::pin(async move { - poise::builtins::register_globally(ctx, &framework.options().commands).await?; - Ok(CommandData {}) - }) - }) .build(); let intents = GatewayIntents::GUILD_MESSAGES @@ -130,29 +68,32 @@ async fn main() { let mut settings = CacheSettings::default(); settings.max_messages = 1_000_000; - let mut client = Client::builder(&config.bot.token, intents) - .framework(framework) - .event_handler(MainHandler::new()) - .event_handler(features::AuthHandler::new()) - .event_handler(features::AutoKickHandler::new()) - .event_handler(features::HoneypotHandler) - .event_handler(features::LoggingHandler) - .event_handler(features::ThreadAutoInviteHandler::new()) - .event_handler(features::ThreadChannelStartupHandler) - .event_handler(features::QuestionHandler) - .event_handler(features::MessageCacheHandler::new(config.message_cache.disabled)) - .cache_settings(settings) - .type_map_insert::(Arc::new(MessageCache::new())) - .type_map_insert::(Arc::new(config)) - .await - .expect("Err creating client"); - - let shard_manager = client.shard_manager.clone(); + let mut client = create_client( + data.config.bot.token.clone(), + intents, + BotEventHandlers::new().add(MainEventHandler::new()), + ) + .framework(Box::new(framework)) + // .event_handler(features::AuthHandler::new()) + // .event_handler(features::AutoKickHandler::new()) + // .event_handler(features::HoneypotHandler) + // .event_handler(features::LoggingHandler) + // .event_handler(features::ThreadAutoInviteHandler::new()) + // .event_handler(features::ThreadChannelStartupHandler) + // .event_handler(features::QuestionHandler) + // .event_handler(features::MessageCacheHandler::new(config.message_cache.disabled)) + .cache_settings(settings) + .data(Arc::new(data)) + // .type_map_insert::(Arc::new(MessageCache::new())) + .await + .expect("Err creating client"); + + let shutdown = client.shard_manager.get_shutdown_trigger(); tokio::spawn(async move { tokio::signal::ctrl_c() .await .expect("Could not register ctrl+c handler"); - shard_manager.shutdown_all().await; + shutdown() }); if let Err(why) = client.start().await { diff --git a/src/main_event_handler.rs b/src/main_event_handler.rs new file mode 100644 index 0000000..da5f86a --- /dev/null +++ b/src/main_event_handler.rs @@ -0,0 +1,88 @@ +use std::time::Duration; + +use futures::lock::Mutex; +use serenity::{ + all::prelude::Context, + async_trait, + gateway::{ActivityData, ChunkGuildFilter}, + http::RatelimitInfo, + model::{event::FullEvent, gateway::Ready, guild::Guild}, +}; +use sysinfo::{Pid, System}; +use tokio::task::JoinHandle; +use tracing::{error, info, warn}; + +use crate::core::BotEventHandler; + +pub struct MainEventHandler { + activity_task: Mutex>>, +} + +impl MainEventHandler { + pub fn new() -> Self { + Self { + activity_task: Mutex::new(None), + } + } + + async fn handle_ready(&self, ready: &Ready) { + info!("{} is connected!", ready.user.name); + } + + async fn handle_cache_ready(&self, ctx: &Context) { + let mut task = self.activity_task.lock().await; + + if let Some(handle) = task.take() { + handle.abort(); + } + + let ctx = ctx.clone(); + + *task = Some(tokio::spawn(async move { + let mut system = System::new_all(); + let pid = Pid::from_u32(std::process::id()); + + loop { + system.refresh_all(); + + if let Some(memory) = system.process(pid).map(|p| p.memory() as f64 / 1024.0 / 1024.0) { + ctx.set_activity(Some(ActivityData::custom(format!("メモリ使用量: {:.1}MB", memory)))); + } else { + error!("Failed to get process info"); + } + + tokio::time::sleep(Duration::from_secs(60)).await; + } + })); + } + + async fn handle_guild_create(&self, ctx: &Context, guild: &Guild) { + // 全てのメンバーを取得する。結果は Serenity によって自動でキャッシュされる。 + ctx.chunk_guild(guild.id, Some(0), false, ChunkGuildFilter::None, None); + } +} + +#[async_trait] +impl BotEventHandler for MainEventHandler { + async fn dispatch(&self, ctx: &Context, event: &FullEvent) { + if let FullEvent::CacheReady { .. } = event { + self.handle_cache_ready(ctx).await; + } + + match event { + FullEvent::CacheReady { .. } => self.handle_cache_ready(ctx).await, + FullEvent::Ready { data_about_bot, .. } => self.handle_ready(data_about_bot).await, + FullEvent::GuildCreate { guild, .. } => self.handle_guild_create(ctx, guild).await, + _ => {} + } + } + + async fn ratelimit(&self, data: &RatelimitInfo) { + warn!( + "Ratelimited {} {}: {}s", + data.method.reqwest_method(), + data.path, + data.timeout.as_secs() + ); + } +} diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..64f846f --- /dev/null +++ b/src/types.rs @@ -0,0 +1,5 @@ +use crate::data::BotData; + +pub type PError = Box; +pub type PContext<'a> = poise::Context<'a, BotData, PError>; +pub type PCommand = poise::Command; diff --git a/src/utils.rs b/src/utils.rs index f8c38a9..e7d5a7b 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -14,66 +14,71 @@ use serenity::{ use similar::{Algorithm, ChangeTag, TextDiff}; use tracing::error; -use crate::{MessageCacheType, PContext, PError, config::get_config, error::BotError}; - -pub fn create_safe_message() -> CreateMessage { - CreateMessage::new().allowed_mentions(CreateAllowedMentions::new().all_users(false)) -} - -pub fn create_message(content: impl Into) -> CreateMessage { - create_safe_message().content(content) -} - -pub fn create_interaction_message( - content: impl Into, - ephemeral: bool, - components: Option>, -) -> CreateInteractionResponse { - let mut msg = CreateInteractionResponseMessage::new() - .content(content) - .ephemeral(ephemeral); - - if let Some(components) = components { - msg = msg.components(components); - } - - CreateInteractionResponse::Message(msg) -} - -/** -thread_create イベントにおいて、初期メッセージが送信されるか5秒経過するまで待機する - -初期メッセージが送信されると、falseを返し、既に初期メッセージが存在する場合、true を返す -```rs -pub struct Handler; - -#[async_trait] -impl EventHandler for Handler { - async fn thread_create(&self, ctx: Context, thread: GuildChannel) { - if await_thread_create(&ctx, &thread).await { - return; - } +use crate::{ + config::get_config, + error::BotError, + // MessageCacheType, + types::{PContext, PError}, +}; - // 処理 - } -} -``` - */ -pub async fn await_initial_message(ctx: &Context, thread: &GuildChannel) -> bool { - // Botがメッセージを送信すると二度イベントが発火するので、初期メッセージ送信後のイベントは無視する - if thread.last_message_id.is_some() { - return true; - } - - // 初期メッセージが送信されるか、5秒経つまで待機 - thread - .await_reply(&ctx.shard) - .channel_id(thread.id) - .author_id(thread.owner_id.unwrap()) - .timeout(Duration::from_secs(5)) - .await; - false -} +// pub fn create_safe_message() -> CreateMessage { +// CreateMessage::new().allowed_mentions(CreateAllowedMentions::new().all_users(false)) +// } + +// pub fn create_message(content: impl Into) -> CreateMessage { +// create_safe_message().content(content) +// } + +// pub fn create_interaction_message( +// content: impl Into, +// ephemeral: bool, +// components: Option>, +// ) -> CreateInteractionResponse { +// let mut msg = CreateInteractionResponseMessage::new() +// .content(content) +// .ephemeral(ephemeral); + +// if let Some(components) = components { +// msg = msg.components(components); +// } + +// CreateInteractionResponse::Message(msg) +// } + +// /** +// thread_create イベントにおいて、初期メッセージが送信されるか5秒経過するまで待機する + +// 初期メッセージが送信されると、falseを返し、既に初期メッセージが存在する場合、true を返す +// ```rs +// pub struct Handler; + +// #[async_trait] +// impl EventHandler for Handler { +// async fn thread_create(&self, ctx: Context, thread: GuildChannel) { +// if await_thread_create(&ctx, &thread).await { +// return; +// } + +// // 処理 +// } +// } +// ``` +// */ +// pub async fn await_initial_message(ctx: &Context, thread: &GuildChannel) -> bool { +// // Botがメッセージを送信すると二度イベントが発火するので、初期メッセージ送信後のイベントは無視する +// if thread.last_message_id.is_some() { +// return true; +// } + +// // 初期メッセージが送信されるか、5秒経つまで待機 +// thread +// .await_reply(&ctx.shard) +// .channel_id(thread.id) +// .author_id(thread.owner_id.unwrap()) +// .timeout(Duration::from_secs(5)) +// .await; +// false +// } /* 認証済みロールを持っているかどうかを確認します。 @@ -91,17 +96,17 @@ pub async fn has_authed_role(ctx: PContext<'_>) -> Result { } } -/* -実行した場所がパブリックスレッドであるかどうかを確認します。 -*/ -pub async fn is_in_public_thread(ctx: PContext<'_>) -> Result { - let channel = ctx.guild_channel().await.ok_or(BotError::IsNotInThread)?; - match channel.kind { - ChannelType::PublicThread | ChannelType::NewsThread => Ok(true), - ChannelType::PrivateThread => Err(BotError::IsPrivateThread.into()), - _ => Err(BotError::IsNotInThread.into()), - } -} +// /* +// 実行した場所がパブリックスレッドであるかどうかを確認します。 +// */ +// pub async fn is_in_public_thread(ctx: PContext<'_>) -> Result { +// let channel = ctx.guild_channel().await.ok_or(BotError::IsNotInThread)?; +// match channel.kind { +// ChannelType::PublicThread | ChannelType::NewsThread => Ok(true), +// ChannelType::PrivateThread => Err(BotError::IsPrivateThread.into()), +// _ => Err(BotError::IsNotInThread.into()), +// } +// } const UNITS: [(u64, &str); 4] = [(86400, "日"), (3600, "時間"), (60, "分"), (1, "秒")]; @@ -123,93 +128,93 @@ pub fn format_duration(duration: Duration, mut count: usize) -> String { parts.join(" ") } -pub async fn send_message(ctx: &Context, channel_id: &ChannelId, builder: CreateMessage) -> Result { - match channel_id.send_message(&ctx.http, builder).await { - Ok(m) => Ok(m), - Err(why) => { - error!("Error sending message: {:?}", why); - Err(why) - } - } -} - -pub async fn get_cached_message(ctx: &Context, channel_id: ChannelId, message_id: MessageId) -> Option { - if let Some(m) = ctx.cache.message(channel_id, message_id) { - return Some(m.clone()); - } - - let data = ctx.data.read().await; - let cache = data.get::().unwrap(); - if let Some(m) = cache.get(channel_id, message_id) { - return Some(m); - } - - None -} - -/** -指定されたチャンネルのアーカイブされたパブリックスレッドを取得します。 -- `ChannelId::get_archived_public_threads`は Discord の要求に従っていないので、自前で実装しています。 - - Serenity の next ブランチで修正済み -*/ -pub async fn fetch_channel_archived_public_threads( - http: impl AsRef, - channel_id: ChannelId, - before: Option, - limit: Option, -) -> Result { - let mut params = vec![]; - if let Some(before) = before { - params.push(("before", before.to_string())); - } - if let Some(limit) = limit { - params.push(("limit", limit.to_string())); - } - - http.as_ref() - .fire(Request::new(Route::ChannelArchivedPublicThreads { channel_id }, LightMethod::Get).params(Some(params))) - .await -} - -/** -指定されたチャンネルのアーカイブされたパブリックスレッドをすべて取得します。 - */ -pub async fn fetch_all_archived_public_thread( - ctx: &Context, - channel_id: ChannelId, - max_retries: Option, -) -> impl Stream + '_ { - let max_retries = max_retries.unwrap_or(5); - Box::pin(stream! { - let mut retries_left = max_retries; - let mut before = None; - loop { - let thread_data = match fetch_channel_archived_public_threads(&ctx, channel_id, before, Some(100)).await { - Ok(data) => data, - Err(_) => { - if retries_left == 0 { - break; - } else { - retries_left -= 1; - continue; - } - } - }; - - before = thread_data.threads - .last() - .and_then(|last| last.thread_metadata.unwrap().archive_timestamp); - - for channel in thread_data.threads { - yield channel; - } - - if !thread_data.has_more || before.is_none() { - break; - } - } - }) -} +// pub async fn send_message(ctx: &Context, channel_id: &ChannelId, builder: CreateMessage) -> Result { +// match channel_id.send_message(&ctx.http, builder).await { +// Ok(m) => Ok(m), +// Err(why) => { +// error!("Error sending message: {:?}", why); +// Err(why) +// } +// } +// } + +// pub async fn get_cached_message(ctx: &Context, channel_id: ChannelId, message_id: MessageId) -> Option { +// if let Some(m) = ctx.cache.message(channel_id, message_id) { +// return Some(m.clone()); +// } + +// let data = ctx.data.read().await; +// let cache = data.get::().unwrap(); +// if let Some(m) = cache.get(channel_id, message_id) { +// return Some(m); +// } + +// None +// } + +// /** +// 指定されたチャンネルのアーカイブされたパブリックスレッドを取得します。 +// - `ChannelId::get_archived_public_threads`は Discord の要求に従っていないので、自前で実装しています。 +// - Serenity の next ブランチで修正済み +// */ +// pub async fn fetch_channel_archived_public_threads( +// http: impl AsRef, +// channel_id: ChannelId, +// before: Option, +// limit: Option, +// ) -> Result { +// let mut params = vec![]; +// if let Some(before) = before { +// params.push(("before", before.to_string())); +// } +// if let Some(limit) = limit { +// params.push(("limit", limit.to_string())); +// } + +// http.as_ref() +// .fire(Request::new(Route::ChannelArchivedPublicThreads { channel_id }, LightMethod::Get).params(Some(params))) +// .await +// } + +// /** +// 指定されたチャンネルのアーカイブされたパブリックスレッドをすべて取得します。 +// */ +// pub async fn fetch_all_archived_public_thread( +// ctx: &Context, +// channel_id: ChannelId, +// max_retries: Option, +// ) -> impl Stream + '_ { +// let max_retries = max_retries.unwrap_or(5); +// Box::pin(stream! { +// let mut retries_left = max_retries; +// let mut before = None; +// loop { +// let thread_data = match fetch_channel_archived_public_threads(&ctx, channel_id, before, Some(100)).await { +// Ok(data) => data, +// Err(_) => { +// if retries_left == 0 { +// break; +// } else { +// retries_left -= 1; +// continue; +// } +// } +// }; + +// before = thread_data.threads +// .last() +// .and_then(|last| last.thread_metadata.unwrap().archive_timestamp); + +// for channel in thread_data.threads { +// yield channel; +// } + +// if !thread_data.has_more || before.is_none() { +// break; +// } +// } +// }) +// } pub fn create_diff_lines_text(old: &str, new: &str) -> String { let diff = TextDiff::configure().algorithm(Algorithm::Myers).diff_lines(old, new); @@ -222,10 +227,10 @@ pub fn create_diff_lines_text(old: &str, new: &str) -> String { .join("") } -pub fn get_guild_members(ctx: &Context, guild_id: GuildId) -> impl Iterator { - guild_id - .to_guild_cached(ctx) - .map(|guild| guild.members.clone().into_values()) - .into_iter() - .flatten() -} +// pub fn get_guild_members(ctx: &Context, guild_id: GuildId) -> impl Iterator { +// guild_id +// .to_guild_cached(ctx) +// .map(|guild| guild.members.clone().into_values()) +// .into_iter() +// .flatten() +// } From 33a7466f8911770461361260e7306aa4b7318594 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 13 Jun 2026 05:45:54 +0900 Subject: [PATCH 080/104] =?UTF-8?q?refactor:=20app=20=E3=83=87=E3=82=A3?= =?UTF-8?q?=E3=83=AC=E3=82=AF=E3=83=88=E3=83=AA=E3=81=AB=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/{ => app}/config.rs | 22 +++--- src/app/data.rs | 35 +++++++++ src/{ => app}/error.rs | 7 +- .../event_handler.rs} | 0 src/app/mod.rs | 12 +++ src/app/types.rs | 5 ++ src/data.rs | 19 ----- src/features/mod.rs | 74 +++++++++---------- src/main.rs | 23 ++---- src/types.rs | 5 -- src/utils.rs | 15 ++-- 11 files changed, 119 insertions(+), 98 deletions(-) rename src/{ => app}/config.rs (86%) create mode 100644 src/app/data.rs rename src/{ => app}/error.rs (95%) rename src/{main_event_handler.rs => app/event_handler.rs} (100%) create mode 100644 src/app/mod.rs create mode 100644 src/app/types.rs delete mode 100644 src/data.rs delete mode 100644 src/types.rs diff --git a/src/config.rs b/src/app/config.rs similarity index 86% rename from src/config.rs rename to src/app/config.rs index 0c80f5b..483f1b5 100644 --- a/src/config.rs +++ b/src/app/config.rs @@ -1,6 +1,6 @@ use std::{ collections::{HashMap, HashSet}, - sync::Arc, + path::Path, }; use chrono::Duration; @@ -8,16 +8,11 @@ use duration_str::deserialize_duration_chrono; use regex::Regex; use serde::{Deserialize, Deserializer}; use serde_with::{DisplayFromStr, serde_as}; -use serenity::all::{ChannelId, Context, ForumTagId, GuildId, RoleId, Token, UserId}; - -use crate::data::{BotData, BotDataGetter}; - -pub async fn get_config(ctx: &Context) -> Arc { - Arc::clone(&ctx.get_bot_data().config) -} +use serenity::all::{ChannelId, ForumTagId, GuildId, RoleId, Token, UserId}; +use tokio::fs::read_to_string; #[derive(Debug, Deserialize)] -pub struct Config { +pub struct AppConfig { pub bot: BotConfig, pub auth: AuthConfig, pub auto_kick: AutoKickConfig, @@ -30,6 +25,15 @@ pub struct Config { pub question: QuestionConfig, } +impl AppConfig { + pub async fn from_file(path: &str) -> Self { + let text = read_to_string(path) + .await + .unwrap_or_else(|e| panic!("Failed to read {path}: {e}")); + toml::from_str(&text).unwrap_or_else(|e| panic!("Failed to parse {path}: {e}")) + } +} + #[derive(Debug, Deserialize)] pub struct BotConfig { pub token: Token, diff --git a/src/app/data.rs b/src/app/data.rs new file mode 100644 index 0000000..cf6e59c --- /dev/null +++ b/src/app/data.rs @@ -0,0 +1,35 @@ +use std::sync::Arc; + +use serenity::all::prelude::Context; + +use crate::app::{AppContext, config::AppConfig}; + +pub struct BotData { + pub config: Arc, +} + +pub trait BotDataGetter { + fn get_bot_data(&self) -> Arc; + + fn get_app_config(&self) -> Arc; +} + +impl BotDataGetter for Context { + fn get_bot_data(&self) -> Arc { + self.data::() + } + + fn get_app_config(&self) -> Arc { + Arc::clone(&self.get_bot_data().config) + } +} + +impl<'a> BotDataGetter for AppContext<'a> { + fn get_bot_data(&self) -> Arc { + self.data() + } + + fn get_app_config(&self) -> Arc { + Arc::clone(&self.data().config) + } +} diff --git a/src/error.rs b/src/app/error.rs similarity index 95% rename from src/error.rs rename to src/app/error.rs index 78d8f3e..933ffd2 100644 --- a/src/error.rs +++ b/src/app/error.rs @@ -2,7 +2,10 @@ use poise::{FrameworkError, say_reply}; use thiserror::Error; use tracing::error; -use crate::{data::BotData, types::PError, utils::format_duration}; +use crate::{ + app::{AppError, BotData}, + utils::format_duration, +}; #[derive(Error, Debug)] pub enum BotError { @@ -14,7 +17,7 @@ pub enum BotError { IsPrivateThread, } -pub async fn on_error(error: FrameworkError<'_, BotData, PError>) { +pub async fn on_error(error: FrameworkError<'_, BotData, AppError>) { match error { FrameworkError::Command { error, ctx, .. } => { let _ = say_reply(ctx, "コマンド実行中にエラーが発生しました。").await; diff --git a/src/main_event_handler.rs b/src/app/event_handler.rs similarity index 100% rename from src/main_event_handler.rs rename to src/app/event_handler.rs diff --git a/src/app/mod.rs b/src/app/mod.rs new file mode 100644 index 0000000..a113a82 --- /dev/null +++ b/src/app/mod.rs @@ -0,0 +1,12 @@ +#![allow(unused_imports)] + +pub mod config; +mod data; +mod error; +mod event_handler; +pub mod types; + +pub use data::{BotData, BotDataGetter}; +pub use error::{BotError, on_error}; +pub use event_handler::MainEventHandler; +pub use types::{AppCommand, AppContext, AppError}; diff --git a/src/app/types.rs b/src/app/types.rs new file mode 100644 index 0000000..35fd048 --- /dev/null +++ b/src/app/types.rs @@ -0,0 +1,5 @@ +use crate::app::BotData; + +pub type AppError = Box; +pub type AppContext<'a> = poise::Context<'a, BotData, AppError>; +pub type AppCommand = poise::Command; diff --git a/src/data.rs b/src/data.rs deleted file mode 100644 index eabfb3c..0000000 --- a/src/data.rs +++ /dev/null @@ -1,19 +0,0 @@ -use std::sync::Arc; - -use serenity::all::prelude::Context; - -use crate::config::Config; - -pub struct BotData { - pub config: Arc, -} - -pub trait BotDataGetter { - fn get_bot_data(&self) -> Arc; -} - -impl BotDataGetter for Context { - fn get_bot_data(&self) -> Arc { - self.data::() - } -} diff --git a/src/features/mod.rs b/src/features/mod.rs index 7d487e9..b8da69b 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -1,59 +1,59 @@ -mod admin; -mod auth; -mod auto_kick; -mod honeypot; -mod logging; -mod message_cache; -mod pin; -mod question; -mod thread_auto_invite; -mod thread_channel_startup; +// mod admin; +// mod auth; +// mod auto_kick; +// mod honeypot; +// mod logging; +// mod message_cache; +// mod pin; +// mod question; +// mod thread_auto_invite; +// mod thread_channel_startup; -pub use auth::Handler as AuthHandler; -pub use auto_kick::Handler as AutoKickHandler; -pub use honeypot::Handler as HoneypotHandler; -pub use logging::Handler as LoggingHandler; -pub use message_cache::Handler as MessageCacheHandler; -pub use question::Handler as QuestionHandler; -pub use thread_auto_invite::ThreadAutoInviteHandler; -pub use thread_channel_startup::Handler as ThreadChannelStartupHandler; +// pub use auth::Handler as AuthHandler; +// pub use auto_kick::Handler as AutoKickHandler; +// pub use honeypot::Handler as HoneypotHandler; +// pub use logging::Handler as LoggingHandler; +// pub use message_cache::Handler as MessageCacheHandler; +// pub use question::Handler as QuestionHandler; +// pub use thread_auto_invite::ThreadAutoInviteHandler; +// pub use thread_channel_startup::Handler as ThreadChannelStartupHandler; -pub use message_cache::{MessageCache, MessageCacheType}; +// pub use message_cache::{MessageCache, MessageCacheType}; -use crate::PCommand; +use std::borrow::Cow; -pub fn commands() -> Vec { +use crate::app::AppCommand; + +pub fn commands() -> Vec { build_commands(vec![ - auth::create_keyword_button, - question::question, - pin::pin, - admin::reload_config, - thread_auto_invite::invite_thread, - thread_auto_invite::add_invite_role, - thread_auto_invite::remove_invite_role, + // auth::create_keyword_button, + // question::question, + // pin::pin, + // admin::reload_config, + // thread_auto_invite::invite_thread, + // thread_auto_invite::add_invite_role, + // thread_auto_invite::remove_invite_role, ]) } -fn alias_command(base: fn() -> PCommand, name: String) -> PCommand { +fn alias_command(base: fn() -> AppCommand, name: Cow<'static, str>) -> AppCommand { let mut command = base(); command.name = name; - command.aliases.clear(); + command.aliases = (&[]).into(); command.context_menu_action = None; command.context_menu_name = None; command } -fn build_commands(commands: Vec PCommand>) -> Vec { +fn build_commands(commands: Vec AppCommand>) -> Vec { commands .into_iter() .flat_map(|cmd| { let base = cmd(); - let aliases = base - .aliases - .clone() - .into_iter() - .map(move |alias| alias_command(cmd, alias)); - std::iter::once(base).chain(aliases).collect::>() + let aliases = base.aliases.clone(); + std::iter::once(base) + .chain(aliases.iter().map(move |a| alias_command(cmd, a.clone()))) + .collect::>() }) .collect() } diff --git a/src/main.rs b/src/main.rs index e243077..aee9277 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,28 +1,20 @@ -mod config; +mod app; mod core; -mod data; -mod error; mod extensions; -mod main_event_handler; -mod types; -// mod features; +mod features; mod utils; -use std::{fs::read_to_string, sync::Arc}; +use std::sync::Arc; use bpaf::Bpaf; -use config::Config; -// use error::on_error; -// use features::{MessageCache, MessageCacheType, commands}; use poise::{Framework, FrameworkOptions}; use serenity::{cache::Settings as CacheSettings, prelude::*}; use tracing::error; use crate::{ + app::{BotData, MainEventHandler, config::AppConfig, on_error}, core::{BotEventHandlers, create_client}, - data::BotData, - error::on_error, - main_event_handler::MainEventHandler, + features::commands, }; #[derive(Clone, Debug, Bpaf)] @@ -36,8 +28,7 @@ struct Options { async fn main() { tracing_subscriber::fmt::init(); - let config = read_to_string("config.toml").expect("Failed to read config.toml"); - let config = toml::from_str::(&config).unwrap_or_else(|e| panic!("Failed to parse config.toml: {}", e)); + let config = AppConfig::from_file("config.toml").await; let options = options().run(); @@ -52,7 +43,7 @@ async fn main() { let framework = Framework::builder() .options(FrameworkOptions { - // commands: commands(), + commands: commands(), on_error: |error| Box::pin(on_error(error)), skip_checks_for_owners: false, owners: data.config.bot.owners.clone(), diff --git a/src/types.rs b/src/types.rs deleted file mode 100644 index 64f846f..0000000 --- a/src/types.rs +++ /dev/null @@ -1,5 +0,0 @@ -use crate::data::BotData; - -pub type PError = Box; -pub type PContext<'a> = poise::Context<'a, BotData, PError>; -pub type PCommand = poise::Command; diff --git a/src/utils.rs b/src/utils.rs index e7d5a7b..66bf69c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,4 @@ -use std::time::Duration; +use std::{sync::Arc, time::Duration}; use async_stream::stream; use futures::Stream; @@ -14,12 +14,7 @@ use serenity::{ use similar::{Algorithm, ChangeTag, TextDiff}; use tracing::error; -use crate::{ - config::get_config, - error::BotError, - // MessageCacheType, - types::{PContext, PError}, -}; +use crate::app::{AppContext, AppError, BotDataGetter, BotError}; // pub fn create_safe_message() -> CreateMessage { // CreateMessage::new().allowed_mentions(CreateAllowedMentions::new().all_users(false)) @@ -83,13 +78,13 @@ use crate::{ /* 認証済みロールを持っているかどうかを確認します。 */ -pub async fn has_authed_role(ctx: PContext<'_>) -> Result { +pub async fn has_authed_role(ctx: AppContext<'_>) -> Result { let Some(member) = ctx.author_member().await else { return Ok(false); }; - let config = &get_config(ctx.serenity_context()).await.auth; - if !member.roles.contains(&config.role_id) { + let config = ctx.get_app_config(); + if !member.roles.contains(&config.auth.role_id) { Err(BotError::HasNoRole.into()) } else { Ok(true) From 263eaa61b66064c0632f842e26eda2c46670e9a7 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 13 Jun 2026 06:01:37 +0900 Subject: [PATCH 081/104] =?UTF-8?q?refactor:=20AppConfig=20=E3=82=92?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=8F=AF=E8=83=BD=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/data.rs | 30 +++++++++++++++++++----------- src/main.rs | 9 ++++----- src/utils.rs | 2 +- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/app/data.rs b/src/app/data.rs index cf6e59c..5636075 100644 --- a/src/app/data.rs +++ b/src/app/data.rs @@ -1,26 +1,38 @@ use std::sync::Arc; use serenity::all::prelude::Context; +use tokio::sync::RwLock; use crate::app::{AppContext, config::AppConfig}; pub struct BotData { - pub config: Arc, + config: RwLock>, +} + +impl BotData { + pub fn new(config: &Arc) -> Self { + Self { + config: RwLock::new(Arc::clone(config)), + } + } } pub trait BotDataGetter { fn get_bot_data(&self) -> Arc; - fn get_app_config(&self) -> Arc; + async fn read_app_config(&self) -> Arc { + self.get_bot_data().config.read().await.clone() + } + + async fn replace_app_config(&self, config: AppConfig) { + let data = self.get_bot_data(); + *data.config.write().await = Arc::new(config); + } } impl BotDataGetter for Context { fn get_bot_data(&self) -> Arc { - self.data::() - } - - fn get_app_config(&self) -> Arc { - Arc::clone(&self.get_bot_data().config) + self.data() } } @@ -28,8 +40,4 @@ impl<'a> BotDataGetter for AppContext<'a> { fn get_bot_data(&self) -> Arc { self.data() } - - fn get_app_config(&self) -> Arc { - Arc::clone(&self.data().config) - } } diff --git a/src/main.rs b/src/main.rs index aee9277..84509ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,16 +37,15 @@ async fn main() { return; } - let data = Arc::new(BotData { - config: Arc::new(config), - }); + let config = Arc::new(config); + let data = Arc::new(BotData::new(&config)); let framework = Framework::builder() .options(FrameworkOptions { commands: commands(), on_error: |error| Box::pin(on_error(error)), skip_checks_for_owners: false, - owners: data.config.bot.owners.clone(), + owners: config.bot.owners.clone(), ..Default::default() }) .build(); @@ -60,7 +59,7 @@ async fn main() { settings.max_messages = 1_000_000; let mut client = create_client( - data.config.bot.token.clone(), + config.bot.token.clone(), intents, BotEventHandlers::new().add(MainEventHandler::new()), ) diff --git a/src/utils.rs b/src/utils.rs index 66bf69c..de63328 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -83,7 +83,7 @@ pub async fn has_authed_role(ctx: AppContext<'_>) -> Result { return Ok(false); }; - let config = ctx.get_app_config(); + let config = ctx.read_app_config().await; if !member.roles.contains(&config.auth.role_id) { Err(BotError::HasNoRole.into()) } else { From bd4cdf28e21d32520d56eba82459dcb6abe26a86 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 13 Jun 2026 06:21:31 +0900 Subject: [PATCH 082/104] =?UTF-8?q?refactor:=20reload=5Fconfig=20=E3=82=92?= =?UTF-8?q?=E5=86=8D=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/config.rs | 10 +++++----- src/app/data.rs | 4 ++-- src/features/admin.rs | 21 ++++----------------- src/features/mod.rs | 4 ++-- src/main.rs | 15 +++++++-------- 5 files changed, 20 insertions(+), 34 deletions(-) diff --git a/src/app/config.rs b/src/app/config.rs index 483f1b5..80405cd 100644 --- a/src/app/config.rs +++ b/src/app/config.rs @@ -11,6 +11,8 @@ use serde_with::{DisplayFromStr, serde_as}; use serenity::all::{ChannelId, ForumTagId, GuildId, RoleId, Token, UserId}; use tokio::fs::read_to_string; +use crate::app::AppError; + #[derive(Debug, Deserialize)] pub struct AppConfig { pub bot: BotConfig, @@ -26,11 +28,9 @@ pub struct AppConfig { } impl AppConfig { - pub async fn from_file(path: &str) -> Self { - let text = read_to_string(path) - .await - .unwrap_or_else(|e| panic!("Failed to read {path}: {e}")); - toml::from_str(&text).unwrap_or_else(|e| panic!("Failed to parse {path}: {e}")) + pub async fn from_file(path: &str) -> Result { + let text = read_to_string(path).await?; + Ok(toml::from_str(&text)?) } } diff --git a/src/app/data.rs b/src/app/data.rs index 5636075..04be184 100644 --- a/src/app/data.rs +++ b/src/app/data.rs @@ -10,9 +10,9 @@ pub struct BotData { } impl BotData { - pub fn new(config: &Arc) -> Self { + pub fn new(config: AppConfig) -> Self { Self { - config: RwLock::new(Arc::clone(config)), + config: RwLock::new(Arc::new(config)), } } } diff --git a/src/features/admin.rs b/src/features/admin.rs index 471311e..885a8bf 100644 --- a/src/features/admin.rs +++ b/src/features/admin.rs @@ -1,25 +1,12 @@ -use std::sync::Arc; - use poise::say_reply; -use tokio::fs::read_to_string; -use crate::{PContext, PError, config::Config}; +use crate::app::{AppContext, AppError, BotDataGetter, config::AppConfig}; /// コンフィグを再読み込み #[poise::command(slash_command, ephemeral, owners_only, dm_only)] -pub async fn reload_config(ctx: PContext<'_>) -> Result<(), PError> { - let Ok(config) = read_to_string("config.toml").await else { - return Err("Failed to read config.toml".into()); - }; - - let Ok(config) = toml::from_str::(&config) else { - return Err("Failed to parse config.toml".into()); - }; - - let mut data = ctx.serenity_context().data.write().await; - data.insert::(Arc::new(config)); - +pub async fn reload_config(ctx: AppContext<'_>) -> Result<(), AppError> { + let config = AppConfig::from_file("config.toml").await?; + ctx.replace_app_config(config).await; say_reply(ctx, "Config reloaded").await?; - Ok(()) } diff --git a/src/features/mod.rs b/src/features/mod.rs index b8da69b..668f386 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -1,4 +1,4 @@ -// mod admin; +mod admin; // mod auth; // mod auto_kick; // mod honeypot; @@ -29,7 +29,7 @@ pub fn commands() -> Vec { // auth::create_keyword_button, // question::question, // pin::pin, - // admin::reload_config, + admin::reload_config, // thread_auto_invite::invite_thread, // thread_auto_invite::add_invite_role, // thread_auto_invite::remove_invite_role, diff --git a/src/main.rs b/src/main.rs index 84509ca..bb62729 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,7 @@ use serenity::{cache::Settings as CacheSettings, prelude::*}; use tracing::error; use crate::{ - app::{BotData, MainEventHandler, config::AppConfig, on_error}, + app::{AppError, BotData, MainEventHandler, config::AppConfig, on_error}, core::{BotEventHandlers, create_client}, features::commands, }; @@ -25,21 +25,18 @@ struct Options { } #[tokio::main] -async fn main() { +async fn main() -> Result<(), AppError> { tracing_subscriber::fmt::init(); - let config = AppConfig::from_file("config.toml").await; + let config = AppConfig::from_file("config.toml").await?; let options = options().run(); if options.check_config { println!("Config is valid"); - return; + return Ok(()); } - let config = Arc::new(config); - let data = Arc::new(BotData::new(&config)); - let framework = Framework::builder() .options(FrameworkOptions { commands: commands(), @@ -73,7 +70,7 @@ async fn main() { // .event_handler(features::QuestionHandler) // .event_handler(features::MessageCacheHandler::new(config.message_cache.disabled)) .cache_settings(settings) - .data(Arc::new(data)) + .data(Arc::new(BotData::new(config))) // .type_map_insert::(Arc::new(MessageCache::new())) .await .expect("Err creating client"); @@ -89,4 +86,6 @@ async fn main() { if let Err(why) = client.start().await { error!("Client error: {:?}", why); } + + Ok(()) } From 7caa62ce56231c0ac9faed9b2c09994632bbf725 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 13 Jun 2026 06:23:50 +0900 Subject: [PATCH 083/104] =?UTF-8?q?chore:=20rust=20toolchain=20=E3=82=92?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 4933b3b..4fedb95 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.95" +channel = "1.96" From 7a0ec0b66d07fc646c17a6c92a805865accfeb5f Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 13 Jun 2026 06:36:17 +0900 Subject: [PATCH 084/104] =?UTF-8?q?chore:=20sysinfo=20=E3=82=92=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 2 +- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9444504..4e7f748 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -474,6 +474,16 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags", + "objc2", +] + [[package]] name = "displaydoc" version = "0.2.6" @@ -1251,6 +1261,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + [[package]] name = "objc2-core-foundation" version = "0.3.2" @@ -1258,6 +1277,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags", + "objc2", ] [[package]] @@ -1270,6 +1307,17 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "objc2-open-directory" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb82bed227edf5201dfedf072bba4015a33d3d4a98519837295a90f0a23f676d" +dependencies = [ + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -2150,15 +2198,16 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.38.4" +version = "0.39.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f" +checksum = "21d0d938c10fcda3e897e28aaddf4ab462375d411f4378cd63b1c945f69aba96" dependencies = [ "libc", "memchr", "ntapi", "objc2-core-foundation", "objc2-io-kit", + "objc2-open-directory", "windows", ] diff --git a/Cargo.toml b/Cargo.toml index 809a476..9f3345f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ regex = "1.0" serde = { version = "1.0", features = [ "derive" ] } serde_with = "3" similar = "3.1" -sysinfo = "0.38" +sysinfo = "0.39" thiserror = "2" # 最新の Serenity と time 0.3.48 に互換性がない time = "=0.3.47" From 2929f6605cd0aaed7c69a69dbd3593d5f36e8136 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 13 Jun 2026 07:25:51 +0900 Subject: [PATCH 085/104] =?UTF-8?q?refactor:=20=E3=83=8F=E3=83=8B=E3=83=BC?= =?UTF-8?q?=E3=83=9D=E3=83=83=E3=83=88=E3=82=92=E5=86=8D=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- macros/src/lib.rs | 4 +- src/features/honeypot.rs | 166 ++++++++++++++++++++------------------- src/features/mod.rs | 4 +- src/main.rs | 6 +- src/utils.rs | 40 ++++------ 5 files changed, 112 insertions(+), 108 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 46b1c94..2c7660b 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -3,7 +3,7 @@ use quote::quote; use syn::{FnArg, ItemFn, ReturnType, parse_macro_input}; #[proc_macro_attribute] -pub fn simple_event_handler(_attr: TokenStream, item: TokenStream) -> TokenStream { +pub fn event_handler(_attr: TokenStream, item: TokenStream) -> TokenStream { let function = parse_macro_input!(item as ItemFn); if function.sig.asyncness.is_none() { @@ -60,7 +60,7 @@ pub fn simple_event_handler(_attr: TokenStream, item: TokenStream) -> TokenStrea #visibility struct #handler_name; #[::serenity::async_trait] - impl crate::BotEventHandler for #handler_name { + impl crate::core::BotEventHandler for #handler_name { async fn dispatch(&self, #inputs) #body } } diff --git a/src/features/honeypot.rs b/src/features/honeypot.rs index 7edfcdb..e9e84d3 100644 --- a/src/features/honeypot.rs +++ b/src/features/honeypot.rs @@ -2,20 +2,21 @@ use std::collections::HashMap; use chrono::Duration; use serenity::{ - all::prelude::{Context, EventHandler}, - async_trait, + all::prelude::Context, builder::CreateEmbed, model::{ Timestamp, channel::Message, + event::FullEvent, id::{ChannelId, GuildId, MessageId, UserId}, }, utils::MessageBuilder, }; use tracing::error; +use valine_bot_macros::event_handler; use crate::{ - config::get_config, + app::BotDataGetter, utils::{create_message, create_safe_message, send_message}, }; @@ -58,124 +59,130 @@ impl From<&Message> for MessageFingerprint { fn from(message: &Message) -> Self { Self { author_id: message.author.id, - content: message.content.clone(), - attachments: message.attachments.iter().map(|a| a.filename.clone()).collect(), + content: message.content.clone().into_string(), + attachments: message + .attachments + .iter() + .map(|a| a.filename.clone().into_string()) + .collect(), } } } -pub struct Handler; - -impl Handler { - /** - 指定されたメッセージと同一の内容を持ち、指定された期間内に送信されたメッセージのID一覧を、 Serenity のキャッシュ内から収集する - - スパム対策の性質上 Bot起動以前のメッセージが必要になる可能性が低いため、Serenityのキャッシュからのみ収集する実装とした - - また、同様にスレッドにメッセージが送信されないというスパムの傾向を踏まえ、スレッド内のメッセージは収集対象から除外する - */ - fn collect_message_ids( - ctx: &Context, - guild_id: GuildId, - target_message: impl Into, - message_lookback: Duration, - ) -> HashMap> { - let Some(guild) = guild_id.to_guild_cached(&ctx) else { - error!("guild {} not found in cache", guild_id); - return HashMap::new(); - }; - - let mut ids = HashMap::new(); - let cutoff = Timestamp::now().unix_timestamp() - message_lookback.num_seconds(); - let target_message = target_message.into(); - - for channel_id in guild.channels.keys() { - if let Some(messages) = ctx.cache.channel_messages(channel_id) { - let message_ids = messages - .iter() - .filter(|(_, message)| { - message.timestamp.unix_timestamp() >= cutoff && target_message.matches_message(message) - }) - .map(|(message_id, _)| *message_id) - .collect::>(); - - if !message_ids.is_empty() { - ids.insert(*channel_id, message_ids); - } +/** +指定されたメッセージと同一の内容を持ち、指定された期間内に送信されたメッセージのID一覧を、 Serenity のキャッシュ内から収集する + +スパム対策の性質上 Bot起動以前のメッセージが必要になる可能性が低いため、Serenityのキャッシュからのみ収集する実装とした + +また、同様にスレッドにメッセージが送信されないというスパムの傾向を踏まえ、スレッド内のメッセージは収集対象から除外する +*/ +fn collect_message_ids( + ctx: &Context, + guild_id: GuildId, + target_message: impl Into, + message_lookback: Duration, +) -> HashMap> { + let Some(guild) = guild_id.to_guild_cached(&ctx.cache) else { + error!("guild {} not found in cache", guild_id); + return HashMap::new(); + }; + + let mut ids = HashMap::new(); + let cutoff = Timestamp::now().unix_timestamp() - message_lookback.num_seconds(); + let target_message = target_message.into(); + + for channel in &guild.channels { + let id = channel.id; + if let Some(messages) = ctx.cache.channel_messages(id.into()) { + let message_ids = messages + .iter() + .filter(|m| m.timestamp.unix_timestamp() >= cutoff && target_message.matches_message(m)) + .map(|m| m.id) + .collect::>(); + + if !message_ids.is_empty() { + ids.insert(id, message_ids); } } - - ids } - async fn delete_messages(ctx: &Context, messages: &HashMap>) { - for (channel_id, message_ids) in messages { - if message_ids.len() > 2 { - for chunk in message_ids.chunks(100) { - if let Err(e) = channel_id.delete_messages(&ctx.http, chunk).await { - error!("Failed to delete messages in channel {}: {:?}", channel_id, e); - } + ids +} + +async fn delete_messages(ctx: &Context, messages: &HashMap>) { + static DELETE_REASON: Option<&str> = Some("ハニーポットに送信されたメッセージと同一のため"); + + for (channel_id, message_ids) in messages { + let channel_id = channel_id.widen(); + if message_ids.len() > 2 { + for chunk in message_ids.chunks(100) { + if let Err(e) = channel_id.delete_messages(&ctx.http, chunk, DELETE_REASON).await { + error!("Failed to delete messages in channel {}: {:?}", channel_id, e); } - continue; } + continue; + } - if let Some(id) = message_ids.first() - && let Err(e) = channel_id.delete_message(&ctx.http, *id).await - { - error!("Failed to delete message {} in channel {}: {:?}", id, channel_id, e); - } + if let Some(id) = message_ids.first() + && let Err(e) = channel_id.delete_message(&ctx.http, *id, DELETE_REASON).await + { + error!("Failed to delete message {} in channel {}: {:?}", id, channel_id, e); } } } -#[async_trait] -impl EventHandler for Handler { - async fn message(&self, ctx: Context, msg: Message) { - let author = &msg.author; +#[event_handler] +pub async fn handle_honeypot_event(ctx: &Context, event: &FullEvent) { + if let FullEvent::Message { new_message, .. } = event { + let author = &new_message.author; - if author.bot { + if author.bot() { return; } - let config = &get_config(&ctx).await; + let config = ctx.read_app_config().await; - if config.honeypot.channel_id != msg.channel_id { + if config.honeypot.channel_id != new_message.channel_id.expect_channel() { return; } let dm_message = author + .id .direct_message(&ctx, create_message(&config.honeypot.kick_message)) .await; - let Ok(member) = msg.member(&ctx).await else { + let Ok(member) = new_message.member(&ctx).await else { error!("user {} not found", author.id); return; }; let _ = member - .kick_with_reason(&ctx, "ハニーポットにメッセージを送信したため") + .kick(&ctx.http, Some("ハニーポットにメッセージを送信したため")) .await; - let delete_message_ids = - Self::collect_message_ids(&ctx, msg.guild_id.unwrap(), &msg, config.honeypot.message_lookback); - Self::delete_messages(&ctx, &delete_message_ids).await; + let delete_message_ids = collect_message_ids( + ctx, + new_message.guild_id.unwrap(), + new_message, + config.honeypot.message_lookback, + ); + delete_messages(ctx, &delete_message_ids).await; - let mut log_builder = MessageBuilder::new(); - log_builder + let mut log_builder = MessageBuilder::new() .push_bold("ユーザー: ") .push_safe(member.display_name()) .push(" ") - .push_mono_line(author.id.to_string()) - .push_line(dm_message.map_or("DMの送信に失敗しました。", |_| "")); + .push_mono_line(&*author.id.to_string()) + .push_line(dm_message.map_or("DMの送信に失敗しました。", |_| "")) + .push_bold_line("削除したメッセージID:"); - log_builder.push_bold_line("削除したメッセージID:"); for (channel_id, message_ids) in &delete_message_ids { for message_id in message_ids { - log_builder + log_builder = log_builder .push("- ") - .push(message_id.link(*channel_id, msg.guild_id)) + .push(&*message_id.link(channel_id.widen(), new_message.guild_id).to_string()) .push(" ") - .push_mono_line(message_id.to_string()); + .push_mono_line(&*message_id.to_string()); } } @@ -187,10 +194,11 @@ impl EventHandler for Handler { author .avatar_url() .unwrap_or("https://cdn.discordapp.com/embed/avatars/0.png".to_string()), + Some("ユーザーアイコン".into()), ); let _ = send_message( - &ctx, + ctx, &config.honeypot.log_channel_id, create_safe_message().add_embed(embed), ) diff --git a/src/features/mod.rs b/src/features/mod.rs index 668f386..79c9cf5 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -1,7 +1,7 @@ mod admin; // mod auth; // mod auto_kick; -// mod honeypot; +mod honeypot; // mod logging; // mod message_cache; // mod pin; @@ -11,7 +11,7 @@ mod admin; // pub use auth::Handler as AuthHandler; // pub use auto_kick::Handler as AutoKickHandler; -// pub use honeypot::Handler as HoneypotHandler; +pub use honeypot::handle_honeypot_event; // pub use logging::Handler as LoggingHandler; // pub use message_cache::Handler as MessageCacheHandler; // pub use question::Handler as QuestionHandler; diff --git a/src/main.rs b/src/main.rs index bb62729..ba4d0a4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ use tracing::error; use crate::{ app::{AppError, BotData, MainEventHandler, config::AppConfig, on_error}, core::{BotEventHandlers, create_client}, - features::commands, + features::{commands, handle_honeypot_event}, }; #[derive(Clone, Debug, Bpaf)] @@ -58,7 +58,9 @@ async fn main() -> Result<(), AppError> { let mut client = create_client( config.bot.token.clone(), intents, - BotEventHandlers::new().add(MainEventHandler::new()), + BotEventHandlers::new() + .add(MainEventHandler::new()) + .add(handle_honeypot_event), ) .framework(Box::new(framework)) // .event_handler(features::AuthHandler::new()) diff --git a/src/utils.rs b/src/utils.rs index de63328..684dce0 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,28 +1,22 @@ -use std::{sync::Arc, time::Duration}; +use std::{borrow::Cow, time::Duration}; -use async_stream::stream; -use futures::Stream; use itertools::Itertools; use serenity::{ Result, - all::{ - ChannelId, ChannelType, Context, CreateActionRow, CreateAllowedMentions, CreateInteractionResponse, - CreateInteractionResponseMessage, CreateMessage, GuildChannel, GuildId, Http, LightMethod, Member, Message, - MessageId, Request, Route, ThreadsData, Timestamp, - }, + all::{ChannelId, Context, CreateAllowedMentions, CreateMessage, Message}, }; use similar::{Algorithm, ChangeTag, TextDiff}; use tracing::error; use crate::app::{AppContext, AppError, BotDataGetter, BotError}; -// pub fn create_safe_message() -> CreateMessage { -// CreateMessage::new().allowed_mentions(CreateAllowedMentions::new().all_users(false)) -// } +pub fn create_safe_message<'a>() -> CreateMessage<'a> { + CreateMessage::new().allowed_mentions(CreateAllowedMentions::new().all_users(false)) +} -// pub fn create_message(content: impl Into) -> CreateMessage { -// create_safe_message().content(content) -// } +pub fn create_message<'a>(content: impl Into>) -> CreateMessage<'a> { + create_safe_message().content(content) +} // pub fn create_interaction_message( // content: impl Into, @@ -123,15 +117,15 @@ pub fn format_duration(duration: Duration, mut count: usize) -> String { parts.join(" ") } -// pub async fn send_message(ctx: &Context, channel_id: &ChannelId, builder: CreateMessage) -> Result { -// match channel_id.send_message(&ctx.http, builder).await { -// Ok(m) => Ok(m), -// Err(why) => { -// error!("Error sending message: {:?}", why); -// Err(why) -// } -// } -// } +pub async fn send_message<'a>(ctx: &Context, channel_id: &ChannelId, builder: CreateMessage<'a>) -> Result { + match channel_id.widen().send_message(&ctx.http, builder).await { + Ok(m) => Ok(m), + Err(why) => { + error!("Error sending message: {:?}", why); + Err(why) + } + } +} // pub async fn get_cached_message(ctx: &Context, channel_id: ChannelId, message_id: MessageId) -> Option { // if let Some(m) = ctx.cache.message(channel_id, message_id) { From f1e99c4729cfb39dca4f643be0fa4df3c8d36ae1 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 13 Jun 2026 19:57:46 +0900 Subject: [PATCH 086/104] =?UTF-8?q?refactor:=20=E8=87=AA=E5=8B=95=20Kick?= =?UTF-8?q?=20=E3=82=92=E5=86=8D=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/auto_kick.rs | 93 ++++++++++++++++++++++++++++----------- src/features/mod.rs | 4 +- src/main.rs | 3 +- 3 files changed, 72 insertions(+), 28 deletions(-) diff --git a/src/features/auto_kick.rs b/src/features/auto_kick.rs index ce5342b..c461af6 100644 --- a/src/features/auto_kick.rs +++ b/src/features/auto_kick.rs @@ -3,47 +3,63 @@ use std::{ time::Duration, }; -use futures::StreamExt; +use futures::{StreamExt, stream}; use serenity::{ - all::{Context, EventHandler, GuildId, MessageBuilder}, + all::{Context, MessageBuilder, prelude::Mentionable}, async_trait, + builder::CreateEmbed, + model::{Color, event::FullEvent}, }; use tokio::pin; use tracing::error; use crate::{ - config::get_config, - utils::{create_message, send_message}, + app::BotDataGetter, + core::BotEventHandler, + utils::{create_message, create_safe_message, send_message}, }; -pub struct Handler { +pub struct AutoKickEventHandler { task_started: AtomicBool, } -impl Handler { +impl AutoKickEventHandler { pub fn new() -> Self { Self { task_started: AtomicBool::new(false), } } -} -#[async_trait] -impl EventHandler for Handler { - async fn cache_ready(&self, ctx: Context, _: Vec) { + async fn handle_cache_ready(&self, ctx: &Context) { if self.task_started.swap(true, Ordering::Relaxed) { return; } + let ctx = ctx.clone(); tokio::spawn(async move { - let config = get_config(&ctx).await; + let config = ctx.read_app_config().await; loop { - let members = config.auto_kick.guild_id.members_iter(&ctx); + let cached_members = ctx + .cache + .guild(config.auto_kick.guild_id) + // メンバーキャッシュが構築されていない場合は API から取ってくるため + .filter(|guild| guild.members.len() as u32 >= guild.member_count.get()) + .map(|guild| guild.members.clone()); + let members = if let Some(members) = cached_members { + stream::iter(members).left_stream() + } else { + config + .auto_kick + .guild_id + .members_iter(&ctx.http) + .filter_map(|r| async { r.ok() }) + .right_stream() + }; pin!(members); - while let Some(Ok(member)) = members.next().await { - if member.user.bot { + while let Some(member) = members.next().await { + if member.user.bot() { continue; } @@ -66,21 +82,39 @@ impl EventHandler for Handler { .direct_message(&ctx, create_message(&config.auto_kick.kick_message)) .await; - if let Err(e) = member.kick(&ctx).await { + if let Err(e) = member + .kick(&ctx.http, Some("一定期間のうちに認証ロールが付与されていないため")) + .await + { error!("Failed to kick user: {:?}", e); continue; }; - let log = create_message( - MessageBuilder::new() - .push_safe(member.display_name()) - .push(" (") - .push_mono(member.user.id.to_string()) - .push(") をキックしました。") - .push(dm_message.map_or("DMの送信に失敗しました。", |_| "")) - .build(), - ); - let _ = send_message(&ctx, &config.auth.log_channel_id, log).await; + let log = MessageBuilder::new() + .push_bold("ユーザー: ") + .push_safe(member.display_name()) + .push(" ") + .push_mono_line(&*member.user.id.to_string()) + .push_bold("メンション: ") + .push_line_safe(&*member.mention().to_string()) + .push_bold("リンク") + .push_line_safe(&*format!("", member.user.id)) + .push_line(dm_message.map_or("DMの送信に失敗しました。", |_| "")) + .build(); + + let embed = CreateEmbed::new() + .title("自動 Kick") + .description(log) + .color(Color::ORANGE) + .thumbnail( + member + .user + .avatar_url() + .unwrap_or("https://cdn.discordapp.com/embed/avatars/0.png".to_string()), + Some("ユーザーアイコン".into()), + ); + + let _ = send_message(&ctx, &config.auth.log_channel_id, create_safe_message().embed(embed)).await; } tokio::time::sleep(Duration::from_secs(3600)).await; @@ -88,3 +122,12 @@ impl EventHandler for Handler { }); } } + +#[async_trait] +impl BotEventHandler for AutoKickEventHandler { + async fn dispatch(&self, ctx: &Context, event: &FullEvent) { + if let FullEvent::CacheReady { guilds, .. } = event { + self.handle_cache_ready(ctx).await; + } + } +} diff --git a/src/features/mod.rs b/src/features/mod.rs index 79c9cf5..a87d8f8 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -1,6 +1,6 @@ mod admin; // mod auth; -// mod auto_kick; +mod auto_kick; mod honeypot; // mod logging; // mod message_cache; @@ -10,7 +10,7 @@ mod honeypot; // mod thread_channel_startup; // pub use auth::Handler as AuthHandler; -// pub use auto_kick::Handler as AutoKickHandler; +pub use auto_kick::AutoKickEventHandler; pub use honeypot::handle_honeypot_event; // pub use logging::Handler as LoggingHandler; // pub use message_cache::Handler as MessageCacheHandler; diff --git a/src/main.rs b/src/main.rs index ba4d0a4..2c11fbb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ use tracing::error; use crate::{ app::{AppError, BotData, MainEventHandler, config::AppConfig, on_error}, core::{BotEventHandlers, create_client}, - features::{commands, handle_honeypot_event}, + features::{AutoKickEventHandler, commands, handle_honeypot_event}, }; #[derive(Clone, Debug, Bpaf)] @@ -60,6 +60,7 @@ async fn main() -> Result<(), AppError> { intents, BotEventHandlers::new() .add(MainEventHandler::new()) + .add(AutoKickEventHandler::new()) .add(handle_honeypot_event), ) .framework(Box::new(framework)) From 9a1d4de8edc40aa52816aecfe2825f8e0d89c76e Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 13 Jun 2026 20:03:54 +0900 Subject: [PATCH 087/104] =?UTF-8?q?feat!:=20=E3=82=B9=E3=83=AC=E3=83=83?= =?UTF-8?q?=E3=83=89=E4=BD=9C=E6=88=90=E6=99=82=E3=81=AE=E3=83=A1=E3=83=83?= =?UTF-8?q?=E3=82=BB=E3=83=BC=E3=82=B8=E8=87=AA=E5=8B=95=E9=80=81=E4=BF=A1?= =?UTF-8?q?=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.sample.toml | 12 --------- src/app/config.rs | 12 --------- src/features/mod.rs | 2 -- src/features/thread_channel_startup.rs | 35 -------------------------- src/main.rs | 1 - 5 files changed, 62 deletions(-) delete mode 100644 src/features/thread_channel_startup.rs diff --git a/config.sample.toml b/config.sample.toml index 7cbe28c..bd022d5 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -61,18 +61,6 @@ channels = [ ] -[thread_channel_startup] -# 空にする方法 -# threads = [] - -[[thread_channel_startup.threads]] -# 初期メッセージを投稿するスレッドチャンネルのID -channel_id = "000000000000000000" -# 初期メッセージの内容 -startup_message = """スレッドの始まりだぁ! -まずはこのテンプレートを読んでね!""" - - [thread_auto_invite] # ロールの追加削除のトリガーとなるロールID display_role_id = "000000000000000000" diff --git a/src/app/config.rs b/src/app/config.rs index 80405cd..50b25d7 100644 --- a/src/app/config.rs +++ b/src/app/config.rs @@ -22,7 +22,6 @@ pub struct AppConfig { pub message_logging: MessageLoggingConfig, pub message_cache: MessageCacheConfig, pub pin: PinConfig, - pub thread_channel_startup: ThreadChannelStartupConfig, pub thread_auto_invite: ThreadAutoInviteConfig, pub question: QuestionConfig, } @@ -102,17 +101,6 @@ where .collect()) } -#[derive(Debug, Deserialize)] -pub struct ThreadChannelStartupConfig { - pub threads: Vec, -} - -#[derive(Debug, Deserialize)] -pub struct ThreadStartupConfig { - pub channel_id: ChannelId, - pub startup_message: String, -} - #[derive(Debug, Deserialize)] pub struct ThreadAutoInviteConfig { pub display_role_id: RoleId, diff --git a/src/features/mod.rs b/src/features/mod.rs index a87d8f8..2d365e9 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -7,7 +7,6 @@ mod honeypot; // mod pin; // mod question; // mod thread_auto_invite; -// mod thread_channel_startup; // pub use auth::Handler as AuthHandler; pub use auto_kick::AutoKickEventHandler; @@ -16,7 +15,6 @@ pub use honeypot::handle_honeypot_event; // pub use message_cache::Handler as MessageCacheHandler; // pub use question::Handler as QuestionHandler; // pub use thread_auto_invite::ThreadAutoInviteHandler; -// pub use thread_channel_startup::Handler as ThreadChannelStartupHandler; // pub use message_cache::{MessageCache, MessageCacheType}; diff --git a/src/features/thread_channel_startup.rs b/src/features/thread_channel_startup.rs deleted file mode 100644 index 508136a..0000000 --- a/src/features/thread_channel_startup.rs +++ /dev/null @@ -1,35 +0,0 @@ -use serenity::{ - all::{Context, EventHandler, GuildChannel}, - async_trait, -}; -use tracing::error; - -use crate::{ - config::get_config, - utils::{await_initial_message, create_message, send_message}, -}; - -pub struct Handler; - -#[async_trait] -impl EventHandler for Handler { - async fn thread_create(&self, ctx: Context, thread: GuildChannel) { - if await_initial_message(&ctx, &thread).await { - return; - } - - let config = &get_config(&ctx).await.thread_channel_startup; - let Some(parent_id) = thread.parent_id else { - return error!("Failed to get parent id: {:?}", thread); - }; - - for thread_config in &config.threads { - if parent_id != thread_config.channel_id { - continue; - } - - let log = create_message(&thread_config.startup_message); - let _ = send_message(&ctx, &thread.id, log).await; - } - } -} diff --git a/src/main.rs b/src/main.rs index 2c11fbb..99a63fc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -69,7 +69,6 @@ async fn main() -> Result<(), AppError> { // .event_handler(features::HoneypotHandler) // .event_handler(features::LoggingHandler) // .event_handler(features::ThreadAutoInviteHandler::new()) - // .event_handler(features::ThreadChannelStartupHandler) // .event_handler(features::QuestionHandler) // .event_handler(features::MessageCacheHandler::new(config.message_cache.disabled)) .cache_settings(settings) From 2fc3509eb8081043e35c3c32367510e72860156d Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sat, 13 Jun 2026 20:28:23 +0900 Subject: [PATCH 088/104] =?UTF-8?q?refactor:=20=E3=83=94=E3=83=B3=E7=95=99?= =?UTF-8?q?=E3=82=81=E3=82=B3=E3=83=9E=E3=83=B3=E3=83=89=E3=82=92=E5=86=8D?= =?UTF-8?q?=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/auto_kick.rs | 2 +- src/features/mod.rs | 4 +-- src/features/pin.rs | 52 +++++++++++++++++++++++---------------- 3 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/features/auto_kick.rs b/src/features/auto_kick.rs index c461af6..a081316 100644 --- a/src/features/auto_kick.rs +++ b/src/features/auto_kick.rs @@ -126,7 +126,7 @@ impl AutoKickEventHandler { #[async_trait] impl BotEventHandler for AutoKickEventHandler { async fn dispatch(&self, ctx: &Context, event: &FullEvent) { - if let FullEvent::CacheReady { guilds, .. } = event { + if let FullEvent::CacheReady { .. } = event { self.handle_cache_ready(ctx).await; } } diff --git a/src/features/mod.rs b/src/features/mod.rs index 2d365e9..cb3d182 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -4,7 +4,7 @@ mod auto_kick; mod honeypot; // mod logging; // mod message_cache; -// mod pin; +mod pin; // mod question; // mod thread_auto_invite; @@ -26,7 +26,7 @@ pub fn commands() -> Vec { build_commands(vec![ // auth::create_keyword_button, // question::question, - // pin::pin, + pin::pin, admin::reload_config, // thread_auto_invite::invite_thread, // thread_auto_invite::add_invite_role, diff --git a/src/features/pin.rs b/src/features/pin.rs index 47b3b5d..dfa539b 100644 --- a/src/features/pin.rs +++ b/src/features/pin.rs @@ -2,28 +2,37 @@ use std::time::Duration; use futures::StreamExt; use poise::say_reply; -use serenity::all::{GuildChannel, Message, MessageType}; +use serenity::{ + all::{Message, MessageType}, + collector::CollectMessages, + model::{channel::Channel, id::MessageId}, +}; -use crate::config::Config; -use crate::utils::has_authed_role; -use crate::{PContext, PError, config::get_config}; +use crate::{ + app::{AppContext, AppError, BotDataGetter, config::AppConfig}, + utils::has_authed_role, +}; -async fn check_owner(ctx: PContext<'_>, config: &Config, channel: &GuildChannel) -> bool { +async fn check_owner(ctx: AppContext<'_>, config: &AppConfig, channel: &Channel) -> bool { let author_id = ctx.author().id; - if channel.owner_id == Some(author_id) { + // コンフィグで設定されたオーナーかどうか + if config.pin.channels.get(&channel.id().expect_channel()) == Some(&author_id) { return true; } - // コンフィグで設定されたオーナーかどうか - if config.pin.channels.get(&channel.id) == Some(&author_id) { + let Channel::GuildThread(channel) = channel else { + return false; + }; + + if channel.owner_id == author_id { return true; } // 質問フォーラムの場合、初期メッセージのメンションからスレッド主を取得 - // スレッドの初期メッセージのIDはスレッドのIDと同じ - if channel.parent_id == Some(config.question.forum_id) { - let Ok(msg) = channel.message(ctx, channel.id.get()).await else { + if channel.parent_id == config.question.forum_id { + // スレッドの初期メッセージのIDはスレッドのIDと同じ + let Ok(msg) = channel.id.widen().message(ctx, MessageId::new(channel.id.get())).await else { // メッセージが取得できない場合はスレッドオーナーではない判定 return false; }; @@ -47,36 +56,37 @@ async fn check_owner(ctx: PContext<'_>, config: &Config, channel: &GuildChannel) check = "has_authed_role" )] pub async fn pin( - ctx: PContext<'_>, + ctx: AppContext<'_>, #[description = "ピン留めするメッセージ (リンクかID)"] msg: Message, -) -> Result<(), PError> { - let config = get_config(ctx.serenity_context()).await; - let channel = ctx.guild_channel().await.unwrap(); +) -> Result<(), AppError> { + let config = ctx.read_app_config().await; + let channel = ctx.channel().await.unwrap(); if !check_owner(ctx, &config, &channel).await { say_reply(ctx, "あなたはこのチャンネルでピン留めできません。").await?; return Ok(()); } - // ストリームを取得することでイベントの受信を開始させる let mut stream = channel - .await_reply(&ctx.serenity_context().shard) + .id() + .collect_messages(&ctx.serenity_context()) .timeout(Duration::from_secs(5)) .channel_id(msg.channel_id) .author_id(ctx.serenity_context().cache.current_user().id) .filter(|r| r.kind == MessageType::PinsAdd) .stream(); - if msg.pinned { - msg.unpin(ctx).await?; + static PIN_REASON: Option<&str> = Some("/pin コマンドによる操作"); + if msg.pinned() { + msg.unpin(&ctx.http(), PIN_REASON).await?; say_reply(ctx, "ピン留めを解除しました。").await?; } else { - msg.pin(ctx).await?; + msg.pin(&ctx.http(), PIN_REASON).await?; say_reply(ctx, "ピン留めしました。").await?; } if let Some(msg) = stream.next().await { - let _ = msg.delete(ctx.http()).await; + let _ = msg.delete(&ctx.http(), None).await; } Ok(()) From 457634cd87e3b4ab121c522f2245db9ca173664d Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sun, 14 Jun 2026 04:04:47 +0900 Subject: [PATCH 089/104] =?UTF-8?q?refactor:=20=E5=90=88=E8=A8=80=E8=91=89?= =?UTF-8?q?=E3=81=AE=E5=85=A5=E5=8A=9B=E6=A9=9F=E8=83=BD=E3=82=92=E5=86=8D?= =?UTF-8?q?=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/auth.rs | 134 +++++++++++++++++++++++-------------------- src/features/mod.rs | 18 ++++-- src/main.rs | 11 ++-- src/utils.rs | 60 ++++++++++++------- 4 files changed, 129 insertions(+), 94 deletions(-) diff --git a/src/features/auth.rs b/src/features/auth.rs index 33d4305..1b572a7 100644 --- a/src/features/auth.rs +++ b/src/features/auth.rs @@ -1,36 +1,48 @@ +use std::str::FromStr; use std::sync::Arc; use std::time::{Duration, Instant}; use dashmap::DashMap; use poise::say_reply; use rand::seq::IndexedRandom; +use serenity::all::prelude::CacheHttp; use serenity::all::{ - ActionRowComponent, ButtonStyle, ComponentInteractionDataKind, Context, CreateActionRow, CreateButton, - CreateInputText, CreateInteractionResponse, CreateInteractionResponseFollowup, CreateModal, EmbedMessageBuilding, - EventHandler, InputTextStyle, Interaction, Mentionable, MessageBuilder, ModalInteractionCollector, Ready, UserId, + ComponentInteractionDataKind, Context, CreateActionRow, CreateButton, CreateInputText, + CreateInteractionResponseFollowup, EmbedMessageBuilding, InputTextStyle, Interaction, Mentionable, MessageBuilder, + ModalInteractionCollector, UserId, }; use serenity::async_trait; +use serenity::builder::{CreateComponent, CreateLabel, CreateModalComponent}; +use serenity::model::application::{ButtonStyle, LabelComponent, ModalComponent}; +use serenity::model::event::FullEvent; +use serenity::small_fixed_array::FixedString; use tracing::error; -use crate::config::get_config; -use crate::utils::{create_interaction_message, create_message, send_message}; -use crate::{PContext, PError}; +use crate::app::{AppContext, AppError, BotDataGetter}; +use crate::core::BotEventHandler; +use crate::utils::{create_ephemeral_message, create_interaction_message, create_message, create_model, send_message}; static KEYWORD_INPUT_BUTTON: &str = "keyword_input:button"; static AUTH_COOLDOWN: Duration = Duration::from_secs(60); -pub struct Handler { +pub struct AuthEventHandler { cooldown: Arc>, } -impl Handler { +impl AuthEventHandler { pub fn new() -> Self { Self { cooldown: Arc::new(DashMap::new()), } } + fn cleanup_cooldown(&self) { + self.cooldown.retain(|_, instant| instant.elapsed() < AUTH_COOLDOWN); + } + fn remaining_cooldown(&self, user_id: UserId) -> Option { + self.cleanup_cooldown(); + if let Some(instant) = self.cooldown.get(&user_id) { let remaining = AUTH_COOLDOWN.checked_sub(instant.elapsed())?.as_secs(); if remaining > 0 { @@ -43,11 +55,8 @@ impl Handler { fn start_cooldown(&self, user_id: UserId) { self.cooldown.insert(user_id, Instant::now()); } -} -#[async_trait] -impl EventHandler for Handler { - async fn interaction_create(&self, ctx: Context, interaction: Interaction) { + async fn handle_interaction_create(&self, ctx: &Context, interaction: &Interaction) { let Interaction::Component(interaction) = interaction else { return; }; @@ -58,12 +67,12 @@ impl EventHandler for Handler { return; } - let config = &get_config(&ctx).await.auth; + let config = &ctx.read_app_config().await.auth; let member = interaction.member.as_ref().unwrap(); if member.roles.contains(&config.role_id) { let _ = interaction - .create_response(&ctx, create_interaction_message("既に認証済みです。", true, None)) + .create_response(ctx.http(), create_ephemeral_message("既に認証済みです。", None)) .await; return; } @@ -71,10 +80,9 @@ impl EventHandler for Handler { if let Some(remaining) = self.remaining_cooldown(interaction.user.id) { let _ = interaction .create_response( - &ctx, - create_interaction_message( - format!("クールダウン中です。\n{}秒後に再度お試しください。", remaining), - true, + ctx.http(), + create_ephemeral_message( + format!("クールダウン中です。\n{remaining}秒後に再度お試しください。"), None, ), ) @@ -82,7 +90,7 @@ impl EventHandler for Handler { return; } - let mut input_text = CreateInputText::new(InputTextStyle::Short, "合言葉", "keyword") + let mut input_text = CreateInputText::new(InputTextStyle::Short, "keyword") .required(true) .placeholder("合言葉を入力してください。"); @@ -90,26 +98,30 @@ impl EventHandler for Handler { input_text = input_text.value(value); } - let custom_id = interaction.id.to_string(); + let custom_id = FixedString::from_str(&interaction.id.to_string()).unwrap(); let _ = interaction .create_response( - &ctx, - CreateInteractionResponse::Modal( - CreateModal::new(&custom_id, "合言葉を入力してください。") - .components([CreateActionRow::InputText(input_text)].to_vec()), + ctx.http(), + create_model( + &custom_id, + "合言葉を入力してください。", + &[CreateModalComponent::Label(CreateLabel::input_text( + "合言葉", + input_text, + ))], ), ) .await; - let Some(interaction) = ModalInteractionCollector::new(&ctx.shard) - .custom_ids(vec![custom_id]) + let Some(interaction) = ModalInteractionCollector::new(ctx) + .custom_ids([custom_id].to_vec()) .timeout(Duration::from_secs(60)) .await else { let _ = interaction .create_followup( - &ctx, + ctx.http(), CreateInteractionResponseFollowup::new() .content("時間切れです。もう一度お試しください。") .ephemeral(true), @@ -118,81 +130,81 @@ impl EventHandler for Handler { return; }; - let keyword = match interaction.data.components.first().unwrap().components.first() { - Some(ActionRowComponent::InputText(text)) => text.value.clone().unwrap(), - _ => return error!("Invalid modal interaction: {:?}", interaction), + let keyword = if let ModalComponent::Label(label) = interaction.data.components.first().unwrap() + && let LabelComponent::InputText(text) = label.component.clone() + { + text.value + } else { + return error!("Invalid modal interaction: {interaction:#?}"); }; if !config.trigger_regex.is_match(&keyword) { let _ = interaction - .create_response(&ctx, create_interaction_message("合言葉が間違っています。", true, None)) + .create_response( + ctx.http(), + create_interaction_message("合言葉が間違っています。", true, None), + ) .await; self.start_cooldown(interaction.user.id); return; } - if let Err(why) = member.add_role(&ctx, config.role_id).await { + if let Err(why) = member.add_role(ctx.http(), config.role_id, None).await { let log = create_message(format!( - "{} にロールを追加できませんでした。\n```\n{}```", - member.mention(), - why + "{} にロールを追加できませんでした。\n```\n{why:#?}```", + member.mention() )); - let _ = send_message(&ctx, &config.log_channel_id, log).await; - return error!("Failed to add role: {:?}", why); + let _ = send_message(ctx, &config.log_channel_id, log).await; + return error!("Failed to add role: {why:#?}"); } let log = create_message( MessageBuilder::new() .push_named_link_safe( member.display_name(), - format!("", member.user.id), + &*format!("", member.user.id), ) .push(" (") - .push_mono(member.user.id.to_string()) + .push_mono(&*member.user.id.to_string()) .push(") にロールを追加しました。") .build(), ); - let _ = send_message(&ctx, &config.log_channel_id, log).await; + let _ = send_message(ctx, &config.log_channel_id, log).await; const AUTH_SUCCESS_MESSAGE: &str = "合言葉を確認しました。\nチャンネルが表示されない場合、アプリの再起動や再読み込み(Ctrl + R)をお試しください。"; let _ = interaction - .create_response(&ctx, create_interaction_message(AUTH_SUCCESS_MESSAGE, true, None)) + .create_response(ctx.http(), create_interaction_message(AUTH_SUCCESS_MESSAGE, true, None)) .await; } +} - async fn ready(&self, _: Context, _: Ready) { - let cooldown = self.cooldown.clone(); - tokio::spawn(async move { - loop { - cooldown.retain(|_, instant| instant.elapsed() < AUTH_COOLDOWN); - tokio::time::sleep(Duration::from_secs(3600)).await; - } - }); +#[async_trait] +impl BotEventHandler for AuthEventHandler { + async fn dispatch(&self, ctx: &Context, event: &FullEvent) { + if let FullEvent::InteractionCreate { interaction, .. } = event { + self.handle_interaction_create(ctx, interaction).await + } } } /// 合言葉を入力するボタンを作成します。 #[poise::command(slash_command, ephemeral, guild_only, default_member_permissions = "ADMINISTRATOR")] pub async fn create_keyword_button( - ctx: PContext<'_>, + ctx: AppContext<'_>, #[description = "ボタンの表示名"] button: String, #[description = "メッセージ内容"] content: String, -) -> Result<(), PError> { +) -> Result<(), AppError> { say_reply(ctx, "ボタンを作成しました。").await?; let _ = ctx .channel_id() .send_message( - ctx, - create_message(content).components( - [CreateActionRow::Buttons( - [CreateButton::new(KEYWORD_INPUT_BUTTON) - .label(button) - .style(ButtonStyle::Primary)] - .to_vec(), - )] - .to_vec(), - ), + ctx.http(), + create_message(content).components(&[CreateComponent::ActionRow(CreateActionRow::buttons(&[ + CreateButton::new(KEYWORD_INPUT_BUTTON) + .label(button) + .style(ButtonStyle::Primary), + ]))]), ) .await?; diff --git a/src/features/mod.rs b/src/features/mod.rs index cb3d182..02e7ff5 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -1,5 +1,5 @@ mod admin; -// mod auth; +mod auth; mod auto_kick; mod honeypot; // mod logging; @@ -9,8 +9,9 @@ mod pin; // mod thread_auto_invite; // pub use auth::Handler as AuthHandler; -pub use auto_kick::AutoKickEventHandler; -pub use honeypot::handle_honeypot_event; +use auth::AuthEventHandler; +use auto_kick::AutoKickEventHandler; +use honeypot::handle_honeypot_event; // pub use logging::Handler as LoggingHandler; // pub use message_cache::Handler as MessageCacheHandler; // pub use question::Handler as QuestionHandler; @@ -20,11 +21,18 @@ pub use honeypot::handle_honeypot_event; use std::borrow::Cow; -use crate::app::AppCommand; +use crate::{app::AppCommand, core::BotEventHandlers}; + +pub fn event_handlers() -> BotEventHandlers { + BotEventHandlers::new() + .add(handle_honeypot_event) + .add(AuthEventHandler::new()) + .add(AutoKickEventHandler::new()) +} pub fn commands() -> Vec { build_commands(vec![ - // auth::create_keyword_button, + auth::create_keyword_button, // question::question, pin::pin, admin::reload_config, diff --git a/src/main.rs b/src/main.rs index 99a63fc..d920656 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,8 +13,8 @@ use tracing::error; use crate::{ app::{AppError, BotData, MainEventHandler, config::AppConfig, on_error}, - core::{BotEventHandlers, create_client}, - features::{AutoKickEventHandler, commands, handle_honeypot_event}, + core::create_client, + features::{commands, event_handlers}, }; #[derive(Clone, Debug, Bpaf)] @@ -58,10 +58,7 @@ async fn main() -> Result<(), AppError> { let mut client = create_client( config.bot.token.clone(), intents, - BotEventHandlers::new() - .add(MainEventHandler::new()) - .add(AutoKickEventHandler::new()) - .add(handle_honeypot_event), + event_handlers().add(MainEventHandler::new()), ) .framework(Box::new(framework)) // .event_handler(features::AuthHandler::new()) @@ -86,7 +83,7 @@ async fn main() -> Result<(), AppError> { }); if let Err(why) = client.start().await { - error!("Client error: {:?}", why); + error!("Client error: {:#?}", why); } Ok(()) diff --git a/src/utils.rs b/src/utils.rs index 684dce0..5e41c43 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -3,7 +3,10 @@ use std::{borrow::Cow, time::Duration}; use itertools::Itertools; use serenity::{ Result, - all::{ChannelId, Context, CreateAllowedMentions, CreateMessage, Message}, + all::{ChannelId, Context, CreateAllowedMentions, CreateMessage, Message, prelude::CacheHttp}, + builder::{ + CreateComponent, CreateInteractionResponse, CreateInteractionResponseMessage, CreateModal, CreateModalComponent, + }, }; use similar::{Algorithm, ChangeTag, TextDiff}; use tracing::error; @@ -18,21 +21,36 @@ pub fn create_message<'a>(content: impl Into>) -> CreateMessage<'a> create_safe_message().content(content) } -// pub fn create_interaction_message( -// content: impl Into, -// ephemeral: bool, -// components: Option>, -// ) -> CreateInteractionResponse { -// let mut msg = CreateInteractionResponseMessage::new() -// .content(content) -// .ephemeral(ephemeral); - -// if let Some(components) = components { -// msg = msg.components(components); -// } +pub fn create_interaction_message<'a>( + content: impl Into>, + ephemeral: bool, + components: Option<&'a [CreateComponent<'a>]>, +) -> CreateInteractionResponse<'a> { + let mut msg = CreateInteractionResponseMessage::new() + .content(content) + .ephemeral(ephemeral); + + if let Some(components) = components { + msg = msg.components(components); + } -// CreateInteractionResponse::Message(msg) -// } + CreateInteractionResponse::Message(msg) +} + +pub fn create_ephemeral_message<'a>( + content: impl Into>, + components: Option<&'a [CreateComponent<'a>]>, +) -> CreateInteractionResponse<'a> { + create_interaction_message(content, true, components) +} + +pub fn create_model<'a>( + custom_id: impl Into>, + title: impl Into>, + components: impl Into]>>, +) -> CreateInteractionResponse<'a> { + CreateInteractionResponse::Modal(CreateModal::new(custom_id, title).components(components)) +} // /** // thread_create イベントにおいて、初期メッセージが送信されるか5秒経過するまで待機する @@ -107,7 +125,7 @@ pub fn format_duration(duration: Duration, mut count: usize) -> String { if remaining >= unit && count > 0 { let value = remaining / unit; if value > 0 { - parts.push(format!("{}{}", value, label)); + parts.push(format!("{value}{label}")); remaining %= unit; count -= 1; } @@ -118,10 +136,10 @@ pub fn format_duration(duration: Duration, mut count: usize) -> String { } pub async fn send_message<'a>(ctx: &Context, channel_id: &ChannelId, builder: CreateMessage<'a>) -> Result { - match channel_id.widen().send_message(&ctx.http, builder).await { + match channel_id.widen().send_message(ctx.http(), builder).await { Ok(m) => Ok(m), Err(why) => { - error!("Error sending message: {:?}", why); + error!("Error sending message: {:#?}", why); Err(why) } } @@ -209,9 +227,9 @@ pub fn create_diff_lines_text(old: &str, new: &str) -> String { let diff = TextDiff::configure().algorithm(Algorithm::Myers).diff_lines(old, new); diff.iter_all_changes() .map(|c| match c.tag() { - ChangeTag::Delete => format!("- {}", c), - ChangeTag::Insert => format!("+ {}", c), - ChangeTag::Equal => format!(" {}", c), + ChangeTag::Delete => format!("- {c}"), + ChangeTag::Insert => format!("+ {c}"), + ChangeTag::Equal => format!(" {c}"), }) .join("") } From 086a95f1e6405b755d344e9f39964241de7cff00 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sun, 14 Jun 2026 04:06:57 +0900 Subject: [PATCH 090/104] =?UTF-8?q?refactor:=20=E6=9B=B8=E3=81=8D=E6=96=B9?= =?UTF-8?q?=E3=82=92=E5=BE=AE=E8=AA=BF=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/error.rs | 13 +++++-------- src/features/auto_kick.rs | 11 +++++++---- src/features/honeypot.rs | 14 +++++++------- src/features/logging.rs | 6 +++--- src/features/message_cache/mod.rs | 8 ++++---- src/features/pin.rs | 8 ++++---- src/features/question/command.rs | 4 ++-- src/features/question/mod.rs | 2 +- src/features/question/question_creation_handler.rs | 12 ++++++------ src/features/thread_auto_invite/command.rs | 4 ++-- src/features/thread_auto_invite/handler.rs | 12 ++++++------ src/main.rs | 3 --- 12 files changed, 47 insertions(+), 50 deletions(-) diff --git a/src/app/error.rs b/src/app/error.rs index 933ffd2..f405ccf 100644 --- a/src/app/error.rs +++ b/src/app/error.rs @@ -21,11 +21,11 @@ pub async fn on_error(error: FrameworkError<'_, BotData, AppError>) { match error { FrameworkError::Command { error, ctx, .. } => { let _ = say_reply(ctx, "コマンド実行中にエラーが発生しました。").await; - error!("Command error: Command: {:?}, Error: {:?}", ctx.command(), error); + error!("Command error: Command: {:#?}, Error: {error:#?}", ctx.command()); } FrameworkError::ArgumentParse { ctx, input, error, .. } => { let Some(input) = input else { - return error!("Error parsing input: {:?}", error); + return error!("Error parsing input: {error:#?}"); }; // let error = match error.downcast_ref::() { @@ -36,17 +36,14 @@ pub async fn on_error(error: FrameworkError<'_, BotData, AppError>) { // _ => &error.to_string(), // }; - let _ = say_reply(ctx, format!("入力 `{}` の解析に失敗しました。\n{:?}", input, error)).await; + let _ = say_reply(ctx, format!("入力 `{input}` の解析に失敗しました。\n{error:#?}")).await; } FrameworkError::MissingBotPermissions { missing_permissions, ctx, .. } => { - let msg = format!( - "ボットに権限が無いためコマンドを実行できません: {}", - missing_permissions, - ); + let msg = format!("ボットに権限が無いためコマンドを実行できません: {missing_permissions}",); let _ = say_reply(ctx, msg).await; } FrameworkError::NotAnOwner { ctx, .. } => { @@ -75,7 +72,7 @@ pub async fn on_error(error: FrameworkError<'_, BotData, AppError>) { } error => { if let Err(e) = poise::builtins::on_error(error).await { - println!("Error while handling error: {}", e) + println!("Error while handling error: {e:#?}") } } } diff --git a/src/features/auto_kick.rs b/src/features/auto_kick.rs index a081316..fcecfb6 100644 --- a/src/features/auto_kick.rs +++ b/src/features/auto_kick.rs @@ -5,7 +5,10 @@ use std::{ use futures::{StreamExt, stream}; use serenity::{ - all::{Context, MessageBuilder, prelude::Mentionable}, + all::{ + Context, MessageBuilder, + prelude::{CacheHttp, Mentionable}, + }, async_trait, builder::CreateEmbed, model::{Color, event::FullEvent}, @@ -52,7 +55,7 @@ impl AutoKickEventHandler { config .auto_kick .guild_id - .members_iter(&ctx.http) + .members_iter(ctx.http()) .filter_map(|r| async { r.ok() }) .right_stream() }; @@ -83,10 +86,10 @@ impl AutoKickEventHandler { .await; if let Err(e) = member - .kick(&ctx.http, Some("一定期間のうちに認証ロールが付与されていないため")) + .kick(ctx.http(), Some("一定期間のうちに認証ロールが付与されていないため")) .await { - error!("Failed to kick user: {:?}", e); + error!("Failed to kick user: {e:#?}"); continue; }; diff --git a/src/features/honeypot.rs b/src/features/honeypot.rs index e9e84d3..73ea976 100644 --- a/src/features/honeypot.rs +++ b/src/features/honeypot.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use chrono::Duration; use serenity::{ - all::prelude::Context, + all::prelude::{CacheHttp, Context}, builder::CreateEmbed, model::{ Timestamp, @@ -83,7 +83,7 @@ fn collect_message_ids( message_lookback: Duration, ) -> HashMap> { let Some(guild) = guild_id.to_guild_cached(&ctx.cache) else { - error!("guild {} not found in cache", guild_id); + error!("guild {guild_id} not found in cache"); return HashMap::new(); }; @@ -116,17 +116,17 @@ async fn delete_messages(ctx: &Context, messages: &HashMap 2 { for chunk in message_ids.chunks(100) { - if let Err(e) = channel_id.delete_messages(&ctx.http, chunk, DELETE_REASON).await { - error!("Failed to delete messages in channel {}: {:?}", channel_id, e); + if let Err(e) = channel_id.delete_messages(ctx.http(), chunk, DELETE_REASON).await { + error!("Failed to delete messages in channel {channel_id}: {e:#?}"); } } continue; } if let Some(id) = message_ids.first() - && let Err(e) = channel_id.delete_message(&ctx.http, *id, DELETE_REASON).await + && let Err(e) = channel_id.delete_message(ctx.http(), *id, DELETE_REASON).await { - error!("Failed to delete message {} in channel {}: {:?}", id, channel_id, e); + error!("Failed to delete message {id} in channel {channel_id}: {e:#?}"); } } } @@ -157,7 +157,7 @@ pub async fn handle_honeypot_event(ctx: &Context, event: &FullEvent) { }; let _ = member - .kick(&ctx.http, Some("ハニーポットにメッセージを送信したため")) + .kick(ctx.http(), Some("ハニーポットにメッセージを送信したため")) .await; let delete_message_ids = collect_message_ids( diff --git a/src/features/logging.rs b/src/features/logging.rs index 0f44d5a..d1004a3 100644 --- a/src/features/logging.rs +++ b/src/features/logging.rs @@ -87,7 +87,7 @@ impl Handler { for (i, answer) in enumerate(&poll.answers) { let answer_text = answer.poll_media.text.clone().unwrap_or("<不明な回答>".to_string()); - builder.push_safe(format!("- {}", answer_text)); + builder.push_safe(format!("- {answer_text}")); if let Some(results) = &poll.results { builder.push_line_safe(format!(": {}票", results.answer_counts[i].count)); } else { @@ -231,7 +231,7 @@ impl EventHandler for Handler { _: Option, ) { let Some(message) = get_cached_message(&ctx, channel_id, deleted_message_id).await else { - return error!("Failed to get message: {}", deleted_message_id); + return error!("Failed to get message: {deleted_message_id}"); }; self.create_and_send_log(&ctx, &message, LogType::Delete).await; @@ -246,7 +246,7 @@ impl EventHandler for Handler { ) { for message_id in deleted_message_ids { let Some(message) = get_cached_message(&ctx, channel_id, message_id).await else { - error!("Failed to get message: {}", message_id); + error!("Failed to get message: {message_id}"); continue; }; self.create_and_send_log(&ctx, &message, LogType::Delete).await; diff --git a/src/features/message_cache/mod.rs b/src/features/message_cache/mod.rs index a6b9bba..80c5a51 100644 --- a/src/features/message_cache/mod.rs +++ b/src/features/message_cache/mod.rs @@ -66,7 +66,7 @@ impl Handler { let cache = data.get_mut::().unwrap(); let len = messages.len(); cache.extend_messages(messages); - info!("Cached {} messages for channel: {} ({})", len, channel.name, channel.id); + info!("Cached {len} messages for channel: {} ({})", channel.name, channel.id); } } @@ -84,19 +84,19 @@ impl EventHandler for Handler { let guild = match guild_id.to_guild_cached(ctx_ref) { Some(guild) => guild.clone(), None => { - error!("Failed to get guild: {:?}", guild_id); + error!("Failed to get guild: {:#?}", guild_id); continue; } }; let bot_id = ctx_ref.cache.current_user().id; let Ok(bot_member) = guild.member(ctx_ref, bot_id).await else { - error!("Failed to get bot member for guild: {:?}", guild_id); + error!("Failed to get bot member for guild: {:#?}", guild_id); continue; }; let Ok(channels) = guild_id.channels(ctx_ref).await else { - error!("Failed to get channels for guild: {:?}", guild_id); + error!("Failed to get channels for guild: {:#?}", guild_id); continue; }; diff --git a/src/features/pin.rs b/src/features/pin.rs index dfa539b..75aa218 100644 --- a/src/features/pin.rs +++ b/src/features/pin.rs @@ -69,7 +69,7 @@ pub async fn pin( let mut stream = channel .id() - .collect_messages(&ctx.serenity_context()) + .collect_messages(ctx.serenity_context()) .timeout(Duration::from_secs(5)) .channel_id(msg.channel_id) .author_id(ctx.serenity_context().cache.current_user().id) @@ -78,15 +78,15 @@ pub async fn pin( static PIN_REASON: Option<&str> = Some("/pin コマンドによる操作"); if msg.pinned() { - msg.unpin(&ctx.http(), PIN_REASON).await?; + msg.unpin(ctx.http(), PIN_REASON).await?; say_reply(ctx, "ピン留めを解除しました。").await?; } else { - msg.pin(&ctx.http(), PIN_REASON).await?; + msg.pin(ctx.http(), PIN_REASON).await?; say_reply(ctx, "ピン留めしました。").await?; } if let Some(msg) = stream.next().await { - let _ = msg.delete(&ctx.http(), None).await; + let _ = msg.delete(ctx.http(), None).await; } Ok(()) diff --git a/src/features/question/command.rs b/src/features/question/command.rs index 943acfb..8d07726 100644 --- a/src/features/question/command.rs +++ b/src/features/question/command.rs @@ -137,7 +137,7 @@ pub async fn question(ctx: ApplicationContext<'_, CommandData, PError>) -> Resul .edit( ctx.into(), CreateReply::default() - .content(format!("{}\n{}", PROMPT, CONFIRM)) + .content(format!("{PROMPT}\n{CONFIRM}")) .components(vec![ create_select_menu( &custom_ids.select_tag, @@ -179,7 +179,7 @@ pub async fn question(ctx: ApplicationContext<'_, CommandData, PError>) -> Resul CreateMessage::default() .content(msg) .components(vec![CreateActionRow::Buttons(vec![ - CreateButton::new(format!("{}:{}", QUESTION_CLOSE_PREFIX, ctx.interaction.user.id)) + CreateButton::new(format!("{QUESTION_CLOSE_PREFIX}:{}", ctx.interaction.user.id)) .label("質問を解決済みにする") .style(ButtonStyle::Danger), ])]), diff --git a/src/features/question/mod.rs b/src/features/question/mod.rs index cf03423..ac0910a 100644 --- a/src/features/question/mod.rs +++ b/src/features/question/mod.rs @@ -42,7 +42,7 @@ impl EventHandler for Handler { let config = &get_config(&ctx).await.question; let Ok(Channel::Guild(channel)) = interaction.channel_id.to_channel(&ctx).await else { - return error!("Failed to get channel: {:?}", interaction.channel_id); + return error!("Failed to get channel: {:#?}", interaction.channel_id); }; if channel.applied_tags.contains(&config.solved_tag) { let _ = interaction diff --git a/src/features/question/question_creation_handler.rs b/src/features/question/question_creation_handler.rs index 61d0cfd..382e6e0 100644 --- a/src/features/question/question_creation_handler.rs +++ b/src/features/question/question_creation_handler.rs @@ -23,10 +23,10 @@ pub struct CustomIds { impl CustomIds { pub fn new(id: u64) -> Self { Self { - basic: format!("open_basic_question_modal:{}", id), - detailed: format!("open_detailed_question_modal:{}", id), - select_tag: format!("question_select_tag:{}", id), - submit: format!("question_submit:{}", id), + basic: format!("open_basic_question_modal:{id}"), + detailed: format!("open_detailed_question_modal:{id}"), + select_tag: format!("question_select_tag:{id}"), + submit: format!("question_submit:{id}"), } } @@ -72,7 +72,7 @@ impl QuestionCreationHandler { async fn send_modal(&self, interaction: &ComponentInteraction, default: Option, custom_id: &str) { let modal = M::create(default, custom_id.to_owned()); let Ok(_) = interaction.create_response(self.ctx.http(), modal.clone()).await else { - error!("Failed to create response: {:?}", modal); + error!("Failed to create response: {modal:#?}"); return; }; } @@ -81,7 +81,7 @@ impl QuestionCreationHandler { match M::parse(data.clone()) { Ok(data) => Some(data), Err(e) => { - error!("Failed to parse modal data: {:?}, {:?}", e, data); + error!("Failed to parse modal data: {e:#?}, {data:#?}"); None } } diff --git a/src/features/thread_auto_invite/command.rs b/src/features/thread_auto_invite/command.rs index d357b4e..ddb0033 100644 --- a/src/features/thread_auto_invite/command.rs +++ b/src/features/thread_auto_invite/command.rs @@ -50,7 +50,7 @@ pub async fn add_invite_role(ctx: ApplicationContext<'_, CommandData, PError>) - } } - say_reply(ctx.into(), format!("{} 人に招待用ロールを付与しました。", added_count)).await?; + say_reply(ctx.into(), format!("{added_count} 人に招待用ロールを付与しました。")).await?; Ok(()) } @@ -76,7 +76,7 @@ pub async fn remove_invite_role(ctx: ApplicationContext<'_, CommandData, PError> say_reply( ctx.into(), - format!("{} 人から招待用ロールを全て削除しました。", role_count), + format!("{role_count} 人から招待用ロールを全て削除しました。"), ) .await?; Ok(()) diff --git a/src/features/thread_auto_invite/handler.rs b/src/features/thread_auto_invite/handler.rs index 732ce10..c91dca0 100644 --- a/src/features/thread_auto_invite/handler.rs +++ b/src/features/thread_auto_invite/handler.rs @@ -25,7 +25,7 @@ impl Handler { .http .get_guild_role_member_counts(guild_id) .await - .map_err(|e| error!("Failed to get guild role member counts: {}", e)) + .map_err(|e| error!("Failed to get guild role member counts: {e:#?}")) .ok()?; config.role_ids.iter().find_map(|&role_id| { @@ -41,9 +41,9 @@ impl Handler { }; if let Err(e) = new.add_role(ctx, role).await { - error!("Failed to add role {} to member {}: {}", role, new.user.id, e); + error!("Failed to add role {role} to member {}: {e:#?}", new.user.id); } else { - info!("Added role {} to member {}", role, new.user.id); + info!("Added role {role} to member {}", new.user.id); } } @@ -56,9 +56,9 @@ impl Handler { for role_id in roles { if let Err(e) = old.remove_role(&ctx, role_id).await { - error!("Failed to remove role {} from member {}: {}", role_id, old.user.id, e); + error!("Failed to remove role {role_id} from member {}: {e:#?}", old.user.id); } else { - info!("Removed role {} from member {}", role_id, old.user.id); + info!("Removed role {role_id} from member {}", old.user.id); break; } } @@ -70,7 +70,7 @@ pub async fn invite_thread_by_roles(ctx: &Context, thread_id: ChannelId, role_id let msg = create_message("スレッド自動招待用メッセージ"); match thread_id.send_message(&ctx, msg).await { Ok(m) => m, - Err(why) => return error!("Error sending message: {:?}", why), + Err(why) => return error!("Error sending message: {why:#?}"), } }; diff --git a/src/main.rs b/src/main.rs index d920656..f2afde7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -61,9 +61,6 @@ async fn main() -> Result<(), AppError> { event_handlers().add(MainEventHandler::new()), ) .framework(Box::new(framework)) - // .event_handler(features::AuthHandler::new()) - // .event_handler(features::AutoKickHandler::new()) - // .event_handler(features::HoneypotHandler) // .event_handler(features::LoggingHandler) // .event_handler(features::ThreadAutoInviteHandler::new()) // .event_handler(features::QuestionHandler) From fb7d3447e6f7baca702bb3c6cf831e4de4fe5348 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sun, 14 Jun 2026 04:09:17 +0900 Subject: [PATCH 091/104] =?UTF-8?q?chore:=20=E3=83=9E=E3=82=AF=E3=83=AD?= =?UTF-8?q?=E3=82=AF=E3=83=AC=E3=83=BC=E3=83=88=E3=81=AB=E3=83=A9=E3=82=A4?= =?UTF-8?q?=E3=82=BB=E3=83=B3=E3=82=B9=E3=82=92=E8=A8=AD=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- macros/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 6659fa1..7b73013 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -3,6 +3,7 @@ name = "valine_bot_macros" version = "0.1.0" edition = "2024" publish = false +license = "MIT" [lib] proc-macro = true From bc5715625ee50d62f0ae100fb90ba50f673c39c0 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sun, 14 Jun 2026 04:19:09 +0900 Subject: [PATCH 092/104] =?UTF-8?q?feat!:=20=E5=90=88=E8=A8=80=E8=91=89?= =?UTF-8?q?=E3=81=AE=E5=88=A4=E5=AE=9A=E3=81=A7=E6=AD=A3=E8=A6=8F=E8=A1=A8?= =?UTF-8?q?=E7=8F=BE=E3=82=92=E4=BD=BF=E3=82=8F=E3=81=AA=E3=81=84=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.sample.toml | 2 +- src/app/config.rs | 3 +-- src/features/auth.rs | 3 ++- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config.sample.toml b/config.sample.toml index bd022d5..1bd51b9 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -9,7 +9,7 @@ log_channel_id = "000000000000000000" # 認証後に付与するロールID role_id = "000000000000000000" # マッチしたら認証成功とする正規表現 -trigger_regex = "[おオオ][のノノ][れレレ][GgGg][RrRr][EeEe][GgGg]" +keyword = "あいことば" # モーダルのデフォルト値にランダムに選択させるダミーのキーワード郡 dummy_keywords = [ "ダミー", "dummy" ] diff --git a/src/app/config.rs b/src/app/config.rs index 50b25d7..35659b0 100644 --- a/src/app/config.rs +++ b/src/app/config.rs @@ -44,8 +44,7 @@ pub struct BotConfig { pub struct AuthConfig { pub log_channel_id: ChannelId, pub role_id: RoleId, - #[serde_as(as = "DisplayFromStr")] - pub trigger_regex: Regex, + pub keyword: String, pub dummy_keywords: Vec, } diff --git a/src/features/auth.rs b/src/features/auth.rs index 1b572a7..7d9c16d 100644 --- a/src/features/auth.rs +++ b/src/features/auth.rs @@ -137,8 +137,9 @@ impl AuthEventHandler { } else { return error!("Invalid modal interaction: {interaction:#?}"); }; + let keyword = keyword.trim(); - if !config.trigger_regex.is_match(&keyword) { + if config.keyword != keyword { let _ = interaction .create_response( ctx.http(), From 65431b678c2f702e0f45552c449bbc92d7a66881 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sun, 14 Jun 2026 05:53:10 +0900 Subject: [PATCH 093/104] =?UTF-8?q?refactor:=20=E5=90=88=E8=A8=80=E8=91=89?= =?UTF-8?q?=E8=AA=8D=E8=A8=BC=E3=81=AE=E3=83=AD=E3=82=B0=E3=82=92=E6=94=B9?= =?UTF-8?q?=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/auth/auto_kick.rs | 121 +++++++++++++++++++ src/features/{auth.rs => auth/keyword.rs} | 100 ++++++++-------- src/features/auth/mod.rs | 6 + src/features/auth/utils.rs | 56 +++++++++ src/features/auto_kick.rs | 136 ---------------------- src/features/mod.rs | 16 +-- 6 files changed, 241 insertions(+), 194 deletions(-) create mode 100644 src/features/auth/auto_kick.rs rename src/features/{auth.rs => auth/keyword.rs} (65%) create mode 100644 src/features/auth/mod.rs create mode 100644 src/features/auth/utils.rs delete mode 100644 src/features/auto_kick.rs diff --git a/src/features/auth/auto_kick.rs b/src/features/auth/auto_kick.rs new file mode 100644 index 0000000..bfb97c5 --- /dev/null +++ b/src/features/auth/auto_kick.rs @@ -0,0 +1,121 @@ +use std::{ + sync::atomic::{AtomicBool, Ordering}, + time::Duration, +}; + +use futures::{StreamExt, stream}; +use serenity::{ + all::{Context, prelude::CacheHttp}, + async_trait, + model::{Color, event::FullEvent}, +}; +use tokio::pin; +use tracing::error; + +use crate::{ + app::BotDataGetter, + core::BotEventHandler, + features::auth::utils::create_auth_log_message, + utils::{create_message, send_message}, +}; + +pub struct AutoKickEventHandler { + task_started: AtomicBool, +} + +impl AutoKickEventHandler { + pub fn new() -> Self { + Self { + task_started: AtomicBool::new(false), + } + } + + async fn run_kick_loop(ctx: Context) { + loop { + let config = ctx.read_app_config().await; + + let cached_members = ctx + .cache + .guild(config.auto_kick.guild_id) + // メンバーキャッシュが構築されていない場合は API から取ってくるため + .filter(|guild| guild.members.len() as u32 >= guild.member_count.get()) + .map(|guild| guild.members.clone()); + let member_stream = if let Some(members) = cached_members { + stream::iter(members).left_stream() + } else { + config + .auto_kick + .guild_id + .members_iter(ctx.http()) + .filter_map(|r| async { r.ok() }) + .right_stream() + }; + pin!(member_stream); + + while let Some(member) = member_stream.next().await { + if member.user.bot() { + continue; + } + + if member.roles.contains(&config.auth.role_id) { + continue; + } + + let joined_at = match member.joined_at { + Some(time) => *time, + None => continue, + }; + + if chrono::Utc::now().signed_duration_since(joined_at) < config.auto_kick.grace_period { + continue; + } + + let dm_result = member + .user + .id + .direct_message(&ctx, create_message(&config.auto_kick.kick_message)) + .await; + + if let Err(error) = member + .kick(ctx.http(), Some("一定期間のうちに認証ロールが付与されていないため")) + .await + { + error!("Failed to kick user: {error:#?}"); + // continue; + }; + + let _ = send_message( + &ctx, + &config.auth.log_channel_id, + create_auth_log_message( + "認証期限切れのため Kick", + Color::ORANGE, + &member, + Some(dm_result.is_ok()), + ), + ) + .await; + } + + tokio::time::sleep(Duration::from_secs(3600)).await; + } + } + + async fn handle_cache_ready(&self, ctx: &Context) { + if self.task_started.swap(true, Ordering::Relaxed) { + return; + } + + let ctx = ctx.clone(); + tokio::spawn(Self::run_kick_loop(ctx)); + } +} + +#[async_trait] +impl BotEventHandler for AutoKickEventHandler { + async fn dispatch(&self, ctx: &Context, event: &FullEvent) { + if let FullEvent::CacheReady { .. } = event { + self.handle_cache_ready(ctx).await; + } + } +} diff --git a/src/features/auth.rs b/src/features/auth/keyword.rs similarity index 65% rename from src/features/auth.rs rename to src/features/auth/keyword.rs index 7d9c16d..284f87a 100644 --- a/src/features/auth.rs +++ b/src/features/auth/keyword.rs @@ -1,6 +1,8 @@ -use std::str::FromStr; -use std::sync::Arc; -use std::time::{Duration, Instant}; +use std::{ + str::FromStr, + sync::Arc, + time::{Duration, Instant}, +}; use dashmap::DashMap; use poise::say_reply; @@ -8,52 +10,54 @@ use rand::seq::IndexedRandom; use serenity::all::prelude::CacheHttp; use serenity::all::{ ComponentInteractionDataKind, Context, CreateActionRow, CreateButton, CreateInputText, - CreateInteractionResponseFollowup, EmbedMessageBuilding, InputTextStyle, Interaction, Mentionable, MessageBuilder, - ModalInteractionCollector, UserId, + CreateInteractionResponseFollowup, InputTextStyle, Interaction, Mentionable, ModalInteractionCollector, UserId, }; use serenity::async_trait; use serenity::builder::{CreateComponent, CreateLabel, CreateModalComponent}; use serenity::model::application::{ButtonStyle, LabelComponent, ModalComponent}; +use serenity::model::colour::colours::branding; use serenity::model::event::FullEvent; use serenity::small_fixed_array::FixedString; use tracing::error; use crate::app::{AppContext, AppError, BotDataGetter}; use crate::core::BotEventHandler; +use crate::features::auth::utils::create_auth_log_message; use crate::utils::{create_ephemeral_message, create_interaction_message, create_message, create_model, send_message}; -static KEYWORD_INPUT_BUTTON: &str = "keyword_input:button"; -static AUTH_COOLDOWN: Duration = Duration::from_secs(60); +const KEYWORD_INPUT_BUTTON_CUSTOM_ID: &str = "keyword_input:button"; +const FAILED_ATTEMPT_COOLDOWN: Duration = Duration::from_secs(60); -pub struct AuthEventHandler { - cooldown: Arc>, +pub struct KeywordAuthEventHandler { + cooldown_started_at: Arc>, } -impl AuthEventHandler { +impl KeywordAuthEventHandler { pub fn new() -> Self { Self { - cooldown: Arc::new(DashMap::new()), + cooldown_started_at: Arc::new(DashMap::new()), } } - fn cleanup_cooldown(&self) { - self.cooldown.retain(|_, instant| instant.elapsed() < AUTH_COOLDOWN); + fn remove_expired_cooldowns(&self) { + self.cooldown_started_at + .retain(|_, started_at| started_at.elapsed() < FAILED_ATTEMPT_COOLDOWN); } - fn remaining_cooldown(&self, user_id: UserId) -> Option { - self.cleanup_cooldown(); + fn remaining_cooldown_seconds(&self, user_id: UserId) -> Option { + self.remove_expired_cooldowns(); - if let Some(instant) = self.cooldown.get(&user_id) { - let remaining = AUTH_COOLDOWN.checked_sub(instant.elapsed())?.as_secs(); - if remaining > 0 { - return Some(remaining); + if let Some(started_at) = self.cooldown_started_at.get(&user_id) { + let remaining_seconds = FAILED_ATTEMPT_COOLDOWN.checked_sub(started_at.elapsed())?.as_secs(); + if remaining_seconds > 0 { + return Some(remaining_seconds); } } None } - fn start_cooldown(&self, user_id: UserId) { - self.cooldown.insert(user_id, Instant::now()); + fn start_cooldown_for(&self, user_id: UserId) { + self.cooldown_started_at.insert(user_id, Instant::now()); } async fn handle_interaction_create(&self, ctx: &Context, interaction: &Interaction) { @@ -63,7 +67,7 @@ impl AuthEventHandler { let ComponentInteractionDataKind::Button = interaction.data.kind else { return; }; - if interaction.data.custom_id != KEYWORD_INPUT_BUTTON { + if interaction.data.custom_id != KEYWORD_INPUT_BUTTON_CUSTOM_ID { return; } @@ -77,12 +81,12 @@ impl AuthEventHandler { return; } - if let Some(remaining) = self.remaining_cooldown(interaction.user.id) { + if let Some(remaining_seconds) = self.remaining_cooldown_seconds(interaction.user.id) { let _ = interaction .create_response( ctx.http(), create_ephemeral_message( - format!("クールダウン中です。\n{remaining}秒後に再度お試しください。"), + format!("クールダウン中です。\n{remaining_seconds}秒後に再度お試しください。"), None, ), ) @@ -90,32 +94,32 @@ impl AuthEventHandler { return; } - let mut input_text = CreateInputText::new(InputTextStyle::Short, "keyword") + let mut keyword_input = CreateInputText::new(InputTextStyle::Short, "keyword") .required(true) .placeholder("合言葉を入力してください。"); if let Some(value) = config.dummy_keywords.choose(&mut rand::rng()) { - input_text = input_text.value(value); + keyword_input = keyword_input.value(value); } - let custom_id = FixedString::from_str(&interaction.id.to_string()).unwrap(); + let modal_custom_id = FixedString::from_str(&interaction.id.to_string()).unwrap(); let _ = interaction .create_response( ctx.http(), create_model( - &custom_id, + &modal_custom_id, "合言葉を入力してください。", &[CreateModalComponent::Label(CreateLabel::input_text( "合言葉", - input_text, + keyword_input, ))], ), ) .await; let Some(interaction) = ModalInteractionCollector::new(ctx) - .custom_ids([custom_id].to_vec()) + .custom_ids([modal_custom_id].to_vec()) .timeout(Duration::from_secs(60)) .await else { @@ -130,47 +134,41 @@ impl AuthEventHandler { return; }; - let keyword = if let ModalComponent::Label(label) = interaction.data.components.first().unwrap() + let submitted_keyword = if let ModalComponent::Label(label) = interaction.data.components.first().unwrap() && let LabelComponent::InputText(text) = label.component.clone() { text.value } else { return error!("Invalid modal interaction: {interaction:#?}"); }; - let keyword = keyword.trim(); + let submitted_keyword = submitted_keyword.trim(); - if config.keyword != keyword { + if config.keyword != submitted_keyword { let _ = interaction .create_response( ctx.http(), create_interaction_message("合言葉が間違っています。", true, None), ) .await; - self.start_cooldown(interaction.user.id); + self.start_cooldown_for(interaction.user.id); return; } - if let Err(why) = member.add_role(ctx.http(), config.role_id, None).await { + if let Err(error) = member.add_role(ctx.http(), config.role_id, Some("認証成功")).await { let log = create_message(format!( - "{} にロールを追加できませんでした。\n```\n{why:#?}```", + "{} にロールを追加できませんでした。\n```\n{error:#?}```", member.mention() )); let _ = send_message(ctx, &config.log_channel_id, log).await; - return error!("Failed to add role: {why:#?}"); + return error!("Failed to add role: {error:#?}"); } - let log = create_message( - MessageBuilder::new() - .push_named_link_safe( - member.display_name(), - &*format!("", member.user.id), - ) - .push(" (") - .push_mono(&*member.user.id.to_string()) - .push(") にロールを追加しました。") - .build(), - ); - let _ = send_message(ctx, &config.log_channel_id, log).await; + let _ = send_message( + ctx, + &config.log_channel_id, + create_auth_log_message("認証成功", branding::GREEN, member, None), + ) + .await; const AUTH_SUCCESS_MESSAGE: &str = "合言葉を確認しました。\nチャンネルが表示されない場合、アプリの再起動や再読み込み(Ctrl + R)をお試しください。"; let _ = interaction @@ -180,7 +178,7 @@ impl AuthEventHandler { } #[async_trait] -impl BotEventHandler for AuthEventHandler { +impl BotEventHandler for KeywordAuthEventHandler { async fn dispatch(&self, ctx: &Context, event: &FullEvent) { if let FullEvent::InteractionCreate { interaction, .. } = event { self.handle_interaction_create(ctx, interaction).await @@ -202,7 +200,7 @@ pub async fn create_keyword_button( .send_message( ctx.http(), create_message(content).components(&[CreateComponent::ActionRow(CreateActionRow::buttons(&[ - CreateButton::new(KEYWORD_INPUT_BUTTON) + CreateButton::new(KEYWORD_INPUT_BUTTON_CUSTOM_ID) .label(button) .style(ButtonStyle::Primary), ]))]), diff --git a/src/features/auth/mod.rs b/src/features/auth/mod.rs new file mode 100644 index 0000000..454d548 --- /dev/null +++ b/src/features/auth/mod.rs @@ -0,0 +1,6 @@ +mod auto_kick; +mod keyword; +mod utils; + +pub use auto_kick::AutoKickEventHandler; +pub use keyword::{KeywordAuthEventHandler, create_keyword_button}; diff --git a/src/features/auth/utils.rs b/src/features/auth/utils.rs new file mode 100644 index 0000000..c15bdab --- /dev/null +++ b/src/features/auth/utils.rs @@ -0,0 +1,56 @@ +use std::borrow::Cow; + +use serenity::all::{Mentionable, MessageBuilder}; +use serenity::builder::{CreateEmbed, CreateMessage}; +use serenity::model::Color; +use serenity::model::guild::Member; +use serenity::utils::EmbedMessageBuilding; + +use crate::utils::create_safe_message; + +pub(in crate::features::auth) fn create_auth_log_message<'a>( + title: impl Into>, + color: impl Into, + member: &Member, + dm_delivery_succeeded: Option, +) -> CreateMessage<'a> { + let mut description = MessageBuilder::new() + .push("- ") + .push_bold_line("ユーザー") + .push(" - ") + .push_bold("表示名: ") + .push_line_safe(member.display_name()) + .push(" - ") + .push_bold("メンション: ") + .push_safe(&*member.mention().to_string()) + .push(" ") + .push_named_link("リンク", &*format!("", member.user.id)) + .push_line("") + .push(" - ") + .push_bold("ユーザー名: ") + .push_line_safe(&*member.user.name) + .push(" - ") + .push_bold("ID: ") + .push_line_safe(&*member.user.id.to_string()); + + if let Some(delivery_succeeded) = dm_delivery_succeeded { + description = description + .push("- ") + .push_bold("DM送信可否: ") + .push_line(if delivery_succeeded { "YES" } else { "NO" }); + } + + let embed = CreateEmbed::new() + .title(title) + .description(description.build()) + .color(color) + .thumbnail( + member + .user + .avatar_url() + .unwrap_or("https://cdn.discordapp.com/embed/avatars/0.png".to_string()), + Some("ユーザーアイコン".into()), + ); + + create_safe_message().embed(embed) +} diff --git a/src/features/auto_kick.rs b/src/features/auto_kick.rs deleted file mode 100644 index fcecfb6..0000000 --- a/src/features/auto_kick.rs +++ /dev/null @@ -1,136 +0,0 @@ -use std::{ - sync::atomic::{AtomicBool, Ordering}, - time::Duration, -}; - -use futures::{StreamExt, stream}; -use serenity::{ - all::{ - Context, MessageBuilder, - prelude::{CacheHttp, Mentionable}, - }, - async_trait, - builder::CreateEmbed, - model::{Color, event::FullEvent}, -}; -use tokio::pin; -use tracing::error; - -use crate::{ - app::BotDataGetter, - core::BotEventHandler, - utils::{create_message, create_safe_message, send_message}, -}; - -pub struct AutoKickEventHandler { - task_started: AtomicBool, -} - -impl AutoKickEventHandler { - pub fn new() -> Self { - Self { - task_started: AtomicBool::new(false), - } - } - - async fn handle_cache_ready(&self, ctx: &Context) { - if self.task_started.swap(true, Ordering::Relaxed) { - return; - } - - let ctx = ctx.clone(); - tokio::spawn(async move { - let config = ctx.read_app_config().await; - - loop { - let cached_members = ctx - .cache - .guild(config.auto_kick.guild_id) - // メンバーキャッシュが構築されていない場合は API から取ってくるため - .filter(|guild| guild.members.len() as u32 >= guild.member_count.get()) - .map(|guild| guild.members.clone()); - let members = if let Some(members) = cached_members { - stream::iter(members).left_stream() - } else { - config - .auto_kick - .guild_id - .members_iter(ctx.http()) - .filter_map(|r| async { r.ok() }) - .right_stream() - }; - pin!(members); - - while let Some(member) = members.next().await { - if member.user.bot() { - continue; - } - - if member.roles.contains(&config.auth.role_id) { - continue; - } - - let joined = match member.joined_at { - Some(time) => *time, - None => continue, - }; - - if chrono::Utc::now().signed_duration_since(joined) < config.auto_kick.grace_period { - continue; - } - - let dm_message = member - .user - .id - .direct_message(&ctx, create_message(&config.auto_kick.kick_message)) - .await; - - if let Err(e) = member - .kick(ctx.http(), Some("一定期間のうちに認証ロールが付与されていないため")) - .await - { - error!("Failed to kick user: {e:#?}"); - continue; - }; - - let log = MessageBuilder::new() - .push_bold("ユーザー: ") - .push_safe(member.display_name()) - .push(" ") - .push_mono_line(&*member.user.id.to_string()) - .push_bold("メンション: ") - .push_line_safe(&*member.mention().to_string()) - .push_bold("リンク") - .push_line_safe(&*format!("", member.user.id)) - .push_line(dm_message.map_or("DMの送信に失敗しました。", |_| "")) - .build(); - - let embed = CreateEmbed::new() - .title("自動 Kick") - .description(log) - .color(Color::ORANGE) - .thumbnail( - member - .user - .avatar_url() - .unwrap_or("https://cdn.discordapp.com/embed/avatars/0.png".to_string()), - Some("ユーザーアイコン".into()), - ); - - let _ = send_message(&ctx, &config.auth.log_channel_id, create_safe_message().embed(embed)).await; - } - - tokio::time::sleep(Duration::from_secs(3600)).await; - } - }); - } -} - -#[async_trait] -impl BotEventHandler for AutoKickEventHandler { - async fn dispatch(&self, ctx: &Context, event: &FullEvent) { - if let FullEvent::CacheReady { .. } = event { - self.handle_cache_ready(ctx).await; - } - } -} diff --git a/src/features/mod.rs b/src/features/mod.rs index 02e7ff5..a9427e9 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -1,6 +1,5 @@ mod admin; mod auth; -mod auto_kick; mod honeypot; // mod logging; // mod message_cache; @@ -8,10 +7,6 @@ mod pin; // mod question; // mod thread_auto_invite; -// pub use auth::Handler as AuthHandler; -use auth::AuthEventHandler; -use auto_kick::AutoKickEventHandler; -use honeypot::handle_honeypot_event; // pub use logging::Handler as LoggingHandler; // pub use message_cache::Handler as MessageCacheHandler; // pub use question::Handler as QuestionHandler; @@ -21,12 +16,19 @@ use honeypot::handle_honeypot_event; use std::borrow::Cow; -use crate::{app::AppCommand, core::BotEventHandlers}; +use crate::{ + app::AppCommand, + core::BotEventHandlers, + features::{ + auth::{AutoKickEventHandler, KeywordAuthEventHandler}, + honeypot::handle_honeypot_event, + }, +}; pub fn event_handlers() -> BotEventHandlers { BotEventHandlers::new() .add(handle_honeypot_event) - .add(AuthEventHandler::new()) + .add(KeywordAuthEventHandler::new()) .add(AutoKickEventHandler::new()) } From 2ae4a19bc0029f5cb0102e870ce10743b99c31db Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sun, 14 Jun 2026 09:50:48 +0900 Subject: [PATCH 094/104] =?UTF-8?q?refactor:=20=E7=B7=A8=E9=9B=86=E3=83=BB?= =?UTF-8?q?=E5=89=8A=E9=99=A4=E3=83=AD=E3=82=B0=E3=81=AE=E5=86=8D=E5=AE=9F?= =?UTF-8?q?=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/data.rs | 21 +- src/app/mod.rs | 1 + src/app/utils/message_cache.rs | 22 ++ src/app/utils/mod.rs | 1 + .../cache.rs => core/message_cache.rs} | 34 ++- src/core/mod.rs | 2 + src/features/logging.rs | 255 ------------------ src/features/message_cache/mod.rs | 124 --------- src/features/message_cache_handler.rs | 237 ++++++++++++++++ src/features/message_logging/embed_builder.rs | 128 +++++++++ src/features/message_logging/handler.rs | 122 +++++++++ src/features/message_logging/log_type.rs | 33 +++ src/features/message_logging/mod.rs | 5 + src/features/mod.rs | 19 +- src/main.rs | 2 +- src/utils.rs | 117 +++----- 16 files changed, 636 insertions(+), 487 deletions(-) create mode 100644 src/app/utils/message_cache.rs create mode 100644 src/app/utils/mod.rs rename src/{features/message_cache/cache.rs => core/message_cache.rs} (57%) delete mode 100644 src/features/logging.rs delete mode 100644 src/features/message_cache/mod.rs create mode 100644 src/features/message_cache_handler.rs create mode 100644 src/features/message_logging/embed_builder.rs create mode 100644 src/features/message_logging/handler.rs create mode 100644 src/features/message_logging/log_type.rs create mode 100644 src/features/message_logging/mod.rs diff --git a/src/app/data.rs b/src/app/data.rs index 04be184..e7e1416 100644 --- a/src/app/data.rs +++ b/src/app/data.rs @@ -3,41 +3,50 @@ use std::sync::Arc; use serenity::all::prelude::Context; use tokio::sync::RwLock; -use crate::app::{AppContext, config::AppConfig}; +use crate::{ + app::{AppContext, config::AppConfig}, + core::MessageCache, +}; pub struct BotData { config: RwLock>, + message_cache: Arc, } impl BotData { pub fn new(config: AppConfig) -> Self { Self { config: RwLock::new(Arc::new(config)), + message_cache: Arc::new(MessageCache::new()), } } } pub trait BotDataGetter { - fn get_bot_data(&self) -> Arc; + fn bot_data(&self) -> Arc; async fn read_app_config(&self) -> Arc { - self.get_bot_data().config.read().await.clone() + self.bot_data().config.read().await.clone() } async fn replace_app_config(&self, config: AppConfig) { - let data = self.get_bot_data(); + let data = self.bot_data(); *data.config.write().await = Arc::new(config); } + + fn message_cache(&self) -> Arc { + Arc::clone(&self.bot_data().message_cache) + } } impl BotDataGetter for Context { - fn get_bot_data(&self) -> Arc { + fn bot_data(&self) -> Arc { self.data() } } impl<'a> BotDataGetter for AppContext<'a> { - fn get_bot_data(&self) -> Arc { + fn bot_data(&self) -> Arc { self.data() } } diff --git a/src/app/mod.rs b/src/app/mod.rs index a113a82..1c83e49 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -5,6 +5,7 @@ mod data; mod error; mod event_handler; pub mod types; +pub mod utils; pub use data::{BotData, BotDataGetter}; pub use error::{BotError, on_error}; diff --git a/src/app/utils/message_cache.rs b/src/app/utils/message_cache.rs new file mode 100644 index 0000000..e0e3c8e --- /dev/null +++ b/src/app/utils/message_cache.rs @@ -0,0 +1,22 @@ +use serenity::{ + all::prelude::Context, + model::{ + channel::Message, + id::{GenericChannelId, MessageId}, + }, +}; + +use crate::app::BotDataGetter; + +pub async fn get_cached_message(ctx: &Context, channel_id: GenericChannelId, message_id: MessageId) -> Option { + if let Some(m) = ctx.cache.message(channel_id, message_id) { + return Some(m.clone()); + } + + let cache = ctx.message_cache(); + if let Some(m) = cache.get(channel_id, message_id) { + return Some(m); + } + + None +} diff --git a/src/app/utils/mod.rs b/src/app/utils/mod.rs new file mode 100644 index 0000000..4388eb2 --- /dev/null +++ b/src/app/utils/mod.rs @@ -0,0 +1 @@ +pub mod message_cache; diff --git a/src/features/message_cache/cache.rs b/src/core/message_cache.rs similarity index 57% rename from src/features/message_cache/cache.rs rename to src/core/message_cache.rs index 9299847..ce5eddb 100644 --- a/src/features/message_cache/cache.rs +++ b/src/core/message_cache.rs @@ -1,14 +1,14 @@ -use std::{collections::HashMap, sync::Arc}; +use std::collections::HashMap; use dashmap::DashMap; use itertools::Itertools; use serenity::{ - all::{ChannelId, Message, MessageId}, - prelude::TypeMapKey, + all::{Message, MessageId}, + model::id::GenericChannelId, }; pub struct MessageCache { - cache: DashMap>, + cache: DashMap>, } impl MessageCache { @@ -17,11 +17,23 @@ impl MessageCache { } pub fn insert(&self, message: Message) { - let mut map = self.cache.entry(message.channel_id).or_insert(HashMap::new()); + let mut map = self.cache.entry(message.channel_id).or_default(); map.insert(message.id, message); } - pub fn extend(&self, iter: impl IntoIterator) { + pub fn remove(&self, channel_id: GenericChannelId, message_id: MessageId) { + let mut map = self.cache.entry(channel_id).or_default(); + map.remove(&message_id); + } + + pub fn remove_all(&self, channel_id: GenericChannelId, message_ids: &[MessageId]) { + let mut map = self.cache.entry(channel_id).or_default(); + for id in message_ids { + map.remove(id); + } + } + + pub fn extend(&self, iter: impl IntoIterator) { iter.into_iter() .into_group_map_by(|(channel_id, _, _)| *channel_id) .into_iter() @@ -35,7 +47,7 @@ impl MessageCache { ) }) .for_each(|(channel_id, messages)| { - let mut map = self.cache.entry(channel_id).or_insert(HashMap::new()); + let mut map = self.cache.entry(channel_id).or_default(); map.extend(messages); }); } @@ -47,7 +59,7 @@ impl MessageCache { ); } - pub fn get(&self, channel_id: ChannelId, message_id: MessageId) -> Option { + pub fn get(&self, channel_id: GenericChannelId, message_id: MessageId) -> Option { let map = self.cache.get(&channel_id)?; map.get(&message_id).cloned() } @@ -58,9 +70,3 @@ impl Default for MessageCache { Self::new() } } - -pub struct MessageCacheType; - -impl TypeMapKey for MessageCacheType { - type Value = Arc; -} diff --git a/src/core/mod.rs b/src/core/mod.rs index e7bc732..2c9c398 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,5 +1,7 @@ mod client; mod event_handler; +mod message_cache; pub use client::create_client; pub use event_handler::{BotEventHandler, BotEventHandlers}; +pub use message_cache::MessageCache; diff --git a/src/features/logging.rs b/src/features/logging.rs deleted file mode 100644 index d1004a3..0000000 --- a/src/features/logging.rs +++ /dev/null @@ -1,255 +0,0 @@ -use std::ops::Not; - -use itertools::{Itertools, enumerate}; -use serenity::{ - all::{ - ChannelId, Context, CreateEmbed, EmbedMessageBuilding, EventHandler, GuildId, Mentionable, Message, - MessageBuilder, MessageId, MessageReferenceKind, MessageUpdateEvent, Timestamp, - }, - async_trait, -}; -use tracing::error; - -use crate::{ - config::get_config, - extensions::MessageBuilderTimestampExt, - features::MessageCacheType, - utils::{create_diff_lines_text, create_safe_message, get_cached_message, send_message}, -}; - -enum LogType { - Edit { new_content: String }, - Delete, -} - -impl LogType { - fn name(&self) -> &'static str { - match self { - LogType::Edit { .. } => "編集", - LogType::Delete => "削除", - } - } - - fn title(&self) -> String { - format!("メッセージ{}ログ", self.name()) - } - - fn color(&self) -> i32 { - match self { - LogType::Edit { .. } => 0xff8800, - LogType::Delete => 0xf00000, - } - } - - fn new_content(&self) -> Option<&str> { - match self { - LogType::Edit { new_content } => Some(new_content), - LogType::Delete => None, - } - } -} - -pub struct Handler; - -impl Handler { - fn build_reply_field(embed: CreateEmbed, message: &Message) -> CreateEmbed { - let Some(m_ref) = &message.message_reference else { - return embed; - }; - let id = m_ref.message_id.unwrap_or(MessageId::default()); - let (name, content) = match m_ref.kind { - MessageReferenceKind::Default => ("__**返信**__", "返信先: "), - MessageReferenceKind::Forward => ("__**転送**__", "転送元: "), - _ => ("__**不明**__", "不明な対象メッセージ: "), - }; - embed.field( - name, - MessageBuilder::new() - .push_bold_safe(content) - .push_safe(id.link(m_ref.channel_id, m_ref.guild_id)) - .push_safe(" ") - .push_mono_line_safe(id.to_string()) - .build(), - false, - ) - } - - fn build_poll_field(embed: CreateEmbed, message: &Message) -> CreateEmbed { - let Some(poll) = &message.poll else { - return embed; - }; - - let mut builder = MessageBuilder::new(); - builder - .push_bold_safe("タイトル: ") - .push_line_safe(poll.question.text.clone().unwrap_or("<不明なタイトル>".to_string())) - .push_bold_line_safe("回答:"); - - for (i, answer) in enumerate(&poll.answers) { - let answer_text = answer.poll_media.text.clone().unwrap_or("<不明な回答>".to_string()); - builder.push_safe(format!("- {answer_text}")); - if let Some(results) = &poll.results { - builder.push_line_safe(format!(": {}票", results.answer_counts[i].count)); - } else { - builder.push_safe("\n"); - } - } - - if let Some(expiry) = poll.expiry { - builder - .push_bold_safe("有効期限: ") - .push_timestamp_long_date_time_line(expiry); - } - - embed.field("__**投票**__", builder.build(), false) - } - - fn build_diff_field(mut embed: CreateEmbed, old_content: &str, new_content: &str) -> CreateEmbed { - if old_content.is_empty() { - return embed; - } - - let diff = create_diff_lines_text(old_content, new_content); - let chunks = diff.lines().peekable().batching(|lines| { - let mut str = String::new(); - while let Some(line) = lines.next_if(|&l| str.len() + l.len() <= 1000) { - str.push_str(line); - str.push('\n'); - } - str.is_empty().not().then_some(str) - }); - - for (i, chunk) in enumerate(chunks) { - let changed = MessageBuilder::new().push_codeblock_safe(chunk, Some("diff")).build(); - embed = embed.field(if i == 0 { "__**テキスト差分**__" } else { "" }, changed, false) - } - embed - } - - fn build_attachments_field(embed: CreateEmbed, message: &Message) -> CreateEmbed { - if message.attachments.is_empty() { - return embed; - } - let mut builder = MessageBuilder::new(); - for attachment in &message.attachments { - builder - .push_safe("- ") - .push_named_link_safe(&attachment.filename, &attachment.url) - .push_safe("\n"); - } - embed.field("__**添付ファイル**__", builder.build(), false) - } - - fn build_embed(&self, message: &Message, new_content: &str, mut embed: CreateEmbed) -> CreateEmbed { - embed = Self::build_reply_field(embed, message); - embed = Self::build_poll_field(embed, message); - embed = Self::build_diff_field(embed, &message.content, new_content); - Self::build_attachments_field(embed, message) - } - - async fn create_and_send_log(&self, ctx: &Context, message: &Message, log_type: LogType) { - if message.author.bot { - return; - } - - let description = MessageBuilder::new() - .push_bold_safe("メンバー: ") - .mention(&message.author.mention()) - .push_safe(" ") - .push_mono_line_safe(message.author.id.to_string()) - .push_bold_safe("メッセージ: ") - .push_safe(message.id.link(message.channel_id, message.guild_id)) - .push_safe(" ") - .push_mono_line_safe(message.id.to_string()) - .push_bold_safe("メッセージ送信日時: ") - .push_timestamp_long_date_time_line(message.timestamp) - .push_bold_safe(format!("{}日時: ", log_type.name())) - .push_timestamp_long_date_time_line(Timestamp::now()) - .build(); - - let mut embed = CreateEmbed::new() - .title(log_type.title()) - .description(description) - .color(log_type.color()) - .thumbnail( - message - .author - .avatar_url() - .unwrap_or("https://cdn.discordapp.com/embed/avatars/0.png".to_string()), - ); - - let new_content = log_type.new_content().unwrap_or(""); - embed = self.build_embed(message, new_content, embed); - self.send_log(ctx, embed).await; - } - - async fn update_cache(&self, ctx: &Context, message: &Message) { - let mut data = ctx.data.write().await; - let cache = data.get_mut::().unwrap(); - cache.insert(message.clone()); - } - - async fn send_log(&self, ctx: &Context, embed: CreateEmbed) { - let config = get_config(ctx).await; - let log = create_safe_message().add_embed(embed); - let _ = send_message(ctx, &config.message_logging.channel_id, log).await; - } -} - -#[async_trait] -impl EventHandler for Handler { - async fn message_update(&self, ctx: Context, old: Option, _: Option, event: MessageUpdateEvent) { - let Some(message) = get_cached_message(&ctx, event.channel_id, event.id).await else { - return error!("Failed to get message: {}", event.id); - }; - let message = old.unwrap_or(message); - - let Ok(new_message) = event.channel_id.message(&ctx.http, event.id).await else { - return error!("Failed to get new message: {}", event.id); - }; - - if message.content == new_message.content { - return; - } - - self.update_cache(&ctx, &new_message).await; - self.create_and_send_log( - &ctx, - &message, - LogType::Edit { - new_content: new_message.content, - }, - ) - .await; - } - - async fn message_delete( - &self, - ctx: Context, - channel_id: ChannelId, - deleted_message_id: MessageId, - _: Option, - ) { - let Some(message) = get_cached_message(&ctx, channel_id, deleted_message_id).await else { - return error!("Failed to get message: {deleted_message_id}"); - }; - - self.create_and_send_log(&ctx, &message, LogType::Delete).await; - } - - async fn message_delete_bulk( - &self, - ctx: Context, - channel_id: ChannelId, - deleted_message_ids: Vec, - _: Option, - ) { - for message_id in deleted_message_ids { - let Some(message) = get_cached_message(&ctx, channel_id, message_id).await else { - error!("Failed to get message: {message_id}"); - continue; - }; - self.create_and_send_log(&ctx, &message, LogType::Delete).await; - } - } -} diff --git a/src/features/message_cache/mod.rs b/src/features/message_cache/mod.rs deleted file mode 100644 index 80c5a51..0000000 --- a/src/features/message_cache/mod.rs +++ /dev/null @@ -1,124 +0,0 @@ -mod cache; - -use std::sync::atomic::{AtomicBool, Ordering}; - -pub use cache::{MessageCache, MessageCacheType}; - -use async_stream::stream; -use futures::{StreamExt, future}; -use serenity::{ - all::{Context, EventHandler, Guild, GuildChannel, GuildId, Member}, - async_trait, -}; -use tracing::{error, info}; - -use crate::{ - config::{Config, get_config}, - utils::fetch_all_archived_public_thread, -}; - -pub struct Handler { - disabled: bool, - collected: AtomicBool, -} - -impl Handler { - pub fn new(disabled: bool) -> Self { - Self { - disabled, - collected: AtomicBool::new(false), - } - } - - async fn cache_channel_message( - &self, - ctx: &Context, - config: &Config, - channel: GuildChannel, - guild: &Guild, - bot_member: &Member, - ) { - if !channel.is_text_based() { - return; - } - - let is_ignored = std::iter::once(channel.id) - .chain(channel.parent_id) - .any(|id| config.message_cache.ignore_channel_ids.contains(&id)); - if is_ignored { - return; - } - - if !guild.user_permissions_in(&channel, bot_member).read_message_history() { - return; - } - - let messages = channel - .id - .messages_iter(&ctx) - .take(config.message_cache.limit) - .take_while(|x| future::ready(x.is_ok())) - .filter_map(|x| future::ready(x.ok())) - .collect::>() - .await; - - let mut data = ctx.data.write().await; - let cache = data.get_mut::().unwrap(); - let len = messages.len(); - cache.extend_messages(messages); - info!("Cached {len} messages for channel: {} ({})", channel.name, channel.id); - } -} - -#[async_trait] -impl EventHandler for Handler { - async fn cache_ready(&self, ctx: Context, _: Vec) { - if self.disabled || self.collected.swap(true, Ordering::Relaxed) { - return; - } - - let ctx_ref = &ctx; - let config = get_config(ctx_ref).await; - - for guild_id in &config.message_cache.target_guild_ids { - let guild = match guild_id.to_guild_cached(ctx_ref) { - Some(guild) => guild.clone(), - None => { - error!("Failed to get guild: {:#?}", guild_id); - continue; - } - }; - - let bot_id = ctx_ref.cache.current_user().id; - let Ok(bot_member) = guild.member(ctx_ref, bot_id).await else { - error!("Failed to get bot member for guild: {:#?}", guild_id); - continue; - }; - - let Ok(channels) = guild_id.channels(ctx_ref).await else { - error!("Failed to get channels for guild: {:#?}", guild_id); - continue; - }; - - let active_threads = guild.threads.clone(); - let _ = stream! { - for thread in active_threads { - yield thread; - } - - for (id, channel) in channels { - yield channel; - - for await thread in fetch_all_archived_public_thread(ctx_ref, id, None).await { - yield thread; - } - } - } - .map(|c| self.cache_channel_message(ctx_ref, &config, c, &guild, &bot_member)) - .buffer_unordered(20) - .collect::>() - .await; - } - info!("Cache ready!"); - } -} diff --git a/src/features/message_cache_handler.rs b/src/features/message_cache_handler.rs new file mode 100644 index 0000000..bc4627d --- /dev/null +++ b/src/features/message_cache_handler.rs @@ -0,0 +1,237 @@ +use std::sync::atomic::{AtomicBool, Ordering}; + +use async_stream::stream; +use futures::{StreamExt, future}; +use serenity::{ + all::{Context, Guild, GuildChannel, Member, prelude::CacheHttp}, + async_trait, + model::{ + Permissions, + channel::{GenericGuildChannelRef, GuildThread, Message}, + event::FullEvent, + id::{GenericChannelId, MessageId}, + }, + small_fixed_array::FixedString, +}; +use tracing::{error, info}; + +use crate::{ + app::{BotDataGetter, config::AppConfig}, + core::BotEventHandler, + utils::fetch_all_archived_public_thread, +}; + +enum ChannelWrapper { + Channel(GuildChannel), + Thread(GuildThread), +} + +impl ChannelWrapper { + fn is_text_based(&self) -> bool { + match self { + Self::Channel(c) => c.is_text_based(), + Self::Thread(_) => true, + } + } + + fn id(&self) -> GenericChannelId { + match self { + Self::Channel(c) => c.id.widen(), + Self::Thread(t) => t.id.widen(), + } + } + + fn parent_id(&self) -> Option { + match self { + Self::Channel(c) => c.parent_id, + Self::Thread(t) => Some(t.parent_id), + } + .map(|id| id.widen()) + } + + fn name(&self) -> FixedString { + (match self { + Self::Channel(c) => &c.base, + Self::Thread(t) => &t.base, + }) + .clone() + .name + } + + fn user_permission(&self, guild: &Guild, member: &Member) -> Option { + match self { + Self::Channel(c) => Some(guild.user_permissions_in(c, member)), + Self::Thread(t) => guild.channel(t.parent_id.into()).and_then(|c| match c { + GenericGuildChannelRef::Channel(gc) => Some(guild.user_permissions_in(gc, member)), + _ => None, + }), + } + } +} + +pub struct MessageCacheHandler { + disabled: bool, + collected: AtomicBool, +} + +impl MessageCacheHandler { + pub fn new(disabled: bool) -> Self { + Self { + disabled, + collected: AtomicBool::new(false), + } + } + + async fn cache_channel_message( + &self, + ctx: &Context, + config: &AppConfig, + channel: ChannelWrapper, + guild: &Guild, + bot_member: &Member, + ) { + if !channel.is_text_based() { + return; + } + + let is_ignored = [Some(channel.id()), channel.parent_id()] + .iter() + .filter_map(|id| *id) + .any(|id| config.message_cache.ignore_channel_ids.contains(&id.expect_channel())); + if is_ignored { + return; + } + + let has_read_message_history_permission = channel + .user_permission(guild, bot_member) + .map(|p| p.read_message_history()) + .unwrap_or(false); + if !has_read_message_history_permission { + return; + } + + let messages = channel + .id() + .messages_iter(&ctx.http()) + .take(config.message_cache.limit) + .take_while(|x| future::ready(x.is_ok())) + .filter_map(|x| future::ready(x.ok())) + .collect::>() + .await; + + let cache = ctx.message_cache(); + let len = messages.len(); + cache.extend_messages(messages); + info!( + "Cached {len} messages for channel: {} ({})", + channel.name(), + channel.id() + ); + } + + async fn handle_cache_ready(&self, ctx: &Context) { + if self.disabled || self.collected.swap(true, Ordering::Relaxed) { + return; + } + + let config = ctx.read_app_config().await; + + for guild_id in &config.message_cache.target_guild_ids { + let guild = match guild_id.to_guild_cached(&ctx.cache) { + Some(guild) => guild.clone(), + None => { + error!("Failed to get guild: {}", guild_id); + continue; + } + }; + + let bot_id = ctx.cache.current_user().id; + + let Ok(bot_member) = guild.member(ctx.http(), bot_id).await else { + error!("Failed to get bot member for guild: {}", guild_id); + continue; + }; + + let Ok(channels) = guild_id.channels(ctx.http()).await else { + error!("Failed to get channels for guild: {}", guild_id); + continue; + }; + + let active_threads = guild.threads.clone(); + let _ = stream! { + for thread in active_threads { + yield ChannelWrapper::Thread(thread); + } + + for channel in channels { + let id = channel.id; + yield ChannelWrapper::Channel(channel); + + for await thread in fetch_all_archived_public_thread(ctx, id, None).await { + yield ChannelWrapper::Thread(thread); + } + } + } + .map(|c| self.cache_channel_message(ctx, &config, c, &guild, &bot_member)) + .buffer_unordered(20) + .collect::>() + .await; + } + info!("Cache ready!"); + } + + async fn handle_message_create(&self, ctx: &Context, new_message: &Message) { + let cache = ctx.message_cache(); + cache.insert(new_message.clone()); + } + + async fn handle_message_update(&self, ctx: &Context, new_message: &Message) { + let cache = ctx.message_cache(); + cache.insert(new_message.clone()); + } + + async fn handle_message_delete(&self, ctx: &Context, channel_id: &GenericChannelId, message_id: &MessageId) { + let cache = ctx.message_cache(); + cache.remove(*channel_id, *message_id); + } + + async fn handle_message_delete_bulk( + &self, + ctx: &Context, + channel_id: &GenericChannelId, + message_ids: &[MessageId], + ) { + let cache = ctx.message_cache(); + cache.remove_all(*channel_id, message_ids); + } +} + +#[async_trait] +impl BotEventHandler for MessageCacheHandler { + async fn dispatch(&self, ctx: &Context, event: &FullEvent) { + match event { + FullEvent::CacheReady { .. } => self.handle_cache_ready(ctx).await, + + FullEvent::Message { new_message, .. } => self.handle_message_create(ctx, new_message).await, + + FullEvent::MessageUpdate { event, .. } => self.handle_message_update(ctx, &event.message).await, + + FullEvent::MessageDelete { + channel_id, + deleted_message_id, + .. + } => self.handle_message_delete(ctx, channel_id, deleted_message_id).await, + + FullEvent::MessageDeleteBulk { + channel_id, + multiple_deleted_messages_ids, + .. + } => { + self.handle_message_delete_bulk(ctx, channel_id, multiple_deleted_messages_ids) + .await + } + + _ => {} + } + } +} diff --git a/src/features/message_logging/embed_builder.rs b/src/features/message_logging/embed_builder.rs new file mode 100644 index 0000000..a6b78fb --- /dev/null +++ b/src/features/message_logging/embed_builder.rs @@ -0,0 +1,128 @@ +use std::ops::{Deref, Not}; + +use itertools::{Itertools, enumerate}; +use serenity::all::{CreateEmbed, EmbedMessageBuilding, Message, MessageBuilder, MessageId, MessageReferenceKind}; +use tracing::error; + +use crate::{extensions::MessageBuilderTimestampExt, utils::create_diff_lines_text}; + +pub(in crate::features::message_logging) fn build_reply_field<'a>( + embed: CreateEmbed<'a>, + message: &Message, +) -> CreateEmbed<'a> { + let Some(m_ref) = &message.message_reference else { + return embed; + }; + let id = m_ref.message_id.unwrap_or(MessageId::default()); + let (name, content) = match m_ref.kind { + MessageReferenceKind::Default => ("__**返信**__", "返信先: "), + MessageReferenceKind::Forward => ("__**転送**__", "転送元: "), + _ => ("__**不明**__", "不明な対象メッセージ: "), + }; + embed.field( + name, + MessageBuilder::new() + .push_bold_safe(content) + .push_safe(&*id.link(m_ref.channel_id, m_ref.guild_id).to_string()) + .push_safe(" ") + .push_mono_line_safe(&*id.to_string()) + .build(), + false, + ) +} + +pub(in crate::features::message_logging) fn build_poll_field<'a>( + embed: CreateEmbed<'a>, + message: &Message, +) -> CreateEmbed<'a> { + let Some(poll) = &message.poll else { + return embed; + }; + + let mut builder = MessageBuilder::new(); + builder = builder + .push_bold_safe("タイトル: ") + .push_line_safe(poll.question.text.as_deref().unwrap_or("<不明なタイトル>")) + .push_bold_line_safe("回答:"); + + for (i, answer) in enumerate(&poll.answers) { + let Ok(i) = i.try_into() else { + error!("poll answer index must fit in u8"); + break; + }; + + let answer_text = answer.poll_media.text.as_deref().unwrap_or("<不明な回答>"); + builder = builder.push_safe(format!("- {answer_text}").as_str()); + + builder = if let Some(results) = &poll.results { + builder.push_line_safe(format!(": {}票", results.answer_counts[i].count).as_str()) + } else { + builder.push_safe("\n") + }; + } + + if let Some(expiry) = poll.expiry { + builder = builder + .push_bold_safe("有効期限: ") + .push_short_date_medium_timestamp(expiry); + } + + embed.field("__**投票**__", builder.build(), false) +} + +pub(in crate::features::message_logging) fn build_diff_field<'a>( + mut embed: CreateEmbed<'a>, + old_content: &str, + new_content: &str, +) -> CreateEmbed<'a> { + if old_content.is_empty() { + return embed; + } + + let diff = create_diff_lines_text(old_content, new_content); + let chunks = diff.lines().peekable().batching(|lines| { + let mut str = String::new(); + while let Some(line) = lines.next_if(|&l| str.len() + l.len() <= 1000) { + str.push_str(line); + str.push('\n'); + } + str.is_empty().not().then_some(str) + }); + + for (i, chunk) in enumerate(chunks) { + let changed = MessageBuilder::new() + .push_codeblock_safe(chunk.as_str(), Some("diff")) + .build(); + + embed = embed.field(if i == 0 { "__**テキスト差分**__" } else { "" }, changed, false) + } + embed +} + +pub(in crate::features::message_logging) fn build_attachments_field<'a>( + embed: CreateEmbed<'a>, + message: &Message, +) -> CreateEmbed<'a> { + if message.attachments.is_empty() { + return embed; + } + let mut builder = MessageBuilder::new(); + for attachment in &message.attachments { + builder = builder + .push_safe("- ") + .push_named_link_safe(attachment.filename.deref(), attachment.url.deref()) + .push_safe("\n"); + } + embed.field("__**添付ファイル**__", builder.build(), false) +} + +pub(in crate::features::message_logging) fn build_embed<'a>( + message: &Message, + new_content: &str, + mut embed: CreateEmbed<'a>, +) -> CreateEmbed<'a> { + embed = build_reply_field(embed, message); + embed = build_poll_field(embed, message); + embed = build_diff_field(embed, &message.content, new_content); + build_attachments_field(embed, message) +} diff --git a/src/features/message_logging/handler.rs b/src/features/message_logging/handler.rs new file mode 100644 index 0000000..ff6e244 --- /dev/null +++ b/src/features/message_logging/handler.rs @@ -0,0 +1,122 @@ +use serenity::{ + all::{Context, CreateEmbed, Mentionable, Message, MessageBuilder, MessageId, Timestamp}, + model::{event::FullEvent, id::GenericChannelId}, +}; +use tracing::{error, info}; +use valine_bot_macros::event_handler; + +use crate::{ + app::{BotDataGetter, utils::message_cache::get_cached_message}, + extensions::MessageBuilderTimestampExt, + features::message_logging::{embed_builder::build_embed, log_type::LogType}, + utils::{create_safe_message, send_message}, +}; + +async fn create_and_send_log(ctx: &Context, message: &Message, log_type: LogType) { + if message.author.bot() { + return; + } + + let description = MessageBuilder::new() + .push_bold_safe("メンバー: ") + .mention(&message.author.mention()) + .push_safe(" ") + .push_mono_line_safe(&*message.author.id.to_string()) + .push_bold_safe("メッセージ: ") + .push_safe(&*message.id.link(message.channel_id, message.guild_id).to_string()) + .push_safe(" ") + .push_mono_line_safe(&*message.id.to_string()) + .push_bold_safe("メッセージ送信日時: ") + .push_short_date_medium_timestamp_line(message.timestamp) + .push_bold_safe(format!("{}日時: ", log_type.name()).as_str()) + .push_short_date_medium_timestamp(Timestamp::now()) + .build(); + + let mut embed = CreateEmbed::new() + .title(log_type.title()) + .description(description) + .color(log_type.color()) + .thumbnail( + message + .author + .avatar_url() + .unwrap_or("https://cdn.discordapp.com/embed/avatars/0.png".to_string()), + Some("ユーザーアイコン".into()), + ); + + let new_content = log_type.new_content().unwrap_or(""); + embed = build_embed(message, new_content, embed); + send_log(ctx, embed).await; +} + +async fn send_log<'a>(ctx: &Context, embed: CreateEmbed<'a>) { + let config = ctx.read_app_config().await; + let log = create_safe_message().add_embed(embed); + let _ = send_message(ctx, &config.message_logging.channel_id, log).await; +} + +async fn handle_message_update(ctx: &Context, old: &Option, new_message: &Message) { + let Some(message) = get_cached_message(ctx, new_message.channel_id, new_message.id).await else { + return error!("Failed to get message: {}", new_message.id); + }; + let message = old.as_ref().unwrap_or(&message); + + if message.content == new_message.content { + return; + } + + // update_cache(ctx, &new_message).await; + create_and_send_log( + ctx, + message, + LogType::Edit { + new_content: new_message.content.to_string(), + }, + ) + .await; +} + +async fn handle_message_delete(ctx: &Context, channel_id: &GenericChannelId, deleted_message_id: &MessageId) { + info!("deleted_message_id: {deleted_message_id}"); + + let Some(message) = get_cached_message(ctx, *channel_id, *deleted_message_id).await else { + return error!("Failed to get message: {deleted_message_id}"); + }; + + create_and_send_log(ctx, &message, LogType::Delete).await; +} + +async fn handle_message_delete_bulk(ctx: &Context, channel_id: &GenericChannelId, deleted_message_ids: &[MessageId]) { + for message_id in deleted_message_ids { + let Some(message) = get_cached_message(ctx, *channel_id, *message_id).await else { + error!("Failed to get message: {message_id}"); + continue; + }; + create_and_send_log(ctx, &message, LogType::Delete).await; + } +} + +#[event_handler] +pub async fn handle_message_logging_event(ctx: &Context, event: &FullEvent) { + match event { + FullEvent::MessageUpdate { + old_if_available, + event, + .. + } => handle_message_update(ctx, old_if_available, &event.message).await, + + FullEvent::MessageDelete { + channel_id, + deleted_message_id, + .. + } => handle_message_delete(ctx, channel_id, deleted_message_id).await, + + FullEvent::MessageDeleteBulk { + channel_id, + multiple_deleted_messages_ids, + .. + } => handle_message_delete_bulk(ctx, channel_id, multiple_deleted_messages_ids).await, + + _ => {} + } +} diff --git a/src/features/message_logging/log_type.rs b/src/features/message_logging/log_type.rs new file mode 100644 index 0000000..9999913 --- /dev/null +++ b/src/features/message_logging/log_type.rs @@ -0,0 +1,33 @@ +use serenity::model::Color; + +pub(in crate::features::message_logging) enum LogType { + Edit { new_content: String }, + Delete, +} + +impl LogType { + pub fn name(&self) -> &'static str { + match self { + LogType::Edit { .. } => "編集", + LogType::Delete => "削除", + } + } + + pub fn title(&self) -> String { + format!("メッセージ{}ログ", self.name()) + } + + pub fn color(&self) -> Color { + match self { + LogType::Edit { .. } => Color::ORANGE, + LogType::Delete => Color::RED, + } + } + + pub fn new_content(&self) -> Option<&str> { + match self { + LogType::Edit { new_content } => Some(new_content), + LogType::Delete => None, + } + } +} diff --git a/src/features/message_logging/mod.rs b/src/features/message_logging/mod.rs new file mode 100644 index 0000000..29a736c --- /dev/null +++ b/src/features/message_logging/mod.rs @@ -0,0 +1,5 @@ +mod embed_builder; +mod handler; +mod log_type; + +pub use handler::handle_message_logging_event; diff --git a/src/features/mod.rs b/src/features/mod.rs index a9427e9..f880524 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -1,35 +1,32 @@ mod admin; mod auth; mod honeypot; -// mod logging; -// mod message_cache; +mod message_cache_handler; +mod message_logging; mod pin; // mod question; // mod thread_auto_invite; -// pub use logging::Handler as LoggingHandler; -// pub use message_cache::Handler as MessageCacheHandler; -// pub use question::Handler as QuestionHandler; -// pub use thread_auto_invite::ThreadAutoInviteHandler; - -// pub use message_cache::{MessageCache, MessageCacheType}; - use std::borrow::Cow; use crate::{ - app::AppCommand, + app::{AppCommand, config::AppConfig}, core::BotEventHandlers, features::{ auth::{AutoKickEventHandler, KeywordAuthEventHandler}, honeypot::handle_honeypot_event, + message_cache_handler::MessageCacheHandler, + message_logging::handle_message_logging_event, }, }; -pub fn event_handlers() -> BotEventHandlers { +pub fn event_handlers(config: &AppConfig) -> BotEventHandlers { BotEventHandlers::new() .add(handle_honeypot_event) + .add(handle_message_logging_event) .add(KeywordAuthEventHandler::new()) .add(AutoKickEventHandler::new()) + .add(MessageCacheHandler::new(config.message_cache.disabled)) } pub fn commands() -> Vec { diff --git a/src/main.rs b/src/main.rs index f2afde7..2411b4f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,7 +58,7 @@ async fn main() -> Result<(), AppError> { let mut client = create_client( config.bot.token.clone(), intents, - event_handlers().add(MainEventHandler::new()), + event_handlers(&config).add(MainEventHandler::new()), ) .framework(Box::new(framework)) // .event_handler(features::LoggingHandler) diff --git a/src/utils.rs b/src/utils.rs index 5e41c43..b8f0203 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,7 @@ use std::{borrow::Cow, time::Duration}; +use async_stream::stream; +use futures::Stream; use itertools::Itertools; use serenity::{ Result, @@ -7,6 +9,7 @@ use serenity::{ builder::{ CreateComponent, CreateInteractionResponse, CreateInteractionResponseMessage, CreateModal, CreateModalComponent, }, + model::channel::GuildThread, }; use similar::{Algorithm, ChangeTag, TextDiff}; use tracing::error; @@ -145,83 +148,45 @@ pub async fn send_message<'a>(ctx: &Context, channel_id: &ChannelId, builder: Cr } } -// pub async fn get_cached_message(ctx: &Context, channel_id: ChannelId, message_id: MessageId) -> Option { -// if let Some(m) = ctx.cache.message(channel_id, message_id) { -// return Some(m.clone()); -// } - -// let data = ctx.data.read().await; -// let cache = data.get::().unwrap(); -// if let Some(m) = cache.get(channel_id, message_id) { -// return Some(m); -// } - -// None -// } - -// /** -// 指定されたチャンネルのアーカイブされたパブリックスレッドを取得します。 -// - `ChannelId::get_archived_public_threads`は Discord の要求に従っていないので、自前で実装しています。 -// - Serenity の next ブランチで修正済み -// */ -// pub async fn fetch_channel_archived_public_threads( -// http: impl AsRef, -// channel_id: ChannelId, -// before: Option, -// limit: Option, -// ) -> Result { -// let mut params = vec![]; -// if let Some(before) = before { -// params.push(("before", before.to_string())); -// } -// if let Some(limit) = limit { -// params.push(("limit", limit.to_string())); -// } - -// http.as_ref() -// .fire(Request::new(Route::ChannelArchivedPublicThreads { channel_id }, LightMethod::Get).params(Some(params))) -// .await -// } +/** +指定されたチャンネルのアーカイブされたパブリックスレッドをすべて取得します。 + */ +pub async fn fetch_all_archived_public_thread( + ctx: &Context, + channel_id: ChannelId, + max_retries: Option, +) -> impl Stream { + let max_retries = max_retries.unwrap_or(5); + Box::pin(stream! { + let mut retries_left = max_retries; + let mut before = None; + loop { + let thread_data = match ctx.http.get_channel_archived_public_threads(channel_id, before, Some(100)).await { + Ok(data) => data, + Err(_) => { + if retries_left == 0 { + break; + } else { + retries_left -= 1; + continue; + } + } + }; + + before = thread_data.threads + .last() + .and_then(|last| last.thread_metadata.archive_timestamp); + + for channel in thread_data.threads { + yield channel; + } -// /** -// 指定されたチャンネルのアーカイブされたパブリックスレッドをすべて取得します。 -// */ -// pub async fn fetch_all_archived_public_thread( -// ctx: &Context, -// channel_id: ChannelId, -// max_retries: Option, -// ) -> impl Stream + '_ { -// let max_retries = max_retries.unwrap_or(5); -// Box::pin(stream! { -// let mut retries_left = max_retries; -// let mut before = None; -// loop { -// let thread_data = match fetch_channel_archived_public_threads(&ctx, channel_id, before, Some(100)).await { -// Ok(data) => data, -// Err(_) => { -// if retries_left == 0 { -// break; -// } else { -// retries_left -= 1; -// continue; -// } -// } -// }; - -// before = thread_data.threads -// .last() -// .and_then(|last| last.thread_metadata.unwrap().archive_timestamp); - -// for channel in thread_data.threads { -// yield channel; -// } - -// if !thread_data.has_more || before.is_none() { -// break; -// } -// } -// }) -// } + if !thread_data.has_more || before.is_none() { + break; + } + } + }) +} pub fn create_diff_lines_text(old: &str, new: &str) -> String { let diff = TextDiff::configure().algorithm(Algorithm::Myers).diff_lines(old, new); From ccf71b8b46c65d57844a3e86a92387e846bced1d Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sun, 14 Jun 2026 10:55:35 +0900 Subject: [PATCH 095/104] =?UTF-8?q?refactor!:=20=E7=8B=AC=E8=87=AA?= =?UTF-8?q?=E3=81=AE=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=AD?= =?UTF-8?q?=E3=83=A3=E3=83=83=E3=82=B7=E3=83=A5=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CacheHttp が実装された Context を渡すと、メッセージの取得時に自動でキャッシュに載るためこちらに変更 - チャンネルの除外機能を削除 - コンフィグでの最大メッセージ数の指定を削除 --- config.sample.toml | 4 - src/app/config.rs | 2 - src/app/data.rs | 11 +-- src/app/utils/message_cache.rs | 22 ------ src/app/utils/mod.rs | 2 +- src/core/message_cache.rs | 72 ----------------- src/core/mod.rs | 2 - src/features/message_cache_handler.rs | 100 +++--------------------- src/features/message_logging/handler.rs | 26 +++--- src/main.rs | 2 +- 10 files changed, 28 insertions(+), 215 deletions(-) delete mode 100644 src/app/utils/message_cache.rs delete mode 100644 src/core/message_cache.rs diff --git a/config.sample.toml b/config.sample.toml index 1bd51b9..d0abdb3 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -45,13 +45,9 @@ channel_id = "000000000000000000" [message_cache] # 過去メッセージのキャッシュを無効化するかどうか disabled = false -# キャッシュする過去メッセージの最大数 -limit = 1000 # 過去のメッセージのキャッシュを取るギルドIDのリスト # target_guild_ids = ["651286346861641735"] target_guild_ids = [ "000000000000000000" ] -# 過去メッセージのキャッシュ取らないチャンネルIDのリスト、親チャンネルを指定すると子のチャンネルも対象 (孫は対象外) -ignore_channel_ids = [ "000000000000000000" ] [pin] diff --git a/src/app/config.rs b/src/app/config.rs index 35659b0..c91dc09 100644 --- a/src/app/config.rs +++ b/src/app/config.rs @@ -73,9 +73,7 @@ pub struct MessageLoggingConfig { #[derive(Debug, Deserialize)] pub struct MessageCacheConfig { pub disabled: bool, - pub limit: usize, pub target_guild_ids: Vec, - pub ignore_channel_ids: Vec, } #[derive(Debug, Deserialize)] diff --git a/src/app/data.rs b/src/app/data.rs index e7e1416..0874e6b 100644 --- a/src/app/data.rs +++ b/src/app/data.rs @@ -3,21 +3,16 @@ use std::sync::Arc; use serenity::all::prelude::Context; use tokio::sync::RwLock; -use crate::{ - app::{AppContext, config::AppConfig}, - core::MessageCache, -}; +use crate::app::{AppContext, config::AppConfig}; pub struct BotData { config: RwLock>, - message_cache: Arc, } impl BotData { pub fn new(config: AppConfig) -> Self { Self { config: RwLock::new(Arc::new(config)), - message_cache: Arc::new(MessageCache::new()), } } } @@ -33,10 +28,6 @@ pub trait BotDataGetter { let data = self.bot_data(); *data.config.write().await = Arc::new(config); } - - fn message_cache(&self) -> Arc { - Arc::clone(&self.bot_data().message_cache) - } } impl BotDataGetter for Context { diff --git a/src/app/utils/message_cache.rs b/src/app/utils/message_cache.rs deleted file mode 100644 index e0e3c8e..0000000 --- a/src/app/utils/message_cache.rs +++ /dev/null @@ -1,22 +0,0 @@ -use serenity::{ - all::prelude::Context, - model::{ - channel::Message, - id::{GenericChannelId, MessageId}, - }, -}; - -use crate::app::BotDataGetter; - -pub async fn get_cached_message(ctx: &Context, channel_id: GenericChannelId, message_id: MessageId) -> Option { - if let Some(m) = ctx.cache.message(channel_id, message_id) { - return Some(m.clone()); - } - - let cache = ctx.message_cache(); - if let Some(m) = cache.get(channel_id, message_id) { - return Some(m); - } - - None -} diff --git a/src/app/utils/mod.rs b/src/app/utils/mod.rs index 4388eb2..8b13789 100644 --- a/src/app/utils/mod.rs +++ b/src/app/utils/mod.rs @@ -1 +1 @@ -pub mod message_cache; + diff --git a/src/core/message_cache.rs b/src/core/message_cache.rs deleted file mode 100644 index ce5eddb..0000000 --- a/src/core/message_cache.rs +++ /dev/null @@ -1,72 +0,0 @@ -use std::collections::HashMap; - -use dashmap::DashMap; -use itertools::Itertools; -use serenity::{ - all::{Message, MessageId}, - model::id::GenericChannelId, -}; - -pub struct MessageCache { - cache: DashMap>, -} - -impl MessageCache { - pub fn new() -> Self { - Self { cache: DashMap::new() } - } - - pub fn insert(&self, message: Message) { - let mut map = self.cache.entry(message.channel_id).or_default(); - map.insert(message.id, message); - } - - pub fn remove(&self, channel_id: GenericChannelId, message_id: MessageId) { - let mut map = self.cache.entry(channel_id).or_default(); - map.remove(&message_id); - } - - pub fn remove_all(&self, channel_id: GenericChannelId, message_ids: &[MessageId]) { - let mut map = self.cache.entry(channel_id).or_default(); - for id in message_ids { - map.remove(id); - } - } - - pub fn extend(&self, iter: impl IntoIterator) { - iter.into_iter() - .into_group_map_by(|(channel_id, _, _)| *channel_id) - .into_iter() - .map(|(channel_id, messages)| { - ( - channel_id, - messages - .into_iter() - .map(|(_, message_id, message)| (message_id, message)) - .collect::>(), - ) - }) - .for_each(|(channel_id, messages)| { - let mut map = self.cache.entry(channel_id).or_default(); - map.extend(messages); - }); - } - - pub fn extend_messages(&self, iter: impl IntoIterator) { - self.extend( - iter.into_iter() - .map(|message| (message.channel_id, message.id, message)), - ); - } - - pub fn get(&self, channel_id: GenericChannelId, message_id: MessageId) -> Option { - let map = self.cache.get(&channel_id)?; - map.get(&message_id).cloned() - } -} - -impl Default for MessageCache { - fn default() -> Self { - Self::new() - } -} diff --git a/src/core/mod.rs b/src/core/mod.rs index 2c9c398..e7bc732 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,7 +1,5 @@ mod client; mod event_handler; -mod message_cache; pub use client::create_client; pub use event_handler::{BotEventHandler, BotEventHandlers}; -pub use message_cache::MessageCache; diff --git a/src/features/message_cache_handler.rs b/src/features/message_cache_handler.rs index bc4627d..715747d 100644 --- a/src/features/message_cache_handler.rs +++ b/src/features/message_cache_handler.rs @@ -7,19 +7,15 @@ use serenity::{ async_trait, model::{ Permissions, - channel::{GenericGuildChannelRef, GuildThread, Message}, + channel::{GenericGuildChannelRef, GuildThread}, event::FullEvent, - id::{GenericChannelId, MessageId}, + id::GenericChannelId, }, small_fixed_array::FixedString, }; use tracing::{error, info}; -use crate::{ - app::{BotDataGetter, config::AppConfig}, - core::BotEventHandler, - utils::fetch_all_archived_public_thread, -}; +use crate::{app::BotDataGetter, core::BotEventHandler, utils::fetch_all_archived_public_thread}; enum ChannelWrapper { Channel(GuildChannel), @@ -41,14 +37,6 @@ impl ChannelWrapper { } } - fn parent_id(&self) -> Option { - match self { - Self::Channel(c) => c.parent_id, - Self::Thread(t) => Some(t.parent_id), - } - .map(|id| id.widen()) - } - fn name(&self) -> FixedString { (match self { Self::Channel(c) => &c.base, @@ -82,26 +70,11 @@ impl MessageCacheHandler { } } - async fn cache_channel_message( - &self, - ctx: &Context, - config: &AppConfig, - channel: ChannelWrapper, - guild: &Guild, - bot_member: &Member, - ) { + async fn cache_channel_message(&self, ctx: &Context, channel: ChannelWrapper, guild: &Guild, bot_member: &Member) { if !channel.is_text_based() { return; } - let is_ignored = [Some(channel.id()), channel.parent_id()] - .iter() - .filter_map(|id| *id) - .any(|id| config.message_cache.ignore_channel_ids.contains(&id.expect_channel())); - if is_ignored { - return; - } - let has_read_message_history_permission = channel .user_permission(guild, bot_member) .map(|p| p.read_message_history()) @@ -110,20 +83,17 @@ impl MessageCacheHandler { return; } - let messages = channel + // Context を渡すと Serenity 標準キャッシュに取得結果が載るようになる + let collected_count = channel .id() - .messages_iter(&ctx.http()) - .take(config.message_cache.limit) + .messages_iter(&ctx) .take_while(|x| future::ready(x.is_ok())) .filter_map(|x| future::ready(x.ok())) - .collect::>() + .count() .await; - let cache = ctx.message_cache(); - let len = messages.len(); - cache.extend_messages(messages); info!( - "Cached {len} messages for channel: {} ({})", + "Cached {collected_count} messages for channel: {} ({})", channel.name(), channel.id() ); @@ -172,66 +142,20 @@ impl MessageCacheHandler { } } } - .map(|c| self.cache_channel_message(ctx, &config, c, &guild, &bot_member)) + .map(|c| self.cache_channel_message(ctx, c, &guild, &bot_member)) .buffer_unordered(20) .collect::>() .await; } info!("Cache ready!"); } - - async fn handle_message_create(&self, ctx: &Context, new_message: &Message) { - let cache = ctx.message_cache(); - cache.insert(new_message.clone()); - } - - async fn handle_message_update(&self, ctx: &Context, new_message: &Message) { - let cache = ctx.message_cache(); - cache.insert(new_message.clone()); - } - - async fn handle_message_delete(&self, ctx: &Context, channel_id: &GenericChannelId, message_id: &MessageId) { - let cache = ctx.message_cache(); - cache.remove(*channel_id, *message_id); - } - - async fn handle_message_delete_bulk( - &self, - ctx: &Context, - channel_id: &GenericChannelId, - message_ids: &[MessageId], - ) { - let cache = ctx.message_cache(); - cache.remove_all(*channel_id, message_ids); - } } #[async_trait] impl BotEventHandler for MessageCacheHandler { async fn dispatch(&self, ctx: &Context, event: &FullEvent) { - match event { - FullEvent::CacheReady { .. } => self.handle_cache_ready(ctx).await, - - FullEvent::Message { new_message, .. } => self.handle_message_create(ctx, new_message).await, - - FullEvent::MessageUpdate { event, .. } => self.handle_message_update(ctx, &event.message).await, - - FullEvent::MessageDelete { - channel_id, - deleted_message_id, - .. - } => self.handle_message_delete(ctx, channel_id, deleted_message_id).await, - - FullEvent::MessageDeleteBulk { - channel_id, - multiple_deleted_messages_ids, - .. - } => { - self.handle_message_delete_bulk(ctx, channel_id, multiple_deleted_messages_ids) - .await - } - - _ => {} + if let FullEvent::CacheReady { .. } = event { + self.handle_cache_ready(ctx).await } } } diff --git a/src/features/message_logging/handler.rs b/src/features/message_logging/handler.rs index ff6e244..48019fa 100644 --- a/src/features/message_logging/handler.rs +++ b/src/features/message_logging/handler.rs @@ -2,11 +2,11 @@ use serenity::{ all::{Context, CreateEmbed, Mentionable, Message, MessageBuilder, MessageId, Timestamp}, model::{event::FullEvent, id::GenericChannelId}, }; -use tracing::{error, info}; +use tracing::error; use valine_bot_macros::event_handler; use crate::{ - app::{BotDataGetter, utils::message_cache::get_cached_message}, + app::BotDataGetter, extensions::MessageBuilderTimestampExt, features::message_logging::{embed_builder::build_embed, log_type::LogType}, utils::{create_safe_message, send_message}, @@ -55,17 +55,15 @@ async fn send_log<'a>(ctx: &Context, embed: CreateEmbed<'a>) { let _ = send_message(ctx, &config.message_logging.channel_id, log).await; } -async fn handle_message_update(ctx: &Context, old: &Option, new_message: &Message) { - let Some(message) = get_cached_message(ctx, new_message.channel_id, new_message.id).await else { +async fn handle_message_update(ctx: &Context, old_if_available: &Option, new_message: &Message) { + let Some(message) = old_if_available else { return error!("Failed to get message: {}", new_message.id); }; - let message = old.as_ref().unwrap_or(&message); if message.content == new_message.content { return; } - // update_cache(ctx, &new_message).await; create_and_send_log( ctx, message, @@ -77,10 +75,9 @@ async fn handle_message_update(ctx: &Context, old: &Option, new_message } async fn handle_message_delete(ctx: &Context, channel_id: &GenericChannelId, deleted_message_id: &MessageId) { - info!("deleted_message_id: {deleted_message_id}"); - - let Some(message) = get_cached_message(ctx, *channel_id, *deleted_message_id).await else { - return error!("Failed to get message: {deleted_message_id}"); + let message = match ctx.cache.message(*channel_id, *deleted_message_id) { + Some(message) => message.clone(), + None => return error!("Failed to get message: {deleted_message_id}"), }; create_and_send_log(ctx, &message, LogType::Delete).await; @@ -88,9 +85,12 @@ async fn handle_message_delete(ctx: &Context, channel_id: &GenericChannelId, del async fn handle_message_delete_bulk(ctx: &Context, channel_id: &GenericChannelId, deleted_message_ids: &[MessageId]) { for message_id in deleted_message_ids { - let Some(message) = get_cached_message(ctx, *channel_id, *message_id).await else { - error!("Failed to get message: {message_id}"); - continue; + let message = match ctx.cache.message(*channel_id, *message_id) { + Some(message) => message.clone(), + None => { + error!("Failed to get message: {message_id}"); + continue; + } }; create_and_send_log(ctx, &message, LogType::Delete).await; } diff --git a/src/main.rs b/src/main.rs index 2411b4f..0b63752 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,7 +53,7 @@ async fn main() -> Result<(), AppError> { | GatewayIntents::MESSAGE_CONTENT; let mut settings = CacheSettings::default(); - settings.max_messages = 1_000_000; + settings.max_messages = usize::MAX; let mut client = create_client( config.bot.token.clone(), From 31ae77ecf3fdf4fb3ffed4a9340c1621ecbb1020 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sun, 14 Jun 2026 19:05:14 +0900 Subject: [PATCH 096/104] =?UTF-8?q?feat:=20=E8=A7=A3=E6=B1=BA=E6=B8=88?= =?UTF-8?q?=E3=81=BF=E3=81=AE=E8=B3=AA=E5=95=8F=E3=82=B9=E3=83=AC=E3=83=83?= =?UTF-8?q?=E3=83=89=E5=90=8D=E3=81=AE=E5=85=88=E9=A0=AD=E3=82=92=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- config.sample.toml | 2 ++ src/config.rs | 1 + src/features/question/mod.rs | 7 ++++++- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 8e8a3e8..58dbb17 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ /THIRD_PARTY_LICENSES* .env -config.toml +config.toml* diff --git a/config.sample.toml b/config.sample.toml index 7cbe28c..34c9b53 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -87,5 +87,7 @@ role_ids = [ "000000000000000000", "000000000000000000" ] forum_id = "000000000000000000" # 解決済みのタグID solved_tag = "000000000000000000" +# 解決済みスレッド名の先頭につく文字 +solved_name_prefix = "✅️ " # 除外するタグIDのリスト exclude_tags = [ "000000000000000000" ] diff --git a/src/config.rs b/src/config.rs index 4aac491..9247f13 100644 --- a/src/config.rs +++ b/src/config.rs @@ -128,4 +128,5 @@ pub struct QuestionConfig { pub forum_id: ChannelId, pub exclude_tags: Vec, pub solved_tag: ForumTagId, + pub solved_name_prefix: String, } diff --git a/src/features/question/mod.rs b/src/features/question/mod.rs index cf03423..94b61e3 100644 --- a/src/features/question/mod.rs +++ b/src/features/question/mod.rs @@ -92,7 +92,12 @@ impl EventHandler for Handler { interaction .channel_id - .edit_thread(ctx.http(), EditThread::new().applied_tags(applied_tags)) + .edit_thread( + ctx.http(), + EditThread::new() + .name(format!("{}{}", config.solved_name_prefix, channel.name)) + .applied_tags(applied_tags), + ) .await .unwrap(); } From ab0786dc23381a911830b7960e1b1a02a330ff8c Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Sun, 14 Jun 2026 19:06:10 +0900 Subject: [PATCH 097/104] chore: bump version --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3d6bf10..9484473 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2541,7 +2541,7 @@ checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0" [[package]] name = "valine_bot" -version = "2.6.1" +version = "2.7.0" dependencies = [ "async-stream", "bpaf", diff --git a/Cargo.toml b/Cargo.toml index 5c234ac..aae623d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "valine_bot" -version = "2.6.1" +version = "2.7.0" edition = "2024" license = "MIT" publish = false From cb8afd27970fd7b559a2872630215a23e8632b90 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 15 Jun 2026 00:18:40 +0900 Subject: [PATCH 098/104] =?UTF-8?q?refactor:=20=E3=82=B9=E3=83=AC=E3=83=83?= =?UTF-8?q?=E3=83=89=E8=87=AA=E5=8B=95=E6=AD=A3=E4=BD=93=E3=81=AE=E5=86=8D?= =?UTF-8?q?=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/data.rs | 3 +- src/features/mod.rs | 10 +- src/features/thread_auto_invite/command.rs | 37 ++-- src/features/thread_auto_invite/handler.rs | 192 +++++++++++---------- src/features/thread_auto_invite/mod.rs | 2 +- src/main.rs | 5 - src/utils.rs | 103 ++++++----- 7 files changed, 177 insertions(+), 175 deletions(-) diff --git a/src/app/data.rs b/src/app/data.rs index 0874e6b..ebc497b 100644 --- a/src/app/data.rs +++ b/src/app/data.rs @@ -1,9 +1,10 @@ use std::sync::Arc; +use poise::ApplicationContext; use serenity::all::prelude::Context; use tokio::sync::RwLock; -use crate::app::{AppContext, config::AppConfig}; +use crate::app::{AppContext, AppError, config::AppConfig}; pub struct BotData { config: RwLock>, diff --git a/src/features/mod.rs b/src/features/mod.rs index f880524..544cf87 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -5,7 +5,7 @@ mod message_cache_handler; mod message_logging; mod pin; // mod question; -// mod thread_auto_invite; +mod thread_auto_invite; use std::borrow::Cow; @@ -17,6 +17,7 @@ use crate::{ honeypot::handle_honeypot_event, message_cache_handler::MessageCacheHandler, message_logging::handle_message_logging_event, + thread_auto_invite::handle_thread_auto_invite_event, }, }; @@ -24,6 +25,7 @@ pub fn event_handlers(config: &AppConfig) -> BotEventHandlers { BotEventHandlers::new() .add(handle_honeypot_event) .add(handle_message_logging_event) + .add(handle_thread_auto_invite_event) .add(KeywordAuthEventHandler::new()) .add(AutoKickEventHandler::new()) .add(MessageCacheHandler::new(config.message_cache.disabled)) @@ -35,9 +37,9 @@ pub fn commands() -> Vec { // question::question, pin::pin, admin::reload_config, - // thread_auto_invite::invite_thread, - // thread_auto_invite::add_invite_role, - // thread_auto_invite::remove_invite_role, + thread_auto_invite::invite_thread, + thread_auto_invite::add_invite_role, + thread_auto_invite::remove_invite_role, ]) } diff --git a/src/features/thread_auto_invite/command.rs b/src/features/thread_auto_invite/command.rs index ddb0033..dafc77e 100644 --- a/src/features/thread_auto_invite/command.rs +++ b/src/features/thread_auto_invite/command.rs @@ -1,14 +1,11 @@ -use poise::{ApplicationContext, say_reply}; +use poise::say_reply; use crate::{ - CommandData, PError, - config::get_config, - features::thread_auto_invite::handler::invite_thread_by_roles, + app::{AppContext, AppError, BotDataGetter}, + features::thread_auto_invite::handler::{handle_role_assignment, handle_role_removal, invite_thread_by_roles}, utils::{get_guild_members, has_authed_role, is_in_public_thread}, }; -use super::handler::Handler; - /// 招待用ロールを持ったメンバーを実行したスレッドに招待します。 #[poise::command( slash_command, @@ -19,20 +16,20 @@ use super::handler::Handler; check = "has_authed_role", check = "is_in_public_thread" )] -pub async fn invite_thread(ctx: ApplicationContext<'_, CommandData, PError>) -> Result<(), PError> { - let config = &get_config(ctx.serenity_context()).await.thread_auto_invite; +pub async fn invite_thread(ctx: AppContext<'_>) -> Result<(), AppError> { + let config = &ctx.read_app_config().await.thread_auto_invite; ctx.defer_ephemeral().await?; invite_thread_by_roles(ctx.serenity_context(), ctx.channel_id(), &config.role_ids).await; - say_reply(ctx.into(), "スレッドに招待しました。").await?; + say_reply(ctx, "スレッドに招待しました。").await?; Ok(()) } /// 表示用のロールを持ったメンバーに呼び出し用のロールを付与します #[poise::command(slash_command, guild_only, default_member_permissions = "MANAGE_ROLES")] -pub async fn add_invite_role(ctx: ApplicationContext<'_, CommandData, PError>) -> Result<(), PError> { +pub async fn add_invite_role(ctx: AppContext<'_>) -> Result<(), AppError> { let members = get_guild_members(ctx.serenity_context(), ctx.guild_id().unwrap()); - let config = &get_config(ctx.serenity_context()).await.thread_auto_invite; + let config = &ctx.read_app_config().await.thread_auto_invite; ctx.defer().await?; @@ -40,26 +37,26 @@ pub async fn add_invite_role(ctx: ApplicationContext<'_, CommandData, PError>) - for member in members { if config.role_ids.iter().any(|r| member.roles.contains(r)) { - Handler::handle_role_removal(ctx.serenity_context(), &member, config).await; + handle_role_removal(ctx.serenity_context(), &member, config).await; } if member.roles.contains(&config.display_role_id) { - Handler::handle_role_assignment(ctx.serenity_context(), &member, config).await; + handle_role_assignment(ctx.serenity_context(), &member, config).await; added_count += 1; continue; } } - say_reply(ctx.into(), format!("{added_count} 人に招待用ロールを付与しました。")).await?; + say_reply(ctx, format!("{added_count} 人に招待用ロールを付与しました。")).await?; Ok(()) } /// 表示用のロールを持ったメンバーに呼び出し用のロールを削除 #[poise::command(slash_command, guild_only, default_member_permissions = "MANAGE_ROLES")] -pub async fn remove_invite_role(ctx: ApplicationContext<'_, CommandData, PError>) -> Result<(), PError> { +pub async fn remove_invite_role(ctx: AppContext<'_>) -> Result<(), AppError> { let members = get_guild_members(ctx.serenity_context(), ctx.guild_id().unwrap()); - let config = &get_config(ctx.serenity_context()).await.thread_auto_invite; + let config = &ctx.read_app_config().await.thread_auto_invite; ctx.defer().await?; @@ -70,14 +67,10 @@ pub async fn remove_invite_role(ctx: ApplicationContext<'_, CommandData, PError> continue; } - Handler::handle_role_removal(ctx.serenity_context(), &member, config).await; + handle_role_removal(ctx.serenity_context(), &member, config).await; role_count += 1; } - say_reply( - ctx.into(), - format!("{role_count} 人から招待用ロールを全て削除しました。"), - ) - .await?; + say_reply(ctx, format!("{role_count} 人から招待用ロールを全て削除しました。")).await?; Ok(()) } diff --git a/src/features/thread_auto_invite/handler.rs b/src/features/thread_auto_invite/handler.rs index c91dca0..b4e916b 100644 --- a/src/features/thread_auto_invite/handler.rs +++ b/src/features/thread_auto_invite/handler.rs @@ -1,74 +1,76 @@ use itertools::Itertools; use serenity::{ - all::{ - ChannelId, ChannelType, Context, EditMessage, EventHandler, GuildChannel, GuildId, GuildMemberUpdateEvent, - Member, Mentionable, RoleId, - }, - async_trait, + all::{ChannelType, Context, EditMessage, GuildId, Member, Mentionable, RoleId, prelude::CacheHttp}, + model::{channel::GuildThread, event::FullEvent, id::GenericChannelId}, }; use tracing::{error, info}; +use valine_bot_macros::event_handler; use crate::{ - config::{ThreadAutoInviteConfig, get_config}, + app::{BotDataGetter, config::ThreadAutoInviteConfig}, utils::{await_initial_message, create_message}, }; -pub struct Handler; +async fn find_role(ctx: &Context, guild_id: GuildId, config: &ThreadAutoInviteConfig) -> Option { + let role_member_counts = ctx + .http + .get_guild_role_member_counts(guild_id) + .await + .map_err(|e| error!("Failed to get guild role member counts: {e:#?}")) + .ok()?; + + config.role_ids.iter().find_map(|&role_id| { + let count = *role_member_counts.get(&role_id)?; + (count < config.min_member_count).then_some(role_id) + }) +} -impl Handler { - pub fn new() -> Self { - Self - } +pub(in crate::features::thread_auto_invite) async fn handle_role_assignment( + ctx: &Context, + new: &Member, + config: &ThreadAutoInviteConfig, +) { + let Some(role) = find_role(ctx, new.guild_id, config).await else { + error!("No role found with count less than {}", config.min_member_count); + return; + }; - async fn find_role(ctx: &Context, guild_id: GuildId, config: &ThreadAutoInviteConfig) -> Option { - let role_member_counts = ctx - .http - .get_guild_role_member_counts(guild_id) - .await - .map_err(|e| error!("Failed to get guild role member counts: {e:#?}")) - .ok()?; - - config.role_ids.iter().find_map(|&role_id| { - let count = *role_member_counts.get(&role_id)?; - (count < config.min_member_count).then_some(role_id) - }) + if let Err(e) = new.add_role(ctx.http(), role, None).await { + error!("Failed to add role {role} to member {}: {e:#?}", new.user.id); + } else { + info!("Added role {role} to member {}", new.user.id); } +} - pub(super) async fn handle_role_assignment(ctx: &Context, new: &Member, config: &ThreadAutoInviteConfig) { - let Some(role) = Self::find_role(ctx, new.guild_id, config).await else { - error!("No role found with count less than {}", config.min_member_count); - return; - }; - - if let Err(e) = new.add_role(ctx, role).await { - error!("Failed to add role {role} to member {}: {e:#?}", new.user.id); +pub(in crate::features::thread_auto_invite) async fn handle_role_removal( + ctx: &Context, + old: &Member, + config: &ThreadAutoInviteConfig, +) { + let roles = old + .roles + .iter() + .filter(|r| config.role_ids.contains(r)) + .collect::>(); + + for role_id in roles { + if let Err(e) = old.remove_role(ctx.http(), *role_id, None).await { + error!("Failed to remove role {role_id} from member {}: {e:#?}", old.user.id); } else { - info!("Added role {role} to member {}", new.user.id); - } - } - - pub(super) async fn handle_role_removal(ctx: &Context, old: &Member, config: &ThreadAutoInviteConfig) { - let roles = old - .roles - .iter() - .filter(|r| config.role_ids.contains(r)) - .collect::>(); - - for role_id in roles { - if let Err(e) = old.remove_role(&ctx, role_id).await { - error!("Failed to remove role {role_id} from member {}: {e:#?}", old.user.id); - } else { - info!("Removed role {role_id} from member {}", old.user.id); - break; - } + info!("Removed role {role_id} from member {}", old.user.id); + break; } } } -pub async fn invite_thread_by_roles(ctx: &Context, thread_id: ChannelId, role_ids: &[RoleId]) { +pub(in crate::features::thread_auto_invite) async fn invite_thread_by_roles( + ctx: &Context, + thread_id: GenericChannelId, + role_ids: &[RoleId], +) { let mut message = { let msg = create_message("スレッド自動招待用メッセージ"); - match thread_id.send_message(&ctx, msg).await { + match thread_id.send_message(ctx.http(), msg).await { Ok(m) => m, Err(why) => return error!("Error sending message: {why:#?}"), } @@ -77,49 +79,61 @@ pub async fn invite_thread_by_roles(ctx: &Context, thread_id: ChannelId, role_id let content = role_ids.iter().map(|r| r.mention().to_string()).join(" "); let _ = message.edit(&ctx, EditMessage::new().content(content)).await; - let _ = message.delete(&ctx).await; + let _ = message.delete(ctx.http(), None).await; } -#[async_trait] -impl EventHandler for Handler { - async fn thread_create(&self, ctx: Context, thread: GuildChannel) { - if thread.kind == ChannelType::PrivateThread { - return; - } +async fn handle_thread_create(ctx: &Context, thread: &GuildThread, newly_created: &Option) { + info!("created: {newly_created:#?}"); - if await_initial_message(&ctx, &thread).await { - return; - } + if !newly_created.unwrap_or(false) { + return; + } - let config = &get_config(&ctx).await.thread_auto_invite; - invite_thread_by_roles(&ctx, thread.id, &config.role_ids).await; + if thread.base.kind == ChannelType::PrivateThread { + return; } - async fn guild_member_update( - &self, - ctx: Context, - old: Option, - new: Option, - _: GuildMemberUpdateEvent, - ) { - let Some(new) = new else { - error!("Member update event with no new member"); - return; - }; - let Some(old) = old else { - error!("Member update event with no old member"); - return; - }; - - let config = &get_config(&ctx).await.thread_auto_invite; - - let has_new_role = new.roles.contains(&config.display_role_id); - let has_old_role = old.roles.contains(&config.display_role_id); - - if has_new_role && !has_old_role { - Handler::handle_role_assignment(&ctx, &new, config).await; - } else if has_old_role && !has_new_role { - Handler::handle_role_removal(&ctx, &old, config).await; - } + if await_initial_message(ctx, thread).await { + return; + } + + let config = &ctx.read_app_config().await.thread_auto_invite; + invite_thread_by_roles(ctx, thread.id.widen(), &config.role_ids).await; +} + +async fn handle_guild_member_update(ctx: &Context, old: &Option, new: &Option) { + let Some(new) = new else { + error!("Member update event with no new member"); + return; + }; + let Some(old) = old else { + error!("Member update event with no old member"); + return; + }; + + let config = &ctx.read_app_config().await.thread_auto_invite; + + let has_new_role = new.roles.contains(&config.display_role_id); + let has_old_role = old.roles.contains(&config.display_role_id); + + if has_new_role && !has_old_role { + handle_role_assignment(ctx, new, config).await; + } else if has_old_role && !has_new_role { + handle_role_removal(ctx, old, config).await; + } +} + +#[event_handler] +pub async fn handle_thread_auto_invite_event(ctx: &Context, event: &FullEvent) { + match event { + FullEvent::ThreadCreate { + thread, newly_created, .. + } => handle_thread_create(ctx, thread, newly_created).await, + + FullEvent::GuildMemberUpdate { + old_if_available, new, .. + } => handle_guild_member_update(ctx, old_if_available, new).await, + + _ => {} } } diff --git a/src/features/thread_auto_invite/mod.rs b/src/features/thread_auto_invite/mod.rs index cd208d3..6cb7d37 100644 --- a/src/features/thread_auto_invite/mod.rs +++ b/src/features/thread_auto_invite/mod.rs @@ -2,4 +2,4 @@ mod command; mod handler; pub use command::{add_invite_role, invite_thread, remove_invite_role}; -pub use handler::Handler as ThreadAutoInviteHandler; +pub use handler::handle_thread_auto_invite_event; diff --git a/src/main.rs b/src/main.rs index 0b63752..bcfa9d4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -61,13 +61,8 @@ async fn main() -> Result<(), AppError> { event_handlers(&config).add(MainEventHandler::new()), ) .framework(Box::new(framework)) - // .event_handler(features::LoggingHandler) - // .event_handler(features::ThreadAutoInviteHandler::new()) - // .event_handler(features::QuestionHandler) - // .event_handler(features::MessageCacheHandler::new(config.message_cache.disabled)) .cache_settings(settings) .data(Arc::new(BotData::new(config))) - // .type_map_insert::(Arc::new(MessageCache::new())) .await .expect("Err creating client"); diff --git a/src/utils.rs b/src/utils.rs index b8f0203..1d7b0d4 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -9,7 +9,12 @@ use serenity::{ builder::{ CreateComponent, CreateInteractionResponse, CreateInteractionResponseMessage, CreateModal, CreateModalComponent, }, - model::channel::GuildThread, + collector::CollectMessages, + model::{ + channel::{ChannelType, GuildThread}, + guild::Member, + id::GuildId, + }, }; use similar::{Algorithm, ChangeTag, TextDiff}; use tracing::error; @@ -55,40 +60,28 @@ pub fn create_model<'a>( CreateInteractionResponse::Modal(CreateModal::new(custom_id, title).components(components)) } -// /** -// thread_create イベントにおいて、初期メッセージが送信されるか5秒経過するまで待機する - -// 初期メッセージが送信されると、falseを返し、既に初期メッセージが存在する場合、true を返す -// ```rs -// pub struct Handler; - -// #[async_trait] -// impl EventHandler for Handler { -// async fn thread_create(&self, ctx: Context, thread: GuildChannel) { -// if await_thread_create(&ctx, &thread).await { -// return; -// } - -// // 処理 -// } -// } -// ``` -// */ -// pub async fn await_initial_message(ctx: &Context, thread: &GuildChannel) -> bool { -// // Botがメッセージを送信すると二度イベントが発火するので、初期メッセージ送信後のイベントは無視する -// if thread.last_message_id.is_some() { -// return true; -// } - -// // 初期メッセージが送信されるか、5秒経つまで待機 -// thread -// .await_reply(&ctx.shard) -// .channel_id(thread.id) -// .author_id(thread.owner_id.unwrap()) -// .timeout(Duration::from_secs(5)) -// .await; -// false -// } +/** +thread_create イベントにおいて、初期メッセージが送信されるか5秒経過するまで待機する + +初期メッセージが送信されると、falseを返し、既に初期メッセージが存在する場合、true を返す + */ +pub async fn await_initial_message(ctx: &Context, thread: &GuildThread) -> bool { + // Botがメッセージを送信すると二度イベントが発火するので、初期メッセージ送信後のイベントは無視する + if thread.base.last_message_id.is_some() { + return true; + } + + // 初期メッセージが送信されるか、5秒経つまで待機 + thread + .id + .widen() + .collect_messages(ctx) + .channel_id(thread.id.widen()) + .author_id(thread.owner_id) + .timeout(Duration::from_secs(5)) + .await; + false +} /* 認証済みロールを持っているかどうかを確認します。 @@ -106,17 +99,21 @@ pub async fn has_authed_role(ctx: AppContext<'_>) -> Result { } } -// /* -// 実行した場所がパブリックスレッドであるかどうかを確認します。 -// */ -// pub async fn is_in_public_thread(ctx: PContext<'_>) -> Result { -// let channel = ctx.guild_channel().await.ok_or(BotError::IsNotInThread)?; -// match channel.kind { -// ChannelType::PublicThread | ChannelType::NewsThread => Ok(true), -// ChannelType::PrivateThread => Err(BotError::IsPrivateThread.into()), -// _ => Err(BotError::IsNotInThread.into()), -// } -// } +/* +実行した場所がパブリックスレッドであるかどうかを確認します。 +*/ +pub async fn is_in_public_thread(ctx: AppContext<'_>) -> Result { + let thread = ctx + .channel() + .await + .and_then(|t| t.thread()) + .ok_or(BotError::IsNotInThread)?; + match thread.base.kind { + ChannelType::PublicThread | ChannelType::NewsThread => Ok(true), + ChannelType::PrivateThread => Err(BotError::IsPrivateThread.into()), + _ => Err(BotError::IsNotInThread.into()), + } +} const UNITS: [(u64, &str); 4] = [(86400, "日"), (3600, "時間"), (60, "分"), (1, "秒")]; @@ -199,10 +196,10 @@ pub fn create_diff_lines_text(old: &str, new: &str) -> String { .join("") } -// pub fn get_guild_members(ctx: &Context, guild_id: GuildId) -> impl Iterator { -// guild_id -// .to_guild_cached(ctx) -// .map(|guild| guild.members.clone().into_values()) -// .into_iter() -// .flatten() -// } +pub fn get_guild_members(ctx: &Context, guild_id: GuildId) -> impl Iterator { + guild_id + .to_guild_cached(&ctx.cache) + .map(|guild| guild.members.clone()) + .into_iter() + .flatten() +} From e529e57c1040dd8f7931ccb4003cc298769f2f71 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 15 Jun 2026 02:17:42 +0900 Subject: [PATCH 099/104] =?UTF-8?q?refactor:=20=E8=B3=AA=E5=95=8F=E6=A9=9F?= =?UTF-8?q?=E8=83=BD=E3=82=92=E5=86=8D=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/data.rs | 8 +- src/app/mod.rs | 2 +- src/app/types.rs | 1 + src/features/mod.rs | 25 ++- src/features/question/command.rs | 56 +++--- src/features/question/mod.rs | 184 ++++++++++-------- .../question/question_creation_handler.rs | 40 ++-- 7 files changed, 172 insertions(+), 144 deletions(-) diff --git a/src/app/data.rs b/src/app/data.rs index ebc497b..8bb6940 100644 --- a/src/app/data.rs +++ b/src/app/data.rs @@ -4,7 +4,7 @@ use poise::ApplicationContext; use serenity::all::prelude::Context; use tokio::sync::RwLock; -use crate::app::{AppContext, AppError, config::AppConfig}; +use crate::app::{AppApplicationContext, AppContext, AppError, config::AppConfig}; pub struct BotData { config: RwLock>, @@ -42,3 +42,9 @@ impl<'a> BotDataGetter for AppContext<'a> { self.data() } } + +impl<'a> BotDataGetter for AppApplicationContext<'a> { + fn bot_data(&self) -> Arc { + self.data() + } +} diff --git a/src/app/mod.rs b/src/app/mod.rs index 1c83e49..298c393 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -10,4 +10,4 @@ pub mod utils; pub use data::{BotData, BotDataGetter}; pub use error::{BotError, on_error}; pub use event_handler::MainEventHandler; -pub use types::{AppCommand, AppContext, AppError}; +pub use types::{AppApplicationContext, AppCommand, AppContext, AppError}; diff --git a/src/app/types.rs b/src/app/types.rs index 35fd048..7844279 100644 --- a/src/app/types.rs +++ b/src/app/types.rs @@ -2,4 +2,5 @@ use crate::app::BotData; pub type AppError = Box; pub type AppContext<'a> = poise::Context<'a, BotData, AppError>; +pub type AppApplicationContext<'a> = poise::ApplicationContext<'a, BotData, AppError>; pub type AppCommand = poise::Command; diff --git a/src/features/mod.rs b/src/features/mod.rs index 544cf87..48e5944 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -4,7 +4,7 @@ mod honeypot; mod message_cache_handler; mod message_logging; mod pin; -// mod question; +mod question; mod thread_auto_invite; use std::borrow::Cow; @@ -17,6 +17,7 @@ use crate::{ honeypot::handle_honeypot_event, message_cache_handler::MessageCacheHandler, message_logging::handle_message_logging_event, + question::handle_question_event, thread_auto_invite::handle_thread_auto_invite_event, }, }; @@ -26,21 +27,25 @@ pub fn event_handlers(config: &AppConfig) -> BotEventHandlers { .add(handle_honeypot_event) .add(handle_message_logging_event) .add(handle_thread_auto_invite_event) + .add(handle_question_event) .add(KeywordAuthEventHandler::new()) .add(AutoKickEventHandler::new()) .add(MessageCacheHandler::new(config.message_cache.disabled)) } pub fn commands() -> Vec { - build_commands(vec![ - auth::create_keyword_button, - // question::question, - pin::pin, - admin::reload_config, - thread_auto_invite::invite_thread, - thread_auto_invite::add_invite_role, - thread_auto_invite::remove_invite_role, - ]) + build_commands( + [ + auth::create_keyword_button, + question::question, + pin::pin, + admin::reload_config, + thread_auto_invite::invite_thread, + thread_auto_invite::add_invite_role, + thread_auto_invite::remove_invite_role, + ] + .to_vec(), + ) } fn alias_command(base: fn() -> AppCommand, name: Cow<'static, str>) -> AppCommand { diff --git a/src/features/question/command.rs b/src/features/question/command.rs index 8d07726..2bf556b 100644 --- a/src/features/question/command.rs +++ b/src/features/question/command.rs @@ -1,35 +1,37 @@ -use poise::{ApplicationContext, CreateReply}; +use poise::CreateReply; use serenity::all::{ - ButtonStyle, Channel, CreateActionRow, CreateButton, CreateForumPost, CreateMessage, CreateSelectMenu, - CreateSelectMenuKind, CreateSelectMenuOption, ForumEmoji, ForumTag, ForumTagId, MessageBuilder, ReactionType, + ButtonStyle, CreateActionRow, CreateButton, CreateForumPost, CreateMessage, CreateSelectMenu, CreateSelectMenuKind, + CreateSelectMenuOption, ForumEmoji, ForumTag, ForumTagId, MessageBuilder, ReactionType, }; +use serenity::builder::CreateComponent; use tokio::sync::{RwLock, mpsc}; use tracing::debug; +use std::borrow::Cow; +use std::ops::Deref; use std::sync::Arc; use std::vec; -use crate::config::get_config; +use crate::app::{AppApplicationContext, AppError, BotDataGetter}; use crate::features::question::QUESTION_CLOSE_PREFIX; use crate::features::question::modal::{BasicQuestionData, DetailedQuestionData}; use crate::features::question::question_creation_handler::{CustomIds, QuestionCreationHandler}; use crate::utils::has_authed_role; -use crate::{CommandData, PError}; fn reaction_from_forum_emoji(emoji: &ForumEmoji) -> Option { match emoji.clone() { ForumEmoji::Id(emoji) => Some(emoji.into()), - ForumEmoji::Name(emoji) => Some(emoji.try_into().unwrap()), + ForumEmoji::Name(emoji) => Some(emoji.deref().try_into().unwrap()), _ => None, } } -fn create_select_menu( - custom_id: impl Into, +fn create_select_menu<'a>( + custom_id: impl Into>, available_tags: &[ForumTag], exclude_tags: &[ForumTagId], selected_tags: &[ForumTagId], -) -> CreateActionRow { +) -> CreateComponent<'a> { let options = available_tags .iter() .filter(|x| !exclude_tags.contains(&x.id)) @@ -43,17 +45,18 @@ fn create_select_menu( }) .collect::>(); + let length = options.len(); let select_menu = CreateSelectMenu::new( custom_id, CreateSelectMenuKind::String { - options: options.clone(), + options: options.into(), }, ) .min_values(1) - .max_values(options.len().try_into().unwrap()) + .max_values(length.try_into().unwrap()) .placeholder("タグを選択してください"); - CreateActionRow::SelectMenu(select_menu) + CreateComponent::ActionRow(CreateActionRow::select_menu(select_menu)) } /// Modに関する質問を行うためのフォーラムを作成します。 @@ -66,7 +69,7 @@ fn create_select_menu( required_bot_permissions = "CREATE_PUBLIC_THREADS", check = "has_authed_role" )] -pub async fn question(ctx: ApplicationContext<'_, CommandData, PError>) -> Result<(), PError> { +pub async fn question(ctx: AppApplicationContext<'_>) -> Result<(), AppError> { let custom_ids = Arc::new(CustomIds::new(ctx.id())); ctx.defer_ephemeral().await?; @@ -75,12 +78,12 @@ pub async fn question(ctx: ApplicationContext<'_, CommandData, PError>) -> Resul .label("質問を送信") .style(ButtonStyle::Success); - let config = &get_config(ctx.serenity_context()).await.question; - let Ok(Channel::Guild(channel)) = config.forum_id.to_channel(ctx.serenity_context()).await else { + let config = &ctx.read_app_config().await.question; + let Ok(channel) = config.forum_id.to_guild_channel(&ctx, ctx.guild_id()).await else { return Err("Failed to create forum channel".into()); }; - let buttons = vec![ + let buttons = &[ CreateButton::new(&custom_ids.basic) .label("質問の基本情報を入力") .style(ButtonStyle::Primary), @@ -92,14 +95,14 @@ pub async fn question(ctx: ApplicationContext<'_, CommandData, PError>) -> Resul const PROMPT: &str = "ボタンをクリックしてすべての情報を入力してください。\nセレクトボックスからタグを設定してください。\nまた、再度ボタンをクリックすると入力内容を編集することができます。"; let message = ctx - .send(CreateReply::default().content(PROMPT).components(vec![ + .send(CreateReply::default().content(PROMPT).components(&[ create_select_menu( &custom_ids.select_tag, &channel.available_tags, &config.exclude_tags, &[], ), - CreateActionRow::Buttons(buttons.clone()), + CreateComponent::ActionRow(CreateActionRow::buttons(&buttons.clone())), ])) .await?; @@ -138,18 +141,18 @@ pub async fn question(ctx: ApplicationContext<'_, CommandData, PError>) -> Resul ctx.into(), CreateReply::default() .content(format!("{PROMPT}\n{CONFIRM}")) - .components(vec![ + .components(&[ create_select_menu( &custom_ids.select_tag, &channel.available_tags, &config.exclude_tags, &forum_tag_ids.read().await, ), - CreateActionRow::Buttons({ + CreateComponent::ActionRow(CreateActionRow::buttons(&{ let mut c = buttons.clone(); c[2] = submit_button.clone(); c - }), + })), ]), ) .await?; @@ -165,24 +168,25 @@ pub async fn question(ctx: ApplicationContext<'_, CommandData, PError>) -> Resul let forum_tag_ids = forum_tag_ids.read().await; let msg = MessageBuilder::new() - .push_line(basic_data.to_string()) - .push_line(detailed_data.to_string()) + .push_line(&*basic_data.to_string()) + .push_line(&*detailed_data.to_string()) .push("\n質問者: ") .mention(&ctx.interaction.user) .build(); let forum_channel = channel + .id .create_forum_post( ctx.http(), CreateForumPost::new( &basic_data.title, CreateMessage::default() .content(msg) - .components(vec![CreateActionRow::Buttons(vec![ - CreateButton::new(format!("{QUESTION_CLOSE_PREFIX}:{}", ctx.interaction.user.id)) + .components(&[CreateComponent::ActionRow(CreateActionRow::buttons(&[ + CreateButton::new(&*format!("{QUESTION_CLOSE_PREFIX}:{}", ctx.interaction.user.id)) .label("質問を解決済みにする") .style(ButtonStyle::Danger), - ])]), + ]))]), ) .set_applied_tags(&*forum_tag_ids), ) diff --git a/src/features/question/mod.rs b/src/features/question/mod.rs index f902456..f500937 100644 --- a/src/features/question/mod.rs +++ b/src/features/question/mod.rs @@ -5,108 +5,120 @@ mod question_creation_handler; use std::{str::FromStr, time::Duration}; pub use command::question; + use serenity::{ all::{ - ButtonStyle, CacheHttp, Channel, ComponentInteractionCollector, ComponentInteractionDataKind, Context, - CreateActionRow, CreateButton, EditInteractionResponse, EditThread, EventHandler, Interaction, UserId, + ButtonStyle, CacheHttp, ComponentInteractionCollector, ComponentInteractionDataKind, Context, CreateActionRow, + CreateButton, EditInteractionResponse, EditThread, Interaction, UserId, }, - async_trait, + builder::CreateComponent, + model::event::FullEvent, }; use tracing::error; +use valine_bot_macros::event_handler; -use crate::{config::get_config, utils::create_interaction_message}; +use crate::{app::BotDataGetter, utils::create_interaction_message}; pub static QUESTION_CLOSE_PREFIX: &str = "close_question_forum"; -pub struct Handler; - -#[async_trait] -impl EventHandler for Handler { - async fn interaction_create(&self, ctx: Context, interaction: Interaction) { - let Interaction::Component(interaction) = interaction else { - return; - }; - let ComponentInteractionDataKind::Button = interaction.data.kind else { - return; - }; - let custom_id = &interaction.data.custom_id; - if !custom_id.starts_with(QUESTION_CLOSE_PREFIX) { - return; - } - - let (_, author_id) = custom_id.split_at(QUESTION_CLOSE_PREFIX.len() + 1); - let author_id = UserId::from_str(author_id).unwrap(); - if author_id != interaction.user.id { - return; - } - - let config = &get_config(&ctx).await.question; - let Ok(Channel::Guild(channel)) = interaction.channel_id.to_channel(&ctx).await else { - return error!("Failed to get channel: {:#?}", interaction.channel_id); - }; - if channel.applied_tags.contains(&config.solved_tag) { - let _ = interaction - .create_response(ctx.http(), create_interaction_message("既に解決済みです。", true, None)) - .await; - } - - let confirm_custom_id = format!("close_question_confirm:{}", interaction.id); - let cancel_custom_id = format!("close_question_cancel:{}", interaction.id); +async fn handle_interaction_create(ctx: &Context, interaction: &Interaction) { + let Interaction::Component(interaction) = interaction else { + return; + }; + let ComponentInteractionDataKind::Button = interaction.data.kind else { + return; + }; + let custom_id = &interaction.data.custom_id; + if !custom_id.starts_with(QUESTION_CLOSE_PREFIX) { + return; + } + let (_, author_id) = custom_id.split_at(QUESTION_CLOSE_PREFIX.len() + 1); + let author_id = UserId::from_str(author_id).unwrap(); + if author_id != interaction.user.id { + return; + } + + let config = &ctx.read_app_config().await.question; + let Ok(thread) = interaction + .channel_id + .expect_thread() + .to_thread(&ctx, interaction.guild_id) + .await + else { + return error!("Failed to get channel: {:#?}", interaction.channel_id); + }; + if thread.applied_tags.contains(&config.solved_tag) { let _ = interaction - .create_response( - ctx.http(), - create_interaction_message( - "本当に質問を終了しますか?", - true, - Some(vec![CreateActionRow::Buttons( - [ - CreateButton::new(&confirm_custom_id) - .label("はい") - .emoji('✅') - .style(ButtonStyle::Danger), - CreateButton::new(&cancel_custom_id) - .label("いいえ") - .emoji('❎') - .style(ButtonStyle::Success), - ] - .to_vec(), - )]), - ), - ) + .create_response(ctx.http(), create_interaction_message("既に解決済みです。", true, None)) .await; + } - let res = ComponentInteractionCollector::new(&ctx.shard) - .custom_ids(vec![confirm_custom_id.clone(), cancel_custom_id.clone()]) - .timeout(Duration::from_secs(60)) - .await; + let confirm_custom_id = format!("close_question_confirm:{}", interaction.id); + let cancel_custom_id = format!("close_question_cancel:{}", interaction.id); - let (confirmed, text) = match res { - Some(i) if i.data.custom_id == confirm_custom_id => (true, "質問を解決済みにしました。"), - _ => (false, "キャンセルしました。"), - }; - - if confirmed { - let mut applied_tags = channel.applied_tags.clone(); - applied_tags.push(config.solved_tag); - - interaction - .channel_id - .edit_thread( - ctx.http(), - EditThread::new() - .name(format!("{}{}", config.solved_name_prefix, channel.name)) - .applied_tags(applied_tags), - ) - .await - .unwrap(); - } + let _ = interaction + .create_response( + ctx.http(), + create_interaction_message( + "本当に質問を終了しますか?", + true, + Some(&[CreateComponent::ActionRow(CreateActionRow::buttons(&[ + CreateButton::new(&confirm_custom_id) + .label("はい") + .emoji('✅') + .style(ButtonStyle::Danger), + CreateButton::new(&cancel_custom_id) + .label("いいえ") + .emoji('❎') + .style(ButtonStyle::Success), + ]))]), + ), + ) + .await; - let _ = interaction - .edit_response( + let res = ComponentInteractionCollector::new(&ctx) + .custom_ids( + [confirm_custom_id.clone(), cancel_custom_id] + .map(|id| id.try_into().unwrap()) + .into(), + ) + .timeout(Duration::from_secs(60)) + .await; + + let (confirmed, text) = match res { + Some(i) if i.data.custom_id == confirm_custom_id => (true, "質問を解決済みにしました。"), + _ => (false, "キャンセルしました。"), + }; + + if confirmed { + let mut applied_tags = thread.applied_tags.to_vec(); + applied_tags.push(config.solved_tag); + + interaction + .channel_id + .expect_thread() + .edit( ctx.http(), - EditInteractionResponse::new().content(text).components(vec![]), + EditThread::new() + .name(format!("{}{}", config.solved_name_prefix, thread.base.name)) + .applied_tags(applied_tags), ) - .await; + .await + .unwrap(); + } + + let _ = interaction + .edit_response( + ctx.http(), + EditInteractionResponse::new().content(text).components(vec![]), + ) + .await; +} + +#[event_handler] +pub async fn handle_question_event(ctx: &Context, event: &FullEvent) { + if let FullEvent::InteractionCreate { interaction, .. } = event { + handle_interaction_create(ctx, interaction).await; } } diff --git a/src/features/question/question_creation_handler.rs b/src/features/question/question_creation_handler.rs index 382e6e0..19c7021 100644 --- a/src/features/question/question_creation_handler.rs +++ b/src/features/question/question_creation_handler.rs @@ -7,6 +7,7 @@ use serenity::{ ComponentInteractionDataKind, Context, ForumTagId, ModalInteractionCollector, ModalInteractionData, }, futures::StreamExt, + small_fixed_array::{FixedArray, FixedString}, }; use tokio::sync::{RwLock, mpsc}; use tracing::error; @@ -14,29 +15,34 @@ use tracing::error; use super::modal::{BasicQuestionData, DetailedQuestionData}; pub struct CustomIds { - pub basic: String, - pub detailed: String, - pub select_tag: String, - pub submit: String, + pub basic: FixedString, + pub detailed: FixedString, + pub select_tag: FixedString, + pub submit: FixedString, } impl CustomIds { pub fn new(id: u64) -> Self { Self { - basic: format!("open_basic_question_modal:{id}"), - detailed: format!("open_detailed_question_modal:{id}"), - select_tag: format!("question_select_tag:{id}"), - submit: format!("question_submit:{id}"), + basic: format!("open_basic_question_modal:{id}").try_into().unwrap(), + detailed: format!("open_detailed_question_modal:{id}").try_into().unwrap(), + select_tag: format!("question_select_tag:{id}").try_into().unwrap(), + submit: format!("question_submit:{id}").try_into().unwrap(), } } - pub fn to_vec(&self) -> Vec { - vec![ + pub fn to_vec(&self) -> Vec { + [ self.basic.clone(), self.detailed.clone(), self.select_tag.clone(), self.submit.clone(), ] + .to_vec() + } + + pub fn to_fixed_array(&self) -> FixedArray { + self.to_vec().try_into().unwrap() } } @@ -78,13 +84,7 @@ impl QuestionCreationHandler { } async fn parse_response(&self, data: &ModalInteractionData) -> Option { - match M::parse(data.clone()) { - Ok(data) => Some(data), - Err(e) => { - error!("Failed to parse modal data: {e:#?}, {data:#?}"); - None - } - } + Some(M::parse(data.clone())) } pub fn handle_component_interaction(&self) { @@ -95,8 +95,8 @@ impl QuestionCreationHandler { } async fn _handle_component_interaction(self) { - let mut stream = ComponentInteractionCollector::new(self.ctx.shard.clone()) - .custom_ids(self.custom_ids.to_vec()) + let mut stream = ComponentInteractionCollector::new(&self.ctx) + .custom_ids(self.custom_ids.to_fixed_array()) .timeout(TIMEOUT) .stream(); let http = self.ctx.http(); @@ -148,7 +148,7 @@ impl QuestionCreationHandler { } async fn _handle_modal_interaction(self) { - let mut stream = ModalInteractionCollector::new(self.ctx.shard.clone()) + let mut stream = ModalInteractionCollector::new(&self.ctx) .custom_ids(self.custom_ids.to_vec()) .timeout(TIMEOUT) .stream(); From 549b82e4a1a6fc80ce81765d18f0d612a3e51be1 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 15 Jun 2026 02:24:47 +0900 Subject: [PATCH 100/104] =?UTF-8?q?feat:=20=E3=83=97=E3=83=AC=E3=83=95?= =?UTF-8?q?=E3=82=A3=E3=83=83=E3=82=AF=E3=82=B9=E3=82=B3=E3=83=9E=E3=83=B3?= =?UTF-8?q?=E3=83=89=E3=82=92=E7=84=A1=E5=8A=B9=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index bcfa9d4..70b9bbe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ mod utils; use std::sync::Arc; use bpaf::Bpaf; -use poise::{Framework, FrameworkOptions}; +use poise::{Framework, FrameworkOptions, PrefixFrameworkOptions}; use serenity::{cache::Settings as CacheSettings, prelude::*}; use tracing::error; @@ -39,6 +39,11 @@ async fn main() -> Result<(), AppError> { let framework = Framework::builder() .options(FrameworkOptions { + prefix_options: PrefixFrameworkOptions { + prefix: None, + mention_as_prefix: false, + ..Default::default() + }, commands: commands(), on_error: |error| Box::pin(on_error(error)), skip_checks_for_owners: false, From 9f6902712f9159497d4eb4169e39f8f081fb19c3 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 15 Jun 2026 02:40:58 +0900 Subject: [PATCH 101/104] =?UTF-8?q?chore:=20clippy=20=E3=81=AB=E5=BE=93?= =?UTF-8?q?=E3=81=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/question/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/question/mod.rs b/src/features/question/mod.rs index f500937..88e7fd6 100644 --- a/src/features/question/mod.rs +++ b/src/features/question/mod.rs @@ -77,7 +77,7 @@ async fn handle_interaction_create(ctx: &Context, interaction: &Interaction) { ) .await; - let res = ComponentInteractionCollector::new(&ctx) + let res = ComponentInteractionCollector::new(ctx) .custom_ids( [confirm_custom_id.clone(), cancel_custom_id] .map(|id| id.try_into().unwrap()) From 22822484f75a59fa19abdd8d68ad617ef91a527f Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 15 Jun 2026 02:41:36 +0900 Subject: [PATCH 102/104] =?UTF-8?q?refactor:=20BotDataExt=20=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/data.rs | 10 +++++----- src/app/mod.rs | 2 +- src/features/admin.rs | 2 +- src/features/auth/auto_kick.rs | 4 ++-- src/features/auth/keyword.rs | 4 ++-- src/features/honeypot.rs | 4 ++-- src/features/message_cache_handler.rs | 4 ++-- src/features/message_logging/handler.rs | 4 ++-- src/features/pin.rs | 4 ++-- src/features/question/command.rs | 4 ++-- src/features/question/mod.rs | 4 ++-- src/features/thread_auto_invite/command.rs | 8 ++++---- src/features/thread_auto_invite/handler.rs | 6 +++--- src/utils.rs | 4 ++-- 14 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/app/data.rs b/src/app/data.rs index 8bb6940..4bffdca 100644 --- a/src/app/data.rs +++ b/src/app/data.rs @@ -18,10 +18,10 @@ impl BotData { } } -pub trait BotDataGetter { +pub trait BotDataExt { fn bot_data(&self) -> Arc; - async fn read_app_config(&self) -> Arc { + async fn app_config(&self) -> Arc { self.bot_data().config.read().await.clone() } @@ -31,19 +31,19 @@ pub trait BotDataGetter { } } -impl BotDataGetter for Context { +impl BotDataExt for Context { fn bot_data(&self) -> Arc { self.data() } } -impl<'a> BotDataGetter for AppContext<'a> { +impl<'a> BotDataExt for AppContext<'a> { fn bot_data(&self) -> Arc { self.data() } } -impl<'a> BotDataGetter for AppApplicationContext<'a> { +impl<'a> BotDataExt for AppApplicationContext<'a> { fn bot_data(&self) -> Arc { self.data() } diff --git a/src/app/mod.rs b/src/app/mod.rs index 298c393..000eab8 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -7,7 +7,7 @@ mod event_handler; pub mod types; pub mod utils; -pub use data::{BotData, BotDataGetter}; +pub use data::{BotData, BotDataExt}; pub use error::{BotError, on_error}; pub use event_handler::MainEventHandler; pub use types::{AppApplicationContext, AppCommand, AppContext, AppError}; diff --git a/src/features/admin.rs b/src/features/admin.rs index 885a8bf..665ecc7 100644 --- a/src/features/admin.rs +++ b/src/features/admin.rs @@ -1,6 +1,6 @@ use poise::say_reply; -use crate::app::{AppContext, AppError, BotDataGetter, config::AppConfig}; +use crate::app::{AppContext, AppError, BotDataExt, config::AppConfig}; /// コンフィグを再読み込み #[poise::command(slash_command, ephemeral, owners_only, dm_only)] diff --git a/src/features/auth/auto_kick.rs b/src/features/auth/auto_kick.rs index bfb97c5..747ac7f 100644 --- a/src/features/auth/auto_kick.rs +++ b/src/features/auth/auto_kick.rs @@ -13,7 +13,7 @@ use tokio::pin; use tracing::error; use crate::{ - app::BotDataGetter, + app::BotDataExt, core::BotEventHandler, features::auth::utils::create_auth_log_message, utils::{create_message, send_message}, @@ -32,7 +32,7 @@ impl AutoKickEventHandler { async fn run_kick_loop(ctx: Context) { loop { - let config = ctx.read_app_config().await; + let config = ctx.app_config().await; let cached_members = ctx .cache diff --git a/src/features/auth/keyword.rs b/src/features/auth/keyword.rs index 284f87a..abaed55 100644 --- a/src/features/auth/keyword.rs +++ b/src/features/auth/keyword.rs @@ -20,7 +20,7 @@ use serenity::model::event::FullEvent; use serenity::small_fixed_array::FixedString; use tracing::error; -use crate::app::{AppContext, AppError, BotDataGetter}; +use crate::app::{AppContext, AppError, BotDataExt}; use crate::core::BotEventHandler; use crate::features::auth::utils::create_auth_log_message; use crate::utils::{create_ephemeral_message, create_interaction_message, create_message, create_model, send_message}; @@ -71,7 +71,7 @@ impl KeywordAuthEventHandler { return; } - let config = &ctx.read_app_config().await.auth; + let config = &ctx.app_config().await.auth; let member = interaction.member.as_ref().unwrap(); if member.roles.contains(&config.role_id) { diff --git a/src/features/honeypot.rs b/src/features/honeypot.rs index 73ea976..a623052 100644 --- a/src/features/honeypot.rs +++ b/src/features/honeypot.rs @@ -16,7 +16,7 @@ use tracing::error; use valine_bot_macros::event_handler; use crate::{ - app::BotDataGetter, + app::BotDataExt, utils::{create_message, create_safe_message, send_message}, }; @@ -140,7 +140,7 @@ pub async fn handle_honeypot_event(ctx: &Context, event: &FullEvent) { return; } - let config = ctx.read_app_config().await; + let config = ctx.app_config().await; if config.honeypot.channel_id != new_message.channel_id.expect_channel() { return; diff --git a/src/features/message_cache_handler.rs b/src/features/message_cache_handler.rs index 715747d..8b798af 100644 --- a/src/features/message_cache_handler.rs +++ b/src/features/message_cache_handler.rs @@ -15,7 +15,7 @@ use serenity::{ }; use tracing::{error, info}; -use crate::{app::BotDataGetter, core::BotEventHandler, utils::fetch_all_archived_public_thread}; +use crate::{app::BotDataExt, core::BotEventHandler, utils::fetch_all_archived_public_thread}; enum ChannelWrapper { Channel(GuildChannel), @@ -104,7 +104,7 @@ impl MessageCacheHandler { return; } - let config = ctx.read_app_config().await; + let config = ctx.app_config().await; for guild_id in &config.message_cache.target_guild_ids { let guild = match guild_id.to_guild_cached(&ctx.cache) { diff --git a/src/features/message_logging/handler.rs b/src/features/message_logging/handler.rs index 48019fa..b9c3130 100644 --- a/src/features/message_logging/handler.rs +++ b/src/features/message_logging/handler.rs @@ -6,7 +6,7 @@ use tracing::error; use valine_bot_macros::event_handler; use crate::{ - app::BotDataGetter, + app::BotDataExt, extensions::MessageBuilderTimestampExt, features::message_logging::{embed_builder::build_embed, log_type::LogType}, utils::{create_safe_message, send_message}, @@ -50,7 +50,7 @@ async fn create_and_send_log(ctx: &Context, message: &Message, log_type: LogType } async fn send_log<'a>(ctx: &Context, embed: CreateEmbed<'a>) { - let config = ctx.read_app_config().await; + let config = ctx.app_config().await; let log = create_safe_message().add_embed(embed); let _ = send_message(ctx, &config.message_logging.channel_id, log).await; } diff --git a/src/features/pin.rs b/src/features/pin.rs index 75aa218..de8eff7 100644 --- a/src/features/pin.rs +++ b/src/features/pin.rs @@ -9,7 +9,7 @@ use serenity::{ }; use crate::{ - app::{AppContext, AppError, BotDataGetter, config::AppConfig}, + app::{AppContext, AppError, BotDataExt, config::AppConfig}, utils::has_authed_role, }; @@ -59,7 +59,7 @@ pub async fn pin( ctx: AppContext<'_>, #[description = "ピン留めするメッセージ (リンクかID)"] msg: Message, ) -> Result<(), AppError> { - let config = ctx.read_app_config().await; + let config = ctx.app_config().await; let channel = ctx.channel().await.unwrap(); if !check_owner(ctx, &config, &channel).await { diff --git a/src/features/question/command.rs b/src/features/question/command.rs index 2bf556b..f038ec9 100644 --- a/src/features/question/command.rs +++ b/src/features/question/command.rs @@ -12,7 +12,7 @@ use std::ops::Deref; use std::sync::Arc; use std::vec; -use crate::app::{AppApplicationContext, AppError, BotDataGetter}; +use crate::app::{AppApplicationContext, AppError, BotDataExt}; use crate::features::question::QUESTION_CLOSE_PREFIX; use crate::features::question::modal::{BasicQuestionData, DetailedQuestionData}; use crate::features::question::question_creation_handler::{CustomIds, QuestionCreationHandler}; @@ -78,7 +78,7 @@ pub async fn question(ctx: AppApplicationContext<'_>) -> Result<(), AppError> { .label("質問を送信") .style(ButtonStyle::Success); - let config = &ctx.read_app_config().await.question; + let config = &ctx.app_config().await.question; let Ok(channel) = config.forum_id.to_guild_channel(&ctx, ctx.guild_id()).await else { return Err("Failed to create forum channel".into()); }; diff --git a/src/features/question/mod.rs b/src/features/question/mod.rs index 88e7fd6..957c80f 100644 --- a/src/features/question/mod.rs +++ b/src/features/question/mod.rs @@ -17,7 +17,7 @@ use serenity::{ use tracing::error; use valine_bot_macros::event_handler; -use crate::{app::BotDataGetter, utils::create_interaction_message}; +use crate::{app::BotDataExt, utils::create_interaction_message}; pub static QUESTION_CLOSE_PREFIX: &str = "close_question_forum"; @@ -39,7 +39,7 @@ async fn handle_interaction_create(ctx: &Context, interaction: &Interaction) { return; } - let config = &ctx.read_app_config().await.question; + let config = &ctx.app_config().await.question; let Ok(thread) = interaction .channel_id .expect_thread() diff --git a/src/features/thread_auto_invite/command.rs b/src/features/thread_auto_invite/command.rs index dafc77e..f7ff4f7 100644 --- a/src/features/thread_auto_invite/command.rs +++ b/src/features/thread_auto_invite/command.rs @@ -1,7 +1,7 @@ use poise::say_reply; use crate::{ - app::{AppContext, AppError, BotDataGetter}, + app::{AppContext, AppError, BotDataExt}, features::thread_auto_invite::handler::{handle_role_assignment, handle_role_removal, invite_thread_by_roles}, utils::{get_guild_members, has_authed_role, is_in_public_thread}, }; @@ -17,7 +17,7 @@ use crate::{ check = "is_in_public_thread" )] pub async fn invite_thread(ctx: AppContext<'_>) -> Result<(), AppError> { - let config = &ctx.read_app_config().await.thread_auto_invite; + let config = &ctx.app_config().await.thread_auto_invite; ctx.defer_ephemeral().await?; invite_thread_by_roles(ctx.serenity_context(), ctx.channel_id(), &config.role_ids).await; say_reply(ctx, "スレッドに招待しました。").await?; @@ -29,7 +29,7 @@ pub async fn invite_thread(ctx: AppContext<'_>) -> Result<(), AppError> { pub async fn add_invite_role(ctx: AppContext<'_>) -> Result<(), AppError> { let members = get_guild_members(ctx.serenity_context(), ctx.guild_id().unwrap()); - let config = &ctx.read_app_config().await.thread_auto_invite; + let config = &ctx.app_config().await.thread_auto_invite; ctx.defer().await?; @@ -56,7 +56,7 @@ pub async fn add_invite_role(ctx: AppContext<'_>) -> Result<(), AppError> { pub async fn remove_invite_role(ctx: AppContext<'_>) -> Result<(), AppError> { let members = get_guild_members(ctx.serenity_context(), ctx.guild_id().unwrap()); - let config = &ctx.read_app_config().await.thread_auto_invite; + let config = &ctx.app_config().await.thread_auto_invite; ctx.defer().await?; diff --git a/src/features/thread_auto_invite/handler.rs b/src/features/thread_auto_invite/handler.rs index b4e916b..385a67a 100644 --- a/src/features/thread_auto_invite/handler.rs +++ b/src/features/thread_auto_invite/handler.rs @@ -7,7 +7,7 @@ use tracing::{error, info}; use valine_bot_macros::event_handler; use crate::{ - app::{BotDataGetter, config::ThreadAutoInviteConfig}, + app::{BotDataExt, config::ThreadAutoInviteConfig}, utils::{await_initial_message, create_message}, }; @@ -97,7 +97,7 @@ async fn handle_thread_create(ctx: &Context, thread: &GuildThread, newly_created return; } - let config = &ctx.read_app_config().await.thread_auto_invite; + let config = &ctx.app_config().await.thread_auto_invite; invite_thread_by_roles(ctx, thread.id.widen(), &config.role_ids).await; } @@ -111,7 +111,7 @@ async fn handle_guild_member_update(ctx: &Context, old: &Option, new: &O return; }; - let config = &ctx.read_app_config().await.thread_auto_invite; + let config = &ctx.app_config().await.thread_auto_invite; let has_new_role = new.roles.contains(&config.display_role_id); let has_old_role = old.roles.contains(&config.display_role_id); diff --git a/src/utils.rs b/src/utils.rs index 1d7b0d4..ff24554 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -19,7 +19,7 @@ use serenity::{ use similar::{Algorithm, ChangeTag, TextDiff}; use tracing::error; -use crate::app::{AppContext, AppError, BotDataGetter, BotError}; +use crate::app::{AppContext, AppError, BotDataExt, BotError}; pub fn create_safe_message<'a>() -> CreateMessage<'a> { CreateMessage::new().allowed_mentions(CreateAllowedMentions::new().all_users(false)) @@ -91,7 +91,7 @@ pub async fn has_authed_role(ctx: AppContext<'_>) -> Result { return Ok(false); }; - let config = ctx.read_app_config().await; + let config = ctx.app_config().await; if !member.roles.contains(&config.auth.role_id) { Err(BotError::HasNoRole.into()) } else { From d06d744fcf4af46a5f71f0f985254b6ac4bae024 Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 15 Jun 2026 03:55:05 +0900 Subject: [PATCH 103/104] =?UTF-8?q?refactor:=20=E3=82=B9=E3=83=AC=E3=83=83?= =?UTF-8?q?=E3=83=89=E4=BD=9C=E6=88=90=E6=99=82=E3=81=AB=E5=88=9D=E6=9C=9F?= =?UTF-8?q?=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92=E5=BE=85?= =?UTF-8?q?=E6=A9=9F=E3=81=99=E3=82=8B=E3=81=AE=E3=82=92=E3=82=84=E3=82=81?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - スレッド作成イベントは Bot がスレッドに参加したときにも発火するため、参加した場合は無視するように --- src/features/thread_auto_invite/handler.rs | 8 +------- src/utils.rs | 24 ---------------------- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/src/features/thread_auto_invite/handler.rs b/src/features/thread_auto_invite/handler.rs index 385a67a..9d2b13b 100644 --- a/src/features/thread_auto_invite/handler.rs +++ b/src/features/thread_auto_invite/handler.rs @@ -8,7 +8,7 @@ use valine_bot_macros::event_handler; use crate::{ app::{BotDataExt, config::ThreadAutoInviteConfig}, - utils::{await_initial_message, create_message}, + utils::create_message, }; async fn find_role(ctx: &Context, guild_id: GuildId, config: &ThreadAutoInviteConfig) -> Option { @@ -83,8 +83,6 @@ pub(in crate::features::thread_auto_invite) async fn invite_thread_by_roles( } async fn handle_thread_create(ctx: &Context, thread: &GuildThread, newly_created: &Option) { - info!("created: {newly_created:#?}"); - if !newly_created.unwrap_or(false) { return; } @@ -93,10 +91,6 @@ async fn handle_thread_create(ctx: &Context, thread: &GuildThread, newly_created return; } - if await_initial_message(ctx, thread).await { - return; - } - let config = &ctx.app_config().await.thread_auto_invite; invite_thread_by_roles(ctx, thread.id.widen(), &config.role_ids).await; } diff --git a/src/utils.rs b/src/utils.rs index ff24554..dcf1edf 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -9,7 +9,6 @@ use serenity::{ builder::{ CreateComponent, CreateInteractionResponse, CreateInteractionResponseMessage, CreateModal, CreateModalComponent, }, - collector::CollectMessages, model::{ channel::{ChannelType, GuildThread}, guild::Member, @@ -60,29 +59,6 @@ pub fn create_model<'a>( CreateInteractionResponse::Modal(CreateModal::new(custom_id, title).components(components)) } -/** -thread_create イベントにおいて、初期メッセージが送信されるか5秒経過するまで待機する - -初期メッセージが送信されると、falseを返し、既に初期メッセージが存在する場合、true を返す - */ -pub async fn await_initial_message(ctx: &Context, thread: &GuildThread) -> bool { - // Botがメッセージを送信すると二度イベントが発火するので、初期メッセージ送信後のイベントは無視する - if thread.base.last_message_id.is_some() { - return true; - } - - // 初期メッセージが送信されるか、5秒経つまで待機 - thread - .id - .widen() - .collect_messages(ctx) - .channel_id(thread.id.widen()) - .author_id(thread.owner_id) - .timeout(Duration::from_secs(5)) - .await; - false -} - /* 認証済みロールを持っているかどうかを確認します。 */ From 63be49282ee624e62859eb6976754790cfb23c9f Mon Sep 17 00:00:00 2001 From: Lapis <45414333+Lapis256@users.noreply.github.com> Date: Mon, 15 Jun 2026 05:24:49 +0900 Subject: [PATCH 104/104] =?UTF-8?q?refactor:=20=E3=83=A1=E3=83=B3=E3=83=90?= =?UTF-8?q?=E3=83=BC=E4=B8=80=E8=A6=A7=E3=81=AE=E5=8F=96=E5=BE=97=E3=82=92?= =?UTF-8?q?=E5=85=B1=E9=80=9A=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/auth/auto_kick.rs | 24 +++--------------- src/features/thread_auto_invite/command.rs | 29 +++++++++------------- src/features/thread_auto_invite/handler.rs | 8 +++--- src/utils.rs | 25 ++++++++++++++----- 4 files changed, 38 insertions(+), 48 deletions(-) diff --git a/src/features/auth/auto_kick.rs b/src/features/auth/auto_kick.rs index 747ac7f..2af02d1 100644 --- a/src/features/auth/auto_kick.rs +++ b/src/features/auth/auto_kick.rs @@ -3,20 +3,19 @@ use std::{ time::Duration, }; -use futures::{StreamExt, stream}; +use futures::StreamExt; use serenity::{ all::{Context, prelude::CacheHttp}, async_trait, model::{Color, event::FullEvent}, }; -use tokio::pin; use tracing::error; use crate::{ app::BotDataExt, core::BotEventHandler, features::auth::utils::create_auth_log_message, - utils::{create_message, send_message}, + utils::{create_message, send_message, stream_members}, }; pub struct AutoKickEventHandler { @@ -34,24 +33,7 @@ impl AutoKickEventHandler { loop { let config = ctx.app_config().await; - let cached_members = ctx - .cache - .guild(config.auto_kick.guild_id) - // メンバーキャッシュが構築されていない場合は API から取ってくるため - .filter(|guild| guild.members.len() as u32 >= guild.member_count.get()) - .map(|guild| guild.members.clone()); - let member_stream = if let Some(members) = cached_members { - stream::iter(members).left_stream() - } else { - config - .auto_kick - .guild_id - .members_iter(ctx.http()) - .filter_map(|r| async { r.ok() }) - .right_stream() - }; - pin!(member_stream); - + let mut member_stream = stream_members(&ctx, config.auto_kick.guild_id); while let Some(member) = member_stream.next().await { if member.user.bot() { continue; diff --git a/src/features/thread_auto_invite/command.rs b/src/features/thread_auto_invite/command.rs index f7ff4f7..d52e589 100644 --- a/src/features/thread_auto_invite/command.rs +++ b/src/features/thread_auto_invite/command.rs @@ -1,9 +1,10 @@ +use futures::StreamExt; use poise::say_reply; use crate::{ app::{AppContext, AppError, BotDataExt}, - features::thread_auto_invite::handler::{handle_role_assignment, handle_role_removal, invite_thread_by_roles}, - utils::{get_guild_members, has_authed_role, is_in_public_thread}, + features::thread_auto_invite::handler::{assign_role, invite_thread_by_roles, remove_role}, + utils::{has_authed_role, is_in_public_thread, stream_members}, }; /// 招待用ロールを持ったメンバーを実行したスレッドに招待します。 @@ -27,21 +28,18 @@ pub async fn invite_thread(ctx: AppContext<'_>) -> Result<(), AppError> { /// 表示用のロールを持ったメンバーに呼び出し用のロールを付与します #[poise::command(slash_command, guild_only, default_member_permissions = "MANAGE_ROLES")] pub async fn add_invite_role(ctx: AppContext<'_>) -> Result<(), AppError> { - let members = get_guild_members(ctx.serenity_context(), ctx.guild_id().unwrap()); - - let config = &ctx.app_config().await.thread_auto_invite; - ctx.defer().await?; + let config = &ctx.app_config().await.thread_auto_invite; + let mut members = stream_members(ctx.serenity_context(), ctx.guild_id().unwrap()); let mut added_count = 0; - - for member in members { + while let Some(member) = members.next().await { if config.role_ids.iter().any(|r| member.roles.contains(r)) { - handle_role_removal(ctx.serenity_context(), &member, config).await; + remove_role(ctx.serenity_context(), &member, config).await; } if member.roles.contains(&config.display_role_id) { - handle_role_assignment(ctx.serenity_context(), &member, config).await; + assign_role(ctx.serenity_context(), &member, config).await; added_count += 1; continue; } @@ -54,20 +52,17 @@ pub async fn add_invite_role(ctx: AppContext<'_>) -> Result<(), AppError> { /// 表示用のロールを持ったメンバーに呼び出し用のロールを削除 #[poise::command(slash_command, guild_only, default_member_permissions = "MANAGE_ROLES")] pub async fn remove_invite_role(ctx: AppContext<'_>) -> Result<(), AppError> { - let members = get_guild_members(ctx.serenity_context(), ctx.guild_id().unwrap()); - - let config = &ctx.app_config().await.thread_auto_invite; - ctx.defer().await?; + let config = &ctx.app_config().await.thread_auto_invite; + let mut members = stream_members(ctx.serenity_context(), ctx.guild_id().unwrap()); let mut role_count = 0; - - for member in members { + while let Some(member) = members.next().await { if !config.role_ids.iter().any(|r| member.roles.contains(r)) { continue; } - handle_role_removal(ctx.serenity_context(), &member, config).await; + remove_role(ctx.serenity_context(), &member, config).await; role_count += 1; } diff --git a/src/features/thread_auto_invite/handler.rs b/src/features/thread_auto_invite/handler.rs index 9d2b13b..0914491 100644 --- a/src/features/thread_auto_invite/handler.rs +++ b/src/features/thread_auto_invite/handler.rs @@ -25,7 +25,7 @@ async fn find_role(ctx: &Context, guild_id: GuildId, config: &ThreadAutoInviteCo }) } -pub(in crate::features::thread_auto_invite) async fn handle_role_assignment( +pub(in crate::features::thread_auto_invite) async fn assign_role( ctx: &Context, new: &Member, config: &ThreadAutoInviteConfig, @@ -42,7 +42,7 @@ pub(in crate::features::thread_auto_invite) async fn handle_role_assignment( } } -pub(in crate::features::thread_auto_invite) async fn handle_role_removal( +pub(in crate::features::thread_auto_invite) async fn remove_role( ctx: &Context, old: &Member, config: &ThreadAutoInviteConfig, @@ -111,9 +111,9 @@ async fn handle_guild_member_update(ctx: &Context, old: &Option, new: &O let has_old_role = old.roles.contains(&config.display_role_id); if has_new_role && !has_old_role { - handle_role_assignment(ctx, new, config).await; + assign_role(ctx, new, config).await; } else if has_old_role && !has_new_role { - handle_role_removal(ctx, old, config).await; + remove_role(ctx, old, config).await; } } diff --git a/src/utils.rs b/src/utils.rs index dcf1edf..d2476a7 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,7 +1,10 @@ use std::{borrow::Cow, time::Duration}; use async_stream::stream; -use futures::Stream; +use futures::{ + Stream, StreamExt, + stream::{self, BoxStream}, +}; use itertools::Itertools; use serenity::{ Result, @@ -172,10 +175,20 @@ pub fn create_diff_lines_text(old: &str, new: &str) -> String { .join("") } -pub fn get_guild_members(ctx: &Context, guild_id: GuildId) -> impl Iterator { - guild_id +pub fn stream_members(ctx: &Context, guild_id: GuildId) -> BoxStream<'_, Member> { + let cached_members = guild_id .to_guild_cached(&ctx.cache) - .map(|guild| guild.members.clone()) - .into_iter() - .flatten() + .filter(|guild| guild.members.len() as u32 >= guild.member_count.get()) + .map(|guild| guild.members.clone()); + + let stream = if let Some(members) = cached_members { + stream::iter(members).left_stream() + } else { + guild_id + .members_iter(ctx.http()) + .filter_map(|result| async { result.ok() }) + .right_stream() + }; + + stream.boxed() }