diff options
| author | Nathan Perry <np@nathanperry.dev> | 2024-05-08 10:28:04 -0400 |
|---|---|---|
| committer | Nathan Perry <np@nathanperry.dev> | 2024-05-08 14:16:01 -0400 |
| commit | fe467f60d99efa54f2ef64606e7d39b9b06d7294 (patch) | |
| tree | a62bb50fedb1959d1a155878f0ff0ab7b1f699b6 /src | |
| parent | 48aa684dece2696e21fd871eb6f3825f28fe0200 (diff) | |
update all deps
Diffstat (limited to 'src')
| -rw-r--r-- | src/audio/mod.rs | 28 | ||||
| -rw-r--r-- | src/audio/play_queue.rs | 4 | ||||
| -rw-r--r-- | src/bot.rs | 234 | ||||
| -rw-r--r-- | src/commands/help.rs | 8 | ||||
| -rw-r--r-- | src/commands/meme/create.rs | 127 | ||||
| -rw-r--r-- | src/commands/meme/delete.rs | 24 | ||||
| -rw-r--r-- | src/commands/meme/history.rs | 188 | ||||
| -rw-r--r-- | src/commands/meme/invoke.rs | 81 | ||||
| -rw-r--r-- | src/commands/meme/mod.rs | 115 | ||||
| -rw-r--r-- | src/commands/mod.rs | 74 | ||||
| -rw-r--r-- | src/commands/playback.rs | 144 | ||||
| -rw-r--r-- | src/commands/roll.rs | 86 | ||||
| -rw-r--r-- | src/commands/sound_levels.rs | 85 | ||||
| -rw-r--r-- | src/commands/today/mod.rs | 93 | ||||
| -rw-r--r-- | src/config.rs | 2 | ||||
| -rw-r--r-- | src/db/mod.rs | 2 | ||||
| -rw-r--r-- | src/db/models.rs | 1 | ||||
| -rw-r--r-- | src/db/schema.rs | 32 | ||||
| -rw-r--r-- | src/game.rs | 176 | ||||
| -rw-r--r-- | src/main.rs | 25 | ||||
| -rw-r--r-- | src/util.rs | 120 |
21 files changed, 937 insertions, 712 deletions
diff --git a/src/audio/mod.rs b/src/audio/mod.rs index 3c3041f..9affdb1 100644 --- a/src/audio/mod.rs +++ b/src/audio/mod.rs @@ -3,24 +3,20 @@ use std::sync::Arc; use chrono::Duration; use either::Either; use serenity::{ - client::bridge::voice::ClientVoiceManager, - model::{ - id::{ - ChannelId, - }, - }, + model::id::ChannelId, prelude::*, - voice::LockedAudio, }; use typemap::Key; -pub use self::play_queue::PlayQueue; -pub use self::timeutil::parse_times; -pub use self::ytdl::*; +pub use self::{ + play_queue::PlayQueue, + timeutil::parse_times, + ytdl::*, +}; +mod play_queue; mod timeutil; mod ytdl; -mod play_queue; pub struct VoiceManager; @@ -37,15 +33,15 @@ impl VoiceManager { #[derive(Clone, Debug)] pub struct PlayArgs { - pub data: Either<String, Vec<u8>>, - pub initiator: String, + pub data: Either<String, Vec<u8>>, + pub initiator: String, pub sender_channel: ChannelId, - pub start: Option<Duration>, - pub end: Option<Duration>, + pub start: Option<Duration>, + pub end: Option<Duration>, } #[derive(Clone)] pub struct CurrentItem { pub init_args: PlayArgs, - pub audio: LockedAudio, + pub audio: LockedAudio, } diff --git a/src/audio/play_queue.rs b/src/audio/play_queue.rs index ff1c2c9..34fc113 100644 --- a/src/audio/play_queue.rs +++ b/src/audio/play_queue.rs @@ -54,6 +54,10 @@ impl Key for PlayQueue { type Value = Arc<RwLock<PlayQueue>>; } +impl serenity::prelude::TypeMapKey for PlayQueue { + type Value = Arc<RwLock<PlayQueue>>; +} + impl PlayQueue { pub fn new() -> Self { PlayQueue { @@ -1,54 +1,67 @@ use std::{ - sync::Mutex, fs::File, - result::Result as StdResult, path::PathBuf, str::FromStr, -}; - -use serenity::{ - prelude::*, - model::{ - gateway::Ready, - id::{ - ChannelId, - MessageId, - }, - channel::Message, - event::ResumedEvent, - }, - framework::StandardFramework, + future::Future, + path::PathBuf, + pin::Pin, + result::Result as StdResult, + str::FromStr, + sync::Mutex, }; +use chrono::Datelike; use fnv::{ FnvHashMap, FnvHashSet, }; - -use serde_json::Value; -use chrono::Datelike; use lazy_static::lazy_static; use log::{ debug, - info, error, + info, trace, warn, }; +use serenity::{ + all::{ + GuildId, + ReactionType, + }, + framework::{ + standard::{ + BucketBuilder, + CommandError, + Configuration, + }, + StandardFramework, + }, + model::{ + channel::Message, + event::ResumedEvent, + gateway::Ready, + id::{ + ChannelId, + MessageId, + }, + }, + prelude::*, +}; use crate::{ - Result, - Error, audio, - util::OAUTH_URL, - util::CtxExt, commands::register_commands, config::CONFIG, + util, + util::OAUTH_URL, + Error, + Result, }; struct Handler; + +#[serenity::async_trait] impl EventHandler for Handler { - fn ready(&self, ctx: Context, r: Ready) { - let guild = r.guilds.iter() - .find(|g| g.id() == CONFIG.discord.guild()); + async fn ready(&self, ctx: Context, r: Ready) { + let guild = r.guilds.iter().find(|g| g.id() == CONFIG.discord.guild()); if guild.is_none() { info!("bot isn't in configured guild. join here: {:?}", OAUTH_URL.as_str()); @@ -70,24 +83,26 @@ impl EventHandler for Handler { }); } - fn resume(&self, _ctx: Context, _resume: ResumedEvent) { + async fn resume(&self, _ctx: Context, _resume: ResumedEvent) { info!("reconnected to discord"); } - fn message_delete(&self, _ctx: Context, _channel_id: ChannelId, deleted_message_id: MessageId) { - MESSAGE_WATCH.lock() - .unwrap() - .remove(&deleted_message_id); - } - - fn unknown(&self, _ctx: Context, name: String, _raw: Value) { - info!("unknown event: {:?}", name); + async fn message_delete( + &self, + _ctx: Context, + _: ChannelId, + deleted_message_id: MessageId, + _: Option<GuildId>, + ) { + MESSAGE_WATCH.lock().unwrap().remove(&deleted_message_id); } } lazy_static! { - static ref MESSAGE_WATCH: Mutex<FnvHashMap<MessageId, MessageId>> = Mutex::new(FnvHashMap::default()); - static ref PREFIXES: Vec<&'static str> = vec!["!thulani ", "!thulan ", "!thulando madando ", "!thulando "]; + static ref MESSAGE_WATCH: Mutex<FnvHashMap<MessageId, MessageId>> = + Mutex::new(FnvHashMap::default()); + static ref PREFIXES: Vec<&'static str> = + vec!["!thulani ", "!thulan ", "!thulando madando ", "!thulando "]; static ref RESTRICTED_PREFIXES: Vec<&'static str> = vec!["!todd ", "!toddbert ", "!toddlani "]; static ref ALL_PREFIXES: Vec<&'static str> = { let mut all_prefixes: Vec<&'static str> = vec![]; @@ -95,7 +110,6 @@ lazy_static! { all_prefixes.extend(RESTRICTED_PREFIXES.iter()); all_prefixes }; - static ref RESTRICT_IDS: FnvHashSet<u64> = { let default_path = PathBuf::from_str("restrict.json").unwrap(); let restrict_path = CONFIG.restrict.as_ref().unwrap_or(&default_path); @@ -108,10 +122,7 @@ lazy_static! { warn!("opening restrict file: {}", e); } - let result = restrict_ids - .unwrap_or_default() - .into_iter() - .collect::<FnvHashSet<_>>(); + let result = restrict_ids.unwrap_or_default().into_iter().collect::<FnvHashSet<_>>(); info!("restricted ids: {:?}", result); @@ -119,91 +130,107 @@ lazy_static! { }; } -fn framework() -> StandardFramework { +async fn framework() -> StandardFramework { + let builder = BucketBuilder::default().delay(1).limit(20).time_span(60); + let framework = StandardFramework::new() - .configure(|c| c - .allow_dm(false) - .with_whitespace(true) - .prefixes(ALL_PREFIXES.iter()) - .ignore_bots(true) - .on_mention(None) - .owners(vec![CONFIG.discord.owner()].into_iter().collect()) - .case_insensitivity(true) - ) .before(before_handle) .after(after_handle) - .bucket("Standard", |b| { - b.delay(1).limit(20).time_span(60) - }); + .bucket("Standard", builder) + .await; + + let config = Configuration::default() + .allow_dm(false) + .with_whitespace(true) + .prefixes(ALL_PREFIXES.iter().map(|x| x.to_string())) + .ignore_bots(true) + .on_mention(None) + .owners(vec![CONFIG.discord.owner()].into_iter().collect()) + .case_insensitivity(true); + + framework.configure(config); register_commands(framework) } -fn before_handle(ctx: &mut Context, message: &Message, cmd: &str) -> bool { +fn before_handle( + ctx: &Context, + message: &Message, + cmd: &str, +) -> Pin<Box<dyn Future<Output = bool> + Send>> { debug!("got command '{}' from user '{}' ({})", cmd, message.author.name, message.author.id); - if !message.guild_id.map_or(false, |x| x == CONFIG.discord.guild()) { - info!("rejecting command '{}' from user '{}': wrong guild", cmd, message.author.name); - return false; - } + Box::pin(async move { + if !message.guild_id.map_or(false, |x| x == CONFIG.discord.guild()) { + info!("rejecting command '{}' from user '{}': wrong guild", cmd, message.author.name); + return false; + } - if message.author.id == CONFIG.discord.owner() { - return true; - } + if message.author.id == CONFIG.discord.owner() { + return true; + } - let restricted_prefix = RESTRICTED_PREFIXES.iter() - .any(|prefix| message.content.starts_with(prefix)); + let restricted_prefix = + RESTRICTED_PREFIXES.iter().any(|prefix| message.content.starts_with(prefix)); - if !restricted_prefix { - return true; - } + if !restricted_prefix { + return true; + } - const PERMITTED_WEEKDAY: chrono::Weekday = chrono::Weekday::Tue; + const PERMITTED_WEEKDAY: chrono::Weekday = chrono::Weekday::Tue; - let user_is_restricted = RESTRICT_IDS.contains(&message.author.id.0); - let restrictions_flipped = chrono::Local::now().weekday() == PERMITTED_WEEKDAY; + let user_is_restricted = RESTRICT_IDS.contains(&message.author.id.get()); + let restrictions_flipped = chrono::Local::now().weekday() == PERMITTED_WEEKDAY; - if user_is_restricted == restrictions_flipped { - return true; - } + if user_is_restricted == restrictions_flipped { + return true; + } - let reason = if !restrictions_flipped { - "restricted prefix".to_owned() - } else { - format!("it is {:?}", PERMITTED_WEEKDAY) - }; + let reason = if !restrictions_flipped { + "restricted prefix".to_owned() + } else { + format!("it is {:?}", PERMITTED_WEEKDAY) + }; - info!("rejecting command '{}' from user '{}': {}", cmd, message.author.name, reason); + info!("rejecting command '{}' from user '{}': {}", cmd, message.author.name, reason); - 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().unwrap(); - mp.insert(message.id, msg_id); + match util::send_result(ctx, message.channel_id, "no", message.tts).await { + Err(e) => error!("sending restricted prefix response: {}", e), + Ok(msg_id) => { + let mut mp = MESSAGE_WATCH.lock().unwrap(); + mp.insert(message.id, msg_id); + }, } - } - false + false + }) } -fn after_handle(ctx: &mut Context, msg: &Message, cmd: &str, err: StdResult<(), Error>) { - match err { - Ok(()) => { - trace!("command '{}' completed successfully", cmd); - }, +fn after_handle<'fut>( + ctx: &'fut Context, + msg: &'fut Message, + cmd: &'fut str, + err: StdResult<(), CommandError>, +) -> Pin<Box<dyn Future<Output = ()> + Send + 'fut>> { + Box::pin(async move { + match err { + Ok(()) => { + trace!("command '{}' completed successfully", cmd); + }, - Err(e) => { - if let Err(e) = msg.react(&ctx, "❌") { - error!("reacting to failed message: {}", e); - } + Err(e) => { + if let Err(e) = msg.react(&ctx, ReactionType::Unicode("❌".to_owned())).await { + error!("reacting to failed message: {}", e); + } - if let Err(e) = ctx.send(msg.channel_id, "BANIC", msg.tts) { - error!("sending BANIC: {}", e); - } + if let Err(e) = util::send(ctx, msg.channel_id, "BANIC", msg.tts).await { + error!("sending BANIC: {}", e); + } - error!("error encountered handling command '{}': {:?}", cmd, e); + error!("error encountered handling command '{}': {:?}", cmd, e); + }, } - } + }) } pub fn run() -> Result<()> { @@ -219,7 +246,8 @@ pub fn run() -> Result<()> { ctrlc::set_handler(move || { info!("shutting down"); shard_manager.lock().shutdown_all(); - }).expect("unable to create SIGINT/SIGTERM handlers"); + }) + .expect("unable to create SIGINT/SIGTERM handlers"); client.start() } diff --git a/src/commands/help.rs b/src/commands/help.rs index 743a099..588cdf7 100644 --- a/src/commands/help.rs +++ b/src/commands/help.rs @@ -16,17 +16,17 @@ use serenity::{ }, prelude::*, }; +use serenity::framework::standard::CommandResult; -use crate::Result; #[help] -pub fn help( - ctx: &mut Context, +pub async fn help( + ctx: &Context, msg: &Message, args: Args, opts: &'static HelpOptions, groups: &[&'static CommandGroup], owners: HashSet<UserId>, -) -> Result<()> { +) -> CommandResult { help_commands::with_embeds(ctx, msg, args, opts, groups, owners) } diff --git a/src/commands/meme/create.rs b/src/commands/meme/create.rs index 06cc4ef..97c5276 100644 --- a/src/commands/meme/create.rs +++ b/src/commands/meme/create.rs @@ -14,9 +14,9 @@ use log::{ }; use serenity::{ framework::standard::{ + macros::command, Args, Delimiter, - macros::command, }, model::channel::Message, prelude::*, @@ -25,20 +25,28 @@ use url::Url; use anyhow::anyhow; use lazy_static::lazy_static; +use serenity::{ + all::ReactionType, + framework::standard::{ + CommandError, + CommandResult, + }, + futures::TryFutureExt, +}; use crate::{ - Result, audio::{ parse_times, ytdl_url, }, db::{ - Audio, connection, + Audio, Image, NewMeme, }, - util::CtxExt, FFMPEG_COMMAND, + util, + FFMPEG_COMMAND, }; lazy_static! { @@ -46,13 +54,17 @@ lazy_static! { } #[command] -pub fn addmeme(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> { +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(); - let text = if text.is_empty() { None } else { Some(text) }; + let text = if text.is_empty() { + None + } else { + Some(text) + }; let mut conn = connection()?; @@ -60,13 +72,17 @@ pub fn addmeme(ctx: &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 ctx.send(msg.channel_id, "hahAA it's empty xdddd", msg.tts); + return util::send(ctx, msg.channel_id, "hahAA it's empty xdddd", msg.tts) + .map_err(CommandError::from) + .await; } - let image_id = image.map(|att| { - let data = att.download()?; - Image::create(&mut conn, &att.filename, data, msg.author.id.0) - }).transpose()?; + let image_id = image + .map(|att| { + let data = att.download()?; + Image::create(&mut conn, &att.filename, data, msg.author.id.get()) + }) + .transpose()?; let save_result = NewMeme { title, @@ -74,25 +90,31 @@ pub fn addmeme(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> { image_id, audio_id: None, metadata_id: 0, - }.save(&mut conn, msg.author.id.0).map(|_| {}); + } + .save(&mut conn, msg.author.id.get()) + .map(|_| {}); use diesel::result::DatabaseErrorKind; match save_result { Ok(_) => msg.react(&ctx, "👌"), Err(e) => { - if let Some(DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, _)) = e.downcast_ref::<DieselError>() { + if let Some(DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, _)) = + e.downcast_ref::<DieselError>() + { error!("tried to create meme that already exists"); - msg.react(&ctx, "❌")?; - return ctx.send(msg.channel_id, "that meme already exists", msg.tts); + 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; } - return Err(e); - } + return Err(e.into()); + }, } } #[command] -pub fn addaudiomeme(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> { +pub async fn addaudiomeme(ctx: &Context, msg: &Message, args: Args) -> CommandResult { let mut args = Args::new(args.rest(), DELIMS.as_ref()); let title = args.single_quoted::<String>()?; @@ -101,8 +123,8 @@ pub fn addaudiomeme(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> let elems = audio_str.split_whitespace().collect::<Vec<_>>(); if elems.len() == 0 { - ctx.send(msg.channel_id, "are you stupid", msg.tts)?; - return Err(anyhow!("no audio link was provided")) + util::send(ctx, msg.channel_id, "are you stupid", msg.tts).await?; + return Err(anyhow!("no audio link was provided").into()); } let audio_link = Url::parse(elems[0])?; @@ -112,16 +134,24 @@ pub fn addaudiomeme(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> let youtube_url = ytdl_url(audio_link.as_str())?; let duration_opts = if let Some(e) = end { - vec! [ - "-ss".to_owned(), start.map_or_else( + vec![ + "-ss".to_owned(), + start.map_or_else( || "00:00:00".to_owned(), - |s| format!("{:02}:{:02}:{:02}", s.num_hours(), s.num_minutes() % 60, s.num_seconds() % 60) + |s| { + format!( + "{:02}:{:02}:{:02}", + s.num_hours(), + s.num_minutes() % 60, + s.num_seconds() % 60 + ) + }, ), - - "-to".to_owned(), format!("{:02}:{:02}:{:02}", e.num_hours(), e.num_minutes() % 60, e.num_seconds() % 60), + "-to".to_owned(), + format!("{:02}:{:02}:{:02}", e.num_hours(), e.num_minutes() % 60, e.num_seconds() % 60), ] } else { - vec! [] + vec![] }; let ffmpeg_command = Command::new(&*FFMPEG_COMMAND) @@ -129,13 +159,8 @@ pub fn addaudiomeme(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> .arg(youtube_url) .args(duration_opts) .args(&[ - "-ac", "2", - "-ar", "48000", - "-f", "opus", - "-acodec", "libopus", - "-b:a", "96k", - "-fs", "5M", - "-", + "-ac", "2", "-ar", "48000", "-f", "opus", "-acodec", "libopus", "-b:a", "96k", "-fs", + "5M", "-", ]) .stdout(Stdio::piped()) .stderr(Stdio::null()) @@ -145,15 +170,21 @@ pub fn addaudiomeme(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> let mut audio_reader = ffmpeg_command.stdout.unwrap(); let text = args.rest().to_owned(); - let text = if text.is_empty() { None } else { Some(text) }; + let text = if text.is_empty() { + None + } else { + Some(text) + }; let mut conn = connection()?; - let image = msg.attachments.first() + let image = msg + .attachments + .first() .ok_or(anyhow!("no attachment")) .and_then(|att| { let data = att.download()?; - Image::create(&mut conn, &att.filename, data, msg.author.id.0) + Image::create(&mut conn, &att.filename, data, msg.author.id.get()) }) .ok(); @@ -162,10 +193,12 @@ pub fn addaudiomeme(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> if bytes == 0 { debug!("read 0 bytes from audio reader"); - return ctx.send(msg.channel_id, "🔇🔇🔇🔕🔕🔕🔕🔕🔇🔕🔕🔇🔕🔕📣📢📣📢📣", msg.tts); + return util::send(ctx, msg.channel_id, "🔇🔇🔇🔕🔕🔕🔕🔕🔇🔕🔕🔇🔕🔕📣📢📣📢📣", msg.tts) + .map_err(CommandError::from) + .await; } - let audio_id = Audio::create(&mut conn, audio_data, msg.author.id.0)?; + let audio_id = Audio::create(&mut conn, audio_data, msg.author.id.get())?; let save_result = NewMeme { title, @@ -173,19 +206,25 @@ pub fn addaudiomeme(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> image_id: image, audio_id: Some(audio_id), metadata_id: 0, - }.save(&mut conn, msg.author.id.0).map(|_| {}); + } + .save(&mut conn, msg.author.id.get()) + .map(|_| {}); use diesel::result::DatabaseErrorKind; match save_result { - Ok(_) => msg.react(&ctx, "👌"), + Ok(_) => msg.react(&ctx, ReactionType::Unicode("👌".to_owned())), Err(e) => { - if let Some(DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, _)) = e.downcast_ref::<DieselError>() { + if let Some(DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, _)) = + e.downcast_ref::<DieselError>() + { error!("tried to create meme that already exists"); - msg.react(&ctx, "❌")?; - return ctx.send(msg.channel_id, "that meme already exists", msg.tts); + 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; } return Err(e); - } + }, } } diff --git a/src/commands/meme/delete.rs b/src/commands/meme/delete.rs index 7eafc80..c06e9d0 100644 --- a/src/commands/meme/delete.rs +++ b/src/commands/meme/delete.rs @@ -1,45 +1,49 @@ use diesel::{ - NotFound, result::Error as DieselError, + NotFound, }; use log::info; use serenity::{ + all::ReactionType, framework::standard::{ - Args, macros::command, + Args, + CommandResult, }, model::channel::Message, prelude::*, }; use crate::{ - Result, db::{ connection, delete_meme, }, - util::CtxExt, + util, }; #[command] #[aliases("delmem")] -pub fn delmeme(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { +pub async fn delmeme(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { let title = args.single_quoted::<String>()?; let mut conn = connection()?; - match delete_meme(&mut conn, &title, msg.author.id.0) { - Ok(_) => msg.react(ctx, "💀"), + match delete_meme(&mut conn, &title, msg.author.id.get()) { + Ok(_) => { + msg.react(ctx, ReactionType::Unicode("💀".to_owned())).await?; + Ok(()) + }, Err(e) => { if let Some(NotFound) = e.downcast_ref::<DieselError>() { - msg.react(&ctx, "❓")?; + msg.react(&ctx, ReactionType::Unicode("❓".to_owned())).await?; info!("attempted to delete nonexistent meme: '{}'", title); - ctx.send(msg.channel_id, "nice try", msg.tts)?; + util::send(ctx, msg.channel_id, "nice try", msg.tts).await?; return Ok(()); } Err(e)?; Ok(()) - } + }, } } diff --git a/src/commands/meme/history.rs b/src/commands/meme/history.rs index f9e8851..5e200b1 100644 --- a/src/commands/meme/history.rs +++ b/src/commands/meme/history.rs @@ -1,6 +1,6 @@ use diesel::{ - NotFound, result::Error as DieselError, + NotFound, }; use log::{ debug, @@ -9,8 +9,8 @@ use log::{ }; use serenity::{ framework::standard::{ - Args, macros::command, + Args, }, model::channel::Message, prelude::*, @@ -22,6 +22,13 @@ use timeago::{ use anyhow::anyhow; use lazy_static::lazy_static; +use serenity::{ + framework::standard::{ + CommandError, + CommandResult, + }, + futures::TryFutureExt, +}; use crate::{ db::{ @@ -31,9 +38,9 @@ use crate::{ Meme, Metadata, }, - CONFIG, + util, Result, - util::CtxExt, + CONFIG, }; lazy_static! { @@ -50,7 +57,7 @@ static CLEAN_DATE_FORMAT: &'static str = "%b %-e %Y"; #[command] #[aliases("what")] -pub fn wat(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { +pub async fn wat(ctx: &Context, msg: &Message, _: Args) -> CommandResult { let mut conn = connection()?; let record = match InvocationRecord::last(&mut conn) { @@ -58,11 +65,13 @@ pub fn wat(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { Err(e) => { if let Some(NotFound) = e.downcast_ref::<DieselError>() { info!("found no memes in history"); - return ctx.send(msg.channel_id, "no one has ever memed before", msg.tts); + return util::send(ctx, msg.channel_id, "no one has ever memed before", msg.tts) + .map_err(CommandError::from) + .await; } - ctx.send(msg.channel_id, "BAD MEME BAD MEME", msg.tts)?; - return Err(e); + util::send(ctx, msg.channel_id, "BAD MEME BAD MEME", msg.tts).await?; + return Err(e.into()); }, }; @@ -73,17 +82,27 @@ pub fn wat(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { let metadata = Metadata::find(&mut conn, meme.metadata_id)?; let author = CONFIG.discord.guild().member(&ctx, metadata.created_by as u64)?; - ctx.send(msg.channel_id, - &format!("that was \"{}\" by {} ({})", - meme.title, author.mention(), metadata.created.date().format(CLEAN_DATE_FORMAT)), msg.tts)? + util::send( + ctx, + msg.channel_id, + &format!( + "that was \"{}\" by {} ({})", + meme.title, + author.mention(), + metadata.created.date().format(CLEAN_DATE_FORMAT) + ), + msg.tts, + ) + .await? }, Err(e) => { if let Some(NotFound) = e.downcast_ref::<DieselError>() { info!("last meme not found in database"); - return ctx.send(msg.channel_id, "heuueueeeeh?", msg.tts); + return util::send(ctx, msg.channel_id, "heuueueeeeh?", msg.tts).await; } - ctx.send(msg.channel_id, "do i look like i know what a jpeg is", msg.tts)?; + util::send(ctx, msg.channel_id, "do i look like i know what a jpeg is", msg.tts) + .await?; return Err(e); }, }; @@ -92,7 +111,7 @@ pub fn wat(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { } #[command] -pub fn history(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { +pub async fn history(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { use itertools::Itertools; let mut conn = connection()?; @@ -101,7 +120,7 @@ pub fn history(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { if n > CONFIG.max_hist { debug!("user requested more than MAX_HIST ({}) items from history", CONFIG.max_hist); - ctx.send(msg.channel_id, "YER PUSHIN ME OVER THE FUCKIN LINE", true)?; + util::send(ctx, msg.channel_id, "YER PUSHIN ME OVER THE FUCKIN LINE", true).await?; } let n = n.min(CONFIG.max_hist); @@ -110,7 +129,9 @@ pub fn history(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { if records.len() == 0 { info!("no memes in history"); - return ctx.send(msg.channel_id, "i don't remember anything :(", msg.tts); + return util::send(ctx, msg.channel_id, "i don't remember anything :(", msg.tts) + .map_err(CommandError::from) + .await; } info!("reporting meme history (len {})", n); @@ -119,18 +140,41 @@ pub fn history(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { .enumerate() .rev() .map(|(i, rec)| { - let dt = chrono::DateTime::from_utc(rec.time, chrono::Utc{}); + let dt = chrono::DateTime::from_utc(rec.time, chrono::Utc {}); let ago = TIME_FORMATTER.convert((chrono::Utc::now() - dt).to_std().unwrap()); - let rand = if rec.random { "R, " } else { "" }; + let rand = if rec.random { + "R, " + } else { + "" + }; Meme::find(&mut conn, rec.meme_id) .and_then(|meme| { Metadata::find(&mut conn, meme.metadata_id).map(|metadata| (metadata, meme)) }) .map(|(metadata, meme)| { - let author_name = CONFIG.discord.guild().member(&ctx, metadata.created_by as u64).map(|m| m.display_name().into_owned()).unwrap_or("???".to_owned()); - let invoker_name = CONFIG.discord.guild().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) + let author_name = CONFIG + .discord + .guild() + .member(&ctx, metadata.created_by as u64) + .map(|m| m.display_name().into_owned()) + .unwrap_or("???".to_owned()); + let invoker_name = CONFIG + .discord + .guild() + .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| { if let Some(variant) = e.downcast_ref::<DieselError>() { @@ -139,18 +183,23 @@ pub fn history(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { } } - let invoker_name = CONFIG.discord.guild().member(&ctx, rec.user_id as u64).map(|m| m.display_name().into_owned()).unwrap_or("???".to_owned()); + let invoker_name = CONFIG + .discord + .guild() + .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"); - ctx.send(msg.channel_id, &resp, false) + util::send(ctx, msg.channel_id, &resp, false).await } #[command] #[aliases("stat")] -pub fn stats(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { +pub async fn stats(ctx: &Context, msg: &Message, _: Args) -> CommandResult { use db; use serenity::model::{ id::UserId, @@ -162,8 +211,8 @@ pub fn stats(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { debug!("reporting stats"); - 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: User = UserId::new(stats.most_random_meme_user).to_user(&ctx)?; + let direct_user: User = UserId::new(stats.most_directly_named_meme_user).to_user(&ctx)?; let rand_user = rand_user.nick_in(&ctx, CONFIG.discord.guild()).unwrap_or(rand_user.name); let direct_user = direct_user.nick_in(&ctx, CONFIG.discord.guild()).unwrap_or(direct_user.name); @@ -200,29 +249,35 @@ and *{}* was the most-memed overall ({})"#, (stats.random_meme_invocations as f64) / (stats.total_meme_invocations as f64) * 100., stats.audio_meme_invocations, (stats.audio_meme_invocations as f64) / (stats.total_meme_invocations as f64) * 100., - stats.most_active_day.format(CLEAN_DATE_FORMAT), stats.most_active_day_count, - stats.most_audio_active_day.format(CLEAN_DATE_FORMAT), stats.most_audio_active_count, - rand_user, stats.most_random_meme_user_count, - direct_user, stats.most_directly_named_meme_count, - stats.most_popular_named_meme, stats.most_popular_named_meme_count, - stats.most_popular_random_meme, stats.most_popular_random_meme_count, - stats.most_popular_meme_overall, stats.most_popular_meme_overall_count, + stats.most_active_day.format(CLEAN_DATE_FORMAT), + stats.most_active_day_count, + stats.most_audio_active_day.format(CLEAN_DATE_FORMAT), + stats.most_audio_active_count, + rand_user, + stats.most_random_meme_user_count, + direct_user, + stats.most_directly_named_meme_count, + stats.most_popular_named_meme, + stats.most_popular_named_meme_count, + stats.most_popular_random_meme, + stats.most_popular_random_meme_count, + stats.most_popular_meme_overall, + stats.most_popular_meme_overall_count, ); - ctx.send(msg.channel_id, s, msg.tts) + + util::send(ctx, msg.channel_id, s, msg.tts).map_err(CommandError::from).await } #[command] -pub fn memers(ctx: &mut Context, msg: &Message, _args: Args) -> Result<()> { +pub async fn memers(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { use db; use itertools::Itertools; - use serenity::model::{ - id::UserId, - }; + use serenity::model::id::UserId; let s = db::memers()? .into_iter() .map(|info| { - let user = UserId(info.user_id).to_user(&ctx)?; + let user = UserId::new(info.user_id).to_user(&ctx)?; let username = user.nick_in(&ctx, CONFIG.discord.guild()).unwrap_or(user.name); let res = format!( @@ -241,11 +296,11 @@ pub fn memers(ctx: &mut Context, msg: &Message, _args: Args) -> Result<()> { .into_iter() .join("\n"); - ctx.send(msg.channel_id, &s, msg.tts) + util::send(ctx, msg.channel_id, &s, msg.tts).map_err(CommandError::from).await } #[command] -pub fn query(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { +pub async fn query(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { use std::borrow::Borrow; use itertools::Itertools; @@ -253,8 +308,8 @@ pub fn query(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { use serenity::model::id::UserId; use crate::{ - game::get_user_id, db, + game::get_user_id, CONFIG, }; @@ -263,24 +318,21 @@ pub fn query(ctx: &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(&ctx)? - .guild() - .ok_or(anyhow!("couldn't find guild"))?; + let guild = msg.channel_id.to_channel(&ctx)?.guild().ok_or(anyhow!("couldn't find guild"))?; - let guild = guild.read() - .guild(&ctx) - .ok_or(anyhow!("couldn't find guild"))?; + let guild = guild.read().guild(&ctx).ok_or(anyhow!("couldn't find guild"))?; - let guild = guild - .read(); + let guild = guild.read(); let creator: Option<u64> = { 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(|s| s.0)) + .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 } @@ -290,8 +342,11 @@ pub fn query(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { 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())) + 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 { @@ -302,16 +357,17 @@ pub fn query(ctx: &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(&ctx)?; + let user = UserId::new(metadata.created_by as u64).to_user(&ctx)?; let username = user.nick_in(&ctx, CONFIG.discord.guild()).unwrap_or(user.name); - Ok(format!("*{}* by **{}** ({}). text length: **{}**, image: **{}**, audio: **{}**", - meme.title, - username, - metadata.created.date().format(CLEAN_DATE_FORMAT), - meme.content.map_or(0, |s| s.len()), - meme.image_id.map_or("NO", |_s| "YES"), - meme.audio_id.map_or("NO", |_s| "YES"), + Ok(format!( + "*{}* by **{}** ({}). text length: **{}**, image: **{}**, audio: **{}**", + meme.title, + username, + metadata.created.date().format(CLEAN_DATE_FORMAT), + meme.content.map_or(0, |s| s.len()), + meme.image_id.map_or("NO", |_s| "YES"), + meme.audio_id.map_or("NO", |_s| "YES"), )) }) .collect::<Result<Vec<_>>>()? @@ -329,9 +385,11 @@ pub fn query(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { if result.len() == 0 { info!("no memes matched query"); - return ctx.send(msg.channel_id, "no match".to_owned(), msg.tts); - } + return util::send(ctx, msg.channel_id, "no match".to_owned(), msg.tts) + .map_err(CommandError::from) + .await; + } - ctx.send(msg.channel_id, &result, msg.tts) + util::send(ctx, msg.channel_id, &result, msg.tts).map_err(CommandError::from).await } diff --git a/src/commands/meme/invoke.rs b/src/commands/meme/invoke.rs index de0272f..03c6251 100644 --- a/src/commands/meme/invoke.rs +++ b/src/commands/meme/invoke.rs @@ -1,63 +1,65 @@ use diesel::{ - NotFound, result::Error as DieselError, + NotFound, }; use itertools::Itertools; use log::info; use serenity::{ framework::standard::{ - Args, macros::command, + Args, + CommandError, + CommandResult, }, + futures::TryFutureExt, model::channel::Message, prelude::*, }; use crate::{ commands::meme::send_meme, - Result, db::{ self, connection, find_meme, InvocationRecord, }, - util::CtxExt, + util, }; #[command] #[aliases("mem")] -pub fn meme(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> { +pub async fn meme(ctx: &Context, msg: &Message, args: Args) -> CommandResult { _meme(ctx, msg, args, AudioPlayback::Optional) } #[command] -pub fn omen(ctx: &mut Context, msg: &Message, _args: Args) -> Result<()> { +pub async fn omen(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { let args = Args::new("", &[]); _meme(ctx, msg, args, AudioPlayback::Optional) } #[command] -pub fn silentomen(ctx: &mut Context, msg: &Message, _args: Args) -> Result<()> { +pub async fn silentomen(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { let args = Args::new("", &[]); _meme(ctx, msg, args, AudioPlayback::Prohibited) } #[command] -pub fn audioomen(ctx: &mut Context, msg: &Message, _args: Args) -> Result<()> { +pub async fn audioomen(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { let args = Args::new("", &[]); _meme(ctx, msg, args, AudioPlayback::Required) } #[command] #[aliases("audiomeme", "audiomem")] -pub fn audio_meme(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> { +pub async fn audio_meme(ctx: &Context, msg: &Message, args: Args) -> CommandResult { _meme(ctx, msg, args, AudioPlayback::Required) } #[command] #[aliases("silentmeme", "silentmem")] -pub fn silent_meme(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> { +pub async fn silent_meme(ctx: &Context, msg: &Message, args: Args) -> CommandResult { _meme(ctx, msg, args, AudioPlayback::Prohibited) } @@ -68,9 +70,14 @@ enum AudioPlayback { Prohibited, } -fn _meme(ctx: &mut Context, msg: &Message, args: Args, audio_playback: AudioPlayback) -> Result<()> { +async fn _meme( + ctx: &Context, + msg: &Message, + args: Args, + audio_playback: AudioPlayback, +) -> CommandResult { if args.len() == 0 || audio_playback != AudioPlayback::Optional { - return rand_meme(ctx, msg, audio_playback); + return rand_meme(ctx, msg, audio_playback).await; } let search = args.raw().join(" "); @@ -78,28 +85,34 @@ fn _meme(ctx: &mut Context, msg: &Message, args: Args, audio_playback: AudioPlay let mut conn = connection()?; let mem = match find_meme(&mut conn, search) { Ok(x) => { - InvocationRecord::create(&mut conn, msg.author.id.0, msg.id.0, x.id, false)?; + InvocationRecord::create(&mut conn, msg.author.id.get(), msg.id.get(), x.id, false)?; x }, Err(e) => { return if let Some(NotFound) = e.downcast_ref::<DieselError>() { info!("requested meme not found in database"); - ctx.send(msg.channel_id, "c'mon baby, guesstimate", msg.tts) + util::send(ctx, msg.channel_id, "c'mon baby, guesstimate", msg.tts) + .await + .map_err(CommandError::from) } else { - ctx.send(msg.channel_id, "what in ryan's name", msg.tts)?; - Err(e) + util::send(ctx, msg.channel_id, "what in ryan's name", msg.tts).await?; + Err(e.into()) }; }, }; - send_meme(ctx, &mem, &mut conn, msg) + send_meme(ctx, &mem, &mut conn, msg).await } -fn rand_meme(ctx: &Context, message: &Message, audio_playback: AudioPlayback) -> Result<()> { +async fn rand_meme( + ctx: &Context, + message: &Message, + audio_playback: AudioPlayback, +) -> CommandResult { let mut conn = connection()?; - let should_audio = ctx.users_listening()?; + let should_audio = util::users_listening(ctx).await?; let mem = match audio_playback { AudioPlayback::Required => db::rand_audio_meme(&mut conn), @@ -109,28 +122,36 @@ fn rand_meme(ctx: &Context, message: &Message, audio_playback: AudioPlayback) -> match mem { Ok(mem) => { - InvocationRecord::create(&mut conn, message.author.id.0, message.id.0, mem.id, true)?; - send_meme(ctx, &mem, &mut conn, message)?; + InvocationRecord::create( + &mut conn, + message.author.id.get(), + message.id.get(), + mem.id, + true, + )?; + send_meme(ctx, &mem, &mut conn, message).await?; Ok(()) }, Err(e) => { match e.downcast_ref::<DieselError>() { Some(NotFound) => { info!("random meme not found"); - return ctx.send(message.channel_id, "i don't know any :(", message.tts) + return util::send(ctx, message.channel_id, "i don't know any :(", message.tts) + .map_err(CommandError::from) + .await; }, _ => {}, } - ctx.send(message.channel_id, "HELP", message.tts)?; - return Err(e); + util::send(ctx, message.channel_id, "HELP", message.tts).await?; + return Err(e.into()); }, } } #[command] #[aliases("rarememe", "raremem")] -pub fn rare_meme(ctx: &mut Context, msg: &Message, _args: Args) -> Result<()> { +pub async fn rare_meme(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { let should_audio = ctx.users_listening()?; let mut conn = connection()?; @@ -138,19 +159,23 @@ pub fn rare_meme(ctx: &mut Context, msg: &Message, _args: Args) -> Result<()> { let meme = db::rare_meme(&mut conn, should_audio); match meme { Ok(meme) => { - InvocationRecord::create(&mut conn, msg.author.id.0, msg.id.0, meme.id, true)?; + InvocationRecord::create(&mut conn, msg.author.id.get(), msg.id.get(), meme.id, true)?; send_meme(ctx, &meme, &mut conn, msg) }, Err(e) => { match e.downcast_ref::<DieselError>() { Some(NotFound) => { info!("rare meme not found"); - return ctx.send(msg.channel_id, "i don't know any :(", msg.tts) + return util::send(ctx, msg.channel_id, "i don't know any :(", msg.tts) + .map_err(CommandError::from) + .await; }, _ => {}, } - ctx.send(msg.channel_id, "THE MEME MARKET IS IN FREEFALL", msg.tts)?; + util::send(ctx, msg.channel_id, "THE MEME MARKET IS IN FREEFALL", msg.tts) + .map_err(CommandError::from) + .await?; Err(e) }, diff --git a/src/commands/meme/mod.rs b/src/commands/meme/mod.rs index 6ce30b6..31d9b78 100644 --- a/src/commands/meme/mod.rs +++ b/src/commands/meme/mod.rs @@ -1,9 +1,16 @@ use diesel::PgConnection; use log::debug; -use rand::{Rng, thread_rng}; +use rand::random; use serenity::{ - framework::standard::macros::group, - http::AttachmentType, + all::ReactionType, + builder::{ + CreateAttachment, + CreateMessage, + }, + framework::standard::{ + macros::group, + CommandResult, + }, model::channel::Message, prelude::*, }; @@ -14,7 +21,6 @@ use crate::{ PlayQueue, }, db::Meme, - Result, }; pub use self::{ @@ -24,68 +30,67 @@ pub use self::{ invoke::*, }; -mod history; mod create; -mod invoke; mod delete; +mod history; +mod invoke; -group!({ - name: "memes", - options: { - only_in: "guild", - }, - commands: [ - meme, - audio_meme, - silent_Meme, - omen, - audioomen, - silentomen, - addmeme, - addaudiomeme, - delmeme, - wat, - stats, - history, - rare_meme, - memers, - query, - ], -}); +#[group] +#[commands( + meme, + audio_meme, + silent_meme, + omen, + audioomen, + silentomen, + addmeme, + addaudiomeme, + delmeme, + wat, + stats, + history, + rare_meme, + memers, + query +)] +struct Memes; -fn send_meme(ctx: &Context, t: &Meme, conn: &mut PgConnection, msg: &Message) -> Result<()> { - let should_tts = t.content.as_ref().map(|t| t.len() > 0).unwrap_or(false) && - thread_rng().gen::<u32>() % 25 == 0; +async fn send_meme( + ctx: &Context, + t: &Meme, + conn: &mut PgConnection, + msg: &Message, +) -> CommandResult { + let should_tts = + t.content.as_ref().map(|t| t.len() > 0).unwrap_or(false) && random::<u32>() % 25 == 0; debug!("sending meme (tts: {}): {:?}", should_tts, t); let image = t.image(conn); let audio = t.audio(conn); + let cmsg = { + let ret = CreateMessage::default().tts(should_tts); + + match t.content { + Some(ref text) if text.len() > 0 => ret.content(text), + _ => ret, + } + }; + match image { Some(image) => { let image = image?; - msg.channel_id.send_files(ctx, vec!(AttachmentType::Bytes((&image.data, &image.filename))), |m| { - let ret = m.tts(should_tts); + let att = CreateAttachment::bytes(image.data.as_slice(), &image.filename); - match t.content { - Some(ref text) if text.len() > 0 => ret.content(text), - _ => ret, - } - })?; + msg.channel_id.send_files(ctx, vec![att], cmsg).await?; }, None => match t.content { - Some(_) => { msg.channel_id.send_message(ctx, |m| { - let ret = m.tts(should_tts); - - match t.content { - Some(ref text) if text.len() > 0 => ret.content(text), - _ => ret, - } - })?; }, + Some(_) => { + msg.channel_id.send_message(ctx, cmsg).await?; + }, None => {}, - }, }; @@ -96,19 +101,19 @@ fn send_meme(ctx: &Context, t: &Meme, conn: &mut PgConnection, msg: &Message) -> let audio = audio?; { - let queue_lock = ctx.data.write().get::<PlayQueue>().cloned().unwrap(); + let queue_lock = ctx.data.write().await.get::<PlayQueue>().cloned().unwrap(); let mut play_queue = queue_lock.write().unwrap(); - play_queue.meme_queue.push_back(PlayArgs{ - initiator: msg.author.name.clone(), - data: ::either::Right(audio.data.clone()), + play_queue.meme_queue.push_back(PlayArgs { + initiator: msg.author.name.clone(), + data: ::either::Right(audio.data.clone()), sender_channel: msg.channel_id, - start: None, - end: None, + start: None, + end: None, }); } - msg.react(ctx, "📣")?; + msg.react(ctx, ReactionType::Unicode("📣".to_owned())).await?; } Ok(()) diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 705d697..4893e73 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,71 +1,55 @@ use log::info; -use serenity::{ - framework::{ - StandardFramework, - standard::{ - macros::group, - }, - }, -}; - -use crate::{ - util::CtxExt, +use serenity::framework::{ + standard::macros::group, + StandardFramework, }; +#[cfg(feature = "diesel")] +pub use self::meme::*; pub use self::{ playback::*, - sound_levels::*, - roll::{roll, ROLL_COMMAND}, - today::{today, TODAY_COMMAND}, + roll::ROLL_COMMAND, + today::{ + today, + TODAY_COMMAND, + }, }; -#[cfg(feature = "diesel")] -pub use self::meme::*; pub(crate) mod playback; -pub(crate) mod sound_levels; pub(crate) mod roll; +pub(crate) mod sound_levels; pub(crate) mod today; mod help; -group!({ - name: "general", - options: { - only_in: "guild", - }, - commands: [ - roll, - today, - ], -}); +#[group] +#[prefix = ""] +#[only_in(guild)] +#[commands(roll, today)] +struct General; pub fn register_commands(f: StandardFramework) -> StandardFramework { - let result = f - .group(&self::playback::PLAYBACK_GROUP) - .group(&GENERAL_GROUP); + let result = f.group(&PLAYBACK_GROUP).group(&GENERAL_GROUP); #[cfg(feature = "diesel")] - let result = result.group(&self::meme::MEMES_GROUP); + let result = result.group(&MEMES_GROUP); #[cfg(feature = "games")] let result = result.group(&crate::game::GAME_GROUP); - result - .help(&help::HELP) - .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; - } - }; + result.help(&help::HELP).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; + }, + }; - let _ = self::playback::_play(ctx, msg, &url); - }) + let _ = _play(ctx, msg, &url); + }) } - #[cfg(feature = "diesel")] mod meme; diff --git a/src/commands/playback.rs b/src/commands/playback.rs index f80ec17..21393a2 100644 --- a/src/commands/playback.rs +++ b/src/commands/playback.rs @@ -1,4 +1,7 @@ -use either::{Left, Right}; +use either::{ + Left, + Right, +}; use log::{ debug, error, @@ -7,12 +10,22 @@ use log::{ }; use serenity::{ framework::standard::{ + macros::{ + command, + group, + }, Args, - macros::{command, group}, + CommandError, + CommandResult, }, + futures::TryFutureExt, model::channel::Message, prelude::*, }; +use tap::{ + Conv, + Pipe, +}; use crate::{ audio::{ @@ -21,44 +34,34 @@ use crate::{ PlayQueue, VoiceManager, }, - Result, - CONFIG, - util::CtxExt, commands::sound_levels::*, + util, + CONFIG, }; -group!({ - name: "playback", - options: { - only_in: "guild", - }, - commands: [ - skip, - pause, - resume, - list, - die, - mute, - unmute, - play, - volume, - ], -}); +#[group] +#[commands(skip, pause, resume, list, die, mute, unmute, play, volume)] +#[only_in(guild)] +struct Playback; -pub fn _play(ctx: &Context, msg: &Message, url: &str) -> Result<()> { - use url::{Url, Host}; +pub async fn _play(ctx: &Context, msg: &Message, url: &str) -> CommandResult { + use url::{ + Host, + Url, + }; debug!("playing '{}'", url); if !url.starts_with("http") { warn!("got bad url argument to play: {}", url); - ctx.send(msg.channel_id, "bAD LiNk", msg.tts)?; + util::send(ctx, msg.channel_id, "bAD LiNk", msg.tts).await?; return Ok(()); } let url = match Url::parse(url) { Err(e) => { error!("bad url: {}", e); - return ctx.send(msg.channel_id, "INVALID URL", msg.tts); + util::send(ctx, msg.channel_id, "INVALID URL", msg.tts).await?; + return Ok(()); }, Ok(u) => u, }; @@ -71,10 +74,10 @@ pub fn _play(ctx: &Context, msg: &Message, url: &str) -> Result<()> { if host.map(|h| h.to_lowercase().contains("imgur")).unwrap_or(false) { info!("detected imgur link"); - if msg.author.id.0 == 106160362109272064 { - ctx.send(msg.channel_id, "fuck you conway", true)?; + if msg.author.id.get() == 106160362109272064 { + util::send(ctx, msg.channel_id, "fuck you conway", true).await?; } else { - ctx.send(msg.channel_id, "IMGUR IS BAD, YOU TRASH CAN MAN", msg.tts)?; + util::send(ctx, msg.channel_id, "IMGUR IS BAD, YOU TRASH CAN MAN", msg.tts).await?; } return Ok(()); @@ -82,12 +85,12 @@ pub fn _play(ctx: &Context, msg: &Message, url: &str) -> Result<()> { let (start, end) = parse_times(&msg.content); - let queue_lock = ctx.data.write().get::<PlayQueue>().cloned().unwrap(); + let queue_lock = ctx.data.write().await.get::<PlayQueue>().cloned().unwrap(); let mut play_queue = queue_lock.write().unwrap(); - play_queue.general_queue.push_back(PlayArgs{ + play_queue.general_queue.push_back(PlayArgs { initiator: msg.author.name.clone(), - data: Left(url.into_string()), + data: Left(url.conv::<String>()), sender_channel: msg.channel_id, start, end, @@ -97,16 +100,18 @@ pub fn _play(ctx: &Context, msg: &Message, url: &str) -> Result<()> { } #[command] -pub fn play(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { +pub async fn play(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { if args.len() == 0 { - return _resume(ctx, msg); + 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 ctx.send(msg.channel_id, "BAD LINK", msg.tts); + return util::send(ctx, msg.channel_id, "BAD LINK", msg.tts) + .await + .map_err(CommandError::from); }, }; @@ -114,16 +119,16 @@ pub fn play(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { } #[command] -pub fn pause(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { - let queue_lock = ctx.data.write().get::<PlayQueue>().cloned().unwrap(); +pub async fn pause(ctx: &Context, msg: &Message, _: Args) -> CommandResult { + let queue_lock = ctx.data.write().await.get::<PlayQueue>().cloned().unwrap(); - let done = || ctx.send(msg.channel_id, "r u srs", msg.tts); + let done = || util::send(ctx, msg.channel_id, "r u srs", msg.tts).map_err(CommandError::from); let playing = { let play_queue = queue_lock.read().unwrap(); let current_item = match play_queue.playing { Some(ref x) => x, - None => return done(), + None => return done().await, }; let audio = current_item.audio.lock(); @@ -131,7 +136,7 @@ pub fn pause(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { }; if !playing { - return done(); + return done().await; } { @@ -147,21 +152,21 @@ pub fn pause(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { #[command] #[aliases("continue")] -pub fn resume(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { - _resume(ctx, msg) +pub async fn resume(ctx: &Context, msg: &Message, _: Args) -> CommandResult { + _resume(ctx, msg).await } -fn _resume(ctx: &mut Context, msg: &Message) -> Result<()> { - let queue_lock = ctx.data.write().get::<PlayQueue>().cloned().unwrap(); +async fn _resume(ctx: &Context, msg: &Message) -> CommandResult { + let queue_lock = ctx.data.write().await.get::<PlayQueue>().cloned().unwrap(); - let done = || ctx.send(msg.channel_id, "r u srs", msg.tts); + let done = || util::send(ctx, msg.channel_id, "r u srs", msg.tts).map_err(CommandError::from); let playing = { let play_queue = queue_lock.read().unwrap(); let current_item = match play_queue.playing { Some(ref x) => x, None => { - done()?; + done().await?; return Ok(()); }, }; @@ -171,7 +176,7 @@ fn _resume(ctx: &mut Context, msg: &Message) -> Result<()> { }; if playing { - done()?; + done().await?; debug!("attempted to resume playback while sound was already playing"); return Ok(()); } @@ -188,8 +193,8 @@ fn _resume(ctx: &mut Context, msg: &Message) -> Result<()> { #[command] #[aliases("next")] -pub fn skip(ctx: &mut Context, _msg: &Message, _args: Args) -> Result<()> { - let data = ctx.data.write(); +pub async fn skip(ctx: &Context, _msg: &Message, _args: Args) -> CommandResult { + let data = ctx.data.write().await; let mgr_lock = data.get::<VoiceManager>().cloned().unwrap(); let mut manager = mgr_lock.lock(); @@ -210,8 +215,8 @@ pub fn skip(ctx: &mut Context, _msg: &Message, _args: Args) -> Result<()> { #[command] #[aliases("sudoku", "fuckoff", "stop")] -pub fn die(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { - let data = ctx.data.write(); +pub async fn die(ctx: &Context, msg: &Message, _: Args) -> CommandResult { + let data = ctx.data.write().await; let mgr_lock = data.get::<VoiceManager>().cloned().unwrap(); let mut manager = mgr_lock.lock(); @@ -231,7 +236,7 @@ pub fn die(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { handler.stop(); handler.leave(); } else { - ctx.send(msg.channel_id, "YOU die", msg.tts)?; + util::send(ctx, msg.channel_id, "YOU die", msg.tts).await?; debug!("got die with no handler attached"); } @@ -240,43 +245,56 @@ pub fn die(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { #[command] #[aliases("queue")] -pub fn list(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { - let queue_lock = ctx.data.write().get::<PlayQueue>().cloned().unwrap(); +pub async fn list(ctx: &Context, msg: &Message, _: Args) -> CommandResult { + let queue_lock = ctx.data.write().await.get::<PlayQueue>().cloned().unwrap(); let play_queue = queue_lock.read().unwrap(); - let channel_tmp = msg.channel(&ctx).unwrap().guild().unwrap(); - let channel = channel_tmp.read(); + let channel = msg.channel(&ctx).await.unwrap().guild().unwrap(); info!("listing queue"); match play_queue.playing { Some(ref info) => { let audio = info.audio.lock(); - let status = if audio.playing { "playing" } else { "paused:" }; + let status = if audio.playing { + "playing" + } else { + "paused:" + }; let playing_info = match info.init_args.data { Left(ref url) => format!(" `{}`", url), Right(_) => "memeing".to_owned(), }; - ctx.send(msg.channel_id, &format!("Currently {} {} ({})", status, playing_info, info.init_args.initiator), msg.tts)?; + util::send( + ctx, + msg.channel_id, + &format!("Currently {} {} ({})", status, playing_info, info.init_args.initiator), + msg.tts, + ) + .await?; }, None => { debug!("`list` called with no items in queue"); - ctx.send(msg.channel_id, "Nothing is playing you meme", msg.tts)?; + util::send(ctx, msg.channel_id, "Nothing is playing you meme", msg.tts).await?; return Ok(()); }, } - play_queue.meme_queue.iter() + play_queue + .meme_queue + .iter() .chain(play_queue.general_queue.iter()) - .for_each(|info| { + .pipe(serenity::futures::stream::iter) + .for_each(|info| async move { let playing_info = match info.data { Left(ref url) => format!("`{}`", url), Right(_) => "meme".to_owned(), }; - let _ = channel.say(&ctx, &format!("{} ({})", playing_info, info.initiator)); - }); + let _ = channel.say(&ctx, &format!("{} ({})", playing_info, info.initiator)).await; + }) + .await; Ok(()) } diff --git a/src/commands/roll.rs b/src/commands/roll.rs index 5f5ff6d..6aefe34 100644 --- a/src/commands/roll.rs +++ b/src/commands/roll.rs @@ -7,23 +7,26 @@ use log::{ use rand::prelude::*; use serenity::{ framework::standard::{ - Args, macros::command, + Args, }, model::channel::Message, prelude::*, }; -use statrs; use thiserror::Error; use lazy_static::lazy_static; - -use crate::{ - Result, - util::CtxExt, +use serenity::{ + framework::standard::{ + CommandError, + CommandResult, + }, + futures::TryFutureExt, }; -#[derive(Parser)] +use crate::util; + +#[derive(pest_derive::Parser)] #[grammar = "commands/calc.pest"] struct Calc; @@ -42,9 +45,12 @@ pub(crate) enum CalcError { impl Calc { fn eval<S: AsRef<str>>(s: S) -> StdResult<f64, CalcError> { use pest::{ - Parser, + iterators::{ + Pair, + Pairs, + }, prec_climber::PrecClimber, - iterators::{Pair, Pairs}, + Parser, }; use self::Rule::*; @@ -52,12 +58,14 @@ impl Calc { lazy_static! { static ref CLIMBER: PrecClimber<self::Rule> = { use pest::prec_climber::{ - Operator, Assoc::*, + Operator, }; PrecClimber::new(vec![ - Operator::new(add, Left) | Operator::new(sub, Left) | Operator::new(modulo, Left), + 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), @@ -77,7 +85,8 @@ impl Calc { _ => unreachable!(), }; - u64::from_str_radix(&pair.as_str()[2..], base).map_err(|_| CalcError::NumberFormat)? as f64 + u64::from_str_radix(&pair.as_str()[2..], base) + .map_err(|_| CalcError::NumberFormat)? as f64 }, float => pair.as_str().parse::<f64>().map_err(|_| CalcError::NumberFormat)?, expr | num => eval_expr(pair.into_inner())?, @@ -151,32 +160,29 @@ impl Calc { } fn eval_expr(p: Pairs<self::Rule>) -> StdResult<f64, CalcError> { - CLIMBER.climb( - p, - eval_single_pair, - |lhs, op, rhs| { - let lhs = lhs?; - let rhs = rhs?; + CLIMBER.climb(p, eval_single_pair, |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) + }) } eval_expr(result) @@ -208,15 +214,19 @@ mod test { #[command] #[aliases("calc", "calculate")] -pub fn roll(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> { +pub async fn roll(ctx: &Context, msg: &Message, args: Args) -> CommandResult { match Calc::eval(args.rest()) { Ok(result) => { debug!("got calc result '{}'", result); - ctx.send(msg.channel_id, &format!("{}", result), msg.tts) + util::send(ctx, msg.channel_id, &format!("{}", result), msg.tts) + .map_err(CommandError::from) + .await }, Err(e) => { error!("error encountered reading calc '{}': {}", args.rest(), e); - ctx.send(msg.channel_id, "I COULDN'T READ THAT YOU FUCK", msg.tts) + util::send(ctx, msg.channel_id, "I COULDN'T READ THAT YOU FUCK", msg.tts) + .map_err(CommandError::from) + .await }, } } diff --git a/src/commands/sound_levels.rs b/src/commands/sound_levels.rs index d913d06..db0b6a6 100644 --- a/src/commands/sound_levels.rs +++ b/src/commands/sound_levels.rs @@ -6,94 +6,107 @@ use log::{ }; use serenity::{ framework::standard::{ - Args, macros::command, + Args, + CommandError, + CommandResult, }, + futures::TryFutureExt, model::channel::Message, prelude::*, }; use crate::{ - Result, + audio::{ + PlayQueue, + VoiceManager, + }, + util, CONFIG, - audio::{PlayQueue, VoiceManager}, - util::CtxExt, }; pub const DEFAULT_VOLUME: f32 = 0.20; const MAX_VOLUME: f32 = 5.0; #[command] -pub fn mute(ctx: &mut Context, _: &Message, _: Args) -> Result<()> { - let mgr_lock = ctx.data.write().get::<VoiceManager>().cloned().unwrap(); +pub async fn mute(ctx: &Context, _: &Message, _: Args) -> CommandResult { + let mgr_lock = ctx.data.write().await.get::<VoiceManager>().cloned().unwrap(); let mut manager = mgr_lock.lock(); - manager.get_mut(CONFIG.discord.guild()) - .map(|handler| { - if handler.self_mute { - trace!("Already muted.") - } else { - handler.mute(true); - trace!("Muted"); - } - }); + manager.get_mut(CONFIG.discord.guild()).map(|handler| { + if handler.self_mute { + trace!("Already muted.") + } else { + handler.mute(true); + trace!("Muted"); + } + }); Ok(()) } #[command] -pub fn unmute(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { - let mgr_lock = ctx.data.write().get::<VoiceManager>().cloned().unwrap(); +pub async fn unmute(ctx: &Context, msg: &Message, _: Args) -> CommandResult { + let mgr_lock = ctx.data.write().await.get::<VoiceManager>().cloned().unwrap(); let mut manager = mgr_lock.lock(); - manager.get_mut(CONFIG.discord.guild()) - .map(|handler| { - if !handler.self_mute { - trace!("Already unmuted.") - } else { - handler.mute(false); - trace!("Unmuted"); - let _ = ctx.send(msg.channel_id, "REEEEEEEEEEEEEE", msg.tts); - } - }); + if let Some(handler) = manager.get_mut(CONFIG.discord.guild()) { + if !handler.self_mute { + trace!("Already unmuted.") + } else { + handler.mute(false); + trace!("Unmuted"); + let _ = util::send(ctx, msg.channel_id, "REEEEEEEEEEEEEE", msg.tts) + .map_err(CommandError::from) + .await; + } + } Ok(()) } #[command] -pub fn volume(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { +pub async fn volume(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { if args.len() == 0 { let vol = { - let queue_lock = ctx.data.write().get::<PlayQueue>().cloned().unwrap(); + 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 ctx.send(msg.channel_id, &format!("volume: {}%", vol), msg.tts); + 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 ctx.send(msg.channel_id, "you're a fuck", msg.tts); + 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 ctx.send(msg.channel_id, "???????", msg.tts) + 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 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() }; + } else { + "".to_owned() + }; vol = vol.clamp(0.0, MAX_VOLUME); - let queue_lock = ctx.data.write().get::<PlayQueue>().cloned().unwrap(); + let queue_lock = ctx.data.write().await.get::<PlayQueue>().cloned().unwrap(); { let mut play_queue = queue_lock.write().unwrap(); @@ -101,7 +114,7 @@ pub fn volume(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { info!("volume updated to {}", vol); } - ctx.send(msg.channel_id, format!("volume adjusted{}", adjusted_text), msg.tts)?; + util::send(ctx, msg.channel_id, format!("volume adjusted{}", adjusted_text), msg.tts).await?; { let play_queue = queue_lock.read().unwrap(); diff --git a/src/commands/today/mod.rs b/src/commands/today/mod.rs index 0a5eefe..0a0ba7b 100644 --- a/src/commands/today/mod.rs +++ b/src/commands/today/mod.rs @@ -1,106 +1,98 @@ -use serenity::{ - prelude::*, - model::{ - channel::Message, - }, - framework::standard::{ - Args, - macros::command, - }, -}; -use chrono::{Duration}; +use chrono::Duration; use either::Left; use lazy_static::lazy_static; +use log::debug; use rand::{ - thread_rng, seq::SliceRandom, + thread_rng, +}; +use serenity::{ + framework::standard::{ + macros::command, + Args, + CommandResult, + }, + model::channel::Message, + prelude::*, }; -use log::debug; use crate::{ - Result, - CtxExt, audio::{ PlayArgs, PlayQueue, }, + util, }; mod prelude; -mod sept_21; mod nov_5; +mod sept_21; -mod halloween; -mod ussr; mod france; -mod shrek; +mod halloween; mod putin; +mod shrek; +mod ussr; -mod wednesday; mod thursday; mod tomorrow; +mod wednesday; mod pianoman; -pub type TodayIter = Box<dyn Iterator<Item=TodayArgs>>; +pub type TodayIter = Box<dyn Iterator<Item = TodayArgs>>; #[derive(Clone, Debug, Hash, Default)] pub struct TodayArgs { - pub url: &'static str, + pub url: &'static str, pub start: Option<Duration>, - pub end: Option<Duration>, + pub end: Option<Duration>, } impl TodayArgs { #[inline] pub fn as_play_args(&self, msg: &Message) -> PlayArgs { PlayArgs { - initiator: "you have done this to yourself :^)".to_string(), - data: Left(self.url.to_owned()), + initiator: "you have done this to yourself :^)".to_string(), + data: Left(self.url.to_owned()), sender_channel: msg.channel_id, - start: self.start, - end: self.end, + start: self.start, + end: self.end, } } } - lazy_static! { - static ref ALL: Vec<fn(chrono::NaiveDateTime) -> TodayIter> = vec! [ + static ref ALL: Vec<fn(chrono::NaiveDateTime) -> TodayIter> = vec![ sept_21::sept_21, nov_5::nov_5, - halloween::halloween, ussr::ussr, france::france, shrek::shrek, putin::putin, - wednesday::wednesday, thursday::thursday, tomorrow::tomorrow, - pianoman::pianoman, ]; } - #[command] -pub fn today(ctx: &mut Context, msg: &Message, _args: Args) -> Result<()> { +pub async fn today(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { 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 time = chrono::NaiveTime::from_hms_opt(12, 0, 0).unwrap(); - date.and_time(time) - }) - }); + #[cfg(debug_assertions)] + { + let dt = _args.parse::<chrono::NaiveDateTime>().or_else(|_| { + _args.parse::<chrono::NaiveDate>().map(|date| { + let time = chrono::NaiveTime::from_hms_opt(12, 0, 0).unwrap(); + date.and_time(time) + }) + }); match dt { Ok(dt) => { @@ -109,34 +101,31 @@ pub fn today(ctx: &mut Context, msg: &Message, _args: Args) -> Result<()> { }, Err(e) => { log::debug!("parsing datetime: {:?}", e); - } + }, }; } result }; - let options: Vec<TodayArgs> = ALL.iter() - .flat_map(|f| f(today)) - .collect(); + let options: Vec<TodayArgs> = ALL.iter().flat_map(|f| f(today)).collect(); debug!("{} options for {}", options.len(), today); - let play_args = options.choose(&mut thread_rng()) - .map(|x| x.as_play_args(msg)); + let play_args = options.choose(&mut thread_rng()).map(|x| x.as_play_args(msg)); if let Some(play_args) = play_args { play_args.data.as_ref().left().iter().for_each(|url| { debug!("today selected: {}", url); }); - let queue_lock = ctx.data.write().get::<PlayQueue>().cloned().unwrap(); + let queue_lock = ctx.data.write().await.get::<PlayQueue>().cloned().unwrap(); let mut play_queue = queue_lock.write().unwrap(); play_queue.general_queue.push_front(play_args); } else { - ctx.send(msg.channel_id, "no", false)?; - ctx.send(msg.channel_id, ":angry:", false)?; + util::send(ctx, msg.channel_id, "no", false).await?; + util::send(ctx, msg.channel_id, ":angry:", false).await?; } Ok(()) diff --git a/src/config.rs b/src/config.rs index 846b25b..e03dbbe 100644 --- a/src/config.rs +++ b/src/config.rs @@ -16,7 +16,7 @@ lazy_static! { pub static ref CONFIG: Config = { dotenv().ok(); - Config::init().unwrap() + Config::init_from_env().unwrap() }; pub static ref FFMPEG_COMMAND: String = { diff --git a/src/db/mod.rs b/src/db/mod.rs index 476d9d9..1ac44e9 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -235,7 +235,7 @@ pub fn rare_meme(conn: &mut PgConnection, audio: bool) -> Result<Meme> { } let mut rng = thread_rng(); - let target_prob = rng.gen_range(0, elems.last().unwrap().1); + let target_prob = rng.gen_range(0..elems.last().unwrap().1); let meme_id = elems.into_iter() .find(|(_, x)| target_prob < *x) diff --git a/src/db/models.rs b/src/db/models.rs index f2127b5..cdcdd99 100644 --- a/src/db/models.rs +++ b/src/db/models.rs @@ -5,6 +5,7 @@ use diesel::{ prelude::*, Queryable, }; +use sha1::Digest; use crate::{ db::schema::*, diff --git a/src/db/schema.rs b/src/db/schema.rs index 01ec1d0..e89b24a 100644 --- a/src/db/schema.rs +++ b/src/db/schema.rs @@ -1,4 +1,4 @@ -table! { +diesel::table! { audio (id) { id -> Int4, data -> Bytea, @@ -7,7 +7,7 @@ table! { } } -table! { +diesel::table! { audit_records (id) { id -> Int4, updated -> Timestamp, @@ -16,7 +16,7 @@ table! { } } -table! { +diesel::table! { images (id) { id -> Int4, data -> Bytea, @@ -26,7 +26,7 @@ table! { } } -table! { +diesel::table! { invocation_records (id) { id -> Int4, user_id -> Int8, @@ -37,7 +37,7 @@ table! { } } -table! { +diesel::table! { memes (id) { id -> Int4, title -> Varchar, @@ -48,7 +48,7 @@ table! { } } -table! { +diesel::table! { metadata (id) { id -> Int4, created -> Timestamp, @@ -56,7 +56,7 @@ table! { } } -table! { +diesel::table! { tombstones (id) { id -> Int4, meme_id -> Int4, @@ -66,16 +66,16 @@ table! { } } -joinable!(audio -> metadata (metadata_id)); -joinable!(audit_records -> metadata (metadata_id)); -joinable!(images -> metadata (metadata_id)); -joinable!(memes -> audio (audio_id)); -joinable!(memes -> images (image_id)); -joinable!(memes -> metadata (metadata_id)); -joinable!(tombstones -> metadata (metadata_id)); -joinable!(invocation_records -> memes (meme_id)); +diesel::joinable!(audio -> metadata (metadata_id)); +diesel::joinable!(audit_records -> metadata (metadata_id)); +diesel::joinable!(images -> metadata (metadata_id)); +diesel::joinable!(memes -> audio (audio_id)); +diesel::joinable!(memes -> images (image_id)); +diesel::joinable!(memes -> metadata (metadata_id)); +diesel::joinable!(tombstones -> metadata (metadata_id)); +diesel::joinable!(invocation_records -> memes (meme_id)); -allow_tables_to_appear_in_same_query!( +diesel::allow_tables_to_appear_in_same_query!( audio, audit_records, images, diff --git a/src/game.rs b/src/game.rs index 0f6637e..362e304 100644 --- a/src/game.rs +++ b/src/game.rs @@ -2,12 +2,12 @@ use std::{ convert::Infallible, fs, iter, + path::PathBuf, result::Result as StdResult, str::{ self, FromStr, }, - path::PathBuf, }; use fnv::{ @@ -44,27 +44,20 @@ use anyhow::{ Error, }; use lazy_static::lazy_static; +use serenity::framework::standard::CommandResult; use crate::{ - util::CtxExt, + util, Result, CONFIG, }; -pub use self::GAME_GROUP as GROUP; +pub use self::Game as GROUP; -group!({ - name: "game", - options: { - only_in: "guild", - }, - commands: [ - game, - installedgame, - ownedgame, - updategaem, - ], -}); +#[group] +#[prefix = "game"] +#[commands(game, installedgame, ownedgame, updategaem)] +pub struct Game; lazy_static! { static ref SPREADSHEET_URL: Url = Url::parse(&format!( @@ -101,7 +94,8 @@ lazy_static! { 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() + let result = v + .into_iter() .map(|ui| { let UserInfo { name, @@ -112,7 +106,11 @@ lazy_static! { }) .collect::<FnvHashMap<_, _>>(); - log::info!("loaded user info for {} users ({:#?})", result.len(), result.keys().collect::<Vec<_>>()); + log::info!( + "loaded user info for {} users ({:#?})", + result.len(), + result.keys().collect::<Vec<_>>() + ); result }; @@ -120,14 +118,16 @@ lazy_static! { USER_INFO_MAP .clone() .into_iter() - .map(|(name, profile)| (UserId(profile.discord_user_id), name)) + .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(profile.discord_user_id), sid))) + .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(); @@ -163,13 +163,13 @@ impl FromStr for GameStatus { #[command] #[aliases("installedgaem")] -pub fn installedgame(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> { +pub async fn installedgame(ctx: &Context, msg: &Message, args: Args) -> CommandResult { _game(ctx, msg, args, GameStatus::Installed) } #[command] #[aliases("ownedgaem")] -pub fn ownedgame(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> { +pub async fn ownedgame(ctx: &Context, msg: &Message, args: Args) -> CommandResult { _game(ctx, msg, args, GameStatus::NotInstalled) } @@ -186,30 +186,34 @@ pub fn get_user_id<S: AsRef<str>>(g: &Guild, s: S) -> StdResult<UserId, UserLook let s = s.as_ref().trim_start_matches("@").to_lowercase(); if let Some(info) = USER_INFO_MAP.get(&s) { - return Ok(UserId(info.discord_user_id)); + 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.user.read().name.to_lowercase() == s); + let exact_match = nicks.iter().find(|(m, _)| m.display_name().to_lowercase() == s); - if let Some(m) = exact_match { - return Ok(m.user_id()); + 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.read().name.to_lowercase() == s); + let exact_match = usernames.iter().find(|(m, _)| m.user.name.to_lowercase() == s); - if let Some(m) = exact_match { - return Ok(m.user_id()); + if let Some((m, _)) = exact_match { + return Ok(m.user.id); } } - let opts = nicks.into_iter().chain(usernames.into_iter()).map(|member| member.user_id()).collect::<FnvHashSet<_>>(); + let opts = nicks + .into_iter() + .chain(usernames.into_iter()) + .map(|(member, _)| member.user.id) + .collect::<FnvHashSet<_>>(); match opts.len() { 0 => Err(UserLookupError::NotFound), @@ -220,16 +224,19 @@ pub fn get_user_id<S: AsRef<str>>(g: &Guild, s: S) -> StdResult<UserId, UserLook #[command] #[aliases("gaem")] -fn game(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> { - _game(ctx, msg, args, GameStatus::Installed) +async fn game(ctx: &Context, msg: &Message, args: Args) -> CommandResult { + _game(ctx, msg, args, GameStatus::Installed).await } -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(anyhow!("couldn't find guild"))?; - - let guild = guild.read().guild(&ctx).ok_or(anyhow!("couldn't find guild"))?; - - let guild = guild.read(); +async fn _game( + ctx: &Context, + msg: &Message, + mut args: Args, + min_status: GameStatus, +) -> CommandResult { + let guild = + msg.channel_id.to_channel(&ctx).await?.guild().ok_or(anyhow!("couldn't find guild"))?; + let guild = guild.guild(&ctx).ok_or_else(|| anyhow!("couldn't find guild"))?; let user_args: Vec<String> = if args.rest().is_empty() { Vec::new() @@ -248,12 +255,22 @@ fn _game(ctx: &mut Context, msg: &Message, mut args: Args, min_status: GameStatu match possible { Err(UserLookupError::NotFound) => { - let _ = ctx.send(msg.channel_id, &format!("didn't recognize {}", &u), msg.tts); + let _ = util::send( + ctx, + msg.channel_id, + &format!("didn't recognize {}", &u), + msg.tts, + ); None }, Ok(x) => Some(x), Err(UserLookupError::Ambiguous(x)) => { - let _ = ctx.send(msg.channel_id, &format!("too many matches ({}) for {}", x, &u), msg.tts); + let _ = util::send( + ctx, + msg.channel_id, + &format!("too many matches ({}) for {}", x, &u), + msg.tts, + ); None }, } @@ -294,11 +311,11 @@ fn _game(ctx: &mut Context, msg: &Message, mut args: Args, min_status: GameStatu if inferred && users.len() < 2 || !inferred && users.len() < 1 { info!("too few known users to make game comparison"); - ctx.send(msg.channel_id, "yer too lonely", msg.tts)?; + util::send(ctx, msg.channel_id, "yer too lonely", msg.tts).await?; return Ok(()); } - let data = load_spreadsheet()?; + let data = load_spreadsheet().await?; let user_indexes = (0..data.len()) .filter_map(|i| { @@ -328,7 +345,8 @@ fn _game(ctx: &mut Context, msg: &Message, mut args: Args, min_status: GameStatu .collect::<FnvHashMap<_, _>>(); (1..data[*col].len()).for_each(|i| { - let status = &data_ref[*col][i].parse::<GameStatus>().unwrap_or(GameStatus::Unknown); + 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); @@ -338,23 +356,29 @@ fn _game(ctx: &mut Context, msg: &Message, mut args: Args, min_status: GameStatu }) .collect::<FnvHashMap<_, _>>(); - let statuses = vec![GameStatus::Installed, GameStatus::NotOwned, GameStatus::NotInstalled, GameStatus::Unknown] - .into_iter() - .filter(|s| s <= &min_status) - .collect::<Vec<_>>(); + 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().nth(0).unwrap(); - statuses - .iter() - .fold(iter::empty().collect::<FnvHashSet<_>>(), |acc, s| acc.union(&game_map[s]).cloned().collect()) + 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()); + 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(); } @@ -366,12 +390,12 @@ fn _game(ctx: &mut Context, msg: &Message, mut args: Args, min_status: GameStatu games_formatted = "**LITERALLY NOTHING**".to_owned(); } - ctx.send(msg.channel_id, &games_formatted, msg.tts)?; + util::send(ctx, msg.channel_id, &games_formatted, msg.tts).await?; Ok(()) } -fn load_spreadsheet() -> Result<Vec<Vec<String>>> { +async fn load_spreadsheet() -> Result<Vec<Vec<String>>> { let mut u = SPREADSHEET_URL.clone(); u.query_pairs_mut() @@ -382,7 +406,7 @@ fn load_spreadsheet() -> Result<Vec<Vec<String>>> { let req = reqwest::Request::new(reqwest::Method::GET, u); let client = reqwest::Client::new(); - let mut resp = client.execute(req)?; + let resp = client.execute(req).await?; #[derive(Deserialize)] struct Resp { @@ -395,14 +419,14 @@ fn load_spreadsheet() -> Result<Vec<Vec<String>>> { values: Vec<Vec<String>>, } - let resp = resp.json::<Resp>()?; + let resp = resp.json::<Resp>().await?; Ok(resp.value_ranges.into_iter().next().unwrap().values) } #[command] #[aliases("updategame")] -pub fn updategaem(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { +pub async fn updategaem(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { use regex::Regex; let arg_user = args.single_quoted::<String>(); @@ -412,7 +436,8 @@ pub fn updategaem(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<() } else { use std::borrow::Borrow; - let guild = msg.channel_id.to_channel(&ctx)?.guild().ok_or(anyhow!("couldn't find guild"))?; + let guild = + msg.channel_id.to_channel(&ctx)?.guild().ok_or(anyhow!("couldn't find guild"))?; let guild = guild.read().guild(&ctx).ok_or(anyhow!("couldn't find guild"))?; @@ -425,21 +450,24 @@ pub fn updategaem(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<() let username = match DISCORD_MAP.get(&user) { Some(s) => s, - None => return ctx.send(msg.channel_id, "WHO THE FUCK ARE YE", msg.tts), + None => return util::send(ctx, msg.channel_id, "WHO THE FUCK ARE YE", msg.tts).await, }; let steam_id = match STEAM_MAP.get(&user) { Some(u) => u, - None => return ctx.send(msg.channel_id, "WHO ARE YE ON STEAM", msg.tts), + None => return util::send(ctx, msg.channel_id, "WHO ARE YE ON STEAM", msg.tts).await, }; - let spreadsheet = load_spreadsheet()?; + let spreadsheet = load_spreadsheet().await?; - let user_column = (0..spreadsheet.len()).find(|x| spreadsheet[*x][0].to_lowercase() == username.to_lowercase()); + 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 => return ctx.send(msg.channel_id, "YER NOT IN THE SPREADSHEET", msg.tts), + None => { + return util::send(ctx, msg.channel_id, "YER NOT IN THE SPREADSHEET", msg.tts).await; + }, }; lazy_static! { @@ -452,14 +480,16 @@ pub 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 ctx.send(msg.channel_id, "SPREADSHEET BROKE", msg.tts); + return util::send(ctx, msg.channel_id, "SPREADSHEET BROKE", msg.tts).await; }, }; 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)))); + .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")?; @@ -487,8 +517,15 @@ pub fn updategaem(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<() play_time: u64, } - let games_owned = - reqwest::get(u)?.json::<SteamResp>()?.response.games.into_iter().map(|ge| ge.app_id).collect::<FnvHashSet<_>>(); + let games_owned = reqwest::get(u) + .await? + .json::<SteamResp>() + .await? + .response + .games + .into_iter() + .map(|ge| ge.app_id) + .collect::<FnvHashSet<_>>(); let found_games = missing_appids .filter_map(|(ai, x)| { @@ -501,7 +538,8 @@ pub fn updategaem(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<() .join("\n"); if found_games.len() > 0 { - ctx.send( + util::send( + ctx, msg.channel_id, &format!( "{} games owned on steam that are missing from the list:\n{}", @@ -511,6 +549,6 @@ pub fn updategaem(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<() msg.tts, ) } else { - ctx.send(msg.channel_id, "up to date", msg.tts) + util::send(ctx, msg.channel_id, "up to date", msg.tts) } } diff --git a/src/main.rs b/src/main.rs index 1bf3e98..ab5db5d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,13 +2,8 @@ #![feature(pattern)] #![feature(concat_idents)] #![feature(associated_type_defaults)] - -#![feature(box_syntax, box_patterns)] - -// trash dependencies that can't be fucked to upgrade to ed. 2018 -#[macro_use] extern crate diesel; -#[macro_use] extern crate pest_derive; -#[macro_use] extern crate envconfig_derive; +#![feature(box_patterns)] +#![allow(deprecated)] use std::{ thread, @@ -23,8 +18,10 @@ use log::{ info, }; -pub use self::util::*; -pub use self::config::*; +pub use self::{ + config::*, + util::*, +}; #[cfg(feature = "diesel")] mod db; @@ -38,16 +35,16 @@ mod game { #[inline] fn register(f: StandardFramework) -> StandardFramework { - return f + return f; } } -mod commands; -mod util; mod audio; +mod bot; +mod commands; mod config; mod log_setup; -mod bot; +mod util; pub type Error = anyhow::Error; pub type Result<T> = anyhow::Result<T>; @@ -74,7 +71,7 @@ fn main() { _ => { // NOTE: we MUST have gotten here through SIGINT/SIGTERM handlers ::std::process::exit(0); - } + }, } if Instant::now() - start >= MIN_RUN_DURATION { diff --git a/src/util.rs b/src/util.rs index e9b6203..ed5fd54 100644 --- a/src/util.rs +++ b/src/util.rs @@ -6,79 +6,95 @@ use serenity::{ MessageId,
},
permissions::Permissions,
- }
+ },
};
-use url::Url;
use lazy_static::lazy_static;
use log::debug;
+use serenity::{
+ all::CreateMessage,
+ futures::{
+ AsyncReadExt,
+ StreamExt,
+ },
+};
+use url::Url;
use crate::{
- CONFIG,
audio::PlayQueue,
Result,
+ CONFIG,
};
-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>;
+pub async fn currently_playing(ctx: &Context) -> bool {
+ let queue_lock = {
+ let data = ctx.data.read().await;
+ data.get::<PlayQueue>().cloned().unwrap()
+ };
+
+ let play_queue = queue_lock.read().unwrap();
+ play_queue.playing.is_some()
}
-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()
- }
+pub async fn users_listening(ctx: &Context) -> Result<bool> {
+ let channel = CONFIG.discord.voice_channel().to_channel(&ctx).await?;
- fn users_listening(&self) -> Result<bool> {
- let channel = CONFIG.discord.voice_channel().to_channel(self)?;
- let res = channel.guild()
- .and_then(|ch| ch.read().guild(self))
- .map(|g| (&g.read().voice_states)
+ let res = channel
+ .guild()
+ .and_then(|ch| ch.guild(&ctx))
+ .map(|g| {
+ (&g.voice_states)
.into_iter()
- .any(|(_, state)| state.channel_id == Some(CONFIG.discord.voice_channel())))
- .unwrap_or(false);
+ .any(|(_, state)| state.channel_id == Some(CONFIG.discord.voice_channel()))
+ })
+ .unwrap_or(false);
- Ok(res)
- }
+ Ok(res)
+}
+
+#[inline]
+pub async fn send(
+ ctx: &Context,
+ channel: ChannelId,
+ text: impl AsRef<str>,
+ tts: bool,
+) -> Result<()> {
+ send_result(ctx, channel, text, tts).await.map(|_| ())
+}
- #[inline]
- fn send<A: AsRef<str>>(&self, channel: ChannelId, text: A, tts: bool) -> Result<()> {
- self.send_result(channel, text, tts).map(|_| ())
- }
+pub async fn send_result(
+ ctx: &Context,
+ channel: ChannelId,
+ text: impl AsRef<str>,
+ tts: bool,
+) -> Result<MessageId> {
+ let text = text.as_ref();
+ debug!("sending message {:?} to channel {:?} (tts: {})", text, channel, tts);
- #[inline]
- fn send_result<A: AsRef<str>>(&self, channel: ChannelId, text: A, tts: bool) -> Result<MessageId> {
- let text = text.as_ref();
- debug!("sending message {:?} to channel {:?} (tts: {})", text, channel, tts);
- let result = channel.send_message(self, |m| m.content(text).tts(tts))?;
- Ok(result.id)
- }
+ let result = channel.send_message(ctx, CreateMessage::default().content(text).tts(tts)).await?;
+ Ok(result.id)
}
lazy_static! {
- static ref REQUIRED_PERMS: Permissions = Permissions::EMBED_LINKS |
- Permissions::READ_MESSAGES |
- Permissions::ADD_REACTIONS |
- Permissions::SEND_MESSAGES |
- Permissions::SEND_TTS_MESSAGES |
- Permissions::MENTION_EVERYONE |
- Permissions::USE_EXTERNAL_EMOJIS |
- Permissions::CONNECT |
- Permissions::SPEAK |
- Permissions::CHANGE_NICKNAME |
- Permissions::USE_VAD |
- Permissions::ATTACH_FILES;
+ static ref REQUIRED_PERMS: Permissions = Permissions::EMBED_LINKS
+ | Permissions::READ_MESSAGES
+ | Permissions::ADD_REACTIONS
+ | Permissions::SEND_MESSAGES
+ | Permissions::SEND_TTS_MESSAGES
+ | Permissions::MENTION_EVERYONE
+ | Permissions::USE_EXTERNAL_EMOJIS
+ | Permissions::CONNECT
+ | Permissions::SPEAK
+ | Permissions::CHANGE_NICKNAME
+ | Permissions::USE_VAD
+ | Permissions::ATTACH_FILES;
}
lazy_static! {
- pub static ref OAUTH_URL: Url = Url::parse(
- &format!(
- "https://discordapp.com/api/oauth2/authorize?scope=bot&permissions={}&client_id={}",
- REQUIRED_PERMS.bits(), CONFIG.discord.auth.client_id,
- )
- ).unwrap();
+ pub static ref OAUTH_URL: Url = Url::parse(&format!(
+ "https://discordapp.com/api/oauth2/authorize?scope=bot&permissions={}&client_id={}",
+ REQUIRED_PERMS.bits(),
+ CONFIG.discord.auth.client_id,
+ ))
+ .unwrap();
}
|
