|  |  | @ -1,3 +1,4 @@ | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | use std::collections::HashSet; | 
			
		
	
		
		
			
				
					
					|  |  |  | use std::sync::Arc; |  |  |  | use std::sync::Arc; | 
			
		
	
		
		
			
				
					
					|  |  |  | use std::time::{Duration, Instant}; |  |  |  | use std::time::{Duration, Instant}; | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
	
		
		
			
				
					|  |  | @ -10,10 +11,10 @@ use elefren::status_builder::Visibility; | 
			
		
	
		
		
			
				
					
					|  |  |  | use futures::StreamExt; |  |  |  | use futures::StreamExt; | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | use crate::command::StatusCommand; |  |  |  | use crate::command::StatusCommand; | 
			
		
	
		
		
			
				
					
					|  |  |  | use crate::store::{ConfigStore, GroupError}; |  |  |  | use crate::error::GroupError; | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | use crate::store::ConfigStore; | 
			
		
	
		
		
			
				
					
					|  |  |  | use crate::store::data::GroupConfig; |  |  |  | use crate::store::data::GroupConfig; | 
			
		
	
		
		
			
				
					
					|  |  |  | use crate::utils::LogError; |  |  |  | use crate::utils::{LogError, normalize_acct}; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  | use std::collections::HashSet; |  |  |  |  | 
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | /// This is one group's config store capable of persistence
 |  |  |  | /// This is one group's config store capable of persistence
 | 
			
		
	
		
		
			
				
					
					|  |  |  | #[derive(Debug)] |  |  |  | #[derive(Debug)] | 
			
		
	
	
		
		
			
				
					|  |  | @ -23,6 +24,12 @@ pub struct GroupHandle { | 
			
		
	
		
		
			
				
					
					|  |  |  |     pub(crate) store: Arc<ConfigStore>, |  |  |  |     pub(crate) store: Arc<ConfigStore>, | 
			
		
	
		
		
			
				
					
					|  |  |  | } |  |  |  | } | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | const DELAY_BEFORE_ACTION: Duration = Duration::from_millis(250); | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | const DELAY_REOPEN_STREAM: Duration = Duration::from_millis(1000); | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | const MAX_CATCHUP_NOTIFS: usize = 25; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | const PERIODIC_SAVE: Duration = Duration::from_secs(60); | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | const PING_INTERVAL: Duration = Duration::from_secs(15); // must be < periodic save!
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | impl GroupHandle { |  |  |  | impl GroupHandle { | 
			
		
	
		
		
			
				
					
					|  |  |  |     pub async fn save(&mut self) -> Result<(), GroupError> { |  |  |  |     pub async fn save(&mut self) -> Result<(), GroupError> { | 
			
		
	
		
		
			
				
					
					|  |  |  |         debug!("Saving group config & status"); |  |  |  |         debug!("Saving group config & status"); | 
			
		
	
	
		
		
			
				
					|  |  | @ -60,9 +67,6 @@ impl NotifTimestamp for Notification { | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | impl GroupHandle { |  |  |  | impl GroupHandle { | 
			
		
	
		
		
			
				
					
					|  |  |  |     pub async fn run(&mut self) -> Result<(), GroupError> { |  |  |  |     pub async fn run(&mut self) -> Result<(), GroupError> { | 
			
		
	
		
		
			
				
					
					|  |  |  |         const PERIODIC_SAVE: Duration = Duration::from_secs(60); |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |         const PING_INTERVAL: Duration = Duration::from_secs(15); |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |         assert!(PERIODIC_SAVE >= PING_INTERVAL); |  |  |  |         assert!(PERIODIC_SAVE >= PING_INTERVAL); | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         let mut next_save = Instant::now() + PERIODIC_SAVE; // so we save at start
 |  |  |  |         let mut next_save = Instant::now() + PERIODIC_SAVE; // so we save at start
 | 
			
		
	
	
		
		
			
				
					|  |  | @ -102,7 +106,8 @@ impl GroupHandle { | 
			
		
	
		
		
			
				
					
					|  |  |  |                         match event { |  |  |  |                         match event { | 
			
		
	
		
		
			
				
					
					|  |  |  |                             Event::Update(_status) => {} |  |  |  |                             Event::Update(_status) => {} | 
			
		
	
		
		
			
				
					
					|  |  |  |                             Event::Notification(n) => { |  |  |  |                             Event::Notification(n) => { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 self.handle_notification(n).await; |  |  |  |                                 self.handle_notification(n).await | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                     .log_error("Error handling a notification"); | 
			
		
	
		
		
			
				
					
					|  |  |  |                             } |  |  |  |                             } | 
			
		
	
		
		
			
				
					
					|  |  |  |                             Event::Delete(_id) => {} |  |  |  |                             Event::Delete(_id) => {} | 
			
		
	
		
		
			
				
					
					|  |  |  |                             Event::FiltersChanged => {} |  |  |  |                             Event::FiltersChanged => {} | 
			
		
	
	
		
		
			
				
					|  |  | @ -122,100 +127,112 @@ impl GroupHandle { | 
			
		
	
		
		
			
				
					
					|  |  |  |             } |  |  |  |             } | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |             warn!("Notif stream closed, will reopen"); |  |  |  |             warn!("Notif stream closed, will reopen"); | 
			
		
	
		
		
			
				
					
					|  |  |  |             tokio::time::sleep(Duration::from_millis(1000)).await; |  |  |  |             tokio::time::sleep(DELAY_REOPEN_STREAM).await; | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |         } |  |  |  |         } | 
			
		
	
		
		
			
				
					
					|  |  |  |     } |  |  |  |     } | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |     async fn handle_notification(&mut self, n: Notification) { |  |  |  |     async fn handle_notification(&mut self, n: Notification) -> Result<(), GroupError> { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         const DELAY_BEFORE_ACTION: Duration = Duration::from_millis(500); |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  |  | 
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |         debug!("Handling notif #{}", n.id); |  |  |  |         debug!("Handling notif #{}", n.id); | 
			
		
	
		
		
			
				
					
					|  |  |  |         let ts = n.timestamp_millis(); |  |  |  |         let ts = n.timestamp_millis(); | 
			
		
	
		
		
			
				
					
					|  |  |  |         self.config.set_last_notif(ts); |  |  |  |         self.config.set_last_notif(ts); | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         let can_write = self.config.can_write(&n.account.acct); |  |  |  |         let group_acct = self.config.get_acct().to_string(); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         let is_admin = self.config.is_admin(&n.account.acct); |  |  |  |         let notif_acct = normalize_acct(&n.account.acct, &group_acct)?; | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         let can_write = self.config.can_write(¬if_acct); | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         let is_admin = self.config.is_admin(¬if_acct); | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         if self.config.is_banned(&n.account.acct) { |  |  |  |         if self.config.is_banned(¬if_acct) { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |             warn!("Notification actor {} is banned!", n.account.acct); |  |  |  |             warn!("Notification actor {} is banned!", notif_acct); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |             return; |  |  |  |             return Ok(()); | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  |         } |  |  |  |         } | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         match n.notification_type { |  |  |  |         match n.notification_type { | 
			
		
	
		
		
			
				
					
					|  |  |  |             NotificationType::Mention => { |  |  |  |             NotificationType::Mention => { | 
			
		
	
		
		
			
				
					
					|  |  |  |                 if let Some(status) = n.status { |  |  |  |                 if let Some(status) = n.status { | 
			
		
	
		
		
			
				
					
					|  |  |  |                     if self.config.is_banned(&status.account.acct) { |  |  |  |                     let status_acct = normalize_acct(&status.account.acct, &group_acct)?; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                         warn!("Status author {} is banned!", status.account.acct); |  |  |  | 
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                         return; |  |  |  |                     if self.config.is_banned(&status_acct) { | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                         warn!("Status author {} is banned!", status_acct); | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                         return Ok(()); | 
			
		
	
		
		
			
				
					
					|  |  |  |                     } |  |  |  |                     } | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |                     let commands = crate::command::parse_status(&status.content); |  |  |  |                     let commands = crate::command::parse_status(&status.content); | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                     let mut replies = vec![]; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                     let mut announcements = vec![]; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                     let mut do_boost_prev_post = false; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                     // let mut new_members = vec![];
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                     // let mut new_admins = vec![];
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                     // let mut removed_admins = vec![];
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                     // let mut instance_ban_announcements = vec![];
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                     // let mut instance_unban_announcements = vec![];
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                     let mut any_admin_cmd = false; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |                     if commands.is_empty() { |  |  |  |                     if commands.is_empty() { | 
			
		
	
		
		
			
				
					
					|  |  |  |                         debug!("No commands in post"); |  |  |  |                         debug!("No commands in post"); | 
			
		
	
		
		
			
				
					
					|  |  |  |                         if !can_write { |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |                             warn!("User {} not allowed to post in group", n.account.acct); |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |                             return; |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |                         } |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |                         if status.in_reply_to_id.is_none() { |  |  |  |                         if status.in_reply_to_id.is_none() { | 
			
		
	
		
		
			
				
					
					|  |  |  |                             // Someone tagged the group in OP, boost it.
 |  |  |  |                             if can_write { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                             info!("Boosting OP mention"); |  |  |  |                                 // Someone tagged the group in OP, boost it.
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                             tokio::time::sleep(DELAY_BEFORE_ACTION).await; |  |  |  |                                 info!("Boosting OP mention"); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                             self.client.reblog(&status.id).await |  |  |  |                                 tokio::time::sleep(DELAY_BEFORE_ACTION).await; | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                 .log_error("Failed to boost"); |  |  |  |                                 self.client.reblog(&status.id).await | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                     .log_error("Failed to boost"); | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                             } else { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                 replies.push(format!("You are not allowed to post to this group")); | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                             } | 
			
		
	
		
		
			
				
					
					|  |  |  |                         } else { |  |  |  |                         } else { | 
			
		
	
		
		
			
				
					
					|  |  |  |                             debug!("Not OP, ignore mention") |  |  |  |                             debug!("Not OP, ignore mention"); | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                         } |  |  |  |                         } | 
			
		
	
		
		
			
				
					
					|  |  |  |                     } else { |  |  |  |                     } else { | 
			
		
	
		
		
			
				
					
					|  |  |  |                         let mut reply = vec![]; |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |                         let mut boost_prev = false; |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |                         let mut new_members = vec![]; |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |                         let mut new_admins = vec![]; |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |                         let mut removed_admins = vec![]; |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |                         let mut instance_ban_announcements = vec![]; |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |                         let mut instance_unban_announcements = vec![]; |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |                         // TODO normalize local user handles
 |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |                         let mut any_admin_cmd = false; |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |                         for cmd in commands { |  |  |  |                         for cmd in commands { | 
			
		
	
		
		
			
				
					
					|  |  |  |                             match cmd { |  |  |  |                             match cmd { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                 // ignore is first on purpose
 | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 StatusCommand::Ignore => { |  |  |  |                                 StatusCommand::Ignore => { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                     debug!("Notif ignored because of ignore command"); |  |  |  |                                     debug!("Notif ignored because of ignore command"); | 
			
		
	
		
		
			
				
					
					|  |  |  |                                     return; |  |  |  |                                     return Ok(()); | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                 StatusCommand::Announce(a) => { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                     debug!("Sending PSA"); | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                     announcements.push(a); | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 } |  |  |  |                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 StatusCommand::Boost => { |  |  |  |                                 StatusCommand::Boost => { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                     if !can_write { |  |  |  |                                     if can_write { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                         warn!("User {} not allowed to boost to group", n.account.acct); |  |  |  |                                         do_boost_prev_post = status.in_reply_to_id.is_some(); | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                     } else { |  |  |  |                                     } else { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         boost_prev = status.in_reply_to_id.is_some(); |  |  |  |                                         replies.push(format!("You are not allowed to share to this group")); | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                     } |  |  |  |                                     } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 } |  |  |  |                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 StatusCommand::BanUser(u) => { |  |  |  |                                 StatusCommand::BanUser(u) => { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                     let u = normalize_acct(&u, &group_acct)?; | 
			
		
	
		
		
			
				
					
					|  |  |  |                                     if is_admin { |  |  |  |                                     if is_admin { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         if !self.config.is_banned(&u) { |  |  |  |                                         if !self.config.is_banned(&u) { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                             match self.config.ban_user(&u, true) { |  |  |  |                                             match self.config.ban_user(&u, true) { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                 Ok(_) => { |  |  |  |                                                 Ok(_) => { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     any_admin_cmd = true; |  |  |  |                                                     any_admin_cmd = true; | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     reply.push(format!("User {} banned from group!", u)); |  |  |  |                                                     replies.push(format!("User {} banned from group!", u)); | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                                     // no announcement here
 | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                 } |  |  |  |                                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                 Err(e) => { |  |  |  |                                                 Err(e) => { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     reply.push(format!("Failed to ban user {}: {}", u, e)); |  |  |  |                                                     replies.push(format!("Failed to ban user {}: {}", u, e)); | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                                 } |  |  |  |                                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                             } |  |  |  |                                             } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         } |  |  |  |                                         } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                     } else { |  |  |  |                                     } else { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         warn!("Not admin, can't manage bans"); |  |  |  |                                         replies.push(format!("Only admins can manage user bans")); | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                     } |  |  |  |                                     } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 } |  |  |  |                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 StatusCommand::UnbanUser(u) => { |  |  |  |                                 StatusCommand::UnbanUser(u) => { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                     let u = normalize_acct(&u, &group_acct)?; | 
			
		
	
		
		
			
				
					
					|  |  |  |                                     if is_admin { |  |  |  |                                     if is_admin { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         if self.config.is_banned(&u) { |  |  |  |                                         if self.config.is_banned(&u) { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                             match self.config.ban_user(&u, false) { |  |  |  |                                             match self.config.ban_user(&u, false) { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                 Ok(_) => { |  |  |  |                                                 Ok(_) => { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     any_admin_cmd = true; |  |  |  |                                                     any_admin_cmd = true; | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     reply.push(format!("User {} un-banned!", u)); |  |  |  |                                                     replies.push(format!("User {} un-banned!", u)); | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                                     // no announcement here
 | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                 } |  |  |  |                                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                 Err(e) => { |  |  |  |                                                 Err(e) => { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     unreachable!() |  |  |  |                                                     unreachable!() | 
			
		
	
	
		
		
			
				
					|  |  | @ -223,7 +240,7 @@ impl GroupHandle { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                             } |  |  |  |                                             } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         } |  |  |  |                                         } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                     } else { |  |  |  |                                     } else { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         warn!("Not admin, can't manage bans"); |  |  |  |                                         replies.push(format!("Only admins can manage user bans")); | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                     } |  |  |  |                                     } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 } |  |  |  |                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 StatusCommand::BanServer(s) => { |  |  |  |                                 StatusCommand::BanServer(s) => { | 
			
		
	
	
		
		
			
				
					|  |  | @ -232,16 +249,16 @@ impl GroupHandle { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                             match self.config.ban_server(&s, true) { |  |  |  |                                             match self.config.ban_server(&s, true) { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                 Ok(_) => { |  |  |  |                                                 Ok(_) => { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     any_admin_cmd = true; |  |  |  |                                                     any_admin_cmd = true; | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     instance_ban_announcements.push(s.clone()); |  |  |  |                                                     announcements.push(format!("Server \"{}\" has been banned.", s)); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                                     reply.push(format!("Instance {} banned from group!", s)); |  |  |  |                                                     replies.push(format!("Server {} banned from group!", s)); | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                                 } |  |  |  |                                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                 Err(e) => { |  |  |  |                                                 Err(e) => { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     reply.push(format!("Failed to ban instance {}: {}", s, e)); |  |  |  |                                                     replies.push(format!("Failed to ban server {}: {}", s, e)); | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                                 } |  |  |  |                                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                             } |  |  |  |                                             } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         } |  |  |  |                                         } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                     } else { |  |  |  |                                     } else { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         warn!("Not admin, can't manage bans"); |  |  |  |                                         replies.push(format!("Only admins can manage server bans")); | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                     } |  |  |  |                                     } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 } |  |  |  |                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 StatusCommand::UnbanServer(s) => { |  |  |  |                                 StatusCommand::UnbanServer(s) => { | 
			
		
	
	
		
		
			
				
					|  |  | @ -250,8 +267,8 @@ impl GroupHandle { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                             match self.config.ban_server(&s, false) { |  |  |  |                                             match self.config.ban_server(&s, false) { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                 Ok(_) => { |  |  |  |                                                 Ok(_) => { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     any_admin_cmd = true; |  |  |  |                                                     any_admin_cmd = true; | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     instance_unban_announcements.push(s.clone()); |  |  |  |                                                     announcements.push(format!("Server \"{}\" has been un-banned.", s)); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                                     reply.push(format!("Instance {} un-banned!", s)); |  |  |  |                                                     replies.push(format!("Server {} un-banned!", s)); | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                                 } |  |  |  |                                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                 Err(e) => { |  |  |  |                                                 Err(e) => { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     unreachable!() |  |  |  |                                                     unreachable!() | 
			
		
	
	
		
		
			
				
					|  |  | @ -259,34 +276,39 @@ impl GroupHandle { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                             } |  |  |  |                                             } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         } |  |  |  |                                         } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                     } else { |  |  |  |                                     } else { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         warn!("Not admin, can't manage bans"); |  |  |  |                                         replies.push(format!("Only admins can manage server bans")); | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                     } |  |  |  |                                     } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 } |  |  |  |                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 StatusCommand::AddMember(u) => { |  |  |  |                                 StatusCommand::AddMember(u) => { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                     let u = normalize_acct(&u, &group_acct)?; | 
			
		
	
		
		
			
				
					
					|  |  |  |                                     if is_admin { |  |  |  |                                     if is_admin { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         if !self.config.is_member(&u) { |  |  |  |                                         if !self.config.is_member(&u) { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                             match self.config.set_member(&u, true) { |  |  |  |                                             match self.config.set_member(&u, true) { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                 Ok(_) => { |  |  |  |                                                 Ok(_) => { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     any_admin_cmd = true; |  |  |  |                                                     any_admin_cmd = true; | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     reply.push(format!("User {} added to group!", u)); |  |  |  |                                                     replies.push(format!("User {} added to the group!", u)); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                                     new_members.push(u); |  |  |  | 
 | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                                     if self.config.is_member_only() { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                                         announcements.push(format!("Welcome new member @{} to the group!", u)); | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                                     } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                 } |  |  |  |                                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                 Err(e) => { |  |  |  |                                                 Err(e) => { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     reply.push(format!("Failed to add user {} to group: {}", u, e)); |  |  |  |                                                     replies.push(format!("Failed to add user {} to group: {}", u, e)); | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                                 } |  |  |  |                                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                             } |  |  |  |                                             } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         } |  |  |  |                                         } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                     } else { |  |  |  |                                     } else { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         warn!("Not admin, can't manage members"); |  |  |  |                                         replies.push(format!("Only admins can manage members")); | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                     } |  |  |  |                                     } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 } |  |  |  |                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 StatusCommand::RemoveMember(u) => { |  |  |  |                                 StatusCommand::RemoveMember(u) => { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                     let u = normalize_acct(&u, &group_acct)?; | 
			
		
	
		
		
			
				
					
					|  |  |  |                                     if is_admin { |  |  |  |                                     if is_admin { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         if self.config.is_member(&u) { |  |  |  |                                         if self.config.is_member(&u) { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                             match self.config.set_member(&u, false) { |  |  |  |                                             match self.config.set_member(&u, false) { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                 Ok(_) => { |  |  |  |                                                 Ok(_) => { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     any_admin_cmd = true; |  |  |  |                                                     any_admin_cmd = true; | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     reply.push(format!("User {} removed from group.", u)); |  |  |  |                                                     replies.push(format!("User {} removed from the group.", u)); | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                                 } |  |  |  |                                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                 Err(e) => { |  |  |  |                                                 Err(e) => { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     unreachable!() |  |  |  |                                                     unreachable!() | 
			
		
	
	
		
		
			
				
					|  |  | @ -294,43 +316,45 @@ impl GroupHandle { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                             } |  |  |  |                                             } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         } |  |  |  |                                         } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                     } else { |  |  |  |                                     } else { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         warn!("Not admin, can't manage members"); |  |  |  |                                         replies.push(format!("Only admins can manage members")); | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                     } |  |  |  |                                     } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 } |  |  |  |                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 StatusCommand::GrantAdmin(u) => { |  |  |  |                                 StatusCommand::GrantAdmin(u) => { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                     let u = normalize_acct(&u, &group_acct)?; | 
			
		
	
		
		
			
				
					
					|  |  |  |                                     if is_admin { |  |  |  |                                     if is_admin { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         if !self.config.is_admin(&u) { |  |  |  |                                         if !self.config.is_admin(&u) { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                             match self.config.set_admin(&u, true) { |  |  |  |                                             match self.config.set_admin(&u, true) { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                 Ok(_) => { |  |  |  |                                                 Ok(_) => { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     any_admin_cmd = true; |  |  |  |                                                     any_admin_cmd = true; | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     reply.push(format!("User {} is now a group admin!", u)); |  |  |  |                                                     replies.push(format!("User {} is now a group admin!", u)); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                                     new_admins.push(u); |  |  |  |                                                     announcements.push(format!("User @{} can now manage this group!", u)); | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                                 } |  |  |  |                                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                 Err(e) => { |  |  |  |                                                 Err(e) => { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     reply.push(format!("Failed to make user {} a group admin: {}", u, e)); |  |  |  |                                                     replies.push(format!("Failed to make user {} a group admin: {}", u, e)); | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                                 } |  |  |  |                                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                             } |  |  |  |                                             } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         } |  |  |  |                                         } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                     } else { |  |  |  |                                     } else { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         warn!("Not admin, can't manage admin rights"); |  |  |  |                                         replies.push(format!("Only admins can manage admins")); | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                     } |  |  |  |                                     } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 } |  |  |  |                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 StatusCommand::RemoveAdmin(u) => { |  |  |  |                                 StatusCommand::RemoveAdmin(u) => { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                     let u = normalize_acct(&u, &group_acct)?; | 
			
		
	
		
		
			
				
					
					|  |  |  |                                     if is_admin { |  |  |  |                                     if is_admin { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         if self.config.is_admin(&u) { |  |  |  |                                         if self.config.is_admin(&u) { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                             match self.config.set_admin(&u, false) { |  |  |  |                                             match self.config.set_admin(&u, false) { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                 Ok(_) => { |  |  |  |                                                 Ok(_) => { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     any_admin_cmd = true; |  |  |  |                                                     any_admin_cmd = true; | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     reply.push(format!("User {} is no longer a group admin!", u)); |  |  |  |                                                     replies.push(format!("User {} is no longer a group admin!", u)); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                                     removed_admins.push(u) |  |  |  |                                                     announcements.push(format!("User @{} no longer manages this group.", u)); | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                                 } |  |  |  |                                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                 Err(e) => { |  |  |  |                                                 Err(e) => { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                                     reply.push(format!("Failed to revoke {}'s group admin: {}", u, e)); |  |  |  |                                                     replies.push(format!("Failed to revoke {}'s group admin: {}", u, e)); | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                                 } |  |  |  |                                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                             } |  |  |  |                                             } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         } |  |  |  |                                         } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                     } else { |  |  |  |                                     } else { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         warn!("Not admin, can't manage admin rights"); |  |  |  |                                         replies.push(format!("Only admins can manage admins")); | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                     } |  |  |  |                                     } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 } |  |  |  |                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 StatusCommand::OpenGroup => { |  |  |  |                                 StatusCommand::OpenGroup => { | 
			
		
	
	
		
		
			
				
					|  |  | @ -338,10 +362,11 @@ impl GroupHandle { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         if self.config.is_member_only() { |  |  |  |                                         if self.config.is_member_only() { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                             any_admin_cmd = true; |  |  |  |                                             any_admin_cmd = true; | 
			
		
	
		
		
			
				
					
					|  |  |  |                                             self.config.set_member_only(false); |  |  |  |                                             self.config.set_member_only(false); | 
			
		
	
		
		
			
				
					
					|  |  |  |                                             reply.push(format!("Group changed to open-access")); |  |  |  |                                             replies.push(format!("Group changed to open-access")); | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                             announcements.push(format!("This group is now open-access!")); | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         } |  |  |  |                                         } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                     } else { |  |  |  |                                     } else { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         warn!("Not admin, can't manage group mode"); |  |  |  |                                         replies.push(format!("Only admins can set group options")); | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                     } |  |  |  |                                     } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 } |  |  |  |                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 StatusCommand::CloseGroup => { |  |  |  |                                 StatusCommand::CloseGroup => { | 
			
		
	
	
		
		
			
				
					|  |  | @ -349,76 +374,100 @@ impl GroupHandle { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         if !self.config.is_member_only() { |  |  |  |                                         if !self.config.is_member_only() { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                             any_admin_cmd = true; |  |  |  |                                             any_admin_cmd = true; | 
			
		
	
		
		
			
				
					
					|  |  |  |                                             self.config.set_member_only(true); |  |  |  |                                             self.config.set_member_only(true); | 
			
		
	
		
		
			
				
					
					|  |  |  |                                             reply.push(format!("Group changed to member-only")); |  |  |  |                                             replies.push(format!("Group changed to member-only")); | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                             announcements.push(format!("This group is now member-only!")); | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         } |  |  |  |                                         } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                     } else { |  |  |  |                                     } else { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         warn!("Not admin, can't manage group mode"); |  |  |  |                                         replies.push(format!("Only admins can set group options")); | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                     } |  |  |  |                                     } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 } |  |  |  |                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 StatusCommand::Help => { |  |  |  |                                 StatusCommand::Help => { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                     reply.push("Mention the group user in a top-level post to share it with the group's members.".to_string()); |  |  |  |                                     replies.push("Mention the group user in a top-level post to share it with the group's members.".to_string()); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                     reply.push("Posts with commands won't be shared. Supported commands:".to_string()); |  |  |  |                                     replies.push("Posts with commands won't be shared. Supported commands:".to_string()); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                     reply.push("/ignore, /ign, /i - don't run any commands in the post".to_string()); |  |  |  |                                     replies.push("/ignore, /ign, /i - don't run any commands in the post".to_string()); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                     reply.push("/boost, /b - boost the replied-to post into the group".to_string()); |  |  |  |                                     replies.push("/boost, /b - boost the replied-to post into the group".to_string()); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                     reply.push("/leave - leave the group as a member".to_string()); |  |  |  |                                     replies.push("/leave - leave the group as a member".to_string()); | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |                                     if is_admin { |  |  |  |                                     if is_admin { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         reply.push("/members".to_string()); |  |  |  |                                         replies.push("/members".to_string()); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                         reply.push("/kick, /remove user - kick a member".to_string()); |  |  |  |                                         replies.push("/kick, /remove user - kick a member".to_string()); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                         reply.push("/add user - add a member".to_string()); |  |  |  |                                         replies.push("/add user - add a member".to_string()); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                         reply.push("/ban x - ban a user or a server".to_string()); |  |  |  |                                         replies.push("/ban x - ban a user or a server".to_string()); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                         reply.push("/unban x - lift a ban".to_string()); |  |  |  |                                         replies.push("/unban x - lift a ban".to_string()); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                         reply.push("/op, /admin user - give admin rights".to_string()); |  |  |  |                                         replies.push("/op, /admin user - give admin rights".to_string()); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                         reply.push("/unop, /unadmin user - remove admin rights".to_string()); |  |  |  |                                         replies.push("/unop, /unadmin user - remove admin rights".to_string()); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                         reply.push("/opengroup, /closegroup - control posting access".to_string()); |  |  |  |                                         replies.push("/opengroup, /closegroup - control posting access".to_string()); | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                     } |  |  |  |                                     } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 } |  |  |  |                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 StatusCommand::ListMembers => { |  |  |  |                                 StatusCommand::ListMembers => { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                     if is_admin { |  |  |  |                                     if is_admin { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         reply.push("Member list:".to_string()); |  |  |  |                                         if self.config.is_member_only() { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                         let admins = self.config.get_admins().collect::<HashSet<_>>(); |  |  |  |                                             replies.push("Group members:".to_string()); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                         let members = self.config.get_members().collect::<Vec<_>>(); |  |  |  |                                             let admins = self.config.get_admins().collect::<HashSet<_>>(); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                         for m in members { |  |  |  |                                             let mut members = self.config.get_members().collect::<Vec<_>>(); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                             if admins.contains(&m) { |  |  |  |                                             members.extend(admins.iter()); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                                 reply.push(format!("{} [admin]", m)); |  |  |  |                                             members.sort(); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                             } else { |  |  |  |                                             members.dedup(); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                                 reply.push(format!("{}", m)); |  |  |  |                                             for m in members { | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                                 if admins.contains(&m) { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                                     replies.push(format!("{} [admin]", m)); | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                                 } else { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                                     replies.push(format!("{}", m)); | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                             } | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                         } else { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                             replies.push("Group admins:".to_string()); | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                             let mut admins = self.config.get_admins().collect::<Vec<_>>(); | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                             admins.sort(); | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                             for a in admins { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                                                 replies.push(format!("{}", a)); | 
			
		
	
		
		
			
				
					
					|  |  |  |                                             } |  |  |  |                                             } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         } |  |  |  |                                         } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                     } |  |  |  |                                     } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 } |  |  |  |                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 StatusCommand::Leave => { |  |  |  |                                 StatusCommand::Leave => { | 
			
		
	
		
		
			
				
					
					|  |  |  |                                     if self.config.is_member(&n.account.acct) { |  |  |  |                                     if self.config.is_member(¬if_acct) { | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                         any_admin_cmd = true; |  |  |  |                                         any_admin_cmd = true; | 
			
		
	
		
		
			
				
					
					|  |  |  |                                         let _ = self.config.set_member(&n.account.acct, false); |  |  |  |                                         let _ = self.config.set_member(¬if_acct, false); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                         reply.push("You left the group.".to_string()); |  |  |  |                                         replies.push("You left the group.".to_string()); | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                                     } |  |  |  |                                     } | 
			
		
	
		
		
			
				
					
					|  |  |  |                                 } |  |  |  |                                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |                             } |  |  |  |                             } | 
			
		
	
		
		
			
				
					
					|  |  |  |                         } |  |  |  |                         } | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |                         tokio::time::sleep(DELAY_BEFORE_ACTION).await; |  |  |  |                         tokio::time::sleep(DELAY_BEFORE_ACTION).await; | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                     } | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |                         if boost_prev { |  |  |  |                     if do_boost_prev_post { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                             self.client.reblog(&status.in_reply_to_id.as_ref().unwrap()).await |  |  |  |                         self.client.reblog(&status.in_reply_to_id.as_ref().unwrap()).await | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                 .log_error("Failed to boost"); |  |  |  |                             .log_error("Failed to boost"); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                         } |  |  |  |                     } | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |                         if !reply.is_empty() { |  |  |  |                     if !replies.is_empty() { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                             let r = reply.join("\n"); |  |  |  |                         let r = replies.join("\n"); | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |                             let post = StatusBuilder::new() |  |  |  |                         let post = StatusBuilder::new() | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                 .status(format!("@{user}\n{msg}", user=n.account.acct, msg=r)) |  |  |  |                             .status(format!("@{user}\n{msg}", user = notif_acct, msg = r)) | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                 .content_type("text/markdown") |  |  |  |                             .content_type("text/markdown") | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                 .visibility(Visibility::Direct) |  |  |  |                             .visibility(Visibility::Direct) | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                                 .build().expect("error build status"); |  |  |  |                             .build().expect("error build status"); | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |                             let _ = self.client.new_status(post).await.log_error("Failed to post"); |  |  |  |                         let _ = self.client.new_status(post).await.log_error("Failed to post"); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                         } |  |  |  |                     } | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |                         if any_admin_cmd { |  |  |  |                     if !announcements.is_empty() { | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                             self.save_if_needed().await.log_error("Failed to save"); |  |  |  |                         let msg = announcements.join("\n"); | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |                         } |  |  |  |                         let post = StatusBuilder::new() | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                             .status(format!("**📢 Group announcement**\n{msg}", msg = msg)) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                             .content_type("text/markdown") | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                             .visibility(Visibility::Unlisted) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                             .build().expect("error build status"); | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                         let _ = self.client.new_status(post).await.log_error("Failed to post"); | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                     } | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                     if any_admin_cmd { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                         self.save_if_needed().await.log_error("Failed to save"); | 
			
		
	
		
		
			
				
					
					|  |  |  |                     } |  |  |  |                     } | 
			
		
	
		
		
			
				
					
					|  |  |  |                 } |  |  |  |                 } | 
			
		
	
		
		
			
				
					
					|  |  |  |             } |  |  |  |             } | 
			
		
	
	
		
		
			
				
					|  |  | @ -434,11 +483,11 @@ impl GroupHandle { | 
			
		
	
		
		
			
				
					
					|  |  |  |                     format!( |  |  |  |                     format!( | 
			
		
	
		
		
			
				
					
					|  |  |  |                         "@{user} welcome to the group! This is a member-only group, you won't be \ |  |  |  |                         "@{user} welcome to the group! This is a member-only group, you won't be \ | 
			
		
	
		
		
			
				
					
					|  |  |  |                          able to post. Ask the group admins if you wish to join!\n\n\ |  |  |  |                          able to post. Ask the group admins if you wish to join!\n\n\ | 
			
		
	
		
		
			
				
					
					|  |  |  |                          Admins: {admins}", user = &n.account.acct, admins = admins.join(", ")) |  |  |  |                          Admins: {admins}", user = notif_acct, admins = admins.join(", ")) | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                 } else { |  |  |  |                 } else { | 
			
		
	
		
		
			
				
					
					|  |  |  |                     format!( |  |  |  |                     format!( | 
			
		
	
		
		
			
				
					
					|  |  |  |                         "@{user} welcome to the group! This is a public-access group. \ |  |  |  |                         "@{user} welcome to the group! This is a public-access group. \ | 
			
		
	
		
		
			
				
					
					|  |  |  |                          To share a post, tag the group user. Use /help for more info.", user = &n.account.acct) |  |  |  |                          To share a post, tag the group user. Use /help for more info.", user = notif_acct) | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |                 }; |  |  |  |                 }; | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |                 let post = StatusBuilder::new() |  |  |  |                 let post = StatusBuilder::new() | 
			
		
	
	
		
		
			
				
					|  |  | @ -451,11 +500,12 @@ impl GroupHandle { | 
			
		
	
		
		
			
				
					
					|  |  |  |             } |  |  |  |             } | 
			
		
	
		
		
			
				
					
					|  |  |  |             _ => {} |  |  |  |             _ => {} | 
			
		
	
		
		
			
				
					
					|  |  |  |         } |  |  |  |         } | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |         Ok(()) | 
			
		
	
		
		
			
				
					
					|  |  |  |     } |  |  |  |     } | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |     /// Catch up with missed notifications, returns true if any were handled
 |  |  |  |     /// Catch up with missed notifications, returns true if any were handled
 | 
			
		
	
		
		
			
				
					
					|  |  |  |     async fn catch_up_with_missed_notifications(&mut self) -> Result<bool, GroupError> { |  |  |  |     async fn catch_up_with_missed_notifications(&mut self) -> Result<bool, GroupError> { | 
			
		
	
		
		
			
				
					
					|  |  |  |         const MAX_CATCHUP_NOTIFS: usize = 25; |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |         let last_notif = self.config.get_last_notif(); |  |  |  |         let last_notif = self.config.get_last_notif(); | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         let notifications = self.client.notifications().await?; |  |  |  |         let notifications = self.client.notifications().await?; | 
			
		
	
	
		
		
			
				
					|  |  | @ -489,7 +539,8 @@ impl GroupHandle { | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         for n in notifs_to_handle { |  |  |  |         for n in notifs_to_handle { | 
			
		
	
		
		
			
				
					
					|  |  |  |             debug!("Handling missed notification: {}", NotificationDisplay(&n)); |  |  |  |             debug!("Handling missed notification: {}", NotificationDisplay(&n)); | 
			
		
	
		
		
			
				
					
					|  |  |  |             self.handle_notification(n).await; |  |  |  |             self.handle_notification(n).await | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |                 .log_error("Error handling a notification"); | 
			
		
	
		
		
			
				
					
					|  |  |  |         } |  |  |  |         } | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |         return Ok(true); |  |  |  |         return Ok(true); | 
			
		
	
	
		
		
			
				
					|  |  | 
 |