├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── cqhttp └── __init__.py ├── cqhttp_helper.py ├── demo.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # ----- Python ----- 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | db.sqlite3 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # Environments 87 | .env 88 | .venv 89 | env/ 90 | venv/ 91 | ENV/ 92 | env.bak/ 93 | venv.bak/ 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | .spyproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | 102 | # mkdocs documentation 103 | /site 104 | 105 | # mypy 106 | .mypy_cache/ 107 | 108 | # ----- Project ----- 109 | 110 | .idea 111 | .vscode 112 | dev.py 113 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 更新日志 2 | 3 | ## v1.3.1 4 | 5 | - 修复 1.3.0 中 `on_*` 装饰器的严重 bug 6 | 7 | ## v1.3.0 8 | 9 | - `CQHttp` 类新增 `logger` 属性,可获取 Flask app 的 logger 10 | - `on_*` 装饰器支持不加括号使用 11 | - 不再支持 CQHTTP v3 和 v4.0~4.7,请升级到 v4.8 或更新版本 12 | - 优化部分代码 13 | 14 | ## v1.2.3 15 | 16 | - 支持插件 v4.5.0 的 `meta_event` 上报 17 | - `CQHttp` 类新增 `server_app` 属性,以明确获得内部的 `Flask` 对象,和原来的 `wsgi` 属性等价 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2017 Richard Chien 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 本项目已停止维护,请使用功能更丰富的 [python-aiocqhttp](https://github.com/nonebot/python-aiocqhttp) 替代 2 | 3 | # CQHTTP Python SDK 4 | 5 | [![License](https://img.shields.io/github/license/richardchien/python-cqhttp.svg)](LICENSE) 6 | [![PyPI](https://img.shields.io/pypi/v/cqhttp.svg)](https://pypi.python.org/pypi/cqhttp) 7 | ![Python Version](https://img.shields.io/badge/python-3.5+-blue.svg) 8 | ![CQHTTP Version](https://img.shields.io/badge/cqhttp-4.8+-black.svg) 9 | [![QQ 群](https://img.shields.io/badge/qq群-768887710-orange.svg)](https://jq.qq.com/?_wv=1027&k=5OFifDh) 10 | [![Telegram](https://img.shields.io/badge/telegram-chat-blue.svg)](https://t.me/cqhttp) 11 | [![QQ 版本发布群](https://img.shields.io/badge/版本发布群-218529254-green.svg)](https://jq.qq.com/?_wv=1027&k=5Nl0zhE) 12 | [![Telegram 版本发布频道](https://img.shields.io/badge/版本发布频道-join-green.svg)](https://t.me/cqhttp_release) 13 | 14 | 本项目为 CQHTTP 插件的 Python SDK,封装了 web server 相关的代码,让使用 Python 的开发者能方便地开发插件。 15 | 16 | 关于 CQHTTP 插件,见 [richardchien/coolq-http-api](https://github.com/richardchien/coolq-http-api)。 17 | 18 | ## 用法 19 | 20 | 首先安装 `cqhttp` 包: 21 | 22 | ```sh 23 | pip install cqhttp 24 | ``` 25 | 26 | 注意可能需要把 `pip` 换成 `pip3`。本 SDK 依赖于 `Flask` 和 `requests` 包,因此它们也会被安装。 27 | 28 | 也可以克隆本仓库之后用 `python setup.py install` 来安装。 29 | 30 | 然后新建 Python 文件,运行 bot: 31 | 32 | ```py 33 | from cqhttp import CQHttp 34 | 35 | bot = CQHttp(api_root='http://127.0.0.1:5700/', 36 | access_token='your-token', 37 | secret='your-secret') 38 | 39 | 40 | @bot.on_message 41 | def handle_msg(event): 42 | bot.send(event, '你好呀,下面一条是你刚刚发的:') 43 | return {'reply': event['message'], 'at_sender': False} 44 | 45 | 46 | @bot.on_notice('group_increase') # 如果插件版本是 3.x,这里需要使用 @bot.on_event 47 | def handle_group_increase(event): 48 | bot.send(event, message='欢迎新人~', auto_escape=True) # 发送欢迎新人 49 | 50 | 51 | @bot.on_request('group', 'friend') 52 | def handle_request(event): 53 | return {'approve': True} # 同意所有加群、加好友请求 54 | 55 | 56 | bot.run(host='127.0.0.1', port=8080, debug=True) 57 | ``` 58 | 59 | ### 创建实例 60 | 61 | 首先创建 `CQHttp` 类的实例,传入 `api_root`,即为 CQHTTP 插件的监听地址,例如 `http://127.0.0.1:5700`,如果你不需要调用 API,也可以不传入。Access token 和签名密钥也在这里传入,如果没有配置 `access_token` 或 `secret` 项,则不传。 62 | 63 | ### 事件处理 64 | 65 | `on_message`、`on_notice`、`on_request`、`on_meta_event` 装饰器分别对应插件的四个上报类型(`post_type`),括号中指出要处理的消息类型(`message_type`)、通知类型(`notice_type`)、请求类型(`request_type`)、元事件类型(`meta_event_type`),一次可指定多个,如果留空,则会处理所有这个上报类型的上报。在上面的例子中 `handle_msg` 函数将会在收到任意渠道的消息时被调用,`handle_group_increase` 函数会在群成员增加时调用。 66 | 67 | 上面装饰器装饰的函数,统一接受一个参数,即为上报的数据,具体数据内容见 [事件上报](https://cqhttp.cc/docs/#/Post);返回值可以是一个字典,会被自动作为 JSON 响应返回给 CQHTTP 插件,具体见 [上报请求的响应数据格式](https://cqhttp.cc/docs/#/Post?id=%E4%B8%8A%E6%8A%A5%E8%AF%B7%E6%B1%82%E7%9A%84%E5%93%8D%E5%BA%94%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F)。 68 | 69 | ### API 调用 70 | 71 | 在设置了 `api_root` 的情况下,直接在 `CQHttp` 类的实例上就可以调用 API,例如 `bot.send_private_msg(user_id=123456, message='hello')`,这里的 `send_private_msg` 即为 [`/send_private_msg` 发送私聊消息](https://cqhttp.cc/docs/#/API?id=send_private_msg-%E5%8F%91%E9%80%81%E7%A7%81%E8%81%8A%E6%B6%88%E6%81%AF) 中的 `/send_private_msg`,**API 所需参数直接通过命名参数(keyword argument)传入**。其它 API 见 [API 列表](https://cqhttp.cc/docs/#/API?id=api-%E5%88%97%E8%A1%A8)。 72 | 73 | 为了简化发送消息的操作,提供了 `send(context, message)` 函数,这里的第一个参数 `context` 也就是上报数据,传入之后函数会自己判断当前需要发送到哪里(哪个好友,或哪个群),无需手动再指定,其它参数仍然可以从 keyword argument 指定,例如 `auto_escape=True`。 74 | 75 | 每个 API 调用最后都会由 `requests` 库来发出请求,如果网络无法连接,它可能会抛出 `ConnectionError` 等异常,见 [错误与异常](http://cn.python-requests.org/zh_CN/latest/user/quickstart.html#id11)。而一旦请求成功,本 SDK 会判断 HTTP 响应状态码,只有当状态码为 200,且 `status` 字段为 `ok` 或 `async` 时,会返回 `data` 字段的内容,否则抛出 `cqhttp.Error` 异常,在这个异常中你可以通过 `status_code` 和 `retcode` 属性来获取 HTTP 状态码和插件的 `retcode`(如果状态码不为 200,则 `retcode` 为 None),具体响应状态码和 `retcode` 的含义,见 [响应说明](https://cqhttp.cc/docs/#/API?id=%E5%93%8D%E5%BA%94%E8%AF%B4%E6%98%8E)。 76 | 77 | ### 运行实例 78 | 79 | 使用装饰器定义好处理函数之后,调用 `bot.run()` 即可运行。你需要传入 `host` 和 `port` 参数,来指定服务端需要运行在哪个地址,**然后在 CQHTTP 插件的配置文件中,在 `post_url` 项填写此地址(`http://host:port/`)**。 80 | 81 | ### CQHttp Helper 82 | 83 | 项目根目录下的 [`cqhttp_helper.py`](cqhttp_helper.py) 文件是 [SuperMarioSF](https://github.com/SuperMarioSF) 贡献的帮助类,在 `CQHttp` 类的基础上提供了每个 API 调用的具体函数,以便在支持的代码编辑器中使用代码补全和文档速览。 84 | 85 | 注意,此文件不在 pip 安装的包中,需单独下载,如果以后插件新增接口,此文件可能没有及时更新,但不影响使用,你仍然可以像使用原始的 `CQHttp` 一样使用它。 86 | 87 | ### 部署 88 | 89 | `bot.run()` 只适用于开发环境,不建议用于生产环境,因此 SDK 从 1.2.1 版本开始提供 `bot.wsgi` 属性以获取其内部兼容 WSGI 的 app 对象,从而可以使用 Gunicorn、uWSGI 等软件来部署。 90 | 91 | ### 添加路由 92 | 93 | `CQHttp` 内部使用 [Flask](http://flask.pocoo.org/) 来提供 web server,默认添加了 bot 所需的 `/` 路由,如需添加其它路由,例如在 `/admin/` 提供管理面板访问,可以通过 `bot.server_app` 访问内部的 `Flask` 实例来做到: 94 | 95 | ```python 96 | app = bot.server_app 97 | 98 | @app.route('/admin') 99 | async def admin(): 100 | return 'This is the admin page.' 101 | ``` 102 | 103 | 目前 `bot.server_app` 和 `bot.wsgi` 等价。 104 | 105 | ## 更新日志 106 | 107 | 更新日志见 [CHANGELOG.md](CHANGELOG.md)。 108 | 109 | ## 遇到问题 110 | 111 | 本 SDK 的代码非常简单,如果发现有问题可以参考下源码,可以自行做一些修复,也欢迎提交 pull request 或 issue。 112 | -------------------------------------------------------------------------------- /cqhttp/__init__.py: -------------------------------------------------------------------------------- 1 | import hmac 2 | from collections import defaultdict 3 | from functools import wraps 4 | from typing import Callable 5 | 6 | import requests 7 | from flask import Flask, request, abort, jsonify 8 | 9 | 10 | class Error(Exception): 11 | def __init__(self, status_code, retcode=None): 12 | self.status_code = status_code 13 | self.retcode = retcode 14 | 15 | 16 | def _api_client(url, access_token=None): 17 | def do_call(**kwargs): 18 | headers = {} 19 | if access_token: 20 | headers['Authorization'] = 'Token ' + access_token 21 | resp = requests.post(url, json=kwargs, headers=headers) 22 | if resp.ok: 23 | data = resp.json() 24 | if data.get('status') == 'failed': 25 | raise Error(resp.status_code, data.get('retcode')) 26 | return data.get('data') 27 | raise Error(resp.status_code) 28 | 29 | return do_call 30 | 31 | 32 | def _deco_maker(type_): 33 | def deco_deco(self, arg=None, *detail_types): 34 | def deco(func): 35 | @wraps(func) 36 | def wrapper(*args, **kwargs): 37 | return func(*args, **kwargs) 38 | 39 | if isinstance(arg, str): 40 | for detail_type in [arg] + list(detail_types): 41 | self._handlers[type_][detail_type] = wrapper 42 | else: 43 | self._handlers[type_]['*'] = wrapper 44 | return wrapper 45 | 46 | if isinstance(arg, Callable): 47 | return deco(arg) 48 | return deco 49 | 50 | return deco_deco 51 | 52 | 53 | class CQHttp: 54 | def __init__(self, api_root=None, access_token=None, secret=None): 55 | self._api_root = api_root.rstrip('/') if api_root else None 56 | self._access_token = access_token 57 | self._secret = secret 58 | self._handlers = defaultdict(dict) 59 | self._server_app = Flask(__name__) 60 | self._server_app.route('/', methods=['POST'])(self._handle) 61 | 62 | @property 63 | def wsgi(self): 64 | return self._server_app 65 | 66 | @property 67 | def server_app(self): 68 | return self._server_app 69 | 70 | @property 71 | def logger(self): 72 | return self._server_app.logger 73 | 74 | on_message = _deco_maker('message') 75 | on_notice = _deco_maker('notice') 76 | on_request = _deco_maker('request') 77 | on_meta_event = _deco_maker('meta_event') 78 | 79 | def _handle(self): 80 | if self._secret: 81 | if 'X-Signature' not in request.headers: 82 | abort(401) 83 | 84 | sec = self._secret 85 | sec = sec.encode('utf-8') if isinstance(sec, str) else sec 86 | sig = hmac.new(sec, request.get_data(), 'sha1').hexdigest() 87 | if request.headers['X-Signature'] != 'sha1=' + sig: 88 | abort(403) 89 | 90 | event = request.json 91 | type_ = event.get('post_type', '') 92 | detail_type = event.get('{}_type'.format(type_), '') 93 | if not detail_type: 94 | abort(400) 95 | 96 | self.logger.info('received event: ' + type_ + '.' + detail_type) 97 | 98 | handler = self._handlers[type_].get(detail_type, 99 | self._handlers[type_].get('*')) 100 | if handler: 101 | response = handler(event) 102 | return jsonify(response) if isinstance(response, dict) else '' 103 | return '' 104 | 105 | def run(self, host=None, port=None, **kwargs): 106 | self._server_app.run(host=host, port=port, **kwargs) 107 | 108 | def send(self, event, message, **kwargs): 109 | params = event.copy() 110 | params['message'] = message 111 | params.pop('raw_message', None) # avoid wasting bandwidth 112 | params.pop('comment', None) 113 | params.pop('sender', None) 114 | params.update(kwargs) 115 | if 'message_type' not in params: 116 | if 'group_id' in params: 117 | params['message_type'] = 'group' 118 | elif 'discuss_id' in params: 119 | params['message_type'] = 'discuss' 120 | elif 'user_id' in params: 121 | params['message_type'] = 'private' 122 | return self.send_msg(**params) 123 | 124 | def __getattr__(self, item): 125 | if self._api_root: 126 | return _api_client(self._api_root + '/' + item, self._access_token) 127 | -------------------------------------------------------------------------------- /cqhttp_helper.py: -------------------------------------------------------------------------------- 1 | import cqhttp as _cqhttp 2 | 3 | # working as a replacement. 4 | Error = _cqhttp.Error 5 | 6 | 7 | class CQHttp(_cqhttp.CQHttp): 8 | """ 9 | **CoolQ HTTP API Python SDK封装类** 10 | 11 | :作者: SuperMarioSF 12 | :参考: HTTP API v3.4 13 | :版本: v1.0 14 | 15 | 本类封装了CoolQ HTTP API的Python SDK中对应HTTP API的所有API。 16 | 封装时参考并引用了HTTP API所提供的文档。 17 | 18 | 19 | 感谢 **richardchien** 为我们提供了如此简便酷Q应用开发方式。 20 | 21 | 本文件使用 WTFPL 2.0 许可证发布。 22 | 23 | ------------ 24 | 25 | 本文件适用于JetBrains PyCharm开发环境。 26 | 你可以在PyCharm中在任何使用到本类的封装方法的位置按下 **Ctrl+Q** 来快速查看文档的内容。 27 | 28 | 本页帮助文档也会下方会介绍原Python SDK中已有的功能的用法。 29 | 30 | 要查看HTTP API的完整在线文档,请访问: 31 | 32 | | https://richardchien.github.io/coolq-http-api/ 33 | | http://richardchien.gitee.io/coolq-http-api/docs/ 34 | 35 | ------------ 36 | 37 | **使用本文件** 38 | 39 | 你可以将本文件作为导入 cqhttp 包的替代来使用。 40 | 但需要注意,本文件需要你的 Python 环境中能够导入原 cqhttp 包,因为本文件只是对原 cqhttp 包的封装。 41 | 在用本文件在PyCharm IDE开发完毕之后,你可以直接把对本文件的引用恢复为对原 cqhttp 包的引用。 42 | 43 | 将原先导入 cqhttp 包的写法: 44 | 45 | >>> from cqhttp import CQHttp, Error 46 | 47 | 更换成: 48 | 49 | >>> from cqhttp_helper import CQHttp, Error 50 | 51 | 即可完成对本文件的所有内容的引用,享受自动补全和即时文档吧! 52 | 53 | 在本帮助页面中,`bot` 指的是已初始化的 CQHttp 对象。 54 | 55 | ------------ 56 | 57 | **对象列表** 58 | 59 | | cqhttp_helper.CQHttp(api_root=None, access_token=None, secret=None): CQHttp 封装类,继承原 cqhttp 的 CQHttp 类,提供封装的方法列表和实时帮助信息。 60 | | cqhttp_helper.Error(status_code, retcode=None): CQHttp 异常类。直接引用 cqhttp 的 Error 类,用于兼容目的。 61 | 62 | ------------ 63 | 64 | **cqhttp_helper.CQHttp 中封装的方法列表** 65 | 66 | | send_private_msg(self, *, user_id, message, auto_escape=False): 发送私聊消息 67 | | send_private_msg_async(self, *, user_id, message, auto_escape=False): 发送私聊消息 (异步版本) 68 | | send_group_msg(self, *, group_id, message, auto_escape=False): 发送群消息 69 | | send_group_msg_async(self, *, group_id, message, auto_escape=False): 发送群消息 (异步版本) 70 | | send_discuss_msg(self, *, discuss_id, message, auto_escape=False): 发送讨论组消息 71 | | send_discuss_msg_async(self, *, discuss_id, message, auto_escape=False): 发送讨论组消息 (异步版本) 72 | | send_msg(self, *, message_type, user_id=None, group_id=None, discuss_id=None, message, auto_escape=False): 发送消息 73 | | send_msg_async(self, *, message_type, user_id=None, group_id=None, discuss_id=None, message, auto_escape=False): 发送消息 (异步版本) 74 | | delete_msg(self, *, message_id): 撤回消息 75 | | send_like(self, *, user_id, times=1): 发送好友赞 76 | | set_group_kick(self, *, group_id, user_id, reject_add_request=False): 群组踢人 77 | | set_group_ban(self, *, group_id, user_id, duration=30 * 60): 群组单人禁言 78 | | set_group_anonymous_ban(self, *, group_id, flag, duration=30 * 60): 群组匿名用户禁言 79 | | set_group_whole_ban(self, *, group_id, enable=True): 群组全员禁言 80 | | set_group_admin(self, *, group_id, user_id, enable=True): 群组设置管理员 81 | | set_group_anonymous(self, *, group_id, enable=True): 群组匿名 82 | | set_group_card(self, *, group_id, user_id, card=None): 设置群名片(群备注) 83 | | set_group_leave(self, *, group_id, is_dismiss=False): 退出群组 84 | | set_group_special_title(self, *, group_id, user_id, special_title, duration=-1): 设置群组专属头衔 85 | | set_discuss_leave(self, *, discuss_id): 退出讨论组 86 | | set_friend_add_request(self, *, flag, approve=True, remark=None): 处理加好友请求 87 | | set_group_add_request(self, *, flag, type, approve=True, reason=None): 处理加群请求、群组成员邀请 88 | | get_login_info(self): 获取登录号信息 89 | | get_stranger_info(self, *, user_id, no_cache=False): 获取陌生人信息 90 | | get_group_list(self): 获取群列表 91 | | get_group_member_info(self, *, group_id, user_id, no_cache=False): 获取群成员信息 92 | | get_group_member_list(self, *, group_id): 获取群成员列表 93 | | get_cookies(self): 获取 Cookies 94 | | get_csrf_token(self): 获取 CSRF Token 95 | | get_record(self, *, file, out_format): 获取语音 96 | | get_status(self): 获取插件运行状态 97 | | get_version_info(self): 获取酷 Q 及 HTTP API 插件的版本信息 98 | | set_restart(self): 重启酷 Q,并以当前登录号自动登录(需勾选快速登录) 99 | | set_restart_plugin(self): 重启 HTTP API 插件 100 | | clean_data_dir(self, *, data_dir): 清理数据目录 101 | | clean_data_dir_async(self, *, data_dir): 清理数据目录 (异步版本) 102 | | _get_friend_list(self): 获取好友列表 (实验性功能) 103 | 104 | ------------ 105 | 106 | **创建实例** 107 | 108 | 首先创建 CQHttp 类的实例,传入 api_root,即为酷 Q HTTP API 插件的监听地址,如果你不需要调用 API,也可以不传入。Access token 和签名密钥也在这里传入,如果没有配置 access_token 或 secret 项,则不传。 109 | 110 | ------------ 111 | 112 | **异常处理** 113 | 114 | 每个 API 调用最后都会由 `requests` 库来发出请求,如果网络无法连接,它可能会抛出 `ConnectionError` 等异常,见 requests错误与异常_ 。而一旦请求成功,本 SDK 会判断 HTTP 响应状态码,只有当状态码为 200,且 status 字段为 ok 或 async 时,会返回 data 字段的内容,否则抛出 cqhttp.Error 异常,在这个异常中你可以通过 status_code 和 retcode 属性来获取 HTTP 状态码和插件的 retcode(如果状态码不为 200,则 retcode 为 None),具体响应状态码和 retcode 的含义,见 响应说明_ 。 115 | 116 | .. _requests错误与异常: http://cn.python-requests.org/zh_CN/latest/user/quickstart.html#id11 117 | .. _响应说明: https://richardchien.github.io/coolq-http-api/#/API?id=%E5%93%8D%E5%BA%94%E8%AF%B4%E6%98%8E 118 | 119 | ------------ 120 | 121 | **消息发送** 122 | 123 | 为了简化发送消息的操作,Python SDK 提供了 send(context, message) 函数,这里的第一个参数 context 也就是上报数据,传入之后函数会自己判断当前需要发送到哪里(哪个好友,或哪个群),无需手动再指定,其它参数仍然可以从 keyword argument 指定,例如 auto_escape=True。 124 | 125 | 以 `send_` 开头(也就是消息发送相关)的接口都会包含一个 `message` 的参数。此参数接受普通的字符串(str),也接受一个特定格式的列表(list)。列表格式在原HTTP API文档中是表示为 `array` ,称为 JSON数组类型。在Python中,表示为一个列表(list)中包括的数个字典(dict)。 126 | 127 | 以下是调用范例: 128 | 129 | .. code-block:: python 130 | 131 | # 以字符串形式发送的普通消息。 132 | bot.send_private_message(user_id=1234567, 133 | message="这是一条普通消息。\\n可以用这种方式换行。" 134 | 135 | # 以列表(list)形式发送的多段消息组合。 136 | bot.send_private_message(user_id=1234567, 137 | message=[ 138 | { 139 | "type": "text", 140 | "data": {"text": "这是第一段"} 141 | }, 142 | { 143 | "type": "face", 144 | "data": {"id": "111"} 145 | }, 146 | { 147 | "type": "text", 148 | "data": {"text": "这是表情之后的一段。"} 149 | } 150 | ]) 151 | 152 | 关于多段消息的每个单段的具体格式,请参见 HTTP API 文档中的 **消息格式** 的 **消息段(广义 CQ 码)** 一节。 153 | 154 | ------------ 155 | 156 | **事件处理** 157 | 158 | `on_message`、`on_event`、`on_request` 三个装饰器分别对应三个上报类型(`post_type`),括号中指出要处理的消息类型(`message_type`)、事件名(`event`)、请求类型(`request_type`),一次可指定多个,如果留空,则会处理所有这个上报类型的上报。 159 | 160 | 以下为范例: 161 | 162 | .. code-block:: python 163 | 164 | # 监听消息输入 165 | @bot.on_message() 166 | def handle_msg(context): 167 | bot.send(context, '你好呀,下面一条是你刚刚发的:') 168 | return {'reply': context['message'], 'at_sender': False} 169 | 170 | # 监听群组成员增加事件 171 | @bot.on_event('group_increase') 172 | def handle_group_increase(context): 173 | bot.send(context, message='欢迎新人~', is_raw=True) # 发送欢迎新人 174 | 175 | # 监听加群和加好友请求 176 | @bot.on_request('group', 'friend') 177 | def handle_request(context): 178 | return {'approve': True} # 同意所有加群、加好友请求 179 | 180 | 181 | bot.run(host='127.0.0.1', port=8080) 182 | 183 | 上面三个装饰器装饰的函数,统一接受一个参数,即为上报的数据,具体数据内容见 事件上报_ ;返回值可以是一个字典,会被自动作为 JSON 响应返回给 HTTP API 插件,具体见 上报请求的响应数据格式_ 。 184 | 185 | 186 | .. _事件上报: https://richardchien.github.io/coolq-http-api/#/Post 187 | .. _上报请求的响应数据格式: https://richardchien.github.io/coolq-http-api/#/Post?id=%E4%B8%8A%E6%8A%A5%E8%AF%B7%E6%B1%82%E7%9A%84%E5%93%8D%E5%BA%94%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F 188 | 189 | ------------ 190 | 191 | **运行实例** 192 | 193 | 使用装饰器定义好处理函数之后,调用 `bot.run()` 即可运行。你需要传入 `host` 和 `port` 参数,来指定服务端需要运行在哪个地址,**然后在 HTTP API 插件的配置文件中,在 `post_url` 项中配置此地址(http://host:port/)**。 194 | 195 | **务必注意:** 如果不在插件配置文件中指定 `post_url` 为运行实例时指定的地址,Python SDK 将无法收到任何消息和事件。 196 | 197 | 198 | 199 | """ 200 | def __init__(self, api_root=None, access_token=None, secret=None): 201 | """ 202 | 创建 CoolQ HTTP API 对象 203 | 204 | ------------ 205 | 206 | :param str | None api_root: 酷 Q HTTP API 插件的监听地址的 URL ,与 HTTP API 的配置文件设定和实际使用环境相关。如果你不需要调用 API,也可以不传入。 207 | :param str | None access_token: 插件配置文件中所指定的 `access_token` 。如果未设定可不传此参数。 208 | :param str | None secret: 插件配置文件中所指定的 `secret` 。如果未设定可不传此参数。 209 | """ 210 | super().__init__(api_root=api_root, access_token=access_token, secret=secret) 211 | 212 | def send_private_msg(self, *, user_id, message, auto_escape=False): 213 | ''' 214 | 发送私聊消息 215 | 216 | ------------ 217 | 218 | :param int user_id: 对方 QQ 号 219 | :param str | list[ dict[ str, unknown ] ] message: 要发送的内容 220 | :param bool auto_escape: 消息内容是否作为纯文本发送(即不解析 CQ 码),`message` 数据类型为 `list` 时无效 221 | :return: {"message_id": int 消息ID} 222 | :rtype: dict[string, int] 223 | ''' 224 | return super().__getattr__('send_private_msg') \ 225 | (user_id=user_id, message=message, auto_escape=auto_escape) 226 | 227 | def send_private_msg_async(self, *, user_id, message, auto_escape=False): 228 | """ 229 | 发送私聊消息 (异步版本) 230 | 231 | ------------ 232 | 233 | :param int user_id: 对方 QQ 号 234 | :param str | list[ dict[ str, unknown ] ] message: 要发送的内容 235 | :param bool auto_escape: 消息内容是否作为纯文本发送(即不解析 CQ 码),`message` 数据类型为 `list` 时无效 236 | :return: None 237 | :rtype: None 238 | """ 239 | return super().__getattr__('send_private_msg_async') \ 240 | (user_id=user_id, message=message, auto_escape=auto_escape) 241 | 242 | def send_group_msg(self, *, group_id, message, auto_escape=False): 243 | """ 244 | 发送群消息 245 | 246 | ------------ 247 | 248 | :param int group_id: 群号 249 | :param str | list[ dict[ str, unknown ] ] message: 要发送的内容 250 | :param bool auto_escape: 消息内容是否作为纯文本发送(即不解析 CQ 码),`message` 数据类型为 `list` 时无效 251 | :return: {"message_id": int 消息ID} 252 | :rtype: dict[string, int] 253 | """ 254 | return super().__getattr__('send_group_msg') \ 255 | (group_id=group_id, message=message, auto_escape=auto_escape) 256 | 257 | def send_group_msg_async(self, *, group_id, message, auto_escape=False): 258 | """ 259 | 发送群消息 (异步版本) 260 | 261 | ------------ 262 | 263 | :param int group_id: 群号 264 | :param str | list[ dict[ str, unknown ] ] message: 要发送的内容 265 | :param bool auto_escape: 消息内容是否作为纯文本发送(即不解析 CQ 码),`message` 数据类型为 `list` 时无效 266 | :return: None 267 | :rtype: None 268 | """ 269 | return super().__getattr__('send_group_msg_async') \ 270 | (group_id=group_id, message=message, auto_escape=auto_escape) 271 | 272 | def send_discuss_msg(self, *, discuss_id, message, auto_escape=False): 273 | """ 274 | 发送讨论组消息 275 | 276 | ------------ 277 | 278 | :param int discuss_id: 讨论组 ID(正常情况下看不到,需要从讨论组消息上报的数据中获得) 279 | :param str | list[ dict[ str, unknown ] ] message: 要发送的内容 280 | :param bool auto_escape: 消息内容是否作为纯文本发送(即不解析 CQ 码),`message` 数据类型为 `list` 时无效 281 | :return: {"message_id": int 消息ID} 282 | :rtype: dict[string, int] 283 | """ 284 | return super().__getattr__('send_discuss_msg') \ 285 | (discuss_id=discuss_id, message=message, auto_escape=auto_escape) 286 | 287 | def send_discuss_msg_async(self, *, discuss_id, message, auto_escape=False): 288 | """ 289 | 发送讨论组消息 (异步版本) 290 | 291 | ------------ 292 | 293 | :param int discuss_id: 讨论组 ID(正常情况下看不到,需要从讨论组消息上报的数据中获得) 294 | :param str | list[ dict[ str, unknown ] ] message: 要发送的内容 295 | :param bool auto_escape: 消息内容是否作为纯文本发送(即不解析 CQ 码),`message` 数据类型为 `list` 时无效 296 | :return: None 297 | :rtype: None 298 | """ 299 | return super().__getattr__('send_discuss_msg_async') \ 300 | (discuss_id=discuss_id, message=message, auto_escape=auto_escape) 301 | 302 | def send_msg(self, *, message_type, user_id=None, group_id=None, discuss_id=None, message, auto_escape=False): 303 | """ 304 | 发送消息 305 | 306 | ------------ 307 | 308 | :param str message_type: 消息类型,支持 `private`、`group`、`discuss`,分别对应私聊、群组、讨论组 309 | :param int user_id: 对方 QQ 号(消息类型为 `private` 时需要) 310 | :param int group_id: 群号(消息类型为 `group` 时需要) 311 | :param int discuss_id: 讨论组 ID(需要从上报消息中获取,消息类型为 `discuss` 时需要) 312 | :param str | list[ dict[ str, unknown ] ] message: 要发送的内容 313 | :param bool auto_escape: 消息内容是否作为纯文本发送(即不解析 CQ 码),`message` 数据类型为 `list` 时无效 314 | :return: {"message_id": int 消息ID} 315 | :rtype: dict[string, int] 316 | """ 317 | return super().__getattr__('send_msg') \ 318 | (message_type=message_type, user_id=user_id, group_id=group_id, 319 | discuss_id=discuss_id, message=message, auto_escape=auto_escape) 320 | 321 | def send_msg_async(self, *, message_type, user_id=None, group_id=None, discuss_id=None, message, auto_escape=False): 322 | """ 323 | 发送消息 (异步版本) 324 | 325 | ------------ 326 | 327 | :param str message_type: 消息类型,支持 `private`、`group`、`discuss`,分别对应私聊、群组、讨论组 328 | :param int user_id: 对方 QQ 号(消息类型为 `private` 时需要) 329 | :param int group_id: 群号(消息类型为 `group` 时需要) 330 | :param int discuss_id: 讨论组 ID(需要从上报消息中获取,消息类型为 `discuss` 时需要) 331 | :param str | list[ dict[ str, unknown ] ] message: 要发送的内容 332 | :param bool auto_escape: 消息内容是否作为纯文本发送(即不解析 CQ 码),`message` 数据类型为 `list` 时无效 333 | :return: None 334 | :rtype: None 335 | """ 336 | return super().__getattr__('send_msg_async') \ 337 | (message_type=message_type, user_id=user_id, group_id=group_id, 338 | discuss_id=discuss_id, message=message, auto_escape=auto_escape) 339 | 340 | def delete_msg(self, *, message_id): 341 | """ 342 | 撤回消息 343 | 344 | ------------ 345 | 346 | :param int message_id: 消息 ID 347 | :return: None 348 | :rtype: None 349 | """ 350 | return super().__getattr__('delete_msg') \ 351 | (message_id=message_id) 352 | 353 | def send_like(self, *, user_id, times=1): 354 | """ 355 | 发送好友赞 356 | 357 | ------------ 358 | 359 | :param int user_id: 对方 QQ 号 360 | :param int times: 赞的次数,每个好友每天最多 10 次 361 | :return: None 362 | :rtype: None 363 | """ 364 | return super().__getattr__('send_like') \ 365 | (user_id=user_id, times=times) 366 | 367 | def set_group_kick(self, *, group_id, user_id, reject_add_request=False): 368 | """ 369 | 群组踢人 370 | 371 | ------------ 372 | 373 | :param int group_id: 群号 374 | :param int user_id: 要踢的 QQ 号 375 | :param bool reject_add_request: 拒绝此人的加群请求 376 | :return: None 377 | :rtype: None 378 | """ 379 | return super().__getattr__('set_group_kick') \ 380 | (group_id=group_id, user_id=user_id, reject_add_request=reject_add_request) 381 | 382 | def set_group_ban(self, *, group_id, user_id, duration=30 * 60): 383 | """ 384 | 群组单人禁言 385 | 386 | ------------ 387 | 388 | :param int group_id: 群号 389 | :param int user_id: 要禁言的 QQ 号 390 | :param int duration: 禁言时长,单位秒,0 表示取消禁言 391 | :return: None 392 | :rtype: None 393 | """ 394 | return super().__getattr__('set_group_ban') \ 395 | (group_id=group_id, user_id=user_id, duration=duration) 396 | 397 | def set_group_anonymous_ban(self, *, group_id, flag, duration=30 * 60): 398 | """ 399 | 群组匿名用户禁言 400 | 401 | ------------ 402 | 403 | :param int group_id: 群号 404 | :param str flag: 要禁言的匿名用户的 flag(需从群消息上报的数据中获得) 405 | :param int duration: 禁言时长,单位秒,**无法取消匿名用户禁言** 406 | :return: None 407 | :rtype: None 408 | """ 409 | return super().__getattr__('set_group_anonymous_ban') \ 410 | (group_id=group_id, flag=flag, duration=duration) 411 | 412 | def set_group_whole_ban(self, *, group_id, enable=True): 413 | """ 414 | 群组全员禁言 415 | 416 | ------------ 417 | 418 | :param int group_id: 群号 419 | :param bool enable: 是否禁言 420 | :return: None 421 | :rtype: None 422 | """ 423 | return super().__getattr__('set_group_whole_ban') \ 424 | (group_id=group_id, enable=enable) 425 | 426 | def set_group_admin(self, *, group_id, user_id, enable=True): 427 | """ 428 | 群组设置管理员 429 | 430 | ------------ 431 | 432 | :param int group_id: 群号 433 | :param user_id: 要设置管理员的 QQ 号 434 | :param enable: True 为设置,False 为取消 435 | :return: None 436 | :rtype: None 437 | """ 438 | return super().__getattr__('set_group_admin') \ 439 | (group_id=group_id, user_id=user_id, enable=enable) 440 | 441 | def set_group_anonymous(self, *, group_id, enable=True): 442 | """ 443 | 群组匿名 444 | 445 | ------------ 446 | 447 | :param int group_id: 群号 448 | :param bool enable: 是否允许匿名聊天 449 | :return: None 450 | :rtype: None 451 | """ 452 | return super().__getattr__('set_group_anonymous') \ 453 | (group_id=group_id, enable=enable) 454 | 455 | def set_group_card(self, *, group_id, user_id, card=None): 456 | """ 457 | 设置群名片(群备注) 458 | 459 | ------------ 460 | 461 | :param int group_id: 群号 462 | :param int user_id: 要设置的 QQ 号 463 | :param str | None card: 群名片内容,不填或空字符串表示删除群名片 464 | :return: None 465 | :rtype: None 466 | """ 467 | return super().__getattr__('set_group_card') \ 468 | (group_id=group_id, user_id=user_id, card=card) 469 | 470 | def set_group_leave(self, *, group_id, is_dismiss=False): 471 | """ 472 | 退出群组 473 | 474 | ------------ 475 | 476 | :param int group_id: 群号 477 | :param bool is_dismiss: 是否解散,如果登录号是群主,则仅在此项为 true 时能够解散 478 | :return: None 479 | :rtype: None 480 | """ 481 | return super().__getattr__('set_group_leave') \ 482 | (group_id=group_id, is_dismiss=is_dismiss) 483 | 484 | def set_group_special_title(self, *, group_id, user_id, special_title, duration=-1): 485 | """ 486 | 设置群组专属头衔 487 | 488 | ------------ 489 | 490 | :param int group_id: 群号 491 | :param int user_id: 要设置的 QQ 号 492 | :param str special_title: 专属头衔,不填或空字符串表示删除专属头衔,只能保留前6个英文与汉字,Emoji 根据字符实际字符长度占用只能放最多3个甚至更少,超出长度部分会被截断 493 | :param int duration: 专属头衔有效期,单位秒,-1 表示永久,不过此项似乎没有效果,可能是只有某些特殊的时间长度有效,有待测试 494 | :return: None 495 | :rtype: None 496 | """ 497 | return super().__getattr__('set_group_special_title') \ 498 | (group_id=group_id, user_id=user_id, special_title=special_title, duration=duration) 499 | 500 | def set_discuss_leave(self, *, discuss_id): 501 | """ 502 | 退出讨论组 503 | 504 | ------------ 505 | 506 | :param int discuss_id: 讨论组 ID(正常情况下看不到,需要从讨论组消息上报的数据中获得) 507 | :return: None 508 | :rtype: None 509 | """ 510 | return super().__getattr__('set_discuss_leave') \ 511 | (discuss_id=discuss_id) 512 | 513 | def set_friend_add_request(self, *, flag, approve=True, remark=None): 514 | """ 515 | 处理加好友请求 516 | 517 | ------------ 518 | 519 | :param str flag: 加好友请求的 flag(需从上报的数据中获得) 520 | :param bool approve: 是否同意请求 521 | :param str remark: 添加后的好友备注(仅在同意时有效) 522 | :return: None 523 | :rtype: None 524 | """ 525 | return super().__getattr__('set_friend_add_request') \ 526 | (flag=flag, approve=approve, remark=remark) 527 | 528 | def set_group_add_request(self, *, flag, type, approve=True, reason=None): 529 | """ 530 | 处理加群请求、群组成员邀请 531 | 532 | ------------ 533 | 534 | :param str flag: 加群请求的 flag(需从上报的数据中获得) 535 | :param str type: `add` 或 `invite`,请求类型(需要和上报消息中的 `sub_type` 字段相符) 536 | :param bool approve: 是否同意请求/邀请 537 | :param str reason: 拒绝理由(仅在拒绝时有效) 538 | :return: None 539 | :rtype: None 540 | """ 541 | return super().__getattr__('set_group_add_request') \ 542 | (flag=flag, type=type, approve=approve, reason=reason) 543 | 544 | def get_login_info(self): 545 | """ 546 | 获取登录号信息 547 | 548 | ------------ 549 | 550 | :return: { "user_id": (QQ 号: int), "nickname": (QQ 昵称: str) } 551 | :rtype: dict[ str, int | str ] 552 | 553 | ------------ 554 | 555 | ========= ========= ========= 556 | 响应数据 557 | ------------------------------- 558 | 数据类型 字段名 说明 559 | ========= ========= ========= 560 | int user_id QQ 号 561 | str nickname QQ 昵称 562 | ========= ========= ========= 563 | """ 564 | return super().__getattr__('get_login_info') \ 565 | () 566 | 567 | def get_stranger_info(self, *, user_id, no_cache=False): 568 | """ 569 | 获取陌生人信息 570 | 571 | ------------ 572 | 573 | :param int user_id: QQ 号(不可以是登录号) 574 | :param bool no_cache: 是否不使用缓存(使用缓存可能更新不及时,但响应更快) 575 | :return: { "user_id": (QQ 号: int), "nickname": (昵称: str), "sex": (性别: str in ['male', 'female', 'unknown']), "age": (年龄: int) } 576 | :rtype: dict[ str, int | str ] 577 | 578 | ------------ 579 | 580 | ======== ========= ====================================== 581 | 响应数据 582 | ----------------------------------------------------------- 583 | 数据类型 字段名 说明 584 | ======== ========= ====================================== 585 | int user_id QQ 号 586 | str nickname 昵称 587 | str sex 性别,`male` 或 `female` 或 `unknown` 588 | int age 年龄 589 | ======== ========= ====================================== 590 | 591 | """ 592 | return super().__getattr__('get_stranger_info') \ 593 | (user_id=user_id, no_cache=no_cache) 594 | 595 | def get_group_list(self): 596 | """ 597 | 获取群列表 598 | 599 | ------------ 600 | 601 | :return: [{ "group_id": (群号: int), "group_name": (群名称: str) }, ...] 602 | :rtype: list[ dict[ str, int | str ] ] 603 | 604 | ------------ 605 | 606 | ======== =========== ========= 607 | 响应数据 608 | -------------------------------- 609 | 数据类型 字段名 说明 610 | ======== =========== ========= 611 | int group_id 群号 612 | str group_name 群名称 613 | ======== =========== ========= 614 | 615 | """ 616 | return super().__getattr__('get_group_list') \ 617 | () 618 | 619 | def get_group_member_info(self, *, group_id, user_id, no_cache=False): 620 | """ 621 | 获取群成员信息 622 | 623 | ------------ 624 | 625 | :param int group_id: 群号 626 | :param int user_id: QQ 号(不可以是登录号) 627 | :param bool no_cache: 是否不使用缓存(使用缓存可能更新不及时,但响应更快) 628 | :return: { "group_id": (群号: int), "user_id": (QQ 号: int), "nickname": (昵称: str), "card": (群名片/备注: str), "sex": (性别: str in ['male', 'female', 'unknown']), "age": (年龄: int), "area": (地区: str), "join_time": (加群时间戳: int), "last_sent_time": (最后发言时间戳: int), "level": (成员等级: str), "role": (角色: str in ['owner', 'admin', 'member']), "unfriendly": (是否不良记录成员: bool), "title": (专属头衔: str), "title_expire_time": (专属头衔过期时间戳: int), "card_changeable": (是否允许修改群名片: bool) } 629 | :rtype: dict[ str, int | str | bool ] 630 | 631 | ------------ 632 | 633 | ======== =================== ====================================== 634 | 响应数据 635 | --------------------------------------------------------------------- 636 | 数据类型 字段名 说明 637 | ======== =================== ====================================== 638 | int group_id 群号 639 | int user_id QQ 号 640 | str nickname 昵称 641 | str card 群名片/备注 642 | str sex 性别,`male` 或 `female` 或 `unknown` 643 | int age 年龄 644 | str area 地区 645 | int join_time 加群时间戳 646 | int last_sent_time 最后发言时间戳 647 | str level 成员等级 648 | str role 角色,`owner` 或 `admin` 或 `member` 649 | bool unfriendly 是否不良记录成员 650 | str title 专属头衔 651 | int title_expire_time 专属头衔过期时间戳 652 | bool card_changeable 是否允许修改群名片 653 | ======== =================== ====================================== 654 | """ 655 | return super().__getattr__('get_group_member_info') \ 656 | (group_id=group_id, user_id=user_id, no_cache=no_cache) 657 | 658 | def get_group_member_list(self, *, group_id): 659 | """ 660 | 获取群成员列表 661 | 662 | ------------ 663 | 664 | :param int group_id: 群号 665 | :return: [{ "group_id": (群号: int), "user_id": (QQ 号: int), "nickname": (昵称: str), "card": (群名片/备注: str), "sex": (性别: str in ['male', 'female', 'unknown']), "age": (年龄: int), "area": (地区: str), "join_time": (加群时间戳: int), "last_sent_time": (最后发言时间戳: int), "level": (成员等级: str), "role": (角色: str in ['owner', 'admin', 'member']), "unfriendly": (是否不良记录成员: bool), "title": (专属头衔: str), "title_expire_time": (专属头衔过期时间戳: int), "card_changeable": (是否允许修改群名片: bool) }, ...] 666 | :rtype: list[ dict[ str, int | str | bool ] ] 667 | 668 | ------------ 669 | 670 | 响应数据以 **列表** 包装的字典的形式提供。`( List[ Dict[ ...] ] )` 671 | 672 | ======== =================== ====================================== 673 | 响应数据 674 | --------------------------------------------------------------------- 675 | 数据类型 字段名 说明 676 | ======== =================== ====================================== 677 | int group_id 群号 678 | int user_id QQ 号 679 | str nickname 昵称 680 | str card 群名片/备注 681 | str sex 性别,`male` 或 `female` 或 `unknown` 682 | int age 年龄 683 | str area 地区 684 | int join_time 加群时间戳 685 | int last_sent_time 最后发言时间戳 686 | str level 成员等级 687 | str role 角色,`owner` 或 `admin` 或 `member` 688 | bool unfriendly 是否不良记录成员 689 | str title 专属头衔 690 | int title_expire_time 专属头衔过期时间戳 691 | bool card_changeable 是否允许修改群名片 692 | ======== =================== ====================================== 693 | 694 | **备注:** 响应内容为包含字典的列表 *( List[ Dict[] ] )* ,每个元素的内容和 `get_group_member_info` 接口相同,但对于同一个群组的同一个成员,获取列表时和获取单独的成员信息时,某些字段可能有所不同,例如 `area`、`title` 等字段在获取列表时无法获得,具体应以单独的成员信息为准。 695 | 696 | """ 697 | return super().__getattr__('get_group_member_list') \ 698 | (group_id=group_id) 699 | 700 | def get_cookies(self): 701 | """ 702 | 获取 Cookies 703 | 704 | ------------ 705 | 706 | :return: { "cookies": (Cookies: str)} 707 | :rtype: dict[ str, str ] 708 | 709 | ------------ 710 | 711 | ======== =========== ========= 712 | 响应数据 713 | -------------------------------- 714 | 数据类型 字段名 说明 715 | ======== =========== ========= 716 | str cookies Cookies 717 | ======== =========== ========= 718 | 719 | """ 720 | return super().__getattr__('get_cookies') \ 721 | () 722 | 723 | def get_csrf_token(self): 724 | """ 725 | 获取 CSRF Token 726 | 727 | ------------ 728 | 729 | :return: { "token": (CSRF Token: int)} 730 | :rtype: dict[ str, int ] 731 | 732 | ------------ 733 | 734 | ======== =========== ========== 735 | 响应数据 736 | --------------------------------- 737 | 数据类型 字段名 说明 738 | ======== =========== ========== 739 | int token CSRF Token 740 | ======== =========== ========== 741 | 742 | """ 743 | return super().__getattr__('get_csrf_token') \ 744 | () 745 | 746 | def get_record(self, *, file, out_format): 747 | """ 748 | 获取语音 749 | 750 | ------------ 751 | 752 | :param str file: 收到的语音文件名,如 `0B38145AA44505000B38145AA4450500.silk` 753 | :param str out_format: 要转换到的格式,目前支持 `mp3`、`amr`、`wma`、`m4a`、`spx`、`ogg`、`wav`、`flac` 754 | :return: { "file": (转换后的语音文件名: str)} 755 | :rtype: dict[ str, str ] 756 | 757 | 758 | ------------ 759 | 760 | 其实并不是真的获取语音,而是转换语音到指定的格式,然后返回语音文件名(`data/record` 目录下)。 761 | 762 | ======== =========== ============================================================= 763 | 响应数据 764 | ------------------------------------------------------------------------------------ 765 | 数据类型 字段名 说明 766 | ======== =========== ============================================================= 767 | str file 转换后的语音文件名,如 `0B38145AA44505000B38145AA4450500.mp3` 768 | ======== =========== ============================================================= 769 | 770 | """ 771 | return super().__getattr__('get_record') \ 772 | (file=file, out_format=out_format) 773 | 774 | def get_status(self): 775 | """ 776 | 获取插件运行状态 777 | 778 | ------------ 779 | 780 | :return: { "good": (正常运行: bool), "app_initialized": (插件已初始化: bool), "app_enabled": (插件已启用: bool), "online": (当前QQ在线: bool), "http_service_good": (HTTP服务正常运行: bool), "ws_service_good": (WebSocket服务正常运行: bool), "ws_reverse_service_good": (反向WebSocket服务正常运行: bool) } 781 | :rtype: dict[ str, bool ] 782 | 783 | ------------ 784 | 785 | ======== ======================== ==================================== 786 | 响应数据 787 | ------------------------------------------------------------------------ 788 | 数据类型 字段名 说明 789 | ======== ======================== ==================================== 790 | bool good 插件状态符合预期,意味着插件已初始化,需要启动的服务都在正常运行,且 QQ 在线 791 | bool app_initialized 插件已初始化 792 | bool app_enabled 插件已启用 793 | bool online 当前 QQ 在线 794 | bool http_service_good `use_http` 配置项为 `yes` 时有此字段,表示 HTTP 服务正常运行 795 | bool ws_service_good `use_ws` 配置项为 `yes` 时有此字段,表示 WebSocket 服务正常运行 796 | bool ws_reverse_service_good `use_ws_reverse` 配置项为 `yes` 时有此字段,表示反向 WebSocket 服务正常运行 797 | ======== ======================== ==================================== 798 | """ 799 | return super().__getattr__('get_status') \ 800 | () 801 | 802 | def get_version_info(self): 803 | """ 804 | 获取酷 Q 及 HTTP API 插件的版本信息 805 | 806 | ------------ 807 | 808 | :return: { "coolq_directory": (酷Q根目录路径: str), "coolq_edition": (酷Q版本: str in ['air', 'pro']), "plugin_version": (API插件版本: str), "plugin_build_number": (API插件build号: int), "plugin_build_configuration": (API插件编译配置: str in ['debug', 'release']) } 809 | :rtype: dict[ str, int | str ] 810 | 811 | 812 | ------------ 813 | 814 | ======== ========================== =============================== 815 | 响应数据 816 | --------------------------------------------------------------------- 817 | 数据类型 字段名 说明 818 | ======== ========================== =============================== 819 | str coolq_directory 酷 Q 根目录路径 820 | str coolq_edition 酷 Q 版本,`air` 或 `pro` 821 | str plugin_version HTTP API 插件版本,例如 2.1.3 822 | int plugin_build_number HTTP API 插件 build 号 823 | str plugin_build_configuration HTTP API 插件编译配置,`debug` 或 `release` 824 | ======== ========================== =============================== 825 | """ 826 | return super().__getattr__('get_version_info') \ 827 | () 828 | 829 | def set_restart(self): 830 | """ 831 | 重启酷 Q,并以当前登录号自动登录(需勾选快速登录) 832 | 833 | ------------ 834 | 835 | :return: None 836 | :rtype: None 837 | """ 838 | return super().__getattr__('set_restart') \ 839 | () 840 | 841 | def set_restart_plugin(self): 842 | """ 843 | 重启 HTTP API 插件 844 | 845 | ------------ 846 | 847 | :return: None 848 | :rtype: None 849 | 850 | ------------ 851 | 852 | 由于重启插件同时需要重启 API 服务,这意味着当前的 API 请求会被中断,因此这个接口会延迟 2 秒重启插件,接口返回的 status 是 async。 853 | 854 | **在Python SDK中返回 None 。** 855 | """ 856 | return super().__getattr__('set_restart_plugin') \ 857 | () 858 | 859 | def clean_data_dir(self, *, data_dir): 860 | """ 861 | 清理数据目录 862 | 863 | ------------ 864 | 865 | :param str data_dir: 收到清理的目录名,支持 `image`、`record`、`show`、`bface` 866 | :return: None 867 | :rtype: None 868 | 869 | ------------ 870 | 871 | 用于清理积攒了太多旧文件的数据目录,如 `image`。 872 | 873 | HTTP API v3.3.4 新增 874 | """ 875 | return super().__getattr__('clean_data_dir') \ 876 | (data_dir=data_dir) 877 | 878 | def clean_data_dir_async(self, *, data_dir): 879 | """ 880 | 清理数据目录 (异步版本) 881 | 882 | ------------ 883 | 884 | :param str data_dir: 收到清理的目录名,支持 `image`、`record`、`show`、`bface` 885 | :return: None 886 | :rtype: None 887 | 888 | ------------ 889 | 890 | 用于清理积攒了太多旧文件的数据目录,如 `image`。 891 | 892 | HTTP API v3.3.4 新增 893 | """ 894 | return super().__getattr__('clean_data_dir_async') \ 895 | (data_dir=data_dir) 896 | 897 | def _get_friend_list(self): 898 | """ 899 | 获取好友列表 (实验性功能) 900 | 901 | ------------ 902 | 903 | :return: [{ "friend_group_id": (好友分组 ID: int), "friend_group_name": (好友分组名称: str), "friends": (分组中的好友: [{ "nickname": (好友昵称: str), "remark": (好友备注: str), "user_id": (好友 QQ 号: int) }, ...]) }, ...] 904 | :rtype: list[ dict[ str, int | str | list[ dict[ str, int | str ] ] ] ] 905 | 906 | ------------ 907 | 908 | 响应数据以 **列表** 包装的字典的形式提供。`( List[ Dict[ ...] ] )` 909 | 910 | ======== ================== =============================== 911 | 响应数据 912 | ------------------------------------------------------------- 913 | 数据类型 字段名 说明 914 | ======== ================== =============================== 915 | int friend_group_id 好友分组 ID 916 | str friend_group_name 好友分组名称 917 | list friends 分组中的好友 918 | ======== ================== =============================== 919 | 920 | 其中,好友信息结构以 **字典** 的形式存储在响应数据中的分组中的好友 `friends` 的 **列表** 中。`( List[ Dict[ ...] ] )` 921 | 922 | ======== ================== =============================== 923 | 好友信息结构 924 | ------------------------------------------------------------- 925 | 数据类型 字段名 说明 926 | ======== ================== =============================== 927 | str nickname 好友昵称 928 | str remark 好友备注 929 | int user_id 好友 QQ 号 930 | ======== ================== =============================== 931 | 932 | """ 933 | return super().__getattr__('_get_friend_list') \ 934 | () 935 | 936 | def send(self, context, message, **kwargs): 937 | """ 938 | 便捷回复。会根据传入的context自动判断回复对象 939 | ------------ 940 | :param dict context: 事件收到的content 941 | :return: None 942 | :rtype: None 943 | ------------ 944 | """ 945 | context = context.copy() 946 | context['message'] = message 947 | context.update(kwargs) 948 | if 'message_type' not in context: 949 | if 'group_id' in context: 950 | context['message_type'] = 'group' 951 | elif 'discuss_id' in context: 952 | context['message_type'] = 'discuss' 953 | elif 'user_id' in context: 954 | context['message_type'] = 'private' 955 | return super().__getattr__('send_msg')(**context) 956 | 957 | -------------------------------------------------------------------------------- /demo.py: -------------------------------------------------------------------------------- 1 | from cqhttp import CQHttp, Error 2 | 3 | bot = CQHttp(api_root='http://127.0.0.1:5700/', 4 | access_token='123', 5 | secret='abc') 6 | 7 | 8 | @bot.on_message 9 | def handle_msg(event): 10 | try: 11 | # 下面这句等价于 bot.send_private_msg(user_id=event['user_id'], 12 | # message='你好呀,下面一条是你刚刚发的:') 13 | bot.send(event, '你好呀,下面一条是你刚刚发的:') 14 | except Error as e: 15 | print('发送失败,错误码:{}'.format(e.retcode)) 16 | 17 | # 返回 dict 给 CQHTTP 插件,走快速回复途径 18 | return {'reply': event['message'], 'at_sender': False} 19 | 20 | 21 | @bot.on_notice('group_increase') 22 | def handle_group_increase(event): 23 | info = bot.get_group_member_info(group_id=event.group_id, 24 | user_id=event.user_id) 25 | nickname = info['nickname'] 26 | name = nickname if nickname else '新人' 27 | bot.send(event, message='欢迎{}~'.format(name)) 28 | 29 | 30 | @bot.on_request('group', 'friend') 31 | def handle_group_request(event): 32 | if event['comment'] != 'some-secret': 33 | # 验证信息不符,拒绝 34 | return {'approve': False, 'reason': '你填写的验证信息有误'} 35 | return {'approve': True} 36 | 37 | 38 | bot.run(host='127.0.0.1', port=8080) 39 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | with open('README.md', 'r', encoding='utf-8') as f: 4 | long_description = f.read() 5 | 6 | setup( 7 | name='cqhttp', 8 | version='1.3.1', 9 | packages=['cqhttp'], 10 | url='https://github.com/cqmoe/python-cqhttp', 11 | license='MIT License', 12 | author='Richard Chien', 13 | author_email='richardchienthebest@gmail.com', 14 | description='A Python SDK for CQHTTP.', 15 | long_description=long_description, 16 | long_description_content_type="text/markdown", 17 | install_requires=['Flask', 'requests'], 18 | python_requires='>=3.5', 19 | platforms='any', 20 | classifiers=( 21 | "Programming Language :: Python :: 3", 22 | "License :: OSI Approved :: MIT License", 23 | "Operating System :: OS Independent", 24 | ), 25 | ) 26 | --------------------------------------------------------------------------------