use std::{ io::Read, process::{ Command, Stdio, }, }; use diesel::result::Error as DieselError; use log::{ debug, error, warn, }; use serenity::{ framework::standard::{ macros::command, Args, Delimiter, }, model::channel::Message, prelude::*, }; use url::Url; use anyhow::anyhow; use lazy_static::lazy_static; use serenity::{ all::ReactionType, framework::standard::{ CommandError, CommandResult, }, futures::TryFutureExt, }; use crate::{ audio::{ parse_times, ytdl_url, }, db::{ connection, Audio, Image, NewMeme, }, util, FFMPEG_COMMAND, }; lazy_static! { static ref DELIMS: Vec = vec![' '.into(), '\n'.into(), '\t'.into()]; } #[command] pub async fn addmeme(ctx: &Context, msg: &Message, args: Args) -> CommandResult { let mut args = Args::new(args.rest(), DELIMS.as_ref()); let title = args.single_quoted::()?; let text = args.rest().to_owned(); let text = if text.is_empty() { None } else { Some(text) }; let mut conn = connection()?; let image = msg.attachments.first(); if image.is_none() && text.is_none() { warn!("tried to create non-audio meme with no image or text"); return util::send(ctx, msg.channel_id, "hahAA it's empty xdddd", msg.tts) .map_err(CommandError::from) .await; } let image_id = image .map(|att| { let data = att.download()?; Image::create(&mut conn, &att.filename, data, msg.author.id.get()) }) .transpose()?; let save_result = NewMeme { title, content: text, image_id, audio_id: None, metadata_id: 0, } .save(&mut conn, msg.author.id.get()) .map(|_| {}); use diesel::result::DatabaseErrorKind; match save_result { Ok(_) => msg.react(&ctx, "👌"), Err(e) => { if let Some(DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, _)) = e.downcast_ref::() { error!("tried to create meme that already exists"); msg.react(&ctx, ReactionType::Unicode("❌".to_owned())).await?; return util::send(ctx, msg.channel_id, "that meme already exists", msg.tts) .map_err(CommandError::from) .await; } return Err(e.into()); }, } } #[command] pub async fn addaudiomeme(ctx: &Context, msg: &Message, args: Args) -> CommandResult { let mut args = Args::new(args.rest(), DELIMS.as_ref()); let title = args.single_quoted::()?; let audio_str = args.single_quoted::()?; let elems = audio_str.split_whitespace().collect::>(); if elems.len() == 0 { util::send(ctx, msg.channel_id, "are you stupid", msg.tts).await?; return Err(anyhow!("no audio link was provided").into()); } let audio_link = Url::parse(elems[0])?; let opts = elems[1..].join(" "); let (start, end) = parse_times(opts); let youtube_url = ytdl_url(audio_link.as_str())?; let duration_opts = if let Some(e) = end { vec![ "-ss".to_owned(), start.map_or_else( || "00:00:00".to_owned(), |s| { format!( "{:02}:{:02}:{:02}", s.num_hours(), s.num_minutes() % 60, s.num_seconds() % 60 ) }, ), "-to".to_owned(), format!("{:02}:{:02}:{:02}", e.num_hours(), e.num_minutes() % 60, e.num_seconds() % 60), ] } else { vec![] }; let ffmpeg_command = Command::new(&*FFMPEG_COMMAND) .arg("-i") .arg(youtube_url) .args(duration_opts) .args(&[ "-ac", "2", "-ar", "48000", "-f", "opus", "-acodec", "libopus", "-b:a", "96k", "-fs", "5M", "-", ]) .stdout(Stdio::piped()) .stderr(Stdio::null()) .stdin(Stdio::null()) .spawn()?; let mut audio_reader = ffmpeg_command.stdout.unwrap(); let text = args.rest().to_owned(); let text = if text.is_empty() { None } else { Some(text) }; let mut conn = connection()?; let image = msg .attachments .first() .ok_or(anyhow!("no attachment")) .and_then(|att| { let data = att.download()?; Image::create(&mut conn, &att.filename, data, msg.author.id.get()) }) .ok(); let mut audio_data = Vec::new(); let bytes = audio_reader.read_to_end(&mut audio_data)?; if bytes == 0 { debug!("read 0 bytes from audio reader"); return util::send(ctx, msg.channel_id, "🔇🔇🔇🔕🔕🔕🔕🔕🔇🔕🔕🔇🔕🔕📣📢📣📢📣", msg.tts) .map_err(CommandError::from) .await; } let audio_id = Audio::create(&mut conn, audio_data, msg.author.id.get())?; let save_result = NewMeme { title, content: text, image_id: image, audio_id: Some(audio_id), metadata_id: 0, } .save(&mut conn, msg.author.id.get()) .map(|_| {}); use diesel::result::DatabaseErrorKind; match save_result { Ok(_) => msg.react(&ctx, ReactionType::Unicode("👌".to_owned())), Err(e) => { if let Some(DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, _)) = e.downcast_ref::() { error!("tried to create meme that already exists"); msg.react(&ctx, ReactionType::Unicode("❌".to_owned())).await?; return util::send(ctx, msg.channel_id, "that meme already exists", msg.tts) .map_err(CommandError::from) .await; } return Err(e); }, } }