Rework detection & response system, add debug

This commit is contained in:
0xf8 2023-04-14 19:36:55 -04:00
parent 4fa1490707
commit ff2d897549
Signed by: 0xf8
GPG Key ID: 446580D758689584
5 changed files with 123 additions and 36 deletions

View File

@ -1,5 +1,5 @@
{ {
"scams":{ "keywords":{
"verbs":[ "verbs":[
"earn", "earn",
"make", "making", "made", "make", "making", "made",
@ -15,6 +15,7 @@
"upload", "upload",
"login", "login",
"send", "send",
"join",
"buy", "buy",
"check", "check",
"private" "private"
@ -32,8 +33,6 @@
"nft", "nft",
"token", "token",
"free", "free",
"meet",
"upload",
"gift", "gift",
"card", "card",
"nude", "nude",
@ -59,10 +58,9 @@
"wickr", "wickr",
"kik", "kik",
"instagram", "instagram",
"dm me",
"👇", "👆️", "👇", "👆️",
"+1", "+2" "+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 <b>scam</b>, hoping to lure you in and steal your money! Please visit these resources for more information: <ul><li><a href=\"https://www.sec.gov/oiea/investor-alerts-and-bulletins/digital-asset-and-crypto-investment-scams-investor-alert\">https://www.sec.gov/oiea/investor-alerts-and-bulletins/digital-asset-and-crypto-investment-scams-investor-alert</a></li></ul> [!mods !modhelp]"
} }
} }

18
config/responses.json Normal file
View File

@ -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 <b>scam</b>! <u>Please don't do anything they ask you to do</u>! 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 <b>scam</b>, seeking to lure you in and steal your money! Please visit these resources for more information: <ul><li><a href=\"https://www.sec.gov/oiea/investor-alerts-and-bulletins/digital-asset-and-crypto-investment-scams-investor-alert\">https://www.sec.gov/oiea/investor-alerts-and-bulletins/digital-asset-and-crypto-investment-scams-investor-alert</a></li></ul> [!mods !modhelp]"
}
}
}

View File

@ -2,15 +2,20 @@ use serde_json::Value;
pub struct Config { pub struct Config {
pub keywords: Value, pub keywords: Value,
pub responses: Value,
} }
impl Config { impl Config {
pub fn load() -> Config { pub fn load() -> Config {
let keywords_reader = std::fs::File::open("config/keywords.json").expect("Couldn't find keywords.json"); 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 { Self {
keywords keywords,
responses
} }
} }
} }

View File

@ -2,27 +2,37 @@ use crate::{CONFIG, keywords::{Keywords, KeywordCategory}};
use matrix_sdk::{room::Joined, ruma::events::room::message::{RoomMessageEventContent, OriginalRoomMessageEvent}}; use matrix_sdk::{room::Joined, ruma::events::room::message::{RoomMessageEventContent, OriginalRoomMessageEvent}};
use serde_json::json; use serde_json::json;
#[derive(Debug)]
pub enum JudgementResult { pub enum JudgementResult {
Ok, Ok,
MaybeScam, // hit atleast one category MaybeScam, // hit atleast one category
LikelyScam, // hit all categories 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 struct Judgement {
pub text: String, pub text: String
} }
impl Judgement { impl Judgement {
pub fn judge(&self) -> anyhow::Result<JudgementResult> { pub fn judge(&self) -> anyhow::Result<JudgementResult> {
// Load keywords // Load keywords
let mut keywords = CONFIG.keywords.clone(); 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 // Turn json into Keywords
let verbs = Keywords::create("verbs", &scams["verbs"]); let verbs = Keywords::create("verbs", &keywords["verbs"]);
let currencies = Keywords::create("currencies", &scams["currencies"]); let currencies = Keywords::create("currencies", &keywords["currencies"]);
let socials = Keywords::create("socials", &scams["socials"]); let socials = Keywords::create("socials", &keywords["socials"]);
// Count occurences // Count occurences
let mut counter = KeywordCategory::create_counter_map(); let mut counter = KeywordCategory::create_counter_map();
@ -38,33 +48,82 @@ impl Judgement {
} }
} }
if count_all == 0 { return Ok(JudgementResult::Ok) }; if count_all == 0 { return Ok(JudgementResult::Ok) };
if count_all < total { return Ok(JudgementResult::MaybeScam) }; if count_all < total { return Ok(JudgementResult::MaybeScam) };
Ok(JudgementResult::LikelyScam) 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 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();
// Add stats to end of response // Turn json into Keywords
let response = scams["response"].as_str().unwrap(); let verbs = Keywords::create("verbs", &keywords["verbs"]);
let response_html = scams["response_md"].as_str().unwrap(); 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 // Send message
let msg = RoomMessageEventContent::text_html(response, response_html); let msg = RoomMessageEventContent::text_html(
format!("{counter:?}, {count_all}/{total}, {result:?}"),
format!("<code>{counter:?}</code><br>Categories covered: <code>{count_all}/{total}</code><br>Verdict: <code>{result:?}</code>"));
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); let reply = msg.make_reply_to(event);
room.send(reply, None).await.expect("Couldn't send message"); room.send(reply, None).await.expect("Couldn't send message");
// Send reaction // Send reaction
room.send_raw(json!({ if !is_reply {
"m.relates_to": { room.send_raw(json!({
"rel_type": "m.annotation", "m.relates_to": {
"event_id": event.event_id.to_string(), "rel_type": "m.annotation",
"key": "🚨🚨 SCAM 🚨🚨" "event_id": event.event_id.to_string(),
}}), "m.reaction", None).await.expect("Couldn't send reaction"); "key": "🚨🚨 SCAM 🚨🚨"
}}), "m.reaction", None).await.expect("Couldn't send reaction");
}
Ok(()) Ok(())
} }

View File

@ -1,7 +1,7 @@
use matrix_sdk::{ use matrix_sdk::{
room::Room, room::Room,
ruma::{events::room::message::{ ruma::{events::room::message::{
MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent, Relation, RoomMessageEvent MessageType, OriginalSyncRoomMessageEvent, Relation, RoomMessageEvent
}, OwnedRoomId}, }, OwnedRoomId},
Error, LoopCtrl, Error, LoopCtrl,
}; };
@ -23,14 +23,16 @@ async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) -> any
return Ok(()); 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 // 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 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 }; let judgement = judge::Judgement { text: text_content };
// Handle replies // Handle replies
@ -52,15 +54,20 @@ async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) -> any
let event = event.as_original().unwrap(); let event = event.as_original().unwrap();
let content = event.content.to_owned().body().to_lowercase(); 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 }; let reply_judgement = judge::Judgement { text: content };
if debug {
reply_judgement.send_debug(&room).await?;
return Ok(());
}
match reply_judgement.judge()? { match reply_judgement.judge()? {
judge::JudgementResult::Ok => (), judge::JudgementResult::Ok => (),
judge::JudgementResult::MaybeScam => (), judge::JudgementResult::MaybeScam => (),
judge::JudgementResult::LikelyScam => { judge::JudgementResult::LikelyScam => {
judge::Judgement::alert(&room, &orig_event).await?; judge::Judgement::alert(&room, &orig_event, judge::JudgementResult::LikelyScam, true).await?;
return Ok(()); return Ok(());
} }
} }
@ -71,7 +78,7 @@ async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) -> any
judge::JudgementResult::Ok => return Ok(()), judge::JudgementResult::Ok => return Ok(()),
judge::JudgementResult::MaybeScam => return Ok(()), judge::JudgementResult::MaybeScam => return Ok(()),
judge::JudgementResult::LikelyScam => { judge::JudgementResult::LikelyScam => {
judge::Judgement::alert(&room, &orig_event).await?; judge::Judgement::alert(&room, &orig_event, judge::JudgementResult::LikelyScam, false).await?;
return Ok(()); return Ok(());
} }
} }