Filter basics

This commit is contained in:
Mike Dilger 2023-08-15 09:48:17 +12:00
parent a3631cd33e
commit fe891fe2af
5 changed files with 127 additions and 8 deletions

85
src/filter.rs Normal file
View File

@ -0,0 +1,85 @@
use crate::globals::GLOBALS;
use crate::people::Person;
use crate::profile::Profile;
use nostr_types::{Event, EventKind};
use rhai::{AST, Engine, Scope};
use std::fs;
#[derive(Clone, Copy, Debug)]
pub enum EventFilterAction {
Deny,
Allow,
MuteAuthor,
}
pub fn load_script(engine: &Engine) -> Option<AST> {
let profile = match Profile::current() {
Ok(profile) => profile,
Err(e) => {
tracing::error!("Profile failed: {}", e);
return None;
},
};
let mut path = profile.profile_dir.clone();
path.push("filter.rhai");
let script = match fs::read_to_string(&path) {
Ok(script) => script,
Err(e) => {
tracing::info!("No spam filter: {}", e);
return None;
},
};
let ast = match engine.compile(script) {
Ok(ast) => ast,
Err(e) => {
tracing::error!("Failed to compile spam filter: {}", e);
return None;
}
};
tracing::info!("Spam filter loaded.");
Some(ast)
}
pub fn filter(event: Event, author: Option<Person>) -> EventFilterAction {
let ast = match &GLOBALS.filter {
Some(ast) => ast,
None => return EventFilterAction::Allow
};
let mut scope = Scope::new();
scope.push("id", event.id.as_hex_string());
scope.push("pubkey", event.pubkey.as_hex_string());
scope.push("kind", <EventKind as Into<u32>>::into(event.kind));
// FIXME tags
scope.push("content", event.content.clone());
scope.push(
"nip05valid",
match author {
Some(a) => a.nip05_valid,
None => false,
},
);
match GLOBALS.filter_engine.call_fn::<i64>(&mut scope, &ast, "filter", ()) {
Ok(action) => match action {
0 => {
tracing::info!("SPAM FILTER BLOCKING EVENT {}", event.id.as_hex_string());
EventFilterAction::Deny
}
1 => EventFilterAction::Allow,
2 => EventFilterAction::MuteAuthor,
_ => EventFilterAction::Allow,
},
Err(ear) => {
tracing::error!("{}", ear);
EventFilterAction::Allow
}
}
}
// Only call the filter if the author isn't followed

View File

@ -14,6 +14,7 @@ use gossip_relay_picker::RelayPicker;
use nostr_types::{Event, Id, PayRequestData, Profile, PublicKey, RelayUrl, UncheckedUrl};
use parking_lot::RwLock as PRwLock;
use regex::Regex;
use rhai::{AST, Engine};
use std::collections::HashSet;
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize};
use tokio::sync::{broadcast, mpsc, Mutex, RwLock};
@ -115,6 +116,10 @@ pub struct Globals {
/// Events Processed
pub events_processed: AtomicU32,
/// Filter
pub filter_engine: Engine,
pub filter: Option<AST>,
}
lazy_static! {
@ -131,6 +136,9 @@ lazy_static! {
Err(e) => panic!("{e}")
};
let filter_engine = Engine::new();
let filter = crate::filter::load_script(&filter_engine);
Globals {
first_run: AtomicBool::new(false),
to_minions,
@ -161,6 +169,8 @@ lazy_static! {
hashtag_regex: Regex::new(r"(?:^|\W)(#[\w\p{Extended_Pictographic}]+)(?:$|\W)").unwrap(),
storage,
events_processed: AtomicU32::new(0),
filter_engine,
filter,
}
};
}

View File

@ -31,6 +31,7 @@ mod delegation;
mod error;
mod feed;
mod fetcher;
mod filter;
mod globals;
mod media;
mod nip05;

View File

@ -215,6 +215,7 @@ impl People {
});
}
// FIXME this is expensive
pub fn get_followed_pubkeys(&self) -> Vec<PublicKey> {
if let Ok(vec) = GLOBALS.storage.filter_people(|p| p.followed) {
vec.iter().map(|p| p.pubkey).collect()
@ -223,6 +224,11 @@ impl People {
}
}
// FIXME this is expensive
pub fn is_followed(&self, pubkey: &PublicKey) -> bool {
self.get_followed_pubkeys().contains(pubkey)
}
pub fn get_followed_pubkeys_needing_relay_lists(
&self,
among_these: &[PublicKey],

View File

@ -1,5 +1,6 @@
use crate::comms::ToOverlordMessage;
use crate::error::Error;
use crate::filter::EventFilterAction;
use crate::globals::GLOBALS;
use crate::person_relay::PersonRelay;
use nostr_types::{
@ -27,6 +28,22 @@ pub async fn process_new_event(
GLOBALS.events_processed.fetch_add(1, Ordering::SeqCst);
// Spam filter (displayable and author is not followed)
if event.kind.is_feed_displayable() && !GLOBALS.people.is_followed(&event.pubkey) {
let author = GLOBALS.storage.read_person(&event.pubkey)?;
match crate::filter::filter(event.clone(), author) {
EventFilterAction::Allow => {}
EventFilterAction::Deny => {
tracing::info!("SPAM FILTER: Filtered out event {}", event.id.as_hex_string());
return Ok(());
},
EventFilterAction::MuteAuthor => {
GLOBALS.people.mute(&event.pubkey, true)?;
return Ok(());
}
}
}
// Determine if we already had this event
let duplicate = GLOBALS.storage.has_event(event.id)?;
if duplicate {
@ -144,14 +161,6 @@ pub async fn process_new_event(
}
}
// Save event relationships (whether from a relay or not)
let invalid_ids = GLOBALS
.storage
.process_relationships_of_event(event, None)?;
// Invalidate UI events indicated by those relationships
GLOBALS.ui_notes_to_invalidate.write().extend(&invalid_ids);
// Save event_hashtags
if seen_on.is_some() {
let hashtags = event.hashtags();
@ -160,6 +169,14 @@ pub async fn process_new_event(
}
}
// Save event relationships (whether from a relay or not)
let invalid_ids = GLOBALS
.storage
.process_relationships_of_event(event, None)?;
// Invalidate UI events indicated by those relationships
GLOBALS.ui_notes_to_invalidate.write().extend(&invalid_ids);
// If metadata, update person
if event.kind == EventKind::Metadata {
let metadata: Metadata = serde_json::from_str(&event.content)?;