From 547be7608fc63108c40d2a07b788ab15c74bf33b Mon Sep 17 00:00:00 2001
From: hemengyang
Date: Wed, 9 Feb 2022 21:05:14 +0800
Subject: [PATCH] add pre-commit and format files
---
.pre-commit-config.yaml | 22 +
CHANGELOG.md | 33 +-
README.md | 75 +--
bot.py | 6 +-
docs/README.md | 12 +-
docs/dev/README.md | 60 ++-
docs/usage/README.md | 218 +++++----
poetry.lock | 265 ++++++++---
pyproject.toml | 17 +
src/plugins/auto_agree.py | 26 +-
src/plugins/nonebot_bison/__init__.py | 27 +-
.../nonebot_bison/admin_page/__init__.py | 119 +++--
src/plugins/nonebot_bison/admin_page/api.py | 159 ++++---
src/plugins/nonebot_bison/admin_page/jwt.py | 19 +-
.../nonebot_bison/admin_page/token_manager.py | 16 +-
src/plugins/nonebot_bison/config.py | 165 ++++---
src/plugins/nonebot_bison/config_manager.py | 248 ++++++----
.../nonebot_bison/platform/__init__.py | 14 +-
.../nonebot_bison/platform/arknights.py | 148 +++---
.../nonebot_bison/platform/bilibili.py | 109 +++--
.../nonebot_bison/platform/ncm_artist.py | 46 +-
.../nonebot_bison/platform/ncm_radio.py | 48 +-
.../nonebot_bison/platform/platform.py | 160 +++++--
src/plugins/nonebot_bison/platform/rss.py | 31 +-
src/plugins/nonebot_bison/platform/wechat.py | 12 +-
src/plugins/nonebot_bison/platform/weibo.py | 157 ++++---
src/plugins/nonebot_bison/plugin_config.py | 14 +-
src/plugins/nonebot_bison/post.py | 77 +--
src/plugins/nonebot_bison/scheduler.py | 73 ++-
src/plugins/nonebot_bison/send.py | 19 +-
src/plugins/nonebot_bison/types.py | 12 +-
src/plugins/nonebot_bison/utils.py | 163 ++++---
tests/conftest.py | 27 +-
tests/platforms/test_arknights.py | 93 ++--
tests/platforms/test_bilibili.py | 46 +-
tests/platforms/test_ncm_artist.py | 55 ++-
tests/platforms/test_ncm_radio.py | 62 +--
tests/platforms/test_platform.py | 437 ++++++++++--------
tests/platforms/test_weibo.py | 131 +++---
tests/platforms/utils.py | 16 +-
tests/test_config_manager.py | 59 ++-
tests/test_merge_pic.py | 96 ++--
tests/test_render.py | 15 +-
43 files changed, 2242 insertions(+), 1365 deletions(-)
create mode 100644 .pre-commit-config.yaml
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..b577ac8
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,22 @@
+ci:
+ autofix_commit_msg: "auto fix by pre-commit hooks"
+ autofix_prs: true
+ autoupdate_branch: dev
+ autoupdate_schedule: weekly
+ autoupdate_commit_msg: "auto update by pre-commit hooks"
+repos:
+ - repo: https://github.com/pycqa/isort
+ rev: 5.10.1
+ hooks:
+ - id: isort
+
+ - repo: https://github.com/psf/black
+ rev: 22.1.0
+ hooks:
+ - id: black
+
+ - repo: https://github.com/pre-commit/mirrors-prettier
+ rev: v2.5.1
+ hooks:
+ - id: prettier
+ types_or: [markdown]
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 03616fe..9fa00c1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,44 +1,53 @@
# Change Log
## [0.2.11] - 2021-06-17
+
- 增加了简单的单元测试
- 增加了管理员直接管理订阅的能力
## [0.3.0] - 2021-07-06
-- 微博tag支持
-- 修复bug
+
+- 微博 tag 支持
+- 修复 bug
- 增加微博超话和纯文字支持
- 更改浏览器配置
- 将“来源”移动到文末
-- 使用组合来构建新的platform,新增状态改变类型订阅
+- 使用组合来构建新的 platform,新增状态改变类型订阅
## [0.3.1] - 2021-07-10
+
- 修复不发送来源
-- 发送RSS订阅的title
+- 发送 RSS 订阅的 title
- 修复浏览器渲染问题
## [0.3.2] - 2021-09-28
-- 增加NoTargetGroup
-- 增加1x3拼图的支持
+
+- 增加 NoTargetGroup
+- 增加 1x3 拼图的支持
- 增加网易云
## [0.3.3] - 2021-09-28
+
- 修复拼图问题
## [0.4.0] - 2021-11-18
-- 项目更名为nonebot-bison
+
+- 项目更名为 nonebot-bison
## [0.4.1] - 2021-11-31
+
- 加入了管理后台
## [0.4.2]
+
并没有做什么只是为了修复前端文件没有正确打包的问题开了个新的版本号
-推上pypi
+推上 pypi
## [0.4.3]
-- 使用playwright替换pypeteer(大概能修复渲染失败图片之后CPU跑满的问题)
-- 增加了help插件`nonebot-plugin-help`
-- 修复playwright漏内存的问题
-- 增加过滤nonebot日志功能
+
+- 使用 playwright 替换 pypeteer(大概能修复渲染失败图片之后 CPU 跑满的问题)
+- 增加了 help 插件`nonebot-plugin-help`
+- 修复 playwright 漏内存的问题
+- 增加过滤 nonebot 日志功能
- 前端可以刷新了(之前居然不可以)
- 在镜像里塞进了浏览器(导致镜像体积起飞)
diff --git a/README.md b/README.md
index 9cb8798..4c310f0 100644
--- a/README.md
+++ b/README.md
@@ -1,76 +1,85 @@
Bison 通用订阅推送插件
-
[](https://pypi.org/project/nonebot-bison/)
[](https://github.com/felinae98/nonebot-bison/blob/main/LICENSE)
[](https://circleci.com/gh/felinae98/nonebot-bison)
[](https://hub.docker.com/r/felinae98/nonebot-bison)
[](https://codecov.io/gh/felinae98/nonebot-bison)
-[](https://qm.qq.com/cgi-bin/qm/qr?k=pXYMGB_e8b6so3QTqgeV6lkKDtEeYE4f&jump_from=webapi)
+[](https://qm.qq.com/cgi-bin/qm/qr?k=pXYMGB_e8b6so3QTqgeV6lkKDtEeYE4f&jump_from=webapi)
[文档](https://nonebot-bison.vercel.app)|[开发文档](https://nonebot-bison.vercel.app/dev)
+
## 简介
-一款自动爬取各种站点,社交平台更新动态,并将信息推送到QQ的机器人。
-基于 [`NoneBot2`](https://github.com/nonebot/nonebot2 ) 开发(诞生于明日方舟的蹲饼活动)
+
+一款自动爬取各种站点,社交平台更新动态,并将信息推送到 QQ 的机器人。
+基于 [`NoneBot2`](https://github.com/nonebot/nonebot2) 开发(诞生于明日方舟的蹲饼活动)
+
本项目原名原名nonebot-hk-reporter
-寓意本Bot要做全世界跑的最快的搬运机器人,后因名字过于暴力改名
+寓意本 Bot 要做全世界跑的最快的搬运机器人,后因名字过于暴力改名
本项目名称来源于明日方舟角色拜松——一名龙门的信使,曾经骑自行车追上骑摩托车的德克萨斯
支持的平台:
-* 微博
-* B站
-* RSS
-* 明日方舟
- * 塞壬唱片新闻
- * 游戏内公告
- * 版本更新等通知
-* 网易云音乐
- * 歌手发布新专辑
- * 电台更新
+- 微博
+- B 站
+- RSS
+- 明日方舟
+ - 塞壬唱片新闻
+ - 游戏内公告
+ - 版本更新等通知
+- 网易云音乐
+ - 歌手发布新专辑
+ - 电台更新
## 功能
-* 定时爬取指定网站
-* 通过图片发送文本,防止风控
-* 使用队列限制发送频率
-* 使用网页后台管理Bot订阅
+
+- 定时爬取指定网站
+- 通过图片发送文本,防止风控
+- 使用队列限制发送频率
+- 使用网页后台管理 Bot 订阅
## 使用方法
-**!!注意,如果要使用后台管理功能请使用pypi版本或者docker版本,如果直接clone源代码
-需要按下面方式进行build**
+
+**!!注意,如果要使用后台管理功能请使用 pypi 版本或者 docker 版本,如果直接 clone 源代码
+需要按下面方式进行 build**
+
```bash
cd ./admin-frontend
yarn && yarn build
```
-可以使用Docker,docker-compose,作为插件安装在nonebot中,或者直接运行
+
+可以使用 Docker,docker-compose,作为插件安装在 nonebot 中,或者直接运行
参考[文档](https://nonebot-bison.vercel.app/usage/#%E4%BD%BF%E7%94%A8)
## FAQ
+
1. 报错`TypeError: 'type' object is not subscriptable`
- 本项目使用了Python 3.9的语法,请将Python版本升级到3.9及以上,推荐使用docker部署
-2. bot不理我
- 请确认自己是群主或者管理员,并且检查`COMMAND_START`环境变量是否设为`[""]`
+ 本项目使用了 Python 3.9 的语法,请将 Python 版本升级到 3.9 及以上,推荐使用 docker 部署
+2. bot 不理我
+ 请确认自己是群主或者管理员,并且检查`COMMAND_START`环境变量是否设为`[""]`
3. 微博漏订阅了
- 微博更新了新的风控措施,某些含有某些关键词的微博会获取不到。
+ 微博更新了新的风控措施,某些含有某些关键词的微博会获取不到。
## 参与开发
-欢迎各种PR,参与开发本插件很简单,只需要对相应平台完成几个接口的编写就行。你只需要一点简单的爬虫知识就行。
-如果对整体框架有任何意见或者建议,欢迎issue。
+欢迎各种 PR,参与开发本插件很简单,只需要对相应平台完成几个接口的编写就行。你只需要一点简单的爬虫知识就行。
+
+如果对整体框架有任何意见或者建议,欢迎 issue。
## 鸣谢
-* [`go-cqhttp`](https://github.com/Mrs4s/go-cqhttp):简单又完善的 cqhttp 实现
-* [`NoneBot2`](https://github.com/nonebot/nonebot2):超好用的开发框架
-* [`HarukaBot`](https://github.com/SK-415/HarukaBot/): 借鉴了大体的实现思路
-* [`rsshub`](https://github.com/DIYgod/RSSHub):提供了大量的api
+
+- [`go-cqhttp`](https://github.com/Mrs4s/go-cqhttp):简单又完善的 cqhttp 实现
+- [`NoneBot2`](https://github.com/nonebot/nonebot2):超好用的开发框架
+- [`HarukaBot`](https://github.com/SK-415/HarukaBot/): 借鉴了大体的实现思路
+- [`rsshub`](https://github.com/DIYgod/RSSHub):提供了大量的 api
## License
-MIT
+MIT
diff --git a/bot.py b/bot.py
index 6819d73..957a2e5 100644
--- a/bot.py
+++ b/bot.py
@@ -5,11 +5,11 @@ nonebot.init(command_start=[""])
app = nonebot.get_asgi()
driver = nonebot.get_driver()
-driver.register_adapter('cqhttp', CQHTTPBot)
+driver.register_adapter("cqhttp", CQHTTPBot)
nonebot.load_builtin_plugins()
-nonebot.load_plugin('nonebot_plugin_help')
-nonebot.load_plugins('src/plugins')
+nonebot.load_plugin("nonebot_plugin_help")
+nonebot.load_plugins("src/plugins")
if __name__ == "__main__":
nonebot.run(app="bot:app")
diff --git a/docs/README.md b/docs/README.md
index d79be20..704fce1 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -5,11 +5,11 @@ tagline: 本bot励志做全泰拉骑车最快的信使
actionText: 快速部署
actionLink: /usage/
features:
-- title: 拓展性强
- details: 没有自己想要的网站?只要简单的爬虫知识就可以给它适配一个新的网站
-- title: 通用,强大
- details: 社交媒体?网站更新?游戏开服?只要能爬就都能推,还支持自定义过滤
-- title: 后台管理
- details: 提供后台管理页面,简单快捷修改配置
+ - title: 拓展性强
+ details: 没有自己想要的网站?只要简单的爬虫知识就可以给它适配一个新的网站
+ - title: 通用,强大
+ details: 社交媒体?网站更新?游戏开服?只要能爬就都能推,还支持自定义过滤
+ - title: 后台管理
+ details: 提供后台管理页面,简单快捷修改配置
footer: MIT Licensed
---
diff --git a/docs/dev/README.md b/docs/dev/README.md
index 5137a79..d2dc9f9 100644
--- a/docs/dev/README.md
+++ b/docs/dev/README.md
@@ -1,53 +1,63 @@
---
sidebar: auto
---
+
# 开发指南
+
本插件需要你的帮助!只需要会写简单的爬虫,就能给本插件适配新的网站。
## 基本概念
-* `nonebot_bison.post.Post`: 可以理解为推送内容,其中包含需要发送的文字,图片,链接,平台信息等
-* `nonebot_bison.types.RawPost`: 从站点/平台中爬到的单条信息
-* `nonebot_bison.types.Target`: 目标账号,Bilibili,微博等社交媒体中的账号
-* `nonebot_bison.types.Category`: 信息分类,例如视频,动态,图文,文章等
-* `nonebot_bison.types.Tag`: 信息标签,例如微博中的超话或者hashtag
+
+- `nonebot_bison.post.Post`: 可以理解为推送内容,其中包含需要发送的文字,图片,链接,平台信息等
+- `nonebot_bison.types.RawPost`: 从站点/平台中爬到的单条信息
+- `nonebot_bison.types.Target`: 目标账号,Bilibili,微博等社交媒体中的账号
+- `nonebot_bison.types.Category`: 信息分类,例如视频,动态,图文,文章等
+- `nonebot_bison.types.Tag`: 信息标签,例如微博中的超话或者 hashtag
## 快速上手
+
上车!我们走
先明确需要适配的站点类型,先明确两个问题:
-#### 我要发送什么样的推送
-* `nonebot_bison.platform.platform.NewMessage` 最常见的类型,每次爬虫向特定接口爬取一个消息列表,
- 与之前爬取的信息对比,过滤出新的消息,再根据用户自定义的分类和标签进行过滤,最后处理消息,把
- 处理过后的消息发送给用户
- 例如:微博,Bilibili
-* `nonebot_bison.platform.platform.StatusChange` 每次爬虫获取一个状态,在状态改变时发布推送
- 例如:游戏开服提醒,主播上播提醒
-* `nonebot_bison.platform.platform.SimplePost` 与`NewMessage`相似,但是不过滤新的消息
- ,每次发送全部消息
- 例如:每日榜单定时发送
-#### 这个平台是否有账号的概念
-* `nonebot_bison.platform.platform.TargetMixin` 有账号的概念
- 例如:Bilibili用户,微博用户
-* `nonebot_bison.platform.platform.NoTargetMixin` 没有账号的概念
- 例如:游戏公告,教务处公告
-现在你已经选择了两个类,现在你需要在`src/plugins/nonebot_bison/platform`下新建一个py文件,
+#### 我要发送什么样的推送
+
+- `nonebot_bison.platform.platform.NewMessage` 最常见的类型,每次爬虫向特定接口爬取一个消息列表,
+ 与之前爬取的信息对比,过滤出新的消息,再根据用户自定义的分类和标签进行过滤,最后处理消息,把
+ 处理过后的消息发送给用户
+ 例如:微博,Bilibili
+- `nonebot_bison.platform.platform.StatusChange` 每次爬虫获取一个状态,在状态改变时发布推送
+ 例如:游戏开服提醒,主播上播提醒
+- `nonebot_bison.platform.platform.SimplePost` 与`NewMessage`相似,但是不过滤新的消息
+ ,每次发送全部消息
+ 例如:每日榜单定时发送
+
+#### 这个平台是否有账号的概念
+
+- `nonebot_bison.platform.platform.TargetMixin` 有账号的概念
+ 例如:Bilibili 用户,微博用户
+- `nonebot_bison.platform.platform.NoTargetMixin` 没有账号的概念
+ 例如:游戏公告,教务处公告
+
+现在你已经选择了两个类,现在你需要在`src/plugins/nonebot_bison/platform`下新建一个 py 文件,
在里面新建一个类,继承你刚刚选择的两个类,重载一些关键的函数,然后……就完成了,不需要修改别的东西了。
-例如要适配微博,微博有账号,并且我希望bot搬运新的消息,所以微博的类应该这样定义:
+例如要适配微博,微博有账号,并且我希望 bot 搬运新的消息,所以微博的类应该这样定义:
+
```python
class Weibo(NewMessage, TargetMixin):
...
```
当然我们非常希望你对自己适配的平台写一些单元测试,你可以模仿`tests/platforms/test_*.py`中的内容写
-一些单元测试。为保证多次运行测试的一致性,可以mock http的响应,测试的内容包括获取RawPost,处理成Post
-,测试分类以及提取tag等,当然最好和rsshub做一个交叉验证。
+一些单元测试。为保证多次运行测试的一致性,可以 mock http 的响应,测试的内容包括获取 RawPost,处理成 Post
+,测试分类以及提取 tag 等,当然最好和 rsshub 做一个交叉验证。
::: danger
-Nonebot项目使用了全异步的处理方式,所以你需要对异步,Python asyncio的机制有一定了解,当然,
+Nonebot 项目使用了全异步的处理方式,所以你需要对异步,Python asyncio 的机制有一定了解,当然,
依葫芦画瓢也是足够的
:::
## 类的方法与成员变量
+
## 方法与变量的定义
diff --git a/docs/usage/README.md b/docs/usage/README.md
index 558b078..57ad323 100644
--- a/docs/usage/README.md
+++ b/docs/usage/README.md
@@ -1,129 +1,175 @@
---
sidebar: auto
---
+
# 部署和使用
-本节将教你快速部署和使用一个nonebot-bison,如果你不知道要选择哪种部署方式,推荐使用[docker-compose](#docker-compose部署-推荐)
+
+本节将教你快速部署和使用一个 nonebot-bison,如果你不知道要选择哪种部署方式,推荐使用[docker-compose](#docker-compose部署-推荐)
## 部署
-本项目可以作为单独的Bot使用,可以作为nonebot2的插件使用
-### 作为Bot使用
+
+本项目可以作为单独的 Bot 使用,可以作为 nonebot2 的插件使用
+
+### 作为 Bot 使用
+
额外提供自动同意超级用户的好友申请和同意超级用户的加群邀请的功能
-#### docker-compose部署(推荐)
+
+#### docker-compose 部署(推荐)
+
1. 在一个新的目录中下载[docker-compose.yml](https://raw.githubusercontent.com/felinae98/nonebot-bison/main/docker-compose.yml)
- 将其中的``改成自己的QQ号
- ```bash
- wget https://raw.githubusercontent.com/felinae98/nonebot-bison/main/docker-compose.yml
- ```
-2. 运行配置go-cqhttp
- ```bash
- docker-compose run go-cqhttp
- ```
- 通信方式选择:`3: 反向 Websocket 通信`
- 编辑`bot-data/config.yml`,更改下面字段:
- ```
- account: # 账号相关
- uin: # QQ账号
- password: "" # 密码为空时使用扫码登录
+ 将其中的``改成自己的 QQ 号
+ ```bash
+ wget https://raw.githubusercontent.com/felinae98/nonebot-bison/main/docker-compose.yml
+ ```
+2. 运行配置 go-cqhttp
- message:
- post-format: array
+ ```bash
+ docker-compose run go-cqhttp
+ ```
- ............
+ 通信方式选择:`3: 反向 Websocket 通信`
+ 编辑`bot-data/config.yml`,更改下面字段:
- servers:
- - ws-reverse:
- universal: ws://nonebot:8080/cqhttp/ws # 将这个字段写为这个值
- ```
-3. 登录go-cqhttp
- 再次
- ```bash
- docker-compose run go-cqhttp
- ```
- 参考[go-cqhttp文档](https://docs.go-cqhttp.org/faq/slider.html#%E6%96%B9%E6%A1%88a-%E8%87%AA%E8%A1%8C%E6%8A%93%E5%8C%85)
- 完成登录
-4. 确定完成登录后,启动bot:
- ```bash
- docker-compose up -d
- ```
-#### docker部署
-本项目的docker镜像为`felinae98/nonebot-bison`,可以直接pull后run进行使用,
+ ```
+ account: # 账号相关
+ uin: # QQ账号
+ password: "" # 密码为空时使用扫码登录
+
+ message:
+ post-format: array
+
+ ............
+
+ servers:
+ - ws-reverse:
+ universal: ws://nonebot:8080/cqhttp/ws # 将这个字段写为这个值
+ ```
+
+3. 登录 go-cqhttp
+ 再次
+ ```bash
+ docker-compose run go-cqhttp
+ ```
+ 参考[go-cqhttp 文档](https://docs.go-cqhttp.org/faq/slider.html#%E6%96%B9%E6%A1%88a-%E8%87%AA%E8%A1%8C%E6%8A%93%E5%8C%85)
+ 完成登录
+4. 确定完成登录后,启动 bot:
+ ```bash
+ docker-compose up -d
+ ```
+
+#### docker 部署
+
+本项目的 docker 镜像为`felinae98/nonebot-bison`,可以直接 pull 后 run 进行使用,
相关配置参数可以使用`-e`作为环境变量传入
+
#### 直接运行(不推荐)
-可以参考[nonebot的运行方法](https://v2.nonebot.dev/guide/getting-started.html)
+
+可以参考[nonebot 的运行方法](https://v2.nonebot.dev/guide/getting-started.html)
::: danger
直接克隆源代码需要自行编译前端,否则会出现无法使用管理后台等情况。
:::
::: danger
-本项目中使用了Python 3.9的语法,如果出现问题,请检查Python版本
+本项目中使用了 Python 3.9 的语法,如果出现问题,请检查 Python 版本
:::
-1. 首先安装poetry:[安装方法](https://python-poetry.org/docs/#installation)
-2. clone本项目,在项目中`poetry install`安装依赖
-2. 安装yarn,配置yarn源(推荐)
-3. 在`admin-fronted`中运行`yarn && yarn build`编译前端
-3. 编辑`.env.prod`配置各种环境变量,见[Nonebot2配置](https://v2.nonebot.dev/guide/basic-configuration.html)
-4. 运行`poetry run python bot.py`启动机器人
+
+1. 首先安装 poetry:[安装方法](https://python-poetry.org/docs/#installation)
+2. clone 本项目,在项目中`poetry install`安装依赖
+3. 安装 yarn,配置 yarn 源(推荐)
+4. 在`admin-fronted`中运行`yarn && yarn build`编译前端
+5. 编辑`.env.prod`配置各种环境变量,见[Nonebot2 配置](https://v2.nonebot.dev/guide/basic-configuration.html)
+6. 运行`poetry run python bot.py`启动机器人
+
### 作为插件使用
-本部分假设大家会部署nonebot2
+
+本部分假设大家会部署 nonebot2
+
#### 手动安装
-1. 安装pip包`nonebot-bison`
+
+1. 安装 pip 包`nonebot-bison`
2. 在`bot.py`中导入插件`nonebot_bison`
+
### 自动安装
+
使用`nb-cli`执行:`nb plugin install nonebot_bison`
+
## 配置
-可参考[源文件](https://github.com/felinae98/nonebot-bison/blob/main/src/plugins/nonebot_bison/plugin_config.py)
-* `BISON_CONFIG_PATH`: 插件存放配置文件的位置,如果不设定默认为项目目录下的`data`目录
-* `BISON_USE_PIC`: 将文字渲染成图片后进行发送,多用于规避风控
-* `BISON_BROWSER`: 本插件使用Chrome来渲染图片
- * 使用browserless提供的Chrome管理服务,设置为`ws://xxxxxxxx`,值为Chrome Endpoint(推荐)
- * 使用cdp连接相关服务,设置为`wsc://xxxxxxxxx`
- * 使用本地安装的Chrome,设置为`local:`,例如`local:/usr/bin/google-chrome-stable`
- * 如果不进行配置,那么会在启动时候自动进行安装,在官方的docker镜像中已经安装了浏览器
-::: warning
-截止发布时,本项目尚不能完全与browserless兼容,目前建议使用镜像内自带的浏览器,即
-不要配置这个变量
-:::
-* `BISON_OUTER_URL`: 从外部访问服务器的地址,默认为`http://localhost:8080/bison`,如果你的插件部署
- 在服务器上,建议配置为`http://<你的服务器ip>:8080/bison`
-* `BISON_FILTER_LOG`: 是否过滤来自`nonebot`的warning级以下的log,如果你的bot只运行了这个插件可以考虑
-开启,默认关
-* `BISON_USE_QUEUE`: 是否用队列的方式发送消息,降低发送频率,默认开
+
+可参考[源文件](https://github.com/felinae98/nonebot-bison/blob/main/src/plugins/nonebot_bison/plugin_config.py)
+
+- `BISON_CONFIG_PATH`: 插件存放配置文件的位置,如果不设定默认为项目目录下的`data`目录
+- `BISON_USE_PIC`: 将文字渲染成图片后进行发送,多用于规避风控
+- `BISON_BROWSER`: 本插件使用 Chrome 来渲染图片
+ - 使用 browserless 提供的 Chrome 管理服务,设置为`ws://xxxxxxxx`,值为 Chrome Endpoint(推荐)
+ - 使用 cdp 连接相关服务,设置为`wsc://xxxxxxxxx`
+ - 使用本地安装的 Chrome,设置为`local:`,例如`local:/usr/bin/google-chrome-stable`
+ - 如果不进行配置,那么会在启动时候自动进行安装,在官方的 docker 镜像中已经安装了浏览器
+ ::: warning
+ 截止发布时,本项目尚不能完全与 browserless 兼容,目前建议使用镜像内自带的浏览器,即
+ 不要配置这个变量
+ :::
+- `BISON_OUTER_URL`: 从外部访问服务器的地址,默认为`http://localhost:8080/bison`,如果你的插件部署
+ 在服务器上,建议配置为`http://<你的服务器ip>:8080/bison`
+- `BISON_FILTER_LOG`: 是否过滤来自`nonebot`的 warning 级以下的 log,如果你的 bot 只运行了这个插件可以考虑
+ 开启,默认关
+- `BISON_USE_QUEUE`: 是否用队列的方式发送消息,降低发送频率,默认开
+
## 使用
+
::: warning
-本节假设`COMMAND_START`设置中包含`''`,如果出现bot不响应的问题,请先
+本节假设`COMMAND_START`设置中包含`''`,如果出现 bot 不响应的问题,请先
排查这个设置
:::
+
### 命令
+
#### 在本群中进行配置
-所有命令都需要@bot触发
-* 添加订阅(仅管理员和群主和SUPERUSER):`添加订阅`
-* 查询订阅:`查询订阅`
-* 删除订阅(仅管理员和群主和SUPERUSER):`删除订阅`
+
+所有命令都需要@bot 触发
+
+- 添加订阅(仅管理员和群主和 SUPERUSER):`添加订阅`
+- 查询订阅:`查询订阅`
+- 删除订阅(仅管理员和群主和 SUPERUSER):`删除订阅`
+
#### 私聊机器人获取后台地址
+
`后台管理`,之后点击返回的链接
-如果你是superuser,那么你可以管理所有群的订阅;如果你是bot所在的群的其中部分群的管理,
-你可以管理你管理的群里的订阅;如果你不是任意一个群的管理,那么bot将会报错。
+如果你是 superuser,那么你可以管理所有群的订阅;如果你是 bot 所在的群的其中部分群的管理,
+你可以管理你管理的群里的订阅;如果你不是任意一个群的管理,那么 bot 将会报错。
::: tip
-可以和bot通过临时聊天触发
+可以和 bot 通过临时聊天触发
:::
::: warning
-网页的身份鉴别机制全部由bot返回的链接确定,所以这个链接并不能透露给别人。
-并且链接会过期,所以一段时间后需要重新私聊bot获取新的链接。
+网页的身份鉴别机制全部由 bot 返回的链接确定,所以这个链接并不能透露给别人。
+并且链接会过期,所以一段时间后需要重新私聊 bot 获取新的链接。
:::
-#### 私聊机器人进行配置(需要SUPERUER权限)
-* 添加订阅:`管理-添加订阅`
-* 查询订阅:`管理-查询订阅`
-* 删除订阅:`管理-删除订阅`
-### 所支持平台的uid
+
+#### 私聊机器人进行配置(需要 SUPERUER 权限)
+
+- 添加订阅:`管理-添加订阅`
+- 查询订阅:`管理-查询订阅`
+- 删除订阅:`管理-删除订阅`
+
+### 所支持平台的 uid
+
#### Weibo
-* 对于一般用户主页`https://weibo.com/u/6441489862?xxxxxxxxxxxxxxx`,`/u/`后面的数字即为uid
-* 对于有个性域名的用户如:`https://weibo.com/arknights`,需要点击左侧信息标签下“更多”,链接为`https://weibo.com/6279793937/about`,其中中间数字即为uid
+
+- 对于一般用户主页`https://weibo.com/u/6441489862?xxxxxxxxxxxxxxx`,`/u/`后面的数字即为 uid
+- 对于有个性域名的用户如:`https://weibo.com/arknights`,需要点击左侧信息标签下“更多”,链接为`https://weibo.com/6279793937/about`,其中中间数字即为 uid
+
#### Bilibili
-主页链接一般为`https://space.bilibili.com/161775300?xxxxxxxxxx`,数字即为uid
+
+主页链接一般为`https://space.bilibili.com/161775300?xxxxxxxxxx`,数字即为 uid
+
#### RSS
-RSS链接即为uid
+
+RSS 链接即为 uid
+
#### 网易云音乐-歌手
+
在网易云网页上歌手的链接一般为`https://music.163.com/#/artist?id=32540734`,`id=`
-后面的数字即为uid
+后面的数字即为 uid
+
#### 网易云音乐-电台
+
在网易云网页上电台的链接一般为`https://music.163.com/#/djradio?id=793745436`,`id=`
-后面的数字即为uid
+后面的数字即为 uid
diff --git a/poetry.lock b/poetry.lock
index 5dba661..1bbb479 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -149,28 +149,24 @@ python-versions = ">=3.6"
[[package]]
name = "black"
-version = "21.12b0"
+version = "22.1.0"
description = "The uncompromising code formatter."
category = "dev"
optional = false
python-versions = ">=3.6.2"
[package.dependencies]
-click = ">=7.1.2"
+click = ">=8.0.0"
mypy-extensions = ">=0.4.3"
-pathspec = ">=0.9.0,<1"
+pathspec = ">=0.9.0"
platformdirs = ">=2"
-tomli = ">=0.2.6,<2.0.0"
-typing-extensions = [
- {version = ">=3.10.0.0", markers = "python_version < \"3.10\""},
- {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""},
-]
+tomli = ">=1.1.0"
+typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.7.4)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
-python2 = ["typed-ast (>=1.4.3)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]]
@@ -192,6 +188,14 @@ category = "main"
optional = false
python-versions = "*"
+[[package]]
+name = "cfgv"
+version = "3.3.1"
+description = "Validate configuration and produce human readable error messages."
+category = "dev"
+optional = false
+python-versions = ">=3.6.1"
+
[[package]]
name = "charset-normalizer"
version = "2.0.10"
@@ -224,11 +228,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "coverage"
-version = "6.2"
+version = "6.3.1"
description = "Code coverage measurement for Python"
category = "dev"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
[package.dependencies]
tomli = {version = "*", optional = true, markers = "extra == \"toml\""}
@@ -244,6 +248,14 @@ category = "dev"
optional = false
python-versions = ">=3.5"
+[[package]]
+name = "distlib"
+version = "0.3.4"
+description = "Distribution utilities"
+category = "dev"
+optional = false
+python-versions = "*"
+
[[package]]
name = "executing"
version = "0.8.2"
@@ -292,6 +304,18 @@ python-versions = ">=3.6"
[package.dependencies]
sgmllib3k = "*"
+[[package]]
+name = "filelock"
+version = "3.4.2"
+description = "A platform independent file lock."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"]
+testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"]
+
[[package]]
name = "greenlet"
version = "1.1.2"
@@ -360,6 +384,17 @@ brotli = ["brotlicffi", "brotli"]
cli = ["click (>=8.0.0,<9.0.0)", "rich (>=10.0.0,<11.0.0)", "pygments (>=2.0.0,<3.0.0)"]
http2 = ["h2 (>=3,<5)"]
+[[package]]
+name = "identify"
+version = "2.4.8"
+description = "File identification library for Python"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+license = ["ukkonen"]
+
[[package]]
name = "idna"
version = "3.3"
@@ -424,6 +459,20 @@ qtconsole = ["qtconsole"]
test = ["pytest", "pytest-asyncio", "testpath", "pygments"]
test_extra = ["pytest", "testpath", "curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.19)", "pandas", "pygments", "trio"]
+[[package]]
+name = "isort"
+version = "5.10.1"
+description = "A Python utility / library to sort Python imports."
+category = "dev"
+optional = false
+python-versions = ">=3.6.1,<4.0"
+
+[package.extras]
+pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
+requirements_deprecated_finder = ["pipreqs", "pip-api"]
+colors = ["colorama (>=0.4.3,<0.5.0)"]
+plugins = ["setuptools"]
+
[[package]]
name = "jedi"
version = "0.18.1"
@@ -481,6 +530,14 @@ category = "dev"
optional = false
python-versions = "*"
+[[package]]
+name = "nodeenv"
+version = "1.6.0"
+description = "Node.js virtual environment builder"
+category = "dev"
+optional = false
+python-versions = "*"
+
[[package]]
name = "nonebot-adapter-cqhttp"
version = "2.0.0b1"
@@ -649,6 +706,22 @@ python-versions = ">=3.6"
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
+[[package]]
+name = "pre-commit"
+version = "2.17.0"
+description = "A framework for managing and maintaining multi-language pre-commit hooks."
+category = "dev"
+optional = false
+python-versions = ">=3.6.1"
+
+[package.dependencies]
+cfgv = ">=2.0.0"
+identify = ">=1.0.0"
+nodeenv = ">=0.11.1"
+pyyaml = ">=5.1"
+toml = "*"
+virtualenv = ">=20.0.8"
+
[[package]]
name = "prompt-toolkit"
version = "3.0.24"
@@ -1100,6 +1173,24 @@ dev = ["Cython (>=0.29.24,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=4.1.2,<4.2.0
docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)"]
test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"]
+[[package]]
+name = "virtualenv"
+version = "20.13.1"
+description = "Virtual Python Environment builder"
+category = "dev"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+
+[package.dependencies]
+distlib = ">=0.3.1,<1"
+filelock = ">=3.2,<4"
+platformdirs = ">=2,<3"
+six = ">=1.9.0,<2"
+
+[package.extras]
+docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"]
+testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"]
+
[[package]]
name = "watchgod"
version = "0.7"
@@ -1150,7 +1241,7 @@ multidict = ">=4.0"
[metadata]
lock-version = "1.1"
python-versions = "^3.9"
-content-hash = "28089a944acc8f02e76eede5346b11de8042e4bffe1611b17c2a1b1a74ad1bc1"
+content-hash = "06911f820242d669999d542662710b64eaf827ac4bdcc58c5fdab8c352471c02"
[metadata.files]
aiofiles = [
@@ -1201,8 +1292,29 @@ bidict = [
{file = "bidict-0.21.4.tar.gz", hash = "sha256:42c84ffbe6f8de898af6073b4be9ea7ccedcd78d3474aa844c54e49d5a079f6f"},
]
black = [
- {file = "black-21.12b0-py3-none-any.whl", hash = "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"},
- {file = "black-21.12b0.tar.gz", hash = "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3"},
+ {file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"},
+ {file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"},
+ {file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"},
+ {file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"},
+ {file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"},
+ {file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"},
+ {file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"},
+ {file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"},
+ {file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"},
+ {file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"},
+ {file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"},
+ {file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"},
+ {file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"},
+ {file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"},
+ {file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"},
+ {file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"},
+ {file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"},
+ {file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"},
+ {file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"},
+ {file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"},
+ {file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"},
+ {file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"},
+ {file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"},
]
bs4 = [
{file = "bs4-0.0.1.tar.gz", hash = "sha256:36ecea1fd7cc5c0c6e4a1ff075df26d50da647b75376626cc186e2212886dd3a"},
@@ -1211,6 +1323,10 @@ certifi = [
{file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
{file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
]
+cfgv = [
+ {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
+ {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
+]
charset-normalizer = [
{file = "charset-normalizer-2.0.10.tar.gz", hash = "sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd"},
{file = "charset_normalizer-2.0.10-py3-none-any.whl", hash = "sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455"},
@@ -1224,58 +1340,56 @@ colorama = [
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
]
coverage = [
- {file = "coverage-6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b"},
- {file = "coverage-6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0"},
- {file = "coverage-6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da"},
- {file = "coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d"},
- {file = "coverage-6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739"},
- {file = "coverage-6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971"},
- {file = "coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840"},
- {file = "coverage-6.2-cp310-cp310-win32.whl", hash = "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c"},
- {file = "coverage-6.2-cp310-cp310-win_amd64.whl", hash = "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f"},
- {file = "coverage-6.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76"},
- {file = "coverage-6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47"},
- {file = "coverage-6.2-cp311-cp311-win_amd64.whl", hash = "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64"},
- {file = "coverage-6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9"},
- {file = "coverage-6.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d"},
- {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48"},
- {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e"},
- {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d"},
- {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17"},
- {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781"},
- {file = "coverage-6.2-cp36-cp36m-win32.whl", hash = "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a"},
- {file = "coverage-6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0"},
- {file = "coverage-6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49"},
- {file = "coverage-6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521"},
- {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884"},
- {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa"},
- {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64"},
- {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617"},
- {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8"},
- {file = "coverage-6.2-cp37-cp37m-win32.whl", hash = "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4"},
- {file = "coverage-6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74"},
- {file = "coverage-6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e"},
- {file = "coverage-6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58"},
- {file = "coverage-6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc"},
- {file = "coverage-6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd"},
- {file = "coverage-6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953"},
- {file = "coverage-6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475"},
- {file = "coverage-6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57"},
- {file = "coverage-6.2-cp38-cp38-win32.whl", hash = "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c"},
- {file = "coverage-6.2-cp38-cp38-win_amd64.whl", hash = "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2"},
- {file = "coverage-6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd"},
- {file = "coverage-6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685"},
- {file = "coverage-6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c"},
- {file = "coverage-6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3"},
- {file = "coverage-6.2-cp39-cp39-win32.whl", hash = "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282"},
- {file = "coverage-6.2-cp39-cp39-win_amd64.whl", hash = "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644"},
- {file = "coverage-6.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de"},
- {file = "coverage-6.2.tar.gz", hash = "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8"},
+ {file = "coverage-6.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeffd96882d8c06d31b65dddcf51db7c612547babc1c4c5db6a011abe9798525"},
+ {file = "coverage-6.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:621f6ea7260ea2ffdaec64fe5cb521669984f567b66f62f81445221d4754df4c"},
+ {file = "coverage-6.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84f2436d6742c01136dd940ee158bfc7cf5ced3da7e4c949662b8703b5cd8145"},
+ {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de73fca6fb403dd72d4da517cfc49fcf791f74eee697d3219f6be29adf5af6ce"},
+ {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78fbb2be068a13a5d99dce9e1e7d168db880870f7bc73f876152130575bd6167"},
+ {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5a4551dfd09c3bd12fca8144d47fe7745275adf3229b7223c2f9e29a975ebda"},
+ {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7bff3a98f63b47464480de1b5bdd80c8fade0ba2832c9381253c9b74c4153c27"},
+ {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a06c358f4aed05fa1099c39decc8022261bb07dfadc127c08cfbd1391b09689e"},
+ {file = "coverage-6.3.1-cp310-cp310-win32.whl", hash = "sha256:9fff3ff052922cb99f9e52f63f985d4f7a54f6b94287463bc66b7cdf3eb41217"},
+ {file = "coverage-6.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:276b13cc085474e482566c477c25ed66a097b44c6e77132f3304ac0b039f83eb"},
+ {file = "coverage-6.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:56c4a409381ddd7bbff134e9756077860d4e8a583d310a6f38a2315b9ce301d0"},
+ {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eb494070aa060ceba6e4bbf44c1bc5fa97bfb883a0d9b0c9049415f9e944793"},
+ {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e15d424b8153756b7c903bde6d4610be0c3daca3986173c18dd5c1a1625e4cd"},
+ {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d47a897c1e91f33f177c21de897267b38fbb45f2cd8e22a710bcef1df09ac1"},
+ {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:25e73d4c81efa8ea3785274a2f7f3bfbbeccb6fcba2a0bdd3be9223371c37554"},
+ {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fac0bcc5b7e8169bffa87f0dcc24435446d329cbc2b5486d155c2e0f3b493ae1"},
+ {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:72128176fea72012063200b7b395ed8a57849282b207321124d7ff14e26988e8"},
+ {file = "coverage-6.3.1-cp37-cp37m-win32.whl", hash = "sha256:1bc6d709939ff262fd1432f03f080c5042dc6508b6e0d3d20e61dd045456a1a0"},
+ {file = "coverage-6.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:618eeba986cea7f621d8607ee378ecc8c2504b98b3fdc4952b30fe3578304687"},
+ {file = "coverage-6.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ed164af5c9078596cfc40b078c3b337911190d3faeac830c3f1274f26b8320"},
+ {file = "coverage-6.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:352c68e233409c31048a3725c446a9e48bbff36e39db92774d4f2380d630d8f8"},
+ {file = "coverage-6.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:448d7bde7ceb6c69e08474c2ddbc5b4cd13c9e4aa4a717467f716b5fc938a734"},
+ {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fde6b90889522c220dd56a670102ceef24955d994ff7af2cb786b4ba8fe11e4"},
+ {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e647a0be741edbb529a72644e999acb09f2ad60465f80757da183528941ff975"},
+ {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a5cdc3adb4f8bb8d8f5e64c2e9e282bc12980ef055ec6da59db562ee9bdfefa"},
+ {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2dd70a167843b4b4b2630c0c56f1b586fe965b4f8ac5da05b6690344fd065c6b"},
+ {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9ad0a117b8dc2061ce9461ea4c1b4799e55edceb236522c5b8f958ce9ed8fa9a"},
+ {file = "coverage-6.3.1-cp38-cp38-win32.whl", hash = "sha256:e92c7a5f7d62edff50f60a045dc9542bf939758c95b2fcd686175dd10ce0ed10"},
+ {file = "coverage-6.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:482fb42eea6164894ff82abbcf33d526362de5d1a7ed25af7ecbdddd28fc124f"},
+ {file = "coverage-6.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c5b81fb37db76ebea79aa963b76d96ff854e7662921ce742293463635a87a78d"},
+ {file = "coverage-6.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a4f923b9ab265136e57cc14794a15b9dcea07a9c578609cd5dbbfff28a0d15e6"},
+ {file = "coverage-6.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d296cbc8254a7dffdd7bcc2eb70be5a233aae7c01856d2d936f5ac4e8ac1f1"},
+ {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245ab82e8554fa88c4b2ab1e098ae051faac5af829efdcf2ce6b34dccd5567c"},
+ {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f2b05757c92ad96b33dbf8e8ec8d4ccb9af6ae3c9e9bd141c7cc44d20c6bcba"},
+ {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9e3dd806f34de38d4c01416344e98eab2437ac450b3ae39c62a0ede2f8b5e4ed"},
+ {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d651fde74a4d3122e5562705824507e2f5b2d3d57557f1916c4b27635f8fbe3f"},
+ {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:704f89b87c4f4737da2860695a18c852b78ec7279b24eedacab10b29067d3a38"},
+ {file = "coverage-6.3.1-cp39-cp39-win32.whl", hash = "sha256:2aed4761809640f02e44e16b8b32c1a5dee5e80ea30a0ff0912158bde9c501f2"},
+ {file = "coverage-6.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:9976fb0a5709988778ac9bc44f3d50fccd989987876dfd7716dee28beed0a9fa"},
+ {file = "coverage-6.3.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:463e52616ea687fd323888e86bf25e864a3cc6335a043fad6bbb037dbf49bbe2"},
+ {file = "coverage-6.3.1.tar.gz", hash = "sha256:6c3f6158b02ac403868eea390930ae64e9a9a2a5bbfafefbb920d29258d9f2f8"},
]
decorator = [
{file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
{file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
]
+distlib = [
+ {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"},
+ {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"},
+]
executing = [
{file = "executing-0.8.2-py2.py3-none-any.whl", hash = "sha256:32fc6077b103bd19e6494a72682d66d5763cf20a106d5aa7c5ccbea4e47b0df7"},
{file = "executing-0.8.2.tar.gz", hash = "sha256:c23bf42e9a7b9b212f185b1b2c3c91feb895963378887bb10e64a2e612ec0023"},
@@ -1291,6 +1405,10 @@ feedparser = [
{file = "feedparser-6.0.8-py3-none-any.whl", hash = "sha256:1b7f57841d9cf85074deb316ed2c795091a238adb79846bc46dccdaf80f9c59a"},
{file = "feedparser-6.0.8.tar.gz", hash = "sha256:5ce0410a05ab248c8c7cfca3a0ea2203968ee9ff4486067379af4827a59f9661"},
]
+filelock = [
+ {file = "filelock-3.4.2-py3-none-any.whl", hash = "sha256:cf0fc6a2f8d26bd900f19bf33915ca70ba4dd8c56903eeb14e1e7a2fd7590146"},
+ {file = "filelock-3.4.2.tar.gz", hash = "sha256:38b4f4c989f9d06d44524df1b24bd19e167d851f19b50bf3e3559952dddc5b80"},
+]
greenlet = [
{file = "greenlet-1.1.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6"},
{file = "greenlet-1.1.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a"},
@@ -1303,6 +1421,7 @@ greenlet = [
{file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497"},
{file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1"},
{file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58"},
+ {file = "greenlet-1.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b336501a05e13b616ef81ce329c0e09ac5ed8c732d9ba7e3e983fcc1a9e86965"},
{file = "greenlet-1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708"},
{file = "greenlet-1.1.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23"},
{file = "greenlet-1.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee"},
@@ -1315,6 +1434,7 @@ greenlet = [
{file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce"},
{file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08"},
{file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168"},
+ {file = "greenlet-1.1.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b8c008de9d0daba7b6666aa5bbfdc23dcd78cafc33997c9b7741ff6353bafb7f"},
{file = "greenlet-1.1.2-cp36-cp36m-win32.whl", hash = "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa"},
{file = "greenlet-1.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d"},
{file = "greenlet-1.1.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4"},
@@ -1323,6 +1443,7 @@ greenlet = [
{file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1"},
{file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28"},
{file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5"},
+ {file = "greenlet-1.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c5d5b35f789a030ebb95bff352f1d27a93d81069f2adb3182d99882e095cefe"},
{file = "greenlet-1.1.2-cp37-cp37m-win32.whl", hash = "sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc"},
{file = "greenlet-1.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06"},
{file = "greenlet-1.1.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0"},
@@ -1331,6 +1452,7 @@ greenlet = [
{file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43"},
{file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711"},
{file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b"},
+ {file = "greenlet-1.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2bde6792f313f4e918caabc46532aa64aa27a0db05d75b20edfc5c6f46479de2"},
{file = "greenlet-1.1.2-cp38-cp38-win32.whl", hash = "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd"},
{file = "greenlet-1.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3"},
{file = "greenlet-1.1.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67"},
@@ -1339,6 +1461,7 @@ greenlet = [
{file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88"},
{file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b"},
{file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3"},
+ {file = "greenlet-1.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0051c6f1f27cb756ffc0ffbac7d2cd48cb0362ac1736871399a739b2885134d3"},
{file = "greenlet-1.1.2-cp39-cp39-win32.whl", hash = "sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf"},
{file = "greenlet-1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd"},
{file = "greenlet-1.1.2.tar.gz", hash = "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a"},
@@ -1372,6 +1495,10 @@ httpx = [
{file = "httpx-0.21.3-py3-none-any.whl", hash = "sha256:df9a0fd43fa79dbab411d83eb1ea6f7a525c96ad92e60c2d7f40388971b25777"},
{file = "httpx-0.21.3.tar.gz", hash = "sha256:7a3eb67ef0b8abbd6d9402248ef2f84a76080fa1c839f8662e6eb385640e445a"},
]
+identify = [
+ {file = "identify-2.4.8-py2.py3-none-any.whl", hash = "sha256:a55bdd671b6063eb837af938c250ec00bba6e610454265133b0d2db7ae718d0f"},
+ {file = "identify-2.4.8.tar.gz", hash = "sha256:97e839c1779f07011b84c92af183e1883d9745d532d83412cca1ca76d3808c1c"},
+]
idna = [
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
@@ -1387,6 +1514,10 @@ ipython = [
{file = "ipython-8.0.0-py3-none-any.whl", hash = "sha256:5b58cf977635abad74d76be49dbb2e97fddd825fb8503083d55496aa1160b854"},
{file = "ipython-8.0.0.tar.gz", hash = "sha256:004a0d05aeecd32adec4841b6e2586d5ca35785b1477db4d8333a39333e0ce98"},
]
+isort = [
+ {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
+ {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
+]
jedi = [
{file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"},
{file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"},
@@ -1477,6 +1608,10 @@ mypy-extensions = [
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
]
+nodeenv = [
+ {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"},
+ {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"},
+]
nonebot-adapter-cqhttp = [
{file = "nonebot-adapter-cqhttp-2.0.0b1.tar.gz", hash = "sha256:57a27c5c6b47bd77dbaddaf063738c0548110424f559deca2e37654814f122d0"},
{file = "nonebot_adapter_cqhttp-2.0.0b1-py3-none-any.whl", hash = "sha256:09433d0d91cfbae42acd1e1ffab96d51e8949ed979d39eff142916fcc27698ab"},
@@ -1577,6 +1712,10 @@ pluggy = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
]
+pre-commit = [
+ {file = "pre_commit-2.17.0-py2.py3-none-any.whl", hash = "sha256:725fa7459782d7bec5ead072810e47351de01709be838c2ce1726b9591dad616"},
+ {file = "pre_commit-2.17.0.tar.gz", hash = "sha256:c1a8040ff15ad3d648c70cc3e55b93e4d2d5b687320955505587fd79bbaed06a"},
+]
prompt-toolkit = [
{file = "prompt_toolkit-3.0.24-py3-none-any.whl", hash = "sha256:e56f2ff799bacecd3e88165b1e2f5ebf9bcd59e80e06d395fa0cc4b8bd7bb506"},
{file = "prompt_toolkit-3.0.24.tar.gz", hash = "sha256:1bb05628c7d87b645974a1bad3f17612be0c29fa39af9f7688030163f680bad6"},
@@ -1809,6 +1948,10 @@ uvloop = [
{file = "uvloop-0.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e5f2e2ff51aefe6c19ee98af12b4ae61f5be456cd24396953244a30880ad861"},
{file = "uvloop-0.16.0.tar.gz", hash = "sha256:f74bc20c7b67d1c27c72601c78cf95be99d5c2cdd4514502b4f3eb0933ff1228"},
]
+virtualenv = [
+ {file = "virtualenv-20.13.1-py2.py3-none-any.whl", hash = "sha256:45e1d053cad4cd453181ae877c4ffc053546ae99e7dd049b9ff1d9be7491abf7"},
+ {file = "virtualenv-20.13.1.tar.gz", hash = "sha256:e0621bcbf4160e4e1030f05065c8834b4e93f4fcc223255db2a823440aca9c14"},
+]
watchgod = [
{file = "watchgod-0.7-py3-none-any.whl", hash = "sha256:d6c1ea21df37847ac0537ca0d6c2f4cdf513562e95f77bb93abbcf05573407b7"},
{file = "watchgod-0.7.tar.gz", hash = "sha256:48140d62b0ebe9dd9cf8381337f06351e1f2e70b2203fa9c6eff4e572ca84f29"},
diff --git a/pyproject.toml b/pyproject.toml
index c3bae32..145e1ec 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -46,6 +46,9 @@ pytest-asyncio = "^0.16"
respx = "^0.19.0"
pytest-cov = "^3.0.0"
nonebug = "^0.2.0"
+black = "^22.1.0"
+isort = "^5.10.1"
+pre-commit = "^2.17.0"
[build-system]
requires = ["poetry>=0.12"]
@@ -56,3 +59,17 @@ markers = [
"compare: compare fetching result with rsshub",
"render: render img by chrome"
]
+
+[tool.black]
+line-length = 88
+target-version = ["py39", "py310"]
+include = '\.pyi?$'
+extend-exclude = '''
+'''
+
+[tool.isort]
+profile = "black"
+line_length = 88
+length_sort = true
+skip_gitignore = true
+force_sort_within_sections = true
diff --git a/src/plugins/auto_agree.py b/src/plugins/auto_agree.py
index 2901c2b..f859397 100644
--- a/src/plugins/auto_agree.py
+++ b/src/plugins/auto_agree.py
@@ -1,18 +1,26 @@
-from nonebot import on_request, logger
-from nonebot.adapters.onebot.v11 import Bot, Event
-from nonebot.permission import SUPERUSER
from nonebot.typing import T_State
+from nonebot import logger, on_request
+from nonebot.permission import SUPERUSER
+from nonebot.adapters.onebot.v11 import Bot, Event
from nonebot.adapters.onebot.v11.permission import PRIVATE_FRIEND
-from nonebot.adapters.onebot.v11.event import FriendRequestEvent, GroupRequestEvent, RequestEvent
+from nonebot.adapters.onebot.v11.event import (
+ RequestEvent,
+ GroupRequestEvent,
+ FriendRequestEvent,
+)
friend_req = on_request(priority=5)
+
@friend_req.handle()
async def add_superuser(bot: Bot, event: RequestEvent, state: T_State):
- if str(event.user_id) in bot.config.superusers and event.request_type == 'private':
+ if str(event.user_id) in bot.config.superusers and event.request_type == "private":
await event.approve(bot)
- logger.info('add user {}'.format(event.user_id))
- elif event.sub_type == 'invite' and str(event.user_id) in bot.config.superusers and event.request_type == 'group':
+ logger.info("add user {}".format(event.user_id))
+ elif (
+ event.sub_type == "invite"
+ and str(event.user_id) in bot.config.superusers
+ and event.request_type == "group"
+ ):
await event.approve(bot)
- logger.info('add group {}'.format(event.group_id))
-
+ logger.info("add group {}".format(event.group_id))
diff --git a/src/plugins/nonebot_bison/__init__.py b/src/plugins/nonebot_bison/__init__.py
index 73abbeb..9083158 100644
--- a/src/plugins/nonebot_bison/__init__.py
+++ b/src/plugins/nonebot_bison/__init__.py
@@ -1,16 +1,17 @@
import nonebot
-from . import config_manager
-from . import config
-from . import scheduler
-from . import send
-from . import post
-from . import platform
-from . import types
-from . import utils
-from . import admin_page
+from . import (
+ post,
+ send,
+ types,
+ utils,
+ config,
+ platform,
+ scheduler,
+ admin_page,
+ config_manager,
+)
-__help__version__ = '0.4.3'
-__help__plugin__name__ = 'nonebot_bison'
-__usage__ = ('本bot可以提供b站、微博等社交媒体的消息订阅,详情'
- '请查看本bot文档,或者at本bot发送“添加订阅”订阅第一个帐号')
+__help__version__ = "0.4.3"
+__help__plugin__name__ = "nonebot_bison"
+__usage__ = "本bot可以提供b站、微博等社交媒体的消息订阅,详情" "请查看本bot文档,或者at本bot发送“添加订阅”订阅第一个帐号"
diff --git a/src/plugins/nonebot_bison/admin_page/__init__.py b/src/plugins/nonebot_bison/admin_page/__init__.py
index 383d2f9..129ce63 100644
--- a/src/plugins/nonebot_bison/admin_page/__init__.py
+++ b/src/plugins/nonebot_bison/admin_page/__init__.py
@@ -1,47 +1,47 @@
-from dataclasses import dataclass
import os
from pathlib import Path
from typing import Union
+from dataclasses import dataclass
-from fastapi.staticfiles import StaticFiles
-from nonebot import get_driver, on_command
-from nonebot.adapters.onebot.v11 import Bot
-from nonebot.adapters.onebot.v11.event import GroupMessageEvent, PrivateMessageEvent
-from nonebot.drivers.fastapi import Driver
+import socketio
from nonebot.log import logger
from nonebot.rule import to_me
from nonebot.typing import T_State
-import socketio
+from nonebot import get_driver, on_command
+from nonebot.drivers.fastapi import Driver
+from fastapi.staticfiles import StaticFiles
+from nonebot.adapters.onebot.v11 import Bot
+from nonebot.adapters.onebot.v11.event import GroupMessageEvent, PrivateMessageEvent
+from .jwt import load_jwt
from ..plugin_config import plugin_config
+from .token_manager import token_manager as tm
from .api import (
- add_group_sub,
auth,
- del_group_sub,
- get_global_conf,
- get_subs_info,
- get_target_name,
test,
+ add_group_sub,
+ del_group_sub,
+ get_subs_info,
+ get_global_conf,
+ get_target_name,
update_group_sub,
)
-from .jwt import load_jwt
-from .token_manager import token_manager as tm
-URL_BASE = '/bison/'
-GLOBAL_CONF_URL = f'{URL_BASE}api/global_conf'
-AUTH_URL = f'{URL_BASE}api/auth'
-SUBSCRIBE_URL = f'{URL_BASE}api/subs'
-GET_TARGET_NAME_URL = f'{URL_BASE}api/target_name'
-TEST_URL = f'{URL_BASE}test'
+URL_BASE = "/bison/"
+GLOBAL_CONF_URL = f"{URL_BASE}api/global_conf"
+AUTH_URL = f"{URL_BASE}api/auth"
+SUBSCRIBE_URL = f"{URL_BASE}api/subs"
+GET_TARGET_NAME_URL = f"{URL_BASE}api/target_name"
+TEST_URL = f"{URL_BASE}test"
STATIC_PATH = (Path(__file__).parent / "dist").resolve()
sio = socketio.AsyncServer(async_mode="asgi", cors_allowed_origins="*")
socket_app = socketio.ASGIApp(sio, socketio_path="socket")
-class SinglePageApplication(StaticFiles):
- def __init__(self, directory: os.PathLike, index='index.html'):
+class SinglePageApplication(StaticFiles):
+ def __init__(self, directory: os.PathLike, index="index.html"):
self.index = index
super().__init__(directory=directory, packages=None, html=True, check_dir=True)
@@ -51,12 +51,13 @@ class SinglePageApplication(StaticFiles):
return await super().lookup_path(self.index)
return (full_path, stat_res)
-def register_router_fastapi(driver: Driver, socketio):
- from fastapi.security import OAuth2PasswordBearer
- from fastapi.param_functions import Depends
- from fastapi import HTTPException, status
- oath_scheme = OAuth2PasswordBearer(tokenUrl='token')
+def register_router_fastapi(driver: Driver, socketio):
+ from fastapi import HTTPException, status
+ from fastapi.param_functions import Depends
+ from fastapi.security import OAuth2PasswordBearer
+
+ oath_scheme = OAuth2PasswordBearer(tokenUrl="token")
async def get_jwt_obj(token: str = Depends(oath_scheme)):
obj = load_jwt(token)
@@ -64,10 +65,12 @@ def register_router_fastapi(driver: Driver, socketio):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
return obj
- async def check_group_permission(groupNumber: str, token_obj: dict = Depends(get_jwt_obj)):
- groups = token_obj['groups']
+ async def check_group_permission(
+ groupNumber: str, token_obj: dict = Depends(get_jwt_obj)
+ ):
+ groups = token_obj["groups"]
for group in groups:
- if int(groupNumber) == group['id']:
+ if int(groupNumber) == group["id"]:
return
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
@@ -88,17 +91,35 @@ def register_router_fastapi(driver: Driver, socketio):
@app.get(SUBSCRIBE_URL)
async def subs(jwt_obj: dict = Depends(get_jwt_obj)):
return await get_subs_info(jwt_obj)
+
@app.get(GET_TARGET_NAME_URL)
- async def _get_target_name(platformName: str, target: str, jwt_obj: dict = Depends(get_jwt_obj)):
+ async def _get_target_name(
+ platformName: str, target: str, jwt_obj: dict = Depends(get_jwt_obj)
+ ):
return await get_target_name(platformName, target, jwt_obj)
+
@app.post(SUBSCRIBE_URL, dependencies=[Depends(check_group_permission)])
async def _add_group_subs(groupNumber: str, req: AddSubscribeReq):
- return await add_group_sub(group_number=groupNumber, platform_name=req.platformName,
- target=req.target, target_name=req.targetName, cats=req.cats, tags=req.tags)
+ return await add_group_sub(
+ group_number=groupNumber,
+ platform_name=req.platformName,
+ target=req.target,
+ target_name=req.targetName,
+ cats=req.cats,
+ tags=req.tags,
+ )
+
@app.patch(SUBSCRIBE_URL, dependencies=[Depends(check_group_permission)])
async def _update_group_subs(groupNumber: str, req: AddSubscribeReq):
- return await update_group_sub(group_number=groupNumber, platform_name=req.platformName,
- target=req.target, target_name=req.targetName, cats=req.cats, tags=req.tags)
+ return await update_group_sub(
+ group_number=groupNumber,
+ platform_name=req.platformName,
+ target=req.target,
+ target_name=req.targetName,
+ cats=req.cats,
+ tags=req.tags,
+ )
+
@app.delete(SUBSCRIBE_URL, dependencies=[Depends(check_group_permission)])
async def _del_group_subs(groupNumber: str, target: str, platformName: str):
return await del_group_sub(groupNumber, platformName, target)
@@ -108,8 +129,8 @@ def register_router_fastapi(driver: Driver, socketio):
def init():
driver = get_driver()
- if driver.type == 'fastapi':
- assert(isinstance(driver, Driver))
+ if driver.type == "fastapi":
+ assert isinstance(driver, Driver)
register_router_fastapi(driver, socket_app)
else:
logger.warning(f"Driver {driver.type} not supported")
@@ -118,19 +139,25 @@ def init():
port = driver.config.port
if host in ["0.0.0.0", "127.0.0.1"]:
host = "localhost"
- logger.opt(colors=True).info(f"Nonebot test frontend will be running at: "
- f"http://{host}:{port}{URL_BASE}")
+ logger.opt(colors=True).info(
+ f"Nonebot test frontend will be running at: "
+ f"http://{host}:{port}{URL_BASE}"
+ )
-if (STATIC_PATH / 'index.html').exists():
+
+if (STATIC_PATH / "index.html").exists():
init()
- get_token = on_command('后台管理', rule=to_me(), priority=5)
+ get_token = on_command("后台管理", rule=to_me(), priority=5)
+
@get_token.handle()
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}')
- get_token.__help__name__ = '获取后台管理地址'
- get_token.__help__info__ = ('获取管理bot后台的地址,该地址会'
- '在一段时间过后过期,请不要泄漏该地址')
+ await get_token.finish(f"请访问: {plugin_config.bison_outer_url}auth/{token}")
+
+ get_token.__help__name__ = "获取后台管理地址"
+ get_token.__help__info__ = "获取管理bot后台的地址,该地址会" "在一段时间过后过期,请不要泄漏该地址"
else:
- logger.warning("Frontend file not found, please compile it or use docker or pypi version")
+ logger.warning(
+ "Frontend file not found, please compile it or use docker or pypi version"
+ )
diff --git a/src/plugins/nonebot_bison/admin_page/api.py b/src/plugins/nonebot_bison/admin_page/api.py
index 8741cd4..a69f23f 100644
--- a/src/plugins/nonebot_bison/admin_page/api.py
+++ b/src/plugins/nonebot_bison/admin_page/api.py
@@ -1,115 +1,146 @@
import nonebot
from nonebot.adapters.onebot.v11.bot import Bot
-from ..config import Config, NoSuchSubscribeException, NoSuchUserException
-from ..platform import check_sub_target, platform_manager
from .jwt import pack_jwt
from .token_manager import token_manager
+from ..platform import check_sub_target, platform_manager
+from ..config import Config, NoSuchUserException, NoSuchSubscribeException
+
async def test():
return {"status": 200, "text": "test"}
+
async def get_global_conf():
res = {}
for platform_name, platform in platform_manager.items():
res[platform_name] = {
- 'platformName': platform_name,
- 'categories': platform.categories,
- 'enabledTag': platform.enable_tag,
- 'name': platform.name,
- 'hasTarget': getattr(platform, 'has_target')
- }
- return { 'platformConf': res }
+ "platformName": platform_name,
+ "categories": platform.categories,
+ "enabledTag": platform.enable_tag,
+ "name": platform.name,
+ "hasTarget": getattr(platform, "has_target"),
+ }
+ return {"platformConf": res}
+
async def get_admin_groups(qq: int):
bot = nonebot.get_bot()
- groups = await bot.call_api('get_group_list')
+ groups = await bot.call_api("get_group_list")
res = []
for group in groups:
- group_id = group['group_id']
- users = await bot.call_api('get_group_member_list', group_id=group_id)
+ group_id = group["group_id"]
+ users = await bot.call_api("get_group_member_list", group_id=group_id)
for user in users:
- if user['user_id'] == qq and user['role'] in ('owner', 'admin'):
- res.append({'id': group_id, 'name': group['group_name']})
+ if user["user_id"] == qq and user["role"] in ("owner", "admin"):
+ res.append({"id": group_id, "name": group["group_name"]})
return res
+
async def auth(token: str):
if qq_tuple := token_manager.get_user(token):
qq, nickname = qq_tuple
bot = nonebot.get_bot()
- assert(isinstance(bot, Bot))
- groups = await bot.call_api('get_group_list')
+ assert isinstance(bot, Bot)
+ groups = await bot.call_api("get_group_list")
if str(qq) in nonebot.get_driver().config.superusers:
jwt_obj = {
- 'id': str(qq),
- 'groups': list(map(
- lambda info: {'id': info['group_id'], 'name': info['group_name']},
- groups)),
- }
+ "id": str(qq),
+ "groups": list(
+ map(
+ lambda info: {
+ "id": info["group_id"],
+ "name": info["group_name"],
+ },
+ groups,
+ )
+ ),
+ }
ret_obj = {
- 'type': 'admin',
- 'name': nickname,
- 'id': str(qq),
- 'token': pack_jwt(jwt_obj)
- }
- return { 'status': 200, **ret_obj }
+ "type": "admin",
+ "name": nickname,
+ "id": str(qq),
+ "token": pack_jwt(jwt_obj),
+ }
+ return {"status": 200, **ret_obj}
if admin_groups := await get_admin_groups(int(qq)):
- jwt_obj = {
- 'id': str(qq),
- 'groups': admin_groups
- }
+ jwt_obj = {"id": str(qq), "groups": admin_groups}
ret_obj = {
- 'type': 'user',
- 'name': nickname,
- 'id': str(qq),
- 'token': pack_jwt(jwt_obj)
- }
- return { 'status': 200, **ret_obj }
+ "type": "user",
+ "name": nickname,
+ "id": str(qq),
+ "token": pack_jwt(jwt_obj),
+ }
+ return {"status": 200, **ret_obj}
else:
- return { 'status': 400, 'type': '', 'name': '', 'id': '', 'token': '' }
+ return {"status": 400, "type": "", "name": "", "id": "", "token": ""}
else:
- return { 'status': 400, 'type': '', 'name': '', 'id': '', 'token': '' }
+ return {"status": 400, "type": "", "name": "", "id": "", "token": ""}
+
async def get_subs_info(jwt_obj: dict):
- groups = jwt_obj['groups']
+ groups = jwt_obj["groups"]
res = {}
for group in groups:
- group_id = group['id']
+ group_id = group["id"]
config = Config()
- subs = list(map(lambda sub: {
- 'platformName': sub['target_type'], 'target': sub['target'], 'targetName': sub['target_name'], 'cats': sub['cats'], 'tags': sub['tags']
- }, config.list_subscribe(group_id, 'group')))
- res[group_id] = {
- 'name': group['name'],
- 'subscribes': subs
- }
+ subs = list(
+ map(
+ lambda sub: {
+ "platformName": sub["target_type"],
+ "target": sub["target"],
+ "targetName": sub["target_name"],
+ "cats": sub["cats"],
+ "tags": sub["tags"],
+ },
+ config.list_subscribe(group_id, "group"),
+ )
+ )
+ res[group_id] = {"name": group["name"], "subscribes": subs}
return res
-async def get_target_name(platform_name: str, target: str, jwt_obj: dict):
- return {'targetName': await check_sub_target(platform_name, target)}
-async def add_group_sub(group_number: str, platform_name: str, target: str,
- target_name: str, cats: list[int], tags: list[str]):
+async def get_target_name(platform_name: str, target: str, jwt_obj: dict):
+ return {"targetName": await check_sub_target(platform_name, target)}
+
+
+async def add_group_sub(
+ group_number: str,
+ platform_name: str,
+ target: str,
+ target_name: str,
+ cats: list[int],
+ tags: list[str],
+):
config = Config()
- config.add_subscribe(int(group_number), 'group', target, target_name, platform_name, cats, tags)
- return { 'status': 200, 'msg': '' }
+ config.add_subscribe(
+ int(group_number), "group", target, target_name, platform_name, cats, tags
+ )
+ return {"status": 200, "msg": ""}
+
async def del_group_sub(group_number: str, platform_name: str, target: str):
config = Config()
try:
- config.del_subscribe(int(group_number), 'group', target, platform_name)
+ config.del_subscribe(int(group_number), "group", target, platform_name)
except (NoSuchUserException, NoSuchSubscribeException):
- return { 'status': 400, 'msg': '删除错误' }
- return { 'status': 200, 'msg': '' }
+ return {"status": 400, "msg": "删除错误"}
+ return {"status": 200, "msg": ""}
-async def update_group_sub(group_number: str, platform_name: str, target: str,
- target_name: str, cats: list[int], tags: list[str]):
+async def update_group_sub(
+ group_number: str,
+ platform_name: str,
+ target: str,
+ target_name: str,
+ cats: list[int],
+ tags: list[str],
+):
config = Config()
try:
- config.update_subscribe(int(group_number), 'group',
- target, target_name, platform_name, cats, tags)
+ config.update_subscribe(
+ int(group_number), "group", target, target_name, platform_name, cats, tags
+ )
except (NoSuchUserException, NoSuchSubscribeException):
- return { 'status': 400, 'msg': '更新错误' }
- return { 'status': 200, 'msg': '' }
-
+ return {"status": 400, "msg": "更新错误"}
+ return {"status": 200, "msg": ""}
diff --git a/src/plugins/nonebot_bison/admin_page/jwt.py b/src/plugins/nonebot_bison/admin_page/jwt.py
index c607747..4344521 100644
--- a/src/plugins/nonebot_bison/admin_page/jwt.py
+++ b/src/plugins/nonebot_bison/admin_page/jwt.py
@@ -1,20 +1,23 @@
import random
import string
-from typing import Optional
-import jwt
import datetime
+from typing import Optional
+
+import jwt
+
+_key = "".join(random.SystemRandom().choice(string.ascii_letters) for _ in range(16))
-_key = ''.join(random.SystemRandom().choice(string.ascii_letters) for _ in range(16))
def pack_jwt(obj: dict) -> str:
return jwt.encode(
- {'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1), **obj},
- _key, algorithm='HS256'
- )
+ {"exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1), **obj},
+ _key,
+ algorithm="HS256",
+ )
+
def load_jwt(token: str) -> Optional[dict]:
try:
- return jwt.decode(token, _key, algorithms=['HS256'])
+ return jwt.decode(token, _key, algorithms=["HS256"])
except:
return None
-
diff --git a/src/plugins/nonebot_bison/admin_page/token_manager.py b/src/plugins/nonebot_bison/admin_page/token_manager.py
index 47e3927..e540656 100644
--- a/src/plugins/nonebot_bison/admin_page/token_manager.py
+++ b/src/plugins/nonebot_bison/admin_page/token_manager.py
@@ -1,24 +1,26 @@
-from typing import Optional
-from expiringdict import ExpiringDict
import random
-import string
+import string
+from typing import Optional
+
+from expiringdict import ExpiringDict
+
class TokenManager:
-
def __init__(self):
- self.token_manager = ExpiringDict(max_len=100, max_age_seconds=60*10)
+ self.token_manager = ExpiringDict(max_len=100, max_age_seconds=60 * 10)
def get_user(self, token: str) -> Optional[tuple]:
res = self.token_manager.get(token)
- assert(res is None or isinstance(res, tuple))
+ assert res is None or isinstance(res, tuple)
return res
def save_user(self, token: str, qq: tuple) -> None:
self.token_manager[token] = qq
def get_user_token(self, qq: tuple) -> str:
- token = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
+ token = "".join(random.choices(string.ascii_letters + string.digits, k=16))
self.save_user(token, qq)
return token
+
token_manager = TokenManager()
diff --git a/src/plugins/nonebot_bison/config.py b/src/plugins/nonebot_bison/config.py
index 0712fcb..2d67e2c 100644
--- a/src/plugins/nonebot_bison/config.py
+++ b/src/plugins/nonebot_bison/config.py
@@ -1,39 +1,43 @@
-from collections import defaultdict
-from os import path
import os
-from typing import DefaultDict, Literal, Mapping, TypedDict
+from os import path
+from collections import defaultdict
+from typing import Literal, Mapping, TypedDict, DefaultDict
import nonebot
from nonebot import logger
from tinydb import Query, TinyDB
+from .utils import Singleton
+from .types import User, Target
from .platform import platform_manager
from .plugin_config import plugin_config
-from .types import Target, User
-from .utils import Singleton
supported_target_type = platform_manager.keys()
+
def get_config_path() -> str:
if plugin_config.bison_config_path:
data_dir = plugin_config.bison_config_path
else:
working_dir = os.getcwd()
- data_dir = path.join(working_dir, 'data')
+ data_dir = path.join(working_dir, "data")
if not path.isdir(data_dir):
os.makedirs(data_dir)
- old_path = path.join(data_dir, 'hk_reporter.json')
- new_path = path.join(data_dir, 'bison.json')
+ old_path = path.join(data_dir, "hk_reporter.json")
+ new_path = path.join(data_dir, "bison.json")
if os.path.exists(old_path) and not os.path.exists(new_path):
os.rename(old_path, new_path)
return new_path
+
class NoSuchUserException(Exception):
pass
+
class NoSuchSubscribeException(Exception):
pass
+
class SubscribeContent(TypedDict):
target: str
target_type: str
@@ -41,75 +45,105 @@ class SubscribeContent(TypedDict):
cats: list[int]
tags: list[str]
+
class ConfigContent(TypedDict):
user: str
user_type: Literal["group", "private"]
subs: list[SubscribeContent]
+
class Config(metaclass=Singleton):
migrate_version = 2
-
+
def __init__(self):
- self.db = TinyDB(get_config_path(), encoding='utf-8')
- self.kv_config = self.db.table('kv')
- self.user_target = self.db.table('user_target')
+ self.db = TinyDB(get_config_path(), encoding="utf-8")
+ self.kv_config = self.db.table("kv")
+ self.user_target = self.db.table("user_target")
self.target_user_cache: dict[str, defaultdict[Target, list[User]]] = {}
self.target_user_cat_cache = {}
self.target_user_tag_cache = {}
self.target_list = {}
self.next_index: DefaultDict[str, int] = defaultdict(lambda: 0)
-
- def add_subscribe(self, user, user_type, target, target_name, target_type, cats, tags):
+
+ def add_subscribe(
+ self, user, user_type, target, target_name, target_type, cats, tags
+ ):
user_query = Query()
query = (user_query.user == user) & (user_query.user_type == user_type)
- if (user_data := self.user_target.get(query)):
+ if user_data := self.user_target.get(query):
# update
- subs: list = user_data.get('subs', [])
- subs.append({"target": target, "target_type": target_type, 'target_name': target_name, 'cats': cats, 'tags': tags})
+ subs: list = user_data.get("subs", [])
+ subs.append(
+ {
+ "target": target,
+ "target_type": target_type,
+ "target_name": target_name,
+ "cats": cats,
+ "tags": tags,
+ }
+ )
self.user_target.update({"subs": subs}, query)
else:
# insert
- self.user_target.insert({
- 'user': user, 'user_type': user_type,
- 'subs': [{'target': target, 'target_type': target_type, 'target_name': target_name, 'cats': cats, 'tags': tags }]
- })
+ self.user_target.insert(
+ {
+ "user": user,
+ "user_type": user_type,
+ "subs": [
+ {
+ "target": target,
+ "target_type": target_type,
+ "target_name": target_name,
+ "cats": cats,
+ "tags": tags,
+ }
+ ],
+ }
+ )
self.update_send_cache()
def list_subscribe(self, user, user_type) -> list[SubscribeContent]:
query = Query()
- if user_sub := self.user_target.get((query.user == user) & (query.user_type ==user_type)):
- return user_sub['subs']
+ if user_sub := self.user_target.get(
+ (query.user == user) & (query.user_type == user_type)
+ ):
+ return user_sub["subs"]
return []
def get_all_subscribe(self):
return self.user_target
-
+
def del_subscribe(self, user, user_type, target, target_type):
user_query = Query()
query = (user_query.user == user) & (user_query.user_type == user_type)
if not (query_res := self.user_target.get(query)):
raise NoSuchUserException()
- subs = query_res.get('subs', [])
+ subs = query_res.get("subs", [])
for idx, sub in enumerate(subs):
- if sub.get('target') == target and sub.get('target_type') == target_type:
+ if sub.get("target") == target and sub.get("target_type") == target_type:
subs.pop(idx)
- self.user_target.update({'subs': subs}, query)
+ self.user_target.update({"subs": subs}, query)
self.update_send_cache()
return
raise NoSuchSubscribeException()
- def update_subscribe(self, user, user_type, target, target_name, target_type, cats, tags):
+ def update_subscribe(
+ self, user, user_type, target, target_name, target_type, cats, tags
+ ):
user_query = Query()
query = (user_query.user == user) & (user_query.user_type == user_type)
- if (user_data := self.user_target.get(query)):
+ if user_data := self.user_target.get(query):
# update
- subs: list = user_data.get('subs', [])
+ subs: list = user_data.get("subs", [])
find_flag = False
for item in subs:
- if item['target'] == target and item['target_type'] == target_type:
- item['target_name'], item['cats'], item['tags'] = \
- target_name, cats, tags
+ if item["target"] == target and item["target_type"] == target_type:
+ item["target_name"], item["cats"], item["tags"] = (
+ target_name,
+ cats,
+ tags,
+ )
find_flag = True
break
if not find_flag:
@@ -121,33 +155,58 @@ class Config(metaclass=Singleton):
def update_send_cache(self):
res = {target_type: defaultdict(list) for target_type in supported_target_type}
- cat_res = {target_type: defaultdict(lambda: defaultdict(list)) for target_type in supported_target_type}
- tag_res = {target_type: defaultdict(lambda: defaultdict(list)) for target_type in supported_target_type}
+ cat_res = {
+ target_type: defaultdict(lambda: defaultdict(list))
+ for target_type in supported_target_type
+ }
+ tag_res = {
+ target_type: defaultdict(lambda: defaultdict(list))
+ for target_type in supported_target_type
+ }
# res = {target_type: defaultdict(lambda: defaultdict(list)) for target_type in supported_target_type}
to_del = []
for user in self.user_target.all():
- for sub in user.get('subs', []):
- if not sub.get('target_type') in supported_target_type:
- to_del.append({'user': user['user'], 'user_type': user['user_type'], 'target': sub['target'], 'target_type': sub['target_type']})
+ for sub in user.get("subs", []):
+ if not sub.get("target_type") in supported_target_type:
+ to_del.append(
+ {
+ "user": user["user"],
+ "user_type": user["user_type"],
+ "target": sub["target"],
+ "target_type": sub["target_type"],
+ }
+ )
continue
- res[sub['target_type']][sub['target']].append(User(user['user'], user['user_type']))
- cat_res[sub['target_type']][sub['target']]['{}-{}'.format(user['user_type'], user['user'])] = sub['cats']
- tag_res[sub['target_type']][sub['target']]['{}-{}'.format(user['user_type'], user['user'])] = sub['tags']
+ res[sub["target_type"]][sub["target"]].append(
+ User(user["user"], user["user_type"])
+ )
+ cat_res[sub["target_type"]][sub["target"]][
+ "{}-{}".format(user["user_type"], user["user"])
+ ] = sub["cats"]
+ tag_res[sub["target_type"]][sub["target"]][
+ "{}-{}".format(user["user_type"], user["user"])
+ ] = sub["tags"]
self.target_user_cache = res
self.target_user_cat_cache = cat_res
self.target_user_tag_cache = tag_res
for target_type in self.target_user_cache:
- self.target_list[target_type] = list(self.target_user_cache[target_type].keys())
-
- logger.info(f'Deleting {to_del}')
+ self.target_list[target_type] = list(
+ self.target_user_cache[target_type].keys()
+ )
+
+ logger.info(f"Deleting {to_del}")
for d in to_del:
self.del_subscribe(**d)
def get_sub_category(self, target_type, target, user_type, user):
- return self.target_user_cat_cache[target_type][target]['{}-{}'.format(user_type, user)]
+ return self.target_user_cat_cache[target_type][target][
+ "{}-{}".format(user_type, user)
+ ]
def get_sub_tags(self, target_type, target, user_type, user):
- return self.target_user_tag_cache[target_type][target]['{}-{}'.format(user_type, user)]
+ return self.target_user_tag_cache[target_type][target][
+ "{}-{}".format(user_type, user)
+ ]
def get_next_target(self, target_type):
# FIXME 插入或删除target后对队列的影响(但是并不是大问题
@@ -158,25 +217,27 @@ class Config(metaclass=Singleton):
self.next_index[target_type] += 1
return res
+
def start_up():
config = Config()
- if not (search_res := config.kv_config.search(Query().name=="version")):
+ if not (search_res := config.kv_config.search(Query().name == "version")):
config.kv_config.insert({"name": "version", "value": config.migrate_version})
elif search_res[0].get("value") < config.migrate_version:
query = Query()
- version_query = (query.name == 'version')
+ version_query = query.name == "version"
cur_version = search_res[0].get("value")
if cur_version == 1:
cur_version = 2
for user_conf in config.user_target.all():
conf_id = user_conf.doc_id
- subs = user_conf['subs']
+ subs = user_conf["subs"]
for sub in subs:
- sub['cats'] = []
- sub['tags'] = []
- config.user_target.update({'subs': subs}, doc_ids=[conf_id])
+ sub["cats"] = []
+ sub["tags"] = []
+ config.user_target.update({"subs": subs}, doc_ids=[conf_id])
config.kv_config.update({"value": config.migrate_version}, version_query)
# do migration
config.update_send_cache()
+
nonebot.get_driver().on_startup(start_up)
diff --git a/src/plugins/nonebot_bison/config_manager.py b/src/plugins/nonebot_bison/config_manager.py
index d5c57bf..6f370fd 100644
--- a/src/plugins/nonebot_bison/config_manager.py
+++ b/src/plugins/nonebot_bison/config_manager.py
@@ -1,152 +1,203 @@
from typing import Type
-from nonebot import logger, on_command
-from nonebot.adapters._bot import Bot as AbstractBot
-from nonebot.adapters._event import Event as AbstractEvent
-from nonebot.adapters.onebot.v11 import Bot, Event, GroupMessageEvent
-from nonebot.adapters.onebot.v11.message import Message
-from nonebot.adapters.onebot.v11.permission import GROUP_ADMIN, GROUP_MEMBER, GROUP_OWNER
-from nonebot.matcher import Matcher
-from nonebot.permission import Permission, SUPERUSER
from nonebot.rule import to_me
from nonebot.typing import T_State
+from nonebot.matcher import Matcher
+from nonebot import logger, on_command
+from nonebot.adapters._bot import Bot as AbstractBot
+from nonebot.permission import SUPERUSER, Permission
+from nonebot.adapters.onebot.v11.message import Message
+from nonebot.adapters._event import Event as AbstractEvent
+from nonebot.adapters.onebot.v11 import Bot, Event, GroupMessageEvent
+from nonebot.adapters.onebot.v11.permission import (
+ GROUP_ADMIN,
+ GROUP_OWNER,
+ GROUP_MEMBER,
+)
-from .config import Config, NoSuchSubscribeException
-from .platform import check_sub_target, platform_manager
from .types import Target
from .utils import parse_text
+from .config import Config, NoSuchSubscribeException
+from .platform import check_sub_target, platform_manager
+
def _gen_prompt_template(prompt: str):
- if hasattr(Message, 'template'):
+ if hasattr(Message, "template"):
return Message.template(prompt)
return prompt
-common_platform = [p.platform_name for p in \
- filter(lambda platform: platform.enabled and platform.is_common,
- platform_manager.values())
- ]
-help_match = on_command('help', rule=to_me(), priority=5)
+common_platform = [
+ p.platform_name
+ for p in filter(
+ lambda platform: platform.enabled and platform.is_common,
+ platform_manager.values(),
+ )
+]
+
+help_match = on_command("help", rule=to_me(), priority=5)
+
+
@help_match.handle()
async def send_help(bot: Bot, event: Event, state: T_State):
- message = '使用方法:\n@bot 添加订阅(仅管理员)\n@bot 查询订阅\n@bot 删除订阅(仅管理员)'
+ message = "使用方法:\n@bot 添加订阅(仅管理员)\n@bot 查询订阅\n@bot 删除订阅(仅管理员)"
await help_match.finish(Message(await parse_text(message)))
def do_add_sub(add_sub: Type[Matcher]):
@add_sub.handle()
async def init_promote(bot: Bot, event: Event, state: T_State):
- state['_prompt'] = '请输入想要订阅的平台,目前支持,请输入冒号左边的名称:\n' + \
- ''.join(['{}:{}\n'.format(platform_name, platform_manager[platform_name].name) \
- for platform_name in common_platform]) + \
- '要查看全部平台请输入:“全部”'
+ state["_prompt"] = (
+ "请输入想要订阅的平台,目前支持,请输入冒号左边的名称:\n"
+ + "".join(
+ [
+ "{}:{}\n".format(
+ platform_name, platform_manager[platform_name].name
+ )
+ for platform_name in common_platform
+ ]
+ )
+ + "要查看全部平台请输入:“全部”"
+ )
- async def parse_platform(bot: AbstractBot, event: AbstractEvent, state: T_State) -> None:
+ async def parse_platform(
+ bot: AbstractBot, event: AbstractEvent, state: T_State
+ ) -> None:
platform = str(event.get_message()).strip()
- if platform == '全部':
- message = '全部平台\n' + \
- '\n'.join(['{}:{}'.format(platform_name, platform.name) \
- for platform_name, platform in platform_manager.items()])
+ if platform == "全部":
+ message = "全部平台\n" + "\n".join(
+ [
+ "{}:{}".format(platform_name, platform.name)
+ for platform_name, platform in platform_manager.items()
+ ]
+ )
await add_sub.reject(message)
elif platform in platform_manager:
- state['platform'] = platform
+ state["platform"] = platform
else:
- await add_sub.reject('平台输入错误')
+ await add_sub.reject("平台输入错误")
- @add_sub.got('platform', _gen_prompt_template('{_prompt}'), parse_platform)
+ @add_sub.got("platform", _gen_prompt_template("{_prompt}"), parse_platform)
@add_sub.handle()
async def init_id(bot: Bot, event: Event, state: T_State):
- if platform_manager[state['platform']].has_target:
- state['_prompt'] = '请输入订阅用户的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'
+ if platform_manager[state["platform"]].has_target:
+ state[
+ "_prompt"
+ ] = "请输入订阅用户的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"
else:
- state['id'] = 'default'
- state['name'] = await platform_manager[state['platform']].get_target_name(Target(''))
+ state["id"] = "default"
+ state["name"] = await platform_manager[state["platform"]].get_target_name(
+ Target("")
+ )
async def parse_id(bot: AbstractBot, event: AbstractEvent, state: T_State):
target = str(event.get_message()).strip()
try:
- name = await check_sub_target(state['platform'], target)
+ name = await check_sub_target(state["platform"], target)
if not name:
- await add_sub.reject('id输入错误')
- state['id'] = target
- state['name'] = name
+ await add_sub.reject("id输入错误")
+ state["id"] = target
+ state["name"] = name
except:
- await add_sub.reject('id输入错误')
+ await add_sub.reject("id输入错误")
- @add_sub.got('id', _gen_prompt_template('{_prompt}'), parse_id)
+ @add_sub.got("id", _gen_prompt_template("{_prompt}"), parse_id)
@add_sub.handle()
async def init_cat(bot: Bot, event: Event, state: T_State):
- if not platform_manager[state['platform']].categories:
- state['cats'] = []
+ if not platform_manager[state["platform"]].categories:
+ state["cats"] = []
return
- state['_prompt'] = '请输入要订阅的类别,以空格分隔,支持的类别有:{}'.format(
- ' '.join(list(platform_manager[state['platform']].categories.values())))
+ state["_prompt"] = "请输入要订阅的类别,以空格分隔,支持的类别有:{}".format(
+ " ".join(list(platform_manager[state["platform"]].categories.values()))
+ )
async def parser_cats(bot: AbstractBot, event: AbstractEvent, state: T_State):
res = []
for cat in str(event.get_message()).strip().split():
- if cat not in platform_manager[state['platform']].reverse_category:
- await add_sub.reject('不支持 {}'.format(cat))
- res.append(platform_manager[state['platform']].reverse_category[cat])
- state['cats'] = res
+ if cat not in platform_manager[state["platform"]].reverse_category:
+ await add_sub.reject("不支持 {}".format(cat))
+ res.append(platform_manager[state["platform"]].reverse_category[cat])
+ state["cats"] = res
- @add_sub.got('cats', _gen_prompt_template('{_prompt}'), parser_cats)
+ @add_sub.got("cats", _gen_prompt_template("{_prompt}"), parser_cats)
@add_sub.handle()
async def init_tag(bot: Bot, event: Event, state: T_State):
- if not platform_manager[state['platform']].enable_tag:
- state['tags'] = []
+ if not platform_manager[state["platform"]].enable_tag:
+ state["tags"] = []
return
- state['_prompt'] = '请输入要订阅的tag,订阅所有tag输入"全部标签"'
+ state["_prompt"] = '请输入要订阅的tag,订阅所有tag输入"全部标签"'
async def parser_tags(bot: AbstractBot, event: AbstractEvent, state: T_State):
- if str(event.get_message()).strip() == '全部标签':
- state['tags'] = []
+ if str(event.get_message()).strip() == "全部标签":
+ state["tags"] = []
else:
- state['tags'] = str(event.get_message()).strip().split()
+ state["tags"] = str(event.get_message()).strip().split()
- @add_sub.got('tags', _gen_prompt_template('{_prompt}'), parser_tags)
+ @add_sub.got("tags", _gen_prompt_template("{_prompt}"), parser_tags)
@add_sub.handle()
async def add_sub_process(bot: Bot, event: Event, state: T_State):
config = Config()
- config.add_subscribe(state.get('_user_id') or event.group_id, user_type='group',
- target=state['id'],
- target_name=state['name'], target_type=state['platform'],
- cats=state.get('cats', []), tags=state.get('tags', []))
- await add_sub.finish('添加 {} 成功'.format(state['name']))
+ config.add_subscribe(
+ state.get("_user_id") or event.group_id,
+ user_type="group",
+ target=state["id"],
+ target_name=state["name"],
+ target_type=state["platform"],
+ cats=state.get("cats", []),
+ tags=state.get("tags", []),
+ )
+ await add_sub.finish("添加 {} 成功".format(state["name"]))
+
def do_query_sub(query_sub: Type[Matcher]):
@query_sub.handle()
async def _(bot: Bot, event: Event, state: T_State):
config: Config = Config()
- sub_list = config.list_subscribe(state.get('_user_id') or event.group_id, "group")
- res = '订阅的帐号为:\n'
+ sub_list = config.list_subscribe(
+ state.get("_user_id") or event.group_id, "group"
+ )
+ res = "订阅的帐号为:\n"
for sub in sub_list:
- res += '{} {} {}'.format(sub['target_type'], sub['target_name'], sub['target'])
- platform = platform_manager[sub['target_type']]
+ res += "{} {} {}".format(
+ sub["target_type"], sub["target_name"], sub["target"]
+ )
+ platform = platform_manager[sub["target_type"]]
if platform.categories:
- res += ' [{}]'.format(', '.join(map(lambda x: platform.categories[x], sub['cats'])))
+ res += " [{}]".format(
+ ", ".join(map(lambda x: platform.categories[x], sub["cats"]))
+ )
if platform.enable_tag:
- res += ' {}'.format(', '.join(sub['tags']))
- res += '\n'
+ res += " {}".format(", ".join(sub["tags"]))
+ res += "\n"
await query_sub.finish(Message(await parse_text(res)))
+
def do_del_sub(del_sub: Type[Matcher]):
@del_sub.handle()
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")
- res = '订阅的帐号为:\n'
- state['sub_table'] = {}
+ sub_list = config.list_subscribe(
+ state.get("_user_id") or event.group_id, "group"
+ )
+ res = "订阅的帐号为:\n"
+ state["sub_table"] = {}
for index, sub in enumerate(sub_list, 1):
- state['sub_table'][index] = {'target_type': sub['target_type'], 'target': sub['target']}
- res += '{} {} {} {}\n'.format(index, sub['target_type'], sub['target_name'], sub['target'])
- platform = platform_manager[sub['target_type']]
+ state["sub_table"][index] = {
+ "target_type": sub["target_type"],
+ "target": sub["target"],
+ }
+ res += "{} {} {} {}\n".format(
+ index, sub["target_type"], sub["target_name"], sub["target"]
+ )
+ platform = platform_manager[sub["target_type"]]
if platform.categories:
- res += ' [{}]'.format(', '.join(map(lambda x: platform.categories[x], sub['cats'])))
+ res += " [{}]".format(
+ ", ".join(map(lambda x: platform.categories[x], sub["cats"]))
+ )
if platform.enable_tag:
- res += ' {}'.format(', '.join(sub['tags']))
- res += '\n'
- res += '请输入要删除的订阅的序号'
+ res += " {}".format(", ".join(sub["tags"]))
+ res += "\n"
+ res += "请输入要删除的订阅的序号"
await bot.send(event=event, message=Message(await parse_text(res)))
@del_sub.receive()
@@ -154,39 +205,60 @@ def do_del_sub(del_sub: Type[Matcher]):
try:
index = int(str(event.get_message()).strip())
config = Config()
- config.del_subscribe(state.get('_user_id') or event.group_id, 'group', **state['sub_table'][index])
+ config.del_subscribe(
+ state.get("_user_id") or event.group_id,
+ "group",
+ **state["sub_table"][index],
+ )
except Exception as e:
- await del_sub.reject('删除错误')
+ await del_sub.reject("删除错误")
logger.warning(e)
else:
- await del_sub.finish('删除成功')
+ await del_sub.finish("删除成功")
+
async def parse_group_number(bot: AbstractBot, event: AbstractEvent, state: T_State):
state[state["_current_key"]] = int(str(event.get_message()))
-add_sub_matcher = on_command("添加订阅", rule=to_me(), permission=GROUP_ADMIN | GROUP_OWNER | SUPERUSER, priority=5)
+add_sub_matcher = on_command(
+ "添加订阅", rule=to_me(), permission=GROUP_ADMIN | GROUP_OWNER | SUPERUSER, priority=5
+)
do_add_sub(add_sub_matcher)
-manage_add_sub_mather = on_command('管理-添加订阅', permission=SUPERUSER, priority=5)
-@manage_add_sub_mather.got('_user_id', "群号", parse_group_number)
+manage_add_sub_mather = on_command("管理-添加订阅", permission=SUPERUSER, priority=5)
+
+
+@manage_add_sub_mather.got("_user_id", "群号", parse_group_number)
async def handle(bot: Bot, event: Event, state: T_State):
pass
+
+
do_add_sub(manage_add_sub_mather)
query_sub_macher = on_command("查询订阅", rule=to_me(), priority=5)
do_query_sub(query_sub_macher)
-manage_query_sub_mather = on_command('管理-查询订阅', permission=SUPERUSER, priority=5)
-@manage_query_sub_mather.got('_user_id', "群号", parse_group_number)
+manage_query_sub_mather = on_command("管理-查询订阅", permission=SUPERUSER, priority=5)
+
+
+@manage_query_sub_mather.got("_user_id", "群号", parse_group_number)
async def handle(bot: Bot, event: Event, state: T_State):
pass
+
+
do_query_sub(manage_query_sub_mather)
-del_sub_macher = on_command("删除订阅", rule=to_me(), permission=GROUP_ADMIN | GROUP_OWNER | SUPERUSER, priority=5)
+del_sub_macher = on_command(
+ "删除订阅", rule=to_me(), permission=GROUP_ADMIN | GROUP_OWNER | SUPERUSER, priority=5
+)
do_del_sub(del_sub_macher)
-manage_del_sub_mather = on_command('管理-删除订阅', permission=SUPERUSER, priority=5)
-@manage_del_sub_mather.got('_user_id', "群号", parse_group_number)
+manage_del_sub_mather = on_command("管理-删除订阅", permission=SUPERUSER, priority=5)
+
+
+@manage_del_sub_mather.got("_user_id", "群号", parse_group_number)
async def handle(bot: Bot, event: Event, state: T_State):
pass
+
+
do_del_sub(manage_del_sub_mather)
diff --git a/src/plugins/nonebot_bison/platform/__init__.py b/src/plugins/nonebot_bison/platform/__init__.py
index 028f002..bdd0591 100644
--- a/src/plugins/nonebot_bison/platform/__init__.py
+++ b/src/plugins/nonebot_bison/platform/__init__.py
@@ -1,18 +1,19 @@
+from pathlib import Path
+from pkgutil import iter_modules
from collections import defaultdict
+from importlib import import_module
from .platform import Platform, NoTargetGroup
-from pkgutil import iter_modules
-from pathlib import Path
-from importlib import import_module
_package_dir = str(Path(__file__).resolve().parent)
for (_, module_name, _) in iter_modules([_package_dir]):
- import_module(f'{__name__}.{module_name}')
+ import_module(f"{__name__}.{module_name}")
async def check_sub_target(target_type, target):
return await platform_manager[target_type].get_target_name(target)
+
_platform_list = defaultdict(list)
for _platform in Platform.registry:
if not _platform.enabled:
@@ -24,5 +25,6 @@ for name, platform_list in _platform_list.items():
if len(platform_list) == 1:
platform_manager[name] = platform_list[0]()
else:
- platform_manager[name] = NoTargetGroup([_platform() for _platform in platform_list])
-
+ platform_manager[name] = NoTargetGroup(
+ [_platform() for _platform in platform_list]
+ )
diff --git a/src/plugins/nonebot_bison/platform/arknights.py b/src/plugins/nonebot_bison/platform/arknights.py
index fb25729..506d261 100644
--- a/src/plugins/nonebot_bison/platform/arknights.py
+++ b/src/plugins/nonebot_bison/platform/arknights.py
@@ -1,37 +1,39 @@
import json
from typing import Any
-from bs4 import BeautifulSoup as bs
import httpx
+from bs4 import BeautifulSoup as bs
from ..post import Post
-from ..types import Category, RawPost, Target
from ..utils import Render
-from .platform import CategoryNotSupport, NewMessage, StatusChange
+from ..types import Target, RawPost, Category
+from .platform import NewMessage, StatusChange, CategoryNotSupport
class Arknights(NewMessage):
- categories = {1: '游戏公告'}
- platform_name = 'arknights'
- name = '明日方舟游戏信息'
+ categories = {1: "游戏公告"}
+ platform_name = "arknights"
+ name = "明日方舟游戏信息"
enable_tag = False
enabled = True
is_common = False
- schedule_type = 'interval'
- schedule_kw = {'seconds': 30}
+ schedule_type = "interval"
+ schedule_kw = {"seconds": 30}
has_target = False
async def get_target_name(self, _: Target) -> str:
- return '明日方舟游戏信息'
+ return "明日方舟游戏信息"
async def get_sub_list(self, _) -> list[RawPost]:
async with httpx.AsyncClient() as client:
- raw_data = await client.get('https://ak-conf.hypergryph.com/config/prod/announce_meta/IOS/announcement.meta.json')
- return json.loads(raw_data.text)['announceList']
+ raw_data = await client.get(
+ "https://ak-conf.hypergryph.com/config/prod/announce_meta/IOS/announcement.meta.json"
+ )
+ return json.loads(raw_data.text)["announceList"]
def get_id(self, post: RawPost) -> Any:
- return post['announceId']
+ return post["announceId"]
def get_date(self, _: RawPost) -> None:
return None
@@ -40,64 +42,85 @@ class Arknights(NewMessage):
return Category(1)
async def parse(self, raw_post: RawPost) -> Post:
- announce_url = raw_post['webUrl']
- text = ''
+ announce_url = raw_post["webUrl"]
+ text = ""
async with httpx.AsyncClient() as client:
raw_html = await client.get(announce_url)
- soup = bs(raw_html, 'html.parser')
+ soup = bs(raw_html, "html.parser")
pics = []
if soup.find("div", class_="standerd-container"):
# 图文
render = Render()
- viewport = {'width': 320, 'height': 6400, 'deviceScaleFactor': 3}
- pic_data = await render.render(announce_url, viewport=viewport, target='div.main')
+ viewport = {"width": 320, "height": 6400, "deviceScaleFactor": 3}
+ pic_data = await render.render(
+ announce_url, viewport=viewport, target="div.main"
+ )
if pic_data:
pics.append(pic_data)
else:
- text = '图片渲染失败'
- elif (pic := soup.find('img', class_='banner-image')):
- pics.append(pic['src'])
+ text = "图片渲染失败"
+ elif pic := soup.find("img", class_="banner-image"):
+ pics.append(pic["src"])
else:
raise CategoryNotSupport()
- return Post('arknights', text=text, url='', target_name="明日方舟游戏内公告", pics=pics, compress=True, override_use_pic=False)
+ return Post(
+ "arknights",
+ text=text,
+ url="",
+ target_name="明日方舟游戏内公告",
+ pics=pics,
+ compress=True,
+ override_use_pic=False,
+ )
+
class AkVersion(StatusChange):
- categories = {2: '更新信息'}
- platform_name = 'arknights'
- name = '明日方舟游戏信息'
+ categories = {2: "更新信息"}
+ platform_name = "arknights"
+ name = "明日方舟游戏信息"
enable_tag = False
enabled = True
is_common = False
- schedule_type = 'interval'
- schedule_kw = {'seconds': 30}
+ schedule_type = "interval"
+ schedule_kw = {"seconds": 30}
has_target = False
async def get_target_name(self, _: Target) -> str:
- return '明日方舟游戏信息'
+ return "明日方舟游戏信息"
async def get_status(self, _):
async with httpx.AsyncClient() as client:
- res_ver = await client.get('https://ak-conf.hypergryph.com/config/prod/official/IOS/version')
- res_preanounce = await client.get('https://ak-conf.hypergryph.com/config/prod/announce_meta/IOS/preannouncement.meta.json')
+ res_ver = await client.get(
+ "https://ak-conf.hypergryph.com/config/prod/official/IOS/version"
+ )
+ res_preanounce = await client.get(
+ "https://ak-conf.hypergryph.com/config/prod/announce_meta/IOS/preannouncement.meta.json"
+ )
res = res_ver.json()
res.update(res_preanounce.json())
return res
def compare_status(self, _, old_status, new_status):
res = []
- if old_status.get('preAnnounceType') == 2 and new_status.get('preAnnounceType') == 0:
- res.append(Post('arknights',
- text='登录界面维护公告上线(大概是开始维护了)',
- target_name='明日方舟更新信息'))
- elif old_status.get('preAnnounceType') == 0 and new_status.get('preAnnounceType') == 2:
- res.append(Post('arknights',
- text='登录界面维护公告下线(大概是开服了,冲!)',
- target_name='明日方舟更新信息'))
- if old_status.get('clientVersion') != new_status.get('clientVersion'):
- res.append(Post('arknights', text='游戏本体更新(大更新)', target_name='明日方舟更新信息'))
- if old_status.get('resVersion') != new_status.get('resVersion'):
- res.append(Post('arknights', text='游戏资源更新(小更新)', target_name='明日方舟更新信息'))
+ if (
+ old_status.get("preAnnounceType") == 2
+ and new_status.get("preAnnounceType") == 0
+ ):
+ res.append(
+ Post("arknights", text="登录界面维护公告上线(大概是开始维护了)", target_name="明日方舟更新信息")
+ )
+ elif (
+ old_status.get("preAnnounceType") == 0
+ and new_status.get("preAnnounceType") == 2
+ ):
+ res.append(
+ Post("arknights", text="登录界面维护公告下线(大概是开服了,冲!)", target_name="明日方舟更新信息")
+ )
+ if old_status.get("clientVersion") != new_status.get("clientVersion"):
+ res.append(Post("arknights", text="游戏本体更新(大更新)", target_name="明日方舟更新信息"))
+ if old_status.get("resVersion") != new_status.get("resVersion"):
+ res.append(Post("arknights", text="游戏资源更新(小更新)", target_name="明日方舟更新信息"))
return res
def get_category(self, _):
@@ -106,28 +129,29 @@ class AkVersion(StatusChange):
async def parse(self, raw_post):
return raw_post
+
class MonsterSiren(NewMessage):
- categories = {3: '塞壬唱片新闻'}
- platform_name = 'arknights'
- name = '明日方舟游戏信息'
+ categories = {3: "塞壬唱片新闻"}
+ platform_name = "arknights"
+ name = "明日方舟游戏信息"
enable_tag = False
enabled = True
is_common = False
- schedule_type = 'interval'
- schedule_kw = {'seconds': 30}
+ schedule_type = "interval"
+ schedule_kw = {"seconds": 30}
has_target = False
async def get_target_name(self, _: Target) -> str:
- return '明日方舟游戏信息'
+ return "明日方舟游戏信息"
async def get_sub_list(self, _) -> list[RawPost]:
async with httpx.AsyncClient() as client:
- raw_data = await client.get('https://monster-siren.hypergryph.com/api/news')
- return raw_data.json()['data']['list']
+ raw_data = await client.get("https://monster-siren.hypergryph.com/api/news")
+ return raw_data.json()["data"]["list"]
def get_id(self, post: RawPost) -> Any:
- return post['cid']
+ return post["cid"]
def get_date(self, _) -> None:
return None
@@ -138,13 +162,21 @@ class MonsterSiren(NewMessage):
async def parse(self, raw_post: RawPost) -> Post:
url = f'https://monster-siren.hypergryph.com/info/{raw_post["cid"]}'
async with httpx.AsyncClient() as client:
- res = await client.get(f'https://monster-siren.hypergryph.com/api/news/{raw_post["cid"]}')
+ res = await client.get(
+ f'https://monster-siren.hypergryph.com/api/news/{raw_post["cid"]}'
+ )
raw_data = res.json()
- content = raw_data['data']['content']
- content = content.replace('', '\n')
- soup = bs(content, 'html.parser')
- imgs = list(map(lambda x: x['src'], soup('img')))
+ content = raw_data["data"]["content"]
+ content = content.replace("", "\n")
+ soup = bs(content, "html.parser")
+ imgs = list(map(lambda x: x["src"], soup("img")))
text = f'{raw_post["title"]}\n{soup.text.strip()}'
- return Post('monster-siren', text=text, pics=imgs,
- url=url, target_name="塞壬唱片新闻", compress=True,
- override_use_pic=False)
+ return Post(
+ "monster-siren",
+ text=text,
+ pics=imgs,
+ url=url,
+ target_name="塞壬唱片新闻",
+ compress=True,
+ override_use_pic=False,
+ )
diff --git a/src/plugins/nonebot_bison/platform/bilibili.py b/src/plugins/nonebot_bison/platform/bilibili.py
index 8980eb1..2f62ee4 100644
--- a/src/plugins/nonebot_bison/platform/bilibili.py
+++ b/src/plugins/nonebot_bison/platform/bilibili.py
@@ -4,51 +4,58 @@ from typing import Any, Optional
import httpx
from ..post import Post
-from ..types import Category, RawPost, Tag, Target
+from ..types import Tag, Target, RawPost, Category
from .platform import NewMessage, CategoryNotSupport
+
class Bilibili(NewMessage):
categories = {
- 1: "一般动态",
- 2: "专栏文章",
- 3: "视频",
- 4: "纯文字",
- 5: "转发"
- # 5: "短视频"
- }
- platform_name = 'bilibili'
+ 1: "一般动态",
+ 2: "专栏文章",
+ 3: "视频",
+ 4: "纯文字",
+ 5: "转发"
+ # 5: "短视频"
+ }
+ platform_name = "bilibili"
enable_tag = True
enabled = True
is_common = True
- schedule_type = 'interval'
- schedule_kw = {'seconds': 10}
- name = 'B站'
+ schedule_type = "interval"
+ schedule_kw = {"seconds": 10}
+ name = "B站"
has_target = True
async def get_target_name(self, target: Target) -> Optional[str]:
async with httpx.AsyncClient() as client:
- res = await client.get('https://api.bilibili.com/x/space/acc/info', params={'mid': target})
+ res = await client.get(
+ "https://api.bilibili.com/x/space/acc/info", params={"mid": target}
+ )
res_data = json.loads(res.text)
- if res_data['code']:
+ if res_data["code"]:
return None
- return res_data['data']['name']
+ return res_data["data"]["name"]
async def get_sub_list(self, target: Target) -> list[RawPost]:
async with httpx.AsyncClient() as client:
- params = {'host_uid': target, 'offset': 0, 'need_top': 0}
- res = await client.get('https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/space_history', params=params, timeout=4.0)
+ params = {"host_uid": target, "offset": 0, "need_top": 0}
+ res = await client.get(
+ "https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/space_history",
+ params=params,
+ timeout=4.0,
+ )
res_dict = json.loads(res.text)
- if res_dict['code'] == 0:
- return res_dict['data']['cards']
+ if res_dict["code"] == 0:
+ return res_dict["data"]["cards"]
else:
return []
def get_id(self, post: RawPost) -> Any:
- return post['desc']['dynamic_id']
-
+ return post["desc"]["dynamic_id"]
+
def get_date(self, post: RawPost) -> int:
- return post['desc']['timestamp']
+ return post["desc"]["timestamp"]
def _do_get_category(self, post_type: int) -> Category:
if post_type == 2:
@@ -65,63 +72,75 @@ class Bilibili(NewMessage):
raise CategoryNotSupport()
def get_category(self, post: RawPost) -> Category:
- post_type = post['desc']['type']
+ post_type = post["desc"]["type"]
return self._do_get_category(post_type)
def get_tags(self, raw_post: RawPost) -> list[Tag]:
- return [*map(lambda tp: tp['topic_name'], raw_post['display']['topic_info']['topic_details'])]
+ return [
+ *map(
+ lambda tp: tp["topic_name"],
+ raw_post["display"]["topic_info"]["topic_details"],
+ )
+ ]
def _get_info(self, post_type: Category, card) -> tuple[str, list]:
if post_type == 1:
# 一般动态
- text = card['item']['description']
- pic = [img['img_src'] for img in card['item']['pictures']]
+ text = card["item"]["description"]
+ pic = [img["img_src"] for img in card["item"]["pictures"]]
elif post_type == 2:
# 专栏文章
- text = '{} {}'.format(card['title'], card['summary'])
- pic = card['image_urls']
+ text = "{} {}".format(card["title"], card["summary"])
+ pic = card["image_urls"]
elif post_type == 3:
# 视频
- text = card['dynamic']
- pic = [card['pic']]
+ text = card["dynamic"]
+ pic = [card["pic"]]
elif post_type == 4:
# 纯文字
- text = card['item']['content']
+ text = card["item"]["content"]
pic = []
else:
raise CategoryNotSupport()
return text, pic
async def parse(self, raw_post: RawPost) -> Post:
- card_content = json.loads(raw_post['card'])
+ card_content = json.loads(raw_post["card"])
post_type = self.get_category(raw_post)
- target_name = raw_post['desc']['user_profile']['info']['uname']
+ target_name = raw_post["desc"]["user_profile"]["info"]["uname"]
if post_type >= 1 and post_type < 5:
- url = ''
+ url = ""
if post_type == 1:
# 一般动态
- url = 'https://t.bilibili.com/{}'.format(raw_post['desc']['dynamic_id_str'])
+ url = "https://t.bilibili.com/{}".format(
+ raw_post["desc"]["dynamic_id_str"]
+ )
elif post_type == 2:
# 专栏文章
- url = 'https://www.bilibili.com/read/cv{}'.format(raw_post['desc']['rid'])
+ url = "https://www.bilibili.com/read/cv{}".format(
+ raw_post["desc"]["rid"]
+ )
elif post_type == 3:
# 视频
- url = 'https://www.bilibili.com/video/{}'.format(raw_post['desc']['bvid'])
+ url = "https://www.bilibili.com/video/{}".format(
+ raw_post["desc"]["bvid"]
+ )
elif post_type == 4:
# 纯文字
- url = 'https://t.bilibili.com/{}'.format(raw_post['desc']['dynamic_id_str'])
+ url = "https://t.bilibili.com/{}".format(
+ raw_post["desc"]["dynamic_id_str"]
+ )
text, pic = self._get_info(post_type, card_content)
elif post_type == 5:
# 转发
- url = 'https://t.bilibili.com/{}'.format(raw_post['desc']['dynamic_id_str'])
- text = card_content['item']['content']
- orig_type = card_content['item']['orig_type']
- orig = json.loads(card_content['origin'])
+ url = "https://t.bilibili.com/{}".format(raw_post["desc"]["dynamic_id_str"])
+ text = card_content["item"]["content"]
+ orig_type = card_content["item"]["orig_type"]
+ orig = json.loads(card_content["origin"])
orig_text, _ = self._get_info(self._do_get_category(orig_type), orig)
- text += '\n--------------\n'
+ text += "\n--------------\n"
text += orig_text
pic = []
else:
raise CategoryNotSupport(post_type)
- return Post('bilibili', text=text, url=url, pics=pic, target_name=target_name)
-
+ return Post("bilibili", text=text, url=url, pics=pic, target_name=target_name)
diff --git a/src/plugins/nonebot_bison/platform/ncm_artist.py b/src/plugins/nonebot_bison/platform/ncm_artist.py
index e230f65..b3cd106 100644
--- a/src/plugins/nonebot_bison/platform/ncm_artist.py
+++ b/src/plugins/nonebot_bison/platform/ncm_artist.py
@@ -1,54 +1,58 @@
from typing import Any, Optional
import httpx
+
from ..post import Post
-from ..types import RawPost, Target
from .platform import NewMessage
+from ..types import Target, RawPost
+
class NcmArtist(NewMessage):
categories = {}
- platform_name = 'ncm-artist'
+ platform_name = "ncm-artist"
enable_tag = False
enabled = True
is_common = True
- schedule_type = 'interval'
- schedule_kw = {'minutes': 1}
+ schedule_type = "interval"
+ schedule_kw = {"minutes": 1}
name = "网易云-歌手"
has_target = True
async def get_target_name(self, target: Target) -> Optional[str]:
async with httpx.AsyncClient() as client:
res = await client.get(
- "https://music.163.com/api/artist/albums/{}".format(target),
- headers={'Referer': 'https://music.163.com/'}
- )
+ "https://music.163.com/api/artist/albums/{}".format(target),
+ headers={"Referer": "https://music.163.com/"},
+ )
res_data = res.json()
- if res_data['code'] != 200:
+ if res_data["code"] != 200:
return
- return res_data['artist']['name']
+ return res_data["artist"]["name"]
async def get_sub_list(self, target: Target) -> list[RawPost]:
async with httpx.AsyncClient() as client:
res = await client.get(
- "https://music.163.com/api/artist/albums/{}".format(target),
- headers={'Referer': 'https://music.163.com/'}
- )
+ "https://music.163.com/api/artist/albums/{}".format(target),
+ headers={"Referer": "https://music.163.com/"},
+ )
res_data = res.json()
- if res_data['code'] != 200:
+ if res_data["code"] != 200:
return []
else:
- return res_data['hotAlbums']
+ return res_data["hotAlbums"]
def get_id(self, post: RawPost) -> Any:
- return post['id']
+ return post["id"]
def get_date(self, post: RawPost) -> int:
- return post['publishTime'] // 1000
+ return post["publishTime"] // 1000
async def parse(self, raw_post: RawPost) -> Post:
- text = '新专辑发布:{}'.format(raw_post['name'])
- target_name = raw_post['artist']['name']
- pics = [raw_post['picUrl']]
- url = "https://music.163.com/#/album?id={}".format(raw_post['id'])
- return Post('ncm-artist', text=text, url=url, pics=pics, target_name=target_name)
+ text = "新专辑发布:{}".format(raw_post["name"])
+ target_name = raw_post["artist"]["name"]
+ pics = [raw_post["picUrl"]]
+ url = "https://music.163.com/#/album?id={}".format(raw_post["id"])
+ return Post(
+ "ncm-artist", text=text, url=url, pics=pics, target_name=target_name
+ )
diff --git a/src/plugins/nonebot_bison/platform/ncm_radio.py b/src/plugins/nonebot_bison/platform/ncm_radio.py
index 6fae725..ea7a0f0 100644
--- a/src/plugins/nonebot_bison/platform/ncm_radio.py
+++ b/src/plugins/nonebot_bison/platform/ncm_radio.py
@@ -1,56 +1,58 @@
from typing import Any, Optional
import httpx
+
from ..post import Post
-from ..types import RawPost, Target
from .platform import NewMessage
+from ..types import Target, RawPost
+
class NcmRadio(NewMessage):
categories = {}
- platform_name = 'ncm-radio'
+ platform_name = "ncm-radio"
enable_tag = False
enabled = True
is_common = False
- schedule_type = 'interval'
- schedule_kw = {'minutes': 10}
+ schedule_type = "interval"
+ schedule_kw = {"minutes": 10}
name = "网易云-电台"
has_target = True
async def get_target_name(self, target: Target) -> Optional[str]:
async with httpx.AsyncClient() as client:
res = await client.post(
- "http://music.163.com/api/dj/program/byradio",
- headers={'Referer': 'https://music.163.com/'},
- data={"radioId": target, "limit": 1000, "offset": 0}
- )
+ "http://music.163.com/api/dj/program/byradio",
+ headers={"Referer": "https://music.163.com/"},
+ data={"radioId": target, "limit": 1000, "offset": 0},
+ )
res_data = res.json()
- if res_data['code'] != 200 or res_data['programs'] == 0:
+ if res_data["code"] != 200 or res_data["programs"] == 0:
return
- return res_data['programs'][0]['radio']['name']
+ return res_data["programs"][0]["radio"]["name"]
async def get_sub_list(self, target: Target) -> list[RawPost]:
async with httpx.AsyncClient() as client:
res = await client.post(
- "http://music.163.com/api/dj/program/byradio",
- headers={'Referer': 'https://music.163.com/'},
- data={"radioId": target, "limit": 1000, "offset": 0}
- )
+ "http://music.163.com/api/dj/program/byradio",
+ headers={"Referer": "https://music.163.com/"},
+ data={"radioId": target, "limit": 1000, "offset": 0},
+ )
res_data = res.json()
- if res_data['code'] != 200:
+ if res_data["code"] != 200:
return []
else:
- return res_data['programs']
+ return res_data["programs"]
def get_id(self, post: RawPost) -> Any:
- return post['id']
+ return post["id"]
def get_date(self, post: RawPost) -> int:
- return post['createTime'] // 1000
+ return post["createTime"] // 1000
async def parse(self, raw_post: RawPost) -> Post:
- text = '网易云电台更新:{}'.format(raw_post['name'])
- target_name = raw_post['radio']['name']
- pics = [raw_post['coverUrl']]
- url = "https://music.163.com/#/program/{}".format(raw_post['id'])
- return Post('ncm-radio', text=text, url=url, pics=pics, target_name=target_name)
+ text = "网易云电台更新:{}".format(raw_post["name"])
+ target_name = raw_post["radio"]["name"]
+ pics = [raw_post["coverUrl"]]
+ url = "https://music.163.com/#/program/{}".format(raw_post["id"])
+ return Post("ncm-radio", text=text, url=url, pics=pics, target_name=target_name)
diff --git a/src/plugins/nonebot_bison/platform/platform.py b/src/plugins/nonebot_bison/platform/platform.py
index cff3ff6..fc813f5 100644
--- a/src/plugins/nonebot_bison/platform/platform.py
+++ b/src/plugins/nonebot_bison/platform/platform.py
@@ -1,15 +1,15 @@
-from abc import abstractmethod, ABC
-from collections import defaultdict
-from dataclasses import dataclass
import time
-from typing import Any, Collection, Optional, Literal
+from dataclasses import dataclass
+from abc import ABC, abstractmethod
+from collections import defaultdict
+from typing import Any, Literal, Optional, Collection
import httpx
from nonebot import logger
-from ..plugin_config import plugin_config
from ..post import Post
-from ..types import Category, RawPost, Tag, Target, User, UserSubInfo
+from ..plugin_config import plugin_config
+from ..types import Tag, User, Target, RawPost, Category, UserSubInfo
class CategoryNotSupport(Exception):
@@ -17,26 +17,27 @@ class CategoryNotSupport(Exception):
class RegistryMeta(type):
-
def __new__(cls, name, bases, namespace, **kwargs):
return super().__new__(cls, name, bases, namespace)
def __init__(cls, name, bases, namespace, **kwargs):
- if kwargs.get('base'):
+ if kwargs.get("base"):
# this is the base class
cls.registry = []
- elif not kwargs.get('abstract'):
+ elif not kwargs.get("abstract"):
# this is the subclass
cls.registry.append(cls)
super().__init__(name, bases, namespace, **kwargs)
+
class RegistryABCMeta(RegistryMeta, ABC):
...
+
class Platform(metaclass=RegistryABCMeta, base=True):
-
- schedule_type: Literal['date', 'interval', 'cron']
+
+ schedule_type: Literal["date", "interval", "cron"]
schedule_kw: dict
is_common: bool
enabled: bool
@@ -52,7 +53,9 @@ class Platform(metaclass=RegistryABCMeta, base=True):
...
@abstractmethod
- async def fetch_new_post(self, target: Target, users: list[UserSubInfo]) -> list[tuple[User, list[Post]]]:
+ async def fetch_new_post(
+ self, target: Target, users: list[UserSubInfo]
+ ) -> list[tuple[User, list[Post]]]:
...
@abstractmethod
@@ -67,7 +70,7 @@ class Platform(metaclass=RegistryABCMeta, base=True):
super().__init__()
self.reverse_category = {}
for key, val in self.categories.items():
- self.reverse_category[val] = key
+ self.reverse_category[val] = key
self.store = dict()
@abstractmethod
@@ -75,12 +78,14 @@ class Platform(metaclass=RegistryABCMeta, base=True):
"Return Tag list of given RawPost"
def get_stored_data(self, target: Target) -> Any:
- return self.store.get(target)
+ return self.store.get(target)
def set_stored_data(self, target: Target, data: Any):
self.store[target] = data
- async def filter_user_custom(self, raw_post_list: list[RawPost], cats: list[Category], tags: list[Tag]) -> list[RawPost]:
+ async def filter_user_custom(
+ self, raw_post_list: list[RawPost], cats: list[Category], tags: list[Tag]
+ ) -> list[RawPost]:
res: list[RawPost] = []
for raw_post in raw_post_list:
if self.categories:
@@ -99,12 +104,16 @@ class Platform(metaclass=RegistryABCMeta, base=True):
res.append(raw_post)
return res
- async def dispatch_user_post(self, target: Target, new_posts: list[RawPost], users: list[UserSubInfo]) -> list[tuple[User, list[Post]]]:
+ async def dispatch_user_post(
+ self, target: Target, new_posts: list[RawPost], users: list[UserSubInfo]
+ ) -> list[tuple[User, list[Post]]]:
res: list[tuple[User, list[Post]]] = []
for user, category_getter, tag_getter in users:
required_tags = tag_getter(target) if self.enable_tag else []
cats = category_getter(target)
- user_raw_post = await self.filter_user_custom(new_posts, cats, required_tags)
+ user_raw_post = await self.filter_user_custom(
+ new_posts, cats, required_tags
+ )
user_post: list[Post] = []
for raw_post in user_raw_post:
user_post.append(await self.do_parse(raw_post))
@@ -116,6 +125,7 @@ class Platform(metaclass=RegistryABCMeta, base=True):
"Return category of given Rawpost"
raise NotImplementedError()
+
class MessageProcess(Platform, abstract=True):
"General message process fetch, parse, filter progress"
@@ -127,7 +137,6 @@ class MessageProcess(Platform, abstract=True):
def get_id(self, post: RawPost) -> Any:
"Get post id of given RawPost"
-
async def do_parse(self, raw_post: RawPost) -> Post:
post_id = self.get_id(raw_post)
if post_id not in self.parse_cache:
@@ -156,8 +165,11 @@ class MessageProcess(Platform, abstract=True):
# post_id = self.get_id(raw_post)
# if post_id in exists_posts_set:
# continue
- if (post_time := self.get_date(raw_post)) and time.time() - post_time > 2 * 60 * 60 and \
- plugin_config.bison_init_filter:
+ if (
+ (post_time := self.get_date(raw_post))
+ and time.time() - post_time > 2 * 60 * 60
+ and plugin_config.bison_init_filter
+ ):
continue
try:
self.get_category(raw_post)
@@ -168,15 +180,18 @@ class MessageProcess(Platform, abstract=True):
res.append(raw_post)
return res
+
class NewMessage(MessageProcess, abstract=True):
"Fetch a list of messages, filter the new messages, dispatch it to different users"
@dataclass
- class MessageStorage():
+ class MessageStorage:
inited: bool
exists_posts: set[Any]
- async def filter_common_with_diff(self, target: Target, raw_post_list: list[RawPost]) -> list[RawPost]:
+ async def filter_common_with_diff(
+ self, target: Target, raw_post_list: list[RawPost]
+ ) -> list[RawPost]:
filtered_post = await self.filter_common(raw_post_list)
store = self.get_stored_data(target) or self.MessageStorage(False, set())
res = []
@@ -185,7 +200,11 @@ class NewMessage(MessageProcess, abstract=True):
for raw_post in filtered_post:
post_id = self.get_id(raw_post)
store.exists_posts.add(post_id)
- logger.info('init {}-{} with {}'.format(self.platform_name, target, store.exists_posts))
+ logger.info(
+ "init {}-{} with {}".format(
+ self.platform_name, target, store.exists_posts
+ )
+ )
store.inited = True
else:
for raw_post in filtered_post:
@@ -197,8 +216,9 @@ class NewMessage(MessageProcess, abstract=True):
self.set_stored_data(target, store)
return res
-
- async def fetch_new_post(self, target: Target, users: list[UserSubInfo]) -> list[tuple[User, list[Post]]]:
+ async def fetch_new_post(
+ self, target: Target, users: list[UserSubInfo]
+ ) -> list[tuple[User, list[Post]]]:
try:
post_list = await self.get_sub_list(target)
new_posts = await self.filter_common_with_diff(target, post_list)
@@ -206,17 +226,25 @@ class NewMessage(MessageProcess, abstract=True):
return []
else:
for post in new_posts:
- logger.info('fetch new post from {} {}: {}'.format(
- self.platform_name,
- target if self.has_target else '-',
- self.get_id(post)))
+ logger.info(
+ "fetch new post from {} {}: {}".format(
+ self.platform_name,
+ target if self.has_target else "-",
+ self.get_id(post),
+ )
+ )
res = await self.dispatch_user_post(target, new_posts, users)
self.parse_cache = {}
return res
except httpx.RequestError as err:
- logger.warning("network connection error: {}, url: {}".format(type(err), err.request.url))
+ logger.warning(
+ "network connection error: {}, url: {}".format(
+ type(err), err.request.url
+ )
+ )
return []
+
class StatusChange(Platform, abstract=True):
"Watch a status, and fire a post when status changes"
@@ -232,49 +260,69 @@ class StatusChange(Platform, abstract=True):
async def parse(self, raw_post: RawPost) -> Post:
...
- async def fetch_new_post(self, target: Target, users: list[UserSubInfo]) -> list[tuple[User, list[Post]]]:
+ async def fetch_new_post(
+ self, target: Target, users: list[UserSubInfo]
+ ) -> list[tuple[User, list[Post]]]:
try:
new_status = await self.get_status(target)
res = []
if old_status := self.get_stored_data(target):
diff = self.compare_status(target, old_status, new_status)
if diff:
- logger.info("status changes {} {}: {} -> {}".format(
- self.platform_name,
- target if self.has_target else '-',
- old_status, new_status
- ))
+ logger.info(
+ "status changes {} {}: {} -> {}".format(
+ self.platform_name,
+ target if self.has_target else "-",
+ old_status,
+ new_status,
+ )
+ )
res = await self.dispatch_user_post(target, diff, users)
self.set_stored_data(target, new_status)
return res
except httpx.RequestError as err:
- logger.warning("network connection error: {}, url: {}".format(type(err), err.request.url))
+ logger.warning(
+ "network connection error: {}, url: {}".format(
+ type(err), err.request.url
+ )
+ )
return []
+
class SimplePost(MessageProcess, abstract=True):
"Fetch a list of messages, dispatch it to different users"
- async def fetch_new_post(self, target: Target, users: list[UserSubInfo]) -> list[tuple[User, list[Post]]]:
+ async def fetch_new_post(
+ self, target: Target, users: list[UserSubInfo]
+ ) -> list[tuple[User, list[Post]]]:
try:
new_posts = await self.get_sub_list(target)
if not new_posts:
return []
else:
for post in new_posts:
- logger.info('fetch new post from {} {}: {}'.format(
- self.platform_name,
- target if self.has_target else '-',
- self.get_id(post)))
+ logger.info(
+ "fetch new post from {} {}: {}".format(
+ self.platform_name,
+ target if self.has_target else "-",
+ self.get_id(post),
+ )
+ )
res = await self.dispatch_user_post(target, new_posts, users)
self.parse_cache = {}
return res
except httpx.RequestError as err:
- logger.warning("network connection error: {}, url: {}".format(type(err), err.request.url))
+ logger.warning(
+ "network connection error: {}, url: {}".format(
+ type(err), err.request.url
+ )
+ )
return []
+
class NoTargetGroup(Platform, abstract=True):
enable_tag = False
- DUMMY_STR = '_DUMMY'
+ DUMMY_STR = "_DUMMY"
enabled = True
has_target = False
@@ -287,24 +335,35 @@ class NoTargetGroup(Platform, abstract=True):
self.schedule_kw = platform_list[0].schedule_kw
for platform in platform_list:
if platform.has_target:
- raise RuntimeError('Platform {} should have no target'.format(platform.name))
+ raise RuntimeError(
+ "Platform {} should have no target".format(platform.name)
+ )
if name == self.DUMMY_STR:
name = platform.name
elif name != platform.name:
- raise RuntimeError('Platform name for {} not fit'.format(self.platform_name))
+ raise RuntimeError(
+ "Platform name for {} not fit".format(self.platform_name)
+ )
platform_category_key_set = set(platform.categories.keys())
if platform_category_key_set & categories_keys:
- raise RuntimeError('Platform categories for {} duplicate'.format(self.platform_name))
+ raise RuntimeError(
+ "Platform categories for {} duplicate".format(self.platform_name)
+ )
categories_keys |= platform_category_key_set
self.categories.update(platform.categories)
- if platform.schedule_kw != self.schedule_kw or platform.schedule_type != self.schedule_type:
- raise RuntimeError('Platform scheduler for {} not fit'.format(self.platform_name))
+ if (
+ platform.schedule_kw != self.schedule_kw
+ or platform.schedule_type != self.schedule_type
+ ):
+ raise RuntimeError(
+ "Platform scheduler for {} not fit".format(self.platform_name)
+ )
self.name = name
self.is_common = platform_list[0].is_common
super().__init__()
def __str__(self):
- return '[' + ' '.join(map(lambda x: x.name, self.platform_list)) + ']'
+ return "[" + " ".join(map(lambda x: x.name, self.platform_list)) + "]"
async def get_target_name(self, _):
return await self.platform_list[0].get_target_name(_)
@@ -316,4 +375,3 @@ class NoTargetGroup(Platform, abstract=True):
for user, posts in platform_res:
res[user].extend(posts)
return [[key, val] for key, val in res.items()]
-
diff --git a/src/plugins/nonebot_bison/platform/rss.py b/src/plugins/nonebot_bison/platform/rss.py
index 4cc18cc..4e5aefd 100644
--- a/src/plugins/nonebot_bison/platform/rss.py
+++ b/src/plugins/nonebot_bison/platform/rss.py
@@ -1,31 +1,32 @@
import calendar
from typing import Any, Optional
-from bs4 import BeautifulSoup as bs
-import feedparser
import httpx
+import feedparser
+from bs4 import BeautifulSoup as bs
from ..post import Post
-from ..types import RawPost, Target
from .platform import NewMessage
+from ..types import Target, RawPost
+
class Rss(NewMessage):
categories = {}
enable_tag = False
- platform_name = 'rss'
+ platform_name = "rss"
name = "Rss"
enabled = True
is_common = True
- schedule_type = 'interval'
- schedule_kw = {'seconds': 30}
+ schedule_type = "interval"
+ schedule_kw = {"seconds": 30}
has_target = True
async def get_target_name(self, target: Target) -> Optional[str]:
async with httpx.AsyncClient() as client:
res = await client.get(target, timeout=10.0)
feed = feedparser.parse(res.text)
- return feed['feed']['title']
+ return feed["feed"]["title"]
def get_date(self, post: RawPost) -> int:
return calendar.timegm(post.published_parsed)
@@ -39,12 +40,18 @@ class Rss(NewMessage):
feed = feedparser.parse(res)
entries = feed.entries
for entry in entries:
- entry['_target_name'] = feed.feed.title
+ entry["_target_name"] = feed.feed.title
return feed.entries
async def parse(self, raw_post: RawPost) -> Post:
- text = raw_post.get('title', '') + '\n' if raw_post.get('title') else ''
- soup = bs(raw_post.description, 'html.parser')
+ text = raw_post.get("title", "") + "\n" if raw_post.get("title") else ""
+ soup = bs(raw_post.description, "html.parser")
text += soup.text.strip()
- pics = list(map(lambda x: x.attrs['src'], soup('img')))
- return Post('rss', text=text, url=raw_post.link, pics=pics, target_name=raw_post['_target_name'])
+ pics = list(map(lambda x: x.attrs["src"], soup("img")))
+ return Post(
+ "rss",
+ text=text,
+ url=raw_post.link,
+ pics=pics,
+ target_name=raw_post["_target_name"],
+ )
diff --git a/src/plugins/nonebot_bison/platform/wechat.py b/src/plugins/nonebot_bison/platform/wechat.py
index 7c04306..7486488 100644
--- a/src/plugins/nonebot_bison/platform/wechat.py
+++ b/src/plugins/nonebot_bison/platform/wechat.py
@@ -1,14 +1,15 @@
-from datetime import datetime
-import hashlib
-import json
import re
+import json
+import hashlib
+from datetime import datetime
from typing import Any, Optional
-from bs4 import BeautifulSoup as bs
import httpx
+from bs4 import BeautifulSoup as bs
-from ..post import Post
from ..types import *
+from ..post import Post
+
# from .platform import Platform
@@ -75,4 +76,3 @@ from ..types import *
# pics=[],
# url=''
# )
-
diff --git a/src/plugins/nonebot_bison/platform/weibo.py b/src/plugins/nonebot_bison/platform/weibo.py
index 19d8703..99b9818 100644
--- a/src/plugins/nonebot_bison/platform/weibo.py
+++ b/src/plugins/nonebot_bison/platform/weibo.py
@@ -1,121 +1,152 @@
-from datetime import datetime
-import json
import re
+import json
+from datetime import datetime
from typing import Any, Optional
-from bs4 import BeautifulSoup as bs
import httpx
from nonebot import logger
+from bs4 import BeautifulSoup as bs
-from ..post import Post
from ..types import *
+from ..post import Post
from .platform import NewMessage
+
class Weibo(NewMessage):
categories = {
- 1: '转发',
- 2: '视频',
- 3: '图文',
- 4: '文字',
- }
+ 1: "转发",
+ 2: "视频",
+ 3: "图文",
+ 4: "文字",
+ }
enable_tag = True
- platform_name = 'weibo'
- name = '新浪微博'
+ platform_name = "weibo"
+ name = "新浪微博"
enabled = True
is_common = True
- schedule_type = 'interval'
- schedule_kw = {'seconds': 3}
+ schedule_type = "interval"
+ schedule_kw = {"seconds": 3}
has_target = True
async def get_target_name(self, target: Target) -> Optional[str]:
async with httpx.AsyncClient() as client:
- param = {'containerid': '100505' + target}
- res = await client.get('https://m.weibo.cn/api/container/getIndex', params=param)
+ param = {"containerid": "100505" + target}
+ res = await client.get(
+ "https://m.weibo.cn/api/container/getIndex", params=param
+ )
res_dict = json.loads(res.text)
- if res_dict.get('ok') == 1:
- return res_dict['data']['userInfo']['screen_name']
+ if res_dict.get("ok") == 1:
+ return res_dict["data"]["userInfo"]["screen_name"]
else:
return None
async def get_sub_list(self, target: Target) -> list[RawPost]:
async with httpx.AsyncClient() as client:
- params = { 'containerid': '107603' + target}
- res = await client.get('https://m.weibo.cn/api/container/getIndex?', params=params, timeout=4.0)
+ params = {"containerid": "107603" + target}
+ res = await client.get(
+ "https://m.weibo.cn/api/container/getIndex?", params=params, timeout=4.0
+ )
res_data = json.loads(res.text)
- if not res_data['ok']:
+ if not res_data["ok"]:
return []
- custom_filter: Callable[[RawPost], bool] = lambda d: d['card_type'] == 9
- return list(filter(custom_filter, res_data['data']['cards']))
+ custom_filter: Callable[[RawPost], bool] = lambda d: d["card_type"] == 9
+ return list(filter(custom_filter, res_data["data"]["cards"]))
def get_id(self, post: RawPost) -> Any:
- return post['mblog']['id']
+ return post["mblog"]["id"]
def filter_platform_custom(self, raw_post: RawPost) -> bool:
- return raw_post['card_type'] == 9
+ return raw_post["card_type"] == 9
def get_date(self, raw_post: RawPost) -> float:
- created_time = datetime.strptime(raw_post['mblog']['created_at'], '%a %b %d %H:%M:%S %z %Y')
+ created_time = datetime.strptime(
+ raw_post["mblog"]["created_at"], "%a %b %d %H:%M:%S %z %Y"
+ )
return created_time.timestamp()
def get_tags(self, raw_post: RawPost) -> Optional[list[Tag]]:
"Return Tag list of given RawPost"
- text = raw_post['mblog']['text']
- soup = bs(text, 'html.parser')
- res = list(map(
- lambda x: x[1:-1],
- filter(
- lambda s: s[0] == '#' and s[-1] == '#',
- map(lambda x:x.text, soup.find_all('span', class_='surl-text'))
- )
- ))
- super_topic_img = soup.find('img', src=re.compile(r'timeline_card_small_super_default'))
+ text = raw_post["mblog"]["text"]
+ soup = bs(text, "html.parser")
+ res = list(
+ map(
+ lambda x: x[1:-1],
+ filter(
+ lambda s: s[0] == "#" and s[-1] == "#",
+ map(lambda x: x.text, soup.find_all("span", class_="surl-text")),
+ ),
+ )
+ )
+ super_topic_img = soup.find(
+ "img", src=re.compile(r"timeline_card_small_super_default")
+ )
if super_topic_img:
try:
- res.append(super_topic_img.parent.parent.find('span', class_='surl-text').text + '超话')
+ res.append(
+ super_topic_img.parent.parent.find("span", class_="surl-text").text
+ + "超话"
+ )
except:
- logger.info('super_topic extract error: {}'.format(text))
+ logger.info("super_topic extract error: {}".format(text))
return res
def get_category(self, raw_post: RawPost) -> Category:
- if raw_post['mblog'].get('retweeted_status'):
+ if raw_post["mblog"].get("retweeted_status"):
return Category(1)
- elif raw_post['mblog'].get('page_info') and raw_post['mblog']['page_info'].get('type') == 'video':
+ elif (
+ raw_post["mblog"].get("page_info")
+ and raw_post["mblog"]["page_info"].get("type") == "video"
+ ):
return Category(2)
- elif raw_post['mblog'].get('pics'):
+ elif raw_post["mblog"].get("pics"):
return Category(3)
else:
return Category(4)
def _get_text(self, raw_text: str) -> str:
- text = raw_text.replace('
', '\n')
- return bs(text, 'html.parser').text
+ text = raw_text.replace("
", "\n")
+ return bs(text, "html.parser").text
async def parse(self, raw_post: RawPost) -> Post:
header = {
- 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
- 'accept-language': 'zh-CN,zh;q=0.9',
- 'authority': 'm.weibo.cn',
- 'cache-control': 'max-age=0',
- 'sec-fetch-dest': 'empty',
- 'sec-fetch-mode': 'same-origin',
- 'sec-fetch-site': 'same-origin',
- 'upgrade-insecure-requests': '1',
- 'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) '
- 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 '
- 'Mobile Safari/537.36'}
- info = raw_post['mblog']
- if info['isLongText'] or info['pic_num'] > 9:
+ "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
+ "accept-language": "zh-CN,zh;q=0.9",
+ "authority": "m.weibo.cn",
+ "cache-control": "max-age=0",
+ "sec-fetch-dest": "empty",
+ "sec-fetch-mode": "same-origin",
+ "sec-fetch-site": "same-origin",
+ "upgrade-insecure-requests": "1",
+ "user-agent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) "
+ "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 "
+ "Mobile Safari/537.36",
+ }
+ info = raw_post["mblog"]
+ if info["isLongText"] or info["pic_num"] > 9:
async with httpx.AsyncClient() as client:
- res = await client.get('https://m.weibo.cn/detail/{}'.format(info['mid']), headers=header)
+ res = await client.get(
+ "https://m.weibo.cn/detail/{}".format(info["mid"]), headers=header
+ )
try:
- full_json_text = re.search(r'"status": ([\s\S]+),\s+"hotScheme"', res.text).group(1)
+ full_json_text = re.search(
+ r'"status": ([\s\S]+),\s+"hotScheme"', res.text
+ ).group(1)
info = json.loads(full_json_text)
except:
- logger.info('detail message error: https://m.weibo.cn/detail/{}'.format(info['mid']))
- parsed_text = self._get_text(info['text'])
- pic_urls = [img['large']['url'] for img in info.get('pics', [])]
- detail_url = 'https://weibo.com/{}/{}'.format(info['user']['id'], info['bid'])
+ logger.info(
+ "detail message error: https://m.weibo.cn/detail/{}".format(
+ info["mid"]
+ )
+ )
+ parsed_text = self._get_text(info["text"])
+ pic_urls = [img["large"]["url"] for img in info.get("pics", [])]
+ detail_url = "https://weibo.com/{}/{}".format(info["user"]["id"], info["bid"])
# return parsed_text, detail_url, pic_urls
- return Post('weibo', text=parsed_text, url=detail_url, pics=pic_urls, target_name=info['user']['screen_name'])
-
+ return Post(
+ "weibo",
+ text=parsed_text,
+ url=detail_url,
+ pics=pic_urls,
+ target_name=info["user"]["screen_name"],
+ )
diff --git a/src/plugins/nonebot_bison/plugin_config.py b/src/plugins/nonebot_bison/plugin_config.py
index e477b83..2e48b4e 100644
--- a/src/plugins/nonebot_bison/plugin_config.py
+++ b/src/plugins/nonebot_bison/plugin_config.py
@@ -1,23 +1,25 @@
+import warnings
+
+import nonebot
from pydantic import BaseSettings
-import warnings
-import nonebot
class PlugConfig(BaseSettings):
bison_config_path: str = ""
bison_use_pic: bool = False
bison_use_local: bool = False
- bison_browser: str = ''
+ bison_browser: str = ""
bison_init_filter: bool = True
bison_use_queue: bool = True
- bison_outer_url: str = 'http://localhost:8080/bison/'
+ bison_outer_url: str = "http://localhost:8080/bison/"
bison_filter_log: bool = False
class Config:
- extra = 'ignore'
+ extra = "ignore"
+
global_config = nonebot.get_driver().config
plugin_config = PlugConfig(**global_config.dict())
if plugin_config.bison_use_local:
- warnings.warn('BISON_USE_LOCAL is deprecated, please use BISON_BROWSER')
+ warnings.warn("BISON_USE_LOCAL is deprecated, please use BISON_BROWSER")
diff --git a/src/plugins/nonebot_bison/post.py b/src/plugins/nonebot_bison/post.py
index e288dc4..5f7420b 100644
--- a/src/plugins/nonebot_bison/post.py
+++ b/src/plugins/nonebot_bison/post.py
@@ -1,16 +1,17 @@
import base64
-from dataclasses import dataclass, field
-from functools import reduce
from io import BytesIO
-from typing import Optional, Union
+from functools import reduce
+from typing import Union, Optional
+from dataclasses import field, dataclass
-from PIL import Image
import httpx
+from PIL import Image
from nonebot import logger
from nonebot.adapters.onebot.v11.message import Message, MessageSegment
-from .plugin_config import plugin_config
from .utils import parse_text
+from .plugin_config import plugin_config
+
@dataclass
class Post:
@@ -21,7 +22,9 @@ class Post:
target_name: Optional[str] = None
compress: bool = False
override_use_pic: Optional[bool] = None
- pics: Union[list[Union[str,bytes]], list[str], list[bytes]] = field(default_factory=list)
+ pics: Union[list[Union[str, bytes]], list[str], list[bytes]] = field(
+ default_factory=list
+ )
extra_msg: list[Message] = field(default_factory=list)
_message: Optional[list] = None
@@ -56,7 +59,7 @@ class Post:
cur_img = await self._pic_url_to_image(self.pics[i])
if not self._check_image_square(cur_img.size):
return
- if cur_img.size[1] != images[0].size[1]: # height not equal
+ if cur_img.size[1] != images[0].size[1]: # height not equal
return
images.append(cur_img)
_tmp = 0
@@ -65,6 +68,7 @@ class Post:
_tmp += images[i].size[0]
x_coord.append(_tmp)
y_coord = [0, first_image.size[1]]
+
async def process_row(row: int) -> bool:
if len(self.pics) < (row + 1) * 3:
return False
@@ -86,44 +90,48 @@ class Post:
images.extend(image_row)
y_coord.append(y_coord[-1] + row_first_img.size[1])
return True
+
if await process_row(1):
- matrix = (3,2)
+ matrix = (3, 2)
else:
- matrix = (3,1)
+ matrix = (3, 1)
if await process_row(2):
- matrix = (3,3)
- logger.info('trigger merge image')
- target = Image.new('RGB', (x_coord[-1], y_coord[-1]))
+ matrix = (3, 3)
+ logger.info("trigger merge image")
+ target = Image.new("RGB", (x_coord[-1], y_coord[-1]))
for y in range(matrix[1]):
for x in range(matrix[0]):
- target.paste(images[y * matrix[0] + x], (
- x_coord[x], y_coord[y], x_coord[x+1], y_coord[y+1]
- ))
+ target.paste(
+ images[y * matrix[0] + x],
+ (x_coord[x], y_coord[y], x_coord[x + 1], y_coord[y + 1]),
+ )
target_io = BytesIO()
- target.save(target_io, 'JPEG')
- self.pics = self.pics[matrix[0] * matrix[1]: ]
+ target.save(target_io, "JPEG")
+ self.pics = self.pics[matrix[0] * matrix[1] :]
self.pics.insert(0, target_io.getvalue())
async def generate_messages(self):
if self._message is None:
await self._pic_merge()
msgs = []
- text = ''
+ text = ""
if self.text:
if self._use_pic():
- text += '{}'.format(self.text)
+ text += "{}".format(self.text)
else:
- text += '{}'.format(self.text if len(self.text) < 500 else self.text[:500] + '...')
- text += '\n来源: {}'.format(self.target_type)
+ text += "{}".format(
+ self.text if len(self.text) < 500 else self.text[:500] + "..."
+ )
+ text += "\n来源: {}".format(self.target_type)
if self.target_name:
- text += ' {}'.format(self.target_name)
+ text += " {}".format(self.target_name)
if self._use_pic():
msgs.append(await parse_text(text))
- if not self.target_type == 'rss' and self.url:
+ if not self.target_type == "rss" and self.url:
msgs.append(MessageSegment.text(self.url))
else:
if self.url:
- text += ' \n详情: {}'.format(self.url)
+ text += " \n详情: {}".format(self.url)
msgs.append(MessageSegment.text(text))
for pic in self.pics:
# if isinstance(pic, bytes):
@@ -137,10 +145,17 @@ class Post:
return self._message
def __str__(self):
- return 'type: {}\nfrom: {}\ntext: {}\nurl: {}\npic: {}'.format(
- self.target_type,
- self.target_name,
- self.text if len(self.text) < 500 else self.text[:500] + '...',
- self.url,
- ', '.join(map(lambda x: 'b64img' if isinstance(x, bytes) or x.startswith('base64') else x, self.pics))
- )
+ return "type: {}\nfrom: {}\ntext: {}\nurl: {}\npic: {}".format(
+ self.target_type,
+ self.target_name,
+ self.text if len(self.text) < 500 else self.text[:500] + "...",
+ self.url,
+ ", ".join(
+ map(
+ lambda x: "b64img"
+ if isinstance(x, bytes) or x.startswith("base64")
+ else x,
+ self.pics,
+ )
+ ),
+ )
diff --git a/src/plugins/nonebot_bison/scheduler.py b/src/plugins/nonebot_bison/scheduler.py
index 949504c..956c51f 100644
--- a/src/plugins/nonebot_bison/scheduler.py
+++ b/src/plugins/nonebot_bison/scheduler.py
@@ -1,68 +1,91 @@
import asyncio
import logging
-from apscheduler.schedulers.asyncio import AsyncIOScheduler
-import logging
import nonebot
-from nonebot import get_driver, logger
from nonebot.log import LoguruHandler
+from nonebot import logger, get_driver
+from apscheduler.schedulers.asyncio import AsyncIOScheduler
from .config import Config
-from .platform import platform_manager
-from .send import do_send_msgs
-from .send import send_msgs
from .types import UserSubInfo
+from .platform import platform_manager
from .plugin_config import plugin_config
+from .send import send_msgs, do_send_msgs
scheduler = AsyncIOScheduler()
+
@get_driver().on_startup
async def _start():
scheduler.configure({"apscheduler.timezone": "Asia/Shanghai"})
scheduler.start()
+
# get_driver().on_startup(_start)
+
async def fetch_and_send(target_type: str):
config = Config()
target = config.get_next_target(target_type)
if not target:
return
- logger.debug('try to fecth new posts from {}, target: {}'.format(target_type, target))
+ logger.debug(
+ "try to fecth new posts from {}, target: {}".format(target_type, target)
+ )
send_user_list = config.target_user_cache[target_type][target]
- send_userinfo_list = list(map(
- lambda user: UserSubInfo(
- user,
- lambda target: config.get_sub_category(target_type, target, user.user_type, user.user),
- lambda target: config.get_sub_tags(target_type, target, user.user_type, user.user)
- ), send_user_list))
+ send_userinfo_list = list(
+ map(
+ lambda user: UserSubInfo(
+ user,
+ lambda target: config.get_sub_category(
+ target_type, target, user.user_type, user.user
+ ),
+ lambda target: config.get_sub_tags(
+ target_type, target, user.user_type, user.user
+ ),
+ ),
+ send_user_list,
+ )
+ )
bot_list = list(nonebot.get_bots().values())
bot = bot_list[0] if bot_list else None
- to_send = await platform_manager[target_type].fetch_new_post(target, send_userinfo_list)
+ to_send = await platform_manager[target_type].fetch_new_post(
+ target, send_userinfo_list
+ )
for user, send_list in to_send:
for send_post in send_list:
- logger.info('send to {}: {}'.format(user, send_post))
+ logger.info("send to {}: {}".format(user, send_post))
if not bot:
- logger.warning('no bot connected')
+ logger.warning("no bot connected")
else:
- await send_msgs(bot, user.user, user.user_type, await send_post.generate_messages())
+ await send_msgs(
+ bot, user.user, user.user_type, await send_post.generate_messages()
+ )
+
for platform_name, platform in platform_manager.items():
- if platform.schedule_type in ['cron', 'interval', 'date']:
- logger.info(f'start scheduler for {platform_name} with {platform.schedule_type} {platform.schedule_kw}')
+ if platform.schedule_type in ["cron", "interval", "date"]:
+ logger.info(
+ f"start scheduler for {platform_name} with {platform.schedule_type} {platform.schedule_kw}"
+ )
scheduler.add_job(
- fetch_and_send, platform.schedule_type, **platform.schedule_kw,
- args=(platform_name,))
+ fetch_and_send,
+ platform.schedule_type,
+ **platform.schedule_kw,
+ args=(platform_name,),
+ )
+
class CustomLogHandler(LoguruHandler):
-
def filter(self, record: logging.LogRecord):
- return record.msg != ('Execution of job "%s" '
- 'skipped: maximum number of running instances reached (%d)')
+ return record.msg != (
+ 'Execution of job "%s" '
+ "skipped: maximum number of running instances reached (%d)"
+ )
if plugin_config.bison_use_queue:
- scheduler.add_job(do_send_msgs, 'interval', seconds=0.3, coalesce=True)
+ scheduler.add_job(do_send_msgs, "interval", seconds=0.3, coalesce=True)
aps_logger = logging.getLogger("apscheduler")
aps_logger.setLevel(30)
diff --git a/src/plugins/nonebot_bison/send.py b/src/plugins/nonebot_bison/send.py
index 341fdd0..413fcf4 100644
--- a/src/plugins/nonebot_bison/send.py
+++ b/src/plugins/nonebot_bison/send.py
@@ -8,11 +8,13 @@ from .plugin_config import plugin_config
QUEUE = []
LAST_SEND_TIME = time.time()
-async def _do_send(bot: 'Bot', user: str, user_type: str, msg):
- if user_type == 'group':
- await bot.call_api('send_group_msg', group_id=user, message=msg)
- elif user_type == 'private':
- await bot.call_api('send_private_msg', user_id=user, message=msg)
+
+async def _do_send(bot: "Bot", user: str, user_type: str, msg):
+ if user_type == "group":
+ await bot.call_api("send_group_msg", group_id=user, message=msg)
+ elif user_type == "private":
+ await bot.call_api("send_private_msg", user_id=user, message=msg)
+
async def do_send_msgs():
global LAST_SEND_TIME
@@ -28,10 +30,11 @@ async def do_send_msgs():
else:
msg_str = str(msg)
if len(msg_str) > 50:
- msg_str = msg_str[:50] + '...'
- logger.warning(f'send msg err {e} {msg_str}')
+ msg_str = msg_str[:50] + "..."
+ logger.warning(f"send msg err {e} {msg_str}")
LAST_SEND_TIME = time.time()
+
async def send_msgs(bot, user, user_type, msgs):
if plugin_config.bison_use_queue:
for msg in msgs:
@@ -39,5 +42,3 @@ async def send_msgs(bot, user, user_type, msgs):
else:
for msg in msgs:
await _do_send(bot, user, user_type, msg)
-
-
diff --git a/src/plugins/nonebot_bison/types.py b/src/plugins/nonebot_bison/types.py
index 089b2b0..56a0fdf 100644
--- a/src/plugins/nonebot_bison/types.py
+++ b/src/plugins/nonebot_bison/types.py
@@ -1,16 +1,18 @@
-from typing import Any, Callable, NamedTuple, NewType
from dataclasses import dataclass
+from typing import Any, NewType, Callable, NamedTuple
+
+RawPost = NewType("RawPost", Any)
+Target = NewType("Target", str)
+Category = NewType("Category", int)
+Tag = NewType("Tag", str)
-RawPost = NewType('RawPost', Any)
-Target = NewType('Target', str)
-Category = NewType('Category', int)
-Tag = NewType('Tag', str)
@dataclass(eq=True, frozen=True)
class User:
user: str
user_type: str
+
class UserSubInfo(NamedTuple):
user: User
category_getter: Callable[[Target], list[Category]]
diff --git a/src/plugins/nonebot_bison/utils.py b/src/plugins/nonebot_bison/utils.py
index 2af8ebd..4035ac3 100644
--- a/src/plugins/nonebot_bison/utils.py
+++ b/src/plugins/nonebot_bison/utils.py
@@ -1,30 +1,32 @@
-import asyncio
-import base64
-from html import escape
import os
import re
-import subprocess
import sys
+import base64
+import asyncio
+import subprocess
+from html import escape
from time import asctime
-from typing import Awaitable, Callable, Optional, Union
+from typing import Union, Callable, Optional, Awaitable
-from bs4 import BeautifulSoup as bs
import nonebot
+from bs4 import BeautifulSoup as bs
+from nonebot.log import logger, default_format
from nonebot.adapters.onebot.v11.message import MessageSegment
-from nonebot.log import logger
-from nonebot.log import default_format
from playwright._impl._driver import compute_driver_executable
-from playwright.async_api import Browser, Page, Playwright, async_playwright
+from playwright.async_api import Page, Browser, Playwright, async_playwright
from .plugin_config import plugin_config
+
class Singleton(type):
_instances = {}
+
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
+
@nonebot.get_driver().on_startup
def download_browser():
if not plugin_config.bison_browser and not plugin_config.bison_use_local:
@@ -35,65 +37,84 @@ def download_browser():
class Render(metaclass=Singleton):
-
def __init__(self):
self.lock = asyncio.Lock()
self.browser: Browser
- self.interval_log = ''
+ self.interval_log = ""
self.remote_browser = False
async def get_browser(self, playwright: Playwright) -> Browser:
if plugin_config.bison_browser:
- if plugin_config.bison_browser.startswith('local:'):
- path = plugin_config.bison_browser.split('local:', 1)[1]
+ if plugin_config.bison_browser.startswith("local:"):
+ path = plugin_config.bison_browser.split("local:", 1)[1]
return await playwright.chromium.launch(
- executable_path=path, args=['--no-sandbox'])
- if plugin_config.bison_browser.startswith('ws:'):
+ executable_path=path, args=["--no-sandbox"]
+ )
+ if plugin_config.bison_browser.startswith("ws:"):
self.remote_browser = True
return await playwright.chromium.connect(plugin_config.bison_browser)
- if plugin_config.bison_browser.startswith('wsc:'):
+ if plugin_config.bison_browser.startswith("wsc:"):
self.remote_browser = True
return await playwright.chromium.connect_over_cdp(
- 'ws:' + plugin_config.bison_browser[4:]
- )
- raise RuntimeError('bison_BROWSER error')
+ "ws:" + plugin_config.bison_browser[4:]
+ )
+ raise RuntimeError("bison_BROWSER error")
if plugin_config.bison_use_local:
return await playwright.chromium.launch(
- executable_path='/usr/bin/chromium', args=['--no-sandbox'])
- return await playwright.chromium.launch(args=['--no-sandbox'])
+ executable_path="/usr/bin/chromium", args=["--no-sandbox"]
+ )
+ return await playwright.chromium.launch(args=["--no-sandbox"])
async def close_browser(self):
if not self.remote_browser:
await self.browser.close()
- async def render(self, url: str, viewport: Optional[dict] = None, target: Optional[str] = None,
- operation: Optional[Callable[[Page], Awaitable[None]]] = None) -> Optional[bytes]:
+ async def render(
+ self,
+ url: str,
+ viewport: Optional[dict] = None,
+ target: Optional[str] = None,
+ operation: Optional[Callable[[Page], Awaitable[None]]] = None,
+ ) -> Optional[bytes]:
retry_times = 0
- self.interval_log = ''
+ self.interval_log = ""
while retry_times < 3:
try:
- return await asyncio.wait_for(self.do_render(url, viewport, target, operation), 20)
+ return await asyncio.wait_for(
+ self.do_render(url, viewport, target, operation), 20
+ )
except asyncio.TimeoutError:
retry_times += 1
- logger.warning("render error {}\n".format(retry_times) + self.interval_log)
- self.interval_log = ''
+ logger.warning(
+ "render error {}\n".format(retry_times) + self.interval_log
+ )
+ self.interval_log = ""
# if self.browser:
# await self.browser.close()
# self.lock.release()
def _inter_log(self, message: str) -> None:
- self.interval_log += asctime() + '' + message + '\n'
+ self.interval_log += asctime() + "" + message + "\n"
- async def do_render(self, url: str, viewport: Optional[dict] = None, target: Optional[str] = None,
- operation: Optional[Callable[[Page], Awaitable[None]]] = None) -> Optional[bytes]:
+ async def do_render(
+ self,
+ url: str,
+ viewport: Optional[dict] = None,
+ target: Optional[str] = None,
+ operation: Optional[Callable[[Page], Awaitable[None]]] = None,
+ ) -> Optional[bytes]:
async with self.lock:
async with async_playwright() as playwright:
self.browser = await self.get_browser(playwright)
- self._inter_log('open browser')
+ self._inter_log("open browser")
if viewport:
constext = await self.browser.new_context(
- viewport={'width': viewport['width'], 'height': viewport['height']},
- device_scale_factor=viewport.get('deviceScaleFactor', 1))
+ viewport={
+ "width": viewport["width"],
+ "height": viewport["height"],
+ },
+ device_scale_factor=viewport.get("deviceScaleFactor", 1),
+ )
page = await constext.new_page()
else:
page = await self.browser.new_page()
@@ -101,51 +122,57 @@ class Render(metaclass=Singleton):
await operation(page)
else:
await page.goto(url)
- self._inter_log('open page')
+ self._inter_log("open page")
if target:
target_ele = page.locator(target)
if not target_ele:
return None
- data = await target_ele.screenshot(type='jpeg')
+ data = await target_ele.screenshot(type="jpeg")
else:
- data = await page.screenshot(type='jpeg')
- self._inter_log('screenshot')
+ data = await page.screenshot(type="jpeg")
+ self._inter_log("screenshot")
await page.close()
- self._inter_log('close page')
+ self._inter_log("close page")
await self.close_browser()
- self._inter_log('close browser')
- assert(isinstance(data, bytes))
+ self._inter_log("close browser")
+ assert isinstance(data, bytes)
return data
async def text_to_pic(self, text: str) -> Optional[bytes]:
- lines = text.split('\n')
- parsed_lines = list(map(lambda x: '{}
'.format(escape(x)), lines))
- html_text = '{}
'.format(''.join(parsed_lines))
- url = 'data:text/html;charset=UTF-8;base64,{}'.format(base64.b64encode(html_text.encode()).decode())
- data = await self.render(url, target='div')
+ lines = text.split("\n")
+ parsed_lines = list(map(lambda x: "{}
".format(escape(x)), lines))
+ html_text = '{}
'.format(
+ "".join(parsed_lines)
+ )
+ url = "data:text/html;charset=UTF-8;base64,{}".format(
+ base64.b64encode(html_text.encode()).decode()
+ )
+ data = await self.render(url, target="div")
return data
- async def text_to_pic_cqcode(self, text:str) -> MessageSegment:
+ async def text_to_pic_cqcode(self, text: str) -> MessageSegment:
data = await self.text_to_pic(text)
# logger.debug('file size: {}'.format(len(data)))
if data:
# logger.debug(code)
return MessageSegment.image(data)
else:
- return MessageSegment.text('生成图片错误')
+ return MessageSegment.text("生成图片错误")
+
async def parse_text(text: str) -> MessageSegment:
- 'return raw text if don\'t use pic, otherwise return rendered opcode'
+ "return raw text if don't use pic, otherwise return rendered opcode"
if plugin_config.bison_use_pic:
render = Render()
return await render.text_to_pic_cqcode(text)
else:
return MessageSegment.text(text)
+
def html_to_text(html: str, query_dict: dict = {}) -> str:
- html = re.sub(r'
', '
\n', html)
- html = html.replace('', '\n')
- soup = bs(html, 'html.parser')
+ html = re.sub(r"
", "
\n", html)
+ html = html.replace("", "\n")
+ soup = bs(html, "html.parser")
if query_dict:
node = soup.find(**query_dict)
else:
@@ -155,7 +182,6 @@ def html_to_text(html: str, query_dict: dict = {}) -> str:
class Filter:
-
def __init__(self) -> None:
self.level: Union[int, str] = "DEBUG"
@@ -165,22 +191,31 @@ class Filter:
if module:
module_name = getattr(module, "__module_name__", module_name)
record["name"] = module_name.split(".")[0]
- levelno = logger.level(self.level).no if isinstance(self.level,
- str) else self.level
+ levelno = (
+ logger.level(self.level).no if isinstance(self.level, str) else self.level
+ )
nonebot_warning_level = logger.level("WARNING").no
- return record["level"].no >= levelno if record["name"] != "nonebot" \
- else record["level"].no >= nonebot_warning_level
+ return (
+ record["level"].no >= levelno
+ if record["name"] != "nonebot"
+ else record["level"].no >= nonebot_warning_level
+ )
+
if plugin_config.bison_filter_log:
logger.remove()
default_filter = Filter()
- logger.add(sys.stdout,
- colorize=True,
- diagnose=False,
- filter=default_filter,
- format=default_format)
+ logger.add(
+ sys.stdout,
+ colorize=True,
+ diagnose=False,
+ filter=default_filter,
+ format=default_format,
+ )
config = nonebot.get_driver().config
- logger.success("Muted info & success from nonebot")
+ logger.success("Muted info & success from nonebot")
default_filter.level = (
- "DEBUG" if config.debug else
- "INFO") if config.log_level is None else config.log_level
+ ("DEBUG" if config.debug else "INFO")
+ if config.log_level is None
+ else config.log_level
+ )
diff --git a/tests/conftest.py b/tests/conftest.py
index 8355941..06a7148 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,33 +1,36 @@
+import typing
+
import pytest
import nonebot
-import typing
from nonebug.fixture import nonebug_init, nonebug_clear
if typing.TYPE_CHECKING:
import sys
- sys.path.append('./src/plugins')
+
+ sys.path.append("./src/plugins")
import nonebot_bison
-@pytest.fixture#(scope="module")
+
+@pytest.fixture # (scope="module")
def plugin_module(tmpdir):
nonebot.init(bison_config_path=str(tmpdir))
- nonebot.load_plugins('src/plugins')
+ nonebot.load_plugins("src/plugins")
plugins = nonebot.get_loaded_plugins()
- plugin = list(filter(lambda x: x.name == 'nonebot_bison', plugins))[0]
+ plugin = list(filter(lambda x: x.name == "nonebot_bison", plugins))[0]
return plugin.module
+
@pytest.fixture
-def dummy_user_subinfo(plugin_module: 'nonebot_bison'):
- user = plugin_module.types.User('123', 'group')
+def dummy_user_subinfo(plugin_module: "nonebot_bison"):
+ user = plugin_module.types.User("123", "group")
return plugin_module.types.UserSubInfo(
- user=user,
- category_getter=lambda _: [],
- tag_getter=lambda _: []
- )
+ user=user, category_getter=lambda _: [], tag_getter=lambda _: []
+ )
+
@pytest.fixture
def n_plugin_module(tmpdir):
nonebot.init(bison_config_path=str(tmpdir))
- nonebot.load_plugins('src/plugins')
+ nonebot.load_plugins("src/plugins")
yield None
nonebug_clear()
diff --git a/tests/platforms/test_arknights.py b/tests/platforms/test_arknights.py
index 1058019..d5a336f 100644
--- a/tests/platforms/test_arknights.py
+++ b/tests/platforms/test_arknights.py
@@ -1,64 +1,93 @@
-import pytest
import typing
+
import respx
+import pytest
from httpx import Response
if typing.TYPE_CHECKING:
import sys
- sys.path.append('./src/plugins')
+
+ sys.path.append("./src/plugins")
import nonebot_bison
-from .utils import get_json, get_file
+from .utils import get_file, get_json
+
@pytest.fixture
-def arknights(plugin_module: 'nonebot_bison'):
- return plugin_module.platform.platform_manager['arknights']
+def arknights(plugin_module: "nonebot_bison"):
+ return plugin_module.platform.platform_manager["arknights"]
-@pytest.fixture(scope='module')
+
+@pytest.fixture(scope="module")
def arknights_list_0():
- return get_json('arknights_list_0.json')
+ return get_json("arknights_list_0.json")
-@pytest.fixture(scope='module')
+
+@pytest.fixture(scope="module")
def arknights_list_1():
- return get_json('arknights_list_1.json')
+ return get_json("arknights_list_1.json")
-@pytest.fixture(scope='module')
+
+@pytest.fixture(scope="module")
def monster_siren_list_0():
- return get_json('monster-siren_list_0.json')
+ return get_json("monster-siren_list_0.json")
-@pytest.fixture(scope='module')
+
+@pytest.fixture(scope="module")
def monster_siren_list_1():
- return get_json('monster-siren_list_1.json')
+ return get_json("monster-siren_list_1.json")
+
@pytest.mark.asyncio
@respx.mock
-async def test_fetch_new(arknights, dummy_user_subinfo, arknights_list_0, arknights_list_1, monster_siren_list_0, monster_siren_list_1):
- ak_list_router = respx.get("https://ak-conf.hypergryph.com/config/prod/announce_meta/IOS/announcement.meta.json")
- detail_router = respx.get("https://ak.hycdn.cn/announce/IOS/announcement/805_1640074952.html")
- version_router = respx.get('https://ak-conf.hypergryph.com/config/prod/official/IOS/version')
- preannouncement_router = respx.get('https://ak-conf.hypergryph.com/config/prod/announce_meta/IOS/preannouncement.meta.json')
+async def test_fetch_new(
+ arknights,
+ dummy_user_subinfo,
+ arknights_list_0,
+ arknights_list_1,
+ monster_siren_list_0,
+ monster_siren_list_1,
+):
+ ak_list_router = respx.get(
+ "https://ak-conf.hypergryph.com/config/prod/announce_meta/IOS/announcement.meta.json"
+ )
+ detail_router = respx.get(
+ "https://ak.hycdn.cn/announce/IOS/announcement/805_1640074952.html"
+ )
+ version_router = respx.get(
+ "https://ak-conf.hypergryph.com/config/prod/official/IOS/version"
+ )
+ preannouncement_router = respx.get(
+ "https://ak-conf.hypergryph.com/config/prod/announce_meta/IOS/preannouncement.meta.json"
+ )
monster_siren_router = respx.get("https://monster-siren.hypergryph.com/api/news")
ak_list_router.mock(return_value=Response(200, json=arknights_list_0))
- detail_router.mock(return_value=Response(200, text=get_file('arknights-detail-805')))
- version_router.mock(return_value=Response(200, json=get_json('arknights-version-0.json')))
- preannouncement_router.mock(return_value=Response(200, json=get_json('arknights-pre-0.json')))
+ detail_router.mock(
+ return_value=Response(200, text=get_file("arknights-detail-805"))
+ )
+ version_router.mock(
+ return_value=Response(200, json=get_json("arknights-version-0.json"))
+ )
+ preannouncement_router.mock(
+ return_value=Response(200, json=get_json("arknights-pre-0.json"))
+ )
monster_siren_router.mock(return_value=Response(200, json=monster_siren_list_0))
- target = ''
+ target = ""
res = await arknights.fetch_new_post(target, [dummy_user_subinfo])
- assert(ak_list_router.called)
- assert(len(res) == 0)
- assert(not detail_router.called)
+ assert ak_list_router.called
+ assert len(res) == 0
+ assert not detail_router.called
mock_data = arknights_list_1
ak_list_router.mock(return_value=Response(200, json=mock_data))
res3 = await arknights.fetch_new_post(target, [dummy_user_subinfo])
- assert(len(res3[0][1]) == 1)
- assert(detail_router.called)
+ assert len(res3[0][1]) == 1
+ assert detail_router.called
post = res3[0][1][0]
- assert(post.target_type == 'arknights')
- assert(post.text == '')
- assert(post.url == '')
- assert(post.target_name == '明日方舟游戏内公告')
- assert(len(post.pics) == 1)
+ assert post.target_type == "arknights"
+ assert post.text == ""
+ assert post.url == ""
+ assert post.target_name == "明日方舟游戏内公告"
+ assert len(post.pics) == 1
# assert(post.pics == ['https://ak-fs.hypergryph.com/announce/images/20210623/e6f49aeb9547a2278678368a43b95b07.jpg'])
print(res3[0][1])
r = await post.generate_messages()
diff --git a/tests/platforms/test_bilibili.py b/tests/platforms/test_bilibili.py
index d04c604..4b99c83 100644
--- a/tests/platforms/test_bilibili.py
+++ b/tests/platforms/test_bilibili.py
@@ -1,37 +1,53 @@
-import pytest
import typing
+
+import pytest
from httpx import Response
if typing.TYPE_CHECKING:
import sys
- sys.path.append('./src/plugins')
+
+ sys.path.append("./src/plugins")
import nonebot_bison
from .utils import get_json
-@pytest.fixture(scope='module')
+
+@pytest.fixture(scope="module")
def bing_dy_list():
- return get_json('bilibili_bing_list.json')['data']['cards']
+ return get_json("bilibili_bing_list.json")["data"]["cards"]
+
@pytest.fixture
-def bilibili(plugin_module: 'nonebot_bison'):
- return plugin_module.platform.platform_manager['bilibili']
+def bilibili(plugin_module: "nonebot_bison"):
+ return plugin_module.platform.platform_manager["bilibili"]
+
@pytest.mark.asyncio
async def test_video_forward(bilibili, bing_dy_list):
- post = await bilibili.parse(bing_dy_list[1])
- assert(post.text == '答案揭晓:宿舍!来看看投票结果\nhttps://t.bilibili.com/568093580488553786\n--------------\n#可露希尔的秘密档案# \n11:来宿舍休息一下吧 \n档案来源:lambda:\\罗德岛内务\\秘密档案 \n发布时间:9/12 1:00 P.M. \n档案类型:可见 \n档案描述:今天请了病假在宿舍休息。很舒适。 \n提供者:赫默')
+ post = await bilibili.parse(bing_dy_list[1])
+ assert (
+ post.text
+ == "答案揭晓:宿舍!来看看投票结果\nhttps://t.bilibili.com/568093580488553786\n--------------\n#可露希尔的秘密档案# \n11:来宿舍休息一下吧 \n档案来源:lambda:\\罗德岛内务\\秘密档案 \n发布时间:9/12 1:00 P.M. \n档案类型:可见 \n档案描述:今天请了病假在宿舍休息。很舒适。 \n提供者:赫默"
+ )
+
@pytest.mark.asyncio
async def test_article_forward(bilibili, bing_dy_list):
- post = await bilibili.parse(bing_dy_list[4])
- assert(post.text == '#明日方舟##饼学大厦#\n9.11专栏更新完毕,这还塌了实属没跟新运营对上\n后边除了周日发饼和PV没提及的中文语音,稳了\n别忘了来参加#可露希尔的秘密档案#的主题投票\nhttps://t.bilibili.com/568093580488553786?tab=2' +
- '\n--------------\n' +
- '【明日方舟】饼学大厦#12~14(风暴瞭望&玛莉娅·临光&红松林&感谢庆典)9.11更新 更新记录09.11更新:覆盖09.10更新;以及排期更新,猜测周一周五开活动09.10更新:以周五开活动为底,PV/公告调整位置,整体结构更新09.08更新:饼学大厦#12更新,新增一件六星商店服饰(周日发饼)09.06更新:饼学大厦整栋整栋翻新,改为9.16开主线(四日无饼!)09.05凌晨更新:10.13后的排期(两日无饼,鹰角背刺,心狠手辣)前言感谢楪筱祈ぺ的动态-哔哩哔哩 (bilibili.com) 对饼学的贡献!后续排期:9.17【风暴瞭望】、10.01【玛莉娅·临光】复刻、10.1')
+ post = await bilibili.parse(bing_dy_list[4])
+ assert (
+ post.text
+ == "#明日方舟##饼学大厦#\n9.11专栏更新完毕,这还塌了实属没跟新运营对上\n后边除了周日发饼和PV没提及的中文语音,稳了\n别忘了来参加#可露希尔的秘密档案#的主题投票\nhttps://t.bilibili.com/568093580488553786?tab=2"
+ + "\n--------------\n"
+ + "【明日方舟】饼学大厦#12~14(风暴瞭望&玛莉娅·临光&红松林&感谢庆典)9.11更新 更新记录09.11更新:覆盖09.10更新;以及排期更新,猜测周一周五开活动09.10更新:以周五开活动为底,PV/公告调整位置,整体结构更新09.08更新:饼学大厦#12更新,新增一件六星商店服饰(周日发饼)09.06更新:饼学大厦整栋整栋翻新,改为9.16开主线(四日无饼!)09.05凌晨更新:10.13后的排期(两日无饼,鹰角背刺,心狠手辣)前言感谢楪筱祈ぺ的动态-哔哩哔哩 (bilibili.com) 对饼学的贡献!后续排期:9.17【风暴瞭望】、10.01【玛莉娅·临光】复刻、10.1"
+ )
+
@pytest.mark.asyncio
async def test_dynamic_forward(bilibili, bing_dy_list):
post = await bilibili.parse(bing_dy_list[5])
- assert(post.text == '饼组主线饼学预测——9.11版\n①今日结果\n9.11 殿堂上的游禽-星极(x,新运营实锤了)\n②后续预测\n9.12 #罗德岛相簿#+#可露希尔的秘密档案#11话\n9.13 六星先锋(执旗手)干员-琴柳\n9.14 宣传策略-空弦+家具\n9.15 轮换池(+中文语音前瞻)\n9.16 停机\n9.17 #罗德岛闲逛部#+新六星EP+EP09·风暴瞭望开启\n9.19 #罗德岛相簿#' +
- '\n--------------\n' +
- '#明日方舟#\n【新增服饰】\n//殿堂上的游禽 - 星极\n塞壬唱片偶像企划《闪耀阶梯》特供服饰/殿堂上的游禽。星极自费参加了这项企划,尝试着用大众能接受的方式演绎天空之上的故事。\n\n_____________\n谦逊留给观众,骄傲发自歌喉,此夜,唯我璀璨。 ')
+ assert (
+ post.text
+ == "饼组主线饼学预测——9.11版\n①今日结果\n9.11 殿堂上的游禽-星极(x,新运营实锤了)\n②后续预测\n9.12 #罗德岛相簿#+#可露希尔的秘密档案#11话\n9.13 六星先锋(执旗手)干员-琴柳\n9.14 宣传策略-空弦+家具\n9.15 轮换池(+中文语音前瞻)\n9.16 停机\n9.17 #罗德岛闲逛部#+新六星EP+EP09·风暴瞭望开启\n9.19 #罗德岛相簿#"
+ + "\n--------------\n"
+ + "#明日方舟#\n【新增服饰】\n//殿堂上的游禽 - 星极\n塞壬唱片偶像企划《闪耀阶梯》特供服饰/殿堂上的游禽。星极自费参加了这项企划,尝试着用大众能接受的方式演绎天空之上的故事。\n\n_____________\n谦逊留给观众,骄傲发自歌喉,此夜,唯我璀璨。 "
+ )
diff --git a/tests/platforms/test_ncm_artist.py b/tests/platforms/test_ncm_artist.py
index f5ed1a2..96e272f 100644
--- a/tests/platforms/test_ncm_artist.py
+++ b/tests/platforms/test_ncm_artist.py
@@ -1,49 +1,54 @@
-from .utils import get_json
-import pytest
-import respx
-import typing
import time
+import typing
+
+import respx
+import pytest
from httpx import Response
+from .utils import get_json
+
if typing.TYPE_CHECKING:
import sys
- sys.path.append('./src/plugins')
+
+ sys.path.append("./src/plugins")
import nonebot_bison
+
@pytest.fixture
-def ncm_artist(plugin_module: 'nonebot_bison'):
- return plugin_module.platform.platform_manager['ncm-artist']
+def ncm_artist(plugin_module: "nonebot_bison"):
+ return plugin_module.platform.platform_manager["ncm-artist"]
-@pytest.fixture(scope='module')
+
+@pytest.fixture(scope="module")
def ncm_artist_raw():
- return get_json('ncm_siren.json')
+ return get_json("ncm_siren.json")
-@pytest.fixture(scope='module')
+
+@pytest.fixture(scope="module")
def ncm_artist_0(ncm_artist_raw):
- return {
- **ncm_artist_raw,
- 'hotAlbums': ncm_artist_raw['hotAlbums'][1:]
- }
+ return {**ncm_artist_raw, "hotAlbums": ncm_artist_raw["hotAlbums"][1:]}
-@pytest.fixture(scope='module')
+
+@pytest.fixture(scope="module")
def ncm_artist_1(ncm_artist_raw: dict):
res = ncm_artist_raw.copy()
- res['hotAlbums'] = ncm_artist_raw['hotAlbums'][:]
- res['hotAlbums'][0]['publishTime'] = int(time.time() * 1000)
- return res
+ res["hotAlbums"] = ncm_artist_raw["hotAlbums"][:]
+ res["hotAlbums"][0]["publishTime"] = int(time.time() * 1000)
+ return res
+
@pytest.mark.asyncio
@respx.mock
async def test_fetch_new(ncm_artist, ncm_artist_0, ncm_artist_1, dummy_user_subinfo):
- ncm_router = respx.get("https://music.163.com/api/artist/albums/32540734")
+ ncm_router = respx.get("https://music.163.com/api/artist/albums/32540734")
ncm_router.mock(return_value=Response(200, json=ncm_artist_0))
- target = '32540734'
+ target = "32540734"
res = await ncm_artist.fetch_new_post(target, [dummy_user_subinfo])
- assert(ncm_router.called)
- assert(len(res) == 0)
+ assert ncm_router.called
+ assert len(res) == 0
ncm_router.mock(return_value=Response(200, json=ncm_artist_1))
res2 = await ncm_artist.fetch_new_post(target, [dummy_user_subinfo])
post = res2[0][1][0]
- assert(post.target_type == 'ncm-artist')
- assert(post.text == '新专辑发布:Y1K')
- assert(post.url == 'https://music.163.com/#/album?id=131074504')
+ assert post.target_type == "ncm-artist"
+ assert post.text == "新专辑发布:Y1K"
+ assert post.url == "https://music.163.com/#/album?id=131074504"
diff --git a/tests/platforms/test_ncm_radio.py b/tests/platforms/test_ncm_radio.py
index d248b0f..f6e3d86 100644
--- a/tests/platforms/test_ncm_radio.py
+++ b/tests/platforms/test_ncm_radio.py
@@ -1,53 +1,59 @@
+import time
+import typing
+
+import respx
+import pytest
+from httpx import Response
from .utils import get_json
-import pytest
-import respx
-import typing
-import time
-from httpx import Response
if typing.TYPE_CHECKING:
import sys
- sys.path.append('./src/plugins')
+
+ sys.path.append("./src/plugins")
import nonebot_bison
+
@pytest.fixture
-def ncm_radio(plugin_module: 'nonebot_bison'):
- return plugin_module.platform.platform_manager['ncm-radio']
+def ncm_radio(plugin_module: "nonebot_bison"):
+ return plugin_module.platform.platform_manager["ncm-radio"]
-@pytest.fixture(scope='module')
+
+@pytest.fixture(scope="module")
def ncm_radio_raw():
- return get_json('ncm_radio_ark.json')
+ return get_json("ncm_radio_ark.json")
-@pytest.fixture(scope='module')
+
+@pytest.fixture(scope="module")
def ncm_radio_0(ncm_radio_raw):
- return {
- **ncm_radio_raw,
- 'programs': ncm_radio_raw['programs'][1:]
- }
+ return {**ncm_radio_raw, "programs": ncm_radio_raw["programs"][1:]}
-@pytest.fixture(scope='module')
+
+@pytest.fixture(scope="module")
def ncm_radio_1(ncm_radio_raw: dict):
res = ncm_radio_raw.copy()
- res['programs'] = ncm_radio_raw['programs'][:]
- res['programs'][0]['createTime'] = int(time.time() * 1000)
- return res
+ res["programs"] = ncm_radio_raw["programs"][:]
+ res["programs"][0]["createTime"] = int(time.time() * 1000)
+ return res
+
@pytest.mark.asyncio
@respx.mock
async def test_fetch_new(ncm_radio, ncm_radio_0, ncm_radio_1, dummy_user_subinfo):
- ncm_router = respx.post("http://music.163.com/api/dj/program/byradio")
+ ncm_router = respx.post("http://music.163.com/api/dj/program/byradio")
ncm_router.mock(return_value=Response(200, json=ncm_radio_0))
- target = '793745436'
+ target = "793745436"
res = await ncm_radio.fetch_new_post(target, [dummy_user_subinfo])
- assert(ncm_router.called)
- assert(len(res) == 0)
+ assert ncm_router.called
+ assert len(res) == 0
ncm_router.mock(return_value=Response(200, json=ncm_radio_1))
res2 = await ncm_radio.fetch_new_post(target, [dummy_user_subinfo])
post = res2[0][1][0]
print(post)
- assert(post.target_type == 'ncm-radio')
- assert(post.text == '网易云电台更新:「松烟行动」灰齐山麓')
- assert(post.url == 'https://music.163.com/#/program/2494997688')
- assert(post.pics == ['http://p1.music.126.net/H5em5xUNIYXcjJhOmeaSqQ==/109951166647436789.jpg'])
- assert(post.target_name == '《明日方舟》游戏原声OST')
+ assert post.target_type == "ncm-radio"
+ assert post.text == "网易云电台更新:「松烟行动」灰齐山麓"
+ assert post.url == "https://music.163.com/#/program/2494997688"
+ assert post.pics == [
+ "http://p1.music.126.net/H5em5xUNIYXcjJhOmeaSqQ==/109951166647436789.jpg"
+ ]
+ assert post.target_name == "《明日方舟》游戏原声OST"
diff --git a/tests/platforms/test_platform.py b/tests/platforms/test_platform.py
index f708863..ae55b38 100644
--- a/tests/platforms/test_platform.py
+++ b/tests/platforms/test_platform.py
@@ -6,42 +6,48 @@ import pytest
if typing.TYPE_CHECKING:
import sys
- sys.path.append('./src/plugins')
+
+ sys.path.append("./src/plugins")
import nonebot_bison
from nonebot_bison.types import *
from nonebot_bison.post import Post
from time import time
+
now = time()
passed = now - 3 * 60 * 60
raw_post_list_1 = [
- {'id': 1, 'text': 'p1', 'date': now, 'tags': ['tag1'], 'category': 1}
- ]
+ {"id": 1, "text": "p1", "date": now, "tags": ["tag1"], "category": 1}
+]
raw_post_list_2 = raw_post_list_1 + [
- {'id': 2, 'text': 'p2', 'date': now, 'tags': ['tag1'], 'category': 1},
- {'id': 3, 'text': 'p3', 'date': now, 'tags': ['tag2'], 'category': 2},
- {'id': 4, 'text': 'p4', 'date': now, 'tags': ['tag2'], 'category': 3}
- ]
+ {"id": 2, "text": "p2", "date": now, "tags": ["tag1"], "category": 1},
+ {"id": 3, "text": "p3", "date": now, "tags": ["tag2"], "category": 2},
+ {"id": 4, "text": "p4", "date": now, "tags": ["tag2"], "category": 3},
+]
+
@pytest.fixture
-def dummy_user(plugin_module: 'nonebot_bison'):
- user = plugin_module.types.User('123', 'group')
+def dummy_user(plugin_module: "nonebot_bison"):
+ user = plugin_module.types.User("123", "group")
return user
+
@pytest.fixture
-def user_info_factory(plugin_module: 'nonebot_bison', dummy_user):
+def user_info_factory(plugin_module: "nonebot_bison", dummy_user):
def _user_info(category_getter, tag_getter):
return plugin_module.types.UserSubInfo(dummy_user, category_getter, tag_getter)
+
return _user_info
+
@pytest.fixture
-def mock_platform_without_cats_tags(plugin_module: 'nonebot_bison'):
+def mock_platform_without_cats_tags(plugin_module: "nonebot_bison"):
class MockPlatform(plugin_module.platform.platform.NewMessage):
- platform_name = 'mock_platform'
- name = 'Mock Platform'
+ platform_name = "mock_platform"
+ name = "Mock Platform"
enabled = True
is_common = True
schedule_interval = 10
@@ -52,21 +58,26 @@ def mock_platform_without_cats_tags(plugin_module: 'nonebot_bison'):
def __init__(self):
self.sub_index = 0
super().__init__()
-
+
@staticmethod
- async def get_target_name(_: 'Target'):
- return 'MockPlatform'
+ async def get_target_name(_: "Target"):
+ return "MockPlatform"
- def get_id(self, post: 'RawPost') -> Any:
- return post['id']
+ def get_id(self, post: "RawPost") -> Any:
+ return post["id"]
- def get_date(self, raw_post: 'RawPost') -> float:
- return raw_post['date']
+ def get_date(self, raw_post: "RawPost") -> float:
+ return raw_post["date"]
- async def parse(self, raw_post: 'RawPost') -> 'Post':
- return plugin_module.post.Post('mock_platform', raw_post['text'], 'http://t.tt/' + str(self.get_id(raw_post)), target_name='Mock')
+ async def parse(self, raw_post: "RawPost") -> "Post":
+ return plugin_module.post.Post(
+ "mock_platform",
+ raw_post["text"],
+ "http://t.tt/" + str(self.get_id(raw_post)),
+ target_name="Mock",
+ )
- async def get_sub_list(self, _: 'Target'):
+ async def get_sub_list(self, _: "Target"):
if self.sub_index == 0:
self.sub_index += 1
return raw_post_list_1
@@ -75,45 +86,52 @@ def mock_platform_without_cats_tags(plugin_module: 'nonebot_bison'):
return MockPlatform()
+
@pytest.fixture
-def mock_platform(plugin_module: 'nonebot_bison'):
+def mock_platform(plugin_module: "nonebot_bison"):
class MockPlatform(plugin_module.platform.platform.NewMessage):
- platform_name = 'mock_platform'
- name = 'Mock Platform'
+ platform_name = "mock_platform"
+ name = "Mock Platform"
enabled = True
is_common = True
schedule_interval = 10
enable_tag = True
has_target = True
categories = {
- 1: '转发',
- 2: '视频',
- }
+ 1: "转发",
+ 2: "视频",
+ }
+
def __init__(self):
self.sub_index = 0
super().__init__()
-
+
@staticmethod
- async def get_target_name(_: 'Target'):
- return 'MockPlatform'
+ async def get_target_name(_: "Target"):
+ return "MockPlatform"
- def get_id(self, post: 'RawPost') -> Any:
- return post['id']
+ def get_id(self, post: "RawPost") -> Any:
+ return post["id"]
- def get_date(self, raw_post: 'RawPost') -> float:
- return raw_post['date']
+ def get_date(self, raw_post: "RawPost") -> float:
+ return raw_post["date"]
- def get_tags(self, raw_post: 'RawPost') -> list['Tag']:
- return raw_post['tags']
+ def get_tags(self, raw_post: "RawPost") -> list["Tag"]:
+ return raw_post["tags"]
- def get_category(self, raw_post: 'RawPost') -> 'Category':
- return raw_post['category']
+ def get_category(self, raw_post: "RawPost") -> "Category":
+ return raw_post["category"]
- async def parse(self, raw_post: 'RawPost') -> 'Post':
- return plugin_module.post.Post('mock_platform', raw_post['text'], 'http://t.tt/' + str(self.get_id(raw_post)), target_name='Mock')
+ async def parse(self, raw_post: "RawPost") -> "Post":
+ return plugin_module.post.Post(
+ "mock_platform",
+ raw_post["text"],
+ "http://t.tt/" + str(self.get_id(raw_post)),
+ target_name="Mock",
+ )
- async def get_sub_list(self, _: 'Target'):
+ async def get_sub_list(self, _: "Target"):
if self.sub_index == 0:
self.sub_index += 1
return raw_post_list_1
@@ -122,49 +140,52 @@ def mock_platform(plugin_module: 'nonebot_bison'):
return MockPlatform()
+
@pytest.fixture
-def mock_platform_no_target(plugin_module: 'nonebot_bison'):
+def mock_platform_no_target(plugin_module: "nonebot_bison"):
class MockPlatform(plugin_module.platform.platform.NewMessage):
- platform_name = 'mock_platform'
- name = 'Mock Platform'
+ platform_name = "mock_platform"
+ name = "Mock Platform"
enabled = True
is_common = True
- schedule_type = 'interval'
- schedule_kw = {'seconds': 30}
+ schedule_type = "interval"
+ schedule_kw = {"seconds": 30}
enable_tag = True
has_target = False
- categories = {
- 1: '转发',
- 2: '视频',
- 3: '不支持'
- }
+ categories = {1: "转发", 2: "视频", 3: "不支持"}
+
def __init__(self):
self.sub_index = 0
super().__init__()
-
+
@staticmethod
- async def get_target_name(_: 'Target'):
- return 'MockPlatform'
+ async def get_target_name(_: "Target"):
+ return "MockPlatform"
- def get_id(self, post: 'RawPost') -> Any:
- return post['id']
+ def get_id(self, post: "RawPost") -> Any:
+ return post["id"]
- def get_date(self, raw_post: 'RawPost') -> float:
- return raw_post['date']
+ def get_date(self, raw_post: "RawPost") -> float:
+ return raw_post["date"]
- def get_tags(self, raw_post: 'RawPost') -> list['Tag']:
- return raw_post['tags']
+ def get_tags(self, raw_post: "RawPost") -> list["Tag"]:
+ return raw_post["tags"]
- def get_category(self, raw_post: 'RawPost') -> 'Category':
- if raw_post['category'] == 3:
+ def get_category(self, raw_post: "RawPost") -> "Category":
+ if raw_post["category"] == 3:
raise plugin_module.platform.platform.CategoryNotSupport()
- return raw_post['category']
+ return raw_post["category"]
- async def parse(self, raw_post: 'RawPost') -> 'Post':
- return plugin_module.post.Post('mock_platform', raw_post['text'], 'http://t.tt/' + str(self.get_id(raw_post)), target_name='Mock')
+ async def parse(self, raw_post: "RawPost") -> "Post":
+ return plugin_module.post.Post(
+ "mock_platform",
+ raw_post["text"],
+ "http://t.tt/" + str(self.get_id(raw_post)),
+ target_name="Mock",
+ )
- async def get_sub_list(self, _: 'Target'):
+ async def get_sub_list(self, _: "Target"):
if self.sub_index == 0:
self.sub_index += 1
return raw_post_list_1
@@ -173,54 +194,61 @@ def mock_platform_no_target(plugin_module: 'nonebot_bison'):
return MockPlatform()
+
@pytest.fixture
-def mock_platform_no_target_2(plugin_module: 'nonebot_bison'):
+def mock_platform_no_target_2(plugin_module: "nonebot_bison"):
class MockPlatform(plugin_module.platform.platform.NewMessage):
- platform_name = 'mock_platform'
- name = 'Mock Platform'
+ platform_name = "mock_platform"
+ name = "Mock Platform"
enabled = True
- schedule_type = 'interval'
- schedule_kw = {'seconds': 30}
+ schedule_type = "interval"
+ schedule_kw = {"seconds": 30}
is_common = True
enable_tag = True
has_target = False
categories = {
- 4: 'leixing4',
- 5: 'leixing5',
- }
+ 4: "leixing4",
+ 5: "leixing5",
+ }
+
def __init__(self):
self.sub_index = 0
super().__init__()
-
+
@staticmethod
- async def get_target_name(_: 'Target'):
- return 'MockPlatform'
+ async def get_target_name(_: "Target"):
+ return "MockPlatform"
- def get_id(self, post: 'RawPost') -> Any:
- return post['id']
+ def get_id(self, post: "RawPost") -> Any:
+ return post["id"]
- def get_date(self, raw_post: 'RawPost') -> float:
- return raw_post['date']
+ def get_date(self, raw_post: "RawPost") -> float:
+ return raw_post["date"]
- def get_tags(self, raw_post: 'RawPost') -> list['Tag']:
- return raw_post['tags']
+ def get_tags(self, raw_post: "RawPost") -> list["Tag"]:
+ return raw_post["tags"]
- def get_category(self, raw_post: 'RawPost') -> 'Category':
- return raw_post['category']
+ def get_category(self, raw_post: "RawPost") -> "Category":
+ return raw_post["category"]
- async def parse(self, raw_post: 'RawPost') -> 'Post':
- return plugin_module.post.Post('mock_platform_2', raw_post['text'], 'http://t.tt/' + str(self.get_id(raw_post)), target_name='Mock')
+ async def parse(self, raw_post: "RawPost") -> "Post":
+ return plugin_module.post.Post(
+ "mock_platform_2",
+ raw_post["text"],
+ "http://t.tt/" + str(self.get_id(raw_post)),
+ target_name="Mock",
+ )
- async def get_sub_list(self, _: 'Target'):
+ async def get_sub_list(self, _: "Target"):
list_1 = [
- {'id': 5, 'text': 'p5', 'date': now, 'tags': ['tag1'], 'category': 4}
- ]
+ {"id": 5, "text": "p5", "date": now, "tags": ["tag1"], "category": 4}
+ ]
list_2 = list_1 + [
- {'id': 6, 'text': 'p6', 'date': now, 'tags': ['tag1'], 'category': 4},
- {'id': 7, 'text': 'p7', 'date': now, 'tags': ['tag2'], 'category': 5},
- ]
+ {"id": 6, "text": "p6", "date": now, "tags": ["tag1"], "category": 4},
+ {"id": 7, "text": "p7", "date": now, "tags": ["tag2"], "category": 5},
+ ]
if self.sub_index == 0:
self.sub_index += 1
return list_1
@@ -229,145 +257,190 @@ def mock_platform_no_target_2(plugin_module: 'nonebot_bison'):
return MockPlatform()
+
@pytest.fixture
-def mock_status_change(plugin_module: 'nonebot_bison'):
+def mock_status_change(plugin_module: "nonebot_bison"):
class MockPlatform(plugin_module.platform.platform.StatusChange):
- platform_name = 'mock_platform'
- name = 'Mock Platform'
+ platform_name = "mock_platform"
+ name = "Mock Platform"
enabled = True
is_common = True
enable_tag = False
- schedule_type = 'interval'
- schedule_kw = {'seconds': 10}
+ schedule_type = "interval"
+ schedule_kw = {"seconds": 10}
has_target = False
categories = {
- 1: '转发',
- 2: '视频',
- }
+ 1: "转发",
+ 2: "视频",
+ }
+
def __init__(self):
self.sub_index = 0
super().__init__()
- async def get_status(self, _: 'Target'):
+ async def get_status(self, _: "Target"):
if self.sub_index == 0:
self.sub_index += 1
- return {'s': False}
+ return {"s": False}
elif self.sub_index == 1:
self.sub_index += 1
- return {'s': True}
+ return {"s": True}
else:
- return {'s': False}
+ return {"s": False}
- def compare_status(self, target, old_status, new_status) -> list['RawPost']:
- if old_status['s'] == False and new_status['s'] == True:
- return [{'text': 'on', 'cat': 1}]
- elif old_status['s'] == True and new_status['s'] == False:
- return [{'text': 'off', 'cat': 2}]
+ def compare_status(self, target, old_status, new_status) -> list["RawPost"]:
+ if old_status["s"] == False and new_status["s"] == True:
+ return [{"text": "on", "cat": 1}]
+ elif old_status["s"] == True and new_status["s"] == False:
+ return [{"text": "off", "cat": 2}]
return []
- async def parse(self, raw_post) -> 'Post':
- return plugin_module.post.Post('mock_status', raw_post['text'], '')
+ async def parse(self, raw_post) -> "Post":
+ return plugin_module.post.Post("mock_status", raw_post["text"], "")
def get_category(self, raw_post):
- return raw_post['cat']
+ return raw_post["cat"]
return MockPlatform()
@pytest.mark.asyncio
-async def test_new_message_target_without_cats_tags(mock_platform_without_cats_tags, user_info_factory):
- res1 = await mock_platform_without_cats_tags.fetch_new_post('dummy', [user_info_factory(lambda _: [1,2], lambda _: [])])
- assert(len(res1) == 0)
- res2 = await mock_platform_without_cats_tags.fetch_new_post('dummy', [
- user_info_factory(lambda _: [], lambda _: []),
- ])
- assert(len(res2) == 1)
+async def test_new_message_target_without_cats_tags(
+ mock_platform_without_cats_tags, user_info_factory
+):
+ res1 = await mock_platform_without_cats_tags.fetch_new_post(
+ "dummy", [user_info_factory(lambda _: [1, 2], lambda _: [])]
+ )
+ assert len(res1) == 0
+ res2 = await mock_platform_without_cats_tags.fetch_new_post(
+ "dummy",
+ [
+ user_info_factory(lambda _: [], lambda _: []),
+ ],
+ )
+ assert len(res2) == 1
posts_1 = res2[0][1]
- assert(len(posts_1) == 3)
+ assert len(posts_1) == 3
id_set_1 = set(map(lambda x: x.text, posts_1))
- assert('p2' in id_set_1 and 'p3' in id_set_1 and 'p4' in id_set_1)
+ assert "p2" in id_set_1 and "p3" in id_set_1 and "p4" in id_set_1
+
@pytest.mark.asyncio
async def test_new_message_target(mock_platform, user_info_factory):
- res1 = await mock_platform.fetch_new_post('dummy', [user_info_factory(lambda _: [1,2], lambda _: [])])
- assert(len(res1) == 0)
- res2 = await mock_platform.fetch_new_post('dummy', [
- user_info_factory(lambda _: [1,2], lambda _: []),
- user_info_factory(lambda _: [1], lambda _: []),
- user_info_factory(lambda _: [1,2], lambda _: ['tag1'])
- ])
- assert(len(res2) == 3)
+ res1 = await mock_platform.fetch_new_post(
+ "dummy", [user_info_factory(lambda _: [1, 2], lambda _: [])]
+ )
+ assert len(res1) == 0
+ res2 = await mock_platform.fetch_new_post(
+ "dummy",
+ [
+ user_info_factory(lambda _: [1, 2], lambda _: []),
+ user_info_factory(lambda _: [1], lambda _: []),
+ user_info_factory(lambda _: [1, 2], lambda _: ["tag1"]),
+ ],
+ )
+ assert len(res2) == 3
posts_1 = res2[0][1]
posts_2 = res2[1][1]
posts_3 = res2[2][1]
- assert(len(posts_1) == 2)
- assert(len(posts_2) == 1)
- assert(len(posts_3) == 1)
+ assert len(posts_1) == 2
+ assert len(posts_2) == 1
+ assert len(posts_3) == 1
id_set_1 = set(map(lambda x: x.text, posts_1))
id_set_2 = set(map(lambda x: x.text, posts_2))
id_set_3 = set(map(lambda x: x.text, posts_3))
- assert('p2' in id_set_1 and 'p3' in id_set_1)
- assert('p2' in id_set_2)
- assert('p2' in id_set_3)
+ assert "p2" in id_set_1 and "p3" in id_set_1
+ assert "p2" in id_set_2
+ assert "p2" in id_set_3
+
@pytest.mark.asyncio
async def test_new_message_no_target(mock_platform_no_target, user_info_factory):
- res1 = await mock_platform_no_target.fetch_new_post('dummy', [user_info_factory(lambda _: [1,2], lambda _: [])])
- assert(len(res1) == 0)
- res2 = await mock_platform_no_target.fetch_new_post('dummy', [
- user_info_factory(lambda _: [1,2], lambda _: []),
- user_info_factory(lambda _: [1], lambda _: []),
- user_info_factory(lambda _: [1,2], lambda _: ['tag1'])
- ])
- assert(len(res2) == 3)
+ res1 = await mock_platform_no_target.fetch_new_post(
+ "dummy", [user_info_factory(lambda _: [1, 2], lambda _: [])]
+ )
+ assert len(res1) == 0
+ res2 = await mock_platform_no_target.fetch_new_post(
+ "dummy",
+ [
+ user_info_factory(lambda _: [1, 2], lambda _: []),
+ user_info_factory(lambda _: [1], lambda _: []),
+ user_info_factory(lambda _: [1, 2], lambda _: ["tag1"]),
+ ],
+ )
+ assert len(res2) == 3
posts_1 = res2[0][1]
posts_2 = res2[1][1]
posts_3 = res2[2][1]
- assert(len(posts_1) == 2)
- assert(len(posts_2) == 1)
- assert(len(posts_3) == 1)
+ assert len(posts_1) == 2
+ assert len(posts_2) == 1
+ assert len(posts_3) == 1
id_set_1 = set(map(lambda x: x.text, posts_1))
id_set_2 = set(map(lambda x: x.text, posts_2))
id_set_3 = set(map(lambda x: x.text, posts_3))
- assert('p2' in id_set_1 and 'p3' in id_set_1)
- assert('p2' in id_set_2)
- assert('p2' in id_set_3)
- res3 = await mock_platform_no_target.fetch_new_post('dummy', [user_info_factory(lambda _: [1,2], lambda _: [])])
- assert(len(res3) == 0)
+ assert "p2" in id_set_1 and "p3" in id_set_1
+ assert "p2" in id_set_2
+ assert "p2" in id_set_3
+ res3 = await mock_platform_no_target.fetch_new_post(
+ "dummy", [user_info_factory(lambda _: [1, 2], lambda _: [])]
+ )
+ assert len(res3) == 0
+
@pytest.mark.asyncio
async def test_status_change(mock_status_change, user_info_factory):
- res1 = await mock_status_change.fetch_new_post('dummy', [user_info_factory(lambda _: [1,2], lambda _: [])])
- assert(len(res1) == 0)
- res2 = await mock_status_change.fetch_new_post('dummy', [
- user_info_factory(lambda _: [1,2], lambda _:[])
- ])
- assert(len(res2) == 1)
+ res1 = await mock_status_change.fetch_new_post(
+ "dummy", [user_info_factory(lambda _: [1, 2], lambda _: [])]
+ )
+ assert len(res1) == 0
+ res2 = await mock_status_change.fetch_new_post(
+ "dummy", [user_info_factory(lambda _: [1, 2], lambda _: [])]
+ )
+ assert len(res2) == 1
posts = res2[0][1]
- assert(len(posts) == 1)
- assert(posts[0].text == 'on')
- res3 = await mock_status_change.fetch_new_post('dummy', [
- user_info_factory(lambda _: [1,2], lambda _: []),
- user_info_factory(lambda _: [1], lambda _: []),
- ])
- assert(len(res3) == 2)
- assert(len(res3[0][1]) == 1)
- assert(res3[0][1][0].text == 'off')
- assert(len(res3[1][1]) == 0)
- res4 = await mock_status_change.fetch_new_post('dummy', [user_info_factory(lambda _: [1,2], lambda _: [])])
- assert(len(res4) == 0)
+ assert len(posts) == 1
+ assert posts[0].text == "on"
+ res3 = await mock_status_change.fetch_new_post(
+ "dummy",
+ [
+ user_info_factory(lambda _: [1, 2], lambda _: []),
+ user_info_factory(lambda _: [1], lambda _: []),
+ ],
+ )
+ assert len(res3) == 2
+ assert len(res3[0][1]) == 1
+ assert res3[0][1][0].text == "off"
+ assert len(res3[1][1]) == 0
+ res4 = await mock_status_change.fetch_new_post(
+ "dummy", [user_info_factory(lambda _: [1, 2], lambda _: [])]
+ )
+ assert len(res4) == 0
+
@pytest.mark.asyncio
-async def test_group(plugin_module: 'nonebot_bison', mock_platform_no_target, mock_platform_no_target_2, user_info_factory):
- group_platform = plugin_module.platform.platform.NoTargetGroup([mock_platform_no_target, mock_platform_no_target_2])
- res1 = await group_platform.fetch_new_post('dummy', [user_info_factory(lambda _: [1,4], lambda _: [])])
- assert(len(res1) == 0)
- res2 = await group_platform.fetch_new_post('dummy', [user_info_factory(lambda _: [1,4], lambda _: [])])
- assert(len(res2) == 1)
+async def test_group(
+ plugin_module: "nonebot_bison",
+ mock_platform_no_target,
+ mock_platform_no_target_2,
+ user_info_factory,
+):
+ group_platform = plugin_module.platform.platform.NoTargetGroup(
+ [mock_platform_no_target, mock_platform_no_target_2]
+ )
+ res1 = await group_platform.fetch_new_post(
+ "dummy", [user_info_factory(lambda _: [1, 4], lambda _: [])]
+ )
+ assert len(res1) == 0
+ res2 = await group_platform.fetch_new_post(
+ "dummy", [user_info_factory(lambda _: [1, 4], lambda _: [])]
+ )
+ assert len(res2) == 1
posts = res2[0][1]
- assert(len(posts) == 2)
+ assert len(posts) == 2
id_set_2 = set(map(lambda x: x.text, posts))
- assert('p2' in id_set_2 and 'p6' in id_set_2)
- res3 = await group_platform.fetch_new_post('dummy', [user_info_factory(lambda _: [1,4], lambda _: [])])
- assert(len(res3) == 0)
+ assert "p2" in id_set_2 and "p6" in id_set_2
+ res3 = await group_platform.fetch_new_post(
+ "dummy", [user_info_factory(lambda _: [1, 4], lambda _: [])]
+ )
+ assert len(res3) == 0
diff --git a/tests/platforms/test_weibo.py b/tests/platforms/test_weibo.py
index 5fa9c00..4139bed 100644
--- a/tests/platforms/test_weibo.py
+++ b/tests/platforms/test_weibo.py
@@ -1,110 +1,131 @@
-import pytest
import typing
-import respx
from datetime import datetime
+
+import respx
+import pytest
+import feedparser
from pytz import timezone
from httpx import Response
-import feedparser
if typing.TYPE_CHECKING:
import sys
- sys.path.append('./src/plugins')
+
+ sys.path.append("./src/plugins")
import nonebot_bison
-from .utils import get_json, get_file
+from .utils import get_file, get_json
+
@pytest.fixture
-def weibo(plugin_module: 'nonebot_bison'):
- return plugin_module.platform.platform_manager['weibo']
+def weibo(plugin_module: "nonebot_bison"):
+ return plugin_module.platform.platform_manager["weibo"]
-@pytest.fixture(scope='module')
+
+@pytest.fixture(scope="module")
def weibo_ak_list_1():
- return get_json('weibo_ak_list_1.json')
+ return get_json("weibo_ak_list_1.json")
+
@pytest.mark.asyncio
async def test_get_name(weibo):
- name = await weibo.get_target_name('6279793937')
- assert(name == "明日方舟Arknights")
+ name = await weibo.get_target_name("6279793937")
+ assert name == "明日方舟Arknights"
+
@pytest.mark.asyncio
@respx.mock
async def test_fetch_new(weibo, dummy_user_subinfo):
- ak_list_router = respx.get("https://m.weibo.cn/api/container/getIndex?containerid=1076036279793937")
+ ak_list_router = respx.get(
+ "https://m.weibo.cn/api/container/getIndex?containerid=1076036279793937"
+ )
detail_router = respx.get("https://m.weibo.cn/detail/4649031014551911")
- ak_list_router.mock(return_value=Response(200, json=get_json('weibo_ak_list_0.json')))
- detail_router.mock(return_value=Response(200, text=get_file('weibo_detail_4649031014551911')))
- target = '6279793937'
+ ak_list_router.mock(
+ return_value=Response(200, json=get_json("weibo_ak_list_0.json"))
+ )
+ detail_router.mock(
+ return_value=Response(200, text=get_file("weibo_detail_4649031014551911"))
+ )
+ target = "6279793937"
res = await weibo.fetch_new_post(target, [dummy_user_subinfo])
- assert(ak_list_router.called)
- assert(len(res) == 0)
- assert(not detail_router.called)
- mock_data = get_json('weibo_ak_list_1.json')
+ assert ak_list_router.called
+ assert len(res) == 0
+ assert not detail_router.called
+ mock_data = get_json("weibo_ak_list_1.json")
ak_list_router.mock(return_value=Response(200, json=mock_data))
# import ipdb; ipdb.set_trace()
res2 = await weibo.fetch_new_post(target, [dummy_user_subinfo])
- assert(len(res2) == 0)
- mock_data['data']['cards'][1]['mblog']['created_at'] = \
- datetime.now(timezone('Asia/Shanghai')).strftime('%a %b %d %H:%M:%S %z %Y')
+ assert len(res2) == 0
+ mock_data["data"]["cards"][1]["mblog"]["created_at"] = datetime.now(
+ timezone("Asia/Shanghai")
+ ).strftime("%a %b %d %H:%M:%S %z %Y")
ak_list_router.mock(return_value=Response(200, json=mock_data))
res3 = await weibo.fetch_new_post(target, [dummy_user_subinfo])
- assert(len(res3[0][1]) == 1)
- assert(not detail_router.called)
+ assert len(res3[0][1]) == 1
+ assert not detail_router.called
post = res3[0][1][0]
- assert(post.target_type == 'weibo')
- assert(post.text == '#明日方舟#\nSideStory「沃伦姆德的薄暮」复刻现已开启! ')
- assert(post.url == 'https://weibo.com/6279793937/KkBtUx2dv')
- assert(post.target_name == '明日方舟Arknights')
- assert(len(post.pics) == 1)
+ assert post.target_type == "weibo"
+ assert post.text == "#明日方舟#\nSideStory「沃伦姆德的薄暮」复刻现已开启! "
+ assert post.url == "https://weibo.com/6279793937/KkBtUx2dv"
+ assert post.target_name == "明日方舟Arknights"
+ assert len(post.pics) == 1
+
@pytest.mark.asyncio
async def test_classification(weibo):
- mock_data = get_json('weibo_ak_list_1.json')
- tuwen = mock_data['data']['cards'][1]
- retweet = mock_data['data']['cards'][3]
- video = mock_data['data']['cards'][0]
- mock_data_ys = get_json('weibo_ys_list_0.json')
- text = mock_data_ys['data']['cards'][2]
- assert(weibo.get_category(retweet) == 1)
- assert(weibo.get_category(video) == 2)
- assert(weibo.get_category(tuwen) == 3)
- assert(weibo.get_category(text) == 4)
+ mock_data = get_json("weibo_ak_list_1.json")
+ tuwen = mock_data["data"]["cards"][1]
+ retweet = mock_data["data"]["cards"][3]
+ video = mock_data["data"]["cards"][0]
+ mock_data_ys = get_json("weibo_ys_list_0.json")
+ text = mock_data_ys["data"]["cards"][2]
+ assert weibo.get_category(retweet) == 1
+ assert weibo.get_category(video) == 2
+ assert weibo.get_category(tuwen) == 3
+ assert weibo.get_category(text) == 4
+
@pytest.mark.asyncio
@respx.mock
async def test_parse_long(weibo):
detail_router = respx.get("https://m.weibo.cn/detail/4645748019299849")
- detail_router.mock(return_value=Response(200, text=get_file('weibo_detail_4645748019299849')))
- raw_post = get_json('weibo_ak_list_1.json')['data']['cards'][0]
+ detail_router.mock(
+ return_value=Response(200, text=get_file("weibo_detail_4645748019299849"))
+ )
+ raw_post = get_json("weibo_ak_list_1.json")["data"]["cards"][0]
post = await weibo.parse(raw_post)
- assert(not '全文' in post.text)
- assert(detail_router.called)
+ assert not "全文" in post.text
+ assert detail_router.called
+
def test_tag(weibo, weibo_ak_list_1):
- raw_post = weibo_ak_list_1['data']['cards'][0]
- assert(weibo.get_tags(raw_post) == ['明日方舟', '音律联觉'])
+ raw_post = weibo_ak_list_1["data"]["cards"][0]
+ assert weibo.get_tags(raw_post) == ["明日方舟", "音律联觉"]
+
@pytest.mark.asyncio
@pytest.mark.compare
async def test_rsshub_compare(weibo):
- target = '6279793937'
+ target = "6279793937"
raw_posts = filter(weibo.filter_platform_custom, await weibo.get_sub_list(target))
posts = []
for raw_post in raw_posts:
posts.append(await weibo.parse(raw_post))
url_set = set(map(lambda x: x.url, posts))
- feedres = feedparser.parse('https://rsshub.app/weibo/user/6279793937')
+ feedres = feedparser.parse("https://rsshub.app/weibo/user/6279793937")
for entry in feedres.entries[:5]:
# print(entry)
- assert(entry.link in url_set)
+ assert entry.link in url_set
+
test_post = {
- "mblog": {
- "text": "#刚出生的小羊驼长啥样#
小羊驼三三来也![[好喜欢]](\"https://h5.sinaimg.cn/m/emoticon/icon/lxh/lxh_haoxihuan-51860b62e6.png\")
小羊驼三三 ",
- "bid": "KnssqeqKK"
- }
+ "mblog": {
+ "text": '#刚出生的小羊驼长啥样#
小羊驼三三来也![[好喜欢]](https://h5.sinaimg.cn/m/emoticon/icon/lxh/lxh_haoxihuan-51860b62e6.png)
小羊驼三三 ',
+ "bid": "KnssqeqKK",
+ }
}
+
+
def test_chaohua_tag(weibo):
tags = weibo.get_tags(test_post)
- assert('刚出生的小羊驼长啥样' in tags)
- assert('小羊驼三三超话' in tags)
-
+ assert "刚出生的小羊驼长啥样" in tags
+ assert "小羊驼三三超话" in tags
diff --git a/tests/platforms/utils.py b/tests/platforms/utils.py
index da6d0e9..b80374c 100644
--- a/tests/platforms/utils.py
+++ b/tests/platforms/utils.py
@@ -1,12 +1,16 @@
-from pathlib import Path
import json
+from pathlib import Path
+
path = Path(__file__).parent
+
+
def get_json(file_name: str):
- with open(path / file_name, 'r') as f:
- file_text = f.read()
+ with open(path / file_name, "r") as f:
+ file_text = f.read()
return json.loads(file_text)
-def get_file(file_name:str):
- with open(path / file_name, 'r') as f:
- file_text = f.read()
+
+def get_file(file_name: str):
+ with open(path / file_name, "r") as f:
+ file_text = f.read()
return file_text
diff --git a/tests/test_config_manager.py b/tests/test_config_manager.py
index 5ae7fe2..bd7a638 100644
--- a/tests/test_config_manager.py
+++ b/tests/test_config_manager.py
@@ -1,38 +1,47 @@
-import pytest
import typing
+import pytest
+
if typing.TYPE_CHECKING:
import sys
- sys.path.append('./src/plugins')
+
+ sys.path.append("./src/plugins")
import nonebot_bison
+
@pytest.fixture
def config(plugin_module):
plugin_module.config.start_up()
return plugin_module.config.Config()
-def test_create_and_get(config: 'nonebot_bison.config.Config', plugin_module: 'nonebot_bison'):
+
+def test_create_and_get(
+ config: "nonebot_bison.config.Config", plugin_module: "nonebot_bison"
+):
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']['weibo_id'] == \
- [plugin_module.types.User('123', 'group')])
- assert(confs[0]['cats'] == [])
+ 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"]["weibo_id"] == [
+ plugin_module.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'])
+ 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_merge_pic.py b/tests/test_merge_pic.py
index 6dcd2c2..2008f1f 100644
--- a/tests/test_merge_pic.py
+++ b/tests/test_merge_pic.py
@@ -1,75 +1,87 @@
-import pytest
import typing
+import pytest
+
if typing.TYPE_CHECKING:
import sys
- sys.path.append('./src/plugins')
+
+ sys.path.append("./src/plugins")
import nonebot_bison
merge_source_9 = [
- 'https://wx1.sinaimg.cn/large/0071VPLMgy1gq0vib7zooj30dx0dxmz5.jpg',
- "https://wx4.sinaimg.cn/large/0071VPLMgy1gq0vib5oqjj30dw0dxjt2.jpg",
- "https://wx2.sinaimg.cn/large/0071VPLMgy1gq0vib8bjmj30dv0dxgn7.jpg",
- "https://wx1.sinaimg.cn/large/0071VPLMgy1gq0vib6pn1j30dx0dw75v.jpg",
- "https://wx4.sinaimg.cn/large/0071VPLMgy1gq0vib925mj30dw0dwabb.jpg",
- "https://wx2.sinaimg.cn/large/0071VPLMgy1gq0vib7ujuj30dv0dwtap.jpg",
- "https://wx1.sinaimg.cn/large/0071VPLMgy1gq0vibaexnj30dx0dvq49.jpg",
- "https://wx1.sinaimg.cn/large/0071VPLMgy1gq0vibehw4j30dw0dv74u.jpg",
- "https://wx1.sinaimg.cn/large/0071VPLMgy1gq0vibfb5fj30dv0dvtac.jpg",
- "https://wx3.sinaimg.cn/large/0071VPLMgy1gq0viexkjxj30rs3pcx6p.jpg",
- "https://wx2.sinaimg.cn/large/0071VPLMgy1gq0vif6qrpj30rs4mou10.jpg",
- "https://wx4.sinaimg.cn/large/0071VPLMgy1gq0vifc826j30rs4a64qs.jpg",
- "https://wx1.sinaimg.cn/large/0071VPLMgy1gq0vify21lj30rsbj71ld.jpg",
- ]
+ "https://wx1.sinaimg.cn/large/0071VPLMgy1gq0vib7zooj30dx0dxmz5.jpg",
+ "https://wx4.sinaimg.cn/large/0071VPLMgy1gq0vib5oqjj30dw0dxjt2.jpg",
+ "https://wx2.sinaimg.cn/large/0071VPLMgy1gq0vib8bjmj30dv0dxgn7.jpg",
+ "https://wx1.sinaimg.cn/large/0071VPLMgy1gq0vib6pn1j30dx0dw75v.jpg",
+ "https://wx4.sinaimg.cn/large/0071VPLMgy1gq0vib925mj30dw0dwabb.jpg",
+ "https://wx2.sinaimg.cn/large/0071VPLMgy1gq0vib7ujuj30dv0dwtap.jpg",
+ "https://wx1.sinaimg.cn/large/0071VPLMgy1gq0vibaexnj30dx0dvq49.jpg",
+ "https://wx1.sinaimg.cn/large/0071VPLMgy1gq0vibehw4j30dw0dv74u.jpg",
+ "https://wx1.sinaimg.cn/large/0071VPLMgy1gq0vibfb5fj30dv0dvtac.jpg",
+ "https://wx3.sinaimg.cn/large/0071VPLMgy1gq0viexkjxj30rs3pcx6p.jpg",
+ "https://wx2.sinaimg.cn/large/0071VPLMgy1gq0vif6qrpj30rs4mou10.jpg",
+ "https://wx4.sinaimg.cn/large/0071VPLMgy1gq0vifc826j30rs4a64qs.jpg",
+ "https://wx1.sinaimg.cn/large/0071VPLMgy1gq0vify21lj30rsbj71ld.jpg",
+]
merge_source_9_2 = [
- 'https://wx2.sinaimg.cn/large/0071VPLMgy1gxo0eyycd7j30dw0dd3zk.jpg',
- 'https://wx1.sinaimg.cn/large/0071VPLMgy1gxo0eyx6mhj30dw0ddjs8.jpg',
- 'https://wx4.sinaimg.cn/large/0071VPLMgy1gxo0eyxf2bj30dw0dddh4.jpg',
- 'https://wx3.sinaimg.cn/large/0071VPLMgy1gxo0ez1h5zj30dw0efwfs.jpg',
- 'https://wx3.sinaimg.cn/large/0071VPLMgy1gxo0eyyku4j30dw0ef3zm.jpg',
- 'https://wx1.sinaimg.cn/large/0071VPLMgy1gxo0ez0bjhj30dw0efabs.jpg',
- 'https://wx4.sinaimg.cn/large/0071VPLMgy1gxo0ezdcafj30dw0dwacb.jpg',
- 'https://wx1.sinaimg.cn/large/0071VPLMgy1gxo0ezg2g3j30dw0dwq51.jpg',
- 'https://wx3.sinaimg.cn/large/0071VPLMgy1gxo0ez5oloj30dw0dw0uf.jpg',
- 'https://wx4.sinaimg.cn/large/0071VPLMgy1gxo0fnk6stj30rs44ne81.jpg',
- 'https://wx2.sinaimg.cn/large/0071VPLMgy1gxo0fohgcoj30rs3wpe81.jpg',
- 'https://wx3.sinaimg.cn/large/0071VPLMgy1gxo0fpr6chj30rs3m1b29.jpg'
- ]
+ "https://wx2.sinaimg.cn/large/0071VPLMgy1gxo0eyycd7j30dw0dd3zk.jpg",
+ "https://wx1.sinaimg.cn/large/0071VPLMgy1gxo0eyx6mhj30dw0ddjs8.jpg",
+ "https://wx4.sinaimg.cn/large/0071VPLMgy1gxo0eyxf2bj30dw0dddh4.jpg",
+ "https://wx3.sinaimg.cn/large/0071VPLMgy1gxo0ez1h5zj30dw0efwfs.jpg",
+ "https://wx3.sinaimg.cn/large/0071VPLMgy1gxo0eyyku4j30dw0ef3zm.jpg",
+ "https://wx1.sinaimg.cn/large/0071VPLMgy1gxo0ez0bjhj30dw0efabs.jpg",
+ "https://wx4.sinaimg.cn/large/0071VPLMgy1gxo0ezdcafj30dw0dwacb.jpg",
+ "https://wx1.sinaimg.cn/large/0071VPLMgy1gxo0ezg2g3j30dw0dwq51.jpg",
+ "https://wx3.sinaimg.cn/large/0071VPLMgy1gxo0ez5oloj30dw0dw0uf.jpg",
+ "https://wx4.sinaimg.cn/large/0071VPLMgy1gxo0fnk6stj30rs44ne81.jpg",
+ "https://wx2.sinaimg.cn/large/0071VPLMgy1gxo0fohgcoj30rs3wpe81.jpg",
+ "https://wx3.sinaimg.cn/large/0071VPLMgy1gxo0fpr6chj30rs3m1b29.jpg",
+]
+
@pytest.mark.asyncio
-async def test_9_merge(plugin_module: 'nonebot_bison'):
- post = plugin_module.post.Post('', '', '', pics=merge_source_9)
- await post._pic_merge()
+async def test_9_merge(plugin_module: "nonebot_bison"):
+ post = plugin_module.post.Post("", "", "", pics=merge_source_9)
+ await post._pic_merge()
assert len(post.pics) == 5
await post.generate_messages()
+
@pytest.mark.asyncio
-async def test_9_merge_2(plugin_module: 'nonebot_bison'):
- post = plugin_module.post.Post('', '', '', pics=merge_source_9_2)
+async def test_9_merge_2(plugin_module: "nonebot_bison"):
+ post = plugin_module.post.Post("", "", "", pics=merge_source_9_2)
await post._pic_merge()
assert len(post.pics) == 4
await post.generate_messages()
+
@pytest.mark.asyncio
async def test_6_merge(plugin_module):
- post = plugin_module.post.Post('', '', '', pics=merge_source_9[0:6]+merge_source_9[9:])
- await post._pic_merge()
+ post = plugin_module.post.Post(
+ "", "", "", pics=merge_source_9[0:6] + merge_source_9[9:]
+ )
+ await post._pic_merge()
assert len(post.pics) == 5
+
@pytest.mark.asyncio
async def test_3_merge(plugin_module):
- post = plugin_module.post.Post('', '', '', pics=merge_source_9[0:3]+merge_source_9[9:])
- await post._pic_merge()
+ post = plugin_module.post.Post(
+ "", "", "", pics=merge_source_9[0:3] + merge_source_9[9:]
+ )
+ await post._pic_merge()
assert len(post.pics) == 5
+
@pytest.mark.asyncio
async def test_6_merge_only(plugin_module):
- post = plugin_module.post.Post('', '', '', pics=merge_source_9[0:6])
- await post._pic_merge()
+ post = plugin_module.post.Post("", "", "", pics=merge_source_9[0:6])
+ await post._pic_merge()
assert len(post.pics) == 1
+
@pytest.mark.asyncio
async def test_3_merge_only(plugin_module):
- post = plugin_module.post.Post('', '', '', pics=merge_source_9[0:3])
- await post._pic_merge()
+ post = plugin_module.post.Post("", "", "", pics=merge_source_9[0:3])
+ await post._pic_merge()
assert len(post.pics) == 1
diff --git a/tests/test_render.py b/tests/test_render.py
index 0d180ff..edeb478 100644
--- a/tests/test_render.py
+++ b/tests/test_render.py
@@ -1,20 +1,25 @@
-import pytest
import typing
+import pytest
+
if typing.TYPE_CHECKING:
import sys
- sys.path.append('./src/plugins')
+
+ sys.path.append("./src/plugins")
import nonebot_bison
+
@pytest.mark.asyncio
@pytest.mark.render
-async def test_render(plugin_module: 'nonebot_bison'):
+async def test_render(plugin_module: "nonebot_bison"):
render = plugin_module.utils.Render()
- res = await render.text_to_pic('''a\nbbbbbbbbbbbbbbbbbbbbbb\ncd
+ res = await render.text_to_pic(
+ """a\nbbbbbbbbbbbbbbbbbbbbbb\ncd
中文
VuePress 由两部分组成:第一部分是一个极简静态网站生成器
(opens new window),它包含由 Vue 驱动的主题系统和插件 API,另一个部分是为书写技术文档而优化的默认主题,它的诞生初衷是为了支持 Vue 及其子项目的文档需求。
每一个由 VuePress 生成的页面都带有预渲染好的 HTML,也因此具有非常好的加载性能和搜索引擎优化(SEO)。同时,一旦页面被加载,Vue 将接管这些静态内容,并将其转换成一个完整的单页应用(SPA),其他的页面则会只在用户浏览到的时候才按需加载。
-''')
+"""
+ )