diff --git a/src/group_handler/handle_mention.rs b/src/group_handler/handle_mention.rs index 3bdba71..0f28630 100644 --- a/src/group_handler/handle_mention.rs +++ b/src/group_handler/handle_mention.rs @@ -2,19 +2,25 @@ use std::cmp::Ordering; use std::collections::HashSet; use std::time::Duration; +use elefren::{FediClient, SearchType, StatusBuilder}; use elefren::entities::account::Account; use elefren::entities::prelude::Status; use elefren::status_builder::Visibility; -use elefren::{FediClient, SearchType, StatusBuilder}; -use crate::command::{StatusCommand, RE_NOBOT_TAG}; +use crate::command::{RE_NOBOT_TAG, StatusCommand}; use crate::error::GroupError; use crate::group_handler::GroupHandle; -use crate::store::group_config::GroupConfig; use crate::store::CommonConfig; +use crate::store::group_config::GroupConfig; use crate::tr::TranslationTable; use crate::utils; -use crate::utils::{normalize_acct, LogError}; +use crate::utils::{LogError, normalize_acct, VisExt}; + +use crate::{ + grp_debug, + grp_warn, + grp_info +}; pub struct ProcessMention<'a> { status: Status, @@ -38,7 +44,7 @@ impl<'a> ProcessMention<'a> { } async fn lookup_acct_id(&self, acct: &str, followed: bool) -> Result, GroupError> { - debug!("Looking up user ID by acct: {}", acct); + grp_debug!(self, "Looking up user ID by acct: {}", acct); match tokio::time::timeout( Duration::from_secs(5), @@ -48,7 +54,7 @@ impl<'a> ProcessMention<'a> { .await { Err(_) => { - warn!("Account lookup timeout!"); + grp_warn!(self, "Account lookup timeout!"); Err(GroupError::ApiTimeout) } Ok(Err(e)) => { @@ -60,14 +66,14 @@ impl<'a> ProcessMention<'a> { // XXX limit is 1! let acct_normalized = normalize_acct(&item.acct, &self.group_acct)?; if acct_normalized == acct { - debug!("Search done, account found: {}", item.acct); + grp_debug!(self, "Search done, account found: {}", item.acct); return Ok(Some(item.id)); } else { - warn!("Found wrong account: {}", item.acct); + grp_warn!(self, "Found wrong account: {}", item.acct); } } - debug!("Search done, nothing found"); + grp_debug!(self, "Search done, nothing found"); Ok(None) } } @@ -101,14 +107,14 @@ impl<'a> ProcessMention<'a> { } async fn follow_user_by_id(&self, id: &str) -> Result<(), GroupError> { - debug!("Trying to follow user #{}", id); + grp_debug!(self, "Trying to follow user #{}", id); self.client.follow(id).await?; self.delay_after_post().await; Ok(()) } async fn unfollow_user_by_id(&self, id: &str) -> Result<(), GroupError> { - debug!("Trying to unfollow user #{}", id); + grp_debug!(self, "Trying to unfollow user #{}", id); self.client.unfollow(id).await?; self.delay_after_post().await; Ok(()) @@ -119,7 +125,7 @@ impl<'a> ProcessMention<'a> { let status_acct = normalize_acct(&status.account.acct, &group_acct)?.to_string(); if gh.config.is_banned(&status_acct) { - warn!("Status author {} is banned!", status_acct); + grp_warn!(gh, "Status author {} is banned!", status_acct); return Ok(()); } @@ -163,7 +169,7 @@ impl<'a> ProcessMention<'a> { self.handle_post_with_no_commands().await; } else { if commands.contains(&StatusCommand::Ignore) { - debug!("Notif ignored because of ignore command"); + grp_debug!(self, "Notif ignored because of ignore command"); return Ok(()); } @@ -259,7 +265,7 @@ impl<'a> ProcessMention<'a> { self.delay_after_post().await; } Err(e) => { - warn!("Can't reblog: {}", e); + grp_warn!(self, "Can't reblog: {}", e); } } } @@ -267,9 +273,9 @@ impl<'a> ProcessMention<'a> { if !self.replies.is_empty() { let mut msg = std::mem::take(&mut self.replies); - debug!("r={}", msg); + grp_debug!(self, "r={}", msg); - apply_trailing_hashtag_pleroma_bug_workaround(&mut msg); + self.apply_trailing_hashtag_pleroma_bug_workaround(&mut msg); let mention = crate::tr!(self, "mention_prefix", user = &self.status_acct); self.send_reply_multipart(mention, msg).await?; @@ -277,9 +283,9 @@ impl<'a> ProcessMention<'a> { if !self.announcements.is_empty() { let mut msg = std::mem::take(&mut self.announcements); - debug!("a={}", msg); + grp_debug!(self, "a={}", msg); - apply_trailing_hashtag_pleroma_bug_workaround(&mut msg); + self.apply_trailing_hashtag_pleroma_bug_workaround(&mut msg); let msg = crate::tr!(self, "group_announcement", message = &msg); self.send_announcement_multipart(&msg).await?; @@ -339,24 +345,45 @@ impl<'a> ProcessMention<'a> { } async fn handle_post_with_no_commands(&mut self) { - debug!("No commands in post"); - if self.status.in_reply_to_id.is_none() { - if self.can_write { + grp_debug!(self, "No commands in post"); + + if self.status.visibility.is_private() { + grp_debug!(self, "Mention is private, discard"); + return; + } + + if self.can_write { + if self.status.in_reply_to_id.is_none() { // Someone tagged the group in OP, boost it. - info!("Boosting OP mention"); + grp_info!(self, "Boosting OP mention"); // tokio::time::sleep(DELAY_BEFORE_ACTION).await; self.reblog_status().await; + // Otherwise, don't react } else { - warn!("User @{} can't post to group!", self.status_acct); + // Check for tags + let tags = crate::command::parse_status_tags(&self.status.content); + grp_debug!(self, "Tags in mention: {:?}", tags); + + for t in tags { + if self.config.is_tag_followed(&t) { + grp_info!(self, "REBLOG #{} STATUS", t); + self.client.reblog(&self.status.id).await.log_error("Failed to reblog"); + self.delay_after_post().await; + return; + } else { + grp_debug!(self, "#{} is not a group tag", t); + } + } + + grp_debug!(self, "Not OP & no tags, ignore mention"); } - // Otherwise, don't react } else { - debug!("Not OP, ignore mention"); + grp_warn!(self, "User @{} can't post to group!", self.status_acct); } } async fn cmd_announce(&mut self, msg: String) { - info!("Sending PSA"); + grp_info!(self, "Sending PSA"); self.add_announcement(msg); } @@ -364,7 +391,7 @@ impl<'a> ProcessMention<'a> { if self.can_write { self.do_boost_prev_post = self.status.in_reply_to_id.is_some(); } else { - warn!("User @{} can't share to group!", self.status_acct); + grp_warn!(self, "User @{} can't share to group!", self.status_acct); } } @@ -398,19 +425,19 @@ impl<'a> ProcessMention<'a> { // This is a post sent by the group user, likely an announcement. // Undo here means delete it. if self.is_admin { - info!("Deleting group post #{}", parent_status_id); + grp_info!(self, "Deleting group post #{}", parent_status_id); self.client.delete_status(parent_status_id).await?; self.delay_after_post().await; } else { - warn!("Only admin can delete posts made by the group user"); + grp_warn!(self, "Only admin can delete posts made by the group user"); } } else if self.is_admin || parent_account_id == &self.status_user_id { - info!("Un-reblogging post #{}", parent_status_id); + grp_info!(self, "Un-reblogging post #{}", parent_status_id); // User unboosting own post boosted by accident, or admin doing it self.client.unreblog(parent_status_id).await?; self.delay_after_post().await; } else { - warn!("Only the author and admins can undo reblogs"); + grp_warn!(self, "Only the author and admins can undo reblogs"); // XXX this means when someone /b's someone else's post to a group, // they then can't reverse that (only admin or the post's author can) } @@ -436,7 +463,7 @@ impl<'a> ProcessMention<'a> { self.add_reply(crate::tr!(self, "cmd_ban_user_fail_already", user = &u)); } } else { - warn!("Ignore cmd, user not admin"); + grp_warn!(self, "Ignore cmd, user not admin"); } Ok(()) } @@ -458,7 +485,7 @@ impl<'a> ProcessMention<'a> { self.add_reply(crate::tr!(self, "cmd_unban_user_fail_already", user = &u)); } } else { - warn!("Ignore cmd, user not admin"); + grp_warn!(self, "Ignore cmd, user not admin"); } Ok(()) } @@ -478,7 +505,7 @@ impl<'a> ProcessMention<'a> { self.add_reply(crate::tr!(self, "cmd_ban_server_fail_already", server = s)); } } else { - warn!("Ignore cmd, user not admin"); + grp_warn!(self, "Ignore cmd, user not admin"); } } @@ -497,7 +524,7 @@ impl<'a> ProcessMention<'a> { self.add_reply(crate::tr!(self, "cmd_unban_server_fail_already", server = s)); } } else { - warn!("Ignore cmd, user not admin"); + grp_warn!(self, "Ignore cmd, user not admin"); } } @@ -516,7 +543,7 @@ impl<'a> ProcessMention<'a> { } } } else { - warn!("Ignore cmd, user not admin"); + grp_warn!(self, "Ignore cmd, user not admin"); } Ok(()) } @@ -534,7 +561,7 @@ impl<'a> ProcessMention<'a> { } } } else { - warn!("Ignore cmd, user not admin"); + grp_warn!(self, "Ignore cmd, user not admin"); } Ok(()) } @@ -548,7 +575,7 @@ impl<'a> ProcessMention<'a> { self.add_reply(crate::tr!(self, "cmd_add_tag_fail_already", tag = &tag)); } } else { - warn!("Ignore cmd, user not admin"); + grp_warn!(self, "Ignore cmd, user not admin"); } } @@ -561,7 +588,7 @@ impl<'a> ProcessMention<'a> { self.add_reply(crate::tr!(self, "cmd_remove_tag_fail_already", tag = &tag)); } } else { - warn!("Ignore cmd, user not admin"); + grp_warn!(self, "Ignore cmd, user not admin"); } } @@ -584,7 +611,7 @@ impl<'a> ProcessMention<'a> { self.add_reply(crate::tr!(self, "cmd_admin_fail_already", user = &u)); } } else { - warn!("Ignore cmd, user not admin"); + grp_warn!(self, "Ignore cmd, user not admin"); } Ok(()) } @@ -605,7 +632,7 @@ impl<'a> ProcessMention<'a> { self.add_reply(crate::tr!(self, "cmd_unadmin_fail_already", user = &u)); } } else { - warn!("Ignore cmd, user not admin"); + grp_warn!(self, "Ignore cmd, user not admin"); } Ok(()) } @@ -619,7 +646,7 @@ impl<'a> ProcessMention<'a> { self.add_reply(crate::tr!(self, "cmd_open_resp_already")); } } else { - warn!("Ignore cmd, user not admin"); + grp_warn!(self, "Ignore cmd, user not admin"); } } @@ -632,7 +659,7 @@ impl<'a> ProcessMention<'a> { self.add_reply(crate::tr!(self, "cmd_close_resp_already")); } } else { - warn!("Ignore cmd, user not admin"); + grp_warn!(self, "Ignore cmd, user not admin"); } } @@ -701,7 +728,7 @@ impl<'a> ProcessMention<'a> { async fn cmd_join(&mut self) { if self.config.is_member_or_admin(&self.status_acct) { - debug!("Already member or admin, try to follow-back again"); + grp_debug!(self, "Already member or admin, try to follow-back again"); // Already a member, so let's try to follow the user // again, maybe first time it failed self.follow_user_by_id(&self.status_user_id).await.log_error("Failed to follow"); @@ -765,13 +792,13 @@ impl<'a> ProcessMention<'a> { async fn delay_after_post(&self) { tokio::time::sleep(Duration::from_secs_f64(self.cc.delay_after_post_s)).await; } -} -fn apply_trailing_hashtag_pleroma_bug_workaround(msg: &mut String) { - if crate::command::RE_HASHTAG_TRIGGERING_PLEROMA_BUG.is_match(msg) { - // if a status ends with a hashtag, pleroma will fuck it up - debug!("Adding \" .\" to fix pleroma hashtag eating bug!"); - msg.push_str(" ."); + fn apply_trailing_hashtag_pleroma_bug_workaround(&self, msg: &mut String) { + if crate::command::RE_HASHTAG_TRIGGERING_PLEROMA_BUG.is_match(msg) { + // if a status ends with a hashtag, pleroma will fuck it up + grp_debug!(self, "Adding \" .\" to fix pleroma hashtag eating bug!"); + msg.push_str(" ."); + } } } @@ -785,29 +812,29 @@ fn smart_split(msg: &str, prefix: Option, limit: usize) -> Vec { let mut parts_to_send = vec![]; let mut this_piece = prefix.clone(); for l in msg.split('\n') { - println!("* Line: {:?}", l); + // println!("* Line: {:?}", l); match (this_piece.len() + l.len()).cmp(&limit) { Ordering::Less => { - println!("append line"); + // println!("append line"); // this line still fits comfortably this_piece.push_str(l); this_piece.push('\n'); } Ordering::Equal => { - println!("exactly fits within limit"); + // println!("exactly fits within limit"); // this line exactly reaches the limit this_piece.push_str(l); parts_to_send.push(std::mem::take(&mut this_piece).trim().to_owned()); this_piece.push_str(&prefix); } Ordering::Greater => { - println!("too long to append (already {} + new {})", this_piece.len(), l.len()); + // println!("too long to append (already {} + new {})", this_piece.len(), l.len()); // line too long to append if this_piece != prefix { let trimmed = this_piece.trim(); if !trimmed.is_empty() { - println!("flush buffer: {:?}", trimmed); + // println!("flush buffer: {:?}", trimmed); parts_to_send.push(trimmed.to_owned()); } } @@ -818,18 +845,18 @@ fn smart_split(msg: &str, prefix: Option, limit: usize) -> Vec { while this_piece.len() > limit { // line too long, try splitting at the last space, if any let to_send = if let Some(last_space) = (&this_piece[..=limit]).rfind(' ') { - println!("line split at word boundary"); + // println!("line split at word boundary"); let mut p = this_piece.split_off(last_space + 1); std::mem::swap(&mut p, &mut this_piece); p } else { - println!("line split at exact len (no word boundary found)"); + // println!("line split at exact len (no word boundary found)"); let mut p = this_piece.split_off(limit); std::mem::swap(&mut p, &mut this_piece); p }; let part_trimmed = to_send.trim(); - println!("flush buffer: {:?}", part_trimmed); + // println!("flush buffer: {:?}", part_trimmed); parts_to_send.push(part_trimmed.to_owned()); this_piece = format!("{}{}", prefix, this_piece.trim()); } @@ -841,7 +868,7 @@ fn smart_split(msg: &str, prefix: Option, limit: usize) -> Vec { if this_piece != prefix { let leftover_trimmed = this_piece.trim(); if !leftover_trimmed.is_empty() { - println!("flush buffer: {:?}", leftover_trimmed); + // println!("flush buffer: {:?}", leftover_trimmed); parts_to_send.push(leftover_trimmed.to_owned()); } } diff --git a/src/group_handler/mod.rs b/src/group_handler/mod.rs index 662a0cf..c0d0393 100644 --- a/src/group_handler/mod.rs +++ b/src/group_handler/mod.rs @@ -47,6 +47,7 @@ impl Default for GroupInternal { } } +#[macro_export] macro_rules! grp_debug { ($self:ident, $f:expr) => { ::log::debug!(concat!("(@{}) ", $f), $self.config.get_acct()); @@ -56,6 +57,7 @@ macro_rules! grp_debug { }; } +#[macro_export] macro_rules! grp_info { ($self:ident, $f:expr) => { ::log::info!(concat!("(@{}) ", $f), $self.config.get_acct()); @@ -65,6 +67,7 @@ macro_rules! grp_info { }; } +#[macro_export] macro_rules! grp_trace { ($self:ident, $f:expr) => { ::log::trace!(concat!("(@{}) ", $f), $self.config.get_acct()); @@ -74,6 +77,7 @@ macro_rules! grp_trace { }; } +#[macro_export] macro_rules! grp_warn { ($self:ident, $f:expr) => { ::log::warn!(concat!("(@{}) ", $f), $self.config.get_acct()); @@ -83,6 +87,7 @@ macro_rules! grp_warn { }; } +#[macro_export] macro_rules! grp_error { ($self:ident, $f:expr) => { ::log::error!(concat!("(@{}) ", $f), $self.config.get_acct()); diff --git a/src/main.rs b/src/main.rs index f139a12..2f91ced 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,7 @@ use crate::utils::acct_to_server; mod command; mod error; +#[macro_use] mod group_handler; mod store; mod utils;