RUFF/src/responder.rs

212 lines
6.5 KiB
Rust

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<MxcUri>,
/// 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::<u32>() {
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::<u32>().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::<CachedMeme>(&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(())
}