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 {