Compare commits

..

No commits in common. "c23b3ee67ac3a8239838a800d5bdb2cae387db01" and "16fee092bab3324a3d54df4a0ae700dae58f5160" have entirely different histories.

16 changed files with 440 additions and 881 deletions

534
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package] [package]
name = "ica-rs" name = "ica-rs"
version = "0.8.2" version = "0.8.0"
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
@ -36,12 +36,12 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
chrono = "0.4" chrono = "0.4"
toml = "0.8" toml = "0.8"
toml_edit = "0.22" toml_edit = "0.22.20"
colored = "3.0" colored = "2.1"
# runtime # runtime
tokio = { version = "1.43", features = ["full"] } tokio = { version = "1.41", features = ["full"] }
futures-util = "0.3" futures-util = "0.3.30"
pyo3 = { version = "0.23", features = ["experimental-async", "py-clone"] } pyo3 = { version = "0.23", features = ["experimental-async", "py-clone"] }
anyhow = { version = "1.0", features = ["backtrace"] } anyhow = { version = "1.0", features = ["backtrace"] }
# async 这玩意以后在搞 # async 这玩意以后在搞
@ -49,6 +49,5 @@ anyhow = { version = "1.0", features = ["backtrace"] }
# pyo3-asyncio = { version = "0.20.0", features = ["attributes", "tokio-runtime"] } # pyo3-asyncio = { version = "0.20.0", features = ["attributes", "tokio-runtime"] }
# log # log
tracing = "0.1" tracing = "0.1.40"
tracing-subscriber = { version = "0.3", features = ["time"] } tracing-subscriber = { version = "0.3.18", features = ["time"] }
foldhash = "0.1.4"

View File

