tailchat p3

This commit is contained in:
shenjack 2024-03-30 14:24:19 +08:00
parent 2f535cc960
commit b3e2da9df6
Signed by: shenjack
GPG Key ID: 7B1134A979775551
8 changed files with 138 additions and 52 deletions

View File

@ -2,16 +2,30 @@
from typing import Callable, Tuple from typing import Callable, Tuple
""" """
ica.rs
pub type RoomId = i64; pub type RoomId = i64;
pub type UserId = i64; pub type UserId = i64;
pub type MessageId = String; pub type MessageId = String;
""" """
class IcaType:
RoomId = int RoomId = int
UserId = int UserId = int
MessageId = str MessageId = str
"""
tailchat.rs
pub type GroupId = String;
pub type ConverseId = String;
pub type UserId = String;
pub type MessageId = String;
"""
class TailchatType:
GroupId = str
ConverseId = str
UserId = str
MessageId = str
class IcaStatus: class IcaStatus:
""" """
@ -25,7 +39,7 @@ class IcaStatus:
def online(self) -> bool: def online(self) -> bool:
... ...
@property @property
def self_id(self) -> UserId: def self_id(self) -> IcaType.UserId:
... ...
@property @property
def nick_name(self) -> str: def nick_name(self) -> str:
@ -82,13 +96,13 @@ class IcaNewMessage:
def __str__(self) -> str: def __str__(self) -> str:
... ...
@property @property
def id(self) -> MessageId: def id(self) -> IcaType.MessageId:
... ...
@property @property
def content(self) -> str: def content(self) -> str:
... ...
@property @property
def sender_id(self) -> UserId: def sender_id(self) -> IcaType.UserId:
... ...
@property @property
def is_from_self(self) -> bool: def is_from_self(self) -> bool:
@ -103,7 +117,7 @@ class IcaNewMessage:
def is_chat_msg(self) -> bool: def is_chat_msg(self) -> bool:
"""是否是私聊消息""" """是否是私聊消息"""
@property @property
def room_id(self) -> RoomId: def room_id(self) -> IcaType.RoomId:
""" """
如果是群聊消息, 返回 (-群号) 如果是群聊消息, 返回 (-群号)
如果是私聊消息, 返回 对面qq 如果是私聊消息, 返回 对面qq
@ -148,11 +162,18 @@ class IcaClient:
"""向日志中输出警告信息""" """向日志中输出警告信息"""
class MatrixClient: class TailchatClient:
""" """
Matrix 的客户端 Tailchat 的客户端
""" """
def debug(self, message: str) -> None:
"""向日志中输出调试信息"""
def info(self, message: str) -> None:
"""向日志中输出信息"""
def warn(self, message: str) -> None:
"""向日志中输出警告信息"""
class ConfigData: class ConfigData:
def __getitem__(self, key: str): def __getitem__(self, key: str):
@ -169,12 +190,12 @@ on_ica_message = Callable[[IcaNewMessage, IcaClient], None]
# def on_message(msg: NewMessage, client: IcaClient) -> None: # def on_message(msg: NewMessage, client: IcaClient) -> None:
# ... # ...
on_ica_delete_message = Callable[[MessageId, IcaClient], None] on_ica_delete_message = Callable[[IcaType.MessageId, IcaClient], None]
# def on_delete_message(msg_id: MessageId, client: IcaClient) -> None: # def on_delete_message(msg_id: MessageId, client: IcaClient) -> None:
# ... # ...
# TODO: Matrix adapter # TODO: Tailchat adapter
on_matrix_message = Callable[[], None] on_tailchat_message = Callable[[], None]
on_config = Callable[[None], Tuple[str, str]] on_config = Callable[[None], Tuple[str, str]]

View File

