bot: setup autojoin
Currently broken on v0.4.0, unfortunately. Investigating.
This commit is contained in:
parent
fbdb9e3a96
commit
ef34dbe2df
7 changed files with 615 additions and 66 deletions
68
src/bot/handlers/autojoin.rs
Normal file
68
src/bot/handlers/autojoin.rs
Normal 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
1
src/bot/handlers/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub mod autojoin;
|
||||
102
src/bot/mod.rs
Normal file
102
src/bot/mod.rs
Normal 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
20
src/config.rs
Normal 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,
|
||||
}
|
||||
35
src/main.rs
35
src/main.rs
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue