forked from projectmoon/tenebrous-dicebot
Upgrade to Matrix SDK latest (Store Rewrite) and Tokio 1.0
This upgrade introduces a handful of breaking changes in the Rust Matrix SDK. - Some types have disappeared and changed name. - Some functions are no longer async. - Room display name now has a Result type instead of just returning the value. - Client state store has breaking changes (not really a big deal). This required introduction of a new type to store room information that we are interested in on the context struct. This new RoomContext is required mostly due to unit tests, because it is no longer possible to instantiate the Room type in the Matrix SDK.
This commit is contained in:
parent
a4cdad4936
commit
1b0003ff1b
File diff suppressed because it is too large
Load Diff
|
@ -37,5 +37,5 @@ version = "1"
|
|||
features = ['derive']
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "0.2"
|
||||
version = "1.0"
|
||||
features = [ "full" ]
|
|
@ -1,18 +1,8 @@
|
|||
use chronicle_dicebot::commands;
|
||||
use chronicle_dicebot::context::Context;
|
||||
use chronicle_dicebot::context::{Context, RoomContext};
|
||||
use chronicle_dicebot::db::Database;
|
||||
use chronicle_dicebot::error::BotError;
|
||||
use matrix_sdk::{
|
||||
identifiers::{room_id, user_id},
|
||||
Room,
|
||||
};
|
||||
|
||||
fn dummy_room() -> Room {
|
||||
Room::new(
|
||||
&room_id!("!fakeroomid:example.com"),
|
||||
&user_id!("@fakeuserid:example.com"),
|
||||
)
|
||||
}
|
||||
use matrix_sdk::identifiers::room_id;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), BotError> {
|
||||
|
@ -26,7 +16,10 @@ async fn main() -> Result<(), BotError> {
|
|||
db: Database::new_temp()?,
|
||||
matrix_client: &matrix_sdk::Client::new("http://example.com")
|
||||
.expect("Could not create matrix client"),
|
||||
room: &dummy_room(),
|
||||
room: RoomContext {
|
||||
id: &room_id!("!fakeroomid:example.com"),
|
||||
display_name: "fake room",
|
||||
},
|
||||
username: "@localuser:example.com",
|
||||
message_body: &input,
|
||||
};
|
||||
|
|
18
src/bot.rs
18
src/bot.rs
|
@ -1,6 +1,6 @@
|
|||
use crate::commands::execute_command;
|
||||
use crate::config::*;
|
||||
use crate::context::Context;
|
||||
use crate::context::{Context, RoomContext};
|
||||
use crate::db::Database;
|
||||
use crate::error::BotError;
|
||||
use crate::state::DiceBotState;
|
||||
|
@ -13,7 +13,7 @@ use matrix_sdk::{
|
|||
room::message::{MessageEventContent, NoticeMessageEventContent},
|
||||
AnyMessageEventContent,
|
||||
},
|
||||
Client, ClientConfig, JsonStore, Room, SyncSettings,
|
||||
Client, ClientConfig, JoinedRoom, SyncSettings,
|
||||
};
|
||||
//use matrix_sdk_common_macros::async_trait;
|
||||
use std::clone::Clone;
|
||||
|
@ -48,8 +48,8 @@ fn cache_dir() -> Result<PathBuf, BotError> {
|
|||
/// Creates the matrix client.
|
||||
fn create_client(config: &Config) -> Result<Client, BotError> {
|
||||
let cache_dir = cache_dir()?;
|
||||
let store = JsonStore::open(&cache_dir)?;
|
||||
let client_config = ClientConfig::new().state_store(Box::new(store));
|
||||
//let store = JsonStore::open(&cache_dir)?;
|
||||
let client_config = ClientConfig::new().store_path(cache_dir);
|
||||
let homeserver_url = Url::parse(&config.matrix_homeserver())?;
|
||||
|
||||
Ok(Client::new_with_config(homeserver_url, client_config)?)
|
||||
|
@ -90,7 +90,7 @@ impl DiceBot {
|
|||
let password = &self.config.matrix_password();
|
||||
|
||||
//TODO provide a device id from config.
|
||||
let mut client = self.client.clone();
|
||||
let client = self.client.clone();
|
||||
client
|
||||
.login(username, password, None, Some("matrix dice bot"))
|
||||
.await?;
|
||||
|
@ -118,9 +118,9 @@ impl DiceBot {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn execute_commands(&self, room: &Room, sender_username: &str, msg_body: &str) {
|
||||
let room_name = room.display_name().clone();
|
||||
let room_id = room.room_id.clone();
|
||||
async fn execute_commands(&self, room: &JoinedRoom, sender_username: &str, msg_body: &str) {
|
||||
let room_name = room.display_name().await.ok().unwrap_or_default();
|
||||
let room_id = room.room_id().clone();
|
||||
|
||||
let mut results = Vec::with_capacity(msg_body.lines().count());
|
||||
|
||||
|
@ -130,7 +130,7 @@ impl DiceBot {
|
|||
let ctx = Context {
|
||||
db: self.db.clone(),
|
||||
matrix_client: &self.client,
|
||||
room: room,
|
||||
room: RoomContext::new_with_name(&room, &room_name),
|
||||
username: &sender_username,
|
||||
message_body: &command,
|
||||
};
|
||||
|
|
|
@ -11,7 +11,8 @@ use matrix_sdk::{
|
|||
room::message::{MessageEventContent, TextMessageEventContent},
|
||||
StrippedStateEvent, SyncMessageEvent, SyncStateEvent,
|
||||
},
|
||||
EventEmitter, SyncRoom,
|
||||
identifiers::RoomId,
|
||||
EventEmitter, RoomState,
|
||||
};
|
||||
use std::clone::Clone;
|
||||
use std::ops::Sub;
|
||||
|
@ -92,17 +93,37 @@ fn should_process_event(db: &Database, room_id: &str, event_id: &str) -> bool {
|
|||
})
|
||||
}
|
||||
|
||||
/// Convert room state object to the room ID and display name, if
|
||||
/// possible. We only care about the room if it is a joined or left
|
||||
/// room.
|
||||
async fn convert_room_state(state: &RoomState) -> Option<(&RoomId, String)> {
|
||||
match state {
|
||||
RoomState::Joined(room) => Some((
|
||||
room.room_id(),
|
||||
room.display_name().await.ok().unwrap_or_default(),
|
||||
)),
|
||||
RoomState::Left(room) => Some((
|
||||
room.room_id(),
|
||||
room.display_name().await.ok().unwrap_or_default(),
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// This event emitter listens for messages with dice rolling commands.
|
||||
/// Originally adapted from the matrix-rust-sdk examples.
|
||||
#[async_trait]
|
||||
impl EventEmitter for DiceBot {
|
||||
async fn on_room_member(&self, room: SyncRoom, event: &SyncStateEvent<MemberEventContent>) {
|
||||
if let SyncRoom::Joined(room) | SyncRoom::Left(room) = room {
|
||||
//Clone to avoid holding lock.
|
||||
let room = room.read().await.clone();
|
||||
let (room_id, username) = (room.room_id.as_str(), &event.state_key);
|
||||
async fn on_room_member(&self, state: RoomState, event: &SyncStateEvent<MemberEventContent>) {
|
||||
let (room_id, room_display_name) = match convert_room_state(&state).await {
|
||||
Some((room_id, room_display_name)) => (room_id, room_display_name),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if !should_process_event(&self.db, room_id, event.event_id.as_str()) {
|
||||
let room_id_str = room_id.as_str();
|
||||
let username = &event.state_key;
|
||||
|
||||
if !should_process_event(&self.db, room_id_str, event.event_id.as_str()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -121,16 +142,23 @@ impl EventEmitter for DiceBot {
|
|||
|
||||
let result = if event_affects_us && !adding_user {
|
||||
info!("Clearing all information for room ID {}", room_id);
|
||||
self.db.rooms.clear_info(room_id)
|
||||
self.db.rooms.clear_info(room_id_str)
|
||||
} else if event_affects_us && adding_user {
|
||||
info!("Joined room {}; recording room information", room_id);
|
||||
record_room_information(&self.client, &self.db, &room, &event.state_key).await
|
||||
record_room_information(
|
||||
&self.client,
|
||||
&self.db,
|
||||
&room_id,
|
||||
&room_display_name,
|
||||
&event.state_key,
|
||||
)
|
||||
.await
|
||||
} else if !event_affects_us && adding_user {
|
||||
info!("Adding user {} to room ID {}", username, room_id);
|
||||
self.db.rooms.add_user_to_room(username, room_id)
|
||||
self.db.rooms.add_user_to_room(username, room_id_str)
|
||||
} else if !event_affects_us && !adding_user {
|
||||
info!("Removing user {} from room ID {}", username, room_id);
|
||||
self.db.rooms.remove_user_from_room(username, room_id)
|
||||
self.db.rooms.remove_user_from_room(username, room_id_str)
|
||||
} else {
|
||||
debug!("Ignoring a room member event: {:#?}", event);
|
||||
Ok(())
|
||||
|
@ -142,36 +170,35 @@ impl EventEmitter for DiceBot {
|
|||
debug!("Successfully processed room member update.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_stripped_state_member(
|
||||
&self,
|
||||
room: SyncRoom,
|
||||
state: RoomState,
|
||||
event: &StrippedStateEvent<MemberEventContent>,
|
||||
_: Option<MemberEventContent>,
|
||||
) {
|
||||
if let SyncRoom::Invited(room) = room {
|
||||
if let RoomState::Invited(room) = state {
|
||||
if let Some(user_id) = self.client.user_id().await {
|
||||
if event.state_key != user_id {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//Clone to avoid holding lock.
|
||||
let room = room.read().await.clone();
|
||||
info!("Autojoining room {}", room.display_name());
|
||||
info!("Autojoining room {}", room.display_name().await);
|
||||
|
||||
if let Err(e) = self.client.join_room_by_id(&room.room_id).await {
|
||||
if let Err(e) = self.client.join_room_by_id(&room.room_id()).await {
|
||||
warn!("Could not join room: {}", e.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_room_message(&self, room: SyncRoom, event: &SyncMessageEvent<MessageEventContent>) {
|
||||
if let SyncRoom::Joined(room) = room {
|
||||
//Clone to avoid holding lock.
|
||||
let room = room.read().await.clone();
|
||||
let room_id = room.room_id.as_str();
|
||||
async fn on_room_message(
|
||||
&self,
|
||||
state: RoomState,
|
||||
event: &SyncMessageEvent<MessageEventContent>,
|
||||
) {
|
||||
if let RoomState::Joined(room) = state {
|
||||
let room_id = room.room_id().as_str();
|
||||
if !should_process_event(&self.db, room_id, event.event_id.as_str()) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -326,12 +326,13 @@ mod tests {
|
|||
use super::*;
|
||||
use crate::db::Database;
|
||||
|
||||
/// Create dummy room instance.
|
||||
fn dummy_room() -> matrix_sdk::Room {
|
||||
matrix_sdk::Room::new(
|
||||
&matrix_sdk::identifiers::room_id!("!fakeroomid:example.com"),
|
||||
&matrix_sdk::identifiers::user_id!("@fakeuserid:example.com"),
|
||||
)
|
||||
macro_rules! dummy_room {
|
||||
() => {
|
||||
crate::context::RoomContext {
|
||||
id: &matrix_sdk::identifiers::room_id!("!fakeroomid:example.com"),
|
||||
display_name: "displayname",
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
///Instead of being random, generate a series of numbers we have complete
|
||||
|
@ -475,7 +476,7 @@ mod tests {
|
|||
let ctx = Context {
|
||||
db: db,
|
||||
matrix_client: &matrix_sdk::Client::new("http://example.com").unwrap(),
|
||||
room: &dummy_room(),
|
||||
room: dummy_room!(),
|
||||
username: "username",
|
||||
message_body: "message",
|
||||
};
|
||||
|
@ -506,7 +507,7 @@ mod tests {
|
|||
let ctx = Context {
|
||||
db: db,
|
||||
matrix_client: &matrix_sdk::Client::new("http://example.com").unwrap(),
|
||||
room: &dummy_room(),
|
||||
room: dummy_room!(),
|
||||
username: "username",
|
||||
message_body: "message",
|
||||
};
|
||||
|
@ -536,12 +537,12 @@ mod tests {
|
|||
let ctx = Context {
|
||||
db: db.clone(),
|
||||
matrix_client: &matrix_sdk::Client::new("http://example.com").unwrap(),
|
||||
room: &dummy_room(),
|
||||
room: dummy_room!(),
|
||||
username: "username",
|
||||
message_body: "message",
|
||||
};
|
||||
|
||||
let user_and_room = UserAndRoom(&ctx.username, &ctx.room.room_id.as_str());
|
||||
let user_and_room = UserAndRoom(&ctx.username, &ctx.room.id.as_str());
|
||||
|
||||
db.variables
|
||||
.set_user_variable(&user_and_room, "myvariable", 10)
|
||||
|
|
|
@ -78,12 +78,13 @@ pub async fn execute_command(ctx: &Context<'_>) -> CommandResult {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Create a dummy room instance.
|
||||
fn dummy_room() -> matrix_sdk::Room {
|
||||
matrix_sdk::Room::new(
|
||||
&matrix_sdk::identifiers::room_id!("!fakeroomid:example.com"),
|
||||
&matrix_sdk::identifiers::user_id!("@fakeuserid:example.com"),
|
||||
)
|
||||
macro_rules! dummy_room {
|
||||
() => {
|
||||
crate::context::RoomContext {
|
||||
id: &matrix_sdk::identifiers::room_id!("!fakeroomid:example.com"),
|
||||
display_name: "displayname",
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -92,7 +93,7 @@ mod tests {
|
|||
let ctx = Context {
|
||||
db: db,
|
||||
matrix_client: &matrix_sdk::Client::new("http://example.com").unwrap(),
|
||||
room: &dummy_room(),
|
||||
room: dummy_room!(),
|
||||
username: "myusername",
|
||||
message_body: "!notacommand",
|
||||
};
|
||||
|
|
|
@ -19,8 +19,14 @@ impl Command for ResyncCommand {
|
|||
let our_username: Option<UserId> = ctx.matrix_client.user_id().await;
|
||||
let our_username: &str = our_username.as_ref().map_or("", UserId::as_str);
|
||||
|
||||
let result: ResyncResult =
|
||||
record_room_information(ctx.matrix_client, &ctx.db, &ctx.room, our_username).await;
|
||||
let result: ResyncResult = record_room_information(
|
||||
ctx.matrix_client,
|
||||
&ctx.db,
|
||||
ctx.room.id,
|
||||
&ctx.room.display_name,
|
||||
our_username,
|
||||
)
|
||||
.await;
|
||||
|
||||
let (plain, html) = match result {
|
||||
Ok(()) => {
|
||||
|
|
|
@ -13,7 +13,7 @@ impl Command for GetAllVariablesCommand {
|
|||
}
|
||||
|
||||
async fn execute(&self, ctx: &Context<'_>) -> Execution {
|
||||
let key = UserAndRoom(&ctx.username, &ctx.room.room_id.as_str());
|
||||
let key = UserAndRoom(&ctx.username, &ctx.room.id.as_str());
|
||||
let result = ctx.db.variables.get_user_variables(&key);
|
||||
|
||||
let value = match result {
|
||||
|
@ -48,7 +48,7 @@ impl Command for GetVariableCommand {
|
|||
|
||||
async fn execute(&self, ctx: &Context<'_>) -> Execution {
|
||||
let name = &self.0;
|
||||
let key = UserAndRoom(&ctx.username, &ctx.room.room_id.as_str());
|
||||
let key = UserAndRoom(&ctx.username, &ctx.room.id.as_str());
|
||||
let result = ctx.db.variables.get_user_variable(&key, name);
|
||||
|
||||
let value = match result {
|
||||
|
@ -74,7 +74,7 @@ impl Command for SetVariableCommand {
|
|||
async fn execute(&self, ctx: &Context<'_>) -> Execution {
|
||||
let name = &self.0;
|
||||
let value = self.1;
|
||||
let key = UserAndRoom(&ctx.username, ctx.room.room_id.as_str());
|
||||
let key = UserAndRoom(&ctx.username, ctx.room.id.as_str());
|
||||
let result = ctx.db.variables.set_user_variable(&key, name, value);
|
||||
|
||||
let content = match result {
|
||||
|
@ -98,7 +98,7 @@ impl Command for DeleteVariableCommand {
|
|||
|
||||
async fn execute(&self, ctx: &Context<'_>) -> Execution {
|
||||
let name = &self.0;
|
||||
let key = UserAndRoom(&ctx.username, ctx.room.room_id.as_str());
|
||||
let key = UserAndRoom(&ctx.username, ctx.room.id.as_str());
|
||||
let result = ctx.db.variables.delete_user_variable(&key, name);
|
||||
|
||||
let value = match result {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::db::Database;
|
||||
use matrix_sdk::identifiers::RoomId;
|
||||
use matrix_sdk::Client;
|
||||
use matrix_sdk::Room;
|
||||
use matrix_sdk::JoinedRoom;
|
||||
|
||||
/// A context carried through the system providing access to things
|
||||
/// like the database.
|
||||
|
@ -8,7 +9,22 @@ use matrix_sdk::Room;
|
|||
pub struct Context<'a> {
|
||||
pub db: Database,
|
||||
pub matrix_client: &'a Client,
|
||||
pub room: &'a Room,
|
||||
pub room: RoomContext<'a>,
|
||||
pub username: &'a str,
|
||||
pub message_body: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RoomContext<'a> {
|
||||
pub id: &'a RoomId,
|
||||
pub display_name: &'a str,
|
||||
}
|
||||
|
||||
impl RoomContext<'_> {
|
||||
pub fn new_with_name<'a>(room: &'a JoinedRoom, display_name: &'a str) -> RoomContext<'a> {
|
||||
RoomContext {
|
||||
id: room.room_id(),
|
||||
display_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -343,12 +343,13 @@ mod tests {
|
|||
use crate::db::Database;
|
||||
use crate::parser::{Amount, Element, Operator};
|
||||
|
||||
/// Create a dummy room instance.
|
||||
fn dummy_room() -> matrix_sdk::Room {
|
||||
matrix_sdk::Room::new(
|
||||
&matrix_sdk::identifiers::room_id!("!fakeroomid:example.com"),
|
||||
&matrix_sdk::identifiers::user_id!("@fakeuserid:example.com"),
|
||||
)
|
||||
macro_rules! dummy_room {
|
||||
() => {
|
||||
crate::context::RoomContext {
|
||||
id: &matrix_sdk::identifiers::room_id!("!fakeroomid:example.com"),
|
||||
display_name: "displayname",
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Generate a series of numbers manually for testing. For this
|
||||
|
@ -391,7 +392,7 @@ mod tests {
|
|||
let ctx = Context {
|
||||
db: db,
|
||||
matrix_client: &matrix_sdk::Client::new("https://example.com").unwrap(),
|
||||
room: &dummy_room(),
|
||||
room: dummy_room!(),
|
||||
username: "username",
|
||||
message_body: "message",
|
||||
};
|
||||
|
|
|
@ -9,7 +9,7 @@ use futures::stream::{self, StreamExt, TryStreamExt};
|
|||
//New hotness
|
||||
pub async fn calculate_dice_amount(amounts: &[Amount], ctx: &Context<'_>) -> Result<i32, BotError> {
|
||||
let stream = stream::iter(amounts);
|
||||
let key = UserAndRoom(&ctx.username, ctx.room.room_id.as_str());
|
||||
let key = UserAndRoom(&ctx.username, ctx.room.id.as_str());
|
||||
let variables = &ctx.db.variables.get_user_variables(&key)?;
|
||||
|
||||
use DiceRollingError::VariableNotFound;
|
||||
|
|
|
@ -27,6 +27,9 @@ pub enum BotError {
|
|||
#[error("could not parse URL")]
|
||||
UrlParseError(#[from] url::ParseError),
|
||||
|
||||
#[error("error in matrix state store: {0}")]
|
||||
MatrixStateStoreError(#[from] matrix_sdk::StoreError),
|
||||
|
||||
#[error("uncategorized matrix SDK error")]
|
||||
MatrixError(#[from] matrix_sdk::Error),
|
||||
|
||||
|
|
11
src/logic.rs
11
src/logic.rs
|
@ -1,21 +1,22 @@
|
|||
use crate::db::errors::DataError;
|
||||
use crate::matrix;
|
||||
use crate::models::RoomInfo;
|
||||
use matrix_sdk::{self, Client, Room};
|
||||
use matrix_sdk::{self, identifiers::RoomId, Client};
|
||||
|
||||
/// Record the information about a room, including users in it.
|
||||
pub async fn record_room_information(
|
||||
client: &Client,
|
||||
db: &crate::db::Database,
|
||||
room: &Room,
|
||||
room_id: &RoomId,
|
||||
room_display_name: &str,
|
||||
our_username: &str,
|
||||
) -> Result<(), DataError> {
|
||||
let room_id_str = room.room_id.as_str();
|
||||
let usernames = matrix::get_users_in_room(&client, &room.room_id).await;
|
||||
let room_id_str = room_id.as_str();
|
||||
let usernames = matrix::get_users_in_room(&client, &room_id).await;
|
||||
|
||||
let info = RoomInfo {
|
||||
room_id: room_id_str.to_owned(),
|
||||
room_name: room.display_name(),
|
||||
room_name: room_display_name.to_owned(),
|
||||
};
|
||||
|
||||
// TODO this and the username adding should be one whole
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
use matrix_sdk::{identifiers::RoomId, Client, Room};
|
||||
use matrix_sdk::{identifiers::RoomId, Client};
|
||||
|
||||
/// Retrieve a list of users in a given room.
|
||||
pub async fn get_users_in_room(client: &Client, room_id: &RoomId) -> Vec<String> {
|
||||
if let Some(joined_room) = client.get_joined_room(room_id).await {
|
||||
let joined_room: Room = joined_room.read().await.clone();
|
||||
if let Some(joined_room) = client.get_joined_room(room_id) {
|
||||
joined_room
|
||||
.joined_members
|
||||
.keys()
|
||||
.map(|user_id| format!("@{}:{}", user_id.localpart(), user_id.server_name()))
|
||||
.joined_members()
|
||||
.await
|
||||
.ok()
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|member| {
|
||||
format!(
|
||||
"@{}:{}",
|
||||
member.user_id().localpart(),
|
||||
member.user_id().server_name()
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
|
|
Loading…
Reference in New Issue