Compare commits

...

3 commits

Author SHA1 Message Date
gil 58ef806e56 Prettify + tinker with clap options 2024-06-02 01:24:54 -05:00
gil 0438084eeb Reorder planned features by priority 2024-05-31 02:15:32 -05:00
gil dede602939 Rename structs for the 1 millionth time 2024-05-31 02:13:02 -05:00
4 changed files with 144 additions and 141 deletions

View file

@ -5,8 +5,8 @@ Goal - a commandline-based tool for creating blocklists for ActivityPub software
## Planned features ## Planned features
- [ ] Accept and parse commandline arguments - [ ] Accept and parse commandline arguments
- [ ] Add ability to specify reasons - [ ] Create tiered or thresholded lists
- [ ] Support CSV and JSON
- [ ] Use URLs as sources - [ ] Use URLs as sources
- [ ] Directly request lists from API endpoints - [ ] Directly request lists from API endpoints
- [ ] Create tiered or thresholded lists - [ ] Add ability to specify reasons
- [ ] Support CSV and JSON

View file

@ -22,6 +22,10 @@ struct Cli {
#[arg(short = 'M', long)] #[arg(short = 'M', long)]
mute: Vec<PathBuf>, mute: Vec<PathBuf>,
/// Specifies a source (*.block.txt, *.mute.txt)
#[arg(short = 'S', long)]
src: Vec<PathBuf>,
/// Specifies confidence in a source. Default = 100 /// Specifies confidence in a source. Default = 100
#[arg(short, long)] #[arg(short, long)]
trust: Vec<u16>, trust: Vec<u16>,
@ -34,14 +38,17 @@ struct Cli {
} }
fn main() { fn main() {
env_logger::init(); env_logger::init(); // TODO add more logging
let cli = Cli::parse(); let cli = Cli::parse();
if cli.block.is_empty() && cli.mute.is_empty() && cli.config.is_none() { if cli.block.is_empty() && cli.mute.is_empty() && cli.config.is_none() {
error!("No lists or configuration provided."); error!("No lists or configuration provided.");
} }
// TODO parse config file if one is provided
// TODO argument parsing - IN PROGRESS // TODO read modsources from files
// TODO logging // TODO combine modsources into modmap
// TODO write modmap to files
} }

View file

