|  |  |  | @ -262,8 +262,30 @@ impl<'a> ProcessMention<'a> { | 
			
		
	
		
			
				
					|  |  |  |  |                 apply_trailing_hashtag_pleroma_bug_workaround(&mut msg); | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |             let mention = format!("@{user}", user = self.status_acct); | 
			
		
	
		
			
				
					|  |  |  |  |             self.send_reply_multipart(mention, msg).await?; | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         if !self.announcements.is_empty() { | 
			
		
	
		
			
				
					|  |  |  |  |             let mut msg = self.announcements.join("\n"); | 
			
		
	
		
			
				
					|  |  |  |  |             debug!("a={}", msg); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |             if self.want_markdown { | 
			
		
	
		
			
				
					|  |  |  |  |                 apply_trailing_hashtag_pleroma_bug_workaround(&mut msg); | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |             let msg = format!("**📢 Group announcement**\n{msg}", msg = msg); | 
			
		
	
		
			
				
					|  |  |  |  |             self.send_announcement_multipart(&msg).await?; | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         Ok(()) | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     async fn send_reply_multipart(&self, mention : String, msg : String) -> Result<(), GroupError> { | 
			
		
	
		
			
				
					|  |  |  |  |         let parts = smart_split(&msg, Some(mention), self.config.get_character_limit()); | 
			
		
	
		
			
				
					|  |  |  |  |         for p in parts { | 
			
		
	
		
			
				
					|  |  |  |  |             if let Ok(post) = StatusBuilder::new() | 
			
		
	
		
			
				
					|  |  |  |  |                 .status(format!("@{user} {msg}", user = self.status_acct, msg = msg)) | 
			
		
	
		
			
				
					|  |  |  |  |                 .status(p) | 
			
		
	
		
			
				
					|  |  |  |  |                 .content_type(if self.want_markdown { | 
			
		
	
		
			
				
					|  |  |  |  |                     "text/markdown" | 
			
		
	
		
			
				
					|  |  |  |  |                 } else { | 
			
		
	
	
		
			
				
					|  |  |  | @ -272,30 +294,31 @@ impl<'a> ProcessMention<'a> { | 
			
		
	
		
			
				
					|  |  |  |  |                 .visibility(Visibility::Direct) | 
			
		
	
		
			
				
					|  |  |  |  |                 .build() | 
			
		
	
		
			
				
					|  |  |  |  |             { | 
			
		
	
		
			
				
					|  |  |  |  |                 let _ = self.client.new_status(post) | 
			
		
	
		
			
				
					|  |  |  |  |                     .await.log_error("Failed to post"); | 
			
		
	
		
			
				
					|  |  |  |  |                 // Sleep a bit to avoid throttling
 | 
			
		
	
		
			
				
					|  |  |  |  |                 tokio::time::sleep(Duration::from_secs(1)).await; | 
			
		
	
		
			
				
					|  |  |  |  |                 self.client.new_status(post).await?; | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |             // Sleep a bit to avoid throttling
 | 
			
		
	
		
			
				
					|  |  |  |  |             tokio::time::sleep(Duration::from_secs(1)).await; | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         if !self.announcements.is_empty() { | 
			
		
	
		
			
				
					|  |  |  |  |             let mut msg = self.announcements.join("\n"); | 
			
		
	
		
			
				
					|  |  |  |  |             debug!("a={}", msg); | 
			
		
	
		
			
				
					|  |  |  |  |         Ok(()) | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |             if self.want_markdown { | 
			
		
	
		
			
				
					|  |  |  |  |                 apply_trailing_hashtag_pleroma_bug_workaround(&mut msg); | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  |     async fn send_announcement_multipart(&self, msg : &str) -> Result<(), GroupError> { | 
			
		
	
		
			
				
					|  |  |  |  |         let parts = smart_split(msg, None, self.config.get_character_limit()); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         for p in parts { | 
			
		
	
		
			
				
					|  |  |  |  |             let post = StatusBuilder::new() | 
			
		
	
		
			
				
					|  |  |  |  |                 .status(format!("**📢 Group announcement**\n{msg}", msg = msg)) | 
			
		
	
		
			
				
					|  |  |  |  |                 .status(p) | 
			
		
	
		
			
				
					|  |  |  |  |                 .content_type("text/markdown") | 
			
		
	
		
			
				
					|  |  |  |  |                 .visibility(Visibility::Public) | 
			
		
	
		
			
				
					|  |  |  |  |                 .build() | 
			
		
	
		
			
				
					|  |  |  |  |                 .expect("error build status"); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |             let _ = self.client.new_status(post) | 
			
		
	
		
			
				
					|  |  |  |  |                 .await.log_error("Failed to post"); | 
			
		
	
		
			
				
					|  |  |  |  |             self.client.new_status(post).await?; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |             // Sleep a bit to avoid throttling
 | 
			
		
	
		
			
				
					|  |  |  |  |             tokio::time::sleep(Duration::from_secs(1)).await; | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         Ok(()) | 
			
		
	
	
		
			
				
					|  |  |  | @ -772,3 +795,95 @@ fn apply_trailing_hashtag_pleroma_bug_workaround(msg: &mut String) { | 
			
		
	
		
			
				
					|  |  |  |  |         msg.push_str(" ."); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | fn smart_split(msg : &str, prefix: Option<String>, limit: usize) -> Vec<String> { | 
			
		
	
		
			
				
					|  |  |  |  |     let prefix = prefix.unwrap_or_default(); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     if msg.len() + prefix.len() < limit { | 
			
		
	
		
			
				
					|  |  |  |  |         return vec![format!("{}{}", prefix, msg)]; | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     let mut parts_to_send = vec![]; | 
			
		
	
		
			
				
					|  |  |  |  |     let mut this_piece = prefix.clone(); | 
			
		
	
		
			
				
					|  |  |  |  |     for l in msg.split("\n") { | 
			
		
	
		
			
				
					|  |  |  |  |         if this_piece.len() + l.len() == limit { | 
			
		
	
		
			
				
					|  |  |  |  |             this_piece.push_str(l); | 
			
		
	
		
			
				
					|  |  |  |  |             parts_to_send.push(std::mem::take(&mut this_piece).trim().to_owned()); | 
			
		
	
		
			
				
					|  |  |  |  |             this_piece.push_str(&prefix); | 
			
		
	
		
			
				
					|  |  |  |  |         } else if this_piece.len() + l.len() > limit { | 
			
		
	
		
			
				
					|  |  |  |  |             let trimmed = this_piece.trim(); | 
			
		
	
		
			
				
					|  |  |  |  |             if !trimmed.is_empty() { | 
			
		
	
		
			
				
					|  |  |  |  |                 parts_to_send.push(trimmed.to_owned()); | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |             // start new piece
 | 
			
		
	
		
			
				
					|  |  |  |  |             this_piece = format!("{}{}", prefix, l); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |             while this_piece.len() > limit { | 
			
		
	
		
			
				
					|  |  |  |  |                 let to_send = if let Some(last_space) = (&this_piece[..limit]).rfind(' ') { | 
			
		
	
		
			
				
					|  |  |  |  |                     let mut p = this_piece.split_off(last_space); | 
			
		
	
		
			
				
					|  |  |  |  |                     std::mem::swap(&mut p, &mut this_piece); | 
			
		
	
		
			
				
					|  |  |  |  |                     p | 
			
		
	
		
			
				
					|  |  |  |  |                 } else { | 
			
		
	
		
			
				
					|  |  |  |  |                     let mut p = this_piece.split_off(limit); | 
			
		
	
		
			
				
					|  |  |  |  |                     std::mem::swap(&mut p, &mut this_piece); | 
			
		
	
		
			
				
					|  |  |  |  |                     p | 
			
		
	
		
			
				
					|  |  |  |  |                 }; | 
			
		
	
		
			
				
					|  |  |  |  |                 parts_to_send.push(to_send); | 
			
		
	
		
			
				
					|  |  |  |  |                 this_piece = format!("{}{}", prefix, this_piece); | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  |             this_piece.push('\n'); | 
			
		
	
		
			
				
					|  |  |  |  |         } else { | 
			
		
	
		
			
				
					|  |  |  |  |             this_piece.push_str(l); | 
			
		
	
		
			
				
					|  |  |  |  |             this_piece.push('\n'); | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     parts_to_send | 
			
		
	
		
			
				
					|  |  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | #[cfg(test)] | 
			
		
	
		
			
				
					|  |  |  |  | mod test { | 
			
		
	
		
			
				
					|  |  |  |  |     #[test] | 
			
		
	
		
			
				
					|  |  |  |  |     fn test_smart_split1() { | 
			
		
	
		
			
				
					|  |  |  |  |         let to_split = "a234567890\nb234567890\nc234567890\nd234\n67890\ne234567890\n"; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         let parts = super::smart_split(to_split, None, 10); | 
			
		
	
		
			
				
					|  |  |  |  |         assert_eq!(vec![ | 
			
		
	
		
			
				
					|  |  |  |  |             "a234567890".to_string(), | 
			
		
	
		
			
				
					|  |  |  |  |             "b234567890".to_string(), | 
			
		
	
		
			
				
					|  |  |  |  |             "c234567890".to_string(), | 
			
		
	
		
			
				
					|  |  |  |  |             "d234\n67890".to_string(), | 
			
		
	
		
			
				
					|  |  |  |  |             "e234567890".to_string(), | 
			
		
	
		
			
				
					|  |  |  |  |         ], parts); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     #[test] | 
			
		
	
		
			
				
					|  |  |  |  |     fn test_smart_split2() { | 
			
		
	
		
			
				
					|  |  |  |  |         let to_split = "foo\nbar\nbaz"; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         let parts = super::smart_split(to_split, None, 1000); | 
			
		
	
		
			
				
					|  |  |  |  |         assert_eq!(vec![ | 
			
		
	
		
			
				
					|  |  |  |  |             "foo\nbar\nbaz".to_string(), | 
			
		
	
		
			
				
					|  |  |  |  |         ], parts); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     #[test] | 
			
		
	
		
			
				
					|  |  |  |  |     fn test_smart_split3() { | 
			
		
	
		
			
				
					|  |  |  |  |         let to_split = "foo\nbar\nbaz"; | 
			
		
	
		
			
				
					|  |  |  |  |         let parts = super::smart_split(to_split, Some("PREFIX".to_string()), 1000); | 
			
		
	
		
			
				
					|  |  |  |  |         assert_eq!(vec![ | 
			
		
	
		
			
				
					|  |  |  |  |             "PREFIXfoo\nbar\nbaz".to_string(), | 
			
		
	
		
			
				
					|  |  |  |  |         ], parts); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     #[test] | 
			
		
	
		
			
				
					|  |  |  |  |     fn test_smart_split4() { | 
			
		
	
		
			
				
					|  |  |  |  |         let to_split = "1234\n56\n7"; | 
			
		
	
		
			
				
					|  |  |  |  |         let parts = super::smart_split(to_split, Some("PREFIX".to_string()), 10); | 
			
		
	
		
			
				
					|  |  |  |  |         assert_eq!(vec![ | 
			
		
	
		
			
				
					|  |  |  |  |             "PREFIX1234".to_string(), | 
			
		
	
		
			
				
					|  |  |  |  |             "PREFIX56\n7".to_string(), | 
			
		
	
		
			
				
					|  |  |  |  |         ], parts); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | } |