|  |  |  | @ -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() { | 
			
		
	
		
			
				
					|  |  |  |  |         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,14 +792,14 @@ 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) { | 
			
		
	
		
			
				
					|  |  |  |  |     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
 | 
			
		
	
		
			
				
					|  |  |  |  |         debug!("Adding \" .\" to fix pleroma hashtag eating bug!"); | 
			
		
	
		
			
				
					|  |  |  |  |             grp_debug!(self, "Adding \" .\" to fix pleroma hashtag eating bug!"); | 
			
		
	
		
			
				
					|  |  |  |  |             msg.push_str(" ."); | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | fn smart_split(msg: &str, prefix: Option<String>, limit: usize) -> Vec<String> { | 
			
		
	
	
		
			
				
					|  |  |  | @ -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()); | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
	
		
			
				
					|  |  |  | 
 |