diff --git a/Cargo.lock b/Cargo.lock index 33c2f24..863ba62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2067,7 +2067,7 @@ checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "scam-police" -version = "0.3.0" +version = "0.4.0" dependencies = [ "anyhow", "dirs", diff --git a/Cargo.toml b/Cargo.toml index 134156d..bcd40d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "scam-police" -version = "0.3.0" +version = "0.4.0" edition = "2021" authors = [ "@0xf8:projectsegfau.lt", "@jjj333:pain.agency" ] diff --git a/src/judge.rs b/src/judge.rs new file mode 100644 index 0000000..c719b75 --- /dev/null +++ b/src/judge.rs @@ -0,0 +1,71 @@ +use crate::{CONFIG, keywords::{Keywords, KeywordCategory}}; +use matrix_sdk::{room::Joined, ruma::events::room::message::{RoomMessageEventContent, OriginalRoomMessageEvent}}; +use serde_json::json; + +pub enum JudgementResult { + Ok, + MaybeScam, // hit atleast one category + LikelyScam, // hit all categories +} + +pub struct Judgement { + pub text: String, +} + +impl Judgement { + pub fn judge(&self) -> anyhow::Result { + + // Load keywords + let mut keywords = CONFIG.keywords.clone(); + let scams = keywords.as_object_mut().unwrap().get_mut("scams").unwrap(); + + // Turn json into Keywords + let verbs = Keywords::create("verbs", &scams["verbs"]); + let currencies = Keywords::create("currencies", &scams["currencies"]); + let socials = Keywords::create("socials", &scams["socials"]); + + // Count occurences + let mut counter = KeywordCategory::create_counter_map(); + counter.insert(KeywordCategory::Verb, verbs.find(&self.text)); + counter.insert(KeywordCategory::Currency, currencies.find(&self.text)); + counter.insert(KeywordCategory::Social, socials.find(&self.text)); + + let mut count_all = 0; + let total = counter.len(); + for (_category, count) in counter.to_owned() { + if count > 0 { + count_all = count_all + 1; + } + } + + + if count_all == 0 { return Ok(JudgementResult::Ok) }; + if count_all < total { return Ok(JudgementResult::MaybeScam) }; + + Ok(JudgementResult::LikelyScam) + } + + pub async fn alert(room: &Joined, event: &OriginalRoomMessageEvent) -> anyhow::Result<()> { + let mut keywords = CONFIG.keywords.clone(); + let scams = keywords.as_object_mut().unwrap().get_mut("scams").unwrap(); + + // Add stats to end of response + let response = scams["response"].as_str().unwrap(); + let response_html = scams["response_md"].as_str().unwrap(); + + // Send message + let msg = RoomMessageEventContent::text_html(response, response_html); + let reply = msg.make_reply_to(event); + room.send(reply, None).await.expect("Couldn't send message"); + + // Send reaction + room.send_raw(json!({ + "m.relates_to": { + "rel_type": "m.annotation", + "event_id": event.event_id.to_string(), + "key": "🚨🚨 SCAM 🚨🚨" + }}), "m.reaction", None).await.expect("Couldn't send reaction"); + + Ok(()) + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index bd386b5..aa6a1c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,86 +1,83 @@ use matrix_sdk::{ room::Room, ruma::{events::room::message::{ - MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent, Relation + MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent, Relation, RoomMessageEvent }, OwnedRoomId}, Error, LoopCtrl, }; use once_cell::sync::Lazy; -use serde_json::json; pub mod matrix; pub mod config; pub mod keywords; +pub mod judge; static CONFIG: Lazy = Lazy::new(|| config::Config::load()); -async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) { +async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) -> anyhow::Result<()> { if let Room::Joined(room) = room { let orig_event = event.to_owned().into_full_event(OwnedRoomId::from(room.room_id())); // Ignore non-text let MessageType::Text(text_content) = event.to_owned().content.msgtype else { - return; + return Ok(()); }; // Ignore own messages - if event.sender == room.client().user_id().expect("Couldn't get user_id").to_string() { return } - - // Ignore replies - if let Some(relation) = orig_event.to_owned().content.relates_to { - if match relation { - Relation::Reply { in_reply_to: _ } => true, - _ => false - } { - return - } - } + if event.sender == room.client().user_id().expect("Couldn't get user_id").to_string() { return Ok(()) } // Too short to be a scam lol - if text_content.body.chars().count() < 12 { return } + if text_content.body.chars().count() < 12 { return Ok(()) } let text_content = text_content.body.to_lowercase(); - // Load keywords - let mut keywords = CONFIG.keywords.clone(); - let scams = keywords.as_object_mut().unwrap().get_mut("scams").unwrap(); - - // Turn json into Keywords - let verbs = keywords::Keywords::create("verbs", &scams["verbs"]); - let currencies = keywords::Keywords::create("currencies", &scams["currencies"]); - let socials = keywords::Keywords::create("socials", &scams["socials"]); - - // Count occurences - let mut counter = keywords::KeywordCategory::create_counter_map(); - counter.insert(keywords::KeywordCategory::Verb, verbs.find(&text_content)); - counter.insert(keywords::KeywordCategory::Currency, currencies.find(&text_content)); - counter.insert(keywords::KeywordCategory::Social, socials.find(&text_content)); - - // Test if every category has atleast one hit - let mut hit_all = true; - for (_category, count) in counter.to_owned() { - hit_all = hit_all && count != 0; + let judgement = judge::Judgement { text: text_content }; + + // Handle replies + if let Some(relation) = orig_event.to_owned().content.relates_to { + if let Some(event) = match relation { + Relation::Reply { in_reply_to } => { + let replied_event = room.event(&in_reply_to.event_id).await?; + let t = replied_event.event.get_field::<&str>("type")?; + + if t.as_deref() == Some("m.room.message") { + let event = replied_event.event.deserialize_as::()?; + Some(event.to_owned()) + } else { + None + } + }, + _ => None + } { + let event = event.as_original().unwrap(); + let content = event.content.to_owned().body().to_lowercase(); + + if event.sender == room.client().user_id().expect("Couldn't get user_id").to_string() { return Ok(()) } + + let reply_judgement = judge::Judgement { text: content }; + + match reply_judgement.judge()? { + judge::JudgementResult::Ok => (), + judge::JudgementResult::MaybeScam => (), + judge::JudgementResult::LikelyScam => { + judge::Judgement::alert(&room, &orig_event).await?; + return Ok(()); + } + } + } } - - if hit_all { - // Add stats to end of response - let response = format!("{}\nDetection stats: {:?}", scams["response"].as_str().unwrap(), counter.to_owned()); - let response_html = format!("{}
Detection stats: {:?}", scams["response_md"].as_str().unwrap(), counter.to_owned()); - - // Send message - let msg = RoomMessageEventContent::text_html(response, response_html); - let reply = msg.make_reply_to(&orig_event); - room.send(reply, None).await.expect("Couldn't send message"); - - // Send reaction - room.send_raw(json!({ - "m.relates_to": { - "rel_type": "m.annotation", - "event_id": orig_event.event_id.to_string(), - "key": "🚨🚨 SCAM 🚨🚨" - }}), "m.reaction", None).await.expect("Couldn't send reaction"); + + match judgement.judge()? { + judge::JudgementResult::Ok => return Ok(()), + judge::JudgementResult::MaybeScam => return Ok(()), + judge::JudgementResult::LikelyScam => { + judge::Judgement::alert(&room, &orig_event).await?; + return Ok(()); + } } } + + Ok(()) } #[tokio::main]