diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/audio/mod.rs | 37 | ||||
| -rw-r--r-- | src/audio/play_queue.rs | 17 | ||||
| -rw-r--r-- | src/commands/meme/create.rs | 42 | ||||
| -rw-r--r-- | src/commands/meme/delete.rs | 13 | ||||
| -rw-r--r-- | src/commands/meme/history.rs | 82 | ||||
| -rw-r--r-- | src/commands/meme/invoke.rs | 33 | ||||
| -rw-r--r-- | src/commands/meme/mod.rs | 11 | ||||
| -rw-r--r-- | src/commands/mod.rs | 230 | ||||
| -rw-r--r-- | src/commands/playback.rs | 48 | ||||
| -rw-r--r-- | src/commands/roll.rs | 22 | ||||
| -rw-r--r-- | src/commands/sound_levels.rs | 23 | ||||
| -rw-r--r-- | src/db/mod.rs | 14 | ||||
| -rw-r--r-- | src/game.rs | 97 | ||||
| -rw-r--r-- | src/main.rs | 46 | ||||
| -rw-r--r-- | src/util.rs | 55 |
15 files changed, 377 insertions, 393 deletions
diff --git a/src/audio/mod.rs b/src/audio/mod.rs index 5044c49..3c3041f 100644 --- a/src/audio/mod.rs +++ b/src/audio/mod.rs @@ -5,18 +5,15 @@ use either::Either; use serenity::{ client::bridge::voice::ClientVoiceManager, model::{ - id::ChannelId, + id::{ + ChannelId, + }, }, prelude::*, voice::LockedAudio, }; use typemap::Key; -use crate::{ - must_env_lookup, - Result, -}; - pub use self::play_queue::PlayQueue; pub use self::timeutil::parse_times; pub use self::ytdl::*; @@ -25,32 +22,6 @@ mod timeutil; mod ytdl; mod play_queue; -pub trait CtxExt { - fn currently_playing(&self) -> bool; - fn users_listening(&self) -> Result<bool>; -} - -impl CtxExt for Context { - fn currently_playing(&self) -> bool { - let queue_lock = self.data.lock().get::<PlayQueue>().cloned().unwrap(); - let play_queue = queue_lock.read().unwrap(); - play_queue.playing.is_some() - } - - fn users_listening(&self) -> Result<bool> { - let channel_id = ChannelId(must_env_lookup::<u64>("VOICE_CHANNEL")); - let channel = channel_id.to_channel()?; - let res = channel.guild() - .and_then(|ch| ch.read().guild()) - .map(|g| (&g.read().voice_states) - .into_iter() - .any(|(_, state)| state.channel_id == Some(channel_id))) - .unwrap_or(false); - - Ok(res) - } -} - pub struct VoiceManager; impl Key for VoiceManager { @@ -59,7 +30,7 @@ impl Key for VoiceManager { impl VoiceManager { pub fn register(c: &mut Client) { - let mut data = c.data.lock(); + let mut data = c.data.write(); data.insert::<VoiceManager>(Arc::clone(&c.voice_manager)); } } diff --git a/src/audio/play_queue.rs b/src/audio/play_queue.rs index aa2ccc5..1f80961 100644 --- a/src/audio/play_queue.rs +++ b/src/audio/play_queue.rs @@ -9,6 +9,7 @@ use std::{ use either::{Left, Right}; use serenity::{ + CacheAndHttp, client::bridge::voice::ClientVoiceManager, prelude::*, voice, @@ -22,7 +23,6 @@ use crate::{ ytdl_url, }, commands::{ - send, sound_levels::DEFAULT_VOLUME, }, must_env_lookup, @@ -63,23 +63,28 @@ impl PlayQueue { pub fn register(c: &mut Client) { let voice_manager = Arc::clone(&c.voice_manager); - let mut data = c.data.lock(); let queue = Arc::new(RwLock::new(PlayQueue::new())); - data.insert::<PlayQueue>(Arc::clone(&queue)); + { + let mut data = c.data.write(); + data.insert::<PlayQueue>(Arc::clone(&queue)); + } + + let cache_http = c.cache_and_http.clone(); thread::spawn(move || { loop { - if let Err(e) = Self::update(&queue, &voice_manager) { + if let Err(e) = Self::update(cache_http, &queue, &voice_manager) { error!("updating playqueue: {}", e); } thread::sleep(Duration::from_millis(250)); } }); + } - fn update(queue_lck: &Arc<RwLock<Self>>, voice_manager: &Arc<Mutex<ClientVoiceManager>>) -> Result<()> { + fn update(cache_http: Arc<CacheAndHttp>, queue_lck: &Arc<RwLock<Self>>, voice_manager: &Arc<Mutex<ClientVoiceManager>>) -> Result<()> { let (queue_is_empty, queue_has_playing) = { let queue = queue_lck.read().unwrap(); @@ -236,7 +241,7 @@ impl PlayQueue { }, None => { error!("couldn't join channel"); - send(item.sender_channel, "something happened somewhere somehow.", false)?; + item.sender_channel.say(cache_http.http, "something happened somewhere somehow.")?; } } diff --git a/src/commands/meme/create.rs b/src/commands/meme/create.rs index 245f067..0851961 100644 --- a/src/commands/meme/create.rs +++ b/src/commands/meme/create.rs @@ -8,7 +8,7 @@ use std::{ use diesel::result::Error as DieselError; use serenity::{ - framework::standard::Args, + framework::standard::{Args, Delimiter}, model::channel::Message, prelude::*, }; @@ -19,7 +19,6 @@ use crate::{ parse_times, ytdl_url, }, - commands::send, db::{ Audio, connection, @@ -27,10 +26,16 @@ use crate::{ NewMeme, }, Result, + util::CtxExt, }; -pub fn addmeme(_: &mut Context, msg: &Message, args: Args) -> Result<()> { - let mut args = Args::new(args.rest(), &[" ".to_owned(), "\n".to_owned(), "\t".to_owned()]); +lazy_static! { + static ref delims: Vec<Delimiter> = vec![' '.into(), '\n'.into(), '\t'.into()]; +} + +#[command] +pub fn addmeme(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> { + let mut args = Args::new(args.rest(), delims.as_ref()); let title = args.single_quoted::<String>()?; let text = args.rest().to_owned(); @@ -40,7 +45,7 @@ pub fn addmeme(_: &mut Context, msg: &Message, args: Args) -> Result<()> { let conn = connection()?; let image = msg.attachments.first() - .ok_or(::failure::err_msg("no attachment")) + .ok_or(anyhow!("no attachment")) .and_then(|att| { let data = att.download()?; Image::create(&conn, &att.filename, data, msg.author.id.0) @@ -49,7 +54,7 @@ pub fn addmeme(_: &mut Context, msg: &Message, args: Args) -> Result<()> { if image.is_none() && text.is_none() { warn!("tried to create non-audio meme with no image or text"); - return send(msg.channel_id, "hahAA it's empty xdddd", msg.tts); + return ctx.send(msg.channel_id, "hahAA it's empty xdddd", msg.tts); } let save_result = NewMeme { @@ -62,12 +67,12 @@ pub fn addmeme(_: &mut Context, msg: &Message, args: Args) -> Result<()> { use diesel::result::DatabaseErrorKind; match save_result { - Ok(_) => msg.react("👌"), + Ok(_) => msg.react(ctx, "👌"), Err(e) => { if let Some(DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, _)) = e.downcast_ref::<DieselError>() { error!("tried to create meme that already exists"); - msg.react("❌")?; - return send(msg.channel_id, "that meme already exists", msg.tts); + msg.react(ctx, "❌")?; + return ctx.send(msg.channel_id, "that meme already exists", msg.tts); } return Err(e); @@ -75,8 +80,9 @@ pub fn addmeme(_: &mut Context, msg: &Message, args: Args) -> Result<()> { } } -pub fn addaudiomeme(_: &mut Context, msg: &Message, args: Args) -> Result<()> { - let mut args = Args::new(args.rest(), &[" ".to_owned(), "\n".to_owned(), "\t".to_owned()]); +#[command] +pub fn addaudiomeme(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> { + let mut args = Args::new(args.rest(), delims.as_ref()); let title = args.single_quoted::<String>()?; let audio_str = args.single_quoted::<String>()?; @@ -84,8 +90,8 @@ pub fn addaudiomeme(_: &mut Context, msg: &Message, args: Args) -> Result<()> { let elems = audio_str.split_whitespace().collect::<Vec<_>>(); if elems.len() == 0 { - send(msg.channel_id, "are you stupid", msg.tts)?; - return Err(::failure::err_msg("no audio link was provided")) + ctx.send(msg.channel_id, "are you stupid", msg.tts)?; + return Err(anyhow!("no audio link was provided")) } let audio_link = Url::parse(elems[0])?; @@ -133,7 +139,7 @@ pub fn addaudiomeme(_: &mut Context, msg: &Message, args: Args) -> Result<()> { let conn = connection()?; let image = msg.attachments.first() - .ok_or(::failure::err_msg("no attachment")) + .ok_or(anyhow!("no attachment")) .and_then(|att| { let data = att.download()?; Image::create(&conn, &att.filename, data, msg.author.id.0) @@ -145,7 +151,7 @@ pub fn addaudiomeme(_: &mut Context, msg: &Message, args: Args) -> Result<()> { if bytes == 0 { debug!("read 0 bytes from audio reader"); - return send(msg.channel_id, "🔇🔇🔇🔕🔕🔕🔕🔕🔇🔕🔕🔇🔕🔕📣📢📣📢📣", msg.tts); + return ctx.send(msg.channel_id, "🔇🔇🔇🔕🔕🔕🔕🔕🔇🔕🔕🔇🔕🔕📣📢📣📢📣", msg.tts); } let audio_id = Audio::create(&conn, audio_data, msg.author.id.0)?; @@ -160,12 +166,12 @@ pub fn addaudiomeme(_: &mut Context, msg: &Message, args: Args) -> Result<()> { use diesel::result::DatabaseErrorKind; match save_result { - Ok(_) => msg.react("👌"), + Ok(_) => msg.react(ctx, "👌"), Err(e) => { if let Some(DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, _)) = e.downcast_ref::<DieselError>() { error!("tried to create meme that already exists"); - msg.react("❌")?; - return send(msg.channel_id, "that meme already exists", msg.tts); + msg.react(ctx, "❌")?; + return ctx.send(msg.channel_id, "that meme already exists", msg.tts); } return Err(e); diff --git a/src/commands/meme/delete.rs b/src/commands/meme/delete.rs index 5c6eb13..72226e5 100644 --- a/src/commands/meme/delete.rs +++ b/src/commands/meme/delete.rs @@ -9,25 +9,28 @@ use serenity::{ }; use crate::{ - commands::send, db::{ connection, delete_meme, }, Result, + util::CtxExt, }; -pub fn delmeme(_: &mut Context, msg: &Message, mut args: Args) -> Result<()> { +#[command] +#[aliases("delmem")] +pub fn delmeme(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { let title = args.single_quoted::<String>()?; let conn = connection()?; + match delete_meme(&conn, &title, msg.author.id.0) { - Ok(_) => msg.react("💀"), + Ok(_) => msg.react(ctx, "💀"), Err(e) => { if let Some(NotFound) = e.downcast_ref::<DieselError>() { - msg.react("❓")?; + msg.react(ctx, "❓")?; info!("attempted to delete nonexistent meme: '{}'", title); - send(msg.channel_id, "nice try", msg.tts)?; + ctx.send(msg.channel_id, "nice try", msg.tts)?; return Ok(()); } diff --git a/src/commands/meme/history.rs b/src/commands/meme/history.rs index e5f15f5..6c03bef 100644 --- a/src/commands/meme/history.rs +++ b/src/commands/meme/history.rs @@ -13,7 +13,6 @@ use timeago::{ }; use crate::{ - commands::send, db::{ connection, InvocationRecord, @@ -22,6 +21,7 @@ use crate::{ }, must_env_lookup, Result, + util::CtxExt, }; lazy_static! { @@ -36,7 +36,9 @@ lazy_static! { static CLEAN_DATE_FORMAT: &'static str = "%b %-e %Y"; -pub fn wat(_: &mut Context, msg: &Message, _: Args) -> Result<()> { +#[command] +#[aliases("what")] +pub fn wat(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { let conn = connection()?; let record = match InvocationRecord::last(&conn) { @@ -44,10 +46,10 @@ pub fn wat(_: &mut Context, msg: &Message, _: Args) -> Result<()> { Err(e) => { if let Some(NotFound) = e.downcast_ref::<DieselError>() { info!("found no memes in history"); - return send(msg.channel_id, "no one has ever memed before", msg.tts); + return ctx.send(msg.channel_id, "no one has ever memed before", msg.tts); } - send(msg.channel_id, "BAD MEME BAD MEME", msg.tts)?; + ctx.send(msg.channel_id, "BAD MEME BAD MEME", msg.tts)?; return Err(e); }, }; @@ -57,19 +59,19 @@ pub fn wat(_: &mut Context, msg: &Message, _: Args) -> Result<()> { match meme { Ok(ref meme) => { let metadata = Metadata::find(&conn, meme.metadata_id)?; - let author = crate::TARGET_GUILD_ID.member(metadata.created_by as u64)?; + let author = crate::TARGET_GUILD_ID.member(ctx, metadata.created_by as u64)?; - send(msg.channel_id, + ctx.send(msg.channel_id, &format!("that was \"{}\" by {} ({})", meme.title, author.mention(), metadata.created.date().format(CLEAN_DATE_FORMAT)), msg.tts)? }, Err(e) => { if let Some(NotFound) = e.downcast_ref::<DieselError>() { info!("last meme not found in database"); - return send(msg.channel_id, "heuueueeeeh?", msg.tts); + return ctx.send(msg.channel_id, "heuueueeeeh?", msg.tts); } - send(msg.channel_id, "do i look like i know what a jpeg is", msg.tts)?; + ctx.send(msg.channel_id, "do i look like i know what a jpeg is", msg.tts)?; return Err(e); }, }; @@ -77,7 +79,8 @@ pub fn wat(_: &mut Context, msg: &Message, _: Args) -> Result<()> { meme.map(|_| {}) } -pub fn history(_: &mut Context, msg: &Message, mut args: Args) -> Result<()> { +#[command] +pub fn history(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { use itertools::Itertools; lazy_static! { @@ -91,7 +94,7 @@ pub fn history(_: &mut Context, msg: &Message, mut args: Args) -> Result<()> { if n > *MAX_HIST { debug!("user requested more than MAX_HIST ({}) items from history", *MAX_HIST); - send(msg.channel_id, "YER PUSHIN ME OVER THE FUCKIN LINE", true)?; + ctx.send(msg.channel_id, "YER PUSHIN ME OVER THE FUCKIN LINE", true)?; } let n = n.min(*MAX_HIST); @@ -100,7 +103,7 @@ pub fn history(_: &mut Context, msg: &Message, mut args: Args) -> Result<()> { if records.len() == 0 { info!("no memes in history"); - return send(msg.channel_id, "i don't remember anything :(", msg.tts); + return ctx.send(msg.channel_id, "i don't remember anything :(", msg.tts); } info!("reporting meme history (len {})", n); @@ -118,8 +121,8 @@ pub fn history(_: &mut Context, msg: &Message, mut args: Args) -> Result<()> { Metadata::find(&conn, meme.metadata_id).map(|metadata| (metadata, meme)) }) .map(|(metadata, meme)| { - let author_name = crate::TARGET_GUILD_ID.member(metadata.created_by as u64).map(|m| m.display_name().into_owned()).unwrap_or("???".to_owned()); - let invoker_name = crate::TARGET_GUILD_ID.member(rec.user_id as u64).map(|m| m.display_name().into_owned()).unwrap_or("???".to_owned()); + let author_name = crate::TARGET_GUILD_ID.member(ctx, metadata.created_by as u64).map(|m| m.display_name().into_owned()).unwrap_or("???".to_owned()); + let invoker_name = crate::TARGET_GUILD_ID.member(ctx, rec.user_id as u64).map(|m| m.display_name().into_owned()).unwrap_or("???".to_owned()); format!("{}. [{}{}] \"{}\" by {} ({}). invoked by {}.", i + 1, rand, ago, meme.title, author_name, metadata.created.date().format(CLEAN_DATE_FORMAT), invoker_name) }) .unwrap_or_else(|e| { @@ -129,16 +132,18 @@ pub fn history(_: &mut Context, msg: &Message, mut args: Args) -> Result<()> { } } - let invoker_name = crate::TARGET_GUILD_ID.member(rec.user_id as u64).map(|m| m.display_name().into_owned()).unwrap_or("???".to_owned()); + let invoker_name = crate::TARGET_GUILD_ID.member(ctx, rec.user_id as u64).map(|m| m.display_name().into_owned()).unwrap_or("???".to_owned()); format!("{}. [{}{}] not found. invoked by {}.", i + 1, rand, ago, invoker_name) }) }) .join("\n"); - send(msg.channel_id, &resp, false) + ctx.send(msg.channel_id, &resp, false) } -pub fn stats(_: &mut Context, msg: &Message, _: Args) -> Result<()> { +#[command] +#[aliases("stat")] +pub fn stats(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { use db; use serenity::model::{ id::UserId, @@ -151,11 +156,11 @@ pub fn stats(_: &mut Context, msg: &Message, _: Args) -> Result<()> { debug!("reporting stats"); - let rand_user: User = UserId(stats.most_random_meme_user).to_user()?; - let direct_user: User = UserId(stats.most_directly_named_meme_user).to_user()?; + let rand_user: User = UserId(stats.most_random_meme_user).to_user(ctx)?; + let direct_user: User = UserId(stats.most_directly_named_meme_user).to_user(ctx)?; - let rand_user = rand_user.nick_in(*TARGET_GUILD_ID).unwrap_or(rand_user.name); - let direct_user = direct_user.nick_in(*TARGET_GUILD_ID).unwrap_or(direct_user.name); + let rand_user = rand_user.nick_in(ctx, *TARGET_GUILD_ID).unwrap_or(rand_user.name); + let direct_user = direct_user.nick_in(ctx, *TARGET_GUILD_ID).unwrap_or(direct_user.name); let s = format!( r#" @@ -197,10 +202,11 @@ and *{}* was the most-memed overall ({})"#, stats.most_popular_random_meme, stats.most_popular_random_meme_count, stats.most_popular_meme_overall, stats.most_popular_meme_overall_count, ); - send(msg.channel_id, s, msg.tts) + ctx.send(msg.channel_id, s, msg.tts) } -pub fn memers(_: &mut Context, msg: &Message, _args: Args) -> Result<()> { +#[command] +pub fn memers(ctx: &mut Context, msg: &Message, _args: Args) -> Result<()> { use db; use itertools::Itertools; use serenity::model::{ @@ -211,8 +217,8 @@ pub fn memers(_: &mut Context, msg: &Message, _args: Args) -> Result<()> { let s = db::memers()? .into_iter() .map(|info| { - let user = UserId(info.user_id).to_user()?; - let username = user.nick_in(*TARGET_GUILD_ID).unwrap_or(user.name); + let user = UserId(info.user_id).to_user(ctx)?; + let username = user.nick_in(ctx, *TARGET_GUILD_ID).unwrap_or(user.name); let res = format!( "**{}**: {} total, {} random, {} specific. favorite meme: *{}* ({})", @@ -230,15 +236,15 @@ pub fn memers(_: &mut Context, msg: &Message, _args: Args) -> Result<()> { .into_iter() .join("\n"); - send(msg.channel_id, &s, msg.tts) + ctx.send(msg.channel_id, &s, msg.tts) } -pub fn query(_: &mut Context, msg: &Message, mut args: Args) -> Result<()> { +#[command] +pub fn query(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { use std::borrow::Borrow; use itertools::Itertools; use regex::Regex; - use failure::err_msg; use serenity::model::id::UserId; use crate::{ @@ -252,19 +258,19 @@ pub fn query(_: &mut Context, msg: &Message, mut args: Args) -> Result<()> { static ref AGE_REGEX: Regex = Regex::new(r"(?i)(?:age|order)=(.*)").unwrap(); } - let guild = msg.channel_id.to_channel()? + let guild = msg.channel_id.to_channel(ctx)? .guild() - .ok_or(err_msg("couldn't find guild"))?; + .ok_or(anyhow!("couldn't find guild"))?; let guild = guild.read() - .guild() - .ok_or(err_msg("couldn't find guild"))?; + .guild(ctx) + .ok_or(anyhow!("couldn't find guild"))?; let guild = guild .read(); let creator: Option<u64> = { - let creator = args.current_quoted().map(|s| CREATOR_REGEX.is_match(s)).unwrap_or(false); + let creator = args.quoted().current().map(|s| CREATOR_REGEX.is_match(s)).unwrap_or(false); if creator { args.single_quoted::<String>() .ok() @@ -276,12 +282,12 @@ pub fn query(_: &mut Context, msg: &Message, mut args: Args) -> Result<()> { }; let order = { - let order = args.current_quoted().map(|s| AGE_REGEX.is_match(s)).unwrap_or(false); + let order = args.quoted().current().map(|s| AGE_REGEX.is_match(s)).unwrap_or(false); 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| s.contains("new")) + .map(|s: String| s.contains("new")) .unwrap_or(true) } else { true @@ -291,8 +297,8 @@ pub fn query(_: &mut Context, msg: &Message, mut args: Args) -> Result<()> { let result = db::query_meme(args.rest(), creator, order)? .into_iter() .map(|(meme, metadata)| { - let user = UserId(metadata.created_by as u64).to_user()?; - let username = user.nick_in(*TARGET_GUILD_ID).unwrap_or(user.name); + let user = UserId(metadata.created_by as u64).to_user(ctx)?; + let username = user.nick_in(ctx, *TARGET_GUILD_ID).unwrap_or(user.name); Ok(format!("*{}* by **{}** ({}). text length: **{}**, image: **{}**, audio: **{}**", meme.title, @@ -318,8 +324,8 @@ pub fn query(_: &mut Context, msg: &Message, mut args: Args) -> Result<()> { if result.len() == 0 { info!("no memes matched query"); - return send(msg.channel_id, "no match".to_owned(), msg.tts); + return ctx.send(msg.channel_id, "no match".to_owned(), msg.tts); } - send(msg.channel_id, &result, msg.tts) + ctx.send(msg.channel_id, &result, msg.tts) }
\ No newline at end of file diff --git a/src/commands/meme/invoke.rs b/src/commands/meme/invoke.rs index 0f992c2..89ca999 100644 --- a/src/commands/meme/invoke.rs +++ b/src/commands/meme/invoke.rs @@ -2,7 +2,7 @@ use diesel::{ NotFound, result::Error as DieselError, }; -use failure::Error; +use itertools::Itertools; use serenity::{ framework::standard::Args, model::channel::Message, @@ -10,11 +10,7 @@ use serenity::{ }; use crate::{ - audio::CtxExt, - commands::{ - meme::send_meme, - send, - }, + commands::meme::send_meme, db::{ self, connection, @@ -22,18 +18,25 @@ use crate::{ InvocationRecord, }, Result, + util::CtxExt, }; +#[command] +#[aliases("mem")] #[inline] pub fn meme(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> { _meme(ctx, msg, args, AudioPlayback::Optional) } +#[command] +#[aliases("audiomeme", "audiomem")] #[inline] pub fn audio_meme(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> { _meme(ctx, msg, args, AudioPlayback::Required) } +#[command] +#[aliases("silentmeme", "silentmem")] #[inline] pub fn silent_meme(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> { _meme(ctx, msg, args, AudioPlayback::Prohibited) @@ -51,7 +54,7 @@ fn _meme(ctx: &mut Context, msg: &Message, args: Args, audio_playback: AudioPlay return rand_meme(ctx, msg, audio_playback); } - let search = args.full(); + let search = args.raw().join(" "); let conn = connection()?; let mem = match find_meme(&conn, search) { @@ -63,9 +66,9 @@ fn _meme(ctx: &mut Context, msg: &Message, args: Args, audio_playback: AudioPlay Err(e) => { return if let Some(NotFound) = e.downcast_ref::<DieselError>() { info!("requested meme not found in database"); - send(msg.channel_id, "c'mon baby, guesstimate", msg.tts) + ctx.send(msg.channel_id, "c'mon baby, guesstimate", msg.tts) } else { - send(msg.channel_id, "what in ryan's name", msg.tts)?; + ctx.send(msg.channel_id, "what in ryan's name", msg.tts)?; Err(e) }; }, @@ -74,6 +77,8 @@ fn _meme(ctx: &mut Context, msg: &Message, args: Args, audio_playback: AudioPlay send_meme(ctx, &mem, &conn, msg) } +#[command] +#[aliases("rarememe", "raremem")] fn rand_meme(ctx: &Context, message: &Message, audio_playback: AudioPlayback) -> Result<()> { let conn = connection()?; @@ -94,17 +99,19 @@ fn rand_meme(ctx: &Context, message: &Message, audio_playback: AudioPlayback) -> match e.downcast_ref::<DieselError>() { Some(NotFound) => { info!("random meme not found"); - return send(message.channel_id, "i don't know any :(", message.tts) + return ctx.send(message.channel_id, "i don't know any :(", message.tts) }, _ => {}, } - send(message.channel_id, "HELP", message.tts)?; + ctx.send(message.channel_id, "HELP", message.tts)?; return Err(e); }, } } +#[command] +#[aliases("rarememe", "raremem")] pub fn rare_meme(ctx: &mut Context, msg: &Message, _args: Args) -> Result<()> { let should_audio = ctx.users_listening()?; @@ -120,12 +127,12 @@ pub fn rare_meme(ctx: &mut Context, msg: &Message, _args: Args) -> Result<()> { match e.downcast_ref::<DieselError>() { Some(NotFound) => { info!("rare meme not found"); - return send(msg.channel_id, "i don't know any :(", msg.tts) + return ctx.send(msg.channel_id, "i don't know any :(", msg.tts) }, _ => {}, } - send(msg.channel_id, "THE MEME MARKET IS IN FREEFALL", msg.tts)?; + ctx.send(msg.channel_id, "THE MEME MARKET IS IN FREEFALL", msg.tts)?; Err(e) }, diff --git a/src/commands/meme/mod.rs b/src/commands/meme/mod.rs index 3738eab..e5244aa 100644 --- a/src/commands/meme/mod.rs +++ b/src/commands/meme/mod.rs @@ -37,7 +37,7 @@ fn send_meme(ctx: &Context, t: &Meme, conn: &PgConnection, msg: &Message) -> Res let image = t.image(conn); let audio = t.audio(conn); - let create_msg = |m: CreateMessage| { + let create_msg = |m: &mut CreateMessage| { let ret = m.tts(should_tts); match t.content { @@ -49,10 +49,11 @@ fn send_meme(ctx: &Context, t: &Meme, conn: &PgConnection, msg: &Message) -> Res match image { Some(image) => { let image = image?; - msg.channel_id.send_files(vec!(AttachmentType::Bytes((&image.data, &image.filename))), create_msg)?; + msg.channel_id.send_files(ctx, vec!(AttachmentType::Bytes((&image.data, &image.filename))), create_msg)?; }, + None => match t.content { - Some(_) => { msg.channel_id.send_message(create_msg)?; }, + Some(_) => { msg.channel_id.send_message(ctx, create_msg)?; }, None => {}, }, }; @@ -64,7 +65,7 @@ fn send_meme(ctx: &Context, t: &Meme, conn: &PgConnection, msg: &Message) -> Res let audio = audio?; { - let queue_lock = ctx.data.lock().get::<PlayQueue>().cloned().unwrap(); + let queue_lock = ctx.data.write().get::<PlayQueue>().cloned().unwrap(); let mut play_queue = queue_lock.write().unwrap(); play_queue.meme_queue.push_back(PlayArgs{ @@ -76,7 +77,7 @@ fn send_meme(ctx: &Context, t: &Meme, conn: &PgConnection, msg: &Message) -> Res }); } - msg.react("📣")?; + msg.react(ctx, "📣")?; } Ok(()) diff --git a/src/commands/mod.rs b/src/commands/mod.rs index f4ff9cb..619335c 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,163 +1,107 @@ use serenity::{ framework::StandardFramework, - model::id::{ChannelId, MessageId}, }; -use crate::Result; +use crate::{ + util::CtxExt, +}; +#[cfg(feature = "games")] +use crate::game::*; +pub use self::{ + playback::*, + sound_levels::*, +}; #[cfg(feature = "diesel")] pub use self::meme::*; -pub use self::playback::*; -pub use self::sound_levels::*; pub(crate) mod playback; pub(crate) mod sound_levels; pub(crate) mod roll; +group!("playback", { + options: { + only_in: "guild", + }, + commands: [ + skip, + pause, + resume, + list, + die, + mute, + unmute, + play, + volume, + ], +}); + +group!("general", { + options: { + only_in: "guild", + }, + commands: [ + roll, + ], +}); + pub fn register_commands(f: StandardFramework) -> StandardFramework { - let f: StandardFramework = register_db(f); - f - .command("skip", |c| c - .desc("skip the rest of the current request") - .guild_only(true) - .exec(skip)) - .command("pause", |c| c - .desc("pause playback (currently broken)") - .guild_only(true) - .exec(pause)) - .command("resume", |c| c - .desc("resume playing (currently broken)") - .guild_only(true) - .exec(resume)) - .command("list", |c| c - .known_as("queue") - .desc("list playing and queued requests") - .guild_only(true) - .exec(list)) - .command("die", |c| c - .batch_known_as(vec!["sudoku", "stop"]) - .desc("stop playing and empty the queue") - .guild_only(true) - .exec(die)) - .command("mute", |c| c - .desc("mute thulani (playback continues)") - .guild_only(true) - .exec(mute)) - .command("unmute", |c| c - .desc("unmute thulani") - .guild_only(true) - .exec(unmute)) - .command("play", |c| c - .desc("queue a request") - .guild_only(true) - .exec(play)) - .command("volume", |c| c - .desc("set playback volume") - .guild_only(true) - .exec(volume)) - .command("roll", |c| c - .known_as("calc") - .desc("simulate rolling dice") - .guild_only(true) - .exec(roll::roll)) - .unrecognised_command(|ctx, msg, unrec| { - let url = match msg.content.split_whitespace().skip(1).next() { - Some(x) if x.starts_with("http") => x, - _ => { - info!("bad command formatting: '{}'", unrec); - let _ = send(msg.channel_id, "format your commands right. fuck you.", msg.tts); - return; - } - }; + let result = f + .group(&PLAYBACK_GROUP) + .group(&GENERAL_GROUP); - let _ = self::playback::_play(ctx, msg, &url); - }) -} + #[cfg(feature = "diesel")] + let result = result.group(&MEMES_GROUP); -#[cfg(feature = "diesel")] -mod meme; + #[cfg(feature = "games")] + let result = result.group(&GAME_GROUP); -#[cfg(feature = "diesel")] -fn register_db(f: StandardFramework) -> StandardFramework { - f - .command("meme", |c| c - .guild_only(true) - .help_available(false) - .cmd(meme)) - .command("audiomeme", |c| c - .guild_only(true) - .help_available(false) - .cmd(audio_meme) - ) - .command("silentmeme", |c| c - .guild_only(true) - .help_available(false) - .cmd(silent_meme) - ) - .command("addmeme", |c| c - .guild_only(true) - .desc("first argument is title, everything after is text. one attached image is included if present.") - .cmd(addmeme) - ) - .command("addaudiomeme", |c| c - .guild_only(true) - .desc("title, audio link, text, with optional image") - .cmd(addaudiomeme) - ) - .command("delmeme", |c| c - .guild_only(true) - .desc("delete a meme by name (exact match only)") - .cmd(delmeme) - ) - .command("wat", |c| c - .known_as("what") - .known_as("last") - .known_as("lastmeme") - .guild_only(true) - .desc("check info for last meme") - .cmd(wat) - ) - .command("stats", |c| c - .guild_only(true) - .desc("get meme stats") - .cmd(stats) - ) - .command("history", |c| c - .known_as("hist") - .guild_only(true) - .desc("history of recent messages") - .cmd(history) - ) - .command("rarememe", |c| c - .known_as("rare_meme") - .guild_only(true) - .desc("deliver an underutilized meme") - .cmd(rare_meme) - ) - .command("memers", |c| c - .guild_only(true) - .desc("list stats for all server memers") - .cmd(memers) - ) - .command("query", |c| c - .guild_only(true) - .desc("find a lot of matching memes") - .cmd(query) - ) -} + result.unrecognised_command(|ctx, msg, unrec| { + let url = match msg.content.split_whitespace().skip(1).next() { + Some(x) if x.starts_with("http") => x, + _ => { + info!("bad command formatting: '{}'", unrec); + let _ = ctx.send(msg.channel_id, "format your commands right. fuck you.", msg.tts); + return; + } + }; -#[cfg(not(feature = "diesel"))] -fn register_db(f: StandardFramework) -> StandardFramework { - f + let _ = self::playback::_play(ctx, msg, &url); + }) } -#[inline] -pub(crate) fn send<A: AsRef<str>>(channel: ChannelId, text: A, tts: bool) -> Result<()> { - send_result(channel, text, tts).map(|_| ()) -} +#[cfg(feature = "games")] +group!("game", { + options: { + only_in: "guild", + }, + commands: [ + installedgame, + ownedgame, + updategaem, + ], +}); -#[inline] -pub(crate) fn send_result<A: AsRef<str>>(channel: ChannelId, text: A, tts: bool) -> Result<MessageId> { - let result = channel.send_message(|m| m.content(text.as_ref()).tts(tts))?; - Ok(result.id) -} +#[cfg(feature = "diesel")] +mod meme; + +#[cfg(feature = "diesel")] +group!("memes", { + options: { + only_in: "guild", + }, + commands: [ + meme, + audio_meme, + silent_Meme, + addmeme, + addaudiomeme, + delmeme, + wat, + stats, + history, + rare_meme, + memers, + query, + ], +}); diff --git a/src/commands/playback.rs b/src/commands/playback.rs index 64d30cd..49ff44c 100644 --- a/src/commands/playback.rs +++ b/src/commands/playback.rs @@ -12,9 +12,9 @@ use crate::{ PlayQueue, VoiceManager, }, - commands::send, Result, TARGET_GUILD_ID, + util::CtxExt, }; pub fn _play(ctx: &Context, msg: &Message, url: &str) -> Result<()> { @@ -23,14 +23,14 @@ pub fn _play(ctx: &Context, msg: &Message, url: &str) -> Result<()> { debug!("playing '{}'", url); if !url.starts_with("http") { warn!("got bad url argument to play: {}", url); - send(msg.channel_id, "bAD LiNk", msg.tts)?; + ctx.send(msg.channel_id, "bAD LiNk", msg.tts)?; return Ok(()); } let url = match Url::parse(url) { Err(e) => { error!("bad url: {}", e); - return send(msg.channel_id, "INVALID URL", msg.tts); + return ctx.send(msg.channel_id, "INVALID URL", msg.tts); }, Ok(u) => u, }; @@ -44,9 +44,9 @@ pub fn _play(ctx: &Context, msg: &Message, url: &str) -> Result<()> { info!("detected imgur link"); if msg.author.id.0 == 106160362109272064 { - send(msg.channel_id, "fuck you conway", true)?; + ctx.send(msg.channel_id, "fuck you conway", true)?; } else { - send(msg.channel_id, "IMGUR IS BAD, YOU TRASH CAN MAN", msg.tts)?; + ctx.send(msg.channel_id, "IMGUR IS BAD, YOU TRASH CAN MAN", msg.tts)?; } return Ok(()); @@ -54,7 +54,7 @@ pub fn _play(ctx: &Context, msg: &Message, url: &str) -> Result<()> { let (start, end) = parse_times(&msg.content); - let queue_lock = ctx.data.lock().get::<PlayQueue>().cloned().unwrap(); + let queue_lock = ctx.data.write().get::<PlayQueue>().cloned().unwrap(); let mut play_queue = queue_lock.write().unwrap(); play_queue.general_queue.push_back(PlayArgs{ @@ -68,6 +68,7 @@ pub fn _play(ctx: &Context, msg: &Message, url: &str) -> Result<()> { Ok(()) } +#[command] pub fn play(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { if args.len() == 0 { return _resume(ctx, msg); @@ -77,17 +78,18 @@ pub fn play(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { Ok(url) => url, Err(e) => { error!("unable to parse url from args: {}", e); - return send(msg.channel_id, "BAD LINK", msg.tts); + return ctx.send(msg.channel_id, "BAD LINK", msg.tts); }, }; _play(ctx, msg, &url) } +#[command] pub fn pause(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { - let queue_lock = ctx.data.lock().get::<PlayQueue>().cloned().unwrap(); + let queue_lock = ctx.data.write().get::<PlayQueue>().cloned().unwrap(); - let done = || send(msg.channel_id, "r u srs", msg.tts); + let done = || ctx.send(msg.channel_id, "r u srs", msg.tts); let playing = { let play_queue = queue_lock.read().unwrap(); @@ -115,14 +117,16 @@ pub fn pause(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { Ok(()) } +#[command] +#[aliases("continue")] pub fn resume(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { _resume(ctx, msg) } fn _resume(ctx: &mut Context, msg: &Message) -> Result<()> { - let queue_lock = ctx.data.lock().get::<PlayQueue>().cloned().unwrap(); + let queue_lock = ctx.data.write().get::<PlayQueue>().cloned().unwrap(); - let done = || send(msg.channel_id, "r u srs", msg.tts); + let done = || ctx.send(msg.channel_id, "r u srs", msg.tts); let playing = { let play_queue = queue_lock.read().unwrap(); @@ -154,8 +158,10 @@ fn _resume(ctx: &mut Context, msg: &Message) -> Result<()> { Ok(()) } +#[command] +#[aliases("next")] pub fn skip(ctx: &mut Context, _msg: &Message, _args: Args) -> Result<()> { - let data = ctx.data.lock(); + let data = ctx.data.write(); let mgr_lock = data.get::<VoiceManager>().cloned().unwrap(); let mut manager = mgr_lock.lock(); @@ -174,8 +180,10 @@ pub fn skip(ctx: &mut Context, _msg: &Message, _args: Args) -> Result<()> { Ok(()) } +#[command] +#[aliases("sudoku", "fuckoff", "stop")] pub fn die(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { - let data = ctx.data.lock(); + let data = ctx.data.write(); let mgr_lock = data.get::<VoiceManager>().cloned().unwrap(); let mut manager = mgr_lock.lock(); @@ -195,18 +203,20 @@ pub fn die(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { handler.stop(); handler.leave(); } else { - send(msg.channel_id, "YOU die", msg.tts)?; + ctx.send(msg.channel_id, "YOU die", msg.tts)?; debug!("got die with no handler attached"); } Ok(()) } +#[command] +#[aliases("queue")] pub fn list(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { - let queue_lock = ctx.data.lock().get::<PlayQueue>().cloned().unwrap(); + let queue_lock = ctx.data.write().get::<PlayQueue>().cloned().unwrap(); let play_queue = queue_lock.read().unwrap(); - let channel_tmp = msg.channel().unwrap().guild().unwrap(); + let channel_tmp = msg.channel(ctx).unwrap().guild().unwrap(); let channel = channel_tmp.read(); info!("listing queue"); @@ -220,11 +230,11 @@ pub fn list(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { Right(_) => "memeing".to_owned(), }; - send(msg.channel_id, &format!("Currently {} {} ({})", status, playing_info, info.init_args.initiator), msg.tts)?; + ctx.send(msg.channel_id, &format!("Currently {} {} ({})", status, playing_info, info.init_args.initiator), msg.tts)?; }, None => { debug!("`list` called with no items in queue"); - send(msg.channel_id, "Nothing is playing you meme", msg.tts)?; + ctx.send(msg.channel_id, "Nothing is playing you meme", msg.tts)?; return Ok(()); }, } @@ -237,7 +247,7 @@ pub fn list(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { Right(_) => "meme".to_owned(), }; - let _ = channel.say(&format!("{} ({})", playing_info, info.initiator)); + let _ = channel.say(ctx, &format!("{} ({})", playing_info, info.initiator)); }); Ok(()) diff --git a/src/commands/roll.rs b/src/commands/roll.rs index b7b98d6..3df91d0 100644 --- a/src/commands/roll.rs +++ b/src/commands/roll.rs @@ -9,23 +9,23 @@ use serenity::{ use statrs; use crate::{ - commands::send, Result, + util::CtxExt, }; #[derive(Parser)] #[grammar = "commands/calc.pest"] struct Calc; -#[derive(Copy, Clone, Fail, Debug, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, Error, Debug, PartialEq, Eq, Hash)] pub(crate) enum CalcError { - #[fail(display = "pest was unable to parse the input")] + #[error("pest was unable to parse the input")] Pest, - #[fail(display = "invalid number format")] + #[error("invalid number format")] NumberFormat, - #[fail(display = "bad argument count")] + #[error("bad argument count")] ArgCount, } @@ -143,9 +143,7 @@ impl Calc { fn eval_expr(p: Pairs<self::Rule>) -> StdResult<f64, CalcError> { CLIMBER.climb( p, - |pair| { - eval_single_pair(pair) - }, + eval_single_pair, |lhs, op, rhs| { let lhs = lhs?; let rhs = rhs?; @@ -198,15 +196,17 @@ mod test { } } -pub fn roll(_ctx: &mut Context, msg: &Message, args: Args) -> Result<()> { +#[command] +#[aliases("calc", "calculate")] +pub fn roll(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> { match Calc::eval(args.rest()) { Ok(result) => { debug!("got calc result '{}'", result); - send(msg.channel_id, &format!("{}", result), msg.tts) + ctx.send(msg.channel_id, &format!("{}", result), msg.tts) }, Err(e) => { error!("error encountered reading calc '{}': {}", args.rest(), e); - send(msg.channel_id, "I COULDN'T READ THAT YOU FUCK", msg.tts) + ctx.send(msg.channel_id, "I COULDN'T READ THAT YOU FUCK", msg.tts) }, } } diff --git a/src/commands/sound_levels.rs b/src/commands/sound_levels.rs index 60803a3..81508a6 100644 --- a/src/commands/sound_levels.rs +++ b/src/commands/sound_levels.rs @@ -6,15 +6,16 @@ use serenity::{ use crate::{ audio::{PlayQueue, VoiceManager}, - commands::send, Result, TARGET_GUILD_ID, + util::CtxExt, }; pub const DEFAULT_VOLUME: f32 = 0.10; +#[command] pub fn mute(ctx: &mut Context, _: &Message, _: Args) -> Result<()> { - let mgr_lock = ctx.data.lock().get::<VoiceManager>().cloned().unwrap(); + let mgr_lock = ctx.data.write().get::<VoiceManager>().cloned().unwrap(); let mut manager = mgr_lock.lock(); manager.get_mut(*TARGET_GUILD_ID) @@ -30,8 +31,9 @@ pub fn mute(ctx: &mut Context, _: &Message, _: Args) -> Result<()> { Ok(()) } +#[command] pub fn unmute(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { - let mgr_lock = ctx.data.lock().get::<VoiceManager>().cloned().unwrap(); + let mgr_lock = ctx.data.write().get::<VoiceManager>().cloned().unwrap(); let mut manager = mgr_lock.lock(); manager.get_mut(*TARGET_GUILD_ID) @@ -41,35 +43,36 @@ pub fn unmute(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { } else { handler.mute(false); trace!("Unmuted"); - let _ = send(msg.channel_id, "REEEEEEEEEEEEEE", msg.tts); + let _ = ctx.send(msg.channel_id, "REEEEEEEEEEEEEE", msg.tts); } }); Ok(()) } +#[command] pub fn volume(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { if args.len() == 0 { let vol = { - let queue_lock = ctx.data.lock().get::<PlayQueue>().cloned().unwrap(); + let queue_lock = ctx.data.write().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 send(msg.channel_id, &format!("volume: {}%", vol), msg.tts); + return ctx.send(msg.channel_id, &format!("volume: {}%", vol), msg.tts); } let vol: usize = match args.single::<f32>() { Ok(vol) if vol.is_nan() => { warn!("reporting NaN volume"); - return send(msg.channel_id, "you're a fuck", msg.tts); + return ctx.send(msg.channel_id, "you're a fuck", msg.tts); }, Ok(vol) => vol as usize, Err(e) => { error!("parsing volume arg: {}", e); - return send(msg.channel_id, "???????", msg.tts) + return ctx.send(msg.channel_id, "???????", msg.tts) }, }; @@ -84,7 +87,7 @@ pub fn volume(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { vol = 0.0; } - let queue_lock = ctx.data.lock().get::<PlayQueue>().cloned().unwrap(); + let queue_lock = ctx.data.write().get::<PlayQueue>().cloned().unwrap(); { let mut play_queue = queue_lock.write().unwrap(); @@ -92,7 +95,7 @@ pub fn volume(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { info!("volume updated to {}", vol); } - send(msg.channel_id, format!("volume adjusted{}", adjusted_text), msg.tts)?; + ctx.send(msg.channel_id, format!("volume adjusted{}", adjusted_text), msg.tts)?; { let play_queue = queue_lock.read().unwrap(); diff --git a/src/db/mod.rs b/src/db/mod.rs index 8702099..2a6d1f4 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -164,7 +164,6 @@ pub fn delete_meme<T: AsRef<str>>(conn: &PgConnection, search: T, deleted_by: u6 pub fn rare_meme(conn: &PgConnection, audio: bool) -> Result<Meme> { use rand::prelude::*; - use failure::err_msg; let raw_conn = raw_connection()?; @@ -205,7 +204,7 @@ pub fn rare_meme(conn: &PgConnection, audio: bool) -> Result<Meme> { .collect::<Vec<_>>(); if elems.len() == 0 { - return Err(err_msg("no rare memes found")); + return Err(anyhow!("no rare memes found")); } let mut rng = thread_rng(); @@ -213,7 +212,7 @@ pub fn rare_meme(conn: &PgConnection, audio: bool) -> Result<Meme> { let meme_id = elems.into_iter() .find(|(_, x)| target_prob < *x) - .ok_or(err_msg("couldn't locate meme satisfying target probability"))? + .ok_or(anyhow!("couldn't locate meme satisfying target probability"))? .0; Meme::find(conn, meme_id) @@ -221,7 +220,6 @@ pub fn rare_meme(conn: &PgConnection, audio: bool) -> Result<Meme> { pub fn rand_meme(conn: &PgConnection, audio: bool) -> Result<Meme> { use rand::{thread_rng, seq::SliceRandom}; - use failure::err_msg; use std::ops::Try; let ids: Vec<i32> = if audio { @@ -243,7 +241,7 @@ pub fn rand_meme(conn: &PgConnection, audio: bool) -> Result<Meme> { let id = ids.choose(&mut thread_rng()) .into_result() - .map_err( |_| err_msg("couldn't load meme"))?; + .map_err( |_| anyhow!("couldn't load meme"))?; memes::table .find(id) @@ -253,7 +251,6 @@ pub fn rand_meme(conn: &PgConnection, audio: bool) -> Result<Meme> { pub fn rand_audio_meme(conn: &PgConnection) -> Result<Meme> { use rand::{thread_rng, seq::SliceRandom}; - use failure::err_msg; use std::ops::Try; let ids: Vec<i32> = memes::table @@ -264,7 +261,7 @@ pub fn rand_audio_meme(conn: &PgConnection) -> Result<Meme> { let id = ids.choose(&mut thread_rng()) .into_result() - .map_err(|_| err_msg("couldn't load audio meme"))?; + .map_err(|_| anyhow!("couldn't load audio meme"))?; memes::table .find(id) @@ -274,7 +271,6 @@ pub fn rand_audio_meme(conn: &PgConnection) -> Result<Meme> { pub fn rand_silent_meme(conn: &PgConnection) -> Result<Meme> { use rand::{thread_rng, seq::SliceRandom}; - use failure::err_msg; use std::ops::Try; let ids: Vec<i32> = memes::table @@ -285,7 +281,7 @@ pub fn rand_silent_meme(conn: &PgConnection) -> Result<Meme> { let id = ids.choose(&mut thread_rng()) .into_result() - .map_err(|_| err_msg("couldn't load audio meme"))?; + .map_err(|_| anyhow!("couldn't load audio meme"))?; memes::table .find(id) diff --git a/src/game.rs b/src/game.rs index 807953d..5ca5c72 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,4 +1,5 @@ use std::{ + convert::Infallible, iter, result::Result as StdResult, str::{ @@ -7,10 +8,7 @@ use std::{ }, }; -use failure::{ - err_msg, - Error, -}; +use anyhow::Error; use fnv::{ FnvHashMap, FnvHashSet, @@ -18,8 +16,8 @@ use fnv::{ use itertools::Itertools; use serenity::{ framework::standard::{ + ArgError, Args, - StandardFramework, }, model::{ channel::Message, @@ -31,9 +29,9 @@ use serenity::{ use url::Url; use crate::{ - commands::send, must_env_lookup, Result, + util::CtxExt, VOICE_CHANNEL_ID, }; @@ -44,27 +42,6 @@ lazy_static! { static ref MAX_SHEET_COLUMN: String = must_env_lookup("MAX_SHEET_COLUMN"); } -pub fn register(s: StandardFramework) -> StandardFramework { - s - .command("game", |c| c - .known_as("gaem") - .known_as("installedgaem") - .known_as("installedgame") - .desc("what game should we play?") - .exec(installedgame) - ) - .command("ownedgame", |c| c - .known_as("ownedgaem") - .desc("what games does everyone have?") - .exec(ownedgame) - ) - .command("updategame", |c| c - .known_as("updategaem") - .desc("update your games on the spreadsheet") - .exec(updategaem) - ) -} - #[derive(Deserialize, Debug, Clone, PartialEq, Eq, Hash)] struct UserInfo { name: String, @@ -139,25 +116,29 @@ impl FromStr for GameStatus { } else if s.chars().all(char::is_whitespace) { Ok(GameStatus::Unknown) } else { - Err(err_msg(format!("unexpected status '{}'", s))) + Err(anyhow!(format!("unexpected status '{}'", s))) } } } -fn installedgame(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> { +#[command] +#[aliases("installedgaem")] +pub fn installedgame(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> { game(ctx, msg, args, GameStatus::Installed) } -fn ownedgame(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> { +#[command] +#[aliases("ownedgaem")] +pub fn ownedgame(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> { game(ctx, msg, args, GameStatus::NotInstalled) } -#[derive(Copy, Clone, Debug, Fail, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, Debug, Error, PartialEq, Eq, Hash)] pub enum UserLookupError { - #[fail(display = "too many possible options ({}) for query", _0)] + #[error("too many possible options ({}) for query", _0)] Ambiguous(usize), - #[fail(display = "user wasn't found in the guild")] + #[error("user wasn't found in the guild")] NotFound, } @@ -203,22 +184,24 @@ pub fn get_user_id<S: AsRef<str>>(g: &Guild, s: S) -> StdResult<UserId, UserLook } } -fn game(_ctx: &mut Context, msg: &Message, args: Args, min_status: GameStatus) -> Result<()> { - let guild = msg.channel_id.to_channel()? +#[command] +#[aliases("gaem")] +pub fn game(ctx: &mut Context, msg: &Message, mut args: Args, min_status: GameStatus) -> Result<()> { + let guild = msg.channel_id.to_channel(ctx)? .guild() - .ok_or(err_msg("couldn't find guild"))?; + .ok_or(anyhow!("couldn't find guild"))?; let guild = guild.read() - .guild() - .ok_or(err_msg("couldn't find guild"))?; + .guild(ctx) + .ok_or(anyhow!("couldn't find guild"))?; let guild = guild .read(); - let user_args = if args.rest().is_empty() { + let user_args: Vec<String> = if args.rest().is_empty() { Vec::new() } else { - args.multiple_quoted::<String>()? + args.quoted().iter::<String>().collect::<StdResult<Vec<_>, ArgError<Infallible>>>()? }; let mut users = user_args @@ -232,12 +215,12 @@ fn game(_ctx: &mut Context, msg: &Message, args: Args, min_status: GameStatus) - match possible { Err(UserLookupError::NotFound) => { - let _ = send(msg.channel_id, &format!("didn't recognize {}", &u), msg.tts); + let _ = ctx.send(msg.channel_id, &format!("didn't recognize {}", &u), msg.tts); None }, Ok(x) => Some(x), Err(UserLookupError::Ambiguous(x)) => { - let _ = send(msg.channel_id, &format!("too many matches ({}) for {}", x, &u), msg.tts); + let _ = ctx.send(msg.channel_id, &format!("too many matches ({}) for {}", x, &u), msg.tts); None }, } @@ -278,7 +261,7 @@ fn game(_ctx: &mut Context, msg: &Message, args: Args, min_status: GameStatus) - if inferred && users.len() < 2 || !inferred && users.len() < 1 { info!("too few known users to make game comparison"); - send(msg.channel_id, "yer too lonely", msg.tts)?; + ctx.send(msg.channel_id, "yer too lonely", msg.tts)?; return Ok(()); } @@ -354,7 +337,7 @@ fn game(_ctx: &mut Context, msg: &Message, args: Args, min_status: GameStatus) - games_formatted = "**LITERALLY NOTHING**".to_owned(); } - send(msg.channel_id, &games_formatted, msg.tts) + ctx.send(msg.channel_id, &games_formatted, msg.tts) } fn load_spreadsheet() -> Result<Vec<Vec<String>>> { @@ -368,9 +351,7 @@ fn load_spreadsheet() -> Result<Vec<Vec<String>>> { .append_pair("key", &*SHEETS_API_KEY); let req = reqwest::Request::new(reqwest::Method::GET, u); - let client = reqwest::Client::new(); - let mut resp = client.execute(req)?; #[derive(Deserialize)] @@ -389,7 +370,9 @@ fn load_spreadsheet() -> Result<Vec<Vec<String>>> { Ok(resp.value_ranges.into_iter().next().unwrap().values) } -fn updategaem(_ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { +#[command] +#[aliases("updategame")] +pub fn updategaem(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { use regex::Regex; let arg_user = args.single_quoted::<String>(); @@ -399,13 +382,13 @@ fn updategaem(_ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { } else { use std::borrow::Borrow; - let guild = msg.channel_id.to_channel()? + let guild = msg.channel_id.to_channel(ctx)? .guild() - .ok_or(err_msg("couldn't find guild"))?; + .ok_or(anyhow!("couldn't find guild"))?; let guild = guild.read() - .guild() - .ok_or(err_msg("couldn't find guild"))?; + .guild(ctx) + .ok_or(anyhow!("couldn't find guild"))?; let guild = guild .read(); @@ -417,12 +400,12 @@ fn updategaem(_ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { let username = match DISCORD_MAP.get(&user) { Some(s) => s, - None => return send(msg.channel_id, "WHO THE FUCK ARE YE", msg.tts), + None => return ctx.send(msg.channel_id, "WHO THE FUCK ARE YE", msg.tts), }; let steam_id = match STEAM_MAP.get(&user) { Some(u) => u, - None => return send(msg.channel_id, "WHO ARE YE ON STEAM", msg.tts), + None => return ctx.send(msg.channel_id, "WHO ARE YE ON STEAM", msg.tts), }; let spreadsheet = load_spreadsheet()?; @@ -432,7 +415,7 @@ fn updategaem(_ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { let user_column = match user_column { Some(c) => &spreadsheet[c][1..], - None => return send(msg.channel_id, "YER NOT IN THE SPREADSHEET", msg.tts), + None => return ctx.send(msg.channel_id, "YER NOT IN THE SPREADSHEET", msg.tts), }; lazy_static! { @@ -446,7 +429,7 @@ fn updategaem(_ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { Some(c) => &spreadsheet[c][1..], None => { error!("didn't find an appid column in the spreadsheet"); - return send(msg.channel_id, "SPREADSHEET BROKE", msg.tts) + return ctx.send(msg.channel_id, "SPREADSHEET BROKE", msg.tts) }, }; @@ -499,10 +482,10 @@ fn updategaem(_ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { .join("\n"); if found_games.len() > 0 { - send(msg.channel_id, &format!("{} games owned on steam that are missing from the list:\n{}", + ctx.send(msg.channel_id, &format!("{} games owned on steam that are missing from the list:\n{}", found_games.chars().filter(|x| *x == '\n').count() + 1, found_games), msg.tts) } else { - send(msg.channel_id, "up to date", msg.tts) + ctx.send(msg.channel_id, "up to date", msg.tts) } }
\ No newline at end of file diff --git a/src/main.rs b/src/main.rs index aac7587..6af4660 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,13 +6,13 @@ #![feature(box_syntax, box_patterns)] +#[macro_use] extern crate anyhow; extern crate chrono; #[cfg(feature = "db")] #[macro_use] extern crate diesel; extern crate dotenv; #[macro_use] extern crate dotenv_codegen; extern crate either; -#[macro_use] extern crate failure; extern crate fern; extern crate fnv; #[cfg_attr(test, macro_use)] extern crate itertools; @@ -27,9 +27,10 @@ extern crate regex; extern crate reqwest; #[macro_use] extern crate serde; extern crate serde_json; -extern crate serenity; +#[macro_use] extern crate serenity; extern crate sha1; extern crate statrs; +#[macro_use] extern crate thiserror; extern crate time; extern crate timeago; extern crate typemap; @@ -47,13 +48,9 @@ use std::{ use chrono::Datelike; use dotenv::dotenv; -use failure::Error; use fnv::{FnvHashMap, FnvHashSet}; use serenity::{ - framework::{ - standard::help_commands, - StandardFramework, - }, + framework::StandardFramework, model::{ gateway::Ready, id::{ChannelId, GuildId, MessageId, UserId}, @@ -84,7 +81,9 @@ mod commands; mod util; mod audio; -pub type Result<T> = ::std::result::Result<T, Error>; +pub type Error = anyhow::Error; + +pub type Result<T> = anyhow::Result<T>; lazy_static! { static ref TARGET_GUILD: u64 = dotenv!("TARGET_GUILD").parse().expect("unable to parse TARGET_GUILD as u64"); @@ -94,7 +93,7 @@ lazy_static! { struct Handler; impl EventHandler for Handler { - fn ready(&self, _: Context, r: Ready) { + fn ready(&self, ctx: Context, r: Ready) { let guild = r.guilds.iter() .find(|g| g.id().0 == *TARGET_GUILD); @@ -103,20 +102,20 @@ impl EventHandler for Handler { } #[cfg(debug_assertions)] { - let _ = guild.map(|g| g.id().edit_nickname(Some("thulani (dev)"))); + let _ = guild.map(|g| g.id().edit_nickname(ctx, Some("thulani (dev)"))); } #[cfg(not(debug_assertions))] { - let _ = guild.map(|g| g.id().edit_nickname(Some("thulani"))); + let _ = guild.map(|g| g.id().edit_nickname(ctx, Some("thulani"))); } } - fn message_delete(&self, _ctx: Context, channel_id: ChannelId, deleted_message_id: MessageId) { + fn message_delete(&self, ctx: Context, channel_id: ChannelId, deleted_message_id: MessageId) { MESSAGE_WATCH.lock() .remove(&deleted_message_id) .iter() .for_each(|id| { - if let Err(e) = channel_id.delete_message(id) { + if let Err(e) = channel_id.delete_message(ctx, id) { error!("deleting message: {}", e); } }); @@ -161,15 +160,15 @@ fn run() -> Result<()> { let mut framework = StandardFramework::new() .configure(|c| c .allow_dm(false) - .allow_whitespace(true) + .with_whitespace(true) .prefixes(all_prefixes) .ignore_bots(true) - .on_mention(false) + .on_mention(None) .owners(vec![UserId(owner_id)].into_iter().collect()) .case_insensitivity(true) .delimiter("\t") ) - .before(move |_ctx, message, cmd| { + .before(move |ctx, message, cmd| { debug!("got command '{}' from user '{}' ({})", cmd, message.author.name, message.author.id); if !message.guild_id.map_or(false, |x| x.0 == *TARGET_GUILD) { info!("rejecting command '{}' from user '{}': wrong guild", cmd, message.author.name); @@ -202,7 +201,7 @@ fn run() -> Result<()> { info!("rejecting command '{}' from user '{}': {}", cmd, message.author.name, reason); - match crate::commands::send_result(message.channel_id, "no", message.tts) { + match ctx.send_result(message.channel_id, "no", message.tts) { Err(e) => error!("sending restricted prefix response: {}", e), Ok(msg_id) => { let mut mp = MESSAGE_WATCH.lock(); @@ -212,17 +211,18 @@ fn run() -> Result<()> { return false; }) - .after(|_ctx, msg, cmd, err| { + .after(|ctx, msg, cmd, err| { match err { Ok(()) => { trace!("command '{}' completed successfully", cmd); }, + Err(e) => { - if let Err(e) = msg.react("❌") { + if let Err(e) = msg.react(ctx, "❌") { error!("reacting to failed message: {}", e); } - if let Err(e) = crate::commands::send(msg.channel_id, "BANIC", msg.tts) { + if let Err(e) = ctx.send(msg.channel_id, "BANIC", msg.tts) { error!("sending BANIC: {}", e); } @@ -230,13 +230,9 @@ fn run() -> Result<()> { } } }) - .bucket("Standard", 1, 10, 3) - .customised_help(help_commands::with_embeds, |c| { - c - }); + .bucket("Standard", |b| b.delay(1).limit(20).time_span(60)); framework = register_commands(framework); - framework = game::register(framework); client.with_framework(framework); diff --git a/src/util.rs b/src/util.rs index 85ec6d7..1e478d3 100644 --- a/src/util.rs +++ b/src/util.rs @@ -3,9 +3,62 @@ use std::{ str::FromStr,
};
-use serenity::model::permissions::Permissions;
+use serenity::{
+ client::Context,
+ model::{
+ id::{
+ ChannelId,
+ MessageId,
+ },
+ permissions::Permissions,
+ }
+};
use url::Url;
+use crate::{
+ audio::PlayQueue,
+ Result,
+};
+
+pub trait CtxExt {
+ fn currently_playing(&self) -> bool;
+ fn users_listening(&self) -> Result<bool>;
+ fn send<A: AsRef<str>>(&self, channel: ChannelId, text: A, tts: bool) -> Result<()>;
+ fn send_result<A: AsRef<str>>(&self, channel: ChannelId, text: A, tts: bool) -> Result<MessageId>;
+}
+
+impl CtxExt for Context {
+ fn currently_playing(&self) -> bool {
+ let queue_lock = self.data.read().get::<PlayQueue>().cloned().unwrap();
+ let play_queue = queue_lock.read().unwrap();
+ play_queue.playing.is_some()
+ }
+
+ fn users_listening(&self) -> Result<bool> {
+ let channel_id = ChannelId(must_env_lookup::<u64>("VOICE_CHANNEL"));
+ let channel = channel_id.to_channel(self)?;
+ let res = channel.guild()
+ .and_then(|ch| ch.read().guild(self))
+ .map(|g| (&g.read().voice_states)
+ .into_iter()
+ .any(|(_, state)| state.channel_id == Some(channel_id)))
+ .unwrap_or(false);
+
+ Ok(res)
+ }
+
+ #[inline]
+ fn send<A: AsRef<str>>(&self, channel: ChannelId, text: A, tts: bool) -> Result<()> {
+ self.send_result(channel, text, tts).map(|_| ())
+ }
+
+ #[inline]
+ fn send_result<A: AsRef<str>>(&self, channel: ChannelId, text: A, tts: bool) -> Result<MessageId> {
+ let result = channel.send_message(self, |m| m.content(text.as_ref()).tts(tts))?;
+ Ok(result.id)
+ }
+}
+
lazy_static! {
static ref REQUIRED_PERMS: Permissions = Permissions::EMBED_LINKS |
Permissions::READ_MESSAGES |
|
