Compare commits

..

No commits in common. "57d7e8c8ccce82fb859e821796a97129c8f10f59" and "e41279d843174922c1d94d030232cc5fea3573fd" have entirely different histories.

26 changed files with 198 additions and 810 deletions

132
Cargo.lock generated
View File

@ -165,9 +165,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cc"
version = "1.2.18"
version = "1.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c"
checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a"
dependencies = [
"shlex",
]
@ -287,9 +287,9 @@ dependencies = [
[[package]]
name = "deranged"
version = "0.4.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058"
dependencies = [
"powerfmt",
]
@ -356,9 +356,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.11"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys 0.59.0",
@ -645,9 +645,9 @@ dependencies = [
[[package]]
name = "hyper-util"
version = "0.1.11"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2"
checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4"
dependencies = [
"bytes",
"futures-channel",
@ -655,7 +655,6 @@ dependencies = [
"http",
"http-body",
"hyper",
"libc",
"pin-project-lite",
"socket2",
"tokio",
@ -665,9 +664,9 @@ dependencies = [
[[package]]
name = "iana-time-zone"
version = "0.1.63"
version = "0.1.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127"
dependencies = [
"android_system_properties",
"core-foundation-sys",
@ -854,9 +853,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.9.0"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
dependencies = [
"equivalent",
"hashbrown",
@ -913,9 +912,9 @@ checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "linux-raw-sys"
version = "0.9.4"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413"
[[package]]
name = "litemap"
@ -972,9 +971,9 @@ dependencies = [
[[package]]
name = "miniz_oxide"
version = "0.8.8"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
dependencies = [
"adler2",
]
@ -1043,15 +1042,15 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.21.3"
version = "1.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
checksum = "c2806eaa3524762875e21c3dcd057bc4b7bfa01ce4da8d46be1cd43649e1cc6b"
[[package]]
name = "openssl"
version = "0.10.72"
version = "0.10.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da"
checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd"
dependencies = [
"bitflags",
"cfg-if",
@ -1081,9 +1080,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-sys"
version = "0.9.107"
version = "0.9.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07"
checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd"
dependencies = [
"cc",
"libc",
@ -1163,9 +1162,9 @@ dependencies = [
[[package]]
name = "pyo3"
version = "0.24.1"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17da310086b068fbdcefbba30aeb3721d5bb9af8db4987d6735b2183ca567229"
checksum = "7f1c6c3591120564d64db2261bec5f910ae454f01def849b9c22835a84695e86"
dependencies = [
"cfg-if",
"indoc",
@ -1181,9 +1180,9 @@ dependencies = [
[[package]]
name = "pyo3-build-config"
version = "0.24.1"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e27165889bd793000a098bb966adc4300c312497ea25cf7a690a9f0ac5aa5fc1"
checksum = "e9b6c2b34cf71427ea37c7001aefbaeb85886a074795e35f161f5aecc7620a7a"
dependencies = [
"once_cell",
"target-lexicon",
@ -1191,9 +1190,9 @@ dependencies = [
[[package]]
name = "pyo3-ffi"
version = "0.24.1"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05280526e1dbf6b420062f3ef228b78c0c54ba94e157f5cb724a609d0f2faabc"
checksum = "5507651906a46432cdda02cd02dd0319f6064f1374c9147c45b978621d2c3a9c"
dependencies = [
"libc",
"pyo3-build-config",
@ -1201,9 +1200,9 @@ dependencies = [
[[package]]
name = "pyo3-macros"
version = "0.24.1"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c3ce5686aa4d3f63359a5100c62a127c9f15e8398e5fdeb5deef1fed5cd5f44"
checksum = "b0d394b5b4fd8d97d48336bb0dd2aebabad39f1d294edd6bcd2cccf2eefe6f42"
dependencies = [
"proc-macro2",
"pyo3-macros-backend",
@ -1213,9 +1212,9 @@ dependencies = [
[[package]]
name = "pyo3-macros-backend"
version = "0.24.1"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4cf6faa0cbfb0ed08e89beb8103ae9724eb4750e3a78084ba4017cbe94f3855"
checksum = "fd72da09cfa943b1080f621f024d2ef7e2773df7badd51aa30a2be1f8caa7c8e"
dependencies = [
"heck",
"proc-macro2",
@ -1423,9 +1422,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "1.0.5"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96"
dependencies = [
"bitflags",
"errno",
@ -1436,9 +1435,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.26"
version = "0.23.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0"
checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c"
dependencies = [
"once_cell",
"rustls-pki-types",
@ -1642,15 +1641,15 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.15.0"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
[[package]]
name = "socket2"
version = "0.5.9"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef"
checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
dependencies = [
"libc",
"windows-sys 0.52.0",
@ -1842,9 +1841,9 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.44.2"
version = "1.44.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a"
dependencies = [
"backtrace",
"bytes",
@ -2279,37 +2278,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
version = "0.61.0"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings 0.4.0",
]
[[package]]
name = "windows-implement"
version = "0.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
dependencies = [
"proc-macro2",
"quote",
"syn",
"windows-targets 0.52.6",
]
[[package]]
@ -2325,7 +2298,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3"
dependencies = [
"windows-result",
"windows-strings 0.3.1",
"windows-strings",
"windows-targets 0.53.0",
]
@ -2347,15 +2320,6 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
@ -2504,9 +2468,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "winnow"
version = "0.7.6"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10"
checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36"
dependencies = [
"memchr",
]

View File

@ -1,7 +1,7 @@
[package]
name = "ica-rs"
version = "0.9.0"
edition = "2024"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -42,7 +42,7 @@ colored = "3.0"
# runtime
tokio = { version = "1.43", features = ["rt-multi-thread", "time", "signal", "macros"] }
futures-util = "0.3"
pyo3 = { version = "0.24", features = ["experimental-async"] }
pyo3 = { version = "0.24", features = ["experimental-async", "py-clone"] }
anyhow = { version = "1.0", features = ["backtrace"] }
# async 这玩意以后在搞
# pyo3-async = "0.3.2"

View File

@ -1,5 +1,5 @@
use crate::data_struct::ica::messages::{At, LastMessage, SendMessage};
use crate::data_struct::ica::{RoomId, UserId};
use crate::data_struct::ica::RoomId;
use serde::{Deserialize, Serialize};
use serde_json::{Number, Value as JsonValue};
@ -101,37 +101,3 @@ struct InnerRoom {
// #[serde(rename = "downloadPath")]
// pub download_path: Option<String>,
}
/// ```json
/// {
/// "comment": "问题:从哪里了解到的本群\n答案aaa",
/// "flag": "e4cd5a892ba34bed063196a0cc47a8",
/// "group_id": xxxxx,
/// "group_name": "Nuitka 和 Python 打包",
/// "nickname": "jashcken",
/// "post_type": "request",
/// "request_type": "group",
/// "self_id": 45620725,
/// "sub_type": "add",
/// "time": 1743372872,
/// "tips": "",
/// "user_id": 3838663305
/// }
/// ```
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct JoinRequestRoom {
/// 问题+答案
pub comment: String,
pub group_id: RoomId,
pub group_name: String,
pub user_id: UserId,
pub nickname: String,
// 剩下的应该没用了……吧?
pub request_type: String,
pub post_type: String,
pub sub_type: String,
pub time: i64,
pub tips: String,
pub flag: String,
}

View File

@ -3,7 +3,7 @@ use crate::data_struct::ica::{MessageId, RoomId, UserId};
use chrono::DateTime;
use serde::{Deserialize, Serialize};
use serde_json::{Value as JsonValue, json};
use serde_json::{json, Value as JsonValue};
use tracing::warn;
pub mod msg_trait;
@ -331,7 +331,7 @@ impl SendMessage {
/// file_type: 图片类型(MIME) (image/png; image/jpeg)
pub fn set_img(&mut self, file: &Vec<u8>, file_type: &str, as_sticker: bool) {
self.sticker = as_sticker;
use base64::{Engine as _, engine::general_purpose};
use base64::{engine::general_purpose, Engine as _};
let base64_data = general_purpose::STANDARD.encode(file);
self.file_data = Some(format!("data:{};base64,{}", file_type, base64_data));
}

View File

@ -4,9 +4,9 @@ use chrono::DateTime;
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use crate::MainStatus;
use crate::data_struct::ica::messages::{At, Message, NewMessage};
use crate::data_struct::ica::{MessageId, UserId};
use crate::MainStatus;
impl Serialize for At {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>

View File

@ -1,7 +1,7 @@
use std::fmt::Display;
use serde::{Deserialize, Serialize};
use serde_json::{Value as JsonValue, json};
use serde_json::{json, Value as JsonValue};
use crate::data_struct::tailchat::{ConverseId, GroupId, MessageId, UserId};

View File

@ -33,8 +33,6 @@ pub enum PyPluginError {
/// 插件内函数调用错误
/// pyerr, func_name, module_name
FuncCallError(pyo3::PyErr, String, String),
/// 插件停不下来!
PluginNotStopped,
}
impl From<rust_socketio::Error> for IcaError {
@ -83,9 +81,6 @@ impl std::fmt::Display for PyPluginError {
PyPluginError::FuncCallError(py_err, name, module) => {
write!(f, "插件内函数调用错误: {:#?}|{} in {}", py_err, name, module)
}
PyPluginError::PluginNotStopped => {
write!(f, "插件未停止")
}
}
}
}
@ -116,7 +111,6 @@ impl std::error::Error for PyPluginError {
PyPluginError::CouldNotGetFunc(e, _, _) => Some(e),
PyPluginError::FuncNotCallable(_, _) => None,
PyPluginError::FuncCallError(e, _, _) => Some(e),
PyPluginError::PluginNotStopped => None,
}
}
}

View File

@ -5,13 +5,13 @@ pub mod events;
use colored::Colorize;
use rust_socketio::asynchronous::{Client, ClientBuilder};
use rust_socketio::{Event, Payload, TransportType};
use rust_socketio::{async_any_callback, async_callback};
use tracing::{Level, event, span};
use rust_socketio::{Event, Payload, TransportType};
use tracing::{event, span, Level};
use crate::config::IcaConfig;
use crate::error::{ClientResult, IcaError};
use crate::{StopGetter, version_str};
use crate::{version_str, StopGetter};
/// icalingua 客户端的兼容版本号
pub const ICA_PROTOCOL_VERSION: &str = "2.12.28";
@ -63,7 +63,6 @@ pub async fn start_ica(config: &IcaConfig, stop_reciver: StopGetter) -> ClientRe
.on("setMessages", async_callback!(events::set_messages))
.on("addMessage", async_callback!(events::add_message))
.on("deleteMessage", async_callback!(events::delete_message))
.on("handleRequest", async_callback!(events::join_request))
.connect()
.await
{
@ -101,12 +100,12 @@ pub async fn start_ica(config: &IcaConfig, stop_reciver: StopGetter) -> ClientRe
}
}
// 等待停止信号
event!(Level::INFO, "{}", "ica client waiting for stop signal".purple());
event!(Level::INFO, "ica client waiting for stop signal");
stop_reciver.await.ok();
event!(Level::INFO, "{}", "socketio client stopping".yellow());
event!(Level::INFO, "socketio client stopping");
match socket.disconnect().await {
Ok(_) => {
event!(Level::INFO, "{}", "socketio client stopped".green());
event!(Level::INFO, "socketio client stopped");
Ok(())
}
Err(e) => {
@ -114,7 +113,7 @@ pub async fn start_ica(config: &IcaConfig, stop_reciver: StopGetter) -> ClientRe
match e {
rust_socketio::Error::IncompleteResponseFromEngineIo(inner_e) => {
if inner_e.to_string().contains("AlreadyClosed") {
event!(Level::INFO, "{}", "socketio client stopped".green());
event!(Level::INFO, "socketio client stopped");
Ok(())
} else {
event!(Level::ERROR, "socketio 客户端出现了 Error: {:?}", inner_e);

View File

@ -1,25 +1,25 @@
use crate::MainStatus;
use crate::data_struct::ica::messages::{DeleteMessage, SendMessage};
use crate::data_struct::ica::{RoomId, RoomIdTrait, UserId};
use crate::error::{ClientResult, IcaError};
use crate::MainStatus;
use colored::Colorize;
use ed25519_dalek::{Signature, Signer, SigningKey};
use rust_socketio::Payload;
use rust_socketio::asynchronous::Client;
use serde_json::{Value, json};
use tracing::{Level, event, span};
use rust_socketio::Payload;
use serde_json::{json, Value};
use tracing::{debug, event, span, warn, Level};
/// "安全" 的 发送一条消息
pub async fn send_message(client: &Client, message: &SendMessage) -> bool {
let value = message.as_value();
match client.emit("sendMessage", value).await {
Ok(_) => {
event!(Level::INFO, "send_message {}", format!("{:#?}", message).cyan());
debug!("send_message {}", format!("{:#?}", message).cyan());
true
}
Err(e) => {
event!(Level::WARN, "send_message faild:{}", format!("{:#?}", e).red());
warn!("send_message faild:{}", format!("{:#?}", e).red());
false
}
}
@ -30,11 +30,11 @@ pub async fn delete_message(client: &Client, message: &DeleteMessage) -> bool {
let value = message.as_value();
match client.emit("deleteMessage", value).await {
Ok(_) => {
event!(Level::DEBUG, "delete_message {}", format!("{:#?}", message).yellow());
debug!("delete_message {}", format!("{:#?}", message).yellow());
true
}
Err(e) => {
event!(Level::WARN, "delete_message faild:{}", format!("{:#?}", e).red());
warn!("delete_message faild:{}", format!("{:#?}", e).red());
false
}
}
@ -71,8 +71,7 @@ async fn inner_sign(payload: Payload, client: &Client) -> ClientResult<(), IcaEr
.as_str()
.unwrap_or("unknow");
if server_protocol_version != crate::ica::ICA_PROTOCOL_VERSION {
event!(
Level::WARN,
warn!(
"服务器版本与兼容版本不一致\n服务器协议版本:{:?}\n兼容版本:{}",
version.get("protocolVersion"),
crate::ica::ICA_PROTOCOL_VERSION

View File

@ -2,14 +2,14 @@ use colored::Colorize;
use rust_socketio::asynchronous::Client;
use rust_socketio::{Event, Payload};
use serde_json::json;
use tracing::{Level, event, info, span, warn};
use tracing::{event, info, span, warn, Level};
use crate::data_struct::ica::RoomId;
use crate::data_struct::ica::all_rooms::{JoinRequestRoom, Room};
use crate::data_struct::ica::all_rooms::Room;
use crate::data_struct::ica::messages::{Message, MessageTrait, NewMessage};
use crate::data_struct::ica::online_data::OnlineData;
use crate::data_struct::ica::RoomId;
use crate::ica::client::send_message;
use crate::{MainStatus, VERSION, client_id, help_msg, py, version_str};
use crate::{client_id, help_msg, py, version_str, MainStatus, VERSION};
/// 获取在线数据
pub async fn get_online_data(payload: Payload, _client: Client) {
@ -175,29 +175,6 @@ pub async fn failed_message(payload: Payload, _client: Client) {
}
}
/// 处理加群申请
///
/// add: 2.0.1
pub async fn join_request(payload: Payload, _client: Client) {
if let Payload::Text(values) = payload {
if let Some(value) = values.first() {
match serde_json::from_value::<JoinRequestRoom>(value.clone()) {
Ok(join_room) => {
event!(Level::INFO, "{}", format!("收到加群申请 {:?}", join_room).on_blue());
}
Err(e) => {
event!(
Level::WARN,
"呼叫 shenjack! JoinRequestRoom 的 serde 没写好! {}\nraw: {:#?}",
e,
value
)
}
}
}
}
}
pub async fn fetch_history(client: Client, room: RoomId) { let mut request_body = json!(room); }
pub async fn fetch_messages(client: &Client, room: RoomId) {
@ -223,11 +200,11 @@ pub async fn any_event(event: Event, payload: Payload, _client: Client) {
"deleteMessage",
"setAllRooms",
"setMessages",
"handleRequest", // 处理验证消息 (加入请求之类的)
// 也许以后会用到
"messageSuccess",
"messageFailed",
"setAllChatGroups",
"handleRequest", // 处理验证消息 (加入请求之类的)
// 忽略的
"notify",
"setShutUp", // 禁言

View File

@ -16,10 +16,8 @@ mod ica;
#[cfg(feature = "tailchat")]
mod tailchat;
use colored::Colorize;
use config::BotConfig;
use error::PyPluginError;
use tracing::{Level, event, span};
use tracing::{event, span, Level};
pub static mut MAIN_STATUS: status::BotStatus = status::BotStatus {
config: None,
@ -141,34 +139,20 @@ fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt().with_max_level(level).init();
let rt = tokio::runtime::Builder::new_multi_thread()
let _ = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.thread_name("shenbot-rs")
.worker_threads(10)
.build()
.unwrap();
let result = rt.block_on(inner_main());
.unwrap()
.block_on(inner_main());
event!(Level::INFO, "shenbot-rs v{} exiting", VERSION);
match result {
Ok(_) => {}
Err(e) => {
if let Some(PyPluginError::PluginNotStopped) = e.downcast_ref::<PyPluginError>() {
event!(Level::WARN, "Python 插件停不下来, 3s 后终止 tokio rt");
rt.shutdown_timeout(Duration::from_secs(3));
} else {
event!(Level::ERROR, "shenbot-rs v{} exiting with error: {}", VERSION, e);
}
}
}
Ok(())
}
async fn inner_main() -> anyhow::Result<()> {
let span = span!(Level::INFO, "bot-main");
let span = span!(Level::INFO, "Shenbot Main");
let _enter = span.enter();
event!(Level::INFO, "shenbot-rs v{} starting", VERSION);
@ -185,28 +169,28 @@ async fn inner_main() -> anyhow::Result<()> {
}
// 准备一个用于停止 socket 的变量
event!(Level::INFO, "启动 ICA");
let (ica_send, ica_recv) = tokio::sync::oneshot::channel::<()>();
if bot_config.check_ica() {
event!(Level::INFO, "{}", "开始启动 ICA".green());
let config = bot_config.ica();
tokio::spawn(async move {
ica::start_ica(&config, ica_recv).await.unwrap();
});
} else {
event!(Level::INFO, "{}", "ica 未启用, 不管他".cyan());
event!(Level::INFO, "未启用 ica");
}
let (tailchat_send, tailchat_recv) = tokio::sync::oneshot::channel::<()>();
if bot_config.check_tailchat() {
event!(Level::INFO, "{}", "开始启动 tailchat".green());
event!(Level::INFO, "启动 Tailchat");
let config = bot_config.tailchat();
tokio::spawn(async move {
tailchat::start_tailchat(config, tailchat_recv).await.unwrap();
});
} else {
event!(Level::INFO, "{}", "tailchat 未启用, 不管他".bright_magenta());
event!(Level::INFO, "未启用 Tailchat");
}
tokio::time::sleep(Duration::from_secs(1)).await;
@ -233,8 +217,8 @@ async fn test_macro() {
use std::sync::Arc;
use tokio::sync::RwLock;
use rust_socketio::Payload;
use rust_socketio::asynchronous::{Client, ClientBuilder};
use rust_socketio::Payload;
/// 一个简单的例子
#[derive(Clone)]

View File

@ -4,13 +4,12 @@ use std::sync::LazyLock;
use pyo3::prelude::*;
use rust_socketio::asynchronous::Client;
use tokio::sync::Mutex;
use tracing::{Level, event, info, warn};
use tracing::{event, info, warn, Level};
use crate::MainStatus;
use crate::data_struct::{ica, tailchat};
use crate::error::PyPluginError;
use crate::py::consts::events_func;
use crate::py::{PyPlugin, PyStatus, class};
use crate::py::{class, PyPlugin, PyStatus};
use crate::MainStatus;
pub struct PyTasks {
pub ica_new_message: Vec<tokio::task::JoinHandle<()>>,
@ -175,6 +174,11 @@ pub fn verify_and_reload_plugins() {
}
}
pub const ICA_NEW_MESSAGE_FUNC: &str = "on_ica_message";
pub const ICA_DELETE_MESSAGE_FUNC: &str = "on_ica_delete_message";
pub const TAILCHAT_NEW_MESSAGE_FUNC: &str = "on_tailchat_message";
macro_rules! call_py_func {
($args:expr, $plugin:expr, $plugin_path:expr, $func_name:expr, $client:expr) => {
tokio::spawn(async move {
@ -220,7 +224,7 @@ pub async fn ica_new_message_py(message: &ica::messages::NewMessage, client: &Cl
let msg = class::ica::NewMessagePy::new(message);
let client = class::ica::IcaClientPy::new(client);
let args = (msg, client);
let task = call_py_func!(args, plugin, path, events_func::ICA_NEW_MESSAGE, client);
let task = call_py_func!(args, plugin, path, ICA_NEW_MESSAGE_FUNC, client);
PY_TASKS.lock().await.push_ica_new_message(task);
}
}
@ -233,7 +237,7 @@ pub async fn ica_delete_message_py(msg_id: ica::MessageId, client: &Client) {
let msg_id = msg_id.clone();
let client = class::ica::IcaClientPy::new(client);
let args = (msg_id.clone(), client);
let task = call_py_func!(args, plugin, path, events_func::ICA_DELETE_MESSAGE, client);
let task = call_py_func!(args, plugin, path, ICA_DELETE_MESSAGE_FUNC, client);
PY_TASKS.lock().await.push_ica_delete_message(task);
}
}
@ -249,7 +253,7 @@ pub async fn tailchat_new_message_py(
let msg = class::tailchat::TailchatReceiveMessagePy::from_recive_message(message);
let client = class::tailchat::TailchatClientPy::new(client);
let args = (msg, client);
let task = call_py_func!(args, plugin, path, events_func::TAILCHAT_NEW_MESSAGE, client);
let task = call_py_func!(args, plugin, path, TAILCHAT_NEW_MESSAGE_FUNC, client);
PY_TASKS.lock().await.push_tailchat_new_message(task);
}
}

View File

@ -1,17 +1,27 @@
pub mod commander;
pub mod config;
pub mod ica;
pub mod schdule;
pub mod tailchat;
use pyo3::{
Bound, IntoPyObject, PyAny, PyRef, PyResult, pyclass, pymethods, pymodule,
types::{PyBool, PyModule, PyModuleMethods, PyString},
pyclass, pymethods,
types::{PyBool, PyString},
Bound, IntoPyObject, PyAny, PyRef,
};
use toml::Value as TomlValue;
use tracing::{Level, event};
// #[derive(Clone)]
#[derive(Clone)]
#[pyclass]
#[pyo3(name = "ConfigRequest")]
pub struct ConfigRequestPy {
pub path: String,
}
#[pymethods]
impl ConfigRequestPy {
#[new]
pub fn py_new(path: String) -> Self { Self { path } }
}
#[derive(Clone)]
#[pyclass]
#[pyo3(name = "ConfigData")]
pub struct ConfigDataPy {
@ -51,35 +61,3 @@ impl ConfigDataPy {
impl ConfigDataPy {
pub fn new(data: TomlValue) -> Self { Self { data } }
}
/// Rust 侧向 Python 侧提供的 api
#[pymodule]
#[pyo3(name = "shenbot_api")]
fn rs_api_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add("__version__", crate::VERSION)?;
m.add("_version_", crate::VERSION)?;
m.add("_ica_version_", crate::ICA_VERSION)?;
m.add("_tailchat_version_", crate::TAILCHAT_VERSION)?;
m.add_class::<ConfigDataPy>()?;
m.add_class::<config::ConfigStoragePy>()?;
m.add_class::<schdule::SchedulerPy>()?;
Ok(())
}
/// 在 python 初始化之前注册所有需要的类
///
/// WARNING: 这个函数需要在 Python 初始化之前调用,否则会导致报错
///
/// (pyo3 提供的宏会检查一遍, 不过我这里就直接用原始形式了)
pub fn regist_class() {
event!(Level::INFO, "向 Python 注册 Rust 侧模块/函数");
unsafe {
// 单纯没用 macro 而已
pyo3::ffi::PyImport_AppendInittab(
rs_api_module::__PYO3_NAME.as_ptr(),
Some(rs_api_module::__pyo3_init),
);
}
event!(Level::INFO, "注册完成");
}

View File

@ -1 +0,0 @@

View File

@ -1,343 +0,0 @@
use std::collections::HashMap;
use pyo3::{
Bound, PyAny, PyResult, pyclass, pymethods,
types::{
PyAnyMethods, PyBool, PyBoolMethods, PyDict, PyDictMethods, PyFloat, PyInt, PyList,
PyListMethods, PyString, PyStringMethods, PyTypeMethods,
},
};
use tracing::{Level, event};
#[derive(Debug, Clone)]
pub enum ConfigItem {
None,
String(String),
Int(i64),
Float(f64),
Bool(bool),
Array(Vec<ConfigItemPy>),
Table(HashMap<String, ConfigItemPy>),
}
#[derive(Clone, Debug)]
#[pyclass]
#[pyo3(name = "ConfigItem")]
pub struct ConfigItemPy {
pub item: ConfigItem,
pub default_value: ConfigItem,
}
impl ConfigItemPy {
pub fn new(item: ConfigItem, default_value: ConfigItem) -> Self {
Self {
item,
default_value,
}
}
pub fn new_uninit(default_value: ConfigItem) -> Self {
Self {
item: ConfigItem::None,
default_value,
}
}
}
#[derive(Clone)]
#[pyclass]
#[pyo3(name = "ConfigStorage")]
pub struct ConfigStoragePy {
pub keys: HashMap<String, ConfigItemPy>,
}
/// Storage 里允许的最大层级深度
///
/// 我也不知道为啥就突然有这玩意了(
pub const MAX_CFG_DEPTH: usize = 10;
fn parse_py_string(obj: &Bound<'_, PyAny>) -> PyResult<String> {
let py_str = obj.downcast::<PyString>()?;
let value = py_str.to_str()?;
Ok(value.to_string())
}
fn parse_py_bool(obj: &Bound<'_, PyAny>) -> PyResult<bool> {
let py_bool = obj.downcast::<PyBool>()?;
Ok(py_bool.is_true())
}
fn parse_py_int(obj: &Bound<'_, PyAny>) -> PyResult<i64> {
let py_int = obj.downcast::<PyInt>()?;
py_int.extract::<i64>()
}
fn parse_py_float(obj: &Bound<'_, PyAny>) -> PyResult<f64> {
let py_float = obj.downcast::<PyFloat>()?;
py_float.extract::<f64>()
}
impl ConfigStoragePy {
/// 递归 list 解析配置
///
/// 用个 Result 来标记递归过深
fn parse_py_list(
args: &Bound<'_, PyList>,
list: &mut Vec<ConfigItemPy>,
current_deepth: usize,
) -> Result<(), usize> {
if current_deepth > MAX_CFG_DEPTH {
return Err(current_deepth);
} else {
for value in args.iter() {
// 匹配 item
let value_type = value.get_type();
if value_type.is_instance_of::<PyDict>() {
let py_dict = value.downcast::<PyDict>().unwrap();
let mut new_map = HashMap::new();
match Self::parse_py_dict(py_dict, &mut new_map, current_deepth + 1) {
Ok(_) => {
list.push(ConfigItemPy::new_uninit(ConfigItem::Table(new_map)));
}
Err(e) => {
event!(
Level::WARN,
"value(dict) 解析时出现错误: {}\nraw: {}",
e,
value
);
}
}
} else if value_type.is_instance_of::<PyList>() {
let py_list = value.downcast::<PyList>().unwrap();
let mut new_list = Vec::new();
match Self::parse_py_list(py_list, &mut new_list, current_deepth + 1) {
Ok(_) => {
list.push(ConfigItemPy::new_uninit(ConfigItem::Array(new_list)));
}
Err(e) => {
event!(
Level::WARN,
"value(list) 解析时出现错误: {}\nraw: {}",
e,
value
);
}
}
} else if value_type.is_instance_of::<PyString>() {
match parse_py_string(&value) {
Ok(value) => {
list.push(ConfigItemPy::new_uninit(ConfigItem::String(value)));
}
Err(e) => {
event!(
Level::WARN,
"value(string) 解析时出现错误: {}\nraw: {}",
e,
value
);
}
}
} else if value_type.is_instance_of::<PyBool>() {
match parse_py_bool(&value) {
Ok(value) => {
list.push(ConfigItemPy::new_uninit(ConfigItem::Bool(value)));
}
Err(e) => {
event!(
Level::WARN,
"value(bool) 解析时出现错误: {}\nraw: {}",
e,
value
);
}
}
} else if value_type.is_instance_of::<PyInt>() {
match parse_py_int(&value) {
Ok(value) => {
list.push(ConfigItemPy::new_uninit(ConfigItem::Int(value)));
}
Err(e) => {
event!(Level::WARN, "value(int) 解析时出现错误: {}\nraw: {}", e, value);
}
}
} else if value_type.is_instance_of::<PyFloat>() {
match parse_py_float(&value) {
Ok(value) => {
list.push(ConfigItemPy::new_uninit(ConfigItem::Float(value)));
}
Err(e) => {
event!(
Level::WARN,
"value(float) 解析时出现错误: {}\nraw: {}",
e,
value
);
}
}
} else {
// 先丢个 warning 出去
match value_type.name() {
Ok(type_name) => {
event!(
Level::WARN,
"value 为不支持的 {} 类型\nraw: {}",
type_name,
value
)
}
Err(e) => {
event!(
Level::WARN,
"value 为不支持的类型 (获取类型名失败: {})\nraw: {}",
e,
value
)
}
}
}
}
}
Ok(())
}
/// 递归 dict 解析配置
///
/// 用个 Result 来标记递归过深
fn parse_py_dict(
kwargs: &Bound<'_, PyDict>,
map: &mut HashMap<String, ConfigItemPy>,
current_deepth: usize,
) -> Result<(), usize> {
if current_deepth > MAX_CFG_DEPTH {
Err(current_deepth)
} else {
for (key, value) in kwargs.iter() {
if let Ok(name) = key.downcast::<PyString>() {
let name = name.to_string();
// 匹配 item
let value_type = value.get_type();
if value_type.is_instance_of::<PyDict>() {
let py_dict = value.downcast::<PyDict>().unwrap();
let mut new_map = HashMap::new();
match Self::parse_py_dict(py_dict, &mut new_map, current_deepth + 1) {
Ok(_) => {
map.insert(
name.clone(),
ConfigItemPy::new_uninit(ConfigItem::Table(new_map)),
);
}
Err(e) => {
event!(Level::WARN, "value(dict) {} 解析时出现错误: {}", name, e);
}
}
} else if value_type.is_instance_of::<PyList>() {
let py_list = value.downcast::<PyList>().unwrap();
let mut new_list = Vec::new();
match Self::parse_py_list(py_list, &mut new_list, current_deepth + 1) {
Ok(_) => {
map.insert(
name.clone(),
ConfigItemPy::new_uninit(ConfigItem::Array(new_list)),
);
}
Err(e) => {
event!(Level::WARN, "value(list) {} 解析时出现错误: {}", name, e);
}
}
} else if value_type.is_instance_of::<PyString>() {
match parse_py_string(&value) {
Ok(value) => {
map.insert(
name.clone(),
ConfigItemPy::new_uninit(ConfigItem::String(value)),
);
}
Err(e) => {
event!(Level::WARN, "value(string) {} 解析时出现错误: {}", name, e);
}
}
} else if value_type.is_instance_of::<PyBool>() {
match parse_py_bool(&value) {
Ok(value) => {
map.insert(
name.clone(),
ConfigItemPy::new_uninit(ConfigItem::Bool(value)),
);
}
Err(e) => {
event!(Level::WARN, "value(bool) {} 解析时出现错误: {}", name, e);
}
}
} else if value_type.is_instance_of::<PyInt>() {
match parse_py_int(&value) {
Ok(value) => {
map.insert(
name.clone(),
ConfigItemPy::new_uninit(ConfigItem::Int(value)),
);
}
Err(e) => {
event!(Level::WARN, "value(int) {} 解析时出现错误: {}", name, e);
}
}
} else if value_type.is_instance_of::<PyFloat>() {
match parse_py_float(&value) {
Ok(value) => {
map.insert(
name.clone(),
ConfigItemPy::new_uninit(ConfigItem::Float(value)),
);
}
Err(e) => {
event!(Level::WARN, "value(float) {} 解析时出现错误: {}", name, e);
}
}
} else {
// 先丢个 warning 出去
match value_type.name() {
Ok(type_name) => {
event!(Level::WARN, "value {} 为不支持的 {} 类型", name, type_name)
}
Err(e) => event!(
Level::WARN,
"value {} 为不支持的类型 (获取类型名失败: {})",
name,
e
),
}
continue;
}
}
}
Ok(())
}
}
}
#[pymethods]
impl ConfigStoragePy {
#[new]
#[pyo3(signature = (**kwargs))]
pub fn new(kwargs: Option<&Bound<'_, PyDict>>) -> PyResult<Self> {
match kwargs {
Some(kwargs) => {
let mut keys = HashMap::new();
// 解析 kwargs
Self::parse_py_dict(kwargs, &mut keys, 0).map_err(|e| {
event!(Level::ERROR, "配置解析过深: {}", e);
pyo3::exceptions::PyValueError::new_err(format!("配置解析过深: {}", e))
})?;
// 解析完成
Ok(Self { keys })
}
None => Ok(Self {
keys: HashMap::new(),
}),
}
}
#[getter]
/// 获取最大允许的层级深度
pub fn get_max_allowed_depth(&self) -> usize { MAX_CFG_DEPTH }
}

View File

@ -3,15 +3,15 @@ use std::time::SystemTime;
use pyo3::{pyclass, pymethods};
use rust_socketio::asynchronous::Client;
use tokio::runtime::Runtime;
use tracing::{Level, event};
use tracing::{event, Level};
use crate::MainStatus;
use crate::data_struct::ica::messages::{
DeleteMessage, MessageTrait, NewMessage, ReplyMessage, SendMessage,
};
use crate::data_struct::ica::{MessageId, RoomId, RoomIdTrait, UserId};
use crate::ica::client::{delete_message, send_message, send_poke, send_room_sign_in};
use crate::py::PyStatus;
use crate::MainStatus;
#[pyclass]
#[pyo3(name = "IcaStatus")]
@ -70,20 +70,6 @@ impl IcaStatusPy {
pub fn get_rooms(&self) -> Vec<IcaRoomPy> {
MainStatus::global_ica_status().rooms.iter().map(|r| r.into()).collect()
}
#[getter]
/// 获取所有管理员
///
/// 添加自 2.0.1
pub fn get_admins(&self) -> Vec<UserId> { MainStatus::global_config().ica().admin_list.clone() }
#[getter]
/// 获取所有被屏蔽的人
///
/// (好像没啥用就是了, 反正被过滤的不会给到插件)
///
/// 添加自 2.0.1
pub fn get_filtered(&self) -> Vec<UserId> {
MainStatus::global_config().ica().filter_list.clone()
}
}
impl Default for IcaStatusPy {
@ -124,10 +110,6 @@ impl IcaRoomPy {
pub fn get_room_name(&self) -> String { self.inner.room_name.clone() }
#[getter]
pub fn get_unread_count(&self) -> u64 { self.inner.unread_count }
#[getter]
pub fn get_priority(&self) -> u8 { self.inner.priority }
#[getter]
pub fn get_utime(&self) -> i64 { self.inner.utime }
pub fn is_group(&self) -> bool { self.inner.room_id.is_room() }
pub fn is_chat(&self) -> bool { self.inner.room_id.is_chat() }
pub fn new_message_to(&self, content: String) -> SendMessagePy {

View File

@ -1,59 +0,0 @@
use std::time::Duration;
use pyo3::{Bound, Py, PyTraverseError, PyVisit, Python, pyclass, pymethods, types::PyFunction};
use tracing::{Level, event};
#[derive(Debug)]
#[pyclass]
#[pyo3(name = "Scheduler")]
/// 用于计划任务的类
///
/// 给 Python 侧使用
///
/// add: 0.9.0
pub struct SchedulerPy {
/// 回调函数
///
/// 你最好不要把他清理掉
pub callback: Py<PyFunction>,
/// 预计等待时间
pub schdule_time: Duration,
}
#[pymethods]
impl SchedulerPy {
fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
visit.call(&self.callback)?;
Ok(())
}
#[new]
pub fn new(func: Bound<'_, PyFunction>, schdule_time: Duration) -> Self {
Self {
callback: func.unbind(),
schdule_time,
}
}
/// 开始
pub fn start(&self, py: Python<'_>) {
let wait = self.schdule_time;
let cb = self.callback.clone_ref(py);
tokio::spawn(async move {
let second = Duration::from_secs(1);
if wait > second {
let big_sleep = wait.checked_sub(second).unwrap();
tokio::time::sleep(big_sleep).await;
tokio::time::sleep(second).await;
} else {
tokio::time::sleep(wait).await;
}
Python::with_gil(|py| {
event!(Level::INFO, "正在调用计划 {:?}", wait);
if let Err(e) = cb.call0(py) {
event!(Level::WARN, "调用时出现错误 {}", e);
}
});
});
}
}

View File

@ -1,11 +1,10 @@
use std::{path::Path, str::FromStr};
use colored::Colorize;
use toml_edit::{DocumentMut, Key, Table, TomlError, Value, value};
use tracing::{Level, event};
use toml_edit::{value, DocumentMut, Key, Table, TomlError, Value};
use tracing::{event, Level};
use crate::MainStatus;
use crate::py::PyStatus;
use crate::MainStatus;
/// ```toml
/// # 这个文件是由 shenbot 自动生成的, 请 **谨慎** 修改
@ -28,7 +27,6 @@ pub const DEFAULT_CONFIG: &str = r#"
[plugins]
"#;
#[allow(unused)]
impl PluginConfigFile {
pub fn from_str(data: &str) -> Result<Self, TomlError> {
let mut data = DocumentMut::from_str(data)?;
@ -136,35 +134,18 @@ impl PluginConfigFile {
}
event!(Level::INFO, "同步插件状态");
let plugins = PyStatus::get_mut();
fn fmt_bool(b: bool) -> String {
if b {
"启用".green().to_string()
} else {
"禁用".red().to_string()
}
}
plugins.files.iter_mut().for_each(|(path, status)| {
let plugin_id = status.get_id();
let config_status = self.get_status(&plugin_id);
if config_status != status.enabled {
event!(
Level::INFO,
"插件状态: {} {} -> {}",
status.get_id(),
fmt_bool(status.enabled),
fmt_bool(config_status)
);
status.enabled = config_status;
} else {
event!(
Level::INFO,
"插件状态: {} {} (没变)",
status.get_id(),
fmt_bool(status.enabled)
);
}
event!(
Level::INFO,
"插件状态: {}({:?}) {} -> {}",
status.get_id(),
path,
status.enabled,
config_status
);
status.enabled = config_status;
});
true
}

View File

@ -1,21 +0,0 @@
pub mod events_func {
/// icalingua 的 加群请求
///
/// added: 2.0.1
pub const ICA_JOIN_REQUEST: &str = "on_ica_join_request";
/// icalingua 的 新消息
pub const ICA_NEW_MESSAGE: &str = "on_ica_message";
/// icalingua 的 消息撤回
pub const ICA_DELETE_MESSAGE: &str = "on_ica_delete_message";
/// tailchat 的 新消息
pub const TAILCHAT_NEW_MESSAGE: &str = "on_tailchat_message";
}
pub mod config_func {
/// 请求配置用的函数
pub const REQUIRE_CONFIG: &str = "require_config";
/// 接受配置用的函数
pub const ON_CONFIG: &str = "on_config";
}

View File

@ -1,7 +1,6 @@
pub mod call;
pub mod class;
pub mod config;
pub mod consts;
use std::ffi::CString;
use std::fmt::Display;
@ -11,20 +10,17 @@ use std::time::SystemTime;
use std::{collections::HashMap, path::PathBuf};
use colored::Colorize;
use pyo3::{
Bound, Py, PyErr, PyResult, Python,
exceptions::PyTypeError,
intern,
types::{PyAnyMethods, PyModule, PyTracebackMethods, PyTuple},
};
use tracing::{Level, event, span, warn};
use pyo3::exceptions::PyTypeError;
use pyo3::types::PyTuple;
use pyo3::{intern, prelude::*};
use tracing::{event, span, warn, Level};
use crate::MainStatus;
use crate::error::PyPluginError;
use consts::config_func;
const REQUIRE_CONFIG_FUNC_NAME: &str = "require_config";
const ON_CONFIG_FUNC_NAME: &str = "on_config";
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct PyStatus {
pub files: PyPlugins,
pub config: config::PluginConfigFile,
@ -36,7 +32,6 @@ pub type RawPyPlugin = (PathBuf, Option<SystemTime>, String);
#[allow(non_upper_case_globals)]
static mut PyPluginStatus: OnceLock<PyStatus> = OnceLock::new();
#[allow(static_mut_refs)]
impl PyStatus {
pub fn init() {
let config =
@ -121,24 +116,15 @@ pub fn get_py_err_traceback(py_err: &PyErr) -> String {
.to_string()
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct PyPlugin {
pub file_path: PathBuf,
pub modify_time: Option<SystemTime>,
pub py_module: Py<PyModule>,
pub changed_time: Option<SystemTime>,
pub py_module: Py<PyAny>,
pub enabled: bool,
}
impl PyPlugin {
pub fn new(path: PathBuf, modify_time: Option<SystemTime>, module: Py<PyModule>) -> Self {
PyPlugin {
file_path: path.clone(),
modify_time,
py_module: module,
enabled: false,
}
}
/// 从文件创建一个新的
pub fn new_from_path(path: &PathBuf) -> Option<Self> {
let raw_file = load_py_file(path);
@ -169,7 +155,7 @@ impl PyPlugin {
Ok(raw_file) => match Self::try_from(raw_file) {
Ok(plugin) => {
self.py_module = plugin.py_module;
self.modify_time = plugin.modify_time;
self.changed_time = plugin.changed_time;
self.enabled = PyStatus::get().config.get_status(&self.get_id());
event!(Level::INFO, "更新 Python 插件文件 {:?} 完成", self.file_path);
true
@ -196,7 +182,7 @@ impl PyPlugin {
match get_change_time(&self.file_path) {
None => false,
Some(time) => {
if let Some(changed_time) = self.modify_time {
if let Some(changed_time) = self.changed_time {
time.eq(&changed_time)
} else {
true
@ -258,13 +244,13 @@ fn set_str_cfg_default_plugin(
}
// 给到 on config
if let Ok(attr) = module.getattr(intern!(module.py(), config_func::ON_CONFIG)) {
if let Ok(attr) = module.getattr(intern!(module.py(), ON_CONFIG_FUNC_NAME)) {
if !attr.is_callable() {
event!(
Level::WARN,
"Python 插件 {:?} 的 {} 函数不是 Callable",
path,
config_func::ON_CONFIG
ON_CONFIG_FUNC_NAME
);
return Ok(());
}
@ -274,7 +260,7 @@ fn set_str_cfg_default_plugin(
Level::WARN,
"Python 插件 {:?} 的 {} 函数返回了一个报错 {}",
path,
config_func::ON_CONFIG,
ON_CONFIG_FUNC_NAME,
e
);
}
@ -328,13 +314,13 @@ fn set_bytes_cfg_default_plugin(
}
// 给到 on config
if let Ok(attr) = module.getattr(intern!(module.py(), config_func::ON_CONFIG)) {
if let Ok(attr) = module.getattr(intern!(module.py(), ON_CONFIG_FUNC_NAME)) {
if !attr.is_callable() {
event!(
Level::WARN,
"Python 插件 {:?} 的 {} 函数不是 Callable",
path,
config_func::ON_CONFIG
ON_CONFIG_FUNC_NAME
);
return Ok(());
}
@ -344,7 +330,7 @@ fn set_bytes_cfg_default_plugin(
Level::WARN,
"Python 插件 {:?} 的 {} 函数返回了一个报错 {}",
path,
config_func::ON_CONFIG,
ON_CONFIG_FUNC_NAME,
e
);
}
@ -355,7 +341,7 @@ fn set_bytes_cfg_default_plugin(
impl TryFrom<RawPyPlugin> for PyPlugin {
type Error = PyErr;
fn try_from(value: RawPyPlugin) -> Result<Self, Self::Error> {
let (path, modify_time, content) = value;
let (path, changed_time, content) = value;
let py_module: Py<PyModule> = match py_module_from_code(&content, &path) {
Ok(module) => module,
Err(e) => {
@ -365,7 +351,7 @@ impl TryFrom<RawPyPlugin> for PyPlugin {
};
Python::with_gil(|py| {
let module = py_module.bind(py);
if let Ok(config_func) = call::get_func(module, config_func::REQUIRE_CONFIG) {
if let Ok(config_func) = call::get_func(module, REQUIRE_CONFIG_FUNC_NAME) {
match config_func.call0() {
Ok(config) => {
if config.is_instance_of::<PyTuple>() {
@ -382,20 +368,32 @@ impl TryFrom<RawPyPlugin> for PyPlugin {
"加载 Python 插件 {:?} 的配置文件信息时失败:返回的不是 [str, bytes | str]",
path
);
return Err(PyTypeError::new_err(
return Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
"返回的不是 [str, bytes | str]".to_string(),
));
}
Ok(PyPlugin::new(path, modify_time, module.clone().unbind()))
Ok(PyPlugin {
file_path: path,
changed_time,
py_module: module.clone().into_any().unbind(),
enabled: true,
})
} else if config.is_none() {
// 没有配置文件
Ok(PyPlugin::new(path, modify_time, module.clone().unbind()))
Ok(PyPlugin {
file_path: path,
changed_time,
py_module: module.clone().into_any().unbind(),
enabled: true,
})
} else {
warn!(
"加载 Python 插件 {:?} 的配置文件信息时失败:返回的不是 [str, str]",
path
);
Err(PyTypeError::new_err("返回的不是 [str, str]".to_string()))
Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
"返回的不是 [str, str]".to_string(),
))
}
}
Err(e) => {
@ -404,7 +402,12 @@ impl TryFrom<RawPyPlugin> for PyPlugin {
}
}
} else {
Ok(PyPlugin::new(path, modify_time, module.clone().unbind()))
Ok(PyPlugin {
file_path: path,
changed_time,
py_module: module.clone().into_any().unbind(),
enabled: true,
})
}
})
}
@ -467,8 +470,9 @@ pub fn py_module_from_code(content: &str, path: &Path) -> PyResult<Py<PyModule>>
.unwrap()
.as_c_str(),
// !!!! 请注意, 一定要给他一个名字, cpython 会自动把后面的重名模块覆盖掉前面的
)?;
Ok(module.unbind())
)
.map(|module| module.unbind());
module
})
}
@ -516,7 +520,7 @@ fn init_py_with_env_path(path: &str) {
event!(Level::INFO, "根据配置初始化 python 完成");
}
pyo3::ffi::_PyStatus_TYPE::_PyStatus_TYPE_EXIT => {
event!(Level::ERROR, "不对啊, 怎么刚刚初始化 Python 就 EXIT 了");
event!(Level::ERROR, "初始化 python 时发生错误: EXIT");
}
pyo3::ffi::_PyStatus_TYPE::_PyStatus_TYPE_ERROR => {
event!(Level::ERROR, "初始化 python 时发生错误: ERROR");
@ -532,15 +536,9 @@ pub fn init_py() {
let span = span!(Level::INFO, "py init");
let _enter = span.enter();
event!(Level::INFO, "开始初始化 python");
// 注册东西
class::regist_class();
let plugin_path = MainStatus::global_config().py().plugin_path;
let cli_args = std::env::args().collect::<Vec<String>>();
if cli_args.contains(&"-env".to_string()) {
let env_path = cli_args.iter().find(|&arg| arg != "-env").expect("未找到 -env 参数的值");
event!(Level::INFO, "找到 -env 参数: {} 正在初始化", env_path);
@ -577,13 +575,18 @@ pub async fn post_py() -> anyhow::Result<()> {
status.config.sync_status_to_config();
status.config.write_to_default()?;
stop_tasks().await?;
stop_tasks().await;
unsafe {
if !pyo3::ffi::Py_FinalizeEx() == 0 {
event!(Level::ERROR, "Python 退出失败 (不过应该无所谓)");
}
}
Ok(())
}
async fn stop_tasks() -> Result<(), PyPluginError> {
async fn stop_tasks() {
if call::PY_TASKS.lock().await.is_empty() {
return Ok(());
return;
}
let waiter = tokio::spawn(async {
call::PY_TASKS.lock().await.join_all().await;
@ -591,11 +594,10 @@ async fn stop_tasks() -> Result<(), PyPluginError> {
tokio::select! {
_ = waiter => {
event!(Level::INFO, "Python 任务完成");
Ok(())
}
_ = tokio::signal::ctrl_c() => {
event!(Level::WARN, "正在强制结束 Python 任务");
Err(PyPluginError::PluginNotStopped)
call::PY_TASKS.lock().await.cancel_all();
event!(Level::INFO, "Python 任务被中断");
}
}
}

View File

@ -1,5 +1,5 @@
use crate::MAIN_STATUS;
use crate::config::BotConfig;
use crate::MAIN_STATUS;
#[derive(Debug, Clone)]
pub struct BotStatus {

View File

@ -9,13 +9,13 @@ use reqwest::ClientBuilder as reqwest_ClientBuilder;
use rust_socketio::async_callback;
use rust_socketio::asynchronous::{Client, ClientBuilder};
use rust_socketio::{Event, Payload, TransportType};
use serde_json::{Value, json};
use tracing::{Level, event, span};
use serde_json::{json, Value};
use tracing::{event, span, Level};
use crate::config::TailchatConfig;
use crate::data_struct::tailchat::status::{BotStatus, LoginData};
use crate::error::{ClientResult, TailchatError};
use crate::{StopGetter, async_any_callback_with_state, async_callback_with_state, version_str};
use crate::{async_any_callback_with_state, async_callback_with_state, version_str, StopGetter};
pub async fn start_tailchat(
config: TailchatConfig,

View File

@ -4,8 +4,8 @@ use crate::data_struct::tailchat::messages::SendingMessage;
use colored::Colorize;
use reqwest::multipart;
use rust_socketio::asynchronous::Client;
use serde_json::{Value, json};
use tracing::{Level, event, span};
use serde_json::{json, Value};
use tracing::{event, span, Level};
pub async fn send_message(client: &Client, message: &SendingMessage) -> bool {
let span = span!(Level::INFO, "tailchat send message");

View File

@ -3,14 +3,14 @@ use std::sync::Arc;
use colored::Colorize;
use rust_socketio::asynchronous::Client;
use rust_socketio::{Event, Payload};
use tracing::{Level, event, info};
use tracing::{event, info, Level};
use crate::data_struct::tailchat::messages::ReceiveMessage;
use crate::data_struct::tailchat::status::{BotStatus, UpdateDMConverse};
use crate::py::PyStatus;
use crate::py::call::tailchat_new_message_py;
use crate::py::PyStatus;
use crate::tailchat::client::{emit_join_room, send_message};
use crate::{MainStatus, VERSION, client_id, help_msg, version_str};
use crate::{client_id, help_msg, version_str, MainStatus, VERSION};
/// 所有
pub async fn any_event(event: Event, payload: Payload, _client: Client, _status: Arc<BotStatus>) {

View File

@ -1,13 +1,6 @@
# 0.9 更新日志
## 0.9.0
- 修复了 Python 插件停不下来就真的停不下来的问题
- 让初始化的时候 插件启/禁状态显示更明显了
- 有颜色啦!
- 加了不少颜色
### ica 2.0.1
## ica 2.0.1
> 添加了 `Room` 相关的 api
@ -23,16 +16,6 @@
- `room_name -> int` 群名 (String)
- `unread_count -> int` 未读消息数 (u64)
- `priority -> int` 优先级 (u8)
- `utime -> int` 最后活跃时间 (unix sec * 1000)
- `def new_message_to(self, content: str) -> IcaSendMessage`
- 用于创建一条指向这个房间的消息
> 添加了 Ica 侧的相关配置获取
- `IcaStatus` 添加了 `admins(self) -> list[UserId]` 方法
- 用于获取当前所有的管理员
- `IcaStatus` 添加了 `blocked(self) -> list[UserId]` 方法
- 用于获取当前所有的被屏蔽的人

View File

@ -1,8 +1,9 @@
# icalingua bot
这是一个基于 icalingua-bridge 的 bot
这是一个基于 icalingua docker 版的 bot
[插件市场(确信)](https://github.com/shenjackyuanjie/shenbot-plugins)
> 出于某个企鹅, 和保护 作者 和 原项目 ( ica ) 的原因 \
> 功能请自行理解
## 通用环境准备
@ -14,14 +15,12 @@
choco install python
# 或者
scoop install python
# 又或者
uv venv
```
- 启动 icalingua 后端
```bash
# 用你自己的方法启动你的 icalingua-bridge
# 用你自己的方法启动你的 icalingua 后端
# 例如
docker start icalingua
docker-compose up -d