v0.4.0 - Refactor scam detection code
This commit is contained in:
parent
0d5ac4b344
commit
4fa1490707
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -2067,7 +2067,7 @@ checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scam-police"
|
name = "scam-police"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"dirs",
|
"dirs",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "scam-police"
|
name = "scam-police"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = [ "@0xf8:projectsegfau.lt", "@jjj333:pain.agency" ]
|
authors = [ "@0xf8:projectsegfau.lt", "@jjj333:pain.agency" ]
|
||||||
|
|
||||||
|
71
src/judge.rs
Normal file
71
src/judge.rs
Normal file
@ -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<JudgementResult> {
|
||||||
|
|
||||||
|
// 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(())
|
||||||
|
}
|
||||||
|
}
|
103
src/main.rs
103
src/main.rs
@ -1,86 +1,83 @@
|
|||||||
use matrix_sdk::{
|
use matrix_sdk::{
|
||||||
room::Room,
|
room::Room,
|
||||||
ruma::{events::room::message::{
|
ruma::{events::room::message::{
|
||||||
MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent, Relation
|
MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent, Relation, RoomMessageEvent
|
||||||
}, OwnedRoomId},
|
}, OwnedRoomId},
|
||||||
Error, LoopCtrl,
|
Error, LoopCtrl,
|
||||||
};
|
};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use serde_json::json;
|
|
||||||
|
|
||||||
pub mod matrix;
|
pub mod matrix;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod keywords;
|
pub mod keywords;
|
||||||
|
pub mod judge;
|
||||||
|
|
||||||
static CONFIG: Lazy<config::Config> = Lazy::new(|| config::Config::load());
|
static CONFIG: Lazy<config::Config> = 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 {
|
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
|
// Ignore non-text
|
||||||
let MessageType::Text(text_content) = event.to_owned().content.msgtype else {
|
let MessageType::Text(text_content) = event.to_owned().content.msgtype else {
|
||||||
return;
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ignore own messages
|
// Ignore own messages
|
||||||
if event.sender == room.client().user_id().expect("Couldn't get user_id").to_string() { return }
|
if event.sender == room.client().user_id().expect("Couldn't get user_id").to_string() { return Ok(()) }
|
||||||
|
|
||||||
// Ignore replies
|
|
||||||
if let Some(relation) = orig_event.to_owned().content.relates_to {
|
|
||||||
if match relation {
|
|
||||||
Relation::Reply { in_reply_to: _ } => true,
|
|
||||||
_ => false
|
|
||||||
} {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Too short to be a scam lol
|
// 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();
|
let text_content = text_content.body.to_lowercase();
|
||||||
|
|
||||||
// Load keywords
|
let judgement = judge::Judgement { text: text_content };
|
||||||
let mut keywords = CONFIG.keywords.clone();
|
|
||||||
let scams = keywords.as_object_mut().unwrap().get_mut("scams").unwrap();
|
// Handle replies
|
||||||
|
if let Some(relation) = orig_event.to_owned().content.relates_to {
|
||||||
// Turn json into Keywords
|
if let Some(event) = match relation {
|
||||||
let verbs = keywords::Keywords::create("verbs", &scams["verbs"]);
|
Relation::Reply { in_reply_to } => {
|
||||||
let currencies = keywords::Keywords::create("currencies", &scams["currencies"]);
|
let replied_event = room.event(&in_reply_to.event_id).await?;
|
||||||
let socials = keywords::Keywords::create("socials", &scams["socials"]);
|
let t = replied_event.event.get_field::<&str>("type")?;
|
||||||
|
|
||||||
// Count occurences
|
if t.as_deref() == Some("m.room.message") {
|
||||||
let mut counter = keywords::KeywordCategory::create_counter_map();
|
let event = replied_event.event.deserialize_as::<RoomMessageEvent>()?;
|
||||||
counter.insert(keywords::KeywordCategory::Verb, verbs.find(&text_content));
|
Some(event.to_owned())
|
||||||
counter.insert(keywords::KeywordCategory::Currency, currencies.find(&text_content));
|
} else {
|
||||||
counter.insert(keywords::KeywordCategory::Social, socials.find(&text_content));
|
None
|
||||||
|
}
|
||||||
// Test if every category has atleast one hit
|
},
|
||||||
let mut hit_all = true;
|
_ => None
|
||||||
for (_category, count) in counter.to_owned() {
|
} {
|
||||||
hit_all = hit_all && count != 0;
|
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 {
|
match judgement.judge()? {
|
||||||
// Add stats to end of response
|
judge::JudgementResult::Ok => return Ok(()),
|
||||||
let response = format!("{}\nDetection stats: {:?}", scams["response"].as_str().unwrap(), counter.to_owned());
|
judge::JudgementResult::MaybeScam => return Ok(()),
|
||||||
let response_html = format!("{}<br>Detection stats: <code>{:?}</code>", scams["response_md"].as_str().unwrap(), counter.to_owned());
|
judge::JudgementResult::LikelyScam => {
|
||||||
|
judge::Judgement::alert(&room, &orig_event).await?;
|
||||||
// Send message
|
return Ok(());
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
Loading…
Reference in New Issue
Block a user