You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

272 satır
11KB

  1. # encoding:utf-8
  2. import json
  3. import os
  4. import traceback
  5. from typing import Tuple
  6. from bridge.bridge import Bridge
  7. from bridge.context import ContextType
  8. from bridge.reply import Reply, ReplyType
  9. from config import load_config
  10. import plugins
  11. from plugins import *
  12. from common.log import logger
  13. # 定义指令集
  14. COMMANDS = {
  15. "help": {
  16. "alias": ["help", "帮助"],
  17. "desc": "打印指令集合",
  18. },
  19. "auth": {
  20. "alias": ["auth", "认证"],
  21. "args": ["口令"],
  22. "desc": "管理员认证",
  23. },
  24. # "id": {
  25. # "alias": ["id", "用户"],
  26. # "desc": "获取用户id", #目前无实际意义
  27. # },
  28. "reset": {
  29. "alias": ["reset", "重置会话"],
  30. "desc": "重置会话",
  31. },
  32. }
  33. ADMIN_COMMANDS = {
  34. "resume": {
  35. "alias": ["resume", "恢复服务"],
  36. "desc": "恢复服务",
  37. },
  38. "stop": {
  39. "alias": ["stop", "暂停服务"],
  40. "desc": "暂停服务",
  41. },
  42. "reconf": {
  43. "alias": ["reconf", "重载配置"],
  44. "desc": "重载配置(不包含插件配置)",
  45. },
  46. "resetall": {
  47. "alias": ["resetall", "重置所有会话"],
  48. "desc": "重置所有会话",
  49. },
  50. "scanp": {
  51. "alias": ["scanp", "扫描插件"],
  52. "desc": "扫描插件目录是否有新插件",
  53. },
  54. "plist": {
  55. "alias": ["plist", "插件"],
  56. "desc": "打印当前插件列表",
  57. },
  58. "setpri": {
  59. "alias": ["setpri", "设置插件优先级"],
  60. "args": ["插件名", "优先级"],
  61. "desc": "设置指定插件的优先级,越大越优先",
  62. },
  63. "enablep": {
  64. "alias": ["enablep", "启用插件"],
  65. "args": ["插件名"],
  66. "desc": "启用指定插件",
  67. },
  68. "disablep": {
  69. "alias": ["disablep", "禁用插件"],
  70. "args": ["插件名"],
  71. "desc": "禁用指定插件",
  72. },
  73. "debug": {
  74. "alias": ["debug", "调试模式", "DEBUG"],
  75. "desc": "开启机器调试日志",
  76. },
  77. }
  78. # 定义帮助函数
  79. def get_help_text(isadmin, isgroup):
  80. help_text = "可用指令:\n"
  81. for cmd, info in COMMANDS.items():
  82. if cmd=="auth" and (isadmin or isgroup): # 群聊不可认证
  83. continue
  84. alias=["#"+a for a in info['alias']]
  85. help_text += f"{','.join(alias)} "
  86. if 'args' in info:
  87. args=["{"+a+"}" for a in info['args']]
  88. help_text += f"{' '.join(args)} "
  89. help_text += f": {info['desc']}\n"
  90. if ADMIN_COMMANDS and isadmin:
  91. help_text += "\n管理员指令:\n"
  92. for cmd, info in ADMIN_COMMANDS.items():
  93. alias=["#"+a for a in info['alias']]
  94. help_text += f"{','.join(alias)} "
  95. help_text += f": {info['desc']}\n"
  96. return help_text
  97. @plugins.register(name="Godcmd", desc="为你的机器人添加指令集,有用户和管理员两种角色,加载顺序请放在首位,初次运行后插件目录会生成配置文件, 填充管理员密码后即可认证", version="1.0", author="lanvent", desire_priority= 999)
  98. class Godcmd(Plugin):
  99. def __init__(self):
  100. super().__init__()
  101. curdir=os.path.dirname(__file__)
  102. config_path=os.path.join(curdir,"config.json")
  103. gconf=None
  104. if not os.path.exists(config_path):
  105. gconf={"password":"","admin_users":[]}
  106. with open(config_path,"w") as f:
  107. json.dump(gconf,f,indent=4)
  108. else:
  109. with open(config_path,"r") as f:
  110. gconf=json.load(f)
  111. self.password = gconf["password"]
  112. self.admin_users = gconf["admin_users"] # 预存的管理员账号,这些账号不需要认证 TODO: 用户名每次都会变,目前不可用
  113. self.isrunning = True # 机器人是否运行中
  114. self.handlers[Event.ON_HANDLE_CONTEXT] = self.on_handle_context
  115. logger.info("[Godcmd] inited")
  116. def on_handle_context(self, e_context: EventContext):
  117. context_type = e_context['context'].type
  118. if context_type != ContextType.TEXT:
  119. if not self.isrunning:
  120. e_context.action = EventAction.BREAK_PASS
  121. return
  122. content = e_context['context'].content
  123. logger.debug("[Godcmd] on_handle_context. content: %s" % content)
  124. if content.startswith("#"):
  125. # msg = e_context['context']['msg']
  126. user = e_context['context']['receiver']
  127. session_id = e_context['context']['session_id']
  128. isgroup = e_context['context']['isgroup']
  129. bottype = Bridge().get_bot_type("chat")
  130. bot = Bridge().get_bot("chat")
  131. # 将命令和参数分割
  132. command_parts = content[1:].split(" ")
  133. cmd = command_parts[0]
  134. args = command_parts[1:]
  135. isadmin=False
  136. if user in self.admin_users:
  137. isadmin=True
  138. ok=False
  139. result="string"
  140. if any(cmd in info['alias'] for info in COMMANDS.values()):
  141. cmd = next(c for c, info in COMMANDS.items() if cmd in info['alias'])
  142. if cmd == "auth":
  143. ok, result = self.authenticate(user, args, isadmin, isgroup)
  144. elif cmd == "help":
  145. ok, result = True, get_help_text(isadmin, isgroup)
  146. elif cmd == "id":
  147. ok, result = True, f"用户id=\n{user}"
  148. elif cmd == "reset":
  149. if bottype == "chatGPT":
  150. bot.sessions.clear_session(session_id)
  151. ok, result = True, "会话已重置"
  152. else:
  153. ok, result = False, "当前对话机器人不支持重置会话"
  154. logger.debug("[Godcmd] command: %s by %s" % (cmd, user))
  155. elif any(cmd in info['alias'] for info in ADMIN_COMMANDS.values()):
  156. if isadmin:
  157. if isgroup:
  158. ok, result = False, "群聊不可执行管理员指令"
  159. else:
  160. cmd = next(c for c, info in ADMIN_COMMANDS.items() if cmd in info['alias'])
  161. if cmd == "stop":
  162. self.isrunning = False
  163. ok, result = True, "服务已暂停"
  164. elif cmd == "resume":
  165. self.isrunning = True
  166. ok, result = True, "服务已恢复"
  167. elif cmd == "reconf":
  168. load_config()
  169. ok, result = True, "配置已重载"
  170. elif cmd == "resetall":
  171. if bottype == "chatGPT":
  172. bot.sessions.clear_all_session()
  173. ok, result = True, "重置所有会话成功"
  174. else:
  175. ok, result = False, "当前对话机器人不支持重置会话"
  176. elif cmd == "debug":
  177. logger.setLevel('DEBUG')
  178. ok, result = True, "DEBUG模式已开启"
  179. elif cmd == "plist":
  180. plugins = PluginManager().list_plugins()
  181. ok = True
  182. result = "插件列表:\n"
  183. for name,plugincls in plugins.items():
  184. result += f"{name}_v{plugincls.version} {plugincls.priority} - "
  185. if plugincls.enabled:
  186. result += "已启用\n"
  187. else:
  188. result += "未启用\n"
  189. elif cmd == "scanp":
  190. new_plugins = PluginManager().scan_plugins()
  191. ok, result = True, "插件扫描完成"
  192. PluginManager().activate_plugins()
  193. if len(new_plugins) >0 :
  194. result += "\n发现新插件:\n"
  195. result += "\n".join([f"{p.name}_v{p.version}" for p in new_plugins])
  196. else :
  197. result +=", 未发现新插件"
  198. elif cmd == "setpri":
  199. if len(args) != 2:
  200. ok, result = False, "请提供插件名和优先级"
  201. else:
  202. ok = PluginManager().set_plugin_priority(args[0], int(args[1]))
  203. if ok:
  204. result = "插件" + args[0] + "优先级已设置为" + args[1]
  205. else:
  206. result = "插件不存在"
  207. elif cmd == "enablep":
  208. if len(args) != 1:
  209. ok, result = False, "请提供插件名"
  210. else:
  211. ok = PluginManager().enable_plugin(args[0])
  212. if ok:
  213. result = "插件已启用"
  214. else:
  215. result = "插件不存在"
  216. elif cmd == "disablep":
  217. if len(args) != 1:
  218. ok, result = False, "请提供插件名"
  219. else:
  220. ok = PluginManager().disable_plugin(args[0])
  221. if ok:
  222. result = "插件已禁用"
  223. else:
  224. result = "插件不存在"
  225. logger.debug("[Godcmd] admin command: %s by %s" % (cmd, user))
  226. else:
  227. ok, result = False, "需要管理员权限才能执行该指令"
  228. else:
  229. ok, result = False, f"未知指令:{cmd}\n查看指令列表请输入#help \n"
  230. reply = Reply()
  231. if ok:
  232. reply.type = ReplyType.INFO
  233. else:
  234. reply.type = ReplyType.ERROR
  235. reply.content = result
  236. e_context['reply'] = reply
  237. e_context.action = EventAction.BREAK_PASS # 事件结束,并跳过处理context的默认逻辑
  238. elif not self.isrunning:
  239. e_context.action = EventAction.BREAK_PASS
  240. def authenticate(self, userid, args, isadmin, isgroup) -> Tuple[bool,str] :
  241. if isgroup:
  242. return False,"请勿在群聊中认证"
  243. if isadmin:
  244. return False,"管理员账号无需认证"
  245. if len(args) != 1:
  246. return False,"请提供口令"
  247. password = args[0]
  248. if password == self.password:
  249. self.admin_users.append(userid)
  250. return True,"认证成功"
  251. else:
  252. return False,"认证失败"