RUFF/src/meme.rs

154 lines
4.4 KiB
Rust

use crate::Bot;
use log::error;
use rand::seq::IteratorRandom;
use serde::Deserialize;
pub const ALLOWED_SPACES: &str = " ,.;:!?({-_";
#[derive(Debug)]
pub struct Meme {
pub keyword: String,
pub ident: MemeIdent,
pub matcher: Matcher,
pub match_case: bool,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Matcher {
Begins,
Contains,
}
impl Meme {
/// checks if the meme should be triggered for the given message
pub fn matches(&self, msg: &str) -> bool {
let mut msg = msg.to_string();
let mut keyword = self.keyword.clone();
if !self.match_case {
msg = msg.to_ascii_lowercase();
keyword = keyword.to_ascii_lowercase();
}
match self.matcher {
Matcher::Begins => {
msg.starts_with(&keyword) &&
// msg must have one of allowed chars after keyword
msg.chars().nth(keyword.len()).map(|c| ALLOWED_SPACES.contains(c)).unwrap_or(true)
},
Matcher::Contains => msg
.match_indices(&keyword)
.map(|(idx, subs)| {
(idx == 0 ||
msg.chars()
.nth(idx - 1)
.map(|c| ALLOWED_SPACES.contains(c))
.unwrap_or(true)) &&
msg.chars()
.nth(idx + subs.len())
.map(|c| ALLOWED_SPACES.contains(c))
.unwrap_or(true)
})
.any(|b| b),
}
}
pub async fn get_id(&self, bot: &Bot) -> anyhow::Result<Option<u32>> {
let memes = bot.jm_client.read().await.get_memes().await?;
match &self.ident {
MemeIdent::Id(i) => Ok(Some(*i)),
MemeIdent::RandomCat(c) => {
let meme = memes
.iter()
.filter(|m| &m.category == c)
.choose(&mut *bot.rng.lock().await);
if let Some(meme) = meme {
let id = meme.id.parse::<u32>();
match id {
Err(e) => {
error!(
"Error parsing meme ID {} for meme {:?} thanks to tilera's PHP api",
&meme.id, &meme
);
Err(e.into())
},
Ok(id) => Ok(Some(id)),
}
} else {
Ok(None)
}
},
}
}
}
#[derive(Debug)]
pub enum MemeIdent {
RandomCat(String),
Id(u32),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn matches_begins_test() {
let meme = Meme {
keyword: String::from("test"),
ident: MemeIdent::Id(42),
matcher: Matcher::Begins,
match_case: false,
};
assert!(!meme.matches("xxx"));
assert!(!meme.matches("testxxx"));
assert!(!meme.matches("xxxtestxxx"));
assert!(!meme.matches("xxxtest xxx"));
assert!(!meme.matches("xxx testxxx"));
assert!(meme.matches("test"));
assert!(meme.matches("test xxx"));
assert!(meme.matches("test; xxx"));
assert!(meme.matches("test;xxx"));
assert!(meme.matches("TEST"));
assert!(meme.matches("TeSt"));
}
#[test]
fn matches_contains_test() {
let meme = Meme {
keyword: String::from("test"),
ident: MemeIdent::Id(42),
matcher: Matcher::Contains,
match_case: false,
};
assert!(!meme.matches("xxx"));
assert!(!meme.matches("xxxtestxxx"));
assert!(!meme.matches("xxxtest xxx"));
assert!(!meme.matches("xxx testxxx"));
assert!(!meme.matches("xxxtest"));
assert!(!meme.matches("testxxx"));
assert!(meme.matches("xxx test xxx"));
assert!(meme.matches("xxx,test.xxx"));
assert!(meme.matches("xxx,TEST.xxx"));
assert!(meme.matches("xxx,TeSt.xxx"));
}
#[test]
fn matches_case_test() {
let meme = Meme {
keyword: String::from("TeSt"),
ident: MemeIdent::Id(42),
matcher: Matcher::Contains,
match_case: true,
};
assert!(!meme.matches("test"));
assert!(meme.matches("TeSt"));
}
}