aboutsummaryrefslogtreecommitdiff
path: root/src/commands/meme.rs
diff options
context:
space:
mode:
authorNathan Perry <avaglir@gmail.com>2018-04-30 02:29:07 -0400
committerNathan Perry <avaglir@gmail.com>2018-04-30 02:29:07 -0400
commitfbb50f6a151c0ba91a7c88589f0d4ad30cc2e53d (patch)
tree47305b567979d1dcd387b3ca0ce5ca29802f1e11 /src/commands/meme.rs
parent45f92ee5394ee6edb10e0c9b0c668441d6df1928 (diff)
maybe as far as we're going to go with clap
Diffstat (limited to 'src/commands/meme.rs')
-rw-r--r--src/commands/meme.rs355
1 files changed, 224 insertions, 131 deletions
diff --git a/src/commands/meme.rs b/src/commands/meme.rs
index f6f9abe..26f6ee3 100644
--- a/src/commands/meme.rs
+++ b/src/commands/meme.rs
@@ -19,162 +19,145 @@ use reqwest::{
},
mime
};
+use regex::{Regex, Match};
+use clap::{Arg, App, SubCommand, AppSettings};
use super::*;
use super::playback::CtxExt;
-use ::db::*;
-use ::{Error, Result};
+use db::*;
+use failure::Error;
+use Result;
-pub fn meme(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> {
- if args.len_quoted() == 0 {
- return rand_meme(ctx, msg);
- }
-
- macro_rules! next { () => { args.single_quoted::<String>()?.to_lowercase() }; }
-
- match next!().as_ref() {
- "add" => { // e.g.: !thulani meme add title [image IMAGE] [audio|sound AUDIO] [text TEXT...]
- let mut new_meme = NewMeme {
- title: next!(),
- content: None,
- image_id: None,
- audio_id: None,
- metadata_id: 0,
- };
-
- let mut headers = Headers::new();
- headers.set(AcceptEncoding(vec!(qitem(Encoding::Gzip))));
- headers.set(UserAgent::new("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0)"));
- headers.set(Accept(vec![
- qitem(mime::IMAGE_STAR),
- qitem("video/webm".parse().unwrap())
- ]));
-
- let client = Client::builder()
- .default_headers(headers)
- .timeout(Duration::from_secs(5))
- .build()?;
-
- let conn = connection()?;
+lazy_static! {
+ static ref COMMAND_REGEX: Regex = Regex::new(
+ r"^!(?:thulani|thulando|thulando madando|thulan)\s+meme\s*(.*)"
+ ).expect("unable to compile command regex");
- 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());
- break;
- },
- "image" => {
- 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");
- }
+ static ref QUOTES_REGEX: Regex = Regex::new(
+ r##"^\s*(?:"([^"]*)"|([^"\s]*))\s*$"##
+ ).expect("unable to compile quotes regex");
- 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);
+ static ref HELP_REGEX: Regex = Regex::new(
+ r##"(?:^|\s)(?:--help|-h)(?:\s|$)"##
+ ).expect("unable to compile help regex");
+}
- Ok(())
- });
+pub fn meme(ctx: &mut Context, msg: &Message, _: Args) -> Result<()> {
+ let arg_str = COMMAND_REGEX
+ .captures(&msg.content)
+ .ok_or::<Error>(::failure::err_msg("message content not recognized"))?
+ .get(1)
+ .ok_or::<Error>(::failure::err_msg("first capture group not found"))?
+ .as_str();
- if res.is_err() {
- send(msg.channel_id, "fix yer gotdang attachments", msg.tts)?;
- return res;
- }
+ let args = QUOTES_REGEX
+ .captures_iter(arg_str)
+ .map(|capture| {
+ capture.iter()
+ .skip(1)
+ .fold(None, |acc, opt| acc.or(opt))
+ .map(|m: Match| m.as_str())
+ .ok_or::<Error>(::failure::err_msg("couldn't extract matching group from capture"))
+ })
+ .collect::<Result<Vec<_>>>()?;
- continue;
- }
+ let matches = match app().get_matches_from_safe_borrow(args.iter()) {
+ Ok(x) => x,
+ Err(_) => {
+ return send(msg.channel_id, "hwaet the fuck fix your syntax", msg.tts);
+ }
+ };
- let resp = client.head(&url).send()?;
+ lazy_static! {
+ static ref HELP: String = {
+ let mut str = Vec::new();
+ app().write_long_help(&mut str).expect("unable to write out help");
+ String::from_utf8(str).expect("unable to read long help as utf8")
+ };
+ }
- if !resp.status().is_success() {
- return send(msg.channel_id, "pick a better url next time thanks", msg.tts);
- }
+ if HELP_REGEX.is_match(arg_str) { // because clap is stupid
+ return send(msg.channel_id, &format!("```{}```", &*HELP), msg.tts);
+ }
- let len = resp.headers().get::<ContentLength>()
- .map(|ct_len| **ct_len)
- .unwrap_or(0);
+ if let Some(add_matches) = matches.subcommand_matches("add") {
+ lazy_static! {
+ static ref ADD_HELP: String = {
+ let mut str = Vec::new();
+ app().write_long_help(&mut str).expect("unable to write out help");
+ String::from_utf8(str).expect("unable to read long help as utf8")
+ };
+ }
- let content_type_valid = resp.headers().get::<ContentType>()
- .map(|ct_type| ct_type.type_() == "image" || (ct_type.type_() == "video" && ct_type.subtype() == "webm"))
- .unwrap_or(false);
+ if HELP_REGEX.is_match(arg_str) { // because clap is stupid
+ return send(msg.channel_id, &format!("```{}```", &*ADD_HELP), msg.tts);
+ }
- if len > 20_000_000 || !content_type_valid {
- return send(msg.channel_id, "yer pushin me over the fuckin line", msg.tts);
- }
+ let image = add_matches.value_of("image");
+ let audio = add_matches.value_of("audio");
+ let text = add_matches.value_of("text");
- let mut resp = client.get(&url).send()?;
+ let title = add_matches.value_of("title")
+ .ok_or::<Error>(::failure::err_msg("somehow found no title for new meme"))?;
- if !resp.status().is_success() {
- return send(msg.channel_id, "bad link reeeeee", msg.tts);
- }
+ if image.is_none() && audio.is_none() && text.is_none() {
+ return send(msg.channel_id, "hahAA it's empty xdddd", msg.tts);
+ }
- let len = resp.headers().get::<ContentLength>()
- .map(|ct_len| **ct_len)
- .unwrap_or(0);
+ let conn = connection()?;
- let content_type_valid = resp.headers().get::<ContentType>()
- .map(|ct_type| ct_type.type_() == "image" || (ct_type.type_() == "video" && ct_type.subtype() == "webm"))
- .unwrap_or(false);
+ lazy_static! {
+ static ref CLIENT: Client = {
+ let mut headers = Headers::new();
+ headers.set(AcceptEncoding(vec!(qitem(Encoding::Gzip))));
+ headers.set(UserAgent::new("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0)"));
+ headers.set(Accept(vec![
+ qitem(mime::IMAGE_STAR),
+ qitem("video/webm".parse().unwrap())
+ ]));
- if len > 20_000_000 || !content_type_valid {
- return send(msg.channel_id, "are ye fuckin serious", msg.tts);
- }
+ Client::builder()
+ .default_headers(headers)
+ .timeout(Duration::from_secs(5))
+ .build()
+ .expect("couldn't construct http client")
+ };
+ }
- let mut data = Vec::with_capacity(len as usize);
- ::std::io::copy(&mut resp, &mut data)?;
+ let image_id = image.map(|url| load_image(&*CLIENT, &conn, url, title, msg)).transpose()?;
- 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");
+ if let Some(_) = audio {
+ return send(msg.channel_id, "hueh?", msg.tts);
+ }
- let filename = format!("{}.{}", new_meme.title, *ext);
+ return NewMeme {
+ title: title.to_owned(),
+ content: text.map(|s| s.to_owned()),
+ image_id,
+ audio_id: None,
+ metadata_id: 0,
+ }.save(&conn, msg.author.id.0).map(|_| {});
+ }
- 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>()?;
- },
- _ => {
- return send(msg.channel_id, "hueh?", msg.tts);
- }
- }
- }
+ if let Some(_) = matches.subcommand_matches("delete") {
+ return send(msg.channel_id, "hwaet", msg.tts);
+ }
- if new_meme.content.is_none() && new_meme.image_id.is_none() && new_meme.audio_id.is_none() {
- return send(msg.channel_id, "hahAA it's empty xdddd", msg.tts);
- }
+ if let Some(search) = matches.value_of("SEARCH") {
+ let conn = connection()?;
+ let mem = match find_meme(&conn, search) {
+ Ok(x) => x,
+ Err(e) => {
+ send(msg.channel_id, "what in ryan's name", msg.tts)?;
+ return Err(e)
+ },
+ };
- new_meme.save(&conn, msg.author.id.0)?;
- send(msg.channel_id, "i hate my job", msg.tts)?
- },
- "delete" | "remove" => {
- send(msg.channel_id, "hwaet", msg.tts)?
- },
- search => {
- let conn = connection()?;
- let mem = match find_meme(&conn, search) {
- Ok(x) => x,
- Err(e) => {
- send(msg.channel_id, "what in ryan's name", msg.tts)?;
- return Err(e)
- },
- };
-
- send_meme(ctx, &mem, &conn, msg)?;
- }
+ return send_meme(ctx, &mem, &conn, msg);
}
- Ok(())
+ rand_meme(ctx, msg)
}
fn rand_meme(ctx: &Context, message: &Message) -> Result<()> {
@@ -183,7 +166,7 @@ fn rand_meme(ctx: &Context, message: &Message) -> Result<()> {
let should_audio = ctx.currently_playing() && ctx.users_listening()?;
let modulus = if should_audio { 3 } else { 2 };
- let mut mem = match thread_rng().gen::<u32>() % modulus {
+ let mem = match thread_rng().gen::<u32>() % modulus {
0 => rand_text(&conn),
1 => rand_image(&conn),
2 => rand_audio(&conn),
@@ -259,8 +242,118 @@ fn send_meme(ctx: &Context, t: &Meme, conn: &PgConnection, msg: &Message) -> Res
Ok(())
}
-pub fn db_fallback(ctx: &mut Context, msg: &Message, s: &str) -> Result<()> {
+fn load_image(client: &Client, conn: &PgConnection, url: &str, title: &str, msg: &Message) -> Result<i32> {
+ let url = url.to_owned();
+ 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)?;
+ Ok(image_id)
+ });
- Ok(())
+ if res.is_err() {
+ send(msg.channel_id, "fix yer gotdang attachments", msg.tts)?;
+ }
+
+ return res;
+ }
+
+ let resp = client.head(&url).send()?;
+
+ if !resp.status().is_success() {
+ send(msg.channel_id, "pick a better url next time thanks", msg.tts)?;
+ bail!("request failed");
+ }
+
+ let len = resp.headers().get::<ContentLength>()
+ .map(|ct_len| **ct_len)
+ .unwrap_or(0);
+
+ let content_type_valid = resp.headers().get::<ContentType>()
+ .map(|ct_type| ct_type.type_() == "image" || (ct_type.type_() == "video" && ct_type.subtype() == "webm"))
+ .unwrap_or(false);
+
+ if len > 20_000_000 || !content_type_valid {
+ send(msg.channel_id, "yer pushin me over the fuckin line", msg.tts)?;
+ bail!("content invalid");
+ }
+
+ let mut resp = client.get(&url).send()?;
+
+ if !resp.status().is_success() {
+ send(msg.channel_id, "bad link reeeeee", msg.tts)?;
+ bail!("request failed");
+ }
+
+ let len = resp.headers().get::<ContentLength>()
+ .map(|ct_len| **ct_len)
+ .unwrap_or(0);
+
+ let content_type_valid = resp.headers().get::<ContentType>()
+ .map(|ct_type| ct_type.type_() == "image" || (ct_type.type_() == "video" && ct_type.subtype() == "webm"))
+ .unwrap_or(false);
+
+ if len > 20_000_000 || !content_type_valid {
+ send(msg.channel_id, "are ye fuckin serious", msg.tts)?;
+ bail!("content invalid");
+ }
+
+ let mut data = Vec::with_capacity(len as usize);
+ ::std::io::copy(&mut resp, &mut data)?;
+
+ 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!("{}.{}", title, *ext);
+ Image::create(conn, &filename, data, msg.author.id.0)
}
+
+fn app<'a, 'b>() -> App<'a, 'b> {
+ App::new("!thulani meme")
+ .about("manipulate memes. pass no arguments to produce a randomly-selected meme.")
+ .settings(&vec![AppSettings::DisableHelpSubcommand, AppSettings::DisableVersion])
+ .arg(Arg::with_name("SEARCH")
+ .index(1)
+ .help("search for a meme by name or content (exact, case-insensitive matches only)")
+ )
+ .subcommand(SubCommand::with_name("add")
+ .about("add a meme to the database")
+ .arg(Arg::with_name("TITLE")
+ .help("title for new meme")
+ .required(true)
+ .index(1)
+ )
+ .arg(Arg::with_name("image")
+ .short("i")
+ .long("image")
+ .multiple(false)
+ .help("url of image to attach (use 'attached' to use image attached to message)")
+ .takes_value(true)
+ )
+ .arg(Arg::with_name("audio")
+ .short("a")
+ .long("audio")
+ .multiple(false)
+ .help("address of a video downloadable with youtube-dl. timestamps not yet supported.")
+ .takes_value(true)
+ )
+ .arg(Arg::with_name("text")
+ .short("t")
+ .long("text")
+ .multiple(false)
+ .help("text to play back")
+ .takes_value(true)
+ )
+ )
+ .subcommand(SubCommand::with_name("delete")
+ .about("delete a meme from the database")
+ .arg(Arg::with_name("title")
+ .index(1)
+ )
+ )
+} \ No newline at end of file