Add room-specific configs
This commit is contained in:
parent
45b4099c1f
commit
f7c7526081
@ -1,12 +1,83 @@
|
||||
use serde_json::Value;
|
||||
use matrix_sdk::ruma::OwnedRoomId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeSet;
|
||||
use matrix_sdk::{ruma::OwnedRoomId, room::Joined};
|
||||
use tokio::fs;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct RoomConfig {
|
||||
pub id: OwnedRoomId,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<OwnedRoomId>,
|
||||
#[serde(rename = "muted")]
|
||||
pub no_reply: bool,
|
||||
#[serde(rename = "add_reply")]
|
||||
pub reply_to_scam: bool,
|
||||
}
|
||||
|
||||
impl Default for RoomConfig {
|
||||
fn default() -> Self {
|
||||
Self { id: None, no_reply: false, reply_to_scam: false }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct RoomConfigController {
|
||||
pub rooms: BTreeSet<RoomConfig>,
|
||||
}
|
||||
|
||||
impl RoomConfigController {
|
||||
pub async fn find(&self, id: OwnedRoomId) -> Option<&RoomConfig> {
|
||||
let id = Some(id);
|
||||
for room in self.rooms.to_owned() {
|
||||
if room.id == id { return Some(self.rooms.get(&room).unwrap()) }
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub async fn create_config(&mut self, room: &Joined) -> anyhow::Result<bool> {
|
||||
let config = RoomConfig {
|
||||
id: Some(room.room_id().to_owned()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
Ok(self.rooms.insert(config))
|
||||
}
|
||||
|
||||
pub async fn find_or_create(&mut self, room: &Joined, id: OwnedRoomId) -> anyhow::Result<&RoomConfig> {
|
||||
if self.find(id.to_owned()).await.is_none() {
|
||||
self.create_config(room).await?;
|
||||
}
|
||||
|
||||
Ok(self.find(id.to_owned()).await.unwrap())
|
||||
}
|
||||
|
||||
pub async fn save(&mut self, config: &RoomConfig) -> anyhow::Result<()> {
|
||||
self.rooms.take(self.to_owned().find(config.id.to_owned().unwrap()).await.unwrap());
|
||||
self.rooms.insert(config.to_owned());
|
||||
|
||||
let serialized = serde_json::to_string_pretty(self)?;
|
||||
fs::write(crate::ROOMS_CONFIG_FILE.to_owned(), serialized).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn restore() -> anyhow::Result<Self> {
|
||||
if !std::path::Path::new(crate::ROOMS_CONFIG_FILE.to_owned().to_str().unwrap()).exists() {
|
||||
return Ok(Self { rooms: BTreeSet::<RoomConfig>::new() });
|
||||
}
|
||||
|
||||
let serialized = std::fs::read_to_string(crate::ROOMS_CONFIG_FILE.to_owned())?;
|
||||
let ctrl: Result<RoomConfigController, serde_json::Error> = serde_json::from_str(&serialized);
|
||||
if ctrl.is_err() {
|
||||
return Ok(Self { rooms: BTreeSet::<RoomConfig>::new() });
|
||||
}
|
||||
Ok(ctrl.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Config {
|
||||
pub keywords: Value,
|
||||
|
45
src/judge.rs
45
src/judge.rs
@ -1,5 +1,6 @@
|
||||
use crate::{
|
||||
keywords::{KeywordCategory, Keywords},
|
||||
config::RoomConfig,
|
||||
CONFIG,
|
||||
};
|
||||
use matrix_sdk::{
|
||||
@ -30,7 +31,7 @@ pub struct Judgement {
|
||||
}
|
||||
|
||||
impl Judgement {
|
||||
pub fn judge(&self) -> anyhow::Result<JudgementResult> {
|
||||
pub fn judge(&self, config: &RoomConfig) -> anyhow::Result<JudgementResult> {
|
||||
// Load keywords
|
||||
let mut keywords = CONFIG.keywords.clone();
|
||||
let keywords = keywords
|
||||
@ -67,7 +68,7 @@ impl Judgement {
|
||||
Ok(JudgementResult::LikelyScam)
|
||||
}
|
||||
|
||||
pub async fn send_debug(&self, room: &Joined) -> anyhow::Result<()> {
|
||||
pub async fn send_debug(&self, config: &RoomConfig, room: &Joined) -> anyhow::Result<()> {
|
||||
// Load keywords
|
||||
let mut keywords = CONFIG.keywords.clone();
|
||||
let keywords = keywords
|
||||
@ -105,19 +106,24 @@ impl Judgement {
|
||||
|
||||
// Send message
|
||||
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>"));
|
||||
format!("{counter:?}\nCategories covered: {count_all}/{total}\nVerdict: {result:?}\n{config:?}"),
|
||||
format!("<code>{counter:?}</code><br>Categories covered: <code>{count_all}/{total}</code><br>Verdict: <code>{result:?}</code><br><code>{config:?}</code>"));
|
||||
room.send(msg, None).await.expect("Couldn't send message");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn alert(
|
||||
config: &RoomConfig,
|
||||
room: &Joined,
|
||||
event: &OriginalRoomMessageEvent,
|
||||
result: JudgementResult,
|
||||
is_reply: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
if config.no_reply {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut responses = CONFIG.responses.clone();
|
||||
let responses = responses.as_object_mut().unwrap();
|
||||
|
||||
@ -139,25 +145,28 @@ impl Judgement {
|
||||
|
||||
// Send message
|
||||
let msg = RoomMessageEventContent::text_html(plain, html);
|
||||
room.send(msg, None).await.expect("Couldn't send message");
|
||||
// Todo: Add room config?
|
||||
// let reply = msg.make_reply_to(event);
|
||||
// room.send(reply, None).await.expect("Couldn't send message");
|
||||
|
||||
if config.reply_to_scam {
|
||||
let reply = msg.make_reply_to(event);
|
||||
room.send(reply, None).await.expect("Couldn't send message");
|
||||
} else {
|
||||
room.send(msg, None).await.expect("Couldn't send message");
|
||||
}
|
||||
|
||||
// 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");
|
||||
"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(())
|
||||
|
45
src/main.rs
45
src/main.rs
@ -8,6 +8,7 @@ use matrix_sdk::{
|
||||
},
|
||||
Error, LoopCtrl,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub mod config;
|
||||
@ -15,10 +16,17 @@ pub mod judge;
|
||||
pub mod keywords;
|
||||
pub mod matrix;
|
||||
|
||||
static DATA_DIR: Lazy<PathBuf> = Lazy::new(|| dirs::data_dir().expect("No data_dir found").join("scam_police"));
|
||||
static SESSION_FILE: Lazy<PathBuf> = Lazy::new(|| DATA_DIR.join("session"));
|
||||
static ROOMS_CONFIG_FILE: Lazy<PathBuf> = Lazy::new(|| DATA_DIR.join("rooms_config"));
|
||||
|
||||
static CONFIG: Lazy<config::Config> = Lazy::new(|| config::Config::load());
|
||||
|
||||
async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) -> anyhow::Result<()> {
|
||||
if let Room::Joined(room) = room {
|
||||
let mut room_ctrl = config::RoomConfigController::restore().expect("Couldn't restore room configs");
|
||||
let mut config: config::RoomConfig = room_ctrl.find_or_create(&room, room.room_id().to_owned()).await?.to_owned();
|
||||
|
||||
let orig_event = event
|
||||
.to_owned()
|
||||
.into_full_event(OwnedRoomId::from(room.room_id()));
|
||||
@ -28,13 +36,30 @@ async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) -> any
|
||||
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 mut debug = false;
|
||||
if text_content.contains(";sp") {
|
||||
let command = unsafe { text_content.get_unchecked((text_content.find(";sp").unwrap()+3)..) };
|
||||
|
||||
match command {
|
||||
"debug" => debug = true,
|
||||
|
||||
"mute" => config.no_reply = true,
|
||||
"unmute" => config.no_reply = false,
|
||||
|
||||
"replyto on" => config.reply_to_scam = true,
|
||||
"replyto off" => config.reply_to_scam = false,
|
||||
|
||||
_ => ()
|
||||
}
|
||||
|
||||
room_ctrl.save(&config).await?;
|
||||
}
|
||||
|
||||
let text_content = text_content.body.to_lowercase();
|
||||
let debug = text_content.contains(";spdebug");
|
||||
// // Too short to be a scam lol
|
||||
// if text_content.chars().count() < 12 {
|
||||
// return Ok(());
|
||||
// }
|
||||
|
||||
// Ignore own messages
|
||||
if !debug
|
||||
@ -83,15 +108,16 @@ async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) -> any
|
||||
let reply_judgement = judge::Judgement { text: content };
|
||||
|
||||
if debug {
|
||||
reply_judgement.send_debug(&room).await?;
|
||||
reply_judgement.send_debug(&config, &room).await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match reply_judgement.judge()? {
|
||||
match reply_judgement.judge(&config)? {
|
||||
judge::JudgementResult::Ok => (),
|
||||
judge::JudgementResult::MaybeScam => (),
|
||||
judge::JudgementResult::LikelyScam => {
|
||||
judge::Judgement::alert(
|
||||
&config,
|
||||
&room,
|
||||
&orig_event,
|
||||
judge::JudgementResult::LikelyScam,
|
||||
@ -104,11 +130,12 @@ async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) -> any
|
||||
}
|
||||
}
|
||||
|
||||
match judgement.judge()? {
|
||||
match judgement.judge(&config)? {
|
||||
judge::JudgementResult::Ok => return Ok(()),
|
||||
judge::JudgementResult::MaybeScam => return Ok(()),
|
||||
judge::JudgementResult::LikelyScam => {
|
||||
judge::Judgement::alert(
|
||||
&config,
|
||||
&room,
|
||||
&orig_event,
|
||||
judge::JudgementResult::LikelyScam,
|
||||
@ -141,7 +168,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
)
|
||||
} else {
|
||||
anyhow::bail!(
|
||||
"No previous session found, please run \"{} <MXID>\"",
|
||||
"No previous session found, please run \"{} <MXID>\" to login",
|
||||
args.get(0).unwrap()
|
||||
);
|
||||
};
|
||||
|
@ -198,18 +198,12 @@ pub async fn sync<'a>(
|
||||
}
|
||||
|
||||
pub async fn persist_sync_token(sync_token: String) -> anyhow::Result<()> {
|
||||
let data_dir = dirs::data_dir()
|
||||
.expect("no data_dir directory found")
|
||||
.join("scam_police");
|
||||
let session_file = data_dir.join("session");
|
||||
|
||||
let serialized_session = fs::read_to_string(&session_file).await?;
|
||||
let serialized_session = fs::read_to_string(crate::SESSION_FILE.to_owned()).await?;
|
||||
let mut full_session: FullSession = from_str(&serialized_session)?;
|
||||
|
||||
full_session.sync_token = Some(sync_token);
|
||||
let serialized_session = serde_json::to_string(&full_session)?;
|
||||
fs::write(session_file, serialized_session).await?;
|
||||
|
||||
fs::write(crate::SESSION_FILE.to_owned(), serialized_session).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user