diff --git a/blocks.txt b/blocks.txt new file mode 100644 index 0000000..de54ac6 --- /dev/null +++ b/blocks.txt @@ -0,0 +1 @@ +example.com diff --git a/mutes.txt b/mutes.txt new file mode 100644 index 0000000..db90029 --- /dev/null +++ b/mutes.txt @@ -0,0 +1,2 @@ +example.net +example.org diff --git a/src/list.rs b/src/list.rs deleted file mode 100644 index 7a581ff..0000000 --- a/src/list.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::collections::HashMap; - -mod source; -mod tests; - -pub use source::*; - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Action { - Block, - Silence, -} - -#[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/source.rs b/src/list/source.rs deleted file mode 100644 index 3afed2f..0000000 --- a/src/list/source.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::{collections::HashMap, fs}; - -use super::*; - -#[derive(Debug, PartialEq, Eq)] -pub struct Source { - pub actions: HashMap, - pub trust: u16, -} - -impl Default for Source { - fn default() -> Self { - Self { - actions: HashMap::new(), - trust: 100, - } - } -} - -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 - } - - pub fn add_from_file(&mut self, path: &str, action: Action) -> &mut Self { - let contents = fs::read_to_string(path).unwrap(); - for host in contents.lines().filter(|line| !line.is_empty()) { - self.add_action(host, action); - } - - self - } - - fn add_action(&mut self, host: &str, action: Action) -> &mut Self { - self.actions.insert(host.to_string(), action); - self - } -} \ No newline at end of file diff --git a/src/list/tests.rs b/src/list/tests.rs deleted file mode 100644 index 98bc145..0000000 --- a/src/list/tests.rs +++ /dev/null @@ -1,144 +0,0 @@ -#![cfg(test)] - -use std::collections::HashMap; - -use super::*; - -#[test] -fn add_action() { - let mut at = ActionTrust::default(); - - at.add_action(Action::Block, 123) - .add_action(Action::Silence, 456); - - let test_at = ActionTrust { - block: 123, - silence: 456, - }; - - assert_eq!(at, test_at); -} - -#[test] -fn add_action_overlap() { - let mut at = ActionTrust::default(); - - at.add_action(Action::Block, 123) - .add_action(Action::Block, 333) - .add_action(Action::Silence, 123); - - 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 source_add_from_file() { - let mut src = Source::default(); - src.add_from_file("example_blocklist1.txt", Action::Block) - .add_from_file("example_mutelist1.txt", Action::Silence); - - let test_src = Source::from(HashMap::from([ - (String::from("example.com"), Action::Block), - (String::from("example.org"), Action::Block), - (String::from("example.net"), Action::Silence), - ])); - - assert_eq!(test_src, src); -} - -#[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 mut src2 = Source::default(); - src2.add_from_file("example_blocklist1.txt", Action::Block) - .add_from_file("example_mutelist1.txt", 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); -} diff --git a/src/main.rs b/src/main.rs index 673afcb..10336a1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ // src/main.rs -mod list; +mod manip; +mod tests; fn main() { println!("Hello, world!"); diff --git a/src/manip.rs b/src/manip.rs new file mode 100644 index 0000000..ed930bc --- /dev/null +++ b/src/manip.rs @@ -0,0 +1,133 @@ +use std::{collections::HashMap, fs}; + +use log::error; + +// ENUMS + +/// Specifies a moderation action, whether block or silence +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ModAction { + Block, + Silence, +} + +// STRUCTS + +/// Indicates weight of moderation action on a host +#[derive(Debug, Default, Eq, PartialEq)] +pub struct ModActionTrust { + pub block: u16, + pub silence: u16, +} + +impl ModActionTrust { + pub fn add_action(&mut self, action: ModAction, trust: u16) -> &mut Self { + match action { + ModAction::Block => self.block += trust, + ModAction::Silence => self.silence += trust, + } + self + } +} + +impl From<(u16, u16)> for ModActionTrust { + fn from(value: (u16, u16)) -> Self { + Self { + block: value.0, + silence: value.1, + } + } +} + +/// Contains a mapping of hosts to moderation actions as well as a trust value, +/// which is used to weight moderation actions when building a mod map +#[derive(Debug, PartialEq, Eq)] +pub struct ModSource { + pub actions: HashMap, + pub trust: u16, +} + +impl Default for ModSource { + fn default() -> Self { + Self { + actions: HashMap::new(), + trust: 100, + } + } +} + +impl From> for ModSource { + fn from(map: HashMap) -> Self { + Self { + actions: map, + trust: 100, + } + } +} + +impl ModSource { + fn add_action(&mut self, host: &str, action: ModAction) -> &mut Self { + self.actions.insert(host.to_string(), action); + self + } + + pub fn build(map: HashMap, trust: u16) -> Self { + let mut src = Self::from(map); + src.trust = trust; + src + } + + pub fn import_file(&mut self, path: &str, action: ModAction) -> &mut Self { + let contents = fs::read_to_string(path).unwrap(); + for host in contents.lines().filter(|line| !line.is_empty()) { + self.add_action(host, action); + } + + self + } +} + +/// A map of hosts (as strings) to weighted mod actions +#[derive(Debug, Default, Eq, PartialEq)] +pub struct ModMap(pub HashMap); + +impl ModMap { + pub fn add_source(&mut self, src: ModSource) -> &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 + } + + pub fn export_file(self, block_path: &str, mute_path: &str, heat: (u16, u16)) -> std::io::Result<()> { + if self.0.is_empty() { + error!("Nothing to export!"); + return Ok(()) + } + + let (block_thresh, mute_thresh) = heat; + let mut block_output: String = String::default(); + let mut mute_output: String = String::default(); + + for item in self.0.into_iter() { + let (block_trust, mute_trust) = (item.1.block, item.1.silence); + + if block_trust >= block_thresh { + block_output.push_str(&(item.0.clone() + "\n")); + } + + if mute_trust >= mute_thresh { + mute_output.push_str(&(item.0.clone() + "\n")); + } + } + + fs::write(block_path, block_output)?; + fs::write(mute_path, mute_output)?; + + Ok(()) + } +} diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..5ac1536 --- /dev/null +++ b/src/tests.rs @@ -0,0 +1 @@ +mod manip; \ No newline at end of file diff --git a/src/tests/manip.rs b/src/tests/manip.rs new file mode 100644 index 0000000..b58978a --- /dev/null +++ b/src/tests/manip.rs @@ -0,0 +1,200 @@ +#![cfg(test)] + +use std::{collections::HashMap, fs}; + +use crate::manip::*; + +#[test] +fn modaction_add() { + let mut at = ModActionTrust::default(); + + at.add_action(ModAction::Block, 123) + .add_action(ModAction::Silence, 456); + + let test_at = ModActionTrust { + block: 123, + silence: 456, + }; + + assert_eq!(at, test_at); +} + +#[test] +fn modaction_combine() { + let mut at = ModActionTrust::default(); + + at.add_action(ModAction::Block, 123) + .add_action(ModAction::Block, 333) + .add_action(ModAction::Silence, 123); + + let test_at = ModActionTrust { + block: 456, + silence: 123, + }; + + assert_eq!(at, test_at); +} + +#[test] +fn modsource_from_map() { + let src1 = ModSource::from(HashMap::from([ + (String::from("example.com"), ModAction::Block), + (String::from("example.org"), ModAction::Silence), + (String::from("example.net"), ModAction::Block), + ])); + + assert_eq!( + src1.actions, + HashMap::from([ + (String::from("example.com"), ModAction::Block), + (String::from("example.org"), ModAction::Silence), + (String::from("example.net"), ModAction::Block), + ]) + ); + + assert_eq!(src1.trust, 100); +} + +#[test] +fn modsource_from_map_and_trust() { + let src2 = ModSource::build( + HashMap::from([ + (String::from("example.com"), ModAction::Block), + (String::from("example.org"), ModAction::Silence), + (String::from("example.net"), ModAction::Block), + ]), + 123, + ); + + assert_eq!( + src2.actions, + HashMap::from([ + (String::from("example.com"), ModAction::Block), + (String::from("example.org"), ModAction::Silence), + (String::from("example.net"), ModAction::Block), + ]) + ); + assert_eq!(src2.trust, 123); +} + +#[test] +fn modsource_from_file() { + let mut src = ModSource::default(); + src.import_file("example_blocklist1.txt", ModAction::Block) + .import_file("example_mutelist1.txt", ModAction::Silence); + + let test_src = ModSource::from(HashMap::from([ + (String::from("example.com"), ModAction::Block), + (String::from("example.org"), ModAction::Block), + (String::from("example.net"), ModAction::Silence), + ])); + + assert_eq!(test_src, src); +} + +#[test] +fn modmap_from_modsource() { + let mut ml = ModMap::default(); + + let src1 = ModSource::from(HashMap::from([ + (String::from("example.com"), ModAction::Block), + (String::from("example.org"), ModAction::Silence), + (String::from("example.net"), ModAction::Block), + ])); + + let mut src2 = ModSource::default(); + src2.import_file("example_blocklist1.txt", ModAction::Block) + .import_file("example_mutelist1.txt", ModAction::Silence); + + ml.add_source(src1).add_source(src2); + + let test_ml = ModMap(HashMap::from([ + (String::from("example.com"), ModActionTrust::from((200, 0))), + ( + String::from("example.org"), + ModActionTrust::from((100, 100)), + ), + ( + String::from("example.net"), + ModActionTrust::from((100, 100)), + ), + ])); + + assert_eq!(ml, test_ml); + + let src3 = ModSource::build( + HashMap::from([ + (String::from("example.com"), ModAction::Block), + (String::from("example.org"), ModAction::Silence), + ]), + 200, + ); + + let src4 = ModSource::build( + HashMap::from([ + (String::from("example.com"), ModAction::Block), + (String::from("example.net"), ModAction::Silence), + ]), + 50, + ); + + ml.add_source(src3).add_source(src4); + + let test_ml = ModMap(HashMap::from([ + (String::from("example.com"), ModActionTrust::from((450, 0))), + ( + String::from("example.org"), + ModActionTrust::from((100, 300)), + ), + ( + String::from("example.net"), + ModActionTrust::from((100, 150)), + ), + ])); + + assert_eq!(ml, test_ml); +} + +#[test] +fn modmap_export_txt() { + let mut ml = ModMap::default(); + + let src1 = ModSource::from(HashMap::from([ + (String::from("example.com"), ModAction::Block), + (String::from("example.org"), ModAction::Silence), + (String::from("example.net"), ModAction::Block), + ])); + + let mut src2 = ModSource::default(); + src2.import_file("example_blocklist1.txt", ModAction::Block) + .import_file("example_mutelist1.txt", ModAction::Silence); + + let src3 = ModSource::build( + HashMap::from([ + (String::from("example.com"), ModAction::Block), + (String::from("example.org"), ModAction::Silence), + ]), + 200, + ); + + let src4 = ModSource::build( + HashMap::from([ + (String::from("example.com"), ModAction::Block), + (String::from("example.net"), ModAction::Silence), + ]), + 50, + ); + + ml.add_source(src1) + .add_source(src2) + .add_source(src3) + .add_source(src4); + + let _ = ml.export_file("test_blocks.txt", "test_mutes.txt", (200, 150)); + + let blocks: String = fs::read_to_string("test_blocks.txt").unwrap(); + let mutes: String = fs::read_to_string("test_mutes.txt").unwrap(); + + assert_eq!(blocks, "example.com\n"); + assert!(mutes == "example.org\nexample.net\n" || mutes == "example.net\nexample.org\n"); +} diff --git a/test_blocks.txt b/test_blocks.txt new file mode 100644 index 0000000..de54ac6 --- /dev/null +++ b/test_blocks.txt @@ -0,0 +1 @@ +example.com diff --git a/test_mutes.txt b/test_mutes.txt new file mode 100644 index 0000000..db90029 --- /dev/null +++ b/test_mutes.txt @@ -0,0 +1,2 @@ +example.net +example.org