matrix: skip room invite when the user is already invited or joined
This commit is contained in:
parent
3795ffc1c6
commit
d090df3c36
1 changed files with 45 additions and 2 deletions
|
|
@ -1353,9 +1353,41 @@ async fn invite_to_room(
|
||||||
invite_user_id(client, admin_token, room_id, &user_id).await
|
invite_user_id(client, admin_token, room_id, &user_id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetch a user's current membership in a room via the admin token, or
|
||||||
|
/// `None` if there is no membership event (never invited) or the lookup
|
||||||
|
/// fails. Returns the raw membership string (`invite`, `join`, `leave`, …).
|
||||||
|
async fn room_membership(
|
||||||
|
client: &reqwest::Client,
|
||||||
|
admin_token: &str,
|
||||||
|
encoded_room_id: &str,
|
||||||
|
user_id: &str,
|
||||||
|
) -> Option<String> {
|
||||||
|
// `:` must be percent-encoded in both the room-id and user-id path
|
||||||
|
// segments; `@` and `!` are permitted path characters per RFC 3986.
|
||||||
|
let encoded_user = user_id.replace(':', "%3A");
|
||||||
|
let url = format!(
|
||||||
|
"{MATRIX_HTTP}/_matrix/client/v3/rooms/{encoded_room_id}/state/m.room.member/{encoded_user}"
|
||||||
|
);
|
||||||
|
let resp = client
|
||||||
|
.get(&url)
|
||||||
|
.bearer_auth(admin_token)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.ok()?;
|
||||||
|
if !resp.status().is_success() {
|
||||||
|
// 404 = no membership event yet; anything else we treat as "unknown"
|
||||||
|
// and let the caller fall through to the invite attempt.
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let body = resp.json::<serde_json::Value>().await.ok()?;
|
||||||
|
body["membership"].as_str().map(ToOwned::to_owned)
|
||||||
|
}
|
||||||
|
|
||||||
/// Invite a fully-qualified Matrix user id (`@user:server`) to `room_id`
|
/// Invite a fully-qualified Matrix user id (`@user:server`) to `room_id`
|
||||||
/// using the admin token. Idempotent: a 403 `M_FORBIDDEN` / `M_BAD_STATE`
|
/// using the admin token. Idempotent: a user who is already a member or
|
||||||
/// (already a member or pending invite) is treated as success.
|
/// already has a pending invite is left untouched (no fresh invite is sent,
|
||||||
|
/// so they are not re-notified), and a 403 `M_FORBIDDEN` / `M_BAD_STATE`
|
||||||
|
/// from a racing invite is still treated as success.
|
||||||
async fn invite_user_id(
|
async fn invite_user_id(
|
||||||
client: &reqwest::Client,
|
client: &reqwest::Client,
|
||||||
admin_token: &str,
|
admin_token: &str,
|
||||||
|
|
@ -1365,6 +1397,17 @@ async fn invite_user_id(
|
||||||
// `:` must be percent-encoded in the room-id path segment; `!` is
|
// `:` must be percent-encoded in the room-id path segment; `!` is
|
||||||
// permitted in URL path characters per RFC 3986.
|
// permitted in URL path characters per RFC 3986.
|
||||||
let encoded_room_id = room_id.replace(':', "%3A");
|
let encoded_room_id = room_id.replace(':', "%3A");
|
||||||
|
|
||||||
|
// Skip the invite entirely when the user is already invited or joined.
|
||||||
|
// Re-POSTing an invite to a pending member re-sends the invite event,
|
||||||
|
// which re-notifies the agent on every provisioning sweep.
|
||||||
|
if let Some(membership) = room_membership(client, admin_token, &encoded_room_id, user_id).await
|
||||||
|
&& matches!(membership.as_str(), "invite" | "join")
|
||||||
|
{
|
||||||
|
tracing::debug!(%user_id, %room_id, %membership, "matrix: invite skipped (already a member/invited)");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let url = format!("{MATRIX_HTTP}/_matrix/client/v3/rooms/{encoded_room_id}/invite");
|
let url = format!("{MATRIX_HTTP}/_matrix/client/v3/rooms/{encoded_room_id}/invite");
|
||||||
let resp = client
|
let resp = client
|
||||||
.post(&url)
|
.post(&url)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue