2024-09-12 13:44:06 +03:00
|
|
|
use mxlink::matrix_sdk::{
|
2025-02-27 07:46:16 +02:00
|
|
|
Room,
|
2024-09-12 13:44:06 +03:00
|
|
|
ruma::{
|
2025-02-27 07:46:16 +02:00
|
|
|
OwnedEventId, api::client::receipt::create_receipt::v3::ReceiptType,
|
|
|
|
|
events::room::message::OriginalSyncRoomMessageEvent,
|
2024-09-12 13:44:06 +03:00
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
use mxlink::{CallbackError, MessageResponseType};
|
|
|
|
|
|
|
|
|
|
use tracing::Instrument;
|
|
|
|
|
|
|
|
|
|
use crate::{
|
2024-10-01 21:06:54 +03:00
|
|
|
conversation::matrix::determine_interaction_context_for_room_event,
|
2024-09-12 13:44:06 +03:00
|
|
|
entity::{MessageContext, MessagePayload, RoomConfigContext, TriggerEventInfo},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub struct Messaging {
|
|
|
|
|
bot: super::Bot,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Messaging {
|
|
|
|
|
pub fn new(bot: super::Bot) -> Self {
|
|
|
|
|
Self { bot }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn send_text_markdown_no_fail(
|
|
|
|
|
&self,
|
|
|
|
|
room: &Room,
|
|
|
|
|
message: String,
|
|
|
|
|
response_type: MessageResponseType,
|
|
|
|
|
) -> Option<mxlink::matrix_sdk::ruma::api::client::message::send_message_event::v3::Response>
|
|
|
|
|
{
|
|
|
|
|
let result = self
|
|
|
|
|
.bot
|
|
|
|
|
.matrix_link()
|
|
|
|
|
.messaging()
|
|
|
|
|
.send_text_markdown(room, message, response_type)
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
match result {
|
|
|
|
|
Ok(result) => Some(result),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
tracing::error!(
|
|
|
|
|
room_id = format!("{:?}", room.room_id()),
|
|
|
|
|
?err,
|
|
|
|
|
"Failed to send text message to room",
|
|
|
|
|
);
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn send_notice_markdown_no_fail(
|
|
|
|
|
&self,
|
|
|
|
|
room: &Room,
|
|
|
|
|
message: String,
|
|
|
|
|
response_type: MessageResponseType,
|
|
|
|
|
) -> Option<mxlink::matrix_sdk::ruma::api::client::message::send_message_event::v3::Response>
|
|
|
|
|
{
|
|
|
|
|
let result = self
|
|
|
|
|
.bot
|
|
|
|
|
.matrix_link()
|
|
|
|
|
.messaging()
|
|
|
|
|
.send_notice_markdown(room, message, response_type)
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
match result {
|
|
|
|
|
Ok(result) => Some(result),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
tracing::error!(
|
|
|
|
|
room_id = format!("{:?}", room.room_id()),
|
|
|
|
|
?err,
|
|
|
|
|
"Failed to send notice message to room",
|
|
|
|
|
);
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn send_tooltip_markdown_no_fail(
|
|
|
|
|
&self,
|
|
|
|
|
room: &Room,
|
|
|
|
|
message: &str,
|
|
|
|
|
response_type: MessageResponseType,
|
|
|
|
|
) -> Option<mxlink::matrix_sdk::ruma::api::client::message::send_message_event::v3::Response>
|
|
|
|
|
{
|
|
|
|
|
self.send_notice_markdown_no_fail(
|
|
|
|
|
room,
|
|
|
|
|
crate::utils::status::create_tooltip_message_text(message),
|
|
|
|
|
response_type,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn send_success_markdown_no_fail(
|
|
|
|
|
&self,
|
|
|
|
|
room: &Room,
|
|
|
|
|
message: &str,
|
|
|
|
|
response_type: MessageResponseType,
|
|
|
|
|
) -> Option<mxlink::matrix_sdk::ruma::api::client::message::send_message_event::v3::Response>
|
|
|
|
|
{
|
|
|
|
|
self.send_notice_markdown_no_fail(
|
|
|
|
|
room,
|
|
|
|
|
crate::utils::status::create_success_message_text(message),
|
|
|
|
|
response_type,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn send_error_markdown_no_fail(
|
|
|
|
|
&self,
|
|
|
|
|
room: &Room,
|
|
|
|
|
err: &str,
|
|
|
|
|
response_type: MessageResponseType,
|
|
|
|
|
) -> Option<mxlink::matrix_sdk::ruma::api::client::message::send_message_event::v3::Response>
|
|
|
|
|
{
|
|
|
|
|
self.send_notice_markdown_no_fail(
|
|
|
|
|
room,
|
|
|
|
|
crate::utils::status::create_error_message_text(err),
|
|
|
|
|
response_type,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn redact_event_no_fail(
|
|
|
|
|
&self,
|
|
|
|
|
room: &Room,
|
|
|
|
|
target_event_id: OwnedEventId,
|
|
|
|
|
reason: Option<String>,
|
|
|
|
|
) -> Option<mxlink::matrix_sdk::ruma::api::client::redact::redact_event::v3::Response> {
|
|
|
|
|
let result = self
|
|
|
|
|
.bot
|
|
|
|
|
.matrix_link()
|
|
|
|
|
.messaging()
|
|
|
|
|
.redact_event(room, target_event_id.clone(), reason)
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
match result {
|
|
|
|
|
Ok(result) => Some(result),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
tracing::error!(
|
|
|
|
|
room_id = format!("{:?}", room.room_id()),
|
|
|
|
|
?target_event_id,
|
|
|
|
|
?err,
|
|
|
|
|
"Failed to send redaction to room",
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(super) async fn attach_event_handlers(&self) {
|
|
|
|
|
let matrix_link_messaging = self.bot.matrix_link().messaging();
|
|
|
|
|
|
|
|
|
|
let this = self.clone();
|
|
|
|
|
matrix_link_messaging.on_actionable_room_message(|event, room| async move {
|
|
|
|
|
this.on_actionable_message(event, room).await
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tracing::instrument(name = "bot_on_actionable_message", skip_all, fields(room_id = room.room_id().as_str(), event_id = event.event_id.as_str()))]
|
|
|
|
|
async fn on_actionable_message(
|
|
|
|
|
&self,
|
|
|
|
|
event: OriginalSyncRoomMessageEvent,
|
|
|
|
|
room: Room,
|
|
|
|
|
) -> Result<(), CallbackError> {
|
|
|
|
|
if self
|
|
|
|
|
.bot
|
|
|
|
|
.is_caught_up(event.origin_server_ts)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| {
|
|
|
|
|
CallbackError::Unknown(
|
|
|
|
|
format!("Failed to determine catch-up state: {:?}", e).into(),
|
|
|
|
|
)
|
|
|
|
|
})?
|
|
|
|
|
{
|
|
|
|
|
tracing::debug!(
|
|
|
|
|
event_origin_server_ts = format!("{:?}", event.origin_server_ts),
|
|
|
|
|
"Ignoring old message event",
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tracing::info!("Processing message");
|
|
|
|
|
|
|
|
|
|
let global_config = self
|
|
|
|
|
.bot
|
|
|
|
|
.global_config()
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|err| CallbackError::Unknown(err.into()))?;
|
|
|
|
|
|
|
|
|
|
tracing::trace!(?global_config, "Global config");
|
|
|
|
|
|
|
|
|
|
let room_config = self
|
|
|
|
|
.bot
|
|
|
|
|
.room_config_manager()
|
|
|
|
|
.lock()
|
|
|
|
|
.await
|
|
|
|
|
.get_or_create_for_room(&room)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|err| CallbackError::Unknown(err.into()))?;
|
|
|
|
|
|
|
|
|
|
tracing::trace!(?room_config, "Room config");
|
|
|
|
|
|
|
|
|
|
let trigger_event_sender_is_admin = mxidwc::match_user_id(
|
|
|
|
|
event.sender.clone().as_str(),
|
|
|
|
|
self.bot.admin_pattern_regexes(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let trigger_event_sender_is_allowed_user = match &global_config.access.user_patterns {
|
|
|
|
|
Some(user_patterns) => {
|
|
|
|
|
let allowed_user_regexes = mxidwc::parse_patterns_vector(user_patterns)
|
|
|
|
|
.map_err(|err| CallbackError::Unknown(err.into()))?;
|
|
|
|
|
|
|
|
|
|
mxidwc::match_user_id(event.sender.clone().as_str(), &allowed_user_regexes)
|
|
|
|
|
}
|
|
|
|
|
None => false,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if !trigger_event_sender_is_admin && !trigger_event_sender_is_allowed_user {
|
|
|
|
|
tracing::debug!("Ignoring message from non-admin/non-allowed user");
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let payload: Result<MessagePayload, String> = event.content.msgtype.clone().try_into();
|
|
|
|
|
let payload = match payload {
|
|
|
|
|
Ok(payload) => payload,
|
|
|
|
|
Err(err) => {
|
|
|
|
|
tracing::debug!(
|
|
|
|
|
msg_type = event.content.msgtype(),
|
|
|
|
|
?err,
|
|
|
|
|
"Ignoring message not supported by us",
|
|
|
|
|
);
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-10-03 16:28:49 +03:00
|
|
|
let bot_display_name = self.bot.user_display_name_in_room(&room).await;
|
|
|
|
|
|
2024-10-01 21:06:54 +03:00
|
|
|
let interaction_context = determine_interaction_context_for_room_event(
|
2024-09-12 13:44:06 +03:00
|
|
|
self.bot.user_id(),
|
2024-10-03 16:28:49 +03:00
|
|
|
&bot_display_name,
|
2024-09-12 13:44:06 +03:00
|
|
|
&room,
|
|
|
|
|
&event,
|
|
|
|
|
&payload,
|
|
|
|
|
&self.bot.room_event_fetcher(),
|
|
|
|
|
)
|
|
|
|
|
.await;
|
|
|
|
|
|
2024-10-01 21:06:54 +03:00
|
|
|
let interaction_context = match interaction_context {
|
2024-09-12 13:44:06 +03:00
|
|
|
Ok(value) => value,
|
|
|
|
|
Err(err) => {
|
2024-10-01 21:06:54 +03:00
|
|
|
tracing::error!(?err, "Failed to determine interaction context for event");
|
2024-09-12 13:44:06 +03:00
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-10-01 21:06:54 +03:00
|
|
|
let Some(interaction_context) = interaction_context else {
|
|
|
|
|
tracing::debug!(
|
|
|
|
|
"Ignoring message with unknown interaction context (likely not a message for us)"
|
|
|
|
|
);
|
2024-09-12 13:44:06 +03:00
|
|
|
return Ok(());
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let room_config_context =
|
|
|
|
|
RoomConfigContext::new(global_config.clone(), room_config.clone());
|
|
|
|
|
|
|
|
|
|
let trigger_event_info = TriggerEventInfo::new(
|
|
|
|
|
event.event_id.clone(),
|
|
|
|
|
event.sender.clone(),
|
|
|
|
|
payload,
|
|
|
|
|
trigger_event_sender_is_admin,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let message_context = MessageContext::new(
|
|
|
|
|
room.clone(),
|
|
|
|
|
room_config_context,
|
|
|
|
|
self.bot.admin_pattern_regexes().clone(),
|
|
|
|
|
trigger_event_info,
|
2024-10-01 21:06:54 +03:00
|
|
|
interaction_context.thread_info.clone(),
|
2024-10-03 16:28:49 +03:00
|
|
|
)
|
|
|
|
|
.with_bot_display_name(bot_display_name);
|
2024-09-12 13:44:06 +03:00
|
|
|
|
|
|
|
|
let controller_type = crate::controller::determine_controller(
|
|
|
|
|
self.bot.command_prefix(),
|
2024-10-01 21:06:54 +03:00
|
|
|
&interaction_context.trigger,
|
2024-09-12 13:44:06 +03:00
|
|
|
&message_context,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
tracing::info!(?controller_type, "Determined controller");
|
|
|
|
|
|
|
|
|
|
let _ = room
|
|
|
|
|
.send_single_receipt(
|
|
|
|
|
ReceiptType::Read,
|
2024-10-01 21:06:54 +03:00
|
|
|
interaction_context.thread_info.clone().into(),
|
2024-09-12 13:44:06 +03:00
|
|
|
event.event_id.clone(),
|
|
|
|
|
)
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
let start_time = std::time::Instant::now();
|
|
|
|
|
|
|
|
|
|
let event_span = tracing::error_span!("message_controller", ?controller_type);
|
|
|
|
|
|
|
|
|
|
crate::controller::dispatch_controller(&controller_type, &message_context, &self.bot)
|
|
|
|
|
.instrument(event_span)
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
let duration = std::time::Instant::now().duration_since(start_time);
|
|
|
|
|
|
|
|
|
|
tracing::debug!(?duration, "Controller finished");
|
|
|
|
|
|
|
|
|
|
self.bot.catch_up(event.origin_server_ts).await;
|
|
|
|
|
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
}
|