aboutsummaryrefslogtreecommitdiff
path: root/src/commands
diff options
context:
space:
mode:
authorNathan Perry <np@nathanperry.dev>2024-08-06 10:45:06 -0400
committerNathan Perry <np@nathanperry.dev>2024-08-06 10:45:06 -0400
commit72d9bbe15220c21909dec8e30fb80729a24cec72 (patch)
tree5025c799e3065553c1e6a91b82cb2eae8e00c43e /src/commands
parent9319e0b9987114ffef2cc2be2d00f127925ba3a8 (diff)
first pass convert to poise
Diffstat (limited to 'src/commands')
-rw-r--r--src/commands/game.rs572
-rw-r--r--src/commands/help.rs31
-rw-r--r--src/commands/meme/create.rs104
-rw-r--r--src/commands/meme/delete.rs29
-rw-r--r--src/commands/meme/history.rs151
-rw-r--r--src/commands/meme/invoke.rs149
-rw-r--r--src/commands/meme/mod.rs75
-rw-r--r--src/commands/mod.rs90
-rw-r--r--src/commands/playback.rs153
-rw-r--r--src/commands/roll.rs107
-rw-r--r--src/commands/sound_levels.rs79
-rw-r--r--src/commands/today/mod.rs32
12 files changed, 979 insertions, 593 deletions
diff --git a/src/commands/game.rs b/src/commands/game.rs
new file mode 100644
index 0000000..72633b5
--- /dev/null
+++ b/src/commands/game.rs
@@ -0,0 +1,572 @@
+use std::{
+ fs,
+ iter,
+ path::PathBuf,
+ result::Result as StdResult,
+ str::{
+ self,
+ FromStr,
+ },
+};
+
+use anyhow::anyhow;
+use fnv::{
+ FnvHashMap,
+ FnvHashSet,
+};
+use itertools::Itertools;
+use lazy_static::lazy_static;
+use log::{
+ debug,
+ error,
+ info,
+};
+use serde::Deserialize;
+use serenity::model::{
+ guild::Guild,
+ id::UserId,
+};
+use tap::Pipe;
+use url::Url;
+
+use crate::{
+ bot::HttpKey,
+ util,
+ PoiseContext,
+ Result,
+ CONFIG,
+};
+
+lazy_static! {
+ static ref SPREADSHEET_URL: Url = Url::parse(&format!(
+ "https://sheets.googleapis.com/v4/spreadsheets/{}/values:batchGet",
+ &CONFIG.sheets.spreadsheet,
+ ))
+ .expect("parsing spreadsheet url");
+}
+
+#[derive(Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
+struct UserInfo {
+ name: String,
+
+ #[serde(flatten)]
+ profile: ProfileInfo,
+}
+
+#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
+struct ProfileInfo {
+ #[serde(rename = "steam")]
+ steam_id: Option<u64>,
+
+ #[serde(rename = "discord")]
+ discord_user_id: u64,
+}
+
+lazy_static! {
+ static ref USER_MAP_STR: String = {
+ let default_path = PathBuf::from_str("user_id_mapping").unwrap();
+ let mapping_path = CONFIG.user_id_mapping.as_ref().unwrap_or(&default_path);
+
+ fs::read_to_string(mapping_path).unwrap_or("{}".to_owned())
+ };
+ static ref USER_INFO_MAP: FnvHashMap<String, ProfileInfo> = {
+ let v: Vec<UserInfo> = serde_json::from_str(&USER_MAP_STR).unwrap();
+
+ let result = v
+ .into_iter()
+ .map(|ui| {
+ let UserInfo {
+ name,
+ profile,
+ } = ui;
+
+ (name, profile)
+ })
+ .collect::<FnvHashMap<_, _>>();
+
+ info!(
+ "loaded user info for {} users ({:#?})",
+ result.len(),
+ result.keys().collect::<Vec<_>>()
+ );
+
+ result
+ };
+ static ref DISCORD_MAP: FnvHashMap<UserId, String> = {
+ USER_INFO_MAP
+ .clone()
+ .into_iter()
+ .map(|(name, profile)| (UserId::new(profile.discord_user_id), name))
+ .collect::<FnvHashMap<_, _>>()
+ };
+ static ref STEAM_MAP: FnvHashMap<UserId, u64> = {
+ USER_INFO_MAP
+ .clone()
+ .into_iter()
+ .filter_map(|(_, profile)| {
+ profile.steam_id.map(|sid| (UserId::new(profile.discord_user_id), sid))
+ })
+ .collect::<FnvHashMap<_, _>>()
+ };
+ static ref ALPHABET: Vec<char> = (0..26).map(|x| (x + b'a') as char).collect();
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd)]
+enum GameStatus {
+ Installed,
+ NotInstalled,
+ NotOwned,
+ Unknown,
+}
+
+impl FromStr for GameStatus {
+ type Err = anyhow::Error;
+
+ fn from_str(s: &str) -> Result<Self> {
+ use std::char;
+
+ if s.starts_with('y') {
+ Ok(GameStatus::Installed)
+ } else if s.starts_with("n/i") {
+ Ok(GameStatus::NotInstalled)
+ } else if s.starts_with('n') {
+ Ok(GameStatus::NotOwned)
+ } else if s.chars().all(char::is_whitespace) {
+ Ok(GameStatus::Unknown)
+ } else {
+ Err(anyhow!(format!("unexpected status '{}'", s)))
+ }
+ }
+}
+
+pub fn commands() -> Vec<poise::Command<crate::PoiseData, anyhow::Error>> {
+ vec![installedgame(), ownedgame(), game(), updategaem()]
+}
+
+#[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
+}
+
+#[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
+}
+
+#[derive(Copy, Clone, Debug, thiserror::Error, PartialEq, Eq, Hash)]
+pub enum UserLookupError {
+ #[error("too many possible options ({}) for query", _0)]
+ Ambiguous(usize),
+
+ #[error("user wasn't found in the guild")]
+ NotFound,
+}
+
+pub fn get_user_id<S: AsRef<str>>(g: &Guild, s: S) -> StdResult<UserId, UserLookupError> {
+ let s = s.as_ref().trim_start_matches('@').to_lowercase();
+
+ if let Some(info) = USER_INFO_MAP.get(&s) {
+ return Ok(UserId::new(info.discord_user_id));
+ }
+
+ let nicks = g.members_nick_containing(&s, false, false);
+
+ {
+ let exact_match = nicks.iter().find(|(m, _)| m.display_name().to_lowercase() == s);
+
+ if let Some((m, _)) = exact_match {
+ return Ok(m.user.id);
+ }
+ }
+
+ let usernames = g.members_username_containing(&s, false, false);
+
+ {
+ let exact_match = usernames.iter().find(|(m, _)| m.user.name.to_lowercase() == s);
+
+ if let Some((m, _)) = exact_match {
+ return Ok(m.user.id);
+ }
+ }
+
+ let opts = nicks
+ .into_iter()
+ .chain(usernames)
+ .map(|(member, _)| member.user.id)
+ .collect::<FnvHashSet<_>>();
+
+ match opts.len() {
+ 0 => Err(UserLookupError::NotFound),
+ 1 => Ok(opts.into_iter().next().unwrap()),
+ x => Err(UserLookupError::Ambiguous(x)),
+ }
+}
+
+#[poise::command(prefix_command, guild_only, category = "gaem", aliases("gaem"))]
+async fn game(ctx: PoiseContext<'_>, args: util::RestVec) -> anyhow::Result<()> {
+ _game(ctx, args.into_inner(), GameStatus::Installed).await
+}
+
+async fn _game(
+ ctx: PoiseContext<'_>,
+ user_args: Vec<String>,
+ min_status: GameStatus,
+) -> anyhow::Result<()> {
+ use serenity::futures::StreamExt;
+
+ let users = {
+ let guild = ctx
+ .channel_id()
+ .to_channel(&ctx)
+ .await?
+ .guild()
+ .ok_or(anyhow!("couldn't find guild"))?;
+
+ let mut users = user_args
+ .into_iter()
+ .pipe(serenity::futures::stream::iter)
+ .filter_map(|u| {
+ let guild = &guild;
+ async move {
+ use std::borrow::Borrow;
+
+ let possible = {
+ let Ok(guild) =
+ guild.guild(&ctx).ok_or_else(|| anyhow!("couldn't find guild"))
+ else {
+ error!("failed retrieving guild");
+ return None;
+ };
+
+ get_user_id(guild.borrow(), &u)
+ };
+
+ debug!("parsed userid {:?}", possible);
+
+ match possible {
+ Ok(x) => Some(x),
+ Err(UserLookupError::NotFound) => {
+ let _ = util::reply(ctx, format!("didn't recognize {u}")).await;
+
+ None
+ },
+ Err(UserLookupError::Ambiguous(x)) => {
+ let _ =
+ util::reply(ctx, format!("too many matches ({x}) for {u}")).await;
+ None
+ },
+ }
+ }
+ })
+ .filter_map(|uid| async move {
+ let res = DISCORD_MAP.get(&uid).map(|s| s.to_lowercase());
+
+ if res.is_none() {
+ info!("user {uid} is not recognized");
+ }
+
+ res
+ })
+ .collect::<FnvHashSet<_>>()
+ .await;
+
+ if users.is_empty() {
+ let guild = guild.guild(&ctx).ok_or_else(|| anyhow!("couldn't find guild"))?;
+
+ let pairs = guild
+ .voice_states
+ .iter()
+ .filter_map(|(uid, voice)| voice.channel_id.map(|cid| (*uid, cid)))
+ .collect::<FnvHashMap<_, _>>();
+
+ let channel =
+ pairs.get(&ctx.author().id).cloned().unwrap_or(CONFIG.discord.voice_channel());
+
+ users = pairs
+ .iter()
+ .filter_map(|(uid, cid)| {
+ if *cid == channel {
+ DISCORD_MAP.get(uid).map(|s| s.to_lowercase())
+ } else {
+ None
+ }
+ })
+ .collect::<FnvHashSet<_>>();
+ }
+
+ users
+ };
+
+ let inferred = users.is_empty();
+
+ if inferred && users.len() < 2 || !inferred && users.is_empty() {
+ info!("too few known users to make game comparison");
+ util::reply(ctx, "yer too lonely").await?;
+ return Ok(());
+ }
+
+ let client = {
+ let data = ctx.serenity_context().data.read().await;
+ data.get::<HttpKey>().unwrap().clone()
+ };
+ let data = load_spreadsheet(&client).await?;
+
+ let user_indexes = (0..data.len())
+ .filter_map(|i| {
+ let user = data[i][0].to_lowercase();
+
+ if users.contains(&user) {
+ Some((user, i))
+ } else {
+ None
+ }
+ })
+ .collect::<FnvHashMap<_, _>>();
+
+ let data_ref = &data;
+ let user_games = user_indexes
+ .iter()
+ .map(|(user, col)| {
+ let empty_hash_set: FnvHashSet<_> = vec![].into_iter().collect();
+
+ let mut game_map = vec![
+ (GameStatus::Installed, empty_hash_set.clone()),
+ (GameStatus::NotInstalled, empty_hash_set.clone()),
+ (GameStatus::NotOwned, empty_hash_set.clone()),
+ (GameStatus::Unknown, empty_hash_set),
+ ]
+ .into_iter()
+ .collect::<FnvHashMap<_, _>>();
+
+ (1..data[*col].len()).for_each(|i| {
+ let status =
+ &data_ref[*col][i].parse::<GameStatus>().unwrap_or(GameStatus::Unknown);
+ let game = &data_ref[0][i];
+
+ game_map.get_mut(status).unwrap().insert(game);
+ });
+
+ (user, game_map)
+ })
+ .collect::<FnvHashMap<_, _>>();
+
+ let statuses = vec![
+ GameStatus::Installed,
+ GameStatus::NotOwned,
+ GameStatus::NotInstalled,
+ GameStatus::Unknown,
+ ]
+ .into_iter()
+ .filter(|s| s <= &min_status)
+ .collect::<Vec<_>>();
+
+ let mut games_in_common = {
+ let game_map = user_games.values().next().unwrap();
+
+ statuses.iter().fold(iter::empty().collect::<FnvHashSet<_>>(), |acc, s| {
+ acc.union(&game_map[s]).cloned().collect()
+ })
+ };
+
+ for (_user, game_map) in user_games.iter() {
+ let relevant_games =
+ statuses.iter().fold(iter::empty().collect::<FnvHashSet<_>>(), |acc, s| {
+ acc.union(&game_map[s]).cloned().collect()
+ });
+
+ games_in_common = games_in_common.intersection(&relevant_games).cloned().collect();
+ }
+
+ let mut games_formatted =
+ games_in_common.iter().sorted_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase())).join("\n");
+
+ if games_formatted.is_empty() {
+ games_formatted = "**LITERALLY NOTHING**".to_owned();
+ }
+
+ util::reply(ctx, games_formatted).await?;
+
+ Ok(())
+}
+
+async fn load_spreadsheet(client: &reqwest::Client) -> Result<Vec<Vec<String>>> {
+ let mut u = SPREADSHEET_URL.clone();
+
+ u.query_pairs_mut()
+ .append_pair("ranges", &format!("a1:{}", &CONFIG.sheets.max_column))
+ .append_pair("valueRenderOption", "FORMATTED_VALUE")
+ .append_pair("majorDimension", "COLUMNS")
+ .append_pair("key", &CONFIG.sheets.api_key);
+
+ let req = reqwest::Request::new(reqwest::Method::GET, u);
+ let resp = client.execute(req).await?;
+
+ #[derive(Deserialize)]
+ struct Resp {
+ #[serde(rename = "valueRanges")]
+ value_ranges: Vec<Inner>,
+ }
+
+ #[derive(Deserialize)]
+ struct Inner {
+ values: Vec<Vec<String>>,
+ }
+
+ let resp = resp.json::<Resp>().await?;
+
+ Ok(resp.value_ranges.into_iter().next().unwrap().values)
+}
+
+#[poise::command(
+ slash_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;
+
+ let client = {
+ let data = ctx.serenity_context().data.read().await;
+ data.get::<HttpKey>().unwrap().clone()
+ };
+
+ let user = match user {
+ None => ctx.author().id,
+ Some(user) => {
+ let guild = ctx
+ .channel_id()
+ .to_channel(&ctx)
+ .await?
+ .guild()
+ .ok_or(anyhow!("couldn't find guild"))?;
+
+ let guild = guild.guild(&ctx).ok_or(anyhow!("couldn't find guild"))?;
+
+ get_user_id(guild.borrow(), user).map_err(anyhow::Error::from)?
+ },
+ };
+
+ debug!("parsed userid {:?}", user);
+
+ let username = match DISCORD_MAP.get(&user) {
+ Some(s) => s,
+ None => {
+ util::reply(ctx, "WHO THE FUCK ARE YE").await?;
+ return Ok(());
+ },
+ };
+
+ let steam_id = match STEAM_MAP.get(&user) {
+ Some(u) => u,
+ None => {
+ util::reply(ctx, "WHO ARE YE ON STEAM").await?;
+ return Ok(());
+ },
+ };
+
+ let spreadsheet = load_spreadsheet(&client).await?;
+
+ let user_column = (0..spreadsheet.len())
+ .find(|x| spreadsheet[*x][0].to_lowercase() == username.to_lowercase());
+
+ let user_column = match user_column {
+ Some(c) => &spreadsheet[c][1..],
+ None => {
+ util::reply(ctx, "YER NOT IN THE SPREADSHEET").await?;
+ return Ok(());
+ },
+ };
+
+ lazy_static! {
+ static ref APPID_REGEX: Regex = Regex::new(r#"(?i)^\s*app\s*id\s*$"#).unwrap();
+ }
+
+ let appid_column = (0..spreadsheet.len()).find(|x| APPID_REGEX.is_match(&spreadsheet[*x][0]));
+
+ let appid_column = match appid_column {
+ Some(c) => &spreadsheet[c][1..],
+ None => {
+ error!("didn't find an appid column in the spreadsheet");
+ util::reply(ctx, "SPREADSHEET BROKE").await?;
+ return Ok(());
+ },
+ };
+
+ let missing_appids = (0..user_column.len())
+ .filter_map(|x| user_column[x].parse::<GameStatus>().ok().map(|s| (x, s)))
+ .filter(|(_, s)| *s == GameStatus::Unknown || *s == GameStatus::NotOwned)
+ .filter_map(|(x, _)| {
+ appid_column.get(x).and_then(|s| s.parse::<u64>().ok().map(|appid| (appid, x)))
+ });
+
+ let mut u = Url::parse("https://api.steampowered.com/IPlayerService/GetOwnedGames/v1")?;
+
+ u.query_pairs_mut()
+ .append_pair("key", &CONFIG.steam_api_key)
+ .append_pair("include_played_free_games", "1")
+ .append_pair("steamid", &steam_id.to_string());
+
+ #[derive(Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
+ struct SteamResp {
+ response: SteamInner,
+ }
+
+ #[derive(Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
+ struct SteamInner {
+ games: Vec<SteamGameEntry>,
+ }
+
+ #[derive(Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash)]
+ struct SteamGameEntry {
+ #[serde(rename = "appid")]
+ app_id: u64,
+
+ #[serde(rename = "playtime_forever")]
+ play_time: u64,
+ }
+
+ let client = {
+ let data = ctx.serenity_context().data.read().await;
+ data.get::<HttpKey>().unwrap().clone()
+ };
+
+ let games_owned = client
+ .get(u)
+ .send()
+ .await?
+ .json::<SteamResp>()
+ .await?
+ .response
+ .games
+ .into_iter()
+ .map(|ge| ge.app_id)
+ .collect::<FnvHashSet<_>>();
+
+ let found_games = missing_appids
+ .filter_map(|(ai, x)| {
+ if games_owned.contains(&ai) {
+ Some(&spreadsheet[0][x + 1])
+ } else {
+ None
+ }
+ })
+ .join("\n");
+
+ if !found_games.is_empty() {
+ let n_missing = found_games.chars().filter(|x| *x == '\n').count() + 1;
+ util::reply(
+ ctx,
+ format!(
+ "{n_missing} games owned on steam that are missing from the list:\n{found_games}"
+ ),
+ )
+ .await?;
+ } else {
+ util::reply(ctx, "up to date").await?;
+ }
+
+ Ok(())
+}
diff --git a/src/commands/help.rs b/src/commands/help.rs
deleted file mode 100644
index 0d84b2d..0000000
--- a/src/commands/help.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-use std::collections::HashSet;
-
-use serenity::{
- framework::standard::{
- help_commands,
- macros::help,
- Args,
- CommandGroup,
- CommandResult,
- HelpOptions,
- },
- model::{
- channel::Message,
- id::UserId,
- },
- prelude::*,
-};
-
-#[help]
-pub async fn help(
- ctx: &Context,
- msg: &Message,
- args: Args,
- opts: &'static HelpOptions,
- groups: &[&'static CommandGroup],
- owners: HashSet<UserId>,
-) -> CommandResult {
- help_commands::with_embeds(ctx, msg, args, opts, groups, owners).await?;
-
- Ok(())
-}
diff --git a/src/commands/meme/create.rs b/src/commands/meme/create.rs
index 9aff370..cad9bfc 100644
--- a/src/commands/meme/create.rs
+++ b/src/commands/meme/create.rs
@@ -2,25 +2,12 @@ use std::process::Stdio;
use anyhow::anyhow;
use diesel::result::Error as DieselError;
-use lazy_static::lazy_static;
use log::{
debug,
error,
warn,
};
-use serenity::{
- all::ReactionType,
- framework::standard::{
- macros::command,
- Args,
- CommandError,
- CommandResult,
- Delimiter,
- },
- futures::TryFutureExt,
- model::channel::Message,
- prelude::*,
-};
+use serenity::all::ReactionType;
use tap::Pipe;
use tokio::{
io::AsyncReadExt,
@@ -37,20 +24,16 @@ use crate::{
},
parse_times,
util,
+ PoiseContext,
FFMPEG_COMMAND,
};
-lazy_static! {
- static ref DELIMS: Vec<Delimiter> = vec![' '.into(), '\n'.into(), '\t'.into()];
-}
-
-#[command]
-pub async fn addmeme(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
- let mut args = Args::new(args.rest(), DELIMS.as_ref());
-
- let title = args.single_quoted::<String>()?;
- let text = args.rest().to_owned();
-
+#[poise::command(slash_command, prefix_command, guild_only, category = "memes")]
+pub async fn addmeme(
+ ctx: PoiseContext<'_>,
+ title: String,
+ #[rest] text: String,
+) -> anyhow::Result<()> {
let text = if text.is_empty() {
None
} else {
@@ -59,20 +42,21 @@ pub async fn addmeme(ctx: &Context, msg: &Message, args: Args) -> CommandResult
let mut conn = connection().await?;
- let image = msg.attachments.first();
+ let image = util::msg(ctx).and_then(|msg| msg.attachments.first());
if image.is_none() && text.is_none() {
warn!("tried to create non-audio meme with no image or text");
- return util::send(ctx, msg.channel_id, "hahAA it's empty xdddd", msg.tts)
- .map_err(CommandError::from)
- .await;
+
+ util::reply(ctx, "hahAA it's empty xdddd").await?;
+ return Ok(());
}
let mut image_id = None;
if let Some(att) = image {
let data = att.download().await?;
- image_id = Some(Image::create(&mut conn, &att.filename, data, msg.author.id.get()).await?);
+ image_id =
+ Some(Image::create(&mut conn, &att.filename, data, ctx.author().id.get()).await?);
};
let save_result = NewMeme {
@@ -82,24 +66,25 @@ pub async fn addmeme(ctx: &Context, msg: &Message, args: Args) -> CommandResult
audio_id: None,
metadata_id: 0,
}
- .save(&mut conn, msg.author.id.get())
+ .save(&mut conn, ctx.author().id.get())
.await
.map(|_| {});
use diesel::result::DatabaseErrorKind;
match save_result {
Ok(_) => {
- msg.react(&ctx, ReactionType::Unicode("👌".to_string())).await?;
+ util::react(ctx, ReactionType::Unicode("👌".to_string())).await?;
},
Err(e) => {
if let Some(DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, _)) =
e.downcast_ref::<DieselError>()
{
error!("tried to create meme that already exists");
- msg.react(&ctx, ReactionType::Unicode("❌".to_owned())).await?;
- return util::send(ctx, msg.channel_id, "that meme already exists", msg.tts)
- .map_err(CommandError::from)
- .await;
+
+ util::react(ctx, ReactionType::Unicode("❌".to_owned())).await?;
+ util::reply(ctx, "that meme already exists").await?;
+
+ return Ok(());
}
return Err(e.into());
@@ -109,19 +94,19 @@ pub async fn addmeme(ctx: &Context, msg: &Message, args: Args) -> CommandResult
Ok(())
}
-#[command]
-pub async fn addaudiomeme(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
+#[poise::command(slash_command, prefix_command, guild_only, category = "memes")]
+pub async fn addaudiomeme(
+ ctx: PoiseContext<'_>,
+ title: String,
+ audio_str: String,
+ #[rest] rest: String,
+) -> anyhow::Result<()> {
debug!("running addaudiomeme");
- let mut args = Args::new(args.rest(), DELIMS.as_ref());
-
- let title = args.single_quoted::<String>()?;
- let audio_str = args.single_quoted::<String>()?;
-
let elems = audio_str.split_whitespace().collect::<Vec<_>>();
if elems.is_empty() {
- util::send(ctx, msg.channel_id, "are you stupid", msg.tts).await?;
+ util::reply(ctx, "are you stupid").await?;
return Err(anyhow!("no audio link was provided").into());
}
@@ -168,23 +153,23 @@ pub async fn addaudiomeme(ctx: &Context, msg: &Message, args: Args) -> CommandRe
let mut audio_reader = ffmpeg_command.stdout.unwrap();
- let text = args.rest().to_owned();
- let text = if text.is_empty() {
+ let text = if rest.is_empty() {
None
} else {
- Some(text)
+ Some(rest)
};
let mut conn = connection().await?;
- let image_att = msg.attachments.first().ok_or(anyhow!("no attachment"));
+ let image_att =
+ util::msg(ctx).and_then(|x| x.attachments.first()).ok_or(anyhow!("no attachment"));
let mut image_id = None;
if let Ok(att) = image_att {
let data = att.download().await?;
image_id =
- Image::create(&mut conn, &att.filename, data, msg.author.id.get()).await?.pipe(Some);
+ Image::create(&mut conn, &att.filename, data, ctx.author().id.get()).await?.pipe(Some);
}
let mut audio_data = Vec::new();
@@ -193,12 +178,12 @@ pub async fn addaudiomeme(ctx: &Context, msg: &Message, args: Args) -> CommandRe
if bytes == 0 {
debug!("read 0 bytes from audio reader");
- return util::send(ctx, msg.channel_id, "🔇🔇🔇🔕🔕🔕🔕🔕🔇🔕🔕🔇🔕🔕📣📢📣📢📣", msg.tts)
- .map_err(CommandError::from)
- .await;
+
+ util::reply(ctx, "🔇🔇🔇🔕🔕🔕🔕🔕🔇🔕🔕🔇🔕🔕📣📢📣📢📣").await?;
+ return Ok(());
}
- let audio_id = Audio::create(&mut conn, audio_data, msg.author.id.get()).await?;
+ let audio_id = Audio::create(&mut conn, audio_data, ctx.author().id.get()).await?;
let save_result = NewMeme {
title,
@@ -207,24 +192,25 @@ pub async fn addaudiomeme(ctx: &Context, msg: &Message, args: Args) -> CommandRe
audio_id: Some(audio_id),
metadata_id: 0,
}
- .save(&mut conn, msg.author.id.get())
+ .save(&mut conn, ctx.author().id.get())
.await
.map(|_| {});
use diesel::result::DatabaseErrorKind;
match save_result {
Ok(_) => {
- msg.react(&ctx, ReactionType::Unicode("👌".to_owned())).await?;
+ util::react(ctx, ReactionType::Unicode("👌".to_owned())).await?;
},
Err(e) => {
if let Some(DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, _)) =
e.downcast_ref::<DieselError>()
{
error!("tried to create meme that already exists");
- msg.react(&ctx, ReactionType::Unicode("❌".to_owned())).await?;
- return util::send(ctx, msg.channel_id, "that meme already exists", msg.tts)
- .map_err(CommandError::from)
- .await;
+
+ util::react(ctx, ReactionType::Unicode("❌".to_owned())).await?;
+ util::reply(ctx, "that meme already exists").await?;
+
+ return Ok(());
}
return Err(e.into());
diff --git a/src/commands/meme/delete.rs b/src/commands/meme/delete.rs
index 6af1b6b..25ddf0d 100644
--- a/src/commands/meme/delete.rs
+++ b/src/commands/meme/delete.rs
@@ -3,42 +3,33 @@ use diesel::{
NotFound,
};
use log::info;
-use serenity::{
- all::ReactionType,
- framework::standard::{
- macros::command,
- Args,
- CommandResult,
- },
- model::channel::Message,
- prelude::*,
-};
+use serenity::all::ReactionType;
use crate::{
db::{
connection,
delete_meme,
},
+ msg,
util,
+ PoiseContext,
};
-#[command]
-#[aliases("delmem")]
-pub async fn delmeme(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
- let title = args.single_quoted::<String>()?;
-
+#[poise::command(slash_command, prefix_command, guild_only, category = "memes", aliases("delmem"))]
+pub async fn delmeme(ctx: PoiseContext<'_>, title: String) -> anyhow::Result<()> {
let mut conn = connection().await?;
- match delete_meme(&mut conn, &title, msg.author.id.get()).await {
+ match delete_meme(&mut conn, &title, ctx.author().id.get()).await {
Ok(_) => {
- msg.react(ctx, ReactionType::Unicode("💀".to_owned())).await?;
+ util::react(ctx, ReactionType::Unicode("💀".to_owned())).await?;
Ok(())
},
Err(e) => {
if let Some(NotFound) = e.downcast_ref::<DieselError>() {
- msg.react(&ctx, ReactionType::Unicode("❓".to_owned())).await?;
info!("attempted to delete nonexistent meme: '{}'", title);
- util::send(ctx, msg.channel_id, "nice try", msg.tts).await?;
+
+ util::react(ctx, ReactionType::Unicode("❓".to_owned())).await?;
+ util::reply(ctx, "nice try").await?;
return Ok(());
}
diff --git a/src/commands/meme/history.rs b/src/commands/meme/history.rs
index edc75cd..cfd78df 100644
--- a/src/commands/meme/history.rs
+++ b/src/commands/meme/history.rs
@@ -1,4 +1,3 @@
-use anyhow::anyhow;
use diesel::{
result::Error as DieselError,
NotFound,
@@ -11,18 +10,10 @@ use log::{
info,
};
use serenity::{
- framework::standard::{
- macros::command,
- Args,
- CommandError,
- CommandResult,
- },
futures::{
StreamExt,
- TryFutureExt,
TryStreamExt,
},
- model::channel::Message,
prelude::*,
};
use tap::Pipe;
@@ -32,6 +23,7 @@ use timeago::{
};
use crate::{
+ commands::game::get_user_id,
db::{
self,
connection,
@@ -40,6 +32,7 @@ use crate::{
Metadata,
},
util,
+ PoiseContext,
CONFIG,
};
@@ -55,9 +48,14 @@ lazy_static! {
static CLEAN_DATE_FORMAT: &str = "%b %-e %Y";
-#[command]
-#[aliases("what", "hwaet", "hwæt")]
-pub async fn wat(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
+#[poise::command(
+ slash_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?;
let record = match InvocationRecord::last(&mut conn).await {
@@ -65,12 +63,12 @@ pub async fn wat(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
Err(e) => {
if let Some(NotFound) = e.downcast_ref::<DieselError>() {
info!("found no memes in history");
- return util::send(ctx, msg.channel_id, "no one has ever memed before", msg.tts)
- .map_err(CommandError::from)
- .await;
+
+ util::reply(ctx, "no one has ever memed before").await?;
+ return Ok(());
}
- util::send(ctx, msg.channel_id, "BAD MEME BAD MEME", msg.tts).await?;
+ util::reply(ctx, "BAD MEME BAD MEME").await?;
return Err(e.into());
},
};
@@ -82,44 +80,41 @@ pub async fn wat(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
let metadata = Metadata::find(&mut conn, meme.metadata_id).await?;
let author = CONFIG.discord.guild().member(&ctx, metadata.created_by as u64).await?;
- util::send(
+ util::reply(
ctx,
- msg.channel_id,
&format!(
"that was \"{}\" by {} ({})",
meme.title,
author.mention(),
metadata.created.date().format(CLEAN_DATE_FORMAT)
),
- msg.tts,
)
- .await?
+ .await?;
},
Err(e) => {
if let Some(NotFound) = e.downcast_ref::<DieselError>() {
info!("last meme not found in database");
- return util::send(ctx, msg.channel_id, "heuueueeeeh?", msg.tts)
- .await
- .map_err(CommandError::from);
+
+ util::reply(ctx, "heuueueeeeh?").await?;
+ return Ok(());
}
- util::send(ctx, msg.channel_id, "do i look like i know what a jpeg is", msg.tts)
- .await?;
+ util::reply(ctx, "do i look like i know what a jpeg is").await?;
return Err(e.into());
},
};
- meme.map(|_| {}).map_err(CommandError::from)
+ let _meme = meme?;
+ Ok(())
}
-#[command]
-#[aliases("hist")]
-pub async fn history(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
- let n = args.single_quoted::<usize>().unwrap_or(CONFIG.default_hist);
-
+#[poise::command(slash_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);
+
if n > CONFIG.max_hist {
debug!("user requested more than MAX_HIST ({}) items from history", CONFIG.max_hist);
- util::send(ctx, msg.channel_id, "YER PUSHIN ME OVER THE FUCKIN LINE", true).await?;
+ util::reply(ctx, "YER PUSHIN ME OVER THE FUCKIN LINE").await?;
}
let n = n.min(CONFIG.max_hist);
@@ -131,9 +126,9 @@ pub async fn history(ctx: &Context, msg: &Message, mut args: Args) -> CommandRes
if records.is_empty() {
info!("no memes in history");
- return util::send(ctx, msg.channel_id, "i don't remember anything :(", msg.tts)
- .map_err(CommandError::from)
- .await;
+ util::reply(ctx, "i don't remember anything :(").await?;
+
+ return Ok(());
}
info!("reporting meme history (len {})", n);
@@ -198,19 +193,19 @@ pub async fn history(ctx: &Context, msg: &Message, mut args: Args) -> CommandRes
},
};
- Result::<_, CommandError>::Ok(result)
+ anyhow::Ok(result)
})
.try_collect::<Vec<String>>()
.await?;
let resp = resp.join("\n");
+ util::reply(ctx, resp).await?;
- util::send(ctx, msg.channel_id, &resp, false).await.map_err(CommandError::from)
+ Ok(())
}
-#[command]
-#[aliases("stat")]
-pub async fn stats(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
+#[poise::command(slash_command, prefix_command, guild_only, category = "memes", aliases("stat"))]
+pub async fn stats(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
use db;
use serenity::model::{
id::UserId,
@@ -277,11 +272,12 @@ and *{}* was the most-memed overall ({})"#,
stats.most_popular_meme_overall_count,
);
- util::send(ctx, msg.channel_id, s, msg.tts).map_err(CommandError::from).await
+ util::reply(ctx, s).await?;
+ Ok(())
}
-#[command]
-pub async fn memers(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
+#[poise::command(slash_command, prefix_command, guild_only, category = "memes")]
+pub async fn memers(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
use serenity::model::id::UserId;
let s = db::memers()
@@ -302,25 +298,25 @@ pub async fn memers(ctx: &Context, msg: &Message, _args: Args) -> CommandResult
info.most_used_meme_count,
);
- Result::<_, CommandError>::Ok(res)
+ anyhow::Ok(res)
})
.try_collect::<Vec<String>>()
.await?
.into_iter()
.join("\n");
- util::send(ctx, msg.channel_id, &s, msg.tts).map_err(CommandError::from).await
+ util::reply(ctx, s).await?;
+
+ Ok(())
}
-#[command]
-pub async fn query(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
+#[poise::command(prefix_command, guild_only, category = "memes")]
+pub async fn query(ctx: PoiseContext<'_>, rest: util::RestVec) -> anyhow::Result<()> {
use regex::Regex;
use serenity::model::id::UserId;
- use std::borrow::Borrow;
use crate::{
db,
- game::get_user_id,
CONFIG,
};
@@ -329,42 +325,31 @@ pub async fn query(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
static ref AGE_REGEX: Regex = Regex::new(r"(?i)(?:age|order)=(.*)").unwrap();
}
- let creator: Option<u64> = {
- let guild =
- msg.channel_id.to_channel(&ctx).await?.guild().ok_or(anyhow!("couldn't find guild"))?;
+ let mut rest = rest.into_inner();
- let guild = guild.guild(&ctx).ok_or(anyhow!("couldn't find guild"))?;
+ let creator: Option<u64> = try {
+ let fst = rest.first()?;
+ let captures = CREATOR_REGEX.captures(fst)?;
+ let creator = captures.get(1)?.as_str().to_owned();
- let creator = args.quoted().current().map(|s| CREATOR_REGEX.is_match(s)).unwrap_or(false);
- if creator {
- args.single_quoted::<String>()
- .ok()
- .and_then(|s| {
- CREATOR_REGEX.captures(&s).and_then(|c| c.get(1)).map(|x| x.as_str().to_owned())
- })
- .and_then(|s| get_user_id(guild.borrow(), s).ok().map(UserId::get))
- } else {
- None
- }
+ let guild = ctx.guild()?;
+ let user_id = get_user_id(&guild, creator).ok()?.get();
+ rest.pop();
+
+ user_id
};
- let order = {
- let order = args.quoted().current().map(|s| AGE_REGEX.is_match(s)).unwrap_or(false);
+ let order: Option<String> = try {
+ let fst = rest.first()?;
+ let captures = AGE_REGEX.captures(fst)?;
+ let order = captures.get(1)?.as_str().to_owned();
- if order {
- args.single_quoted::<String>()
- .ok()
- .and_then(|s| {
- AGE_REGEX.captures(&s).and_then(|c| c.get(1)).map(|x| x.as_str().to_owned())
- })
- .map(|s: String| s.contains("new"))
- .unwrap_or(true)
- } else {
- true
- }
+ order
};
- let iter = db::query_meme(args.rest(), creator, order).await?.into_iter();
+ let order = order.is_some_and(|o| o.contains("new"));
+
+ let iter = db::query_meme(rest.join(" "), creator, order).await?.into_iter();
let result = iter
.pipe(serenity::futures::stream::iter)
@@ -380,7 +365,7 @@ pub async fn query(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
meme.content.map_or(0, |s| s.len()),
meme.image_id.map_or("NO", |_s| "YES"),
meme.audio_id.map_or("NO", |_s| "YES"),
- )) as Result<String, CommandError>
+ )) as anyhow::Result<String>
})
.try_collect::<Vec<String>>()
.await;
@@ -401,10 +386,10 @@ pub async fn query(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
if result.is_empty() {
info!("no memes matched query");
- return util::send(ctx, msg.channel_id, "no match".to_owned(), msg.tts)
- .map_err(CommandError::from)
- .await;
+ util::reply(ctx, "no match").await?;
+ return Ok(());
}
- util::send(ctx, msg.channel_id, &result, msg.tts).map_err(CommandError::from).await
+ util::reply(ctx, result).await?;
+ Ok(())
}
diff --git a/src/commands/meme/invoke.rs b/src/commands/meme/invoke.rs
index 1400452..e399e82 100644
--- a/src/commands/meme/invoke.rs
+++ b/src/commands/meme/invoke.rs
@@ -2,19 +2,7 @@ use diesel::{
result::Error as DieselError,
NotFound,
};
-use itertools::Itertools;
use log::info;
-use serenity::{
- framework::standard::{
- macros::command,
- Args,
- CommandError,
- CommandResult,
- },
- futures::TryFutureExt,
- model::channel::Message,
- prelude::*,
-};
use crate::{
commands::meme::send_meme,
@@ -25,42 +13,49 @@ use crate::{
InvocationRecord,
},
util,
+ PoiseContext,
};
-#[command]
-#[aliases("mem")]
-pub async fn meme(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
- _meme(ctx, msg, args, AudioPlayback::Optional).await
+#[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
}
-#[command]
-pub async fn omen(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
- let args = Args::new("", &[]);
- _meme(ctx, msg, args, AudioPlayback::Optional).await
+#[poise::command(slash_command, prefix_command, guild_only, category = "memes")]
+pub async fn omen(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
+ _meme(ctx, "", AudioPlayback::Optional).await
}
-#[command]
-pub async fn silentomen(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
- let args = Args::new("", &[]);
- _meme(ctx, msg, args, AudioPlayback::Prohibited).await
+#[poise::command(slash_command, prefix_command, guild_only, category = "memes")]
+pub async fn silentomen(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
+ _meme(ctx, "", AudioPlayback::Prohibited).await
}
-#[command]
-pub async fn audioomen(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
- let args = Args::new("", &[]);
- _meme(ctx, msg, args, AudioPlayback::Required).await
+#[poise::command(slash_command, prefix_command, guild_only, category = "memes")]
+pub async fn audioomen(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
+ _meme(ctx, "", AudioPlayback::Required).await
}
-#[command]
-#[aliases("audiomeme", "audiomem")]
-pub async fn audio_meme(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
- _meme(ctx, msg, args, 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
}
-#[command]
-#[aliases("silentmeme", "silentmem")]
-pub async fn silent_meme(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
- _meme(ctx, msg, args, AudioPlayback::Prohibited).await
+#[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
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
@@ -71,21 +66,20 @@ enum AudioPlayback {
}
async fn _meme(
- ctx: &Context,
- msg: &Message,
- args: Args,
+ ctx: PoiseContext<'_>,
+ args: impl AsRef<str>,
audio_playback: AudioPlayback,
-) -> CommandResult {
+) -> anyhow::Result<()> {
+ let args = args.as_ref().trim();
+
if args.is_empty() || audio_playback != AudioPlayback::Optional {
- return rand_meme(ctx, msg, audio_playback).await;
+ return rand_meme(ctx, audio_playback).await;
}
- let search = args.raw().join(" ");
-
let mut conn = connection().await?;
- let mem = match find_meme(&mut conn, search).await {
+ let mem = match find_meme(&mut conn, args).await {
Ok(x) => {
- InvocationRecord::create(&mut conn, msg.author.id.get(), msg.id.get(), x.id, false)
+ InvocationRecord::create(&mut conn, ctx.author().id.get(), ctx.id(), x.id, false)
.await?;
x
@@ -93,27 +87,23 @@ async fn _meme(
Err(e) => {
return if let Some(NotFound) = e.downcast_ref::<DieselError>() {
info!("requested meme not found in database");
- util::send(ctx, msg.channel_id, "c'mon baby, guesstimate", msg.tts)
- .await
- .map_err(CommandError::from)
+
+ util::reply(ctx, "c'mon baby, guesstimate").await?;
+ Ok(())
} else {
- util::send(ctx, msg.channel_id, "what in ryan's name", msg.tts).await?;
+ util::reply(ctx, "what in ryan's name").await?;
Err(e.into())
};
},
};
- send_meme(ctx, &mem, &mut conn, msg).await
+ send_meme(ctx, &mem, &mut conn).await
}
-async fn rand_meme(
- ctx: &Context,
- message: &Message,
- audio_playback: AudioPlayback,
-) -> CommandResult {
+async fn rand_meme(ctx: PoiseContext<'_>, audio_playback: AudioPlayback) -> anyhow::Result<()> {
let mut conn = connection().await?;
- let should_audio = util::users_listening(ctx).await?;
+ let should_audio = util::users_listening(ctx.serenity_context()).await?;
let mem = match audio_playback {
AudioPlayback::Required => db::rand_audio_meme(&mut conn).await,
@@ -123,56 +113,53 @@ async fn rand_meme(
match mem {
Ok(mem) => {
- InvocationRecord::create(
- &mut conn,
- message.author.id.get(),
- message.id.get(),
- mem.id,
- true,
- )
- .await?;
- send_meme(ctx, &mem, &mut conn, message).await?;
+ InvocationRecord::create(&mut conn, ctx.author().id.get(), ctx.id(), mem.id, true)
+ .await?;
+ send_meme(ctx, &mem, &mut conn).await?;
Ok(())
},
Err(e) => {
if let Some(NotFound) = e.downcast_ref::<DieselError>() {
info!("random meme not found");
- return util::send(ctx, message.channel_id, "i don't know any :(", message.tts)
- .map_err(CommandError::from)
- .await;
+
+ util::reply(ctx, "i don't know any :(").await?;
+ return Ok(());
}
- util::send(ctx, message.channel_id, "HELP", message.tts).await?;
+ util::reply(ctx, "HELP").await?;
Err(e.into())
},
}
}
-#[command]
-#[aliases("rarememe", "raremem")]
-pub async fn rare_meme(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
- let should_audio = util::users_listening(ctx).await?;
+#[poise::command(
+ slash_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?;
let mut conn = connection().await?;
let meme = db::rare_meme(&mut conn, should_audio).await;
match meme {
Ok(meme) => {
- InvocationRecord::create(&mut conn, msg.author.id.get(), msg.id.get(), meme.id, true)
+ InvocationRecord::create(&mut conn, ctx.author().id.get(), ctx.id(), meme.id, true)
.await?;
- send_meme(ctx, &meme, &mut conn, msg).await
+ send_meme(ctx, &meme, &mut conn).await
},
Err(e) => {
if let Some(NotFound) = e.downcast_ref::<DieselError>() {
info!("rare meme not found");
- return util::send(ctx, msg.channel_id, "i don't know any :(", msg.tts)
- .map_err(CommandError::from)
- .await;
+ util::reply(ctx, "i don't know any :(").await?;
+
+ return Ok(());
}
- util::send(ctx, msg.channel_id, "THE MEME MARKET IS IN FREEFALL", msg.tts)
- .map_err(CommandError::from)
- .await?;
+ util::reply(ctx, "THE MEME MARKET IS IN FREEFALL").await?;
Err(e.into())
},
diff --git a/src/commands/meme/mod.rs b/src/commands/meme/mod.rs
index cfe02ee..0108219 100644
--- a/src/commands/meme/mod.rs
+++ b/src/commands/meme/mod.rs
@@ -8,12 +8,6 @@ use serenity::{
CreateAttachment,
CreateMessage,
},
- framework::standard::{
- macros::group,
- CommandResult,
- },
- model::channel::Message,
- prelude::*,
};
use songbird::input::{
core::{
@@ -26,53 +20,54 @@ use songbird::input::{
Input,
};
+pub use self::{
+ create::*,
+ delete::*,
+ history::*,
+ invoke::*,
+};
use crate::{
- commands::songbird,
+ commands::playback::songbird,
db::{
Audio,
Meme,
},
+ msg,
+ util,
+ PoiseContext,
CONFIG,
};
-pub use self::{
- create::*,
- delete::*,
- history::*,
- invoke::*,
-};
-
mod create;
mod delete;
mod history;
mod invoke;
-#[group]
-#[commands(
- meme,
- audio_meme,
- silent_meme,
- omen,
- audioomen,
- silentomen,
- addmeme,
- addaudiomeme,
- delmeme,
- wat,
- stats,
- history,
- rare_meme,
- memers,
- query
-)]
-struct Memes;
+pub fn commands() -> Vec<poise::Command<crate::PoiseData, anyhow::Error>> {
+ vec![
+ meme(),
+ silent_meme(),
+ audio_meme(),
+ rare_meme(),
+ omen(),
+ silentomen(),
+ audioomen(),
+ addmeme(),
+ addaudiomeme(),
+ delmeme(),
+ history(),
+ stats(),
+ memers(),
+ wat(),
+ query(),
+ ]
+}
async fn send_meme(
- ctx: &Context,
+ ctx: PoiseContext<'_>,
t: &Meme,
conn: &mut AsyncPgConnection,
- msg: &Message,
-) -> CommandResult {
+) -> anyhow::Result<()> {
let should_tts =
t.content.as_ref().map(|t| !t.is_empty()).unwrap_or(false) && random::<u32>() % 25 == 0;
@@ -95,12 +90,12 @@ async fn send_meme(
let image = image?;
let att = CreateAttachment::bytes(image.data.as_slice(), &image.filename);
- msg.channel_id.send_files(ctx, vec![att], cmsg).await?;
+ ctx.channel_id().send_files(ctx, vec![att], cmsg).await?;
},
None => {
if t.content.is_some() {
- msg.channel_id.send_message(ctx, cmsg).await?;
+ ctx.channel_id().send_message(ctx, cmsg).await?;
}
},
};
@@ -108,7 +103,7 @@ async fn send_meme(
if let Some(audio) = audio {
let audio = audio?;
- let (_sb, call) = songbird(ctx, msg).await?;
+ let (_sb, call) = songbird(ctx).await?;
let mut call = call.lock().await;
if call.current_channel().is_none() {
@@ -117,7 +112,7 @@ async fn send_meme(
call.enqueue_input(Input::Lazy(Box::new(audio))).await;
- msg.react(ctx, ReactionType::Unicode("📣".to_owned())).await?;
+ util::react(ctx, ReactionType::Unicode("📣".to_owned())).await?;
}
Ok(())
diff --git a/src/commands/mod.rs b/src/commands/mod.rs
index 69c9185..ba87adb 100644
--- a/src/commands/mod.rs
+++ b/src/commands/mod.rs
@@ -1,18 +1,14 @@
-use crate::util;
-use log::info;
-use serenity::framework::{
- standard::macros::group,
- StandardFramework,
-};
+use poise::builtins::PrettyHelpConfiguration;
-#[cfg(feature = "db")]
-pub use self::meme::*;
-pub use self::{
- playback::*,
- roll::ROLL_COMMAND,
- today::TODAY_COMMAND,
+use crate::{
+ commands::playback::_play,
+ util,
+ PoiseContext,
};
+#[cfg(feature = "games")]
+pub mod game;
+
#[cfg(feature = "db")]
pub(crate) mod meme;
pub(crate) mod playback;
@@ -20,40 +16,52 @@ pub(crate) mod roll;
pub(crate) mod sound_levels;
pub(crate) mod today;
-mod help;
+#[cfg(feature = "db")]
+pub use self::meme::*;
-#[group]
-#[only_in(guild)]
-#[commands(roll, today)]
-struct General;
+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(),
+ ];
-pub fn register_commands(f: StandardFramework) -> StandardFramework {
- let result = f.group(&PLAYBACK_GROUP).group(&GENERAL_GROUP);
+ #[cfg(feature = "games")]
+ commands.extend(game::commands());
#[cfg(feature = "db")]
- let result = result.group(&MEMES_GROUP);
+ commands.extend(meme::commands());
- #[cfg(feature = "games")]
- let result = result.group(&crate::game::GAME_GROUP);
+ commands
+}
+
+#[poise::command(slash_command, prefix_command, aliases("halp"))]
+pub async fn help(ctx: PoiseContext<'_>, command: Option<String>) -> anyhow::Result<()> {
+ poise::builtins::pretty_help(
+ ctx,
+ command.as_ref().map(|x| x.as_str()),
+ PrettyHelpConfiguration {
+ ..Default::default()
+ },
+ )
+ .await?;
+
+ Ok(())
+}
+
+pub async fn unrecognized(ctx: PoiseContext<'_>, u: url::Url) -> anyhow::Result<()> {
+ if !u.scheme().starts_with("http") {
+ util::reply(ctx, "format your commands right. fuck you.").await?;
+ return Ok(());
+ }
- result.help(&help::HELP).unrecognised_command(|ctx, msg, unrec| {
- Box::pin(async move {
- let url = match msg.content.split_whitespace().nth(1) {
- Some(x) if x.starts_with("http") => x,
- _ => {
- info!("bad command formatting: '{}'", unrec);
- let _ = util::send(
- ctx,
- msg.channel_id,
- "format your commands right. fuck you.",
- msg.tts,
- )
- .await;
- return;
- },
- };
+ let _ = _play(ctx, &u).await?;
- let _ = _play(ctx, msg, url).await;
- })
- })
+ Ok(())
}
diff --git a/src/commands/playback.rs b/src/commands/playback.rs
index 1c3ab95..98ae613 100644
--- a/src/commands/playback.rs
+++ b/src/commands/playback.rs
@@ -1,78 +1,46 @@
+use std::sync::Arc;
+
use log::{
debug,
- error,
info,
warn,
};
-use serenity::{
- framework::standard::{
- macros::{
- command,
- group,
- },
- Args,
- CommandError,
- CommandResult,
- },
- model::channel::Message,
- prelude::*,
-};
+use serenity::prelude::*;
use songbird::{
input::YoutubeDl,
Call,
Songbird,
};
-use std::sync::Arc;
-use tap::Conv;
use crate::{
bot::HttpKey,
- commands::sound_levels::*,
util,
+ PoiseContext,
CONFIG,
};
-#[group]
-#[commands(skip, pause, resume, list, die, mute, unmute, play)]
-#[only_in(guild)]
-struct Playback;
-
-pub async fn songbird(
- ctx: &Context,
- msg: &Message,
-) -> Result<(Arc<Songbird>, Arc<Mutex<Call>>), CommandError> {
- let Some(gid) = msg.guild_id else {
+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());
};
- let sb = songbird::get(ctx).await.expect("acquiring songbird handle");
+ let sb = songbird::get(ctx.serenity_context()).await.expect("acquiring songbird handle");
let call = sb.get_or_insert(gid);
Ok((sb, call))
}
-pub async fn _play(ctx: &Context, msg: &Message, url: &str) -> CommandResult {
- use url::{
- Host,
- Url,
- };
+pub async fn _play(ctx: PoiseContext<'_>, url: &url::Url) -> anyhow::Result<()> {
+ use url::Host;
debug!("playing '{}'", url);
- if !url.starts_with("http") {
+ if !url.scheme().starts_with("http") {
warn!("got bad url argument to play: {}", url);
- util::send(ctx, msg.channel_id, "bAD LiNk", msg.tts).await?;
+
+ util::reply(ctx, "bAD LiNk").await?;
return Ok(());
}
- let url = match Url::parse(url) {
- Err(e) => {
- error!("bad url: {}", e);
- util::send(ctx, msg.channel_id, "INVALID URL", msg.tts).await?;
- return Ok(());
- },
- Ok(u) => u,
- };
-
let host = url.host().and_then(|u| match u {
Host::Domain(h) => Some(h.to_owned()),
_ => None,
@@ -81,16 +49,16 @@ pub async fn _play(ctx: &Context, msg: &Message, url: &str) -> CommandResult {
if host.map(|h| h.to_lowercase().contains("imgur")).unwrap_or(false) {
info!("detected imgur link");
- if msg.author.id.get() == 106160362109272064 {
- util::send(ctx, msg.channel_id, "fuck you conway", true).await?;
+ if ctx.author().id == 106160362109272064 {
+ util::reply(ctx, "fuck you conway").await?;
} else {
- util::send(ctx, msg.channel_id, "IMGUR IS BAD, YOU TRASH CAN MAN", msg.tts).await?;
+ util::reply(ctx, "IMGUR IS BAD, YOU TRASH CAN MAN").await?;
}
return Ok(());
}
- let (_sb, call) = songbird(ctx, msg).await?;
+ let (_sb, call) = songbird(ctx).await?;
let mut call = call.lock().await;
if call.current_channel().is_none() {
@@ -98,38 +66,31 @@ pub async fn _play(ctx: &Context, msg: &Message, url: &str) -> CommandResult {
}
let client = {
- let data = ctx.data.read().await;
+ let data = ctx.serenity_context().data.read().await;
data.get::<HttpKey>().unwrap().clone()
};
- let input = YoutubeDl::new_ytdl_like("yt-dlp", client.clone(), url.conv::<String>());
+ let input = YoutubeDl::new_ytdl_like("yt-dlp", client.clone(), url.to_string());
call.enqueue_input(input.into()).await;
Ok(())
}
-#[command]
-pub async fn play(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
- if args.is_empty() {
- return _resume(ctx, msg).await;
- }
-
- let url = match args.single::<String>() {
- Ok(url) => url,
- Err(e) => {
- error!("unable to parse url from args: {}", e);
- return util::send(ctx, msg.channel_id, "BAD LINK", msg.tts)
- .await
- .map_err(CommandError::from);
- },
+#[poise::command(slash_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>,
+) -> anyhow::Result<()> {
+ let Some(u) = u else {
+ return _resume(ctx).await;
};
- _play(ctx, msg, &url).await
+ _play(ctx, &u).await
}
-#[command]
-pub async fn pause(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
- let (_sb, call) = songbird(ctx, msg).await?;
+#[poise::command(slash_command, prefix_command, guild_only, category = "playback")]
+pub async fn pause(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
+ let (_sb, call) = songbird(ctx).await?;
let call = call.lock().await;
call.queue().pause()?;
@@ -137,14 +98,19 @@ pub async fn pause(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
Ok(())
}
-#[command]
-#[aliases("continue")]
-pub async fn resume(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
- _resume(ctx, msg).await
+#[poise::command(
+ slash_command,
+ prefix_command,
+ guild_only,
+ aliases("continue"),
+ category = "playback"
+)]
+pub async fn resume(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
+ _resume(ctx).await
}
-async fn _resume(ctx: &Context, msg: &Message) -> CommandResult {
- let (_sb, call) = songbird(ctx, msg).await?;
+async fn _resume(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
+ let (_sb, call) = songbird(ctx).await?;
let call = call.lock().await;
call.queue().resume()?;
@@ -152,10 +118,9 @@ async fn _resume(ctx: &Context, msg: &Message) -> CommandResult {
Ok(())
}
-#[command]
-#[aliases("next")]
-pub async fn skip(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
- let (_sb, call) = songbird(ctx, msg).await?;
+#[poise::command(slash_command, prefix_command, guild_only, category = "playback", aliases("next"))]
+pub async fn skip(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
+ let (_sb, call) = songbird(ctx).await?;
let call = call.lock().await;
call.queue().skip()?;
@@ -163,10 +128,15 @@ pub async fn skip(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
Ok(())
}
-#[command]
-#[aliases("sudoku", "fuckoff", "stop")]
-pub async fn die(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
- let (_sb, call) = songbird(ctx, msg).await?;
+#[poise::command(
+ slash_command,
+ prefix_command,
+ guild_only,
+ category = "playback",
+ aliases("sudoku", "fuckoff", "stop")
+)]
+pub async fn die(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
+ let (_sb, call) = songbird(ctx).await?;
let mut call = call.lock().await;
call.queue().stop();
@@ -176,21 +146,26 @@ pub async fn die(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
Ok(())
}
-#[command]
-#[aliases("queue")]
-pub async fn list(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
- let (_sb, call) = songbird(ctx, msg).await?;
+#[poise::command(
+ slash_command,
+ prefix_command,
+ guild_only,
+ category = "playback",
+ aliases("queue")
+)]
+pub async fn list(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
+ let (_sb, call) = songbird(ctx).await?;
let call = call.lock().await;
let queue = call.queue();
- util::send(ctx, msg.channel_id, "(command fix work-in-progress)", msg.tts).await?;
+ util::reply(ctx, "(command fix work-in-progress)").await?;
for track in queue.current_queue().into_iter() {
let info = track.get_info().await?;
- util::send(ctx, msg.channel_id, format!("track playing for {:?}", info.play_time), msg.tts)
- .await?;
+ let fmt = format!("track playing for {:?}", info.play_time);
+ util::reply(ctx, fmt).await?;
}
Ok(())
diff --git a/src/commands/roll.rs b/src/commands/roll.rs
index 45e3ba8..6cd084c 100644
--- a/src/commands/roll.rs
+++ b/src/commands/roll.rs
@@ -1,31 +1,18 @@
use std::result::Result as StdResult;
+use lazy_static::lazy_static;
use log::{
debug,
error,
};
use rand::prelude::*;
-use serenity::{
- framework::standard::{
- macros::command,
- Args,
- },
- model::channel::Message,
- prelude::*,
-};
use thiserror::Error;
-use lazy_static::lazy_static;
-use serenity::{
- framework::standard::{
- CommandError,
- CommandResult,
- },
- futures::TryFutureExt,
+use crate::{
+ util,
+ PoiseContext,
};
-use crate::util;
-
#[derive(pest_derive::Parser)]
#[grammar = "commands/calc.pest"]
struct Calc;
@@ -49,27 +36,26 @@ impl Calc {
Pair,
Pairs,
},
- prec_climber::PrecClimber,
+ pratt_parser::PrattParser,
Parser,
};
use self::Rule::*;
lazy_static! {
- static ref CLIMBER: PrecClimber<Rule> = {
- use pest::prec_climber::{
+ static ref CLIMBER: PrattParser<Rule> = {
+ use pest::pratt_parser::{
Assoc::*,
- Operator,
+ Op as Operator,
};
- PrecClimber::new(vec![
- Operator::new(add, Left)
- | Operator::new(sub, Left)
- | Operator::new(modulo, Left),
- Operator::new(mul, Left) | Operator::new(div, Left),
- Operator::new(dice, Left),
- Operator::new(pow, Right),
- ])
+ PrattParser::new()
+ .op(Operator::infix(add, Left)
+ | Operator::infix(sub, Left)
+ | Operator::infix(modulo, Left))
+ .op(Operator::infix(mul, Left) | Operator::infix(div, Left))
+ .op(Operator::infix(dice, Left))
+ .op(Operator::infix(pow, Right))
};
}
@@ -160,29 +146,33 @@ impl Calc {
}
fn eval_expr(p: Pairs<Rule>) -> StdResult<f64, CalcError> {
- CLIMBER.climb(p, eval_single_pair, |lhs, op, rhs| {
- let lhs = lhs?;
- let rhs = rhs?;
+ CLIMBER
+ .map_primary(eval_single_pair)
+ .map_infix(|lhs, op, rhs| {
+ let lhs = lhs?;
+ let rhs = rhs?;
- let result = match op.as_rule() {
- add => lhs + rhs,
- sub => lhs - rhs,
- mul => lhs * rhs,
- div => lhs / rhs,
- pow => lhs.powf(rhs),
- dice => {
- let dice_count = lhs as usize;
- let dice_faces = rhs as usize;
+ let result = match op.as_rule() {
+ add => lhs + rhs,
+ sub => lhs - rhs,
+ mul => lhs * rhs,
+ div => lhs / rhs,
+ pow => lhs.powf(rhs),
+ dice => {
+ let dice_count = lhs as usize;
+ let dice_faces = rhs as usize;
- let mut rng = thread_rng();
- (0..dice_count).map(|_| rng.gen_range(1..(dice_faces + 1))).sum::<usize>()
- as f64
- },
- _ => unreachable!(),
- };
+ let mut rng = thread_rng();
+ (0..dice_count)
+ .map(|_| rng.gen_range(1..(dice_faces + 1)))
+ .sum::<usize>() as f64
+ },
+ _ => unreachable!(),
+ };
- Ok(result)
- })
+ Ok(result)
+ })
+ .parse(p)
}
eval_expr(result)
@@ -212,21 +202,18 @@ mod test {
}
}
-#[command]
-#[aliases("calc", "calculate")]
-pub async fn roll(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
- match Calc::eval(args.rest()) {
+#[poise::command(slash_command, prefix_command, guild_only, aliases("calc", "calculate"))]
+pub async fn roll(ctx: PoiseContext<'_>, #[rest] rest: String) -> anyhow::Result<()> {
+ match Calc::eval(&rest) {
Ok(result) => {
debug!("got calc result '{}'", result);
- util::send(ctx, msg.channel_id, &format!("{}", result), msg.tts)
- .map_err(CommandError::from)
- .await
+ util::reply(ctx, result.to_string()).await?;
},
Err(e) => {
- error!("error encountered reading calc '{}': {}", args.rest(), e);
- util::send(ctx, msg.channel_id, "I COULDN'T READ THAT YOU FUCK", msg.tts)
- .map_err(CommandError::from)
- .await
+ error!("error encountered reading calc '{}': {}", rest, e);
+ util::reply(ctx, "I COULDN'T READ THAT YOU FUCK").await?;
},
}
+
+ Ok(())
}
diff --git a/src/commands/sound_levels.rs b/src/commands/sound_levels.rs
index 8c75b37..9a6cfc6 100644
--- a/src/commands/sound_levels.rs
+++ b/src/commands/sound_levels.rs
@@ -1,21 +1,14 @@
-use serenity::{
- framework::standard::{
- macros::command,
- Args,
- CommandResult,
- },
- model::channel::Message,
- prelude::*,
+use crate::{
+ commands::playback::songbird,
+ PoiseContext,
};
-use crate::commands::songbird;
-
pub const DEFAULT_VOLUME: f32 = 0.20;
const MAX_VOLUME: f32 = 5.0;
-#[command]
-pub async fn mute(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
- let (_sb, call) = songbird(ctx, msg).await?;
+#[poise::command(slash_command, prefix_command, guild_only)]
+pub async fn mute(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
+ let (_sb, call) = songbird(ctx).await?;
let mut call = call.lock().await;
call.mute(true).await?;
@@ -23,66 +16,12 @@ pub async fn mute(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
Ok(())
}
-#[command]
-pub async fn unmute(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
- let (_sb, call) = songbird(ctx, msg).await?;
+#[poise::command(slash_command, prefix_command, guild_only)]
+pub async fn unmute(ctx: PoiseContext<'_>) -> anyhow::Result<()> {
+ let (_sb, call) = songbird(ctx).await?;
let mut call = call.lock().await;
call.mute(true).await?;
Ok(())
}
-
-// #[command]
-// pub async fn volume(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
-// if args.len() == 0 {
-// let vol = {
-// let queue_lock = ctx.data.write().await.get::<PlayQueue>().cloned().unwrap();
-// let play_queue = queue_lock.read().unwrap();
-// (play_queue.volume / DEFAULT_VOLUME * 100.0) as usize
-// };
-//
-// trace!("reporting volume {}", vol);
-//
-// return util::send(ctx, msg.channel_id, &format!("volume: {}%", vol), msg.tts)
-// .map_err(CommandError::from)
-// .await;
-// }
-//
-// let vol: usize = match args.single::<f32>() {
-// Ok(vol) if vol.is_nan() => {
-// warn!("reporting NaN volume");
-// return util::send(ctx, msg.channel_id, "you're a fuck", msg.tts)
-// .map_err(CommandError::from)
-// .await;
-// },
-// Ok(vol) => vol as usize,
-// Err(e) => {
-// error!("parsing volume arg: {}", e);
-// return util::send(ctx, msg.channel_id, "???????", msg.tts)
-// .map_err(CommandError::from)
-// .await;
-// },
-// };
-//
-// let mut vol: f32 = (vol as f32) / 100.0; // force aliasing to reasonable values
-// let adjusted_text = if vol > MAX_VOLUME {
-// format!(" ({:.0}% max)", MAX_VOLUME * 100.0)
-// } else {
-// "".to_owned()
-// };
-//
-// vol = vol.clamp(0.0, MAX_VOLUME);
-//
-// let queue_lock = ctx.data.write().await.get::<PlayQueue>().cloned().unwrap();
-//
-// {
-// let mut play_queue = queue_lock.write().unwrap();
-// play_queue.volume = vol * DEFAULT_VOLUME;
-// info!("volume updated to {}", vol);
-// }
-//
-// util::send(ctx, msg.channel_id, format!("volume adjusted{}", adjusted_text), msg.tts).await?;
-//
-// Ok(())
-// }
diff --git a/src/commands/today/mod.rs b/src/commands/today/mod.rs
index 7f1dca7..c1a02d5 100644
--- a/src/commands/today/mod.rs
+++ b/src/commands/today/mod.rs
@@ -5,22 +5,14 @@ use rand::{
seq::SliceRandom,
thread_rng,
};
-use serenity::{
- framework::standard::{
- macros::command,
- Args,
- CommandResult,
- },
- model::channel::Message,
- prelude::*,
-};
use songbird::input::YoutubeDl;
use tap::Conv;
use crate::{
bot::HttpKey,
- commands::songbird,
+ commands::playback::songbird,
util,
+ PoiseContext,
CONFIG,
};
@@ -66,16 +58,16 @@ lazy_static! {
];
}
-#[command]
-pub async fn today(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
+#[poise::command(slash_command, prefix_command, guild_only)]
+pub async fn today(ctx: PoiseContext<'_>, #[rest] _rest: String) -> anyhow::Result<()> {
let today = {
#[allow(unused_mut)]
let mut result = chrono::Local::now().naive_local();
#[cfg(debug_assertions)]
{
- let dt = _args.parse::<chrono::NaiveDateTime>().or_else(|_| {
- _args.parse::<chrono::NaiveDate>().map(|date| {
+ let dt = _rest.parse::<chrono::NaiveDateTime>().or_else(|_| {
+ _rest.parse::<chrono::NaiveDate>().map(|date| {
let time = chrono::NaiveTime::from_hms_opt(12, 0, 0).unwrap();
date.and_time(time)
})
@@ -83,11 +75,11 @@ pub async fn today(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
match dt {
Ok(dt) => {
- log::debug!("overriding with datetime: {}", dt);
+ debug!("overriding with datetime: {dt}");
result = dt;
},
Err(e) => {
- log::debug!("parsing datetime: {:?}", e);
+ debug!("parsing datetime: {e:?}");
},
};
}
@@ -102,7 +94,7 @@ pub async fn today(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
let play_args = options.choose(&mut thread_rng());
if let Some(play_args) = play_args {
- let (_sb, call) = songbird(ctx, msg).await?;
+ let (_sb, call) = songbird(ctx).await?;
let mut call = call.lock().await;
if call.current_channel().is_none() {
@@ -110,7 +102,7 @@ pub async fn today(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
}
let client = {
- let data = ctx.data.read().await;
+ let data = ctx.serenity_context().data.read().await;
data.get::<HttpKey>().unwrap().clone()
};
@@ -130,8 +122,8 @@ pub async fn today(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
});
q.resume()?;
} else {
- util::send(ctx, msg.channel_id, "no", false).await?;
- util::send(ctx, msg.channel_id, ":angry:", false).await?;
+ util::reply(ctx, "no").await?;
+ util::reply(ctx, ":angry:").await?;
}
Ok(())