aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/commands/meme.rs93
-rw-r--r--src/commands/mod.rs18
-rw-r--r--src/commands/playback/mod.rs76
-rw-r--r--src/commands/sound.rs42
-rw-r--r--src/db/mod.rs8
-rw-r--r--src/db/models.rs5
-rw-r--r--src/db/schema.rs1
-rw-r--r--src/main.rs49
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