Compare commits

...

3 Commits

Author SHA1 Message Date
5636c8e1d9
疑似有点过于代码多了 2024-08-18 00:11:13 +08:00
d12773981d
hmmm 2024-08-17 21:49:16 +08:00
4da93570c9
丢点更新上去 2024-08-17 21:02:52 +08:00
11 changed files with 323 additions and 110 deletions

26
Cargo.lock generated
View File

@ -675,8 +675,10 @@ dependencies = [
"rust_socketio",
"serde",
"serde_json",
"thiserror",
"tokio",
"toml",
"toml_edit",
"tracing",
"tracing-subscriber",
]
@ -1390,9 +1392,9 @@ dependencies = [
[[package]]
name = "serde_spanned"
version = "0.6.6"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"
checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d"
dependencies = [
"serde",
]
@ -1557,18 +1559,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.61"
version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.61"
version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
dependencies = [
"proc-macro2",
"quote",
@ -1712,18 +1714,18 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.6.6"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.13"
version = "0.22.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c"
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
dependencies = [
"indexmap",
"serde",
@ -2196,9 +2198,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
[[package]]
name = "winnow"
version = "0.6.9"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86c949fede1d13936a99f14fafd3e76fd642b556dd2ce96287fbe2e0151bfac6"
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
dependencies = [
"memchr",
]

View File

@ -50,3 +50,5 @@ anyhow = { version = "1.0", features = ["backtrace"] }
# log
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["time"] }
thiserror = "1.0.63"
toml_edit = "0.22.20"

View File

@ -1,3 +1,5 @@
// use thiserror::Error;
pub type ClientResult<T, E> = Result<T, E>;
#[derive(Debug)]

View File

@ -10,7 +10,8 @@ use crate::config::IcaConfig;
use crate::error::{ClientResult, IcaError};
use crate::StopGetter;
const ICA_PROTOCOL_VERSION: &str = "2.12.11";
/// icalingua 客户端的兼容版本号
pub const ICA_PROTOCOL_VERSION: &str = "2.12.12";
pub async fn start_ica(config: &IcaConfig, stop_reciver: StopGetter) -> ClientResult<(), IcaError> {
let span = span!(Level::INFO, "Icalingua Client");
@ -80,7 +81,7 @@ pub async fn start_ica(config: &IcaConfig, stop_reciver: StopGetter) -> ClientRe
event!(Level::INFO, "socketio client stopped");
Ok(())
} else {
event!(Level::ERROR, "socketio client stopped with error: {:?}", inner_e);
event!(Level::ERROR, "socketio 客户端出现了 Error: {:?}", inner_e);
Err(IcaError::SocketIoError(
rust_socketio::Error::IncompleteResponseFromEngineIo(inner_e),
))

View File

@ -7,7 +7,7 @@ use ed25519_dalek::{Signature, Signer, SigningKey};
use rust_socketio::asynchronous::Client;
use rust_socketio::Payload;
use serde_json::Value;
use tracing::{debug, info, span, warn, Level};
use tracing::{debug, event, span, warn, Level};
/// "安全" 的 发送一条消息
pub async fn send_message(client: &Client, message: &SendMessage) -> bool {
@ -56,7 +56,12 @@ async fn inner_sign(payload: Payload, client: Client) -> ClientResult<(), IcaErr
let (auth_key, version) = (&require_data[0], &require_data[1]);
info!("auth_key: {:?}, server_version: {:?}", auth_key, version);
event!(
Level::INFO,
"服务器发过来的待签名key: {:?}, 服务端版本号: {:?}",
auth_key,
version
);
// 判定和自己的兼容版本号是否 一致
let server_protocol_version = version
.get("protocolVersion")
@ -81,15 +86,15 @@ async fn inner_sign(payload: Payload, client: Client) -> ClientResult<(), IcaErr
let private_key = MainStatus::global_config().ica().private_key.clone();
let array_key: [u8; 32] = hex::decode(private_key)
.expect("Not a vaild pub key")
.expect("配置文件设置的私钥不是一个有效的私钥, 无法使用hex解析")
.try_into()
.expect("Not a vaild pub key");
.expect("配置文件设置的私钥不是一个有效的私钥, 无法转换为[u8; 32]数组");
let signing_key: SigningKey = SigningKey::from_bytes(&array_key);
let signature: Signature = signing_key.sign(salt.as_slice());
// 发送签名
let sign = signature.to_bytes().to_vec();
client.emit("auth", sign).await.expect("Faild to send signin data");
client.emit("auth", sign).await.expect("发送签名信息失败");
Ok(())
}

View File

@ -25,8 +25,6 @@ pub async fn get_online_data(payload: Payload, _client: Client) {
pub async fn add_message(payload: Payload, client: Client) {
if let Payload::Text(values) = payload {
if let Some(value) = values.first() {
let span = span!(Level::INFO, "ica new_msg");
let _enter = span.enter();
let message: NewMessage = serde_json::from_value(value.clone()).unwrap();
// 检测是否在过滤列表内
if MainStatus::global_config().ica().filter_list.contains(&message.msg.sender_id) {
@ -184,7 +182,7 @@ pub async fn connect_callback(payload: Payload, _client: Client) {
Some(msg) => {
event!(Level::INFO, "{}{}", "未知消息".yellow(), msg);
}
None => (),
_ => (),
}
}
}

View File

@ -29,6 +29,10 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub const ICA_VERSION: &str = "1.6.1";
pub const TAILCHAT_VERSION: &str = "1.2.1";
/// 是否为稳定版本
/// 会在 release 的时候设置为 true
pub const STABLE: bool = false;
#[macro_export]
macro_rules! async_callback_with_state {
($f:expr, $state:expr) => {{
@ -69,7 +73,10 @@ async fn main() {
let span = span!(Level::INFO, "Shenbot Main");
let _enter = span.enter();
event!(Level::INFO, "shenbot-async-rs v{} starting", VERSION);
event!(Level::INFO, "shenbot-rs v{} starting", VERSION);
if !STABLE {
event!(Level::WARN, "这是一个开发版本, 有问题记得找 shenjack");
}
let bot_config = BotConfig::new_from_cli();
MainStatus::static_init(bot_config);

View File

@ -60,7 +60,7 @@ pub fn get_func<'py>(
}
}
pub fn verify_plugins() {
pub fn verify_and_reload_plugins() {
let mut need_reload_files: Vec<PathBuf> = Vec::new();
let plugin_path = MainStatus::global_config().py().plugin_path.clone();
@ -77,14 +77,20 @@ pub fn verify_plugins() {
return;
}
info!("file change list: {:?}", need_reload_files);
let exist_plugins = PyStatus::get_map_mut();
for reload_file in need_reload_files {
match PyPlugin::new_from_path(&reload_file) {
Some(plugin) => {
PyStatus::add_file(reload_file.clone(), plugin);
info!("重载 Python 插件: {:?}", reload_file);
}
None => {
warn!("重载 Python 插件: {:?} 失败", reload_file);
if let Some(plugin) = exist_plugins.get_mut(&reload_file) {
plugin.reload_from_file();
event!(Level::INFO, "重载 Python 插件: {:?} 完成", reload_file);
} else {
match PyPlugin::new_from_path(&reload_file) {
Some(plugin) => {
PyStatus::add_file(reload_file.clone(), plugin);
info!("加载 Python 插件: {:?} 完成", reload_file);
}
None => {
warn!("加载 Python 插件: {:?} 失败", reload_file);
}
}
}
}
@ -133,9 +139,9 @@ macro_rules! call_py_func {
/// 执行 new message 的 python 插件
pub async fn ica_new_message_py(message: &ica::messages::NewMessage, client: &Client) {
// 验证插件是否改变
verify_plugins();
verify_and_reload_plugins();
let plugins = PyStatus::get_files();
let plugins = PyStatus::get_map();
for (path, plugin) in plugins.iter().filter(|(_, plugin)| plugin.enabled) {
let msg = class::ica::NewMessagePy::new(message);
let client = class::ica::IcaClientPy::new(client);
@ -146,9 +152,9 @@ pub async fn ica_new_message_py(message: &ica::messages::NewMessage, client: &Cl
}
pub async fn ica_delete_message_py(msg_id: ica::MessageId, client: &Client) {
verify_plugins();
verify_and_reload_plugins();
let plugins = PyStatus::get_files();
let plugins = PyStatus::get_map();
for (path, plugin) in plugins.iter().filter(|(_, plugin)| plugin.enabled) {
let msg_id = msg_id.clone();
let client = class::ica::IcaClientPy::new(client);
@ -161,9 +167,9 @@ pub async fn tailchat_new_message_py(
message: &tailchat::messages::ReceiveMessage,
client: &Client,
) {
verify_plugins();
verify_and_reload_plugins();
let plugins = PyStatus::get_files();
let plugins = PyStatus::get_map();
for (path, plugin) in plugins.iter().filter(|(_, plugin)| plugin.enabled) {
let msg = class::tailchat::TailchatReceiveMessagePy::from_recive_message(message);
let client = class::tailchat::TailchatClientPy::new(client);

View File

@ -0,0 +1,109 @@
use std::{path::{Path, PathBuf}, str::FromStr};
use toml_edit::{value, DocumentMut, Key, Table, TomlError, Value};
use crate::py::PyStatus;
/// ```toml
/// # 这个文件是由 shenbot 自动生成的, 请 **谨慎** 修改
/// # 请不要修改这个文件, 除非你知道你在做什么
///
/// [plugins]
/// "xxxxxxx" = false # 被禁用的插件
/// "xxxxxxx" = true # 被启用的插件
/// ```
#[derive(Debug, Clone)]
pub struct PluginConfigFile {
pub data: DocumentMut,
}
const CONFIG_KEY: &str = "plugins";
pub const CONFIG_FILE_NAME: &str = "/plugins.toml";
pub const DEFAULT_CONFIG: &str = r#"
# shenbot , ****
# ,
[plugins]
"#;
impl PluginConfigFile {
pub fn from_str(data: &str) -> Result<Self, TomlError> {
let data = DocumentMut::from_str(data)?;
Ok(Self { data })
}
pub fn from_config_path(path: &Path) -> anyhow::Result<Self> {
let config_path = path.join(CONFIG_FILE_NAME);
if !config_path.exists() {
std::fs::write(&config_path, DEFAULT_CONFIG)?;
Ok(Self::from_str(DEFAULT_CONFIG)?)
} else {
let data = std::fs::read_to_string(&config_path)?;
Ok(Self::from_str(&data)?)
}
}
pub fn verify_and_init(&mut self) {
if self.data.get(CONFIG_KEY).is_none() {
self.data.insert_formatted(
&Key::from_str(CONFIG_KEY).unwrap(),
toml_edit::Item::Table(Table::new()),
);
}
}
/// 获取插件状态
/// 默认为 true
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(table) = item.as_table() {
if let Some(item) = table.get(path_str) {
if let Some(bool) = item.as_bool() {
return bool;
}
}
}
}
true
}
/// 设置插件状态
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) {
match table.get_mut(path_str).unwrap().as_value_mut() {
Some(value) => *value = Value::from(status),
None => {
table.insert(path_str, value(status));
}
}
} else {
table.insert(path_str, value(status));
}
}
pub fn sync_status_from_config(&mut self) {
let plugins = PyStatus::get_map_mut();
self.verify_and_init();
plugins.iter_mut().for_each(|(path, status)| {
status.enabled = self.get_status(path);
});
}
pub fn sync_status_to_config(&mut self) {
let plugins = PyStatus::get_map();
self.verify_and_init();
let table = self.data.get_mut(CONFIG_KEY).unwrap().as_table_mut().unwrap();
table.clear();
plugins.iter().for_each(|(path, status)| {
table.insert(path.to_str().unwrap(), value(status.enabled));
});
}
pub fn write_to_file(&self, path: &PathBuf) -> Result<(), std::io::Error> {
std::fs::write(path, self.data.to_string())?;
Ok(())
}
}

View File

@ -9,18 +9,106 @@ use std::{collections::HashMap, path::PathBuf};
use colored::Colorize;
use pyo3::prelude::*;
use pyo3::types::PyTuple;
use tracing::{debug, info, span, warn, Level};
use tracing::{event, info, span, warn, Level};
use crate::MainStatus;
#[derive(Debug, Clone)]
pub struct PyStatus {
pub files: Option<HashMap<PathBuf, PyPlugin>>,
pub files: Option<PyPlugins>,
pub config: Option<config::PluginConfigFile>,
}
pub type PyPluginData = HashMap<PathBuf, PyPlugin>;
pub type PyPlugins = HashMap<PathBuf, PyPlugin>;
pub type RawPyPlugin = (PathBuf, Option<SystemTime>, String);
impl PyStatus {
pub fn init() {
unsafe {
if PYSTATUS.files.is_none() {
PYSTATUS.files = Some(HashMap::new());
}
if PYSTATUS.config.is_none() {
let plugin_path = MainStatus::global_config().py().plugin_path;
let mut config =
config::PluginConfigFile::from_config_path(&PathBuf::from(plugin_path))
.unwrap();
config.verify_and_init();
PYSTATUS.config = Some(config);
}
}
}
pub fn add_file(path: PathBuf, plugin: PyPlugin) { Self::get_map_mut().insert(path, plugin); }
pub fn verify_file(path: &PathBuf) -> bool {
Self::get_map().get(path).map_or(false, |plugin| plugin.verifiy())
}
pub fn get_map() -> &'static PyPlugins {
unsafe {
match PYSTATUS.files.as_ref() {
Some(files) => files,
None => {
Self::init();
PYSTATUS.files.as_ref().unwrap()
}
}
}
}
pub fn get_map_mut() -> &'static mut PyPlugins {
unsafe {
match PYSTATUS.files.as_mut() {
Some(files) => files,
None => {
Self::init();
PYSTATUS.files.as_mut().unwrap()
}
}
}
}
pub fn get_config() -> &'static config::PluginConfigFile {
unsafe {
match PYSTATUS.config.as_ref() {
Some(config) => config,
None => {
Self::init();
PYSTATUS.config.as_ref().unwrap()
}
}
}
}
pub fn get_config_mut() -> &'static mut config::PluginConfigFile {
unsafe {
match PYSTATUS.config.as_mut() {
Some(config) => config,
None => {
Self::init();
PYSTATUS.config.as_mut().unwrap()
}
}
}
}
pub fn get_status(path: &Path) -> bool { Self::get_config().get_status(path) }
// pub fn list_plugins() -> Vec<PathBuf> { Self::get_map().keys().cloned().collect() }
pub fn display() -> String {
let map = Self::get_map();
format!(
"Python 插件 {{ {} }}",
map.iter()
.map(|(k, v)| format!("{:?}: {:?}", k, v))
.collect::<Vec<String>>()
.join(", ")
)
}
}
pub fn get_py_err_traceback(py_err: &PyErr) -> String {
Python::with_gil(|py| match py_err.traceback_bound(py) {
Some(traceback) => match traceback.format() {
@ -42,6 +130,7 @@ pub struct PyPlugin {
}
impl PyPlugin {
/// 从文件创建一个新的
pub fn new_from_path(path: &PathBuf) -> Option<Self> {
let raw_file = load_py_file(path);
match raw_file {
@ -63,6 +152,34 @@ impl PyPlugin {
}
}
}
/// 从文件更新
pub fn reload_from_file(&mut self) {
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_status(self.file_path.as_path());
event!(Level::INFO, "更新 Python 插件文件 {:?} 完成", self.file_path);
}
Err(e) => {
warn!(
"更新 Python 插件文件{:?}: {:?} 失败\n{}",
self.file_path,
e,
get_py_err_traceback(&e)
);
}
},
Err(e) => {
warn!("更新插件 {:?}: {:?} 失败", self.file_path, e);
}
}
}
/// 检查文件是否被修改
pub fn verifiy(&self) -> bool {
match get_change_time(&self.file_path) {
None => false,
@ -127,29 +244,26 @@ impl TryFrom<RawPyPlugin> for PyPlugin {
}
let py_config = py_config.unwrap();
// 先判定一下原来有没有
match module.hasattr(CONFIG_DATA_NAME) {
Ok(true) => {
// get 过来, 后面直接覆盖, 这里用于发个警告
match module.getattr(CONFIG_DATA_NAME) {
Ok(old_config) => {
// 先判断是不是 None, 直接忽略掉 None
// 毕竟有可能有占位
if !old_config.is_none() {
warn!(
"Python 插件 {:?} 的配置文件信息已经存在\n原始内容: {}",
path, old_config
);
}
}
Err(e) => {
if let Ok(true) = module.hasattr(CONFIG_DATA_NAME) {
// get 过来, 后面直接覆盖, 这里用于发个警告
match module.getattr(CONFIG_DATA_NAME) {
Ok(old_config) => {
// 先判断是不是 None, 直接忽略掉 None
// 毕竟有可能有占位
if !old_config.is_none() {
warn!(
"Python 插件 {:?} 的配置文件信息已经存在, 但获取失败:{:?}",
path, e
"Python 插件 {:?} 的配置文件信息已经存在\n原始内容: {}",
path, old_config
);
}
}
Err(e) => {
warn!(
"Python 插件 {:?} 的配置文件信息已经存在, 但获取失败:{:?}",
path, e
);
}
}
_ => {}
}
match module.setattr(CONFIG_DATA_NAME, py_config) {
Ok(()) => Ok(PyPlugin {
@ -218,56 +332,18 @@ impl TryFrom<RawPyPlugin> for PyPlugin {
}
}
impl PyStatus {
pub fn get_files() -> &'static PyPluginData {
unsafe {
match PYSTATUS.files.as_ref() {
Some(files) => files,
None => {
PYSTATUS.files = Some(HashMap::new());
PYSTATUS.files.as_ref().unwrap()
}
}
}
}
pub fn add_file(path: PathBuf, plugin: PyPlugin) {
unsafe {
match PYSTATUS.files.as_mut() {
Some(files) => {
files.insert(path, plugin);
}
None => {
let mut files: PyPluginData = HashMap::new();
files.insert(path, plugin);
PYSTATUS.files = Some(files);
}
}
}
}
pub fn verify_file(path: &PathBuf) -> bool {
unsafe {
match PYSTATUS.files.as_ref() {
Some(files) => match files.get(path) {
Some(plugin) => plugin.verifiy(),
None => false,
},
None => false,
}
}
}
}
pub static mut PYSTATUS: PyStatus = PyStatus { files: None };
pub static mut PYSTATUS: PyStatus = PyStatus {
files: None,
config: None,
};
pub fn load_py_plugins(path: &PathBuf) {
if path.exists() {
info!("finding plugins in: {:?}", path);
event!(Level::INFO, "找到位于 {:?} 的插件", path);
// 搜索所有的 py 文件 和 文件夹单层下面的 py 文件
match path.read_dir() {
Err(e) => {
warn!("failed to read plugin path: {:?}", e);
event!(Level::WARN, "failed to read plugin path: {:?}", e);
}
Ok(dir) => {
for entry in dir {
@ -284,12 +360,14 @@ pub fn load_py_plugins(path: &PathBuf) {
}
}
} else {
warn!("plugin path not exists: {:?}", path);
event!(Level::WARN, "插件加载目录不存在: {:?}", path);
}
info!(
PyStatus::get_config_mut().sync_status_from_config();
event!(
Level::INFO,
"python 插件目录: {:?} 加载完成, 加载到 {} 个插件",
path,
PyStatus::get_files().len()
PyStatus::get_map().len()
);
}
@ -320,17 +398,18 @@ pub fn load_py_file(path: &PathBuf) -> std::io::Result<RawPyPlugin> {
/// Python 侧初始化
pub fn init_py() {
// 从 全局配置中获取 python 插件路径
let span = span!(Level::INFO, "Init Python Plugin");
let span = span!(Level::INFO, "初始化 python 及其插件.ing");
let _enter = span.enter();
let global_config = MainStatus::global_config().py();
let plugin_path = MainStatus::global_config().py().plugin_path;
debug!("initing python threads");
event!(Level::INFO, "正在初始化 python");
pyo3::prepare_freethreaded_python();
let plugin_path = PathBuf::from(global_config.plugin_path);
PyStatus::init();
let plugin_path = PathBuf::from(plugin_path);
load_py_plugins(&plugin_path);
debug!("python 插件列表: {:#?}", PyStatus::get_files());
event!(Level::DEBUG, "python 插件列表: {}", PyStatus::display());
info!("python inited")
}

View File

@ -6,6 +6,8 @@
- 现在会在插件加载时警告你的插件原来定义了 `CONFIG_DATA` 这一项
- `IcaNewMessage` 添加了新的 api
- `get_sender_name` 获取发送人昵称
- `ica` 兼容版本号 `2.12.11` -> `2.12.12`
- 加入了 `STABLE` 信息, 用于标记稳定版本
## 0.6.10