@ -102,7 +102,7 @@ impl BotConfig {
pub fn new_from_cli() -> Self { pub fn new_from_cli() -> Self {
// let config_file_path = env::args().nth(1).expect("No config path given"); // let config_file_path = env::args().nth(1).expect("No config path given");
// -c <config_file_path> // -c <config_file_path>
let mut config_file_path = "./config.toml".to_string(); let mut config_file_path = String::new();
let mut args = env::args(); let mut args = env::args();
while let Some(arg) = args.next() { while let Some(arg) = args.next() {
if arg == "-c" { if arg == "-c" {

View File

@ -44,11 +44,9 @@ impl Room {
// ica issue: https://github.com/Icalingua-plus-plus/Icalingua-plus-plus/issues/793 // ica issue: https://github.com/Icalingua-plus-plus/Icalingua-plus-plus/issues/793
if parse_json.get("roomId").is_none_or(|id| id.is_null()) { if parse_json.get("roomId").is_none_or(|id| id.is_null()) {
use tracing::warn; use tracing::warn;
warn!("Room::new_from_json roomId is None, patching it to -1, raw: {:?}", raw_json); warn!("Room::new_from_json roomId is None, patching it to -1, raw: {:#?}", raw_json);
parse_json["roomId"] = JsonValue::Number(Number::from(-1)); parse_json["roomId"] = JsonValue::Number(Number::from(-1));
} }
// 现在 fix 了
let inner = match serde_json::from_value::<InnerRoom>(parse_json) { let inner = match serde_json::from_value::<InnerRoom>(parse_json) {
Ok(data) => data, Ok(data) => data,
Err(e) => { Err(e) => {

View File

@ -88,26 +88,7 @@ impl<'de> Deserialize<'de> for Message {
impl Display for Message { impl Display for Message {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if !self.content.is_empty() && !self.content.trim().is_empty() {
write!(f, "{}|{}|{}|{}", self.msg_id(), self.sender_id, self.sender_name, self.content) write!(f, "{}|{}|{}|{}", self.msg_id(), self.sender_id, self.sender_name, self.content)
} else if !self.files.is_empty() {
write!(
f,
"{}|{}|{}|{:?}",
self.msg_id(),
self.sender_id,
self.sender_name,
self.files[0].name
)
} else {
write!(
f,
"{}|{}|{}|empty content & empty files",
self.msg_id(),
self.sender_id,
self.sender_name
)
}
} }
} }
@ -133,32 +114,14 @@ impl MessageTrait for NewMessage {
impl Display for NewMessage { impl Display for NewMessage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if !self.msg.content.trim().is_empty() {
write!( write!(
f, f,
"{}|{}|{}|{}|{}", "{}|{}|{}|{}|{}",
self.msg.msg_id, self.msg_id(),
self.room_id, self.room_id,
self.msg.sender_id, self.msg.sender_id,
self.msg.sender_name, self.msg.sender_name,
self.msg.content self.msg.content
) )
} else if !self.msg.files.is_empty() {
write!(
f,
"{}|{}|{}|{}|{:?}",
self.msg.msg_id,
self.room_id,
self.msg.sender_id,
self.msg.sender_name,
self.msg.files[0]
)
} else {
write!(
f,
"{}|{}|{}|{}|empty content & empty files",
self.msg.msg_id, self.room_id, self.msg.sender_id, self.msg.sender_name
)
}
} }
} }

View File

@ -1,9 +1,8 @@
pub mod client; pub mod client;
pub mod events; pub mod events;
// use std::sync::OnceLock; use std::sync::OnceLock;
use colored::Colorize;
use rust_socketio::asynchronous::{Client, ClientBuilder}; use rust_socketio::asynchronous::{Client, ClientBuilder};
use rust_socketio::{async_any_callback, async_callback}; use rust_socketio::{async_any_callback, async_callback};
use rust_socketio::{Event, Payload, TransportType}; use rust_socketio::{Event, Payload, TransportType};
@ -14,33 +13,33 @@ use crate::error::{ClientResult, IcaError};
use crate::{version_str, StopGetter}; use crate::{version_str, StopGetter};
/// icalingua 客户端的兼容版本号 /// icalingua 客户端的兼容版本号
pub const ICA_PROTOCOL_VERSION: &str = "2.12.28"; pub const ICA_PROTOCOL_VERSION: &str = "2.12.24";
// mod status { mod status {
// use crate::data_struct::ica::all_rooms::Room; use crate::data_struct::ica::all_rooms::Room;
// pub use crate::data_struct::ica::online_data::OnlineData; pub use crate::data_struct::ica::online_data::OnlineData;
// #[derive(Debug, Clone)] #[derive(Debug, Clone)]
// pub struct MainStatus { pub struct MainStatus {
// /// 是否启用 ica /// 是否启用 ica
// pub enable: bool, pub enable: bool,
// /// qq 是否登录 /// qq 是否登录
// pub qq_login: bool, pub qq_login: bool,
// /// 当前已加载的消息数量 /// 当前已加载的消息数量
// pub current_loaded_messages_count: u64, pub current_loaded_messages_count: u64,
// /// 房间数据 /// 房间数据
// pub rooms: Vec<Room>, pub rooms: Vec<Room>,
// /// 在线数据 (Icalingua 信息) /// 在线数据 (Icalingua 信息)
// pub online_status: OnlineData, pub online_status: OnlineData,
// } }
// impl MainStatus { impl MainStatus {
// pub fn update_rooms(&mut self, room: Vec<Room>) { self.rooms = room; } pub fn update_rooms(&mut self, room: Vec<Room>) { self.rooms = room; }
// 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; }
// } }
// } }
// static ICA_STATUS: OnceLock<status::MainStatus> = OnceLock::new(); static ICA_STATUS: OnceLock<status::MainStatus> = OnceLock::new();
pub async fn start_ica(config: &IcaConfig, stop_reciver: StopGetter) -> ClientResult<(), IcaError> { pub async fn start_ica(config: &IcaConfig, stop_reciver: StopGetter) -> ClientResult<(), IcaError> {
let span = span!(Level::INFO, "Icalingua Client"); let span = span!(Level::INFO, "Icalingua Client");
@ -48,7 +47,6 @@ pub async fn start_ica(config: &IcaConfig, stop_reciver: StopGetter) -> ClientRe
event!(Level::INFO, "ica-async-rs v{} initing", crate::ICA_VERSION); event!(Level::INFO, "ica-async-rs v{} initing", crate::ICA_VERSION);
let start_connect_time = std::time::Instant::now();
let socket = match ClientBuilder::new(config.host.clone()) let socket = match ClientBuilder::new(config.host.clone())
.transport_type(TransportType::Websocket) .transport_type(TransportType::Websocket)
.on_any(async_any_callback!(events::any_event)) .on_any(async_any_callback!(events::any_event))
@ -67,11 +65,7 @@ pub async fn start_ica(config: &IcaConfig, stop_reciver: StopGetter) -> ClientRe
.await .await
{ {
Ok(client) => { Ok(client) => {
event!( event!(Level::INFO, "socketio connected");
Level::INFO,
"{}",
format!("socketio connected time: {:?}", start_connect_time.elapsed()).on_cyan()
);
client client
} }
Err(e) => { Err(e) => {
@ -100,7 +94,6 @@ pub async fn start_ica(config: &IcaConfig, stop_reciver: StopGetter) -> ClientRe
} }
} }
// 等待停止信号 // 等待停止信号
event!(Level::INFO, "ica client waiting for stop signal");
stop_reciver.await.ok(); stop_reciver.await.ok();
event!(Level::INFO, "socketio client stopping"); event!(Level::INFO, "socketio client stopping");
match socket.disconnect().await { match socket.disconnect().await {

View File

@ -7,7 +7,7 @@ use colored::Colorize;
use ed25519_dalek::{Signature, Signer, SigningKey}; use ed25519_dalek::{Signature, Signer, SigningKey};
use rust_socketio::asynchronous::Client; use rust_socketio::asynchronous::Client;
use rust_socketio::Payload; use rust_socketio::Payload;
use serde_json::{json, Value}; use serde_json::{Value, json};
use tracing::{debug, event, span, warn, Level}; use tracing::{debug, event, span, warn, Level};
/// "安全" 的 发送一条消息 /// "安全" 的 发送一条消息
@ -129,8 +129,10 @@ pub async fn send_room_sign_in(client: &Client, room_id: RoomId) -> bool {
/// 向某个群/私聊的某个人发送戳一戳 /// 向某个群/私聊的某个人发送戳一戳
pub async fn send_poke(client: &Client, room_id: RoomId, target: UserId) -> bool { pub async fn send_poke(client: &Client, room_id: RoomId, target: UserId) -> bool {
let data = vec![json!(room_id), json!(target)]; let data = json!([room_id, target]);
match client.emit("sendGroupPoke", data).await { match client.emit(
"sendGroupPoke", data
).await {
Ok(_) => { Ok(_) => {
event!(Level::INFO, "已向 {} 的 {} 发送戳一戳", room_id, target); event!(Level::INFO, "已向 {} 的 {} 发送戳一戳", room_id, target);
true true

View File

@ -1,15 +1,15 @@
use std::path::PathBuf;
use colored::Colorize; use colored::Colorize;
use rust_socketio::asynchronous::Client; use rust_socketio::asynchronous::Client;
use rust_socketio::{Event, Payload}; use rust_socketio::{Event, Payload};
use serde_json::json;
use tracing::{event, info, span, warn, Level}; use tracing::{event, info, span, warn, Level};
use crate::data_struct::ica::all_rooms::Room; 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::data_struct::ica::RoomId;
use crate::ica::client::send_message; use crate::ica::client::send_message;
use crate::{client_id, help_msg, py, version_str, MainStatus, VERSION}; use crate::{client_id, help_msg, py, start_up_time, version_str, MainStatus, VERSION};
/// 获取在线数据 /// 获取在线数据
pub async fn get_online_data(payload: Payload, _client: Client) { pub async fn get_online_data(payload: Payload, _client: Client) {
@ -32,7 +32,7 @@ pub async fn add_message(payload: Payload, client: Client) {
return; return;
} }
println!("new_msg {}", message.to_string().cyan()); event!(Level::INFO, "new_msg {}", message.to_string().cyan());
// 就在这里处理掉最基本的消息 // 就在这里处理掉最基本的消息
// 之后的处理交给插件 // 之后的处理交给插件
if !message.is_from_self() && !message.is_reply() { if !message.is_from_self() && !message.is_reply() {
@ -72,7 +72,8 @@ pub async fn add_message(payload: Payload, client: Client) {
if message.content().starts_with(&format!("/bot-enable-{}", client_id)) { if message.content().starts_with(&format!("/bot-enable-{}", client_id)) {
// 尝试获取后面的信息 // 尝试获取后面的信息
if let Some((_, name)) = message.content().split_once(" ") { if let Some((_, name)) = message.content().split_once(" ") {
match py::PyStatus::get().get_status(name) { let path_name = PathBuf::from(name);
match py::PyStatus::get().get_status(&path_name) {
None => { None => {
let reply = message.reply_with("未找到插件"); let reply = message.reply_with("未找到插件");
send_message(&client, &reply).await; send_message(&client, &reply).await;
@ -82,7 +83,7 @@ pub async fn add_message(payload: Payload, client: Client) {
send_message(&client, &reply).await; send_message(&client, &reply).await;
} }
Some(false) => { Some(false) => {
py::PyStatus::get_mut().set_status(name, true); py::PyStatus::get_mut().set_status(&path_name, true);
let reply = message.reply_with("启用插件完成"); let reply = message.reply_with("启用插件完成");
send_message(&client, &reply).await; send_message(&client, &reply).await;
} }
@ -91,7 +92,8 @@ pub async fn add_message(payload: Payload, client: Client) {
} else if message.content().starts_with(&format!("/bot-disable-{}", client_id)) } else if message.content().starts_with(&format!("/bot-disable-{}", client_id))
{ {
if let Some((_, name)) = message.content().split_once(" ") { if let Some((_, name)) = message.content().split_once(" ") {
match py::PyStatus::get().get_status(name) { let path_name = PathBuf::from(name);
match py::PyStatus::get().get_status(&path_name) {
None => { None => {
let reply = message.reply_with("未找到插件"); let reply = message.reply_with("未找到插件");
send_message(&client, &reply).await; send_message(&client, &reply).await;
@ -101,16 +103,12 @@ pub async fn add_message(payload: Payload, client: Client) {
send_message(&client, &reply).await; send_message(&client, &reply).await;
} }
Some(true) => { Some(true) => {
py::PyStatus::get_mut().set_status(name, false); py::PyStatus::get_mut().set_status(&path_name, false);
let reply = message.reply_with("禁用插件完成"); let reply = message.reply_with("禁用插件完成");
send_message(&client, &reply).await; send_message(&client, &reply).await;
} }
} }
} }
} else if message.content() == "/bot-fetch" {
let reply = message.reply_with("正在更新当前群消息");
send_message(&client, &reply).await;
fetch_messages(&client, message.room_id).await;
} }
} }
} }
@ -175,18 +173,6 @@ pub async fn failed_message(payload: Payload, _client: Client) {
} }
} }
pub async fn fetch_history(client: Client, room: RoomId) { let mut request_body = json!(room); }
pub async fn fetch_messages(client: &Client, room: RoomId) {
let mut request_body = json!(room);
match client.emit("fetchMessages", request_body).await {
Ok(_) => {}
Err(e) => {
event!(Level::WARN, "fetch_messages {}", e);
}
}
}
/// 所有 /// 所有
pub async fn any_event(event: Event, payload: Payload, _client: Client) { pub async fn any_event(event: Event, payload: Payload, _client: Client) {
let handled = vec![ let handled = vec![
@ -207,7 +193,6 @@ pub async fn any_event(event: Event, payload: Payload, _client: Client) {
"handleRequest", // 处理验证消息 (加入请求之类的) "handleRequest", // 处理验证消息 (加入请求之类的)
// 忽略的 // 忽略的
"notify", "notify",
"setShutUp", // 禁言
"syncRead", // 同步已读 "syncRead", // 同步已读
"closeLoading", // 发送消息/加载新聊天 有一个 loading "closeLoading", // 发送消息/加载新聊天 有一个 loading
"renewMessage", // 我也不确定到底是啥事件 "renewMessage", // 我也不确定到底是啥事件

View File

@ -29,8 +29,8 @@ pub type MainStatus = status::BotStatus;
pub type StopGetter = tokio::sync::oneshot::Receiver<()>; 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.6.7"; pub const ICA_VERSION: &str = "1.6.5";
pub const TAILCHAT_VERSION: &str = "1.2.6"; pub const TAILCHAT_VERSION: &str = "1.2.4";
const HELP_MSG: &str = r#"/bot-rs const HELP_MSG: &str = r#"/bot-rs
rust rust
@ -46,9 +46,7 @@ const HELP_MSG: &str = r#"/bot-rs
by shenjackyuanjie"#; by shenjackyuanjie"#;
/// 获取帮助信息 /// 获取帮助信息
pub fn help_msg() -> String { pub fn help_msg() -> String { format!("{}\n{}", version_str(), HELP_MSG) }
format!("{}\n{}", version_str(), HELP_MSG).replace("<client-id>", client_id().as_str())
}
static STARTUP_TIME: OnceLock<SystemTime> = OnceLock::new(); static STARTUP_TIME: OnceLock<SystemTime> = OnceLock::new();
@ -79,7 +77,7 @@ pub fn version_str() -> String {
/// 是否为稳定版本 /// 是否为稳定版本
/// 会在 release 的时候设置为 true /// 会在 release 的时候设置为 true
pub const STABLE: bool = true; pub const STABLE: bool = false;
#[macro_export] #[macro_export]
macro_rules! async_callback_with_state { macro_rules! async_callback_with_state {
@ -101,32 +99,25 @@ macro_rules! async_any_callback_with_state {
}}; }};
} }
const CLI_HELP_MSG: &str = r#"{VERSION}
-d
debug
-t
trace
-h
-env <env>
-c <config_file_path>
"#;
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
let start_up_time = SystemTime::now(); let start_up_time = SystemTime::now();
STARTUP_TIME.set(start_up_time).expect("WTF, why did you panic?"); STARTUP_TIME.set(start_up_time).expect("WTF, why did you panic?");
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.thread_name("shenbot-rs")
.worker_threads(4)
.build()
.unwrap()
.block_on(inner_main())
}
async fn inner_main() -> anyhow::Result<()> {
// -d -> debug // -d -> debug
// none -> info // none -> info
let level = {
let args = std::env::args(); let args = std::env::args();
let args = args.collect::<Vec<String>>(); let args = args.collect::<Vec<String>>();
if args.contains(&"-h".to_string()) {
println!("{}", CLI_HELP_MSG.replace("{VERSION}", version_str().as_str()));
return Ok(());
}
let level = {
if args.contains(&"-d".to_string()) { if args.contains(&"-d".to_string()) {
Level::DEBUG Level::DEBUG
} else if args.contains(&"-t".to_string()) { } else if args.contains(&"-t".to_string()) {
@ -137,20 +128,6 @@ fn main() -> anyhow::Result<()> {
}; };
tracing_subscriber::fmt().with_max_level(level).init(); tracing_subscriber::fmt().with_max_level(level).init();
let _ = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.thread_name("shenbot-rs")
.worker_threads(10)
.build()
.unwrap()
.block_on(inner_main());
event!(Level::INFO, "shenbot-rs v{} exiting", VERSION);
Ok(())
}
async fn inner_main() -> anyhow::Result<()> {
let span = span!(Level::INFO, "Shenbot Main"); let span = span!(Level::INFO, "Shenbot Main");
let _enter = span.enter(); let _enter = span.enter();
@ -172,6 +149,7 @@ async fn inner_main() -> anyhow::Result<()> {
let (ica_send, ica_recv) = tokio::sync::oneshot::channel::<()>(); let (ica_send, ica_recv) = tokio::sync::oneshot::channel::<()>();
if bot_config.check_ica() { if bot_config.check_ica() {
event!(Level::INFO, "启动 ica");
let config = bot_config.ica(); let config = bot_config.ica();
tokio::spawn(async move { tokio::spawn(async move {
ica::start_ica(&config, ica_recv).await.unwrap(); ica::start_ica(&config, ica_recv).await.unwrap();
@ -192,19 +170,18 @@ async fn inner_main() -> anyhow::Result<()> {
event!(Level::INFO, "未启用 Tailchat"); event!(Level::INFO, "未启用 Tailchat");
} }
tokio::time::sleep(Duration::from_secs(1)).await; tokio::time::sleep(Duration::from_secs(2)).await;
// 等待一个输入 // 等待一个输入
event!(Level::INFO, "Press ctrl+c to exit, second ctrl+c to force exit"); event!(Level::INFO, "Press any key to exit");
tokio::signal::ctrl_c().await.ok(); let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
ica_send.send(()).ok(); ica_send.send(()).ok();
tailchat_send.send(()).ok(); tailchat_send.send(()).ok();
event!(Level::INFO, "Disconnected"); event!(Level::INFO, "Disconnected");
py::post_py().await?; py::post_py()?;
event!(Level::INFO, "Shenbot-rs exiting");
Ok(()) Ok(())
} }

View File

@ -1,9 +1,7 @@
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::LazyLock;
use pyo3::prelude::*; use pyo3::prelude::*;
use rust_socketio::asynchronous::Client; use rust_socketio::asynchronous::Client;
use tokio::sync::Mutex;
use tracing::{event, info, warn, Level}; use tracing::{event, info, warn, Level};
use crate::data_struct::{ica, tailchat}; use crate::data_struct::{ica, tailchat};
@ -11,74 +9,6 @@ use crate::error::PyPluginError;
use crate::py::{class, PyPlugin, PyStatus}; use crate::py::{class, PyPlugin, PyStatus};
use crate::MainStatus; use crate::MainStatus;
pub struct PyTasks {
pub ica_new_message: Vec<tokio::task::JoinHandle<()>>,
pub ica_delete_message: Vec<tokio::task::JoinHandle<()>>,
pub tailchat_new_message: Vec<tokio::task::JoinHandle<()>>,
}
impl PyTasks {
pub fn push_ica_new_message(&mut self, handle: tokio::task::JoinHandle<()>) {
self.ica_new_message.push(handle);
self.ica_new_message.retain(|handle| !handle.is_finished());
}
pub fn push_ica_delete_message(&mut self, handle: tokio::task::JoinHandle<()>) {
self.ica_delete_message.push(handle);
self.ica_delete_message.retain(|handle| !handle.is_finished());
}
pub fn push_tailchat_new_message(&mut self, handle: tokio::task::JoinHandle<()>) {
self.tailchat_new_message.push(handle);
self.tailchat_new_message.retain(|handle| !handle.is_finished());
}
pub async fn join_all(&mut self) {
for handle in self.ica_new_message.drain(..) {
let _ = handle.await;
}
for handle in self.ica_delete_message.drain(..) {
let _ = handle.await;
}
for handle in self.tailchat_new_message.drain(..) {
let _ = handle.await;
}
}
pub fn len_check(&mut self) -> usize {
self.ica_delete_message.retain(|handle| !handle.is_finished());
self.ica_new_message.retain(|handle| !handle.is_finished());
self.tailchat_new_message.retain(|handle| !handle.is_finished());
self.ica_new_message.len() + self.ica_delete_message.len() + self.tailchat_new_message.len()
}
pub fn len(&self) -> usize {
self.ica_new_message.len() + self.ica_delete_message.len() + self.tailchat_new_message.len()
}
pub fn is_empty(&self) -> bool { self.len() == 0 }
pub fn cancel_all(&mut self) {
for handle in self.ica_new_message.drain(..) {
handle.abort();
}
for handle in self.ica_delete_message.drain(..) {
handle.abort();
}
for handle in self.tailchat_new_message.drain(..) {
handle.abort();
}
}
}
pub static PY_TASKS: LazyLock<Mutex<PyTasks>> = LazyLock::new(|| {
Mutex::new(PyTasks {
ica_new_message: Vec::new(),
ica_delete_message: Vec::new(),
tailchat_new_message: Vec::new(),
})
});
pub fn get_func<'py>( pub fn get_func<'py>(
py_module: &Bound<'py, PyAny>, py_module: &Bound<'py, PyAny>,
name: &'py str, name: &'py str,
@ -224,8 +154,8 @@ pub async fn ica_new_message_py(message: &ica::messages::NewMessage, client: &Cl
let msg = class::ica::NewMessagePy::new(message); let msg = class::ica::NewMessagePy::new(message);
let client = class::ica::IcaClientPy::new(client); let client = class::ica::IcaClientPy::new(client);
let args = (msg, client); let args = (msg, client);
let task = call_py_func!(args, plugin, path, ICA_NEW_MESSAGE_FUNC, client); // 甚至实际上压根不需要await这个spawn, 直接让他自己跑就好了(离谱)
PY_TASKS.lock().await.push_ica_new_message(task); call_py_func!(args, plugin, path, ICA_NEW_MESSAGE_FUNC, client);
} }
} }
@ -237,8 +167,7 @@ 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);
let task = call_py_func!(args, plugin, path, ICA_DELETE_MESSAGE_FUNC, client); call_py_func!(args, plugin, path, ICA_DELETE_MESSAGE_FUNC, client);
PY_TASKS.lock().await.push_ica_delete_message(task);
} }
} }
@ -253,7 +182,6 @@ pub async fn tailchat_new_message_py(
let msg = class::tailchat::TailchatReceiveMessagePy::from_recive_message(message); let msg = class::tailchat::TailchatReceiveMessagePy::from_recive_message(message);
let client = class::tailchat::TailchatClientPy::new(client); let client = class::tailchat::TailchatClientPy::new(client);
let args = (msg, client); let args = (msg, client);
let task = call_py_func!(args, plugin, path, TAILCHAT_NEW_MESSAGE_FUNC, client); call_py_func!(args, plugin, path, TAILCHAT_NEW_MESSAGE_FUNC, client);
PY_TASKS.lock().await.push_tailchat_new_message(task);
} }
} }

View File

@ -10,7 +10,6 @@ use crate::data_struct::ica::messages::{
}; };
use crate::data_struct::ica::{MessageId, RoomId, RoomIdTrait, UserId}; 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::ica::client::{delete_message, send_message, send_poke, send_room_sign_in};
use crate::py::PyStatus;
use crate::MainStatus; use crate::MainStatus;
#[pyclass] #[pyclass]
@ -146,10 +145,6 @@ impl SendMessagePy {
pub fn get_content(&self) -> String { self.msg.content.clone() } pub fn get_content(&self) -> String { self.msg.content.clone() }
#[setter] #[setter]
pub fn set_content(&mut self, content: String) { self.msg.content = content; } pub fn set_content(&mut self, content: String) { self.msg.content = content; }
#[getter]
pub fn get_room_id(&self) -> RoomId { self.msg.room_id }
#[setter]
pub fn set_room_id(&mut self, room_id: RoomId) { self.msg.room_id = room_id; }
/// 设置消息图片 /// 设置消息图片
pub fn set_img(&mut self, file: Vec<u8>, file_type: String, as_sticker: bool) { pub fn set_img(&mut self, file: Vec<u8>, file_type: String, as_sticker: bool) {
self.msg.set_img(&file, &file_type, as_sticker); self.msg.set_img(&file, &file_type, as_sticker);
@ -255,38 +250,6 @@ impl IcaClientPy {
#[getter] #[getter]
pub fn get_startup_time(&self) -> SystemTime { crate::start_up_time() } pub fn get_startup_time(&self) -> SystemTime { crate::start_up_time() }
#[getter]
pub fn get_py_tasks_count(&self) -> usize {
tokio::task::block_in_place(|| {
let rt = Runtime::new().unwrap();
rt.block_on(async { crate::py::call::PY_TASKS.lock().await.len_check() })
})
}
/// 重新加载插件状态
/// 返回是否成功
pub fn reload_plugin_status(&self) -> bool { PyStatus::get_mut().config.reload_from_default() }
/// 设置某个插件的状态
pub fn set_plugin_status(&self, plugin_name: String, status: bool) {
PyStatus::get_mut().set_status(&plugin_name, status);
}
pub fn get_plugin_status(&self, plugin_name: String) -> Option<bool> {
PyStatus::get().get_status(&plugin_name)
}
/// 同步状态到配置文件
/// 这样关闭的时候就会保存状态
pub fn sync_status_to_config(&self) { PyStatus::get_mut().config.sync_status_to_config(); }
/// 重新加载插件
///
/// 返回是否成功
pub fn reload_plugin(&self, plugin_name: String) -> bool {
PyStatus::get_mut().reload_plugin(&plugin_name)
}
pub fn debug(&self, content: String) { pub fn debug(&self, content: String) {
event!(Level::DEBUG, "{}", content); event!(Level::DEBUG, "{}", content);
} }

View File

@ -3,12 +3,10 @@ use std::time::SystemTime;
use pyo3::prelude::*; use pyo3::prelude::*;
use rust_socketio::asynchronous::Client; use rust_socketio::asynchronous::Client;
use tokio::runtime::Runtime;
use tracing::{debug, info, warn}; use tracing::{debug, info, warn};
use crate::data_struct::tailchat::messages::{ReceiveMessage, SendingFile, SendingMessage}; use crate::data_struct::tailchat::messages::{ReceiveMessage, SendingFile, SendingMessage};
use crate::data_struct::tailchat::{ConverseId, GroupId, MessageId, UserId}; use crate::data_struct::tailchat::{ConverseId, GroupId, MessageId, UserId};
use crate::py::PyStatus;
use crate::tailchat::client::send_message; use crate::tailchat::client::send_message;
#[pyclass] #[pyclass]
@ -75,38 +73,6 @@ impl TailchatClientPy {
#[getter] #[getter]
pub fn get_startup_time(&self) -> SystemTime { crate::start_up_time() } pub fn get_startup_time(&self) -> SystemTime { crate::start_up_time() }
#[getter]
pub fn get_py_tasks_count(&self) -> usize {
tokio::task::block_in_place(|| {
let rt = Runtime::new().unwrap();
rt.block_on(async { crate::py::call::PY_TASKS.lock().await.len_check() })
})
}
/// 重新加载插件状态
/// 返回是否成功
pub fn reload_plugin_status(&self) -> bool { PyStatus::get_mut().config.reload_from_default() }
/// 设置某个插件的状态
pub fn set_plugin_status(&self, plugin_name: String, status: bool) {
PyStatus::get_mut().set_status(&plugin_name, status);
}
pub fn get_plugin_status(&self, plugin_name: String) -> Option<bool> {
PyStatus::get().get_status(&plugin_name)
}
/// 同步状态到配置文件
/// 这样关闭的时候就会保存状态
pub fn sync_status_to_config(&self) { PyStatus::get_mut().config.sync_status_to_config(); }
/// 重新加载插件
///
/// 返回是否成功
pub fn reload_plugin(&self, plugin_name: String) -> bool {
PyStatus::get_mut().reload_plugin(&plugin_name)
}
#[pyo3(signature = (content, converse_id, group_id = None))] #[pyo3(signature = (content, converse_id, group_id = None))]
pub fn new_message( pub fn new_message(
&self, &self,

View File

@ -1,10 +1,12 @@
use std::{path::Path, str::FromStr}; use std::{
path::{Path, PathBuf},
str::FromStr,
};
use toml_edit::{value, DocumentMut, Key, Table, TomlError, Value}; use toml_edit::{value, DocumentMut, Key, Table, TomlError, Value};
use tracing::{event, Level}; use tracing::{event, Level};
use crate::py::PyStatus; use crate::py::PyStatus;
use crate::MainStatus;
/// ```toml /// ```toml
/// # 这个文件是由 shenbot 自动生成的, 请 **谨慎** 修改 /// # 这个文件是由 shenbot 自动生成的, 请 **谨慎** 修改
@ -29,38 +31,14 @@ pub const DEFAULT_CONFIG: &str = r#"
impl PluginConfigFile { impl PluginConfigFile {
pub fn from_str(data: &str) -> Result<Self, TomlError> { pub fn from_str(data: &str) -> Result<Self, TomlError> {
let mut data = DocumentMut::from_str(data)?; let data = DocumentMut::from_str(data)?;
if data.get(CONFIG_KEY).is_none() {
event!(Level::WARN, "插件配置文件缺少 plugins 字段, 正在初始化");
data.insert_formatted(
&Key::from_str(CONFIG_KEY).unwrap(),
toml_edit::Item::Table(Table::new()),
);
}
Ok(Self { data }) Ok(Self { data })
} }
pub fn default_init() -> anyhow::Result<Self> {
let config_path = MainStatus::global_config().py().config_path.clone();
let path = Path::new(&config_path);
Self::from_config_path(path)
}
pub fn reload_from_default(&mut self) -> bool {
let new_config = Self::default_init();
if let Err(e) = new_config {
event!(Level::ERROR, "从配置文件重加载时遇到错误: {}", e);
return false;
}
let new_config = new_config.unwrap();
self.data = new_config.data;
true
}
pub fn from_config_path(path: &Path) -> anyhow::Result<Self> { pub fn from_config_path(path: &Path) -> anyhow::Result<Self> {
let config_path = path.join(CONFIG_FILE_NAME); let config_path = path.join(CONFIG_FILE_NAME);
if !config_path.exists() { if !config_path.exists() {
event!(Level::WARN, "插件配置文件不存在, 正在创建"); event!(Level::INFO, "插件配置文件不存在, 正在创建");
std::fs::write(&config_path, DEFAULT_CONFIG)?; std::fs::write(&config_path, DEFAULT_CONFIG)?;
Ok(Self::from_str(DEFAULT_CONFIG)?) Ok(Self::from_str(DEFAULT_CONFIG)?)
} else { } else {
@ -69,6 +47,16 @@ impl PluginConfigFile {
} }
} }
pub fn verify_and_init(&mut self) {
if self.data.get(CONFIG_KEY).is_none() {
event!(Level::INFO, "插件配置文件缺少 plugins 字段, 正在初始化");
self.data.insert_formatted(
&Key::from_str(CONFIG_KEY).unwrap(),
toml_edit::Item::Table(Table::new()),
);
}
}
fn get_table(&self) -> Option<&Table> { fn get_table(&self) -> Option<&Table> {
self.data.get(CONFIG_KEY).and_then(|item| item.as_table()) self.data.get(CONFIG_KEY).and_then(|item| item.as_table())
} }
@ -79,10 +67,11 @@ impl PluginConfigFile {
/// 获取插件状态 /// 获取插件状态
/// 默认为 true /// 默认为 true
pub fn get_status(&self, plugin_id: &str) -> bool { pub fn get_status(&self, path: &Path) -> bool {
let path_str = path.to_str().unwrap();
if let Some(item) = self.data.get(CONFIG_KEY) { if let Some(item) = self.data.get(CONFIG_KEY) {
if let Some(table) = item.as_table() { if let Some(table) = item.as_table() {
if let Some(item) = table.get(plugin_id) { if let Some(item) = table.get(path_str) {
if let Some(bool) = item.as_bool() { if let Some(bool) = item.as_bool() {
return bool; return bool;
} }
@ -111,6 +100,7 @@ impl PluginConfigFile {
/// 设置插件状态 /// 设置插件状态
pub fn set_status(&mut self, path: &Path, status: bool) { pub fn set_status(&mut self, path: &Path, status: bool) {
self.verify_and_init();
let path_str = path.to_str().unwrap(); let path_str = path.to_str().unwrap();
let table = self.data.get_mut(CONFIG_KEY).unwrap().as_table_mut().unwrap(); let table = self.data.get_mut(CONFIG_KEY).unwrap().as_table_mut().unwrap();
if table.contains_key(path_str) { if table.contains_key(path_str) {
@ -125,47 +115,27 @@ impl PluginConfigFile {
} }
} }
/// 从默认文件读取状态 pub fn sync_status_from_config(&mut self) {
///
/// 返回是否成功
pub fn read_status_from_default(&mut self) -> bool {
if !self.reload_from_default() {
return false;
}
event!(Level::INFO, "同步插件状态");
let plugins = PyStatus::get_mut(); let plugins = PyStatus::get_mut();
self.verify_and_init();
plugins.files.iter_mut().for_each(|(path, status)| { plugins.files.iter_mut().for_each(|(path, status)| {
let plugin_id = status.get_id(); let config_status = self.get_status(path);
let config_status = self.get_status(&plugin_id); event!(Level::INFO, "插件状态: {:?} {} -> {}", path, status.enabled, config_status);
event!(
Level::INFO,
"插件状态: {}({:?}) {} -> {}",
status.get_id(),
path,
status.enabled,
config_status
);
status.enabled = config_status; status.enabled = config_status;
}); });
true
} }
pub fn sync_status_to_config(&mut self) { pub fn sync_status_to_config(&mut self) {
let plugins = PyStatus::get(); let plugins = PyStatus::get();
self.verify_and_init();
let table = self.data.get_mut(CONFIG_KEY).unwrap().as_table_mut().unwrap(); let table = self.data.get_mut(CONFIG_KEY).unwrap().as_table_mut().unwrap();
table.clear(); table.clear();
plugins.files.iter().for_each(|(_, status)| { plugins.files.iter().for_each(|(path, status)| {
table.insert(&status.get_id(), value(status.enabled)); table.insert(path.to_str().unwrap(), value(status.enabled));
}); });
} }
pub fn write_to_default(&self) -> Result<(), std::io::Error> { pub fn write_to_file(&self, path: &PathBuf) -> Result<(), std::io::Error> {
let config_path = MainStatus::global_config().py().config_path.clone();
let config_path = Path::new(&config_path);
self.write_to_file(config_path)
}
pub fn write_to_file(&self, path: &Path) -> Result<(), std::io::Error> {
let config_path = path.join(CONFIG_FILE_NAME); let config_path = path.join(CONFIG_FILE_NAME);
std::fs::write(config_path, self.data.to_string())?; std::fs::write(config_path, self.data.to_string())?;
Ok(()) Ok(())

View File

@ -2,8 +2,7 @@ pub mod call;
pub mod class; pub mod class;
pub mod config; pub mod config;
use std::ffi::{CString, OsStr}; use std::ffi::CString;
use std::fmt::Display;
use std::path::Path; use std::path::Path;
use std::sync::OnceLock; use std::sync::OnceLock;
use std::time::SystemTime; use std::time::SystemTime;
@ -12,7 +11,7 @@ use std::{collections::HashMap, path::PathBuf};
use colored::Colorize; use colored::Colorize;
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::types::PyTuple; use pyo3::types::PyTuple;
use tracing::{event, span, warn, Level}; use tracing::{event, info, span, warn, Level};
use crate::MainStatus; use crate::MainStatus;
@ -30,8 +29,10 @@ static mut PyPluginStatus: OnceLock<PyStatus> = OnceLock::new();
impl PyStatus { impl PyStatus {
pub fn init() { pub fn init() {
let config = let plugin_path = MainStatus::global_config().py().plugin_path.clone();
config::PluginConfigFile::default_init().expect("初始化 Python 插件配置文件失败"); let mut config =
config::PluginConfigFile::from_config_path(&PathBuf::from(plugin_path)).unwrap();
config.verify_and_init();
let status = PyStatus { let status = PyStatus {
files: HashMap::new(), files: HashMap::new(),
config, config,
@ -43,57 +44,37 @@ impl PyStatus {
pub fn get_mut() -> &'static mut PyStatus { unsafe { PyPluginStatus.get_mut().unwrap() } } pub fn get_mut() -> &'static mut PyStatus { unsafe { PyPluginStatus.get_mut().unwrap() } }
/// 添加一个插件
pub fn add_file(&mut self, path: PathBuf, plugin: PyPlugin) { self.files.insert(path, plugin); } pub fn add_file(&mut self, path: PathBuf, plugin: PyPlugin) { self.files.insert(path, plugin); }
/// 重新加载一个插件
pub fn reload_plugin(&mut self, plugin_name: &str) -> bool {
let plugin = self.files.iter_mut().find_map(|(_, plugin)| {
if plugin.get_id() == plugin_name {
Some(plugin)
} else {
None
}
});
if let Some(plugin) = plugin {
plugin.reload_from_file()
} else {
event!(Level::WARN, "没有找到插件: {}", plugin_name);
false
}
}
/// 删除一个插件 /// 删除一个插件
pub fn delete_file(&mut self, path: &PathBuf) -> Option<PyPlugin> { self.files.remove(path) } pub fn delete_file(&mut self, path: &PathBuf) -> Option<PyPlugin> { self.files.remove(path) }
pub fn get_status(&self, pluging_id: &str) -> Option<bool> {
self.files.iter().find_map(|(_, plugin)| {
if plugin.get_id() == pluging_id {
return Some(plugin.enabled);
}
None
})
}
pub fn set_status(&mut self, pluging_id: &str, status: bool) {
self.files.iter_mut().for_each(|(_, plugin)| {
if plugin.get_id() == pluging_id {
plugin.enabled = status;
}
});
}
pub fn verify_file(&self, path: &PathBuf) -> bool { pub fn verify_file(&self, path: &PathBuf) -> bool {
self.files.get(path).is_some_and(|plugin| plugin.verifiy()) self.files.get(path).is_some_and(|plugin| plugin.verifiy())
} }
/// 获取某个插件的状态
/// 以 config 优先
pub fn get_status(&self, path: &PathBuf) -> Option<bool> {
self.files.get(path).map(|plugin| plugin.enabled)
}
pub fn sync_status(&mut self) { self.config.sync_status_from_config(); }
pub fn set_status(&mut self, path: &Path, status: bool) {
self.config.set_status(path, status);
if let Some(plugin) = self.files.get_mut(path) {
plugin.enabled = status;
}
}
pub fn display() -> String { pub fn display() -> String {
format!( format!(
"Python 插件 {{ {} }}", "Python 插件 {{ {} }}",
Self::get() Self::get()
.files .files
.values() .iter()
.map(|v| v.to_string()) .map(|(k, v)| format!("{:?}-{}", k, v.enabled))
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join("\n") .join("\n")
) )
@ -145,16 +126,15 @@ impl PyPlugin {
} }
/// 从文件更新 /// 从文件更新
pub fn reload_from_file(&mut self) -> bool { pub fn reload_from_file(&mut self) {
let raw_file = load_py_file(&self.file_path); let raw_file = load_py_file(&self.file_path);
match raw_file { match raw_file {
Ok(raw_file) => match Self::try_from(raw_file) { Ok(raw_file) => match Self::try_from(raw_file) {
Ok(plugin) => { Ok(plugin) => {
self.py_module = plugin.py_module; self.py_module = plugin.py_module;
self.changed_time = plugin.changed_time; self.changed_time = plugin.changed_time;
self.enabled = PyStatus::get().config.get_status(&self.get_id()); self.enabled = PyStatus::get().config.get_status(self.file_path.as_path());
event!(Level::INFO, "更新 Python 插件文件 {:?} 完成", self.file_path); event!(Level::INFO, "更新 Python 插件文件 {:?} 完成", self.file_path);
true
} }
Err(e) => { Err(e) => {
warn!( warn!(
@ -163,12 +143,10 @@ impl PyPlugin {
e, e,
get_py_err_traceback(&e) get_py_err_traceback(&e)
); );
false
} }
}, },
Err(e) => { Err(e) => {
warn!("更新插件 {:?}: {:?} 失败", self.file_path, e); warn!("更新插件 {:?}: {:?} 失败", self.file_path, e);
false
} }
} }
} }
@ -186,14 +164,6 @@ impl PyPlugin {
} }
} }
} }
pub fn get_id(&self) -> String { plugin_path_as_id(&self.file_path) }
}
impl Display for PyPlugin {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}({:?})-{}", self.get_id(), self.file_path, self.enabled)
}
} }
pub const CONFIG_DATA_NAME: &str = "CONFIG_DATA"; pub const CONFIG_DATA_NAME: &str = "CONFIG_DATA";
@ -221,26 +191,17 @@ impl TryFrom<RawPyPlugin> for PyPlugin {
let mut base_path: PathBuf = PathBuf::from(base_path); let mut base_path: PathBuf = PathBuf::from(base_path);
if !base_path.exists() { if !base_path.exists() {
event!(Level::WARN, "python 插件路径不存在, 创建: {:?}", base_path); warn!("python 插件路径不存在, 创建: {:?}", base_path);
std::fs::create_dir_all(&base_path)?; std::fs::create_dir_all(&base_path)?;
} }
base_path.push(&config); base_path.push(&config);
let config_value = if base_path.exists() { let config_value = if base_path.exists() {
event!( info!("加载 {:?} 的配置文件 {:?} 中", path, base_path);
Level::INFO,
"加载 {:?} 的配置文件 {:?} 中",
path,
base_path
);
let content = std::fs::read_to_string(&base_path)?; let content = std::fs::read_to_string(&base_path)?;
toml::from_str(&content) toml::from_str(&content)
} else { } else {
event!( warn!("配置文件 {:?} 不存在, 创建默认配置", base_path);
Level::WARN,
"配置文件 {:?} 不存在, 创建默认配置",
base_path
);
// 写入默认配置 // 写入默认配置
std::fs::write(base_path, &default)?; std::fs::write(base_path, &default)?;
toml::from_str(&default) toml::from_str(&default)
@ -250,7 +211,7 @@ impl TryFrom<RawPyPlugin> for PyPlugin {
let py_config = let py_config =
Bound::new(py, class::ConfigDataPy::new(config)); Bound::new(py, class::ConfigDataPy::new(config));
if let Err(e) = py_config { if let Err(e) = py_config {
event!(Level::WARN, "添加配置文件信息失败: {:?}", e); warn!("添加配置文件信息失败: {:?}", e);
return Err(e); return Err(e);
} }
let py_config = py_config.unwrap(); let py_config = py_config.unwrap();
@ -343,15 +304,6 @@ impl TryFrom<RawPyPlugin> for PyPlugin {
} }
} }
/// 插件路径转换为 id
pub fn plugin_path_as_id(path: &Path) -> String {
path.file_name()
.unwrap_or_default()
.to_str()
.unwrap_or("decode-failed")
.to_string()
}
pub fn load_py_plugins(path: &PathBuf) { pub fn load_py_plugins(path: &PathBuf) {
let plugins = PyStatus::get_mut(); let plugins = PyStatus::get_mut();
if path.exists() { if path.exists() {
@ -378,8 +330,7 @@ pub fn load_py_plugins(path: &PathBuf) {
} else { } else {
event!(Level::WARN, "插件加载目录不存在: {:?}", path); event!(Level::WARN, "插件加载目录不存在: {:?}", path);
} }
plugins.config.read_status_from_default(); plugins.config.sync_status_from_config();
plugins.config.sync_status_to_config();
event!( event!(
Level::INFO, Level::INFO,
"python 插件目录: {:?} 加载完成, 加载到 {} 个插件", "python 插件目录: {:?} 加载完成, 加载到 {} 个插件",
@ -414,117 +365,29 @@ pub fn load_py_file(path: &PathBuf) -> std::io::Result<RawPyPlugin> {
Ok((path.clone(), changed_time, content)) Ok((path.clone(), changed_time, content))
} }
fn init_py_with_env_path(path: &str) {
unsafe {
#[cfg(target_os = "linux")]
use std::os::unix::ffi::OsStrExt;
#[cfg(target_os = "windows")]
use std::os::windows::ffi::OsStrExt;
let mut config = std::mem::zeroed::<pyo3::ffi::PyConfig>();
let config_ptr = &mut config as *mut pyo3::ffi::PyConfig;
// 初始化配置
// pyo3::ffi::PyConfig_InitIsolatedConfig(config_ptr);
pyo3::ffi::PyConfig_InitPythonConfig(config_ptr);
let wide_path = OsStr::new(path).encode_wide().chain(Some(0)).collect::<Vec<u16>>();
// 设置 prefix 和 exec_prefix
pyo3::ffi::PyConfig_SetString(config_ptr, &mut config.prefix as *mut _, wide_path.as_ptr());
pyo3::ffi::PyConfig_SetString(
config_ptr,
&mut config.exec_prefix as *mut _,
wide_path.as_ptr(),
);
// 使用 Py_InitializeFromConfig 初始化 python
let status = pyo3::ffi::Py_InitializeFromConfig(&config as *const _);
pyo3::ffi::PyEval_SaveThread();
// 清理配置
pyo3::ffi::PyConfig_Clear(config_ptr);
match status._type {
pyo3::ffi::_PyStatus_TYPE::_PyStatus_TYPE_OK => {
event!(Level::INFO, "根据配置初始化 python 完成");
}
pyo3::ffi::_PyStatus_TYPE::_PyStatus_TYPE_EXIT => {
event!(Level::ERROR, "初始化 python 时发生错误: EXIT");
}
pyo3::ffi::_PyStatus_TYPE::_PyStatus_TYPE_ERROR => {
event!(Level::ERROR, "初始化 python 时发生错误: ERROR");
pyo3::ffi::Py_ExitStatusException(status);
}
}
}
}
/// Python 侧初始化 /// Python 侧初始化
pub fn init_py() { pub fn init_py() {
// 从 全局配置中获取 python 插件路径 // 从 全局配置中获取 python 插件路径
let span = span!(Level::INFO, "py init"); let span = span!(Level::INFO, "初始化 python 及其插件.ing");
let _enter = span.enter(); let _enter = span.enter();
let plugin_path = MainStatus::global_config().py().plugin_path; let plugin_path = MainStatus::global_config().py().plugin_path;
let cli_args = std::env::args().collect::<Vec<String>>(); event!(Level::INFO, "正在初始化 python");
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);
// 判断一下是否有 VIRTUAL_ENV 环境变量
if let Ok(virtual_env) = std::env::var("VIRTUAL_ENV") {
event!(Level::WARN, "找到 VIRTUAL_ENV 环境变量: {} 将会被 -env 参数覆盖", virtual_env);
}
init_py_with_env_path(env_path);
} else {
// 根据 VIRTUAL_ENV 环境变量 进行一些处理
match std::env::var("VIRTUAL_ENV") {
Ok(virtual_env) => {
event!(Level::INFO, "找到 VIRTUAL_ENV 环境变量: {} 正在初始化", virtual_env);
init_py_with_env_path(&virtual_env);
}
Err(_) => {
event!(Level::INFO, "未找到 VIRTUAL_ENV 环境变量, 正常初始化");
pyo3::prepare_freethreaded_python(); pyo3::prepare_freethreaded_python();
event!(Level::INFO, "prepare_freethreaded_python 完成");
}
}
}
PyStatus::init(); PyStatus::init();
let plugin_path = PathBuf::from(plugin_path); let plugin_path = PathBuf::from(plugin_path);
load_py_plugins(&plugin_path); load_py_plugins(&plugin_path);
event!(Level::DEBUG, "python 插件列表: {}", PyStatus::display()); event!(Level::DEBUG, "python 插件列表: {}", PyStatus::display());
event!(Level::INFO, "python 初始化完成") info!("python inited")
} }
pub async fn post_py() -> anyhow::Result<()> { pub fn post_py() -> anyhow::Result<()> {
let status = PyStatus::get_mut(); PyStatus::get_mut().config.sync_status_to_config();
status.config.sync_status_to_config(); PyStatus::get()
status.config.write_to_default()?; .config
.write_to_file(&PathBuf::from(MainStatus::global_config().py().config_path))?;
stop_tasks().await;
unsafe {
if !pyo3::ffi::Py_FinalizeEx() == 0 {
event!(Level::ERROR, "Python 退出失败 (不过应该无所谓)");
}
}
Ok(()) Ok(())
} }
async fn stop_tasks() {
if call::PY_TASKS.lock().await.is_empty() {
return;
}
let waiter = tokio::spawn(async {
call::PY_TASKS.lock().await.join_all().await;
});
tokio::select! {
_ = waiter => {
event!(Level::INFO, "Python 任务完成");
}
_ = tokio::signal::ctrl_c() => {
call::PY_TASKS.lock().await.cancel_all();
event!(Level::INFO, "Python 任务被中断");
}
}
}

View File

@ -1,3 +1,4 @@
use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use colored::Colorize; use colored::Colorize;
@ -10,7 +11,7 @@ use crate::data_struct::tailchat::status::{BotStatus, UpdateDMConverse};
use crate::py::call::tailchat_new_message_py; use crate::py::call::tailchat_new_message_py;
use crate::py::PyStatus; use crate::py::PyStatus;
use crate::tailchat::client::{emit_join_room, send_message}; use crate::tailchat::client::{emit_join_room, send_message};
use crate::{client_id, help_msg, version_str, MainStatus, VERSION}; use crate::{client_id, help_msg, start_up_time, version_str, MainStatus, VERSION};
/// 所有 /// 所有
pub async fn any_event(event: Event, payload: Payload, _client: Client, _status: Arc<BotStatus>) { pub async fn any_event(event: Event, payload: Payload, _client: Client, _status: Arc<BotStatus>) {
@ -97,6 +98,16 @@ pub async fn on_message(payload: Payload, client: Client, _status: Arc<BotStatus
let reply = message.reply_with(&help_msg()); let reply = message.reply_with(&help_msg());
send_message(&client, &reply).await; send_message(&client, &reply).await;
} }
// else if message.content == "/bot-uptime" {
// let duration = match start_up_time().elapsed() {
// Ok(d) => format!("{:?}", d),
// Err(e) => format!("出问题啦 {:?}", e),
// };
// let reply = message.reply_with(&format!(
// "shenbot 已运行: {}", duration
// ));
// send_message(&client, &reply).await;
// }
if MainStatus::global_config().tailchat().admin_list.contains(&message.sender_id) { if MainStatus::global_config().tailchat().admin_list.contains(&message.sender_id) {
// admin 区 // admin 区
let client_id = client_id(); let client_id = client_id();
@ -104,7 +115,8 @@ pub async fn on_message(payload: Payload, client: Client, _status: Arc<BotStatus
// 先判定是否为 admin // 先判定是否为 admin
// 尝试获取后面的信息 // 尝试获取后面的信息
if let Some((_, name)) = message.content.split_once(" ") { if let Some((_, name)) = message.content.split_once(" ") {
match PyStatus::get().get_status(name) { let path_name = PathBuf::from(name);
match PyStatus::get().get_status(&path_name) {
None => { None => {
let reply = message.reply_with("未找到插件"); let reply = message.reply_with("未找到插件");
send_message(&client, &reply).await; send_message(&client, &reply).await;
@ -114,7 +126,7 @@ pub async fn on_message(payload: Payload, client: Client, _status: Arc<BotStatus
send_message(&client, &reply).await; send_message(&client, &reply).await;
} }
Some(false) => { Some(false) => {
PyStatus::get_mut().set_status(name, true); PyStatus::get_mut().set_status(&path_name, true);
let reply = message.reply_with("启用插件完成"); let reply = message.reply_with("启用插件完成");
send_message(&client, &reply).await; send_message(&client, &reply).await;
} }
@ -122,7 +134,8 @@ pub async fn on_message(payload: Payload, client: Client, _status: Arc<BotStatus
} }
} else if message.content.starts_with(&format!("/bot-disable-{}", client_id)) { } else if message.content.starts_with(&format!("/bot-disable-{}", client_id)) {
if let Some((_, name)) = message.content.split_once(" ") { if let Some((_, name)) = message.content.split_once(" ") {
match PyStatus::get().get_status(name) { let path_name = PathBuf::from(name);
match PyStatus::get().get_status(&path_name) {
None => { None => {
let reply = message.reply_with("未找到插件"); let reply = message.reply_with("未找到插件");
send_message(&client, &reply).await; send_message(&client, &reply).await;
@ -132,7 +145,7 @@ pub async fn on_message(payload: Payload, client: Client, _status: Arc<BotStatus
send_message(&client, &reply).await; send_message(&client, &reply).await;
} }
Some(true) => { Some(true) => {
PyStatus::get_mut().set_status(name, false); PyStatus::get_mut().set_status(&path_name, false);
let reply = message.reply_with("禁用插件完成"); let reply = message.reply_with("禁用插件完成");
send_message(&client, &reply).await; send_message(&client, &reply).await;
} }

67
news.md
View File

@ -1,51 +1,10 @@
# 更新日志 # 更新日志
## 0.8.2
- ica 兼容版本号更新到 `2.12.28`
- 现在支持通过读取环境变量里的 `VIRTUAL_ENV` 来获取虚拟环境路径
- 用于在虚拟环境中运行插件
- 添加了 `-h` 参数
- 用于展示帮助信息
- 添加了 `-env` 参数
- 用于指定 python 插件的虚拟环境路径
- 会覆盖环境变量中的 `VIRTUAL_ENV`
- 现在加入了默认的配置文件路径 `./config.toml`
- 现在会记录所有的 python 运行中 task 了
- 也会在退出的时候等待所有的 task 结束
- 二次 ctrl-c 会立即退出
- 改进了一下 ica 的新消息显示
- 添加了 ica 链接用时的显示
### ica 1.6.7
- 为 `IcaClinet` 添加了 `py_tasks_count -> int` 属性
- 用于获取当前运行中的 python task 数量
### tailchat 1.2.6
- 为 `TailchatClient` 添加了 `py_tasks_count -> int` 属性
- 用于获取当前运行中的 python task 数量
## 0.8.1
- 修复了 Python 插件状态写入的时候写入路径错误的问题
- `ica-typing` 加入了 `from __future__ import annotations`
- 这样就可以随便用 typing 了
- 把 NewType 都扬了
### ica 1.6.6
- 修复了 `send_poke` api 的问题
- 现在可以正常使用了
## 0.8.0 ## 0.8.0
- ica 兼容版本号更新到 ~~`2.12.24`~~ `2.12.26` - ica 兼容版本号更新到 `2.12.24`
- 从 `py::PyStatus` 开始进行一个 `static mut` -> `static mut OnceLock` 的改造 - 从 `py::PyStatus` 开始进行一个 `static mut` -> `static mut OnceLock` 的改造
- 用于看着更舒服(逃) - 用于看着更舒服(逃)
- 部分重构了一下 读取插件启用状态 的配置文件的代码
- 现在 `/bot-help` 会直接输出实际的 client id, 而不是给你一个默认的 `<client-id>`
### ica 1.6.5 ### ica 1.6.5
@ -58,29 +17,6 @@
- 或者指定好友 - 或者指定好友
- 目前还是有点问题 - 目前还是有点问题
- socketio event: `sendGroupPoke` - socketio event: `sendGroupPoke`
- 添加了 `reload_plugin_status` api
- 用于重新加载插件状态
- 添加了 `reload_plugin(plugin_name: str)` api
- 用于重新加载指定插件
- 添加了 `set_plugin_status(plugin_name: str, status: bool)` api
- 用于设置插件的启用状态
- 添加了 `get_plugin_status(plugin_name: str) -> bool` api
- 用于获取插件的启用状态
- 添加了 `sync_status_to_config` api
- 用于将内存中的插件状态同步到配置文件中
### tailchat 1.2.5
- 添加了 `reload_plugin_status` api
- 用于重新加载插件状态
- 添加了 `reload_plugin(plugin_name: str)` api
- 用于重新加载指定插件
- 添加了 `set_plugin_status(plugin_name: str, status: bool)` api
- 用于设置插件的启用状态
- 添加了 `get_plugin_status(plugin_name: str) -> bool` api
- 用于获取插件的启用状态
- 添加了 `sync_status_to_config` api
- 用于将内存中的插件状态同步到配置文件中
## 0.7.4 ## 0.7.4
@ -200,7 +136,6 @@
好耶! 好耶!
[!note] [!note]
```text ```text
notice_room = [] notice_room = []
notice_start = true notice_start = true