From 78276e51eecb370140a973490fce5d631fb58e23 Mon Sep 17 00:00:00 2001 From: felinae98 <731499577@qq.com> Date: Wed, 16 Feb 2022 21:42:49 +0800 Subject: [PATCH] update --- docs/dev/README.md | 49 +++++-- poetry.lock | 76 +++++----- pyproject.toml | 4 +- .../nonebot_bison/admin_page/__init__.py | 5 +- src/plugins/nonebot_bison/config_manager.py | 30 ++-- src/plugins/nonebot_bison/types.py | 4 +- tests/conftest.py | 1 + tests/platforms/static/weibo_ak_profile.json | 1 + tests/platforms/static/weibo_err_profile.json | 1 + tests/test_config.py | 52 +++++++ tests/test_config_manager.py | 137 ++++++++++++------ tests/utils.py | 70 +++++++++ 12 files changed, 317 insertions(+), 113 deletions(-) create mode 100644 tests/platforms/static/weibo_ak_profile.json create mode 100644 tests/platforms/static/weibo_err_profile.json create mode 100644 tests/test_config.py create mode 100644 tests/utils.py diff --git a/docs/dev/README.md b/docs/dev/README.md index d2dc9f9..f69fe22 100644 --- a/docs/dev/README.md +++ b/docs/dev/README.md @@ -34,19 +34,50 @@ sidebar: auto #### 这个平台是否有账号的概念 -- `nonebot_bison.platform.platform.TargetMixin` 有账号的概念 - 例如:Bilibili 用户,微博用户 -- `nonebot_bison.platform.platform.NoTargetMixin` 没有账号的概念 - 例如:游戏公告,教务处公告 +- 有账号的概念 + 例如:B 站用户动态,微博用户动态,网易云电台更新 +- 没有账号的概念 + 例如:游戏公告,教务处公告 -现在你已经选择了两个类,现在你需要在`src/plugins/nonebot_bison/platform`下新建一个 py 文件, -在里面新建一个类,继承你刚刚选择的两个类,重载一些关键的函数,然后……就完成了,不需要修改别的东西了。 +现在你需要在`src/plugins/nonebot_bison/platform`下新建一个 py 文件, +在里面新建一个类,继承推送类型的基类,重载一些关键的函数,然后……就完成了,不需要修改别的东西了。 -例如要适配微博,微博有账号,并且我希望 bot 搬运新的消息,所以微博的类应该这样定义: +任何一种订阅类型需要实现的方法/字段如下: + +- `schedule_type`, `schedule_kw` 调度的参数,本质是使用 apscheduler 的[trigger 参数](https://apscheduler.readthedocs.io/en/3.x/userguide.html?highlight=trigger#choosing-the-right-scheduler-job-store-s-executor-s-and-trigger-s),`schedule_type`可以是`date`,`interval`和`cron`, + `schedule_kw`是对应的参数,一个常见的配置是`schedule_type=interval`, `schedule_kw={'seconds':30}` +- `is_common` 是否常用,如果被标记为常用,那么和机器人交互式对话添加订阅时,会直接出现在选择列表中,否则 + 需要输入`全部`才会出现。 +- `enabled` 是否启用 +- `name` 平台的正式名称,例如`微博` +- `has_target` 平台是否有“帐号” +- `category` 平台的发布内容分类,例如 B 站包括专栏,视频,图文动态,普通动态等,如果不包含分类功能则设为`{}` +- `enable_tag` 平台发布内容是否带 Tag,例如微博 +- `platform_name` 唯一的,英文的识别标识,比如`weibo` +- `async get_target_name(Target) -> Optional[str]` 通常用于获取帐号的名称,如果平台没有帐号概念,可以直接返回平台的`name` +- `async parse(RawPost) -> Post`将获取到的 RawPost 处理成 Post +- `get_tags(RawPost) -> Optional[Collection[Tag]]` (可选) 从 RawPost 中提取 Tag +- `get_category(RawPos) -> Optional[Category]` (可选)从 RawPost 中提取 Category + +例如要适配微博,我希望 bot 搬运新的消息,所以微博的类应该这样定义: ```python -class Weibo(NewMessage, TargetMixin): - ... +class Weibo(NewMessage): + + categories = { + 1: "转发", + 2: "视频", + 3: "图文", + 4: "文字", + } + enable_tag = True + platform_name = "weibo" + name = "新浪微博" + enabled = True + is_common = True + schedule_type = "interval" + schedule_kw = {"seconds": 3} + has_target = True ``` 当然我们非常希望你对自己适配的平台写一些单元测试,你可以模仿`tests/platforms/test_*.py`中的内容写 diff --git a/poetry.lock b/poetry.lock index f9c578e..3072c41 100644 --- a/poetry.lock +++ b/poetry.lock @@ -325,7 +325,7 @@ tests = ["dill", "coverage", "coveralls", "mock", "nose"] [[package]] name = "fastapi" -version = "0.70.1" +version = "0.73.0" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" category = "main" optional = false @@ -333,12 +333,12 @@ python-versions = ">=3.6.1" [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.16.0" +starlette = "0.17.1" [package.extras] all = ["requests (>=2.24.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<3.0.0)", "pyyaml (>=5.3.1,<6.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.16.0)"] dev = ["python-jose[cryptography] (>=3.3.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.16.0)"] -doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=7.1.9,<8.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "typer-cli (>=0.0.12,<0.0.13)", "pyyaml (>=5.3.1,<6.0.0)"] +doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "typer-cli (>=0.0.12,<0.0.13)", "pyyaml (>=5.3.1,<6.0.0)"] test = ["pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "mypy (==0.910)", "flake8 (>=3.8.3,<4.0.0)", "black (==21.9b0)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "orjson (>=3.2.1,<4.0.0)", "ujson (>=4.0.1,<5.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "flask (>=1.1.2,<3.0.0)", "anyio[trio] (>=3.2.1,<4.0.0)", "types-ujson (==0.1.1)", "types-orjson (==3.6.0)", "types-dataclasses (==0.1.7)"] [[package]] @@ -564,7 +564,7 @@ jinja2 = "*" [[package]] name = "loguru" -version = "0.5.3" +version = "0.6.0" description = "Python logging made (stupidly) simple" category = "main" optional = false @@ -575,7 +575,7 @@ 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)"] +dev = ["colorama (>=0.3.4)", "docutils (==0.16)", "flake8 (>=3.7.7)", "tox (>=3.9.0)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "black (>=19.10b0)", "isort (>=5.1.1)", "Sphinx (>=4.1.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)"] [[package]] name = "markupsafe" @@ -614,7 +614,7 @@ python-versions = "*" [[package]] name = "nb-cli" -version = "0.6.5" +version = "0.6.6" description = "CLI for nonebot2" category = "dev" optional = false @@ -628,7 +628,7 @@ httpx = ">=0.18.0,<1.0.0" nonebot2 = ">=2.0.0-beta.1,<3.0.0" prompt-toolkit = ">=3.0.19,<4.0.0" pyfiglet = ">=0.8.post1,<0.9" -tomlkit = ">=0.7.0,<0.8.0" +tomlkit = ">=0.9.0,<0.10.0" wcwidth = ">=0.2.5,<0.3.0" [package.extras] @@ -679,26 +679,26 @@ nonebot2 = ">=2.0.0.a16,<3.0.0" [[package]] name = "nonebot2" -version = "2.0.0b1" +version = "2.0.0b2" description = "An asynchronous python bot framework." category = "main" optional = false python-versions = ">=3.7.3,<4.0.0" [package.dependencies] -fastapi = ">=0.70.0,<0.71.0" -loguru = ">=0.5.1,<0.6.0" +fastapi = ">=0.73.0,<0.74.0" +loguru = ">=0.6.0,<0.7.0" pydantic = {version = ">=1.9.0,<1.10.0", extras = ["dotenv"]} pygtrie = ">=2.4.1,<3.0.0" -tomlkit = ">=0.7.0,<0.8.0" +tomlkit = ">=0.9.0,<0.10.0" typing-extensions = ">=3.10.0,<5.0.0" -uvicorn = {version = ">=0.15.0,<0.16.0", extras = ["standard"]} +uvicorn = {version = ">=0.17.0,<0.18.0", extras = ["standard"]} yarl = ">=1.7.2,<2.0.0" [package.extras] quart = ["Quart (>=0.16.0,<0.17.0)"] -all = ["Quart (>=0.16.0,<0.17.0)", "websockets (>=9.1)", "aiohttp[speedups] (>=3.7.4,<4.0.0)", "httpx[http2] (>=0.20.0,<1.0.0)"] -websockets = ["websockets (>=9.1)"] +all = ["Quart (>=0.16.0,<0.17.0)", "websockets (>=10.0,<11.0)", "aiohttp[speedups] (>=3.7.4,<4.0.0)", "httpx[http2] (>=0.20.0,<1.0.0)"] +websockets = ["websockets (>=10.0,<11.0)"] aiohttp = ["aiohttp[speedups] (>=3.7.4,<4.0.0)"] httpx = ["httpx[http2] (>=0.20.0,<1.0.0)"] @@ -1183,7 +1183,7 @@ tests = ["pytest", "typeguard", "pygments", "littleutils"] [[package]] name = "starlette" -version = "0.16.0" +version = "0.17.1" description = "The little ASGI library that shines." category = "main" optional = false @@ -1193,7 +1193,7 @@ python-versions = ">=3.6" anyio = ">=3.0.0,<4" [package.extras] -full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests", "graphene"] +full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"] [[package]] name = "text-unidecode" @@ -1229,11 +1229,11 @@ python-versions = ">=3.6" [[package]] name = "tomlkit" -version = "0.7.2" +version = "0.9.2" description = "Style preserving TOML library" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6,<4.0" [[package]] name = "traitlets" @@ -1293,26 +1293,26 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "uvicorn" -version = "0.15.0" +version = "0.17.5" description = "The lightning-fast ASGI server." category = "main" optional = false -python-versions = "*" +python-versions = ">=3.7" [package.dependencies] asgiref = ">=3.4.0" click = ">=7.0" colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} h11 = ">=0.8" -httptools = {version = ">=0.2.0,<0.3.0", optional = true, markers = "extra == \"standard\""} +httptools = {version = ">=0.2.0,<0.4.0", optional = true, markers = "extra == \"standard\""} python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} PyYAML = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} 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 = ">=9.1", optional = true, markers = "extra == \"standard\""} +websockets = {version = ">=10.0", optional = true, markers = "extra == \"standard\""} [package.extras] -standard = ["websockets (>=9.1)", "httptools (>=0.2.0,<0.3.0)", "watchgod (>=0.6)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"] +standard = ["websockets (>=10.0)", "httptools (>=0.2.0,<0.4.0)", "watchgod (>=0.6)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"] [[package]] name = "uvloop" @@ -1395,7 +1395,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "c67bc1707866ddff4ec0a895755734ff30821321f78a82a4038a33418723bc97" +content-hash = "2f409abbd0b4f5a528a4dc49e3f4837a4fe86dcbb26ae0a225037520f94b3101" [metadata.files] aiofiles = [ @@ -1568,8 +1568,8 @@ expiringdict = [ {file = "expiringdict-1.2.1.tar.gz", hash = "sha256:fe2ba427220425c3c8a3d29f6d2e2985bcee323f8bcd4021e68ebefbd90d8250"}, ] fastapi = [ - {file = "fastapi-0.70.1-py3-none-any.whl", hash = "sha256:5367226c7bcd7bfb2e17edaf225fd9a983095b1372281e9a3eb661336fb93748"}, - {file = "fastapi-0.70.1.tar.gz", hash = "sha256:21d03979b5336375c66fa5d1f3126c6beca650d5d2166fbb78345a30d33c8d06"}, + {file = "fastapi-0.73.0-py3-none-any.whl", hash = "sha256:f0a618aff5f6942862f2d3f20f39b1c037e33314d1b8207fd1c3a2cca76dfd8c"}, + {file = "fastapi-0.73.0.tar.gz", hash = "sha256:dcfee92a7f9a72b5d4b7ca364bd2b009f8fc10d95ed5769be20e94f39f7e5a15"}, ] feedparser = [ {file = "feedparser-6.0.8-py3-none-any.whl", hash = "sha256:1b7f57841d9cf85074deb316ed2c795091a238adb79846bc46dccdaf80f9c59a"}, @@ -1696,8 +1696,8 @@ jinja2-time = [ {file = "jinja2_time-0.2.0-py2.py3-none-any.whl", hash = "sha256:d3eab6605e3ec8b7a0863df09cc1d23714908fa61aa6986a845c20ba488b4efa"}, ] loguru = [ - {file = "loguru-0.5.3-py3-none-any.whl", hash = "sha256:f8087ac396b5ee5f67c963b495d615ebbceac2796379599820e324419d53667c"}, - {file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"}, + {file = "loguru-0.6.0-py3-none-any.whl", hash = "sha256:4e2414d534a2ab57573365b3e6d0234dfb1d84b68b7f3b948e6fb743860a77c3"}, + {file = "loguru-0.6.0.tar.gz", hash = "sha256:066bd06758d0a513e9836fd9c6b5a75bfb3fd36841f4b996bc60b547a309d41c"}, ] markupsafe = [ {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, @@ -1853,8 +1853,8 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] nb-cli = [ - {file = "nb-cli-0.6.5.tar.gz", hash = "sha256:4fba1726419a429e14371c293d2c4ffdc4544f333fead2944febf9692f927774"}, - {file = "nb_cli-0.6.5-py3-none-any.whl", hash = "sha256:f19026c9f90b9edeaab8fe05db828f570005ce7baa4f49019ee1276f99754ac3"}, + {file = "nb-cli-0.6.6.tar.gz", hash = "sha256:501a747dda00b2d384421634a98b136d9976d12d950d54f1e2bb3c71236ffa73"}, + {file = "nb_cli-0.6.6-py3-none-any.whl", hash = "sha256:72a3929e0de4405f5dee7a43cd5358065b36b39125bc99d7dbb1d672bf7f7713"}, ] nodeenv = [ {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, @@ -1873,8 +1873,8 @@ nonebot-plugin-help = [ {file = "nonebot_plugin_help-0.1.5-py3-none-any.whl", hash = "sha256:1c4bb626feb202515f47311e3ca9872ab6c0db5e935bc9561325fcc1a2591511"}, ] nonebot2 = [ - {file = "nonebot2-2.0.0b1-py3-none-any.whl", hash = "sha256:76ee4271a0ba9b1fbdcb34c50a23df391bed00d5d32d63b7fd018685de8130a4"}, - {file = "nonebot2-2.0.0b1.tar.gz", hash = "sha256:e23b6656943738a9f8559e20ac51a8c3af799a2eea01e3e2188be01959f36455"}, + {file = "nonebot2-2.0.0b2-py3-none-any.whl", hash = "sha256:8166490311b607f8fbf5e31934b005e29f6d39ff222a6771ec36c9456ec337ec"}, + {file = "nonebot2-2.0.0b2.tar.gz", hash = "sha256:2950f27a62f2a98b2abf3128c19d898a24c2867e70fb5c6af231eadf558b18a8"}, ] nonebug = [ {file = "nonebug-0.2.0-py3-none-any.whl", hash = "sha256:aa0f639f3fc039803640a952b1c12088e8b1ec5a30d1632e43301f215f96de22"}, @@ -2151,8 +2151,8 @@ stack-data = [ {file = "stack_data-0.1.4.tar.gz", hash = "sha256:7769ed2482ce0030e00175dd1bf4ef1e873603b6ab61cd3da443b410e64e9477"}, ] starlette = [ - {file = "starlette-0.16.0-py3-none-any.whl", hash = "sha256:38eb24bf705a2c317e15868e384c1b8a12ca396e5a3c3a003db7e667c43f939f"}, - {file = "starlette-0.16.0.tar.gz", hash = "sha256:e1904b5d0007aee24bdd3c43994be9b3b729f4f58e740200de1d623f8c3a8870"}, + {file = "starlette-0.17.1-py3-none-any.whl", hash = "sha256:26a18cbda5e6b651c964c12c88b36d9898481cd428ed6e063f5f29c418f73050"}, + {file = "starlette-0.17.1.tar.gz", hash = "sha256:57eab3cc975a28af62f6faec94d355a410634940f10b30d68d31cb5ec1b44ae8"}, ] text-unidecode = [ {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, @@ -2171,8 +2171,8 @@ tomli = [ {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"}, ] tomlkit = [ - {file = "tomlkit-0.7.2-py2.py3-none-any.whl", hash = "sha256:173ad840fa5d2aac140528ca1933c29791b79a374a0861a80347f42ec9328117"}, - {file = "tomlkit-0.7.2.tar.gz", hash = "sha256:d7a454f319a7e9bd2e249f239168729327e4dd2d27b17dc68be264ad1ce36754"}, + {file = "tomlkit-0.9.2-py3-none-any.whl", hash = "sha256:daf4f9c5f2fbf6b861d6adfc51940b98dee36c13e1d88749a6dc9fb280fff304"}, + {file = "tomlkit-0.9.2.tar.gz", hash = "sha256:ebd982d61446af95a1e082b103e250cb9e6d152eae2581d4a07d31a70b34ab0f"}, ] traitlets = [ {file = "traitlets-5.1.1-py3-none-any.whl", hash = "sha256:2d313cc50a42cd6c277e7d7dc8d4d7fedd06a2c215f78766ae7b1a66277e0033"}, @@ -2195,8 +2195,8 @@ urllib3 = [ {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, ] uvicorn = [ - {file = "uvicorn-0.15.0-py3-none-any.whl", hash = "sha256:17f898c64c71a2640514d4089da2689e5db1ce5d4086c2d53699bf99513421c1"}, - {file = "uvicorn-0.15.0.tar.gz", hash = "sha256:d9a3c0dd1ca86728d3e235182683b4cf94cd53a867c288eaeca80ee781b2caff"}, + {file = "uvicorn-0.17.5-py3-none-any.whl", hash = "sha256:8adddf629b79857b48b999ae1b14d6c92c95d4d7840bd86461f09bee75f1653e"}, + {file = "uvicorn-0.17.5.tar.gz", hash = "sha256:c04a9c069111489c324f427501b3840d306c6b91a77b00affc136a840a3f45f1"}, ] uvloop = [ {file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6224f1401025b748ffecb7a6e2652b17768f30b1a6a3f7b44660e5b5b690b12d"}, diff --git a/pyproject.toml b/pyproject.toml index 4cb3ab9..8f182ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ [tool.poetry.dependencies] python = "^3.9" -nonebot2 = "^2.0.0-beta.1" +nonebot2 = "^2.0.0-beta.2" httpx = ">=0.16.1 <1.0.0" bs4 = "^0.0.1" tinydb = "^4.3.0" @@ -49,7 +49,7 @@ nonebug = "^0.2.0" black = "^22.1.0" isort = "^5.10.1" pre-commit = "^2.17.0" -nb-cli = "^0.6.5" +nb-cli = "^0.6.6" [build-system] requires = ["poetry>=0.12"] diff --git a/src/plugins/nonebot_bison/admin_page/__init__.py b/src/plugins/nonebot_bison/admin_page/__init__.py index eaecd1c..a7b8292 100644 --- a/src/plugins/nonebot_bison/admin_page/__init__.py +++ b/src/plugins/nonebot_bison/admin_page/__init__.py @@ -10,7 +10,6 @@ from nonebot.adapters.onebot.v11 import Bot from nonebot.adapters.onebot.v11.event import GroupMessageEvent, PrivateMessageEvent from nonebot.drivers.fastapi import Driver from nonebot.log import logger -from nonebot.params import State from nonebot.rule import to_me from nonebot.typing import T_State @@ -152,9 +151,7 @@ if (STATIC_PATH / "index.html").exists(): get_token = on_command("后台管理", rule=to_me(), priority=5) @get_token.handle() - async def send_token( - bot: "Bot", event: PrivateMessageEvent, state: T_State = State() - ): + async def send_token(bot: "Bot", event: PrivateMessageEvent, state: T_State): token = tm.get_user_token((event.get_user_id(), event.sender.nickname)) await get_token.finish(f"请访问: {plugin_config.bison_outer_url}auth/{token}") diff --git a/src/plugins/nonebot_bison/config_manager.py b/src/plugins/nonebot_bison/config_manager.py index a171b81..bbec1e9 100644 --- a/src/plugins/nonebot_bison/config_manager.py +++ b/src/plugins/nonebot_bison/config_manager.py @@ -1,12 +1,12 @@ from typing import Type from nonebot import on_command -from nonebot.adapters._event import Event as AbstractEvent +from nonebot.adapters import Event as AbstractEvent from nonebot.adapters.onebot.v11 import Bot, Event from nonebot.adapters.onebot.v11.message import Message from nonebot.adapters.onebot.v11.permission import GROUP_ADMIN, GROUP_OWNER from nonebot.matcher import Matcher -from nonebot.params import Depends, State +from nonebot.params import Depends from nonebot.permission import SUPERUSER from nonebot.rule import to_me from nonebot.typing import T_State @@ -42,7 +42,7 @@ async def send_help(): def do_add_sub(add_sub: Type[Matcher]): @add_sub.handle() - async def init_promote(state: T_State = State()): + async def init_promote(state: T_State): state["_prompt"] = ( "请输入想要订阅的平台,目前支持,请输入冒号左边的名称:\n" + "".join( @@ -56,7 +56,7 @@ def do_add_sub(add_sub: Type[Matcher]): + "要查看全部平台请输入:“全部”" ) - async def parse_platform(event: AbstractEvent, state: T_State = State()) -> None: + async def parse_platform(event: AbstractEvent, state: T_State) -> None: if not isinstance(state["platform"], Message): return platform = str(event.get_message()).strip() @@ -76,7 +76,7 @@ def do_add_sub(add_sub: Type[Matcher]): @add_sub.got( "platform", _gen_prompt_template("{_prompt}"), [Depends(parse_platform)] ) - async def init_id(state: T_State = State()): + async def init_id(state: T_State): if platform_manager[state["platform"]].has_target: state[ "_prompt" @@ -87,7 +87,7 @@ def do_add_sub(add_sub: Type[Matcher]): Target("") ) - async def parse_id(event: AbstractEvent, state: T_State = State()): + async def parse_id(event: AbstractEvent, state: T_State): if not isinstance(state["id"], Message): return target = str(event.get_message()).strip() @@ -101,7 +101,7 @@ def do_add_sub(add_sub: Type[Matcher]): await add_sub.reject("id输入错误") @add_sub.got("id", _gen_prompt_template("{_prompt}"), [Depends(parse_id)]) - async def init_cat(state: T_State = State()): + async def init_cat(state: T_State): if not platform_manager[state["platform"]].categories: state["cats"] = [] return @@ -109,7 +109,7 @@ def do_add_sub(add_sub: Type[Matcher]): " ".join(list(platform_manager[state["platform"]].categories.values())) ) - async def parser_cats(event: AbstractEvent, state: T_State = State()): + async def parser_cats(event: AbstractEvent, state: T_State): if not isinstance(state["cats"], Message): return res = [] @@ -120,13 +120,13 @@ def do_add_sub(add_sub: Type[Matcher]): state["cats"] = res @add_sub.got("cats", _gen_prompt_template("{_prompt}"), [Depends(parser_cats)]) - async def init_tag(state: T_State = State()): + async def init_tag(state: T_State): if not platform_manager[state["platform"]].enable_tag: state["tags"] = [] return state["_prompt"] = '请输入要订阅的tag,订阅所有tag输入"全部标签"' - async def parser_tags(event: AbstractEvent, state: T_State = State()): + async def parser_tags(event: AbstractEvent, state: T_State): if not isinstance(state["tags"], Message): return if str(event.get_message()).strip() == "全部标签": @@ -135,7 +135,7 @@ def do_add_sub(add_sub: Type[Matcher]): state["tags"] = str(event.get_message()).strip().split() @add_sub.got("tags", _gen_prompt_template("{_prompt}"), [Depends(parser_tags)]) - async def add_sub_process(event: Event, state: T_State = State()): + async def add_sub_process(event: Event, state: T_State): config = Config() config.add_subscribe( state.get("_user_id") or event.group_id, @@ -151,7 +151,7 @@ def do_add_sub(add_sub: Type[Matcher]): def do_query_sub(query_sub: Type[Matcher]): @query_sub.handle() - async def _(event: Event, state: T_State = State()): + async def _(event: Event, state: T_State): config: Config = Config() sub_list = config.list_subscribe( state.get("_user_id") or event.group_id, "group" @@ -176,7 +176,7 @@ def do_query_sub(query_sub: Type[Matcher]): def do_del_sub(del_sub: Type[Matcher]): @del_sub.handle() - async def send_list(bot: Bot, event: Event, state: T_State = State()): + async def send_list(bot: Bot, event: Event, state: T_State): config: Config = Config() sub_list = config.list_subscribe( state.get("_user_id") or event.group_id, "group" @@ -205,7 +205,7 @@ def do_del_sub(del_sub: Type[Matcher]): await bot.send(event=event, message=Message(await parse_text(res))) @del_sub.receive() - async def do_del(event: Event, state: T_State = State()): + async def do_del(event: Event, state: T_State): try: index = int(str(event.get_message()).strip()) config = Config() @@ -220,7 +220,7 @@ def do_del_sub(del_sub: Type[Matcher]): await del_sub.finish("删除成功") -async def parse_group_number(event: AbstractEvent, state: T_State = State()): +async def parse_group_number(event: AbstractEvent, state: T_State): if not isinstance(state["_user_id"], Message): return state["_user_id"] = int(str(event.get_message())) diff --git a/src/plugins/nonebot_bison/types.py b/src/plugins/nonebot_bison/types.py index 0bd67a1..f447f38 100644 --- a/src/plugins/nonebot_bison/types.py +++ b/src/plugins/nonebot_bison/types.py @@ -3,8 +3,8 @@ from typing import Any, Callable, Literal, NamedTuple, NewType RawPost = NewType("RawPost", Any) Target = NewType("Target", str) -Category = NewType("Category", int) -Tag = NewType("Tag", str) +Category = int +Tag = str @dataclass(eq=True, frozen=True) diff --git a/tests/conftest.py b/tests/conftest.py index 4710617..0fb5aca 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,6 +12,7 @@ async def app(nonebug_init: None, tmp_path: Path, monkeypatch: pytest.MonkeyPatc config = nonebot.get_driver().config config.bison_config_path = str(tmp_path) + config.command_start = {""} return App(monkeypatch) diff --git a/tests/platforms/static/weibo_ak_profile.json b/tests/platforms/static/weibo_ak_profile.json new file mode 100644 index 0000000..cf95c3f --- /dev/null +++ b/tests/platforms/static/weibo_ak_profile.json @@ -0,0 +1 @@ +{"ok":1,"data":{"isVideoCoverStyle":1,"isStarStyle":0,"userInfo":{"id":6279793937,"screen_name":"\u660e\u65e5\u65b9\u821fArknights","profile_image_url":"https:\/\/tvax4.sinaimg.cn\/crop.0.0.756.756.180\/006QZngZly8gdj05mufr9j30l00l0dq4.jpg?KID=imgbed,tva&Expires=1645010805&ssig=POD2VKV4LB","profile_url":"https:\/\/m.weibo.cn\/u\/6279793937?uid=6279793937&luicode=10000011&lfid=1005056279793937","statuses_count":1553,"verified":true,"verified_type":2,"verified_type_ext":50,"verified_reason":"\u4e0a\u6d77\u9e70\u89d2\u7f51\u7edc\u79d1\u6280\u6709\u9650\u516c\u53f8","close_blue_v":false,"description":"\u300a\u660e\u65e5\u65b9\u821f\u300b\u5b98\u65b9\u5fae\u535a\uff0c\u6b22\u8fce\u54a8\u8be2\u5ba2\u670dQQ\u670d\u52a1\u53f7\uff1a800830064","gender":"f","mbtype":12,"urank":4,"mbrank":7,"follow_me":false,"following":false,"follow_count":34,"followers_count":"145.5\u4e07","followers_count_str":"145.5\u4e07","cover_image_phone":"https:\/\/wx4.sinaimg.cn\/crop.0.0.640.640.640\/006QZngZly1gq8sa16csgj30u00u0akt.jpg","avatar_hd":"https:\/\/wx4.sinaimg.cn\/orj480\/006QZngZly8gdj05mufr9j30l00l0dq4.jpg","like":false,"like_me":false,"toolbar_menus":[{"type":"link","name":"\u804a\u5929","pic":"http:\/\/h5.sinaimg.cn\/upload\/2015\/06\/12\/2\/toolbar_icon_discuss_default.png","params":{"scheme":"sinaweibo:\/\/messagelist?uid=6279793937&nick=\u660e\u65e5\u65b9\u821fArknights"},"scheme":"https:\/\/passport.weibo.cn\/signin\/welcome?entry=mweibo&r=https%3A%2F%2Fm.weibo.cn%2Fapi%2Fcontainer%2FgetIndex%3Fcontainerid%3D1005056279793937"},{"type":"profile_follow","name":"\u5173\u6ce8","pic":"","params":{"uid":6279793937,"extparams":{"followcardid":"0001980001_6279793937_b2f217e4"}},"userInfo":{"id":6279793937,"idstr":"6279793937","screen_name":"\u660e\u65e5\u65b9\u821fArknights","profile_image_url":"https:\/\/tvax4.sinaimg.cn\/crop.0.0.756.756.50\/006QZngZly8gdj05mufr9j30l00l0dq4.jpg?KID=imgbed,tva&Expires=1645010805&ssig=pKJtZwv%2FHm","following":false,"verified":true,"verified_type":2,"remark":"","avatar_large":"https:\/\/tvax4.sinaimg.cn\/crop.0.0.756.756.180\/006QZngZly8gdj05mufr9j30l00l0dq4.jpg?KID=imgbed,tva&Expires=1645010805&ssig=POD2VKV4LB","avatar_hd":"https:\/\/tvax4.sinaimg.cn\/crop.0.0.756.756.1024\/006QZngZly8gdj05mufr9j30l00l0dq4.jpg?KID=imgbed,tva&Expires=1645010805&ssig=RBGOJz6PB6","verified_type_ext":50,"follow_me":false,"mbtype":12,"mbrank":6,"level":2,"type":1,"story_read_state":-1,"allow_msg":1,"friendships_relation":0,"close_friends_type":0,"special_follow":false}}]},"fans_scheme":"https:\/\/m.weibo.cn\/c\/attention\/visit?showmenu=0&sign=6279793937&source=MQ%3D%3D&role=NA%3D%3D&luicode=10000011&lfid=1005056279793937","follow_scheme":"https:\/\/m.weibo.cn\/c\/attention\/visit?showmenu=0&sign=6279793937&source=MA%3D%3D&role=NA%3D%3D&luicode=10000011&lfid=1005056279793937","tabsInfo":{"selectedTab":1,"tabs":[{"id":1,"tabKey":"profile","must_show":1,"hidden":0,"title":"\u7cbe\u9009","tab_type":"profile","containerid":"2302836279793937"},{"id":2,"tabKey":"weibo","must_show":1,"hidden":0,"title":"\u5fae\u535a","tab_type":"weibo","containerid":"1076036279793937","apipath":"\/profile\/statuses","tab_icon":"https:\/\/h5.sinaimg.cn\/upload\/1059\/799\/2021\/04\/01\/weibotab.png","tab_icon_dark":"https:\/\/h5.sinaimg.cn\/upload\/1059\/799\/2021\/04\/07\/weibotab_dark.png","url":"\/index\/my"},{"id":4,"tabKey":"original_video","must_show":0,"hidden":0,"title":"\u89c6\u9891","tab_type":"video","containerid":"2315676279793937"},{"id":10,"tabKey":"album","must_show":0,"hidden":0,"title":"\u76f8\u518c","tab_type":"album","containerid":"1078036279793937"}]},"profile_ext":"touid:6279793937","scheme":"sinaweibo:\/\/userinfo?uid=6279793937&_T_WM=38226655008&v_p=42&uid=6279793937&luicode=10000011&lfid=1005056279793937","showAppTips":0}} \ No newline at end of file diff --git a/tests/platforms/static/weibo_err_profile.json b/tests/platforms/static/weibo_err_profile.json new file mode 100644 index 0000000..40f1cbd --- /dev/null +++ b/tests/platforms/static/weibo_err_profile.json @@ -0,0 +1 @@ +{"ok":0,"msg":"\u8fd9\u91cc\u8fd8\u6ca1\u6709\u5185\u5bb9","data":{"cards":[]}} \ No newline at end of file diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..1673309 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,52 @@ +import typing + +import pytest +from nonebug.app import App + +if typing.TYPE_CHECKING: + import sys + + sys.path.append("./src/plugins") + import nonebot_bison + from nonebot_bison.config import Config + + +@pytest.fixture +def config(app: App): + from nonebot_bison import config + + config.start_up() + return config.Config() + + +def test_create_and_get(config: "Config", app: App): + from nonebot_bison import types + from nonebot_bison.types import Target + + config.add_subscribe( + user="123", + user_type="group", + target="weibo_id", + target_name="weibo_name", + target_type="weibo", + cats=[], + tags=[], + ) + confs = config.list_subscribe("123", "group") + assert len(confs) == 1 + assert config.target_user_cache["weibo"][Target("weibo_id")] == [ + types.User("123", "group") + ] + assert confs[0]["cats"] == [] + config.update_subscribe( + user="123", + user_type="group", + target="weibo_id", + target_name="weibo_name", + target_type="weibo", + cats=["1"], + tags=[], + ) + confs = config.list_subscribe("123", "group") + assert len(confs) == 1 + assert confs[0]["cats"] == ["1"] diff --git a/tests/test_config_manager.py b/tests/test_config_manager.py index 1673309..b76820d 100644 --- a/tests/test_config_manager.py +++ b/tests/test_config_manager.py @@ -1,52 +1,103 @@ -import typing - import pytest +import respx +from httpx import Response +from nonebot.adapters.onebot.v11.event import Sender +from nonebot.adapters.onebot.v11.message import MessageSegment from nonebug.app import App -if typing.TYPE_CHECKING: - import sys +from .platforms.utils import get_json +from .utils import fake_admin_user, fake_group_message_event - sys.path.append("./src/plugins") - import nonebot_bison + +@pytest.mark.asyncio +@respx.mock +async def test_add_with_target(app: App): + from nonebot.adapters.onebot.v11.message import Message from nonebot_bison.config import Config + from nonebot_bison.config_manager import add_sub_matcher, common_platform + from nonebot_bison.platform import platform_manager + config = Config() + config.user_target.truncate() -@pytest.fixture -def config(app: App): - from nonebot_bison import config - - config.start_up() - return config.Config() - - -def test_create_and_get(config: "Config", app: App): - from nonebot_bison import types - from nonebot_bison.types import Target - - config.add_subscribe( - user="123", - user_type="group", - target="weibo_id", - target_name="weibo_name", - target_type="weibo", - cats=[], - tags=[], + ak_list_router = respx.get( + "https://m.weibo.cn/api/container/getIndex?containerid=1005056279793937" ) - confs = config.list_subscribe("123", "group") - assert len(confs) == 1 - assert config.target_user_cache["weibo"][Target("weibo_id")] == [ - types.User("123", "group") - ] - assert confs[0]["cats"] == [] - config.update_subscribe( - user="123", - user_type="group", - target="weibo_id", - target_name="weibo_name", - target_type="weibo", - cats=["1"], - tags=[], + ak_list_router.mock( + return_value=Response(200, json=get_json("weibo_ak_profile.json")) ) - confs = config.list_subscribe("123", "group") - assert len(confs) == 1 - assert confs[0]["cats"] == ["1"] + ak_list_bad_router = respx.get( + "https://m.weibo.cn/api/container/getIndex?containerid=100505000" + ) + ak_list_bad_router.mock( + return_value=Response(200, json=get_json("weibo_err_profile.json")) + ) + + async with app.test_matcher(add_sub_matcher) as ctx: + bot = ctx.create_bot() + event_1 = fake_group_message_event( + message=Message("添加订阅"), + sender=Sender(card="", nickname="test", role="admin"), + to_me=True, + ) + ctx.receive_event(bot, event_1) + ctx.should_pass_rule() + ctx.should_call_send( + event_1, + Message( + "请输入想要订阅的平台,目前支持,请输入冒号左边的名称:\n" + + "".join( + [ + "{}:{}\n".format( + platform_name, platform_manager[platform_name].name + ) + for platform_name in common_platform + ] + ) + + "要查看全部平台请输入:“全部”" + ), + True, + ) + event_2 = fake_group_message_event( + message=Message("全部"), sender=Sender(card="", nickname="test", role="admin") + ) + ctx.receive_event(bot, event_2) + ctx.should_rejected() + ctx.should_call_send( + event_2, + ( + "全部平台\n" + + "\n".join( + [ + "{}:{}".format(platform_name, platform.name) + for platform_name, platform in platform_manager.items() + ] + ) + ), + True, + ) + event_3 = fake_group_message_event( + message=Message("weibo"), sender=fake_admin_user + ) + ctx.receive_event(bot, event_3) + ctx.should_call_send( + event_3, + Message( + "请输入订阅用户的id,详情查阅https://nonebot-bison.vercel.app/usage/#%E6%89%80%E6%94%AF%E6%8C%81%E5%B9%B3%E5%8F%B0%E7%9A%84uid" + ), + True, + ) + event_4_err = fake_group_message_event( + message=Message("000"), sender=fake_admin_user + ) + import ipdb + + ipdb.set_trace() + ctx.receive_event(bot, event_4_err) + ctx.should_call_send(event_4_err, "id输入错误", True) + ctx.should_rejected() + event_4_ok = fake_group_message_event( + message=Message("6279793937"), sender=fake_admin_user + ) + ctx.receive_event(bot, event_4_ok) + ctx.should_call_send(event_4_ok, "id输入错误", True) diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..1212a20 --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,70 @@ +from typing import TYPE_CHECKING + +from typing_extensions import Literal + +if TYPE_CHECKING: + from nonebot.adapters.onebot.v11 import GroupMessageEvent, PrivateMessageEvent + + +def fake_group_message_event(**field) -> "GroupMessageEvent": + from nonebot.adapters.onebot.v11 import GroupMessageEvent, Message + from nonebot.adapters.onebot.v11.event import Sender + from pydantic import create_model + + _Fake = create_model("_Fake", __base__=GroupMessageEvent) + + class FakeEvent(_Fake): + time: int = 1000000 + self_id: int = 1 + post_type: Literal["message"] = "message" + sub_type: str = "normal" + user_id: int = 10 + message_type: Literal["group"] = "group" + group_id: int = 10000 + message_id: int = 1 + message: Message = Message("test") + raw_message: str = "test" + font: int = 0 + sender: Sender = Sender( + card="", + nickname="test", + role="member", + ) + to_me: bool = False + + class Config: + extra = "forbid" + + return FakeEvent(**field) + + +def fake_private_message_event(**field) -> "PrivateMessageEvent": + from nonebot.adapters.onebot.v11 import Message, PrivateMessageEvent + from nonebot.adapters.onebot.v11.event import Sender + from pydantic import create_model + + _Fake = create_model("_Fake", __base__=PrivateMessageEvent) + + class FakeEvent(_Fake): + time: int = 1000000 + self_id: int = 1 + post_type: Literal["message"] = "message" + sub_type: str = "friend" + user_id: int = 10 + message_type: Literal["private"] = "private" + message_id: int = 1 + message: Message = Message("test") + raw_message: str = "test" + font: int = 0 + sender: Sender = Sender(nickname="test") + to_me: bool = False + + class Config: + extra = "forbid" + + return FakeEvent(**field) + + +from nonebot.adapters.onebot.v11.event import Sender + +fake_admin_user = Sender(nickname="test", role="admin")