Compare commits

..

No commits in common. "6dfbc4e87992861bda2ffa73abafd9b9d5a7eb4a" and "aa20b7f1c343c0ffd60bb2160a48b319e1100f8d" have entirely different histories.

17 changed files with 1411 additions and 131 deletions

1164
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,39 +1,38 @@
[package] [package]
name = "ica-rs" name = "ica-rs"
version = "0.6.0-dev" version = "0.5.3"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]
default = ["ica", "tailchat"] default = ["ica", "matrix"]
ica = ["dep:ed25519", "dep:ed25519-dalek", "dep:hex", "dep:rust_socketio"] ica = ["dep:ed25519", "dep:ed25519-dalek", "dep:hex", "dep:rust_socketio"]
tailchat = ["dep:rust_socketio", "dep:md-5"] matrix = ["dep:matrix-sdk", "dep:url"]
[dependencies] [dependencies]
# matrix
url = { version = "2.5.0", optional = true }
matrix-sdk = { version = "0.7.1", optional = true, default-features = false, features = ["rustls-tls"] }
# ica # ica
ed25519 = { version = "2.2", optional = true } ed25519 = { version = "2.2.3", optional = true }
ed25519-dalek = { version = "2.1", optional = true } ed25519-dalek = { version = "2.1.1", optional = true }
hex = { version = "0.4", optional = true } hex = { version = "0.4.3", optional = true }
# tailchat
md-5 = { version = "0.10.6", optional = true }
# ica & tailchat (socketio)
rust_socketio = { version = "0.4.4", features = ["async"], optional = true } rust_socketio = { version = "0.4.4", features = ["async"], optional = true }
# data # data
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
chrono = "0.4.37" chrono = "0.4.35"
toml = "0.8.12" toml = "0.8.11"
colored = "2.1.0" colored = "2.1.0"
# runtime # runtime
tokio = { version = "1.37", features = ["full"] } tokio = { version = "1.36", features = ["full"] }
futures-util = "0.3.30" futures-util = "0.3.30"
pyo3 = { version = "0.21.0", features = ["experimental-async"] } pyo3 = "0.20.3"
anyhow = { version = "1.0.81", features = ["backtrace"] } anyhow = { version = "1.0.81", features = ["backtrace"] }
# async 这玩意以后在搞 # async 这玩意以后在搞
# pyo3-async = "0.3.2" # pyo3-async = "0.3.2"

View File

@ -139,6 +139,9 @@ class IcaClient:
@property @property
def ica_version() -> str: def ica_version() -> str:
"""shenbot ica 的版本号""" """shenbot ica 的版本号"""
@property
def matrix_version() -> str:
"""shenbot matrix 的版本号"""
def debug(self, message: str) -> None: def debug(self, message: str) -> None:
"""向日志中输出调试信息""" """向日志中输出调试信息"""

View File

