mrsbfh

Matrix-Rust-SDK-Bot-Framework-Helper
git clone git://archive.git.mtrnord.blog/MTRNord/mrsbfh.git
Log | Files | Refs | README

commit afabbf80a049e5439ccc3acac4ef01ea83fd5af2
parent 7880ae18aeb21a83e762efbe3166211284cd1dfe
Author: MTRNord <mtrnord1@gmail.com>
Date:   Thu,  4 Nov 2021 21:21:35 +0100

feat!: Update to matrix-sdk 0.4.1

BREAKING CHANGE: The matrix_sdk crate dropped the EventHandler. Therefor autojoin is now a function and the commands macro runs on a fn instead

Diffstat:
MREADME.md | 2+-
Mexample-bot/Cargo.toml | 8++++----
Mexample-bot/src/commands/hello_world.rs | 10++++++----
Mexample-bot/src/errors.rs | 2+-
Mexample-bot/src/matrix/mod.rs | 15+++++++++------
Mexample-bot/src/matrix/sync.rs | 52+++++++++++++---------------------------------------
Mmrsbfh-macros/Cargo.toml | 4++--
Mmrsbfh-macros/src/lib.rs | 249+++++++++++++++++++++++++++----------------------------------------------------
Mmrsbfh/Cargo.toml | 6+++---
Mmrsbfh/src/lib.rs | 24++++++++++++++++--------
Amrsbfh/src/sync.rs | 43+++++++++++++++++++++++++++++++++++++++++++
Mmrsbfh/src/utils.rs | 3---
12 files changed, 182 insertions(+), 236 deletions(-)

