It seems like sometimes one sync isn't enough after sending the join request.
195 lines
5.9 KiB
Rust
195 lines
5.9 KiB
Rust
// bebot -- a Gitlab -> Matrix event publisher
|
|
// Copyright (C) 2023-2025 Brian Tarricone <brian@tarricone.org>
|
|
//
|
|
// 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::{fmt, process::exit, time::Duration};
|
|
|
|
use matrix_sdk::{
|
|
config::SyncSettings,
|
|
room::Room,
|
|
ruma::{OwnedRoomOrAliasId, OwnedUserId, RoomOrAliasId, UserId},
|
|
BaseRoom, Client,
|
|
};
|
|
use serde::de;
|
|
use tokio::time::sleep;
|
|
|
|
use crate::config::Config;
|
|
|
|
async fn build_sync_settings() -> SyncSettings {
|
|
SyncSettings::default().timeout(Duration::from_secs(30))
|
|
}
|
|
|
|
pub async fn connect(config: &Config) -> anyhow::Result<Client> {
|
|
let client = Client::builder()
|
|
.server_name(config.user_id.server_name())
|
|
.user_agent(format!("{}/{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")))
|
|
.build()
|
|
.await?;
|
|
client
|
|
.matrix_auth()
|
|
.login_username(&config.user_id, &config.password)
|
|
.initial_device_display_name("Bebot")
|
|
.await?;
|
|
info!("Connected to matrix as {}; waiting for first sync", config.user_id);
|
|
|
|
let settings = build_sync_settings().await;
|
|
client.sync_once(settings).await?;
|
|
info!("First matrix sync complete");
|
|
|
|
let sync_client = client.clone();
|
|
tokio::spawn(async move {
|
|
let settings = build_sync_settings().await;
|
|
if let Err(err) = sync_client.sync(settings).await {
|
|
error!("Matrix sync failed: {err}");
|
|
exit(1);
|
|
}
|
|
});
|
|
|
|
Ok(client)
|
|
}
|
|
|
|
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 {
|
|
let our_room_str = our_room.as_str();
|
|
a_room.room_id().as_str() == our_room_str
|
|
|| a_room
|
|
.canonical_alias()
|
|
.iter()
|
|
.any(|alias| alias.as_str() == our_room_str)
|
|
|| a_room.alt_aliases().iter().any(|alias| alias.as_str() == our_room_str)
|
|
}
|
|
|
|
let mut room = matrix_client
|
|
.joined_rooms()
|
|
.iter()
|
|
.find(|a_room| room_matches(a_room, room_id))
|
|
.cloned();
|
|
if room.is_none() {
|
|
if let Some(invited) = matrix_client
|
|
.invited_rooms()
|
|
.iter()
|
|
.find(|a_room| room_matches(a_room, room_id))
|
|
{
|
|
info!("Accepting invitation to room {room_id}");
|
|
invited.join().await?;
|
|
} else {
|
|
info!("Joining room {room_id}");
|
|
matrix_client.join_room_by_id_or_alias(room_id, &[]).await?;
|
|
}
|
|
|
|
for _ in 0..4 {
|
|
let settings = build_sync_settings().await;
|
|
matrix_client.sync_once(settings).await?;
|
|
room = matrix_client
|
|
.joined_rooms()
|
|
.iter()
|
|
.find(|a_room| room_matches(a_room, room_id))
|
|
.cloned();
|
|
if room.is_some() {
|
|
break;
|
|
}
|
|
sleep(Duration::from_millis(500)).await;
|
|
}
|
|
}
|
|
|
|
room.ok_or_else(|| anyhow!("Unable to join room {}", room_id))
|
|
}
|
|
|
|
pub fn deser_user_id<'de, D>(deserializer: D) -> Result<OwnedUserId, D::Error>
|
|
where
|
|
D: de::Deserializer<'de>,
|
|
{
|
|
struct UserIdVisitor;
|
|
|
|
impl<'de> de::Visitor<'de> for UserIdVisitor {
|
|
type Value = OwnedUserId;
|
|
|
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
formatter.write_str("a matrix user ID")
|
|
}
|
|
|
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
|
where
|
|
E: de::Error,
|
|
{
|
|
UserId::parse(v).map_err(E::custom)
|
|
}
|
|
}
|
|
|
|
deserializer.deserialize_any(UserIdVisitor)
|
|
}
|
|
|
|
fn deser_room_or_alias_id<'de, D>(deserializer: D) -> Result<OwnedRoomOrAliasId, D::Error>
|
|
where
|
|
D: de::Deserializer<'de>,
|
|
{
|
|
struct RoomOrAliasIdVisitor;
|
|
|
|
impl<'de> de::Visitor<'de> for RoomOrAliasIdVisitor {
|
|
type Value = OwnedRoomOrAliasId;
|
|
|
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
formatter.write_str("a matrix room ID")
|
|
}
|
|
|
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
|
where
|
|
E: de::Error,
|
|
{
|
|
RoomOrAliasId::parse(v).map_err(E::custom)
|
|
}
|
|
}
|
|
|
|
deserializer.deserialize_any(RoomOrAliasIdVisitor)
|
|
}
|
|
|
|
pub fn deser_optional_room_or_alias_id<'de, D>(deserializer: D) -> Result<Option<OwnedRoomOrAliasId>, D::Error>
|
|
where
|
|
D: de::Deserializer<'de>,
|
|
{
|
|
struct OptionalRoomOrAliasIdVisitor;
|
|
|
|
impl<'de> de::Visitor<'de> for OptionalRoomOrAliasIdVisitor {
|
|
type Value = Option<OwnedRoomOrAliasId>;
|
|
|
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
formatter.write_str("null or matrix room ID")
|
|
}
|
|
|
|
fn visit_none<E>(self) -> Result<Self::Value, E>
|
|
where
|
|
E: de::Error,
|
|
{
|
|
Ok(None)
|
|
}
|
|
|
|
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
Ok(Some(deser_room_or_alias_id(deserializer)?))
|
|
}
|
|
|
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
|
where
|
|
E: de::Error,
|
|
{
|
|
RoomOrAliasId::parse(v).map(Some).map_err(E::custom)
|
|
}
|
|
}
|
|
|
|
deserializer.deserialize_any(OptionalRoomOrAliasIdVisitor)
|
|
}
|