154 lines
4.4 KiB
Rust
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"));
|
|
}
|
|
}
|