diff --git a/src/config.rs b/src/config.rs index a949b5f..b4fdfc7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -27,12 +27,26 @@ pub struct FeedConfig { pub delay: u64 } +#[derive(Deserialize, Debug)] +#[serde(untagged)] +pub enum AuthConfig { + PasswordAuthConfig { + username: String, + password: String, + }, + TokenAuthConfig { + user_id: String, + device_id: String, + access_token: String, + } +} + #[derive(Deserialize, Debug)] pub struct Config { pub default_room: String, pub feeds: Vec, - pub username: String, - pub password: String, + #[serde(flatten)] + pub auth: AuthConfig, pub homeserver_uri: String } diff --git a/src/main.rs b/src/main.rs index f8aa07b..f503ca5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,8 +24,8 @@ mod state; use tokio::{ time::{sleep, Duration}, + sync::{Notify, broadcast}, task::JoinHandle, - sync::broadcast }; use crate::{ @@ -55,17 +55,25 @@ async fn main() -> anyhow::Result<()> { panic!("Failed to initialize feed reader state db: {e:?}") }); - // This message passing channel is used for sending messages to the matrix module, + let matrix_ready = Arc::new(Notify::new()); + + // This channel is used for sending messages to the matrix module, // it holds a tuple with an HTML message and a list of rooms to post to let (bcast_tx, bcast_rx) = broadcast::channel::<(String, Vec)>(1024); let handles: Vec> = config.feeds.clone().into_iter().map(|feed_config| { let state_db = Arc::clone(&state_db); let bcast_tx = bcast_tx.clone(); + let matrix_ready = Arc::clone(&matrix_ready); let mut backoff: u64 = feed_config.delay; tokio::spawn(async move { + debug!("Waiting until matrix is ready"); + matrix_ready.notified().await; + + debug!("Notified that matrix is ready, starting loop for {}", &feed_config.url); + loop { let delay = rand::random::() % (feed_config.delay / 5); @@ -135,9 +143,9 @@ async fn main() -> anyhow::Result<()> { login_and_sync( config.homeserver_uri.clone().into(), - &config.username, - &config.password, + &config.auth, &config.default_room, + matrix_ready, bcast_rx ).await?; diff --git a/src/matrix.rs b/src/matrix.rs index 966a536..6651b83 100644 --- a/src/matrix.rs +++ b/src/matrix.rs @@ -18,23 +18,34 @@ */ use matrix_sdk::{ + matrix_auth::{ + MatrixAuth, + MatrixSession, + MatrixSessionTokens + }, + SessionMeta, config::SyncSettings, ruma::events::room::{ member::StrippedRoomMemberEvent, message::RoomMessageEventContent }, - ruma::RoomId, + ruma::{RoomId, OwnedUserId, device_id}, Client, Room }; use tokio::{ time::{sleep, Duration}, - sync::broadcast + sync::{broadcast, Notify}, + task }; +use std::sync::Arc; + use tracing::{error, info}; use std::error::Error; +use crate::config::AuthConfig; + async fn on_stripped_state_member( room_member: StrippedRoomMemberEvent, client: Client, @@ -85,23 +96,45 @@ async fn send_to_room( }; } +async fn login( + auth_config: &AuthConfig, + matrix_auth: MatrixAuth +) -> anyhow::Result<()> { + match auth_config { + AuthConfig::PasswordAuthConfig{username, password} => { + matrix_auth.login_username(username, password) + .initial_device_display_name("bender v0.1.5").await?; + }, + AuthConfig::TokenAuthConfig{user_id, device_id, access_token} => { + matrix_auth.restore_session( + MatrixSession { + meta: SessionMeta { + user_id: ::try_from(user_id.as_str())?, + device_id: device_id!(device_id.as_str()).to_owned(), + }, + tokens: MatrixSessionTokens { + access_token: access_token.to_owned(), + refresh_token: None, + } + } + ).await?; + }, + } + + Ok(()) +} + pub async fn login_and_sync( homeserver_url: String, - username: &str, - password: &str, + auth_config: &AuthConfig, default_room_id: &str, + ready: Arc, mut rx: broadcast::Receiver<(String, Vec)> ) -> anyhow::Result<()> { // We are not reading encrypted messages, so we don't care about session persistence let client = Client::builder().homeserver_url(homeserver_url).build().await?; - client - .matrix_auth() - .login_username(username, password) - .initial_device_display_name("bender v0.1.5") - .await?; - - info!("logged in as {username}"); + login(auth_config, client.matrix_auth()).await?; client.add_event_handler(on_stripped_state_member); @@ -116,6 +149,15 @@ pub async fn login_and_sync( // we must pass that sync token to `sync` let settings = SyncSettings::default().token(sync_token); + // make sure all waiters have had a chance to register. + // Ideally the notified futures would be created before spawning + // the feed reader tasks but that would require the Notified structs to + // live for 'static, which I dislike. + task::yield_now().await; + + info!("Matrix is ready, notifying waiters"); + ready.notify_waiters(); + loop { tokio::select! { res = client.sync(settings.clone()) => match res {