gracybot 1.9.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- core/__init__.py +117 -0
- core/config.py +166 -0
- core/config_manager.py +300 -0
- core/decorators/__init__.py +31 -0
- core/decorators/async_utils.py +95 -0
- core/decorators/context.py +46 -0
- core/decorators/handler.py +109 -0
- core/decorators/registration.py +237 -0
- core/decorators/security.py +140 -0
- core/decorators/session.py +50 -0
- core/event/__init__.py +95 -0
- core/gracy_adapter/__init__.py +38 -0
- core/gracy_adapter/adapter.py +77 -0
- core/gracy_adapter/event.py +38 -0
- core/gracy_adapter/gracy_bot.py +42 -0
- core/gracy_adapter/message.py +66 -0
- core/gracy_adapter/onebot/__init__.py +12 -0
- core/gracy_adapter/onebot/adapter.py +122 -0
- core/gracy_adapter/onebot/cq.py +126 -0
- core/gracy_adapter/onebot/http.py +242 -0
- core/gracy_adapter/onebot/onebot_config.template.json +6 -0
- core/gracy_adapter/onebot/sanitize.py +33 -0
- core/gracy_adapter/onebot/ws.py +502 -0
- core/gracy_adapter/send.py +130 -0
- core/gracy_session/__init__.py +49 -0
- core/gracy_session/gracy_session.py +95 -0
- core/gracy_session/gracy_session_handler.py +193 -0
- core/gracy_session/gracy_session_manager.py +286 -0
- core/handler.py +92 -0
- core/logger_manager.py +372 -0
- core/main.py +511 -0
- core/monitor.py +248 -0
- core/pipeline/__init__.py +107 -0
- core/pipeline/stages.py +498 -0
- core/plugin_manager.py +673 -0
- core/security.py +246 -0
- core/security_manager.py +506 -0
- core/tools/__init__.py +15 -0
- core/tools/cli/__init__.py +15 -0
- core/tools/cli/__main__.py +4 -0
- core/tools/cli/app.py +304 -0
- core/tools/cli/main.py +2 -0
- core/tools/cli/plugins.py +138 -0
- core/tools/cli/system.py +284 -0
- core/tools/cli/utils.py +128 -0
- core/tools/validator.py +141 -0
- core/utils.py +78 -0
- gracybot-1.9.2.dist-info/METADATA +304 -0
- gracybot-1.9.2.dist-info/RECORD +121 -0
- gracybot-1.9.2.dist-info/WHEEL +5 -0
- gracybot-1.9.2.dist-info/entry_points.txt +3 -0
- gracybot-1.9.2.dist-info/top_level.txt +3 -0
- plugins/Easysearch/Easysearch.py +74 -0
- plugins/Easysearch/__init__.py +4 -0
- plugins/Easysearch/config.py +16 -0
- plugins/Easysearch/core/draw.py +33 -0
- plugins/Easysearch/main.py +143 -0
- plugins/Easysearch/metadata.toml +23 -0
- plugins/Easysearch/requirements.txt +2 -0
- plugins/ExamplePlugin/ExamplePlugin.py +38 -0
- plugins/ExamplePlugin/README.md +31 -0
- plugins/ExamplePlugin/__init__.py +4 -0
- plugins/ExamplePlugin/metadata.toml +19 -0
- plugins/GracyUI_plugin/GracyUI_plugin.py +151 -0
- plugins/GracyUI_plugin/__init__.py +7 -0
- plugins/GracyUI_plugin/backend/__init__.py +1 -0
- plugins/GracyUI_plugin/backend/app.py +34 -0
- plugins/GracyUI_plugin/backend/routes/__init__.py +8 -0
- plugins/GracyUI_plugin/backend/routes/auth.py +31 -0
- plugins/GracyUI_plugin/backend/routes/bot.py +33 -0
- plugins/GracyUI_plugin/backend/routes/dashboard.py +136 -0
- plugins/GracyUI_plugin/backend/routes/logs.py +139 -0
- plugins/GracyUI_plugin/frontend/QUICKSTART.md +179 -0
- plugins/GracyUI_plugin/frontend/README.md +214 -0
- plugins/GracyUI_plugin/metadata.toml +19 -0
- plugins/Help_plugin/Help_plugin.py +54 -0
- plugins/Help_plugin/__init__.py +4 -0
- plugins/Help_plugin/config.py +24 -0
- plugins/Help_plugin/core/draw.py +538 -0
- plugins/Help_plugin/metadata.toml +14 -0
- plugins/Help_plugin/requirements.txt +1 -0
- plugins/LLM_Chat/LLM_Chat.py +118 -0
- plugins/LLM_Chat/README.md +78 -0
- plugins/LLM_Chat/__init__.py +9 -0
- plugins/LLM_Chat/config.json +14 -0
- plugins/LLM_Chat/config.py +19 -0
- plugins/LLM_Chat/config.template.json +14 -0
- plugins/LLM_Chat/core/api_handler.py +114 -0
- plugins/LLM_Chat/core/database.py +175 -0
- plugins/LLM_Chat/core/event_handler.py +278 -0
- plugins/LLM_Chat/core/poke_handler.py +98 -0
- plugins/LLM_Chat/core/scheduler.py +165 -0
- plugins/LLM_Chat/metadata.toml +14 -0
- plugins/LLM_Chat/requirements.txt +2 -0
- plugins/MonitorPlugin/MonitorPlugin.py +266 -0
- plugins/MonitorPlugin/__init__.py +4 -0
- plugins/MonitorPlugin/metadata.toml +14 -0
- plugins/Screenshot/Screenshot.py +37 -0
- plugins/Screenshot/__init__.py +4 -0
- plugins/Screenshot/capture.py +17 -0
- plugins/Screenshot/main.py +56 -0
- plugins/Screenshot/metadata.toml +18 -0
- plugins/SysInfo_plugin/README.md +31 -0
- plugins/SysInfo_plugin/SysInfo_plugin.py +518 -0
- plugins/SysInfo_plugin/__init__.py +2 -0
- plugins/SysInfo_plugin/core/draw.py +577 -0
- plugins/SysInfo_plugin/core/napcat_api.py +64 -0
- plugins/SysInfo_plugin/metadata.toml +22 -0
- plugins/Update_Plugin/README.md +61 -0
- plugins/Update_Plugin/Update_Plugin.py +691 -0
- plugins/Update_Plugin/__init__.py +4 -0
- plugins/Update_Plugin/metadata.toml +14 -0
- plugins/Xiaoyu_plugin/Xiaoyu_plugin.py +503 -0
- plugins/Xiaoyu_plugin/__init__.py +4 -0
- plugins/Xiaoyu_plugin/core/draw.py +299 -0
- plugins/Xiaoyu_plugin/metadata.toml +25 -0
- style/gracybot_logo.py +475 -0
- style/log_colors.py +97 -0
- style/resource/DouyinSansBold.otf +0 -0
- style/resource/gracybot_logo.png +0 -0
- style/styling.py +240 -0
core/__init__.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""GracyBot 核心模块统一导入文件
|
|
2
|
+
|
|
3
|
+
此模块提供核心组件的统一导出,简化其他模块的导入路径,提高代码可维护性。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# 核心管理器
|
|
7
|
+
def _get_plugin_manager():
|
|
8
|
+
"""延迟导入插件管理器,避免循环依赖"""
|
|
9
|
+
from .plugin_manager import plugin_manager
|
|
10
|
+
return plugin_manager
|
|
11
|
+
|
|
12
|
+
def _get_security_manager():
|
|
13
|
+
"""延迟导入安全管理器,避免循环依赖"""
|
|
14
|
+
from .security_manager import security_manager
|
|
15
|
+
return security_manager
|
|
16
|
+
|
|
17
|
+
def _get_config_manager():
|
|
18
|
+
"""延迟导入配置管理器,避免循环依赖"""
|
|
19
|
+
from .config_manager import config_manager
|
|
20
|
+
return config_manager
|
|
21
|
+
|
|
22
|
+
def _get_monitor_manager():
|
|
23
|
+
"""延迟导入监控管理器,避免循环依赖"""
|
|
24
|
+
from .monitor import monitor_manager
|
|
25
|
+
return monitor_manager
|
|
26
|
+
|
|
27
|
+
def _get_logger_manager():
|
|
28
|
+
"""延迟导入日志管理器,避免循环依赖"""
|
|
29
|
+
from .logger_manager import logger_manager
|
|
30
|
+
return logger_manager
|
|
31
|
+
|
|
32
|
+
# 使用属性描述器实现延迟加载
|
|
33
|
+
class LazyLoader:
|
|
34
|
+
"""延迟加载属性描述器"""
|
|
35
|
+
def __init__(self, loader_func):
|
|
36
|
+
self.loader_func = loader_func
|
|
37
|
+
self.__doc__ = loader_func.__doc__
|
|
38
|
+
|
|
39
|
+
def __get__(self, instance, owner):
|
|
40
|
+
value = self.loader_func()
|
|
41
|
+
setattr(owner, self.name, value)
|
|
42
|
+
return value
|
|
43
|
+
|
|
44
|
+
def __set_name__(self, owner, name):
|
|
45
|
+
self.name = name
|
|
46
|
+
|
|
47
|
+
class Core:
|
|
48
|
+
"""核心组件容器类,提供统一的核心组件访问入口"""
|
|
49
|
+
|
|
50
|
+
# 延迟加载的核心管理器
|
|
51
|
+
plugin_manager = LazyLoader(_get_plugin_manager)
|
|
52
|
+
security_manager = LazyLoader(_get_security_manager)
|
|
53
|
+
config_manager = LazyLoader(_get_config_manager)
|
|
54
|
+
monitor_manager = LazyLoader(_get_monitor_manager)
|
|
55
|
+
logger_manager = LazyLoader(_get_logger_manager)
|
|
56
|
+
|
|
57
|
+
# 创建核心组件实例
|
|
58
|
+
core = Core()
|
|
59
|
+
|
|
60
|
+
# 导出核心组件
|
|
61
|
+
def get_plugin_manager():
|
|
62
|
+
"""获取插件管理器实例"""
|
|
63
|
+
return core.plugin_manager
|
|
64
|
+
|
|
65
|
+
def get_security_manager():
|
|
66
|
+
"""获取安全管理器实例"""
|
|
67
|
+
return core.security_manager
|
|
68
|
+
|
|
69
|
+
def get_config_manager():
|
|
70
|
+
"""获取配置管理器实例"""
|
|
71
|
+
return core.config_manager
|
|
72
|
+
|
|
73
|
+
def get_monitor_manager():
|
|
74
|
+
"""获取监控管理器实例"""
|
|
75
|
+
return core.monitor_manager
|
|
76
|
+
|
|
77
|
+
def get_logger_manager():
|
|
78
|
+
"""获取日志管理器实例"""
|
|
79
|
+
return core.logger_manager
|
|
80
|
+
|
|
81
|
+
# 导出主要工具函数和常量(容错导入,避免缺依赖时崩整个包)
|
|
82
|
+
try:
|
|
83
|
+
from core.handler import callback_base
|
|
84
|
+
except ImportError:
|
|
85
|
+
callback_base = None
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
from core.utils import logger, sanitize_log
|
|
89
|
+
except ImportError:
|
|
90
|
+
logger = None
|
|
91
|
+
sanitize_log = None
|
|
92
|
+
|
|
93
|
+
# 版本信息
|
|
94
|
+
from core.config import BOT_VERSION
|
|
95
|
+
__version__ = BOT_VERSION.removeprefix('v')
|
|
96
|
+
__all__ = [
|
|
97
|
+
# 核心管理器访问函数
|
|
98
|
+
"get_plugin_manager",
|
|
99
|
+
"get_security_manager",
|
|
100
|
+
"get_config_manager",
|
|
101
|
+
"get_monitor_manager",
|
|
102
|
+
"get_logger_manager",
|
|
103
|
+
# 核心组件实例(延迟加载)
|
|
104
|
+
"core",
|
|
105
|
+
# 主要函数
|
|
106
|
+
"callback_base",
|
|
107
|
+
"sanitize_log",
|
|
108
|
+
# 日志对象
|
|
109
|
+
"logger",
|
|
110
|
+
# 版本信息
|
|
111
|
+
"__version__"
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
# 模块加载完成日志(仅子进程打印,避免热重载双重输出)
|
|
115
|
+
import os as _os
|
|
116
|
+
if _os.environ.get("WERKZEUG_RUN_MAIN") == "true":
|
|
117
|
+
logger.info(f"✅ 核心模块加载完成,版本: v{__version__}")
|
core/config.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
from core.config_manager import config_manager, ConfigItem
|
|
4
|
+
|
|
5
|
+
# ═══════════════ 框架配置版本与默认值 ═══════════════
|
|
6
|
+
|
|
7
|
+
CONFIG_VERSION = 3
|
|
8
|
+
"""当前框架配置版本号,用于自动更新检测。"""
|
|
9
|
+
|
|
10
|
+
DEFAULT_CONFIG = {
|
|
11
|
+
"_config_version": CONFIG_VERSION,
|
|
12
|
+
"robot_id": "不要填,填根目录的config.json",
|
|
13
|
+
"callback_port": 3002,
|
|
14
|
+
"master_id": "填写ID(默认,不用改,直接改根目录的config.json)",
|
|
15
|
+
"connection_mode": "http",
|
|
16
|
+
"bot_version": "v1.9.2",
|
|
17
|
+
"log_encoding": "utf-8",
|
|
18
|
+
"log_level": "WARNING",
|
|
19
|
+
"debug_mode": False,
|
|
20
|
+
"auto_replies": {
|
|
21
|
+
"你好": "哈喽~ 我是 GracyBot,有什么可以帮你呀?",
|
|
22
|
+
"在吗": "在呢在呢~ 随时在线为你服务!",
|
|
23
|
+
"谢谢": "不客气呀~ 能帮到你我也很开心!",
|
|
24
|
+
"再见": "拜拜~ 下次见啦,祝你生活愉快!",
|
|
25
|
+
"早上好": "早上好呀~ 新的一天也要元气满满哦!",
|
|
26
|
+
"晚上好": "晚上好~ 记得早点休息,不要熬夜呀!",
|
|
27
|
+
"吃了吗": "哈哈,已经吃过啦~ 你也要按时吃饭呀!",
|
|
28
|
+
"天气怎么样": "抱歉呀,我暂时没法查询天气,记得关注天气预报哦~",
|
|
29
|
+
"你是谁": "我是 GracyBot,一款基于 Napcat 的 QQ 机器人,很高兴认识你!",
|
|
30
|
+
"加油": "谢谢鼓励~ 你也超棒的,一起加油呀!"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
"""完整默认配置字典(包含框架级所有键的默认值)。"""
|
|
34
|
+
|
|
35
|
+
# ═══════════════ 框架级配置(存储在 config.json)═══════════════
|
|
36
|
+
|
|
37
|
+
# 启动路由(决定加载哪个适配器,属于框架级决策)
|
|
38
|
+
config_manager.register_config(ConfigItem(
|
|
39
|
+
key="connection_mode",
|
|
40
|
+
default="http",
|
|
41
|
+
description="连接模式: http(http_reverse), ws_forward, ws_reverse",
|
|
42
|
+
validate_func=lambda x: x in ["http", "http_reverse", "ws_forward", "ws_reverse"]
|
|
43
|
+
))
|
|
44
|
+
|
|
45
|
+
config_manager.register_config(ConfigItem(
|
|
46
|
+
key="robot_id",
|
|
47
|
+
default="不要填,填根目录的config.json",
|
|
48
|
+
description="机器人ID",
|
|
49
|
+
required=True
|
|
50
|
+
))
|
|
51
|
+
config_manager.register_config(ConfigItem(
|
|
52
|
+
key="callback_port",
|
|
53
|
+
default=3002,
|
|
54
|
+
description="回调服务端口",
|
|
55
|
+
validate_func=lambda x: isinstance(x, int) and 1024 <= x <= 65535
|
|
56
|
+
))
|
|
57
|
+
config_manager.register_config(ConfigItem(
|
|
58
|
+
key="master_id",
|
|
59
|
+
default="填写ID(默认,不用改,直接改根目录的config.json)",
|
|
60
|
+
description="主人ID",
|
|
61
|
+
required=True
|
|
62
|
+
))
|
|
63
|
+
config_manager.register_config(ConfigItem(
|
|
64
|
+
key="bot_version",
|
|
65
|
+
default="v1.9.2",
|
|
66
|
+
description="机器人版本"
|
|
67
|
+
))
|
|
68
|
+
config_manager.register_config(ConfigItem(
|
|
69
|
+
key="log_encoding",
|
|
70
|
+
default="utf-8",
|
|
71
|
+
description="日志编码格式"
|
|
72
|
+
))
|
|
73
|
+
config_manager.register_config(ConfigItem(
|
|
74
|
+
key="auto_replies",
|
|
75
|
+
default={
|
|
76
|
+
"你好": "哈喽~ 我是 GracyBot,有什么可以帮你呀?",
|
|
77
|
+
"在吗": "在呢在呢~ 随时在线为你服务!",
|
|
78
|
+
"谢谢": "不客气呀~ 能帮到你我也很开心!",
|
|
79
|
+
"再见": "拜拜~ 下次见啦,祝你生活愉快!",
|
|
80
|
+
"早上好": "早上好呀~ 新的一天也要元气满满哦!",
|
|
81
|
+
"晚上好": "晚上好~ 记得早点休息,不要熬夜呀!",
|
|
82
|
+
"吃了吗": "哈哈,已经吃过啦~ 你也要按时吃饭呀!",
|
|
83
|
+
"天气怎么样": "抱歉呀,我暂时没法查询天气,记得关注天气预报哦~",
|
|
84
|
+
"你是谁": "我是 GracyBot,一款基于 Napcat 的 QQ 机器人,很高兴认识你!",
|
|
85
|
+
"加油": "谢谢鼓励~ 你也超棒的,一起加油呀!"
|
|
86
|
+
},
|
|
87
|
+
description="自动回复配置"
|
|
88
|
+
))
|
|
89
|
+
config_manager.register_config(ConfigItem(
|
|
90
|
+
key="debug_mode",
|
|
91
|
+
default=False,
|
|
92
|
+
description="调试模式"
|
|
93
|
+
))
|
|
94
|
+
config_manager.register_config(ConfigItem(
|
|
95
|
+
key="log_level",
|
|
96
|
+
default="WARNING",
|
|
97
|
+
description="日志级别",
|
|
98
|
+
validate_func=lambda x: x in ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
|
99
|
+
))
|
|
100
|
+
|
|
101
|
+
# ═══════════════ OneBot 适配器专属配置(存储在 onebot_config.json)═══════════════
|
|
102
|
+
# 这些配置项由 OneBot 适配器使用,从独立配置文件加载,
|
|
103
|
+
# 未来新增适配器各自维护自己的配置文件,不污染框架 config.json
|
|
104
|
+
|
|
105
|
+
config_manager.register_config(ConfigItem(
|
|
106
|
+
key="napcat_http_url",
|
|
107
|
+
default="http://localhost:3000",
|
|
108
|
+
description="NapCat HTTP API 地址"
|
|
109
|
+
))
|
|
110
|
+
config_manager.register_config(ConfigItem(
|
|
111
|
+
key="ws_host",
|
|
112
|
+
default="127.0.0.1",
|
|
113
|
+
description="WebSocket 地址(正向=OneBot地址, 反向=监听地址)"
|
|
114
|
+
))
|
|
115
|
+
config_manager.register_config(ConfigItem(
|
|
116
|
+
key="ws_port",
|
|
117
|
+
default=3001,
|
|
118
|
+
description="WebSocket 端口",
|
|
119
|
+
validate_func=lambda x: isinstance(x, int) and 1024 <= x <= 65535
|
|
120
|
+
))
|
|
121
|
+
config_manager.register_config(ConfigItem(
|
|
122
|
+
key="access_token",
|
|
123
|
+
default="",
|
|
124
|
+
description="OneBot access_token(留空=不使用token连接)"
|
|
125
|
+
))
|
|
126
|
+
|
|
127
|
+
# ═══════════════ 加载配置(仅在首次访问配置值时触发文件读取) ═══════════════
|
|
128
|
+
|
|
129
|
+
# OneBot 配置路径:优先 GRACYBOT_HOME,否则回退模块路径
|
|
130
|
+
_gracy_home = os.environ.get("GRACYBOT_HOME", "")
|
|
131
|
+
if _gracy_home:
|
|
132
|
+
_ONEBOT_CONFIG_PATH = os.path.join(_gracy_home, "core", "gracy_adapter", "onebot", "onebot_config.json")
|
|
133
|
+
else:
|
|
134
|
+
_ONEBOT_CONFIG_PATH = os.path.join(
|
|
135
|
+
os.path.dirname(os.path.abspath(__file__)),
|
|
136
|
+
"gracy_adapter", "onebot", "onebot_config.json"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# 延迟加载,不阻塞模块导入
|
|
140
|
+
if not config_manager.load():
|
|
141
|
+
# 配置不存在时静默处理,不抛异常
|
|
142
|
+
pass
|
|
143
|
+
else:
|
|
144
|
+
# 加载成功后检查配置文件版本,自动合并更新
|
|
145
|
+
config_manager._auto_update_config(DEFAULT_CONFIG, CONFIG_VERSION)
|
|
146
|
+
|
|
147
|
+
config_manager.load_from(_ONEBOT_CONFIG_PATH)
|
|
148
|
+
|
|
149
|
+
# 为兼容旧代码,提供直接访问方式
|
|
150
|
+
ROBOT_ID = config_manager.get("robot_id")
|
|
151
|
+
CALLBACK_PORT = config_manager.get("callback_port")
|
|
152
|
+
MASTER_ID = config_manager.get("master_id")
|
|
153
|
+
BOT_VERSION = config_manager.get("bot_version")
|
|
154
|
+
LOG_ENCODING = config_manager.get("log_encoding")
|
|
155
|
+
AUTO_REPLIES = config_manager.get("auto_replies")
|
|
156
|
+
DEBUG_MODE = config_manager.get("debug_mode")
|
|
157
|
+
LOG_LEVEL = config_manager.get("log_level")
|
|
158
|
+
|
|
159
|
+
# 非配置项常量
|
|
160
|
+
ROBOT_START_TIME = time.time()
|
|
161
|
+
|
|
162
|
+
# 连接模式(框架启动路由,保留为模块常量)
|
|
163
|
+
CONNECTION_MODE = config_manager.get("connection_mode")
|
|
164
|
+
|
|
165
|
+
# OneBot 适配器配置不再作为模块常量,
|
|
166
|
+
# 使用方通过 config_manager.get("ws_host") 等方式获取
|
core/config_manager.py
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Dict, Any, Optional, TypeVar, Generic
|
|
5
|
+
|
|
6
|
+
# 配置文件路径
|
|
7
|
+
_gracy_home = os.environ.get("GRACYBOT_HOME", "")
|
|
8
|
+
if _gracy_home:
|
|
9
|
+
CONFIG_FILE_PATH = os.path.join(_gracy_home, 'config.json')
|
|
10
|
+
else:
|
|
11
|
+
CONFIG_FILE_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'config.json')
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# 配置类型定义
|
|
15
|
+
T = TypeVar('T')
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def deep_merge_config(base: dict, override: dict) -> dict:
|
|
19
|
+
"""递归合并两个配置字典
|
|
20
|
+
|
|
21
|
+
规则:
|
|
22
|
+
- override 中已有的键,保留 override 的值(用户设置优先)
|
|
23
|
+
- base 中存在但 override 中不存在的键,从 base 补入
|
|
24
|
+
- 如果某个键在两个 dict 中都是 dict 类型,则递归合并
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
base: 默认配置字典
|
|
28
|
+
override: 用户当前配置字典
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
合并后的新字典
|
|
32
|
+
"""
|
|
33
|
+
result = base.copy()
|
|
34
|
+
for key, value in override.items():
|
|
35
|
+
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
|
36
|
+
result[key] = deep_merge_config(result[key], value)
|
|
37
|
+
else:
|
|
38
|
+
result[key] = value
|
|
39
|
+
return result
|
|
40
|
+
|
|
41
|
+
class ConfigItem(Generic[T]):
|
|
42
|
+
"""配置项类,支持类型转换和验证"""
|
|
43
|
+
def __init__(self, key: str, default: T, description: str = '', required: bool = False,
|
|
44
|
+
env_var: Optional[str] = None, validate_func=None):
|
|
45
|
+
self.key = key
|
|
46
|
+
self.default = default
|
|
47
|
+
self.description = description
|
|
48
|
+
self.required = required
|
|
49
|
+
self.env_var = env_var or f"GRACY_{key.upper()}"
|
|
50
|
+
self.validate_func = validate_func
|
|
51
|
+
self.value: Optional[T] = None
|
|
52
|
+
|
|
53
|
+
def validate(self, value: Any) -> bool:
|
|
54
|
+
"""验证配置值是否合法"""
|
|
55
|
+
if self.validate_func:
|
|
56
|
+
return self.validate_func(value)
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
class ConfigManager:
|
|
60
|
+
"""企业级配置管理器,支持环境变量、配置文件和默认值"""
|
|
61
|
+
_instance = None
|
|
62
|
+
|
|
63
|
+
def __new__(cls):
|
|
64
|
+
if cls._instance is None:
|
|
65
|
+
cls._instance = super().__new__(cls)
|
|
66
|
+
cls._instance._initialized = False
|
|
67
|
+
cls._instance._config_items = {}
|
|
68
|
+
cls._instance._file_config = {}
|
|
69
|
+
cls._instance._logger = logging.getLogger("GracyBot-Config")
|
|
70
|
+
return cls._instance
|
|
71
|
+
|
|
72
|
+
def register_config(self, config_item: ConfigItem) -> None:
|
|
73
|
+
"""注册配置项"""
|
|
74
|
+
self._config_items[config_item.key] = config_item
|
|
75
|
+
|
|
76
|
+
def load(self) -> bool:
|
|
77
|
+
"""加载配置,优先级:环境变量 > 配置文件 > 默认值"""
|
|
78
|
+
try:
|
|
79
|
+
# 加载配置文件
|
|
80
|
+
if os.path.exists(CONFIG_FILE_PATH):
|
|
81
|
+
try:
|
|
82
|
+
with open(CONFIG_FILE_PATH, 'r', encoding='utf-8') as f:
|
|
83
|
+
self._file_config = json.load(f)
|
|
84
|
+
self._logger.info(f"✅ 配置文件加载成功: {CONFIG_FILE_PATH}")
|
|
85
|
+
except json.JSONDecodeError as e:
|
|
86
|
+
self._logger.error(f"❌ 配置文件格式错误: {str(e)}")
|
|
87
|
+
return False
|
|
88
|
+
else:
|
|
89
|
+
self._logger.warning(f"⚠️ 配置文件不存在: {CONFIG_FILE_PATH},将使用默认值和环境变量")
|
|
90
|
+
|
|
91
|
+
# 处理每个配置项
|
|
92
|
+
for key, item in self._config_items.items():
|
|
93
|
+
# 1. 尝试从环境变量获取
|
|
94
|
+
env_value = os.environ.get(item.env_var)
|
|
95
|
+
if env_value is not None:
|
|
96
|
+
# 根据默认值类型进行转换
|
|
97
|
+
if isinstance(item.default, bool):
|
|
98
|
+
item.value = env_value.lower() in ('true', '1', 'yes', 'y')
|
|
99
|
+
elif isinstance(item.default, int):
|
|
100
|
+
try:
|
|
101
|
+
item.value = int(env_value)
|
|
102
|
+
except ValueError:
|
|
103
|
+
self._logger.error(f"❌ 环境变量 {item.env_var} 不是有效的整数")
|
|
104
|
+
item.value = item.default
|
|
105
|
+
else:
|
|
106
|
+
item.value = env_value
|
|
107
|
+
self._logger.debug(f"🔧 从环境变量加载配置 {key}: {item.env_var}")
|
|
108
|
+
# 2. 尝试从配置文件获取
|
|
109
|
+
elif key in self._file_config:
|
|
110
|
+
item.value = self._file_config[key]
|
|
111
|
+
self._logger.debug(f"📄 从配置文件加载配置 {key}")
|
|
112
|
+
# 3. 使用默认值
|
|
113
|
+
else:
|
|
114
|
+
item.value = item.default
|
|
115
|
+
self._logger.debug(f"📌 使用默认配置 {key}: {item.default}")
|
|
116
|
+
|
|
117
|
+
# 验证配置
|
|
118
|
+
if not item.validate(item.value):
|
|
119
|
+
self._logger.error(f"❌ 配置 {key} 的值 {item.value} 无效")
|
|
120
|
+
if item.required:
|
|
121
|
+
return False
|
|
122
|
+
# 无效时回退到默认值
|
|
123
|
+
item.value = item.default
|
|
124
|
+
|
|
125
|
+
# 检查必填项
|
|
126
|
+
if item.required and item.value is None:
|
|
127
|
+
self._logger.error(f"❌ 缺少必填配置 {key}")
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
self._initialized = True
|
|
131
|
+
self._logger.info("✅ 所有配置加载完成")
|
|
132
|
+
return True
|
|
133
|
+
except Exception as e:
|
|
134
|
+
self._logger.error(f"❌ 配置加载异常: {str(e)}", exc_info=True)
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
def load_from(self, filepath: str) -> bool:
|
|
138
|
+
"""从指定路径加载配置文件,将值合并到已注册的配置项中
|
|
139
|
+
|
|
140
|
+
用于适配器独立配置文件(如 onebot_config.json),
|
|
141
|
+
只更新已注册的 ConfigItem,不会自动注册新项。
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
filepath: 配置文件的绝对路径
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
加载成功返回 True
|
|
148
|
+
"""
|
|
149
|
+
try:
|
|
150
|
+
if not os.path.exists(filepath):
|
|
151
|
+
self._logger.warning(f"⚠️ 配置文件不存在: {filepath},将使用默认值")
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
155
|
+
file_data = json.load(f)
|
|
156
|
+
|
|
157
|
+
loaded_keys = []
|
|
158
|
+
for key, value in file_data.items():
|
|
159
|
+
item = self._config_items.get(key)
|
|
160
|
+
if item:
|
|
161
|
+
# 环境变量优先级仍高于文件
|
|
162
|
+
if item.env_var in os.environ:
|
|
163
|
+
continue
|
|
164
|
+
item.value = value
|
|
165
|
+
loaded_keys.append(key)
|
|
166
|
+
else:
|
|
167
|
+
self._logger.debug(f"⏭️ 忽略未注册的配置项: {key}(来自 {os.path.basename(filepath)})")
|
|
168
|
+
|
|
169
|
+
self._logger.info(f"✅ 适配器配置加载成功: {filepath}({len(loaded_keys)} 项)")
|
|
170
|
+
return True
|
|
171
|
+
except json.JSONDecodeError as e:
|
|
172
|
+
self._logger.error(f"❌ 配置文件格式错误: {filepath}: {str(e)}")
|
|
173
|
+
return False
|
|
174
|
+
except Exception as e:
|
|
175
|
+
self._logger.error(f"❌ 配置文件加载异常: {filepath}: {str(e)}", exc_info=True)
|
|
176
|
+
return False
|
|
177
|
+
|
|
178
|
+
def save_to_file_at(self, filepath: str, keys: list = None) -> bool:
|
|
179
|
+
"""将指定配置项保存到指定文件路径
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
filepath: 目标文件路径
|
|
183
|
+
keys: 要保存的配置项键列表,None 表示全部
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
保存成功返回 True
|
|
187
|
+
"""
|
|
188
|
+
try:
|
|
189
|
+
data = {}
|
|
190
|
+
target_keys = keys or list(self._config_items.keys())
|
|
191
|
+
for key in target_keys:
|
|
192
|
+
item = self._config_items.get(key)
|
|
193
|
+
if item and item.value is not None:
|
|
194
|
+
data[key] = item.value
|
|
195
|
+
|
|
196
|
+
with open(filepath, 'w', encoding='utf-8') as f:
|
|
197
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
198
|
+
|
|
199
|
+
self._logger.info(f"✅ 配置已保存到: {filepath}")
|
|
200
|
+
return True
|
|
201
|
+
except Exception as e:
|
|
202
|
+
self._logger.error(f"❌ 保存配置文件失败: {filepath}: {str(e)}")
|
|
203
|
+
return False
|
|
204
|
+
|
|
205
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
206
|
+
"""获取配置值"""
|
|
207
|
+
if not self._initialized:
|
|
208
|
+
if not self.load():
|
|
209
|
+
return default
|
|
210
|
+
|
|
211
|
+
item = self._config_items.get(key)
|
|
212
|
+
if item:
|
|
213
|
+
return item.value
|
|
214
|
+
return default
|
|
215
|
+
|
|
216
|
+
def set(self, key: str, value: Any) -> bool:
|
|
217
|
+
"""动态设置配置值"""
|
|
218
|
+
item = self._config_items.get(key)
|
|
219
|
+
if item:
|
|
220
|
+
if item.validate(value):
|
|
221
|
+
item.value = value
|
|
222
|
+
self._logger.info(f"🔄 动态更新配置 {key}: {value}")
|
|
223
|
+
return True
|
|
224
|
+
else:
|
|
225
|
+
self._logger.error(f"❌ 无法设置配置 {key}: 无效值 {value}")
|
|
226
|
+
return False
|
|
227
|
+
|
|
228
|
+
def missing_in_file(self, *keys) -> list:
|
|
229
|
+
"检查哪些配置项在 config.json 中缺失(用于首次运行引导)"
|
|
230
|
+
if not self._file_config:
|
|
231
|
+
return list(keys)
|
|
232
|
+
return [k for k in keys if k not in self._file_config]
|
|
233
|
+
|
|
234
|
+
def save_to_file(self) -> bool:
|
|
235
|
+
"""保存当前配置到文件(不包含环境变量覆盖的值)"""
|
|
236
|
+
try:
|
|
237
|
+
# 只保存非环境变量覆盖的配置
|
|
238
|
+
config_to_save = self._file_config.copy()
|
|
239
|
+
for key, item in self._config_items.items():
|
|
240
|
+
if item.env_var not in os.environ and key not in os.environ:
|
|
241
|
+
config_to_save[key] = item.value
|
|
242
|
+
|
|
243
|
+
with open(CONFIG_FILE_PATH, 'w', encoding='utf-8') as f:
|
|
244
|
+
json.dump(config_to_save, f, ensure_ascii=False, indent=2)
|
|
245
|
+
|
|
246
|
+
self._logger.info(f"✅ 配置已保存到: {CONFIG_FILE_PATH}")
|
|
247
|
+
return True
|
|
248
|
+
except Exception as e:
|
|
249
|
+
self._logger.error(f"❌ 保存配置文件失败: {str(e)}")
|
|
250
|
+
return False
|
|
251
|
+
|
|
252
|
+
def generate_default_config(self) -> Dict[str, Any]:
|
|
253
|
+
"""生成默认配置字典"""
|
|
254
|
+
default_config = {}
|
|
255
|
+
for key, item in self._config_items.items():
|
|
256
|
+
default_config[key] = {
|
|
257
|
+
'value': item.default,
|
|
258
|
+
'description': item.description,
|
|
259
|
+
'env_var': item.env_var,
|
|
260
|
+
'required': item.required
|
|
261
|
+
}
|
|
262
|
+
return default_config
|
|
263
|
+
|
|
264
|
+
def _auto_update_config(self, default_config: dict, config_version: int) -> None:
|
|
265
|
+
"""自动更新配置文件:检查版本号,低版本则合并默认配置并写回
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
default_config: 完整默认配置字典(来自 core/config.py 的 DEFAULT_CONFIG)
|
|
269
|
+
config_version: 当前框架配置版本号(来自 core/config.py 的 CONFIG_VERSION)
|
|
270
|
+
"""
|
|
271
|
+
# 读取当前配置文件中的版本号
|
|
272
|
+
current_version = self._file_config.get("_config_version", 0)
|
|
273
|
+
if current_version >= config_version:
|
|
274
|
+
return
|
|
275
|
+
|
|
276
|
+
self._logger.info(
|
|
277
|
+
f"🔄 检测到配置版本 {current_version} → {config_version},正在自动更新..."
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# 合并:新增键追加、已有键保留用户值、嵌套 dict 递归合并
|
|
281
|
+
merged = deep_merge_config(default_config, self._file_config)
|
|
282
|
+
merged["_config_version"] = config_version
|
|
283
|
+
|
|
284
|
+
# bot_version 始终从 DEFAULT_CONFIG 同步,标记框架真实版本
|
|
285
|
+
if "bot_version" in default_config:
|
|
286
|
+
merged["bot_version"] = default_config["bot_version"]
|
|
287
|
+
|
|
288
|
+
# 更新内存中的文件配置
|
|
289
|
+
self._file_config = merged
|
|
290
|
+
|
|
291
|
+
# 写回 config.json
|
|
292
|
+
try:
|
|
293
|
+
with open(CONFIG_FILE_PATH, 'w', encoding='utf-8') as f:
|
|
294
|
+
json.dump(merged, f, ensure_ascii=False, indent=2)
|
|
295
|
+
self._logger.info(f"✅ 配置文件已自动更新至 v{config_version}")
|
|
296
|
+
except Exception as e:
|
|
297
|
+
self._logger.error(f"❌ 自动更新配置文件失败: {str(e)}", exc_info=True)
|
|
298
|
+
|
|
299
|
+
# 创建全局配置管理器实例
|
|
300
|
+
config_manager = ConfigManager()
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""GracyBot 装饰器体系 — 声明式插件开发
|
|
2
|
+
|
|
3
|
+
装饰器加载顺序(从下往上/从内到外):
|
|
4
|
+
@on_command("/cmd")
|
|
5
|
+
@require_permission("all")
|
|
6
|
+
@plugin_handler
|
|
7
|
+
async def handler(ctx): ...
|
|
8
|
+
|
|
9
|
+
执行顺序(从上往下/从外到内):
|
|
10
|
+
|
|
11
|
+
1. @on_command → 注册到全局命令池
|
|
12
|
+
2. @require_permission → 权限校验
|
|
13
|
+
3. @plugin_handler → 参数注入 + 计时 + 异常捕获
|
|
14
|
+
4. handler() → 业务逻辑
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from .context import PluginContext
|
|
18
|
+
from .registration import on_command, on_regex, on_keyword, gracy_plugin, DECORATOR_COMMAND_REGISTRY
|
|
19
|
+
from .security import require_permission, require_master, rate_limit, cooldown
|
|
20
|
+
from .handler import plugin_handler
|
|
21
|
+
from .session import with_session
|
|
22
|
+
from .async_utils import async_retry, background
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"PluginContext",
|
|
26
|
+
"on_command", "on_regex", "on_keyword", "gracy_plugin", "DECORATOR_COMMAND_REGISTRY",
|
|
27
|
+
"require_permission", "require_master", "rate_limit", "cooldown",
|
|
28
|
+
"plugin_handler",
|
|
29
|
+
"with_session",
|
|
30
|
+
"async_retry", "background",
|
|
31
|
+
]
|