2023-03-27 13:09:40 +00:00
|
|
|
|
import logging
|
|
|
|
|
|
import copy
|
2023-03-28 03:12:19 +00:00
|
|
|
|
import pkgutil
|
|
|
|
|
|
import traceback
|
2023-03-28 13:47:45 +00:00
|
|
|
|
import json
|
2023-03-27 13:09:40 +00:00
|
|
|
|
|
|
|
|
|
|
|
2023-03-28 12:18:19 +00:00
|
|
|
|
__command_list__ = {}
|
2023-04-01 00:52:35 +08:00
|
|
|
|
|
2023-04-07 13:20:57 +08:00
|
|
|
|
import tips as tips_custom
|
2023-04-01 00:52:35 +08:00
|
|
|
|
|
2023-03-27 13:09:40 +00:00
|
|
|
|
"""命令树
|
|
|
|
|
|
|
|
|
|
|
|
结构:
|
|
|
|
|
|
{
|
|
|
|
|
|
'cmd1': {
|
|
|
|
|
|
'description': 'cmd1 description',
|
|
|
|
|
|
'usage': 'cmd1 usage',
|
|
|
|
|
|
'aliases': ['cmd1 alias1', 'cmd1 alias2'],
|
|
|
|
|
|
'privilege': 0,
|
2023-03-28 12:18:19 +00:00
|
|
|
|
'parent': None,
|
2023-03-27 13:09:40 +00:00
|
|
|
|
'cls': <class 'pkg.qqbot.cmds.cmd1.CommandCmd1'>,
|
2023-03-28 12:18:19 +00:00
|
|
|
|
'sub': [
|
|
|
|
|
|
'cmd1-1'
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
'cmd1.cmd1-1: {
|
|
|
|
|
|
'description': 'cmd1-1 description',
|
|
|
|
|
|
'usage': 'cmd1-1 usage',
|
|
|
|
|
|
'aliases': ['cmd1-1 alias1', 'cmd1-1 alias2'],
|
|
|
|
|
|
'privilege': 0,
|
|
|
|
|
|
'parent': 'cmd1',
|
|
|
|
|
|
'cls': <class 'pkg.qqbot.cmds.cmd1.CommandCmd1_1'>,
|
|
|
|
|
|
'sub': []
|
2023-03-27 13:09:40 +00:00
|
|
|
|
},
|
|
|
|
|
|
'cmd2': {
|
|
|
|
|
|
'description': 'cmd2 description',
|
|
|
|
|
|
'usage': 'cmd2 usage',
|
|
|
|
|
|
'aliases': ['cmd2 alias1', 'cmd2 alias2'],
|
|
|
|
|
|
'privilege': 0,
|
2023-03-28 12:18:19 +00:00
|
|
|
|
'parent': None,
|
2023-03-27 13:09:40 +00:00
|
|
|
|
'cls': <class 'pkg.qqbot.cmds.cmd2.CommandCmd2'>,
|
2023-03-28 12:18:19 +00:00
|
|
|
|
'sub': [
|
|
|
|
|
|
'cmd2-1'
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
'cmd2.cmd2-1': {
|
|
|
|
|
|
'description': 'cmd2-1 description',
|
|
|
|
|
|
'usage': 'cmd2-1 usage',
|
|
|
|
|
|
'aliases': ['cmd2-1 alias1', 'cmd2-1 alias2'],
|
|
|
|
|
|
'privilege': 0,
|
|
|
|
|
|
'parent': 'cmd2',
|
|
|
|
|
|
'cls': <class 'pkg.qqbot.cmds.cmd2.CommandCmd2_1'>,
|
|
|
|
|
|
'sub': [
|
|
|
|
|
|
'cmd2-1-1'
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
'cmd2.cmd2-1.cmd2-1-1': {
|
|
|
|
|
|
'description': 'cmd2-1-1 description',
|
|
|
|
|
|
'usage': 'cmd2-1-1 usage',
|
|
|
|
|
|
'aliases': ['cmd2-1-1 alias1', 'cmd2-1-1 alias2'],
|
|
|
|
|
|
'privilege': 0,
|
|
|
|
|
|
'parent': 'cmd2.cmd2-1',
|
|
|
|
|
|
'cls': <class 'pkg.qqbot.cmds.cmd2.CommandCmd2_1_1'>,
|
|
|
|
|
|
'sub': []
|
|
|
|
|
|
},
|
2023-03-27 13:09:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
__tree_index__: dict[str, list] = {}
|
|
|
|
|
|
"""命令树索引
|
|
|
|
|
|
|
|
|
|
|
|
结构:
|
|
|
|
|
|
{
|
2023-03-28 12:18:19 +00:00
|
|
|
|
'pkg.qqbot.cmds.cmd1.CommandCmd1': 'cmd1', # 顶级指令
|
|
|
|
|
|
'pkg.qqbot.cmds.cmd1.CommandCmd1_1': 'cmd1.cmd1-1', # 类名: 节点路径
|
|
|
|
|
|
'pkg.qqbot.cmds.cmd2.CommandCmd2': 'cmd2',
|
|
|
|
|
|
'pkg.qqbot.cmds.cmd2.CommandCmd2_1': 'cmd2.cmd2-1',
|
|
|
|
|
|
'pkg.qqbot.cmds.cmd2.CommandCmd2_1_1': 'cmd2.cmd2-1.cmd2-1-1',
|
2023-03-27 13:09:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Context:
|
|
|
|
|
|
"""命令执行上下文"""
|
|
|
|
|
|
command: str
|
|
|
|
|
|
"""顶级指令文本"""
|
|
|
|
|
|
|
|
|
|
|
|
crt_command: str
|
|
|
|
|
|
"""当前子指令文本"""
|
|
|
|
|
|
|
|
|
|
|
|
params: list
|
|
|
|
|
|
"""完整参数列表"""
|
|
|
|
|
|
|
|
|
|
|
|
crt_params: list
|
|
|
|
|
|
"""当前子指令参数列表"""
|
|
|
|
|
|
|
|
|
|
|
|
session_name: str
|
|
|
|
|
|
"""会话名"""
|
|
|
|
|
|
|
|
|
|
|
|
text_message: str
|
|
|
|
|
|
"""指令完整文本"""
|
|
|
|
|
|
|
|
|
|
|
|
launcher_type: str
|
|
|
|
|
|
"""指令发起者类型"""
|
|
|
|
|
|
|
|
|
|
|
|
launcher_id: int
|
|
|
|
|
|
"""指令发起者ID"""
|
|
|
|
|
|
|
|
|
|
|
|
sender_id: int
|
|
|
|
|
|
"""指令发送者ID"""
|
|
|
|
|
|
|
|
|
|
|
|
is_admin: bool
|
|
|
|
|
|
"""[过时]指令发送者是否为管理员"""
|
|
|
|
|
|
|
|
|
|
|
|
privilege: int
|
|
|
|
|
|
"""指令发送者权限等级"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
|
|
self.__dict__.update(kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-03-28 12:18:19 +00:00
|
|
|
|
class AbstractCommandNode:
|
2023-03-27 13:09:40 +00:00
|
|
|
|
"""指令抽象类"""
|
|
|
|
|
|
|
2023-03-28 03:12:19 +00:00
|
|
|
|
parent: type
|
|
|
|
|
|
"""父指令类"""
|
|
|
|
|
|
|
2023-03-27 13:09:40 +00:00
|
|
|
|
name: str
|
|
|
|
|
|
"""指令名"""
|
|
|
|
|
|
|
|
|
|
|
|
description: str
|
|
|
|
|
|
"""指令描述"""
|
|
|
|
|
|
|
|
|
|
|
|
usage: str
|
|
|
|
|
|
"""指令用法"""
|
|
|
|
|
|
|
|
|
|
|
|
aliases: list[str]
|
|
|
|
|
|
"""指令别名"""
|
|
|
|
|
|
|
|
|
|
|
|
privilege: int
|
|
|
|
|
|
"""指令权限等级, 权限大于等于此值的用户才能执行指令"""
|
|
|
|
|
|
|
|
|
|
|
|
@classmethod
|
2023-03-28 12:18:19 +00:00
|
|
|
|
def process(cls, ctx: Context) -> tuple[bool, list]:
|
2023-03-27 13:09:40 +00:00
|
|
|
|
"""指令处理函数
|
|
|
|
|
|
|
|
|
|
|
|
:param ctx: 指令执行上下文
|
|
|
|
|
|
|
2023-03-28 12:18:19 +00:00
|
|
|
|
:return: (是否执行, 回复列表(若执行))
|
|
|
|
|
|
|
|
|
|
|
|
若未执行,将自动以下一个参数查找并执行子指令
|
2023-03-27 13:09:40 +00:00
|
|
|
|
"""
|
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
2023-03-28 12:18:19 +00:00
|
|
|
|
@classmethod
|
|
|
|
|
|
def help(cls) -> str:
|
|
|
|
|
|
"""获取指令帮助信息"""
|
2023-03-30 03:38:33 +00:00
|
|
|
|
return '指令: {}\n描述: {}\n用法: \n{}\n别名: {}\n权限: {}'.format(
|
2023-03-28 12:18:19 +00:00
|
|
|
|
cls.name,
|
|
|
|
|
|
cls.description,
|
|
|
|
|
|
cls.usage,
|
|
|
|
|
|
', '.join(cls.aliases),
|
|
|
|
|
|
cls.privilege
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2023-03-27 13:09:40 +00:00
|
|
|
|
@staticmethod
|
2023-03-28 12:53:46 +00:00
|
|
|
|
def register(
|
|
|
|
|
|
parent: type = None,
|
|
|
|
|
|
name: str = None,
|
|
|
|
|
|
description: str = None,
|
|
|
|
|
|
usage: str = None,
|
|
|
|
|
|
aliases: list[str] = None,
|
|
|
|
|
|
privilege: int = 0
|
|
|
|
|
|
):
|
2023-03-27 13:09:40 +00:00
|
|
|
|
"""注册指令
|
|
|
|
|
|
|
|
|
|
|
|
:param cls: 指令类
|
|
|
|
|
|
:param name: 指令名
|
|
|
|
|
|
:param parent: 父指令类
|
|
|
|
|
|
"""
|
2023-03-28 12:18:19 +00:00
|
|
|
|
global __command_list__, __tree_index__
|
2023-03-28 12:53:46 +00:00
|
|
|
|
|
|
|
|
|
|
def wrapper(cls):
|
|
|
|
|
|
cls.name = name
|
|
|
|
|
|
cls.parent = parent
|
|
|
|
|
|
cls.description = description
|
|
|
|
|
|
cls.usage = usage
|
|
|
|
|
|
cls.aliases = aliases
|
|
|
|
|
|
cls.privilege = privilege
|
|
|
|
|
|
|
|
|
|
|
|
logging.debug("cls: {}, name: {}, parent: {}".format(cls, name, parent))
|
|
|
|
|
|
|
|
|
|
|
|
if parent is None:
|
|
|
|
|
|
# 顶级指令注册
|
|
|
|
|
|
__command_list__[name] = {
|
|
|
|
|
|
'description': cls.description,
|
|
|
|
|
|
'usage': cls.usage,
|
|
|
|
|
|
'aliases': cls.aliases,
|
|
|
|
|
|
'privilege': cls.privilege,
|
|
|
|
|
|
'parent': None,
|
|
|
|
|
|
'cls': cls,
|
|
|
|
|
|
'sub': []
|
|
|
|
|
|
}
|
|
|
|
|
|
# 更新索引
|
|
|
|
|
|
__tree_index__[cls.__module__ + '.' + cls.__name__] = name
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 获取父节点名称
|
|
|
|
|
|
path = __tree_index__[parent.__module__ + '.' + parent.__name__]
|
|
|
|
|
|
|
|
|
|
|
|
parent_node = __command_list__[path]
|
|
|
|
|
|
# 链接父子指令
|
|
|
|
|
|
__command_list__[path]['sub'].append(name)
|
|
|
|
|
|
# 注册子指令
|
|
|
|
|
|
__command_list__[path + '.' + name] = {
|
|
|
|
|
|
'description': cls.description,
|
|
|
|
|
|
'usage': cls.usage,
|
|
|
|
|
|
'aliases': cls.aliases,
|
|
|
|
|
|
'privilege': cls.privilege,
|
|
|
|
|
|
'parent': path,
|
|
|
|
|
|
'cls': cls,
|
|
|
|
|
|
'sub': []
|
|
|
|
|
|
}
|
|
|
|
|
|
# 更新索引
|
|
|
|
|
|
__tree_index__[cls.__module__ + '.' + cls.__name__] = path + '.' + name
|
|
|
|
|
|
|
|
|
|
|
|
return cls
|
|
|
|
|
|
|
|
|
|
|
|
return wrapper
|
2023-03-27 13:09:40 +00:00
|
|
|
|
|
2023-03-28 03:12:19 +00:00
|
|
|
|
|
|
|
|
|
|
class CommandPrivilegeError(Exception):
|
|
|
|
|
|
"""指令权限不足或不存在异常"""
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-03-27 13:09:40 +00:00
|
|
|
|
# 传入Context对象,广搜命令树,返回执行结果
|
|
|
|
|
|
# 若命令被处理,返回reply列表
|
|
|
|
|
|
# 若命令未被处理,继续执行下一级指令
|
|
|
|
|
|
# 若命令不存在,报异常
|
|
|
|
|
|
def execute(context: Context) -> list:
|
|
|
|
|
|
"""执行指令
|
|
|
|
|
|
|
|
|
|
|
|
:param ctx: 指令执行上下文
|
|
|
|
|
|
|
|
|
|
|
|
:return: 回复列表
|
|
|
|
|
|
"""
|
2023-03-28 12:18:19 +00:00
|
|
|
|
global __command_list__
|
2023-03-27 13:09:40 +00:00
|
|
|
|
|
|
|
|
|
|
# 拷贝ctx
|
|
|
|
|
|
ctx: Context = copy.deepcopy(context)
|
|
|
|
|
|
|
|
|
|
|
|
# 从树取出顶级指令
|
2023-03-28 12:18:19 +00:00
|
|
|
|
node = __command_list__
|
2023-03-27 13:09:40 +00:00
|
|
|
|
|
2023-03-28 12:18:19 +00:00
|
|
|
|
path = ctx.command
|
2023-03-28 03:12:19 +00:00
|
|
|
|
|
2023-03-27 13:09:40 +00:00
|
|
|
|
while True:
|
|
|
|
|
|
try:
|
2023-03-28 13:47:45 +00:00
|
|
|
|
node = __command_list__[path]
|
2023-05-18 19:44:20 +08:00
|
|
|
|
logging.debug('执行指令: {}'.format(path))
|
2023-03-28 12:18:19 +00:00
|
|
|
|
|
2023-03-27 13:09:40 +00:00
|
|
|
|
# 检查权限
|
2023-03-28 12:18:19 +00:00
|
|
|
|
if ctx.privilege < node['privilege']:
|
2023-04-01 16:09:07 +08:00
|
|
|
|
raise CommandPrivilegeError(tips_custom.command_admin_message+"{}".format(path))
|
2023-03-28 12:18:19 +00:00
|
|
|
|
|
2023-03-27 13:09:40 +00:00
|
|
|
|
# 执行
|
2023-03-28 12:18:19 +00:00
|
|
|
|
execed, reply = node['cls'].process(ctx)
|
2023-03-27 13:09:40 +00:00
|
|
|
|
if execed:
|
|
|
|
|
|
return reply
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 删除crt_params第一个参数
|
2023-03-28 12:18:19 +00:00
|
|
|
|
ctx.crt_command = ctx.crt_params.pop(0)
|
|
|
|
|
|
# 下一个path
|
|
|
|
|
|
path = path + '.' + ctx.crt_command
|
2023-03-27 13:09:40 +00:00
|
|
|
|
except KeyError:
|
2023-03-28 12:53:46 +00:00
|
|
|
|
traceback.print_exc()
|
2023-04-01 16:09:07 +08:00
|
|
|
|
raise CommandPrivilegeError(tips_custom.command_err_message+"{}".format(path))
|
2023-03-28 03:12:19 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def register_all():
|
|
|
|
|
|
"""启动时调用此函数注册所有指令
|
|
|
|
|
|
|
|
|
|
|
|
递归处理pkg.qqbot.cmds包下及其子包下所有模块的所有继承于AbstractCommand的类
|
|
|
|
|
|
"""
|
|
|
|
|
|
# 模块:遍历其中的继承于AbstractCommand的类,进行注册
|
|
|
|
|
|
# 包:递归处理包下的模块
|
|
|
|
|
|
# 排除__开头的属性
|
2023-03-28 12:18:19 +00:00
|
|
|
|
global __command_list__, __tree_index__
|
|
|
|
|
|
|
2023-03-28 03:12:19 +00:00
|
|
|
|
import pkg.qqbot.cmds
|
|
|
|
|
|
|
|
|
|
|
|
def walk(module, prefix, path_prefix):
|
|
|
|
|
|
# 排除不处于pkg.qqbot.cmds中的包
|
|
|
|
|
|
if not module.__name__.startswith('pkg.qqbot.cmds'):
|
|
|
|
|
|
return
|
2023-03-30 03:38:33 +00:00
|
|
|
|
|
|
|
|
|
|
logging.debug('walk: {}, path: {}'.format(module.__name__, module.__path__))
|
2023-03-28 03:12:19 +00:00
|
|
|
|
for item in pkgutil.iter_modules(module.__path__):
|
|
|
|
|
|
if item.name.startswith('__'):
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
if item.ispkg:
|
|
|
|
|
|
walk(__import__(module.__name__ + '.' + item.name, fromlist=['']), prefix + item.name + '.', path_prefix + item.name + '/')
|
|
|
|
|
|
else:
|
|
|
|
|
|
m = __import__(module.__name__ + '.' + item.name, fromlist=[''])
|
2023-03-28 12:53:46 +00:00
|
|
|
|
# for name, cls in inspect.getmembers(m, inspect.isclass):
|
|
|
|
|
|
# # 检查是否为指令类
|
|
|
|
|
|
# if cls.__module__ == m.__name__ and issubclass(cls, AbstractCommandNode) and cls != AbstractCommandNode:
|
|
|
|
|
|
# cls.register(cls, cls.name, cls.parent)
|
2023-03-28 03:12:19 +00:00
|
|
|
|
|
|
|
|
|
|
walk(pkg.qqbot.cmds, '', '')
|
2023-03-28 12:18:19 +00:00
|
|
|
|
logging.debug(__command_list__)
|
2023-03-31 06:49:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def apply_privileges():
|
|
|
|
|
|
"""读取cmdpriv.json并应用指令权限"""
|
2023-04-02 16:24:30 +08:00
|
|
|
|
# 读取内容
|
|
|
|
|
|
json_str = ""
|
|
|
|
|
|
with open('cmdpriv.json', 'r', encoding="utf-8") as f:
|
|
|
|
|
|
json_str = f.read()
|
|
|
|
|
|
|
|
|
|
|
|
data = json.loads(json_str)
|
|
|
|
|
|
for path, priv in data.items():
|
|
|
|
|
|
if path == 'comment':
|
|
|
|
|
|
continue
|
2023-08-06 14:42:23 +08:00
|
|
|
|
|
|
|
|
|
|
if path not in __command_list__:
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
2023-04-02 16:24:30 +08:00
|
|
|
|
if __command_list__[path]['privilege'] != priv:
|
|
|
|
|
|
logging.debug('应用权限: {} -> {}(default: {})'.format(path, priv, __command_list__[path]['privilege']))
|
|
|
|
|
|
|
|
|
|
|
|
__command_list__[path]['privilege'] = priv
|