Compare commits
No commits in common. "0c0ddafd031a0038824dd5431a5a3a80e5a0ee70" and "e9c0a184bdecc88dfc4a8aad51ff1c204a9ba669" have entirely different histories.
0c0ddafd03
...
e9c0a184bd
|
@ -576,15 +576,6 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fuse-rust"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "00df1351ccd6b34c2f67658bd4524b677e30f7269a6de44b038ec20211853e5e"
|
|
||||||
dependencies = [
|
|
||||||
"crossbeam-utils",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futf"
|
name = "futf"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
|
@ -2553,7 +2544,6 @@ dependencies = [
|
||||||
"barrel",
|
"barrel",
|
||||||
"combine",
|
"combine",
|
||||||
"dirs",
|
"dirs",
|
||||||
"fuse-rust",
|
|
||||||
"futures",
|
"futures",
|
||||||
"html2text",
|
"html2text",
|
||||||
"indoc",
|
"indoc",
|
||||||
|
|
|
@ -32,7 +32,6 @@ refinery = { version = "0.5", features = ["rusqlite"]}
|
||||||
barrel = { version = "0.6", features = ["sqlite3"] }
|
barrel = { version = "0.6", features = ["sqlite3"] }
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
substring = "1.4"
|
substring = "1.4"
|
||||||
fuse-rust = "0.2"
|
|
||||||
|
|
||||||
[dependencies.sqlx]
|
[dependencies.sqlx]
|
||||||
version = "0.5"
|
version = "0.5"
|
||||||
|
|
|
@ -132,16 +132,14 @@ fn log_command(cmd: &(impl Command + ?Sized), ctx: &Context, result: &ExecutionR
|
||||||
use substring::Substring;
|
use substring::Substring;
|
||||||
let command = match cmd.is_secure() {
|
let command = match cmd.is_secure() {
|
||||||
true => cmd.name(),
|
true => cmd.name(),
|
||||||
false => ctx.message_body,
|
false => ctx.message_body.substring(0, 30),
|
||||||
};
|
};
|
||||||
|
|
||||||
let dots = match command.len() {
|
let dots = match ctx.message_body.len() {
|
||||||
_len if _len > 30 => "[...]",
|
_len if _len > 30 => "[...]",
|
||||||
_ => "",
|
_ => "",
|
||||||
};
|
};
|
||||||
|
|
||||||
let command = command.substring(0, 30);
|
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
info!(
|
info!(
|
||||||
|
|
|
@ -9,7 +9,7 @@ use crate::commands::{
|
||||||
cthulhu::{CthAdvanceRoll, CthRoll},
|
cthulhu::{CthAdvanceRoll, CthRoll},
|
||||||
management::{CheckCommand, LinkCommand, RegisterCommand, UnlinkCommand, UnregisterCommand},
|
management::{CheckCommand, LinkCommand, RegisterCommand, UnlinkCommand, UnregisterCommand},
|
||||||
misc::HelpCommand,
|
misc::HelpCommand,
|
||||||
rooms::{ListRoomsCommand, SetRoomCommand},
|
rooms::ListRoomsCommand,
|
||||||
variables::{
|
variables::{
|
||||||
DeleteVariableCommand, GetAllVariablesCommand, GetVariableCommand, SetVariableCommand,
|
DeleteVariableCommand, GetAllVariablesCommand, GetVariableCommand, SetVariableCommand,
|
||||||
},
|
},
|
||||||
|
@ -91,7 +91,6 @@ pub fn parse_command(input: &str) -> Result<Box<dyn Command>, BotError> {
|
||||||
"check" => convert_to!(CheckCommand, cmd_input),
|
"check" => convert_to!(CheckCommand, cmd_input),
|
||||||
"unregister" => convert_to!(UnregisterCommand, cmd_input),
|
"unregister" => convert_to!(UnregisterCommand, cmd_input),
|
||||||
"rooms" => convert_to!(ListRoomsCommand, cmd_input),
|
"rooms" => convert_to!(ListRoomsCommand, cmd_input),
|
||||||
"room" => convert_to!(SetRoomCommand, cmd_input),
|
|
||||||
_ => Err(CommandParsingError::UnrecognizedCommand(cmd).into()),
|
_ => Err(CommandParsingError::UnrecognizedCommand(cmd).into()),
|
||||||
},
|
},
|
||||||
//All other errors passed up.
|
//All other errors passed up.
|
||||||
|
|
|
@ -1,81 +1,11 @@
|
||||||
use super::{Command, Execution, ExecutionResult};
|
use super::{Command, Execution, ExecutionResult};
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::error::BotError;
|
use crate::error::BotError;
|
||||||
use crate::matrix;
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use fuse_rust::{Fuse, FuseProperty, Fuseable};
|
|
||||||
use futures::stream::{self, StreamExt, TryStreamExt};
|
use futures::stream::{self, StreamExt, TryStreamExt};
|
||||||
use matrix_sdk::{identifiers::UserId, Client};
|
use matrix_sdk::identifiers::UserId;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
/// Holds matrix room ID and display name as strings, for use with
|
|
||||||
/// searching. See search_for_room.
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
||||||
struct RoomNameAndId {
|
|
||||||
id: String,
|
|
||||||
name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Allows searching for a room name and ID struct, instead of just
|
|
||||||
/// searching room display names directly.
|
|
||||||
impl Fuseable for RoomNameAndId {
|
|
||||||
fn properties(&self) -> Vec<FuseProperty> {
|
|
||||||
return vec![FuseProperty {
|
|
||||||
value: String::from("name"),
|
|
||||||
weight: 1.0,
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lookup(&self, key: &str) -> Option<&str> {
|
|
||||||
return match key {
|
|
||||||
"name" => Some(&self.name),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempt to find a room by either name or Matrix Room ID query
|
|
||||||
/// string. It prefers the exact room ID first, and then falls back to
|
|
||||||
/// fuzzy searching based on room display name. The best match is
|
|
||||||
/// returned, or None if no matches were found.
|
|
||||||
fn search_for_room<'a>(
|
|
||||||
rooms_for_user: &'a [RoomNameAndId],
|
|
||||||
search_for: &str,
|
|
||||||
) -> Option<&'a RoomNameAndId> {
|
|
||||||
//Lowest score is the best match.
|
|
||||||
let best_fuzzy_match = || -> Option<&RoomNameAndId> {
|
|
||||||
Fuse::default()
|
|
||||||
.search_text_in_fuse_list(search_for, &rooms_for_user)
|
|
||||||
.into_iter()
|
|
||||||
.min_by(|r1, r2| r1.score.partial_cmp(&r2.score).unwrap())
|
|
||||||
.and_then(|result| rooms_for_user.get(result.index))
|
|
||||||
};
|
|
||||||
|
|
||||||
rooms_for_user
|
|
||||||
.iter()
|
|
||||||
.find(|room| room.id == search_for)
|
|
||||||
.or_else(best_fuzzy_match)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_rooms_for_user(
|
|
||||||
client: &Client,
|
|
||||||
user_id: &str,
|
|
||||||
) -> Result<Vec<RoomNameAndId>, BotError> {
|
|
||||||
let user_id = UserId::try_from(user_id)?;
|
|
||||||
let rooms_for_user = matrix::get_rooms_for_user(client, &user_id).await?;
|
|
||||||
let rooms_for_user: Vec<RoomNameAndId> = stream::iter(rooms_for_user)
|
|
||||||
.filter_map(|room| async move {
|
|
||||||
Some(room.display_name().await.map(|room_name| RoomNameAndId {
|
|
||||||
id: room.room_id().to_string(),
|
|
||||||
name: room_name,
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
.try_collect()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(rooms_for_user)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ListRoomsCommand;
|
pub struct ListRoomsCommand;
|
||||||
|
|
||||||
impl From<ListRoomsCommand> for Box<dyn Command> {
|
impl From<ListRoomsCommand> for Box<dyn Command> {
|
||||||
|
@ -95,7 +25,7 @@ impl TryFrom<String> for ListRoomsCommand {
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Command for ListRoomsCommand {
|
impl Command for ListRoomsCommand {
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
"list rooms"
|
"list rooms command"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_secure(&self) -> bool {
|
fn is_secure(&self) -> bool {
|
||||||
|
@ -103,78 +33,22 @@ 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 user_id = UserId::try_from(ctx.username)?;
|
||||||
|
let rooms_for_user = crate::matrix::get_rooms_for_user(ctx.matrix_client, &user_id).await?;
|
||||||
|
|
||||||
|
let rooms_for_user: Vec<String> = stream::iter(rooms_for_user)
|
||||||
|
.filter_map(|room| async move {
|
||||||
|
Some(
|
||||||
|
room.display_name()
|
||||||
.await
|
.await
|
||||||
.map(|rooms| {
|
.map(|room_name| (room.room_id().to_string(), room_name)),
|
||||||
rooms
|
)
|
||||||
.into_iter()
|
})
|
||||||
.map(|room| format!(" {} | {}", room.id, room.name))
|
.map_ok(|(room_id, room_name)| format!(" {} | {}", room_id, room_name))
|
||||||
.collect()
|
.try_collect()
|
||||||
})?;
|
.await?;
|
||||||
|
|
||||||
let html = format!("<pre>{}</pre>", rooms_for_user.join("\n"));
|
let html = format!("<pre>{}</pre>", rooms_for_user.join("\n"));
|
||||||
Execution::success(html)
|
Execution::success(html)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SetRoomCommand(String);
|
|
||||||
|
|
||||||
impl From<SetRoomCommand> for Box<dyn Command> {
|
|
||||||
fn from(cmd: SetRoomCommand) -> Self {
|
|
||||||
Box::new(cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<String> for SetRoomCommand {
|
|
||||||
type Error = BotError;
|
|
||||||
|
|
||||||
fn try_from(input: String) -> Result<Self, Self::Error> {
|
|
||||||
Ok(SetRoomCommand(input))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl Command for SetRoomCommand {
|
|
||||||
fn name(&self) -> &'static str {
|
|
||||||
"set active room"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_secure(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn execute(&self, ctx: &Context<'_>) -> ExecutionResult {
|
|
||||||
let rooms_for_user = get_rooms_for_user(ctx.matrix_client, ctx.username).await?;
|
|
||||||
let room = search_for_room(&rooms_for_user, &self.0);
|
|
||||||
|
|
||||||
if let Some(room) = room {
|
|
||||||
Execution::success(format!(r#"Active room set to "{}""#, room.name))
|
|
||||||
} else {
|
|
||||||
Err(BotError::RoomDoesNotExist)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
|
||||||
async fn set_room_prefers_room_id_over_name() {
|
|
||||||
let rooms = vec![
|
|
||||||
RoomNameAndId {
|
|
||||||
id: "roomid".to_string(),
|
|
||||||
name: "room_name".to_string(),
|
|
||||||
},
|
|
||||||
RoomNameAndId {
|
|
||||||
id: "anotherone".to_string(),
|
|
||||||
name: "roomid".to_string(),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
let found_room = search_for_room(&rooms, "roomid");
|
|
||||||
|
|
||||||
assert!(found_room.is_some());
|
|
||||||
assert_eq!(found_room.unwrap(), &rooms[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -90,9 +90,6 @@ pub enum BotError {
|
||||||
|
|
||||||
#[error("user account already exists")]
|
#[error("user account already exists")]
|
||||||
AccountAlreadyExists,
|
AccountAlreadyExists,
|
||||||
|
|
||||||
#[error("room name or id does not exist")]
|
|
||||||
RoomDoesNotExist,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
|
Loading…
Reference in New Issue