From 0d37425c32a8eab3eab0ed8ce2ee423b383718b5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= <ondra@ondrovo.com>
Date: Tue, 12 Oct 2021 10:38:02 +0200
Subject: [PATCH] fix hashtag not working in mention

---
 src/group_handler/handle_mention.rs | 145 +++++++++++++++++-----------
 src/group_handler/mod.rs            |   5 +
 src/main.rs                         |   1 +
 3 files changed, 92 insertions(+), 59 deletions(-)

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<Option<String>, 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<String>, limit: usize) -> Vec<String> {
     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<String>, limit: usize) -> Vec<String> {
                 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<String>, limit: usize) -> Vec<String> {
     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;