From 849ecbd5dcf6c3dfbdad39ed95a7826a5d436946 Mon Sep 17 00:00:00 2001 From: pep Date: Thu, 8 Jan 2026 22:23:55 +0100 Subject: [PATCH 01/10] bot: pass in Config as a parameter In an attempt to pass in more stuff to the bot Signed-off-by: pep --- src/bot.rs | 27 ++++++++++----------------- src/config.rs | 2 +- src/main.rs | 7 +++---- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/bot.rs b/src/bot.rs index deec1ff..be7a56d 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -14,6 +14,7 @@ // along with this program. If not, see . use crate::Error; +use crate::config::Config; use crate::hooks::{Hook, IssueAction, MergeRequestAction, format_hook}; use chrono::{TimeDelta, Utc}; @@ -29,28 +30,21 @@ use xmpp::{ pub struct XmppClient { is_online: bool, agent: Agent, - rooms: Vec, nickname: ResourcePart, - admins: Vec, + config: Config, /// Keep around messages we've sent recently so we're able to prevent spamming the same type of /// messages recent_hooks: Vec, } impl XmppClient { - pub fn new( - jid: BareJid, - password: &str, - rooms: Vec, - nickname: ResourcePart, - admins: Vec, - ) -> XmppClient { - let config = AgentConfig { + pub fn new(jid: BareJid, password: &str, nickname: ResourcePart, config: Config) -> XmppClient { + let agent_config = AgentConfig { bookmarks_autojoin: false, ..AgentConfig::default() }; let agent = ClientBuilder::new(jid, password) - .set_config(config) + .set_config(agent_config) .set_client(ClientType::Bot, "xmpp-rs") .set_website("https://gitlab.com/xmpp-rs/xmpp-rs") .set_default_nick(&nickname) @@ -60,9 +54,8 @@ impl XmppClient { XmppClient { is_online: false, agent, - rooms, nickname, - admins, + config, recent_hooks: Vec::new(), } } @@ -73,7 +66,7 @@ impl XmppClient { Event::Online => { self.is_online = true; debug!("XMPP Online"); - for room in &self.rooms { + for room in &self.config.rooms { self.agent .join_room(JoinRoomSettings { nick: Some(RoomNick::from_resource_ref(self.nickname.as_ref())), @@ -83,7 +76,7 @@ impl XmppClient { } } Event::ChatMessage(_id, bare, message, _timeinfo) => { - if !self.admins.contains(&bare) { + if !self.config.admins.contains(&bare) { debug!("Received chat message from {}, not in admins", bare); continue; } @@ -91,7 +84,7 @@ impl XmppClient { debug!("Received chat message from {}: {}", bare, message); if message == "rejoin" { - for room in &self.rooms { + for room in &self.config.rooms { self.agent .join_room(JoinRoomSettings { nick: Some(RoomNick::from_resource_ref(self.nickname.as_ref())), @@ -209,7 +202,7 @@ impl XmppClient { } if let Some(display) = format_hook(&wh) { debug!("Hook: {}", display); - for room in &self.rooms { + for room in &self.config.rooms { self.agent .send_raw_message(RawMessageSettings { recipient: Jid::from(room.clone()), diff --git a/src/config.rs b/src/config.rs index 81a8e82..487597c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -23,7 +23,7 @@ use xmpp::jid::{BareJid, ResourcePart}; use crate::error::Error; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Config { /// Accounts trusted for admin tasks #[serde(default = "Vec::new")] diff --git a/src/main.rs b/src/main.rs index e9c22d6..8f2f0a3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,11 +56,10 @@ async fn main() -> Result<(), Error> { let (value_tx, value_rx) = mpsc::unbounded_channel::(); let mut bot = XmppClient::new( - config.jid, + config.jid.clone(), config.password.as_str(), - config.rooms, - config.nickname, - config.admins, + config.nickname.clone(), + config.clone(), ); let xmpp_handle = tokio::task::spawn(async move { From 457c40bffe718cadaf03c099706f3304220afbd0 Mon Sep 17 00:00:00 2001 From: pep Date: Thu, 8 Jan 2026 23:16:01 +0100 Subject: [PATCH 02/10] Add build script to make git version available in code Signed-off-by: pep --- Cargo.toml | 3 +++ build.rs | 41 +++++++++++++++++++++++++++++++++++++++++ src/config.rs | 13 +++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 build.rs diff --git a/Cargo.toml b/Cargo.toml index 288bc61..3073801 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,9 @@ hex = "0.4" camino = { version = "1.2", features = ["serde1"] } chrono = { version = "0.4", default-features = false } +[build-dependencies] +git2 = "0.20" + [patch.crates-io] forgejo-hooks = { path = "forgejo-hooks" } diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..da0edcf --- /dev/null +++ b/build.rs @@ -0,0 +1,41 @@ +// Copyright (C) 2026-2099 The crate authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Affero General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at your +// option) any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License +// for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use std::env; +use std::fs; +use std::path::Path; + +use git2::Repository; + +fn main() { + let oid = { + match Repository::open(".") { + Ok(repo) => { + let head = repo.head().unwrap(); + format!("{}", repo.refname_to_id(head.name().unwrap()).unwrap()) + } + Err(e) => { + println!("cargo::warning={}", e); + String::new() + } + } + }; + + let out_dir = env::var_os("OUT_DIR").unwrap(); + let dest_path = Path::new(&out_dir).join("GIT_COMMIT"); + fs::write(&dest_path, oid).unwrap(); + + println!("cargo::rerun-if-changed=build.rs"); +} diff --git a/src/config.rs b/src/config.rs index 487597c..f5b7f5c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -23,6 +23,14 @@ use xmpp::jid::{BareJid, ResourcePart}; use crate::error::Error; +const MAYBE_VERSION: &'static str = include_str!(concat!(env!("OUT_DIR"), "/GIT_COMMIT")); +const fn version() -> Option<&'static str> { + match MAYBE_VERSION { + v if v.is_empty() => None, + v => Some(v), + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Config { /// Accounts trusted for admin tasks @@ -50,6 +58,11 @@ pub struct Config { /// HTTP Webhook listening address and port, e.g., 127.0.0.1:1234 or [::1]:1234 #[serde(default = "Config::default_addr")] pub addr: SocketAddr, + + /// Software version + #[serde(skip)] + #[serde(default = "version")] + pub version: Option<&'static str>, } impl Config { From 6647a9ae92264b75bdd04d64cdc668b4d7796ca5 Mon Sep 17 00:00:00 2001 From: pep Date: Thu, 8 Jan 2026 23:41:35 +0100 Subject: [PATCH 03/10] bot: Add 'version' and help (any other message) command Signed-off-by: pep --- src/bot.rs | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/src/bot.rs b/src/bot.rs index be7a56d..4d09ffa 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -24,7 +24,8 @@ use xmpp::jid::{BareJid, Jid, ResourcePart}; use xmpp::parsers::message::MessageType; use xmpp::{ Agent, ClientBuilder, ClientFeature, ClientType, Config as AgentConfig, Event, RoomNick, - message::send::RawMessageSettings, muc::room::JoinRoomSettings, + message::send::{MessageSettings, RawMessageSettings}, + muc::room::JoinRoomSettings, }; pub struct XmppClient { @@ -83,13 +84,36 @@ impl XmppClient { debug!("Received chat message from {}: {}", bare, message); - if message == "rejoin" { - for room in &self.config.rooms { + match message.as_str() { + "rejoin" => { + for room in &self.config.rooms { + self.agent + .join_room(JoinRoomSettings { + nick: Some(RoomNick::from_resource_ref( + self.nickname.as_ref(), + )), + force_resync: true, + ..JoinRoomSettings::new(room.clone()) + }) + .await + } + } + "version" => { self.agent - .join_room(JoinRoomSettings { - nick: Some(RoomNick::from_resource_ref(self.nickname.as_ref())), - force_resync: true, - ..JoinRoomSettings::new(room.clone()) + .send_message(MessageSettings { + recipient: bare, + message: self.config.version.unwrap_or("No version specified."), + lang: Some("en"), + }) + .await + } + _ => { + let message = "Help:\nrejoin: Re-join all rooms.\nversion: Display software version."; + self.agent + .send_message(MessageSettings { + recipient: bare, + message, + lang: Some("en"), }) .await } From be1379f13f7f786c1e9e0234df18b7019ac3568b Mon Sep 17 00:00:00 2001 From: pep Date: Thu, 15 Jan 2026 18:52:09 +0100 Subject: [PATCH 04/10] Update xmpp dep to 22292c86c Signed-off-by: pep --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3073801..c74b55a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ pretty_env_logger = "0.5" serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0" toml = "0.9" -xmpp = { git = "https://gitlab.com/xmpp-rs/xmpp-rs", branch = "xmpp-join-resync", default-features = false, features = [ "serde", "starttls", "rustls-native-certs", "aws_lc_rs" ] } +xmpp = { git = "https://gitlab.com/xmpp-rs/xmpp-rs", rev = "22292c86c", default-features = false, features = [ "serde", "starttls", "rustls-native-certs", "aws_lc_rs" ] } hmac = "0.12" sha2 = "0.10" hex = "0.4" From 43106fc6571aa9990b67e44acab9fa3674e16d6d Mon Sep 17 00:00:00 2001 From: pep Date: Tue, 26 May 2026 11:45:31 +0200 Subject: [PATCH 05/10] Update dependencies Update xmpp-rs to 559159d4, hmac to 0.13 and sasl to 0.11 Signed-off-by: pep --- Cargo.toml | 8 ++++---- src/web.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c74b55a..429b19e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,10 +19,10 @@ tokio = { version = "1", default-features = false, features = [ "rt", "net", "sy pretty_env_logger = "0.5" serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0" -toml = "0.9" -xmpp = { git = "https://gitlab.com/xmpp-rs/xmpp-rs", rev = "22292c86c", default-features = false, features = [ "serde", "starttls", "rustls-native-certs", "aws_lc_rs" ] } -hmac = "0.12" -sha2 = "0.10" +toml = "1.1" +xmpp = { git = "https://code.bouah.net/pep/xmpp-rs", rev = "559159d4", default-features = false, features = [ "serde", "starttls", "rustls-native-certs", "aws_lc_rs" ] } +hmac = "0.13" +sha2 = "0.11" hex = "0.4" camino = { version = "1.2", features = ["serde1"] } chrono = { version = "0.4", default-features = false } diff --git a/src/web.rs b/src/web.rs index 50fbc0d..83e0de8 100644 --- a/src/web.rs +++ b/src/web.rs @@ -22,7 +22,7 @@ use std::convert::Infallible; use std::io::Read; use bytes::{Buf, Bytes}; -use hmac::{Hmac, Mac}; +use hmac::{Hmac, KeyInit, Mac}; use http_body_util::{BodyExt, Full}; use hyper::{Method, Request, Response, body::Incoming, header}; use log::{debug, error, trace}; From 5fc65c7636e3523b18bdaf6b1aa077ecf0ae9224 Mon Sep 17 00:00:00 2001 From: pep Date: Thu, 11 Jun 2026 22:03:16 +0200 Subject: [PATCH 06/10] Use author name of the current comment not the one of the issue Signed-off-by: pep --- src/hooks/forgejo.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/hooks/forgejo.rs b/src/hooks/forgejo.rs index 9a32686..d1defc3 100644 --- a/src/hooks/forgejo.rs +++ b/src/hooks/forgejo.rs @@ -38,7 +38,11 @@ impl From for Issue { action: Some(other.action.into()), title: other.issue.title, author: User { - name: other.issue.user.login, + name: other + .comment + .as_ref() + .map(|c| c.user.login.clone()) + .unwrap_or(other.issue.user.login), }, repository: Repository { name: other.repository.name, @@ -60,7 +64,11 @@ impl From for Note { merge_request: None, snippet: false, author: User { - name: other.issue.user.login, + name: other + .comment + .as_ref() + .map(|c| c.user.login.clone()) + .unwrap_or(other.issue.user.login), }, repository: Repository { name: other.repository.name, From d54ef5f265c064312bcd77745ffc06d536d1934e Mon Sep 17 00:00:00 2001 From: xmppftw Date: Tue, 23 Jun 2026 17:13:42 +0200 Subject: [PATCH 07/10] feat: gitlab feature --- Cargo.toml | 3 ++- src/hooks/mod.rs | 3 ++- src/web.rs | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 429b19e..bb9548b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ license = "AGPL-3.0+" [dependencies] clap = { version = "4.5", features = [ "cargo" ] } forgejo-hooks = "*" -gitlab = "0.1706" +gitlab = { version = "0.1706", optional = true } hyper = { version = "1.6", default-features = false, features = [ "http1", "server" ] } hyper-util = { version = "0.1", features = [ "tokio" ] } http-body-util = "0.1" @@ -34,4 +34,5 @@ git2 = "0.20" forgejo-hooks = { path = "forgejo-hooks" } [features] +gitlab = [ "dep:gitlab" ] syntax-highlighting = ["xmpp/syntax-highlighting"] diff --git a/src/hooks/mod.rs b/src/hooks/mod.rs index 8fcd544..68bf6c1 100644 --- a/src/hooks/mod.rs +++ b/src/hooks/mod.rs @@ -14,11 +14,12 @@ // along with this program. If not, see . mod forgejo; +#[cfg(feature = "gitlab")] mod gitlab; mod types; pub use crate::hooks::forgejo::ForgejoHook; -#[allow(deprecated)] +#[cfg(feature = "gitlab")] pub use crate::hooks::gitlab::GitlabHook; pub use crate::hooks::types::Hook; pub use crate::hooks::types::{IssueAction, MergeRequestAction, WikiAction}; diff --git a/src/web.rs b/src/web.rs index 83e0de8..3dc01f2 100644 --- a/src/web.rs +++ b/src/web.rs @@ -14,7 +14,7 @@ // along with this program. If not, see . use crate::error::Error; -#[allow(deprecated)] +#[cfg(feature = "gitlab")] use crate::hooks::GitlabHook; use crate::hooks::{ForgejoHook, Hook}; @@ -62,6 +62,7 @@ async fn hooks_inner(req: Request, secret: &str) -> Result Date: Tue, 23 Jun 2026 17:14:10 +0200 Subject: [PATCH 08/10] feat: vendored-openssl feature --- Cargo.toml | 3 ++- README.md | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bb9548b..0ffe8fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ pretty_env_logger = "0.5" serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0" toml = "1.1" -xmpp = { git = "https://code.bouah.net/pep/xmpp-rs", rev = "559159d4", default-features = false, features = [ "serde", "starttls", "rustls-native-certs", "aws_lc_rs" ] } +xmpp = { git = "https://gitlab.com/xmppftw/xmpp-rs", branch = "feat-openssl-vendored", default-features = false, features = [ "serde", "starttls", "rustls-native-certs", "aws_lc_rs" ] } hmac = "0.13" sha2 = "0.11" hex = "0.4" @@ -36,3 +36,4 @@ forgejo-hooks = { path = "forgejo-hooks" } [features] gitlab = [ "dep:gitlab" ] syntax-highlighting = ["xmpp/syntax-highlighting"] +vendored-openssl = [ "xmpp/vendored-openssl", "git2/vendored-openssl" ] diff --git a/README.md b/README.md index 5109c74..b3ba21b 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,12 @@ be somewhat of a testbed for xmpp-rs features. Originally based on tokio-webhook2muc. +# Cross-compile musl + +``` +cross build --release --target x86_64-unknown-linux-musl --features vendored-openssl +``` + ## License AGPL-3.0-or-later. See the LICENSE file. From 0763ce13c981f91e8700ccc719491c1ee607a168 Mon Sep 17 00:00:00 2001 From: xmppftw Date: Tue, 23 Jun 2026 17:15:36 +0200 Subject: [PATCH 09/10] meta: add exmaple config --- config.sample.toml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 config.sample.toml diff --git a/config.sample.toml b/config.sample.toml new file mode 100644 index 0000000..5fed1de --- /dev/null +++ b/config.sample.toml @@ -0,0 +1,8 @@ +admins = [ "foo@localhost" ] +jid = "bar@localhost" +password = "bonjourxmpp" + +rooms = [ "room@muc.localhost" ] +secret = "bonjourforgejo" + +addr = "[::1]:5225" From 732f77f141010c79b046b19e843aa27df6e2ddf9 Mon Sep 17 00:00:00 2001 From: xmppftw Date: Tue, 23 Jun 2026 17:29:43 +0200 Subject: [PATCH 10/10] feat: Notify when updating comment (not just commenting) --- src/hooks/forgejo.rs | 1 + src/hooks/mod.rs | 13 ++++++++++--- src/hooks/types.rs | 1 + 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/hooks/forgejo.rs b/src/hooks/forgejo.rs index d1defc3..47b9bad 100644 --- a/src/hooks/forgejo.rs +++ b/src/hooks/forgejo.rs @@ -77,6 +77,7 @@ impl From for Note { .comment .map(|c| c.html_url) .unwrap_or(other.issue.html_url), + is_update: other.action == FjIssueAction::Edited, } } } diff --git a/src/hooks/mod.rs b/src/hooks/mod.rs index 68bf6c1..3a2fce3 100644 --- a/src/hooks/mod.rs +++ b/src/hooks/mod.rs @@ -109,19 +109,26 @@ pub fn format_hook(hook: &Hook) -> Option { if note.snippet { return None; } + + let updated_fmt = if note.is_update { + "updated comment" + } else { + "commented" + }; + if let Some(commit) = ¬e.commit { format!( - "[{}] {} commented on commit {:?} <{}>", + "[{}] {} {updated_fmt} on commit {:?} <{}>", note.repository.name, note.author.name, commit.ref_, commit.url, ) } else if let Some(issue) = ¬e.issue { format!( - "[{}] {} commented on issue {}: {} <{}>", + "[{}] {} {updated_fmt} on issue {}: {} <{}>", note.repository.name, note.author.name, issue.id, issue.title, note.url, ) } else if let Some(mr) = ¬e.merge_request { format!( - "[{}] {} commented on merge request {}: {} <{}>", + "[{}] {} {updated_fmt} on merge request {}: {} <{}>", note.repository.name, note.author.name, mr.id, mr.title, note.url, ) } else { diff --git a/src/hooks/types.rs b/src/hooks/types.rs index 6525fe2..b828ebe 100644 --- a/src/hooks/types.rs +++ b/src/hooks/types.rs @@ -116,6 +116,7 @@ pub struct Note { pub repository: Repository, pub author: User, pub url: String, + pub is_update: bool, } #[derive(Debug, Clone, PartialEq)]