You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
group-actor/src/command.rs

468 lines
16 KiB

use once_cell::sync::Lazy;
use regex::{Regex, RegexSetBuilder};
#[derive(Debug, Clone, PartialEq)]
pub enum StatusCommand {
Boost,
Ignore,
BanUser(String),
UnbanUser(String),
BanServer(String),
UnbanServer(String),
AddMember(String),
RemoveMember(String),
GrantAdmin(String),
RemoveAdmin(String),
Announce(String),
OpenGroup,
CloseGroup,
Help,
ListMembers,
Leave,
}
macro_rules! p_user {
() => {
r"(@?[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.-]+\.[a-z0-9_-]+|@[a-zA-Z0-9_.-]+)"
}
}
macro_rules! p_server {
() => {
r"([a-zA-Z0-9_.-]+\.[a-zA-Z0-9_-]+)"
}
}
macro_rules! command {
($($val:expr),+) => {
Regex::new(concat!(r"(?:^|\s|>|\n)[\\/]", $($val,)+ r"(?:$|[!,]|\W)")).unwrap()
}
}
static RE_BOOST: once_cell::sync::Lazy<Regex> = Lazy::new(|| {
command!(r"b(?:oost)?")
});
static RE_IGNORE: once_cell::sync::Lazy<Regex> = Lazy::new(|| {
command!(r"i(?:g(?:n(?:ore)?)?)?")
});
static RE_BAN_USER: once_cell::sync::Lazy<Regex> = Lazy::new(|| {
command!(r"ban\s+", p_user!())
});
static RE_UNBAN_USER: once_cell::sync::Lazy<Regex> = Lazy::new(|| {
command!(r"unban\s+", p_user!())
});
static RE_BAN_SERVER: once_cell::sync::Lazy<Regex> = Lazy::new(|| {
command!(r"ban\s+", p_server!())
});
static RE_UNBAN_SERVER: once_cell::sync::Lazy<Regex> = Lazy::new(|| {
command!(r"unban\s+", p_server!())
});
static RE_ADD_MEMBER: once_cell::sync::Lazy<Regex> = Lazy::new(|| {
command!(r"(?:add)\s+", p_user!())
});
static RE_REMOVE_MEMBER: once_cell::sync::Lazy<Regex> = Lazy::new(|| {
command!(r"(?:kick|remove)\s+", p_user!())
});
static RE_GRANT_ADMIN: once_cell::sync::Lazy<Regex> = Lazy::new(|| {
command!(r"(?:op|admin)\s+", p_user!())
});
static RE_REVOKE_ADMIN: once_cell::sync::Lazy<Regex> = Lazy::new(|| {
command!(r"(?:deop|deadmin|revoke)\s+", p_user!())
});
static RE_OPEN_GROUP: once_cell::sync::Lazy<Regex> = Lazy::new(|| {
command!(r"opengroup")
});
static RE_CLOSE_GROUP: once_cell::sync::Lazy<Regex> = Lazy::new(|| {
command!(r"closegroup")
});
static RE_HELP: once_cell::sync::Lazy<Regex> = Lazy::new(|| {
command!(r"help")
});
static RE_MEMBERS: once_cell::sync::Lazy<Regex> = Lazy::new(|| {
command!(r"(?:members)")
});
static RE_LEAVE: once_cell::sync::Lazy<Regex> = Lazy::new(|| {
command!(r"(?:leave)")
});
static RE_ANNOUNCE: once_cell::sync::Lazy<Regex> = Lazy::new(|| {
Regex::new(concat!(r"(?:^|\s|>|\n)[\\/]announce\s+(.*)$")).unwrap()
});
pub fn parse_status(content: &str) -> Vec<StatusCommand> {
debug!("Raw content: {}", content);
let content = content.replace("<br/>", " ");
// let content = content.replace("<br />", " ");
// let content = content.replace("<BR/>", " ");
// let content = content.replace("<BR />", " ");
let content = voca_rs::strip::strip_tags(&content);
debug!("Stripped tags: {}", content);
// short-circuiting commands
if RE_IGNORE.is_match(&content) {
debug!("IGNORE");
return vec![StatusCommand::Ignore];
}
if RE_HELP.is_match(&content) {
debug!("HELP");
return vec![StatusCommand::Help];
}
// additive commands
let mut commands = vec![];
// one-use commands
if RE_BOOST.is_match(&content) {
debug!("BOOST");
commands.push(StatusCommand::Boost);
}
if RE_LEAVE.is_match(&content) {
debug!("LEAVE");
commands.push(StatusCommand::Leave);
}
if RE_MEMBERS.is_match(&content) {
debug!("MEMBERS");
commands.push(StatusCommand::ListMembers);
}
if RE_OPEN_GROUP.is_match(&content) {
debug!("OPEN GROUP");
commands.push(StatusCommand::OpenGroup);
}
if RE_CLOSE_GROUP.is_match(&content) {
debug!("CLOSE GROUP");
commands.push(StatusCommand::CloseGroup);
}
if let Some(c) = RE_ANNOUNCE.captures(&content) {
if let Some(s) = c.get(1) {
let s = s.as_str().trim();
debug!("ANNOUNCE: «{}»", s);
commands.push(StatusCommand::Announce(s.to_owned()));
}
}
// multi-occurence commands
for c in RE_BAN_USER.captures_iter(&content) {
if let Some(s) = c.get(1) {
let s = s.as_str();
let s = s.trim_start_matches('@');
debug!("BAN USER: {}", s);
commands.push(StatusCommand::BanUser(s.to_owned()));
}
}
for c in RE_UNBAN_USER.captures_iter(&content) {
if let Some(s) = c.get(1) {
let s = s.as_str();
let s = s.trim_start_matches('@');
debug!("UNBAN USER: {}", s);
commands.push(StatusCommand::UnbanUser(s.to_owned()));
}
}
for c in RE_BAN_SERVER.captures_iter(&content) {
if let Some(s) = c.get(1) {
debug!("BAN SERVER: {}", s.as_str());
commands.push(StatusCommand::BanServer(s.as_str().to_owned()));
}
}
for c in RE_UNBAN_SERVER.captures_iter(&content) {
if let Some(s) = c.get(1) {
debug!("UNBAN SERVER: {}", s.as_str());
commands.push(StatusCommand::UnbanServer(s.as_str().to_owned()));
}
}
for c in RE_ADD_MEMBER.captures_iter(&content) {
if let Some(s) = c.get(1) {
let s = s.as_str();
let s = s.trim_start_matches('@');
debug!("ADD MEMBER: {}", s);
commands.push(StatusCommand::AddMember(s.to_owned()));
}
}
for c in RE_REMOVE_MEMBER.captures_iter(&content) {
if let Some(s) = c.get(1) {
let s = s.as_str();
let s = s.trim_start_matches('@');
debug!("UNBAN USER: {}", s);
commands.push(StatusCommand::RemoveMember(s.to_owned()));
}
}
for c in RE_GRANT_ADMIN.captures_iter(&content) {
if let Some(s) = c.get(1) {
let s = s.as_str();
let s = s.trim_start_matches('@');
debug!("ADD ADMIN: {}", s);
commands.push(StatusCommand::GrantAdmin(s.to_owned()));
}
}
for c in RE_REVOKE_ADMIN.captures_iter(&content) {
if let Some(s) = c.get(1) {
let s = s.as_str();
let s = s.trim_start_matches('@');
debug!("REMOVE ADMIN: {}", s);
commands.push(StatusCommand::RemoveAdmin(s.to_owned()));
}
}
commands
}
#[cfg(test)]
mod test {
use crate::command::{parse_status, StatusCommand};
use super::{RE_ADD_MEMBER, RE_ANNOUNCE, RE_BAN_SERVER, RE_BAN_USER, RE_BOOST, RE_CLOSE_GROUP, RE_GRANT_ADMIN,
RE_HELP, RE_IGNORE, RE_LEAVE, RE_MEMBERS, RE_OPEN_GROUP, RE_REMOVE_MEMBER, RE_REVOKE_ADMIN};
#[test]
fn test_boost() {
assert!(RE_BOOST.is_match("/b"));
assert!(RE_BOOST.is_match(">/b"));
assert!(RE_BOOST.is_match("/b mm"));
assert!(RE_BOOST.is_match("/b."));
assert!(RE_BOOST.is_match("\\b"));
assert!(!RE_BOOST.is_match("boo/b"));
assert!(RE_BOOST.is_match("bla\n/b"));
assert!(RE_BOOST.is_match("/boost"));
assert!(RE_BOOST.is_match("/boost\n"));
assert!(RE_BOOST.is_match("/boost dfdfg"));
assert!(!RE_BOOST.is_match("/boosty"));
assert!(RE_BOOST.is_match("/b\nxxx"));
assert!(!RE_BOOST.is_match("/bleble\n"));
}
#[test]
fn test_ignore() {
assert!(RE_IGNORE.is_match("/i"));
assert!(RE_IGNORE.is_match("/ig"));
assert!(RE_IGNORE.is_match("/ign"));
assert!(RE_IGNORE.is_match("/i mm"));
assert!(RE_IGNORE.is_match("/i."));
assert!(RE_IGNORE.is_match("\\i"));
assert!(!RE_IGNORE.is_match("boo/i"));
assert!(RE_IGNORE.is_match("bla\n/i"));
assert!(RE_IGNORE.is_match("/ignore"));
assert!(RE_IGNORE.is_match("/ignore x"));
assert!(RE_IGNORE.is_match("/ignore\n"));
assert!(RE_IGNORE.is_match("/ignore dfdfg"));
assert!(!RE_IGNORE.is_match("/ignorey"));
assert!(RE_IGNORE.is_match("/i\nxxx"));
assert!(!RE_IGNORE.is_match("/ileble\n"));
assert!(!RE_IGNORE.is_match("/ignx"));
}
#[test]
fn test_ban_user() {
assert!(RE_BAN_USER.is_match("/ban lain@pleroma.soykaf.com"));
assert!(RE_BAN_USER.is_match("/ban lain@stupidname.uk"));
assert!(RE_BAN_USER.is_match("bababababa /ban lain@stupidname.uk lala"));
assert!(!RE_BAN_USER.is_match("/ban stupidname.uk"));
assert!(RE_BAN_USER.is_match("/ban @lain"));
assert!(RE_BAN_USER.is_match("/ban @lain aaa"));
assert!(RE_BAN_USER.is_match("/ban \t lain@pleroma.soykaf.com"));
assert!(RE_BAN_USER.is_match("/ban @lain@pleroma.soykaf.com"));
assert!(RE_BAN_USER.is_match("/ban @l-a_i.n9@xn--999pleroma-weirdname.com"));
assert!(RE_BAN_USER.is_match("/ban @LAIN@PleromA.soykaf.com"));
let c = RE_BAN_USER.captures("/ban lain@pleroma.soykaf.com");
assert!(c.is_some());
assert_eq!(c.unwrap().get(1).unwrap().as_str(), "lain@pleroma.soykaf.com");
let c = RE_BAN_USER.captures("/ban lain@pleroma.soykaf.com xx");
assert!(c.is_some());
assert_eq!(c.unwrap().get(1).unwrap().as_str(), "lain@pleroma.soykaf.com");
let c = RE_BAN_USER.captures("/ban @lain@pleroma.soykaf.com");
assert!(c.is_some());
assert_eq!(c.unwrap().get(1).unwrap().as_str(), "@lain@pleroma.soykaf.com");
let c = RE_BAN_USER.captures("/ban @lain");
assert!(c.is_some());
assert_eq!(c.unwrap().get(1).unwrap().as_str(), "@lain");
let c = RE_BAN_USER.captures("/ban @lain xx");
assert!(c.is_some());
assert_eq!(c.unwrap().get(1).unwrap().as_str(), "@lain");
}
#[test]
fn test_ban_server() {
assert!(!RE_BAN_SERVER.is_match("/ban lain@pleroma.soykaf.com"));
assert!(RE_BAN_SERVER.is_match("/ban pleroma.soykaf.com"));
assert!(RE_BAN_SERVER.is_match("/ban xn--999pleroma-weirdname.com"));
assert!(RE_BAN_SERVER.is_match("/ban \t xn--999pleroma-weirdname.com"));
assert!(RE_BAN_SERVER.is_match("mamama /ban pleroma.soykaf.com momomo"));
assert!(!RE_BAN_SERVER.is_match("/ban @pleroma.soykaf.com"));
let c = RE_BAN_SERVER.captures("/ban pleroma.soykaf.com");
assert!(c.is_some());
assert_eq!(c.unwrap().get(1).unwrap().as_str(), "pleroma.soykaf.com");
let c = RE_BAN_SERVER.captures("/ban pleroma.soykaf.com xx");
assert!(c.is_some());
assert_eq!(c.unwrap().get(1).unwrap().as_str(), "pleroma.soykaf.com");
}
#[test]
fn test_add_member() {
assert!(RE_ADD_MEMBER.is_match("/add lain@pleroma.soykaf.com"));
assert!(RE_ADD_MEMBER.is_match("/add @lain@pleroma.soykaf.com"));
assert!(RE_ADD_MEMBER.is_match("\\add @lain"));
let c = RE_ADD_MEMBER.captures("/add @lain");
assert!(c.is_some());
assert_eq!(c.unwrap().get(1).unwrap().as_str(), "@lain");
}
#[test]
fn test_remove_member() {
assert!(!RE_REMOVE_MEMBER.is_match("/admin lain@pleroma.soykaf.com"));
assert!(RE_REMOVE_MEMBER.is_match("/remove lain@pleroma.soykaf.com"));
assert!(RE_REMOVE_MEMBER.is_match("/remove @lain@pleroma.soykaf.com"));
assert!(RE_REMOVE_MEMBER.is_match("\\remove @lain"));
assert!(RE_REMOVE_MEMBER.is_match("/kick @lain"));
assert!(RE_REMOVE_MEMBER.is_match("/remove @lain"));
let c = RE_REMOVE_MEMBER.captures("/kick lain@pleroma.soykaf.com");
assert!(c.is_some());
assert_eq!(c.unwrap().get(1).unwrap().as_str(), "lain@pleroma.soykaf.com");
}
#[test]
fn test_add_admin() {
assert!(!RE_GRANT_ADMIN.is_match("/expel lain@pleroma.soykaf.com"));
assert!(RE_GRANT_ADMIN.is_match("/admin lain@pleroma.soykaf.com"));
assert!(RE_GRANT_ADMIN.is_match("/op @lain@pleroma.soykaf.com"));
assert!(RE_GRANT_ADMIN.is_match("\\op @lain"));
let c = RE_GRANT_ADMIN.captures("/op @lain@pleroma.soykaf.com");
assert!(c.is_some());
assert_eq!(c.unwrap().get(1).unwrap().as_str(), "@lain@pleroma.soykaf.com");
}
#[test]
fn test_remove_admin() {
assert!(!RE_REVOKE_ADMIN.is_match("/admin lain@pleroma.soykaf.com"));
assert!(RE_REVOKE_ADMIN.is_match("/revoke @lain"));
assert!(RE_REVOKE_ADMIN.is_match("/deop @lain"));
assert!(RE_REVOKE_ADMIN.is_match("/deadmin @lain"));
let c = RE_REVOKE_ADMIN.captures("/revoke @lain");
assert!(c.is_some());
assert_eq!(c.unwrap().get(1).unwrap().as_str(), "@lain");
}
#[test]
fn test_opengroup() {
assert!(!RE_OPEN_GROUP.is_match("/admin lain@pleroma.soykaf.com"));
assert!(RE_OPEN_GROUP.is_match("/opengroup"));
assert!(RE_OPEN_GROUP.is_match("x /opengroup"));
assert!(RE_OPEN_GROUP.is_match("/opengroup dfgdfg"));
assert!(RE_OPEN_GROUP.is_match("\n\n/opengroup\n dfgdfg\n\n"));
}
#[test]
fn test_closegroup() {
assert!(!RE_CLOSE_GROUP.is_match("/admin lain@pleroma.soykaf.com"));
assert!(RE_CLOSE_GROUP.is_match("/closegroup"));
assert!(RE_CLOSE_GROUP.is_match("x /closegroup"));
assert!(RE_CLOSE_GROUP.is_match("/closegroup dfgdfg"));
assert!(RE_CLOSE_GROUP.is_match("\n\n/closegroup\n dfgdfg\n\n"));
}
#[test]
fn test_help() {
assert!(!RE_HELP.is_match("/admin lain@pleroma.soykaf.com"));
assert!(RE_HELP.is_match("/help"));
assert!(!RE_HELP.is_match("/helpx"));
assert!(!RE_HELP.is_match("a/help"));
assert!(!RE_HELP.is_match("help"));
assert!(RE_HELP.is_match("x /help"));
assert!(RE_HELP.is_match("/help dfgdfg"));
assert!(RE_HELP.is_match("\n\n/help\n dfgdfg\n\n"));
}
#[test]
fn test_members() {
assert!(!RE_MEMBERS.is_match("/admin lain@pleroma.soykaf.com"));
assert!(RE_MEMBERS.is_match("/members"));
}
#[test]
fn test_leave() {
assert!(!RE_LEAVE.is_match("/list"));
assert!(RE_LEAVE.is_match("/leave"));
assert!(RE_LEAVE.is_match("/leave"));
assert!(RE_LEAVE.is_match("x /leave"));
assert!(RE_LEAVE.is_match("/leave z"));
}
#[test]
fn test_announce() {
assert!(!RE_ANNOUNCE.is_match("/list"));
assert!(RE_ANNOUNCE.is_match("sdfsdffsd /announce b"));
assert!(RE_ANNOUNCE.is_match("/announce bla bla bla"));
assert!(RE_ANNOUNCE.is_match("sdfsdffsd /announce bla bla bla"));
assert_eq!("bla bla bla", RE_ANNOUNCE.captures("sdfsdffsd /announce bla bla bla").unwrap().get(1).unwrap().as_str());
}
#[test]
fn test_real_post() {
assert_eq!(Vec::<StatusCommand>::new(), parse_status("Hello there is nothing here /fake command"));
assert_eq!(vec![StatusCommand::Help], parse_status("lets see some \\help and /ban @lain"));
assert_eq!(vec![StatusCommand::Ignore], parse_status("lets see some /ignore and /ban @lain"));
assert_eq!(vec![
StatusCommand::BanUser("lain".to_string()),
StatusCommand::BanUser("piggo@piggo.space".to_string()),
StatusCommand::BanServer("soykaf.com".to_string())
],
parse_status("let's /ban @lain! /ban @piggo@piggo.space and also /ban soykaf.com"));
}
#[test]
fn test_strip() {
assert_eq!(vec![StatusCommand::BanUser("betty".to_string())],
parse_status(r#"Let's bad the naughty bot: /ban <span class="h-card"><a class="u-url mention" data-user="9nXpaGZL88fPAiP8xU" href="https://piggo.space/users/betty" rel="ugc">@<span>betty</span></a></span>"#));
assert_eq!(vec![StatusCommand::BanUser("betty@abstarbauze.com".to_string())],
parse_status(r#"Let's bad the naughty bot: /ban <span class="h-card"><a class="u-url mention" data-user="9nXpaGZL88fPAiP8xU" href="https://piggo.space/users/betty" rel="ugc">@<span>betty@abstarbauze.com</span></a></span>"#));
}
}