diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/commands/meme.rs | 93 | ||||
| -rw-r--r-- | src/commands/mod.rs | 18 | ||||
| -rw-r--r-- | src/commands/playback/mod.rs | 76 | ||||
| -rw-r--r-- | src/commands/sound.rs | 42 | ||||
| -rw-r--r-- | src/db/mod.rs | 8 | ||||
| -rw-r--r-- | src/db/models.rs | 5 | ||||
| -rw-r--r-- | src/db/schema.rs | 1 | ||||
| -rw-r--r-- | src/main.rs | 49 |
8 files changed, 152 insertions, 140 deletions
diff --git a/src/commands/meme.rs b/src/commands/meme.rs index 4bd8a27..b0f460f 100644 --- a/src/commands/meme.rs +++ b/src/commands/meme.rs @@ -3,6 +3,7 @@ use std::time::Duration; use rand::{thread_rng, distributions::{Weighted, WeightedChoice, Distribution}}; use serenity::http::AttachmentType; use serenity::builder::CreateMessage; +use serenity::framework::standard::Args; use diesel::PgConnection; use reqwest::{ Client, @@ -43,10 +44,9 @@ static mut TTS_WEIGHTS: [Weighted<bool>; 2] = [ Weighted { weight: 1, item: true } ]; -command!(meme(ctx, msg, args) { +pub fn meme(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { if args.len_quoted() == 0 { - rand_meme(ctx, msg)?; - return Ok(()); + return rand_meme(ctx, msg); } macro_rules! next { () => { args.single_quoted::<String>()?.to_lowercase() }; } @@ -76,17 +76,44 @@ command!(meme(ctx, msg, args) { let conn = connection()?; - info!("args.len: {}; args: {:?}", args.len(), args); - while args.len() > 0 { + while args.len_quoted() > 0 { + info!("args.len_quoted: {}; args: {:?}", args.len_quoted(), args); match next!().as_ref() { - "text" => new_meme.content = Some(args.full().to_owned()), + "text" => { + new_meme.content = Some(args.full().to_owned()); + break; + }, "image" => { - let url = args.single_quoted::<String>()?; + if new_meme.image_id.is_some() { + send(msg.channel_id, "ONLY ONE IMAGE YOU FUCK", msg.tts)?; + bail!("user tried to supply more than one image"); + } + + let mut url = args.single_quoted::<String>()?; + + if url.to_lowercase().trim() == "attached" { + let res = msg.attachments.first() + .ok_or::<Error>(::failure::err_msg("no attachments found")) + .and_then(|att| { + let data = att.download()?; + let image_id = Image::create(&conn, &att.filename, data, msg.author.id.0)?; + new_meme.image_id = Some(image_id); + + Ok(()) + }); + + if res.is_err() { + send(msg.channel_id, "fix yer gotdang attachments", msg.tts)?; + return res; + } + + continue; + } + let resp = client.head(&url).send()?; if !resp.status().is_success() { - send(msg.channel_id, "pick a better url next time thanks", msg.tts)?; - return Ok(()); + return send(msg.channel_id, "pick a better url next time thanks", msg.tts); } let len = resp.headers().get::<ContentLength>() @@ -98,15 +125,13 @@ command!(meme(ctx, msg, args) { .unwrap_or(false); if len > 20_000_000 || !content_type_valid { - send(msg.channel_id, "yer pushin me over the fuckin line", msg.tts)?; - return Ok(()); + return send(msg.channel_id, "yer pushin me over the fuckin line", msg.tts); } let mut resp = client.get(&url).send()?; if !resp.status().is_success() { - send(msg.channel_id, "pick a better url next time thanks", msg.tts)?; - return Ok(()); + return send(msg.channel_id, "bad link reeeeee", msg.tts); } let len = resp.headers().get::<ContentLength>() @@ -118,56 +143,57 @@ command!(meme(ctx, msg, args) { .unwrap_or(false); if len > 20_000_000 || !content_type_valid { - send(msg.channel_id, "are ye fuckin serious", msg.tts)?; - return Ok(()); - } - - if !resp.status().is_success() { - send(msg.channel_id, "bad link reeeeee", msg.tts)?; - return Ok(()); + return send(msg.channel_id, "are ye fuckin serious", msg.tts); } let mut data = Vec::with_capacity(len as usize); ::std::io::copy(&mut resp, &mut data)?; - let image_id = Image::create(&conn, data, msg.author.id.0)?; + let ext = resp.headers().get::<ContentType>() + .and_then(|typ| ::mime_guess::get_extensions(typ.type_().as_str(), typ.subtype().as_str())) + .and_then(|x| x.first()) + .unwrap_or(&"bin"); + + let filename = format!("{}.{}", new_meme.title, *ext); + + let image_id = Image::create(&conn, &filename, data, msg.author.id.0)?; new_meme.image_id = Some(image_id); }, "audio" | "sound" => { let _url = args.single_quoted::<String>()?; }, _ => { - send(msg.channel_id, "hueh?", msg.tts)?; - return Ok(()); + return send(msg.channel_id, "hueh?", msg.tts); } } } if new_meme.content.is_none() && new_meme.image_id.is_none() && new_meme.audio_id.is_none() { - send(msg.channel_id, "haha it's empty lol xdddd", msg.tts)?; - return Ok(()); + return send(msg.channel_id, "hahAA it's empty xdddd", msg.tts); } new_meme.save(&conn, msg.author.id.0)?; - send(msg.channel_id, "i hate my job", msg.tts)?; + send(msg.channel_id, "i hate my job", msg.tts)? }, "delete" | "remove" => { - send(msg.channel_id, "hwaet", msg.tts)?; + send(msg.channel_id, "hwaet", msg.tts)? }, search => { let conn = connection()?; let mem = match find_meme(&conn, search) { Ok(x) => x, - Err(_) => { + Err(e) => { send(msg.channel_id, "what in ryan's name", msg.tts)?; - return Ok(()); + return Err(e) }, }; send_meme(ctx, &mem, &conn, msg)?; } } -}); + + Ok(()) +} fn rand_meme(ctx: &Context, message: &Message) -> Result<()> { let conn = connection()?; @@ -198,7 +224,7 @@ fn rand_meme(ctx: &Context, message: &Message) -> Result<()> { ctr += 1; if ctr > 10 { send(message.channel_id, "yer listenin to somethin else", message.tts)?; - return Err("looped too many times trying to find a non-audio meme".into()); + bail!("looped too many times trying to find a non-audio meme"); } } @@ -233,7 +259,10 @@ fn send_meme(ctx: &Context, t: &Meme, conn: &PgConnection, msg: &Message) -> Res }; match image { - Some(image) => msg.channel_id.send_files(vec!(AttachmentType::Bytes((&image?.data, &t.title))), create_msg)?, + Some(image) => { + let image = image?; + msg.channel_id.send_files(vec!(AttachmentType::Bytes((&image.data, &image.filename))), create_msg)? + }, None => msg.channel_id.send_message(create_msg)?, }; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index d13082f..9ce518b 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -18,41 +18,41 @@ pub fn register_commands(f: StandardFramework) -> StandardFramework { .command("skip", |c| c .desc("skip the rest of the current request") .guild_only(true) - .cmd(skip)) + .exec(skip)) .command("pause", |c| c .desc("pause playback (currently broken)") .guild_only(true) - .cmd(pause)) + .exec(pause)) .command("resume", |c| c .desc("resume playing (currently broken)") .guild_only(true) - .cmd(resume)) + .exec(resume)) .command("list", |c| c .known_as("queue") .desc("list playing and queued requests") .guild_only(true) - .cmd(list)) + .exec(list)) .command("die", |c| c .batch_known_as(vec!["sudoku", "stop"]) .desc("stop playing and empty the queue") .guild_only(true) - .cmd(die)) + .exec(die)) .command("mute", |c| c .desc("mute thulani (playback continues)") .guild_only(true) - .cmd(mute)) + .exec(mute)) .command("unmute", |c| c .desc("unmute thulani") .guild_only(true) - .cmd(unmute)) + .exec(unmute)) .command("play", |c| c .desc("queue a request") .guild_only(true) - .cmd(play)) + .exec(play)) .command("volume", |c| c .desc("set playback volume") .guild_only(true) - .cmd(volume)) + .exec(volume)) .unrecognised_command(|ctx, msg, unrec| { let url = match msg.content.split_whitespace().skip(1).next() { Some(x) if x.starts_with("http") => x, diff --git a/src/commands/playback/mod.rs b/src/commands/playback/mod.rs index a13ad36..f80bb73 100644 --- a/src/commands/playback/mod.rs +++ b/src/commands/playback/mod.rs @@ -3,6 +3,7 @@ use serenity::voice::{LockedAudio, ytdl}; use super::*; pub use self::types::*; +use serenity::framework::standard::Args; mod types; @@ -56,24 +57,20 @@ pub fn _play(ctx: &Context, msg: &Message, url: &str) -> Result<()> { Ok(()) } -command!(play(ctx, msg, args) { +pub fn play(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { if args.len() == 0 { - _resume(ctx, msg)?; - return Ok(()); + return _resume(ctx, msg); } let url = match args.single::<String>() { Ok(url) => url, - Err(_) => { - send(msg.channel_id, "BAD LINK", msg.tts)?; - return Ok(()); - } + Err(_) => return send(msg.channel_id, "BAD LINK", msg.tts), }; - _play(ctx, msg, &url)?; -}); + _play(ctx, msg, &url) +} -command!(pause(ctx, msg) { +pub fn pause(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { let mut queue_lock = ctx.data.lock().get::<PlayQueue>().cloned().unwrap(); let done = || send(msg.channel_id, "r u srs", msg.tts); @@ -82,10 +79,7 @@ command!(pause(ctx, msg) { let current_item = match play_queue.playing { Some(ref x) => x, - None => { - done()?; - return Ok(()); - }, + None => return done(), }; let audio = current_item.audio.lock(); @@ -93,8 +87,7 @@ command!(pause(ctx, msg) { }; if !playing { - done()?; - return Ok(()); + return done(); } { @@ -102,11 +95,13 @@ command!(pause(ctx, msg) { let ref audio = queue.playing.clone().unwrap().audio; audio.lock().pause(); } -}); -command!(resume(ctx, msg) { - _resume(ctx, msg)?; -}); + Ok(()) +} + +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(); @@ -141,7 +136,7 @@ fn _resume(ctx: &mut Context, msg: &Message) -> Result<()> { Ok(()) } -command!(skip(ctx, _msg) { +pub fn skip(ctx: &mut Context, _msg: &Message, _args: Args) -> Result<()> { let data = ctx.data.lock(); let mut mgr_lock = data.get::<VoiceManager>().cloned().unwrap(); @@ -156,15 +151,17 @@ command!(skip(ctx, _msg) { } else { debug!("got skip with no handler attached"); } -}); -command!(die(ctx, msg) { + Ok(()) +} + +pub fn die(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { let data = ctx.data.lock(); - let mut mgr_lock = data.get::<VoiceManager>().cloned().unwrap(); + let mgr_lock = data.get::<VoiceManager>().cloned().unwrap(); let mut manager = mgr_lock.lock(); - let mut queue_lock = data.get::<PlayQueue>().cloned().unwrap(); + let queue_lock = data.get::<PlayQueue>().cloned().unwrap(); { let mut play_queue = queue_lock.write().unwrap(); @@ -180,11 +177,13 @@ command!(die(ctx, msg) { send(msg.channel_id, "YOU die", msg.tts)?; debug!("got die with no handler attached"); } -}); -command!(list(ctx, msg) { - let mut queue_lock = ctx.data.lock().get::<PlayQueue>().cloned().unwrap(); - let mut play_queue = queue_lock.read().unwrap(); + Ok(()) +} + +pub fn list(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { + let queue_lock = ctx.data.lock().get::<PlayQueue>().cloned().unwrap(); + let play_queue = queue_lock.read().unwrap(); let channel_tmp = msg.channel().unwrap().guild().unwrap(); let channel = channel_tmp.read(); @@ -208,12 +207,15 @@ command!(list(ctx, msg) { }, } - play_queue.queue.iter().for_each(|info| { - let playing_info = match info.data { - Left(ref url) => format!("`{}`", url), - Right(_) => "meme".to_owned(), - }; + play_queue.queue.iter() + .for_each(|info| { + let playing_info = match info.data { + Left(ref url) => format!("`{}`", url), + Right(_) => "meme".to_owned(), + }; - channel.say(&format!("{} ({})", playing_info, info.initiator)).unwrap(); - }); -}); + let _ = channel.say(&format!("{} ({})", playing_info, info.initiator)); + }); + + Ok(()) +} diff --git a/src/commands/sound.rs b/src/commands/sound.rs index 6cac61d..c25e910 100644 --- a/src/commands/sound.rs +++ b/src/commands/sound.rs @@ -1,9 +1,10 @@ use super::*; +use serenity::framework::standard::Args; pub const DEFAULT_VOLUME: f32 = 0.05; -command!(mute(ctx, _msg) { - let mut mgr_lock = ctx.data.lock().get::<VoiceManager>().cloned().unwrap(); +pub fn mute(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { + let mgr_lock = ctx.data.lock().get::<VoiceManager>().cloned().unwrap(); let mut manager = mgr_lock.lock(); manager.get_mut(*TARGET_GUILD_ID) @@ -15,10 +16,12 @@ command!(mute(ctx, _msg) { trace!("Muted"); } }); -}); -command!(unmute(ctx, msg) { - let mut mgr_lock = ctx.data.lock().get::<VoiceManager>().cloned().unwrap(); + Ok(()) +} + +pub fn unmute(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> { + let mgr_lock = ctx.data.lock().get::<VoiceManager>().cloned().unwrap(); let mut manager = mgr_lock.lock(); manager.get_mut(*TARGET_GUILD_ID) @@ -31,9 +34,11 @@ command!(unmute(ctx, msg) { let _ = send(msg.channel_id, "REEEEEEEEEEEEEE", msg.tts); } }); -}); -command!(volume(ctx, msg, args) { + Ok(()) +} + +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(); @@ -41,20 +46,13 @@ command!(volume(ctx, msg, args) { (play_queue.volume / DEFAULT_VOLUME * 100.0) as usize }; - send(msg.channel_id, &format!("Volume: {}/100", vol), msg.tts)?; - return Ok(()); + return send(msg.channel_id, &format!("Volume: {}/100", vol), msg.tts); } - let mut vol: usize = match args.single::<f32>() { - Ok(vol) if vol.is_nan() => { - send(msg.channel_id, "you're a fuck", msg.tts)?; - return Ok(()); - }, + let vol: usize = match args.single::<f32>() { + Ok(vol) if vol.is_nan() => return send(msg.channel_id, "you're a fuck", msg.tts), Ok(vol) => vol as usize, - Err(_) => { - send(msg.channel_id, "???????", msg.tts)?; - return Ok(()); - }, + Err(_) => return send(msg.channel_id, "???????", msg.tts), }; let mut vol: f32 = (vol as f32)/100.0; // force aliasing to reasonable values @@ -67,7 +65,7 @@ command!(volume(ctx, msg, args) { vol = 0.0; } - let mut queue_lock = ctx.data.lock().get::<PlayQueue>().cloned().unwrap(); + let queue_lock = ctx.data.lock().get::<PlayQueue>().cloned().unwrap(); { let mut play_queue = queue_lock.write().unwrap(); @@ -84,5 +82,7 @@ command!(volume(ctx, msg, args) { let mut audio = current_item.audio.lock(); audio.volume(play_queue.volume); - }; -}); + } + + Ok(()) +} diff --git a/src/db/mod.rs b/src/db/mod.rs index 0862883..ac0e321 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -22,12 +22,14 @@ pub fn connection() -> Result<PgConnection> { pub fn find_meme<T: AsRef<str>>(conn: &PgConnection, search: T) -> Result<Meme> {
use diesel::dsl::sql;
+ use diesel::types::Text;
let search = search.as_ref();
let format_search = format!("%{}%", search);
+ // TODO: check for injection
memes::table
- .filter(memes::title.ilike(&format_search).or(sql(&format!("content ILIKE %{}%", search))))
+ .filter(memes::title.ilike(&format_search).or(sql("content ILIKE ").bind::<Text, _>(&format_search)))
.limit(1)
.first::<Meme>(conn)
.map_err(Error::from)
@@ -35,12 +37,14 @@ pub fn find_meme<T: AsRef<str>>(conn: &PgConnection, search: T) -> Result<Meme> pub fn find_text<T: AsRef<str>>(conn: &PgConnection, search: T) -> Result<Meme> {
use diesel::dsl::sql;
+ use diesel::types::Text;
let search = search.as_ref();
let format_search = format!("%{}%", search);
+ // TODO: check for injection
memes::table
- .filter((memes::title.ilike(&format_search).or(sql(&format!("content ILIKE %{}%", search))))
+ .filter((memes::title.ilike(&format_search).or(sql("content ILIKE ").bind::<Text, _>(&format_search)))
.and(memes::content.is_not_null()))
.limit(1)
.first::<Meme>(conn)
diff --git a/src/db/models.rs b/src/db/models.rs index 88ee82c..6ac8b18 100644 --- a/src/db/models.rs +++ b/src/db/models.rs @@ -105,10 +105,11 @@ pub struct Image { pub data: Vec<u8>,
pub metadata_id: i32,
pub data_hash: Vec<u8>,
+ pub filename: String,
}
impl Image {
- pub fn create(conn: &PgConnection, data: Vec<u8>, by_user: u64) -> Result<i32> {
+ pub fn create(conn: &PgConnection, filename: &str, data: Vec<u8>, by_user: u64) -> Result<i32> {
let mut data_hash = ::sha1::Sha1::new();
data_hash.update(&data);
let data_hash = data_hash.digest().bytes().to_vec();
@@ -127,6 +128,7 @@ impl Image { let new_image = NewImage {
data,
data_hash,
+ filename: filename.to_owned(),
metadata_id: metadata.id,
};
@@ -144,6 +146,7 @@ pub struct NewImage { pub data: Vec<u8>,
pub metadata_id: i32,
pub data_hash: Vec<u8>,
+ pub filename: String,
}
diff --git a/src/db/schema.rs b/src/db/schema.rs index d4ba0fa..1822204 100644 --- a/src/db/schema.rs +++ b/src/db/schema.rs @@ -22,6 +22,7 @@ table! { data -> Bytea, metadata_id -> Int4, data_hash -> Bytea, + filename -> Varchar, } } diff --git a/src/main.rs b/src/main.rs index e09e77a..eaaf1f0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,21 +5,21 @@ extern crate chrono; extern crate ctrlc; extern crate dotenv; #[macro_use] extern crate dotenv_codegen; -#[macro_use] extern crate error_chain; +#[macro_use] extern crate failure; extern crate fern; #[macro_use] extern crate lazy_static; #[macro_use] extern crate log; -#[macro_use] extern crate serenity; +extern crate serenity; extern crate typemap; extern crate url; extern crate rand; extern crate either; extern crate reqwest; extern crate sha1; +extern crate mime_guess; use commands::register_commands; use dotenv::dotenv; -use errors::*; use serenity::framework::standard::help_commands; use serenity::framework::StandardFramework; use serenity::model::gateway::Ready; @@ -28,6 +28,9 @@ use serenity::prelude::*; use std::env; use std::thread; use std::time::{Duration, Instant}; + +use failure::Error; + pub use util::*; cfg_if! { if #[cfg(feature = "diesel")] { @@ -39,24 +42,13 @@ cfg_if! { mod commands; mod util; -mod errors { - error_chain! { - foreign_links { - Serenity(::serenity::Error); - MissingVar(::std::env::VarError); - DieselConn(::diesel::ConnectionError) #[cfg(feature = "diesel")]; - Diesel(::diesel::result::Error) #[cfg(feature = "diesel")]; - R2D2(::diesel::r2d2::Error) #[cfg(feature = "diesel")]; - } - } -} +pub type Result<T> = ::std::result::Result<T, Error>; lazy_static! { static ref TARGET_GUILD: u64 = dotenv!("TARGET_GUILD").parse().expect("unable to parse TARGET_GUILD as u64"); static ref TARGET_GUILD_ID: GuildId = GuildId(*TARGET_GUILD); } - struct Handler; impl EventHandler for Handler { fn ready(&self, _: Context, r: Ready) { @@ -93,25 +85,13 @@ fn run() -> Result<()> { result }) - .after(|_ctx, _msg, _cmd, err| { + .after(|_ctx, _msg, cmd, err| { match err { Ok(()) => { - trace!("command completed successfully"); + trace!("command '{}' completed successfully", cmd); }, Err(e) => { - match e { - Error(e) => { - error!("error encountered handling request: {}", e); - e.iter().skip(1).for_each(|e| { - error!("caused by: {}", e); - }); - - if let Some(bt) = e.backtrace() { - error!("backtrace: {:?}", bt); - } - } - e => error!("encountered error: {:?}", e); - } + error!("error encountered handling command '{}': {:?}", cmd, e); } } }) @@ -193,14 +173,7 @@ fn main() { info!("starting bot"); match run() { Err(e) => { - error!("error encountered running client: {}", e); - e.iter().skip(1).for_each(|e| { - error!("caused by: {}", e); - }); - - if let Some(bt) = e.backtrace() { - error!("backtrace: {:?}", bt); - } + error!("error encountered running client: {:?}", e); }, _ => { // NOTE: we MUST have gotten here through SIGINT/SIGTERM handlers |
