支持渠道、指令(自定义前缀)和事件(启用/禁用)管理

This commit is contained in:
xintaofei
2026-03-31 11:49:24 +08:00
parent 54bab306e1
commit edc12a0e39
24 changed files with 1777 additions and 330 deletions

View File

@@ -5,7 +5,10 @@ use tokio::task::JoinHandle;
use super::command_handlers;
use super::manager::ChatChannelManager;
use super::types::IncomingCommand;
use crate::db::service::chat_channel_message_log_service;
use crate::db::service::{app_metadata_service, chat_channel_message_log_service};
const COMMAND_PREFIX_KEY: &str = "chat_command_prefix";
const DEFAULT_COMMAND_PREFIX: &str = "/";
pub fn spawn_command_dispatcher(
mut command_rx: mpsc::Receiver<IncomingCommand>,
@@ -28,7 +31,13 @@ pub fn spawn_command_dispatcher(
)
.await;
let response = dispatch_command(text, &db_conn, &manager).await;
let prefix = app_metadata_service::get_value(&db_conn, COMMAND_PREFIX_KEY)
.await
.ok()
.flatten()
.unwrap_or_else(|| DEFAULT_COMMAND_PREFIX.to_string());
let response = dispatch_command(text, &prefix, &db_conn, &manager).await;
// Send response back via the same channel
let send_result = manager.send_to_channel(cmd.channel_id, &response).await;
@@ -59,45 +68,47 @@ pub fn spawn_command_dispatcher(
async fn dispatch_command(
text: &str,
prefix: &str,
db: &DatabaseConnection,
manager: &ChatChannelManager,
) -> super::types::RichMessage {
let parts: Vec<&str> = text.splitn(2, ' ').collect();
// Check if text starts with the configured prefix
if !text.starts_with(prefix) {
return command_handlers::handle_help(prefix);
}
// Strip prefix and parse command + args
let without_prefix = &text[prefix.len()..];
let parts: Vec<&str> = without_prefix.splitn(2, ' ').collect();
let command = parts[0].to_lowercase();
let args = parts.get(1).map(|s| s.trim()).unwrap_or("");
match command.as_str() {
"/recent" => command_handlers::handle_recent(db).await,
"/search" => {
"recent" => command_handlers::handle_recent(db).await,
"search" => {
if args.is_empty() {
super::types::RichMessage::info("用法: /search <关键词>")
super::types::RichMessage::info(format!("用法: {prefix}search <关键词>"))
.with_title("参数错误")
} else {
command_handlers::handle_search(db, args).await
}
}
"/detail" => {
"detail" => {
if let Ok(id) = args.parse::<i32>() {
command_handlers::handle_detail(db, id).await
} else {
super::types::RichMessage::info("用法: /detail <会话ID>")
super::types::RichMessage::info(format!("用法: {prefix}detail <会话ID>"))
.with_title("参数错误")
}
}
"/today" => command_handlers::handle_today(db).await,
"/status" => command_handlers::handle_status(manager).await,
"/help" | "/start" => command_handlers::handle_help(),
"today" => command_handlers::handle_today(db).await,
"status" => command_handlers::handle_status(manager).await,
"help" | "start" => command_handlers::handle_help(prefix),
_ => {
if text.starts_with('/') {
super::types::RichMessage::info(format!(
"未知命令: {}\n输入 /help 查看可用命令",
command
))
.with_title("未知命令")
} else {
// Non-command messages are ignored
return command_handlers::handle_help();
}
super::types::RichMessage::info(format!(
"未知命令: {prefix}{command}\n输入 {prefix}help 查看可用命令",
))
.with_title("未知命令")
}
}
}

View File

@@ -205,14 +205,14 @@ pub async fn handle_status(manager: &ChatChannelManager) -> RichMessage {
RichMessage::info(body.trim_end()).with_title("渠道状态")
}
pub fn handle_help() -> RichMessage {
RichMessage::info(
"/recent - 最近 5 条会话\n\
/search <关键词> - 搜索会话\n\
/detail <ID> - 会话详情\n\
/today - 今日活动汇总\n\
/status - 渠道连接状态\n\
/help - 显示帮助",
)
pub fn handle_help(prefix: &str) -> RichMessage {
RichMessage::info(format!(
"{prefix}recent - 最近 5 条会话\n\
{prefix}search <关键词> - 搜索会话\n\
{prefix}detail <ID> - 会话详情\n\
{prefix}today - 今日活动汇总\n\
{prefix}status - 渠道连接状态\n\
{prefix}help - 显示帮助",
))
.with_title("Codeg Bot 帮助")
}

View File

@@ -8,7 +8,7 @@ use tokio::task::JoinHandle;
use super::manager::ChatChannelManager;
use super::message_formatter;
use super::types::RichMessage;
use crate::db::service::{chat_channel_message_log_service, chat_channel_service};
use crate::db::service::{app_metadata_service, chat_channel_message_log_service, chat_channel_service};
use crate::web::event_bridge::WebEventBroadcaster;
/// Minimum interval between pushes for the same event type per channel (debounce).
@@ -38,6 +38,19 @@ pub fn spawn_event_subscriber(
let message = match parse_event(&event.channel, &event.payload) {
Some((event_type, msg)) => {
// Global event filter check
let global_filter = app_metadata_service::get_value(&db_conn, "chat_event_filter")
.await
.ok()
.flatten()
.and_then(|json| serde_json::from_str::<Vec<String>>(&json).ok());
if let Some(filter) = &global_filter {
if !filter.contains(&event_type) {
continue;
}
}
// Check enabled channels and forward
let channels = match chat_channel_service::list_enabled(&db_conn).await {
Ok(c) => c,
@@ -48,17 +61,6 @@ pub fn spawn_event_subscriber(
};
for ch in &channels {
// Check event filter
if let Some(filter_json) = &ch.event_filter_json {
if let Ok(filter) =
serde_json::from_str::<Vec<String>>(filter_json)
{
if !filter.contains(&event_type) {
continue;
}
}
}
// Debounce
let key = (ch.id, event_type.clone());
let now = Instant::now();