Compare commits

..
11 changed files with 41 additions and 149 deletions

View file

@ -9,7 +9,7 @@ license = "AGPL-3.0+"
[dependencies]
clap = { version = "4.5", features = [ "cargo" ] }
forgejo-hooks = "*"
gitlab = { version = "0.1706", optional = true }
gitlab = "0.1706"
hyper = { version = "1.6", default-features = false, features = [ "http1", "server" ] }
hyper-util = { version = "0.1", features = [ "tokio" ] }
http-body-util = "0.1"
@ -19,21 +19,16 @@ 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 = "1.1"
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"
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" ] }
hmac = "0.12"
sha2 = "0.10"
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" }
[features]
gitlab = [ "dep:gitlab" ]
syntax-highlighting = ["xmpp/syntax-highlighting"]
vendored-openssl = [ "xmpp/vendored-openssl", "git2/vendored-openssl" ]

View file

@ -7,12 +7,6 @@ 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.

View file

@ -1,41 +0,0 @@
// 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 <https://www.gnu.org/licenses/>.
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");
}

View file

@ -1,8 +0,0 @@
admins = [ "foo@localhost" ]
jid = "bar@localhost"
password = "bonjourxmpp"
rooms = [ "room@muc.localhost" ]
secret = "bonjourforgejo"
addr = "[::1]:5225"

View file

