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]]
name = "ica-rs"
version = "0.6.11"
version = "0.7.0"
dependencies = [
"anyhow",
"base64 0.22.1",

View File

@ -1,6 +1,6 @@
[package]
name = "ica-rs"
version = "0.6.11"
version = "0.7.0"
edition = "2021"
# 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 serde::Deserialize;
use toml::from_str;
use tracing::warn;
use crate::data_struct::{ica, tailchat};
@ -18,12 +17,16 @@ pub struct IcaConfig {
/// bot 的 qq
pub self_id: ica::UserId,
/// 提醒的房间
#[serde(default = "default_empty_i64_vec")]
pub notice_room: Vec<ica::RoomId>,
/// 是否提醒
#[serde(default = "default_false")]
pub notice_start: bool,
/// 管理员列表
#[serde(default = "default_empty_i64_vec")]
pub admin_list: Vec<ica::UserId>,
/// 过滤列表
#[serde(default = "default_empty_i64_vec")]
pub filter_list: Vec<ica::UserId>,
}
@ -38,36 +41,51 @@ pub struct TailchatConfig {
/// 提醒的房间
pub notice_room: Vec<(tailchat::GroupId, tailchat::ConverseId)>,
/// 是否提醒
#[serde(default = "default_false")]
pub notice_start: bool,
/// 管理员列表
#[serde(default = "default_empty_str_vec")]
pub admin_list: Vec<tailchat::UserId>,
/// 过滤列表
#[serde(default = "default_empty_str_vec")]
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)]
pub struct PyConfig {
/// 插件路径
#[serde(default = "default_plugin_path")]
pub plugin_path: String,
/// 配置文件路径
/// 配置文件夹路径
#[serde(default = "default_config_path")]
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)]
pub struct BotConfig {
/// 是否启用 icalingua
pub enable_ica: Option<bool>,
#[serde(default = "default_false")]
pub enable_ica: bool,
/// Ica 配置
pub ica: Option<IcaConfig>,
/// 是否启用 Tailchat
pub enable_tailchat: Option<bool>,
#[serde(default = "default_false")]
pub enable_tailchat: bool,
/// Tailchat 配置
pub tailchat: Option<TailchatConfig>,
/// 是否启用 Python 插件
pub enable_py: Option<bool>,
#[serde(default = "default_false")]
pub enable_py: bool,
/// Python 插件配置
pub py: Option<PyConfig>,
}
@ -88,10 +106,9 @@ impl BotConfig {
let mut args = env::args();
while let Some(arg) = args.next() {
if arg == "-c" {
config_file_path = args.next().expect(&format!(
"{}",
"No config path given\nUsage: -c <config_file_path>".red()
));
config_file_path = args.next().unwrap_or_else(|| {
panic!("{}", "No config path given\nUsage: -c <config_file_path>".red())
});
break;
}
}
@ -99,64 +116,13 @@ impl BotConfig {
}
/// 检查是否启用 ica
pub fn check_ica(&self) -> bool {
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
}
}
}
pub fn check_ica(&self) -> bool { self.enable_ica }
/// 检查是否启用 Tailchat
pub fn check_tailchat(&self) -> bool {
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
}
}
}
pub fn check_tailchat(&self) -> bool { self.enable_tailchat }
/// 检查是否启用 Python 插件
pub fn check_py(&self) -> bool {
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 check_py(&self) -> bool { self.enable_py }
pub fn ica(&self) -> IcaConfig { self.ica.clone().expect("No ica config found") }
pub fn tailchat(&self) -> TailchatConfig {

View File

@ -1,3 +1,5 @@
use std::path::PathBuf;
use colored::Colorize;
use rust_socketio::asynchronous::Client;
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::online_data::OnlineData;
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) {
@ -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) {
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.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!(
"shenbot v{}\nica-async-rs pong v{}",
VERSION, ICA_VERSION
"shenbot-py v{}\n{}",
VERSION,
if MainStatus::global_config().check_py() {
py::PyStatus::display()
} else {
"未启用 Python 插件".to_string()
}
));
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 插件
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 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
pub const STABLE: bool = false;
@ -54,7 +65,9 @@ macro_rules! async_any_callback_with_state {
}
#[tokio::main]
async fn main() {
async fn main() -> anyhow::Result<()> { inner_main().await }
async fn inner_main() -> anyhow::Result<()> {
// -d -> debug
// none -> info
let level = {
@ -82,7 +95,9 @@ async fn main() {
MainStatus::static_init(bot_config);
let bot_config = MainStatus::global_config();
if bot_config.check_py() {
py::init_py();
}
// 准备一个用于停止 socket 的变量
event!(Level::INFO, "启动 ICA");
@ -120,9 +135,14 @@ async fn main() {
tailchat_send.send(()).ok();
event!(Level::INFO, "Disconnected");
py::post_py()?;
Ok(())
}
#[allow(dead_code, unused_variables)]
#[cfg(test)]
#[tokio::test]
async fn test_macro() {
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 tracing::{event, Level};
use crate::py::PyStatus;
@ -18,7 +22,7 @@ pub struct PluginConfigFile {
}
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#"
# shenbot , ****
# ,
@ -34,6 +38,7 @@ impl PluginConfigFile {
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, "插件配置文件不存在, 正在创建");
std::fs::write(&config_path, DEFAULT_CONFIG)?;
Ok(Self::from_str(DEFAULT_CONFIG)?)
} else {
@ -44,6 +49,7 @@ 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()),
@ -88,7 +94,9 @@ impl PluginConfigFile {
let plugins = PyStatus::get_map_mut();
self.verify_and_init();
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> {
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(())
}
}

View File

@ -29,7 +29,7 @@ impl PyStatus {
PYSTATUS.files = Some(HashMap::new());
}
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 =
config::PluginConfigFile::from_config_path(&PathBuf::from(plugin_path))
.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 {
let map = Self::get_map();
format!(
"Python 插件 {{ {} }}",
map.iter()
.map(|(k, v)| format!("{:?}: {:?}", k, v))
.map(|(k, v)| format!("{:?}-{}", k, v.enabled))
.collect::<Vec<String>>()
.join(", ")
.join("\n")
)
}
}
@ -161,7 +170,7 @@ impl PyPlugin {
Ok(plugin) => {
self.py_module = plugin.py_module;
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);
}
Err(e) => {
@ -343,7 +352,7 @@ pub fn load_py_plugins(path: &PathBuf) {
// 搜索所有的 py 文件 和 文件夹单层下面的 py 文件
match path.read_dir() {
Err(e) => {
event!(Level::WARN, "failed to read plugin path: {:?}", e);
event!(Level::WARN, "读取插件路径失败 {:?}", e);
}
Ok(dir) => {
for entry in dir {
@ -413,3 +422,10 @@ pub fn init_py() {
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 colored::Colorize;
@ -8,6 +9,7 @@ use tracing::{event, info, Level};
use crate::data_struct::tailchat::messages::ReceiveMessage;
use crate::data_struct::tailchat::status::{BotStatus, UpdateDMConverse};
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>) {
@ -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>) {
if let Payload::Text(values) = payload {
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" {
let reply = message.reply_with(&format!(
"shenbot v{}\ntailchat-async-rs pong v{}",
crate::VERSION,
crate::TAILCHAT_VERSION
VERSION, 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;
}
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;
}
crate::py::call::tailchat_new_message_py(&message, &client).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;
}
}
}
}
}
}
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` 这一项
@ -8,6 +11,14 @@
- `get_sender_name` 获取发送人昵称
- `ica` 兼容版本号 `2.12.11` -> `2.12.12`
- 加入了 `STABLE` 信息, 用于标记稳定版本
- 添加了 `version_str() -> String` 用于方便的获取版本信息
- 同样在 `py` 侧也有 `version_str` 方法
- 加入了 `/help` 命令
- 用于获取帮助信息
- 加入了 `/bot-ls`
- 用于展示所有插件的信息
- 加入了 `/bot-enable``/bot-disable`
- 用于启用/禁用插件
## 0.6.10