diff --git a/Cargo.lock b/Cargo.lock index 863ba62..8d184c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2067,7 +2067,7 @@ checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "scam-police" -version = "0.4.0" +version = "0.5.0" dependencies = [ "anyhow", "dirs", diff --git a/Cargo.toml b/Cargo.toml index bcd40d6..1c931c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "scam-police" -version = "0.4.0" +version = "0.5.0" edition = "2021" authors = [ "@0xf8:projectsegfau.lt", "@jjj333:pain.agency" ] diff --git a/src/config.rs b/src/config.rs index 9682c74..5a6b092 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,15 +7,19 @@ pub struct Config { 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"); - - 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"); + 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"); + + 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, - responses + responses, } } -} \ No newline at end of file +} diff --git a/src/judge.rs b/src/judge.rs index e9aff13..1429e3e 100644 --- a/src/judge.rs +++ b/src/judge.rs @@ -1,11 +1,17 @@ -use crate::{CONFIG, keywords::{Keywords, KeywordCategory}}; -use matrix_sdk::{room::Joined, ruma::events::room::message::{RoomMessageEventContent, OriginalRoomMessageEvent}}; +use crate::{ + keywords::{KeywordCategory, Keywords}, + CONFIG, +}; +use matrix_sdk::{ + room::Joined, + ruma::events::room::message::{OriginalRoomMessageEvent, RoomMessageEventContent}, +}; use serde_json::json; #[derive(Debug)] pub enum JudgementResult { Ok, - MaybeScam, // hit atleast one category + MaybeScam, // hit atleast one category LikelyScam, // hit all categories } @@ -14,25 +20,29 @@ impl JudgementResult { match self { Self::Ok => "Ok", Self::MaybeScam => "MaybeScam", - Self::LikelyScam => "LikelyScam" + 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 keywords = keywords.as_object_mut().unwrap().get_mut("keywords").unwrap(); + let keywords = keywords + .as_object_mut() + .unwrap() + .get_mut("keywords") + .unwrap(); // Turn json into Keywords - let verbs = Keywords::create("verbs", &keywords["verbs"]); + let verbs = Keywords::create("verbs", &keywords["verbs"]); let currencies = Keywords::create("currencies", &keywords["currencies"]); - let socials = Keywords::create("socials", &keywords["socials"]); + let socials = Keywords::create("socials", &keywords["socials"]); // Count occurences let mut counter = KeywordCategory::create_counter_map(); @@ -47,21 +57,29 @@ impl Judgement { count_all = count_all + 1; } } - - if count_all == 0 { return Ok(JudgementResult::Ok) }; - if count_all < total { return Ok(JudgementResult::MaybeScam) }; + + if count_all == 0 { + return Ok(JudgementResult::Ok); + }; + if count_all < total { + return Ok(JudgementResult::MaybeScam); + }; Ok(JudgementResult::LikelyScam) } - + pub async fn send_debug(&self, room: &Joined) -> anyhow::Result<()> { // Load keywords let mut keywords = CONFIG.keywords.clone(); - let keywords = keywords.as_object_mut().unwrap().get_mut("keywords").unwrap(); + let keywords = keywords + .as_object_mut() + .unwrap() + .get_mut("keywords") + .unwrap(); // Turn json into Keywords - let verbs = Keywords::create("verbs", &keywords["verbs"]); + let verbs = Keywords::create("verbs", &keywords["verbs"]); let currencies = Keywords::create("currencies", &keywords["currencies"]); - let socials = Keywords::create("socials", &keywords["socials"]); + let socials = Keywords::create("socials", &keywords["socials"]); // Count occurences let mut counter = KeywordCategory::create_counter_map(); @@ -78,8 +96,12 @@ impl Judgement { } let mut result = JudgementResult::LikelyScam; - if count_all == 0 { result = JudgementResult::Ok } - if count_all < total { result = JudgementResult::MaybeScam } + if count_all == 0 { + result = JudgementResult::Ok + } + if count_all < total { + result = JudgementResult::MaybeScam + } // Send message let msg = RoomMessageEventContent::text_html( @@ -90,11 +112,16 @@ impl Judgement { Ok(()) } - pub async fn alert(room: &Joined, event: &OriginalRoomMessageEvent, result: JudgementResult, is_reply: bool) -> anyhow::Result<()> { + 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 + + // Determine which message to send let section = if is_reply { responses["reply"].as_object().unwrap() } else { @@ -117,14 +144,20 @@ impl Judgement { // 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"); + 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/keywords.rs b/src/keywords.rs index e05ef68..eb2f634 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -10,54 +10,52 @@ pub enum KeywordCategory { impl std::fmt::Display for KeywordCategory { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { use KeywordCategory::*; - + match self { - Verb => write!(f, "Verb"), + Verb => write!(f, "Verb"), Currency => write!(f, "Currency"), - Social => write!(f, "Social"), + Social => write!(f, "Social"), } } } - impl KeywordCategory { pub fn to_json_var(&self) -> &str { use KeywordCategory::*; - + match self { - Verb => "verbs", + Verb => "verbs", Currency => "currencies", - Social => "socials", + Social => "socials", } } - - pub fn from_json_var(var: &str) -> Result { + + pub fn from_json_var(var: &str) -> Result { use KeywordCategory::*; - + match var { - "verbs" => Ok(Verb), + "verbs" => Ok(Verb), "currencies" => Ok(Currency), - "socials" => Ok(Social), - _ => Err(()) + "socials" => Ok(Social), + _ => Err(()), } } - - pub fn create_counter_map() -> HashMap { + + pub fn create_counter_map() -> HashMap { use KeywordCategory::*; - - let mut map: HashMap = HashMap::new(); + + let mut map: HashMap = HashMap::new(); map.insert(Verb, 0); map.insert(Currency, 0); map.insert(Social, 0); - + map } } - pub struct Keywords { pub category: KeywordCategory, - pub words: Vec + pub words: Vec, } impl Keywords { @@ -66,24 +64,24 @@ impl Keywords { let Ok(category) = KeywordCategory::from_json_var(name) else { panic!("Couldn't translate \"{name}\" to KeywordCategory"); }; - + Self { category, - words: v.to_vec() + words: v.to_vec(), } } - + pub fn find(&self, hay: &str) -> u64 { let mut hits: u64 = 0; - + for kw in self.words.to_owned().into_iter() { let kw = kw.as_str().unwrap(); - + if hay.contains(kw) { hits += 1 } } - + hits } -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index 8d3d28c..3157190 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,37 +1,52 @@ use matrix_sdk::{ room::Room, - ruma::{events::room::message::{ - MessageType, OriginalSyncRoomMessageEvent, Relation, RoomMessageEvent - }, OwnedRoomId}, + ruma::{ + events::room::message::{ + MessageType, OriginalSyncRoomMessageEvent, Relation, RoomMessageEvent, + }, + OwnedRoomId, + }, Error, LoopCtrl, }; use once_cell::sync::Lazy; -pub mod matrix; pub mod config; -pub mod keywords; pub mod judge; +pub mod keywords; +pub mod matrix; static CONFIG: Lazy = Lazy::new(|| config::Config::load()); 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())); - + 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 Ok(()); }; - + // Too short to be a scam lol - if text_content.body.chars().count() < 12 { return Ok(()) } - + 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(()) } + 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 }; @@ -48,13 +63,22 @@ async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) -> any } else { None } - }, - _ => None + } + _ => None, } { let event = event.as_original().unwrap(); let content = event.content.to_owned().body().to_lowercase(); - if !debug && 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 }; @@ -67,7 +91,13 @@ async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) -> any judge::JudgementResult::Ok => (), judge::JudgementResult::MaybeScam => (), judge::JudgementResult::LikelyScam => { - judge::Judgement::alert(&room, &orig_event, judge::JudgementResult::LikelyScam, true).await?; + judge::Judgement::alert( + &room, + &orig_event, + judge::JudgementResult::LikelyScam, + true, + ) + .await?; return Ok(()); } } @@ -78,7 +108,13 @@ 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, judge::JudgementResult::LikelyScam, false).await?; + judge::Judgement::alert( + &room, + &orig_event, + judge::JudgementResult::LikelyScam, + false, + ) + .await?; return Ok(()); } } @@ -99,9 +135,15 @@ async fn main() -> anyhow::Result<()> { let (client, sync_token) = if session_file.exists() { matrix::restore_session(&session_file).await? } else if args.len() > 1 { - (matrix::login(&data_dir, &session_file, args.get(1).unwrap().to_owned()).await?, None) + ( + matrix::login(&data_dir, &session_file, args.get(1).unwrap().to_owned()).await?, + None, + ) } else { - anyhow::bail!("No previous session found, please run \"{} \"", args.get(0).unwrap()); + anyhow::bail!( + "No previous session found, please run \"{} \"", + args.get(0).unwrap() + ); }; let (client, sync_settings) = match matrix::sync(client, sync_token).await { @@ -114,15 +156,17 @@ async fn main() -> anyhow::Result<()> { client.add_event_handler(on_room_message); - client.sync_with_result_callback(sync_settings, |sync_result| async move { - let response = sync_result?; + client + .sync_with_result_callback(sync_settings, |sync_result| async move { + let response = sync_result?; - matrix::persist_sync_token(response.next_batch) + matrix::persist_sync_token(response.next_batch) .await .map_err(|err| Error::UnknownError(err.into()))?; - Ok(LoopCtrl::Continue) - }).await?; + Ok(LoopCtrl::Continue) + }) + .await?; Ok(()) } diff --git a/src/matrix.rs b/src/matrix.rs index 4ce1a09..ed7e834 100644 --- a/src/matrix.rs +++ b/src/matrix.rs @@ -1,12 +1,12 @@ use matrix_sdk::{ - config::SyncSettings, ruma::api::client::filter::FilterDefinition, Client, Session + config::SyncSettings, ruma::api::client::filter::FilterDefinition, Client, Session, }; use rand::{distributions::Alphanumeric, thread_rng, Rng}; -use serde::{Deserialize, Serialize}; -use serde_json::{Value, from_str}; -use std::path::{Path, PathBuf}; use reqwest::Client as http; use rpassword::prompt_password; +use serde::{Deserialize, Serialize}; +use serde_json::{from_str, Value}; +use std::path::{Path, PathBuf}; use tokio::fs; #[derive(Debug, Serialize, Deserialize)] @@ -24,7 +24,6 @@ pub struct FullSession { sync_token: Option, } - // // Matrix Login & Init // @@ -84,33 +83,33 @@ pub async fn build_client(data_dir: &Path, hs: String) -> anyhow::Result<(Client .collect(); match Client::builder() - .homeserver_url(&hs) - .sled_store(&db_path, Some(&passphrase))? - .build() - .await - { - Ok(client) => { - println!("[*] Homeserver OK"); - return Ok(( - client, - ClientSession { - homeserver: hs, - db_path, - passphrase, - }, - )) - } - Err(error) => match &error { - matrix_sdk::ClientBuildError::AutoDiscovery(_) - | matrix_sdk::ClientBuildError::Url(_) - | matrix_sdk::ClientBuildError::Http(_) => { - anyhow::bail!("[!] {error:?}"); - } - _ => { - return Err(error.into()); - } - }, + .homeserver_url(&hs) + .sled_store(&db_path, Some(&passphrase))? + .build() + .await + { + Ok(client) => { + println!("[*] Homeserver OK"); + return Ok(( + client, + ClientSession { + homeserver: hs, + db_path, + passphrase, + }, + )); } + Err(error) => match &error { + matrix_sdk::ClientBuildError::AutoDiscovery(_) + | matrix_sdk::ClientBuildError::Url(_) + | matrix_sdk::ClientBuildError::Http(_) => { + anyhow::bail!("[!] {error:?}"); + } + _ => { + return Err(error.into()); + } + }, + } } // @@ -141,7 +140,10 @@ pub async fn resolve_homeserver(homeserver: String) -> anyhow::Result { hs.pop(); } - let ident = http::new().get(format!("{hs}/.well-known/matrix/client")).send().await; + let ident = http::new() + .get(format!("{hs}/.well-known/matrix/client")) + .send() + .await; match ident { Ok(r) => { let body = r.text().await?; @@ -150,14 +152,11 @@ pub async fn resolve_homeserver(homeserver: String) -> anyhow::Result { let discovered = json["m.homeserver"]["base_url"].as_str().unwrap(); Ok(discovered.to_string()) - }, - Err(e) => { - Err(e.into()) } + Err(e) => Err(e.into()), } } - // // Persistence // @@ -233,4 +232,4 @@ pub async fn restore_session(session_file: &Path) -> anyhow::Result<(Client, Opt client.restore_login(user_session).await?; Ok((client, sync_token)) -} \ No newline at end of file +}