@ -24,6 +24,20 @@ pub struct IcaConfig {
pub filter_list: Vec<i64>, pub filter_list: Vec<i64>,
} }
/// Matrix 配置
#[derive(Debug, Clone, Deserialize)]
pub struct MatrixConfig {
/// home server
pub home_server: String,
/// bot_id
pub bot_id: String,
/// bot password
pub bot_password: String,
/// 提醒的房间
pub notice_room: Vec<String>,
/// 是否提醒
pub notice_start: bool,
}
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
pub struct PyConfig { pub struct PyConfig {
@ -40,7 +54,10 @@ pub struct BotConfig {
pub enable_ica: Option<bool>, pub enable_ica: Option<bool>,
/// Ica 配置 /// Ica 配置
pub ica: Option<IcaConfig>, pub ica: Option<IcaConfig>,
/// 是否启用 Matrix
pub enable_matrix: Option<bool>,
/// Matrix 配置
pub matrix: Option<MatrixConfig>,
/// 是否启用 Python 插件 /// 是否启用 Python 插件
pub enable_py: Option<bool>, pub enable_py: Option<bool>,
/// Python 插件配置 /// Python 插件配置
@ -89,6 +106,26 @@ impl BotConfig {
} }
} }
/// 检查是否启用 Matrix
pub fn check_matrix(&self) -> bool {
match self.enable_matrix {
Some(enable) => {
if enable && self.matrix.is_none() {
warn!("enable_matrix 为 true 但未填写 [matrix] 配置\n将不启用 Matrix");
false
} else {
true
}
}
None => {
if self.matrix.is_some() {
warn!("未填写 enable_matrix 但填写了 [matrix] 配置\n将不启用 Matrix");
}
false
}
}
}
/// 检查是否启用 Python 插件 /// 检查是否启用 Python 插件
pub fn check_py(&self) -> bool { pub fn check_py(&self) -> bool {
match self.enable_py { match self.enable_py {
@ -110,5 +147,6 @@ impl BotConfig {
} }
pub fn ica(&self) -> IcaConfig { self.ica.clone().expect("No ica config found") } pub fn ica(&self) -> IcaConfig { self.ica.clone().expect("No ica config found") }
pub fn matrix(&self) -> MatrixConfig { self.matrix.clone().expect("No matrix config found") }
pub fn py(&self) -> PyConfig { self.py.clone().expect("No py config found") } pub fn py(&self) -> PyConfig { self.py.clone().expect("No py config found") }
} }

View File

@ -11,7 +11,6 @@ pub type RoomId = i64;
pub type UserId = i64; pub type UserId = i64;
pub type MessageId = String; pub type MessageId = String;
#[allow(unused)]
pub trait RoomIdTrait { pub trait RoomIdTrait {
fn is_room(&self) -> bool; fn is_room(&self) -> bool;
fn is_chat(&self) -> bool { !self.is_room() } fn is_chat(&self) -> bool { !self.is_room() }

View File

@ -31,7 +31,6 @@ impl<'de> Deserialize<'de> for At {
} }
} }
#[allow(unused)]
pub trait MessageTrait { pub trait MessageTrait {
fn is_reply(&self) -> bool; fn is_reply(&self) -> bool;
fn is_from_self(&self) -> bool { fn is_from_self(&self) -> bool {

View File

@ -6,10 +6,24 @@ pub enum IcaError {
SocketIoError(rust_socketio::error::Error), SocketIoError(rust_socketio::error::Error),
} }
#[derive(Debug)]
pub enum MatrixError {
/// Homeserver Url 错误
HomeserverUrlError(url::ParseError),
/// Http 请求错误
HttpError(matrix_sdk::HttpError),
/// Matrix Error
MatrixError(matrix_sdk::Error),
}
impl From<rust_socketio::Error> for IcaError { 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<matrix_sdk::Error> for MatrixError {
fn from(e: matrix_sdk::Error) -> Self { MatrixError::MatrixError(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 {
@ -25,3 +39,23 @@ impl std::error::Error for IcaError {
} }
} }
} }
impl std::fmt::Display for MatrixError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MatrixError::HomeserverUrlError(e) => write!(f, "Homeserver Url 错误: {}", e),
MatrixError::HttpError(e) => write!(f, "Http 请求错误: {}", e),
MatrixError::MatrixError(e) => write!(f, "Matrix Error: {}", e),
}
}
}
impl std::error::Error for MatrixError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
MatrixError::HomeserverUrlError(e) => Some(e),
MatrixError::HttpError(e) => Some(e),
MatrixError::MatrixError(e) => Some(e),
}
}
}

View File

@ -7,7 +7,7 @@ use crate::data_struct::ica::all_rooms::Room;
use crate::data_struct::ica::messages::{Message, MessageTrait, NewMessage}; use crate::data_struct::ica::messages::{Message, MessageTrait, NewMessage};
use crate::data_struct::ica::online_data::OnlineData; use crate::data_struct::ica::online_data::OnlineData;
use crate::ica::client::send_message; use crate::ica::client::send_message;
use crate::{py, MainStatus, ICA_VERSION, VERSION}; use crate::{py, MainStatus, ICA_VERSION, MATRIX_VERSION, VERSION};
/// 获取在线数据 /// 获取在线数据
pub async fn get_online_data(payload: Payload, _client: Client) { pub async fn get_online_data(payload: Payload, _client: Client) {
@ -35,8 +35,8 @@ pub async fn add_message(payload: Payload, client: Client) {
// 之后的处理交给插件 // 之后的处理交给插件
if message.content().eq("/bot-rs") && !message.is_from_self() && !message.is_reply() { if message.content().eq("/bot-rs") && !message.is_from_self() && !message.is_reply() {
let reply = message.reply_with(&format!( let reply = message.reply_with(&format!(
"shenbot v{}\nica-async-rs pong v{}", "shenbot v{}\nica-async-rs pong v{}\nmatrix v{}",
VERSION, ICA_VERSION VERSION, ICA_VERSION, MATRIX_VERSION
)); ));
send_message(&client, &reply).await; send_message(&client, &reply).await;
} }
@ -178,7 +178,7 @@ pub async fn connect_callback(payload: Payload, _client: Client) {
event!(Level::INFO, "{}", "需要登录到 icalingua!".yellow()) event!(Level::INFO, "{}", "需要登录到 icalingua!".yellow())
} }
Some(msg) => { Some(msg) => {
event!(Level::INFO, "{}{}", "未知消息".yellow(), msg); event!(Level::INFO, "{}", "未知消息".yellow());
} }
None => (), None => (),
} }

