diff --git a/src/autojoin.rs b/src/autojoin.rs new file mode 100644 index 0000000..d5f17c8 --- /dev/null +++ b/src/autojoin.rs @@ -0,0 +1,60 @@ +use std::time::Duration; + +use matrix_sdk::{ + self, async_trait, + events::{room::member::MemberEventContent, StrippedStateEvent}, + Client, EventEmitter, RoomState, +}; +use tokio::time::sleep; + +pub struct AutoJoinHandler { + client: Client, +} + +impl AutoJoinHandler { + pub fn new(client: Client) -> Self { + Self { client } + } +} + +#[async_trait] +impl EventEmitter for AutoJoinHandler { + async fn on_stripped_state_member( + &self, + room: RoomState, + room_member: &StrippedStateEvent, + _: Option, + ) { + if room_member.state_key != self.client.user_id().await.unwrap() { + return; + } + + if let RoomState::Invited(room) = room { + // TODO: only join room if it's the room specified in the configuration + + println!("Autojoining room {}", room.room_id()); + let mut delay = 2; + + while let Err(err) = self.client.join_room_by_id(room.room_id()).await { + // retry autojoin due to synapse sending invites, before the + // invited user can join for more information see + // https://github.com/matrix-org/synapse/issues/4345 + eprintln!( + "Failed to join room {} ({:?}), retrying in {}s", + room.room_id(), + err, + delay + ); + + sleep(Duration::from_secs(delay)).await; + delay *= 2; + + if delay > 3600 { + eprintln!("Can't join room {} ({:?})", room.room_id(), err); + break; + } + } + println!("Successfully joined room {}", room.room_id()); + } + } +} diff --git a/src/bot.rs b/src/bot.rs new file mode 100644 index 0000000..ddcb44b --- /dev/null +++ b/src/bot.rs @@ -0,0 +1,74 @@ +use std::{ + fs::File, + io::{BufReader, BufWriter}, +}; + +use matrix_sdk::{Client, ClientConfig, Session, SyncSettings}; + +use crate::autojoin::AutoJoinHandler; +use crate::Config; + +pub struct BadNewsBot { + client: Client, + config: Config, +} + +impl BadNewsBot { + pub fn new(config: Config) -> anyhow::Result { + let client_config = ClientConfig::new().store_path(config.state_dir.join("store")); + let client = Client::new_with_config(config.homeserver.clone(), client_config)?; + + Ok(Self { client, config }) + } + + pub async fn init(&self) -> anyhow::Result<()> { + load_or_init_session(&self).await?; + + self.client + .add_event_emitter(Box::new(AutoJoinHandler::new(self.client.clone()))) + .await; + + Ok(()) + } + + pub async fn run(&self) { + self.client.sync(SyncSettings::default()).await + } +} + +async fn load_or_init_session(bot: &BadNewsBot) -> anyhow::Result<()> { + let session_file = bot.config.state_dir.join("session.yaml"); + + if session_file.is_file() { + let reader = BufReader::new(File::open(session_file)?); + + let session: Session = serde_yaml::from_reader(reader)?; + + bot.client.restore_login(session.clone()).await?; + + println!("Reused session: {}, {}", session.user_id, session.device_id); + } else { + let response = bot + .client + .login( + &bot.config.username, + &bot.config.password, + None, + Some("autojoin bot"), + ) + .await?; + + println!("logged in as {}", bot.config.username); + + let session = Session { + access_token: response.access_token, + user_id: response.user_id, + device_id: response.device_id, + }; + + let writer = BufWriter::new(File::create(session_file)?); + serde_yaml::to_writer(writer, &session)?; + } + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 53782cf..b06f5d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,137 +1,18 @@ use std::{ fs::File, - io::{self, BufReader, BufWriter}, + io::{self, BufReader}, path::PathBuf, - time::Duration, }; use clap::Clap; -use matrix_sdk::{ - self, async_trait, - events::{room::member::MemberEventContent, StrippedStateEvent}, - Client, ClientConfig, EventEmitter, RoomState, Session, SyncSettings, -}; use serde::Deserialize; use thiserror::Error; -use tokio::time::sleep; use url::Url; -struct BadNewsBot { - client: Client, - config: Config, -} +mod autojoin; +mod bot; -impl BadNewsBot { - pub fn new(config: Config) -> anyhow::Result { - let client_config = ClientConfig::new().store_path(config.state_dir.join("store")); - let client = Client::new_with_config(config.homeserver.clone(), client_config)?; - - Ok(Self { client, config }) - } - - pub async fn init(&self) -> anyhow::Result<()> { - load_or_init_session(&self).await?; - - self.client - .add_event_emitter(Box::new(AutoJoinHandler::new(self.client.clone()))) - .await; - - Ok(()) - } - - pub async fn run(&self) { - self.client.sync(SyncSettings::default()).await - } -} - -async fn load_or_init_session(bot: &BadNewsBot) -> anyhow::Result<()> { - let session_file = bot.config.state_dir.join("session.yaml"); - - if session_file.is_file() { - let reader = BufReader::new(File::open(session_file)?); - - let session: Session = serde_yaml::from_reader(reader)?; - - bot.client.restore_login(session.clone()).await?; - - println!("Reused session: {}, {}", session.user_id, session.device_id); - } else { - let response = bot - .client - .login( - &bot.config.username, - &bot.config.password, - None, - Some("autojoin bot"), - ) - .await?; - - println!("logged in as {}", bot.config.username); - - let session = Session { - access_token: response.access_token, - user_id: response.user_id, - device_id: response.device_id, - }; - - let writer = BufWriter::new(File::create(session_file)?); - serde_yaml::to_writer(writer, &session)?; - } - - Ok(()) -} - -struct AutoJoinHandler { - client: Client, -} - -impl AutoJoinHandler { - fn new(client: Client) -> Self { - Self { client } - } -} - -#[async_trait] -impl EventEmitter for AutoJoinHandler { - async fn on_stripped_state_member( - &self, - room: RoomState, - room_member: &StrippedStateEvent, - _: Option, - ) { - if room_member.state_key != self.client.user_id().await.unwrap() { - return; - } - - if let RoomState::Invited(room) = room { - // TODO: only join room if it's the room specified in the configuration - - println!("Autojoining room {}", room.room_id()); - let mut delay = 2; - - while let Err(err) = self.client.join_room_by_id(room.room_id()).await { - // retry autojoin due to synapse sending invites, before the - // invited user can join for more information see - // https://github.com/matrix-org/synapse/issues/4345 - eprintln!( - "Failed to join room {} ({:?}), retrying in {}s", - room.room_id(), - err, - delay - ); - - sleep(Duration::from_secs(delay)).await; - delay *= 2; - - if delay > 3600 { - eprintln!("Can't join room {} ({:?})", room.room_id(), err); - break; - } - } - println!("Successfully joined room {}", room.room_id()); - } - } -} +use bot::BadNewsBot; #[derive(Error, Debug)] enum BadNewsError { @@ -151,7 +32,7 @@ struct Opts { /// Holds the configuration for the bot. #[derive(Deserialize)] -struct Config { +pub struct Config { /// The URL for the homeserver we should connect to homeserver: Url, /// The bot's account username