diff options
| author | Nathan Perry <np@nathanperry.dev> | 2024-08-06 16:32:35 -0400 |
|---|---|---|
| committer | Nathan Perry <np@nathanperry.dev> | 2024-08-06 16:32:35 -0400 |
| commit | 501ba27e1cd52741988113ef47ee0fad7d0a5799 (patch) | |
| tree | cfe5985b6896b8cd440638716d9c032735af46bf /src | |
| parent | 011fcf828ebd1325dbd4dfa21c4952f1be38a29f (diff) | |
fixup unknown command, document commands
Diffstat (limited to 'src')
| -rw-r--r-- | src/bot.rs | 102 | ||||
| -rw-r--r-- | src/commands/game.rs | 47 | ||||
| -rw-r--r-- | src/commands/meme/create.rs | 6 | ||||
| -rw-r--r-- | src/commands/meme/delete.rs | 4 | ||||
| -rw-r--r-- | src/commands/meme/history.rs | 23 | ||||
| -rw-r--r-- | src/commands/meme/invoke.rs | 47 | ||||
| -rw-r--r-- | src/commands/meme/mod.rs | 1 | ||||
| -rw-r--r-- | src/commands/mod.rs | 17 | ||||
| -rw-r--r-- | src/commands/playback.rs | 45 | ||||
| -rw-r--r-- | src/commands/sound_levels.rs | 9 | ||||
| -rw-r--r-- | src/lib.rs | 1 | ||||
| -rw-r--r-- | src/util/mod.rs | 36 | ||||
| -rw-r--r-- | src/util/rest_vec.rs | 5 |
13 files changed, 234 insertions, 109 deletions
@@ -24,12 +24,14 @@ use log::{ use poise::{ BoxFuture, FrameworkError, + PrefixContext, }; use serenity::{ all::{ GuildId, ReactionType, }, + builder::CreateMessage, model::{ event::ResumedEvent, gateway::Ready, @@ -52,6 +54,7 @@ use tokio::sync::Mutex; use crate::{ commands, config::CONFIG, + err_msg, util, util::OAUTH_URL, Error, @@ -165,16 +168,99 @@ lazy_static! { fn on_err(err: FrameworkError<PoiseData, anyhow::Error>) -> BoxFuture<()> { Box::pin(async move { - error!("error encountered: {err:?}"); + let Some(msg) = err_msg(&err) else { + warn!("error handler missing poise context"); + return; + }; - if let Some(ctx) = err.ctx() { - if let Err(e) = util::react(ctx, ReactionType::Unicode("❌".to_owned())).await { - error!("reacting to failed message: {e}"); - } + let ctx = err.serenity_context(); - if let Err(e) = util::reply(ctx, "BANIC").await { - error!("sending BANIC: {e}"); + let text = match err { + FrameworkError::ArgumentParse { + .. } + | FrameworkError::SubcommandRequired { + .. + } => "format your commands right. fuck you.".to_string(), + FrameworkError::CooldownHit { + .. + } => "slow the fuck down bitch".to_string(), + FrameworkError::NotAnOwner { + .. + } => "who do you think you are?".to_string(), + FrameworkError::GuildOnly { + .. + } => "what in the sam hill are you smoking".to_string(), + FrameworkError::DmOnly { + .. + } => "take that back or i'm revoking your kitten status".to_string(), + FrameworkError::UnknownCommand { + ctx, + msg, + prefix, + msg_content, + trigger, + invocation_data, + framework, + .. + } => { + let command = poise::Command { + name: "meme".to_owned(), + ..Default::default() + }; + + fn noop<U, E>( + _ctx: PrefixContext<'_, U, E>, + ) -> BoxFuture<core::result::Result<(), FrameworkError<U, E>>> { + Box::pin(async { Ok(()) }) + } + + let ctx = PrefixContext { + serenity_context: ctx, + prefix, + msg, + command: &command, + trigger, + invocation_data, + parent_commands: &[], + data: &(), + invoked_command_name: "", + action: noop, + args: msg_content, + framework, + + __non_exhaustive: (), + }; + + match util::pop_string(msg_content) + .map_err(anyhow::Error::from) + .and_then(|(_rest, s)| s.parse().map_err(anyhow::Error::from)) + { + Ok(u) => { + if let Err(e) = commands::unrecognized(PoiseContext::Prefix(ctx), u).await { + error!("processing audio: {e}"); + "BANIC".to_string() + } else { + return; + } + }, + Err(e) => { + error!("processing unrecognized message: {e}"); + "BANIC".to_string() + }, + } + }, + _ => "BANIC".to_string(), + }; + + error!("error encountered: {err:#?}"); + if let Err(e) = msg.react(ctx, ReactionType::Unicode("❌".to_owned())).await { + error!("reacting to failed message: {e}"); + } + + let cm = CreateMessage::default().content(text).tts(msg.tts); + if let Err(e) = msg.channel_id.send_message(ctx, cm).await { + error!("sending error to chat: {e}"); } }) } @@ -296,7 +382,7 @@ pub async fn run() -> Result<()> { tokio::spawn(async move { tokio::signal::ctrl_c().await.unwrap(); - warn!("got ctrl c"); + warn!("got ^C"); shard_manager.shutdown_all().await; info!("shutdown"); diff --git a/src/commands/game.rs b/src/commands/game.rs index 72633b5..3b16fd3 100644 --- a/src/commands/game.rs +++ b/src/commands/game.rs @@ -9,7 +9,10 @@ use std::{ }, }; -use anyhow::anyhow; +use anyhow::{ + anyhow, + Context, +}; use fnv::{ FnvHashMap, FnvHashSet, @@ -64,7 +67,7 @@ struct ProfileInfo { lazy_static! { static ref USER_MAP_STR: String = { - let default_path = PathBuf::from_str("user_id_mapping").unwrap(); + let default_path = PathBuf::from_str("user_id_mapping.json").unwrap(); let mapping_path = CONFIG.user_id_mapping.as_ref().unwrap_or(&default_path); fs::read_to_string(mapping_path).unwrap_or("{}".to_owned()) @@ -143,11 +146,17 @@ pub fn commands() -> Vec<poise::Command<crate::PoiseData, anyhow::Error>> { vec![installedgame(), ownedgame(), game(), updategaem()] } +/// Find a game everyone can play (marked installed). +/// +/// Looks up users in the general voice channel if no users are passed. #[poise::command(prefix_command, guild_only, category = "gaem", aliases("installedgaem"))] pub async fn installedgame(ctx: PoiseContext<'_>, args: util::RestVec) -> anyhow::Result<()> { _game(ctx, args.into_inner(), GameStatus::Installed).await } +/// Find a game everyone owns. +/// +/// Looks up users in the general voice channel if no users are passed. #[poise::command(prefix_command, guild_only, category = "gaem", aliases("ownedgaem"))] pub async fn ownedgame(ctx: PoiseContext<'_>, args: util::RestVec) -> anyhow::Result<()> { _game(ctx, args.into_inner(), GameStatus::NotInstalled).await @@ -202,8 +211,14 @@ pub fn get_user_id<S: AsRef<str>>(g: &Guild, s: S) -> StdResult<UserId, UserLook } } +/// Find a game everyone can play (marked installed). +/// +/// Looks up users in the general voice channel if no users are passed. #[poise::command(prefix_command, guild_only, category = "gaem", aliases("gaem"))] -async fn game(ctx: PoiseContext<'_>, args: util::RestVec) -> anyhow::Result<()> { +async fn game( + ctx: PoiseContext<'_>, + #[description = "other users to include"] args: util::RestVec, +) -> anyhow::Result<()> { _game(ctx, args.into_inner(), GameStatus::Installed).await } @@ -417,13 +432,8 @@ async fn load_spreadsheet(client: &reqwest::Client) -> Result<Vec<Vec<String>>> Ok(resp.value_ranges.into_iter().next().unwrap().values) } -#[poise::command( - slash_command, - prefix_command, - guild_only, - category = "gaem", - aliases("updategame") -)] +/// Find games that are out-of-date on the spreadsheet. +#[poise::command(prefix_command, guild_only, category = "gaem", aliases("updategame"))] pub async fn updategaem(ctx: PoiseContext<'_>, user: Option<String>) -> anyhow::Result<()> { use regex::Regex; use std::borrow::Borrow; @@ -537,13 +547,16 @@ pub async fn updategaem(ctx: PoiseContext<'_>, user: Option<String>) -> anyhow:: .get(u) .send() .await? + .error_for_status() + .context("retrieve steam info http status")? .json::<SteamResp>() - .await? - .response - .games - .into_iter() - .map(|ge| ge.app_id) - .collect::<FnvHashSet<_>>(); + .await + .context("decode steam resp")?; + + let games_owned = + games_owned.response.games.into_iter().map(|ge| ge.app_id).collect::<FnvHashSet<_>>(); + + debug!("user owns {} steam games", games_owned.len()); let found_games = missing_appids .filter_map(|(ai, x)| { @@ -560,7 +573,7 @@ pub async fn updategaem(ctx: PoiseContext<'_>, user: Option<String>) -> anyhow:: util::reply( ctx, format!( - "{n_missing} games owned on steam that are missing from the list:\n{found_games}" + "{n_missing} games owned on steam that aren't marked on the list:\n{found_games}" ), ) .await?; diff --git a/src/commands/meme/create.rs b/src/commands/meme/create.rs index cad9bfc..e2eacbf 100644 --- a/src/commands/meme/create.rs +++ b/src/commands/meme/create.rs @@ -28,7 +28,8 @@ use crate::{ FFMPEG_COMMAND, }; -#[poise::command(slash_command, prefix_command, guild_only, category = "memes")] +/// Add a text/image meme to the db. +#[poise::command(prefix_command, guild_only, category = "memes")] pub async fn addmeme( ctx: PoiseContext<'_>, title: String, @@ -94,7 +95,8 @@ pub async fn addmeme( Ok(()) } -#[poise::command(slash_command, prefix_command, guild_only, category = "memes")] +/// Add an audiomeme to the meme db. +#[poise::command(prefix_command, guild_only, category = "memes")] pub async fn addaudiomeme( ctx: PoiseContext<'_>, title: String, diff --git a/src/commands/meme/delete.rs b/src/commands/meme/delete.rs index 25ddf0d..4769cc8 100644 --- a/src/commands/meme/delete.rs +++ b/src/commands/meme/delete.rs @@ -10,12 +10,12 @@ use crate::{ connection, delete_meme, }, - msg, util, PoiseContext, }; -#[poise::command(slash_command, prefix_command, guild_only, category = "memes", aliases("delmem"))] +/// Delete a meme by name. +#[poise::command(prefix_command, guild_only, category = "memes", aliases("delmem"))] pub async fn delmeme(ctx: PoiseContext<'_>, title: String) -> anyhow::Result<()> { let mut conn = connection().await?; diff --git a/src/commands/meme/history.rs b/src/commands/meme/history.rs index cfd78df..335603f 100644 --- a/src/commands/meme/history.rs +++ b/src/commands/meme/history.rs @@ -48,13 +48,8 @@ lazy_static! { static CLEAN_DATE_FORMAT: &str = "%b %-e %Y"; -#[poise::command( - slash_command, - prefix_command, - guild_only, - category = "memes", - aliases("what", "hwaet", "hwæt") -)] +/// Print info about the last meme. +#[poise::command(prefix_command, guild_only, category = "memes", aliases("what", "hwaet", "hwæt"))] pub async fn wat(ctx: PoiseContext<'_>) -> anyhow::Result<()> { let mut conn = connection().await?; @@ -108,7 +103,8 @@ pub async fn wat(ctx: PoiseContext<'_>) -> anyhow::Result<()> { Ok(()) } -#[poise::command(slash_command, prefix_command, guild_only, category = "memes", aliases("hist"))] +/// Print recent memes and who invoked them. +#[poise::command(prefix_command, guild_only, category = "memes", aliases("hist"))] pub async fn history(ctx: PoiseContext<'_>, n: Option<usize>) -> anyhow::Result<()> { let n = n.unwrap_or(CONFIG.default_hist); @@ -204,7 +200,8 @@ pub async fn history(ctx: PoiseContext<'_>, n: Option<usize>) -> anyhow::Result< Ok(()) } -#[poise::command(slash_command, prefix_command, guild_only, category = "memes", aliases("stat"))] +/// Print stats about the meme database. +#[poise::command(prefix_command, guild_only, category = "memes", aliases("stat"))] pub async fn stats(ctx: PoiseContext<'_>) -> anyhow::Result<()> { use db; use serenity::model::{ @@ -276,7 +273,8 @@ and *{}* was the most-memed overall ({})"#, Ok(()) } -#[poise::command(slash_command, prefix_command, guild_only, category = "memes")] +/// Print stats about memers. +#[poise::command(prefix_command, guild_only, category = "memes")] pub async fn memers(ctx: PoiseContext<'_>) -> anyhow::Result<()> { use serenity::model::id::UserId; @@ -310,6 +308,11 @@ pub async fn memers(ctx: PoiseContext<'_>) -> anyhow::Result<()> { Ok(()) } +/// Look up a meme by title or content. +/// +/// Can pass: +/// - `by=username` or `creator=username` to look up memes created by a specific user. +/// - `age=new` or `age=old` to sort the result by age. #[poise::command(prefix_command, guild_only, category = "memes")] pub async fn query(ctx: PoiseContext<'_>, rest: util::RestVec) -> anyhow::Result<()> { use regex::Regex; diff --git a/src/commands/meme/invoke.rs b/src/commands/meme/invoke.rs index e399e82..31b0085 100644 --- a/src/commands/meme/invoke.rs +++ b/src/commands/meme/invoke.rs @@ -14,48 +14,50 @@ use crate::{ }, util, PoiseContext, + RestVec, }; -#[poise::command(slash_command, prefix_command, guild_only, category = "memes", aliases("mem"))] -pub async fn meme(ctx: PoiseContext<'_>, #[rest] rest: String) -> anyhow::Result<()> { - _meme(ctx, rest, AudioPlayback::Optional).await +/// Post a meme. +#[poise::command(prefix_command, guild_only, category = "memes", aliases("mem"))] +pub async fn meme(ctx: PoiseContext<'_>, title: RestVec) -> anyhow::Result<()> { + let title = title.into_inner().join(" "); + + _meme(ctx, title.trim(), AudioPlayback::Optional).await } -#[poise::command(slash_command, prefix_command, guild_only, category = "memes")] +/// Post a random omen. +#[poise::command(prefix_command, guild_only, category = "memes", discard_spare_arguments)] pub async fn omen(ctx: PoiseContext<'_>) -> anyhow::Result<()> { _meme(ctx, "", AudioPlayback::Optional).await } -#[poise::command(slash_command, prefix_command, guild_only, category = "memes")] +/// Post a random omen without audio. +#[poise::command(prefix_command, guild_only, category = "memes", discard_spare_arguments)] pub async fn silentomen(ctx: PoiseContext<'_>) -> anyhow::Result<()> { _meme(ctx, "", AudioPlayback::Prohibited).await } -#[poise::command(slash_command, prefix_command, guild_only, category = "memes")] +/// Post a random omen with audio. +#[poise::command(prefix_command, guild_only, category = "memes", discard_spare_arguments)] pub async fn audioomen(ctx: PoiseContext<'_>) -> anyhow::Result<()> { _meme(ctx, "", AudioPlayback::Required).await } -#[poise::command( - slash_command, - prefix_command, - guild_only, - category = "memes", - aliases("audiomeme", "audiomem") -)] -pub async fn audio_meme(ctx: PoiseContext<'_>, #[rest] rest: String) -> anyhow::Result<()> { - _meme(ctx, rest, AudioPlayback::Required).await +/// Post a random meme with audio. +#[poise::command(prefix_command, guild_only, category = "memes", aliases("audiomeme", "audiomem"))] +pub async fn audio_meme(ctx: PoiseContext<'_>) -> anyhow::Result<()> { + _meme(ctx, "", AudioPlayback::Required).await } +/// Post a random meme without audio. #[poise::command( - slash_command, prefix_command, guild_only, category = "memes", aliases("silentmeme", "silentmem") )] -pub async fn silent_meme(ctx: PoiseContext<'_>, #[rest] rest: String) -> anyhow::Result<()> { - _meme(ctx, rest, AudioPlayback::Prohibited).await +pub async fn silent_meme(ctx: PoiseContext<'_>) -> anyhow::Result<()> { + _meme(ctx, "", AudioPlayback::Prohibited).await } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -132,13 +134,8 @@ async fn rand_meme(ctx: PoiseContext<'_>, audio_playback: AudioPlayback) -> anyh } } -#[poise::command( - slash_command, - prefix_command, - guild_only, - category = "memes", - aliases("raremem", "rarememe") -)] +/// Post a rare meme. +#[poise::command(prefix_command, guild_only, category = "memes", aliases("raremem", "rarememe"))] pub async fn rare_meme(ctx: PoiseContext<'_>) -> anyhow::Result<()> { let should_audio = util::users_listening(ctx.serenity_context()).await?; diff --git a/src/commands/meme/mod.rs b/src/commands/meme/mod.rs index 0108219..9495ec7 100644 --- a/src/commands/meme/mod.rs +++ b/src/commands/meme/mod.rs @@ -32,7 +32,6 @@ use crate::{ Audio, Meme, }, - msg, util, PoiseContext, CONFIG, diff --git a/src/commands/mod.rs b/src/commands/mod.rs index ba87adb..1ad4a59 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -20,17 +20,9 @@ pub(crate) mod today; pub use self::meme::*; pub fn commands() -> Vec<poise::Command<crate::PoiseData, anyhow::Error>> { - let mut commands = vec![ - playback::play(), - playback::pause(), - playback::resume(), - playback::die(), - playback::list(), - sound_levels::mute(), - sound_levels::unmute(), - roll::roll(), - help(), - ]; + let mut commands = vec![sound_levels::mute(), sound_levels::unmute(), roll::roll(), help()]; + + commands.extend(playback::commands()); #[cfg(feature = "games")] commands.extend(game::commands()); @@ -41,7 +33,8 @@ pub fn commands() -> Vec<poise::Command<crate::PoiseData, anyhow::Error>> { commands } -#[poise::command(slash_command, prefix_command, aliases("halp"))] +/// Print this help text. +#[poise::command(prefix_command, aliases("halp"))] pub async fn help(ctx: PoiseContext<'_>, command: Option<String>) -> anyhow::Result<()> { poise::builtins::pretty_help( ctx, diff --git a/src/commands/playback.rs b/src/commands/playback.rs index 98ae613..48f3286 100644 --- a/src/commands/playback.rs +++ b/src/commands/playback.rs @@ -5,7 +5,10 @@ use log::{ info, warn, }; -use serenity::prelude::*; +use serenity::{ + all::ReactionType, + prelude::*, +}; use songbird::{ input::YoutubeDl, Call, @@ -16,9 +19,14 @@ use crate::{ bot::HttpKey, util, PoiseContext, + PoiseData, CONFIG, }; +pub fn commands() -> impl IntoIterator<Item = poise::Command<PoiseData, anyhow::Error>> { + vec![play(), pause(), resume(), die(), list(), skip()] +} + pub async fn songbird(ctx: PoiseContext<'_>) -> anyhow::Result<(Arc<Songbird>, Arc<Mutex<Call>>)> { let Some(gid) = ctx.guild_id() else { return Err(anyhow::anyhow!("no guild id").into()); @@ -46,7 +54,7 @@ pub async fn _play(ctx: PoiseContext<'_>, url: &url::Url) -> anyhow::Result<()> _ => None, }); - if host.map(|h| h.to_lowercase().contains("imgur")).unwrap_or(false) { + if host.is_some_and(|h| h.to_lowercase().contains("imgur")) { info!("detected imgur link"); if ctx.author().id == 106160362109272064 { @@ -73,10 +81,13 @@ pub async fn _play(ctx: PoiseContext<'_>, url: &url::Url) -> anyhow::Result<()> let input = YoutubeDl::new_ytdl_like("yt-dlp", client.clone(), url.to_string()); call.enqueue_input(input.into()).await; + util::react(ctx, ReactionType::Unicode("📣".to_owned())).await?; + Ok(()) } -#[poise::command(slash_command, prefix_command, guild_only, category = "playback")] +/// Play a link. +#[poise::command(prefix_command, guild_only, category = "playback")] pub async fn play( ctx: PoiseContext<'_>, #[description = "link to play (if absent, resumes playback)"] u: Option<url::Url>, @@ -88,7 +99,8 @@ pub async fn play( _play(ctx, &u).await } -#[poise::command(slash_command, prefix_command, guild_only, category = "playback")] +/// Pause audio playback. +#[poise::command(prefix_command, guild_only, category = "playback")] pub async fn pause(ctx: PoiseContext<'_>) -> anyhow::Result<()> { let (_sb, call) = songbird(ctx).await?; @@ -98,13 +110,8 @@ pub async fn pause(ctx: PoiseContext<'_>) -> anyhow::Result<()> { Ok(()) } -#[poise::command( - slash_command, - prefix_command, - guild_only, - aliases("continue"), - category = "playback" -)] +/// Resume audio playback. +#[poise::command(prefix_command, guild_only, aliases("continue"), category = "playback")] pub async fn resume(ctx: PoiseContext<'_>) -> anyhow::Result<()> { _resume(ctx).await } @@ -118,7 +125,8 @@ async fn _resume(ctx: PoiseContext<'_>) -> anyhow::Result<()> { Ok(()) } -#[poise::command(slash_command, prefix_command, guild_only, category = "playback", aliases("next"))] +/// Skip the current track in the queue. +#[poise::command(prefix_command, guild_only, category = "playback", aliases("next"))] pub async fn skip(ctx: PoiseContext<'_>) -> anyhow::Result<()> { let (_sb, call) = songbird(ctx).await?; @@ -128,12 +136,12 @@ pub async fn skip(ctx: PoiseContext<'_>) -> anyhow::Result<()> { Ok(()) } +/// Stop playing audio and delete the queue. #[poise::command( - slash_command, prefix_command, guild_only, category = "playback", - aliases("sudoku", "fuckoff", "stop") + aliases("sudoku", "fuckoff", "stop", "kill") )] pub async fn die(ctx: PoiseContext<'_>) -> anyhow::Result<()> { let (_sb, call) = songbird(ctx).await?; @@ -146,13 +154,8 @@ pub async fn die(ctx: PoiseContext<'_>) -> anyhow::Result<()> { Ok(()) } -#[poise::command( - slash_command, - prefix_command, - guild_only, - category = "playback", - aliases("queue") -)] +/// List queued audio. +#[poise::command(prefix_command, guild_only, category = "playback", aliases("queue"))] pub async fn list(ctx: PoiseContext<'_>) -> anyhow::Result<()> { let (_sb, call) = songbird(ctx).await?; diff --git a/src/commands/sound_levels.rs b/src/commands/sound_levels.rs index 9a6cfc6..4946f47 100644 --- a/src/commands/sound_levels.rs +++ b/src/commands/sound_levels.rs @@ -3,10 +3,8 @@ use crate::{ PoiseContext, }; -pub const DEFAULT_VOLUME: f32 = 0.20; -const MAX_VOLUME: f32 = 5.0; - -#[poise::command(slash_command, prefix_command, guild_only)] +/// Mute audio (don't pause). +#[poise::command(prefix_command, guild_only)] pub async fn mute(ctx: PoiseContext<'_>) -> anyhow::Result<()> { let (_sb, call) = songbird(ctx).await?; @@ -16,7 +14,8 @@ pub async fn mute(ctx: PoiseContext<'_>) -> anyhow::Result<()> { Ok(()) } -#[poise::command(slash_command, prefix_command, guild_only)] +/// Unmute audio. +#[poise::command(prefix_command, guild_only)] pub async fn unmute(ctx: PoiseContext<'_>) -> anyhow::Result<()> { let (_sb, call) = songbird(ctx).await?; @@ -1,4 +1,5 @@ #![feature(try_blocks)] +#![feature(let_chains)] #[cfg(feature = "db")] pub mod db; diff --git a/src/util/mod.rs b/src/util/mod.rs index a0105ac..f362ff7 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -3,7 +3,10 @@ use std::process::Stdio; use chrono::Duration; use lazy_static::lazy_static; use log::debug; -use poise::CreateReply; +use poise::{ + CreateReply, + FrameworkError, +}; use regex::{ Match, Regex, @@ -35,7 +38,7 @@ use crate::{ mod rest_vec; -pub use rest_vec::RestVec; +pub use rest_vec::*; pub async fn currently_playing(ctx: PoiseContext<'_>) -> bool { let (_sb, call) = songbird(ctx).await.expect("no songbird"); @@ -61,9 +64,9 @@ pub async fn users_listening(ctx: &Context) -> Result<bool> { } #[inline] -pub fn msg(ctx: PoiseContext<'_>) -> Option<&Message> { +pub fn msg<U, E>(ctx: poise::Context<'_, U, E>) -> Option<&Message> { match ctx { - PoiseContext::Prefix(poise::PrefixContext { + poise::Context::Prefix(poise::PrefixContext { msg, .. }) => Some(msg), @@ -72,6 +75,31 @@ pub fn msg(ctx: PoiseContext<'_>) -> Option<&Message> { } #[inline] +pub fn err_msg<'a, U, E>(err: &'a FrameworkError<U, E>) -> Option<&'a Message> { + use FrameworkError::*; + + if let Some(ctx) = err.ctx() { + return msg(ctx); + } + + match *err { + UnknownCommand { + msg, + .. + } + | NonCommandMessage { + msg, + .. + } + | DynamicPrefix { + msg, + .. + } => Some(msg), + _ => None, + } +} + +#[inline] pub fn tts(ctx: PoiseContext<'_>) -> Option<bool> { msg(ctx).map(|msg| msg.tts) } diff --git a/src/util/rest_vec.rs b/src/util/rest_vec.rs index 82889cd..31b783b 100644 --- a/src/util/rest_vec.rs +++ b/src/util/rest_vec.rs @@ -1,15 +1,16 @@ +use std::error::Error; + use serenity::all::{ Context, Message, }; -use std::error::Error; /// Pop a whitespace-separated word from the front of the arguments. Supports quotes and quote /// escaping. /// /// Leading whitespace will be trimmed; trailing whitespace is not consumed. // From https://github.com/serenity-rs/poise/blob/current/src/prefix_argument/mod.rs -fn pop_string(args: &str) -> Result<(&str, String), poise::TooFewArguments> { +pub fn pop_string(args: &str) -> Result<(&str, String), poise::TooFewArguments> { // TODO: consider changing the behavior to parse quotes literally if they're in the middle // of the string: // - `"hello world"` => `hello world` |
