Initial commit

This commit is contained in:
Slavi Pantaleev
2024-09-12 13:44:06 +03:00
commit 946aa9d9e9
220 changed files with 26033 additions and 0 deletions

View File

@@ -0,0 +1,63 @@
#[cfg(test)]
mod tests;
use super::super::ControllerType;
#[derive(Debug, PartialEq)]
pub enum AccessControllerType {
Help,
GetUsers,
SetUsers(Option<Vec<String>>),
GetRoomLocalAgentManagers,
SetRoomLocalAgentManagers(Option<Vec<String>>),
}
pub fn determine_controller(text: &str) -> ControllerType {
if text.starts_with("users") {
return ControllerType::Access(AccessControllerType::GetUsers);
}
if let Some(patterns_string) = text.strip_prefix("set-users") {
let patterns_string = patterns_string.trim().to_owned();
let patterns_option = if patterns_string.is_empty() {
None
} else {
let patterns_vector = patterns_string
.split(" ")
.map(|s| s.to_string())
.collect::<Vec<String>>();
Some(patterns_vector)
};
return ControllerType::Access(AccessControllerType::SetUsers(patterns_option));
}
if text.starts_with("room-local-agent-managers") {
return ControllerType::Access(AccessControllerType::GetRoomLocalAgentManagers);
}
if let Some(patterns_string) = text.strip_prefix("set-room-local-agent-managers") {
let patterns_string = patterns_string.trim().to_owned();
let patterns_option = if patterns_string.is_empty() {
None
} else {
let patterns_vector = patterns_string
.split(" ")
.map(|s| s.to_string())
.collect::<Vec<String>>();
Some(patterns_vector)
};
return ControllerType::Access(AccessControllerType::SetRoomLocalAgentManagers(
patterns_option,
));
}
ControllerType::Access(AccessControllerType::Help)
}

View File

@@ -0,0 +1,58 @@
#[test]
fn determine_controller() {
struct TestCase {
name: &'static str,
input: &'static str,
expected: super::ControllerType,
}
let test_cases = vec![
TestCase {
name: "Top-level is help",
input: "",
expected: super::ControllerType::Access(super::AccessControllerType::Help),
},
TestCase {
name: "Anything else goes to top-level",
input: "whatever",
expected: super::ControllerType::Access(super::AccessControllerType::Help),
},
TestCase {
name: "Users",
input: "users",
expected: super::ControllerType::Access(super::AccessControllerType::GetUsers),
},
TestCase {
name: "Set-users",
input: "set-users @user:example.com @bot.*:example.org",
expected: super::ControllerType::Access(super::AccessControllerType::SetUsers(Some(
vec![
"@user:example.com".to_owned(),
"@bot.*:example.org".to_owned(),
],
))),
},
TestCase {
name: "Room-local-agent-managers",
input: "room-local-agent-managers",
expected: super::ControllerType::Access(
super::AccessControllerType::GetRoomLocalAgentManagers,
),
},
TestCase {
name: "Set-room-local-agent-managers",
input: "set-room-local-agent-managers @user:example.com @bot.*:example.org",
expected: super::ControllerType::Access(
super::AccessControllerType::SetRoomLocalAgentManagers(Some(vec![
"@user:example.com".to_owned(),
"@bot.*:example.org".to_owned(),
])),
),
},
];
for test_case in test_cases {
let result = super::determine_controller(test_case.input);
assert_eq!(result, test_case.expected, "Test case: {}", test_case.name);
}
}

View File

@@ -0,0 +1,45 @@
use mxlink::MessageResponseType;
use crate::{entity::MessageContext, strings, Bot};
use super::AccessControllerType;
pub async fn dispatch_controller(
handler: &AccessControllerType,
message_context: &MessageContext,
bot: &Bot,
) -> anyhow::Result<()> {
// Only the help command is available without access control, so that all users can get familiar with how the bot's access system works.
match handler {
AccessControllerType::Help => {}
_ => {
if !message_context.sender_can_manage_global_config()? {
bot.messaging()
.send_error_markdown_no_fail(
message_context.room(),
strings::global_config::no_permissions_to_administrate(),
MessageResponseType::Reply(
message_context.thread_info().root_event_id.clone(),
),
)
.await;
return Ok(());
}
}
};
match handler {
AccessControllerType::Help => super::help::handle(bot, message_context).await,
AccessControllerType::GetUsers => super::users::handle_get(bot, message_context).await,
AccessControllerType::SetUsers(patterns) => {
super::users::handle_set(bot, message_context, patterns).await
}
AccessControllerType::GetRoomLocalAgentManagers => {
super::room_local_agent_managers::handle_get(bot, message_context).await
}
AccessControllerType::SetRoomLocalAgentManagers(patterns) => {
super::room_local_agent_managers::handle_set(bot, message_context, patterns).await
}
}
}

