Compare commits

...

2 Commits

Author SHA1 Message Date
113a1518d1
逆天 2024-08-18 02:05:41 +08:00
680934ad3f
ruaaa 2024-08-18 02:04:32 +08:00
9 changed files with 226 additions and 87 deletions

2
Cargo.lock generated
View File

@ -659,7 +659,7 @@ dependencies = [
[[package]] [[package]]
name = "ica-rs" name = "ica-rs"
version = "0.6.11" version = "0.7.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64 0.22.1", "base64 0.22.1",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "ica-rs" name = "ica-rs"
version = "0.6.11" version = "0.7.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

View File

@ -4,7 +4,6 @@ use std::fs;
use colored::Colorize; use colored::Colorize;
use serde::Deserialize; use serde::Deserialize;
use toml::from_str; use toml::from_str;
use tracing::warn;
use crate::data_struct::{ica, tailchat}; use crate::data_struct::{ica, tailchat};
@ -18,12 +17,16 @@ pub struct IcaConfig {
/// bot 的 qq /// bot 的 qq
pub self_id: ica::UserId, pub self_id: ica::UserId,
/// 提醒的房间 /// 提醒的房间
#[serde(default = "default_empty_i64_vec")]
pub notice_room: Vec<ica::RoomId>, pub notice_room: Vec<ica::RoomId>,
/// 是否提醒 /// 是否提醒
#[serde(default = "default_false")]
pub notice_start: bool, pub notice_start: bool,
/// 管理员列表 /// 管理员列表
#[serde(default = "default_empty_i64_vec")]
pub admin_list: Vec<ica::UserId>, pub admin_list: Vec<ica::UserId>,
/// 过滤列表 /// 过滤列表
#[serde(default = "default_empty_i64_vec")]
pub filter_list: Vec<ica::UserId>, pub filter_list: Vec<ica::UserId>,
} }
@ -38,36 +41,51 @@ pub struct TailchatConfig {
/// 提醒的房间 /// 提醒的房间
pub notice_room: Vec<(tailchat::GroupId, tailchat::ConverseId)>, pub notice_room: Vec<(tailchat::GroupId, tailchat::ConverseId)>,
/// 是否提醒 /// 是否提醒
#[serde(default = "default_false")]
pub notice_start: bool, pub notice_start: bool,
/// 管理员列表 /// 管理员列表
#[serde(default = "default_empty_str_vec")]
pub admin_list: Vec<tailchat::UserId>, pub admin_list: Vec<tailchat::UserId>,
/// 过滤列表 /// 过滤列表
#[serde(default = "default_empty_str_vec")]
pub filter_list: Vec<tailchat::UserId>, pub filter_list: Vec<tailchat::UserId>,
} }
fn default_plugin_path() -> String { "./plugins".to_string() }
fn default_config_path() -> String { "./config".to_string() }
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
pub struct PyConfig { pub struct PyConfig {
/// 插件路径 /// 插件路径
#[serde(default = "default_plugin_path")]
pub plugin_path: String, pub plugin_path: String,
/// 配置文件路径 /// 配置文件夹路径
#[serde(default = "default_config_path")]
pub config_path: String, pub config_path: String,
} }
fn default_empty_i64_vec() -> Vec<i64> { Vec::new() }
fn default_empty_str_vec() -> Vec<String> { Vec::new() }
fn default_false() -> bool { false }
/// 主配置 /// 主配置
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
pub struct BotConfig { pub struct BotConfig {
/// 是否启用 icalingua /// 是否启用 icalingua
pub enable_ica: Option<bool>, #[serde(default = "default_false")]
pub enable_ica: bool,
/// Ica 配置 /// Ica 配置
pub ica: Option<IcaConfig>, pub ica: Option<IcaConfig>,
/// 是否启用 Tailchat /// 是否启用 Tailchat
pub enable_tailchat: Option<bool>, #[serde(default = "default_false")]
pub enable_tailchat: bool,
/// Tailchat 配置 /// Tailchat 配置
pub tailchat: Option<TailchatConfig>, pub tailchat: Option<TailchatConfig>,
/// 是否启用 Python 插件 /// 是否启用 Python 插件
pub enable_py: Option<bool>, #[serde(default = "default_false")]
pub enable_py: bool,
/// Python 插件配置 /// Python 插件配置
pub py: Option<PyConfig>, pub py: Option<PyConfig>,
} }
@ -88,10 +106,9 @@ impl BotConfig {
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" {
config_file_path = args.next().expect(&format!( config_file_path = args.next().unwrap_or_else(|| {
"{}", panic!("{}", "No config path given\nUsage: -c <config_file_path>".red())
"No config path given\nUsage: -c <config_file_path>".red() });
));
break; break;
} }
} }
@ -99,64 +116,13 @@ impl BotConfig {
} }
/// 检查是否启用 ica /// 检查是否启用 ica
pub fn check_ica(&self) -> bool { pub fn check_ica(&self) -> bool { self.enable_ica }
match self.enable_ica {
Some(enable) => {
if enable && self.ica.is_none() {
warn!("enable_ica 为 true 但未填写 [ica] 配置\n将不启用 ica");
false
} else {
enable
}
}
None => {
if self.ica.is_some() {
warn!("未填写 enable_ica 但填写了 [ica] 配置\n将不启用 ica");
}
false
}
}
}
/// 检查是否启用 Tailchat /// 检查是否启用 Tailchat
pub fn check_tailchat(&self) -> bool { pub fn check_tailchat(&self) -> bool { self.enable_tailchat }
match self.enable_tailchat {
Some(enable) => {
if enable && self.tailchat.is_none() {
warn!("enable_tailchat 为 true 但未填写 [tailchat] 配置\n将不启用 Tailchat");
false
} else {
enable
}
}
None => {
if self.tailchat.is_some() {
warn!("未填写 enable_tailchat 但填写了 [tailchat] 配置\n将不启用 Tailchat");
}
false
}
}
}
/// 检查是否启用 Python 插件 /// 检查是否启用 Python 插件
pub fn check_py(&self) -> bool { pub fn check_py(&self) -> bool { self.enable_py }
match self.enable_py {
Some(enable) => {
if enable && self.py.is_none() {
warn!("enable_py 为 true 但未填写 [py] 配置\n将不启用 Python 插件");
false
} else {
true
}
}
None => {
if self.py.is_some() {
warn!("未填写 enable_py 但填写了 [py] 配置\n将不启用 Python 插件");
}
false
}
}
}
pub fn ica(&self) -> IcaConfig { self.ica.clone().expect("No ica config found") } pub fn ica(&self) -> IcaConfig { self.ica.clone().expect("No ica config found") }
pub fn tailchat(&self) -> TailchatConfig { pub fn tailchat(&self) -> TailchatConfig {

View File

@ -1,3 +1,5 @@
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};
@ -7,7 +9,7 @@ use crate::data_struct::ica::all_rooms::Room;
use crate::data_struct::ica::messages::{Message, MessageTrait, NewMessage}; use crate::data_struct::ica::messages::{Message, MessageTrait, NewMessage};
use crate::data_struct::ica::online_data::OnlineData; use crate::data_struct::ica::online_data::OnlineData;
use crate::ica::client::send_message; use crate::ica::client::send_message;
use crate::{py, MainStatus, ICA_VERSION, VERSION}; use crate::{py, version_str, MainStatus, VERSION};
/// 获取在线数据 /// 获取在线数据
pub async fn get_online_data(payload: Payload, _client: Client) { pub async fn get_online_data(payload: Payload, _client: Client) {
@ -20,7 +22,7 @@ pub async fn get_online_data(payload: Payload, _client: Client) {
} }
} }
#[allow(clippy::collapsible_if)] // #[allow(clippy::collapsible_if)]
/// 接收消息 /// 接收消息
pub async fn add_message(payload: Payload, client: Client) { pub async fn add_message(payload: Payload, client: Client) {
if let Payload::Text(values) = payload { if let Payload::Text(values) = payload {
@ -36,12 +38,68 @@ pub async fn add_message(payload: Payload, client: Client) {
// 之后的处理交给插件 // 之后的处理交给插件
if !message.is_from_self() && !message.is_reply() { if !message.is_from_self() && !message.is_reply() {
if message.content() == "/bot-rs" { if message.content() == "/bot-rs" {
let reply = message.reply_with(&version_str());
send_message(&client, &reply).await;
} else if message.content() == "/bot-ls" {
let reply = message.reply_with(&format!( let reply = message.reply_with(&format!(
"shenbot v{}\nica-async-rs pong v{}", "shenbot-py v{}\n{}",
VERSION, ICA_VERSION VERSION,
if MainStatus::global_config().check_py() {
py::PyStatus::display()
} else {
"未启用 Python 插件".to_string()
}
)); ));
send_message(&client, &reply).await; send_message(&client, &reply).await;
} }
if MainStatus::global_config().ica().admin_list.contains(&message.sender_id()) {
// admin 区
if message.content().starts_with("/bot-enable") {
// 先判定是否为 admin
// 尝试获取后面的信息
let mut content = message.content().split_whitespace();
content.next();
if let Some(name) = content.next() {
let path_name = PathBuf::from(name);
match py::PyStatus::get_status(&path_name) {
None => {
let reply = message.reply_with("未找到插件");
send_message(&client, &reply).await;
}
Some(true) => {
let reply = message.reply_with("无变化, 插件已经启用");
send_message(&client, &reply).await;
}
Some(false) => {
py::PyStatus::set_status(&path_name, true);
let reply = message.reply_with("启用插件完成");
send_message(&client, &reply).await;
}
}
}
} else if message.content().starts_with("/bot-disable") {
let mut content = message.content().split_whitespace();
content.next();
if let Some(name) = content.next() {
let path_name = PathBuf::from(name);
match py::PyStatus::get_status(&path_name) {
None => {
let reply = message.reply_with("未找到插件");
send_message(&client, &reply).await;
}
Some(false) => {
let reply = message.reply_with("无变化, 插件已经禁用");
send_message(&client, &reply).await;
}
Some(true) => {
py::PyStatus::set_status(&path_name, false);
let reply = message.reply_with("禁用插件完成");
send_message(&client, &reply).await;
}
}
}
}
}
} }
// python 插件 // python 插件
py::call::ica_new_message_py(&message, &client).await; py::call::ica_new_message_py(&message, &client).await;

View File

@ -29,6 +29,17 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub const ICA_VERSION: &str = "1.6.1"; pub const ICA_VERSION: &str = "1.6.1";
pub const TAILCHAT_VERSION: &str = "1.2.1"; pub const TAILCHAT_VERSION: &str = "1.2.1";
pub fn version_str() -> String {
format!(
"shenbot-rs v{}-{} ica v{}({}) tailchat v{}",
VERSION,
if STABLE { "" } else { "开发版" },
ICA_VERSION,
ica::ICA_PROTOCOL_VERSION,
TAILCHAT_VERSION,
)
}
/// 是否为稳定版本 /// 是否为稳定版本
/// 会在 release 的时候设置为 true /// 会在 release 的时候设置为 true
pub const STABLE: bool = false; pub const STABLE: bool = false;
@ -54,7 +65,9 @@ macro_rules! async_any_callback_with_state {
} }
#[tokio::main] #[tokio::main]
async fn main() { async fn main() -> anyhow::Result<()> { inner_main().await }
async fn inner_main() -> anyhow::Result<()> {
// -d -> debug // -d -> debug
// none -> info // none -> info
let level = { let level = {
@ -82,7 +95,9 @@ async fn main() {
MainStatus::static_init(bot_config); MainStatus::static_init(bot_config);
let bot_config = MainStatus::global_config(); let bot_config = MainStatus::global_config();
py::init_py(); if bot_config.check_py() {
py::init_py();
}
// 准备一个用于停止 socket 的变量 // 准备一个用于停止 socket 的变量
event!(Level::INFO, "启动 ICA"); event!(Level::INFO, "启动 ICA");
@ -120,9 +135,14 @@ async fn main() {
tailchat_send.send(()).ok(); tailchat_send.send(()).ok();
event!(Level::INFO, "Disconnected"); event!(Level::INFO, "Disconnected");
py::post_py()?;
Ok(())
} }
#[allow(dead_code, unused_variables)] #[allow(dead_code, unused_variables)]
#[cfg(test)]
#[tokio::test] #[tokio::test]
async fn test_macro() { async fn test_macro() {
use std::sync::Arc; use std::sync::Arc;

View File

@ -1,6 +1,10 @@
use std::{path::{Path, PathBuf}, 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 crate::py::PyStatus; use crate::py::PyStatus;
@ -18,7 +22,7 @@ pub struct PluginConfigFile {
} }
const CONFIG_KEY: &str = "plugins"; const CONFIG_KEY: &str = "plugins";
pub const CONFIG_FILE_NAME: &str = "/plugins.toml"; pub const CONFIG_FILE_NAME: &str = "plugins.toml";
pub const DEFAULT_CONFIG: &str = r#" pub const DEFAULT_CONFIG: &str = r#"
# shenbot , **** # shenbot , ****
# , # ,
@ -34,6 +38,7 @@ impl PluginConfigFile {
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::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 {
@ -44,6 +49,7 @@ impl PluginConfigFile {
pub fn verify_and_init(&mut self) { pub fn verify_and_init(&mut self) {
if self.data.get(CONFIG_KEY).is_none() { if self.data.get(CONFIG_KEY).is_none() {
event!(Level::INFO, "插件配置文件缺少 plugins 字段, 正在初始化");
self.data.insert_formatted( self.data.insert_formatted(
&Key::from_str(CONFIG_KEY).unwrap(), &Key::from_str(CONFIG_KEY).unwrap(),
toml_edit::Item::Table(Table::new()), toml_edit::Item::Table(Table::new()),
@ -88,7 +94,9 @@ impl PluginConfigFile {
let plugins = PyStatus::get_map_mut(); let plugins = PyStatus::get_map_mut();
self.verify_and_init(); self.verify_and_init();
plugins.iter_mut().for_each(|(path, status)| { plugins.iter_mut().for_each(|(path, status)| {
status.enabled = self.get_status(path); let config_status = self.get_status(path);
event!(Level::INFO, "插件状态: {:?} {} -> {}", path, status.enabled, config_status);
status.enabled = config_status;
}); });
} }
@ -103,7 +111,8 @@ impl PluginConfigFile {
} }
pub fn write_to_file(&self, path: &PathBuf) -> Result<(), std::io::Error> { pub fn write_to_file(&self, path: &PathBuf) -> Result<(), std::io::Error> {
std::fs::write(path, self.data.to_string())?; let config_path = path.join(CONFIG_FILE_NAME);
std::fs::write(config_path, self.data.to_string())?;
Ok(()) Ok(())
} }
} }

View File

@ -29,7 +29,7 @@ impl PyStatus {
PYSTATUS.files = Some(HashMap::new()); PYSTATUS.files = Some(HashMap::new());
} }
if PYSTATUS.config.is_none() { if PYSTATUS.config.is_none() {
let plugin_path = MainStatus::global_config().py().plugin_path; let plugin_path = MainStatus::global_config().py().config_path.clone();
let mut config = let mut config =
config::PluginConfigFile::from_config_path(&PathBuf::from(plugin_path)) config::PluginConfigFile::from_config_path(&PathBuf::from(plugin_path))
.unwrap(); .unwrap();
@ -93,18 +93,27 @@ impl PyStatus {
} }
} }
pub fn get_status(path: &Path) -> bool { Self::get_config().get_status(path) } /// 获取某个插件的状态
/// 以 config 优先
pub fn get_status(path: &PathBuf) -> Option<bool> {
Self::get_config_mut().sync_status_from_config();
Self::get_map().get(path).map(|plugin| plugin.enabled)
}
// pub fn list_plugins() -> Vec<PathBuf> { Self::get_map().keys().cloned().collect() } pub fn set_status(path: &Path, status: bool) {
let cfg = Self::get_config_mut();
cfg.set_status(path, status);
cfg.sync_status_from_config();
}
pub fn display() -> String { pub fn display() -> String {
let map = Self::get_map(); let map = Self::get_map();
format!( format!(
"Python 插件 {{ {} }}", "Python 插件 {{ {} }}",
map.iter() map.iter()
.map(|(k, v)| format!("{:?}: {:?}", k, v)) .map(|(k, v)| format!("{:?}-{}", k, v.enabled))
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", ") .join("\n")
) )
} }
} }
@ -161,7 +170,7 @@ impl PyPlugin {
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_status(self.file_path.as_path()); 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);
} }
Err(e) => { Err(e) => {
@ -343,7 +352,7 @@ pub fn load_py_plugins(path: &PathBuf) {
// 搜索所有的 py 文件 和 文件夹单层下面的 py 文件 // 搜索所有的 py 文件 和 文件夹单层下面的 py 文件
match path.read_dir() { match path.read_dir() {
Err(e) => { Err(e) => {
event!(Level::WARN, "failed to read plugin path: {:?}", e); event!(Level::WARN, "读取插件路径失败 {:?}", e);
} }
Ok(dir) => { Ok(dir) => {
for entry in dir { for entry in dir {
@ -413,3 +422,10 @@ pub fn init_py() {
info!("python inited") info!("python inited")
} }
pub fn post_py() -> anyhow::Result<()> {
PyStatus::get_config_mut().sync_status_to_config();
PyStatus::get_config()
.write_to_file(&PathBuf::from(MainStatus::global_config().py().config_path))?;
Ok(())
}

View File

@ -1,3 +1,4 @@
use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use colored::Colorize; use colored::Colorize;
@ -8,6 +9,7 @@ use tracing::{event, info, Level};
use crate::data_struct::tailchat::messages::ReceiveMessage; use crate::data_struct::tailchat::messages::ReceiveMessage;
use crate::data_struct::tailchat::status::{BotStatus, UpdateDMConverse}; use crate::data_struct::tailchat::status::{BotStatus, UpdateDMConverse};
use crate::tailchat::client::{emit_join_room, send_message}; use crate::tailchat::client::{emit_join_room, send_message};
use crate::{py, MainStatus, TAILCHAT_VERSION, 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>) {
@ -61,7 +63,6 @@ pub async fn any_event(event: Event, payload: Payload, _client: Client, _status:
} }
} }
#[allow(clippy::collapsible_if)]
pub async fn on_message(payload: Payload, client: Client, _status: Arc<BotStatus>) { pub async fn on_message(payload: Payload, client: Client, _status: Arc<BotStatus>) {
if let Payload::Text(values) = payload { if let Payload::Text(values) = payload {
if let Some(value) = values.first() { if let Some(value) = values.first() {
@ -79,13 +80,71 @@ pub async fn on_message(payload: Payload, client: Client, _status: Arc<BotStatus
if message.content == "/bot-rs" { if message.content == "/bot-rs" {
let reply = message.reply_with(&format!( let reply = message.reply_with(&format!(
"shenbot v{}\ntailchat-async-rs pong v{}", "shenbot v{}\ntailchat-async-rs pong v{}",
crate::VERSION, VERSION, TAILCHAT_VERSION
crate::TAILCHAT_VERSION ));
send_message(&client, &reply).await;
} else if message.content == "/bot-ls" {
let reply = message.reply_with(&format!(
"shenbot-py v{}\n{}",
VERSION,
if MainStatus::global_config().check_py() {
py::PyStatus::display()
} else {
"未启用 Python 插件".to_string()
}
)); ));
send_message(&client, &reply).await; send_message(&client, &reply).await;
} }
if MainStatus::global_config().tailchat().admin_list.contains(&message.sender_id) {
// admin 区
if message.content.starts_with("/bot-enable") {
// 先判定是否为 admin
// 尝试获取后面的信息
let mut content = message.content.split_whitespace();
content.next();
if let Some(name) = content.next() {
let path_name = PathBuf::from(name);
match py::PyStatus::get_status(&path_name) {
None => {
let reply = message.reply_with("未找到插件");
send_message(&client, &reply).await;
}
Some(true) => {
let reply = message.reply_with("无变化, 插件已经启用");
send_message(&client, &reply).await;
}
Some(false) => {
py::PyStatus::set_status(&path_name, true);
let reply = message.reply_with("启用插件完成");
send_message(&client, &reply).await;
}
}
}
} else if message.content.starts_with("/bot-disable") {
let mut content = message.content.split_whitespace();
content.next();
if let Some(name) = content.next() {
let path_name = PathBuf::from(name);
match py::PyStatus::get_status(&path_name) {
None => {
let reply = message.reply_with("未找到插件");
send_message(&client, &reply).await;
}
Some(false) => {
let reply = message.reply_with("无变化, 插件已经禁用");
send_message(&client, &reply).await;
}
Some(true) => {
py::PyStatus::set_status(&path_name, false);
let reply = message.reply_with("禁用插件完成");
send_message(&client, &reply).await;
}
}
}
}
}
} }
crate::py::call::tailchat_new_message_py(&message, &client).await; py::call::tailchat_new_message_py(&message, &client).await;
} }
} }
} }

13
news.md
View File

@ -1,6 +1,9 @@
# 更新日志 # 更新日志
## 0.6.11 ## 0.7.0
> 我决定叫他 0.7.0
> 因为修改太多了.png
- 加入了 禁用/启用 插件功能 - 加入了 禁用/启用 插件功能
- 现在会在插件加载时警告你的插件原来定义了 `CONFIG_DATA` 这一项 - 现在会在插件加载时警告你的插件原来定义了 `CONFIG_DATA` 这一项
@ -8,6 +11,14 @@
- `get_sender_name` 获取发送人昵称 - `get_sender_name` 获取发送人昵称
- `ica` 兼容版本号 `2.12.11` -> `2.12.12` - `ica` 兼容版本号 `2.12.11` -> `2.12.12`
- 加入了 `STABLE` 信息, 用于标记稳定版本 - 加入了 `STABLE` 信息, 用于标记稳定版本
- 添加了 `version_str() -> String` 用于方便的获取版本信息
- 同样在 `py` 侧也有 `version_str` 方法
- 加入了 `/help` 命令
- 用于获取帮助信息
- 加入了 `/bot-ls`
- 用于展示所有插件的信息
- 加入了 `/bot-enable``/bot-disable`
- 用于启用/禁用插件
## 0.6.10 ## 0.6.10