use crate::database::db_conn; use crate::model::entity::{drive_file, note}; use once_cell::sync::Lazy; use regex::Regex; use sea_orm::{prelude::*, QuerySelect}; /// TODO: handle name collisions better #[crate::export(object, js_name = "NoteLikeForCheckWordMute")] pub struct NoteLike { pub file_ids: Vec, pub user_id: Option, pub text: Option, pub cw: Option, pub renote_id: Option, pub reply_id: Option, } async fn all_texts(note: NoteLike) -> Result, DbErr> { let db = db_conn().await?; let mut texts: Vec = vec![]; if let Some(text) = note.text { texts.push(text); } if let Some(cw) = note.cw { texts.push(cw); } texts.extend( drive_file::Entity::find() .select_only() .column(drive_file::Column::Comment) .filter(drive_file::Column::Id.is_in(note.file_ids)) .into_tuple::>() .all(db) .await? .into_iter() .flatten(), ); if let Some(renote_id) = note.renote_id { if let Some((text, cw)) = note::Entity::find_by_id(renote_id) .select_only() .columns([note::Column::Text, note::Column::Cw]) .into_tuple::<(Option, Option)>() .one(db) .await? { if let Some(t) = text { texts.push(t); } if let Some(c) = cw { texts.push(c); } } } if let Some(reply_id) = note.reply_id { if let Some((text, cw)) = note::Entity::find_by_id(reply_id) .select_only() .columns([note::Column::Text, note::Column::Cw]) .into_tuple::<(Option, Option)>() .one(db) .await? { if let Some(t) = text { texts.push(t); } if let Some(c) = cw { texts.push(c); } } } Ok(texts) } fn convert_regex(js_regex: &str) -> String { static RE: Lazy = Lazy::new(|| Regex::new(r"^/(.+)/(.*)$").unwrap()); RE.replace(js_regex, "(?$2)$1").to_string() } fn check_word_mute_impl( texts: &[String], muted_word_lists: &[Vec], muted_patterns: &[String], ) -> bool { muted_word_lists.iter().any(|muted_word_list| { texts.iter().any(|text| { let text_lower = text.to_lowercase(); muted_word_list .iter() .all(|muted_word| text_lower.contains(&muted_word.to_lowercase())) }) }) || muted_patterns.iter().any(|muted_pattern| { Regex::new(convert_regex(muted_pattern).as_str()) .map(|re| texts.iter().any(|text| re.is_match(text))) .unwrap_or(false) }) } #[crate::export] pub async fn check_word_mute( note: NoteLike, muted_word_lists: Vec>, muted_patterns: Vec, ) -> Result { if muted_word_lists.is_empty() && muted_patterns.is_empty() { Ok(false) } else { Ok(check_word_mute_impl( &all_texts(note).await?, &muted_word_lists, &muted_patterns, )) } }