Add room-specific configs
This commit is contained in:
parent
45b4099c1f
commit
f7c7526081
@ -1,12 +1,83 @@
|
|||||||
use serde_json::Value;
|
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 struct RoomConfig {
|
||||||
pub id: OwnedRoomId,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub id: Option<OwnedRoomId>,
|
||||||
|
#[serde(rename = "muted")]
|
||||||
pub no_reply: bool,
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub keywords: Value,
|
pub keywords: Value,
|
||||||
|
45
src/judge.rs
45
src/judge.rs
@ -1,5 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
keywords::{KeywordCategory, Keywords},
|
keywords::{KeywordCategory, Keywords},
|
||||||
|
config::RoomConfig,
|
||||||
CONFIG,
|
CONFIG,
|
||||||
};
|
};
|
||||||
use matrix_sdk::{
|
use matrix_sdk::{
|
||||||
@ -30,7 +31,7 @@ pub struct Judgement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Judgement {
|
impl Judgement {
|
||||||
pub fn judge(&self) -> anyhow::Result<JudgementResult> {
|
pub fn judge(&self, config: &RoomConfig) -> anyhow::Result<JudgementResult> {
|
||||||
// Load keywords
|
// Load keywords
|
||||||
let mut keywords = CONFIG.keywords.clone();
|
let mut keywords = CONFIG.keywords.clone();
|
||||||
let keywords = keywords
|
let keywords = keywords
|
||||||
@ -67,7 +68,7 @@ impl Judgement {
|
|||||||
Ok(JudgementResult::LikelyScam)
|
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
|
// Load keywords
|
||||||
let mut keywords = CONFIG.keywords.clone();
|
let mut keywords = CONFIG.keywords.clone();
|
||||||
let keywords = keywords
|
let keywords = keywords
|
||||||
@ -105,19 +106,24 @@ impl Judgement {
|
|||||||
|
|
||||||
// Send message
|
// Send message
|
||||||
let msg = RoomMessageEventContent::text_html(
|
let msg = RoomMessageEventContent::text_html(
|
||||||
format!("{counter:?}, {count_all}/{total}, {result:?}"),
|
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>"));
|
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");
|
room.send(msg, None).await.expect("Couldn't send message");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn alert(
|
pub async fn alert(
|
||||||
|
config: &RoomConfig,
|
||||||
room: &Joined,
|
room: &Joined,
|
||||||
event: &OriginalRoomMessageEvent,
|
event: &OriginalRoomMessageEvent,
|
||||||
result: JudgementResult,
|
result: JudgementResult,
|
||||||
is_reply: bool,
|
is_reply: bool,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
|
if config.no_reply {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let mut responses = CONFIG.responses.clone();
|
let mut responses = CONFIG.responses.clone();
|
||||||
let responses = responses.as_object_mut().unwrap();
|
let responses = responses.as_object_mut().unwrap();
|
||||||
|
|
||||||
@ -139,25 +145,28 @@ impl Judgement {
|
|||||||
|
|
||||||
// Send message
|
// Send message
|
||||||
let msg = RoomMessageEventContent::text_html(plain, html);
|
let msg = RoomMessageEventContent::text_html(plain, html);
|
||||||
room.send(msg, None).await.expect("Couldn't send message");
|
|
||||||
// Todo: Add room config?
|
if config.reply_to_scam {
|
||||||
// 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");
|
||||||
|
} else {
|
||||||
|
room.send(msg, None).await.expect("Couldn't send message");
|
||||||
|
}
|
||||||
|
|
||||||
// Send reaction
|
// Send reaction
|
||||||
if !is_reply {
|
if !is_reply {
|
||||||
room.send_raw(
|
room.send_raw(
|
||||||
json!({
|
json!({
|
||||||
"m.relates_to": {
|
"m.relates_to": {
|
||||||
"rel_type": "m.annotation",
|
"rel_type": "m.annotation",
|
||||||
"event_id": event.event_id.to_string(),
|
"event_id": event.event_id.to_string(),
|
||||||
"key": "🚨🚨 SCAM 🚨🚨"
|
"key": "🚨🚨 SCAM 🚨🚨"
|
||||||
}}),
|
}}),
|
||||||
"m.reaction",
|
"m.reaction",
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("Couldn't send reaction");
|
.expect("Couldn't send reaction");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
45
src/main.rs
45
src/main.rs
@ -8,6 +8,7 @@ use matrix_sdk::{
|
|||||||
},
|
},
|
||||||
Error, LoopCtrl,
|
Error, LoopCtrl,
|
||||||
};
|
};
|
||||||
|
use std::path::PathBuf;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
@ -15,10 +16,17 @@ pub mod judge;
|
|||||||
pub mod keywords;
|
pub mod keywords;
|
||||||
pub mod matrix;
|
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());
|
static CONFIG: Lazy<config::Config> = Lazy::new(|| config::Config::load());
|
||||||
|
|
||||||
async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) -> anyhow::Result<()> {
|
async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) -> anyhow::Result<()> {
|
||||||
if let Room::Joined(room) = room {
|
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
|
let orig_event = event
|
||||||
.to_owned()
|
.to_owned()
|
||||||
.into_full_event(OwnedRoomId::from(room.room_id()));
|
.into_full_event(OwnedRoomId::from(room.room_id()));
|
||||||
@ -28,13 +36,30 @@ async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) -> any
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
// Too short to be a scam lol
|
let text_content = text_content.body.to_lowercase();
|
||||||
if text_content.body.chars().count() < 12 {
|
let mut debug = false;
|
||||||
return Ok(());
|
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();
|
// // Too short to be a scam lol
|
||||||
let debug = text_content.contains(";spdebug");
|
// if text_content.chars().count() < 12 {
|
||||||
|
// return Ok(());
|
||||||
|
// }
|
||||||
|
|
||||||
// Ignore own messages
|
// Ignore own messages
|
||||||
if !debug
|
if !debug
|
||||||
@ -83,15 +108,16 @@ async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) -> any
|
|||||||
let reply_judgement = judge::Judgement { text: content };
|
let reply_judgement = judge::Judgement { text: content };
|
||||||
|
|
||||||
if debug {
|
if debug {
|
||||||
reply_judgement.send_debug(&room).await?;
|
reply_judgement.send_debug(&config, &room).await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
match reply_judgement.judge()? {
|
match reply_judgement.judge(&config)? {
|
||||||
judge::JudgementResult::Ok => (),
|
judge::JudgementResult::Ok => (),
|
||||||
judge::JudgementResult::MaybeScam => (),
|
judge::JudgementResult::MaybeScam => (),
|
||||||
judge::JudgementResult::LikelyScam => {
|
judge::JudgementResult::LikelyScam => {
|
||||||
judge::Judgement::alert(
|
judge::Judgement::alert(
|
||||||
|
&config,
|
||||||
&room,
|
&room,
|
||||||
&orig_event,
|
&orig_event,
|
||||||
judge::JudgementResult::LikelyScam,
|
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::Ok => return Ok(()),
|
||||||
judge::JudgementResult::MaybeScam => return Ok(()),
|
judge::JudgementResult::MaybeScam => return Ok(()),
|
||||||
judge::JudgementResult::LikelyScam => {
|
judge::JudgementResult::LikelyScam => {
|
||||||
judge::Judgement::alert(
|
judge::Judgement::alert(
|
||||||
|
&config,
|
||||||
&room,
|
&room,
|
||||||
&orig_event,
|
&orig_event,
|
||||||
judge::JudgementResult::LikelyScam,
|
judge::JudgementResult::LikelyScam,
|
||||||
@ -141,7 +168,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
"No previous session found, please run \"{} <MXID>\"",
|
"No previous session found, please run \"{} <MXID>\" to login",
|
||||||
args.get(0).unwrap()
|
args.get(0).unwrap()
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -198,18 +198,12 @@ pub async fn sync<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn persist_sync_token(sync_token: String) -> anyhow::Result<()> {
|
pub async fn persist_sync_token(sync_token: String) -> anyhow::Result<()> {
|
||||||
let data_dir = dirs::data_dir()
|
let serialized_session = fs::read_to_string(crate::SESSION_FILE.to_owned()).await?;
|
||||||
.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 mut full_session: FullSession = from_str(&serialized_session)?;
|
let mut full_session: FullSession = from_str(&serialized_session)?;
|
||||||
|
|
||||||
full_session.sync_token = Some(sync_token);
|
full_session.sync_token = Some(sync_token);
|
||||||
let serialized_session = serde_json::to_string(&full_session)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user