Watch
0
0
Fork
You've already forked hyperhive
0

matrix-mcp: join invites from the own homeserver automatically

This commit is contained in:
damocles 2026-11-07 10:50:32 +01:00 • committed by mara
commit 2728a02369

View file

@ -17,7 +17,10 @@
use std::collections::HashSet;
use std::hash::BuildHasher;
use matrix_sdk::{Client, ruma::OwnedRoomId};
use matrix_sdk::{
Client,
ruma::{OwnedRoomId, ServerName, UserId},
};
use tokio::sync::Mutex;
use crate::{handlers, wake};
@ -127,13 +130,41 @@ pub async fn sweep_invites<S: BuildHasher>(
// from the unread sweep's `<room>` key) so the two never collide; the
// harness coalesces the wake iff the summary is new or changed, so
// re-upserting an unchanged invite every tick is a cheap no-op.
let own_server = client.user_id().map(|u| u.server_name().to_owned());
for room in &current {
let room_id = room.room_id().to_owned();
let inviter = room
.invite_details()
.await
.ok()
.and_then(|d| d.inviter)
.map(|m| m.user_id().to_owned());
// An invite from the swarm's own homeserver is trusted: join it
// without waking the agent. A failed join falls through to the todo
// below, so the invite is never lost.
if is_local(own_server.as_deref(), inviter.as_deref()) {
match room.join().await {
Ok(()) => {
tracing::info!(room = %room_id, inviter = ?inviter, "matrix: auto-joined an invite from the swarm homeserver");
continue;
}
Err(e) => {
tracing::warn!(error = %e, room = %room_id, "matrix: auto-join failed; leaving the invite as a todo");
}
}
}
let label = room.name().unwrap_or_else(|| room_id.to_string());
let source = match &inviter {
Some(u) if !is_local(own_server.as_deref(), Some(u)) => {
format!("an external matrix user ({u})")
}
Some(u) => u.to_string(),
None => "an unknown inviter".to_owned(),
};
let summary = wake::tag_account(
account_tag,
format!(
"[matrix] invited to {label} ({room_id}) — \
"[matrix] invited to {label} ({room_id}) by {source} — \
use list_invites to see pending invites, resolve_invite to accept or reject"
),
);
@ -148,6 +179,12 @@ pub async fn sweep_invites<S: BuildHasher>(
}
}
/// Whether `inviter` is a user of the agent's own homeserver. No known
/// inviter or no known own server counts as external.
fn is_local(own: Option<&ServerName>, inviter: Option<&UserId>) -> bool {
matches!((own, inviter), (Some(own), Some(u)) if u.server_name() == own)
}
/// Todo dedup key for a pending invite. Namespaced with an `invite:`
/// prefix so an invited room and an unread room (which the unread sweep
/// keys by bare room id) never share a todo row. `pub(crate)` so the
@ -155,3 +192,28 @@ pub async fn sweep_invites<S: BuildHasher>(
pub(crate) fn invite_key(room_id: &matrix_sdk::ruma::RoomId) -> String {
format!("invite:{room_id}")
}
#[cfg(test)]
mod invite_tests {
use matrix_sdk::ruma::{server_name, user_id};
use super::is_local;
#[test]
fn only_an_inviter_on_the_own_homeserver_is_local() {
let own = server_name!("pr1ma.darkest.space");
assert!(is_local(
Some(own),
Some(user_id!("@mara:pr1ma.darkest.space"))
));
assert!(!is_local(Some(own), Some(user_id!("@mara:matrix.org"))));
// A look-alike server name is not the own homeserver.
assert!(!is_local(
Some(own),
Some(user_id!("@x:pr1ma.darkest.space.evil.example"))
));
// Anything unknown is external.
assert!(!is_local(Some(own), None));
assert!(!is_local(None, Some(user_id!("@mara:pr1ma.darkest.space"))));
}
}