View File

@ -5,8 +5,8 @@ mod data_struct;
mod error; mod error;
#[cfg(feature = "ica")] #[cfg(feature = "ica")]
mod ica; mod ica;
// #[cfg(feature = "tailchat")] #[cfg(feature = "matrix")]
// mod tailchat; mod matrix;
mod py; mod py;
mod status; mod status;
@ -16,6 +16,7 @@ use tracing::{event, info, span, Level};
pub static mut MAIN_STATUS: status::BotStatus = status::BotStatus { pub static mut MAIN_STATUS: status::BotStatus = status::BotStatus {
config: None, config: None,
ica_status: None, ica_status: None,
matrix_status: None,
}; };
pub type MainStatus = status::BotStatus; pub type MainStatus = status::BotStatus;
@ -24,7 +25,7 @@ pub type StopGetter = tokio::sync::oneshot::Receiver<()>;
pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub const ICA_VERSION: &str = "1.4.0"; pub const ICA_VERSION: &str = "1.4.0";
pub const TAILCHAT_VERSION: &str = "0.1.0"; pub const MATRIX_VERSION: &str = "0.1.0";
#[macro_export] #[macro_export]
macro_rules! wrap_callback { macro_rules! wrap_callback {
@ -79,6 +80,19 @@ async fn main() {
event!(Level::INFO, "未启用 ica"); event!(Level::INFO, "未启用 ica");
} }
event!(Level::INFO, "启动 Matrix");
let (matrix_send, matrix_recv) = tokio::sync::oneshot::channel::<()>();
if bot_config.check_matrix() {
event!(Level::INFO, "启动 Matrix");
let config = bot_config.matrix();
tokio::spawn(async move {
matrix::start_matrix(&config, matrix_recv).await.unwrap();
});
} else {
event!(Level::INFO, "未启用 Matrix");
}
tokio::time::sleep(Duration::from_secs(2)).await; tokio::time::sleep(Duration::from_secs(2)).await;
// 等待一个输入 // 等待一个输入
info!("Press any key to exit"); info!("Press any key to exit");
@ -86,6 +100,7 @@ async fn main() {
std::io::stdin().read_line(&mut input).unwrap(); std::io::stdin().read_line(&mut input).unwrap();
ica_send.send(()).ok(); ica_send.send(()).ok();
matrix_send.send(()).ok();
info!("Disconnected"); info!("Disconnected");
} }

140
ica-rs/src/matrix.rs Normal file
View File