diff --git a/README.md b/README.md @@ -13,7 +13,7 @@ A toolkit for writing commandbots more efficient in rust for matrix. To use it you need to add mrsbfh just like any regular create: ``` -mrsbfh = "0.1.2" +mrsbfh = "0.2.0" ``` After that there are the possible helpers available that are further described in the docs. diff --git a/example-bot/Cargo.toml b/example-bot/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "example-bot" -version = "0.1.0" +version = "0.2.0" authors = ["MTRNord <mtrnord1@gmail.com>"] edition = "2021" license = "AGPL-3.0-or-later" @@ -10,13 +10,13 @@ repository = "https://github.com/MTRNord/mrsbfh" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies.matrix-sdk] -version = "0.3" +version = "0.4.1" [dependencies] -mrsbfh = {path = "../mrsbfh"} +mrsbfh = {version = "0.2.0", path = "../mrsbfh"} serde = "1.0" tracing = "0.1" -tracing-subscriber = "0.2" +tracing-subscriber = "0.3.1" tracing-futures = "0.2.4" tokio = { version = "1", features = ["full"] } clap = "=3.0.0-beta.2" diff --git a/example-bot/src/commands/hello_world.rs b/example-bot/src/commands/hello_world.rs @@ -1,15 +1,17 @@ use crate::config::Config; use crate::errors::Error; -use matrix_sdk::events::{room::message::MessageEventContent, AnyMessageEventContent}; -use mrsbfh::commands::command; +use matrix_sdk::ruma::events::{room::message::MessageEventContent, AnyMessageEventContent}; +use matrix_sdk::ruma::RoomId; use matrix_sdk::Client; -use matrix_sdk::identifiers::RoomId; +use mrsbfh::commands::command; +use std::sync::Arc; +use tokio::sync::Mutex; #[command(help = "`!hello_world` - Prints \"hello world\".")] pub async fn hello_world<'a>( _client: Client, tx: mrsbfh::Sender, - _config: Config<'a>, + _config: Arc<Mutex<Config<'a>>>, _sender: String, _room_id: RoomId, mut _args: Vec<&str>, diff --git a/example-bot/src/errors.rs b/example-bot/src/errors.rs @@ -1,4 +1,4 @@ -use matrix_sdk::events::AnyMessageEventContent; +use matrix_sdk::ruma::events::AnyMessageEventContent; use thiserror::Error as ThisError; #[derive(ThisError, Debug)] diff --git a/example-bot/src/matrix/mod.rs b/example-bot/src/matrix/mod.rs @@ -2,10 +2,8 @@ use crate::config::Config; use matrix_sdk::{Client, ClientConfig, Session as SDKSession, SyncSettings}; use mrsbfh::url::Url; use mrsbfh::utils::Session; -use std::convert::TryFrom; -use std::error::Error; -use std::fs; -use std::path::Path; +use std::{convert::TryFrom, error::Error, fs, path::Path, sync::Arc}; +use tokio::sync::Mutex; use tracing::*; mod sync; @@ -30,7 +28,7 @@ pub async fn setup(config: Config<'_>) -> Result<Client, Box<dyn Error>> { let session = SDKSession { access_token: session.access_token, device_id: session.device_id.into(), - user_id: matrix_sdk::identifiers::UserId::try_from(session.user_id.as_str()).unwrap(), + user_id: matrix_sdk::ruma::UserId::try_from(session.user_id.as_str()).unwrap(), }; if let Err(e) = client.restore_login(session).await { @@ -72,8 +70,13 @@ pub async fn start_sync( client: &mut Client, config: Config<'static>, ) -> Result<(), Box<dyn Error>> { + client.register_event_handler(mrsbfh::sync::autojoin).await; + + let config = Arc::new(Mutex::new(config)); client - .set_event_handler(Box::new(sync::Bot::new(client.clone(), config.clone()))) + .register_event_handler(move |ev, room, client| { + sync::on_room_message(ev, room, client, config.clone()) + }) .await; info!("Starting full Sync..."); diff --git a/example-bot/src/matrix/sync.rs b/example-bot/src/matrix/sync.rs @@ -1,45 +1,19 @@ use crate::commands::match_command; -use crate::config::Config; -use matrix_sdk::async_trait; +use crate::Config; +use matrix_sdk::Client; use matrix_sdk::{ - events::{ - room::member::MemberEventContent, room::message::MessageEventContent, StrippedStateEvent, - SyncMessageEvent, - }, - Client, EventHandler, room::Room, + room::Room, + ruma::events::{room::message::MessageEventContent, SyncMessageEvent}, }; -use tokio::sync::mpsc; -use tracing::*; - -#[derive(Debug, Clone)] -pub struct Bot { - client: Client, - config: Config<'static>, -} - -impl Bot { - pub fn new(client: Client, config: Config<'static>) -> Self { - Self { - client, - config: config.clone(), - } - } -} +use std::sync::Arc; +use tokio::sync::Mutex; #[mrsbfh::commands::commands] -#[mrsbfh::utils::autojoin] -#[async_trait] -impl EventHandler for Bot { - async fn on_room_message(&self, room: Room, event: &SyncMessageEvent<MessageEventContent>) { - println!("message example") - } - - async fn on_stripped_state_member( - &self, - room: Room, - room_member: &StrippedStateEvent<MemberEventContent>, - _: Option<MemberEventContent>, - ) { - println!("autojoin example") - } +pub(crate) async fn on_room_message( + event: SyncMessageEvent<MessageEventContent>, + room: Room, + client: Client, + config: Arc<Mutex<Config<'static>>>, +) { + println!("message example") } diff --git a/mrsbfh-macros/Cargo.toml b/mrsbfh-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrsbfh-macros" -version = "0.1.0" +version = "0.2.0" authors = ["MTRNord <mtrnord1@gmail.com>"] edition = "2021" description = "Maros for the mrsbfh crate" @@ -13,7 +13,7 @@ categories = ["network-programming", "parsing"] proc-macro = true [dependencies] -syn = { version= "1.0", features=["full"] } +syn = { version= "1.0", features = ["full"] } quote = "1.0" convert_case = "0.4.0" proc-macro2 = "1.0" diff --git a/mrsbfh-macros/src/lib.rs b/mrsbfh-macros/src/lib.rs @@ -9,8 +9,11 @@ use syn::spanned::Spanned; /// Used to define a command /// /// ```compile_fail +/// use std::sync::Arc; +/// use tokio::sync::Mutex; +/// /// #[command(help = "Description")] -/// async fn hello_world(mut tx: mrsbfh::Sender, config: Config, sender: String, mut args: Vec<&str>) -> Result<(), Box<dyn std::error::Error>> where Config: mrsbfh::config::Loader + Clone {} +/// async fn hello_world(mut tx: mrsbfh::Sender, config: Arc<Mutex<Config>>, sender: String, mut args: Vec<&str>) -> Result<(), Box<dyn std::error::Error>> where Config: mrsbfh::config::Loader + Clone {} /// ``` #[proc_macro_attribute] pub fn command(args: TokenStream, input: TokenStream) -> TokenStream { @@ -144,8 +147,8 @@ pub fn command_generate(args: TokenStream, input: TokenStream) -> TokenStream { let owned_html = html.to_owned(); mrsbfh::tokio::spawn(async move { - let content = matrix_sdk::events::AnyMessageEventContent::RoomMessage( - matrix_sdk::events::room::message::MessageEventContent::notice_html( + let content = matrix_sdk::ruma::events::AnyMessageEventContent::RoomMessage( + matrix_sdk::ruma::events::room::message::MessageEventContent::notice_html( &help_markdown, owned_html, ), @@ -159,7 +162,7 @@ pub fn command_generate(args: TokenStream, input: TokenStream) -> TokenStream { Ok(()) } - pub async fn match_command<'a>(cmd: &str, client: matrix_sdk::Client, config: Config<'a>, tx: mrsbfh::Sender, sender: String, room_id: matrix_sdk::identifiers::RoomId, args: Vec<&str>,) -> Result<(), Error> where Config<'a>: mrsbfh::config::Loader + Clone { + pub async fn match_command<'a>(cmd: &str, client: matrix_sdk::Client, config: std::sync::Arc<tokio::sync::Mutex<Config<'a>>>, tx: mrsbfh::Sender, sender: String, room_id: matrix_sdk::ruma::RoomId, args: Vec<&str>,) -> Result<(), Error> where Config<'a>: mrsbfh::config::Loader + Clone { match cmd { #(#commands)* "help" => { @@ -195,88 +198,12 @@ pub fn config_derive(input: TokenStream) -> TokenStream { TokenStream::from(expanded) } -/// Used to generate code to autojoin when we get a invite for the bot -/// -/// Requirements: -/// -/// * Tokio -/// * Naming of arguments needs to be EXACTLY like in the example -/// * the async_trait macro needs to be BELOW the autojoin macro -/// -/// ```compile_fail -/// #[mrsbfh::utils::autojoin] -/// #[async_trait] -/// impl EventEmitter for Bot { -/// -/// async fn on_stripped_state_member( -/// &self, -/// room: SyncRoom, -/// room_member: &StrippedStateEvent<MemberEventContent>, -/// _: Option<MemberEventContent>, -/// ) { -/// // Your own logic. (Executed BEFORE the autojoin) -/// } -/// } -/// ``` -/// -#[proc_macro_attribute] -pub fn autojoin(_: TokenStream, input: TokenStream) -> TokenStream { - let mut input = parse_macro_input!(input as syn::ItemImpl); - let items = &mut input.items; - - for item in items { - if let syn::ImplItem::Method(method) = item { - if method.sig.ident == "on_stripped_state_member" { - let original = method.block.clone(); - let new_block = syn::parse_quote! { - { - #original - - // Autojoin logic - if room_member.state_key != self.client.user_id().await.unwrap() { - warn!("Got invite that isn't for us"); - return; - } - if let matrix_sdk::room::Room::Invited(room) = room { - info!("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 - error!( - "Failed to join room {} ({:?}), retrying in {}s", - room.room_id(), - err, - delay - ); - - tokio::time::sleep(tokio::time::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()); - } - } - }; - method.block = new_block; - } - } - } - - TokenStream::from(quote! {#input}) -} - /// Used to generate code to detect commands when we get a message for the bot /// /// Requirements: /// /// * Tokio +/// * Tokio tracing /// * Naming of arguments needs to be EXACTLY like in the example /// * the async_trait macro needs to be BELOW the commands macro /// * The match_command MUST be imported @@ -285,10 +212,7 @@ pub fn autojoin(_: TokenStream, input: TokenStream) -> TokenStream { /// use crate::commands::match_command; /// /// #[mrsbfh::commands::commands] -/// #[async_trait] -/// impl EventEmitter for Bot { -/// -/// async fn on_room_message(&self, room: SyncRoom, event: &SyncMessageEvent<MessageEventContent>) { +/// async fn on_room_message(event: SyncMessageEvent<MessageEventContent>, room: Room) { /// // Your own logic. (Executed BEFORE the commands matching) /// } /// } @@ -296,90 +220,85 @@ pub fn autojoin(_: TokenStream, input: TokenStream) -> TokenStream { /// #[proc_macro_attribute] pub fn commands(_: TokenStream, input: TokenStream) -> TokenStream { - let mut input = parse_macro_input!(input as syn::ItemImpl); - let items = &mut input.items; - - for item in items { - if let syn::ImplItem::Method(method) = item { - if method.sig.ident == "on_room_message" { - let original = method.block.clone(); - let new_block = syn::parse_quote! { + let mut method = parse_macro_input!(input as syn::ItemFn); + + if method.sig.ident == "on_room_message" { + let original = method.block.clone(); + let new_block = syn::parse_quote! { + { + #original + + // Command matching logic + if let matrix_sdk::room::Room::Joined(room) = room { + let msg_body = if let matrix_sdk::ruma::events::SyncMessageEvent { + content: matrix_sdk::ruma::events::room::message::MessageEventContent { + msgtype: matrix_sdk::ruma::events::room::message::MessageType::Text(matrix_sdk::ruma::events::room::message::TextMessageEventContent { body: msg_body, .. }), + .. + }, + .. + } = event { - #original - - // Command matching logic - if let matrix_sdk::room::Room::Joined(room) = room { - let msg_body = if let matrix_sdk::events::SyncMessageEvent { - content: matrix_sdk::events::room::message::MessageEventContent { - msgtype: matrix_sdk::events::room::message::MessageType::Text(matrix_sdk::events::room::message::TextMessageEventContent { body: msg_body, .. }), - .. - }, - .. - } = event - { - msg_body.clone() - } else { - String::new() - }; - if msg_body.is_empty() { - return; - } - - let sender = event.sender.clone().to_string(); - - let (tx, mut rx) = mpsc::channel(100); - let room_id = room.room_id().clone(); - - let cloned_config = self.config.clone(); - let cloned_client = self.client.clone(); - tokio::spawn(async move { - let whitespace_deduplicator_magic = regex::Regex::new(r"\s+").unwrap(); - let command_matcher_magic = regex::Regex::new(r"!([\w-]+)").unwrap(); - let normalized_body = whitespace_deduplicator_magic.replace_all(&msg_body, " "); - let mut split = msg_body.split_whitespace(); - - let command_raw = split.next().expect("This is not a command").to_lowercase(); - let command = command_matcher_magic.captures(command_raw.as_str()) - .map_or(String::new(), |caps| { - caps.get(1) - .map_or(String::new(), - |m| String::from(m.as_str())) - }); - if !command.is_empty() { - info!("Got command: {}", command); - } - // Make sure this is immutable - let args: Vec<&str> = split.collect(); - if let Err(e) = match_command( - command.as_str(), - cloned_client.clone(), - cloned_config.clone(), - tx, - sender, - room_id, - args, - ) - .await - { - error!("{}", e); - } - - }); - - while let Some(v) = rx.recv().await { - if let Err(e) = room.send(v, None) - .await - { - error!("{}", e); - } - } + msg_body.clone() + } else { + String::new() + }; + if msg_body.is_empty() { + return; + } + + let sender = event.sender.clone().to_string(); + + let (tx, mut rx) = tokio::sync::mpsc::channel(100); + let room_id = room.room_id().clone(); + + let cloned_config = config.clone(); + let cloned_client = client.clone(); + tokio::spawn(async move { + let whitespace_deduplicator_magic = regex::Regex::new(r"\s+").unwrap(); + let command_matcher_magic = regex::Regex::new(r"!([\w-]+)").unwrap(); + let normalized_body = whitespace_deduplicator_magic.replace_all(&msg_body, " "); + let mut split = msg_body.split_whitespace(); + + let command_raw = split.next().expect("This is not a command").to_lowercase(); + let command = command_matcher_magic.captures(command_raw.as_str()) + .map_or(String::new(), |caps| { + caps.get(1) + .map_or(String::new(), + |m| String::from(m.as_str())) + }); + if !command.is_empty() { + tracing::info!("Got command: {}", command); + } + // Make sure this is immutable + let args: Vec<&str> = split.collect(); + if let Err(e) = match_command( + command.as_str(), + cloned_client.clone(), + cloned_config.clone(), + tx, + sender, + room_id, + args, + ) + .await + { + tracing::error!("{}", e); + } + + }); + + while let Some(v) = rx.recv().await { + if let Err(e) = room.send(v, None) + .await + { + tracing::error!("{}", e); } } - }; - method.block = new_block; + } } - } + }; + method.block = new_block; } - TokenStream::from(quote! {#input}) + TokenStream::from(quote! {#method}) } diff --git a/mrsbfh/Cargo.toml b/mrsbfh/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrsbfh" -version = "0.1.2" +version = "0.2.0" authors = ["MTRNord <mtrnord1@gmail.com>"] edition = "2021" description = "A toolkit for writing commandbots more efficient in rust for matrix." @@ -11,7 +11,7 @@ categories = ["network-programming"] readme = "../README.md" [dependencies.matrix-sdk] -version = "0.3" +version = "0.4.1" default_features = false features = ["encryption", "sled_cryptostore", "sled_state_store", "require_auth_for_profile_requests"] @@ -21,7 +21,7 @@ url = "2.2.1" thiserror = "1.0" # Command macros -mrsbfh-macros = {version = "0.1.0", path = "../mrsbfh-macros", optional = true} +mrsbfh-macros = {version = "0.2.0", path = "../mrsbfh-macros", optional = true} tokio = { version = "1", features = ["full"] } tracing = "0.1" diff --git a/mrsbfh/src/lib.rs b/mrsbfh/src/lib.rs @@ -25,9 +25,10 @@ pub mod commands; pub mod config; pub mod errors; +pub mod sync; pub mod utils; -pub type Sender = tokio::sync::mpsc::Sender<matrix_sdk::events::AnyMessageEventContent>; +pub type Sender = tokio::sync::mpsc::Sender<matrix_sdk::ruma::events::AnyMessageEventContent>; #[async_trait::async_trait] pub trait MatrixMessageExt { @@ -35,7 +36,10 @@ pub trait MatrixMessageExt { &mut self, body: String, formatted_body: Option<String>, - ) -> Result<(), tokio::sync::mpsc::error::SendError<matrix_sdk::events::AnyMessageEventContent>>; + ) -> Result< + (), + tokio::sync::mpsc::error::SendError<matrix_sdk::ruma::events::AnyMessageEventContent>, + >; } #[async_trait::async_trait] @@ -44,12 +48,14 @@ impl MatrixMessageExt for Sender { &mut self, body: String, formatted_body: Option<String>, - ) -> Result<(), tokio::sync::mpsc::error::SendError<matrix_sdk::events::AnyMessageEventContent>> - { + ) -> Result< + (), + tokio::sync::mpsc::error::SendError<matrix_sdk::ruma::events::AnyMessageEventContent>, + > { match formatted_body { Some(formatted_body) => { - let content = matrix_sdk::events::AnyMessageEventContent::RoomMessage( - matrix_sdk::events::room::message::MessageEventContent::notice_html( + let content = matrix_sdk::ruma::events::AnyMessageEventContent::RoomMessage( + matrix_sdk::ruma::events::room::message::MessageEventContent::notice_html( body, formatted_body, ), @@ -58,8 +64,10 @@ impl MatrixMessageExt for Sender { self.send(content).await } None => { - let content = matrix_sdk::events::AnyMessageEventContent::RoomMessage( - matrix_sdk::events::room::message::MessageEventContent::notice_plain(body), + let content = matrix_sdk::ruma::events::AnyMessageEventContent::RoomMessage( + matrix_sdk::ruma::events::room::message::MessageEventContent::notice_plain( + body, + ), ); self.send(content).await } diff --git a/mrsbfh/src/sync.rs b/mrsbfh/src/sync.rs @@ -0,0 +1,43 @@ +use matrix_sdk::{ + room::Room, + ruma::events::{room::member::MemberEventContent, StrippedStateEvent}, + Client, +}; +use tracing::*; + +pub async fn autojoin( + room_member: StrippedStateEvent<MemberEventContent>, + client: Client, + room: Room, +) { + // Autojoin logic + if room_member.state_key != client.user_id().await.unwrap() { + warn!("Got invite that isn't for us"); + return; + } + if let matrix_sdk::room::Room::Invited(room) = room { + info!("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 + error!( + "Failed to join room {} ({:?}), retrying in {}s", + room.room_id(), + err, + delay + ); + + tokio::time::sleep(tokio::time::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()); + } +} diff --git a/mrsbfh/src/utils.rs b/mrsbfh/src/utils.rs @@ -104,9 +104,6 @@ use serde::{Deserialize, Serialize}; use std::path::PathBuf; use tracing::*; -#[cfg(feature = "macros")] -pub use mrsbfh_macros::autojoin; - #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Session { /// The homeserver used for this session.