├── .github └── workflows │ └── docker-image-push.yaml ├── .gitignore ├── .gitmodules ├── DEV.md ├── Dockerfile ├── README.md ├── bot.py ├── docker-compose.yaml ├── docker └── entrypoint.sh ├── loader.py ├── log.py ├── main.py ├── processer.py ├── requirements.txt ├── util.py ├── variable.py └── ws.py /.github/workflows/docker-image-push.yaml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | 15 | - name: Set up Python 16 | uses: actions/setup-python@v4 17 | with: 18 | python-version: '3.x' 19 | 20 | - name: Install dependencies 21 | run: | 22 | python -m pip install --upgrade pip 23 | pip install -r requirements.txt # Assuming you have a requirements.txt file 24 | 25 | - name: Login to Docker Hub 26 | uses: docker/login-action@v2 27 | with: 28 | username: ${{ secrets.DOCKER_HUB_USERNAME }} 29 | password: ${{ secrets.DOCKER_HUB_TOKEN }} 30 | 31 | - name: Generate App Version 32 | run: echo APP_VERSION=$(git describe --tags --always) >> $GITHUB_ENV 33 | 34 | - name: Set up Docker Buildx 35 | uses: docker/setup-buildx-action@v2 36 | 37 | - name: Build and push 38 | uses: docker/build-push-action@v4 39 | with: 40 | context: . 41 | file: ./Dockerfile 42 | push: true 43 | platforms: | 44 | linux/amd64 45 | linux/arm64/v8 46 | tags: | 47 | ${{ secrets.DOCKER_HUB_USERNAME }}/sagiri-bot:latest 48 | ${{ secrets.DOCKER_HUB_USERNAME }}/sagiri-bot:${{ env.APP_VERSION }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | **/__pycache__/ 3 | data/ 4 | logs/ 5 | config.json 6 | ./config/config.json 7 | .venv -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "plugins"] 2 | path = plugins 3 | url = https://github.com/School-Robot/Python-Plugins 4 | -------------------------------------------------------------------------------- /DEV.md: -------------------------------------------------------------------------------- 1 | # 插件开发文档 2 | 3 | ## 插件信息 4 | 5 | ### 插件基本信息 6 | 7 | - `plugin_name` 插件名称 8 | - `plugin_version` 插件版本 9 | - `plugin_id` 插件识别ID 10 | - `plugin_author` 插件作者 11 | - `plugin_desc` 插件描述 12 | 13 | 插件基本信息应直接暴露,加载器会在加载过程中读取 14 | 15 | ### 插件类 16 | 17 | 插件类命名为`Plugin`,应直接暴露,加载器会在注册过程中进行实例化 18 | 19 | #### 类属性 20 | 21 | - `plugin_methods` 类方法,必需 22 | - `plugin_commands` 类命令,必需 23 | - `plugin_auths` 类权限,必需 24 | - `auth` 插件权限ID,必需 25 | - `status` 插件状态,必需 26 | 27 | #### 类方法 28 | 29 | 类方法使用字典表示,例如 30 | 31 | ```python 32 | { 33 | 'register':{ 34 | 'priority': 30000, 35 | 'func': 'zhuce', 36 | 'desc': '注册插件' 37 | }, 38 | 'enable':{ 39 | 'priority': 30000, 40 | 'func': 'qiyong', 41 | 'desc': '启用插件' 42 | } 43 | } 44 | ``` 45 | 46 | 其中`register`为标准方法名,加载器会根据标准方法名获取插件内方法,`priority`为优先级,值为数字,`func`为插件内方法名,`desc`为方法描述 47 | 48 | 标准方法名见[标准方法名](#标准方法名) 49 | 50 | 优先级见[优先级](#优先级) 51 | 52 | 方法信息见[方法信息](#方法信息) 53 | 54 | #### 类命令 55 | 56 | 类命令使用字典表示,例如 57 | 58 | ```python 59 | { 60 | 'echo': 'echo_command', 61 | 'help': 'echo - 向控制台输出消息' 62 | } 63 | ``` 64 | 65 | 命令使用空格分隔,其中`echo`为命令关键字,即第一个空格之前的内容,`echo_command`为命令处理函数,每个命令对应一个命令处理函数,命令会在去掉第一个参数后以数组形式传入,`help`为保留命令,对应值不会被认为是函数,`help`命令会被加载器拦截,并输出加载器内置命令及所有插件中`help`对应内容,如插件无命令请使用`{}`留空 66 | 67 | #### 类权限 68 | 69 | 类权限使用集合表示,例如 70 | 71 | ```python 72 | {'send_group_msg'} 73 | ``` 74 | 75 | 集合中的每个元素对应一个权限,不申请权限请使用`{}`留空 76 | 77 | 权限列表见[API列表](#API列表) 78 | 79 | #### 权限ID 80 | 81 | 权限ID为一串随机字符串,在启用插件时分配给插件,插件在调用需要权限的API时需传递权限ID以验证权限是否已经申请 82 | 83 | #### 插件状态 84 | 85 | 插件状态为插件生命周期状态,在注册、启用、禁用时会改变 86 | 87 | ### 生命周期 88 | 89 | 插件的生命周期分为六个阶段,分别为加载、注册、启用、禁用、注销、卸载 90 | 91 | - 加载:导入插件并加载插件基本信息 92 | - 注册:实例化插件类,传入工具类等,读取插件方法,并调用`register`方法 93 | - 启用:读取插件命令及权限,分配权限ID,并调用`enable`方法 94 | - 禁用:调用`disable`方法,并删除插件命令及权限信息,删除权限ID 95 | - 注销:调用`unregister`方法,并删除插件方法,销毁插件类 96 | - 卸载:删除导入的插件,删除插件基本信息 97 | 98 | 插件启用前请不要调用工具类中的方法 99 | 100 | ### 标准方法名 101 | 102 | |标准方法名|作用|是否必须| 103 | |-|-|-| 104 | |`register`|注册插件逻辑|是| 105 | |`enable`|启用插件逻辑|是| 106 | |`disable`|禁用插件逻辑|是| 107 | |`unregister`|卸载插件逻辑|是| 108 | |`private_message`|私聊消息处理|否| 109 | |`group_message`|群消息处理|否| 110 | |`group_upload`|群文件上传|否| 111 | |`group_admin`|群管理员变动|否| 112 | |`group_decrease`|群成员减少|否| 113 | |`group_increase`|群成员增加|否| 114 | |`group_ban`|群禁言|否| 115 | |`friend_add`|好友添加|否| 116 | |`group_recall`|群消息撤回|否| 117 | |`friend_recall`|好友消息撤回|否| 118 | |`group_poke`|群内戳一戳|否| 119 | |`lucky_king`|群红包运气王|否| 120 | |`honor`|群成员荣誉变更|否| 121 | |`friend_request`|加好友请求|否| 122 | |`group_request`|加群请求|否| 123 | |`raw_ws_process`|处理原始WebSocket信息|否| 124 | 125 | ### 优先级 126 | 127 | |优先级|值|说明| 128 | |-|-|-| 129 | |最高|10000|监控类应用,无法用于拦截消息。(如:消息数目统计等)| 130 | |高|20000|消息控制类应用,可用于拦截消息。(如:机器人开关等)| 131 | |一般|30000|普通功能类应用。(如:天气查询、游戏类等)| 132 | |低|40000|聊天对话类应用。(如:词库、云词库)| 133 | 134 | ### 方法信息 135 | 136 | #### `register` 137 | 138 | 参数 139 | 140 | |字段名|数据类型|可能的值|说明| 141 | |-|-|-|-| 142 | |`logger`|`object Logger`|-|日志,用于记录插件日志| 143 | |`util`|`object Util`|-|工具类,用于调用API| 144 | |`bot`|`object Bot`|-|Bot类,用于获取机器人基本信息| 145 | |`data_dir`|`str`|-|插件数据目录,用于存放插件数据| 146 | 147 | 返回值 148 | 149 | 无 150 | 151 | #### `enable` 152 | 153 | 参数 154 | 155 | |字段名|数据类型|可能的值|说明| 156 | |-|-|-|-| 157 | |`auth`|`str`|-|权限ID| 158 | 159 | 返回值 160 | 161 | 无 162 | 163 | #### `disable` 164 | 165 | 参数 166 | 167 | 无 168 | 169 | 返回值 170 | 171 | 无 172 | 173 | #### `unregister` 174 | 175 | 参数 176 | 177 | 无 178 | 179 | 返回值 180 | 181 | 无 182 | 183 | #### `private_message` 184 | 185 | 参数 186 | 187 | |字段名|数据类型|可能的值|说明| 188 | |-|-|-|-| 189 | |`time`|`int`|-|事件发生时间| 190 | |`self_id`|`int`|-|收到事件的机器人的QQ号| 191 | |`sub_type`|`str`|`friend`、`group`、`other`|消息子类型,如果是好友则是`friend`,如果是群临时会话则是`group`| 192 | |`message_id`|`int`|-|消息ID| 193 | |`user_id`|`int`|-|发送者QQ号| 194 | |`message`|`list`|-|消息内容| 195 | |`raw_message`|`str`|-|原始消息内容| 196 | |`font`|`int`|-|字体| 197 | |`sender`|`dict`|-|发送人信息| 198 | 199 | 其中`sender`字段的内容如下: 200 | 201 | |字段名|数据类型|说明| 202 | |-|-|-| 203 | |`user_id`|`int`|发送者QQ号| 204 | |`nickname`|`str`|昵称| 205 | |`sex`|`str`|性别,`male`或`female`或`unknown`| 206 | |`age`|`int`|年龄| 207 | 208 | 根据OneBot协议,`sender`中的内容为尽可能提供,使用前请自行判断是否存在 209 | 210 | 返回值 211 | 212 | |返回类型|说明| 213 | |-|-| 214 | |`bool`|是否拦截消息,优先级小于等于`10000`时无法拦截| 215 | 216 | #### `group_message` 217 | 218 | 参数 219 | 220 | |字段名|数据类型|可能的值|说明| 221 | |-|-|-|-| 222 | |`time`|`int`|-|事件发生时间| 223 | |`self_id`|`int`|-|收到事件的机器人的QQ号| 224 | |`sub_type`|`str`|`normal`、`anonymous`、`notice`|消息子类型,正常消息是`normal`,匿名消息是`anonymous`,系统提示(如「管理员已禁止群内匿名聊天」)是`notice`| 225 | |`message_id`|`int`|-|消息ID| 226 | |`group_id`|`int`|-|群号| 227 | |`user_id`|`int`|-|发送者QQ号| 228 | |`anonymous`|`dict`|-|匿名信息,如果不是匿名消息则为`None`| 229 | |`message`|`list`|-|消息内容| 230 | |`raw_message`|`str`|-|原始消息内容| 231 | |`font`|`int`|-|字体| 232 | |`sender`|`dict`|-|发送人信息| 233 | 234 | 其中`anonymous`字段的内容如下: 235 | 236 | |字段名|数据类型|说明| 237 | |-|-|-| 238 | |`id`|`int`|匿名用户ID| 239 | |`name`|`str`|匿名用户名称| 240 | |`flag`|`str`|匿名用户flag,在调用禁言API时需要传入| 241 | 242 | `sender`字段的内容如下: 243 | 244 | |字段名|数据类型|说明| 245 | |-|-|-| 246 | |`user_id`|`int`|发送者QQ号| 247 | |`nickname`|`str`|昵称| 248 | |`card`|`str`|群名片/备注| 249 | |`sex`|`str`|性别,`male`或`female`或`unknown`| 250 | |`age`|`int`|年龄| 251 | |`area`|`str`|地区| 252 | |`level`|`str`|成员等级| 253 | |`role`|`str`|角色,`owner`或`admin`或`member`| 254 | |`title`|`str`|专属头衔| 255 | 256 | 返回值 257 | 258 | |返回类型|说明| 259 | |-|-| 260 | |`bool`|是否拦截消息,优先级小于等于`10000`时无法拦截| 261 | 262 | #### `group_upload` 263 | 264 | 参数 265 | 266 | |字段名|数据类型|可能的值|说明| 267 | |-|-|-|-| 268 | |`time`|`int`|-|事件发生时间| 269 | |`self_id`|`int`|-|收到事件的机器人的QQ号| 270 | |`group_id`|`int`|-|群号| 271 | |`user_id`|`int`|-|发送者QQ号| 272 | |`file`|`dict`|-|文件信息| 273 | 274 | 其中`file`字段的内容如下: 275 | 276 | |字段名|数据类型|说明| 277 | |-|-|-| 278 | |`id`|`str`|文件ID| 279 | |`name`|`str`|文件名| 280 | |`size`|`int`|文件大小(字节数)| 281 | |`busid`|`int`|busid(目前不清楚有什么作用)| 282 | 283 | 返回值 284 | 285 | |返回类型|说明| 286 | |-|-| 287 | |`bool`|是否拦截消息,优先级小于等于`10000`时无法拦截| 288 | 289 | #### `group_admin` 290 | 291 | 参数 292 | 293 | |字段名|数据类型|可能的值|说明| 294 | |-|-|-|-| 295 | |`time`|`int`|-|事件发生时间| 296 | |`self_id`|`int`|-|收到事件的机器人的QQ号| 297 | |`sub_type`|`str`|`set`、`unset`|事件子类型,分别表示设置和取消管理员| 298 | |`group_id`|`int`|-|群号| 299 | |`user_id`|`int`|-|管理员QQ号| 300 | 301 | 返回值 302 | 303 | |返回类型|说明| 304 | |-|-| 305 | |`bool`|是否拦截消息,优先级小于等于`10000`时无法拦截| 306 | 307 | #### `group_decrease` 308 | 309 | 参数 310 | 311 | |字段名|数据类型|可能的值|说明| 312 | |-|-|-|-| 313 | |`time`|`int`|-|事件发生时间| 314 | |`self_id`|`int`|-|收到事件的机器人的QQ号| 315 | |`sub_type`|`str`|`leave`、`kick`、`kick_me`|事件子类型,分别表示主动退群、成员被踢、登录号被踢| 316 | |`group_id`|`int`|-|群号| 317 | |`operator_id`|`int`|-|操作者QQ号(如果是主动退群,则和`user_id`相同)| 318 | |`user_id`|`int`|-|离开者QQ号| 319 | 320 | 返回值 321 | 322 | |返回类型|说明| 323 | |-|-| 324 | |`bool`|是否拦截消息,优先级小于等于`10000`时无法拦截| 325 | 326 | #### `group_increase` 327 | 328 | 参数 329 | 330 | |字段名|数据类型|可能的值|说明| 331 | |-|-|-|-| 332 | |`time`|`int`|-|事件发生时间| 333 | |`self_id`|`int`|-|收到事件的机器人的QQ号| 334 | |`sub_type`|`str`|`approve`、`invite`|事件子类型,分别表示管理员已同意入群、管理员邀请入群| 335 | |`group_id`|`int`|-|群号| 336 | |`operator_id`|`int`|-|操作者QQ号(如果是主动退群,则和`user_id`相同)| 337 | |`user_id`|`int`|-|加入者QQ号| 338 | 339 | 返回值 340 | 341 | |返回类型|说明| 342 | |-|-| 343 | |`bool`|是否拦截消息,优先级小于等于`10000`时无法拦截| 344 | 345 | #### `group_ban` 346 | 347 | 参数 348 | 349 | |字段名|数据类型|可能的值|说明| 350 | |-|-|-|-| 351 | |`time`|`int`|-|事件发生时间| 352 | |`self_id`|`int`|-|收到事件的机器人的QQ号| 353 | |`sub_type`|`str`|`ban`、`lift_ban`|事件子类型,分别表示禁言、解除禁言| 354 | |`group_id`|`int`|-|群号| 355 | |`operator_id`|`int`|-|操作者QQ号| 356 | |`user_id`|`int`|-|被禁言QQ号| 357 | |`duration`|`int`|-|禁言时长,单位秒| 358 | 359 | 返回值 360 | 361 | |返回类型|说明| 362 | |-|-| 363 | |`bool`|是否拦截消息,优先级小于等于`10000`时无法拦截| 364 | 365 | #### `friend_add` 366 | 367 | 参数 368 | 369 | |字段名|数据类型|可能的值|说明| 370 | |-|-|-|-| 371 | |`time`|`int`|-|事件发生时间| 372 | |`self_id`|`int`|-|收到事件的机器人的QQ号| 373 | |`user_id`|`int`|-|新添加好友QQ号 374 | 375 | 返回值 376 | 377 | |返回类型|说明| 378 | |-|-| 379 | |`bool`|是否拦截消息,优先级小于等于`10000`时无法拦截| 380 | 381 | #### `group_recall` 382 | 383 | 参数 384 | 385 | |字段名|数据类型|可能的值|说明| 386 | |-|-|-|-| 387 | |`time`|`int`|-|事件发生时间| 388 | |`self_id`|`int`|-|收到事件的机器人的QQ号| 389 | |`group_id`|`int`|-|群号| 390 | |`user_id`|`int`|-|消息发送者QQ号| 391 | |`operator_id`|`int`|-|操作者QQ号| 392 | |`message_id`|`int`|-|被撤回的消息ID| 393 | 394 | 返回值 395 | 396 | |返回类型|说明| 397 | |-|-| 398 | |`bool`|是否拦截消息,优先级小于等于`10000`时无法拦截| 399 | 400 | #### `friend_recall` 401 | 402 | 参数 403 | 404 | |字段名|数据类型|可能的值|说明| 405 | |-|-|-|-| 406 | |`time`|`int`|-|事件发生时间| 407 | |`self_id`|`int`|-|收到事件的机器人的QQ号| 408 | |`user_id`|`int`|-|好友QQ号| 409 | |`message_id`|`int`|-|被撤回的消息ID 410 | 411 | 返回值 412 | 413 | |返回类型|说明| 414 | |-|-| 415 | |`bool`|是否拦截消息,优先级小于等于`10000`时无法拦截| 416 | 417 | #### `group_poke` 418 | 419 | 参数 420 | 421 | |字段名|数据类型|可能的值|说明| 422 | |-|-|-|-| 423 | |`time`|`int`|-|事件发生时间| 424 | |`self_id`|`int`|-|收到事件的机器人的QQ号| 425 | |`group_id`|`int`|-|群号| 426 | |`user_id`|`int`|-|发送者QQ号| 427 | |`target_id`|`int`|-|被戳者QQ号 428 | 429 | 返回值 430 | 431 | |返回类型|说明| 432 | |-|-| 433 | |`bool`|是否拦截消息,优先级小于等于`10000`时无法拦截| 434 | 435 | #### `lucky_king` 436 | 437 | 参数 438 | 439 | |字段名|数据类型|可能的值|说明| 440 | |-|-|-|-| 441 | |`time`|`int`|-|事件发生时间| 442 | |`self_id`|`int`|-|收到事件的机器人的QQ号| 443 | |`group_id`|`int`|-|群号| 444 | |`user_id`|`int`|-|红包发送者QQ号| 445 | |`target_id`|`int`|-|运气王QQ号| 446 | 447 | 返回值 448 | 449 | |返回类型|说明| 450 | |-|-| 451 | |`bool`|是否拦截消息,优先级小于等于`10000`时无法拦截| 452 | 453 | #### `honor` 454 | 455 | 参数 456 | 457 | |字段名|数据类型|可能的值|说明| 458 | |-|-|-|-| 459 | |`time`|`int`|-|事件发生时间| 460 | |`self_id`|`int`|-|收到事件的机器人的QQ号| 461 | |`group_id`|`int`|-|群号| 462 | |`honor_type`|`str`|`talkative`、`performer`、`emotion`荣誉类型,分别表示龙王、群聊之火、快乐源泉| 463 | |`user_id`|`int`|-|成员QQ号| 464 | 465 | 返回值 466 | 467 | |返回类型|说明| 468 | |-|-| 469 | |`bool`|是否拦截消息,优先级小于等于`10000`时无法拦截| 470 | 471 | #### `friend_request` 472 | 473 | 参数 474 | 475 | |字段名|数据类型|可能的值|说明| 476 | |-|-|-|-| 477 | |`time`|`int`|-|事件发生时间| 478 | |`self_id`|`int`|-|收到事件的机器人的QQ号| 479 | |`user_id`|`int`|-|发送请求的QQ号| 480 | |`comment`|`str`|-|验证信息| 481 | |`flag`|`str`|-|请求flag,在调用处理请求的API时需要传入| 482 | 483 | 返回值 484 | 485 | |返回类型|说明| 486 | |-|-| 487 | |`bool`|是否拦截消息,优先级小于等于`10000`时无法拦截| 488 | 489 | #### `group_request` 490 | 491 | 参数 492 | 493 | |字段名|数据类型|可能的值|说明| 494 | |-|-|-|-| 495 | |`time`|`int`|-|事件发生时间| 496 | |`self_id`|`int`|-|收到事件的机器人的QQ号| 497 | |`sub_type`|`str`|`add`、`invite`|请求子类型,分别表示加群请求、邀请登录号入群| 498 | |`group_id`|`int`|-|群号| 499 | |`user_id`|`int`|-|发送请求的QQ号| 500 | |`comment`|`str`|-|验证信息| 501 | |`flag`|`str`|-|请求flag,在调用处理请求的API时需要传入| 502 | 503 | 返回值 504 | 505 | |返回类型|说明| 506 | |-|-| 507 | |`bool`|是否拦截消息,优先级小于等于`10000`时无法拦截| 508 | 509 | #### `raw_ws_process` 510 | 511 | 参数 512 | 513 | |字段名|数据类型|可能的值|说明| 514 | |-|-|-|-| 515 | |`msg`|`dict`|-|原始WebSocket消息| 516 | 517 | 返回值 518 | 519 | 无 520 | 521 | ### API列表 522 | 523 | 使用工具类`Util`进行API调用,接口的返回类型为元组,分别为状态和数据,对字符串和消息段处理的接口直接返回数据 524 | 525 | |名称|是否需要权限|描述| 526 | |-|-|-| 527 | |`send_private_msg`|是|发送私聊消息| 528 | |`send_group_msg`|是|发送群消息| 529 | |`send_msg`|是|发送消息| 530 | |`delete_msg`|是|撤回消息| 531 | |`get_msg`|是|获取消息| 532 | |`get_forward_msg`|是|获取合并转发消息| 533 | |`send_like`|是|发送好友赞| 534 | |`set_group_kick`|是|群组踢人| 535 | |`set_group_ban`|是|群组单人禁言| 536 | |`set_group_anonymous_ban`|是|群组匿名用户禁言| 537 | |`set_group_whole_ban`|是|群组全员禁言| 538 | |`set_group_admin`|是|群组设置管理员| 539 | |`set_group_anonymous`|是|群组匿名| 540 | |`set_group_card`|是|设置群名片| 541 | |`set_group_name`|是|设置群名| 542 | |`set_group_leave`|是|退出群组| 543 | |`set_group_special_title`|是|设置群组专属头衔| 544 | |`set_friend_add_request`|是|处理加好友请求| 545 | |`set_group_add_request`|是|处理加群请求| 546 | |`get_login_info`|是|获取登录号信息| 547 | |`get_stranger_info`|是|获取陌生人信息| 548 | |`get_friend_list`|是|获取好友列表| 549 | |`get_group_info`|是|获取群信息| 550 | |`get_group_list`|是|获取群列表| 551 | |`get_group_member_info`|是|获取群成员信息| 552 | |`get_group_member_list`|是|获取群成员列表| 553 | |`get_group_honor_info`|是|获取群荣耀信息| 554 | |`get_cookies`|是|获取Cookies| 555 | |`get_csrf_token`|是|获取CSRF Token| 556 | |`get_credentials`|是|获取QQ相关接口凭证| 557 | |`get_record`|是|获取语音| 558 | |`get_image`|是|获取图片| 559 | |`can_send_image`|否| 检查是否可以发送图片| 560 | |`can_send_record`|否| 检查是否可以发送语音| 561 | |`get_status`|是|获取运行状态| 562 | |`get_version_info`|是|获取版本信息| 563 | |`set_restart`|是|重启OneBot| 564 | |`clean_cache`|是|清理缓存| 565 | |`plugin_control`|是|插件控制| 566 | |`send_ws_msg`|是|发送WebSocket消息| 567 | |`get_ws_msg`|是|获取WebSocket返回内容| 568 | |`escape`|否|转义字符| 569 | |`unescape`|否|反转义字符| 570 | |`seg_text`|否|文本消息段| 571 | |`cq_text`|否|文本CQ码| 572 | |`seg_face`|否|表情消息段| 573 | |`cq_face`|否|表情CQ码| 574 | |`seg_image`|否|图片消息段| 575 | |`cq_image`|否|图片CQ码| 576 | |`seg_record`|否|语音消息段| 577 | |`cq_record`|否|语音CQ码| 578 | |`seg_video`|否|视频消息段| 579 | |`cq_video`|否|视频CQ码| 580 | |`seg_at`|否|At消息段| 581 | |`cq_at`|否|At CQ码| 582 | |`seg_rps`|否|猜拳消息段| 583 | |`cq_rps`|否|猜拳CQ码| 584 | |`seg_dice`|否|骰子消息段| 585 | |`cq_dice`|否|骰子CQ码| 586 | |`seg_shake`|否|窗口抖动消息段| 587 | |`cq_shake`|否|窗口抖动CQ码| 588 | |`seg_poke`|否|戳一戳消息段| 589 | |`cq_poke`|否|戳一戳CQ码| 590 | |`seg_anonymous`|否|匿名消息段| 591 | |`cq_anonymous`|否|匿名CQ码| 592 | |`seg_share`|否|分享消息段| 593 | |`cq_share`|否|分享CQ码| 594 | |`seg_contact`|否|推荐消息段| 595 | |`cq_contact`|否|推荐CQ码| 596 | |`seg_location`|否|位置消息段| 597 | |`cq_location`|否|位置CQ码| 598 | |`seg_music`|否|音乐消息段| 599 | |`cq_music`|否|音乐CQ码| 600 | |`seg_reply`|否|回复消息段| 601 | |`cq_reply`|否|回复CQ码| 602 | |`seg_node`|否|转发消息段| 603 | |`cq_node`|否|转发CQ码| 604 | |`seg_xml`|否|XML消息段| 605 | |`cq_xml`|否|XML CQ码| 606 | |`seg_json`|否|JSON消息段| 607 | |`cq_json`|否|JSON CQ码| 608 | 609 | 610 | #### `send_private_msg` 611 | 612 | 参数 613 | 614 | |字段名|数据类型|默认值|说明 615 | |-|-|-|-| 616 | |`auth`|`str`|-|权限ID| 617 | |`user_id`|`int`|-|对方QQ号| 618 | |`message`|`list`、`str`|-|要发送的内容,类型为`list`时以消息段发送,类型为`str`时以CQ码发送| 619 | |`auto_escape`|`bool`|`False`|消息内容是否作为纯文本发送(即不解析 CQ 码),只在`message`字段是字符串时有效| 620 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 621 | 622 | 数据返回值 623 | 624 | |类型|说明| 625 | |-|-| 626 | |`dict`、`str`|返回`dict`时为返回的数据,返回`str`时为状态说明| 627 | 628 | 数据内容 629 | 630 | |字段名|类型|说明| 631 | |-|-|-| 632 | |`message_id`|`int`|消息ID| 633 | 634 | 额外说明 635 | 636 | 支持`send_private_msg_async`,返回数据始终为`async`,无`timeout`参数 637 | 638 | 支持`send_private_msg_rate_limit`,返回数据始终为`async`,无`timeout`参数 639 | 640 | #### `send_group_msg` 641 | 642 | 参数 643 | 644 | |字段名|数据类型|默认值|说明 645 | |-|-|-|-| 646 | |`auth`|`str`|-|权限ID| 647 | |`group_id`|`int`|-|群号| 648 | |`message`|`list`、`str`|-|要发送的内容,类型为`list`时以消息段发送,类型为`str`时以CQ码发送| 649 | |`auto_escape`|`bool`|`False`|消息内容是否作为纯文本发送(即不解析 CQ 码),只在`message`字段是字符串时有效| 650 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 651 | 652 | 数据返回值 653 | 654 | |类型|说明| 655 | |-|-| 656 | |`dict`、`str`|返回`dict`时为返回的数据,返回`str`时为状态说明| 657 | 658 | 数据内容 659 | 660 | |字段名|类型|说明| 661 | |-|-|-| 662 | |`message_id`|`int`|消息ID| 663 | 664 | 额外说明 665 | 666 | 支持`send_group_msg_async`,返回数据始终为`async`,无`timeout`参数 667 | 668 | 支持`send_group_msg_rate_limit`,返回数据始终为`async`,无`timeout`参数 669 | 670 | #### `send_msg` 671 | 672 | 参数 673 | 674 | |字段名|数据类型|默认值|说明 675 | |-|-|-|-| 676 | |`auth`|`str`|-|权限ID| 677 | |`message_type`|`str`、`None`|-|消息类型,支持`private`、`group`,分别对应私聊、群组,如不传入,则根据传入的 *_id 参数判断| 678 | |`user_id`|`int`、`None`|-|对方QQ号(消息类型为`private`时需要)| 679 | |`group_id`|`int`、`None`|-|群号(消息类型为`group`时需要)| 680 | |`message`|`list`、`str`|-|要发送的内容,类型为`list`时以消息段发送,类型为`str`时以CQ码发送| 681 | |`auto_escape`|`bool`|`False`|消息内容是否作为纯文本发送(即不解析 CQ 码),只在`message`字段是字符串时有效| 682 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 683 | 684 | 数据返回值 685 | 686 | |类型|说明| 687 | |-|-| 688 | |`dict`、`str`|返回`dict`时为返回的数据,返回`str`时为状态说明| 689 | 690 | 数据内容 691 | 692 | |字段名|类型|说明| 693 | |-|-|-| 694 | |`message_id`|`int`|消息ID| 695 | 696 | 额外说明 697 | 698 | 支持`send_msg_async`,返回数据始终为`async`,无`timeout`参数 699 | 700 | 支持`send_msg_rate_limit`,返回数据始终为`async`,无`timeout`参数 701 | 702 | #### `delete_msg` 703 | 704 | 参数 705 | 706 | |字段名|数据类型|默认值|说明 707 | |-|-|-|-| 708 | |`auth`|`str`|-|权限ID| 709 | |`message_id`|`int`|-|消息ID| 710 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 711 | 712 | 数据返回值 713 | 714 | |类型|说明| 715 | |-|-| 716 | |`str`|状态说明| 717 | 718 | 额外说明 719 | 720 | 支持`delete_msg_async`,返回数据始终为`async`,无`timeout`参数 721 | 722 | #### `get_msg` 723 | 724 | 参数 725 | 726 | |字段名|数据类型|默认值|说明 727 | |-|-|-|-| 728 | |`auth`|`str`|-|权限ID| 729 | |`message_id`|`int`|-|消息ID| 730 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 731 | 732 | 数据返回值 733 | 734 | |类型|说明| 735 | |-|-| 736 | |`dict`、`str`|返回`dict`时为返回的数据,返回`str`时为状态说明| 737 | 738 | 数据内容 739 | 740 | |字段名|类型|说明| 741 | |-|-|-| 742 | |`time`|`int`|发送时间| 743 | |`message_type`|`private`、`group`|消息类型| 744 | |`message_id`|`int`|消息ID| 745 | |`real_id`|`int`|消息真实ID| 746 | |`sender`|`dict`|发送人信息| 747 | |`message`|`list`|消息内容| 748 | 749 | 其中`sender`字段的内容如下: 750 | 751 | |字段名|数据类型|说明| 752 | |-|-|-| 753 | |`user_id`|`int`|发送者QQ号| 754 | |`nickname`|`str`|昵称| 755 | |`sex`|`str`|性别,`male`或`female`或`unknown`| 756 | |`age`|`int`|年龄| 757 | 758 | #### `get_forward_msg` 759 | 760 | 参数 761 | 762 | |字段名|数据类型|默认值|说明 763 | |-|-|-|-| 764 | |`auth`|`str`|-|权限ID| 765 | |`id`|`int`|-|合并转发ID| 766 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 767 | 768 | 数据返回值 769 | 770 | |类型|说明| 771 | |-|-| 772 | |`dict`、`str`|返回`dict`时为返回的数据,返回`str`时为状态说明| 773 | 774 | 数据内容 775 | 776 | |字段名|类型|说明| 777 | |-|-|-| 778 | |`message`|`list`|消息内容| 779 | 780 | #### `send_like` 781 | 782 | 参数 783 | 784 | |字段名|数据类型|默认值|说明 785 | |-|-|-|-| 786 | |`auth`|`str`|-|权限ID| 787 | |`user_id`|`int`|-|对方QQ号| 788 | |`times`|`int`|`1`|赞的次数,每个好友每天最多 10 次| 789 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 790 | 791 | 数据返回值 792 | 793 | |类型|说明| 794 | |-|-| 795 | |`str`|状态说明| 796 | 797 | 额外说明 798 | 799 | 支持`send_like_async`,返回数据始终为`async`,无`timeout`参数 800 | 801 | #### `set_group_kick` 802 | 803 | 参数 804 | 805 | |字段名|数据类型|默认值|说明 806 | |-|-|-|-| 807 | |`auth`|`str`|-|权限ID| 808 | |`group_id`|`int`|-|群号| 809 | |`user_id`|`int`|-|要踢的QQ号| 810 | |`reject_add_request`|`bool`|`False`|拒绝此人的加群请求| 811 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 812 | 813 | 数据返回值 814 | 815 | |类型|说明| 816 | |-|-| 817 | |`str`|状态说明| 818 | 819 | 额外说明 820 | 821 | 支持`set_group_kick_async`,返回数据始终为`async`,无`timeout`参数 822 | 823 | #### `set_group_ban` 824 | 825 | 参数 826 | 827 | |字段名|数据类型|默认值|说明 828 | |-|-|-|-| 829 | |`auth`|`str`|-|权限ID| 830 | |`group_id`|`int`|-|群号| 831 | |`user_id`|`int`|-|要禁言的QQ号| 832 | |`duration`|`int`|`30*60`|禁言时长,单位秒,`0`表示取消禁言| 833 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 834 | 835 | 数据返回值 836 | 837 | |类型|说明| 838 | |-|-| 839 | |`str`|状态说明| 840 | 841 | 额外说明 842 | 843 | 支持`set_group_ban_async`,返回数据始终为`async`,无`timeout`参数 844 | 845 | #### `set_group_anonymous_ban` 846 | 847 | 参数 848 | 849 | |字段名|数据类型|默认值|说明 850 | |-|-|-|-| 851 | |`auth`|`str`|-|权限ID| 852 | |`group_id`|`int`|-|群号| 853 | |`anonymous`|`dict`、`None`|-|可选,要禁言的匿名用户对象(群消息上报的`anonymous`字段)| 854 | |`anonymous_flag`|`str`、`None`|-|可选,要禁言的匿名用户的`flag`(需从群消息上报的数据中获得)| 855 | |`duration`|`int`|`30*60`|禁言时长,单位秒,无法取消匿名用户禁言| 856 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 857 | 858 | 上面的`anonymous`和`anonymous_flag`两者任选其一传入即可,若都传入,则使用 anonymous。 859 | 860 | 数据返回值 861 | 862 | |类型|说明| 863 | |-|-| 864 | |`str`|状态说明| 865 | 866 | 额外说明 867 | 868 | 支持`set_group_anonymous_ban_async`,返回数据始终为`async`,无`timeout`参数 869 | 870 | #### `set_group_whole_ban` 871 | 872 | 参数 873 | 874 | |字段名|数据类型|默认值|说明 875 | |-|-|-|-| 876 | |`auth`|`str`|-|权限ID| 877 | |`group_id`|`int`|-|群号| 878 | |`enable`|`bool`|`True`|是否禁言| 879 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 880 | 881 | 数据返回值 882 | 883 | |类型|说明| 884 | |-|-| 885 | |`str`|状态说明| 886 | 887 | 额外说明 888 | 889 | 支持`set_group_whole_ban_async`,返回数据始终为`async`,无`timeout`参数 890 | 891 | #### `set_group_admin` 892 | 893 | 参数 894 | 895 | |字段名|数据类型|默认值|说明| 896 | |-|-|-|-| 897 | |`auth`|`str`|-|权限ID| 898 | |`group_id`|`int`|-群号| 899 | |`user_id`|`int`|-|要设置管理员的QQ号| 900 | |`enable`|`bool`|`True`|`True`为设置,`False` 为取消| 901 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 902 | 903 | 数据返回值 904 | 905 | |类型|说明| 906 | |-|-| 907 | |`str`|状态说明| 908 | 909 | 额外说明 910 | 911 | 支持`set_group_admin_async`,返回数据始终为`async`,无`timeout`参数 912 | 913 | #### `set_group_anonymous` 914 | 915 | 参数 916 | 917 | |字段名|数据类型|默认值|说明| 918 | |-|-|-|-| 919 | |`auth`|`str`|-|权限ID| 920 | |`group_id`|`int`|-|群号| 921 | |`enable`|`bool`|`True`|是否允许匿名聊天| 922 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 923 | 924 | 数据返回值 925 | 926 | |类型|说明| 927 | |-|-| 928 | |`str`|状态说明| 929 | 930 | 额外说明 931 | 932 | 支持`set_group_admin_async`,返回数据始终为`async`,无`timeout`参数 933 | 934 | #### `set_group_card` 935 | 936 | 参数 937 | 938 | |字段名|数据类型|默认值|说明| 939 | |-|-|-|-| 940 | |`auth`|`str`|-|权限ID| 941 | |`group_id`|`int`|-|群号| 942 | |`user_id`|`int`|-|要设置的QQ号| 943 | |`card`|`str`|空|群名片内容,不填或空字符串表示删除群名片| 944 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 945 | 946 | 数据返回值 947 | 948 | |类型|说明| 949 | |-|-| 950 | |`str`|状态说明| 951 | 952 | 额外说明 953 | 954 | 支持`set_group_card_async`,返回数据始终为`async`,无`timeout`参数 955 | 956 | #### `set_group_name` 957 | 958 | 参数 959 | 960 | |字段名|数据类型|默认值|说明| 961 | |-|-|-|-| 962 | |`auth`|`str`|-|权限ID| 963 | |`group_id`|`int`|-|群号| 964 | |`group_name`|`str`|-|新群名| 965 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 966 | 967 | 数据返回值 968 | 969 | |类型|说明| 970 | |-|-| 971 | |`str`|状态说明| 972 | 973 | 额外说明 974 | 975 | 支持`set_group_name_async`,返回数据始终为`async`,无`timeout`参数 976 | 977 | #### `set_group_leave` 978 | 979 | 参数 980 | 981 | |字段名|数据类型|默认值|说明| 982 | |-|-|-|-| 983 | |`auth`|`str`|-|权限ID| 984 | |`group_id`|`int`|-|群号| 985 | |`is_dismiss`|`bool`|`False`|是否解散,如果登录号是群主,则仅在此项为`true`时能够解散| 986 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 987 | 988 | 数据返回值 989 | 990 | |类型|说明| 991 | |-|-| 992 | |`str`|状态说明| 993 | 994 | 额外说明 995 | 996 | 支持`set_group_leave_async`,返回数据始终为`async`,无`timeout`参数 997 | 998 | #### `set_group_special_title` 999 | 1000 | 参数 1001 | 1002 | |字段名|数据类型|默认值|说明| 1003 | |-|-|-|-| 1004 | |`auth`|`str`|-|权限ID| 1005 | |`group_id`|`int`|-|群号| 1006 | |`user_id`|`int`|-|要设置的QQ号| 1007 | |`special_title`|`str`|空|专属头衔,不填或空字符串表示删除专属头衔| 1008 | |`duration`|`int`|-1|专属头衔有效期,单位秒,`-1`表示永久,不过此项似乎没有效果,可能是只有某些特殊的时间长度有效,有待测试| 1009 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 1010 | 1011 | 数据返回值 1012 | 1013 | |类型|说明| 1014 | |-|-| 1015 | |`str`|状态说明| 1016 | 1017 | 额外说明 1018 | 1019 | 支持`set_group_special_async`,返回数据始终为`async`,无`timeout`参数 1020 | 1021 | #### `set_friend_add_request` 1022 | 1023 | 参数 1024 | 1025 | |字段名|数据类型|默认值|说明| 1026 | |-|-|-|-| 1027 | |`auth`|`str`|-|权限ID| 1028 | |`flag`|`str`|-|加好友请求的`flag`(需从上报的数据中获得)| 1029 | |`approve`|`bool`|`True`|是否同意请求| 1030 | |`remark`|`str`|空|添加后的好友备注(仅在同意时有效)| 1031 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 1032 | 1033 | 数据返回值 1034 | 1035 | |类型|说明| 1036 | |-|-| 1037 | |`str`|状态说明| 1038 | 1039 | 额外说明 1040 | 1041 | 支持`set_friend_add_request_async`,返回数据始终为`async`,无`timeout`参数 1042 | 1043 | #### `set_group_add_request` 1044 | 1045 | 参数 1046 | 1047 | |字段名|数据类型|默认值|说明| 1048 | |-|-|-|-| 1049 | |`auth`|`str`|-|权限ID| 1050 | |`flag`|`str`|-|加群请求的`flag`(需从上报的数据中获得)| 1051 | |`sub_type`|`str`|-|`add`或`invite`,请求类型(需要和上报消息中的`sub_type`字段相符)| 1052 | |`approve`|`bool`|`True`|是否同意请求/邀请| 1053 | |`reason`|`str`|空|拒绝理由(仅在拒绝时有效)| 1054 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 1055 | 1056 | 数据返回值 1057 | 1058 | |类型|说明| 1059 | |-|-| 1060 | |`str`|状态说明| 1061 | 1062 | 额外说明 1063 | 1064 | 支持`set_group_add_request_async`,返回数据始终为`async`,无`timeout`参数 1065 | 1066 | #### `get_login_info` 1067 | 1068 | 参数 1069 | 1070 | |字段名|数据类型|默认值|说明| 1071 | |-|-|-|-| 1072 | |`auth`|`str`|-|权限ID| 1073 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 1074 | 1075 | 数据返回值 1076 | 1077 | |类型|说明| 1078 | |-|-| 1079 | |`dict`、`str`|返回`dict`时为返回的数据,返回`str`时为状态说明| 1080 | 1081 | 数据内容 1082 | 1083 | |字段名|类型|说明| 1084 | |-|-|-| 1085 | |`user_id`|`int`|QQ号| 1086 | |`nickname`|`str`|QQ昵称| 1087 | 1088 | #### `get_stranger_info` 1089 | 1090 | 参数 1091 | 1092 | |字段名|数据类型|默认值|说明| 1093 | |-|-|-|-| 1094 | |`auth`|`str`|-|权限ID| 1095 | |`user_id`|`int`|-|QQ号| 1096 | |`no_cache`|`bool`|`False`|是否不使用缓存(使用缓存可能更新不及时,但响应更快)| 1097 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 1098 | 1099 | 1100 | 数据返回值 1101 | 1102 | |类型|说明| 1103 | |-|-| 1104 | |`dict`、`str`|返回`dict`时为返回的数据,返回`str`时为状态说明| 1105 | 1106 | 数据内容 1107 | 1108 | |字段名|类型|说明| 1109 | |-|-|-| 1110 | |`user_id`|`int`|QQ号| 1111 | |`nickname`|`str`|QQ昵称| 1112 | |`sex`|`str`|性别,`male`或`female`或`unknown`| 1113 | |`age`|`int`|年龄| 1114 | 1115 | #### `get_friend_list` 1116 | 1117 | 参数 1118 | 1119 | |字段名|数据类型|默认值|说明| 1120 | |-|-|-|-| 1121 | |`auth`|`str`|-|权限ID| 1122 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 1123 | 1124 | 数据返回值 1125 | 1126 | |类型|说明| 1127 | |-|-| 1128 | |`list`、`str`|返回`list`时为返回的数据,返回`str`时为状态说明| 1129 | 1130 | 数据内容 1131 | 1132 | |字段名|类型|说明| 1133 | |-|-|-| 1134 | |`user_id`|`int`|QQ号| 1135 | |`nickname`|`str`|QQ昵称| 1136 | |`remark`|`str`|备注名| 1137 | 1138 | #### `get_group_info` 1139 | 1140 | 参数 1141 | 1142 | |字段名|数据类型|默认值|说明| 1143 | |-|-|-|-| 1144 | |`auth`|`str`|-|权限ID| 1145 | |`group_id`|`int`|-|群号| 1146 | |`no_cache`|`bool`|`False`|是否不使用缓存(使用缓存可能更新不及时,但响应更快)| 1147 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 1148 | 1149 | 数据返回值 1150 | 1151 | |类型|说明| 1152 | |-|-| 1153 | |`dict`、`str`|返回`dict`时为返回的数据,返回`str`时为状态说明| 1154 | 1155 | 数据内容 1156 | 1157 | |字段名|类型|说明| 1158 | |-|-|-| 1159 | |`group_id`|`int`|群号| 1160 | |`group_name`|`str`|群名称| 1161 | |`member_count`|`int`|成员数| 1162 | |`max_member_count`|`int`|最大成员数(群容量)| 1163 | 1164 | #### `get_group_list` 1165 | 1166 | 参数 1167 | 1168 | |字段名|数据类型|默认值|说明| 1169 | |-|-|-|-| 1170 | |`auth`|`str`|-|权限ID| 1171 | |`group_id`|`int`|-|群号| 1172 | |`no_cache`|`bool`|`False`|是否不使用缓存(使用缓存可能更新不及时,但响应更快)| 1173 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 1174 | 1175 | 数据返回值 1176 | 1177 | |类型|说明| 1178 | |-|-| 1179 | |`list`、`str`|返回`list`时为返回的数据,返回`str`时为状态说明| 1180 | 1181 | 数据内容 1182 | 1183 | |字段名|类型|说明| 1184 | |-|-|-| 1185 | |`group_id`|`int`|群号| 1186 | |`group_name`|`str`|群名称| 1187 | |`member_count`|`int`|成员数| 1188 | |`max_member_count`|`int`|最大成员数(群容量)| 1189 | 1190 | #### `get_group_member_info` 1191 | 1192 | 参数 1193 | 1194 | |字段名|数据类型|默认值|说明| 1195 | |-|-|-|-| 1196 | |`auth`|`str`|-|权限ID| 1197 | |`group_id`|`int`|-|群号| 1198 | |`user_id`|`int`|-|QQ号| 1199 | |`no_cache`|`bool`|`False`|是否不使用缓存(使用缓存可能更新不及时,但响应更快)| 1200 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 1201 | 1202 | 数据返回值 1203 | 1204 | |类型|说明| 1205 | |-|-| 1206 | |`dict`、`str`|返回`dict`时为返回的数据,返回`str`时为状态说明| 1207 | 1208 | 数据内容 1209 | 1210 | |字段名|类型|说明| 1211 | |-|-|-| 1212 | |`group_id`|`int`|群号| 1213 | |`user_id`|`int`|QQ号| 1214 | |`nickname`|`str`|昵称| 1215 | |`card`|`str`|群名片/备注| 1216 | |`sex`|`str`|性别,`male`或`female`或`unknown`| 1217 | |`age`|`int`|年龄| 1218 | |`area`|`str`|地区| 1219 | |`join_time`|`int`|加群时间戳| 1220 | |`last_sent_time`|`int`|最后发言时间戳| 1221 | |`level`|`str`|成员等级| 1222 | |`role`|`str`|角色,`owner`或`admin`或`member`| 1223 | |`unfriendly`|`bool`|是否不良记录成员| 1224 | |`title`|`str`|专属头衔| 1225 | |`title_expire_time`|`int`|专属头衔过期时间戳| 1226 | |`card_changeable`|`bool`|是否允许修改群名片| 1227 | 1228 | #### `get_group_member_list` 1229 | 1230 | 参数 1231 | 1232 | |字段名|数据类型|默认值|说明| 1233 | |-|-|-|-| 1234 | |`auth`|`str`|-|权限ID| 1235 | |`group_id`|`int`|-|群号| 1236 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 1237 | 1238 | 数据返回值 1239 | 1240 | |类型|说明| 1241 | |-|-| 1242 | |`list`、`str`|返回`list`时为返回的数据,返回`str`时为状态说明| 1243 | 1244 | 数据内容 1245 | 1246 | |字段名|类型|说明| 1247 | |-|-|-| 1248 | |`group_id`|`int`|群号| 1249 | |`user_id`|`int`|QQ号| 1250 | |`nickname`|`str`|昵称| 1251 | |`card`|`str`|群名片/备注| 1252 | |`sex`|`str`|性别,`male`或`female`或`unknown`| 1253 | |`age`|`int`|年龄| 1254 | |`area`|`str`|地区| 1255 | |`join_time`|`int`|加群时间戳| 1256 | |`last_sent_time`|`int`|最后发言时间戳| 1257 | |`level`|`str`|成员等级| 1258 | |`role`|`str`|角色,`owner`或`admin`或`member`| 1259 | |`unfriendly`|`bool`|是否不良记录成员| 1260 | |`title`|`str`|专属头衔| 1261 | |`title_expire_time`|`int`|专属头衔过期时间戳| 1262 | |`card_changeable`|`bool`|是否允许修改群名片| 1263 | 1264 | 额外说明 1265 | 1266 | 对于同一个群组的同一个成员,获取列表时和获取单独的成员信息时,某些字段可能有所不同,例如`area`、`title`等字段在获取列表时无法获得,具体应以单独的成员信息为准。 1267 | 1268 | #### `get_group_honor_info` 1269 | 1270 | 参数 1271 | 1272 | |字段名|数据类型|默认值|说明| 1273 | |-|-|-|-| 1274 | |`auth`|`str`|-|权限ID| 1275 | |`group_id`|`int`|-|群号| 1276 | |`type`|`str`|-|要获取的群荣誉类型,可传入`talkative`、`performer`、`legend`、`strong_newbie`、`emotion`以分别获取单个类型的群荣誉数据,或传入`all`获取所有数据 1277 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 1278 | 1279 | 数据返回值 1280 | 1281 | |类型|说明| 1282 | |-|-| 1283 | |`dict`、`str`|返回`dict`时为返回的数据,返回`str`时为状态说明| 1284 | 1285 | 数据内容 1286 | 1287 | |字段名|类型|说明| 1288 | |-|-|-| 1289 | |`group_id`|`int`|群号| 1290 | |`current_talkative`|`dict`|当前龙王,仅`type`为`talkative`或`all`时有数据| 1291 | |`talkative_list`|`list`|历史龙王,仅`type`为`talkative`或`all`时有数据| 1292 | |`performer_list`|`list`|群聊之火,仅`type`为`performer`或`all`时有数据| 1293 | |`legend_list`|`list`|群聊炽焰,仅`type`为`legend`或`all`时有数据| 1294 | |`strong_newbie_list`|`list`|冒尖小春笋,仅`type`为`strong_newbie`或`all`时有数据| 1295 | |`emotion_list`|`list`|快乐之源,仅`type`为`emotion`或`all`时有数据| 1296 | 1297 | 其中`current_talkative`字段的内容如下: 1298 | 1299 | |字段名|数据类型|说明| 1300 | |-|-|-| 1301 | |`user_id`|`int`|QQ号| 1302 | |`nickname`|`str`|昵称| 1303 | |`avatar`|`str`|头像URL| 1304 | |`day_count`|`int`|持续天数| 1305 | 1306 | 其它各`*_list`字段的内容如下: 1307 | 1308 | |字段名|数据类型|说明| 1309 | |-|-|-| 1310 | |`user_id`|`int`|QQ号| 1311 | |`nickname`|`str`|昵称| 1312 | |`avatar`|`str`|头像URL| 1313 | |`description`|`str`|荣誉描述| 1314 | 1315 | #### `get_cookies` 1316 | 1317 | 参数 1318 | 1319 | |字段名|数据类型|默认值|说明| 1320 | |-|-|-|-| 1321 | |`auth`|`str`|-|权限ID| 1322 | |`domain`|`str`|空|需要获取`cookies`的域名| 1323 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 1324 | 1325 | 数据返回值 1326 | 1327 | |类型|说明| 1328 | |-|-| 1329 | |`dict`、`str`|返回`dict`时为返回的数据,返回`str`时为状态说明| 1330 | 1331 | 数据内容 1332 | 1333 | |字段名|类型|说明| 1334 | |-|-|-| 1335 | |`cookies`|`str`|Cookies| 1336 | 1337 | #### `get_csrf_token` 1338 | 1339 | 参数 1340 | 1341 | |字段名|数据类型|默认值|说明| 1342 | |-|-|-|-| 1343 | |`auth`|`str`|-|权限ID| 1344 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 1345 | 1346 | 数据返回值 1347 | 1348 | |类型|说明| 1349 | |-|-| 1350 | |`dict`、`str`|返回`dict`时为返回的数据,返回`str`时为状态说明| 1351 | 1352 | 数据内容 1353 | 1354 | |字段名|类型|说明| 1355 | |-|-|-| 1356 | |`token`|`int`|CSRF Token| 1357 | 1358 | #### `get_credentials` 1359 | 1360 | 参数 1361 | 1362 | |字段名|数据类型|默认值|说明| 1363 | |-|-|-|-| 1364 | |`auth`|`str`|-|权限ID| 1365 | |`domain`|`str`|空|需要获取`cookies`的域名| 1366 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 1367 | 1368 | 数据返回值 1369 | 1370 | |类型|说明| 1371 | |-|-| 1372 | |`dict`、`str`|返回`dict`时为返回的数据,返回`str`时为状态说明| 1373 | 1374 | 数据内容 1375 | 1376 | |字段名|类型|说明| 1377 | |-|-|-| 1378 | |`cookies`|`str`|Cookies| 1379 | |`token`|`int`|CSRF Token| 1380 | 1381 | #### `get_record` 1382 | 1383 | 参数 1384 | 1385 | |字段名|数据类型|默认值|说明| 1386 | |-|-|-|-| 1387 | |`auth`|`str`|-|权限ID| 1388 | |`file`|`str`|-|收到的语音文件名(消息段的`file`参数),如`0B38145AA44505000B38145AA4450500.silk`| 1389 | |`out_format`|`str`|-|要转换到的格式,目前支持`mp3`、`amr`、`wma`、`m4a`、`spx`、`ogg`、`wav`、`flac`| 1390 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 1391 | 1392 | 数据返回值 1393 | 1394 | |类型|说明| 1395 | |-|-| 1396 | |`dict`、`str`|返回`dict`时为返回的数据,返回`str`时为状态说明| 1397 | 1398 | 数据内容 1399 | 1400 | |字段名|类型|说明| 1401 | |-|-|-| 1402 | |`file`|`str`|转换后的语音文件路径,如`/home/somebody/cqhttp/data/record/0B38145AA44505000B38145AA4450500.mp3`| 1403 | 1404 | #### `get_image` 1405 | 1406 | 参数 1407 | 1408 | |字段名|数据类型|默认值|说明| 1409 | |-|-|-|-| 1410 | |`auth`|`str`|-|权限ID| 1411 | |`file`|`str`|-|收到的图片文件名(消息段的`file`参数),如`6B4DE3DFD1BD271E3297859D41C530F5.jpg`| 1412 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 1413 | 1414 | 数据返回值 1415 | 1416 | |类型|说明| 1417 | |-|-| 1418 | |`dict`、`str`|返回`dict`时为返回的数据,返回`str`时为状态说明| 1419 | 1420 | 数据内容 1421 | 1422 | |字段名|类型|说明| 1423 | |-|-|-| 1424 | |`file`|`str`|下载后的图片文件路径,如`/home/somebody/cqhttp/data/image/6B4DE3DFD1BD271E3297859D41C530F5.jpg`| 1425 | 1426 | #### `can_send_image` 1427 | 1428 | 参数 1429 | 1430 | |字段名|数据类型|默认值|说明| 1431 | |-|-|-|-| 1432 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 1433 | 1434 | 数据返回值 1435 | 1436 | |类型|说明| 1437 | |-|-| 1438 | |`dict`、`str`|返回`dict`时为返回的数据,返回`str`时为状态说明| 1439 | 1440 | 数据内容 1441 | 1442 | |字段名|类型|说明| 1443 | |-|-|-| 1444 | |`yes`|`bool`|是或否| 1445 | 1446 | #### `can_send_record` 1447 | 1448 | 参数 1449 | 1450 | |字段名|数据类型|默认值|说明| 1451 | |-|-|-|-| 1452 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 1453 | 1454 | 数据返回值 1455 | 1456 | |类型|说明| 1457 | |-|-| 1458 | |`dict`、`str`|返回`dict`时为返回的数据,返回`str`时为状态说明| 1459 | 1460 | 数据内容 1461 | 1462 | |字段名|类型|说明| 1463 | |-|-|-| 1464 | |`yes`|`bool`|是或否| 1465 | 1466 | #### `get_status` 1467 | 1468 | 参数 1469 | 1470 | |字段名|数据类型|默认值|说明| 1471 | |-|-|-|-| 1472 | |`auth`|`str`|-|权限ID| 1473 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 1474 | 1475 | 数据返回值 1476 | 1477 | |类型|说明| 1478 | |-|-| 1479 | |`dict`、`str`|返回`dict`时为返回的数据,返回`str`时为状态说明| 1480 | 1481 | 数据内容 1482 | 1483 | |字段名|类型|说明| 1484 | |-|-|-| 1485 | |`online`|`bool`|当前QQ在线,`None`表示无法查询到在线状态| 1486 | |`good`|`bool`|状态符合预期,意味着各模块正常运行、功能正常,且QQ在线| 1487 | |`……`|-|OneBot实现自行添加的其它内容| 1488 | 1489 | 额外说明 1490 | 1491 | 通常情况下建议只使用`online`和`good`这两个字段来判断运行状态,因为根据OneBot实现的不同,其它字段可能完全不同。 1492 | 1493 | #### `get_version_info` 1494 | 1495 | 参数 1496 | 1497 | |字段名|数据类型|默认值|说明| 1498 | |-|-|-|-| 1499 | |`auth`|`str`|-|权限ID| 1500 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 1501 | 1502 | 数据返回值 1503 | 1504 | |类型|说明| 1505 | |-|-| 1506 | |`dict`、`str`|返回`dict`时为返回的数据,返回`str`时为状态说明| 1507 | 1508 | 数据内容 1509 | 1510 | |字段名|类型|说明| 1511 | |-|-|-| 1512 | |`app_name`|`str`|应用标识,如`mirai-native`| 1513 | |`app_version`|`str`|应用版本,如`1.2.3`| 1514 | |`protocol_version`|`str`|OneBot标准版本,如`v11`| 1515 | |`……`|-|OneBot实现自行添加的其它内容| 1516 | 1517 | #### `set_restart` 1518 | 1519 | 参数 1520 | 1521 | |字段名|数据类型|默认值|说明| 1522 | |-|-|-|-| 1523 | |`auth`|`str`|-|权限ID| 1524 | |`delay`|`int`|`0`|要延迟的毫秒数,如果默认情况下无法重启,可以尝试设置延迟为`2000`左右| 1525 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 1526 | 1527 | 数据返回值 1528 | 1529 | |类型|说明| 1530 | |-|-| 1531 | |`str`|状态说明| 1532 | 1533 | 通常为`async` 1534 | 1535 | 额外说明 1536 | 1537 | 支持`set_restart_async`,返回数据始终为`async`,无`timeout`参数 1538 | 1539 | #### `clean_cache` 1540 | 1541 | 参数 1542 | 1543 | |字段名|数据类型|默认值|说明| 1544 | |-|-|-|-| 1545 | |`auth`|`str`|-|权限ID| 1546 | |`timeout`|`int`|`5`|获取返回数据超时时间,单位为秒| 1547 | 1548 | 数据返回值 1549 | 1550 | |类型|说明| 1551 | |-|-| 1552 | |`str`|状态说明| 1553 | 1554 | 额外说明 1555 | 1556 | 支持`clean_cache_async`,返回数据始终为`async`,无`timeout`参数 1557 | 1558 | #### `plugin_control` 1559 | 1560 | 参数 1561 | 1562 | |字段名|数据类型|默认值|说明| 1563 | |-|-|-|-| 1564 | |`auth`|`str`|-|权限ID| 1565 | |`action`|`str`|-|操作,包括`load`、`register`、`enable`、`disable`、`unregister`、`unload`分别对应生命周期,`reload`重载,`get_list`获取插件列表| 1566 | |`plugin`|`str`|-|操作为`load`时为文件名或目录名,其他为ID| 1567 | 1568 | 数据返回值 1569 | 1570 | |类型|说明| 1571 | |-|-| 1572 | |`str`、`dict`|状态说明,返回值为`dict`时为数据内容| 1573 | 1574 | 数据内容 1575 | 1576 | |字段名|类型|说明| 1577 | |-|-|-| 1578 | |`infos`|`dict`|插件加载原始信息| 1579 | |`methods`|`dict`|插件注册的方法| 1580 | |`registers`|`dict`|注册的插件信息| 1581 | |`enables`|`list`|启用的插件列表| 1582 | |`commands`|`dict`|插件注册的命令| 1583 | |`auths`|`dict`|插件注册的权限| 1584 | 1585 | #### `send_ws_msg` 1586 | 1587 | 参数 1588 | 1589 | |字段名|数据类型|默认值|说明| 1590 | |-|-|-|-| 1591 | |`auth`|`str`|-|权限ID| 1592 | |`message`|`str`|-|发送的消息| 1593 | 1594 | 数据返回值 1595 | 1596 | |类型|说明| 1597 | |-|-| 1598 | |`str`|状态说明| 1599 | 1600 | 通常为`success` 1601 | 1602 | 额外说明 1603 | 1604 | 若需要取回返回内容,请在发送的消息中加入`echo`字段,使用`get_ws_msg`取回 1605 | 1606 | #### `get_ws_msg` 1607 | 1608 | 参数 1609 | 1610 | |字段名|数据类型|默认值|说明| 1611 | |-|-|-|-| 1612 | |`auth`|`str`|-|权限ID| 1613 | |`echo`|`str`|-|标识| 1614 | 1615 | 数据返回值 1616 | 1617 | |类型|说明| 1618 | |-|-| 1619 | |`dict`、`str`|返回`dict`时为返回的数据,返回`str`时为状态说明| 1620 | 1621 | #### `escape` 1622 | 1623 | 参数 1624 | 1625 | |字段名|数据类型|默认值|说明| 1626 | |-|-|-|-| 1627 | |`s`|`str`|-|需要转义的字符串| 1628 | 1629 | 数据返回值 1630 | 1631 | |类型|说明| 1632 | |-|-| 1633 | |`str`|转义后的字符串| 1634 | 1635 | 转义说明 1636 | 1637 | |转义前|转义后| 1638 | |-|-| 1639 | |`&`|`&`| 1640 | |`[`|`[`| 1641 | |`]`|`]`| 1642 | |`,`|`,`| 1643 | |`\n`|`\\n`| 1644 | 1645 | #### `unescape` 1646 | 1647 | 参数 1648 | 1649 | |字段名|数据类型|默认值|说明| 1650 | |-|-|-|-| 1651 | |`s`|`str`|-|需要反转义的字符串| 1652 | 1653 | 数据返回值 1654 | 1655 | |类型|说明| 1656 | |-|-| 1657 | |`str`|反转义后的字符串| 1658 | 1659 | 转义说明 1660 | 1661 | |转义前|转义后| 1662 | |-|-| 1663 | |`&`|`&`| 1664 | |`[`|`[`| 1665 | |`]`|`]`| 1666 | |`,`|`,`| 1667 | |`\n`|`\\n`| 1668 | 1669 | #### `seg_*` 1670 | 1671 | 将消息转换为消息段 1672 | 1673 | #### `cq_*` 1674 | 1675 | 将消息转换为CQ码 1676 | 1677 | ### Bot类 1678 | 1679 | |名称|描述| 1680 | |-|-| 1681 | |`get_status`|获取Bot状态| 1682 | |`get_id`|获取Bot QQ号| 1683 | |`set_status`|设置Bot状态| -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-bullseye 2 | 3 | ENV TZ Asia/Shanghai 4 | ENV school_robot_docker_mode 1 5 | 6 | WORKDIR school-robot 7 | 8 | RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list && \ 9 | apt-get update && \ 10 | apt-get install -y libgbm-dev libgtk-3-0 --fix-missing && \ 11 | git clone https://github.com/School-Robot/Plugin-Loader.git . && \ 12 | pip config set global.index-url https://mirrors.ustc.edu.cn/pypi/web/simple && \ 13 | pip install --upgrade pip \ 14 | pip install websocket-client 15 | 16 | 17 | #TESTING 18 | # COPY . /school-robot 19 | 20 | COPY ./docker/entrypoint.sh /entrypoint.sh 21 | ENTRYPOINT ["bash","/entrypoint.sh"] 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Plugin Loader 2 | 3 | ## 介绍 4 | 5 | Python编写的插件加载器,使用WebSocket客户端模式对接上游服务,支持OneBot协议,仿酷Q插件风格,目前处于测试状态,程序运行不稳定,如遇到问题请提issue 6 | 7 | ## 使用 8 | 9 | ### 安装依赖 10 | 11 | ```shell 12 | pip install websocket-client -i https://pypi.tuna.tsinghua.edu.cn/simple 13 | ``` 14 | 15 | ### 运行 16 | 17 | 运行`main.py`,首次运行需要配置相关信息 18 | 19 | ### 安装插件 20 | 21 | 将插件放入plugins文件夹中 22 | 23 | ### 命令界面 24 | 25 | 运行时可以输入命令,内置命令有`help`、`?`、`plugin`、`exit` 26 | 27 | 使用`plugin`可以对插件进行管理 28 | 29 | ## 开发 30 | 31 | ### 插件开发 32 | 33 | 请参考[开发文档](DEV.md) 34 | 35 | ### 加载器开发 36 | 37 | 提交issue或pr 38 | 39 | ## 致谢/参考 40 | 41 | - [OneBot v11](https://11.onebot.dev) 42 | - 酷Q 43 | - 所有测试人员 44 | 45 | ## 友链 46 | [W1ndys的模块加载器](https://github.com/W1ndys-bot/Module-Loader) -------------------------------------------------------------------------------- /bot.py: -------------------------------------------------------------------------------- 1 | from variable import variable 2 | 3 | 4 | class Bot(object): 5 | status = True 6 | 7 | def set_status(self, status): 8 | self.status = status 9 | 10 | def get_status(self): 11 | return self.status 12 | 13 | def get_id(self): 14 | return variable.bot_id 15 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | school-bot-plugin-loader: 4 | build: 5 | context: . 6 | image: school-bot-plugin-loader 7 | container_name: School-Bot-Plugin-Loader 8 | volumes: 9 | - ./data/data:/school-robot/data 10 | - ./data/logs:/school-robot/logs 11 | - ./data/config:/school-robot/config 12 | - ./data/plugins:/school-robot/plugins 13 | restart: unless-stopped 14 | tty: true 15 | stdin_open: true 16 | environment: 17 | - school_robot_docker_mode=1 18 | -------------------------------------------------------------------------------- /docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cnt_failed_running=0 #计数器,当失败次数达到一定程度,则进入bash模式,手动调试 4 | cnt_max_failed_running=10 #最大重试次数 5 | restart_time=5 #自动重启的秒数 6 | 7 | trap 'onCtrlC' INT 8 | 9 | function entry_bash_maintain(){ 10 | echo -e "\033[31m[School-Bot-Plugin-Loaderdocker]进入shell模式,请通过 \033[36m docker attach School-Bot-Plugin-Loader(School-Bot-Plugin-Loader为容器名) \033[0m 进入容器shell手动维护,结束维护输入\033[36m exit \033[0m \033[0m" 11 | /bin/bash 12 | echo -e "\033[32m[School-Bot-Plugin-Loaderdocker]检测到shell模式退出,重置计数器,并且尝试重新启动School-Bot-Plugin-Loader\033[0m" 13 | cnt_failed_running=0 14 | } 15 | 16 | function onCtrlC() { 17 | echo -e "\033[36m[School-Bot-Plugin-Loaderdocker]检测到Ctrl+C,进入shell模式..\033[0m" 18 | entry_bash_maintain 19 | } 20 | 21 | while : 22 | do 23 | echo -e "\033[32m[School-Bot-Plugin-Loaderdocker]当前运行目录为(容器内部运行目录): $PWD \033[0m" 24 | stillRunning=$(ps -ef |grep "python main.py" |grep -v "grep") 25 | if [ "$stillRunning" ] ; then 26 | echo -e "\033[31m[School-Bot-Plugin-Loaderdocker]检测到School-Bot-Plugin-Loader已经正在运行\033[0m" 27 | echo -e "\033[31m[School-Bot-Plugin-Loaderdocker]避免冲突,正在尝试杀死School-Bot-Plugin-Loader\033[0m" 28 | kill -9 $(pidof python) 29 | stillRunning2=$(ps -ef |grep "python main.py" |grep -v "grep") 30 | if [ "$stillRunning2" ] 31 | then 32 | echo -e "\033[31m[School-Bot-Plugin-Loaderdocker]进程杀死失败\033[0m" 33 | echo -e "\033[31m[School-Bot-Plugin-Loaderdocker]也许你应该手动检查下,尝试进入shell模式..." 34 | entry_bash_maintain 35 | else 36 | echo -e "\033[32m[School-Bot-Plugin-Loaderdocker]已经杀死进程\033[0m" 37 | fi 38 | else 39 | echo -e "\033[32m[School-Bot-Plugin-Loaderdocker]School-Bot-Plugin-Loader似乎没有启动,正在尝试启动School-Bot-Plugin-Loader...\033[0m" 40 | echo -e "\033[32m[School-Bot-Plugin-Loaderdocker]以下为School-Bot-Plugin-Loader的输出:\033[0m" 41 | 42 | # 执行的命令: 43 | python main.py 44 | # 45 | 46 | echo -e "\033[32m[School-Bot-Plugin-Loaderdocker]以上为School-Bot-Plugin-Loader的输出:\033[0m" 47 | echo -e "\033[33m[School-Bot-Plugin-Loaderdocker]School-Bot-Plugin-Loader进程退出! \033[0m" 48 | 49 | ((cnt_failed_running=cnt_failed_running+1)); 50 | 51 | echo -e "\033[31m[School-Bot-Plugin-Loaderdocker]当前School-Bot-Plugin-Loader运行失败次数为:$cnt_failed_running" 52 | if [ $cnt_failed_running -gt $cnt_max_failed_running ]; 53 | then 54 | entry_bash_maintain 55 | fi 56 | echo -e "\033[32m[School-Bot-Plugin-Loaderdocker]将会在$restart_time秒后重新尝试启动School-Bot-Plugin-Loader \033[0m" 57 | fi 58 | sleep $restart_time 59 | done -------------------------------------------------------------------------------- /loader.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | import os 3 | import sys 4 | import gc 5 | import uuid 6 | 7 | from variable import variable 8 | 9 | logger = variable.log.getChild("Loader") 10 | 11 | 12 | class PluginInfo(object): 13 | plugin_version = None 14 | plugin_id = None 15 | plugin_name = None 16 | plugin_author = None 17 | plugin_desc = None 18 | plugin = None 19 | 20 | def __init__(self, plugin_version, plugin_id, plugin_name, plugin_author, plugin_desc, plugin): 21 | self.plugin_author = plugin_author 22 | self.plugin_desc = plugin_desc 23 | self.plugin_id = plugin_id 24 | self.plugin_name = plugin_name 25 | self.plugin_version = plugin_version 26 | self.plugin = plugin 27 | 28 | 29 | class PluginLoader(object): 30 | plugin_infos = {} 31 | plugin_methods = {} 32 | plugin_registers = {} 33 | plugin_enables = [] 34 | plugin_commands = {} 35 | plugin_auths = {} 36 | auths = {} 37 | auth = {'send_private_msg': '发送私聊消息', 'send_group_msg': '发送群消息', 'send_msg': '发送消息', 'delete_msg': '撤回消息', 38 | 'get_msg': '获取消息', 'get_forward_msg': '获取合并转发消息','send_group_forward_msg':'发送合并转发消息', 'send_like': '发送好友赞', 'set_group_kick': '群组踢人', 39 | 'set_group_ban': '群组单人禁言', 'set_group_anonymous_ban': '群组匿名用户禁言', 'set_group_whole_ban': '群组全员禁言', 40 | 'set_group_admin': '群组设置管理员', 'set_group_anonymous': '群组匿名', 'set_group_card': '设置群名片', 41 | 'set_group_name': '设置群名', 'set_group_leave': '退出群组', 'set_group_special_title': '设置群组专属头衔', 42 | 'set_friend_add_request': '处理加好友请求', 'set_group_add_request': '处理加群请求', 'get_login_info': '获取登录号信息', 43 | 'get_stranger_info': '获取陌生人信息', 'get_friend_list': '获取好友列表', 'get_group_info': '获取群信息', 44 | 'get_group_list': '获取群列表', 'get_group_member_info': '获取群成员信息', 'get_group_member_list': '获取群成员列表', 45 | 'get_group_honor_info': '获取群荣耀信息', 'get_cookies': '获取Cookies', 'get_csrf_token': '获取CSRF Token', 46 | 'get_credentials': '获取QQ相关接口凭证', 'get_record': '获取语音', 'get_image': '获取图片', 'get_status': '获取运行状态', 47 | 'get_version_info': '获取版本信息', 'set_restart': '重启OneBot', 'clean_cache': '清理缓存', 'plugin_control': '插件控制', 48 | 'send_ws_msg': '发送WebSocket消息', 'get_ws_msg': '获取WebSocket返回内容'} 49 | 50 | def __init__(self): 51 | self.load_plugins() 52 | 53 | def load_plugins(self): 54 | logger.info("正在加载插件...") 55 | succ = 0 56 | err = 0 57 | for filename in os.listdir('plugins'): 58 | if filename.endswith('.py'): 59 | filename = filename[:-3] 60 | if filename == "__pycache__": 61 | continue 62 | try: 63 | if filename in variable.config['plugin']: 64 | if 'load' in variable.config['plugin'][filename]: 65 | if not variable.config['plugin'][filename]['load']: 66 | continue 67 | else: 68 | variable.config['plugin'][filename]['load'] = True 69 | else: 70 | variable.config['plugin'][filename] = {'load': True} 71 | plugin = importlib.import_module('plugins.' + filename) 72 | logger.debug(f"正在加载: {plugin}") 73 | id = plugin.plugin_id 74 | name = plugin.plugin_name 75 | version = plugin.plugin_version 76 | author = plugin.plugin_author 77 | desc = plugin.plugin_desc 78 | info = PluginInfo(version, id, name, author, desc, plugin) 79 | self.plugin_infos[id] = {'name': filename, 'info': info} 80 | del name 81 | del version 82 | del author 83 | del desc 84 | del id 85 | del plugin 86 | del info 87 | succ += 1 88 | except Exception as e: 89 | id = "" 90 | for plugin_id in self.plugin_infos: 91 | if filename == self.plugin_infos[plugin_id]['name']: 92 | id = plugin_id 93 | if id != '': 94 | del self.plugin_infos[id] 95 | del_modules = [] 96 | for module_name in sys.modules: 97 | if module_name.startswith('plugins.' + filename): 98 | del_modules.append(module_name) 99 | for module_name in del_modules: 100 | del sys.modules[module_name] 101 | logger.error(f"插件: {filename} 出现错误") 102 | logger.exception(e) 103 | err += 1 104 | logger.info(f"加载完成! 加载成功{succ}个插件,加载失败{err}个插件") 105 | 106 | def register_plugins(self): 107 | logger.info("正在注册插件...") 108 | succ = 0 109 | err = 0 110 | for id in self.plugin_infos: 111 | try: 112 | if self.plugin_infos[id]['name'] in variable.config['plugin']: 113 | if 'reg' in variable.config['plugin'][self.plugin_infos[id]['name']]: 114 | if not variable.config['plugin'][self.plugin_infos[id]['name']]['reg']: 115 | continue 116 | else: 117 | variable.config['plugin'][self.plugin_infos[id]['name']]['reg'] = True 118 | else: 119 | variable.config['plugin'][self.plugin_infos[id]['name']] = {'reg': True} 120 | plugin = self.plugin_infos[id]['info'].plugin.Plugin() 121 | methods = plugin.plugin_methods 122 | self.plugin_methods[id] = methods 123 | if not os.path.exists(os.path.join('data', id)): 124 | os.makedirs(os.path.join('data', id)) 125 | data_dir = os.path.abspath(os.path.join('data', id)) 126 | plugin_logger = variable.log.getChild(self.plugin_infos[id]['info'].plugin_name) 127 | register = getattr(plugin, self.plugin_methods[id]['register']['func']) 128 | register(plugin_logger, variable.util, variable.bot, data_dir) 129 | plugin.status = "registered" 130 | self.plugin_registers[id] = plugin 131 | del plugin 132 | del register 133 | del methods 134 | succ += 1 135 | except Exception as e: 136 | if id in self.plugin_registers: 137 | del self.plugin_registers[id] 138 | if id in self.plugin_methods: 139 | del self.plugin_methods[id] 140 | logger.error(f"插件: {id} 出现错误") 141 | logger.exception(e) 142 | err += 1 143 | logger.debug(self.plugin_registers) 144 | logger.info(f"注册完成! 注册成功{succ}个插件,注册失败{err}个插件") 145 | 146 | def enable_plugins(self): 147 | logger.info("正在启用插件...") 148 | succ = 0 149 | err = 0 150 | self.plugin_enables = [] 151 | for id in self.plugin_registers: 152 | try: 153 | if self.plugin_infos[id]['name'] in variable.config['plugin']: 154 | if 'enable' in variable.config['plugin'][self.plugin_infos[id]['name']]: 155 | if not variable.config['plugin'][self.plugin_infos[id]['name']]['enable']: 156 | continue 157 | else: 158 | variable.config['plugin'][self.plugin_infos[id]['name']]['enable'] = True 159 | else: 160 | variable.config['plugin'][self.plugin_infos[id]['name']] = {'enable': True} 161 | plugin = self.plugin_registers[id] 162 | commands = plugin.plugin_commands 163 | self.plugin_commands[id] = commands 164 | auths = plugin.plugin_auths 165 | uid = uuid.uuid4().hex 166 | self.plugin_auths[id] = {"uid": uid, "auths": auths} 167 | self.auths[uid] = auths 168 | del auths 169 | enable = getattr(self.plugin_registers[id], self.plugin_methods[id]['enable']['func']) 170 | enable(uid) 171 | self.plugin_registers[id].status = "enabled" 172 | self.plugin_enables.append(id) 173 | del uid 174 | del enable 175 | del plugin 176 | del commands 177 | succ += 1 178 | except Exception as e: 179 | if id in self.plugin_enables: 180 | self.plugin_enables.remove(id) 181 | if id in self.plugin_commands: 182 | del self.plugin_commands[id] 183 | if id in self.plugin_auths: 184 | uid = self.plugin_auths[id]['uid'] 185 | if uid in self.auths: 186 | del self.auths[uid] 187 | del uid 188 | del self.plugin_auths[id] 189 | self.plugin_registers[id].status = "error" 190 | logger.error(f"插件: {id} 出现错误") 191 | logger.exception(e) 192 | err += 1 193 | logger.debug(self.plugin_enables) 194 | logger.info(f"启用完成! 启用成功{succ}个插件,启用失败{err}个插件") 195 | 196 | def disable_plugins(self): 197 | logger.info("正在禁用插件...") 198 | succ = 0 199 | err = 0 200 | plugin_disables = [] 201 | for id in self.plugin_enables: 202 | try: 203 | disable = getattr(self.plugin_registers[id], self.plugin_methods[id]['disable']['func']) 204 | disable() 205 | self.plugin_registers[id].status = "registered" 206 | plugin_disables.append(id) 207 | del disable 208 | del self.plugin_commands[id] 209 | uid = self.plugin_auths[id]['uid'] 210 | del self.auths[uid] 211 | del uid 212 | del self.plugin_auths[id] 213 | succ += 1 214 | except Exception as e: 215 | self.plugin_registers[id] = "error" 216 | logger.error(f"插件: {id} 出现错误") 217 | logger.exception(e) 218 | err += 1 219 | 220 | for id in plugin_disables: 221 | self.plugin_enables.remove(id) 222 | logger.info(f"禁用完成! 禁用成功{succ}个插件,禁用失败{err}个插件") 223 | 224 | def unregister_plugins(self): 225 | logger.info("正在注销插件...") 226 | succ = 0 227 | err = 0 228 | plugin_unregisters = [] 229 | for id in self.plugin_registers: 230 | try: 231 | if self.plugin_registers[id].status != "registered": 232 | self.disable_plugins() 233 | unregister = getattr(self.plugin_registers[id], self.plugin_methods[id]['unregister']['func']) 234 | unregister() 235 | plugin_unregisters.append(id) 236 | del unregister 237 | succ += 1 238 | except Exception as e: 239 | logger.error(f"插件: {id} 出现错误") 240 | logger.exception(e) 241 | err += 1 242 | 243 | for id in plugin_unregisters: 244 | del self.plugin_registers[id] 245 | del self.plugin_methods[id] 246 | gc.collect() 247 | logger.info(f"注销完成! 注销成功{succ}个插件,注销失败{err}个插件") 248 | 249 | def unload_plugins(self): 250 | logger.info("正在卸载插件...") 251 | plugin_unloads = [] 252 | for id in self.plugin_infos: 253 | if id in self.plugin_enables: 254 | self.disable_plugins() 255 | if id in self.plugin_registers: 256 | self.unregister_plugins() 257 | plugin_unloads.append(id) 258 | 259 | for id in plugin_unloads: 260 | del self.plugin_infos[id]['info'].plugin 261 | info = self.plugin_infos[id]['info'] 262 | del_modules = [] 263 | for module_name in sys.modules: 264 | if module_name.startswith('plugins.' + self.plugin_infos[id]['name']): 265 | del_modules.append(module_name) 266 | for module_name in del_modules: 267 | del sys.modules[module_name] 268 | del self.plugin_infos[id] 269 | del info 270 | 271 | del_modules = [] 272 | for module_name in sys.modules: 273 | if module_name.startswith('plugins'): 274 | del_modules.append(module_name) 275 | for module_name in del_modules: 276 | del sys.modules[module_name] 277 | gc.collect() 278 | importlib.invalidate_caches() 279 | logger.info("卸载完成") 280 | 281 | def list_plugins(self): 282 | for id in self.plugin_infos: 283 | if id in self.plugin_registers: 284 | logger.info("ID:", id, "状态:", self.plugin_registers[id].status, "插件名称:", 285 | self.plugin_infos[id]['info'].plugin_name, "插件版本:", 286 | self.plugin_infos[id]['info'].plugin_version) 287 | else: 288 | logger.info("ID:", id, "状态: not register") 289 | 290 | def view_plugin(self, id): 291 | if id in self.plugin_registers: 292 | logger.info("ID:", id) 293 | logger.info("状态:", self.plugin_registers[id].status) 294 | logger.info("插件名称:", self.plugin_infos[id]['info'].plugin_name) 295 | logger.info("插件版本:", self.plugin_infos[id]['info'].plugin_version) 296 | logger.info("插件作者:", self.plugin_infos[id]['info'].plugin_author) 297 | logger.info("插件描述:", self.plugin_infos[id]['info'].plugin_desc) 298 | logger.info("注册的方法:") 299 | for method in self.plugin_methods[id]: 300 | logger.info(method, self.plugin_methods[id][method]['desc']) 301 | if id in self.plugin_enables: 302 | logger.info("注册的命令:") 303 | for command in self.plugin_commands[id]: 304 | logger.info(command) 305 | logger.info("申请的权限:") 306 | for auth in self.plugin_auths[id]['auths']: 307 | logger.info(auth, self.auth[auth]) 308 | elif id in self.plugin_infos: 309 | logger.info("ID:", id) 310 | logger.info("状态:", "not register") 311 | logger.info("插件名称:", self.plugin_infos[id]['info'].plugin_name) 312 | logger.info("插件版本:", self.plugin_infos[id]['info'].plugin_version) 313 | logger.info("插件作者:", self.plugin_infos[id]['info'].plugin_author) 314 | logger.info("插件描述:", self.plugin_infos[id]['info'].plugin_desc) 315 | else: 316 | logger.warning("没有这个插件") 317 | 318 | def load_plugin(self, filename): 319 | logger.info(f"加载插件: {filename}") 320 | if not os.path.isdir('plugins/' + filename): 321 | if filename.endswith('.py'): 322 | filename = filename[:-3] 323 | if os.path.isfile('plugins/' + filename + '.py.py'): 324 | filename += '.py' 325 | elif os.path.isfile('plugins/' + filename + '.py'): 326 | filename = filename 327 | else: 328 | logger.warning(f"插件 {filename} 不存在") 329 | return 330 | for id in self.plugin_infos: 331 | if filename == self.plugin_infos[id]['name']: 332 | logger.warning(f"插件 {filename} 已经被加载") 333 | return 334 | try: 335 | if filename in variable.config['plugin']: 336 | if 'load' in variable.config['plugin'][filename]: 337 | variable.config['plugin'][filename]['load'] = True 338 | else: 339 | variable.config['plugin'][filename]['load'] = True 340 | else: 341 | variable.config['plugin'][filename] = {'load': True} 342 | plugin = importlib.import_module('plugins.' + filename) 343 | logger.debug(plugin) 344 | id = plugin.plugin_id 345 | name = plugin.plugin_name 346 | version = plugin.plugin_version 347 | author = plugin.plugin_author 348 | desc = plugin.plugin_desc 349 | info = PluginInfo(version, id, name, author, desc, plugin) 350 | self.plugin_infos[id] = {'name': filename, 'info': info} 351 | logger.info(f"加载成功: {filename}") 352 | del name 353 | del version 354 | del author 355 | del desc 356 | del id 357 | del plugin 358 | del info 359 | except Exception as e: 360 | id = "" 361 | for plugin_id in self.plugin_infos: 362 | if filename == self.plugin_infos[plugin_id]['name']: 363 | id = plugin_id 364 | if id != '': 365 | del self.plugin_infos[id] 366 | del_modules = [] 367 | for module_name in sys.modules: 368 | if module_name.startswith('plugins.' + filename): 369 | del_modules.append(module_name) 370 | for module_name in del_modules: 371 | del sys.modules[module_name] 372 | logger.error(f"插件: {filename} 出现错误") 373 | logger.exception(e) 374 | logger.info(f"加载失败: {filename}") 375 | 376 | def register_plugin(self, id): 377 | logger.info(f"注册插件: {id}") 378 | if id in self.plugin_infos: 379 | try: 380 | if self.plugin_infos[id]['name'] in variable.config['plugin']: 381 | if 'reg' in variable.config['plugin'][self.plugin_infos[id]['name']]: 382 | variable.config['plugin'][self.plugin_infos[id]['name']]['reg'] = True 383 | else: 384 | variable.config['plugin'][self.plugin_infos[id]['name']]['reg'] = True 385 | else: 386 | variable.config['plugin'][self.plugin_infos[id]['name']] = {'reg': True} 387 | if id in self.plugin_registers: 388 | logger.warning(f"插件 {id} 已经被注册") 389 | return 390 | plugin = self.plugin_infos[id]['info'].plugin.Plugin() 391 | methods = plugin.plugin_methods 392 | self.plugin_methods[id] = methods 393 | plugin_logger = variable.log.getChild(self.plugin_infos[id]['info'].plugin_name) 394 | if not os.path.exists(os.path.join('data', id)): 395 | os.makedirs(os.path.join('data', id)) 396 | data_dir = os.path.abspath(os.path.join('data', id)) 397 | register = getattr(plugin, self.plugin_methods[id]['register']['func']) 398 | register(plugin_logger, variable.util, variable.bot, data_dir) 399 | plugin.status = "registered" 400 | self.plugin_registers[id] = plugin 401 | del plugin 402 | del register 403 | del methods 404 | logger.info(f"注册成功: {id}") 405 | except Exception as e: 406 | if id in self.plugin_registers: 407 | del self.plugin_registers[id] 408 | if id in self.plugin_methods: 409 | del self.plugin_methods[id] 410 | logger.error(f"插件: {id} 出现错误") 411 | logger.exception(e) 412 | logger.info(f"注册失败: {id}") 413 | else: 414 | logger.error(f"插件: {id} 未加载") 415 | logger.debug(self.plugin_registers) 416 | 417 | def enable_plugin(self, id): 418 | logger.info(f"启用插件: {id}") 419 | if id in self.plugin_registers: 420 | try: 421 | if self.plugin_infos[id]['name'] in variable.config['plugin']: 422 | if 'load' in variable.config['plugin'][self.plugin_infos[id]['name']]: 423 | variable.config['plugin'][self.plugin_infos[id]['name']]['load'] = True 424 | else: 425 | variable.config['plugin'][self.plugin_infos[id]['name']]['load'] = True 426 | else: 427 | variable.config['plugin'][self.plugin_infos[id]['name']] = {'load': True} 428 | if id in self.plugin_enables: 429 | logger.warning(f"插件 {id} 已经被启用") 430 | return 431 | plugin = self.plugin_registers[id] 432 | commands = plugin.plugin_commands 433 | self.plugin_commands[id] = commands 434 | auths = plugin.plugin_auths 435 | uid = uuid.uuid4().hex 436 | self.plugin_auths[id] = {'uid': uid, 'auths': auths} 437 | self.auths[uid] = auths 438 | del auths 439 | enable = getattr(self.plugin_registers[id], self.plugin_methods[id]['enable']['func']) 440 | enable(uid) 441 | self.plugin_registers[id].status = "enabled" 442 | self.plugin_enables.append(id) 443 | del uid 444 | del enable 445 | del plugin 446 | del commands 447 | logger.info(f"启用成功: {id}") 448 | except Exception as e: 449 | if id in self.plugin_commands: 450 | del self.plugin_commands[id] 451 | if id in self.plugin_auths: 452 | uid = self.plugin_auths[id]['uid'] 453 | if uid in self.auths: 454 | del self.auths[uid] 455 | del uid 456 | del self.plugin_auths[id] 457 | if id in self.plugin_enables: 458 | self.plugin_enables.remove(id) 459 | self.plugin_registers[id].status = "error" 460 | logger.error(f"插件: {id} 出现错误") 461 | logger.exception(e) 462 | logger.info(f"启用失败: {id}") 463 | else: 464 | logger.error(f"插件: {id} 未注册") 465 | logger.debug(self.plugin_enables) 466 | 467 | def disable_plugin(self, id): 468 | logger.info(f"禁用插件: {id}") 469 | if id in self.plugin_enables: 470 | try: 471 | if self.plugin_infos[id]['name'] in variable.config['plugin']: 472 | if 'load' in variable.config['plugin'][self.plugin_infos[id]['name']]: 473 | variable.config['plugin'][self.plugin_infos[id]['name']]['load'] = False 474 | else: 475 | variable.config['plugin'][self.plugin_infos[id]['name']]['load'] = False 476 | else: 477 | variable.config['plugin'][self.plugin_infos[id]['name']] = {'load': False} 478 | disable = getattr(self.plugin_registers[id], self.plugin_methods[id]['disable']['func']) 479 | disable() 480 | self.plugin_registers[id].status = "registered" 481 | del disable 482 | del self.plugin_commands[id] 483 | uid = self.plugin_auths[id]['uid'] 484 | del self.auths[uid] 485 | del uid 486 | del self.plugin_auths[id] 487 | self.plugin_enables.remove(id) 488 | logger.info(f"禁用成功: {id}") 489 | except Exception as e: 490 | self.plugin_registers[id] = "error" 491 | logger.error(f"插件: {id} 出现错误") 492 | logger.exception(e) 493 | logger.info(f"禁用失败: {id}") 494 | else: 495 | logger.error(f"插件: {id} 未启用") 496 | 497 | def unregister_plugin(self, id): 498 | logger.info(f"正在注销插件: {id}") 499 | if id in self.plugin_registers: 500 | try: 501 | if self.plugin_infos[id]['name'] in variable.config['plugin']: 502 | if 'reg' in variable.config['plugin'][self.plugin_infos[id]['name']]: 503 | variable.config['plugin'][self.plugin_infos[id]['name']]['reg'] = False 504 | else: 505 | variable.config['plugin'][self.plugin_infos[id]['name']]['reg'] = False 506 | else: 507 | variable.config['plugin'][self.plugin_infos[id]['name']] = {'reg': False} 508 | if self.plugin_registers[id].status != "registered": 509 | self.disable_plugin(id) 510 | unregister = getattr(self.plugin_registers[id], self.plugin_methods[id]['unregister']['func']) 511 | unregister() 512 | p = self.plugin_registers[id] 513 | del p 514 | del self.plugin_registers[id] 515 | del self.plugin_methods[id] 516 | del unregister 517 | gc.collect() 518 | logger.info(f"注销成功: {id}") 519 | except Exception as e: 520 | logger.error(f"插件: {id} 出现错误") 521 | logger.exception(e) 522 | logger.info(f"注销失败: {id}") 523 | else: 524 | logger.error(f"插件: {id} 未注册") 525 | return 526 | 527 | def unload_plugin(self, id): 528 | logger.info("正在卸载插件...") 529 | if id in self.plugin_infos: 530 | if self.plugin_infos[id]['name'] in variable.config['plugin']: 531 | if 'load' in variable.config['plugin'][self.plugin_infos[id]['name']]: 532 | variable.config['plugin'][self.plugin_infos[id]['name']]['load'] = False 533 | else: 534 | variable.config['plugin'][self.plugin_infos[id]['name']]['load'] = False 535 | else: 536 | variable.config['plugin'][self.plugin_infos[id]['name']] = {'load': False} 537 | if id in self.plugin_enables: 538 | self.disable_plugin(id) 539 | if id in self.plugin_registers: 540 | self.unregister_plugin(id) 541 | del self.plugin_infos[id]['info'].plugin 542 | info = self.plugin_infos[id]['info'] 543 | del_modules = [] 544 | for module_name in sys.modules: 545 | if module_name.startswith('plugins.' + self.plugin_infos[id]['name']): 546 | del_modules.append(module_name) 547 | for module_name in del_modules: 548 | del sys.modules[module_name] 549 | del self.plugin_infos[id] 550 | del info 551 | else: 552 | logger.error(f"插件 {id} 未加载") 553 | gc.collect() 554 | importlib.invalidate_caches() 555 | logger.info(f"卸载成功: {id}") 556 | 557 | def reload_plugin(self, id): 558 | name = "" 559 | if id in self.plugin_infos: 560 | name = self.plugin_infos[id]['name'] 561 | else: 562 | logger.error(f"插件 {id} 未加载") 563 | if id in self.plugin_enables: 564 | self.disable_plugin(id) 565 | self.unregister_plugin(id) 566 | self.unload_plugin(id) 567 | self.load_plugin(name) 568 | for plugin_id in self.plugin_infos: 569 | if name == self.plugin_infos[plugin_id]['name']: 570 | id = plugin_id 571 | self.register_plugin(id) 572 | self.enable_plugin(id) 573 | elif id in self.plugin_registers: 574 | self.unregister_plugin(id) 575 | self.unload_plugin(id) 576 | self.load_plugin(name) 577 | for plugin_id in self.plugin_infos: 578 | if name == self.plugin_infos[plugin_id]['name']: 579 | id = plugin_id 580 | self.register_plugin(id) 581 | else: 582 | self.unload_plugin(id) 583 | self.load_plugin(name) 584 | logger.info("重载完成") 585 | 586 | def processPluginCommand(self, cmd): 587 | for plugin in self.plugin_enables: 588 | for command in self.plugin_commands[plugin]: 589 | if command == cmd[0]: 590 | logger.debug(f'命令匹配到 {plugin}') 591 | process = getattr(self.plugin_registers[plugin], self.plugin_commands[plugin][command]) 592 | process(cmd[1:]) 593 | del process 594 | 595 | def get_plugin_list(self): 596 | return {"infos": self.plugin_infos, "methods": self.plugin_methods, "registers": self.plugin_registers, 597 | "enables": self.plugin_enables, "commands": self.plugin_commands, "auths": self.plugin_auths} 598 | -------------------------------------------------------------------------------- /log.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import logging.handlers 3 | import os 4 | import re 5 | 6 | if not os.path.exists('logs'): 7 | os.mkdir('logs') 8 | 9 | class ColoredBase64Formatter(logging.Formatter): 10 | COLORS = { 11 | 'DEBUG': '\033[94m', # 蓝色 12 | 'INFO': '\033[92m', # 绿色 13 | 'WARNING': '\033[93m', # 黄色 14 | 'ERROR': '\033[91m', # 红色 15 | 'CRITICAL': '\033[95m', # 紫色 16 | 'RESET': '\033[0m', # 重置颜色 17 | 'CYAN': '\033[96m', # 青色 18 | 'MAGENTA': '\033[95m', # 洋红 19 | 'BOLD': '\033[1m', # 粗体 20 | 'UNDERLINE': '\033[4m', # 下划线 21 | 'YELLOW':'\033[33m' # 黄色 22 | } 23 | 24 | def __init__(self, fmt=None, datefmt=None, style='%', use_color=True): 25 | super().__init__(fmt, datefmt, style) 26 | self.use_color = use_color 27 | self.base64_pattern = re.compile(r'(base64://)[a-zA-Z0-9+/]*={0,2}') 28 | 29 | def format(self, record): 30 | if hasattr(record, 'msg'): 31 | record.msg = self.process_base64(record.msg) 32 | # record.msg = self._logger_debug_clean_abnormal_data(record.msg) 33 | message = super().format(record) 34 | if self.use_color: 35 | timestamp_pattern = r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}' 36 | message = re.sub(timestamp_pattern, lambda m: f"{self.COLORS['INFO']}{m.group()}{self.COLORS['RESET']}", message, 1) 37 | message = message.replace(f"[{record.levelname}]", f"{self.COLORS[record.levelname]}[{record.levelname}]{self.COLORS['RESET']}") 38 | message = self.color_special_info(message) 39 | return message 40 | 41 | def process_base64(self, message): 42 | if isinstance(message, str): 43 | return self.base64_pattern.sub(self.repl_base64, message) 44 | return message 45 | def process_data_size(self, message): 46 | if isinstance(message, str): 47 | return self.data_size_pattern.sub(lambda m: f"<{m.group(1)}>", message) 48 | return message 49 | 50 | def repl_base64(self, match): 51 | base64_data = match.group(0) 52 | size_bytes = len(base64_data) * 3 // 4 53 | if size_bytes < 1024: 54 | size_str = f"{size_bytes}B" 55 | elif size_bytes < 1024 * 1024: 56 | size_str = f"{size_bytes/1024:.2f}KB" 57 | else: 58 | size_str = f"{size_bytes/(1024*1024):.2f}MB" 59 | return f"base64://<{size_str}>" 60 | 61 | def color_special_info(self, message): 62 | message = re.sub(r'\[(Thread-\d+.*?)\]', f"{self.COLORS['MAGENTA']}[\\1]{self.COLORS['RESET']}", message) 63 | message = re.sub(r'\[(Plugin-Loader\.[^\]]+)\]', f"{self.COLORS['CYAN']}[\\1]{self.COLORS['RESET']}", message) 64 | message = re.sub(r'(\[)(\d+)(\])', f"\\1{self.COLORS['YELLOW']}\\2{self.COLORS['RESET']}\\3", message) 65 | message = re.sub(r'(\()(\d+)(\))', f"\\1{self.COLORS['YELLOW']}\\2{self.COLORS['RESET']}\\3", message) 66 | message = re.sub(r'(撤回消息ID:)', f"{self.COLORS['BOLD']}\\1{self.COLORS['RESET']}", message) 67 | message = re.sub(r'(收到.*?的群消息:)', f"{self.COLORS['BOLD']}\\1{self.COLORS['RESET']}", message) 68 | message = re.sub(r'(在群.*?中撤回了消息)', f"{self.COLORS['BOLD']}\\1{self.COLORS['RESET']}", message) 69 | message = re.sub(r'(\[CQ:[^\]]+\])', f"{self.COLORS['MAGENTA']}\\1{self.COLORS['RESET']}", message) 70 | message = re.sub(r'(正在.*?插件)', f"{self.COLORS['UNDERLINE']}\\1{self.COLORS['RESET']}", message) 71 | message = re.sub(r'(WebSocket.*?)', f"{self.COLORS['UNDERLINE']}\\1{self.COLORS['RESET']}", message) 72 | message = re.sub(r'(<\d+(?:\.\d+)?[BKM]B_data>)', f"{self.COLORS['CYAN']}\\1{self.COLORS['RESET']}", message) 73 | return message 74 | 75 | loggerClass = logging.getLogger("Plugin-Loader") 76 | loggerClass.setLevel(logging.DEBUG) 77 | 78 | #文件 79 | log_path = os.path.join("logs", "loader.log") 80 | handler = logging.handlers.TimedRotatingFileHandler(log_path, when="midnight", interval=1, encoding="utf-8") 81 | file_formatter = ColoredBase64Formatter('%(asctime)s [%(threadName)s] [%(name)s] [%(levelname)s] %(message)s', use_color=False) 82 | handler.setFormatter(file_formatter) 83 | handler.setLevel(logging.DEBUG) 84 | loggerClass.addHandler(handler) 85 | 86 | #控制台 87 | console = logging.StreamHandler() 88 | console.setLevel(logging.INFO) 89 | console_formatter = ColoredBase64Formatter('%(asctime)s [%(threadName)s] [%(name)s] [%(levelname)s] %(message)s', use_color=True) 90 | console.setFormatter(console_formatter) 91 | loggerClass.addHandler(console) 92 | 93 | 94 | if __name__ == "__main__": 95 | loggerClass.debug("debug message") 96 | loggerClass.info("info message") 97 | loggerClass.warning("warning message") 98 | loggerClass.error("error message") 99 | loggerClass.critical("critical message") 100 | loggerClass.info("message base64://SGVsbG8gV29ybGQ= data") 101 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import json 2 | import threading 3 | import time 4 | import os 5 | 6 | from loader import PluginLoader 7 | from ws import WS 8 | from bot import Bot 9 | from util import Util 10 | from processer import Processer 11 | 12 | global variable 13 | from variable import variable 14 | 15 | 16 | def processCommand(cmd): 17 | cmd = cmd.split(' ') 18 | if cmd[0] == 'send': 19 | if len(cmd) > 1: 20 | cmd = ' '.join(cmd[1:]) 21 | variable.ws.send(cmd) 22 | elif cmd[0] == 'plugin': 23 | if len(cmd) > 1: 24 | cmd = cmd[1:] 25 | if cmd[0] == 'load': 26 | cmd = ' '.join(cmd[1:]) 27 | variable.loader.load_plugin(cmd) 28 | elif cmd[0] == 'reg': 29 | cmd = ' '.join(cmd[1:]) 30 | variable.loader.register_plugin(cmd) 31 | elif cmd[0] == 'enable': 32 | cmd = ' '.join(cmd[1:]) 33 | variable.loader.enable_plugin(cmd) 34 | elif cmd[0] == 'disable': 35 | cmd = ' '.join(cmd[1:]) 36 | variable.loader.disable_plugin(cmd) 37 | elif cmd[0] == 'unreg': 38 | cmd = ' '.join(cmd[1:]) 39 | variable.loader.unregister_plugin(cmd) 40 | elif cmd[0] == 'unload': 41 | cmd = ' '.join(cmd[1:]) 42 | variable.loader.unload_plugin(cmd) 43 | elif cmd[0] == 'list': 44 | variable.loader.list_plugins() 45 | elif cmd[0] == 'view': 46 | cmd = ' '.join(cmd[1:]) 47 | variable.loader.view_plugin(cmd) 48 | elif cmd[0] == 'reload': 49 | cmd = ' '.join(cmd[1:]) 50 | variable.loader.reload_plugin(cmd) 51 | else: 52 | print( 53 | "子命令:\nload - 加载插件\nreg - 注册插件\nenable - 启用插件\ndisable - 禁用插件\nunreg - 注销插件\nunload - 卸载插件\nlist - 列出插件\nview - 查看插件信息\nreload - 重载插件") 54 | else: 55 | print( 56 | "子命令:\nload - 加载插件\nreg - 注册插件\nenable - 启用插件\ndisable - 禁用插件\nunreg - 注销插件\nunload - 卸载插件\nlist - 列出插件\nview - 查看插件信息\nreload - 重载插件") 57 | elif cmd[0] == 'help' or cmd[0] == '?': 58 | print("命令列表:\nexit - 退出\nsend - 发送WebSocket消息\nplugin - 插件管理") 59 | for plugin in variable.loader.plugin_commands: 60 | print(plugin, "的命令:") 61 | if 'help' in variable.loader.plugin_commands[plugin]: 62 | print(variable.loader.plugin_commands[plugin]['help']) 63 | else: 64 | for command in variable.loader.plugin_commands[plugin]: 65 | print(command) 66 | else: 67 | variable.loader.processPluginCommand(cmd) 68 | 69 | 70 | logger = variable.log.getChild("Main") 71 | 72 | 73 | def main(): 74 | global variable 75 | if os.path.exists('data') == False: 76 | os.mkdir('data') 77 | if os.path.exists('plugins') == False: 78 | os.mkdir('plugins') 79 | 80 | # 读取配置文件 81 | config_file_path = './config.json' 82 | is_docker = os.environ.get('school_robot_docker_mode') == '1' 83 | if is_docker: 84 | config_dir = './config' 85 | config_file_path = os.path.join(config_dir, 'config.json') 86 | os.makedirs(config_dir, exist_ok=True) 87 | logger.info("[+] 检测到鲸鱼!") 88 | logger.info('[+] TIPS:如果您首次在Docker中使用,或者需要维护,请通过 \033[36m docker attach School-Bot-Plugin-Loader(School-Bot-Plugin-Loader为容器名) \033[0m 进入容器shell手动维护,结束维护输入\033[36m exit \033[0m \033[0m') 89 | else: 90 | config_file_path = './config.json' 91 | 92 | if not os.path.exists(config_file_path) or os.path.getsize(config_file_path) == 0: 93 | logger.info('[-] 首次使用,请先进行配置, 配置文件不存在或为空,请先进行配置') 94 | api_url = input('请输入WebSocket地址: ') 95 | if api_url == '': 96 | logger.error('WebSocket地址不能为空') 97 | exit() 98 | bot_id = input('请输入Bot QQ: ') 99 | if bot_id == '': 100 | logger.error('Bot QQ不能为空') 101 | exit() 102 | bot_id = int(bot_id) 103 | token = input('请输入Token(没有请留空): ') 104 | if token == '': 105 | token = None 106 | config = {'conn': {'api_url': api_url, 'bot_id': bot_id, 'token': token}, 'plugin': {}} 107 | with open(config_file_path, 'w') as c: 108 | c.write(json.dumps(config)) 109 | with open(config_file_path, 'r') as c: 110 | try: 111 | variable.config = json.loads(c.read()) 112 | except: 113 | logger.error('无WebSocket配置') 114 | exit() 115 | conn = variable.config['conn'] 116 | api_url = None 117 | if 'api_url' in conn: 118 | api_url = conn['api_url'] 119 | variable.bot_id = None 120 | if 'bot_id' in conn: 121 | variable.bot_id = conn['bot_id'] 122 | token = None 123 | if 'token' in conn: 124 | token = conn['token'] 125 | if os.path.exists('data') == False: 126 | os.mkdir('data') 127 | variable.loader = PluginLoader() 128 | variable.ws = WS(api_url, token) 129 | variable.bot = Bot() 130 | variable.processer = Processer() 131 | websocket_thread = threading.Thread(target=variable.ws.start, args=()) 132 | logger.info("正在启动WebSocket...") 133 | websocket_thread.start() 134 | time.sleep(3) 135 | logger.info("WebSocket启动完成") 136 | variable.util = Util() 137 | logger.info("正在启动插件...") 138 | variable.loader.register_plugins() 139 | variable.loader.enable_plugins() 140 | 141 | def ws_mon(): 142 | global variable 143 | logger = variable.log.getChild("WebSocket Monitor") 144 | ws_retry = 0 145 | while True: 146 | if variable.main_stop: 147 | logger.info("接收到停止命令") 148 | break 149 | if not variable.ws.get_status(): 150 | logger.error("WebSocket断开") 151 | if ws_retry > 3: 152 | logger.info("重试次数过多") 153 | logger.info("正在退出...") 154 | variable.loader.unload_plugins() 155 | variable.ws.close() 156 | exit() 157 | ws_retry += 1 158 | if ws_retry == 1: 159 | variable.loader.disable_plugins() 160 | variable.loader.unregister_plugins() 161 | logger.info(f"{ws_retry * 5}秒后重试") 162 | time.sleep(5 * ws_retry) 163 | try: 164 | logger.info("正在重连WebSocket...") 165 | websocket_thread = threading.Thread(target=variable.ws.start, args=()) 166 | websocket_thread.start() 167 | logger.info(f"重试次数: {ws_retry}") 168 | time.sleep(3) 169 | except: 170 | pass 171 | else: 172 | if ws_retry != 0: 173 | logger.info("WebSocket重连成功") 174 | ws_retry = 0 175 | variable.loader.register_plugins() 176 | variable.loader.enable_plugins() 177 | time.sleep(0.01) 178 | 179 | ws_mon_thread = threading.Thread(target=ws_mon, args=()) 180 | ws_mon_thread.start() 181 | 182 | while True: 183 | a = "" 184 | try: 185 | a = input() 186 | except: 187 | a = "exit" 188 | if a == "exit": 189 | try: 190 | logger.info("正在退出...") 191 | with open('config.json', 'w') as c: 192 | c.write(json.dumps(variable.config)) 193 | variable.loader.disable_plugins() 194 | variable.loader.unregister_plugins() 195 | variable.loader.unload_plugins() 196 | variable.main_stop = True 197 | variable.ws.close() 198 | websocket_thread.join() 199 | ws_mon_thread.join() 200 | exit() 201 | except Exception as e: 202 | variable.main_stop = True 203 | websocket_thread.join() 204 | ws_mon_thread.join() 205 | logger.exception(e) 206 | exit() 207 | else: 208 | try: 209 | processCommand(a) 210 | except Exception as e: 211 | logger.exception(e) 212 | time.sleep(0.01) 213 | 214 | 215 | if __name__ == '__main__': 216 | try: 217 | main() 218 | except: 219 | exit() 220 | -------------------------------------------------------------------------------- /processer.py: -------------------------------------------------------------------------------- 1 | import json 2 | import threading 3 | from variable import variable 4 | 5 | logger = variable.log.getChild("Processer") 6 | 7 | 8 | class Processer(object): 9 | 10 | def processMessage(self, msg): 11 | try: 12 | msg = json.loads(msg) 13 | if "echo" in msg: 14 | variable.util.put_retmsg(msg) 15 | return 16 | if 'self_id' in msg and msg['self_id'] != variable.bot_id: 17 | return 18 | else: 19 | raw_thread = threading.Thread(target=self.raw_ws_process, args=(msg,)) 20 | raw_thread.start() 21 | if "status" in msg: 22 | if "meta_event_type" in msg and msg['meta_event_type'] == 'heartbeat': 23 | variable.bot.set_status(msg['status']['online']) 24 | else: 25 | logger.info(f"返回信息: {msg}") 26 | else: 27 | if msg['post_type'] == 'meta_event': 28 | if msg['meta_event_type'] == 'lifecycle': 29 | if msg['sub_type'] == 'connect': 30 | logger.info(f"WebSocket连接成功") 31 | else: 32 | logger.debug(msg) 33 | else: 34 | logger.debug(msg) 35 | elif msg['post_type'] == 'message': 36 | if msg['message_type'] == 'private': 37 | logger.info(f"收到 ({msg['user_id']}) 的私聊消息: {msg['raw_message']}") 38 | self.private_message(msg['time'], msg['self_id'], msg['sub_type'], msg['message_id'], 39 | msg['user_id'], msg['message'], msg['raw_message'], msg['font'], 40 | msg['sender']) 41 | elif msg['message_type'] == 'group': 42 | logger.info( 43 | f"收到 [{msg['group_id']}]({msg['user_id']}) 在群 [{msg['group_id']}] 中的群消息: {msg['raw_message']}") 44 | if msg['sub_type'] != 'anonymous': 45 | msg['anonymous'] = None 46 | self.group_message(msg['time'], msg['self_id'], msg['sub_type'], msg['message_id'], 47 | msg['group_id'], msg['user_id'], msg['anonymous'], msg['message'], 48 | msg['raw_message'], msg['font'], msg['sender']) 49 | else: 50 | logger.debug(msg) 51 | elif msg['post_type'] == 'notice': 52 | if msg['notice_type'] == 'group_upload': 53 | logger.info(f"({msg['user_id']}) 在群 [{msg['group_id']}] 中上传了文件: {msg['file']['name']}") 54 | self.group_upload(msg['time'], msg['self_id'], msg['group_id'], msg['user_id'], msg['file']) 55 | elif msg['notice_type'] == 'group_admin': 56 | if msg['sub_type'] == 'set': 57 | logger.info(f"({msg['user_id']}) 在群 [{msg['group_id']}] 中被设置为管理员") 58 | else: 59 | logger.info(f"({msg['user_id']}) 在群 [{msg['group_id']}] 中被取消管理员") 60 | self.group_admin(msg['time'], msg['self_id'], msg['sub_type'], msg['group_id'], 61 | msg['user_id']) 62 | elif msg['notice_type'] == 'group_decrease': 63 | if msg['sub_type'] == 'leave': 64 | logger.info(f"({msg['user_id']}) 在群 [{msg['group_id']}] 中离开了群") 65 | elif msg['sub_type'] == 'kick_me': 66 | logger.info(f"Bot 在群 [{msg['group_id']}] 中被踢出群") 67 | else: 68 | logger.info(f"({msg['user_id']}) 在群 [{msg['group_id']}] 中被 ({msg['operator_id']}) 踢出群") 69 | self.group_decrease(msg['time'], msg['self_id'], msg['sub_type'], msg['group_id'], 70 | msg['operator_id'], msg['user_id']) 71 | elif msg['notice_type'] == 'group_increase': 72 | if msg['sub_type'] == 'approve': 73 | logger.info(f"({msg['user_id']}) 在群 [{msg['group_id']}] 中通过了入群申请") 74 | else: 75 | logger.info(f"({msg['user_id']}) 在群 [{msg['group_id']}] 中被 ({msg['operator_id']}) 邀请入群") 76 | self.group_increase(msg['time'], msg['self_id'], msg['sub_type'], msg['group_id'], 77 | msg['operator_id'], msg['user_id']) 78 | elif msg['notice_type'] == 'group_ban': 79 | if msg['sub_type'] == 'ban': 80 | logger.info( 81 | f"({msg['user_id']}) 在群 [{msg['group_id']}] 中被 ({msg['operator_id']}) 禁言 {msg['duration']} 秒") 82 | else: 83 | logger.info(f"({msg['user_id']}) 在群 [{msg['group_id']}] 中被 ({msg['operator_id']}) 解除禁言") 84 | self.group_ban(msg['time'], msg['self_id'], msg['sub_type'], msg['group_id'], 85 | msg['operator_id'], msg['user_id'], msg['duration']) 86 | elif msg['notice_type'] == 'friend_add': 87 | logger.info(f"({msg['user_id']}) 成为了好友") 88 | self.friend_add(msg['time'], msg['self_id'], msg['user_id']) 89 | elif msg['notice_type'] == 'group_recall': 90 | logger.info(f"({msg['user_id']}) 在群 [{msg['group_id']}] 中撤回了消息") 91 | self.group_recall(msg['time'], msg['self_id'], msg['group_id'], msg['user_id'], 92 | msg['operator_id'], msg['message_id']) 93 | elif msg['notice_type'] == 'friend_recall': 94 | logger.info(f"({msg['user_id']}) 撤回了消息") 95 | self.friend_recall(msg['time'], msg['self_id'], msg['user_id'], msg['message_id']) 96 | elif msg['notice_type'] == 'notify': 97 | if msg['sub_type'] == 'poke': 98 | self.group_poke(msg['time'], msg['self_id'], msg['group_id'], msg['user_id'], 99 | msg['target_id']) 100 | elif msg['sub_type'] == 'lucky_king': 101 | self.lucky_king(msg['time'], msg['self_id'], msg['group_id'], msg['user_id'], 102 | msg['target_id']) 103 | elif msg['sub_type'] == 'honor': 104 | self.honor(msg['time'], msg['self_id'], msg['group_id'], msg['honor_type'], 105 | msg['user_id']) 106 | else: 107 | logger.debug(msg) 108 | else: 109 | logger.debug(msg) 110 | elif msg['post_type'] == 'request': 111 | if msg['request_type'] == 'friend': 112 | logger.info(f"({msg['user_id']}) 请求添加好友") 113 | self.friend_request(msg['time'], msg['self_id'], msg['user_id'], msg['comment'], 114 | msg['flag']) 115 | elif msg['request_type'] == 'group': 116 | logger.info(f"({msg['user_id']}) 请求加入群 [{msg['group_id']}]") 117 | self.group_request(msg['time'], msg['self_id'], msg['sub_type'], msg['group_id'], 118 | msg['user_id'], msg['comment'], msg['flag']) 119 | else: 120 | logger.debug(msg) 121 | else: 122 | logger.debug(f"未知消息: {msg}") 123 | except Exception as e: 124 | logger.error(f"消息处理出错: {msg}") 125 | logger.exception(e) 126 | 127 | def get_sorted_func(self, method): 128 | funcs = {} 129 | for plugin in variable.loader.plugin_enables: 130 | if method in variable.loader.plugin_methods[plugin]: 131 | if hasattr(variable.loader.plugin_registers[plugin], 132 | variable.loader.plugin_methods[plugin][method]['func']): 133 | try: 134 | funcs[variable.loader.plugin_methods[plugin][method]['priority']].append((plugin, getattr( 135 | variable.loader.plugin_registers[plugin], 136 | variable.loader.plugin_methods[plugin][method]['func']))) 137 | except: 138 | funcs[variable.loader.plugin_methods[plugin][method]['priority']] = [(plugin, getattr( 139 | variable.loader.plugin_registers[plugin], 140 | variable.loader.plugin_methods[plugin][method]['func']))] 141 | return funcs 142 | 143 | def private_message(self, time, self_id, sub_type, message_id, user_id, message, raw_message, font, sender): 144 | funcs = self.get_sorted_func('private_message') 145 | for priority in sorted(funcs.keys()): 146 | for plugin in funcs[priority]: 147 | logger.debug(f"正在调用优先级为 {priority} 中 {plugin[0]} 的 private_message 方法") 148 | if plugin[1](time, self_id, sub_type, message_id, user_id, message, raw_message, font, 149 | sender) and priority > 10000: 150 | logger.debug(f"插件 {plugin[0]} 阻塞了消息") 151 | return 152 | 153 | def group_message(self, time, self_id, sub_type, message_id, group_id, user_id, anonymous, message, raw_message, 154 | font, sender): 155 | funcs = self.get_sorted_func('group_message') 156 | for priority in sorted(funcs.keys()): 157 | for plugin in funcs[priority]: 158 | logger.debug(f"正在调用优先级为 {priority} 中 {plugin[0]} 的 group_message 方法") 159 | if plugin[1](time, self_id, sub_type, message_id, group_id, user_id, anonymous, message, raw_message, 160 | font, sender) and priority > 10000: 161 | logger.debug(f"插件 {plugin[0]} 阻塞了消息") 162 | return 163 | 164 | def group_upload(self, time, self_id, group_id, user_id, file): 165 | funcs = self.get_sorted_func('group_upload') 166 | for priority in sorted(funcs.keys()): 167 | for plugin in funcs[priority]: 168 | logger.debug(f"正在调用优先级为 {priority} 中 {plugin[0]} 的 group_upload 方法") 169 | if plugin[1](time, self_id, group_id, user_id, file) and priority > 10000: 170 | logger.debug(f"插件 {plugin[0]} 阻塞了消息") 171 | return 172 | 173 | def group_admin(self, time, self_id, sub_type, group_id, user_id): 174 | funcs = self.get_sorted_func('group_admin') 175 | for priority in sorted(funcs.keys()): 176 | for plugin in funcs[priority]: 177 | logger.debug(f"正在调用优先级为 {priority} 中 {plugin[0]} 的 group_admin 方法") 178 | if plugin[1](time, self_id, sub_type, group_id, user_id) and priority > 10000: 179 | logger.debug(f"插件 {plugin[0]} 阻塞了消息") 180 | return 181 | 182 | def group_decrease(self, time, self_id, sub_type, group_id, operator_id, user_id): 183 | funcs = self.get_sorted_func('group_decrease') 184 | for priority in sorted(funcs.keys()): 185 | for plugin in funcs[priority]: 186 | logger.debug(f"正在调用优先级为 {priority} 中 {plugin[0]} 的 group_decrease 方法") 187 | if plugin[1](time, self_id, sub_type, group_id, operator_id, user_id) and priority > 10000: 188 | logger.debug(f"插件 {plugin[0]} 阻塞了消息") 189 | return 190 | 191 | def group_increase(self, time, self_id, sub_type, group_id, operator_id, user_id): 192 | funcs = self.get_sorted_func('group_increase') 193 | for priority in sorted(funcs.keys()): 194 | for plugin in funcs[priority]: 195 | logger.debug(f"正在调用优先级为 {priority} 中 {plugin[0]} 的 group_increase 方法") 196 | if plugin[1](time, self_id, sub_type, group_id, operator_id, user_id) and priority > 10000: 197 | logger.debug(f"插件 {plugin[0]} 阻塞了消息") 198 | return 199 | 200 | def group_ban(self, time, self_id, sub_type, group_id, operator_id, user_id, duration): 201 | funcs = self.get_sorted_func('group_ban') 202 | for priority in sorted(funcs.keys()): 203 | for plugin in funcs[priority]: 204 | logger.debug(f"正在调用优先级为 {priority} 中 {plugin[0]} 的 group_ban 方法") 205 | if plugin[1](time, self_id, sub_type, group_id, operator_id, user_id, duration) and priority > 10000: 206 | logger.debug(f"插件 {plugin[0]} 阻塞了消息") 207 | return 208 | 209 | def friend_add(self, time, self_id, user_id): 210 | funcs = self.get_sorted_func('friend_add') 211 | for priority in sorted(funcs.keys()): 212 | for plugin in funcs[priority]: 213 | logger.debug(f"正在调用优先级为 {priority} 中 {plugin[0]} 的 friend_add 方法") 214 | if plugin[1](time, self_id, user_id) and priority > 10000: 215 | logger.debug(f"插件 {plugin[0]} 阻塞了消息") 216 | return 217 | 218 | def group_recall(self, time, self_id, group_id, user_id, operator_id, message_id): 219 | funcs = self.get_sorted_func('group_recall') 220 | for priority in sorted(funcs.keys()): 221 | for plugin in funcs[priority]: 222 | logger.debug(f"正在调用优先级为 {priority} 中 {plugin[0]} 的 group_recall 方法") 223 | if plugin[1](time, self_id, group_id, user_id, operator_id, message_id) and priority > 10000: 224 | logger.debug(f"插件 {plugin[0]} 阻塞了消息") 225 | return 226 | 227 | def friend_recall(self, time, self_id, user_id, message_id): 228 | funcs = self.get_sorted_func('friend_recall') 229 | for priority in sorted(funcs.keys()): 230 | for plugin in funcs[priority]: 231 | logger.debug(f"正在调用优先级为 {priority} 中 {plugin[0]} 的 friend_recall 方法") 232 | if plugin[1](time, self_id, user_id, message_id) and priority > 10000: 233 | logger.debug(f"插件 {plugin[0]} 阻塞了消息") 234 | return 235 | 236 | def group_poke(self, time, self_id, group_id, user_id, target_id): 237 | funcs = self.get_sorted_func('group_poke') 238 | for priority in sorted(funcs.keys()): 239 | for plugin in funcs[priority]: 240 | logger.debug(f"正在调用优先级为 {priority} 中 {plugin[0]} 的 group_poke 方法") 241 | if plugin[1](time, self_id, group_id, user_id, target_id) and priority > 10000: 242 | logger.debug(f"插件 {plugin[0]} 阻塞了消息") 243 | return 244 | 245 | def lucky_king(self, time, self_id, group_id, user_id, target_id): 246 | funcs = self.get_sorted_func('lucky_king') 247 | for priority in sorted(funcs.keys()): 248 | for plugin in funcs[priority]: 249 | logger.debug(f"正在调用优先级为 {priority} 中 {plugin[0]} 的 lucky_king 方法") 250 | if plugin[1](time, self_id, group_id, user_id, target_id) and priority > 10000: 251 | logger.debug(f"插件 {plugin[0]} 阻塞了消息") 252 | return 253 | 254 | def honor(self, time, self_id, group_id, honor_type, user_id): 255 | funcs = self.get_sorted_func('honor') 256 | for priority in sorted(funcs.keys()): 257 | for plugin in funcs[priority]: 258 | logger.debug(f"正在调用优先级为 {priority} 中 {plugin[0]} 的 honor 方法") 259 | if plugin[1](time, self_id, group_id, honor_type, user_id) and priority > 10000: 260 | logger.debug(f"插件 {plugin[0]} 阻塞了消息") 261 | return 262 | 263 | def friend_request(self, time, self_id, user_id, comment, flag): 264 | funcs = self.get_sorted_func('friend_request') 265 | for priority in sorted(funcs.keys()): 266 | for plugin in funcs[priority]: 267 | logger.debug(f"正在调用优先级为 {priority} 中 {plugin[0]} 的 friend_request 方法") 268 | if plugin[1](time, self_id, user_id, comment, flag) and priority > 10000: 269 | logger.debug(f"插件 {plugin[0]} 阻塞了消息") 270 | return 271 | 272 | def group_request(self, time, self_id, sub_type, group_id, user_id, comment, flag): 273 | funcs = self.get_sorted_func('group_request') 274 | for priority in sorted(funcs.keys()): 275 | for plugin in funcs[priority]: 276 | logger.debug(f"正在调用优先级为 {priority} 中 {plugin[0]} 的 group_request 方法") 277 | if plugin[1](time, self_id, sub_type, group_id, user_id, comment, flag) and priority > 10000: 278 | logger.debug(f"插件 {plugin[0]} 阻塞了消息") 279 | return 280 | 281 | def raw_ws_process(self, msg): 282 | funcs = self.get_sorted_func('raw_ws_process') 283 | for priority in sorted(funcs.keys()): 284 | for plugin in funcs[priority]: 285 | logger.debug(f"正在调用优先级为 {priority} 中 {plugin[0]} 的 raw_ws_process 方法") 286 | plugin[1](msg) 287 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | websocket-client -------------------------------------------------------------------------------- /util.py: -------------------------------------------------------------------------------- 1 | import json 2 | import time 3 | import uuid 4 | 5 | from variable import variable 6 | 7 | logger = variable.log.getChild("Util") 8 | 9 | 10 | class Util(object): 11 | retmsg = {} 12 | 13 | def check_auth(self, auth, perm): 14 | try: 15 | if perm in variable.loader.auths[auth]: 16 | return True 17 | else: 18 | return False 19 | except Exception as e: 20 | logger.exception(e) 21 | return False 22 | 23 | def send_private_msg(self, auth, user_id, message, auto_escape=False, timeout=5): 24 | if self.check_auth(auth, 'send_private_msg'): 25 | if type(message) == str and message == "": 26 | return False, 'message is empty' 27 | if type(message) == list and len(message) == 0: 28 | return False, 'message is empty' 29 | uid = uuid.uuid4().hex 30 | m = {"action": "send_private_msg", 31 | "params": {"user_id": user_id, "message": message, "auto_escape": auto_escape}, "echo": uid} 32 | logger.info(f"向 ({m['params']['user_id']}) 发送私聊消息: {m['params']['message']}") 33 | m = json.dumps(m) 34 | variable.ws.send(m) 35 | ret = self.waitFor(uid, timeout=timeout) 36 | if ret['status'] == 'ok': 37 | return True, ret['data'] 38 | elif ret['status'] == 'async': 39 | return True, 'async' 40 | elif ret['status'] == 'timeout': 41 | return False, 'timeout' 42 | else: 43 | return False, 'error' 44 | else: 45 | logger.debug(f'插件权限不足') 46 | return False, 'Permission Denied' 47 | 48 | def send_private_msg_async(self, auth, user_id, message, auto_escape=False): 49 | if self.check_auth(auth, 'send_private_msg'): 50 | if type(message) == str and message == "": 51 | return False, 'message is empty' 52 | if type(message) == list and len(message) == 0: 53 | return False, 'message is empty' 54 | m = {"action": "send_private_msg_async", 55 | "params": {"user_id": user_id, "message": message, "auto_escape": auto_escape}} 56 | logger.info(f"向 ({m['params']['user_id']}) 发送私聊消息: {m['params']['message']}") 57 | m = json.dumps(m) 58 | variable.ws.send(m) 59 | return True, 'async' 60 | else: 61 | logger.debug(f'插件权限不足') 62 | return False, 'Permission Denied' 63 | 64 | def send_private_msg_rate_limit(self, auth, user_id, message, auto_escape=False): 65 | if self.check_auth(auth, 'send_private_msg'): 66 | if type(message) == str and message == "": 67 | return False, 'message is empty' 68 | if type(message) == list and len(message) == 0: 69 | return False, 'message is empty' 70 | m = {"action": "send_private_msg_rate_limit", 71 | "params": {"user_id": user_id, "message": message, "auto_escape": auto_escape}} 72 | logger.info(f"向 ({m['params']['user_id']}) 发送私聊消息: {m['params']['message']}") 73 | m = json.dumps(m) 74 | variable.ws.send(m) 75 | return True, 'async' 76 | else: 77 | logger.debug(f'插件权限不足') 78 | return False, 'Permission Denied' 79 | 80 | def send_group_msg(self, auth, group_id, message, auto_escape=False, timeout=5): 81 | if self.check_auth(auth, 'send_group_msg'): 82 | if type(message) == str and message == "": 83 | return False, 'message is empty' 84 | if type(message) == list and len(message) == 0: 85 | return False, 'message is empty' 86 | uid = uuid.uuid4().hex 87 | m = {"action": "send_group_msg", 88 | "params": {"group_id": group_id, "message": message, "auto_escape": auto_escape}, "echo": uid} 89 | logger.info(f"向 [{m['params']['group_id']}] 发送群消息: {m['params']['message']}") 90 | m = json.dumps(m) 91 | variable.ws.send(m) 92 | ret = self.waitFor(uid, timeout=timeout) 93 | if ret['status'] == 'ok': 94 | return True, ret['data'] 95 | elif ret['status'] == 'async': 96 | return True, 'async' 97 | elif ret['status'] == 'timeout': 98 | return False, 'timeout' 99 | else: 100 | return False, 'error' 101 | else: 102 | logger.debug(f'插件权限不足') 103 | return False, 'Permission Denied' 104 | 105 | def send_group_msg_async(self, auth, group_id, message, auto_escape=False): 106 | if self.check_auth(auth, 'send_group_msg'): 107 | if type(message) == str and message == "": 108 | return False, 'message is empty' 109 | if type(message) == list and len(message) == 0: 110 | return False, 'message is empty' 111 | m = {"action": "send_group_msg_async", 112 | "params": {"group_id": group_id, "message": message, "auto_escape": auto_escape}} 113 | logger.info(f"向 [{m['params']['group_id']}] 发送群消息: {m['params']['message']}") 114 | m = json.dumps(m) 115 | variable.ws.send(m) 116 | return True, 'async' 117 | else: 118 | logger.debug(f'插件权限不足') 119 | return False, 'Permission Denied' 120 | 121 | def send_group_forward_msg(self, auth, group_id, messages, timeout=5): 122 | """ 123 | 发送合并转发消息到群。 124 | 参数: 125 | - auth (str): 调用此API的插件认证信息。 126 | - group_id (int): 目标群号。 127 | - messages (list): 消息节点列表。每个元素可以是以下格式之一: 128 | - dict: 完整的消息节点,格式为 {"type": "node", "data": {...}} 129 | - tuple 或 list: (user_id, nickname, content) 或 [user_id, nickname, content] 130 | - str: 纯文本消息内容 131 | - int: 作为user_id,将自动获取昵称并使用空字符串作为内容 132 | - 样例: 133 | - messages = [ 134 | "你好,我是插件加载器哦~", 135 | (10086, "User2", "How are you?"), 136 | {"type": "node", "data": {"user_id": "123456", "nickname": "User1", "content": "Hello"}}, 137 | 123 #message_id 138 | ] 139 | code,ret = self.util.send_group_forward_msg(self.auth, group_id, messages) 140 | 141 | timeout (int, 可选): 等待响应的超时时间(秒)。默认为5秒。 142 | 返回: 143 | tuple: (bool, str|dict) 144 | - 第一个元素表示是否成功 145 | - 第二个元素在成功时为响应数据,失败时为错误信息 146 | """ 147 | if (not self.check_auth(auth, 'send_group_forward_msg')) and (not self.check_auth(auth,'send_group_msg')): 148 | logger.warning(f'插件权限不足,请检查是否存在 send_group_forward_msg 或 send_group_msg') 149 | return False, 'Permission Denied' 150 | if not messages: 151 | return False, 'messages is empty' 152 | processed_messages = self._process_forward_messages(auth,group_id, messages) 153 | uid = uuid.uuid4().hex 154 | msg = { 155 | "action": "send_group_forward_msg", 156 | "params": { 157 | "group_id": group_id, 158 | "messages": processed_messages 159 | }, 160 | "echo": uid 161 | } 162 | logger.info(f"向群 [{group_id}] 发送合并转发消息") 163 | msg = json.dumps(msg) 164 | variable.ws.send(msg) 165 | ret = self.waitFor(uid, timeout=timeout) 166 | if ret['status'] == 'ok': 167 | return True, ret['data'] 168 | elif ret['status'] == 'async': 169 | return True, 'async' 170 | elif ret['status'] == 'timeout': 171 | return False, 'timeout' 172 | else: 173 | return False, 'error' 174 | 175 | 176 | 177 | 178 | def _process_forward_messages(self,auth,group_id,messages): 179 | """ 180 | 合并转发解析 181 | """ 182 | processed_messages = [] 183 | for msg in messages: 184 | if isinstance(msg, dict): 185 | if msg.get('type') == 'node' and 'data' in msg: 186 | processed_messages.append(msg) 187 | else: 188 | raise ValueError(f"[合并转发处理] 不合法的合并转发结构: {msg}") 189 | elif isinstance(msg, (tuple, list)): 190 | if len(msg) != 3: 191 | raise ValueError(f"[合并转发处理] 元组或者列表必须包含三个元素: {msg}") 192 | uin, name, content = msg 193 | node = { 194 | "type": "node", 195 | "data": { 196 | "uin": str(uin), 197 | "name": name, 198 | "content": content 199 | } 200 | } 201 | processed_messages.append(node) 202 | elif isinstance(msg, str): 203 | node = { 204 | "type": "node", 205 | "data": { 206 | # "uin": "10000", 207 | # "name": "Anonymous", 208 | "content": msg 209 | } 210 | } 211 | processed_messages.append(node) 212 | elif isinstance(msg, int): 213 | node = { 214 | "type": "node", 215 | "data": { 216 | "id": str(msg) 217 | } 218 | } 219 | processed_messages.append(node) 220 | else: 221 | raise ValueError(f"[合并转发处理] 不合法的消息格式: {msg}") 222 | return processed_messages 223 | 224 | def get_group_member_info(self,auth, group_id, user_id): 225 | """ 226 | 获得用户信息: 227 | 参数: 228 | - auth (str): 调用此API的插件认证信息。 229 | - group_id (int): 目标群号。 230 | - user_id (int): 用户的账号ID 231 | 232 | 返回信息见: 的获得群成员信息 233 | 以下是常见信息: 234 | - nickname : 昵称 235 | - qq_level : 等级 236 | - join_time: 1724467603, 237 | - last_sent_time: 1724467603 238 | """ 239 | if not self.check_auth(auth,'send_group_msg'): 240 | logger.warning(f'插件权限不足,你需要:send_group_msg 权限') 241 | return False, 'Permission Denied' 242 | logger.info(f"正在获得群[{group_id}]成员[{user_id}]的信息") 243 | uid = uuid.uuid4().hex 244 | msg = { 245 | "action": "get_group_member_info", 246 | "params": { 247 | "group_id": group_id, 248 | "user_id": user_id 249 | }, 250 | "echo": uid 251 | } 252 | msg = json.dumps(msg) 253 | variable.ws.send(msg) 254 | ret = self.waitFor(uid, timeout=3) 255 | if ret['status'] == 'ok': 256 | logger.info(ret['data']) 257 | return True, ret['data'] 258 | if ret['status'] == 'async': 259 | return True, 'async' 260 | if ret['status'] == 'timeout': 261 | return False, 'timeout' 262 | return False, 'error' 263 | 264 | def recall_msg(self,auth,message_id): 265 | """ 266 | 权限:recall_msg 或 delete_msg 267 | 撤回消息: 268 | 参数: 269 | - auth (str): 调用此API的插件认证信息。 270 | - message_id (int): 消息 ID 271 | """ 272 | return self.delete_msg(auth=auth,message_id=message_id) 273 | 274 | 275 | def send_group_msg_rate_limit(self, auth, group_id, message, auto_escape=False): 276 | if self.check_auth(auth, 'send_group_msg'): 277 | if type(message) == str and message == "": 278 | return False, 'message is empty' 279 | if type(message) == list and len(message) == 0: 280 | return False, 'message is empty' 281 | m = {"action": "send_group_msg_rate_limit", 282 | "params": {"group_id": group_id, "message": message, "auto_escape": auto_escape}} 283 | logger.info(f"向 [{m['params']['group_id']}] 发送群消息: {m['params']['message']}") 284 | m = json.dumps(m) 285 | variable.ws.send(m) 286 | return True, 'async' 287 | else: 288 | logger.debug(f'插件权限不足') 289 | return False, 'Permission Denied' 290 | 291 | def send_msg(self, auth, message_type, user_id, group_id, message, auto_escape=False, timeout=5): 292 | if self.check_auth(auth, 'send_msg'): 293 | if message_type == 'private' and user_id is None: 294 | return False, 'user_id is None' 295 | if message_type == 'group' and group_id is None: 296 | return False, 'group_id is None' 297 | if message_type is None and user_id is None and group_id is None: 298 | return False, 'message_type is None' 299 | if type(message) == str and message == "": 300 | return False, 'message is empty' 301 | if type(message) == list and len(message) == 0: 302 | return False, 'message is empty' 303 | if message_type == 'private': 304 | uid = uuid.uuid4().hex 305 | m = {"action": "send_msg", 306 | "params": {"message_type": message_type, "user_id": user_id, "message": message, 307 | "auto_escape": auto_escape}, "echo": uid} 308 | logger.info(f"向 ({m['params']['user_id']}) 发送私聊消息: {m['params']['message']}") 309 | m = json.dumps(m) 310 | variable.ws.send(m) 311 | ret = self.waitFor(uid, timeout=timeout) 312 | if ret['status'] == 'ok': 313 | return True, ret['data'] 314 | elif ret['status'] == 'async': 315 | return True, 'async' 316 | elif ret['status'] == 'timeout': 317 | return False, 'timeout' 318 | else: 319 | return False, 'error' 320 | elif message_type == 'group': 321 | uid = uuid.uuid4().hex 322 | m = {"action": "send_msg", 323 | "params": {"message_type": message_type, "group_id": group_id, "message": message, 324 | "auto_escape": auto_escape}, "echo": uid} 325 | logger.info(f"向 [{m['params']['group_id']}] 发送群消息: {m['params']['message']}") 326 | m = json.dumps(m) 327 | variable.ws.send(m) 328 | ret = self.waitFor(uid, timeout=timeout) 329 | if ret['status'] == 'ok': 330 | return True, ret['data'] 331 | elif ret['status'] == 'async': 332 | return True, 'async' 333 | elif ret['status'] == 'timeout': 334 | return False, 'timeout' 335 | else: 336 | return False, 'error' 337 | else: 338 | if user_id is None: 339 | uid = uuid.uuid4().hex 340 | m = {"action": "send_msg", 341 | "params": {"group_id": group_id, "message": message, "auto_escape": auto_escape}, "echo": uid} 342 | logger.info(f"向 [{m['params']['group_id']}] 发送群消息: {m['params']['message']}") 343 | m = json.dumps(m) 344 | variable.ws.send(m) 345 | ret = self.waitFor(uid, timeout=timeout) 346 | if ret['status'] == 'ok': 347 | return True, ret['data'] 348 | elif ret['status'] == 'async': 349 | return True, 'async' 350 | elif ret['status'] == 'timeout': 351 | return False, 'timeout' 352 | else: 353 | return False, 'error' 354 | elif group_id is None: 355 | uid = uuid.uuid4().hex 356 | m = {"action": "send_msg", 357 | "params": {"user_id": user_id, "message": message, "auto_escape": auto_escape}, "echo": uid} 358 | logger.info(f"向 ({m['params']['user_id']}) 发送私聊消息: {m['params']['message']}") 359 | m = json.dumps(m) 360 | variable.ws.send(m) 361 | ret = self.waitFor(uid, timeout=timeout) 362 | if ret['status'] == 'ok': 363 | return True, ret['data'] 364 | elif ret['status'] == 'async': 365 | return True, 'async' 366 | elif ret['status'] == 'timeout': 367 | return False, 'timeout' 368 | else: 369 | return False, 'error' 370 | else: 371 | return False, 'message_type is None' 372 | else: 373 | logger.debug(f'插件权限不足') 374 | return False, 'Permission Denied' 375 | 376 | def send_msg_async(self, auth, message_type, user_id, group_id, message, auto_escape=False): 377 | if self.check_auth(auth, 'send_msg'): 378 | if message_type == 'private' and user_id is None: 379 | return False, 'user_id is None' 380 | if message_type == 'group' and group_id is None: 381 | return False, 'group_id is None' 382 | if message_type is None and user_id is None and group_id is None: 383 | return False, 'message_type is None' 384 | if type(message) == str and message == "": 385 | return False, 'message is empty' 386 | if type(message) == list and len(message) == 0: 387 | return False, 'message is empty' 388 | if message_type == 'private': 389 | m = {"action": "send_msg_async", 390 | "params": {"message_type": message_type, "user_id": user_id, "message": message, 391 | "auto_escape": auto_escape}} 392 | logger.info(f"向 ({m['params']['user_id']}) 发送私聊消息: {m['params']['message']}") 393 | m = json.dumps(m) 394 | variable.ws.send(m) 395 | return True, 'async' 396 | elif message_type == 'group': 397 | m = {"action": "send_msg_async", 398 | "params": {"message_type": message_type, "group_id": group_id, "message": message, 399 | "auto_escape": auto_escape}} 400 | logger.info(f"向 [{m['params']['group_id']}] 发送群消息: {m['params']['message']}") 401 | m = json.dumps(m) 402 | variable.ws.send(m) 403 | return True, 'async' 404 | else: 405 | if user_id is None: 406 | m = {"action": "send_msg_async", 407 | "params": {"group_id": group_id, "message": message, "auto_escape": auto_escape}} 408 | logger.info(f"向 [{m['params']['group_id']}] 发送群消息: {m['params']['message']}") 409 | m = json.dumps(m) 410 | variable.ws.send(m) 411 | return True, 'async' 412 | elif group_id is None: 413 | m = {"action": "send_msg_async", 414 | "params": {"user_id": user_id, "message": message, "auto_escape": auto_escape}} 415 | logger.info(f"向 ({m['params']['user_id']}) 发送私聊消息: {m['params']['message']}") 416 | m = json.dumps(m) 417 | variable.ws.send(m) 418 | return True, 'async' 419 | else: 420 | return False, 'message_type is None' 421 | else: 422 | logger.debug(f'插件权限不足') 423 | return False, 'Permission Denied' 424 | 425 | def send_msg_rate_limit(self, auth, message_type, user_id, group_id, message, auto_escape=False): 426 | if self.check_auth(auth, 'send_msg'): 427 | if message_type == 'private' and user_id is None: 428 | return False, 'user_id is None' 429 | if message_type == 'group' and group_id is None: 430 | return False, 'group_id is None' 431 | if message_type is None and user_id is None and group_id is None: 432 | return False, 'message_type is None' 433 | if type(message) == str and message == "": 434 | return False, 'message is empty' 435 | if type(message) == list and len(message) == 0: 436 | return False, 'message is empty' 437 | if message_type == 'private': 438 | m = {"action": "send_msg_rate_limit", 439 | "params": {"message_type": message_type, "user_id": user_id, "message": message, 440 | "auto_escape": auto_escape}} 441 | logger.info(f"向 ({m['params']['user_id']}) 发送私聊消息: {m['params']['message']}") 442 | m = json.dumps(m) 443 | variable.ws.send(m) 444 | return True, 'async' 445 | elif message_type == 'group': 446 | m = {"action": "send_msg_rate_limit", 447 | "params": {"message_type": message_type, "group_id": group_id, "message": message, 448 | "auto_escape": auto_escape}} 449 | logger.info(f"向 [{m['params']['group_id']}] 发送群消息: {m['params']['message']}") 450 | m = json.dumps(m) 451 | variable.ws.send(m) 452 | return True, 'async' 453 | else: 454 | if user_id is None: 455 | m = {"action": "send_msg_rate_limit", 456 | "params": {"group_id": group_id, "message": message, "auto_escape": auto_escape}} 457 | logger.info(f"向 [{m['params']['group_id']}] 发送群消息: {m['params']['message']}") 458 | m = json.dumps(m) 459 | variable.ws.send(m) 460 | return True, 'async' 461 | elif group_id is None: 462 | m = {"action": "send_msg_rate_limit", 463 | "params": {"user_id": user_id, "message": message, "auto_escape": auto_escape}} 464 | logger.info(f"向 ({m['params']['user_id']}) 发送私聊消息: {m['params']['message']}") 465 | m = json.dumps(m) 466 | variable.ws.send(m) 467 | return True, 'async' 468 | else: 469 | return False, 'message_type is None' 470 | else: 471 | logger.debug(f'插件权限不足') 472 | return False, 'Permission Denied' 473 | 474 | def delete_msg(self, auth, message_id, timeout=5): 475 | if (not self.check_auth(auth,'recall_msg')) and (not self.check_auth(auth,'delete_msg')): 476 | logger.warning(f'插件权限不足,你需要:recall_msg 权限 或 delete_msg 权限') 477 | return False, 'Permission Denied' 478 | 479 | uid = uuid.uuid4().hex 480 | m = {"action": "delete_msg", "params": {"message_id": message_id}, "echo": uid} 481 | logger.info(f"撤回消息ID: {m['params']['message_id']}") 482 | m = json.dumps(m) 483 | variable.ws.send(m) 484 | ret = self.waitFor(uid, timeout=timeout) 485 | if ret['status'] == 'ok': 486 | return True, 'success' 487 | elif ret['status'] == 'async': 488 | return True, 'async' 489 | elif ret['status'] == 'timeout': 490 | return False, 'timeout' 491 | else: 492 | return False, 'error' 493 | 494 | def delete_msg_async(self, auth, message_id): 495 | if self.check_auth(auth, 'delete_msg'): 496 | m = {"action": "delete_msg_async", "params": {"message_id": message_id}} 497 | logger.info(f"撤回消息ID: {m['params']['message_id']}") 498 | m = json.dumps(m) 499 | variable.ws.send(m) 500 | return True, 'async' 501 | else: 502 | logger.debug(f'插件权限不足') 503 | return False, 'Permission Denied' 504 | 505 | def get_msg(self, auth, message_id, timeout=5): 506 | if self.check_auth(auth, 'get_msg'): 507 | uid = uuid.uuid4().hex 508 | m = {"action": "get_msg", "params": {"message_id": message_id}, "echo": uid} 509 | logger.debug(f"获取消息ID: {m['params']['message_id']}") 510 | m = json.dumps(m) 511 | variable.ws.send(m) 512 | ret = self.waitFor(uid, timeout=timeout) 513 | if ret['status'] == 'ok': 514 | return True, ret['data'] 515 | elif ret['status'] == 'async': 516 | return True, 'async' 517 | elif ret['status'] == 'timeout': 518 | return False, 'timeout' 519 | else: 520 | return False, 'error' 521 | else: 522 | logger.debug(f'插件权限不足') 523 | return False, 'Permission Denied' 524 | 525 | def get_forward_msg(self, auth, id, timeout=5): 526 | if self.check_auth(auth, 'get_forward_msg'): 527 | uid = uuid.uuid4().hex 528 | m = {"action": "get_forward_msg", "params": {"id": id}, "echo": uid} 529 | logger.debug(f"获取合并转发消息ID: {m['params']['id']}") 530 | m = json.dumps(m) 531 | variable.ws.send(m) 532 | ret = self.waitFor(uid, timeout=timeout) 533 | if ret['status'] == 'ok': 534 | return True, ret['data'] 535 | elif ret['status'] == 'async': 536 | return True, 'async' 537 | elif ret['status'] == 'timeout': 538 | return False, 'timeout' 539 | else: 540 | return False, 'error' 541 | else: 542 | logger.debug(f'插件权限不足') 543 | return False, 'Permission Denied' 544 | 545 | def send_like(self, auth, user_id, times, timeout=5): 546 | if self.check_auth(auth, 'send_like'): 547 | if times > 10: 548 | times = 10 549 | if times < 1: 550 | times = 1 551 | uid = uuid.uuid4().hex 552 | m = {"action": "send_like", "params": {"user_id": user_id, "times": times}, "echo": uid} 553 | logger.info(f"向 ({m['params']['user_id']}) 发送赞: {m['params']['times']}") 554 | m = json.dumps(m) 555 | variable.ws.send(m) 556 | ret = self.waitFor(uid, timeout=timeout) 557 | if ret['status'] == 'ok': 558 | return True, 'success' 559 | elif ret['status'] == 'async': 560 | return True, 'async' 561 | elif ret['status'] == 'timeout': 562 | return False, 'timeout' 563 | else: 564 | return False, 'error' 565 | else: 566 | logger.debug(f'插件权限不足') 567 | return False, 'Permission Denied' 568 | 569 | def send_like_async(self, auth, user_id, times): 570 | if self.check_auth(auth, 'send_like'): 571 | if times > 10: 572 | times = 10 573 | if times < 1: 574 | times = 1 575 | m = {"action": "send_like_async", "params": {"user_id": user_id, "times": times}} 576 | logger.info(f"向 ({m['params']['user_id']}) 发送赞: {m['params']['times']}") 577 | m = json.dumps(m) 578 | variable.ws.send(m) 579 | return True, 'async' 580 | else: 581 | logger.debug(f'插件权限不足') 582 | return False, 'Permission Denied' 583 | 584 | def set_group_kick(self, auth, group_id, user_id, reject_add_request=False, timeout=5): 585 | if self.check_auth(auth, 'set_group_kick'): 586 | uid = uuid.uuid4().hex 587 | m = {"action": "set_group_kick", 588 | "params": {"group_id": group_id, "user_id": user_id, "reject_add_request": reject_add_request}, 589 | "echo": uid} 590 | logger.info(f"将 ({m['params']['user_id']}) 移出群 [{m['params']['group_id']}]") 591 | m = json.dumps(m) 592 | variable.ws.send(m) 593 | ret = self.waitFor(uid, timeout=timeout) 594 | if ret['status'] == 'ok': 595 | return True, 'success' 596 | elif ret['status'] == 'async': 597 | return True, 'async' 598 | elif ret['status'] == 'timeout': 599 | return False, 'timeout' 600 | else: 601 | return False, 'error' 602 | else: 603 | logger.debug(f'插件权限不足') 604 | return False, 'Permission Denied' 605 | 606 | def set_group_kick_async(self, auth, group_id, user_id, reject_add_request=False): 607 | if self.check_auth(auth, 'set_group_kick'): 608 | m = {"action": "set_group_kick_async", 609 | "params": {"group_id": group_id, "user_id": user_id, "reject_add_request": reject_add_request}} 610 | logger.info(f"将 ({m['params']['user_id']}) 移出群 [{m['params']['group_id']}]") 611 | m = json.dumps(m) 612 | variable.ws.send(m) 613 | return True, 'async' 614 | else: 615 | logger.debug(f'插件权限不足') 616 | return False, 'Permission Denied' 617 | 618 | def set_group_ban(self, auth, group_id, user_id, duration=30 * 60, timeout=5): 619 | if self.check_auth(auth, 'set_group_ban'): 620 | if duration > 2592000: 621 | duration = 2592000 622 | if duration < 0: 623 | duration = 0 624 | uid = uuid.uuid4().hex 625 | m = {"action": "set_group_ban", "params": {"group_id": group_id, "user_id": user_id, "duration": duration}, 626 | "echo": uid} 627 | logger.info(f"在 [{m['params']['group_id']}] 将 [{m['params']['user_id']}] 禁言 {m['params']['duration']} 秒") 628 | m = json.dumps(m) 629 | variable.ws.send(m) 630 | ret = self.waitFor(uid, timeout=timeout) 631 | if ret['status'] == 'ok': 632 | return True, 'success' 633 | elif ret['status'] == 'async': 634 | return True, 'async' 635 | elif ret['status'] == 'timeout': 636 | return False, 'timeout' 637 | else: 638 | return False, 'error' 639 | else: 640 | logger.debug(f'插件权限不足') 641 | return False, 'Permission Denied' 642 | 643 | def set_group_ban_async(self, auth, group_id, user_id, duration=30 * 60): 644 | if self.check_auth(auth, 'set_group_ban'): 645 | if duration > 2592000: 646 | duration = 2592000 647 | if duration < 0: 648 | duration = 0 649 | m = {"action": "set_group_ban_async", 650 | "params": {"group_id": group_id, "user_id": user_id, "duration": duration}} 651 | logger.info(f"在 [{m['params']['group_id']}] 将 [{m['params']['user_id']}] 禁言 {m['params']['duration']} 秒") 652 | m = json.dumps(m) 653 | variable.ws.send(m) 654 | return True, 'async' 655 | else: 656 | logger.debug(f'插件权限不足') 657 | return False, 'Permission Denied' 658 | 659 | def set_group_anonymous_ban(self, auth, group_id, anonymous, anonymous_flag, duration=30 * 60, timeout=5): 660 | if self.check_auth(auth, 'set_group_anonymous_ban'): 661 | if duration > 2592000: 662 | duration = 2592000 663 | if duration < 0: 664 | duration = 0 665 | if anonymous is None and anonymous_flag is None: 666 | return False, 667 | if anonymous is None: 668 | uid = uuid.uuid4().hex 669 | m = {"action": "set_group_anonymous_ban", 670 | "params": {"group_id": group_id, "anonymous_flag": anonymous_flag, "duration": duration}, 671 | "echo": uid} 672 | logger.info( 673 | f"在 [{m['params']['group_id']}] 将匿名用户 {m['params']['anonymous_flag']} 禁言 {m['params']['duration']} 秒") 674 | m = json.dumps(m) 675 | variable.ws.send(m) 676 | ret = self.waitFor(uid, timeout=timeout) 677 | if ret['status'] == 'ok': 678 | return True, 'success' 679 | elif ret['status'] == 'async': 680 | return True, 'async' 681 | elif ret['status'] == 'timeout': 682 | return False, 'timeout' 683 | else: 684 | return False, 'error' 685 | else: 686 | uid = uuid.uuid4().hex 687 | m = {"action": "set_group_anonymous_ban", 688 | "params": {"group_id": group_id, "anonymous": anonymous, "duration": duration}, "echo": uid} 689 | logger.info( 690 | f"在 [{m['params']['group_id']}] 将匿名用户 ({m['params']['anonymous']['anonymous_flag']}) 禁言 {m['params']['duration']} 秒") 691 | m = json.dumps(m) 692 | variable.ws.send(m) 693 | ret = self.waitFor(uid) 694 | if ret['status'] == 'ok': 695 | return True, 'success' 696 | elif ret['status'] == 'async': 697 | return True, 'async' 698 | else: 699 | return False, 'error' 700 | else: 701 | logger.debug(f'插件权限不足') 702 | return False, 'Permission Denied' 703 | 704 | def set_group_anonymous_ban_async(self, auth, group_id, anonymous, anonymous_flag, duration=30 * 60): 705 | if self.check_auth(auth, 'set_group_anonymous_ban'): 706 | if duration > 2592000: 707 | duration = 2592000 708 | if duration < 0: 709 | duration = 0 710 | if anonymous is None and anonymous_flag is None: 711 | return False, 712 | if anonymous is None: 713 | m = {"action": "set_group_anonymous_ban", 714 | "params": {"group_id": group_id, "anonymous_flag": anonymous_flag, "duration": duration}} 715 | logger.info( 716 | f"在 [{m['params']['group_id']}] 将匿名用户 {m['params']['anonymous_flag']} 禁言 {m['params']['duration']} 秒") 717 | m = json.dumps(m) 718 | variable.ws.send(m) 719 | return True, 'async' 720 | else: 721 | m = {"action": "set_group_anonymous_ban", 722 | "params": {"group_id": group_id, "anonymous": anonymous, "duration": duration}} 723 | logger.info( 724 | f"在 [{m['params']['group_id']}] 将匿名用户 ({m['params']['anonymous']['anonymous_flag']}) 禁言 {m['params']['duration']} 秒") 725 | m = json.dumps(m) 726 | variable.ws.send(m) 727 | return True, 'async' 728 | else: 729 | logger.debug(f'插件权限不足') 730 | return False, 'Permission Denied' 731 | 732 | def set_group_whole_ban(self, auth, group_id, enable=True, timeout=5): 733 | if self.check_auth(auth, 'set_group_whole_ban'): 734 | uid = uuid.uuid4().hex 735 | m = {"action": "set_group_whole_ban", "params": {"group_id": group_id, "enable": enable}, "echo": uid} 736 | logger.info(f"在 [{m['params']['group_id']}] 设置全员禁言: {m['params']['enable']}") 737 | m = json.dumps(m) 738 | variable.ws.send(m) 739 | ret = self.waitFor(uid, timeout=timeout) 740 | if ret['status'] == 'ok': 741 | return True, 'success' 742 | elif ret['status'] == 'async': 743 | return True, 'async' 744 | elif ret['status'] == 'timeout': 745 | return False, 'timeout' 746 | else: 747 | return False, 'error' 748 | else: 749 | logger.debug(f'插件权限不足') 750 | return False, 'Permission Denied' 751 | 752 | def set_group_whole_ban_async(self, auth, group_id, enable=True): 753 | if self.check_auth(auth, 'set_group_whole_ban'): 754 | m = {"action": "set_group_whole_ban_async", "params": {"group_id": group_id, "enable": enable}} 755 | logger.info(f"在 [{m['params']['group_id']}] 设置全员禁言: {m['params']['enable']}") 756 | m = json.dumps(m) 757 | variable.ws.send(m) 758 | return True, 'async' 759 | else: 760 | logger.debug(f'插件权限不足') 761 | return False, 'Permission Denied' 762 | 763 | def set_group_admin(self, auth, group_id, user_id, enable=True, timeout=5): 764 | if self.check_auth(auth, 'set_group_admin'): 765 | uid = uuid.uuid4().hex 766 | m = {"action": "set_group_admin", "params": {"group_id": group_id, "user_id": user_id, "enable": enable}, 767 | "echo": uid} 768 | logger.info(f"在 [{m['params']['group_id']}] 设置管理员 ({m['params']['user_id']}) 状态: {m['params']['enable']}") 769 | m = json.dumps(m) 770 | variable.ws.send(m) 771 | ret = self.waitFor(uid, timeout=timeout) 772 | if ret['status'] == 'ok': 773 | return True, 'success' 774 | elif ret['status'] == 'async': 775 | return True, 'async' 776 | elif ret['status'] == 'timeout': 777 | return False, 'timeout' 778 | else: 779 | return False, 'error' 780 | else: 781 | logger.debug(f'插件权限不足') 782 | return False, 'Permission Denied' 783 | 784 | def set_group_admin_async(self, auth, group_id, user_id, enable=True): 785 | if self.check_auth(auth, 'set_group_admin'): 786 | m = {"action": "set_group_admin_async", 787 | "params": {"group_id": group_id, "user_id": user_id, "enable": enable}} 788 | logger.info(f"在 [{m['params']['group_id']}] 设置管理员 ({m['params']['user_id']}) 状态: {m['params']['enable']}") 789 | m = json.dumps(m) 790 | variable.ws.send(m) 791 | return True, 'async' 792 | else: 793 | logger.debug(f'插件权限不足') 794 | return False, 'Permission Denied' 795 | 796 | def set_group_anonymous(self, auth, group_id, enable=True, timeout=5): 797 | if self.check_auth(auth, 'set_group_anonymous'): 798 | uid = uuid.uuid4().hex 799 | m = {"action": "set_group_anonymous", "params": {"group_id": group_id, "enable": enable}, "echo": uid} 800 | logger.info(f"在 [{m['params']['group_id']}] 设置匿名聊天状态: {m['params']['enable']}") 801 | m = json.dumps(m) 802 | variable.ws.send(m) 803 | ret = self.waitFor(uid, timeout=timeout) 804 | if ret['status'] == 'ok': 805 | return True, 'success' 806 | elif ret['status'] == 'async': 807 | return True, 'async' 808 | elif ret['status'] == 'timeout': 809 | return False, 'timeout' 810 | else: 811 | return False, 'error' 812 | else: 813 | logger.debug(f'插件权限不足') 814 | return False, 'Permission Denied' 815 | 816 | def set_group_anonymous_async(self, auth, group_id, enable=True): 817 | if self.check_auth(auth, 'set_group_anonymous'): 818 | m = {"action": "set_group_anonymous_async", "params": {"group_id": group_id, "enable": enable}} 819 | logger.info(f"在 [{m['params']['group_id']}] 设置匿名聊天状态: {m['params']['enable']}") 820 | m = json.dumps(m) 821 | variable.ws.send(m) 822 | return True, 'async' 823 | else: 824 | logger.debug(f'插件权限不足') 825 | return False, 'Permission Denied' 826 | 827 | def set_group_card(self, auth, group_id, user_id, card=None, timeout=5): 828 | if self.check_auth(auth, 'set_group_card'): 829 | if card is None: 830 | uid = uuid.uuid4().hex 831 | m = {"action": "set_group_card", "params": {"group_id": group_id, "user_id": user_id}, "echo": uid} 832 | logger.info(f"在 [{m['params']['group_id']}] 设置 ({m['params']['user_id']}) 群名片为空") 833 | m = json.dumps(m) 834 | variable.ws.send(m) 835 | ret = self.waitFor(uid, timeout=timeout) 836 | if ret['status'] == 'ok': 837 | return True, 'success' 838 | elif ret['status'] == 'async': 839 | return True, 'async' 840 | elif ret['status'] == 'timeout': 841 | return False, 'timeout' 842 | else: 843 | return False, 'error' 844 | else: 845 | uid = uuid.uuid4().hex 846 | m = {"action": "set_group_card", "params": {"group_id": group_id, "user_id": user_id, "card": card}, 847 | "echo": uid} 848 | logger.info(f"在 [{m['params']['group_id']}] 设置 ({m['params']['user_id']}) 群名片为: {m['params']['card']}") 849 | m = json.dumps(m) 850 | variable.ws.send(m) 851 | ret = self.waitFor(uid, timeout=timeout) 852 | if ret['status'] == 'ok': 853 | return True, 'success' 854 | elif ret['status'] == 'async': 855 | return True, 'async' 856 | elif ret['status'] == 'timeout': 857 | return False, 'timeout' 858 | else: 859 | return False, 'error' 860 | else: 861 | logger.debug(f'插件权限不足') 862 | return False, 'Permission Denied' 863 | 864 | def set_group_card_async(self, auth, group_id, user_id, card=None): 865 | if self.check_auth(auth, 'set_group_card'): 866 | if card is None: 867 | m = {"action": "set_group_card_async", "params": {"group_id": group_id, "user_id": user_id}} 868 | logger.info(f"在 [{m['params']['group_id']}] 设置 ({m['params']['user_id']}) 群名片为空") 869 | m = json.dumps(m) 870 | variable.ws.send(m) 871 | return True, 'async' 872 | else: 873 | m = {"action": "set_group_card_async", 874 | "params": {"group_id": group_id, "user_id": user_id, "card": card}} 875 | logger.info(f"在 [{m['params']['group_id']}] 设置 ({m['params']['user_id']}) 群名片为: {m['params']['card']}") 876 | m = json.dumps(m) 877 | variable.ws.send(m) 878 | return True, 'async' 879 | else: 880 | logger.debug(f'插件权限不足') 881 | return False, 'Permission Denied' 882 | 883 | def set_group_name(self, auth, group_id, group_name, timeout=5): 884 | if self.check_auth(auth, 'set_group_name'): 885 | if group_name == "": 886 | return False, 'group_name is empty' 887 | uid = uuid.uuid4().hex 888 | m = {"action": "set_group_name", "params": {"group_id": group_id, "group_name": group_name}, "echo": uid} 889 | logger.info(f"在 [{m['params']['group_id']}] 设置群名为: {m['params']['group_name']}") 890 | m = json.dumps(m) 891 | variable.ws.send(m) 892 | ret = self.waitFor(uid, timeout=timeout) 893 | if ret['status'] == 'ok': 894 | return True, 'success' 895 | elif ret['status'] == 'async': 896 | return True, 'async' 897 | elif ret['status'] == 'timeout': 898 | return False, 'timeout' 899 | else: 900 | return False, 'error' 901 | else: 902 | logger.debug(f'插件权限不足') 903 | return False, 'Permission Denied' 904 | 905 | def set_group_name_async(self, auth, group_id, group_name): 906 | if self.check_auth(auth, 'set_group_name'): 907 | if group_name == "": 908 | return False, 'group_name is empty' 909 | m = {"action": "set_group_name_async", "params": {"group_id": group_id, "group_name": group_name}} 910 | logger.info(f"在 [{m['params']['group_id']}] 设置群名为: {m['params']['group_name']}") 911 | m = json.dumps(m) 912 | variable.ws.send(m) 913 | return True, 'async' 914 | else: 915 | logger.debug(f'插件权限不足') 916 | return False, 'Permission Denied' 917 | 918 | def set_group_leave(self, auth, group_id, is_dismiss=False, timeout=5): 919 | if self.check_auth(auth, 'set_group_leave'): 920 | uid = uuid.uuid4().hex 921 | m = {"action": "set_group_leave", "params": {"group_id": group_id, "is_dismiss": is_dismiss}, "echo": uid} 922 | logger.info(f"退出群 [{m['params']['group_id']}]") 923 | m = json.dumps(m) 924 | variable.ws.send(m) 925 | ret = self.waitFor(uid, timeout=timeout) 926 | if ret['status'] == 'ok': 927 | return True, 'success' 928 | elif ret['status'] == 'async': 929 | return True, 'async' 930 | elif ret['status'] == 'timeout': 931 | return False, 'timeout' 932 | else: 933 | return False, 'error' 934 | else: 935 | logger.debug(f'插件权限不足') 936 | return False, 'Permission Denied' 937 | 938 | def set_group_leave_async(self, auth, group_id, is_dismiss=False): 939 | if self.check_auth(auth, 'set_group_leave'): 940 | m = {"action": "set_group_leave_async", "params": {"group_id": group_id, "is_dismiss": is_dismiss}} 941 | logger.info(f"退出群 [{m['params']['group_id']}]") 942 | m = json.dumps(m) 943 | variable.ws.send(m) 944 | return True, 'async' 945 | else: 946 | logger.debug(f'插件权限不足') 947 | return False, 'Permission Denied' 948 | 949 | def set_group_special_title(self, auth, group_id, user_id, special_title=None, duration=-1, timeout=5): 950 | if self.check_auth(auth, 'set_group_special_title'): 951 | if special_title is None: 952 | uid = uuid.uuid4().hex 953 | m = {"action": "set_group_special_title", "params": {"group_id": group_id, "user_id": user_id}, 954 | "echo": uid} 955 | logger.info(f"在 [{m['params']['group_id']}] 设置 ({m['params']['user_id']}) 群头衔为空") 956 | m = json.dumps(m) 957 | variable.ws.send(m) 958 | ret = self.waitFor(uid, timeout=timeout) 959 | if ret['status'] == 'ok': 960 | return True, 'success' 961 | elif ret['status'] == 'async': 962 | return True, 'async' 963 | elif ret['status'] == 'timeout': 964 | return False, 'timeout' 965 | else: 966 | return False, 'error' 967 | else: 968 | uid = uuid.uuid4().hex 969 | m = {"action": "set_group_special_title", 970 | "params": {"group_id": group_id, "user_id": user_id, "special_title": special_title, 971 | "duration": duration}, "echo": uid} 972 | logger.info( 973 | f"在 [{m['params']['group_id']}] 设置 ({m['params']['user_id']}) 群头衔为: {m['params']['special_title']}") 974 | m = json.dumps(m) 975 | variable.ws.send(m) 976 | ret = self.waitFor(uid, timeout=timeout) 977 | if ret['status'] == 'ok': 978 | return True, 'success' 979 | elif ret['status'] == 'async': 980 | return True, 'async' 981 | elif ret['status'] == 'timeout': 982 | return False, 'timeout' 983 | else: 984 | return False, 'error' 985 | else: 986 | logger.debug(f'插件权限不足') 987 | return False, 'Permission Denied' 988 | 989 | def set_group_special_title_async(self, auth, group_id, user_id, special_title=None, duration=-1): 990 | if self.check_auth(auth, 'set_group_special_title'): 991 | if special_title is None: 992 | m = {"action": "set_group_special_title_async", "params": {"group_id": group_id, "user_id": user_id}} 993 | logger.info(f"在 [{m['params']['group_id']}] 设置 ({m['params']['user_id']}) 群头衔为空") 994 | m = json.dumps(m) 995 | variable.ws.send(m) 996 | return True, 'async' 997 | else: 998 | m = {"action": "set_group_special_title_async", 999 | "params": {"group_id": group_id, "user_id": user_id, "special_title": special_title, 1000 | "duration": duration}} 1001 | logger.info( 1002 | f"在 [{m['params']['group_id']}] 设置 ({m['params']['user_id']}) 群头衔为: {m['params']['special_title']}") 1003 | m = json.dumps(m) 1004 | variable.ws.send(m) 1005 | return True, 'async' 1006 | else: 1007 | logger.debug(f'插件权限不足') 1008 | return False, 'Permission Denied' 1009 | 1010 | def set_friend_add_request(self, auth, flag, approve=True, remark=None, timeout=5): 1011 | if self.check_auth(auth, 'set_friend_add_request'): 1012 | if remark is None: 1013 | uid = uuid.uuid4().hex 1014 | m = {"action": "set_friend_add_request", "params": {"flag": flag, "approve": approve}, "echo": uid} 1015 | logger.info(f"处理好友请求: {m['params']['flag']} {m['params']['approve']}") 1016 | m = json.dumps(m) 1017 | variable.ws.send(m) 1018 | ret = self.waitFor(uid, timeout=timeout) 1019 | if ret['status'] == 'ok': 1020 | return True, 'success' 1021 | elif ret['status'] == 'async': 1022 | return True, 'async' 1023 | elif ret['status'] == 'timeout': 1024 | return False, 'timeout' 1025 | else: 1026 | return False, 'error' 1027 | else: 1028 | uid = uuid.uuid4().hex 1029 | m = {"action": "set_friend_add_request", "params": {"flag": flag, "approve": approve, "remark": remark}, 1030 | "echo": uid} 1031 | logger.info(f"处理好友请求: {m['params']['flag']} {m['params']['approve']} {m['params']['remark']}") 1032 | m = json.dumps(m) 1033 | variable.ws.send(m) 1034 | ret = self.waitFor(uid, timeout=timeout) 1035 | if ret['status'] == 'ok': 1036 | return True, 'success' 1037 | elif ret['status'] == 'async': 1038 | return True, 'async' 1039 | elif ret['status'] == 'timeout': 1040 | return False, 'timeout' 1041 | else: 1042 | return False, 'error' 1043 | else: 1044 | logger.debug(f'插件权限不足') 1045 | return False, 'Permission Denied' 1046 | 1047 | def set_friend_add_request_async(self, auth, flag, approve=True, remark=None): 1048 | if self.check_auth(auth, 'set_friend_add_request'): 1049 | if remark is None: 1050 | m = {"action": "set_friend_add_request_async", "params": {"flag": flag, "approve": approve}} 1051 | logger.info(f"处理好友请求: {m['params']['flag']} {m['params']['approve']}") 1052 | m = json.dumps(m) 1053 | variable.ws.send(m) 1054 | return True, 'async' 1055 | else: 1056 | m = {"action": "set_friend_add_request_async", 1057 | "params": {"flag": flag, "approve": approve, "remark": remark}} 1058 | logger.info(f"处理好友请求: {m['params']['flag']} {m['params']['approve']} {m['params']['remark']}") 1059 | m = json.dumps(m) 1060 | variable.ws.send(m) 1061 | return True, 'async' 1062 | else: 1063 | logger.debug(f'插件权限不足') 1064 | return False, 'Permission Denied' 1065 | 1066 | def set_group_add_request(self, auth, flag, sub_type, approve=True, reason=None, timeout=5): 1067 | if self.check_auth(auth, 'set_group_add_request'): 1068 | if reason is None: 1069 | uid = uuid.uuid4().hex 1070 | m = {"action": "set_group_add_request", 1071 | "params": {"flag": flag, "sub_type": sub_type, "approve": approve}, "echo": uid} 1072 | logger.info(f"处理加群请求: {m['params']['flag']} {m['params']['sub_type']} {m['params']['approve']}") 1073 | m = json.dumps(m) 1074 | variable.ws.send(m) 1075 | ret = self.waitFor(uid, timeout=timeout) 1076 | if ret['status'] == 'ok': 1077 | return True, 'success' 1078 | elif ret['status'] == 'async': 1079 | return True, 'async' 1080 | elif ret['status'] == 'timeout': 1081 | return False, 'timeout' 1082 | else: 1083 | return False, 'error' 1084 | else: 1085 | uid = uuid.uuid4().hex 1086 | m = {"action": "set_group_add_request", 1087 | "params": {"flag": flag, "sub_type": sub_type, "approve": approve, "reason": reason}, "echo": uid} 1088 | logger.info( 1089 | f"处理加群请求: {m['params']['flag']} {m['params']['sub_type']} {m['params']['approve']} {m['params']['reason']}") 1090 | m = json.dumps(m) 1091 | variable.ws.send(m) 1092 | ret = self.waitFor(uid, timeout=timeout) 1093 | if ret['status'] == 'ok': 1094 | return True, 'success' 1095 | elif ret['status'] == 'async': 1096 | return True, 'async' 1097 | elif ret['status'] == 'timeout': 1098 | return False, 'timeout' 1099 | else: 1100 | return False, 'error' 1101 | else: 1102 | logger.debug(f'插件权限不足') 1103 | return False, 'Permission Denied' 1104 | 1105 | def set_group_add_request_async(self, auth, flag, sub_type, approve=True, reason=None): 1106 | if self.check_auth(auth, 'set_group_add_request'): 1107 | if reason is None: 1108 | m = {"action": "set_group_add_request_async", 1109 | "params": {"flag": flag, "sub_type": sub_type, "approve": approve}} 1110 | logger.info(f"处理加群请求: {m['params']['flag']} {m['params']['sub_type']} {m['params']['approve']}") 1111 | m = json.dumps(m) 1112 | variable.ws.send(m) 1113 | return True, 'async' 1114 | else: 1115 | m = {"action": "set_group_add_request_async", 1116 | "params": {"flag": flag, "sub_type": sub_type, "approve": approve, "reason": reason}} 1117 | logger.info( 1118 | f"处理加群请求: {m['params']['flag']} {m['params']['sub_type']} {m['params']['approve']} {m['params']['reason']}") 1119 | m = json.dumps(m) 1120 | variable.ws.send(m) 1121 | return True, 'async' 1122 | else: 1123 | logger.debug(f'插件权限不足') 1124 | return False, 'Permission Denied' 1125 | 1126 | def get_login_info(self, auth, timeout=5): 1127 | if self.check_auth(auth, 'get_login_info'): 1128 | uid = uuid.uuid4().hex 1129 | m = {"action": "get_login_info", "echo": uid} 1130 | m = json.dumps(m) 1131 | variable.ws.send(m) 1132 | ret = self.waitFor(uid, timeout=timeout) 1133 | if ret['status'] == 'ok': 1134 | return True, ret['data'] 1135 | elif ret['status'] == 'async': 1136 | return True, 'async' 1137 | elif ret['status'] == 'timeout': 1138 | return False, 'timeout' 1139 | else: 1140 | return False, 'error' 1141 | else: 1142 | logger.debug(f'插件权限不足') 1143 | return False, 'Permission Denied' 1144 | 1145 | def get_stranger_info(self, auth, user_id, timeout=5): 1146 | if self.check_auth(auth, 'get_stranger_info'): 1147 | uid = uuid.uuid4().hex 1148 | m = {"action": "get_stranger_info", "params": {"user_id": user_id}, "echo": uid} 1149 | m = json.dumps(m) 1150 | variable.ws.send(m) 1151 | ret = self.waitFor(uid, timeout=timeout) 1152 | if ret['status'] == 'ok': 1153 | return True, ret['data'] 1154 | elif ret['status'] == 'async': 1155 | return True, 'async' 1156 | elif ret['status'] == 'timeout': 1157 | return False, 'timeout' 1158 | else: 1159 | return False, 'error' 1160 | else: 1161 | logger.debug(f'插件权限不足') 1162 | return False, 'Permission Denied' 1163 | 1164 | def get_friend_list(self, auth, timeout=5): 1165 | if self.check_auth(auth, 'get_friend_list'): 1166 | uid = uuid.uuid4().hex 1167 | m = {"action": "get_friend_list", "echo": uid} 1168 | m = json.dumps(m) 1169 | variable.ws.send(m) 1170 | ret = self.waitFor(uid, timeout=timeout) 1171 | if ret['status'] == 'ok': 1172 | return True, ret['data'] 1173 | elif ret['status'] == 'async': 1174 | return True, 'async' 1175 | elif ret['status'] == 'timeout': 1176 | return False, 'timeout' 1177 | else: 1178 | return False, 'error' 1179 | else: 1180 | logger.debug(f'插件权限不足') 1181 | return False, 'Permission Denied' 1182 | 1183 | def get_group_info(self, auth, group_id, no_cache=False, timeout=5): 1184 | if self.check_auth(auth, 'get_group_info'): 1185 | uid = uuid.uuid4().hex 1186 | m = {"action": "get_group_info", "params": {"group_id": group_id, "no_cache": no_cache}, "echo": uid} 1187 | m = json.dumps(m) 1188 | variable.ws.send(m) 1189 | ret = self.waitFor(uid, timeout=timeout) 1190 | if ret['status'] == 'ok': 1191 | return True, ret['data'] 1192 | elif ret['status'] == 'async': 1193 | return True, 'async' 1194 | elif ret['status'] == 'timeout': 1195 | return False, 'timeout' 1196 | else: 1197 | return False, 'error' 1198 | else: 1199 | logger.debug(f'插件权限不足') 1200 | return False, 'Permission Denied' 1201 | 1202 | def get_group_list(self, auth, timeout=5): 1203 | if self.check_auth(auth, 'get_group_list'): 1204 | uid = uuid.uuid4().hex 1205 | m = {"action": "get_group_list", "echo": uid} 1206 | m = json.dumps(m) 1207 | variable.ws.send(m) 1208 | ret = self.waitFor(uid, timeout=timeout) 1209 | if ret['status'] == 'ok': 1210 | return True, ret['data'] 1211 | elif ret['status'] == 'async': 1212 | return True, 'async' 1213 | elif ret['status'] == 'timeout': 1214 | return False, 'timeout' 1215 | else: 1216 | return False, 'error' 1217 | else: 1218 | logger.debug(f'插件权限不足') 1219 | return False, 'Permission Denied' 1220 | 1221 | def get_group_member_info(self, auth, group_id, user_id, no_cache=False, timeout=5): 1222 | if self.check_auth(auth, 'get_group_member_info'): 1223 | uid = uuid.uuid4().hex 1224 | m = {"action": "get_group_member_info", 1225 | "params": {"group_id": group_id, "user_id": user_id, "no_cache": no_cache}, "echo": uid} 1226 | m = json.dumps(m) 1227 | variable.ws.send(m) 1228 | ret = self.waitFor(uid, timeout=timeout) 1229 | if ret['status'] == 'ok': 1230 | return True, ret['data'] 1231 | elif ret['status'] == 'async': 1232 | return True, 'async' 1233 | elif ret['status'] == 'timeout': 1234 | return False, 'timeout' 1235 | else: 1236 | return False, 'error' 1237 | else: 1238 | logger.debug(f'插件权限不足') 1239 | return False, 'Permission Denied' 1240 | 1241 | def get_group_member_list(self, auth, group_id, timeout=5): 1242 | if self.check_auth(auth, 'get_group_member_list'): 1243 | uid = uuid.uuid4().hex 1244 | m = {"action": "get_group_member_list", "params": {"group_id": group_id}, "echo": uid} 1245 | m = json.dumps(m) 1246 | variable.ws.send(m) 1247 | ret = self.waitFor(uid, timeout=timeout) 1248 | if ret['status'] == 'ok': 1249 | return True, ret['data'] 1250 | elif ret['status'] == 'async': 1251 | return True, 'async' 1252 | elif ret['status'] == 'timeout': 1253 | return False, 'timeout' 1254 | else: 1255 | return False, 'error' 1256 | else: 1257 | logger.debug(f'插件权限不足') 1258 | return False, 'Permission Denied' 1259 | 1260 | def get_group_honor_info(self, auth, group_id, type, timeout=5): 1261 | if self.check_auth(auth, 'get_group_honor_info'): 1262 | uid = uuid.uuid4().hex 1263 | m = {"action": "get_group_honor_info", "params": {"group_id": group_id, "type": type}, "echo": uid} 1264 | m = json.dumps(m) 1265 | variable.ws.send(m) 1266 | ret = self.waitFor(uid, timeout=timeout) 1267 | if ret['status'] == 'ok': 1268 | return True, ret['data'] 1269 | elif ret['status'] == 'async': 1270 | return True, 'async' 1271 | elif ret['status'] == 'timeout': 1272 | return False, 'timeout' 1273 | else: 1274 | return False, 'error' 1275 | else: 1276 | logger.debug(f'插件权限不足') 1277 | return False, 'Permission Denied' 1278 | 1279 | def get_cookies(self, auth, domain=None, timeout=5): 1280 | if self.check_auth(auth, 'get_cookies'): 1281 | if domain is None: 1282 | uid = uuid.uuid4().hex 1283 | m = {"action": "get_cookies", "echo": uid} 1284 | m = json.dumps(m) 1285 | variable.ws.send(m) 1286 | ret = self.waitFor(uid, timeout=timeout) 1287 | if ret['status'] == 'ok': 1288 | return True, ret['data'] 1289 | elif ret['status'] == 'async': 1290 | return True, 'async' 1291 | elif ret['status'] == 'timeout': 1292 | return False, 'timeout' 1293 | else: 1294 | return False, 'error' 1295 | else: 1296 | uid = uuid.uuid4().hex 1297 | m = {"action": "get_cookies", "params": {"domain": domain}, "echo": uid} 1298 | m = json.dumps(m) 1299 | variable.ws.send(m) 1300 | ret = self.waitFor(uid, timeout=timeout) 1301 | if ret['status'] == 'ok': 1302 | return True, ret['data'] 1303 | elif ret['status'] == 'async': 1304 | return True, 'async' 1305 | elif ret['status'] == 'timeout': 1306 | return False, 'timeout' 1307 | else: 1308 | return False, 'error' 1309 | else: 1310 | logger.debug(f'插件权限不足') 1311 | return False, 'Permission Denied' 1312 | 1313 | def get_csrf_token(self, auth, timeout=5): 1314 | if self.check_auth(auth, 'get_csrf_token'): 1315 | uid = uuid.uuid4().hex 1316 | m = {"action": "get_csrf_token", "echo": uid} 1317 | m = json.dumps(m) 1318 | variable.ws.send(m) 1319 | ret = self.waitFor(uid, timeout=timeout) 1320 | if ret['status'] == 'ok': 1321 | return True, ret['data'] 1322 | elif ret['status'] == 'async': 1323 | return True, 'async' 1324 | elif ret['status'] == 'timeout': 1325 | return False, 'timeout' 1326 | else: 1327 | return False, 'error' 1328 | else: 1329 | logger.debug(f'插件权限不足') 1330 | return False, 'Permission Denied' 1331 | 1332 | def get_credentials(self, auth, domain=None, timeout=5): 1333 | if self.check_auth(auth, 'get_credentials'): 1334 | if domain is None: 1335 | uid = uuid.uuid4().hex 1336 | m = {"action": "get_credentials", "echo": uid} 1337 | m = json.dumps(m) 1338 | variable.ws.send(m) 1339 | ret = self.waitFor(uid, timeout=timeout) 1340 | if ret['status'] == 'ok': 1341 | return True, ret['data'] 1342 | elif ret['status'] == 'async': 1343 | return True, 'async' 1344 | elif ret['status'] == 'timeout': 1345 | return False, 'timeout' 1346 | else: 1347 | return False, 'error' 1348 | else: 1349 | uid = uuid.uuid4().hex 1350 | m = {"action": "get_credentials", "params": {"domain": domain}, "echo": uid} 1351 | m = json.dumps(m) 1352 | variable.ws.send(m) 1353 | ret = self.waitFor(uid, timeout=timeout) 1354 | if ret['status'] == 'ok': 1355 | return True, ret['data'] 1356 | elif ret['status'] == 'async': 1357 | return True, 'async' 1358 | elif ret['status'] == 'timeout': 1359 | return False, 'timeout' 1360 | else: 1361 | return False, 'error' 1362 | else: 1363 | logger.debug(f'插件权限不足') 1364 | return False, 'Permission Denied' 1365 | 1366 | def get_record(self, auth, file, out_format, timeout=5): 1367 | if self.check_auth(auth, 'get_record'): 1368 | uid = uuid.uuid4().hex 1369 | m = {"action": "get_record", "params": {"file": file, "out_format": out_format}, "echo": uid} 1370 | m = json.dumps(m) 1371 | variable.ws.send(m) 1372 | ret = self.waitFor(uid, timeout=timeout) 1373 | if ret['status'] == 'ok': 1374 | return True, ret['data'] 1375 | elif ret['status'] == 'async': 1376 | return True, 'async' 1377 | elif ret['status'] == 'timeout': 1378 | return False, 'timeout' 1379 | else: 1380 | return False, 'error' 1381 | else: 1382 | logger.debug(f'插件权限不足') 1383 | return False, 'Permission Denied' 1384 | 1385 | def get_image(self, auth, file, timeout=5): 1386 | if self.check_auth(auth, 'get_image'): 1387 | uid = uuid.uuid4().hex 1388 | m = {"action": "get_image", "params": {"file": file}, "echo": uid} 1389 | m = json.dumps(m) 1390 | variable.ws.send(m) 1391 | ret = self.waitFor(uid, timeout=timeout) 1392 | if ret['status'] == 'ok': 1393 | return True, ret['data'] 1394 | elif ret['status'] == 'async': 1395 | return True, 'async' 1396 | elif ret['status'] == 'timeout': 1397 | return False, 'timeout' 1398 | else: 1399 | return False, 'error' 1400 | else: 1401 | logger.debug(f'插件权限不足') 1402 | return False, 'Permission Denied' 1403 | 1404 | def can_send_image(self, timeout=5): 1405 | uid = uuid.uuid4().hex 1406 | m = {"action": "can_send_image", "echo": uid} 1407 | m = json.dumps(m) 1408 | variable.ws.send(m) 1409 | ret = self.waitFor(uid, timeout=timeout) 1410 | if ret['status'] == 'ok': 1411 | return True, ret['data'] 1412 | elif ret['status'] == 'async': 1413 | return True, 'async' 1414 | elif ret['status'] == 'timeout': 1415 | return False, 'timeout' 1416 | else: 1417 | return False, 'error' 1418 | 1419 | def can_send_record(self, timeout=5): 1420 | uid = uuid.uuid4().hex 1421 | m = {"action": "can_send_record", "echo": uid} 1422 | m = json.dumps(m) 1423 | variable.ws.send(m) 1424 | ret = self.waitFor(uid, timeout=timeout) 1425 | if ret['status'] == 'ok': 1426 | return True, ret['data'] 1427 | elif ret['status'] == 'async': 1428 | return True, 'async' 1429 | elif ret['status'] == 'timeout': 1430 | return False, 'timeout' 1431 | else: 1432 | return False, 'error' 1433 | 1434 | def get_status(self, auth, timeout=5): 1435 | if self.check_auth(auth, 'get_status'): 1436 | uid = uuid.uuid4().hex 1437 | m = {"action": "get_status", "echo": uid} 1438 | m = json.dumps(m) 1439 | variable.ws.send(m) 1440 | ret = self.waitFor(uid, timeout=timeout) 1441 | if ret['status'] == 'ok': 1442 | return True, ret['data'] 1443 | elif ret['status'] == 'async': 1444 | return True, 'async' 1445 | elif ret['status'] == 'timeout': 1446 | return False, 'timeout' 1447 | else: 1448 | return False, 'error' 1449 | else: 1450 | logger.debug(f'插件权限不足') 1451 | return False, 'Permission Denied' 1452 | 1453 | def get_version_info(self, auth, timeout=5): 1454 | if self.check_auth(auth, 'get_version_info'): 1455 | uid = uuid.uuid4().hex 1456 | m = {"action": "get_version_info", "echo": uid} 1457 | m = json.dumps(m) 1458 | variable.ws.send(m) 1459 | ret = self.waitFor(uid, timeout=timeout) 1460 | if ret['status'] == 'ok': 1461 | return True, ret['data'] 1462 | elif ret['status'] == 'async': 1463 | return True, 'async' 1464 | elif ret['status'] == 'timeout': 1465 | return False, 'timeout' 1466 | else: 1467 | return False, 'error' 1468 | else: 1469 | logger.debug(f'插件权限不足') 1470 | return False, 'Permission Denied' 1471 | 1472 | def set_restart(self, auth, delay=0, timeout=5): 1473 | if self.check_auth(auth, 'set_restart'): 1474 | uid = uuid.uuid4().hex 1475 | m = {"action": "set_restart", "params": {"delay": delay}, "echo": uid} 1476 | m = json.dumps(m) 1477 | variable.ws.send(m) 1478 | ret = self.waitFor(uid, timeout=timeout) 1479 | if ret['status'] == 'ok': 1480 | return True, 'success' 1481 | elif ret['status'] == 'async': 1482 | return True, 'async' 1483 | elif ret['status'] == 'timeout': 1484 | return False, 'timeout' 1485 | else: 1486 | return False, 'error' 1487 | else: 1488 | logger.debug(f'插件权限不足') 1489 | return False, 'Permission Denied' 1490 | 1491 | def set_restart_async(self, auth, delay=0): 1492 | if self.check_auth(auth, 'set_restart'): 1493 | m = {"action": "set_restart_async", "params": {"delay": delay}} 1494 | m = json.dumps(m) 1495 | variable.ws.send(m) 1496 | return True, 'async' 1497 | else: 1498 | logger.debug(f'插件权限不足') 1499 | return False, 'Permission Denied' 1500 | 1501 | def clear_cache(self, auth, timeout=5): 1502 | if self.check_auth(auth, 'clear_cache'): 1503 | uid = uuid.uuid4().hex 1504 | m = {"action": "clear_cache", "echo": uid} 1505 | m = json.dumps(m) 1506 | variable.ws.send(m) 1507 | ret = self.waitFor(uid, timeout=timeout) 1508 | if ret['status'] == 'ok': 1509 | return True, 'success' 1510 | elif ret['status'] == 'async': 1511 | return True, 'async' 1512 | elif ret['status'] == 'timeout': 1513 | return False, 'timeout' 1514 | else: 1515 | return False, 'error' 1516 | else: 1517 | logger.debug(f'插件权限不足') 1518 | return False, 'Permission Denied' 1519 | 1520 | def clear_cache_async(self, auth): 1521 | if self.check_auth(auth, 'clear_cache'): 1522 | m = {"action": "clear_cache"} 1523 | m = json.dumps(m) 1524 | variable.ws.send(m) 1525 | return True, 'async' 1526 | else: 1527 | logger.debug(f'插件权限不足') 1528 | return False, 'Permission Denied' 1529 | 1530 | def plugin_control(self, auth, action, plugin): 1531 | if self.check_auth(auth, 'plugin_control'): 1532 | if action not in ['enable', 'disable', 'reload', 'load', 'unload', 'register', 'unregister']: 1533 | return False, 'action is invalid' 1534 | if action == "get_list": 1535 | try: 1536 | plugin_list = variable.loader.get_plugin_list() 1537 | return True, plugin_list 1538 | except Exception as e: 1539 | return False, str(e) 1540 | if plugin == "": 1541 | return False, 'plugin is empty' 1542 | if action == "load": 1543 | try: 1544 | variable.loader.load_plugin(plugin) 1545 | return True, 'success' 1546 | except Exception as e: 1547 | return False, str(e) 1548 | elif action == "unload": 1549 | try: 1550 | variable.loader.unload_plugin(plugin) 1551 | return True, 'success' 1552 | except Exception as e: 1553 | return False, str(e) 1554 | elif action == "reload": 1555 | try: 1556 | variable.loader.reload_plugin(plugin) 1557 | return True, 'success' 1558 | except Exception as e: 1559 | return False, str(e) 1560 | elif action == "register": 1561 | try: 1562 | variable.loader.register_plugin(plugin) 1563 | return True, 'success' 1564 | except Exception as e: 1565 | return False, str(e) 1566 | elif action == "unregister": 1567 | try: 1568 | variable.loader.unregister_plugin(plugin) 1569 | return True, 'success' 1570 | except Exception as e: 1571 | return False, str(e) 1572 | elif action == "enable": 1573 | try: 1574 | variable.loader.enable_plugin(plugin) 1575 | return True, 'success' 1576 | except Exception as e: 1577 | return False, str(e) 1578 | elif action == "disable": 1579 | try: 1580 | variable.loader.disable_plugin(plugin) 1581 | return True, 'success' 1582 | except Exception as e: 1583 | return False, str(e) 1584 | else: 1585 | return False, 'action is invalid' 1586 | else: 1587 | logger.debug(f'插件权限不足') 1588 | return False, 'Permission Denied' 1589 | 1590 | def send_ws_msg(self, auth, message): 1591 | if self.check_auth(auth, 'send_ws_msg'): 1592 | m = json.dumps(message) 1593 | variable.ws.send(m) 1594 | return True, 'success' 1595 | else: 1596 | logger.debug(f'插件权限不足') 1597 | return False, 'Permission Denied' 1598 | 1599 | def get_ws_msg(self, auth, echo): 1600 | if self.check_auth(auth, 'get_ws_msg'): 1601 | if echo in self.retmsg: 1602 | msg = self.retmsg[echo] 1603 | del self.retmsg[echo] 1604 | return True, msg 1605 | else: 1606 | return False, 'echo not found' 1607 | else: 1608 | logger.debug(f'插件权限不足') 1609 | return False, 'Permission Denied' 1610 | 1611 | def waitFor(self, uid, timeout=5): 1612 | start = time.time() 1613 | while True: 1614 | if time.time() - start > timeout: 1615 | return {'status': 'timeout'} 1616 | if uid in self.retmsg: 1617 | msg = self.retmsg[uid] 1618 | del self.retmsg[uid] 1619 | return msg 1620 | time.sleep(0.01) 1621 | 1622 | def put_retmsg(self, msg): 1623 | self.retmsg[msg['echo']] = msg 1624 | 1625 | def escape(self, s): 1626 | return s.replace('&', '&').replace('[', '[').replace(']', ']').replace(',', ',').replace('\n', 1627 | '\\n') 1628 | 1629 | def unescape(self, s): 1630 | return s.replace('&', '&').replace('[', '[').replace(']', ']').replace(',', ',').replace('\\n', 1631 | '\n') 1632 | 1633 | def seg_text(self, text): 1634 | return [{'type': 'text', 'data': {'text': text}}] 1635 | 1636 | def cq_text(self, text): 1637 | return text 1638 | 1639 | def seg_face(self, id): 1640 | return [{'type': 'face', 'data': {'id': id}}] 1641 | 1642 | def cq_face(self, id): 1643 | return f'[CQ:face,id={id}]' 1644 | 1645 | def seg_image(self, file, type, cache=1, proxy=1, timeout=None): 1646 | if file == "": 1647 | return [{'type': 'text', 'data': {'text': '[图片]'}}] 1648 | else: 1649 | if file.startswith('http'): 1650 | if type == "flash": 1651 | if timeout is None: 1652 | return [ 1653 | {'type': 'image', 'data': {'file': file, 'type': 'flash', 'cache': cache, 'proxy': proxy}}] 1654 | else: 1655 | return [{'type': 'image', 1656 | 'data': {'file': file, 'type': 'flash', 'cache': cache, 'proxy': proxy, 1657 | 'timeout': timeout}}] 1658 | else: 1659 | if timeout is None: 1660 | return [{'type': 'image', 'data': {'file': file, 'cache': cache, 'proxy': proxy}}] 1661 | else: 1662 | return [{'type': 'image', 1663 | 'data': {'file': file, 'cache': cache, 'proxy': proxy, 'timeout': timeout}}] 1664 | else: 1665 | if type == "flash": 1666 | return [{'type': 'image', 'data': {'file': file, 'type': 'flash'}}] 1667 | else: 1668 | return [{'type': 'image', 'data': {'file': file}}] 1669 | 1670 | def cq_image(self, file, type="", cache=1, proxy=1, timeout=None): 1671 | if file == "": 1672 | return '[图片]' 1673 | else: 1674 | if file.startswith('http'): 1675 | if type == "flash": 1676 | return f'[CQ:image,file={file},type=flash,cache={cache},proxy={proxy}]' 1677 | else: 1678 | return f'[CQ:image,file={file},cache={cache},proxy={proxy}]' 1679 | else: 1680 | return f'[CQ:image,file={file}]' 1681 | 1682 | def seg_record(self, file, magic=0, cache=1, proxy=1, timeout=None): 1683 | if file == "": 1684 | return [{'type': 'text', 'data': {'text': '[语音]'}}] 1685 | else: 1686 | if file.startswith('http'): 1687 | if timeout is None: 1688 | return [{'type': 'record', 'data': {'file': file, 'magic': magic, 'cache': cache, 'proxy': proxy}}] 1689 | else: 1690 | return [{'type': 'record', 'data': {'file': file, 'magic': magic, 'cache': cache, 'proxy': proxy, 1691 | 'timeout': timeout}}] 1692 | else: 1693 | return [{'type': 'record', 'data': {'file': file, 'magic': magic}}] 1694 | 1695 | def cq_record(self, file, magic=0, cache=1, proxy=1, timeout=None): 1696 | if file == "": 1697 | return '[语音]' 1698 | else: 1699 | if file.startswith('http'): 1700 | return f'[CQ:record,file={file},magic={magic},cache={cache},proxy={proxy}]' 1701 | else: 1702 | return f'[CQ:record,file={file},magic={magic}]' 1703 | 1704 | def seg_video(self, file, cache=1, proxy=1, timeout=None): 1705 | if file == "": 1706 | return [{'type': 'text', 'data': {'text': '[视频]'}}] 1707 | else: 1708 | if file.startswith('http'): 1709 | if timeout is None: 1710 | return [{'type': 'video', 'data': {'file': file, 'cache': cache, 'proxy': proxy}}] 1711 | else: 1712 | return [ 1713 | {'type': 'video', 'data': {'file': file, 'cache': cache, 'proxy': proxy, 'timeout': timeout}}] 1714 | else: 1715 | return [{'type': 'video', 'data': {'file': file}}] 1716 | 1717 | def cq_video(self, file, cache=1, proxy=1, timeout=None): 1718 | if file == "": 1719 | return '[视频]' 1720 | else: 1721 | if file.startswith('http'): 1722 | return f'[CQ:video,file={file},cache={cache},proxy={proxy}]' 1723 | else: 1724 | return f'[CQ:video,file={file}]' 1725 | 1726 | def seg_at(self, qq): 1727 | return [{'type': 'at', 'data': {'qq': qq}}] 1728 | 1729 | def cq_at(self, qq): 1730 | return f'[CQ:at,qq={qq}]' 1731 | 1732 | def seg_rps(self): 1733 | return [{'type': 'rps', 'data': {}}] 1734 | 1735 | def cq_rps(self): 1736 | return '[CQ:rps]' 1737 | 1738 | def seg_dice(self): 1739 | return [{'type': 'dice', 'data': {}}] 1740 | 1741 | def cq_dice(self): 1742 | return '[CQ:dice]' 1743 | 1744 | def seg_shake(self): 1745 | return [{'type': 'shake', 'data': {}}] 1746 | 1747 | def cq_shake(self): 1748 | return '[CQ:shake]' 1749 | 1750 | def seg_poke(self, type, id): 1751 | return [{'type': 'poke', 'data': {'type': type, 'id': id}}] 1752 | 1753 | def cq_poke(self, type, id): 1754 | return f'[CQ:poke,type={type},id={id}]' 1755 | 1756 | def seg_anonymous(self, ignore=0): 1757 | return [{'type': 'anonymous', 'data': {'ignore': ignore}}] 1758 | 1759 | def cq_anonymous(self, ignore=0): 1760 | return f'[CQ:anonymous,ignore={ignore}]' 1761 | 1762 | def seg_share(self, url, title, content, image): 1763 | return [{'type': 'share', 'data': {'url': url, 'title': title, 'content': content, 'image': image}}] 1764 | 1765 | def cq_share(self, url, title, content, image): 1766 | return f'[CQ:share,url={url},title={title},content={content},image={image}]' 1767 | 1768 | def seg_contact(self, type, id): 1769 | return [{'type': 'contact', 'data': {'type': type, 'id': id}}] 1770 | 1771 | def cq_contact(self, type, id): 1772 | return f'[CQ:contact,type={type},id={id}]' 1773 | 1774 | def seg_location(self, lat, lon, title=None, content=None): 1775 | if title is None and content is None: 1776 | return [{'type': 'location', 'data': {'lat': lat, 'lon': lon}}] 1777 | else: 1778 | if title is None: 1779 | return [{'type': 'location', 'data': {'lat': lat, 'lon': lon, 'content': content}}] 1780 | else: 1781 | return [{'type': 'location', 'data': {'lat': lat, 'lon': lon, 'title': title}}] 1782 | 1783 | def cq_location(self, lat, lon, title, content): 1784 | if title is None and content is None: 1785 | return f'[CQ:location,lat={lat},lon={lon}]' 1786 | else: 1787 | if title is None: 1788 | return f'[CQ:location,lat={lat},lon={lon},content={content}]' 1789 | else: 1790 | return f'[CQ:location,lat={lat},lon={lon},title={title}]' 1791 | 1792 | def seg_music(self, type, id=None, url=None, audio=None, title=None, content=None, image=None): 1793 | if type == "custom": 1794 | return [{'type': 'music', 1795 | 'data': {'type': type, 'url': url, 'audio': audio, 'title': title, 'content': content, 1796 | 'image': image}}] 1797 | else: 1798 | return [{'type': 'music', 'data': {'type': type, 'id': id}}] 1799 | 1800 | def cq_music(self, type, id, url, audio, title, content, image): 1801 | if type == "custom": 1802 | return f'[CQ:music,type=custom,url={url},audio={audio},title={title},content={content},image={image}]' 1803 | else: 1804 | return f'[CQ:music,type={type},id={id}]' 1805 | 1806 | def seg_reply(self, id): 1807 | return [{'type': 'reply', 'data': {'id': id}}] 1808 | 1809 | def cq_reply(self, id): 1810 | return f'[CQ:reply,id={id}]' 1811 | 1812 | def seg_node(self, id=None, user_id=None, nickname=None, content=None): 1813 | if id is None: 1814 | return [{'type': 'node', 'data': {'user_id': user_id, 'nickname': nickname, 'content': content}}] 1815 | else: 1816 | return [{'type': 'node', 'data': {'id': id}}] 1817 | 1818 | def cq_node(self, id, user_id, nickname, content): 1819 | if id is None: 1820 | return f'[CQ:node,user_id={user_id},nickname={nickname},content={content}]' 1821 | else: 1822 | return f'[CQ:node,id={id}]' 1823 | 1824 | def seg_xml(self, data): 1825 | return [{'type': 'xml', 'data': {'data': data}}] 1826 | 1827 | def cq_xml(self, data): 1828 | return f'[CQ:xml,data={data}]' 1829 | 1830 | def seg_json(self, data): 1831 | return [{'type': 'json', 'data': {'data': data}}] 1832 | 1833 | def cq_json(self, data): 1834 | return f'[CQ:json,data={data}]' 1835 | -------------------------------------------------------------------------------- /variable.py: -------------------------------------------------------------------------------- 1 | from log import loggerClass 2 | class Variable(object): 3 | processer=None 4 | loader=None 5 | util=None 6 | ws=None 7 | bot=None 8 | bot_id=None 9 | main_stop=False 10 | config=None 11 | log=loggerClass 12 | 13 | variable=Variable() -------------------------------------------------------------------------------- /ws.py: -------------------------------------------------------------------------------- 1 | import websocket 2 | import threading 3 | 4 | from variable import variable 5 | 6 | logger=variable.log.getChild('WebSocket') 7 | class WS(object): 8 | api_url=None 9 | token=None 10 | status=False 11 | wsc=None 12 | def __init__(self,api_url=None,token=None): 13 | self.api_url=api_url 14 | self.token=token 15 | 16 | def on_message(self,ws,message): 17 | if variable.main_stop: 18 | self.wsc.close() 19 | logger.debug(f"接收到消息: {message}") 20 | threading.Thread(target=variable.processer.processMessage,args=(message,)).start() 21 | 22 | def on_error(self,ws,error): 23 | logger.error(f"出现错误: {error}") 24 | 25 | def on_close(self,ws,close_status_code,close_msg): 26 | logger.info(f"WebSocket连接已关闭") 27 | logger.info(f"关闭状态码: {close_status_code}") 28 | logger.info(f"关闭信息: {close_msg}") 29 | self.status=False 30 | 31 | def on_open(self,ws): 32 | logger.info(f"WebSocket连接已建立") 33 | self.status=True 34 | 35 | def start(self): 36 | header=dict() 37 | if (not self.api_url is None) and (not variable.bot_id is None): 38 | if self.token is None: 39 | header={"bot_id": str(variable.bot_id)} 40 | else: 41 | header={"bot_id": str(variable.bot_id),"Authorization": "Bearer "+self.token} 42 | self.wsc=websocket.WebSocketApp(self.api_url,header=header, 43 | on_open=self.on_open, 44 | on_message=self.on_message, 45 | on_error=self.on_error, 46 | on_close=self.on_close) 47 | self.wsc.run_forever() 48 | def send(self,msg): 49 | self.wsc.send(msg) 50 | 51 | def close(self): 52 | self.wsc.close() 53 | 54 | def get_status(self): 55 | return self.status 56 | --------------------------------------------------------------------------------