View File

@@ -0,0 +1,183 @@
use mxlink::MessageResponseType;
use crate::{entity::MessageContext, strings, Bot};
pub async fn handle(bot: &Bot, message_context: &MessageContext) -> anyhow::Result<()> {
let mut message = String::new();
message.push_str(&build_section_intro());
message.push_str("\n\n");
message.push_str(&build_section_joining_rooms());
message.push_str("\n\n");
message.push_str(&build_section_users(
bot.command_prefix(),
bot.homeserver_name(),
message_context,
));
message.push_str("\n\n");
message.push_str(&build_section_administrators(bot.admin_patterns()));
message.push_str("\n\n");
message.push_str(&build_section_room_local_agent_managers(
bot.command_prefix(),
bot.homeserver_name(),
message_context,
));
bot.messaging()
.send_text_markdown_no_fail(
message_context.room(),
message,
MessageResponseType::Reply(message_context.thread_info().root_event_id.clone()),
)
.await;
Ok(())
}
fn build_section_intro() -> String {
let mut message = String::new();
message.push_str(&format!("## {}", strings::help::access::heading()));
message.push_str("\n\n");
message.push_str(&strings::help::access::intro());
message
}
fn build_section_joining_rooms() -> String {
let mut message = String::new();
message.push_str(&format!(
"### {}",
strings::help::access::room_auto_join_heading()
));
message.push_str("\n\n");
message.push_str(&strings::help::access::room_auto_join_intro());
message.push_str("\n\n");
message
}
fn build_section_users(
command_prefix: &str,
homeserver_name: &str,
message_context: &MessageContext,
) -> String {
let mut message = String::new();
message.push_str(&format!("### {}", strings::help::access::users_heading()));
message.push_str("\n\n");
message.push_str(&strings::help::access::users_intro());
message.push('\n');
message.push_str(&strings::help::access::users_access());
message.push_str("\n\n");
if let Some(user_patterns) = &message_context.global_config().access.user_patterns {
if user_patterns.is_empty() {
message.push_str(&strings::access::users_no_patterns());
} else {
message.push_str(&strings::access::users_now_match_patterns(user_patterns));
}
} else {
message.push_str(&strings::access::users_no_patterns());
}
let can_manage_global_config = message_context.sender_can_manage_global_config();
if let Ok(can_manage_global_config) = can_manage_global_config {
if can_manage_global_config {
message.push_str("\n\n");
message.push_str(strings::the_following_commands_are_available());
message.push('\n');
message.push_str(&strings::help::access::users_command_get(command_prefix));
message.push('\n');
message.push_str(&strings::help::access::users_command_set(command_prefix));
message.push_str("\n\n");
message.push_str(&strings::help::access::example_user_patterns(
homeserver_name,
));
}
}
message
}
fn build_section_administrators(admin_patterns: &[String]) -> String {
let mut message = String::new();
message.push_str(&format!(
"### {}",
strings::help::access::administrators_heading()
));
message.push_str("\n\n");
message.push_str(&strings::help::access::administrators_intro());
message.push_str("\n\n");
message.push_str(&strings::help::access::administrators_now_match_patterns(
admin_patterns,
));
message.push_str("\n\n");
message.push_str(&strings::help::access::administrators_outro());
message
}
fn build_section_room_local_agent_managers(
command_prefix: &str,
homeserver_name: &str,
message_context: &MessageContext,
) -> String {
let mut message = String::new();
message.push_str(&format!(
"### {}",
strings::help::access::room_local_agent_managers_heading()
));
message.push_str("\n\n");
message.push_str(&strings::help::access::room_local_agent_managers_intro(
command_prefix,
));
message.push('\n');
message.push_str(&strings::help::access::room_local_agent_managers_security_warning());
message.push_str("\n\n");
if let Some(user_patterns) = &message_context
.global_config()
.access
.room_local_agent_manager_patterns
{
if user_patterns.is_empty() {
message.push_str(&strings::access::room_local_agent_managers_no_patterns());
} else {
message.push_str(
&strings::access::room_local_agent_managers_now_match_patterns(user_patterns),
);
}
} else {
message.push_str(&strings::access::room_local_agent_managers_no_patterns());
}
let can_manage_global_config = message_context.sender_can_manage_global_config();
if let Ok(can_manage_global_config) = can_manage_global_config {
if can_manage_global_config {
message.push_str("\n\n");
message.push_str(strings::the_following_commands_are_available());
message.push('\n');
message.push_str(
&strings::help::access::room_local_agent_managers_command_get(command_prefix),
);
message.push('\n');
message.push_str(
&strings::help::access::room_local_agent_managers_command_set(command_prefix),
);
message.push_str("\n\n");
message.push_str(&strings::help::access::example_user_patterns(
homeserver_name,
));
}
}
message
}

View File

@@ -0,0 +1,8 @@
mod determination;
mod dispatching;
pub mod help;
mod room_local_agent_managers;
mod users;
pub use determination::{determine_controller, AccessControllerType};
pub use dispatching::dispatch_controller;

View File

@@ -0,0 +1,67 @@
use mxlink::MessageResponseType;
use crate::{entity::MessageContext, strings, Bot};
pub async fn handle_get(bot: &Bot, message_context: &MessageContext) -> anyhow::Result<()> {
let message = match &message_context
.global_config()
.access
.room_local_agent_manager_patterns
{
Some(patterns) => strings::access::room_local_agent_managers_now_match_patterns(patterns),
None => strings::access::room_local_agent_managers_no_patterns(),
};
bot.messaging()
.send_text_markdown_no_fail(
message_context.room(),
message,
MessageResponseType::Reply(message_context.thread_info().root_event_id.clone()),
)
.await;
Ok(())
}
pub async fn handle_set(
bot: &Bot,
message_context: &MessageContext,
patterns: &Option<Vec<String>>,
) -> anyhow::Result<()> {
if let Some(patterns) = patterns {
if let Err(err) = mxidwc::parse_patterns_vector(patterns) {
bot.messaging()
.send_error_markdown_no_fail(
message_context.room(),
&strings::access::failed_to_parse_patterns(&err.to_string()),
MessageResponseType::Reply(message_context.thread_info().root_event_id.clone()),
)
.await;
return Ok(());
}
}
let mut global_config_manager_guard = bot.global_config_manager().lock().await;
let mut global_config = global_config_manager_guard.get_or_create().await?;
global_config.access.room_local_agent_manager_patterns = patterns.clone();
global_config_manager_guard.persist(&global_config).await?;
let message = match patterns {
Some(patterns) => strings::access::room_local_agent_managers_now_match_patterns(patterns),
None => strings::access::room_local_agent_managers_no_patterns(),
};
bot.messaging()
.send_success_markdown_no_fail(
message_context.room(),
&message,
MessageResponseType::Reply(message_context.thread_info().root_event_id.clone()),
)
.await;
Ok(())
}

View File

@@ -0,0 +1,63 @@
use mxlink::MessageResponseType;
use crate::{entity::MessageContext, strings, Bot};
pub async fn handle_get(bot: &Bot, message_context: &MessageContext) -> anyhow::Result<()> {
let message = match &message_context.global_config().access.user_patterns {
Some(patterns) => strings::access::users_now_match_patterns(patterns),
None => strings::access::users_no_patterns(),
};
bot.messaging()
.send_text_markdown_no_fail(
message_context.room(),
message,
MessageResponseType::Reply(message_context.thread_info().root_event_id.clone()),
)
.await;
Ok(())
}
pub async fn handle_set(
bot: &Bot,
message_context: &MessageContext,
patterns: &Option<Vec<String>>,
) -> anyhow::Result<()> {
if let Some(patterns) = patterns {
if let Err(err) = mxidwc::parse_patterns_vector(patterns) {
bot.messaging()
.send_error_markdown_no_fail(
message_context.room(),
&strings::access::failed_to_parse_patterns(&err.to_string()),
MessageResponseType::Reply(message_context.thread_info().root_event_id.clone()),
)
.await;
return Ok(());
}
}
let mut global_config_manager_guard = bot.global_config_manager().lock().await;
let mut global_config = global_config_manager_guard.get_or_create().await?;
global_config.access.user_patterns = patterns.clone();
global_config_manager_guard.persist(&global_config).await?;
let message = match patterns {
Some(patterns) => strings::access::users_now_match_patterns(patterns),
None => strings::access::users_no_patterns(),
};
bot.messaging()
.send_success_markdown_no_fail(
message_context.room(),
&message,
MessageResponseType::Reply(message_context.thread_info().root_event_id.clone()),
)
.await;
Ok(())
}