bot: setup autojoin

Currently broken on v0.4.0, unfortunately. Investigating.
This commit is contained in:
Antoine Martin 2021-09-11 19:31:40 +02:00
parent fbdb9e3a96
commit ef34dbe2df
7 changed files with 615 additions and 66 deletions

View file

@ -0,0 +1,68 @@
use std::time::Duration;
use matrix_sdk::{
room::Room,
ruma::{
events::{room::member::MemberEventContent, StrippedStateEvent},
RoomId,
},
Client,
};
use tokio::time::sleep;
use tracing::{debug, error, info, warn};
pub async fn autojoin_authorized_rooms(
room_member: StrippedStateEvent<MemberEventContent>,
client: Client,
room: Room,
authorized_rooms: Vec<RoomId>,
) {
if room_member.state_key != client.user_id().await.unwrap() {
return;
}
if let Room::Invited(room) = room {
let room_id = room.room_id();
let room_name = room
.display_name()
.await
.expect("couldn't get joined room name!");
info!(
"Received invitation for room `{}`: `{}`",
room_id, room_name
);
if authorized_rooms.contains(room_id) {
warn!(
"Bot isn't authorized to join room `{}`, declining invitation",
room_id
);
room.reject_invitation().await.unwrap();
return;
}
debug!("Autojoining room {}", room.room_id());
let mut delay = 2;
while let Err(err) = room.accept_invitation().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
warn!(
"Failed to join room {} ({:?}), retrying in {}s",
room.room_id(),
err,
delay
);
sleep(Duration::from_secs(delay)).await;
delay *= 2;
if delay > 3600 {
error!("Can't join room {} ({:?})", room.room_id(), err);
break;
}
}
info!("Successfully joined room {}", room.room_id());
}
}

1
src/bot/handlers/mod.rs Normal file
View file

@ -0,0 +1 @@
pub mod autojoin;

102
src/bot/mod.rs Normal file
View file

@ -0,0 +1,102 @@
use std::{
fs::File,
io::{BufReader, BufWriter},
};
use matrix_sdk::{
room::Room,
ruma::events::{room::member::MemberEventContent, StrippedStateEvent},
Client, ClientConfig, Session, SyncSettings,
};
use tracing::{debug, info};
use crate::config::ProloloConfig;
mod handlers;
use handlers::autojoin::autojoin_authorized_rooms;
pub struct Prololo {
client: Client,
config: ProloloConfig,
}
impl Prololo {
/// Creates a new [`Prololo`] bot and builds a [`matrix_sdk::Client`] using the provided
/// [`Config`].
///
/// The [`Client`] is only initialized, not ready to be used yet.
pub fn new(config: ProloloConfig) -> anyhow::Result<Self> {
let client_config = ClientConfig::new().store_path(config.matrix_state_dir.join("store"));
let client = Client::new_with_config(config.matrix_homeserver.clone(), client_config)?;
Ok(Self { client, config })
}
/// Loads session information from file, or creates it if no previous session is found.
///
/// The bot is ready to run once this function has been called.
pub async fn init(&self) -> anyhow::Result<()> {
self.load_or_init_session().await?;
let authorized_rooms = vec![self.config.matrix_room_id.clone()];
self.client
.register_event_handler({
move |ev: StrippedStateEvent<MemberEventContent>, client: Client, room: Room| {
let authorized_rooms = authorized_rooms.clone();
debug!("handler!!!second");
async move { autojoin_authorized_rooms(ev, client, room, authorized_rooms).await }
}
})
.await;
Ok(())
}
/// Start listening to Matrix events.
///
/// [`Prololo::init`] **must** be called before this function, otherwise the [`Client`] isn't
/// logged in.
pub async fn run(&self) {
debug!("running...");
self.client.sync(SyncSettings::default()).await
}
/// This loads the session information from an existing file, and tries to login with it. If no such
/// file is found, then login using username and password, and save the new session information on
/// disk.
async fn load_or_init_session(&self) -> anyhow::Result<()> {
let session_file = self.config.matrix_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)?;
self.client.restore_login(session.clone()).await?;
info!("Reused session: {}, {}", session.user_id, session.device_id);
} else {
let response = self
.client
.login(
&self.config.matrix_username,
&self.config.matrix_password,
None,
Some("autojoin bot"),
)
.await?;
info!("logged in as {}", self.config.matrix_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(())
}
}

20
src/config.rs Normal file
View file

@ -0,0 +1,20 @@
use std::path::PathBuf;
use matrix_sdk::ruma::RoomId;
use serde::Deserialize;
use url::Url;
#[derive(Debug, Deserialize)]
pub struct ProloloConfig {
/// The URL for the homeserver we should connect to
pub matrix_homeserver: Url,
/// The bot's account username
pub matrix_username: String,
/// The bot's account password
pub matrix_password: String,
/// Path to a directory where the bot will store Matrix state and current session information.
pub matrix_state_dir: PathBuf,
/// ID of the Matrix room where the bot should post messages. The bot will only accept
/// invitations to this room.
pub matrix_room_id: RoomId,
}

View file

@ -1,3 +1,34 @@
fn main() {
println!("Hello, world!");
use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;
use clap::Clap;
mod bot;
use bot::Prololo;
mod config;
use config::ProloloConfig;
#[derive(Clap)]
#[clap(version = "0.1")]
struct Opts {
/// File where session information will be saved
#[clap(short, long, parse(from_os_str))]
config: PathBuf,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
let opts = Opts::parse();
let config_file = opts.config;
let config: ProloloConfig = serde_yaml::from_reader(BufReader::new(File::open(config_file)?))?;
let prololo = Prololo::new(config)?;
prololo.init().await?;
prololo.run().await;
Ok(())
}