diff --git a/src/bot.rs b/src/bot.rs index 9bffc3e..a505251 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -14,8 +14,9 @@ // along with this program. If not, see . use crate::Error; -use crate::hooks::{Hook, format_hook}; +use crate::hooks::{Hook, IssueAction, MergeRequestAction, format_hook}; +use chrono::TimeDelta; use log::debug; use tokio::{signal::ctrl_c, sync::mpsc}; use xmpp::jid::{BareJid, Jid, ResourcePart}; @@ -31,6 +32,9 @@ pub struct XmppClient { rooms: Vec, nickname: ResourcePart, admins: Vec, + /// Keep around messages we've sent recently so we're able to prevent spamming the same type of + /// messages + recent_hooks: Vec, } impl XmppClient { @@ -59,6 +63,7 @@ impl XmppClient { rooms, nickname, admins, + recent_hooks: Vec::new(), } } @@ -125,8 +130,79 @@ impl XmppClient { } } + /// Compare incoming hook with recently sent hooks and update the list. + /// Compare only hook type and author. If they match, check how long ago it was. + /// Returns true if the hook is recent enough and thus not to be sent. + fn update_recent_hooks(&mut self, new_hook: Hook) -> bool { + // Do we need to remove/update the hook? + let mut removal: Option = None; + + for (i, old_hook) in self.recent_hooks.iter().enumerate() { + match (old_hook, &new_hook) { + (_, &Hook::MergeRequest(ref new)) + if new.action != Some(MergeRequestAction::Update) => + { + return false; + } + (_, &Hook::Issue(ref new)) if new.action != Some(IssueAction::Update) => { + return false; + } + (&Hook::MergeRequest(ref old), &Hook::MergeRequest(ref new)) => { + // Action is MergeRequestAction::Update, otherwise it would have matched the other branch + // and the method would have returned. + if old.id == new.id { + if old.author.name == new.author.name + && old.updated_at > (new.updated_at - TimeDelta::minutes(5)) + { + // If everything matches and we're still within the time frame, let the old hook + // expire, don't update it. + return true; + } else { + // The old hook either doesn't match the new author (we want to announce messages + // from different authors), or has expired, and in both cases can be replaced by the + // new hook. + removal = Some(i); + break; + } + } + } + (&Hook::Issue(ref old), &Hook::Issue(ref new)) => { + // See the MergeRequest branch for comments + if old.id == new.id { + if old.author.name == new.author.name + && old.updated_at > (new.updated_at - TimeDelta::minutes(5)) + { + return true; + } else { + removal = Some(i); + break; + } + } + } + _ => (), + } + } + + if let Some(index) = removal { + self.recent_hooks.swap_remove(index); + } + + // The new hook hasn't been matched and needs to be added to the list, or it has and will + // replace the matched hook. + match new_hook { + Hook::Issue(_) | Hook::MergeRequest(_) => self.recent_hooks.push(new_hook), + _ => (), + } + + false + } + pub async fn hook(&mut self, wh: Hook) { debug!("XMPP Bot Processing Hook"); + if self.update_recent_hooks(wh.clone()) { + debug!("Hook already sent recently"); + return; + } if let Some(display) = format_hook(&wh) { debug!("Hook: {}", display); for room in &self.rooms {