@ -0,0 +1,140 @@
pub mod events;
use std::{str::FromStr, time::Duration};
use futures_util::StreamExt;
use matrix_sdk::{
config::SyncSettings,
ruma::{
api::client::message::send_message_event, events::room::message::RoomMessageEventContent,
OwnedRoomId, TransactionId,
},
Client,
};
use tracing::{event, span, Level};
use url::Url;
use crate::config::MatrixConfig;
use crate::error::{ClientResult, MatrixError};
use crate::StopGetter;
pub async fn start_matrix(
config: &MatrixConfig,
mut stop_reciver: StopGetter,
) -> ClientResult<(), MatrixError> {
let span = span!(Level::INFO, "Matrix Client");
let _enter = span.enter();
let homeserver_url = match Url::parse(&config.home_server) {
Ok(url) => url,
Err(e) => {
event!(Level::ERROR, "Homeserver Url 错误: {}", e);
return Err(MatrixError::HomeserverUrlError(e));
}
};
let password = &config.bot_password;
let username = &config.bot_id;
let client = match Client::new(homeserver_url).await {
Ok(client) => {
event!(Level::INFO, "Logged in as {}", username);
client
}
Err(error) => {
event!(Level::ERROR, "Failed to log in as {}: {}", username, error);
return Err(MatrixError::HttpError(error));
}
};
let display_name = format!("shenbot-matrix v{}", crate::MATRIX_VERSION);
match client
.matrix_auth()
.login_username(&username, &password)
.initial_device_display_name(&display_name)
.await
{
Ok(_) => {
event!(Level::INFO, "Logged in as {}", username);
}
Err(error) => {
event!(Level::ERROR, "Failed to log in as {}: {}", username, error);
return Err(MatrixError::MatrixError(error));
}
}
// 发送启动消息
if config.notice_start {
for room in config.notice_room.iter() {
let startup_msg = RoomMessageEventContent::text_plain(format!(
"shenbot v {}\nmatrix-rs v{} started!",
crate::VERSION,
crate::MATRIX_VERSION
));
let startup_req: send_message_event::v3::Request =
send_message_event::v3::Request::new(
OwnedRoomId::from_str(&room).unwrap(),
TransactionId::new(),
&startup_msg,
)
.unwrap();
event!(Level::INFO, "发送启动消息到房间: {}", room);
if let Err(e) = client.send::<send_message_event::v3::Request>(startup_req, None).await
{
event!(Level::INFO, "启动信息发送失败 房间:{}|e:{}", room, e);
}
}
} else {
event!(Level::INFO, "未启用启动消息");
}
client.add_event_handler(events::on_room_message);
let init_sync_setting = SyncSettings::new().timeout(Duration::from_mins(10));
match client.sync_once(init_sync_setting).await {
Ok(_) => {
event!(Level::INFO, "Synced");
}
Err(error) => {
event!(Level::ERROR, "Failed to sync: {}", error);
return Err(MatrixError::MatrixError(error));
}
}
let mut stream_sync =
Box::pin(client.sync_stream(SyncSettings::new().timeout(Duration::from_mins(10))).await);
while let Some(Ok(response)) = stream_sync.next().await {
for room in response.rooms.join.values() {
for e in &room.timeline.events {
if let Ok(event) = e.event.deserialize() {
println!("Received event {:?}", event);
}
}
}
if stop_reciver.try_recv().is_ok() {
event!(Level::INFO, "Matrix client stopping");
break;
}
}
// loop {
// match stop_reciver.try_recv() {
// Ok(_) => {
// event!(Level::INFO, "Matrix client stopping");
// break;
// }
// Err(tokio::sync::oneshot::error::TryRecvError::Empty) => {
// }
// Err(tokio::sync::oneshot::error::TryRecvError::Closed) => {
// event!(Level::INFO, "Matrix client stopping");
// break;
// }
// }
// }
Ok(())
}

View File

@ -0,0 +1,42 @@
use matrix_sdk::{
ruma::events::room::message::{
AddMentions, ForwardThread, MessageType, OriginalSyncRoomMessageEvent,
RoomMessageEventContent,
},
Room, RoomState,
};
use tracing::{event, span, Level};
use crate::py::call::matrix_new_message_py;
pub async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) {
// We only want to listen to joined rooms.
if room.state() != RoomState::Joined {
return;
}
// We only want to log text messages.
let MessageType::Text(msgtype) = &event.content.msgtype else {
return;
};
// 匹配消息
// /bot
if msgtype.body == "/bot" {
let pong = format!("shenbot v {}\nmatrix-rs v{}", crate::VERSION, crate::MATRIX_VERSION);
let reply = RoomMessageEventContent::text_plain(pong);
let reply = reply.make_reply_to(
&event.into_full_event(room.room_id().to_owned()),
ForwardThread::Yes,
AddMentions::No,
);
room.send(reply).await.expect("Failed to send message");
return;
}
// 发给 Python 处理剩下的
matrix_new_message_py().await;
}

View File

@ -76,7 +76,7 @@ pub fn verify_plugins() {
pub const ICA_NEW_MESSAGE_FUNC: &str = "on_ica_message"; pub const ICA_NEW_MESSAGE_FUNC: &str = "on_ica_message";
pub const ICA_DELETE_MESSAGE_FUNC: &str = "on_ica_delete_message"; pub const ICA_DELETE_MESSAGE_FUNC: &str = "on_ica_delete_message";
pub const TAILCHAT_NEW_MESSAGE_FUNC: &str = "on_tailchat_message"; pub const MATRIX_NEW_MESSAGE_FUNC: &str = "on_matrix_message";
/// 执行 new message 的 python 插件 /// 执行 new message 的 python 插件
pub async fn ica_new_message_py(message: &NewMessage, client: &Client) { pub async fn ica_new_message_py(message: &NewMessage, client: &Client) {
@ -124,3 +124,22 @@ pub async fn ica_delete_message_py(msg_id: MessageId, client: &Client) {
}); });
} }
} }
pub async fn matrix_new_message_py() {
verify_plugins();
let plugins = PyStatus::get_files();
for (path, plugin) in plugins.iter() {
tokio::spawn(async move {
Python::with_gil(|py| {
if let Some(py_func) =
get_func(plugin.py_module.as_ref(py), path, MATRIX_NEW_MESSAGE_FUNC)
{
if let Err(e) = py_func.call0() {
warn!("failed to call function<{}>: {:?}", MATRIX_NEW_MESSAGE_FUNC, e);
}
}
})
});
}
}