@ -14,7 +14,6 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::Error;
use crate::config::Config;
use crate::hooks::{Hook, IssueAction, MergeRequestAction, format_hook};
use chrono::{TimeDelta, Utc};
@ -24,28 +23,34 @@ use xmpp::jid::{BareJid, Jid, ResourcePart};
use xmpp::parsers::message::MessageType;
use xmpp::{
Agent, ClientBuilder, ClientFeature, ClientType, Config as AgentConfig, Event, RoomNick,
message::send::{MessageSettings, RawMessageSettings},
muc::room::JoinRoomSettings,
message::send::RawMessageSettings, muc::room::JoinRoomSettings,
};
pub struct XmppClient {
is_online: bool,
agent: Agent,
rooms: Vec<BareJid>,
nickname: ResourcePart,
config: Config,
admins: Vec<BareJid>,
/// Keep around messages we've sent recently so we're able to prevent spamming the same type of
/// messages
recent_hooks: Vec<Hook>,
}
impl XmppClient {
pub fn new(jid: BareJid, password: &str, nickname: ResourcePart, config: Config) -> XmppClient {
let agent_config = AgentConfig {
pub fn new(
jid: BareJid,
password: &str,
rooms: Vec<BareJid>,
nickname: ResourcePart,
admins: Vec<BareJid>,
) -> XmppClient {
let config = AgentConfig {
bookmarks_autojoin: false,
..AgentConfig::default()
};
let agent = ClientBuilder::new(jid, password)
.set_config(agent_config)
.set_config(config)
.set_client(ClientType::Bot, "xmpp-rs")
.set_website("https://gitlab.com/xmpp-rs/xmpp-rs")
.set_default_nick(&nickname)
@ -55,8 +60,9 @@ impl XmppClient {
XmppClient {
is_online: false,
agent,
rooms,
nickname,
config,
admins,
recent_hooks: Vec::new(),
}
}
@ -67,7 +73,7 @@ impl XmppClient {
Event::Online => {
self.is_online = true;
debug!("XMPP Online");
for room in &self.config.rooms {
for room in &self.rooms {
self.agent
.join_room(JoinRoomSettings {
nick: Some(RoomNick::from_resource_ref(self.nickname.as_ref())),
@ -77,43 +83,20 @@ impl XmppClient {
}
}
Event::ChatMessage(_id, bare, message, _timeinfo) => {
if !self.config.admins.contains(&bare) {
if !self.admins.contains(&bare) {
debug!("Received chat message from {}, not in admins", bare);
continue;
}
debug!("Received chat message from {}: {}", bare, message);
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" => {
if message == "rejoin" {
for room in &self.rooms {
self.agent
.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"),
.join_room(JoinRoomSettings {
nick: Some(RoomNick::from_resource_ref(self.nickname.as_ref())),
force_resync: true,
..JoinRoomSettings::new(room.clone())
})
.await
}
@ -226,7 +209,7 @@ impl XmppClient {
}
if let Some(display) = format_hook(&wh) {
debug!("Hook: {}", display);
for room in &self.config.rooms {
for room in &self.rooms {
self.agent
.send_raw_message(RawMessageSettings {
recipient: Jid::from(room.clone()),

View file

@ -23,15 +23,7 @@ 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)]
#[derive(Debug, Serialize, Deserialize)]
pub struct Config {
/// Accounts trusted for admin tasks
#[serde(default = "Vec::new")]
@ -58,11 +50,6 @@ 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 {

View file

@ -38,11 +38,7 @@ impl From<FjIssue> for Issue {
action: Some(other.action.into()),
title: other.issue.title,
author: User {
name: other
.comment
.as_ref()
.map(|c| c.user.login.clone())
.unwrap_or(other.issue.user.login),
name: other.issue.user.login,
},
repository: Repository {
name: other.repository.name,
@ -64,11 +60,7 @@ impl From<FjIssue> for Note {
merge_request: None,
snippet: false,
author: User {
name: other
.comment
.as_ref()
.map(|c| c.user.login.clone())
.unwrap_or(other.issue.user.login),
name: other.issue.user.login,
},
repository: Repository {
name: other.repository.name,
@ -77,7 +69,6 @@ impl From<FjIssue> for Note {
.comment
.map(|c| c.html_url)
.unwrap_or(other.issue.html_url),
is_update: other.action == FjIssueAction::Edited,
}
}
}

View file

@ -14,12 +14,11 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
mod forgejo;
#[cfg(feature = "gitlab")]
mod gitlab;
mod types;
pub use crate::hooks::forgejo::ForgejoHook;
#[cfg(feature = "gitlab")]
#[allow(deprecated)]
pub use crate::hooks::gitlab::GitlabHook;
pub use crate::hooks::types::Hook;
pub use crate::hooks::types::{IssueAction, MergeRequestAction, WikiAction};
@ -109,26 +108,19 @@ pub fn format_hook(hook: &Hook) -> Option<String> {
if note.snippet {
return None;
}
let updated_fmt = if note.is_update {
"updated comment"
} else {
"commented"
};
if let Some(commit) = &note.commit {
format!(
"[{}] {} {updated_fmt} on commit {:?} <{}>",
"[{}] {} commented on commit {:?} <{}>",
note.repository.name, note.author.name, commit.ref_, commit.url,
)
} else if let Some(issue) = &note.issue {
format!(
"[{}] {} {updated_fmt} on issue {}: {} <{}>",
"[{}] {} commented on issue {}: {} <{}>",
note.repository.name, note.author.name, issue.id, issue.title, note.url,
)
} else if let Some(mr) = &note.merge_request {
format!(
"[{}] {} {updated_fmt} on merge request {}: {} <{}>",
"[{}] {} commented on merge request {}: {} <{}>",
note.repository.name, note.author.name, mr.id, mr.title, note.url,
)
} else {

View file

@ -116,7 +116,6 @@ pub struct Note {
pub repository: Repository,
pub author: User,
pub url: String,
pub is_update: bool,
}
#[derive(Debug, Clone, PartialEq)]

View file

@ -56,10 +56,11 @@ async fn main() -> Result<(), Error> {
let (value_tx, value_rx) = mpsc::unbounded_channel::<Hook>();
let mut bot = XmppClient::new(
config.jid.clone(),
config.jid,
config.password.as_str(),
config.nickname.clone(),
config.clone(),
config.rooms,
config.nickname,
config.admins,
);
let xmpp_handle = tokio::task::spawn(async move {

View file

@ -14,7 +14,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::error::Error;
#[cfg(feature = "gitlab")]
#[allow(deprecated)]
use crate::hooks::GitlabHook;
use crate::hooks::{ForgejoHook, Hook};
@ -22,7 +22,7 @@ use std::convert::Infallible;
use std::io::Read;
use bytes::{Buf, Bytes};
use hmac::{Hmac, KeyInit, Mac};
use hmac::{Hmac, Mac};
use http_body_util::{BodyExt, Full};
use hyper::{Method, Request, Response, body::Incoming, header};
use log::{debug, error, trace};
@ -62,7 +62,6 @@ async fn hooks_inner(req: Request<Incoming>, secret: &str) -> Result<Hook, Error
let whole_body = req.collect().await?.aggregate();
whole_body.reader().read_to_end(&mut payload)?;
#[cfg(feature = "gitlab")]
if let Some(val) = headers.get("X-Gitlab-Token") {
if secret != val {
return Err(Error::InvalidSecret);