Change room in context to origin_room, add active_room.

The context now knows about origin room (the room where the command
was executed), and the "active room," which is the room that the user
wants the command to apply to. If no active room is defined, then the
origin room acts as the active room. In a public room with the bot,
the active room is also the same as the origin room.
This commit is contained in:
projectmoon 2021-05-29 22:29:30 +00:00
parent 53339282e0
commit 3d2eb14cd3
7 changed files with 133 additions and 77 deletions

View File

@ -1,4 +1,5 @@
use matrix_sdk::identifiers::room_id; use matrix_sdk::identifiers::room_id;
use matrix_sdk::Client;
use tenebrous_dicebot::commands; use tenebrous_dicebot::commands;
use tenebrous_dicebot::commands::ResponseExtractor; use tenebrous_dicebot::commands::ResponseExtractor;
use tenebrous_dicebot::context::{Context, RoomContext}; use tenebrous_dicebot::context::{Context, RoomContext};
@ -26,11 +27,15 @@ async fn main() -> Result<(), BotError> {
.await?; .await?;
let context = Context { let context = Context {
db: db, db,
account: Account::default(), account: Account::default(),
matrix_client: &matrix_sdk::Client::new(homeserver) matrix_client: Client::new(homeserver).expect("Could not create matrix client"),
.expect("Could not create matrix client"), origin_room: RoomContext {
room: RoomContext { id: &room_id!("!fakeroomid:example.com"),
display_name: "fake room".to_owned(),
secure: false,
},
active_room: RoomContext {
id: &room_id!("!fakeroomid:example.com"), id: &room_id!("!fakeroomid:example.com"),
display_name: "fake room".to_owned(), display_name: "fake room".to_owned(),
secure: false, secure: false,

View File

@ -1,12 +1,21 @@
use crate::commands::{execute_command, ExecutionResult, ResponseExtractor};
use crate::context::{Context, RoomContext}; use crate::context::{Context, RoomContext};
use crate::db::sqlite::Database; use crate::db::sqlite::Database;
use crate::error::BotError; use crate::error::BotError;
use crate::logic; use crate::logic;
use crate::matrix; use crate::matrix;
use crate::{
commands::{execute_command, ExecutionResult, ResponseExtractor},
models::Account,
};
use futures::stream::{self, StreamExt}; use futures::stream::{self, StreamExt};
use matrix_sdk::{self, identifiers::EventId, room::Joined, Client}; use matrix_sdk::{
self,
identifiers::{EventId, RoomId},
room::Joined,
Client,
};
use std::clone::Clone; use std::clone::Clone;
use std::convert::TryFrom;
/// Handle responding to a single command being executed. Wil print /// Handle responding to a single command being executed. Wil print
/// out the full result of that command. /// out the full result of that command.
@ -95,24 +104,57 @@ pub(super) async fn handle_multiple_results(
matrix::send_message(client, room.room_id(), (&message, &plain), None).await; matrix::send_message(client, room.room_id(), (&message, &plain), None).await;
} }
/// Create a context for command execution. Can fai if the room /// Map an account's active room value to an actual matrix room, if
/// context creation fails. /// the account has an active room. This only retrieves the
async fn create_context<'a>( /// user-specified active room, and doesn't perform any further
db: &'a Database, /// filtering.
client: &'a Client, fn get_account_active_room(client: &Client, account: &Account) -> Result<Option<Joined>, BotError> {
room: &'a Joined, let active_room = account
sender: &'a str, .registered_user()
command: &'a str, .and_then(|u| u.active_room.as_deref())
) -> Result<Context<'a>, BotError> { .map(|room_id| RoomId::try_from(room_id))
let room_ctx = RoomContext::new(room, sender).await?; .transpose()?
Ok(Context { .and_then(|active_room_id| client.get_joined_room(&active_room_id));
Ok(active_room)
}
/// Execute a single command in the list of commands. Can fail if the
/// Account value cannot be created/fetched from the database, or if
/// room display names cannot be calculated. Otherwise, the success or
/// error of command execution itself is returned.
async fn execute_single_command(
command: &str,
db: &Database,
client: &Client,
origin_room: &Joined,
sender: &str,
) -> ExecutionResult {
let origin_ctx = RoomContext::new(origin_room, sender).await?;
let account = logic::get_account(db, sender).await?;
let active_room = get_account_active_room(client, &account)?;
// Active room is used in secure command-issuing rooms. In
// "public" rooms, where other users are, treat origin as the
// active room.
let active_room = active_room
.as_ref()
.filter(|_| origin_ctx.secure)
.unwrap_or(origin_room);
let active_ctx = RoomContext::new(active_room, sender).await?;
let ctx = Context {
account,
db: db.clone(), db: db.clone(),
matrix_client: client, matrix_client: client.clone(),
room: room_ctx, origin_room: origin_ctx,
username: &sender, username: &sender,
account: logic::get_account(db, &sender).await?, active_room: active_ctx,
message_body: &command, message_body: &command,
}) };
execute_command(&ctx).await
} }
/// Attempt to execute all commands sent to the bot in a message. This /// Attempt to execute all commands sent to the bot in a message. This
@ -127,13 +169,8 @@ pub(super) async fn execute(
) -> Vec<(String, ExecutionResult)> { ) -> Vec<(String, ExecutionResult)> {
stream::iter(commands) stream::iter(commands)
.then(|command| async move { .then(|command| async move {
match create_context(db, client, room, sender, command).await { let result = execute_single_command(command, db, client, room, sender).await;
Err(e) => (command.to_owned(), Err(e)), (command.to_owned(), result)
Ok(ctx) => {
let cmd_result = execute_command(&ctx).await;
(command.to_owned(), cmd_result)
}
}
}) })
.collect() .collect()
.await .await

View File

@ -485,8 +485,9 @@ mod tests {
let ctx = Context { let ctx = Context {
account: crate::models::Account::default(), account: crate::models::Account::default(),
db: db, db: db,
matrix_client: &matrix_sdk::Client::new(homeserver).unwrap(), matrix_client: matrix_sdk::Client::new(homeserver).unwrap(),
room: dummy_room!(), origin_room: dummy_room!(),
active_room: dummy_room!(),
username: "username", username: "username",
message_body: "message", message_body: "message",
}; };
@ -526,8 +527,9 @@ mod tests {
let ctx = Context { let ctx = Context {
account: crate::models::Account::default(), account: crate::models::Account::default(),
db: db, db: db,
matrix_client: &matrix_sdk::Client::new(homeserver).unwrap(), matrix_client: matrix_sdk::Client::new(homeserver).unwrap(),
room: dummy_room!(), origin_room: dummy_room!(),
active_room: dummy_room!(),
username: "username", username: "username",
message_body: "message", message_body: "message",
}; };
@ -564,15 +566,21 @@ mod tests {
let ctx = Context { let ctx = Context {
account: crate::models::Account::default(), account: crate::models::Account::default(),
db: db.clone(), db: db.clone(),
matrix_client: &matrix_sdk::Client::new(homeserver).unwrap(), matrix_client: matrix_sdk::Client::new(homeserver).unwrap(),
room: dummy_room!(), origin_room: dummy_room!(),
active_room: dummy_room!(),
username: "username", username: "username",
message_body: "message", message_body: "message",
}; };
db.set_user_variable(&ctx.username, &ctx.room.id.as_str(), "myvariable", 10) db.set_user_variable(
.await &ctx.username,
.expect("could not set myvariable to 10"); &ctx.origin_room.id.as_str(),
"myvariable",
10,
)
.await
.expect("could not set myvariable to 10");
let amounts = vec![Amount { let amounts = vec![Amount {
operator: Operator::Plus, operator: Operator::Plus,

View File

@ -146,13 +146,13 @@ fn log_command(cmd: &(impl Command + ?Sized), ctx: &Context, result: &ExecutionR
Ok(_) => { Ok(_) => {
info!( info!(
"[{}] {} <{}{}> - success", "[{}] {} <{}{}> - success",
ctx.room.display_name, ctx.username, command, dots ctx.origin_room.display_name, ctx.username, command, dots
); );
} }
Err(e) => { Err(e) => {
error!( error!(
"[{}] {} <{}{}> - {}", "[{}] {} <{}{}> - {}",
ctx.room.display_name, ctx.username, command, dots, e ctx.origin_room.display_name, ctx.username, command, dots, e
); );
} }
}; };
@ -196,8 +196,9 @@ mod tests {
let ctx = Context { let ctx = Context {
account: crate::models::Account::default(), account: crate::models::Account::default(),
db: db, db: db,
matrix_client: &matrix_sdk::Client::new(homeserver).unwrap(), matrix_client: matrix_sdk::Client::new(homeserver).unwrap(),
room: secure_room!(), origin_room: secure_room!(),
active_room: secure_room!(),
username: "myusername", username: "myusername",
message_body: "!notacommand", message_body: "!notacommand",
}; };
@ -218,8 +219,9 @@ mod tests {
let ctx = Context { let ctx = Context {
account: crate::models::Account::default(), account: crate::models::Account::default(),
db: db, db: db,
matrix_client: &matrix_sdk::Client::new(homeserver).unwrap(), matrix_client: matrix_sdk::Client::new(homeserver).unwrap(),
room: secure_room!(), origin_room: secure_room!(),
active_room: secure_room!(),
username: "myusername", username: "myusername",
message_body: "!notacommand", message_body: "!notacommand",
}; };
@ -240,8 +242,9 @@ mod tests {
let ctx = Context { let ctx = Context {
account: crate::models::Account::default(), account: crate::models::Account::default(),
db: db, db: db,
matrix_client: &matrix_sdk::Client::new(homeserver).unwrap(), matrix_client: matrix_sdk::Client::new(homeserver).unwrap(),
room: dummy_room!(), origin_room: dummy_room!(),
active_room: dummy_room!(),
username: "myusername", username: "myusername",
message_body: "!notacommand", message_body: "!notacommand",
}; };
@ -262,8 +265,9 @@ mod tests {
let ctx = Context { let ctx = Context {
account: crate::models::Account::default(), account: crate::models::Account::default(),
db: db, db: db,
matrix_client: &matrix_sdk::Client::new(homeserver).unwrap(), matrix_client: matrix_sdk::Client::new(homeserver).unwrap(),
room: dummy_room!(), origin_room: dummy_room!(),
active_room: dummy_room!(),
username: "myusername", username: "myusername",
message_body: "!notacommand", message_body: "!notacommand",
}; };
@ -284,8 +288,9 @@ mod tests {
let ctx = Context { let ctx = Context {
account: crate::models::Account::default(), account: crate::models::Account::default(),
db: db, db: db,
matrix_client: &matrix_sdk::Client::new(homeserver).unwrap(), matrix_client: matrix_sdk::Client::new(homeserver).unwrap(),
room: dummy_room!(), origin_room: dummy_room!(),
active_room: dummy_room!(),
username: "myusername", username: "myusername",
message_body: "!notacommand", message_body: "!notacommand",
}; };

View File

@ -110,7 +110,7 @@ impl Command for ListRoomsCommand {
} }
async fn execute(&self, ctx: &Context<'_>) -> ExecutionResult { async fn execute(&self, ctx: &Context<'_>) -> ExecutionResult {
let rooms_for_user: Vec<String> = get_rooms_for_user(ctx.matrix_client, ctx.username) let rooms_for_user: Vec<String> = get_rooms_for_user(&ctx.matrix_client, ctx.username)
.await .await
.map(|rooms| { .map(|rooms| {
rooms rooms
@ -155,7 +155,7 @@ impl Command for SetRoomCommand {
return Err(BotError::AccountDoesNotExist); return Err(BotError::AccountDoesNotExist);
} }
let rooms_for_user = get_rooms_for_user(ctx.matrix_client, ctx.username).await?; let rooms_for_user = get_rooms_for_user(&ctx.matrix_client, ctx.username).await?;
let room = search_for_room(&rooms_for_user, &self.0); let room = search_for_room(&rooms_for_user, &self.0);
if let Some(room) = room { if let Some(room) = room {

View File

@ -11,8 +11,9 @@ use std::convert::TryFrom;
#[derive(Clone)] #[derive(Clone)]
pub struct Context<'a> { pub struct Context<'a> {
pub db: Database, pub db: Database,
pub matrix_client: &'a Client, pub matrix_client: Client,
pub room: RoomContext<'a>, pub origin_room: RoomContext<'a>,
pub active_room: RoomContext<'a>,
pub username: &'a str, pub username: &'a str,
pub message_body: &'a str, pub message_body: &'a str,
pub account: Account, pub account: Account,
@ -20,11 +21,11 @@ pub struct Context<'a> {
impl Context<'_> { impl Context<'_> {
pub fn room_id(&self) -> &RoomId { pub fn room_id(&self) -> &RoomId {
self.room.id self.origin_room.id
} }
pub fn is_secure(&self) -> bool { pub fn is_secure(&self) -> bool {
self.room.secure self.origin_room.secure
} }
} }
@ -38,15 +39,21 @@ pub struct RoomContext<'a> {
impl RoomContext<'_> { impl RoomContext<'_> {
pub async fn new_with_name<'a>( pub async fn new_with_name<'a>(
room: &'a Joined, room: &'a Joined,
display_name: String,
sending_user: &str, sending_user: &str,
) -> Result<RoomContext<'a>, BotError> { ) -> Result<RoomContext<'a>, BotError> {
// TODO is_direct is a hack; should set rooms to Direct // TODO is_direct is a hack; the bot should set eligible rooms
// Message upon joining, if other contact has requested it. // to Direct Message upon joining, if other contact has
// Waiting on SDK support. // requested it. Waiting on SDK support.
let display_name = room
.display_name()
.await
.ok()
.unwrap_or_default()
.to_string();
let sending_user = UserId::try_from(sending_user)?; let sending_user = UserId::try_from(sending_user)?;
let user_in_room = room.get_member(&sending_user).await.ok().is_some(); let user_in_room = room.get_member(&sending_user).await.ok().is_some();
let is_direct = room.joined_members().await?.len() == 2; let is_direct = room.active_members().await?.len() == 2;
Ok(RoomContext { Ok(RoomContext {
id: room.room_id(), id: room.room_id(),
@ -57,17 +64,8 @@ impl RoomContext<'_> {
pub async fn new<'a>( pub async fn new<'a>(
room: &'a Joined, room: &'a Joined,
sending_user: &str, sending_user: &'a str,
) -> Result<RoomContext<'a>, BotError> { ) -> Result<RoomContext<'a>, BotError> {
Self::new_with_name( Self::new_with_name(room, sending_user).await
&room,
room.display_name()
.await
.ok()
.unwrap_or_default()
.to_string(),
sending_user,
)
.await
} }
} }

View File

@ -506,8 +506,9 @@ mod tests {
let ctx = Context { let ctx = Context {
account: crate::models::Account::default(), account: crate::models::Account::default(),
db: db, db: db,
matrix_client: &matrix_sdk::Client::new(homeserver).unwrap(), matrix_client: matrix_sdk::Client::new(homeserver).unwrap(),
room: dummy_room!(), origin_room: dummy_room!(),
active_room: dummy_room!(),
username: "username", username: "username",
message_body: "message", message_body: "message",
}; };
@ -543,8 +544,9 @@ mod tests {
let ctx = Context { let ctx = Context {
account: crate::models::Account::default(), account: crate::models::Account::default(),
db: db, db: db,
matrix_client: &matrix_sdk::Client::new(homeserver).unwrap(), matrix_client: matrix_sdk::Client::new(homeserver).unwrap(),
room: dummy_room!(), origin_room: dummy_room!(),
active_room: dummy_room!(),
username: "username", username: "username",
message_body: "message", message_body: "message",
}; };
@ -580,8 +582,9 @@ mod tests {
let ctx = Context { let ctx = Context {
account: crate::models::Account::default(), account: crate::models::Account::default(),
db: db, db: db,
matrix_client: &matrix_sdk::Client::new(homeserver).unwrap(), matrix_client: matrix_sdk::Client::new(homeserver).unwrap(),
room: dummy_room!(), origin_room: dummy_room!(),
active_room: dummy_room!(),
username: "username", username: "username",
message_body: "message", message_body: "message",
}; };