aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorNathan Perry <np@nathanperry.dev>2024-08-06 16:32:35 -0400
committerNathan Perry <np@nathanperry.dev>2024-08-06 16:32:35 -0400
commit501ba27e1cd52741988113ef47ee0fad7d0a5799 (patch)
treecfe5985b6896b8cd440638716d9c032735af46bf /src
parent011fcf828ebd1325dbd4dfa21c4952f1be38a29f (diff)
fixup unknown command, document commands
Diffstat (limited to 'src')
-rw-r--r--src/bot.rs102
-rw-r--r--src/commands/game.rs47
-rw-r--r--src/commands/meme/create.rs6
-rw-r--r--src/commands/meme/delete.rs4
-rw-r--r--src/commands/meme/history.rs23
-rw-r--r--src/commands/meme/invoke.rs47
-rw-r--r--src/commands/meme/mod.rs1
-rw-r--r--src/commands/mod.rs17
-rw-r--r--src/commands/playback.rs45
-rw-r--r--src/commands/sound_levels.rs9
-rw-r--r--src/lib.rs1
-rw-r--r--src/util/mod.rs36
-rw-r--r--src/util/rest_vec.rs5
13 files changed, 234 insertions, 109 deletions
diff --git a/src/bot.rs b/src/bot.rs
index c8b01ab..6a2ae72 100644
--- a/src/bot.rs
+++ b/src/bot.rs
@@ -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?;
diff --git a/src/lib.rs b/src/lib.rs
index 4ed3e44..b9009be 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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`