use crate::{util, Bot}; use anyhow::Context; use log::{error, info, warn}; use matrix_sdk::{ room::{Joined, Room}, ruma::{ api::client::r0::media::create_content, events::room::{ message::{ FileInfo, FileMessageEventContent, ImageMessageEventContent, MessageType, RoomMessageEventContent, VideoInfo, VideoMessageEventContent, }, ImageInfo, }, MxcUri, UInt, }, }; use mime::Mime; use serde::{Deserialize, Serialize}; use std::{io::Cursor, sync::atomic::Ordering}; /// A meme stored in the cache database #[derive(Debug, Deserialize, Serialize)] struct CachedMeme { /// mxc url of the meme mxc: Box, /// MIME type of the meme #[serde(with = "util::mime_serialize")] mime: Mime, /// file size of the meme size: UInt, /// file name of the meme meme_name: String, } impl CachedMeme { fn into_message_type(self) -> MessageType { let Self { mxc, mime, size, meme_name, } = self; match mime.type_() { mime::IMAGE => MessageType::Image(ImageMessageEventContent::plain( meme_name, mxc, Some(Box::new({ let mut info = ImageInfo::new(); info.mimetype = Some(mime.to_string()); info.size = Some(size); info })), )), mime::VIDEO => MessageType::Video(VideoMessageEventContent::plain( meme_name, mxc, Some(Box::new({ let mut info = VideoInfo::new(); info.mimetype = Some(mime.to_string()); info.size = Some(size); info })), )), _ => MessageType::File(FileMessageEventContent::plain( meme_name, mxc, Some(Box::new({ let mut info = FileInfo::new(); info.mimetype = Some(mime.to_string()); info.size = Some(size); info })), )), } } } pub async fn on_msg(msg: &str, room: Room, bot: &Bot) -> anyhow::Result<()> { let room = match room { Room::Joined(room) => room, _ => { warn!( "Received message '{}' in room {:?} that's not joined", msg, room.name() ); return Ok(()); }, }; if let Some(ref sendmeme_cmd) = bot.config.sendmeme_command { let mut words = msg.split(' '); if words.next() == Some(sendmeme_cmd) { if let Some(id) = words.next() { let id = if let Ok(id) = id.parse::() { id } else { room.send(RoomMessageEventContent::text_plain("Invalid ID!"), None) .await?; return Ok(()); }; bot.jm_client.write().await.clear_cache().await; bot.meme_count.store(0, Ordering::SeqCst); cache_send_meme(id, bot, room).await?; return Ok(()); } } } for meme in &bot.config.memes { if meme.matches(msg) { if let Some(id) = meme.get_id(bot).await? { cache_send_meme(id, bot, room).await?; } break; } } Ok(()) } async fn cache_send_meme(meme_id: u32, bot: &Bot, room: Joined) -> anyhow::Result<()> { bot.meme_count.fetch_add(1, Ordering::SeqCst); let memes = bot.jm_client.read().await.get_memes().await?; let meme = memes .iter() .find(|m| m.id.parse::().ok() == Some(meme_id)) .cloned(); if let Some(meme) = meme { if let Some(ivec) = bot.memecache.get(meme_id.to_be_bytes())? { let cached = bincode::deserialize::(&ivec)?; send_meme(&room, cached).await?; } else { info!("Meme {} not found in cache, uploading...", meme_id); let resp = bot .jm_client .read() .await .http .get(&meme.link) .send() .await .context("error downloading meme")?; let resp = resp.bytes().await?; if let Some(mime) = mime_guess::from_path(&meme.link).first() { let size = resp.len(); let create_content::Response { content_uri, .. } = bot.client.upload(&mime, &mut Cursor::new(resp)).await?; let cached = CachedMeme { mxc: content_uri, mime, size: UInt::new(size as u64) .context("Meme has file size over allowed limit!")?, meme_name: meme .link .split('/') .last() .unwrap_or(&meme.link) .to_string(), }; bot.memecache .insert(meme_id.to_be_bytes(), bincode::serialize(&cached)?)?; send_meme(&room, cached).await?; // we do this after we have responded, in order to not delay the response if bot.meme_count.load(Ordering::SeqCst) >= bot.config.clear_cache_threshold { let mut client = bot.jm_client.write().await; bot.meme_count.store(0, Ordering::SeqCst); client.clear_cache().await; // memes requested but not used, but they will be cached client.get_memes().await?; } } else { error!( "Couldn't guess MIME type of meme '{}', skipping.", &meme.link ); } } } else { room.send( RoomMessageEventContent::text_plain(format!("No meme with id '{}'", meme_id)), None, ) .await?; } Ok(()) } async fn send_meme(room: &Joined, cached: CachedMeme) -> anyhow::Result<()> { let msg_ty = cached.into_message_type(); room.send(RoomMessageEventContent::new(msg_ty), None) .await .context("Failed to send meme")?; Ok(()) }