mirror of
http://shenjack.top:5100/shenjack/icalingua-python-bot.git
synced 2025-04-19 08:19:54 +08:00
Compare commits
25 Commits
16fee092ba
...
c23b3ee67a
Author | SHA1 | Date | |
---|---|---|---|
c23b3ee67a | |||
ede6640aa9 | |||
76a3628d2d | |||
4866f2ec2e | |||
4eb553473d | |||
12a32da61e | |||
0cec518f1d | |||
a114a92cba | |||
56a6c39df7 | |||
f8cd207923 | |||
79b306d089 | |||
d8b4fe06f9 | |||
e5f67475db | |||
d94841b1bd | |||
62a0a8d3fa | |||
6638a1f645 | |||
073c711c7c | |||
0275863cfe | |||
3ed1c3e738 | |||
d4c7a55dcc | |||
9711af9444 | |||
974c2577c3 | |||
c80e938a78 | |||
32958031d2 | |||
fcf88f0ebb |
538
Cargo.lock
generated
538
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "ica-rs"
|
||||
version = "0.8.0"
|
||||
version = "0.8.2"
|
||||
edition = "2021"
|
||||
|
||||
# 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"
|
||||
chrono = "0.4"
|
||||
toml = "0.8"
|
||||
toml_edit = "0.22.20"
|
||||
colored = "2.1"
|
||||
toml_edit = "0.22"
|
||||
colored = "3.0"
|
||||
|
||||
# runtime
|
||||
tokio = { version = "1.41", features = ["full"] }
|
||||
futures-util = "0.3.30"
|
||||
tokio = { version = "1.43", features = ["full"] }
|
||||
futures-util = "0.3"
|
||||
pyo3 = { version = "0.23", features = ["experimental-async", "py-clone"] }
|
||||
anyhow = { version = "1.0", features = ["backtrace"] }
|
||||
# async 这玩意以后在搞
|
||||
|
@ -49,5 +49,6 @@ anyhow = { version = "1.0", features = ["backtrace"] }
|
|||
# pyo3-asyncio = { version = "0.20.0", features = ["attributes", "tokio-runtime"] }
|
||||
|
||||
# log
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["time"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["time"] }
|
||||
foldhash = "0.1.4"
|
||||
|
|
|
@ -102,7 +102,7 @@ impl BotConfig {
|
|||
pub fn new_from_cli() -> Self {
|
||||
// let config_file_path = env::args().nth(1).expect("No config path given");
|
||||
// -c <config_file_path>
|
||||
let mut config_file_path = String::new();
|
||||
let mut config_file_path = "./config.toml".to_string();
|
||||
let mut args = env::args();
|
||||
while let Some(arg) = args.next() {
|
||||
if arg == "-c" {
|
||||
|
|
|
@ -44,9 +44,11 @@ impl Room {
|
|||
// 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()) {
|
||||
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));
|
||||
}
|
||||
// 现在 fix 了
|
||||
|
||||
let inner = match serde_json::from_value::<InnerRoom>(parse_json) {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
|
|
|
@ -88,7 +88,26 @@ impl<'de> Deserialize<'de> for Message {
|
|||
|
||||
impl Display for Message {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}|{}|{}|{}", self.msg_id(), self.sender_id, self.sender_name, self.content)
|
||||
if !self.content.is_empty() && !self.content.trim().is_empty() {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,14 +133,32 @@ impl MessageTrait for NewMessage {
|
|||
|
||||
impl Display for NewMessage {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}|{}|{}|{}|{}",
|
||||
self.msg_id(),
|
||||
self.room_id,
|
||||
self.msg.sender_id,
|
||||
self.msg.sender_name,
|
||||
self.msg.content
|
||||
)
|
||||
if !self.msg.content.trim().is_empty() {
|
||||
write!(
|
||||
f,
|
||||
"{}|{}|{}|{}|{}",
|
||||
self.msg.msg_id,
|
||||
self.room_id,
|
||||
self.msg.sender_id,
|
||||
self.msg.sender_name,
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
pub mod client;
|
||||
pub mod events;
|
||||
|
||||
use std::sync::OnceLock;
|
||||
// use std::sync::OnceLock;
|
||||
|
||||
use colored::Colorize;
|
||||
use rust_socketio::asynchronous::{Client, ClientBuilder};
|
||||
use rust_socketio::{async_any_callback, async_callback};
|
||||
use rust_socketio::{Event, Payload, TransportType};
|
||||
|
@ -13,33 +14,33 @@ use crate::error::{ClientResult, IcaError};
|
|||
use crate::{version_str, StopGetter};
|
||||
|
||||
/// icalingua 客户端的兼容版本号
|
||||
pub const ICA_PROTOCOL_VERSION: &str = "2.12.24";
|
||||
pub const ICA_PROTOCOL_VERSION: &str = "2.12.28";
|
||||
|
||||
mod status {
|
||||
use crate::data_struct::ica::all_rooms::Room;
|
||||
pub use crate::data_struct::ica::online_data::OnlineData;
|
||||
// mod status {
|
||||
// use crate::data_struct::ica::all_rooms::Room;
|
||||
// pub use crate::data_struct::ica::online_data::OnlineData;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MainStatus {
|
||||
/// 是否启用 ica
|
||||
pub enable: bool,
|
||||
/// qq 是否登录
|
||||
pub qq_login: bool,
|
||||
/// 当前已加载的消息数量
|
||||
pub current_loaded_messages_count: u64,
|
||||
/// 房间数据
|
||||
pub rooms: Vec<Room>,
|
||||
/// 在线数据 (Icalingua 信息)
|
||||
pub online_status: OnlineData,
|
||||
}
|
||||
// #[derive(Debug, Clone)]
|
||||
// pub struct MainStatus {
|
||||
// /// 是否启用 ica
|
||||
// pub enable: bool,
|
||||
// /// qq 是否登录
|
||||
// pub qq_login: bool,
|
||||
// /// 当前已加载的消息数量
|
||||
// pub current_loaded_messages_count: u64,
|
||||
// /// 房间数据
|
||||
// pub rooms: Vec<Room>,
|
||||
// /// 在线数据 (Icalingua 信息)
|
||||
// pub online_status: OnlineData,
|
||||
// }
|
||||
|
||||
impl MainStatus {
|
||||
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; }
|
||||
}
|
||||
}
|
||||
// impl MainStatus {
|
||||
// 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; }
|
||||
// }
|
||||
// }
|
||||
|
||||
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> {
|
||||
let span = span!(Level::INFO, "Icalingua Client");
|
||||
|
@ -47,6 +48,7 @@ pub async fn start_ica(config: &IcaConfig, stop_reciver: StopGetter) -> ClientRe
|
|||
|
||||
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())
|
||||
.transport_type(TransportType::Websocket)
|
||||
.on_any(async_any_callback!(events::any_event))
|
||||
|
@ -65,7 +67,11 @@ pub async fn start_ica(config: &IcaConfig, stop_reciver: StopGetter) -> ClientRe
|
|||
.await
|
||||
{
|
||||
Ok(client) => {
|
||||
event!(Level::INFO, "socketio connected");
|
||||
event!(
|
||||
Level::INFO,
|
||||
"{}",
|
||||
format!("socketio connected time: {:?}", start_connect_time.elapsed()).on_cyan()
|
||||
);
|
||||
client
|
||||
}
|
||||
Err(e) => {
|
||||
|
@ -94,6 +100,7 @@ pub async fn start_ica(config: &IcaConfig, stop_reciver: StopGetter) -> ClientRe
|
|||
}
|
||||
}
|
||||
// 等待停止信号
|
||||
event!(Level::INFO, "ica client waiting for stop signal");
|
||||
stop_reciver.await.ok();
|
||||
event!(Level::INFO, "socketio client stopping");
|
||||
match socket.disconnect().await {
|
||||
|
|
|
@ -7,7 +7,7 @@ use colored::Colorize;
|
|||
use ed25519_dalek::{Signature, Signer, SigningKey};
|
||||
use rust_socketio::asynchronous::Client;
|
||||
use rust_socketio::Payload;
|
||||
use serde_json::{Value, json};
|
||||
use serde_json::{json, Value};
|
||||
use tracing::{debug, event, span, warn, Level};
|
||||
|
||||
/// "安全" 的 发送一条消息
|
||||
|
@ -129,10 +129,8 @@ 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 {
|
||||
let data = json!([room_id, target]);
|
||||
match client.emit(
|
||||
"sendGroupPoke", data
|
||||
).await {
|
||||
let data = vec![json!(room_id), json!(target)];
|
||||
match client.emit("sendGroupPoke", data).await {
|
||||
Ok(_) => {
|
||||
event!(Level::INFO, "已向 {} 的 {} 发送戳一戳", room_id, target);
|
||||
true
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use colored::Colorize;
|
||||
use rust_socketio::asynchronous::Client;
|
||||
use rust_socketio::{Event, Payload};
|
||||
use serde_json::json;
|
||||
use tracing::{event, info, span, warn, Level};
|
||||
|
||||
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::{client_id, help_msg, py, start_up_time, version_str, MainStatus, VERSION};
|
||||
use crate::{client_id, help_msg, py, version_str, MainStatus, VERSION};
|
||||
|
||||
/// 获取在线数据
|
||||
pub async fn get_online_data(payload: Payload, _client: Client) {
|
||||
|
@ -32,7 +32,7 @@ pub async fn add_message(payload: Payload, client: Client) {
|
|||
return;
|
||||
}
|
||||
|
||||
event!(Level::INFO, "new_msg {}", message.to_string().cyan());
|
||||
println!("new_msg {}", message.to_string().cyan());
|
||||
// 就在这里处理掉最基本的消息
|
||||
// 之后的处理交给插件
|
||||
if !message.is_from_self() && !message.is_reply() {
|
||||
|
@ -72,8 +72,7 @@ pub async fn add_message(payload: Payload, client: Client) {
|
|||
if message.content().starts_with(&format!("/bot-enable-{}", client_id)) {
|
||||
// 尝试获取后面的信息
|
||||
if let Some((_, name)) = message.content().split_once(" ") {
|
||||
let path_name = PathBuf::from(name);
|
||||
match py::PyStatus::get().get_status(&path_name) {
|
||||
match py::PyStatus::get().get_status(name) {
|
||||
None => {
|
||||
let reply = message.reply_with("未找到插件");
|
||||
send_message(&client, &reply).await;
|
||||
|
@ -83,7 +82,7 @@ pub async fn add_message(payload: Payload, client: Client) {
|
|||
send_message(&client, &reply).await;
|
||||
}
|
||||
Some(false) => {
|
||||
py::PyStatus::get_mut().set_status(&path_name, true);
|
||||
py::PyStatus::get_mut().set_status(name, true);
|
||||
let reply = message.reply_with("启用插件完成");
|
||||
send_message(&client, &reply).await;
|
||||
}
|
||||
|
@ -92,8 +91,7 @@ pub async fn add_message(payload: Payload, client: Client) {
|
|||
} else if message.content().starts_with(&format!("/bot-disable-{}", client_id))
|
||||
{
|
||||
if let Some((_, name)) = message.content().split_once(" ") {
|
||||
let path_name = PathBuf::from(name);
|
||||
match py::PyStatus::get().get_status(&path_name) {
|
||||
match py::PyStatus::get().get_status(name) {
|
||||
None => {
|
||||
let reply = message.reply_with("未找到插件");
|
||||
send_message(&client, &reply).await;
|
||||
|
@ -103,12 +101,16 @@ pub async fn add_message(payload: Payload, client: Client) {
|
|||
send_message(&client, &reply).await;
|
||||
}
|
||||
Some(true) => {
|
||||
py::PyStatus::get_mut().set_status(&path_name, false);
|
||||
py::PyStatus::get_mut().set_status(name, false);
|
||||
let reply = message.reply_with("禁用插件完成");
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -173,6 +175,18 @@ 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) {
|
||||
let handled = vec![
|
||||
|
@ -193,6 +207,7 @@ pub async fn any_event(event: Event, payload: Payload, _client: Client) {
|
|||
"handleRequest", // 处理验证消息 (加入请求之类的)
|
||||
// 忽略的
|
||||
"notify",
|
||||
"setShutUp", // 禁言
|
||||
"syncRead", // 同步已读
|
||||
"closeLoading", // 发送消息/加载新聊天 有一个 loading
|
||||
"renewMessage", // 我也不确定到底是啥事件
|
||||
|
|
|
@ -29,8 +29,8 @@ pub type MainStatus = status::BotStatus;
|
|||
pub type StopGetter = tokio::sync::oneshot::Receiver<()>;
|
||||
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
pub const ICA_VERSION: &str = "1.6.5";
|
||||
pub const TAILCHAT_VERSION: &str = "1.2.4";
|
||||
pub const ICA_VERSION: &str = "1.6.7";
|
||||
pub const TAILCHAT_VERSION: &str = "1.2.6";
|
||||
|
||||
const HELP_MSG: &str = r#"/bot-rs
|
||||
展示 rust 侧信息
|
||||
|
@ -46,7 +46,9 @@ const HELP_MSG: &str = r#"/bot-rs
|
|||
by shenjackyuanjie"#;
|
||||
|
||||
/// 获取帮助信息
|
||||
pub fn help_msg() -> String { format!("{}\n{}", version_str(), HELP_MSG) }
|
||||
pub fn help_msg() -> String {
|
||||
format!("{}\n{}", version_str(), HELP_MSG).replace("<client-id>", client_id().as_str())
|
||||
}
|
||||
|
||||
static STARTUP_TIME: OnceLock<SystemTime> = OnceLock::new();
|
||||
|
||||
|
@ -77,7 +79,7 @@ pub fn version_str() -> String {
|
|||
|
||||
/// 是否为稳定版本
|
||||
/// 会在 release 的时候设置为 true
|
||||
pub const STABLE: bool = false;
|
||||
pub const STABLE: bool = true;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! async_callback_with_state {
|
||||
|
@ -99,25 +101,32 @@ 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<()> {
|
||||
let start_up_time = SystemTime::now();
|
||||
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
|
||||
// none -> info
|
||||
let args = std::env::args();
|
||||
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 = {
|
||||
let args = std::env::args();
|
||||
let args = args.collect::<Vec<String>>();
|
||||
if args.contains(&"-d".to_string()) {
|
||||
Level::DEBUG
|
||||
} else if args.contains(&"-t".to_string()) {
|
||||
|
@ -128,6 +137,20 @@ async fn inner_main() -> anyhow::Result<()> {
|
|||
};
|
||||
|
||||
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 _enter = span.enter();
|
||||
|
||||
|
@ -149,7 +172,6 @@ async fn inner_main() -> anyhow::Result<()> {
|
|||
let (ica_send, ica_recv) = tokio::sync::oneshot::channel::<()>();
|
||||
|
||||
if bot_config.check_ica() {
|
||||
event!(Level::INFO, "启动 ica");
|
||||
let config = bot_config.ica();
|
||||
tokio::spawn(async move {
|
||||
ica::start_ica(&config, ica_recv).await.unwrap();
|
||||
|
@ -170,18 +192,19 @@ async fn inner_main() -> anyhow::Result<()> {
|
|||
event!(Level::INFO, "未启用 Tailchat");
|
||||
}
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
// 等待一个输入
|
||||
event!(Level::INFO, "Press any key to exit");
|
||||
let mut input = String::new();
|
||||
std::io::stdin().read_line(&mut input).unwrap();
|
||||
event!(Level::INFO, "Press ctrl+c to exit, second ctrl+c to force exit");
|
||||
tokio::signal::ctrl_c().await.ok();
|
||||
|
||||
ica_send.send(()).ok();
|
||||
tailchat_send.send(()).ok();
|
||||
|
||||
event!(Level::INFO, "Disconnected");
|
||||
|
||||
py::post_py()?;
|
||||
py::post_py().await?;
|
||||
|
||||
event!(Level::INFO, "Shenbot-rs exiting");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use std::path::PathBuf;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use pyo3::prelude::*;
|
||||
use rust_socketio::asynchronous::Client;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::{event, info, warn, Level};
|
||||
|
||||
use crate::data_struct::{ica, tailchat};
|
||||
|
@ -9,6 +11,74 @@ use crate::error::PyPluginError;
|
|||
use crate::py::{class, PyPlugin, PyStatus};
|
||||
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>(
|
||||
py_module: &Bound<'py, PyAny>,
|
||||
name: &'py str,
|
||||
|
@ -154,8 +224,8 @@ 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);
|
||||
// 甚至实际上压根不需要await这个spawn, 直接让他自己跑就好了(离谱)
|
||||
call_py_func!(args, plugin, path, ICA_NEW_MESSAGE_FUNC, client);
|
||||
let task = call_py_func!(args, plugin, path, ICA_NEW_MESSAGE_FUNC, client);
|
||||
PY_TASKS.lock().await.push_ica_new_message(task);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,7 +237,8 @@ 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);
|
||||
call_py_func!(args, plugin, path, ICA_DELETE_MESSAGE_FUNC, client);
|
||||
let task = call_py_func!(args, plugin, path, ICA_DELETE_MESSAGE_FUNC, client);
|
||||
PY_TASKS.lock().await.push_ica_delete_message(task);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,6 +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);
|
||||
call_py_func!(args, plugin, path, TAILCHAT_NEW_MESSAGE_FUNC, client);
|
||||
let task = call_py_func!(args, plugin, path, TAILCHAT_NEW_MESSAGE_FUNC, client);
|
||||
PY_TASKS.lock().await.push_tailchat_new_message(task);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ use crate::data_struct::ica::messages::{
|
|||
};
|
||||
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]
|
||||
|
@ -145,6 +146,10 @@ impl SendMessagePy {
|
|||
pub fn get_content(&self) -> String { self.msg.content.clone() }
|
||||
#[setter]
|
||||
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) {
|
||||
self.msg.set_img(&file, &file_type, as_sticker);
|
||||
|
@ -250,6 +255,38 @@ impl IcaClientPy {
|
|||
#[getter]
|
||||
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) {
|
||||
event!(Level::DEBUG, "{}", content);
|
||||
}
|
||||
|
|
|
@ -3,10 +3,12 @@ use std::time::SystemTime;
|
|||
use pyo3::prelude::*;
|
||||
|
||||
use rust_socketio::asynchronous::Client;
|
||||
use tokio::runtime::Runtime;
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
use crate::data_struct::tailchat::messages::{ReceiveMessage, SendingFile, SendingMessage};
|
||||
use crate::data_struct::tailchat::{ConverseId, GroupId, MessageId, UserId};
|
||||
use crate::py::PyStatus;
|
||||
use crate::tailchat::client::send_message;
|
||||
|
||||
#[pyclass]
|
||||
|
@ -73,6 +75,38 @@ impl TailchatClientPy {
|
|||
#[getter]
|
||||
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))]
|
||||
pub fn new_message(
|
||||
&self,
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
use std::{path::Path, str::FromStr};
|
||||
|
||||
use toml_edit::{value, DocumentMut, Key, Table, TomlError, Value};
|
||||
use tracing::{event, Level};
|
||||
|
||||
use crate::py::PyStatus;
|
||||
use crate::MainStatus;
|
||||
|
||||
/// ```toml
|
||||
/// # 这个文件是由 shenbot 自动生成的, 请 **谨慎** 修改
|
||||
|
@ -31,14 +29,38 @@ pub const DEFAULT_CONFIG: &str = r#"
|
|||
|
||||
impl PluginConfigFile {
|
||||
pub fn from_str(data: &str) -> Result<Self, TomlError> {
|
||||
let data = DocumentMut::from_str(data)?;
|
||||
let mut 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 })
|
||||
}
|
||||
|
||||
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> {
|
||||
let config_path = path.join(CONFIG_FILE_NAME);
|
||||
if !config_path.exists() {
|
||||
event!(Level::INFO, "插件配置文件不存在, 正在创建");
|
||||
event!(Level::WARN, "插件配置文件不存在, 正在创建");
|
||||
std::fs::write(&config_path, DEFAULT_CONFIG)?;
|
||||
Ok(Self::from_str(DEFAULT_CONFIG)?)
|
||||
} else {
|
||||
|
@ -47,16 +69,6 @@ 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> {
|
||||
self.data.get(CONFIG_KEY).and_then(|item| item.as_table())
|
||||
}
|
||||
|
@ -67,11 +79,10 @@ impl PluginConfigFile {
|
|||
|
||||
/// 获取插件状态
|
||||
/// 默认为 true
|
||||
pub fn get_status(&self, path: &Path) -> bool {
|
||||
let path_str = path.to_str().unwrap();
|
||||
pub fn get_status(&self, plugin_id: &str) -> bool {
|
||||
if let Some(item) = self.data.get(CONFIG_KEY) {
|
||||
if let Some(table) = item.as_table() {
|
||||
if let Some(item) = table.get(path_str) {
|
||||
if let Some(item) = table.get(plugin_id) {
|
||||
if let Some(bool) = item.as_bool() {
|
||||
return bool;
|
||||
}
|
||||
|
@ -100,7 +111,6 @@ impl PluginConfigFile {
|
|||
|
||||
/// 设置插件状态
|
||||
pub fn set_status(&mut self, path: &Path, status: bool) {
|
||||
self.verify_and_init();
|
||||
let path_str = path.to_str().unwrap();
|
||||
let table = self.data.get_mut(CONFIG_KEY).unwrap().as_table_mut().unwrap();
|
||||
if table.contains_key(path_str) {
|
||||
|
@ -115,27 +125,47 @@ 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();
|
||||
self.verify_and_init();
|
||||
plugins.files.iter_mut().for_each(|(path, status)| {
|
||||
let config_status = self.get_status(path);
|
||||
event!(Level::INFO, "插件状态: {:?} {} -> {}", path, status.enabled, config_status);
|
||||
let plugin_id = status.get_id();
|
||||
let config_status = self.get_status(&plugin_id);
|
||||
event!(
|
||||
Level::INFO,
|
||||
"插件状态: {}({:?}) {} -> {}",
|
||||
status.get_id(),
|
||||
path,
|
||||
status.enabled,
|
||||
config_status
|
||||
);
|
||||
status.enabled = config_status;
|
||||
});
|
||||
true
|
||||
}
|
||||
|
||||
pub fn sync_status_to_config(&mut self) {
|
||||
let plugins = PyStatus::get();
|
||||
self.verify_and_init();
|
||||
let table = self.data.get_mut(CONFIG_KEY).unwrap().as_table_mut().unwrap();
|
||||
table.clear();
|
||||
plugins.files.iter().for_each(|(path, status)| {
|
||||
table.insert(path.to_str().unwrap(), value(status.enabled));
|
||||
plugins.files.iter().for_each(|(_, status)| {
|
||||
table.insert(&status.get_id(), value(status.enabled));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn write_to_file(&self, path: &PathBuf) -> Result<(), std::io::Error> {
|
||||
pub fn write_to_default(&self) -> 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);
|
||||
std::fs::write(config_path, self.data.to_string())?;
|
||||
Ok(())
|
||||
|
|
|
@ -2,7 +2,8 @@ pub mod call;
|
|||
pub mod class;
|
||||
pub mod config;
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::ffi::{CString, OsStr};
|
||||
use std::fmt::Display;
|
||||
use std::path::Path;
|
||||
use std::sync::OnceLock;
|
||||
use std::time::SystemTime;
|
||||
|
@ -11,7 +12,7 @@ use std::{collections::HashMap, path::PathBuf};
|
|||
use colored::Colorize;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::PyTuple;
|
||||
use tracing::{event, info, span, warn, Level};
|
||||
use tracing::{event, span, warn, Level};
|
||||
|
||||
use crate::MainStatus;
|
||||
|
||||
|
@ -29,10 +30,8 @@ static mut PyPluginStatus: OnceLock<PyStatus> = OnceLock::new();
|
|||
|
||||
impl PyStatus {
|
||||
pub fn init() {
|
||||
let plugin_path = MainStatus::global_config().py().plugin_path.clone();
|
||||
let mut config =
|
||||
config::PluginConfigFile::from_config_path(&PathBuf::from(plugin_path)).unwrap();
|
||||
config.verify_and_init();
|
||||
let config =
|
||||
config::PluginConfigFile::default_init().expect("初始化 Python 插件配置文件失败");
|
||||
let status = PyStatus {
|
||||
files: HashMap::new(),
|
||||
config,
|
||||
|
@ -44,37 +43,57 @@ impl PyStatus {
|
|||
|
||||
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 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 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 {
|
||||
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 {
|
||||
format!(
|
||||
"Python 插件 {{ {} }}",
|
||||
Self::get()
|
||||
.files
|
||||
.iter()
|
||||
.map(|(k, v)| format!("{:?}-{}", k, v.enabled))
|
||||
.values()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n")
|
||||
)
|
||||
|
@ -126,15 +145,16 @@ impl PyPlugin {
|
|||
}
|
||||
|
||||
/// 从文件更新
|
||||
pub fn reload_from_file(&mut self) {
|
||||
pub fn reload_from_file(&mut self) -> bool {
|
||||
let raw_file = load_py_file(&self.file_path);
|
||||
match raw_file {
|
||||
Ok(raw_file) => match Self::try_from(raw_file) {
|
||||
Ok(plugin) => {
|
||||
self.py_module = plugin.py_module;
|
||||
self.changed_time = plugin.changed_time;
|
||||
self.enabled = PyStatus::get().config.get_status(self.file_path.as_path());
|
||||
self.enabled = PyStatus::get().config.get_status(&self.get_id());
|
||||
event!(Level::INFO, "更新 Python 插件文件 {:?} 完成", self.file_path);
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
|
@ -143,10 +163,12 @@ impl PyPlugin {
|
|||
e,
|
||||
get_py_err_traceback(&e)
|
||||
);
|
||||
false
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
warn!("更新插件 {:?}: {:?} 失败", self.file_path, e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -164,6 +186,14 @@ 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";
|
||||
|
@ -191,17 +221,26 @@ impl TryFrom<RawPyPlugin> for PyPlugin {
|
|||
let mut base_path: PathBuf = PathBuf::from(base_path);
|
||||
|
||||
if !base_path.exists() {
|
||||
warn!("python 插件路径不存在, 创建: {:?}", base_path);
|
||||
event!(Level::WARN, "python 插件路径不存在, 创建: {:?}", base_path);
|
||||
std::fs::create_dir_all(&base_path)?;
|
||||
}
|
||||
base_path.push(&config);
|
||||
|
||||
let config_value = if base_path.exists() {
|
||||
info!("加载 {:?} 的配置文件 {:?} 中", path, base_path);
|
||||
event!(
|
||||
Level::INFO,
|
||||
"加载 {:?} 的配置文件 {:?} 中",
|
||||
path,
|
||||
base_path
|
||||
);
|
||||
let content = std::fs::read_to_string(&base_path)?;
|
||||
toml::from_str(&content)
|
||||
} else {
|
||||
warn!("配置文件 {:?} 不存在, 创建默认配置", base_path);
|
||||
event!(
|
||||
Level::WARN,
|
||||
"配置文件 {:?} 不存在, 创建默认配置",
|
||||
base_path
|
||||
);
|
||||
// 写入默认配置
|
||||
std::fs::write(base_path, &default)?;
|
||||
toml::from_str(&default)
|
||||
|
@ -211,7 +250,7 @@ impl TryFrom<RawPyPlugin> for PyPlugin {
|
|||
let py_config =
|
||||
Bound::new(py, class::ConfigDataPy::new(config));
|
||||
if let Err(e) = py_config {
|
||||
warn!("添加配置文件信息失败: {:?}", e);
|
||||
event!(Level::WARN, "添加配置文件信息失败: {:?}", e);
|
||||
return Err(e);
|
||||
}
|
||||
let py_config = py_config.unwrap();
|
||||
|
@ -304,6 +343,15 @@ 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) {
|
||||
let plugins = PyStatus::get_mut();
|
||||
if path.exists() {
|
||||
|
@ -330,7 +378,8 @@ pub fn load_py_plugins(path: &PathBuf) {
|
|||
} else {
|
||||
event!(Level::WARN, "插件加载目录不存在: {:?}", path);
|
||||
}
|
||||
plugins.config.sync_status_from_config();
|
||||
plugins.config.read_status_from_default();
|
||||
plugins.config.sync_status_to_config();
|
||||
event!(
|
||||
Level::INFO,
|
||||
"python 插件目录: {:?} 加载完成, 加载到 {} 个插件",
|
||||
|
@ -365,29 +414,117 @@ pub fn load_py_file(path: &PathBuf) -> std::io::Result<RawPyPlugin> {
|
|||
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 侧初始化
|
||||
pub fn init_py() {
|
||||
// 从 全局配置中获取 python 插件路径
|
||||
let span = span!(Level::INFO, "初始化 python 及其插件.ing");
|
||||
let span = span!(Level::INFO, "py init");
|
||||
let _enter = span.enter();
|
||||
|
||||
let plugin_path = MainStatus::global_config().py().plugin_path;
|
||||
|
||||
event!(Level::INFO, "正在初始化 python");
|
||||
pyo3::prepare_freethreaded_python();
|
||||
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);
|
||||
// 判断一下是否有 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();
|
||||
event!(Level::INFO, "prepare_freethreaded_python 完成");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PyStatus::init();
|
||||
let plugin_path = PathBuf::from(plugin_path);
|
||||
load_py_plugins(&plugin_path);
|
||||
event!(Level::DEBUG, "python 插件列表: {}", PyStatus::display());
|
||||
|
||||
info!("python inited")
|
||||
event!(Level::INFO, "python 初始化完成")
|
||||
}
|
||||
|
||||
pub fn post_py() -> anyhow::Result<()> {
|
||||
PyStatus::get_mut().config.sync_status_to_config();
|
||||
PyStatus::get()
|
||||
.config
|
||||
.write_to_file(&PathBuf::from(MainStatus::global_config().py().config_path))?;
|
||||
pub async fn post_py() -> anyhow::Result<()> {
|
||||
let status = PyStatus::get_mut();
|
||||
status.config.sync_status_to_config();
|
||||
status.config.write_to_default()?;
|
||||
|
||||
stop_tasks().await;
|
||||
unsafe {
|
||||
if !pyo3::ffi::Py_FinalizeEx() == 0 {
|
||||
event!(Level::ERROR, "Python 退出失败 (不过应该无所谓)");
|
||||
}
|
||||
}
|
||||
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 任务被中断");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use colored::Colorize;
|
||||
|
@ -11,7 +10,7 @@ use crate::data_struct::tailchat::status::{BotStatus, UpdateDMConverse};
|
|||
use crate::py::call::tailchat_new_message_py;
|
||||
use crate::py::PyStatus;
|
||||
use crate::tailchat::client::{emit_join_room, send_message};
|
||||
use crate::{client_id, help_msg, start_up_time, version_str, MainStatus, VERSION};
|
||||
use crate::{client_id, help_msg, version_str, MainStatus, VERSION};
|
||||
|
||||
/// 所有
|
||||
pub async fn any_event(event: Event, payload: Payload, _client: Client, _status: Arc<BotStatus>) {
|
||||
|
@ -98,16 +97,6 @@ pub async fn on_message(payload: Payload, client: Client, _status: Arc<BotStatus
|
|||
let reply = message.reply_with(&help_msg());
|
||||
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) {
|
||||
// admin 区
|
||||
let client_id = client_id();
|
||||
|
@ -115,8 +104,7 @@ pub async fn on_message(payload: Payload, client: Client, _status: Arc<BotStatus
|
|||
// 先判定是否为 admin
|
||||
// 尝试获取后面的信息
|
||||
if let Some((_, name)) = message.content.split_once(" ") {
|
||||
let path_name = PathBuf::from(name);
|
||||
match PyStatus::get().get_status(&path_name) {
|
||||
match PyStatus::get().get_status(name) {
|
||||
None => {
|
||||
let reply = message.reply_with("未找到插件");
|
||||
send_message(&client, &reply).await;
|
||||
|
@ -126,7 +114,7 @@ pub async fn on_message(payload: Payload, client: Client, _status: Arc<BotStatus
|
|||
send_message(&client, &reply).await;
|
||||
}
|
||||
Some(false) => {
|
||||
PyStatus::get_mut().set_status(&path_name, true);
|
||||
PyStatus::get_mut().set_status(name, true);
|
||||
let reply = message.reply_with("启用插件完成");
|
||||
send_message(&client, &reply).await;
|
||||
}
|
||||
|
@ -134,8 +122,7 @@ pub async fn on_message(payload: Payload, client: Client, _status: Arc<BotStatus
|
|||
}
|
||||
} else if message.content.starts_with(&format!("/bot-disable-{}", client_id)) {
|
||||
if let Some((_, name)) = message.content.split_once(" ") {
|
||||
let path_name = PathBuf::from(name);
|
||||
match PyStatus::get().get_status(&path_name) {
|
||||
match PyStatus::get().get_status(name) {
|
||||
None => {
|
||||
let reply = message.reply_with("未找到插件");
|
||||
send_message(&client, &reply).await;
|
||||
|
@ -145,7 +132,7 @@ pub async fn on_message(payload: Payload, client: Client, _status: Arc<BotStatus
|
|||
send_message(&client, &reply).await;
|
||||
}
|
||||
Some(true) => {
|
||||
PyStatus::get_mut().set_status(&path_name, false);
|
||||
PyStatus::get_mut().set_status(name, false);
|
||||
let reply = message.reply_with("禁用插件完成");
|
||||
send_message(&client, &reply).await;
|
||||
}
|
||||
|
|
67
news.md
67
news.md
|
@ -1,10 +1,51 @@
|
|||
# 更新日志
|
||||
|
||||
## 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
|
||||
|
||||
- ica 兼容版本号更新到 `2.12.24`
|
||||
- ica 兼容版本号更新到 ~~`2.12.24`~~ `2.12.26`
|
||||
- 从 `py::PyStatus` 开始进行一个 `static mut` -> `static mut OnceLock` 的改造
|
||||
- 用于看着更舒服(逃)
|
||||
- 部分重构了一下 读取插件启用状态 的配置文件的代码
|
||||
- 现在 `/bot-help` 会直接输出实际的 client id, 而不是给你一个默认的 `<client-id>`
|
||||
|
||||
### ica 1.6.5
|
||||
|
||||
|
@ -17,6 +58,29 @@
|
|||
- 或者指定好友
|
||||
- 目前还是有点问题
|
||||
- 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
|
||||
|
||||
|
@ -136,6 +200,7 @@
|
|||
好耶!
|
||||
|
||||
[!note]
|
||||
|
||||
```text
|
||||
notice_room = []
|
||||
notice_start = true
|
||||
|
|
Loading…
Reference in New Issue
Block a user