@ -4,35 +4,35 @@ use log::error;
// ENUMS // ENUMS
/// Specifies a moderation action, whether block or silence /// Specifies a limit type, whether block or silence
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ModAction { pub enum Limit {
Block, Block,
Silence, Silence,
} }
// STRUCTS // STRUCTS
/// Indicates weight of moderation action on a host /// Wraps a host's weighted limits
#[derive(Debug, Default, Eq, PartialEq)] #[derive(Debug, Default, Eq, PartialEq)]
pub struct ModActionTrust { pub struct LimitIndices {
pub block: u16, pub block: u16,
pub silence: u16, pub silence: u16,
} }
impl ModActionTrust { impl LimitIndices {
pub fn add_action(&mut self, action: ModAction, trust: u16) -> &mut Self { pub fn add_limit(&mut self, limit: Limit, weight: u16) -> &mut Self {
match action { match limit {
ModAction::Block => self.block += trust, Limit::Block => self.block += weight,
ModAction::Silence => self.silence += trust, Limit::Silence => self.silence += weight,
} }
self self
} }
} }
impl From<(u16, u16)> for ModActionTrust { impl From<(u16, u16)> for LimitIndices {
/// Creates mod action weights from a tuple of two `u16` weights. Useful /// Creates indices from a tuple of two `u16` weights. Useful mostly for
/// mostly for testing. /// testing.
fn from(value: (u16, u16)) -> Self { fn from(value: (u16, u16)) -> Self {
Self { Self {
block: value.0, block: value.0,
@ -41,67 +41,69 @@ impl From<(u16, u16)> for ModActionTrust {
} }
} }
/// Contains a mapping of hosts to moderation actions as well as a trust value, /// Contains a mapping of hosts to limits as well as a trust value which is
/// which is used to weight moderation actions when building a mod map /// used to weight limits when building a merged list
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct ModSource { pub struct LimitList {
pub actions: HashMap<String, ModAction>, pub limits: HashMap<String, Limit>,
pub trust: u16, pub trust: u16,
} }
impl Default for ModSource { impl Default for LimitList {
fn default() -> Self { fn default() -> Self {
Self { Self {
actions: HashMap::new(), limits: HashMap::new(),
trust: 100, trust: 100,
} }
} }
} }
impl From<HashMap<String, ModAction>> for ModSource { impl From<HashMap<String, Limit>> for LimitList {
fn from(map: HashMap<String, ModAction>) -> Self { fn from(map: HashMap<String, Limit>) -> Self {
Self { Self {
actions: map, limits: map,
trust: 100, trust: 100,
} }
} }
} }
impl ModSource { impl LimitList {
fn add_action(&mut self, host: &str, action: ModAction) -> &mut Self { fn add_host(&mut self, host: &str, limit: Limit) -> &mut Self {
self.actions.insert(host.to_string(), action); self.limits.insert(host.to_string(), limit);
self self
} }
pub fn build(map: HashMap<String, ModAction>, trust: u16) -> Self { pub fn build(map: HashMap<String, Limit>, trust: u16) -> Self {
let mut src = Self::from(map); let mut src = Self::from(map);
src.trust = trust; src.trust = trust;
src src
} }
pub fn import_file(&mut self, path: &str, action: ModAction) -> &mut Self { pub fn import_file(&mut self, path: &str, limit: Limit) -> &mut Self {
let contents = fs::read_to_string(path).unwrap(); let contents = fs::read_to_string(path).unwrap();
for host in contents.lines().filter(|line| !line.is_empty()) { for host in contents.lines().filter(|line| !line.is_empty()) {
self.add_action(host, action); self.add_host(host, limit);
} }
self self
} }
} }
/// A map of hosts (as strings) to weighted mod actions /// A map of hosts (as strings) to their limit weights
#[derive(Debug, Default, Eq, PartialEq)] #[derive(Debug, Default, Eq, PartialEq)]
pub struct ModMap(pub HashMap<String, ModActionTrust>); pub struct MergedLimitList {
pub map: HashMap<String, LimitIndices>,
pub max: u16,
}
impl ModMap { impl MergedLimitList {
pub fn add_source(&mut self, src: ModSource) -> &mut Self { pub fn add_limit_list(&mut self, src: LimitList) -> &mut Self {
let items = src.actions.into_iter(); for (host, limit) in src.limits.into_iter() {
let entry = self.map.entry(host).or_default();
for (host, action) in items { entry.add_limit(limit, src.trust);
let entry = self.0.entry(host).or_default();
entry.add_action(action, src.trust);
} }
self.max += src.trust;
self self
} }
@ -109,18 +111,18 @@ impl ModMap {
self, self,
block_path: &str, block_path: &str,
mute_path: &str, mute_path: &str,
heat: (u16, u16), indices: (u16, u16),
) -> std::io::Result<()> { ) -> std::io::Result<()> {
if self.0.is_empty() { if self.map.is_empty() {
error!("Nothing to export!"); error!("Nothing to export!");
return Ok(()); return Ok(());
} }
let (block_thresh, mute_thresh) = heat; let (block_thresh, mute_thresh) = indices;
let mut block_output: String = String::default(); let mut block_output: String = String::default();
let mut mute_output: String = String::default(); let mut mute_output: String = String::default();
for item in self.0.into_iter() { for item in self.map.into_iter() {
let (block_trust, mute_trust) = (item.1.block, item.1.silence); let (block_trust, mute_trust) = (item.1.block, item.1.silence);
if block_trust >= block_thresh { if block_trust >= block_thresh {

View file

@ -5,13 +5,13 @@ use std::{collections::HashMap, fs};
use crate::manip::*; use crate::manip::*;
#[test] #[test]
fn modaction_add() { fn limit_add() {
let mut at = ModActionTrust::default(); let mut at = LimitIndices::default();
at.add_action(ModAction::Block, 123) at.add_limit(Limit::Block, 123)
.add_action(ModAction::Silence, 456); .add_limit(Limit::Silence, 456);
let test_at = ModActionTrust { let test_at = LimitIndices {
block: 123, block: 123,
silence: 456, silence: 456,
}; };
@ -20,14 +20,14 @@ fn modaction_add() {
} }
#[test] #[test]
fn modaction_combine() { fn limit_combine() {
let mut at = ModActionTrust::default(); let mut at = LimitIndices::default();
at.add_action(ModAction::Block, 123) at.add_limit(Limit::Block, 123)
.add_action(ModAction::Block, 333) .add_limit(Limit::Block, 333)
.add_action(ModAction::Silence, 123); .add_limit(Limit::Silence, 123);
let test_at = ModActionTrust { let test_at = LimitIndices {
block: 456, block: 456,
silence: 123, silence: 123,
}; };
@ -36,19 +36,19 @@ fn modaction_combine() {
} }
#[test] #[test]
fn modsource_from_map() { fn limitlist_from_map() {
let src1 = ModSource::from(HashMap::from([ let src1 = LimitList::from(HashMap::from([
(String::from("example.com"), ModAction::Block), (String::from("example.com"), Limit::Block),
(String::from("example.org"), ModAction::Silence), (String::from("example.org"), Limit::Silence),
(String::from("example.net"), ModAction::Block), (String::from("example.net"), Limit::Block),
])); ]));
assert_eq!( assert_eq!(
src1.actions, src1.limits,
HashMap::from([ HashMap::from([
(String::from("example.com"), ModAction::Block), (String::from("example.com"), Limit::Block),
(String::from("example.org"), ModAction::Silence), (String::from("example.org"), Limit::Silence),
(String::from("example.net"), ModAction::Block), (String::from("example.net"), Limit::Block),
]) ])
); );
@ -56,139 +56,133 @@ fn modsource_from_map() {
} }
#[test] #[test]
fn modsource_from_map_and_trust() { fn limitlist_from_map_and_trust() {
let src2 = ModSource::build( let src2 = LimitList::build(
HashMap::from([ HashMap::from([
(String::from("example.com"), ModAction::Block), (String::from("example.com"), Limit::Block),
(String::from("example.org"), ModAction::Silence), (String::from("example.org"), Limit::Silence),
(String::from("example.net"), ModAction::Block), (String::from("example.net"), Limit::Block),
]), ]),
123, 123,
); );
assert_eq!( assert_eq!(
src2.actions, src2.limits,
HashMap::from([ HashMap::from([
(String::from("example.com"), ModAction::Block), (String::from("example.com"), Limit::Block),
(String::from("example.org"), ModAction::Silence), (String::from("example.org"), Limit::Silence),
(String::from("example.net"), ModAction::Block), (String::from("example.net"), Limit::Block),
]) ])
); );
assert_eq!(src2.trust, 123); assert_eq!(src2.trust, 123);
} }
#[test] #[test]
fn modsource_from_file() { fn limitlist_from_file() {
let mut src = ModSource::default(); let mut src = LimitList::default();
src.import_file("test/example_blocklist.txt", ModAction::Block) src.import_file("test/example_blocklist.txt", Limit::Block)
.import_file("test/example_mutelist.txt", ModAction::Silence); .import_file("test/example_mutelist.txt", Limit::Silence);
let test_src = ModSource::from(HashMap::from([ let test_src = LimitList::from(HashMap::from([
(String::from("example.com"), ModAction::Block), (String::from("example.com"), Limit::Block),
(String::from("example.org"), ModAction::Block), (String::from("example.org"), Limit::Block),
(String::from("example.net"), ModAction::Silence), (String::from("example.net"), Limit::Silence),
])); ]));
assert_eq!(test_src, src); assert_eq!(test_src, src);
} }
#[test] #[test]
fn modmap_from_modsource() { fn mergedlist_from_limitlist() {
let mut ml = ModMap::default(); let mut ml = MergedLimitList::default();
let src1 = ModSource::from(HashMap::from([ let src1 = LimitList::from(HashMap::from([
(String::from("example.com"), ModAction::Block), (String::from("example.com"), Limit::Block),
(String::from("example.org"), ModAction::Silence), (String::from("example.org"), Limit::Silence),
(String::from("example.net"), ModAction::Block), (String::from("example.net"), Limit::Block),
])); ]));
let mut src2 = ModSource::default(); let mut src2 = LimitList::default();
src2.import_file("test/example_blocklist.txt", ModAction::Block) src2.import_file("test/example_blocklist.txt", Limit::Block)
.import_file("test/example_mutelist.txt", ModAction::Silence); .import_file("test/example_mutelist.txt", Limit::Silence);
ml.add_source(src1).add_source(src2); ml.add_limit_list(src1).add_limit_list(src2);
let test_ml = ModMap(HashMap::from([ let test_ml = MergedLimitList {
(String::from("example.com"), ModActionTrust::from((200, 0))), map: HashMap::from([
( (String::from("example.com"), LimitIndices::from((200, 0))),
String::from("example.org"), (String::from("example.org"), LimitIndices::from((100, 100))),
ModActionTrust::from((100, 100)), (String::from("example.net"), LimitIndices::from((100, 100))),
), ]),
( max: 200,
String::from("example.net"), };
ModActionTrust::from((100, 100)),
),
]));
assert_eq!(ml, test_ml); assert_eq!(ml, test_ml);
let src3 = ModSource::build( let src3 = LimitList::build(
HashMap::from([ HashMap::from([
(String::from("example.com"), ModAction::Block), (String::from("example.com"), Limit::Block),
(String::from("example.org"), ModAction::Silence), (String::from("example.org"), Limit::Silence),
]), ]),
200, 200,
); );
let src4 = ModSource::build( let src4 = LimitList::build(
HashMap::from([ HashMap::from([
(String::from("example.com"), ModAction::Block), (String::from("example.com"), Limit::Block),
(String::from("example.net"), ModAction::Silence), (String::from("example.net"), Limit::Silence),
]), ]),
50, 50,
); );
ml.add_source(src3).add_source(src4); ml.add_limit_list(src3).add_limit_list(src4);
let test_ml = ModMap(HashMap::from([ let test_ml = MergedLimitList {
(String::from("example.com"), ModActionTrust::from((450, 0))), map: HashMap::from([
( (String::from("example.com"), LimitIndices::from((450, 0))),
String::from("example.org"), (String::from("example.org"), LimitIndices::from((100, 300))),
ModActionTrust::from((100, 300)), (String::from("example.net"), LimitIndices::from((100, 150))),
), ]),
( max: 450,
String::from("example.net"), };
ModActionTrust::from((100, 150)),
),
]));
assert_eq!(ml, test_ml); assert_eq!(ml, test_ml);
} }
#[test] #[test]
fn modmap_export_txt() { fn mergedlist_export_txt() {
let mut ml = ModMap::default(); let mut ml = MergedLimitList::default();
let src1 = ModSource::from(HashMap::from([ let src1 = LimitList::from(HashMap::from([
(String::from("example.com"), ModAction::Block), (String::from("example.com"), Limit::Block),
(String::from("example.org"), ModAction::Silence), (String::from("example.org"), Limit::Silence),
(String::from("example.net"), ModAction::Block), (String::from("example.net"), Limit::Block),
])); ]));
let mut src2 = ModSource::default(); let mut src2 = LimitList::default();
src2.import_file("test/example_blocklist.txt", ModAction::Block) src2.import_file("test/example_blocklist.txt", Limit::Block)
.import_file("test/example_mutelist.txt", ModAction::Silence); .import_file("test/example_mutelist.txt", Limit::Silence);
let src3 = ModSource::build( let src3 = LimitList::build(
HashMap::from([ HashMap::from([
(String::from("example.com"), ModAction::Block), (String::from("example.com"), Limit::Block),
(String::from("example.org"), ModAction::Silence), (String::from("example.org"), Limit::Silence),
]), ]),
200, 200,
); );
let src4 = ModSource::build( let src4 = LimitList::build(
HashMap::from([ HashMap::from([
(String::from("example.com"), ModAction::Block), (String::from("example.com"), Limit::Block),
(String::from("example.net"), ModAction::Silence), (String::from("example.net"), Limit::Silence),
]), ]),
50, 50,
); );
ml.add_source(src1) ml.add_limit_list(src1)
.add_source(src2) .add_limit_list(src2)
.add_source(src3) .add_limit_list(src3)
.add_source(src4); .add_limit_list(src4);
let _ = ml.export_file("test/test_blocks.txt", "test/test_mutes.txt", (200, 150)); let _ = ml.export_file("test/test_blocks.txt", "test/test_mutes.txt", (200, 150));