4 Commits
v0.2.4 ... main

Author SHA1 Message Date
df394a18e8 Bump version to 0.2.5
All checks were successful
CI / CI (push) Successful in 9m59s
Release / Publish to crates.io (push) Successful in 2m34s
Release / Publish to Docker Hub (push) Successful in 12m34s
2025-08-10 00:11:00 -07:00
3ca4376a5a Bump deps 2025-08-10 00:09:54 -07:00
ed0aa2a2bf Allow setting an avatar image for the bot's account 2025-08-10 00:06:22 -07:00
602d562c66 Fix repo/homepage URL
All checks were successful
CI / CI (push) Successful in 9m50s
2025-07-30 05:40:53 -07:00
6 changed files with 123 additions and 47 deletions

105
Cargo.lock generated
View File

@@ -64,9 +64,9 @@ dependencies = [
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.19" version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"anstyle-parse", "anstyle-parse",
@@ -94,22 +94,22 @@ dependencies = [
[[package]] [[package]]
name = "anstyle-query" name = "anstyle-query"
version = "1.1.3" version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
name = "anstyle-wincon" name = "anstyle-wincon"
version = "3.0.9" version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"once_cell_polyfill", "once_cell_polyfill",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@@ -320,9 +320,9 @@ dependencies = [
[[package]] [[package]]
name = "backon" name = "backon"
version = "1.5.1" version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "302eaff5357a264a2c42f127ecb8bac761cf99749fc3dc95677e2743991f99e7" checksum = "592277618714fbcecda9a02ba7a8781f319d26532a88553bbacc77ba5d2b3a8d"
dependencies = [ dependencies = [
"fastrand", "fastrand",
"gloo-timers", "gloo-timers",
@@ -358,7 +358,7 @@ checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
[[package]] [[package]]
name = "bebot" name = "bebot"
version = "0.2.4" version = "0.2.5"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"axum", "axum",
@@ -371,6 +371,9 @@ dependencies = [
"http", "http",
"log", "log",
"matrix-sdk", "matrix-sdk",
"mime",
"mime-sniffer",
"mime_guess2",
"quick-xml", "quick-xml",
"regex", "regex",
"reqwest", "reqwest",
@@ -462,9 +465,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.30" version = "1.2.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e"
dependencies = [ dependencies = [
"shlex", "shlex",
] ]
@@ -810,9 +813,9 @@ dependencies = [
[[package]] [[package]]
name = "event-listener" name = "event-listener"
version = "5.4.0" version = "5.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
dependencies = [ dependencies = [
"concurrent-queue", "concurrent-queue",
"parking", "parking",
@@ -1060,9 +1063,9 @@ dependencies = [
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.4.11" version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"bytes", "bytes",
@@ -1079,9 +1082,9 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.4" version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
[[package]] [[package]]
name = "headers" name = "headers"
@@ -1826,12 +1829,34 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime-sniffer"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b8b2a64cd735f1d5f17ff6701ced3cc3c54851f9448caf454cd9c923d812408"
dependencies = [
"mime",
"url",
]
[[package]] [[package]]
name = "mime2ext" name = "mime2ext"
version = "0.1.54" version = "0.1.54"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf6f36070878c42c5233846cd3de24cf9016828fd47bc22957a687298bb21fc" checksum = "cbf6f36070878c42c5233846cd3de24cf9016828fd47bc22957a687298bb21fc"
[[package]]
name = "mime_guess2"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1706dc14a2e140dec0a7a07109d9a3d5890b81e85bd6c60b906b249a77adf0ca"
dependencies = [
"mime",
"phf",
"phf_shared",
"unicase",
]
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.8.9" version = "0.8.9"
@@ -2034,6 +2059,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn",
"unicase",
] ]
[[package]] [[package]]
@@ -2043,6 +2069,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
dependencies = [ dependencies = [
"siphasher", "siphasher",
"unicase",
] ]
[[package]] [[package]]
@@ -2205,9 +2232,9 @@ checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
[[package]] [[package]]
name = "quick-xml" name = "quick-xml"
version = "0.38.0" version = "0.38.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8927b0664f5c5a98265138b7e3f90aa19a6b21353182469ace36d4ac527b7b1b" checksum = "9845d9dccf565065824e69f9f235fafba1587031eda353c1f1561cd6a6be78f4"
dependencies = [ dependencies = [
"memchr", "memchr",
"serde", "serde",
@@ -2488,9 +2515,9 @@ dependencies = [
[[package]] [[package]]
name = "ruma" name = "ruma"
version = "0.12.5" version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1d47e42b7dea75a468dea63a230f51331c58d690ca018ea1c6ac782ea98880c" checksum = "3714d4ebd4314e6510bc64194fcdea1b51fe47898169a08f1bb4912e5c10e2c5"
dependencies = [ dependencies = [
"assign", "assign",
"js_int", "js_int",
@@ -2561,9 +2588,9 @@ dependencies = [
[[package]] [[package]]
name = "ruma-events" name = "ruma-events"
version = "0.30.4" version = "0.30.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cdc7abec9bc2a9ca0b4831cc26ce97a6a8c39a0bde44a19281a719e861b4293" checksum = "f141b37dcd3cfa1199d6a13929db59be529b2c69107edc9f1702b81015e970b2"
dependencies = [ dependencies = [
"as_variant", "as_variant",
"indexmap", "indexmap",
@@ -2721,9 +2748,9 @@ dependencies = [
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.21" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]] [[package]]
name = "ryu" name = "ryu"
@@ -2748,9 +2775,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "security-framework" name = "security-framework"
version = "3.2.0" version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"core-foundation 0.10.1", "core-foundation 0.10.1",
@@ -2819,9 +2846,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.141" version = "1.0.142"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@@ -2937,9 +2964,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.10" version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
[[package]] [[package]]
name = "smallvec" name = "smallvec"
@@ -3185,9 +3212,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.47.0" version = "1.47.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35" checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@@ -3236,9 +3263,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.15" version = "0.7.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
@@ -4075,9 +4102,9 @@ dependencies = [
[[package]] [[package]]
name = "zerovec" name = "zerovec"
version = "0.11.2" version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
dependencies = [ dependencies = [
"yoke", "yoke",
"zerofrom", "zerofrom",

View File

@@ -1,13 +1,13 @@
[package] [package]
name = "bebot" name = "bebot"
version = "0.2.4" version = "0.2.5"
description = "Gitlab webhook bot that publishes events to Matrix" description = "Gitlab webhook bot that publishes events to Matrix"
license = "AGPL-3.0" license = "AGPL-3.0"
authors = [ authors = [
"Brian J. Tarricone <brian@tarricone.org>", "Brian J. Tarricone <brian@tarricone.org>",
] ]
homepage = "https://git.spurint.org/kelnos/bebot" homepage = "https://git.spurint.org/brian/bebot"
repository = "https://git.spurint.org/kelnos/bebot" repository = "https://git.spurint.org/brian/bebot"
edition = "2021" edition = "2021"
categories = [ categories = [
"development-tools", "development-tools",
@@ -34,6 +34,9 @@ futures = "0.3"
http = "1.3" http = "1.3"
log = { version = "0.4", features = ["std"] } log = { version = "0.4", features = ["std"] }
matrix-sdk = { version = "0.13", features = ["anyhow", "markdown", "rustls-tls"], default-features = false } matrix-sdk = { version = "0.13", features = ["anyhow", "markdown", "rustls-tls"], default-features = false }
mime = "0.3"
mime-sniffer = "0.1"
mime_guess2 = "2"
quick-xml = { version = "0.38", features = ["serialize"] } quick-xml = { version = "0.38", features = ["serialize"] }
regex = "1" regex = "1"
reqwest = { version = "0.12", default-features = false, features = ["charset", "http2", "gzip", "rustls-tls-native-roots", "system-proxy"] } reqwest = { version = "0.12", default-features = false, features = ["charset", "http2", "gzip", "rustls-tls-native-roots", "system-proxy"] }

View File

@@ -6,6 +6,8 @@ bind_port: 3000
user_id: "@mybebot:example.com" user_id: "@mybebot:example.com"
# Password for Matrix user. # Password for Matrix user.
password: "secret-matrix-account-password" password: "secret-matrix-account-password"
# Optional path to an image file to use as the bot account's avatar.
avatar_image_path: "/path/to/avatar.jpg"
# All Gitlab-specific settings are under here. # All Gitlab-specific settings are under here.
gitlab_webhook: gitlab_webhook:
# Optional prefix to serve the webhook path under (default is empty string). # Optional prefix to serve the webhook path under (default is empty string).

View File

@@ -87,6 +87,7 @@ pub struct Config {
#[serde(deserialize_with = "crate::matrix::deser_user_id")] #[serde(deserialize_with = "crate::matrix::deser_user_id")]
pub user_id: OwnedUserId, pub user_id: OwnedUserId,
pub password: String, pub password: String,
pub avatar_image_path: Option<PathBuf>,
pub gitlab_webhook: Option<GitlabWebhookConfig>, pub gitlab_webhook: Option<GitlabWebhookConfig>,
pub mail_archive: Option<MailArchiveConfig>, pub mail_archive: Option<MailArchiveConfig>,
} }

View File

@@ -41,13 +41,25 @@ async fn run() -> anyhow::Result<()> {
.ok_or_else(|| anyhow!("Config file should be passed as only parameter"))?; .ok_or_else(|| anyhow!("Config file should be passed as only parameter"))?;
let mut config = config::load(config_path).await?; let mut config = config::load(config_path).await?;
let matrix_client = matrix::connect(&config).await.context("Failed to connect to Matrix")?; let mut join_handles = Vec::default();
let handles = if let Some(mail_archive) = config.mail_archive.take() { let matrix_client = matrix::connect(&config).await.context("Failed to connect to Matrix")?;
if let Some(avatar_path) = config.avatar_image_path {
let matrix_client = matrix_client.clone();
let handle = tokio::spawn(async move {
if let Err(err) = matrix::set_avatar_if_needed(&matrix_client, avatar_path).await {
warn!("Failed to set matrix avatar: {err}");
}
});
join_handles.push(handle);
}
let mail_join_handles = if let Some(mail_archive) = config.mail_archive.take() {
mail_archive::start_polling(mail_archive, matrix_client.clone())? mail_archive::start_polling(mail_archive, matrix_client.clone())?
} else { } else {
vec![] vec![]
}; };
join_handles.extend(mail_join_handles);
if let Some(gitlab_webhook) = config.gitlab_webhook.take() { if let Some(gitlab_webhook) = config.gitlab_webhook.take() {
let gitlab = gitlab_webhook::build_route(gitlab_webhook, matrix_client.clone()); let gitlab = gitlab_webhook::build_route(gitlab_webhook, matrix_client.clone());
@@ -60,7 +72,7 @@ async fn run() -> anyhow::Result<()> {
axum::serve(listener, gitlab).await?; axum::serve(listener, gitlab).await?;
} }
join_all(handles).await; join_all(join_handles).await;
error!("No functionality is configured; exiting"); error!("No functionality is configured; exiting");
exit(1); exit(1);

View File

@@ -14,16 +14,19 @@
// You should have received a copy of the GNU Affero General Public License // 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/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
use std::{fmt, process::exit, time::Duration}; use std::{fmt, path::Path, process::exit, time::Duration};
use futures::TryFutureExt;
use matrix_sdk::{ use matrix_sdk::{
config::SyncSettings, config::SyncSettings,
media::MediaFormat,
room::Room, room::Room,
ruma::{OwnedRoomOrAliasId, OwnedUserId, RoomOrAliasId, UserId}, ruma::{OwnedRoomOrAliasId, OwnedUserId, RoomOrAliasId, UserId},
BaseRoom, Client, BaseRoom, Client,
}; };
use mime_sniffer::MimeTypeSnifferExt;
use serde::de; use serde::de;
use tokio::time::sleep; use tokio::{fs, time::sleep};
use crate::config::Config; use crate::config::Config;
@@ -60,6 +63,34 @@ pub async fn connect(config: &Config) -> anyhow::Result<Client> {
Ok(client) Ok(client)
} }
pub async fn set_avatar_if_needed<P: AsRef<Path>>(client: &Client, avatar_path: P) -> anyhow::Result<()> {
let new_avatar = fs::read(&avatar_path).await?;
let account = client.account();
let cur_avatar = account.get_avatar(MediaFormat::File).await?;
if cur_avatar.as_ref() != Some(&new_avatar) {
// Falling back to extension matching because for some reason mime-sniffer can't handle SVG
// files, even though it seems like it should be able to.
if let Some(media_type) = new_avatar
.as_slice()
.sniff_mime_type_ext()
.or_else(|| mime_guess2::from_path(&avatar_path).first())
{
info!("Setting avatar image from '{}'", avatar_path.as_ref().to_string_lossy());
account
.upload_avatar(&media_type, new_avatar)
.map_err(Into::into)
.map_ok(|_| ())
.await
} else {
Err(anyhow!("Cannot determine media type of avatar image"))
}
} else {
Ok(())
}
}
pub async fn ensure_room_joined(matrix_client: &Client, room_id: &OwnedRoomOrAliasId) -> anyhow::Result<Room> { pub async fn ensure_room_joined(matrix_client: &Client, room_id: &OwnedRoomOrAliasId) -> anyhow::Result<Room> {
fn room_matches(a_room: &BaseRoom, our_room: &OwnedRoomOrAliasId) -> bool { fn room_matches(a_room: &BaseRoom, our_room: &OwnedRoomOrAliasId) -> bool {
let our_room_str = our_room.as_str(); let our_room_str = our_room.as_str();