diff --git a/config/keywords.json b/config/keywords.json index bbc75cd..4c57e6d 100644 --- a/config/keywords.json +++ b/config/keywords.json @@ -1,5 +1,5 @@ { - "scams":{ + "keywords":{ "verbs":[ "earn", "make", "making", "made", @@ -15,6 +15,7 @@ "upload", "login", "send", + "join", "buy", "check", "private" @@ -32,8 +33,6 @@ "nft", "token", "free", - "meet", - "upload", "gift", "card", "nude", @@ -59,10 +58,9 @@ "wickr", "kik", "instagram", + "dm me", "👇", "👆️", "+1", "+2" - ], - "response":"Warning! This message is likely to be a scam, hoping to lure you in and steal your money! Please visit these resources for more information:\n- https://www.sec.gov/oiea/investor-alerts-and-bulletins/digital-asset-and-crypto-investment-scams-investor-alert \n [!mods !modhelp]", - "response_md":"Warning! This message is likely to be a scam, hoping to lure you in and steal your money! Please visit these resources for more information: [!mods !modhelp]" + ] } } diff --git a/config/responses.json b/config/responses.json new file mode 100644 index 0000000..c8fe659 --- /dev/null +++ b/config/responses.json @@ -0,0 +1,18 @@ +{ + "reply": { + "Ok": null, + "MaybeScam": null, + "LikelyScam": { + "plain": "Watch out, the message you replied to has been detected as a scam! Please don't do anything they ask you to do! Stay safe", + "html": "Watch out, the message you replied to has been detected as a scam! Please don't do anything they ask you to do! Stay safe" + } + }, + "message": { + "Ok": null, + "MaybeScam": null, + "LikelyScam": { + "plain": "Warning! This message is likely to be a scam, seeking to lure you in and steal your money! Please visit these resources for more information:\n- https://www.sec.gov/oiea/investor-alerts-and-bulletins/digital-asset-and-crypto-investment-scams-investor-alert \n [!mods !modhelp]", + "html": "Warning! This message is likely to be a scam, seeking to lure you in and steal your money! Please visit these resources for more information: [!mods !modhelp]" + } + } +} \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index 10d3f8e..9682c74 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,15 +2,20 @@ use serde_json::Value; pub struct Config { pub keywords: Value, + pub responses: Value, } impl Config { pub fn load() -> Config { let keywords_reader = std::fs::File::open("config/keywords.json").expect("Couldn't find keywords.json"); - let keywords: Value = serde_json::from_reader(keywords_reader).expect("Couldn't read keywords.json"); + let keywords: Value = serde_json::from_reader(keywords_reader).expect("Couldn't read keywords"); + let responses_reader = std::fs::File::open("config/responses.json").expect("Couldn't find responses.json"); + let responses: Value = serde_json::from_reader(responses_reader).expect("Couldn't read responses"); + Self { - keywords + keywords, + responses } } } \ No newline at end of file diff --git a/src/judge.rs b/src/judge.rs index c719b75..e9aff13 100644 --- a/src/judge.rs +++ b/src/judge.rs @@ -2,27 +2,37 @@ use crate::{CONFIG, keywords::{Keywords, KeywordCategory}}; use matrix_sdk::{room::Joined, ruma::events::room::message::{RoomMessageEventContent, OriginalRoomMessageEvent}}; use serde_json::json; +#[derive(Debug)] pub enum JudgementResult { Ok, MaybeScam, // hit atleast one category LikelyScam, // hit all categories } +impl JudgementResult { + pub fn to_json_var(&self) -> &str { + match self { + Self::Ok => "Ok", + Self::MaybeScam => "MaybeScam", + Self::LikelyScam => "LikelyScam" + } + } +} + pub struct Judgement { - pub text: String, + 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(); + let keywords = keywords.as_object_mut().unwrap().get_mut("keywords").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"]); + let verbs = Keywords::create("verbs", &keywords["verbs"]); + let currencies = Keywords::create("currencies", &keywords["currencies"]); + let socials = Keywords::create("socials", &keywords["socials"]); // Count occurences let mut counter = KeywordCategory::create_counter_map(); @@ -38,33 +48,82 @@ impl Judgement { } } - 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<()> { + pub async fn send_debug(&self, room: &Joined) -> anyhow::Result<()> { + // Load keywords 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(); + let keywords = keywords.as_object_mut().unwrap().get_mut("keywords").unwrap(); + + // Turn json into Keywords + let verbs = Keywords::create("verbs", &keywords["verbs"]); + let currencies = Keywords::create("currencies", &keywords["currencies"]); + let socials = Keywords::create("socials", &keywords["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; + } + } + + let mut result = JudgementResult::LikelyScam; + if count_all == 0 { result = JudgementResult::Ok } + if count_all < total { result = JudgementResult::MaybeScam } // Send message - let msg = RoomMessageEventContent::text_html(response, response_html); + let msg = RoomMessageEventContent::text_html( + format!("{counter:?}, {count_all}/{total}, {result:?}"), + format!("{counter:?}
Categories covered: {count_all}/{total}
Verdict: {result:?}")); + room.send(msg, None).await.expect("Couldn't send message"); + + Ok(()) + } + + pub async fn alert(room: &Joined, event: &OriginalRoomMessageEvent, result: JudgementResult, is_reply: bool) -> anyhow::Result<()> { + let mut responses = CONFIG.responses.clone(); + let responses = responses.as_object_mut().unwrap(); + + // Add stats to end of response + let section = if is_reply { + responses["reply"].as_object().unwrap() + } else { + responses["message"].as_object().unwrap() + }; + + let response_type = section.get(result.to_json_var()).unwrap(); + if response_type.is_null() { + anyhow::bail!("Called alert with result that has no detection message"); + } + + let response_type = response_type.as_object().unwrap(); + let plain = response_type["plain"].as_str().unwrap(); + let html = response_type["html"].as_str().unwrap(); + + // Send message + let msg = RoomMessageEventContent::text_html(plain, 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"); + if !is_reply { + 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(()) } diff --git a/src/main.rs b/src/main.rs index aa6a1c1..8d3d28c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use matrix_sdk::{ room::Room, ruma::{events::room::message::{ - MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent, Relation, RoomMessageEvent + MessageType, OriginalSyncRoomMessageEvent, Relation, RoomMessageEvent }, OwnedRoomId}, Error, LoopCtrl, }; @@ -23,14 +23,16 @@ async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) -> any return Ok(()); }; - // Ignore own messages - 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 Ok(()) } let text_content = text_content.body.to_lowercase(); + let debug = text_content.contains(";spdebug"); + + // Ignore own messages + if !debug && event.sender == room.client().user_id().expect("Couldn't get user_id").to_string() { return Ok(()) } + let judgement = judge::Judgement { text: text_content }; // Handle replies @@ -52,15 +54,20 @@ async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) -> any 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(()) } + if !debug && event.sender == room.client().user_id().expect("Couldn't get user_id").to_string() { return Ok(()) } let reply_judgement = judge::Judgement { text: content }; + if debug { + reply_judgement.send_debug(&room).await?; + return Ok(()); + } + match reply_judgement.judge()? { judge::JudgementResult::Ok => (), judge::JudgementResult::MaybeScam => (), judge::JudgementResult::LikelyScam => { - judge::Judgement::alert(&room, &orig_event).await?; + judge::Judgement::alert(&room, &orig_event, judge::JudgementResult::LikelyScam, true).await?; return Ok(()); } } @@ -71,7 +78,7 @@ async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) -> any judge::JudgementResult::Ok => return Ok(()), judge::JudgementResult::MaybeScam => return Ok(()), judge::JudgementResult::LikelyScam => { - judge::Judgement::alert(&room, &orig_event).await?; + judge::Judgement::alert(&room, &orig_event, judge::JudgementResult::LikelyScam, false).await?; return Ok(()); } }