@ -5,6 +5,8 @@ use serde::Deserialize;
use toml::from_str; use toml::from_str;
use tracing::warn; use tracing::warn;
use crate::data_struct::{ica, tailchat};
/// Icalingua bot 的配置 /// Icalingua bot 的配置
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
pub struct IcaConfig { pub struct IcaConfig {
@ -13,15 +15,33 @@ pub struct IcaConfig {
/// icalingua 服务器地址 /// icalingua 服务器地址
pub host: String, pub host: String,
/// bot 的 qq /// bot 的 qq
pub self_id: u64, pub self_id: ica::UserId,
/// 提醒的房间 /// 提醒的房间
pub notice_room: Vec<i64>, pub notice_room: Vec<ica::RoomId>,
/// 是否提醒 /// 是否提醒
pub notice_start: bool, pub notice_start: bool,
/// 管理员列表 /// 管理员列表
pub admin_list: Vec<i64>, pub admin_list: Vec<ica::UserId>,
/// 过滤列表 /// 过滤列表
pub filter_list: Vec<i64>, pub filter_list: Vec<ica::UserId>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct TailchatConfig {
/// 服务器地址
pub host: String,
/// 机器人 App ID
pub app_id: String,
/// 机器人 App Secret
pub app_secret: String,
/// 提醒的房间
pub notice_room: Vec<(tailchat::GroupId, tailchat::ConverseId)>,
/// 是否提醒
pub notice_start: bool,
/// 管理员列表
pub admin_list: Vec<tailchat::UserId>,
/// 过滤列表
pub filter_list: Vec<tailchat::UserId>,
} }
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
@ -40,6 +60,11 @@ pub struct BotConfig {
/// Ica 配置 /// Ica 配置
pub ica: Option<IcaConfig>, pub ica: Option<IcaConfig>,
/// 是否启用 Tailchat
pub enable_tailchat: Option<bool>,
/// Tailchat 配置
pub tailchat: Option<TailchatConfig>,
/// 是否启用 Python 插件 /// 是否启用 Python 插件
pub enable_py: Option<bool>, pub enable_py: Option<bool>,
/// Python 插件配置 /// Python 插件配置

View File

@ -12,6 +12,8 @@ pub enum IcaError {
pub enum TailchatError { pub enum TailchatError {
/// Socket IO 链接错误 /// Socket IO 链接错误
SocketIoError(rust_socketio::error::Error), SocketIoError(rust_socketio::error::Error),
/// reqwest 相关错误
ReqwestError(reqwest::Error),
/// 登录失败 /// 登录失败
LoginFailed(String), LoginFailed(String),
} }
@ -35,6 +37,14 @@ impl From<rust_socketio::Error> for IcaError {
fn from(e: rust_socketio::Error) -> Self { IcaError::SocketIoError(e) } fn from(e: rust_socketio::Error) -> Self { IcaError::SocketIoError(e) }
} }
impl From<rust_socketio::Error> for TailchatError {
fn from(e: rust_socketio::Error) -> Self { TailchatError::SocketIoError(e) }
}
impl From<reqwest::Error> for TailchatError {
fn from(e: reqwest::Error) -> Self { TailchatError::ReqwestError(e) }
}
impl std::fmt::Display for IcaError { impl std::fmt::Display for IcaError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
@ -48,6 +58,7 @@ impl std::fmt::Display for TailchatError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
TailchatError::SocketIoError(e) => write!(f, "Socket IO 链接错误: {}", e), TailchatError::SocketIoError(e) => write!(f, "Socket IO 链接错误: {}", e),
TailchatError::ReqwestError(e) => write!(f, "Reqwest 错误: {}", e),
TailchatError::LoginFailed(e) => write!(f, "登录失败: {}", e), TailchatError::LoginFailed(e) => write!(f, "登录失败: {}", e),
} }
} }
@ -85,6 +96,7 @@ impl std::error::Error for TailchatError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self { match self {
TailchatError::SocketIoError(e) => Some(e), TailchatError::SocketIoError(e) => Some(e),
TailchatError::ReqwestError(e) => Some(e),
TailchatError::LoginFailed(_) => None, TailchatError::LoginFailed(_) => None,
} }
} }

View File

