aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorNathan Perry <np@nathanperry.dev>2019-11-17 21:31:28 -0500
committerNathan Perry <np@nathanperry.dev>2019-11-17 21:31:28 -0500
commit2a38c282dd57c2051a568549d62c80d6036e8920 (patch)
treeec25f84dda5cdb100ae093b0a690ef349636b4dc /src
parent479bb8d584b138054acc6567b72cb3076832e79c (diff)
most restructuring done
Diffstat (limited to 'src')
-rw-r--r--src/audio/mod.rs37
-rw-r--r--src/audio/play_queue.rs17
-rw-r--r--src/commands/meme/create.rs42
-rw-r--r--src/commands/meme/delete.rs13
-rw-r--r--src/commands/meme/history.rs82
-rw-r--r--src/commands/meme/invoke.rs33
-rw-r--r--src/commands/meme/mod.rs11
-rw-r--r--src/commands/mod.rs230
-rw-r--r--src/commands/playback.rs48
-rw-r--r--src/commands/roll.rs22
-rw-r--r--src/commands/sound_levels.rs23
-rw-r--r--src/db/mod.rs14
-rw-r--r--src/game.rs97
-rw-r--r--src/main.rs46
-rw-r--r--src/util.rs55
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 |