use std::process::Stdio; use anyhow::anyhow; use diesel::result::Error as DieselError; use grate::tracing; use serenity::all::ReactionType; use tap::Pipe; use tokio::{ io::AsyncReadExt, process::Command, }; use url::Url; use crate::{ db::{ connection, Audio, Image, NewMeme, }, parse_times, util, PoiseContext, FFMPEG_COMMAND, }; /// Add a text/image meme to the db. #[poise::command(prefix_command, guild_only, category = "memes")] pub async fn addmeme( ctx: PoiseContext<'_>, title: String, #[rest] text: Option, ) -> anyhow::Result<()> { let mut conn = connection().await?; let image = util::msg(ctx).and_then(|msg| msg.attachments.first()); if image.is_none() && text.is_none() { tracing::warn!("tried to create non-audio meme with no image or text"); util::reply(ctx, "hahAA it's empty xdddd").await?; return Ok(()); } let mut image_id = None; if let Some(att) = image { let data = att.download().await?; image_id = Some(Image::create(&mut conn, &att.filename, data, ctx.author().id.get()).await?); }; let save_result = NewMeme { title, guild: util::guild_id(ctx)?.get() as _, content: text, image_id, audio_id: None, metadata_id: 0, } .save(&mut conn, ctx.author().id.get()) .await .map(|_| {}); use diesel::result::DatabaseErrorKind; match save_result { Ok(_) => { util::react(ctx, '👌').await?; }, Err(e) => { if let Some(DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, _)) = e.downcast_ref::() { tracing::error!("tried to create meme that already exists"); util::react(ctx, '❌').await?; util::reply(ctx, "that meme already exists").await?; return Ok(()); } return Err(e.into()); }, } Ok(()) } /// Add an audiomeme to the meme db. #[poise::command(prefix_command, guild_only, category = "memes")] pub async fn addaudiomeme( ctx: PoiseContext<'_>, title: String, audio_str: String, #[rest] text: Option, ) -> anyhow::Result<()> { tracing::debug!("running addaudiomeme"); let elems = audio_str.split_whitespace().collect::>(); if elems.is_empty() { util::reply(ctx, "are you stupid").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); util::react(ctx, '🔃').await?; let youtube_url = util::ytdl_url(audio_link.as_str()).await?; tracing::debug!(url_string = &youtube_url, "got download url"); 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 mut conn = connection().await?; let image_att = util::msg(ctx).and_then(|x| x.attachments.first()).ok_or(anyhow!("no attachment")); let mut image_id = None; if let Ok(att) = image_att { let data = att.download().await?; image_id = Image::create(&mut conn, &att.filename, data, ctx.author().id.get()).await?.pipe(Some); } let mut audio_data = Vec::new(); let bytes = audio_reader.read_to_end(&mut audio_data).await?; tracing::debug!(len_bytes = audio_data.len(), "downloaded audio"); if bytes == 0 { tracing::debug!("read 0 bytes from audio reader"); util::unreact(ctx, '🔃').await?; util::reply(ctx, "🔇🔇🔇🔕🔕🔕🔕🔕🔇🔕🔕🔇🔕🔕📣📢📣📢📣").await?; return Ok(()); } let audio_id = Audio::create(&mut conn, audio_data, ctx.author().id.get()).await?; let save_result = NewMeme { title, content: text, guild: util::guild_id(ctx)?.get() as _, image_id, audio_id: Some(audio_id), metadata_id: 0, } .save(&mut conn, ctx.author().id.get()) .await .map(|_| {}); util::unreact(ctx, '🔃').await?; use diesel::result::DatabaseErrorKind; match save_result { Ok(_) => { util::react(ctx, ReactionType::Unicode("👌".to_owned())).await?; }, Err(e) => { if let Some(DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, _)) = e.downcast_ref::() { tracing::error!("tried to create meme that already exists"); util::react(ctx, ReactionType::Unicode("❌".to_owned())).await?; util::reply(ctx, "that meme already exists").await?; return Ok(()); } return Err(e.into()); }, } Ok(()) }