diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index fdfac5b0..f3fd32b6 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -1,11 +1,11 @@ const path = require("path"); -module.exports = context => ({ +module.exports = (context) => ({ base: process.env.VUEPRESS_BASE || "/", title: "NoneBot", description: "跨平台 Python 异步 QQ 机器人框架", markdown: { - lineNumbers: true + lineNumbers: true, }, /** * Extra tags to be injected to the page HTML `` @@ -21,26 +21,26 @@ module.exports = context => ({ ["meta", { name: "apple-mobile-web-app-capable", content: "yes" }], [ "meta", - { name: "apple-mobile-web-app-status-bar-style", content: "black" } + { name: "apple-mobile-web-app-status-bar-style", content: "black" }, ], [ "link", - { rel: "apple-touch-icon", href: "/icons/apple-touch-icon-180x180.png" } + { rel: "apple-touch-icon", href: "/icons/apple-touch-icon-180x180.png" }, ], [ "link", { rel: "mask-icon", href: "/icons/safari-pinned-tab.svg", - color: "#ea5252" - } + color: "#ea5252", + }, ], [ "meta", { name: "msapplication-TileImage", - content: "/icons/mstile-150x150.png" - } + content: "/icons/mstile-150x150.png", + }, ], ["meta", { name: "msapplication-TileColor", content: "#ea5252" }], [ @@ -48,16 +48,16 @@ module.exports = context => ({ { rel: "stylesheet", href: - "https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5/css/all.min.css" - } - ] + "https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5/css/all.min.css", + }, + ], ], locales: { "/": { lang: "zh-CN", title: "NoneBot", - description: "跨平台 Python 异步 QQ 机器人框架" - } + description: "跨平台 Python 异步 QQ 机器人框架", + }, }, theme: "nonebot", @@ -83,7 +83,7 @@ module.exports = context => ({ { text: "进阶", link: "/advanced/" }, { text: "API", link: "/api/" }, { text: "商店", link: "/store" }, - { text: "更新日志", link: "/changelog" } + { text: "更新日志", link: "/changelog" }, ], sidebarDepth: 2, sidebar: { @@ -97,8 +97,8 @@ module.exports = context => ({ "installation", "getting-started", "creating-a-project", - "basic-configuration" - ] + "basic-configuration", + ], }, { title: "编写插件", @@ -109,15 +109,20 @@ module.exports = context => ({ "creating-a-plugin", "creating-a-matcher", "creating-a-handler", - "end-or-start" - ] + "end-or-start", + ], }, { title: "协议适配", collapsable: false, sidebar: "auto", - children: ["cqhttp-guide", "ding-guide", "mirai-guide"] - } + children: [ + "cqhttp-guide", + "ding-guide", + "mirai-guide", + "feishu-guide", + ], + }, ], "/advanced/": [ { @@ -130,15 +135,15 @@ module.exports = context => ({ "permission", "runtime-hook", "export-and-require", - "overloaded-handlers" - ] + "overloaded-handlers", + ], }, { title: "发布", collapsable: false, sidebar: "auto", - children: ["publish-plugin"] - } + children: ["publish-plugin"], + }, ], "/api/": [ { @@ -148,86 +153,90 @@ module.exports = context => ({ children: [ { title: "nonebot 模块", - path: "nonebot" + path: "nonebot", }, { title: "nonebot.config 模块", - path: "config" + path: "config", }, { title: "nonebot.plugin 模块", - path: "plugin" + path: "plugin", }, { title: "nonebot.message 模块", - path: "message" + path: "message", }, { title: "nonebot.matcher 模块", - path: "matcher" + path: "matcher", }, { title: "nonebot.handler 模块", - path: "handler" + path: "handler", }, { title: "nonebot.rule 模块", - path: "rule" + path: "rule", }, { title: "nonebot.permission 模块", - path: "permission" + path: "permission", }, { title: "nonebot.log 模块", - path: "log" + path: "log", }, { title: "nonebot.utils 模块", - path: "utils" + path: "utils", }, { title: "nonebot.typing 模块", - path: "typing" + path: "typing", }, { title: "nonebot.exception 模块", - path: "exception" + path: "exception", }, { title: "nonebot.drivers 模块", - path: "drivers/" + path: "drivers/", }, { title: "nonebot.drivers.fastapi 模块", - path: "drivers/fastapi" + path: "drivers/fastapi", }, { title: "nonebot.drivers.quart 模块", - path: "drivers/quart" + path: "drivers/quart", }, { title: "nonebot.adapters 模块", - path: "adapters/" + path: "adapters/", }, { title: "nonebot.adapters.cqhttp 模块", - path: "adapters/cqhttp" + path: "adapters/cqhttp", }, { title: "nonebot.adapters.ding 模块", - path: "adapters/ding" + path: "adapters/ding", }, { title: "nonebot.adapters.mirai 模块", - path: "adapters/mirai" - } - ] - } - ] - } - } - } + path: "adapters/mirai", + }, + { + title: "nonebot.adapters.feishu 模块", + path: "adapters/feishu", + }, + ], + }, + ], + }, + }, + }, }, plugins: [ @@ -239,9 +248,9 @@ module.exports = context => ({ serviceWorker: true, updatePopup: { message: "发现新内容", - buttonText: "刷新" - } - } + buttonText: "刷新", + }, + }, ], [ "versioning", @@ -250,16 +259,16 @@ module.exports = context => ({ pagesSourceDir: path.resolve(context.sourceDir, "..", "pages"), onNewVersion(version, versionDestPath) { console.log(`Created version ${version} in ${versionDestPath}`); - } - } + }, + }, ], [ "container", { type: "vue", before: '
',
-        after: "
" - } - ] - ] + after: "", + }, + ], + ], }); diff --git a/docs/.vuepress/public/adapters.json b/docs/.vuepress/public/adapters.json index 4788e1b2..aa4c2b70 100644 --- a/docs/.vuepress/public/adapters.json +++ b/docs/.vuepress/public/adapters.json @@ -30,5 +30,13 @@ "desc": "在原 CQHTTP Adapter 的基础上进行修改以便更好地适配 go-cqhttp", "author": "Jigsaw111", "repo": "Jigsaw111/nonebot-adapter-gocq" + }, + { + "id": "nonebot_adapter_feishu", + "link": "nonebot-adapter-feishu", + "name": "feishu", + "desc": "飞书协议", + "author": "StarHeartHunt", + "repo": "nonebot/nonebot2/tree/master/packages/nonebot-adapter-feishu" } -] \ No newline at end of file +] diff --git a/docs/README.md b/docs/README.md index c96de429..1ee526fb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,5 +11,5 @@ features: details: 精心设计的消息处理流程使得你可以很方便地将原型扩充为具有大量实用功能的完整聊天机器人,并持续保证扩展性。 - title: 高性能 details: 采用异步 I/O,利用 WebSocket 进行通信,以获得极高的性能;同时,支持使用多账号同时接入,减少业务宕机的可能。 -footer: MIT Licensed | Copyright © 2018 - 2020 NoneBot Team +footer: MIT Licensed | Copyright © 2018 - 2021 NoneBot Team --- diff --git a/docs/api/README.md b/docs/api/README.md index 3d5a6497..26d4a0f5 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -59,3 +59,6 @@ * [nonebot.adapters.mirai](adapters/mirai.html) + + + * [nonebot.adapters.feishu](adapters/feishu.html) diff --git a/docs/api/adapters/feishu.md b/docs/api/adapters/feishu.md new file mode 100644 index 00000000..7cbb311d --- /dev/null +++ b/docs/api/adapters/feishu.md @@ -0,0 +1,251 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + +# NoneBot.adapters.feishu 模块 + +# NoneBot.adapters.feishu.config 模块 + + +## _class_ `Config` + +钉钉配置类 + + +* **配置项** + + + * `app_id` / `feishu_app_id`: 飞书开放平台后台“凭证与基础信息”处给出的 App ID + + + * `app_secret` / `feishu_app_secret`: 飞书开放平台后台“凭证与基础信息”处给出的 App Secret + + + * `encrypt_key` / `feishu_encrypt_key`: 飞书开放平台后台“事件订阅”处设置的 Encrypt Key + + + * `verification_token` / `feishu_verification_token`: 飞书开放平台后台“事件订阅”处设置的 Verification Token + + + * `tenant_access_token` / `feishu_tenant_access_token`: 请求飞书 API 后返回的租户密钥 + + +# NoneBot.adapters.feishu.exception 模块 + + +## _exception_ `ActionFailed` + +基类:[`nonebot.exception.ActionFailed`](../exception.md#nonebot.exception.ActionFailed), `nonebot.adapters.feishu.exception.FeishuAdapterException` + + +* **说明** + + API 请求返回错误信息。 + + + +* **参数** + + + * `retcode: Optional[int]`: 错误码 + + + +## _exception_ `NetworkError` + +基类:[`nonebot.exception.NetworkError`](../exception.md#nonebot.exception.NetworkError), `nonebot.adapters.feishu.exception.FeishuAdapterException` + + +* **说明** + + 网络错误。 + + + +* **参数** + + + * `retcode: Optional[int]`: 错误码 + + +# NoneBot.adapters.feishu.bot 模块 + + +## `_check_at_me(bot, event)` + + +* **说明** + + 检查消息开头或结尾是否存在 @机器人,去除并赋值 `event.reply`, `event.to_me` + + + +* **参数** + + + * `bot: Bot`: Bot 对象 + + + * `event: Event`: Event 对象 + + + +## `_check_nickname(bot, event)` + + +* **说明** + + 检查消息开头是否存在昵称,去除并赋值 `event.to_me` + + + +* **参数** + + + * `bot: Bot`: Bot 对象 + + + * `event: Event`: Event 对象 + + + +## `_handle_api_result(result)` + + +* **说明** + + 处理 API 请求返回值。 + + + +* **参数** + + + * `result: Optional[Dict[str, Any]]`: API 返回数据 + + + +* **返回** + + + * `Any`: API 调用返回数据 + + + +* **异常** + + + * `ActionFailed`: API 调用失败 + + + +## _class_ `Bot` + +基类:[`nonebot.adapters._base.Bot`](README.md#nonebot.adapters._base.Bot) + +飞书 协议 Bot 适配。继承属性参考 [BaseBot](./#class-basebot) 。 + + +### _async_ `handle_message(message)` + + +* **说明** + + 处理事件并转换为 [Event](#class-event) + + + +### _async_ `call_api(api, **data)` + + +* **说明** + + 调用 飞书 协议 API + + + +* **参数** + + + * `api: str`: API 名称 + + + * `**data: Any`: API 参数 + + + +* **返回** + + + * `Any`: API 调用返回数据 + + + +* **异常** + + + * `NetworkError`: 网络错误 + + + * `ActionFailed`: API 调用失败 + + +# NoneBot.adapters.feishu.message 模块 + + +## _class_ `MessageSegment` + +基类:[`nonebot.adapters._base.MessageSegment`](README.md#nonebot.adapters._base.MessageSegment)[`Message`] + +飞书 协议 MessageSegment 适配。具体方法参考协议消息段类型或源码。 + + +## _class_ `Message` + +基类:[`nonebot.adapters._base.Message`](README.md#nonebot.adapters._base.Message)[`nonebot.adapters.feishu.message.MessageSegment`] + +飞书 协议 Message 适配。 + + +## _class_ `MessageSerializer` + +基类:`object` + +飞书 协议 Message 序列化器。 + + +## _class_ `MessageDeserializer` + +基类:`object` + +飞书 协议 Message 反序列化器。 + +# NoneBot.adapters.feishu.event 模块 + + +## _class_ `Event` + +基类:[`nonebot.adapters._base.Event`](README.md#nonebot.adapters._base.Event) + +飞书协议事件。各事件字段参考 + +``` +`飞书文档`_ +``` + + + +## `get_event_model(event_name)` + + +* **说明** + + 根据事件名获取对应 `Event Model` 及 `FallBack Event Model` 列表 + + + +* **返回** + + + * `List[Type[Event]]` diff --git a/docs/guide/feishu-guide.md b/docs/guide/feishu-guide.md new file mode 100644 index 00000000..342ea3af --- /dev/null +++ b/docs/guide/feishu-guide.md @@ -0,0 +1,57 @@ +# 飞书机器人使用指南 + +基于飞书开放平台事件回调与 API 进行机器人适配,目前仅适配企业自建应用。 + +## 安装 NoneBot 飞书 适配器 + +```bash +pip install nonebot-adapter-feishu +``` + +## 创建应用与启用应用“机器人”能力 + +::: tip +此部分可参考[飞书开放平台-快速开发机器人-创建应用](https://open.feishu.cn/document/home/develop-a-bot-in-5-minutes/create-an-app)部分文档。 + +::: + +## 开启应用权限 + +应用拥有所需权限后,才能调用飞书接口获取相关信息。如果需要用到所有飞书平台的 API,请开启所有应用权限。 + +在仅群聊功能的情况下,需要为应用开启用户、消息、通讯录和群聊权限组所有权限。 + +## 配置飞书事件订阅 + +::: tip + +在添加事件订阅时请注意,带有**(历史版本)**字样的事件的格式为**不受支持的旧版事件格式**,请使用对应的**新版事件(不带历史版本字样)作为替代**。 + +::: + +目前,飞书适配器支持以下事件: +| 事件名称 | 事件描述| +| ---- | ---- | +|接收消息|机器人接收到用户发送的消息。| +|消息已读|用户阅读机器人发送的单聊消息。| +|群解散|群组被解散。| +|群配置更改|群组配置被修改后触发此事件,包含:群主转移、群基本信息修改、群权限修改。| +|机器人进群|机器人被添加至群聊。| +|机器人被移出群|机器人被移出群聊。| +|用户进群|新用户进群。| +|撤销拉用户进群|撤销拉用户进群。| +|用户被移出群|用户主动退群或被移出群聊。| + +## 在 NoneBot 配置中添加相应配置 + +在 `.env` 文件中添加以下部分 + +``` +APP_ID= +APP_SECRET= +VERIFICATION_TOKEN= +``` + +复制所创建应用**“凭证和基础信息”**中的**App ID**与**App Secret**及**“事件订阅”**中的**Verification Token**,替换上面相应的配置的值。 + +大功告成!现在可以试试向机器人发送消息进行测试了。 diff --git a/docs_build/README.rst b/docs_build/README.rst index 745fc781..35d9368c 100644 --- a/docs_build/README.rst +++ b/docs_build/README.rst @@ -21,3 +21,4 @@ NoneBot Api Reference - `nonebot.adapters.cqhttp `_ - `nonebot.adapters.ding `_ - `nonebot.adapters.mirai `_ + - `nonebot.adapters.feishu `_ diff --git a/docs_build/adapters/feishu.rst b/docs_build/adapters/feishu.rst new file mode 100644 index 00000000..821b6908 --- /dev/null +++ b/docs_build/adapters/feishu.rst @@ -0,0 +1,48 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + +NoneBot.adapters.feishu 模块 +=========================== + +.. automodule:: nonebot.adapters.feishu + +NoneBot.adapters.feishu.config 模块 +=================================== + +.. automodule:: nonebot.adapters.feishu.config + :members: + +NoneBot.adapters.feishu.exception 模块 +===================================== + +.. automodule:: nonebot.adapters.feishu.exception + :members: + :show-inheritance: + + +NoneBot.adapters.feishu.bot 模块 +=============================== + +.. automodule:: nonebot.adapters.feishu.bot + :members: + :private-members: + :show-inheritance: + + +NoneBot.adapters.feishu.message 模块 +=================================== + +.. automodule:: nonebot.adapters.feishu.message + :members: + :private-members: + :show-inheritance: + + +NoneBot.adapters.feishu.event 模块 +================================= + +.. automodule:: nonebot.adapters.feishu.event + :members: + :show-inheritance: diff --git a/packages/nonebot-adapter-feishu/README.md b/packages/nonebot-adapter-feishu/README.md new file mode 100644 index 00000000..8d9c629c --- /dev/null +++ b/packages/nonebot-adapter-feishu/README.md @@ -0,0 +1,11 @@ +

+ nonebot +

+ +
+ +# NoneBot-Adapter-Feishu + +_✨ 飞书协议适配 ✨_ + +
diff --git a/packages/nonebot-adapter-feishu/nonebot/__init__.py b/packages/nonebot-adapter-feishu/nonebot/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/packages/nonebot-adapter-feishu/nonebot/adapters/__init__.py b/packages/nonebot-adapter-feishu/nonebot/adapters/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/packages/nonebot-adapter-feishu/nonebot/adapters/feishu/__init__.py b/packages/nonebot-adapter-feishu/nonebot/adapters/feishu/__init__.py new file mode 100644 index 00000000..532b2d9c --- /dev/null +++ b/packages/nonebot-adapter-feishu/nonebot/adapters/feishu/__init__.py @@ -0,0 +1,4 @@ +from .bot import Bot +from .event import * +from .event import Event +from .message import Message, MessageSegment diff --git a/packages/nonebot-adapter-feishu/nonebot/adapters/feishu/bot.py b/packages/nonebot-adapter-feishu/nonebot/adapters/feishu/bot.py new file mode 100644 index 00000000..9045dbc2 --- /dev/null +++ b/packages/nonebot-adapter-feishu/nonebot/adapters/feishu/bot.py @@ -0,0 +1,355 @@ +import httpx +import json +import re + +from aiocache import cached, Cache +from aiocache.serializers import PickleSerializer +from typing import Any, Dict, Tuple, Union, Optional, TYPE_CHECKING + +from nonebot.log import logger +from nonebot.typing import overrides +from nonebot.message import handle_event +from nonebot.adapters import Bot as BaseBot +from nonebot.drivers import Driver, HTTPRequest, HTTPResponse + +from .config import Config as FeishuConfig +from .event import Event, GroupMessageEvent, MessageEvent, PrivateMessageEvent, Reply, get_event_model +from .exception import ActionFailed, ApiNotAvailable, NetworkError +from .message import Message, MessageSegment, MessageSerializer +from .utils import log, AESCipher + +if TYPE_CHECKING: + from nonebot.config import Config + + +def _check_at_me(bot: "Bot", event: "Event"): + """ + :说明: + + 检查消息开头或结尾是否存在 @机器人,去除并赋值 ``event.reply``, ``event.to_me`` + + :参数: + + * ``bot: Bot``: Bot 对象 + * ``event: Event``: Event 对象 + """ + if not isinstance(event, MessageEvent): + return + + message = event.get_message() + # ensure message not empty + if not message: + message.append(MessageSegment.text("")) + + if event.event.message.chat_type == "p2p": + event.to_me = True + + for index, segment in enumerate(message): + if segment.type == "at" and segment.data.get( + "user_name") in bot.config.nickname: + event.to_me = True + del event.event.message.content[index] + return + elif segment.type == "text" and segment.data.get("mentions"): + for mention in segment.data["mentions"].values(): + if mention["name"] in bot.config.nickname: + event.to_me = True + segment.data["text"] = segment.data["text"].replace( + f"@{mention['name']}", "") + segment.data["text"] = segment.data["text"].lstrip() + break + else: + continue + break + + if not message: + message.append(MessageSegment.text("")) + + +def _check_nickname(bot: "Bot", event: "Event"): + """ + :说明: + + 检查消息开头是否存在昵称,去除并赋值 ``event.to_me`` + + :参数: + + * ``bot: Bot``: Bot 对象 + * ``event: Event``: Event 对象 + """ + if not isinstance(event, MessageEvent): + return + + first_msg_seg = event.get_message()[0] + if first_msg_seg.type != "text": + return + + first_text = first_msg_seg.data["text"] + + nicknames = set(filter(lambda n: n, bot.config.nickname)) + if nicknames: + # check if the user is calling me with my nickname + nickname_regex = "|".join(nicknames) + m = re.search(rf"^({nickname_regex})([\s,,]*|$)", first_text, + re.IGNORECASE) + if m: + nickname = m.group(1) + log("DEBUG", f"User is calling me {nickname}") + event.to_me = True + first_msg_seg.data["text"] = first_text[m.end():] + + +def _handle_api_result(result: Optional[Dict[str, Any]]) -> Any: + """ + :说明: + + 处理 API 请求返回值。 + + :参数: + + * ``result: Optional[Dict[str, Any]]``: API 返回数据 + + :返回: + + - ``Any``: API 调用返回数据 + + :异常: + + - ``ActionFailed``: API 调用失败 + """ + if isinstance(result, dict): + if result.get("code") != 0: + raise ActionFailed(**result) + return result.get("data") + + +class Bot(BaseBot): + """ + 飞书 协议 Bot 适配。继承属性参考 `BaseBot <./#class-basebot>`_ 。 + """ + + @property + def type(self) -> str: + return "feishu" + + @property + def api_root(self) -> str: + return "https://open.feishu.cn/open-apis/" + + @classmethod + def register(cls, driver: Driver, config: "Config"): + super().register(driver, config) + cls.feishu_config = FeishuConfig(**config.dict()) + + @classmethod + @overrides(BaseBot) + async def check_permission( + cls, driver: Driver, request: HTTPRequest + ) -> Tuple[Optional[str], Optional[HTTPResponse]]: + if not isinstance(request, HTTPRequest): + log("WARNING", + "Unsupported connection type, available type: `http`") + return None, HTTPResponse( + 405, b"Unsupported connection type, available type: `http`") + + encrypt_key = cls.feishu_config.encrypt_key + if encrypt_key: + encrypted = json.loads(request.body)["encrypt"] + decrypted = AESCipher(encrypt_key).decrypt_string(encrypted) + data = json.loads(decrypted) + else: + data = json.loads(request.body) + + challenge = data.get("challenge") + if challenge: + return data.get("token"), HTTPResponse( + 200, + json.dumps({ + "challenge": challenge + }).encode()) + + schema = data.get("schema") + if not schema: + return None, HTTPResponse( + 400, + b"Missing `schema` in POST body, only accept event of version 2.0" + ) + + headers = data.get("header") + if headers: + token = headers.get("token") + app_id = headers.get("app_id") + else: + log("WARNING", "Missing `header` in POST body") + return None, HTTPResponse(400, b"Missing `header` in POST body") + + if not token: + log("WARNING", "Missing `verification token` in POST body") + return None, HTTPResponse( + 400, b"Missing `verification token` in POST body") + else: + if token != cls.feishu_config.verification_token: + log("WARNING", "Verification token check failed") + return None, HTTPResponse(403, + b"Verification token check failed") + + return app_id, HTTPResponse(200, b'') + + async def handle_message(self, message: bytes): + """ + :说明: + + 处理事件并转换为 `Event <#class-event>`_ + """ + data = json.loads(message) + if data.get("type") == "url_verification": + return + + try: + header = data["header"] + event_type = header["event_type"] + if data.get("event"): + if data["event"].get("message"): + event_type += f".{data['event']['message']['chat_type']}" + + models = get_event_model(event_type) + for model in models: + try: + event = model.parse_obj(data) + break + except Exception as e: + log("DEBUG", "Event Parser Error", e) + else: + event = Event.parse_obj(data) + + _check_at_me(self, event) + _check_nickname(self, event) + + await handle_event(self, event) + except Exception as e: + logger.opt(colors=True, exception=e).error( + f"Failed to handle event. Raw: {message}" + ) + + def _construct_url(self, path: str) -> str: + return self.api_root + path + + @cached(ttl=60 * 60, + cache=Cache.MEMORY, + key="_feishu_tenant_access_token", + serializer=PickleSerializer()) + async def _fetch_tenant_access_token(self) -> str: + try: + async with httpx.AsyncClient() as client: + response = await client.post( + self._construct_url( + "auth/v3/tenant_access_token/internal/"), + json={ + "app_id": self.feishu_config.app_id, + "app_secret": self.feishu_config.app_secret + }, + timeout=self.config.api_timeout) + + if 200 <= response.status_code < 300: + result = response.json() + return result["tenant_access_token"] + else: + raise NetworkError(f"HTTP request received unexpected " + f"status code: {response.status_code}") + except httpx.InvalidURL: + raise NetworkError("API root url invalid") + except httpx.HTTPError: + raise NetworkError("HTTP request failed") + + @overrides(BaseBot) + async def _call_api(self, api: str, **data) -> Any: + log("DEBUG", f"Calling API {api}") + if isinstance(self.request, HTTPRequest): + if not self.api_root: + raise ApiNotAvailable + + headers = {} + self.feishu_config.tenant_access_token = await self._fetch_tenant_access_token( + ) + headers[ + "Authorization"] = "Bearer " + self.feishu_config.tenant_access_token + + try: + async with httpx.AsyncClient( + timeout=self.config.api_timeout) as client: + response = await client.send( + httpx.Request(data["method"], + self.api_root + api, + json=data.get("body", {}), + params=data.get("query", {}), + headers=headers)) + if 200 <= response.status_code < 300: + result = response.json() + return _handle_api_result(result) + raise NetworkError(f"HTTP request received unexpected " + f"status code: {response.status_code} " + f"response body: {response.text}") + except httpx.InvalidURL: + raise NetworkError("API root url invalid") + except httpx.HTTPError: + raise NetworkError("HTTP request failed") + + @overrides(BaseBot) + async def call_api(self, api: str, **data) -> Any: + """ + :说明: + + 调用 飞书 协议 API + + :参数: + + * ``api: str``: API 名称 + * ``**data: Any``: API 参数 + + :返回: + + - ``Any``: API 调用返回数据 + + :异常: + + - ``NetworkError``: 网络错误 + - ``ActionFailed``: API 调用失败 + """ + return await super().call_api(api, **data) + + @overrides(BaseBot) + async def send(self, + event: Event, + message: Union[str, Message, MessageSegment], + at_sender: bool = False, + **kwargs) -> Any: + msg = message if isinstance(message, Message) else Message(message) + + if isinstance(event, GroupMessageEvent): + receive_id, receive_id_type = event.event.message.chat_id, 'chat_id' + elif isinstance(event, PrivateMessageEvent): + receive_id, receive_id_type = event.get_user_id(), 'union_id' + else: + raise ValueError( + "Cannot guess `receive_id` and `receive_id_type` to reply!") + + at_sender = at_sender and bool(event.get_user_id()) + + if at_sender and receive_id_type != "union_id": + msg = MessageSegment.at(event.get_user_id()) + " " + msg + + msg_type, content = MessageSerializer(msg).serialize() + + params = { + "method": "POST", + "query": { + "receive_id_type": receive_id_type + }, + "body": { + "receive_id": receive_id, + "content": content, + "msg_type": msg_type + } + } + + return await self.call_api(f"im/v1/messages", **params) diff --git a/packages/nonebot-adapter-feishu/nonebot/adapters/feishu/config.py b/packages/nonebot-adapter-feishu/nonebot/adapters/feishu/config.py new file mode 100644 index 00000000..853f553e --- /dev/null +++ b/packages/nonebot-adapter-feishu/nonebot/adapters/feishu/config.py @@ -0,0 +1,28 @@ +from typing import Optional + +from pydantic import BaseModel, Field + + +class Config(BaseModel): + """ + 钉钉配置类 + + :配置项: + + - ``app_id`` / ``feishu_app_id``: 飞书开放平台后台“凭证与基础信息”处给出的 App ID + - ``app_secret`` / ``feishu_app_secret``: 飞书开放平台后台“凭证与基础信息”处给出的 App Secret + - ``encrypt_key`` / ``feishu_encrypt_key``: 飞书开放平台后台“事件订阅”处设置的 Encrypt Key + - ``verification_token`` / ``feishu_verification_token``: 飞书开放平台后台“事件订阅”处设置的 Verification Token + - ``tenant_access_token`` / ``feishu_tenant_access_token``: 请求飞书 API 后返回的租户密钥 + """ + app_id: Optional[str] = Field(default=None, alias="feishu_app_id") + app_secret: Optional[str] = Field(default=None, alias="feishu_app_secret") + encrypt_key: Optional[str] = Field(default=None, alias="feishu_encrypt_key") + verification_token: Optional[str] = Field(default=None, + alias="feishu_verification_token") + tenant_access_token: Optional[str] = Field( + default=None, alias="feishu_tenant_access_token") + + class Config: + extra = "ignore" + allow_population_by_field_name = True diff --git a/packages/nonebot-adapter-feishu/nonebot/adapters/feishu/event.py b/packages/nonebot-adapter-feishu/nonebot/adapters/feishu/event.py new file mode 100644 index 00000000..5abeda95 --- /dev/null +++ b/packages/nonebot-adapter-feishu/nonebot/adapters/feishu/event.py @@ -0,0 +1,415 @@ +import inspect +import json + +from typing import Any, Dict, List, Literal, Optional, Type +from pygtrie import StringTrie +from pydantic import BaseModel, root_validator, Field + +from nonebot.adapters import Event as BaseEvent +from nonebot.typing import overrides + +from .message import Message, MessageDeserializer + + +class EventHeader(BaseModel): + event_id: str + event_type: str + create_time: str + token: str + app_id: str + tenant_key: str + + +class Event(BaseEvent): + """ + 飞书协议事件。各事件字段参考 `飞书文档`_ + + .. _飞书事件列表文档: + https://open.feishu.cn/document/ukTMukTMukTM/uYDNxYjL2QTM24iN0EjN/event-list + """ + __event__ = "" + schema_: str = Field("", alias='schema') + header: EventHeader + event: Any + + @overrides(BaseEvent) + def get_type(self) -> str: + return self.header.event_type + + @overrides(BaseEvent) + def get_event_name(self) -> str: + return self.header.event_type + + @overrides(BaseEvent) + def get_event_description(self) -> str: + return str(self.dict()) + + @overrides(BaseEvent) + def get_message(self) -> Message: + raise ValueError("Event has no message!") + + @overrides(BaseEvent) + def get_plaintext(self) -> str: + raise ValueError("Event has no plaintext!") + + @overrides(BaseEvent) + def get_user_id(self) -> str: + raise ValueError("Event has no user_id!") + + @overrides(BaseEvent) + def get_session_id(self) -> str: + raise ValueError("Event has no session_id!") + + @overrides(BaseEvent) + def is_tome(self) -> bool: + return False + + +class UserId(BaseModel): + union_id: str + user_id: str + open_id: str + + +class Sender(BaseModel): + sender_id: UserId + sender_type: str + tenant_key: str + + +class ReplySender(BaseModel): + id: str + id_type: str + sender_type: str + tenant_key: str + + +class Mention(BaseModel): + key: str + id: UserId + name: str + tenant_key: str + + +class ReplyMention(BaseModel): + id: str + id_type: str + key: str + name: str + tenant_key: str + + +class MessageBody(BaseModel): + content: str + + +class Reply(BaseModel): + message_id: str + root_id: Optional[str] + parent_id: Optional[str] + msg_type: str + create_time: str + update_time: str + deleted: bool + updated: bool + chat_id: str + sender: ReplySender + body: MessageBody + mentions: List[ReplyMention] + upper_message_id: Optional[str] + + class Config: + extra = "allow" + + +class EventMessage(BaseModel): + message_id: str + root_id: Optional[str] + parent_id: Optional[str] + create_time: str + chat_id: str + chat_type: str + message_type: str + content: Message + mentions: Optional[List[Mention]] + + @root_validator(pre=True) + def parse_message(cls, values: dict): + values["content"] = MessageDeserializer( + values["message_type"], json.loads(values["content"]), + values.get("mentions")).deserialize() + return values + + +class GroupEventMessage(EventMessage): + chat_type: Literal["group"] + + +class PrivateEventMessage(EventMessage): + chat_type: Literal["p2p"] + + +class MessageEventDetail(BaseModel): + sender: Sender + message: EventMessage + + +class GroupMessageEventDetail(MessageEventDetail): + message: GroupEventMessage + + +class PrivateMessageEventDetail(MessageEventDetail): + message: PrivateEventMessage + + +class MessageEvent(Event): + __event__ = "im.message.receive_v1" + event: MessageEventDetail + + to_me: bool = False + """ + :说明: 消息是否与机器人有关 + + :类型: ``bool`` + """ + reply: Optional[Reply] + + @overrides(Event) + def get_type(self) -> Literal["message", "notice"]: + return "message" + + @overrides(Event) + def get_event_name(self) -> str: + return f"{self.get_type()}.{self.event.message.chat_type}" + + @overrides(Event) + def get_event_description(self) -> str: + return ( + f"{self.event.message.message_id} from {self.get_user_id()}" + f"@[{self.event.message.chat_type}:{self.event.message.chat_id}]" + f" {self.get_message()}") + + @overrides(Event) + def get_message(self) -> Message: + return self.event.message.content + + @overrides(Event) + def get_plaintext(self) -> str: + return str(self.get_message()) + + @overrides(Event) + def get_user_id(self) -> str: + return self.event.sender.sender_id.open_id + + @overrides(Event) + def get_session_id(self) -> str: + return f"{self.event.message.chat_type}_{self.event.message.chat_id}_{self.get_user_id()}" + + +class GroupMessageEvent(MessageEvent): + __event__ = "im.message.receive_v1.group" + event: GroupMessageEventDetail + + +class PrivateMessageEvent(MessageEvent): + __event__ = "im.message.receive_v1.p2p" + event: PrivateMessageEventDetail + + +class NoticeEvent(Event): + event: Dict[str, Any] + + @overrides(Event) + def get_type(self) -> Literal["message", "notice"]: + return "notice" + + @overrides(Event) + def get_event_name(self) -> str: + raise ValueError("Event has no name!") + + @overrides(Event) + def get_event_description(self) -> str: + raise ValueError("Event has no description!") + + @overrides(Event) + def get_message(self) -> Message: + raise ValueError("Event has no message!") + + @overrides(Event) + def get_plaintext(self) -> str: + raise ValueError("Event has no plaintext!") + + @overrides(Event) + def get_user_id(self) -> str: + raise ValueError("Event has no user_id!") + + @overrides(Event) + def get_session_id(self) -> str: + raise ValueError("Event has no session_id!") + + +class MessageReader(BaseModel): + reader_id: UserId + read_time: str + tenant_key: str + + +class MessageReadEventDetail(BaseModel): + reader: MessageReader + message_id_list: List[str] + + +class MessageReadEvent(NoticeEvent): + __event__ = "im.message.message_read_v1" + event: MessageReadEventDetail + + +class GroupDisbandedEventDetail(BaseModel): + chat_id: str + operator_id: UserId + external: bool + operator_tenant_key: str + + +class GroupDisbandedEvent(NoticeEvent): + __event__ = "im.chat.disbanded_v1" + event: GroupDisbandedEventDetail + + +class I18nNames(BaseModel): + zh_cn: str + en_us: str + ja_jp: str + + +class ChatChange(BaseModel): + avatar: str + name: str + description: str + i18n_names: I18nNames + add_member_permission: str + share_card_permission: str + at_all_permission: str + edit_permission: str + membership_approval: str + join_message_visibility: str + leave_message_visibility: str + moderation_permission: str + owner_id: UserId + + +class EventModerator(BaseModel): + tenant_key: str + user_id: UserId + + +class ModeratorList(BaseModel): + added_member_list: EventModerator + removed_member_list: EventModerator + + +class GroupConfigUpdatedEventDetail(BaseModel): + chat_id: str + operator_id: UserId + external: bool + operator_tenant_key: str + after_change: ChatChange + before_change: ChatChange + moderator_list: ModeratorList + + +class GroupConfigUpdatedEvent(NoticeEvent): + __event__ = "im.chat.updated_v1" + event: GroupConfigUpdatedEventDetail + + +class GroupMemberBotAddedEventDetail(BaseModel): + chat_id: str + operator_id: UserId + external: bool + operator_tenant_key: str + + +class GroupMemberBotAddedEvent(NoticeEvent): + __event__ = "im.chat.member.bot.added_v1" + event: GroupMemberBotAddedEventDetail + + +class GroupMemberBotDeletedEventDetail(BaseModel): + chat_id: str + operator_id: UserId + external: bool + operator_tenant_key: str + + +class GroupMemberBotDeletedEvent(NoticeEvent): + __event__ = "im.chat.member.bot.deleted_v1" + event: GroupMemberBotDeletedEventDetail + + +class ChatMemberUser(BaseModel): + name: str + tenant_key: str + user_id: UserId + + +class GroupMemberUserAddedEventDetail(BaseModel): + chat_id: str + operator_id: UserId + external: bool + operator_tenant_key: str + users: List[ChatMemberUser] + + +class GroupMemberUserAddedEvent(NoticeEvent): + __event__ = "im.chat.member.user.added_v1" + event: GroupMemberUserAddedEventDetail + + +class GroupMemberUserWithdrawnEventDetail(BaseModel): + chat_id: str + operator_id: UserId + external: bool + operator_tenant_key: str + users: List[ChatMemberUser] + + +class GroupMemberUserWithdrawnEvent(NoticeEvent): + __event__ = "im.chat.member.user.withdrawn_v1" + event: GroupMemberUserWithdrawnEventDetail + + +class GroupMemberUserDeletedEventDetail(BaseModel): + chat_id: str + operator_id: UserId + external: bool + operator_tenant_key: str + users: List[ChatMemberUser] + + +class GroupMemberUserDeletedEvent(NoticeEvent): + __event__ = "im.chat.member.user.deleted_v1" + event: GroupMemberUserDeletedEventDetail + + +_t = StringTrie(separator=".") + +# define `model` first to avoid globals changing while `for` +model = None +for model in globals().values(): + if not inspect.isclass(model) or not issubclass(model, Event): + continue + _t["." + model.__event__] = model + + +def get_event_model(event_name) -> List[Type[Event]]: + """ + :说明: + + 根据事件名获取对应 ``Event Model`` 及 ``FallBack Event Model`` 列表 + + :返回: + + - ``List[Type[Event]]`` + """ + return [model.value for model in _t.prefixes("." + event_name)][::-1] diff --git a/packages/nonebot-adapter-feishu/nonebot/adapters/feishu/exception.py b/packages/nonebot-adapter-feishu/nonebot/adapters/feishu/exception.py new file mode 100644 index 00000000..791000af --- /dev/null +++ b/packages/nonebot-adapter-feishu/nonebot/adapters/feishu/exception.py @@ -0,0 +1,61 @@ +from typing import Optional + +from nonebot.exception import (AdapterException, ActionFailed as + BaseActionFailed, NetworkError as + BaseNetworkError, ApiNotAvailable as + BaseApiNotAvailable) + + +class FeishuAdapterException(AdapterException): + + def __init__(self): + super().__init__("feishu") + + +class ActionFailed(BaseActionFailed, FeishuAdapterException): + """ + :说明: + + API 请求返回错误信息。 + + :参数: + + * ``retcode: Optional[int]``: 错误码 + """ + + def __init__(self, **kwargs): + super().__init__() + self.info = kwargs + + def __repr__(self): + return f"" + + def __str__(self): + return self.__repr__() + + +class NetworkError(BaseNetworkError, FeishuAdapterException): + """ + :说明: + + 网络错误。 + + :参数: + + * ``retcode: Optional[int]``: 错误码 + """ + + def __init__(self, msg: Optional[str] = None): + super().__init__() + self.msg = msg + + def __repr__(self): + return f"" + + def __str__(self): + return self.__repr__() + + +class ApiNotAvailable(BaseApiNotAvailable, FeishuAdapterException): + pass diff --git a/packages/nonebot-adapter-feishu/nonebot/adapters/feishu/message.py b/packages/nonebot-adapter-feishu/nonebot/adapters/feishu/message.py new file mode 100644 index 00000000..efcea8b1 --- /dev/null +++ b/packages/nonebot-adapter-feishu/nonebot/adapters/feishu/message.py @@ -0,0 +1,249 @@ +import json +import itertools + +from dataclasses import dataclass +from typing import Any, Dict, List, Optional, Tuple, Type, Union, Mapping, Iterable + +from nonebot.adapters import Message as BaseMessage, MessageSegment as BaseMessageSegment +from nonebot.typing import overrides + + +class MessageSegment(BaseMessageSegment["Message"]): + """ + 飞书 协议 MessageSegment 适配。具体方法参考协议消息段类型或源码。 + """ + + @classmethod + @overrides(BaseMessageSegment) + def get_message_class(cls) -> Type["Message"]: + return Message + + @property + def segment_text(self) -> dict: + return { + "image": "[图片]", + "file": "[文件]", + "audio": "[音频]", + "media": "[视频]", + "sticker": "[表情包]", + "interactive": "[消息卡片]", + "hongbao": "[红包]", + "share_calendar_event": "[日程卡片]", + "share_chat": "[群名片]", + "share_user": "[个人名片]", + "system": "[系统消息]", + "location": "[位置]", + "video_chat": "[视频通话]" + } + + def __str__(self) -> str: + if self.type in ["text", "hongbao", "a"]: + return str(self.data["text"]) + elif self.type == "at": + return str(f"@{self.data['user_name']}") + else: + return self.segment_text.get(self.type, "") + + @overrides(BaseMessageSegment) + def __add__(self, other) -> "Message": + return Message(self) + (MessageSegment.text(other) if isinstance( + other, str) else other) + + @overrides(BaseMessageSegment) + def __radd__(self, other) -> "Message": + return (MessageSegment.text(other) + if isinstance(other, str) else Message(other)) + self + + @overrides(BaseMessageSegment) + def is_text(self) -> bool: + return self.type == "text" + + #接收消息 + @staticmethod + def at(user_id: str) -> "MessageSegment": + return MessageSegment("at", {"user_id": user_id}) + + #发送消息 + @staticmethod + def text(text: str) -> "MessageSegment": + return MessageSegment("text", {"text": text}) + + @staticmethod + def post(title: str, content: list) -> "MessageSegment": + return MessageSegment("post", {"title": title, "content": content}) + + @staticmethod + def image(image_key: str) -> "MessageSegment": + return MessageSegment("image", {"image_key": image_key}) + + @staticmethod + def interactive(title: str, elements: list) -> "MessageSegment": + return MessageSegment("interactive", { + "title": title, + "elements": elements + }) + + @staticmethod + def share_chat(chat_id: str) -> "MessageSegment": + return MessageSegment("share_chat", {"chat_id": chat_id}) + + @staticmethod + def share_user(user_id: str) -> "MessageSegment": + return MessageSegment("share_user", {"user_id": user_id}) + + @staticmethod + def audio(file_key: str, duration: int) -> "MessageSegment": + return MessageSegment("audio", { + "file_key": file_key, + "duration": duration + }) + + @staticmethod + def media(file_key: str, image_key: str, file_name: str, + duration: int) -> "MessageSegment": + return MessageSegment( + "media", { + "file_key": file_key, + "image_key": image_key, + "file_name": file_name, + "duration": duration + }) + + @staticmethod + def file(file_key: str, file_name: str) -> "MessageSegment": + return MessageSegment("file", { + "file_key": file_key, + "file_name": file_name + }) + + @staticmethod + def sticker(file_key) -> "MessageSegment": + return MessageSegment("sticker", {"file_key": file_key}) + + +class Message(BaseMessage[MessageSegment]): + """ + 飞书 协议 Message 适配。 + """ + + @classmethod + @overrides(BaseMessage) + def get_segment_class(cls) -> Type[MessageSegment]: + return MessageSegment + + @overrides(BaseMessage) + def __add__(self, other: Union[str, Mapping, + Iterable[Mapping]]) -> "Message": + return super(Message, self).__add__( + MessageSegment.text(other) if isinstance(other, str) else other) + + @overrides(BaseMessage) + def __radd__(self, other: Union[str, Mapping, + Iterable[Mapping]]) -> "Message": + return super(Message, self).__radd__( + MessageSegment.text(other) if isinstance(other, str) else other) + + @staticmethod + @overrides(BaseMessage) + def _construct( + msg: Union[str, Mapping, + Iterable[Mapping]]) -> Iterable[MessageSegment]: + if isinstance(msg, Mapping): + yield MessageSegment(msg["type"], msg.get("data") or {}) + return + elif isinstance(msg, str): + yield MessageSegment.text(msg) + elif isinstance(msg, Iterable): + for seg in msg: + if isinstance(seg, MessageSegment): + yield seg + else: + yield MessageSegment(seg["type"], seg.get("data") or {}) + + def _merge(self) -> "Message": + i: int + seg: MessageSegment + msg: List[MessageSegment] = [] + for i, seg in enumerate(self): + if seg.type == "text" and i != 0 and msg[-1].type == "text": + msg[-1] = MessageSegment( + "text", {"text": msg[-1].data["text"] + seg.data["text"]}) + else: + msg.append(seg) + return Message(msg) + + @overrides(BaseMessage) + def extract_plain_text(self) -> str: + return "".join(seg.data["text"] for seg in self if seg.is_text()) + + +@dataclass +class MessageSerializer: + """ + 飞书 协议 Message 序列化器。 + """ + message: Message + + def serialize(self) -> Tuple[str, str]: + segments = list(self.message) + last_segment_type: str = "" + if len(segments) > 1: + msg = {"title": "", "content": [[]]} + for segment in segments: + if segment == "image": + if last_segment_type != "image": + msg["content"].append([]) + else: + if last_segment_type == "image": + msg["content"].append([]) + msg["content"][-1].append({ + "tag": segment.type if segment.type != "image" else "img", + **segment.data + }) + last_segment_type = segment.type + return "post", json.dumps({"zh_cn": {**msg}}) + + else: + return self.message[0].type, json.dumps(self.message[0].data) + + +@dataclass +class MessageDeserializer: + """ + 飞书 协议 Message 反序列化器。 + """ + type: str + data: Dict[str, Any] + mentions: Optional[List[dict]] + + def deserialize(self) -> Message: + dict_mention = {} + if self.mentions: + for mention in self.mentions: + dict_mention[mention["key"]] = mention + + if self.type == "post": + msg = Message() + if self.data["title"] != "": + msg += MessageSegment("text", {'text': self.data["title"]}) + + for seg in itertools.chain(*self.data["content"]): + tag = seg.pop("tag") + if tag == "at": + seg["user_name"] = dict_mention[seg["user_id"]]["name"] + seg["user_id"] = dict_mention[ + seg["user_id"]]["id"]["open_id"] + + msg += MessageSegment(tag if tag != "img" else "image", seg) + + return msg._merge() + elif self.type == "text": + for key, mention in dict_mention.items(): + self.data["text"] = self.data["text"].replace( + key, f"@{mention['name']}") + self.data["mentions"] = dict_mention + + return Message(MessageSegment(self.type, self.data)) + + else: + return Message(MessageSegment(self.type, self.data)) diff --git a/packages/nonebot-adapter-feishu/nonebot/adapters/feishu/utils.py b/packages/nonebot-adapter-feishu/nonebot/adapters/feishu/utils.py new file mode 100644 index 00000000..a04456a6 --- /dev/null +++ b/packages/nonebot-adapter-feishu/nonebot/adapters/feishu/utils.py @@ -0,0 +1,34 @@ +import base64 +import hashlib + +from Crypto.Cipher import AES +from nonebot.utils import logger_wrapper + +log = logger_wrapper("FEISHU") + + +class AESCipher(object): + + def __init__(self, key): + self.block_size = AES.block_size + self.key = hashlib.sha256(AESCipher.str_to_bytes(key)).digest() + + @staticmethod + def str_to_bytes(data): + u_type = type(b"".decode('utf8')) + if isinstance(data, u_type): + return data.encode('utf8') + return data + + @staticmethod + def _unpad(s): + return s[:-ord(s[len(s) - 1:])] + + def decrypt(self, enc): + iv = enc[:AES.block_size] + cipher = AES.new(self.key, AES.MODE_CBC, iv) + return self._unpad(cipher.decrypt(enc[AES.block_size:])) + + def decrypt_string(self, enc): + enc = base64.b64decode(enc) + return self.decrypt(enc).decode('utf8') diff --git a/packages/nonebot-adapter-feishu/poetry.lock b/packages/nonebot-adapter-feishu/poetry.lock new file mode 100644 index 00000000..7bfe5f9a --- /dev/null +++ b/packages/nonebot-adapter-feishu/poetry.lock @@ -0,0 +1,677 @@ +[[package]] +name = "aiocache" +version = "0.11.1" +description = "multi backend asyncio cache" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +dev = ["asynctest (>=0.11.0)", "codecov", "coverage", "flake8", "ipdb", "marshmallow", "pystache", "pytest", "pytest-asyncio", "pytest-mock", "sphinx", "sphinx-autobuild", "sphinx-rtd-theme", "black"] +memcached = ["aiomcache (>=0.5.2)"] +msgpack = ["msgpack (>=0.5.5)"] +redis = ["aioredis (>=0.3.3)", "aioredis (>=1.0.0)"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "certifi" +version = "2021.5.30" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = "*" + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "click" +version = "7.1.2" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "fastapi" +version = "0.65.3" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" +starlette = "0.14.2" + +[package.extras] +all = ["requests (>=2.24.0,<3.0.0)", "aiofiles (>=0.5.0,<0.6.0)", "jinja2 (>=2.11.2,<3.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<2.0.0)", "pyyaml (>=5.3.1,<6.0.0)", "graphene (>=2.1.8,<3.0.0)", "ujson (>=4.0.1,<5.0.0)", "orjson (>=3.2.1,<4.0.0)", "email_validator (>=1.1.1,<2.0.0)", "uvicorn[standard] (>=0.12.0,<0.14.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)"] +dev = ["python-jose[cryptography] (>=3.1.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.14.0)", "graphene (>=2.1.8,<3.0.0)"] +doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=7.1.9,<8.0.0)", "markdown-include (>=0.6.0,<0.7.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.2.0)", "typer-cli (>=0.0.12,<0.0.13)", "pyyaml (>=5.3.1,<6.0.0)"] +test = ["pytest (==5.4.3)", "pytest-cov (==2.10.0)", "pytest-asyncio (>=0.14.0,<0.15.0)", "mypy (==0.812)", "flake8 (>=3.8.3,<4.0.0)", "black (==20.8b1)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.15.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<1.4.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.4.0)", "orjson (>=3.2.1,<4.0.0)", "ujson (>=4.0.1,<5.0.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "aiofiles (>=0.5.0,<0.6.0)", "flask (>=1.1.2,<2.0.0)"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "h11" +version = "0.12.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "httpcore" +version = "0.12.3" +description = "A minimal low-level HTTP client." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +h11 = "<1.0.0" +sniffio = ">=1.0.0,<2.0.0" + +[package.extras] +http2 = ["h2 (>=3,<5)"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "httptools" +version = "0.1.2" +description = "A collection of framework independent HTTP protocol utils." +category = "main" +optional = false +python-versions = "*" + +[package.extras] +test = ["Cython (==0.29.22)"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "httpx" +version = "0.17.1" +description = "The next generation HTTP client." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +certifi = "*" +httpcore = ">=0.12.1,<0.13" +rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} +sniffio = "*" + +[package.extras] +brotli = ["brotlipy (>=0.7.0,<0.8.0)"] +http2 = ["h2 (>=3.0.0,<4.0.0)"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "idna" +version = "3.2" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "loguru" +version = "0.5.3" +description = "Python logging made (stupidly) simple" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} +win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} + +[package.extras] +dev = ["codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "tox (>=3.9.0)", "tox-travis (>=0.12)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "Sphinx (>=2.2.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "black (>=19.10b0)", "isort (>=5.1.1)"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "nonebot2" +version = "2.0.0a13.post1" +description = "An asynchronous python bot framework." +category = "main" +optional = false +python-versions = "^3.7.3" +develop = true + +[package.dependencies] +fastapi = "^0.65.2" +loguru = "^0.5.1" +pydantic = {version = "~1.8.0", extras = ["dotenv"]} +pygtrie = "^2.4.1" +tomlkit = "^0.7.0" +uvicorn = {version = "^0.13.0", extras = ["standard"]} + +[package.extras] +quart = ["Quart (>=0.14.1,<0.15.0)"] +aiohttp = ["aiohttp (>=3.7.4,<4.0.0)"] +all = ["Quart (>=0.14.1,<0.15.0)", "aiohttp (>=3.7.4,<4.0.0)"] + +[package.source] +type = "directory" +url = "../.." + +[[package]] +name = "pycryptodome" +version = "3.10.1" +description = "Cryptographic library for Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "pydantic" +version = "1.8.2" +description = "Data validation and settings management using python 3.6 type hinting" +category = "main" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +python-dotenv = {version = ">=0.10.4", optional = true, markers = "extra == \"dotenv\""} +typing-extensions = ">=3.7.4.3" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "pygtrie" +version = "2.4.2" +description = "A pure Python trie data structure implementation." +category = "main" +optional = false +python-versions = "*" + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "python-dotenv" +version = "0.18.0" +description = "Read key-value pairs from a .env file and set them as environment variables" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +cli = ["click (>=5.0)"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "pyyaml" +version = "5.4.1" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "rfc3986" +version = "1.5.0" +description = "Validating URI References per RFC 3986" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} + +[package.extras] +idna2008 = ["idna"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "sniffio" +version = "1.2.0" +description = "Sniff out which async library your code is running under" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "starlette" +version = "0.14.2" +description = "The little ASGI library that shines." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +full = ["aiofiles", "graphene", "itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "tomlkit" +version = "0.7.2" +description = "Style preserving TOML library" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "typing-extensions" +version = "3.10.0.0" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "main" +optional = false +python-versions = "*" + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "uvicorn" +version = "0.13.4" +description = "The lightning-fast ASGI server." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +click = ">=7.0.0,<8.0.0" +colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} +h11 = ">=0.8" +httptools = {version = ">=0.1.0,<0.2.0", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +PyYAML = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} +uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +watchgod = {version = ">=0.6", optional = true, markers = "extra == \"standard\""} +websockets = {version = ">=8.0.0,<9.0.0", optional = true, markers = "extra == \"standard\""} + +[package.extras] +standard = ["websockets (>=8.0.0,<9.0.0)", "watchgod (>=0.6)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "httptools (>=0.1.0,<0.2.0)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "uvloop" +version = "0.15.3" +description = "Fast implementation of asyncio event loop on top of libuv" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +dev = ["Cython (>=0.29.20,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=1.7.3,<1.8.0)", "sphinxcontrib-asyncio (>=0.2.0,<0.3.0)", "sphinx_rtd_theme (>=0.2.4,<0.3.0)", "aiohttp", "flake8 (>=3.8.4,<3.9.0)", "psutil", "pycodestyle (>=2.6.0,<2.7.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"] +docs = ["Sphinx (>=1.7.3,<1.8.0)", "sphinxcontrib-asyncio (>=0.2.0,<0.3.0)", "sphinx_rtd_theme (>=0.2.4,<0.3.0)"] +test = ["aiohttp", "flake8 (>=3.8.4,<3.9.0)", "psutil", "pycodestyle (>=2.6.0,<2.7.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "watchgod" +version = "0.7" +description = "Simple, modern file watching and code reload in python." +category = "main" +optional = false +python-versions = ">=3.5" + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "websockets" +version = "8.1" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +category = "main" +optional = false +python-versions = ">=3.6.1" + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "win32-setctime" +version = "1.0.3" +description = "A small Python utility to set file creation time on Windows" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[metadata] +lock-version = "1.1" +python-versions = "^3.7.3" +content-hash = "8cb6803a739721f108b5d43f0633275f26a97c6486eb67cdb9346f369a5182ee" + +[metadata.files] +aiocache = [ + {file = "aiocache-0.11.1-py2.py3-none-any.whl", hash = "sha256:e55c7caaa5753794fd301c3a2e592737fa1d036db9f8d04ae154facdfb48a157"}, + {file = "aiocache-0.11.1.tar.gz", hash = "sha256:f2ebe0b05cec45782e7b5ea0bb74640f157dd4bb1028b4565364dda9fe33be7f"}, +] +certifi = [ + {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, + {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"}, +] +click = [ + {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, + {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +fastapi = [ + {file = "fastapi-0.65.3-py3-none-any.whl", hash = "sha256:d3e3c0ac35110efb22ee3ed28201cf32f9d11a9a0e52d7dd676cad25f5219523"}, + {file = "fastapi-0.65.3.tar.gz", hash = "sha256:6ea2286e439c4ced7cce2b2862c25859601bf327a515c12dd6e431ef5d49d12f"}, +] +h11 = [ + {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"}, + {file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"}, +] +httpcore = [ + {file = "httpcore-0.12.3-py3-none-any.whl", hash = "sha256:93e822cd16c32016b414b789aeff4e855d0ccbfc51df563ee34d4dbadbb3bcdc"}, + {file = "httpcore-0.12.3.tar.gz", hash = "sha256:37ae835fb370049b2030c3290e12ed298bf1473c41bb72ca4aa78681eba9b7c9"}, +] +httptools = [ + {file = "httptools-0.1.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:1e35aa179b67086cc600a984924a88589b90793c9c1b260152ca4908786e09df"}, + {file = "httptools-0.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c4111a0a8a00eff1e495d43ea5230aaf64968a48ddba8ea2d5f982efae827404"}, + {file = "httptools-0.1.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:dce59ee45dd6ee6c434346a5ac527c44014326f560866b4b2f414a692ee1aca8"}, + {file = "httptools-0.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:f759717ca1b2ef498c67ba4169c2b33eecf943a89f5329abcff8b89d153eb500"}, + {file = "httptools-0.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:08b79e09114e6ab5c3dbf560bba2cb2257ea38cdaeaf99b7cb80d8f92622fcd9"}, + {file = "httptools-0.1.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:8fcca4b7efe353b13a24017211334c57d055a6e132c7adffed13a10d28efca57"}, + {file = "httptools-0.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:aebdf0bd7bf7c90ae6b3be458692bf6e9e5b610b501f9f74c7979015a51db4c4"}, + {file = "httptools-0.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:fbf7ecd31c39728f251b1c095fd27c84e4d21f60a1d079a0333472ff3ae59d34"}, + {file = "httptools-0.1.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:c1c63d860749841024951b0a78e4dec6f543d23751ef061d6ab60064c7b8b524"}, + {file = "httptools-0.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fb7199b8fb0c50a22e77260bb59017e0c075fa80cb03bb2c8692de76e7bb7fe7"}, + {file = "httptools-0.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bda99a5723e7eab355ce57435c70853fc137a65aebf2f1cd4d15d96e2956da7b"}, + {file = "httptools-0.1.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:851026bd63ec0af7e7592890d97d15c92b62d9e17094353f19a52c8e2b33710a"}, + {file = "httptools-0.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:31629e1f1b89959f8c0927bad12184dc07977dcf71e24f4772934aa490aa199b"}, + {file = "httptools-0.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:9abd788465aa46a0f288bd3a99e53edd184177d6379e2098fd6097bb359ad9d6"}, + {file = "httptools-0.1.2.tar.gz", hash = "sha256:07659649fe6b3948b6490825f89abe5eb1cec79ebfaaa0b4bf30f3f33f3c2ba8"}, +] +httpx = [ + {file = "httpx-0.17.1-py3-none-any.whl", hash = "sha256:d379653bd457e8257eb0df99cb94557e4aac441b7ba948e333be969298cac272"}, + {file = "httpx-0.17.1.tar.gz", hash = "sha256:cc2a55188e4b25272d2bcd46379d300f632045de4377682aa98a8a6069d55967"}, +] +idna = [ + {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, + {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"}, +] +loguru = [ + {file = "loguru-0.5.3-py3-none-any.whl", hash = "sha256:f8087ac396b5ee5f67c963b495d615ebbceac2796379599820e324419d53667c"}, + {file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"}, +] +nonebot2 = [] +pycryptodome = [ + {file = "pycryptodome-3.10.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1c5e1ca507de2ad93474be5cfe2bfa76b7cf039a1a32fc196f40935944871a06"}, + {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:6260e24d41149268122dd39d4ebd5941e9d107f49463f7e071fd397e29923b0c"}, + {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:3f840c49d38986f6e17dbc0673d37947c88bc9d2d9dba1c01b979b36f8447db1"}, + {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:2dea65df54349cdfa43d6b2e8edb83f5f8d6861e5cf7b1fbc3e34c5694c85e27"}, + {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:e61e363d9a5d7916f3a4ce984a929514c0df3daf3b1b2eb5e6edbb131ee771cf"}, + {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:2603c98ae04aac675fefcf71a6c87dc4bb74a75e9071ae3923bbc91a59f08d35"}, + {file = "pycryptodome-3.10.1-cp27-cp27m-win32.whl", hash = "sha256:38661348ecb71476037f1e1f553159b80d256c00f6c0b00502acac891f7116d9"}, + {file = "pycryptodome-3.10.1-cp27-cp27m-win_amd64.whl", hash = "sha256:1723ebee5561628ce96748501cdaa7afaa67329d753933296321f0be55358dce"}, + {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:77997519d8eb8a4adcd9a47b9cec18f9b323e296986528186c0e9a7a15d6a07e"}, + {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:99b2f3fc51d308286071d0953f92055504a6ffe829a832a9fc7a04318a7683dd"}, + {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:e0a4d5933a88a2c98bbe19c0c722f5483dc628d7a38338ac2cb64a7dbd34064b"}, + {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d3d6958d53ad307df5e8469cc44474a75393a434addf20ecd451f38a72fe29b8"}, + {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:a8eb8b6ea09ec1c2535bf39914377bc8abcab2c7d30fa9225eb4fe412024e427"}, + {file = "pycryptodome-3.10.1-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:31c1df17b3dc5f39600a4057d7db53ac372f492c955b9b75dd439f5d8b460129"}, + {file = "pycryptodome-3.10.1-cp35-abi3-manylinux1_i686.whl", hash = "sha256:a3105a0eb63eacf98c2ecb0eb4aa03f77f40fbac2bdde22020bb8a536b226bb8"}, + {file = "pycryptodome-3.10.1-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:a92d5c414e8ee1249e850789052608f582416e82422502dc0ac8c577808a9067"}, + {file = "pycryptodome-3.10.1-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:60386d1d4cfaad299803b45a5bc2089696eaf6cdd56f9fc17479a6f89595cfc8"}, + {file = "pycryptodome-3.10.1-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:501ab36aae360e31d0ec370cf5ce8ace6cb4112060d099b993bc02b36ac83fb6"}, + {file = "pycryptodome-3.10.1-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:fc7489a50323a0df02378bc2fff86eb69d94cc5639914346c736be981c6a02e7"}, + {file = "pycryptodome-3.10.1-cp35-abi3-win32.whl", hash = "sha256:9b6f711b25e01931f1c61ce0115245a23cdc8b80bf8539ac0363bdcf27d649b6"}, + {file = "pycryptodome-3.10.1-cp35-abi3-win_amd64.whl", hash = "sha256:7fd519b89585abf57bf47d90166903ec7b43af4fe23c92273ea09e6336af5c07"}, + {file = "pycryptodome-3.10.1-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:09c1555a3fa450e7eaca41ea11cd00afe7c91fef52353488e65663777d8524e0"}, + {file = "pycryptodome-3.10.1-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:758949ca62690b1540dfb24ad773c6da9cd0e425189e83e39c038bbd52b8e438"}, + {file = "pycryptodome-3.10.1-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:e3bf558c6aeb49afa9f0c06cee7fb5947ee5a1ff3bd794b653d39926b49077fa"}, + {file = "pycryptodome-3.10.1-pp27-pypy_73-win32.whl", hash = "sha256:f977cdf725b20f6b8229b0c87acb98c7717e742ef9f46b113985303ae12a99da"}, + {file = "pycryptodome-3.10.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6d2df5223b12437e644ce0a3be7809471ffa71de44ccd28b02180401982594a6"}, + {file = "pycryptodome-3.10.1-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:98213ac2b18dc1969a47bc65a79a8fca02a414249d0c8635abb081c7f38c91b6"}, + {file = "pycryptodome-3.10.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:12222a5edc9ca4a29de15fbd5339099c4c26c56e13c2ceddf0b920794f26165d"}, + {file = "pycryptodome-3.10.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:6bbf7fee7b7948b29d7e71fcacf48bac0c57fb41332007061a933f2d996f9713"}, + {file = "pycryptodome-3.10.1.tar.gz", hash = "sha256:3e2e3a06580c5f190df843cdb90ea28d61099cf4924334d5297a995de68e4673"}, +] +pydantic = [ + {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"}, + {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"}, + {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"}, + {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"}, + {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"}, + {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"}, + {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"}, + {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"}, + {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"}, + {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"}, +] +pygtrie = [ + {file = "pygtrie-2.4.2.tar.gz", hash = "sha256:43205559d28863358dbbf25045029f58e2ab357317a59b11f11ade278ac64692"}, +] +python-dotenv = [ + {file = "python-dotenv-0.18.0.tar.gz", hash = "sha256:effaac3c1e58d89b3ccb4d04a40dc7ad6e0275fda25fd75ae9d323e2465e202d"}, + {file = "python_dotenv-0.18.0-py2.py3-none-any.whl", hash = "sha256:dd8fe852847f4fbfadabf6183ddd4c824a9651f02d51714fa075c95561959c7d"}, +] +pyyaml = [ + {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, + {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, + {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, + {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, + {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, + {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, + {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, + {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, + {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, + {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, + {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, +] +rfc3986 = [ + {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, + {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, +] +sniffio = [ + {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"}, + {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"}, +] +starlette = [ + {file = "starlette-0.14.2-py3-none-any.whl", hash = "sha256:3c8e48e52736b3161e34c9f0e8153b4f32ec5d8995a3ee1d59410d92f75162ed"}, + {file = "starlette-0.14.2.tar.gz", hash = "sha256:7d49f4a27f8742262ef1470608c59ddbc66baf37c148e938c7038e6bc7a998aa"}, +] +tomlkit = [ + {file = "tomlkit-0.7.2-py2.py3-none-any.whl", hash = "sha256:173ad840fa5d2aac140528ca1933c29791b79a374a0861a80347f42ec9328117"}, + {file = "tomlkit-0.7.2.tar.gz", hash = "sha256:d7a454f319a7e9bd2e249f239168729327e4dd2d27b17dc68be264ad1ce36754"}, +] +typing-extensions = [ + {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, + {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, + {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, +] +uvicorn = [ + {file = "uvicorn-0.13.4-py3-none-any.whl", hash = "sha256:7587f7b08bd1efd2b9bad809a3d333e972f1d11af8a5e52a9371ee3a5de71524"}, + {file = "uvicorn-0.13.4.tar.gz", hash = "sha256:3292251b3c7978e8e4a7868f4baf7f7f7bb7e40c759ecc125c37e99cdea34202"}, +] +uvloop = [ + {file = "uvloop-0.15.3-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:e71fb9038bfcd7646ca126c5ef19b17e48d4af9e838b2bcfda7a9f55a6552a32"}, + {file = "uvloop-0.15.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7522df4e45e4f25b50adbbbeb5bb9847495c438a628177099d2721f2751ff825"}, + {file = "uvloop-0.15.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae2b325c0f6d748027f7463077e457006b4fdb35a8788f01754aadba825285ee"}, + {file = "uvloop-0.15.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:0de811931e90ae2da9e19ce70ffad73047ab0c1dba7c6e74f9ae1a3aabeb89bd"}, + {file = "uvloop-0.15.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7f4b8a905df909a407c5791fb582f6c03b0d3b491ecdc1cdceaefbc9bf9e08f6"}, + {file = "uvloop-0.15.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d8ffe44ae709f839c54bacf14ed283f41bee90430c3b398e521e10f8d117b3a"}, + {file = "uvloop-0.15.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:63a3288abbc9c8ee979d7e34c34e780b2fbab3e7e53d00b6c80271119f277399"}, + {file = "uvloop-0.15.3-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5cda65fc60a645470b8525ce014516b120b7057b576fa876cdfdd5e60ab1efbb"}, + {file = "uvloop-0.15.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ff05116ede1ebdd81802df339e5b1d4cab1dfbd99295bf27e90b4cec64d70e9"}, + {file = "uvloop-0.15.3.tar.gz", hash = "sha256:905f0adb0c09c9f44222ee02f6b96fd88b493478fffb7a345287f9444e926030"}, +] +watchgod = [ + {file = "watchgod-0.7-py3-none-any.whl", hash = "sha256:d6c1ea21df37847ac0537ca0d6c2f4cdf513562e95f77bb93abbcf05573407b7"}, + {file = "watchgod-0.7.tar.gz", hash = "sha256:48140d62b0ebe9dd9cf8381337f06351e1f2e70b2203fa9c6eff4e572ca84f29"}, +] +websockets = [ + {file = "websockets-8.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c"}, + {file = "websockets-8.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170"}, + {file = "websockets-8.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8"}, + {file = "websockets-8.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:295359a2cc78736737dd88c343cd0747546b2174b5e1adc223824bcaf3e164cb"}, + {file = "websockets-8.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:1d3f1bf059d04a4e0eb4985a887d49195e15ebabc42364f4eb564b1d065793f5"}, + {file = "websockets-8.1-cp36-cp36m-win32.whl", hash = "sha256:2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a"}, + {file = "websockets-8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5"}, + {file = "websockets-8.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989"}, + {file = "websockets-8.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d"}, + {file = "websockets-8.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779"}, + {file = "websockets-8.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:751a556205d8245ff94aeef23546a1113b1dd4f6e4d102ded66c39b99c2ce6c8"}, + {file = "websockets-8.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3ef56fcc7b1ff90de46ccd5a687bbd13a3180132268c4254fc0fa44ecf4fc422"}, + {file = "websockets-8.1-cp37-cp37m-win32.whl", hash = "sha256:7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc"}, + {file = "websockets-8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308"}, + {file = "websockets-8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c1ec8db4fac31850286b7cd3b9c0e1b944204668b8eb721674916d4e28744092"}, + {file = "websockets-8.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5c01fd846263a75bc8a2b9542606927cfad57e7282965d96b93c387622487485"}, + {file = "websockets-8.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9bef37ee224e104a413f0780e29adb3e514a5b698aabe0d969a6ba426b8435d1"}, + {file = "websockets-8.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d705f8aeecdf3262379644e4b55107a3b55860eb812b673b28d0fbc347a60c55"}, + {file = "websockets-8.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c8a116feafdb1f84607cb3b14aa1418424ae71fee131642fc568d21423b51824"}, + {file = "websockets-8.1-cp38-cp38-win32.whl", hash = "sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36"}, + {file = "websockets-8.1-cp38-cp38-win_amd64.whl", hash = "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b"}, + {file = "websockets-8.1.tar.gz", hash = "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f"}, +] +win32-setctime = [ + {file = "win32_setctime-1.0.3-py3-none-any.whl", hash = "sha256:dc925662de0a6eb987f0b01f599c01a8236cb8c62831c22d9cada09ad958243e"}, + {file = "win32_setctime-1.0.3.tar.gz", hash = "sha256:4e88556c32fdf47f64165a2180ba4552f8bb32c1103a2fafd05723a0bd42bd4b"}, +] diff --git a/packages/nonebot-adapter-feishu/pyproject.toml b/packages/nonebot-adapter-feishu/pyproject.toml new file mode 100644 index 00000000..ba3041b0 --- /dev/null +++ b/packages/nonebot-adapter-feishu/pyproject.toml @@ -0,0 +1,42 @@ +[tool.poetry] +name = "nonebot-adapter-feishu" +version = "2.0.0-alpha.13" +description = "feishu(larksuite) adapter for nonebot2" +authors = ["StarHeartHunt "] +license = "MIT" +readme = "README.md" +homepage = "https://v2.nonebot.dev/" +repository = "https://github.com/nonebot/nonebot2" +documentation = "https://v2.nonebot.dev/" +keywords = ["bot", "qq", "qqbot", "feishu", "larksuite"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Framework :: Robot Framework", + "Framework :: Robot Framework :: Library", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3" +] +packages = [ + { include = "nonebot" } +] +exclude = ["nonebot/__init__.py", "nonebot/adapters/__init__.py"] + +[tool.poetry.dependencies] +python = "^3.7.3" +httpx = "^0.17.0" +aiocache = "^0.11.1" +pycryptodome = "^3.10.1" +nonebot2 = "^2.0.0-alpha.13" + +[tool.poetry.dev-dependencies] +nonebot2 = { path = "../../", develop = true } + +[[tool.poetry.source]] +name = "aliyun" +url = "https://mirrors.aliyun.com/pypi/simple/" +default = true + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/poetry.lock b/poetry.lock index fa4c7843..8b7d2e3b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,22 @@ +[[package]] +name = "aiocache" +version = "0.11.1" +description = "multi backend asyncio cache" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +dev = ["asynctest (>=0.11.0)", "codecov", "coverage", "flake8", "ipdb", "marshmallow", "pystache", "pytest", "pytest-asyncio", "pytest-mock", "sphinx", "sphinx-autobuild", "sphinx-rtd-theme", "black"] +memcached = ["aiomcache (>=0.5.2)"] +msgpack = ["msgpack (>=0.5.5)"] +redis = ["aioredis (>=0.3.3)", "aioredis (>=1.0.0)"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + [[package]] name = "aiofiles" version = "0.6.0" @@ -127,7 +146,7 @@ name = "chardet" version = "4.0.0" description = "Universal encoding detector for Python 2 and 3" category = "main" -optional = false +optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.source] @@ -135,6 +154,22 @@ type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" reference = "aliyun" +[[package]] +name = "charset-normalizer" +version = "2.0.3" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "dev" +optional = false +python-versions = ">=3.5.0" + +[package.extras] +unicode_backport = ["unicodedata2"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + [[package]] name = "click" version = "7.1.2" @@ -176,7 +211,7 @@ reference = "aliyun" [[package]] name = "fastapi" -version = "0.65.2" +version = "0.65.3" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" category = "main" optional = false @@ -189,7 +224,7 @@ starlette = "0.14.2" [package.extras] all = ["requests (>=2.24.0,<3.0.0)", "aiofiles (>=0.5.0,<0.6.0)", "jinja2 (>=2.11.2,<3.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<2.0.0)", "pyyaml (>=5.3.1,<6.0.0)", "graphene (>=2.1.8,<3.0.0)", "ujson (>=4.0.1,<5.0.0)", "orjson (>=3.2.1,<4.0.0)", "email_validator (>=1.1.1,<2.0.0)", "uvicorn[standard] (>=0.12.0,<0.14.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)"] dev = ["python-jose[cryptography] (>=3.1.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.14.0)", "graphene (>=2.1.8,<3.0.0)"] -doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=6.1.4,<7.0.0)", "markdown-include (>=0.5.1,<0.6.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.2.0)", "typer-cli (>=0.0.9,<0.0.10)", "pyyaml (>=5.3.1,<6.0.0)"] +doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=7.1.9,<8.0.0)", "markdown-include (>=0.6.0,<0.7.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.2.0)", "typer-cli (>=0.0.12,<0.0.13)", "pyyaml (>=5.3.1,<6.0.0)"] test = ["pytest (==5.4.3)", "pytest-cov (==2.10.0)", "pytest-asyncio (>=0.14.0,<0.15.0)", "mypy (==0.812)", "flake8 (>=3.8.3,<4.0.0)", "black (==20.8b1)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.15.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<1.4.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.4.0)", "orjson (>=3.2.1,<4.0.0)", "ujson (>=4.0.1,<5.0.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "aiofiles (>=0.5.0,<0.6.0)", "flask (>=1.1.2,<2.0.0)"] [package.source] @@ -354,11 +389,11 @@ reference = "aliyun" [[package]] name = "idna" -version = "2.10" +version = "3.2" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.5" [package.source] type = "legacy" @@ -490,6 +525,25 @@ nonebot2 = "^2.0.0-alpha.12" type = "directory" url = "packages/nonebot-adapter-ding" +[[package]] +name = "nonebot-adapter-feishu" +version = "2.0.0-alpha.13" +description = "feishu(larksuite) adapter for nonebot2" +category = "dev" +optional = false +python-versions = "^3.7.3" +develop = true + +[package.dependencies] +aiocache = "^0.11.1" +httpx = "^0.17.0" +nonebot2 = "^2.0.0-alpha.13" +pycryptodome = "^3.10.1" + +[package.source] +type = "directory" +url = "packages/nonebot-adapter-feishu" + [[package]] name = "nonebot-adapter-mirai" version = "2.0.0-alpha.12" @@ -528,11 +582,11 @@ reference = "aliyun" [[package]] name = "packaging" -version = "20.9" +version = "21.0" description = "Core utilities for Python packages" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] pyparsing = ">=2.0.2" @@ -544,11 +598,24 @@ reference = "aliyun" [[package]] name = "priority" -version = "1.3.0" +version = "2.0.0" description = "A pure-Python implementation of the HTTP/2 priority tree" category = "main" optional = true -python-versions = "*" +python-versions = ">=3.6.1" + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "pycryptodome" +version = "3.10.1" +description = "Cryptographic library for Python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.source] type = "legacy" @@ -578,7 +645,7 @@ reference = "aliyun" [[package]] name = "pydash" -version = "5.0.0" +version = "5.0.2" description = "The kitchen sink of Python utility libraries for doing \"stuff\" in a functional way. Based on the Lo-Dash Javascript library." category = "dev" optional = false @@ -633,7 +700,7 @@ reference = "aliyun" [[package]] name = "python-dotenv" -version = "0.17.1" +version = "0.18.0" description = "Read key-value pairs from a .env file and set them as environment variables" category = "main" optional = false @@ -743,21 +810,21 @@ reference = "aliyun" [[package]] name = "requests" -version = "2.25.1" +version = "2.26.0" description = "Python HTTP for Humans." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<5" -idna = ">=2.5,<3" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} urllib3 = ">=1.21.1,<1.27" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [package.source] type = "legacy" @@ -1067,7 +1134,7 @@ reference = "aliyun" [[package]] name = "urllib3" -version = "1.26.5" +version = "1.26.6" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "dev" optional = false @@ -1113,7 +1180,7 @@ reference = "aliyun" [[package]] name = "uvloop" -version = "0.15.2" +version = "0.15.3" description = "Fast implementation of asyncio event loop on top of libuv" category = "main" optional = false @@ -1242,9 +1309,13 @@ quart = ["Quart"] [metadata] lock-version = "1.1" python-versions = "^3.7.3" -content-hash = "c8d97292fdd918db17f6c53f38ff2d1fb3309da7153f58c4c8e818bd425e302e" +content-hash = "da87abf42c19eca5a1c943ae53d12b57121dda28dcb80533373568aed6c76470" [metadata.files] +aiocache = [ + {file = "aiocache-0.11.1-py2.py3-none-any.whl", hash = "sha256:e55c7caaa5753794fd301c3a2e592737fa1d036db9f8d04ae154facdfb48a157"}, + {file = "aiocache-0.11.1.tar.gz", hash = "sha256:f2ebe0b05cec45782e7b5ea0bb74640f157dd4bb1028b4565364dda9fe33be7f"}, +] aiofiles = [ {file = "aiofiles-0.6.0-py3-none-any.whl", hash = "sha256:bd3019af67f83b739f8e4053c6c0512a7f545b9a8d91aaeab55e6e0f9d123c27"}, {file = "aiofiles-0.6.0.tar.gz", hash = "sha256:e0281b157d3d5d59d803e3f4557dcc9a3dff28a4dd4829a9ff478adae50ca092"}, @@ -1315,6 +1386,10 @@ chardet = [ {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, ] +charset-normalizer = [ + {file = "charset-normalizer-2.0.3.tar.gz", hash = "sha256:c46c3ace2d744cfbdebceaa3c19ae691f53ae621b39fd7570f59d14fb7f2fd12"}, + {file = "charset_normalizer-2.0.3-py3-none-any.whl", hash = "sha256:88fce3fa5b1a84fdcb3f603d889f723d1dd89b26059d0123ca435570e848d5e1"}, +] click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, @@ -1328,8 +1403,8 @@ docutils = [ {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, ] fastapi = [ - {file = "fastapi-0.65.2-py3-none-any.whl", hash = "sha256:39569a18914075b2f1aaa03bcb9dc96a38e0e5dabaf3972e088c9077dfffa379"}, - {file = "fastapi-0.65.2.tar.gz", hash = "sha256:8359e55d8412a5571c0736013d90af235d6949ec4ce978e9b63500c8f4b6f714"}, + {file = "fastapi-0.65.3-py3-none-any.whl", hash = "sha256:d3e3c0ac35110efb22ee3ed28201cf32f9d11a9a0e52d7dd676cad25f5219523"}, + {file = "fastapi-0.65.3.tar.gz", hash = "sha256:6ea2286e439c4ced7cce2b2862c25859601bf327a515c12dd6e431ef5d49d12f"}, ] h11 = [ {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"}, @@ -1381,8 +1456,8 @@ hyperframe = [ {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"}, ] idna = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, + {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, + {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"}, ] imagesize = [ {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"}, @@ -1477,18 +1552,51 @@ multidict = [ ] nonebot-adapter-cqhttp = [] nonebot-adapter-ding = [] +nonebot-adapter-feishu = [] nonebot-adapter-mirai = [] nonebot-plugin-test = [ {file = "nonebot-plugin-test-0.2.0.tar.gz", hash = "sha256:c9ee997c5c96160de4af02d10a7c6301b3fc4e942df7e70906df0534606ea23b"}, {file = "nonebot_plugin_test-0.2.0-py3-none-any.whl", hash = "sha256:75cd18cc282815a03250bb86c7d2a8d6a66a5064ac335bedc9a3e268a1e7dd13"}, ] packaging = [ - {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, - {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, + {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, + {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, ] priority = [ - {file = "priority-1.3.0-py2.py3-none-any.whl", hash = "sha256:be4fcb94b5e37cdeb40af5533afe6dd603bd665fe9c8b3052610fc1001d5d1eb"}, - {file = "priority-1.3.0.tar.gz", hash = "sha256:6bc1961a6d7fcacbfc337769f1a382c8e746566aaa365e78047abe9f66b2ffbe"}, + {file = "priority-2.0.0-py3-none-any.whl", hash = "sha256:6f8eefce5f3ad59baf2c080a664037bb4725cd0a790d53d59ab4059288faf6aa"}, + {file = "priority-2.0.0.tar.gz", hash = "sha256:c965d54f1b8d0d0b19479db3924c7c36cf672dbf2aec92d43fbdaf4492ba18c0"}, +] +pycryptodome = [ + {file = "pycryptodome-3.10.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1c5e1ca507de2ad93474be5cfe2bfa76b7cf039a1a32fc196f40935944871a06"}, + {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:6260e24d41149268122dd39d4ebd5941e9d107f49463f7e071fd397e29923b0c"}, + {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:3f840c49d38986f6e17dbc0673d37947c88bc9d2d9dba1c01b979b36f8447db1"}, + {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:2dea65df54349cdfa43d6b2e8edb83f5f8d6861e5cf7b1fbc3e34c5694c85e27"}, + {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:e61e363d9a5d7916f3a4ce984a929514c0df3daf3b1b2eb5e6edbb131ee771cf"}, + {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:2603c98ae04aac675fefcf71a6c87dc4bb74a75e9071ae3923bbc91a59f08d35"}, + {file = "pycryptodome-3.10.1-cp27-cp27m-win32.whl", hash = "sha256:38661348ecb71476037f1e1f553159b80d256c00f6c0b00502acac891f7116d9"}, + {file = "pycryptodome-3.10.1-cp27-cp27m-win_amd64.whl", hash = "sha256:1723ebee5561628ce96748501cdaa7afaa67329d753933296321f0be55358dce"}, + {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:77997519d8eb8a4adcd9a47b9cec18f9b323e296986528186c0e9a7a15d6a07e"}, + {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:99b2f3fc51d308286071d0953f92055504a6ffe829a832a9fc7a04318a7683dd"}, + {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:e0a4d5933a88a2c98bbe19c0c722f5483dc628d7a38338ac2cb64a7dbd34064b"}, + {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d3d6958d53ad307df5e8469cc44474a75393a434addf20ecd451f38a72fe29b8"}, + {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:a8eb8b6ea09ec1c2535bf39914377bc8abcab2c7d30fa9225eb4fe412024e427"}, + {file = "pycryptodome-3.10.1-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:31c1df17b3dc5f39600a4057d7db53ac372f492c955b9b75dd439f5d8b460129"}, + {file = "pycryptodome-3.10.1-cp35-abi3-manylinux1_i686.whl", hash = "sha256:a3105a0eb63eacf98c2ecb0eb4aa03f77f40fbac2bdde22020bb8a536b226bb8"}, + {file = "pycryptodome-3.10.1-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:a92d5c414e8ee1249e850789052608f582416e82422502dc0ac8c577808a9067"}, + {file = "pycryptodome-3.10.1-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:60386d1d4cfaad299803b45a5bc2089696eaf6cdd56f9fc17479a6f89595cfc8"}, + {file = "pycryptodome-3.10.1-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:501ab36aae360e31d0ec370cf5ce8ace6cb4112060d099b993bc02b36ac83fb6"}, + {file = "pycryptodome-3.10.1-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:fc7489a50323a0df02378bc2fff86eb69d94cc5639914346c736be981c6a02e7"}, + {file = "pycryptodome-3.10.1-cp35-abi3-win32.whl", hash = "sha256:9b6f711b25e01931f1c61ce0115245a23cdc8b80bf8539ac0363bdcf27d649b6"}, + {file = "pycryptodome-3.10.1-cp35-abi3-win_amd64.whl", hash = "sha256:7fd519b89585abf57bf47d90166903ec7b43af4fe23c92273ea09e6336af5c07"}, + {file = "pycryptodome-3.10.1-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:09c1555a3fa450e7eaca41ea11cd00afe7c91fef52353488e65663777d8524e0"}, + {file = "pycryptodome-3.10.1-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:758949ca62690b1540dfb24ad773c6da9cd0e425189e83e39c038bbd52b8e438"}, + {file = "pycryptodome-3.10.1-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:e3bf558c6aeb49afa9f0c06cee7fb5947ee5a1ff3bd794b653d39926b49077fa"}, + {file = "pycryptodome-3.10.1-pp27-pypy_73-win32.whl", hash = "sha256:f977cdf725b20f6b8229b0c87acb98c7717e742ef9f46b113985303ae12a99da"}, + {file = "pycryptodome-3.10.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6d2df5223b12437e644ce0a3be7809471ffa71de44ccd28b02180401982594a6"}, + {file = "pycryptodome-3.10.1-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:98213ac2b18dc1969a47bc65a79a8fca02a414249d0c8635abb081c7f38c91b6"}, + {file = "pycryptodome-3.10.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:12222a5edc9ca4a29de15fbd5339099c4c26c56e13c2ceddf0b920794f26165d"}, + {file = "pycryptodome-3.10.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:6bbf7fee7b7948b29d7e71fcacf48bac0c57fb41332007061a933f2d996f9713"}, + {file = "pycryptodome-3.10.1.tar.gz", hash = "sha256:3e2e3a06580c5f190df843cdb90ea28d61099cf4924334d5297a995de68e4673"}, ] pydantic = [ {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, @@ -1515,8 +1623,8 @@ pydantic = [ {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"}, ] pydash = [ - {file = "pydash-5.0.0-py3-none-any.whl", hash = "sha256:0d87f879a3df4ad9389ab6d63c69eea078517d41541ddd5744cfcff3396e8543"}, - {file = "pydash-5.0.0.tar.gz", hash = "sha256:845262df83b5411742e5f7f7dbfa5ed4d0ddac6d7d0a13c4375c6a3c40d4e8f4"}, + {file = "pydash-5.0.2-py3-none-any.whl", hash = "sha256:a0dfc36087b491653c7fbff4a04a52e1b58b67d3aa751d15e0dbb96fb7e09833"}, + {file = "pydash-5.0.2.tar.gz", hash = "sha256:7c02f5c27524abbd90743c4b60fd8c8c8e846ee0439642704f77a3cf21f7c371"}, ] pygments = [ {file = "Pygments-2.9.0-py3-none-any.whl", hash = "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"}, @@ -1530,8 +1638,8 @@ pyparsing = [ {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] python-dotenv = [ - {file = "python-dotenv-0.17.1.tar.gz", hash = "sha256:b1ae5e9643d5ed987fc57cc2583021e38db531946518130777734f9589b3141f"}, - {file = "python_dotenv-0.17.1-py2.py3-none-any.whl", hash = "sha256:00aa34e92d992e9f8383730816359647f358f4a3be1ba45e5a5cefd27ee91544"}, + {file = "python-dotenv-0.18.0.tar.gz", hash = "sha256:effaac3c1e58d89b3ccb4d04a40dc7ad6e0275fda25fd75ae9d323e2465e202d"}, + {file = "python_dotenv-0.18.0-py2.py3-none-any.whl", hash = "sha256:dd8fe852847f4fbfadabf6183ddd4c824a9651f02d51714fa075c95561959c7d"}, ] python-engineio = [ {file = "python-engineio-3.14.2.tar.gz", hash = "sha256:eab4553f2804c1ce97054c8b22cf0d5a9ab23128075248b97e1a5b2f29553085"}, @@ -1581,8 +1689,8 @@ quart = [ {file = "Quart-0.14.1.tar.gz", hash = "sha256:429c5b4ff27e1d2f9ca0aacc38f6aba0ff49b38b815448bf24b613d3de12ea02"}, ] requests = [ - {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, - {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, + {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, + {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, ] rfc3986 = [ {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, @@ -1653,24 +1761,24 @@ untokenize = [ {file = "untokenize-0.1.1.tar.gz", hash = "sha256:3865dbbbb8efb4bb5eaa72f1be7f3e0be00ea8b7f125c69cbd1f5fda926f37a2"}, ] urllib3 = [ - {file = "urllib3-1.26.5-py2.py3-none-any.whl", hash = "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c"}, - {file = "urllib3-1.26.5.tar.gz", hash = "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098"}, + {file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"}, + {file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"}, ] uvicorn = [ {file = "uvicorn-0.13.4-py3-none-any.whl", hash = "sha256:7587f7b08bd1efd2b9bad809a3d333e972f1d11af8a5e52a9371ee3a5de71524"}, {file = "uvicorn-0.13.4.tar.gz", hash = "sha256:3292251b3c7978e8e4a7868f4baf7f7f7bb7e40c759ecc125c37e99cdea34202"}, ] uvloop = [ - {file = "uvloop-0.15.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:19fa1d56c91341318ac5d417e7b61c56e9a41183946cc70c411341173de02c69"}, - {file = "uvloop-0.15.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:e5e5f855c9bf483ee6cd1eb9a179b740de80cb0ae2988e3fa22309b78e2ea0e7"}, - {file = "uvloop-0.15.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:42eda9f525a208fbc4f7cecd00fa15c57cc57646c76632b3ba2fe005004f051d"}, - {file = "uvloop-0.15.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:90e56f17755e41b425ad19a08c41dc358fa7bf1226c0f8e54d4d02d556f7af7c"}, - {file = "uvloop-0.15.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:7ae39b11a5f4cec1432d706c21ecc62f9e04d116883178b09671aa29c46f7a47"}, - {file = "uvloop-0.15.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:b45218c99795803fb8bdbc9435ff7f54e3a591b44cd4c121b02fa83affb61c7c"}, - {file = "uvloop-0.15.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:114543c84e95df1b4ff546e6e3a27521580466a30127f12172a3278172ad68bc"}, - {file = "uvloop-0.15.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44cac8575bf168601424302045234d74e3561fbdbac39b2b54cc1d1d00b70760"}, - {file = "uvloop-0.15.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:6de130d0cb78985a5d080e323b86c5ecaf3af82f4890492c05981707852f983c"}, - {file = "uvloop-0.15.2.tar.gz", hash = "sha256:2bb0624a8a70834e54dde8feed62ed63b50bad7a1265c40d6403a2ac447bce01"}, + {file = "uvloop-0.15.3-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:e71fb9038bfcd7646ca126c5ef19b17e48d4af9e838b2bcfda7a9f55a6552a32"}, + {file = "uvloop-0.15.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7522df4e45e4f25b50adbbbeb5bb9847495c438a628177099d2721f2751ff825"}, + {file = "uvloop-0.15.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae2b325c0f6d748027f7463077e457006b4fdb35a8788f01754aadba825285ee"}, + {file = "uvloop-0.15.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:0de811931e90ae2da9e19ce70ffad73047ab0c1dba7c6e74f9ae1a3aabeb89bd"}, + {file = "uvloop-0.15.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7f4b8a905df909a407c5791fb582f6c03b0d3b491ecdc1cdceaefbc9bf9e08f6"}, + {file = "uvloop-0.15.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d8ffe44ae709f839c54bacf14ed283f41bee90430c3b398e521e10f8d117b3a"}, + {file = "uvloop-0.15.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:63a3288abbc9c8ee979d7e34c34e780b2fbab3e7e53d00b6c80271119f277399"}, + {file = "uvloop-0.15.3-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5cda65fc60a645470b8525ce014516b120b7057b576fa876cdfdd5e60ab1efbb"}, + {file = "uvloop-0.15.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ff05116ede1ebdd81802df339e5b1d4cab1dfbd99295bf27e90b4cec64d70e9"}, + {file = "uvloop-0.15.3.tar.gz", hash = "sha256:905f0adb0c09c9f44222ee02f6b96fd88b493478fffb7a345287f9444e926030"}, ] watchgod = [ {file = "watchgod-0.7-py3-none-any.whl", hash = "sha256:d6c1ea21df37847ac0537ca0d6c2f4cdf513562e95f77bb93abbcf05573407b7"}, diff --git a/pyproject.toml b/pyproject.toml index 95ed14f7..d86ecf72 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ nonebot-plugin-test = "^0.2.0" nonebot-adapter-cqhttp = { path = "./packages/nonebot-adapter-cqhttp", develop = true } nonebot-adapter-ding = { path = "./packages/nonebot-adapter-ding", develop = true } nonebot-adapter-mirai = { path = "./packages/nonebot-adapter-mirai", develop = true } +nonebot-adapter-feishu = { path = "./packages/nonebot-adapter-feishu", develop = true } sphinx-markdown-builder = { git = "https://github.com/nonebot/sphinx-markdown-builder.git" } [tool.poetry.extras] diff --git a/tests/.env.dev b/tests/.env.dev index 555cbe59..8e167d1d 100644 --- a/tests/.env.dev +++ b/tests/.env.dev @@ -16,3 +16,7 @@ CUSTOM_CONFIG3= MIRAI_AUTH_KEY=12345678 MIRAI_HOST=127.0.0.1 MIRAI_PORT=8080 + +APP_ID=111111111111 +APP_SECRET=222222222222222222 +VERIFICATION_TOKEN=3333333333333333333333333333 diff --git a/tests/bot.py b/tests/bot.py index f60c1878..ccccd009 100644 --- a/tests/bot.py +++ b/tests/bot.py @@ -7,6 +7,7 @@ import nonebot from nonebot.adapters.cqhttp import Bot from nonebot.adapters.ding import Bot as DingBot from nonebot.adapters.mirai import Bot as MiraiBot +from nonebot.adapters.feishu import Bot as FeishuBot from nonebot.log import logger, default_format # test custom log @@ -22,6 +23,7 @@ driver = nonebot.get_driver() driver.register_adapter("cqhttp", Bot) driver.register_adapter("ding", DingBot) driver.register_adapter("mirai", MiraiBot) +driver.register_adapter("feishu", FeishuBot) # load builtin plugin nonebot.load_builtin_plugins() diff --git a/tests/test_plugins/test_feishu.py b/tests/test_plugins/test_feishu.py new file mode 100644 index 00000000..54a0d7ae --- /dev/null +++ b/tests/test_plugins/test_feishu.py @@ -0,0 +1,12 @@ +from nonebot.adapters.feishu.event import MessageEvent +from nonebot.rule import to_me +from nonebot.plugin import on_command +from nonebot.adapters.feishu import Bot as FeishuBot, MessageSegment, MessageEvent + +helper = on_command("say") + + +@helper.handle() +async def feishu_helper(bot: FeishuBot, event: MessageEvent): + message = event.get_message() + await helper.finish(message, at_sender=True)