@ -96,8 +96,21 @@ pub const ICA_DELETE_MESSAGE_FUNC: &str = "on_ica_delete_message";
pub const TAILCHAT_NEW_MESSAGE_FUNC: &str = "on_tailchat_message"; pub const TAILCHAT_NEW_MESSAGE_FUNC: &str = "on_tailchat_message";
macro_rules! call_py_func { macro_rules! call_py_func {
($args:expr, $func_name:expr, $client:expr) => { ($args:expr, $plugin:expr, $plugin_path:expr, $func_name:expr, $client:expr) => {
tokio::spawn(async move {
Python::with_gil(|py| {
if let Ok(py_func) = get_func($plugin.py_module.bind(py), $func_name) {
if let Err(e) = py_func.call1($args) {
let e = PyPluginError::FuncCallError(
e,
$func_name.to_string(),
$plugin_path.to_string_lossy().to_string(),
);
warn!("failed to call function<{}>: {:?}", $func_name, e);
}
}
})
})
}; };
} }
@ -112,20 +125,21 @@ pub async fn ica_new_message_py(message: &ica::messages::NewMessage, client: &Cl
let client = class::ica::IcaClientPy::new(client); let client = class::ica::IcaClientPy::new(client);
let args = (msg, client); let args = (msg, client);
// 甚至实际上压根不需要await这个spawn, 直接让他自己跑就好了(离谱) // 甚至实际上压根不需要await这个spawn, 直接让他自己跑就好了(离谱)
tokio::spawn(async move { call_py_func!(args, plugin, path, ICA_NEW_MESSAGE_FUNC, client);
Python::with_gil(|py| { // tokio::spawn(async move {
if let Ok(py_func) = get_func(plugin.py_module.bind(py), ICA_NEW_MESSAGE_FUNC) { // Python::with_gil(|py| {
if let Err(e) = py_func.call1(args) { // if let Ok(py_func) = get_func(plugin.py_module.bind(py), ICA_NEW_MESSAGE_FUNC) {
let e = PyPluginError::FuncCallError( // if let Err(e) = py_func.call1(args) {
e, // let e = PyPluginError::FuncCallError(
ICA_NEW_MESSAGE_FUNC.to_string(), // e,
path.to_string_lossy().to_string(), // ICA_NEW_MESSAGE_FUNC.to_string(),
); // path.to_string_lossy().to_string(),
warn!("failed to call function<{}>: {:?}", ICA_NEW_MESSAGE_FUNC, e); // );
} // warn!("failed to call function<{}>: {:?}", ICA_NEW_MESSAGE_FUNC, e);
} // }
}) // }
}); // })
// });
} }
} }
@ -137,23 +151,31 @@ pub async fn ica_delete_message_py(msg_id: ica::MessageId, client: &Client) {
let msg_id = msg_id.clone(); let msg_id = msg_id.clone();
let client = class::ica::IcaClientPy::new(client); let client = class::ica::IcaClientPy::new(client);
let args = (msg_id.clone(), client); let args = (msg_id.clone(), client);
tokio::spawn(async move { call_py_func!(args, plugin, path, ICA_DELETE_MESSAGE_FUNC, client);
Python::with_gil(|py| { // tokio::spawn(async move {
if let Ok(py_func) = get_func(plugin.py_module.bind(py), ICA_DELETE_MESSAGE_FUNC) { // Python::with_gil(|py| {
if let Err(e) = py_func.call1(args) { // if let Ok(py_func) = get_func(plugin.py_module.bind(py), ICA_DELETE_MESSAGE_FUNC) {
let e = PyPluginError::FuncCallError( // if let Err(e) = py_func.call1(args) {
e, // let e = PyPluginError::FuncCallError(
ICA_DELETE_MESSAGE_FUNC.to_string(), // e,
path.to_string_lossy().to_string(), // ICA_DELETE_MESSAGE_FUNC.to_string(),
); // path.to_string_lossy().to_string(),
warn!("failed to call function<{}>: {:?}", ICA_DELETE_MESSAGE_FUNC, e); // );
} // warn!("failed to call function<{}>: {:?}", ICA_DELETE_MESSAGE_FUNC, e);
} // }
}) // }
}); // })
// });
} }
} }
pub async fn tailchat_new_message_py(message: tailchat::messages::ReciveMessage, client: &Client) { pub async fn tailchat_new_message_py(message: tailchat::messages::ReciveMessage, client: &Client) {
verify_plugins();
let plugins = PyStatus::get_files();
for (path, plugin) in plugins.iter() {
// let msg = class::tailchat::
let args = ();
call_py_func!(args, plugin, path, TAILCHAT_NEW_MESSAGE_FUNC, client);
}
} }

View File

@ -96,7 +96,8 @@ impl TryFrom<RawPyPlugin> for PyPlugin {
}; };
match config_value { match config_value {
Ok(config) => { Ok(config) => {
let py_config = Bound::new(py, class::ConfigDataPy::new(config)).unwrap(); let py_config =
Bound::new(py, class::ConfigDataPy::new(config)).unwrap();
module.setattr("CONFIG_DATA", py_config).unwrap(); module.setattr("CONFIG_DATA", py_config).unwrap();
Ok(PyPlugin { Ok(PyPlugin {
file_path: path, file_path: path,

View File

@ -1,6 +1,7 @@
pub mod events; pub mod events;
use futures_util::FutureExt; use futures_util::FutureExt;
use reqwest::ClientBuilder as reqwest_ClientBuilder;
use rust_socketio::asynchronous::{Client, ClientBuilder}; use rust_socketio::asynchronous::{Client, ClientBuilder};
use rust_socketio::{Event, Payload, TransportType}; use rust_socketio::{Event, Payload, TransportType};
use tracing::{event, span, Level}; use tracing::{event, span, Level};
@ -14,10 +15,13 @@ pub async fn start_tailchat() -> ClientResult<(), TailchatError> {
event!(Level::INFO, "tailchat-async-rs v{} initing", crate::TAILCHAT_VERSION); event!(Level::INFO, "tailchat-async-rs v{} initing", crate::TAILCHAT_VERSION);
let tailchat_req = reqwest_ClientBuilder::new().build()?;
// tailchat_req.get("http://localhost:8080").send().await?;
// let socket = match ClientBuilder::new() { // let socket = match ClientBuilder::new() {
// }; // };
Ok(()) Ok(())
} }

View File

@ -0,0 +1 @@