View File

@ -1,4 +1,5 @@
pub mod ica; pub mod ica;
pub mod matrix;
use pyo3::prelude::*; use pyo3::prelude::*;
use toml::Value as TomlValue; use toml::Value as TomlValue;

View File

@ -214,6 +214,8 @@ impl IcaClientPy {
pub fn get_version(&self) -> String { crate::VERSION.to_string() } pub fn get_version(&self) -> String { crate::VERSION.to_string() }
#[getter] #[getter]
pub fn get_ica_version(&self) -> String { crate::ICA_VERSION.to_string() } pub fn get_ica_version(&self) -> String { crate::ICA_VERSION.to_string() }
#[getter]
pub fn get_matrix_version(&self) -> String { crate::MATRIX_VERSION.to_string() }
pub fn debug(&self, content: String) { pub fn debug(&self, content: String) {
debug!("{}", content); debug!("{}", content);

View File

@ -0,0 +1 @@

View File

@ -5,6 +5,7 @@ use crate::MAIN_STATUS;
pub struct BotStatus { pub struct BotStatus {
pub config: Option<BotConfig>, pub config: Option<BotConfig>,
pub ica_status: Option<ica::MainStatus>, pub ica_status: Option<ica::MainStatus>,
pub matrix_status: Option<matrix::MainStatus>,
} }
impl BotStatus { impl BotStatus {
@ -18,6 +19,11 @@ impl BotStatus {
MAIN_STATUS.ica_status = Some(status); MAIN_STATUS.ica_status = Some(status);
} }
} }
pub fn update_matrix_status(status: matrix::MainStatus) {
unsafe {
MAIN_STATUS.matrix_status = Some(status);
}
}
pub fn static_init(config: BotConfig) { pub fn static_init(config: BotConfig) {
unsafe { unsafe {
@ -28,6 +34,9 @@ impl BotStatus {
rooms: Vec::new(), rooms: Vec::new(),
online_status: ica::OnlineData::default(), online_status: ica::OnlineData::default(),
}); });
MAIN_STATUS.matrix_status = Some(matrix::MainStatus {
enable: config.check_matrix(),
});
MAIN_STATUS.config = Some(config); MAIN_STATUS.config = Some(config);
} }
} }
@ -36,10 +45,16 @@ impl BotStatus {
pub fn global_ica_status() -> &'static ica::MainStatus { pub fn global_ica_status() -> &'static ica::MainStatus {
unsafe { MAIN_STATUS.ica_status.as_ref().unwrap() } unsafe { MAIN_STATUS.ica_status.as_ref().unwrap() }
} }
pub fn global_matrix_status() -> &'static matrix::MainStatus {
unsafe { MAIN_STATUS.matrix_status.as_ref().unwrap() }
}
pub fn global_ica_status_mut() -> &'static mut ica::MainStatus { pub fn global_ica_status_mut() -> &'static mut ica::MainStatus {
unsafe { MAIN_STATUS.ica_status.as_mut().unwrap() } unsafe { MAIN_STATUS.ica_status.as_mut().unwrap() }
} }
pub fn global_matrix_status_mut() -> &'static mut matrix::MainStatus {
unsafe { MAIN_STATUS.matrix_status.as_mut().unwrap() }
}
} }
pub mod ica { pub mod ica {
@ -65,3 +80,12 @@ pub mod ica {
pub fn update_online_status(&mut self, status: OnlineData) { self.online_status = status; } pub fn update_online_status(&mut self, status: OnlineData) { self.online_status = status; }
} }
} }
pub mod matrix {
#[derive(Debug, Clone)]
pub struct MainStatus {
/// 是否启用 matrix
pub enable: bool,
}
}

10
news.md
View File

@ -1,15 +1,5 @@
# 更新日志 # 更新日志
## 0.6.0-dev
- 去除了 matrix 的支持
- 淦哦
- 去除了相应代码和依赖
- 去除了 Python 侧代码
- 向 tailchat (typescript 低头)
- 修复了没法编译的问题(
## 0.5.3 ## 0.5.3
修复了 Icalingua 断开时 如果 socketio 已经断开会导致程序 返回 Error 的问题 修复了 Icalingua 断开时 如果 socketio 已经断开会导致程序 返回 Error 的问题