Add and use active room value. #80

Manually merged
projectmoon merged 2 commits from use-active-room into master 2021-05-30 15:11:07 +00:00
9 changed files with 148 additions and 83 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

@ -35,7 +35,7 @@ impl Command for GetAllVariablesCommand {
async fn execute(&self, ctx: &Context<'_>) -> ExecutionResult { async fn execute(&self, ctx: &Context<'_>) -> ExecutionResult {
let variables = ctx let variables = ctx
.db .db
.get_user_variables(&ctx.username, ctx.room_id().as_str()) .get_user_variables(&ctx.username, ctx.active_room_id().as_str())
.await?; .await?;
let mut variable_list: Vec<String> = variables let mut variable_list: Vec<String> = variables
@ -85,7 +85,7 @@ impl Command for GetVariableCommand {
let name = &self.0; let name = &self.0;
let result = ctx let result = ctx
.db .db
.get_user_variable(&ctx.username, ctx.room_id().as_str(), name) .get_user_variable(&ctx.username, ctx.active_room_id().as_str(), name)
.await; .await;
let value = match result { let value = match result {
@ -131,7 +131,7 @@ impl Command for SetVariableCommand {
let value = self.1; let value = self.1;
ctx.db ctx.db
.set_user_variable(&ctx.username, ctx.room_id().as_str(), name, value) .set_user_variable(&ctx.username, ctx.active_room_id().as_str(), name, value)
.await?; .await?;
let content = format!("{} = {}", name, value); let content = format!("{} = {}", name, value);
@ -170,7 +170,7 @@ impl Command for DeleteVariableCommand {
let name = &self.0; let name = &self.0;
let result = ctx let result = ctx
.db .db
.delete_user_variable(&ctx.username, ctx.room_id().as_str(), name) .delete_user_variable(&ctx.username, ctx.active_room_id().as_str(), name)
.await; .await;
let value = match result { let value = match result {

View File

@ -11,20 +11,25 @@ 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,
} }
impl Context<'_> { impl Context<'_> {
pub fn active_room_id(&self) -> &RoomId {
self.active_room.id
}
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 +43,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 +68,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

@ -380,7 +380,12 @@ async fn update_skill(ctx: &Context<'_>, variable: &str, value: u32) -> Result<(
use std::convert::TryInto; use std::convert::TryInto;
let value: i32 = value.try_into()?; let value: i32 = value.try_into()?;
ctx.db ctx.db
.set_user_variable(&ctx.username, &ctx.room_id().as_str(), variable, value) .set_user_variable(
&ctx.username,
&ctx.active_room_id().as_str(),
variable,
value,
)
.await?; .await?;
Ok(()) Ok(())
} }
@ -506,8 +511,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 +549,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 +587,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",
}; };

View File

@ -27,7 +27,7 @@ pub async fn calculate_dice_amount(amounts: &[Amount], ctx: &Context<'_>) -> Res
let stream = stream::iter(amounts); let stream = stream::iter(amounts);
let variables = &ctx let variables = &ctx
.db .db
.get_user_variables(&ctx.username, ctx.room_id().as_str()) .get_user_variables(&ctx.username, ctx.active_room_id().as_str())
.await?; .await?;
use DiceRollingError::VariableNotFound; use DiceRollingError::VariableNotFound;