diff --git a/README.md b/README.md new file mode 100644 index 0000000..b3f123b --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# fediloom 🪢 + +Goal - a commandline-based tool for creating blocklists for ActivityPub software ("the Fediverse") + +## Planned features + +- [ ] Accept and parse commandline arguments +- [ ] Add ability to specify reasons +- [ ] Use URLs as sources +- [ ] Directly request lists from API endpoints +- [ ] Create tiered or thresholded lists +- [ ] Support CSV and JSON1 \ No newline at end of file diff --git a/src/list.rs b/src/list.rs new file mode 100644 index 0000000..c3bcbf7 --- /dev/null +++ b/src/list.rs @@ -0,0 +1,71 @@ +use std::collections::HashMap; + +#[derive(Debug, Eq, PartialEq)] +pub enum Action { + Block, + Silence, +} + +#[derive(Debug)] +pub struct Source { + pub actions: HashMap, + pub trust: u16, +} + +impl From> for Source { + fn from(map: HashMap) -> Self { + Self { + actions: map, + trust: 100, + } + } +} + +impl Source { + pub fn build(map: HashMap, trust: u16) -> Self { + let mut src = Self::from(map); + src.trust = trust; + src + } +} + +#[derive(Debug, Default, Eq, PartialEq)] +pub struct ActionTrust { + pub block: u16, + pub silence: u16, +} + +impl ActionTrust { + pub fn add_action(&mut self, action: Action, trust: u16) -> &mut Self { + match action { + Action::Block => self.block += trust, + Action::Silence => self.silence += trust, + } + self + } +} + +impl From<(u16, u16)> for ActionTrust { + fn from(value: (u16, u16)) -> Self { + Self { + block: value.0, + silence: value.1, + } + } +} + +#[derive(Debug, Default, Eq, PartialEq)] +pub struct ModerationList(pub HashMap); + +impl ModerationList { + pub fn add_source(&mut self, src: Source) -> &mut Self { + let items = src.actions.into_iter(); + + for (host, action) in items { + let entry = self.0.entry(host).or_default(); + entry.add_action(action, src.trust); + } + + self + } +} diff --git a/src/list/mod.rs b/src/list/mod.rs deleted file mode 100644 index 386206d..0000000 --- a/src/list/mod.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::collections::HashMap; - -pub struct SourceList { - pub blocks: Vec, - pub confidence: u8, -} - -impl SourceList { - pub fn new() -> Self { - SourceList { - blocks: Vec::new(), - confidence: 100, - } - } -} - -impl From> for SourceList { - fn from(blocked: Vec) -> Self { - SourceList { - blocks: blocked, - confidence: 100, - } - } -} - -pub struct TrustMaps { - pub blocks: HashMap, -} - -impl TrustMaps { - pub fn new() -> TrustMaps { - TrustMaps { - blocks: HashMap::new(), - } - } - - pub fn add_source(&mut self, src: &SourceList) -> &mut TrustMaps { - for host in src.blocks.iter() { - let entry = self.blocks.entry(host.to_string()).or_insert(0); - *entry += src.confidence as usize; - } - self - } - - pub fn remove_source(&mut self, src: &SourceList) -> &mut TrustMaps { - // If blocks already empty, nothing to remove so just return - if self.blocks.is_empty() { - return self; - } - - // Iterate over host strings in source list - for host in src.blocks.iter() { - // If the host isn't in the output list, then skip - if !self.blocks.contains_key(host) { - continue; - } - - // Host is valid entry, subtract confidence value and saturate at 0 - self.blocks.entry(host.to_string()).and_modify(|v| { - *v = v.saturating_sub(src.confidence as usize); - }); - } - - // Finally, if any value became zero, remove the pair from the map - self.blocks.retain(|_, v| *v != 0); - - self - } -} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 52b3a2a..da70c4c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,18 +3,6 @@ mod list; mod tests; -// things we need to do: -// - import 2 or more lists -// - assign "trust" percent to each list -// - combine lists together and weight each element by trust %s -// - export 1 or more lists filtered by heat rating -// - tiered lists, e.g.: -// - tier 0: instances uniformly blocked -// - tier 1: instances mixed blocked and silenced -// - tier 2: instances silenced -// - tier 3: instances mixed silenced and no action -// - tier 4: instances no action (i.e., below all thresholds) - fn main() { println!("Hello, world!"); } \ No newline at end of file diff --git a/src/tests.rs b/src/tests.rs index 35c618e..8bc3831 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,62 +1,131 @@ #![cfg(test)] +use std::collections::HashMap; + use super::*; use list::*; -use std::collections::HashMap; #[test] -fn add_lists() { - let hosts1 = vec![ - String::from("example.com"), - String::from("example.org"), - String::from("example.net"), - ]; - let hosts2 = vec![String::from("example.com"), String::from("example.org")]; - let hosts3 = vec![String::from("example.com")]; +fn add_action() { + let mut at = ActionTrust::default(); - let src1 = SourceList::from(hosts1); - let src2 = SourceList::from(hosts2); - let src3 = SourceList::from(hosts3); + at.add_action(Action::Block, 123) + .add_action(Action::Silence, 456); - let mut output = TrustMaps::new(); - output.add_source(&src1).add_source(&src2).add_source(&src3); + let test_at = ActionTrust { + block: 123, + silence: 456, + }; - let test_map = HashMap::from([ - (String::from("example.com"), 300), - (String::from("example.org"), 200), - (String::from("example.net"), 100), - ]); - assert_eq!(test_map, output.blocks); + assert_eq!(at, test_at); } #[test] -fn remove_lists() { - let hosts1 = vec![ - String::from("example.com"), - String::from("example.org"), - String::from("example.net"), - ]; - let hosts2 = vec![String::from("example.com"), String::from("example.org")]; - let hosts3 = vec![String::from("example.com")]; +fn add_action_overlap() { + let mut at = ActionTrust::default(); - let src1 = SourceList::from(hosts1); - let src2 = SourceList::from(hosts2); - let src3 = SourceList::from(hosts3); + at.add_action(Action::Block, 123) + .add_action(Action::Block, 333) + .add_action(Action::Silence, 123); - let mut output = TrustMaps::new(); - output - .add_source(&src1) - .add_source(&src2) - .add_source(&src3) - .remove_source(&src3) - .remove_source(&src3) - .remove_source(&src3) - .remove_source(&src3); + let test_at = ActionTrust { + block: 456, + silence: 123, + }; + + assert_eq!(at, test_at); +} + +#[test] +fn source_new_from_hashmap() { + let src1 = Source::from(HashMap::from([ + (String::from("example.com"), Action::Block), + (String::from("example.org"), Action::Silence), + (String::from("example.net"), Action::Block), + ])); + + assert_eq!( + src1.actions, + HashMap::from([ + (String::from("example.com"), Action::Block), + (String::from("example.org"), Action::Silence), + (String::from("example.net"), Action::Block), + ]) + ); + + assert_eq!(src1.trust, 100); +} + +#[test] +fn source_build_from_hashmap() { + let src2 = Source::build( + HashMap::from([ + (String::from("example.com"), Action::Block), + (String::from("example.org"), Action::Silence), + (String::from("example.net"), Action::Block), + ]), + 123, + ); + + assert_eq!( + src2.actions, + HashMap::from([ + (String::from("example.com"), Action::Block), + (String::from("example.org"), Action::Silence), + (String::from("example.net"), Action::Block), + ]) + ); + assert_eq!(src2.trust, 123); +} + +#[test] +fn build_moderation_list() { + let mut ml = ModerationList::default(); + + let src1 = Source::from(HashMap::from([ + (String::from("example.com"), Action::Block), + (String::from("example.org"), Action::Silence), + (String::from("example.net"), Action::Block), + ])); + + let src2 = Source::from(HashMap::from([ + (String::from("example.com"), Action::Block), + (String::from("example.org"), Action::Block), + (String::from("example.net"), Action::Silence), + ])); + + ml.add_source(src1).add_source(src2); + + let test_ml = ModerationList( + HashMap::from([ + (String::from("example.com"), ActionTrust::from((200, 0))), + (String::from("example.org"), ActionTrust::from((100, 100))), + (String::from("example.net"), ActionTrust::from((100, 100))), + ]) + ); + + assert_eq!(ml, test_ml); + + let src3 = Source::build(HashMap::from([ + (String::from("example.com"), Action::Block), + (String::from("example.org"), Action::Silence), + ]), 200); + + let src4 = Source::build(HashMap::from([ + (String::from("example.com"), Action::Block), + (String::from("example.net"), Action::Silence), + ]), 50); + + ml.add_source(src3).add_source(src4); + + let test_ml = ModerationList( + HashMap::from([ + (String::from("example.com"), ActionTrust::from((450, 0))), + (String::from("example.org"), ActionTrust::from((100, 300))), + (String::from("example.net"), ActionTrust::from((100, 150))), + ]) + ); + + assert_eq!(ml, test_ml); - let test_map = HashMap::from([ - (String::from("example.org"), 200), - (String::from("example.net"), 100), - ]); - - assert_eq!(test_map, output.blocks); }