Merge branch 'main' into next

This commit is contained in:
felinae98 2022-06-05 16:48:40 +08:00
commit 1f074b1045
No known key found for this signature in database
GPG Key ID: 00C8B010587FF610
29 changed files with 864 additions and 208 deletions

View File

@ -18,23 +18,14 @@ runs:
- name: Install poetry - name: Install poetry
uses: Gr1N/setup-poetry@v7 uses: Gr1N/setup-poetry@v7
- name: Cache Windows dependencies - id: poetry-cache
uses: actions/cache@v2 run: echo "::set-output name=dir::$(poetry config virtualenvs.path)"
if: ${{ runner.os == 'Windows' }} shell: bash
with:
path: ~/AppData/Local/pypoetry/Cache/virtualenvs
key: ${{ runner.os }}-poetry-${{ inputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Cache Linux dependencies - uses: actions/cache@v2
uses: actions/cache@v2
if: ${{ runner.os == 'Linux' }}
with: with:
path: ~/.cache/pypoetry/virtualenvs path: ${{ steps.poetry-cache.outputs.dir }}
key: ${{ runner.os }}-poetry-${{ inputs.python-version }}-${{ hashFiles('**/poetry.lock') }} key: ${{ runner.os }}-poetry-${{ steps.python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Cache macOS dependencies - run: poetry install
uses: actions/cache@v2 shell: bash
if: ${{ runner.os == 'macOS' }}
with:
path: ~/Library/Caches/pypoetry/virtualenvs
key: ${{ runner.os }}-poetry-${{ inputs.python-version }}-${{ hashFiles('**/poetry.lock') }}

View File

@ -1,10 +1,24 @@
name: CI name: test-build
on: on:
push: push:
branches: branches:
- main - main
paths:
- admin-frontend/**
- docker/**
- src/**
- tests/**
- pyproject.toml
- poetry.lock
pull_request: pull_request:
paths:
- admin-frontend/**
- docker/**
- src/**
- tests/**
- pyproject.toml
- poetry.lock
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
@ -63,18 +77,10 @@ jobs:
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v3
with: with:
env_vars: OS,PYTHON_VERSION env_vars: OS,PYTHON_VERSION
docker: docker-main:
name: Docker name: Docker main
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [build-frontend, test] needs: [build-frontend, test]
strategy:
matrix:
include:
- file: ./docker/Dockerfile_with_frontend
tags: felinae98/nonebot-bison:main
- file: ./docker/Dockerfile_with_frontend_sentry
tags: felinae98/nonebot-bison:main-sentry
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -101,8 +107,45 @@ jobs:
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
with: with:
context: . context: .
file: ${{ matrix.file }} file: ./docker/Dockerfile_with_frontend
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
tags: ${{ matrix.tags }} tags: felinae98/nonebot-bison:main
cache-from: type=gha
cache-to: type=gha,mode=max
docker-main-sentry:
name: Docker main sentry
runs-on: ubuntu-latest
needs: [build-frontend, test]
if: github.event_name != 'pull_request'
steps:
- uses: actions/checkout@v3
- name: Download frontend files
uses: actions/download-artifact@v2
with:
name: frontend
path: ./src/plugins/nonebot_bison/admin_page/dist
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./docker/Dockerfile_with_frontend_sentry
push: ${{ github.event_name != 'pull_request' }}
tags: felinae98/nonebot-bison:main-sentry
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max

View File

@ -2,6 +2,10 @@ name: Site Deploy(Preview)
on: on:
pull_request_target: pull_request_target:
paths:
- docs/**
- package.json
- yarn.lock
jobs: jobs:
preview: preview:

View File

@ -20,3 +20,4 @@ repos:
hooks: hooks:
- id: prettier - id: prettier
types_or: [markdown, ts, tsx] types_or: [markdown, ts, tsx]
exclude: 'CHANGELOG.md'

View File

@ -2,7 +2,21 @@
## 最近更新 ## 最近更新
- No changes ### 新功能
- 添加bilibili开播提醒 [@Sichongzou](https://github.com/Sichongzou) ([#60](https://github.com/felinae98/nonebot-bison/pull/60))
- 添加User-Agent配置 [@felinae98](https://github.com/felinae98) ([#78](https://github.com/felinae98/nonebot-bison/pull/78))
- 增加代理设置 [@felinae98](https://github.com/felinae98) ([#71](https://github.com/felinae98/nonebot-bison/pull/71))
- 增加Parse Target功能 [@felinae98](https://github.com/felinae98) ([#72](https://github.com/felinae98/nonebot-bison/pull/72))
### Bug 修复
- 捕获 JSONDecodeError [@felinae98](https://github.com/felinae98) ([#82](https://github.com/felinae98/nonebot-bison/pull/82))
- 捕获SSL异常 [@felinae98](https://github.com/felinae98) ([#75](https://github.com/felinae98/nonebot-bison/pull/75))
### 文档
- 完善开发文档 [@AzideCupric](https://github.com/AzideCupric) ([#80](https://github.com/felinae98/nonebot-bison/pull/80))
## v0.5.3 ## v0.5.3

View File

@ -3,7 +3,7 @@
[![pypi](https://badgen.net/pypi/v/nonebot-bison)](https://pypi.org/project/nonebot-bison/) [![pypi](https://badgen.net/pypi/v/nonebot-bison)](https://pypi.org/project/nonebot-bison/)
[![license](https://img.shields.io/github/license/felinae98/nonebot-bison)](https://github.com/felinae98/nonebot-bison/blob/main/LICENSE) [![license](https://img.shields.io/github/license/felinae98/nonebot-bison)](https://github.com/felinae98/nonebot-bison/blob/main/LICENSE)
[![felinae98](https://circleci.com/gh/felinae98/nonebot-bison.svg?style=shield)](https://circleci.com/gh/felinae98/nonebot-bison) [![action](https://img.shields.io/github/workflow/status/felinae98/nonebot-bison/test-build)](https://github.com/felinae98/nonebot-bison/actions/workflows/main.yml)
[![docker](https://img.shields.io/docker/image-size/felinae98/nonebot-bison)](https://hub.docker.com/r/felinae98/nonebot-bison) [![docker](https://img.shields.io/docker/image-size/felinae98/nonebot-bison)](https://hub.docker.com/r/felinae98/nonebot-bison)
[![codecov](https://codecov.io/gh/felinae98/nonebot-bison/branch/main/graph/badge.svg?token=QCFIODJOOA)](https://codecov.io/gh/felinae98/nonebot-bison) [![codecov](https://codecov.io/gh/felinae98/nonebot-bison/branch/main/graph/badge.svg?token=QCFIODJOOA)](https://codecov.io/gh/felinae98/nonebot-bison)
[![qq group](https://img.shields.io/badge/QQ%E7%BE%A4-868610060-orange)](https://qm.qq.com/cgi-bin/qm/qr?k=pXYMGB_e8b6so3QTqgeV6lkKDtEeYE4f&jump_from=webapi) [![qq group](https://img.shields.io/badge/QQ%E7%BE%A4-868610060-orange)](https://qm.qq.com/cgi-bin/qm/qr?k=pXYMGB_e8b6so3QTqgeV6lkKDtEeYE4f&jump_from=webapi)
@ -38,6 +38,8 @@
- 专栏 - 专栏
- 转发 - 转发
- 纯文字 - 纯文字
- Bilibili 直播
- 开播提醒
- RSS - RSS
- 富文本转换为纯文本 - 富文本转换为纯文本
- 提取出所有图片 - 提取出所有图片

View File

@ -31,6 +31,11 @@ sidebar: auto
本插件需要你的帮助!只需要会写简单的爬虫,就能给本插件适配新的网站。 本插件需要你的帮助!只需要会写简单的爬虫,就能给本插件适配新的网站。
::: danger
Nonebot 项目使用了全异步的处理方式所以你需要对异步Python asyncio 的机制有一定了解,当然,
依葫芦画瓢也是足够的
:::
## 基本概念 ## 基本概念
- `nonebot_bison.post.Post`: 可以理解为推送内容,其中包含需要发送的文字,图片,链接,平台信息等 - `nonebot_bison.post.Post`: 可以理解为推送内容,其中包含需要发送的文字,图片,链接,平台信息等
@ -53,7 +58,7 @@ sidebar: auto
例如微博Bilibili 例如微博Bilibili
- `nonebot_bison.platform.platform.StatusChange` 每次爬虫获取一个状态,在状态改变时发布推送 - `nonebot_bison.platform.platform.StatusChange` 每次爬虫获取一个状态,在状态改变时发布推送
例如:游戏开服提醒,主播上播提醒 例如:游戏开服提醒,主播上播提醒
- `nonebot_bison.platform.platform.SimplePost``NewMessage`相似,但是不过滤新的消息 - `nonebot_bison.platform.platform.SimplePost``NewMessage`相似,但是不过滤之前发过的
,每次发送全部消息 ,每次发送全部消息
例如:每日榜单定时发送 例如:每日榜单定时发送
@ -64,10 +69,61 @@ sidebar: auto
- 没有账号的概念 - 没有账号的概念
例如:游戏公告,教务处公告 例如:游戏公告,教务处公告
## 实现方法
现在你需要在`src/plugins/nonebot_bison/platform`下新建一个 py 文件, 现在你需要在`src/plugins/nonebot_bison/platform`下新建一个 py 文件,
在里面新建一个类,继承推送类型的基类,重载一些关键的函数,然后……就完成了,不需要修改别的东西了。 在里面新建一个类,继承推送类型的基类,重载一些关键的函数,然后……就完成了,不需要修改别的东西了。
任何一种订阅类型需要实现的方法/字段如下: ### 不同类型 Platform 的实现适配以及逻辑
- `nonebot_bison.platform.platform.NewMessage`
需要实现:
- `async get_sub_list(Target) -> list[RawPost]`
- `get_id(RawPost)`
- `get_date(RawPost)` (可选)
::: details 大致流程
1. 调用`get_sub_list`拿到 RawPost 列表
2. 调用`get_id`判断是否重复,如果没有重复就说明是新的 RawPost
3. 如果有`get_category``get_date`,则调用判断 RawPost 是否满足条件
4. 调用`parse`生成正式推文
:::
参考[nonebot_bison.platform.Weibo](https://github.com/felinae98/nonebot-bison/blob/v0.5.3/src/plugins/nonebot_bison/platform/weibo.py)
- `nonebot_bison.platform.platform.StatusChange`
需要实现:
- `async get_status(Target) -> Any`
- `compare_status(Target, old_status, new_status) -> list[RawPost]`
:::details 大致流程
1. `get_status`获取当前状态
2. 传入`compare_status`比较前状态
3. 通过则进入`parser`生成 Post
:::
参考[nonenot_bison.platform.AkVersion](https://github.com/felinae98/nonebot-bison/blob/v0.5.3/src/plugins/nonebot_bison/platform/arknights.py#L86)
- `nonebot_bison.platform.platform.SimplePost`
需要实现:
- `async get_sub_list(Target) -> list[RawPost]`
- `get_date(RawPost)` (可选)
::: details 大致流程
1. 调用`get_sub_list`拿到 RawPost 列表
2. 如果有`get_category``get_date`,则调用判断 RawPost 是否满足条件
3. 调用`parse`生成正式推文
:::
### 公共方法/成员
任何一种订阅类型需要实现的方法/成员如下:
- `schedule_type`, `schedule_kw` 调度的参数,本质是使用 apscheduler 的[trigger 参数](https://apscheduler.readthedocs.io/en/3.x/userguide.html?highlight=trigger#choosing-the-right-scheduler-job-store-s-executor-s-and-trigger-s)`schedule_type`可以是`date`,`interval``cron` - `schedule_type`, `schedule_kw` 调度的参数,本质是使用 apscheduler 的[trigger 参数](https://apscheduler.readthedocs.io/en/3.x/userguide.html?highlight=trigger#choosing-the-right-scheduler-job-store-s-executor-s-and-trigger-s)`schedule_type`可以是`date`,`interval``cron`
`schedule_kw`是对应的参数,一个常见的配置是`schedule_type=interval`, `schedule_kw={'seconds':30}` `schedule_kw`是对应的参数,一个常见的配置是`schedule_type=interval`, `schedule_kw={'seconds':30}`
@ -80,11 +136,40 @@ sidebar: auto
- `enable_tag` 平台发布内容是否带 Tag例如微博 - `enable_tag` 平台发布内容是否带 Tag例如微博
- `platform_name` 唯一的,英文的识别标识,比如`weibo` - `platform_name` 唯一的,英文的识别标识,比如`weibo`
- `async get_target_name(Target) -> Optional[str]` 通常用于获取帐号的名称,如果平台没有帐号概念,可以直接返回平台的`name` - `async get_target_name(Target) -> Optional[str]` 通常用于获取帐号的名称,如果平台没有帐号概念,可以直接返回平台的`name`
- `async parse(RawPost) -> Post`将获取到的 RawPost 处理成 Post
- `get_tags(RawPost) -> Optional[Collection[Tag]]` (可选) 从 RawPost 中提取 Tag - `get_tags(RawPost) -> Optional[Collection[Tag]]` (可选) 从 RawPost 中提取 Tag
- `get_category(RawPos) -> Optional[Category]` (可选)从 RawPost 中提取 Category - `get_category(RawPos) -> Optional[Category]` (可选)从 RawPost 中提取 Category
- `async parse(RawPost) -> Post` 将获取到的 RawPost 处理成 Post
- `async parse_target(str) -> Target` (可选)定制化处理传入用户输入的 Target 字符串,返回 Target一般是把用户的主页链接解析为 Target如果输入本身就是 Target则直接返回 Target
- `parse_target_promot` (可选)在要求用户输入 Target 的时候显示的提示文字
例如要适配微博,我希望 bot 搬运新的消息,所以微博的类应该这样定义: ### 特有的方法/成员
- `async get_sub_list(Target) -> list[RawPost]` 输入一个`Target`,输出一个`RawPost`的 list
- 对于`nonebot_bison.platform.platform.NewMessage`
`get_sub_list(Target) -> list[RawPost]` 用于获取对应 Target 的 RawPost 列表,与上一次`get_sub_list`获取的列表比较,过滤出新的 RawPost
- 对于`nonebot_bison.platform.platform.SimplePost`
`get_sub_list` 用于获取对应 Target 的 RawPost 列表,但不会与上次获取的结果进行比较,而是直接进行发送
- `get_id(RawPost) -> Any` 输入一个`RawPost`,从`RawPost`中获取一个唯一的 ID这个 ID 会用来判断这条`RawPost`是不是之前收到过
- `get_date(RawPost) -> Optional[int]` 输入一个`RawPost`,如果可以从`RawPost`中提取出发文的时间,返回发文时间的 timestamp否则返回`None`
- `async get_status(Target) -> Any`
- 对于`nonebot_bison.platform.platform.StatusChange`
`get_status`用于获取对应 Target 当前的状态,随后将获取的状态作为参数`new_status`传入`compare_status`
- `compare_status(self, target: Target, old_status, new_status) -> list[RawPost]`
- 对于`nonebot_bison.platform.platform.StatusChange`
`compare_status` 用于比较储存的`old_status`与新传入的`new_status`,并返回发生变更的 RawPost 列表
### 单元测试
当然我们非常希望你对自己适配的平台写一些单元测试
你可以参照`tests/platforms/test_*.py`中的内容对单元测试进行编写。
为保证多次运行测试的一致性,可以 mock http 的响应,测试的内容应包括[获取 RawPost](https://github.com/felinae98/nonebot-bison/blob/v0.5.3/tests/platforms/test_weibo.py#L59),处理成 Post
,测试分类以及提取 tag 等,当然最好和 rsshub 做一个交叉验证。
## 一些例子
例如要适配微博,我希望 bot 搬运新的消息,所以微博的类应该这样实现:
```python ```python
class Weibo(NewMessage): class Weibo(NewMessage):
@ -103,17 +188,26 @@ class Weibo(NewMessage):
schedule_type = "interval" schedule_type = "interval"
schedule_kw = {"seconds": 3} schedule_kw = {"seconds": 3}
has_target = True has_target = True
async def get_target_name(self, target: Target) -> Optional[str]:
#获取Target对应的用户名
...
async def get_sub_list(self, target: Target) -> list[RawPost]:
#获取对应Target的RawPost列表会与上一次get_sub_list获取的列表比较过滤出新的RawPost
...
def get_id(self, post: RawPost) -> Any:
#获取可以标识每个Rawpost的不与之前RawPost重复的id用于过滤出新的RawPost
...
def get_date(self, raw_post: RawPost) -> float:
#获取RawPost的发布时间若bot过滤出的新RawPost发布时间与当前时间差超过2小时该RawPost将被忽略可以返回None
...
def get_tags(self, raw_post: RawPost) -> Optional[list[Tag]]:
#获取RawPost中包含的微博话题#xxx#中的内容
...
def get_category(self, raw_post: RawPost) -> Category:
#获取该RawPost在该类定义categories的具体分类(转发?视频?图文?...)
...
async def parse(self, raw_post: RawPost) -> Post:
#将需要bot推送的RawPost处理成正式推送的Post
...
``` ```
当然我们非常希望你对自己适配的平台写一些单元测试,你可以模仿`tests/platforms/test_*.py`中的内容写
一些单元测试。为保证多次运行测试的一致性,可以 mock http 的响应,测试的内容包括获取 RawPost处理成 Post
,测试分类以及提取 tag 等,当然最好和 rsshub 做一个交叉验证。
::: danger
Nonebot 项目使用了全异步的处理方式所以你需要对异步Python asyncio 的机制有一定了解,当然,
依葫芦画瓢也是足够的
:::
## 类的方法与成员变量
## 方法与变量的定义

View File

@ -94,23 +94,27 @@ sidebar: auto
## 配置 ## 配置
可参考[源文件](https://github.com/felinae98/nonebot-bison/blob/main/src/plugins/nonebot_bison/plugin_config.py) ::: tip INFO
- 所有配置项可参考[源文件](https://github.com/felinae98/nonebot-bison/blob/main/src/plugins/nonebot_bison/plugin_config.py)
- **配置项的配置方法** 请参考[NoneBot 配置方式](https://v2.nonebot.dev/docs/tutorial/configuration#%E9%85%8D%E7%BD%AE%E6%96%B9%E5%BC%8F),在`.env`/`.env.*`文件中写入希望配置的 Bison 配置项
:::
- `BISON_CONFIG_PATH`: 插件存放配置文件的位置,如果不设定默认为项目目录下的`data`目录 - `BISON_CONFIG_PATH`: 插件存放配置文件的位置,如果不设定默认为项目目录下的`data`目录
- `BISON_USE_PIC`: 将文字渲染成图片后进行发送,多用于规避风控 - `BISON_USE_PIC`: 将文字渲染成图片后进行发送,多用于规避风控
- `BISON_BROWSER`: 本插件使用 Chrome 来渲染图片 - `BISON_BROWSER`: 本插件使用 Chrome 来渲染图片
- 使用 browserless 提供的 Chrome 管理服务,设置为`ws://xxxxxxxx`,值为 Chrome Endpoint推荐
- 使用 cdp 连接相关服务,设置为`wsc://xxxxxxxxx`
- 使用本地安装的 Chrome设置为`local:<chrome path>`,例如`local:/usr/bin/google-chrome-stable`
- 如果不进行配置,那么会在启动时候自动进行安装,在官方的 docker 镜像中已经安装了浏览器 - 如果不进行配置,那么会在启动时候自动进行安装,在官方的 docker 镜像中已经安装了浏览器
- 使用本地安装的 Chrome设置为`local:<chrome path>`,例如`local:/usr/bin/google-chrome-stable`
- 使用 cdp 连接相关服务,设置为`wsc://xxxxxxxxx`
- 使用 browserless 提供的 Chrome 管理服务,设置为`ws://xxxxxxxx`,值为 Chrome Endpoint
::: warning ::: warning
截止发布时,本项目尚不能完全与 browserless 兼容,目前建议使用镜像内自带的浏览器,即 截止发布时,本项目尚不能完全与 browserless 兼容,目前建议使用镜像内自带的浏览器,即
不要配置这个变量 不要配置这个变量
::: :::
- `BISON_SKIP_BROWSER_CHECK`: 是否在启动时自动下载浏览器,如果选择`False`会在用到浏览器时自动下载, - `BISON_SKIP_BROWSER_CHECK`: 是否在启动时自动下载浏览器,如果选择`False`会在用到浏览器时自动下载,
默认`True` 默认`True`
- `BISON_OUTER_URL`: 从外部访问服务器的地址,默认为`http://localhost:8080/bison`,如果你的插件部署 - `BISON_OUTER_URL`: 从外部访问服务器的地址,默认为`http://localhost:8080/bison/`,如果你的插件部署
在服务器上,建议配置为`http://<你的服务器ip>:8080/bison` 在服务器上,建议配置为`http://<你的服务器ip>:8080/bison/`
::: warning ::: warning
如果需要从外网或者 Docker 容器外访问后台页面,请确保`HOST=0.0.0.0` 如果需要从外网或者 Docker 容器外访问后台页面,请确保`HOST=0.0.0.0`
::: :::
@ -124,7 +128,7 @@ sidebar: auto
- `1`: 首条消息单独发送,剩余图片合并转发 - `1`: 首条消息单独发送,剩余图片合并转发
- `2`: 所有消息全部合并转发 - `2`: 所有消息全部合并转发
::: details 配置项示例 ::: details BISON_USE_PIC_MERGE 配置项示例
- 当`BISON_USE_PIC_MERGE=1`时: - 当`BISON_USE_PIC_MERGE=1`时:
![simple1](/images/forward-msg-simple1.png) ![simple1](/images/forward-msg-simple1.png)
@ -137,6 +141,7 @@ sidebar: auto
::: :::
- `BISON_PROXY`: 使用的代理连接,形如`http://<ip>:<port>`(可选) - `BISON_PROXY`: 使用的代理连接,形如`http://<ip>:<port>`(可选)
- `BISON_UA`: 使用的 User-Agent默认为 Chrome
## 使用 ## 使用
@ -154,12 +159,16 @@ sidebar: auto
所有命令都需要@bot 触发 所有命令都需要@bot 触发
- 添加订阅(仅管理员和群主和 SUPERUSER`添加订阅` - 添加订阅(仅管理员和群主和 SUPERUSER`添加订阅`
::: tip 关于中止订阅 ::: details 关于中止添加订阅
对于[**v0.5.1**](https://github.com/felinae98/nonebot-bison/releases/tag/v0.5.1)及以上的版本中,已经为`添加订阅`命令添加了中止订阅的功能。 对于[**v0.5.1**](https://github.com/felinae98/nonebot-bison/releases/tag/v0.5.1)及以上的版本中,已经为`添加订阅`命令添加了中止添加功能。
在添加订阅命令的~~几乎~~各个阶段,都可以向 Bot 发送`取消`消息来中止订阅过程(需要订阅发起者本人发送) `添加订阅`命令的~~几乎~~各个阶段,都可以向 Bot 发送`取消`消息来中止订阅过程(需要发起者本人发送)
::: :::
- 查询订阅:`查询订阅` - 查询订阅:`查询订阅`
- 删除订阅(仅管理员和群主和 SUPERUSER`删除订阅` - 删除订阅(仅管理员和群主和 SUPERUSER`删除订阅`
::: details 关于中止删除订阅
对于[**v0.5.3**](https://github.com/felinae98/nonebot-bison/releases/tag/v0.5.3)及以上的版本中,已经为`删除订阅`命令添加了中止删除功能。
`删除订阅`命令的~~几乎~~各个阶段,都可以向 Bot 发送`取消`消息来中止订阅过程(需要发起者本人发送)
:::
#### 私聊机器人获取后台地址 #### 私聊机器人获取后台地址
@ -178,8 +187,8 @@ sidebar: auto
#### 私聊机器人进行配置(需要 SUPERUER 权限) #### 私聊机器人进行配置(需要 SUPERUER 权限)
请私聊 bot`群管理` 请私聊 bot`群管理`
::: tip 关于中止订阅 ::: details 关于中止订阅
与普通的[`添加订阅`](#在本群中进行配置)命令一样,在`群管理`命令中使用的`添加订阅`命令也可以使用`取消`来中止订阅过程 与普通的[`添加订阅`/`删除订阅`](#在本群中进行配置)命令一样,在`群管理`命令中使用的`添加订阅`/`删除订阅`命令也可以使用`取消`来中止订阅过程
::: :::
### 所支持平台的 uid ### 所支持平台的 uid

109
poetry.lock generated
View File

@ -229,7 +229,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
name = "charset-normalizer" name = "charset-normalizer"
version = "2.0.12" version = "2.0.12"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main" category = "dev"
optional = false optional = false
python-versions = ">=3.5.0" python-versions = ">=3.5.0"
@ -275,14 +275,14 @@ six = ">=1.10"
[[package]] [[package]]
name = "coverage" name = "coverage"
version = "6.3.3" version = "6.4"
description = "Code coverage measurement for Python" description = "Code coverage measurement for Python"
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
[package.dependencies] [package.dependencies]
tomli = {version = "*", optional = true, markers = "extra == \"toml\""} tomli = {version = "*", optional = true, markers = "python_version < \"3.11\" and extra == \"toml\""}
[package.extras] [package.extras]
toml = ["tomli"] toml = ["tomli"]
@ -392,11 +392,11 @@ python-versions = ">=3.6"
[[package]] [[package]]
name = "httpcore" name = "httpcore"
version = "0.14.7" version = "0.15.0"
description = "A minimal low-level HTTP client." description = "A minimal low-level HTTP client."
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.7"
[package.dependencies] [package.dependencies]
anyio = ">=3.0.0,<4.0.0" anyio = ">=3.0.0,<4.0.0"
@ -421,22 +421,21 @@ test = ["Cython (>=0.29.24,<0.30.0)"]
[[package]] [[package]]
name = "httpx" name = "httpx"
version = "0.22.0" version = "0.23.0"
description = "The next generation HTTP client." description = "The next generation HTTP client."
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.7"
[package.dependencies] [package.dependencies]
certifi = "*" certifi = "*"
charset-normalizer = "*" httpcore = ">=0.15.0,<0.16.0"
httpcore = ">=0.14.5,<0.15.0"
rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
sniffio = "*" sniffio = "*"
[package.extras] [package.extras]
brotli = ["brotlicffi", "brotli"] 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)"] cli = ["click (>=8.0.0,<9.0.0)", "rich (>=10,<13)", "pygments (>=2.0.0,<3.0.0)"]
http2 = ["h2 (>=3,<5)"] http2 = ["h2 (>=3,<5)"]
socks = ["socksio (>=1.0.0,<2.0.0)"] socks = ["socksio (>=1.0.0,<2.0.0)"]
@ -1556,47 +1555,47 @@ cookiecutter = [
{file = "cookiecutter-1.7.3.tar.gz", hash = "sha256:6b9a4d72882e243be077a7397d0f1f76fe66cf3df91f3115dbb5330e214fa457"}, {file = "cookiecutter-1.7.3.tar.gz", hash = "sha256:6b9a4d72882e243be077a7397d0f1f76fe66cf3df91f3115dbb5330e214fa457"},
] ]
coverage = [ coverage = [
{file = "coverage-6.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df32ee0f4935a101e4b9a5f07b617d884a531ed5666671ff6ac66d2e8e8246d8"}, {file = "coverage-6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:50ed480b798febce113709846b11f5d5ed1e529c88d8ae92f707806c50297abf"},
{file = "coverage-6.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:75b5dbffc334e0beb4f6c503fb95e6d422770fd2d1b40a64898ea26d6c02742d"}, {file = "coverage-6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:26f8f92699756cb7af2b30720de0c5bb8d028e923a95b6d0c891088025a1ac8f"},
{file = "coverage-6.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:114944e6061b68a801c5da5427b9173a0dd9d32cd5fcc18a13de90352843737d"}, {file = "coverage-6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60c2147921da7f4d2d04f570e1838db32b95c5509d248f3fe6417e91437eaf41"},
{file = "coverage-6.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ab88a01cd180b5640ccc9c47232e31924d5f9967ab7edd7e5c91c68eee47a69"}, {file = "coverage-6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:750e13834b597eeb8ae6e72aa58d1d831b96beec5ad1d04479ae3772373a8088"},
{file = "coverage-6.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad8f9068f5972a46d50fe5f32c09d6ee11da69c560fcb1b4c3baea246ca4109b"}, {file = "coverage-6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af5b9ee0fc146e907aa0f5fb858c3b3da9199d78b7bb2c9973d95550bd40f701"},
{file = "coverage-6.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4cd696aa712e6cd16898d63cf66139dc70d998f8121ab558f0e1936396dbc579"}, {file = "coverage-6.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a022394996419142b33a0cf7274cb444c01d2bb123727c4bb0b9acabcb515dea"},
{file = "coverage-6.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c1a9942e282cc9d3ed522cd3e3cab081149b27ea3bda72d6f61f84eaf88c1a63"}, {file = "coverage-6.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5a78cf2c43b13aa6b56003707c5203f28585944c277c1f3f109c7b041b16bd39"},
{file = "coverage-6.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c06455121a089252b5943ea682187a4e0a5cf0a3fb980eb8e7ce394b144430a9"}, {file = "coverage-6.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9229d074e097f21dfe0643d9d0140ee7433814b3f0fc3706b4abffd1e3038632"},
{file = "coverage-6.3.3-cp310-cp310-win32.whl", hash = "sha256:cb5311d6ccbd22578c80028c5e292a7ab9adb91bd62c1982087fad75abe2e63d"}, {file = "coverage-6.4-cp310-cp310-win32.whl", hash = "sha256:fb45fe08e1abc64eb836d187b20a59172053999823f7f6ef4f18a819c44ba16f"},
{file = "coverage-6.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:6d4a6f30f611e657495cc81a07ff7aa8cd949144e7667c5d3e680d73ba7a70e4"}, {file = "coverage-6.4-cp310-cp310-win_amd64.whl", hash = "sha256:3cfd07c5889ddb96a401449109a8b97a165be9d67077df6802f59708bfb07720"},
{file = "coverage-6.3.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:79bf405432428e989cad7b8bc60581963238f7645ae8a404f5dce90236cc0293"}, {file = "coverage-6.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:03014a74023abaf5a591eeeaf1ac66a73d54eba178ff4cb1fa0c0a44aae70383"},
{file = "coverage-6.3.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:338c417613f15596af9eb7a39353b60abec9d8ce1080aedba5ecee6a5d85f8d3"}, {file = "coverage-6.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c82f2cd69c71698152e943f4a5a6b83a3ab1db73b88f6e769fabc86074c3b08"},
{file = "coverage-6.3.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db094a6a4ae6329ed322a8973f83630b12715654c197dd392410400a5bfa1a73"}, {file = "coverage-6.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b546cf2b1974ddc2cb222a109b37c6ed1778b9be7e6b0c0bc0cf0438d9e45a6"},
{file = "coverage-6.3.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1414e8b124611bf4df8d77215bd32cba6e3425da8ce9c1f1046149615e3a9a31"}, {file = "coverage-6.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc173f1ce9ffb16b299f51c9ce53f66a62f4d975abe5640e976904066f3c835d"},
{file = "coverage-6.3.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:93b16b08f94c92cab88073ffd185070cdcb29f1b98df8b28e6649145b7f2c90d"}, {file = "coverage-6.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c53ad261dfc8695062fc8811ac7c162bd6096a05a19f26097f411bdf5747aee7"},
{file = "coverage-6.3.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fbc86ae8cc129c801e7baaafe3addf3c8d49c9c1597c44bdf2d78139707c3c62"}, {file = "coverage-6.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:eef5292b60b6de753d6e7f2d128d5841c7915fb1e3321c3a1fe6acfe76c38052"},
{file = "coverage-6.3.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b5ba058610e8289a07db2a57bce45a1793ec0d3d11db28c047aae2aa1a832572"}, {file = "coverage-6.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:543e172ce4c0de533fa892034cce260467b213c0ea8e39da2f65f9a477425211"},
{file = "coverage-6.3.3-cp37-cp37m-win32.whl", hash = "sha256:8329635c0781927a2c6ae068461e19674c564e05b86736ab8eb29c420ee7dc20"}, {file = "coverage-6.4-cp37-cp37m-win32.whl", hash = "sha256:00c8544510f3c98476bbd58201ac2b150ffbcce46a8c3e4fb89ebf01998f806a"},
{file = "coverage-6.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:e5af1feee71099ae2e3b086ec04f57f9950e1be9ecf6c420696fea7977b84738"}, {file = "coverage-6.4-cp37-cp37m-win_amd64.whl", hash = "sha256:b84ab65444dcc68d761e95d4d70f3cfd347ceca5a029f2ffec37d4f124f61311"},
{file = "coverage-6.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e814a4a5a1d95223b08cdb0f4f57029e8eab22ffdbae2f97107aeef28554517e"}, {file = "coverage-6.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d548edacbf16a8276af13063a2b0669d58bbcfca7c55a255f84aac2870786a61"},
{file = "coverage-6.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:61f4fbf3633cb0713437291b8848634ea97f89c7e849c2be17a665611e433f53"}, {file = "coverage-6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:033ebec282793bd9eb988d0271c211e58442c31077976c19c442e24d827d356f"},
{file = "coverage-6.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3401b0d2ed9f726fadbfa35102e00d1b3547b73772a1de5508ef3bdbcb36afe7"}, {file = "coverage-6.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:742fb8b43835078dd7496c3c25a1ec8d15351df49fb0037bffb4754291ef30ce"},
{file = "coverage-6.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8586b177b4407f988731eb7f41967415b2197f35e2a6ee1a9b9b561f6323c8e9"}, {file = "coverage-6.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d55fae115ef9f67934e9f1103c9ba826b4c690e4c5bcf94482b8b2398311bf9c"},
{file = "coverage-6.3.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:892e7fe32191960da559a14536768a62e83e87bbb867e1b9c643e7e0fbce2579"}, {file = "coverage-6.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cd698341626f3c77784858427bad0cdd54a713115b423d22ac83a28303d1d95"},
{file = "coverage-6.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:afb03f981fadb5aed1ac6e3dd34f0488e1a0875623d557b6fad09b97a942b38a"}, {file = "coverage-6.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:62d382f7d77eeeaff14b30516b17bcbe80f645f5cf02bb755baac376591c653c"},
{file = "coverage-6.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cbe91bc84be4e5ef0b1480d15c7b18e29c73bdfa33e07d3725da7d18e1b0aff2"}, {file = "coverage-6.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:016d7f5cf1c8c84f533a3c1f8f36126fbe00b2ec0ccca47cc5731c3723d327c6"},
{file = "coverage-6.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:91502bf27cbd5c83c95cfea291ef387469f2387508645602e1ca0fd8a4ba7548"}, {file = "coverage-6.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:69432946f154c6add0e9ede03cc43b96e2ef2733110a77444823c053b1ff5166"},
{file = "coverage-6.3.3-cp38-cp38-win32.whl", hash = "sha256:c488db059848702aff30aa1d90ef87928d4e72e4f00717343800546fdbff0a94"}, {file = "coverage-6.4-cp38-cp38-win32.whl", hash = "sha256:83bd142cdec5e4a5c4ca1d4ff6fa807d28460f9db919f9f6a31babaaa8b88426"},
{file = "coverage-6.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6534fcdfb5c503affb6b1130db7b5bfc8a0f77fa34880146f7a5c117987d0"}, {file = "coverage-6.4-cp38-cp38-win_amd64.whl", hash = "sha256:4002f9e8c1f286e986fe96ec58742b93484195defc01d5cc7809b8f7acb5ece3"},
{file = "coverage-6.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cc692c9ee18f0dd3214843779ba6b275ee4bb9b9a5745ba64265bce911aefd1a"}, {file = "coverage-6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e4f52c272fdc82e7c65ff3f17a7179bc5f710ebc8ce8a5cadac81215e8326740"},
{file = "coverage-6.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:462105283de203df8de58a68c1bb4ba2a8a164097c2379f664fa81d6baf94b81"}, {file = "coverage-6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b5578efe4038be02d76c344007b13119b2b20acd009a88dde8adec2de4f630b5"},
{file = "coverage-6.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc972d829ad5ef4d4c5fcabd2bbe2add84ce8236f64ba1c0c72185da3a273130"}, {file = "coverage-6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8099ea680201c2221f8468c372198ceba9338a5fec0e940111962b03b3f716a"},
{file = "coverage-6.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:06f54765cdbce99901871d50fe9f41d58213f18e98b170a30ca34f47de7dd5e8"}, {file = "coverage-6.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a00441f5ea4504f5abbc047589d09e0dc33eb447dc45a1a527c8b74bfdd32c65"},
{file = "coverage-6.3.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7835f76a081787f0ca62a53504361b3869840a1620049b56d803a8cb3a9eeea3"}, {file = "coverage-6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e76bd16f0e31bc2b07e0fb1379551fcd40daf8cdf7e24f31a29e442878a827c"},
{file = "coverage-6.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6f5fee77ec3384b934797f1873758f796dfb4f167e1296dc00f8b2e023ce6ee9"}, {file = "coverage-6.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8d2e80dd3438e93b19e1223a9850fa65425e77f2607a364b6fd134fcd52dc9df"},
{file = "coverage-6.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:baa8be8aba3dd1e976e68677be68a960a633a6d44c325757aefaa4d66175050f"}, {file = "coverage-6.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:341e9c2008c481c5c72d0e0dbf64980a4b2238631a7f9780b0fe2e95755fb018"},
{file = "coverage-6.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4d06380e777dd6b35ee936f333d55b53dc4a8271036ff884c909cf6e94be8b6c"}, {file = "coverage-6.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:21e6686a95025927775ac501e74f5940cdf6fe052292f3a3f7349b0abae6d00f"},
{file = "coverage-6.3.3-cp39-cp39-win32.whl", hash = "sha256:f8cabc5fd0091976ab7b020f5708335033e422de25e20ddf9416bdce2b7e07d8"}, {file = "coverage-6.4-cp39-cp39-win32.whl", hash = "sha256:968ed5407f9460bd5a591cefd1388cc00a8f5099de9e76234655ae48cfdbe2c3"},
{file = "coverage-6.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c9441d57b0963cf8340268ad62fc83de61f1613034b79c2b1053046af0c5284"}, {file = "coverage-6.4-cp39-cp39-win_amd64.whl", hash = "sha256:e35217031e4b534b09f9b9a5841b9344a30a6357627761d4218818b865d45055"},
{file = "coverage-6.3.3-pp36.pp37.pp38-none-any.whl", hash = "sha256:d522f1dc49127eab0bfbba4e90fa068ecff0899bbf61bf4065c790ddd6c177fe"}, {file = "coverage-6.4-pp36.pp37.pp38-none-any.whl", hash = "sha256:e637ae0b7b481905358624ef2e81d7fb0b1af55f5ff99f9ba05442a444b11e45"},
{file = "coverage-6.3.3.tar.gz", hash = "sha256:2781c43bffbbec2b8867376d4d61916f5e9c4cc168232528562a61d1b4b01879"}, {file = "coverage-6.4.tar.gz", hash = "sha256:727dafd7f67a6e1cad808dc884bd9c5a2f6ef1f8f6d2f22b37b96cb0080d4f49"},
] ]
decorator = [ decorator = [
{file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
@ -1691,8 +1690,8 @@ h11 = [
{file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"}, {file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"},
] ]
httpcore = [ httpcore = [
{file = "httpcore-0.14.7-py3-none-any.whl", hash = "sha256:47d772f754359e56dd9d892d9593b6f9870a37aeb8ba51e9a88b09b3d68cfade"}, {file = "httpcore-0.15.0-py3-none-any.whl", hash = "sha256:1105b8b73c025f23ff7c36468e4432226cbb959176eab66864b8e31c4ee27fa6"},
{file = "httpcore-0.14.7.tar.gz", hash = "sha256:7503ec1c0f559066e7e39bc4003fd2ce023d01cf51793e3c173b864eb456ead1"}, {file = "httpcore-0.15.0.tar.gz", hash = "sha256:18b68ab86a3ccf3e7dc0f43598eaddcf472b602aba29f9aa6ab85fe2ada3980b"},
] ]
httptools = [ httptools = [
{file = "httptools-0.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcddfe70553be717d9745990dfdb194e22ee0f60eb8f48c0794e7bfeda30d2d5"}, {file = "httptools-0.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcddfe70553be717d9745990dfdb194e22ee0f60eb8f48c0794e7bfeda30d2d5"},
@ -1731,8 +1730,8 @@ httptools = [
{file = "httptools-0.4.0.tar.gz", hash = "sha256:2c9a930c378b3d15d6b695fb95ebcff81a7395b4f9775c4f10a076beb0b2c1ff"}, {file = "httptools-0.4.0.tar.gz", hash = "sha256:2c9a930c378b3d15d6b695fb95ebcff81a7395b4f9775c4f10a076beb0b2c1ff"},
] ]
httpx = [ httpx = [
{file = "httpx-0.22.0-py3-none-any.whl", hash = "sha256:e35e83d1d2b9b2a609ef367cc4c1e66fd80b750348b20cc9e19d1952fc2ca3f6"}, {file = "httpx-0.23.0-py3-none-any.whl", hash = "sha256:42974f577483e1e932c3cdc3cd2303e883cbfba17fe228b0f63589764d7b9c4b"},
{file = "httpx-0.22.0.tar.gz", hash = "sha256:d8e778f76d9bbd46af49e7f062467e3157a5a3d2ae4876a4bbfd8a51ed9c9cb4"}, {file = "httpx-0.23.0.tar.gz", hash = "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef"},
] ]
identify = [ identify = [
{file = "identify-2.5.0-py2.py3-none-any.whl", hash = "sha256:3acfe15a96e4272b4ec5662ee3e231ceba976ef63fd9980ed2ce9cc415df393f"}, {file = "identify-2.5.0-py2.py3-none-any.whl", hash = "sha256:3acfe15a96e4272b4ec5662ee3e231ceba976ef63fd9980ed2ce9cc415df393f"},

View File

@ -17,7 +17,7 @@ from nonebot.rule import to_me
from nonebot.typing import T_State from nonebot.typing import T_State
from .config import Config from .config import Config
from .platform import check_sub_target, platform_manager from .platform import Platform, check_sub_target, platform_manager
from .plugin_config import plugin_config from .plugin_config import plugin_config
from .types import Category, Target, User from .types import Category, Target, User
from .utils import parse_text from .utils import parse_text
@ -108,8 +108,13 @@ def do_add_sub(add_sub: Type[Matcher]):
"platform", _gen_prompt_template("{_prompt}"), [Depends(parse_platform)] "platform", _gen_prompt_template("{_prompt}"), [Depends(parse_platform)]
) )
async def init_id(state: T_State): async def init_id(state: T_State):
if platform_manager[state["platform"]].has_target: cur_platform = platform_manager[state["platform"]]
state["_prompt"] = "请输入订阅用户的id:\n查询id获取方法请回复:“查询”" if cur_platform.has_target:
state["_prompt"] = (
("1." + cur_platform.parse_target_promot + "\n2.")
if cur_platform.parse_target_promot
else ""
) + "请输入订阅用户的id\n查询id获取方法请回复:“查询”"
else: else:
state["id"] = "default" state["id"] = "default"
state["name"] = await platform_manager[state["platform"]].get_target_name( state["name"] = await platform_manager[state["platform"]].get_target_name(
@ -125,6 +130,8 @@ def do_add_sub(add_sub: Type[Matcher]):
raise LookupError raise LookupError
if target == "取消": if target == "取消":
raise KeyboardInterrupt raise KeyboardInterrupt
platform = platform_manager[state["platform"]]
target = await platform.parse_target(target)
name = await check_sub_target(state["platform"], target) name = await check_sub_target(state["platform"], target)
if not name: if not name:
raise ValueError raise ValueError
@ -141,6 +148,8 @@ def do_add_sub(add_sub: Type[Matcher]):
await add_sub.finish("已中止订阅") await add_sub.finish("已中止订阅")
except (ValueError): except (ValueError):
await add_sub.reject("id输入错误") await add_sub.reject("id输入错误")
except (Platform.ParseTargetException):
await add_sub.reject("不能从你的输入中提取出id请检查你输入的内容是否符合预期")
else: else:
await add_sub.send( await add_sub.send(
"即将订阅的用户为:{} {} {}\n如有错误请输入“取消”重新订阅".format( "即将订阅的用户为:{} {} {}\n如有错误请输入“取消”重新订阅".format(

View File

@ -1,10 +1,11 @@
import json import json
import re
from typing import Any, Optional from typing import Any, Optional
from ..post import Post from ..post import Post
from ..types import Category, RawPost, Tag, Target from ..types import Category, RawPost, Tag, Target
from ..utils import http_client from ..utils import http_client
from .platform import CategoryNotSupport, NewMessage from .platform import CategoryNotSupport, NewMessage, StatusChange
class Bilibili(NewMessage): class Bilibili(NewMessage):
@ -25,6 +26,7 @@ class Bilibili(NewMessage):
schedule_kw = {"seconds": 10} schedule_kw = {"seconds": 10}
name = "B站" name = "B站"
has_target = True has_target = True
parse_target_promot = "请输入用户主页的链接"
async def get_target_name(self, target: Target) -> Optional[str]: async def get_target_name(self, target: Target) -> Optional[str]:
async with http_client() as client: async with http_client() as client:
@ -36,6 +38,16 @@ class Bilibili(NewMessage):
return None return None
return res_data["data"]["name"] return res_data["data"]["name"]
async def parse_target(self, target_text: str) -> Target:
if re.match(r"\d+", target_text):
return Target(target_text)
elif match := re.match(
r"(?:https?://)?space\.bilibili\.com/(\d+)", target_text
):
return Target(match.group(1))
else:
raise self.ParseTargetException()
async def get_sub_list(self, target: Target) -> list[RawPost]: async def get_sub_list(self, target: Target) -> list[RawPost]:
async with http_client() as client: async with http_client() as client:
params = {"host_uid": target, "offset": 0, "need_top": 0} params = {"host_uid": target, "offset": 0, "need_top": 0}
@ -143,3 +155,73 @@ class Bilibili(NewMessage):
else: else:
raise CategoryNotSupport(post_type) 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)
class Bilibililive(StatusChange):
# Author : Sichongzou
# Date : 2022-5-18 8:54
# Description : bilibili开播提醒
# E-mail : 1557157806@qq.com
categories = {}
platform_name = "bilibili-live"
enable_tag = True
enabled = True
is_common = True
schedule_type = "interval"
schedule_kw = {"seconds": 10}
name = "Bilibili直播"
has_target = True
async def get_target_name(self, target: Target) -> Optional[str]:
async with http_client() as client:
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"]:
return None
return res_data["data"]["name"]
async def get_status(self, target: Target):
async with http_client() as client:
params = {"mid": target}
res = await client.get(
"https://api.bilibili.com/x/space/acc/info",
params=params,
timeout=4.0,
)
res_dict = json.loads(res.text)
if res_dict["code"] == 0:
info = {}
info["uid"] = res_dict["data"]["mid"]
info["uname"] = res_dict["data"]["name"]
info["live_state"] = res_dict["data"]["live_room"]["liveStatus"]
info["room_id"] = res_dict["data"]["live_room"]["roomid"]
info["title"] = res_dict["data"]["live_room"]["title"]
info["cover"] = res_dict["data"]["live_room"]["cover"]
return info
else:
return []
def compare_status(self, target: Target, old_status, new_status) -> list[RawPost]:
if (
new_status["live_state"] != old_status["live_state"]
and new_status["live_state"] == 1
):
return [new_status]
else:
return []
async def parse(self, raw_post: RawPost) -> Post:
url = "https://live.bilibili.com/{}".format(raw_post["room_id"])
pic = [raw_post["cover"]]
target_name = raw_post["uname"]
title = raw_post["title"]
return Post(
self.name,
text=title,
url=url,
pics=pic,
target_name=target_name,
compress=True,
)

View File

@ -1,3 +1,4 @@
import re
from typing import Any, Optional from typing import Any, Optional
from ..post import Post from ..post import Post
@ -17,6 +18,7 @@ class NcmArtist(NewMessage):
schedule_kw = {"minutes": 1} schedule_kw = {"minutes": 1}
name = "网易云-歌手" name = "网易云-歌手"
has_target = True has_target = True
parse_target_promot = "请输入歌手主页包含数字ID的链接"
async def get_target_name(self, target: Target) -> Optional[str]: async def get_target_name(self, target: Target) -> Optional[str]:
async with http_client() as client: async with http_client() as client:
@ -29,6 +31,16 @@ class NcmArtist(NewMessage):
return return
return res_data["artist"]["name"] return res_data["artist"]["name"]
async def parse_target(self, target_text: str) -> Target:
if re.match(r"^\d+$", target_text):
return Target(target_text)
elif match := re.match(
r"(?:https?://)?music\.163\.com/#/artist\?id=(\d+)", target_text
):
return Target(match.group(1))
else:
raise self.ParseTargetException()
async def get_sub_list(self, target: Target) -> list[RawPost]: async def get_sub_list(self, target: Target) -> list[RawPost]:
async with http_client() as client: async with http_client() as client:
res = await client.get( res = await client.get(

View File

@ -1,3 +1,4 @@
import re
from typing import Any, Optional from typing import Any, Optional
from ..post import Post from ..post import Post
@ -17,6 +18,7 @@ class NcmRadio(NewMessage):
schedule_kw = {"minutes": 10} schedule_kw = {"minutes": 10}
name = "网易云-电台" name = "网易云-电台"
has_target = True has_target = True
parse_target_promot = "请输入主播电台主页包含数字ID的链接"
async def get_target_name(self, target: Target) -> Optional[str]: async def get_target_name(self, target: Target) -> Optional[str]:
async with http_client() as client: async with http_client() as client:
@ -30,6 +32,16 @@ class NcmRadio(NewMessage):
return return
return res_data["programs"][0]["radio"]["name"] return res_data["programs"][0]["radio"]["name"]
async def parse_target(self, target_text: str) -> Target:
if re.match(r"^\d+$", target_text):
return Target(target_text)
elif match := re.match(
r"(?:https?://)?music\.163\.com/#/djradio\?id=(\d+)", target_text
):
return Target(match.group(1))
else:
raise self.ParseTargetException()
async def get_sub_list(self, target: Target) -> list[RawPost]: async def get_sub_list(self, target: Target) -> list[RawPost]:
async with http_client() as client: async with http_client() as client:
res = await client.post( res = await client.post(

View File

@ -1,3 +1,5 @@
import json
import ssl
import time import time
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from collections import defaultdict from collections import defaultdict
@ -47,6 +49,7 @@ class Platform(metaclass=RegistryABCMeta, base=True):
enable_tag: bool enable_tag: bool
store: dict[Target, Any] store: dict[Target, Any]
platform_name: str platform_name: str
parse_target_promot: Optional[str] = None
@abstractmethod @abstractmethod
async def get_target_name(self, target: Target) -> Optional[str]: async def get_target_name(self, target: Target) -> Optional[str]:
@ -58,6 +61,25 @@ class Platform(metaclass=RegistryABCMeta, base=True):
) -> list[tuple[User, list[Post]]]: ) -> list[tuple[User, list[Post]]]:
... ...
async def do_fetch_new_post(
self, target: Target, users: list[UserSubInfo]
) -> list[tuple[User, list[Post]]]:
try:
return await self.fetch_new_post(target, users)
except httpx.RequestError as err:
logger.warning(
"network connection error: {}, url: {}".format(
type(err), err.request.url
)
)
return []
except ssl.SSLError as err:
logger.warning(f"ssl error: {err}")
return []
except json.JSONDecodeError as err:
logger.warning(f"json error, parsing: {err.doc}")
return []
@abstractmethod @abstractmethod
async def parse(self, raw_post: RawPost) -> Post: async def parse(self, raw_post: RawPost) -> Post:
... ...
@ -73,6 +95,12 @@ class Platform(metaclass=RegistryABCMeta, base=True):
self.reverse_category[val] = key self.reverse_category[val] = key
self.store = dict() self.store = dict()
class ParseTargetException(Exception):
pass
async def parse_target(self, target_string: str) -> Target:
return Target(target_string)
@abstractmethod @abstractmethod
def get_tags(self, raw_post: RawPost) -> Optional[Collection[Tag]]: def get_tags(self, raw_post: RawPost) -> Optional[Collection[Tag]]:
"Return Tag list of given RawPost" "Return Tag list of given RawPost"
@ -219,7 +247,6 @@ class NewMessage(MessageProcess, abstract=True):
async def fetch_new_post( async def fetch_new_post(
self, target: Target, users: list[UserSubInfo] self, target: Target, users: list[UserSubInfo]
) -> list[tuple[User, list[Post]]]: ) -> list[tuple[User, list[Post]]]:
try:
post_list = await self.get_sub_list(target) post_list = await self.get_sub_list(target)
new_posts = await self.filter_common_with_diff(target, post_list) new_posts = await self.filter_common_with_diff(target, post_list)
if not new_posts: if not new_posts:
@ -236,13 +263,6 @@ class NewMessage(MessageProcess, abstract=True):
res = await self.dispatch_user_post(target, new_posts, users) res = await self.dispatch_user_post(target, new_posts, users)
self.parse_cache = {} self.parse_cache = {}
return res return res
except httpx.RequestError as err:
logger.warning(
"network connection error: {}, url: {}".format(
type(err), err.request.url
)
)
return []
class StatusChange(Platform, abstract=True): class StatusChange(Platform, abstract=True):
@ -263,7 +283,6 @@ class StatusChange(Platform, abstract=True):
async def fetch_new_post( async def fetch_new_post(
self, target: Target, users: list[UserSubInfo] self, target: Target, users: list[UserSubInfo]
) -> list[tuple[User, list[Post]]]: ) -> list[tuple[User, list[Post]]]:
try:
new_status = await self.get_status(target) new_status = await self.get_status(target)
res = [] res = []
if old_status := self.get_stored_data(target): if old_status := self.get_stored_data(target):
@ -280,13 +299,6 @@ class StatusChange(Platform, abstract=True):
res = await self.dispatch_user_post(target, diff, users) res = await self.dispatch_user_post(target, diff, users)
self.set_stored_data(target, new_status) self.set_stored_data(target, new_status)
return res return res
except httpx.RequestError as err:
logger.warning(
"network connection error: {}, url: {}".format(
type(err), err.request.url
)
)
return []
class SimplePost(MessageProcess, abstract=True): class SimplePost(MessageProcess, abstract=True):
@ -295,7 +307,6 @@ class SimplePost(MessageProcess, abstract=True):
async def fetch_new_post( async def fetch_new_post(
self, target: Target, users: list[UserSubInfo] self, target: Target, users: list[UserSubInfo]
) -> list[tuple[User, list[Post]]]: ) -> list[tuple[User, list[Post]]]:
try:
new_posts = await self.get_sub_list(target) new_posts = await self.get_sub_list(target)
if not new_posts: if not new_posts:
return [] return []
@ -311,13 +322,6 @@ class SimplePost(MessageProcess, abstract=True):
res = await self.dispatch_user_post(target, new_posts, users) res = await self.dispatch_user_post(target, new_posts, users)
self.parse_cache = {} self.parse_cache = {}
return res return res
except httpx.RequestError as err:
logger.warning(
"network connection error: {}, url: {}".format(
type(err), err.request.url
)
)
return []
class NoTargetGroup(Platform, abstract=True): class NoTargetGroup(Platform, abstract=True):

View File

@ -28,6 +28,7 @@ class Weibo(NewMessage):
schedule_type = "interval" schedule_type = "interval"
schedule_kw = {"seconds": 3} schedule_kw = {"seconds": 3}
has_target = True has_target = True
parse_target_promot = "请输入用户主页包含数字UID的链接"
async def get_target_name(self, target: Target) -> Optional[str]: async def get_target_name(self, target: Target) -> Optional[str]:
async with http_client() as client: async with http_client() as client:
@ -41,6 +42,15 @@ class Weibo(NewMessage):
else: else:
return None return None
async def parse_target(self, target_text: str) -> Target:
if re.match(r"\d+", target_text):
return Target(target_text)
elif match := re.match(r"(?:https?://)?weibo\.com/u/(\d+)", target_text):
# 都2202年了应该不会有http了吧不过还是防一手
return Target(match.group(1))
else:
raise self.ParseTargetException()
async def get_sub_list(self, target: Target) -> list[RawPost]: async def get_sub_list(self, target: Target) -> list[RawPost]:
async with http_client() as client: async with http_client() as client:
params = {"containerid": "107603" + target} params = {"containerid": "107603" + target}
@ -133,9 +143,9 @@ class Weibo(NewMessage):
"https://m.weibo.cn/detail/{}".format(info["mid"]), headers=header "https://m.weibo.cn/detail/{}".format(info["mid"]), headers=header
) )
try: try:
full_json_text = re.search( match = re.search(r'"status": ([\s\S]+),\s+"call"', res.text)
r'"status": ([\s\S]+),\s+"call"', res.text assert match
).group(1) full_json_text = match.group(1)
info = json.loads(full_json_text) info = json.loads(full_json_text)
except: except:
logger.info( logger.info(

View File

@ -14,10 +14,11 @@ class PlugConfig(BaseSettings):
bison_filter_log: bool = False bison_filter_log: bool = False
bison_to_me: bool = True bison_to_me: bool = True
bison_skip_browser_check: bool = False bison_skip_browser_check: bool = False
bison_use_pic_merge: int = 0 # 多图片时启用图片合并转发(仅限群),当bison_use_queue为False时该配置不会生效 bison_use_pic_merge: int = 0 # 多图片时启用图片合并转发(仅限群)
# 0不启用1首条消息单独发送剩余照片合并转发2以及以上所有消息全部合并转发 # 0不启用1首条消息单独发送剩余照片合并转发2以及以上所有消息全部合并转发
bison_resend_times: int = 0 bison_resend_times: int = 0
bison_proxy: Optional[str] bison_proxy: Optional[str]
bison_ua: str = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"
class Config: class Config:
extra = "ignore" extra = "ignore"

View File

@ -59,7 +59,7 @@ async def fetch_and_send(target_type: str):
send_user_list, send_user_list,
) )
) )
to_send = await platform_manager[target_type].fetch_new_post( to_send = await platform_manager[target_type].do_fetch_new_post(
target, send_userinfo_list target, send_userinfo_list
) )
if not to_send: if not to_send:

View File

@ -4,9 +4,8 @@ import httpx
from ..plugin_config import plugin_config from ..plugin_config import plugin_config
if plugin_config.bison_proxy: http_client = functools.partial(
http_client = functools.partial( httpx.AsyncClient,
httpx.AsyncClient, proxies=plugin_config.bison_proxy proxies=plugin_config.bison_proxy or None,
) headers={"user-agent": plugin_config.bison_ua},
else: )
http_client = httpx.AsyncClient

View File

@ -0,0 +1,114 @@
{
"code": 0,
"message": "0",
"ttl": 1,
"data": {
"mid": 13164144,
"name": "魔法Zc目录",
"sex": "男",
"face": "http://i0.hdslb.com/bfs/face/a84fa10f90f7060d0336384954ee1cde7a8e9bc6.jpg",
"face_nft": 0,
"sign": "每日18:00~22:00欢乐直播请勿在任何乌有相关内容中刷Zc尊重角色商务合作qq271374252",
"rank": 10000,
"level": 6,
"jointime": 0,
"moral": 0,
"silence": 0,
"coins": 0,
"fans_badge": true,
"fans_medal": {
"show": false,
"wear": false,
"medal": null
},
"official": {
"role": 1,
"title": "bilibili 2021百大UP主、知名游戏UP主、直播高能主播",
"desc": "",
"type": 0
},
"vip": {
"type": 2,
"status": 1,
"due_date": 1702051200000,
"vip_pay_type": 0,
"theme_type": 0,
"label": {
"path": "",
"text": "年度大会员",
"label_theme": "annual_vip",
"text_color": "#FFFFFF",
"bg_style": 1,
"bg_color": "#FB7299",
"border_color": ""
},
"avatar_subscript": 1,
"nickname_color": "#FB7299",
"role": 3,
"avatar_subscript_url": "http://i0.hdslb.com/bfs/vip/icon_Certification_big_member_22_3x.png"
},
"pendant": {
"pid": 3399,
"name": "2233幻星集",
"image": "http://i0.hdslb.com/bfs/garb/item/20c07ded13498a5b12db99660c766ddd92ecfe31.png",
"expire": 0,
"image_enhance": "http://i0.hdslb.com/bfs/garb/item/20c07ded13498a5b12db99660c766ddd92ecfe31.png",
"image_enhance_frame": ""
},
"nameplate": {
"nid": 1,
"name": "黄金殿堂",
"image": "http://i2.hdslb.com/bfs/face/82896ff40fcb4e7c7259cb98056975830cb55695.png",
"image_small": "http://i0.hdslb.com/bfs/face/627e342851dfda6fe7380c2fa0cbd7fae2e61533.png",
"level": "稀有勋章",
"condition": "单个自制视频总播放数\u003e=100万"
},
"user_honour_info": {
"mid": 0,
"colour": null,
"tags": []
},
"is_followed": true,
"top_photo": "http://i2.hdslb.com/bfs/space/853fea2728651588a2cdef0a1e586bcefff8e3d8.png",
"theme": {},
"sys_notice": {},
"live_room": {
"roomStatus": 1,
"liveStatus": 0,
"url": "https://live.bilibili.com/3044248?broadcast_type=0\u0026is_room_feed=1",
"title": "【Zc】早朝危机合约",
"cover": "http://i0.hdslb.com/bfs/live/new_room_cover/cf7d4d3b2f336c6dba299644c3af952c5db82612.jpg",
"roomid": 3044248,
"roundStatus": 1,
"broadcast_type": 0,
"watched_show": {
"switch": true,
"num": 13753,
"text_small": "1.3万",
"text_large": "1.3万人看过",
"icon": "https://i0.hdslb.com/bfs/live/a725a9e61242ef44d764ac911691a7ce07f36c1d.png",
"icon_location": "",
"icon_web": "https://i0.hdslb.com/bfs/live/8d9d0f33ef8bf6f308742752d13dd0df731df19c.png"
}
},
"birthday": "07-21",
"school": {
"name": ""
},
"profession": {
"name": "",
"department": "",
"title": "",
"is_show": 0
},
"tags": [
"评论区UP主",
"目标是星辰大海"
],
"series": {
"user_upgrade_status": 3,
"show_upgrade_window": false
},
"is_senior_member": 1
}
}

View File

@ -0,0 +1 @@
{"code":0,"message":"0","ttl":1,"data":{"mid":161775300,"name":"明日方舟","sex":"保密","face":"http://i0.hdslb.com/bfs/face/89154378c06a5ed332c40c2ca56f50cd641c0c90.jpg","face_nft":0,"sign":"重铸未来 方舟启航","rank":10000,"level":6,"jointime":0,"moral":0,"silence":0,"coins":0,"fans_badge":true,"fans_medal":{"show":false,"wear":false,"medal":null},"official":{"role":3,"title":"明日方舟官方账号","desc":"","type":1},"vip":{"type":2,"status":1,"due_date":1648828800000,"vip_pay_type":0,"theme_type":0,"label":{"path":"","text":"年度大会员","label_theme":"annual_vip","text_color":"#FFFFFF","bg_style":1,"bg_color":"#FB7299","border_color":""},"avatar_subscript":1,"nickname_color":"#FB7299","role":3,"avatar_subscript_url":"http://i0.hdslb.com/bfs/vip/icon_Certification_big_member_22_3x.png"},"pendant":{"pid":5305,"name":"明日方舟音律系列","image":"http://i0.hdslb.com/bfs/garb/item/615a1653281141ddf64cbb98c792ddaee78f7f40.png","expire":0,"image_enhance":"http://i0.hdslb.com/bfs/garb/item/516ecdf2d495a62f1bac31497c831b711823140c.webp","image_enhance_frame":"http://i0.hdslb.com/bfs/garb/item/c0751afbf950373c260254d02768eabf30ff3906.png"},"nameplate":{"nid":0,"name":"","image":"","image_small":"","level":"","condition":""},"user_honour_info":{"mid":0,"colour":null,"tags":[]},"is_followed":true,"top_photo":"http://i1.hdslb.com/bfs/space/6c6084808ec5bdff1985acc05ce0e126c49ad76e.png","theme":{},"sys_notice":{},"live_room":{"roomStatus":1,"liveStatus":0,"url":"https://live.bilibili.com/5555734?broadcast_type=0\u0026is_room_feed=1","title":"《明日方舟》2022新春前瞻特辑","cover":"http://i0.hdslb.com/bfs/live/new_room_cover/79af83a27f6001c1acfb47d1c0b879290f7c3308.jpg","roomid":5555734,"roundStatus":1,"broadcast_type":0,"watched_show":{"switch":true,"num":13033,"text_small":"1.3万","text_large":"1.3万人看过","icon":"https://i0.hdslb.com/bfs/live/a725a9e61242ef44d764ac911691a7ce07f36c1d.png","icon_location":"","icon_web":"https://i0.hdslb.com/bfs/live/8d9d0f33ef8bf6f308742752d13dd0df731df19c.png"}},"birthday":"","school":null,"profession":{"name":"","department":"","title":"","is_show":0},"tags":null,"series":{"user_upgrade_status":3,"show_upgrade_window":false},"is_senior_member":0}}

View File

@ -1,3 +1,4 @@
import typing
from datetime import datetime from datetime import datetime
import pytest import pytest
@ -14,6 +15,10 @@ def bing_dy_list():
return get_json("bilibili_bing_list.json")["data"]["cards"] return get_json("bilibili_bing_list.json")["data"]["cards"]
if typing.TYPE_CHECKING:
from nonebot_bison.platform.bilibili import Bilibili
@pytest.fixture @pytest.fixture
def bilibili(app: App): def bilibili(app: App):
from nonebot_bison.platform import platform_manager from nonebot_bison.platform import platform_manager
@ -76,3 +81,20 @@ async def test_fetch_new(bilibili, dummy_user_subinfo):
post.text post.text
== "#罗德厨房——回甘##明日方舟#\r\n明日方舟官方美食漫画,正式开餐。\r\n往事如烟,安然即好。\r\nMenu 01高脚羽兽烤串与罗德岛的领袖\r\n\r\n哔哩哔哩漫画阅读https://manga.bilibili.com/detail/mc31998?from=manga_search\r\n\r\n关注并转发本动态我们将会在5月27日抽取10位博士赠送【兔兔奇境】周边礼盒一份。 互动抽奖" == "#罗德厨房——回甘##明日方舟#\r\n明日方舟官方美食漫画,正式开餐。\r\n往事如烟,安然即好。\r\nMenu 01高脚羽兽烤串与罗德岛的领袖\r\n\r\n哔哩哔哩漫画阅读https://manga.bilibili.com/detail/mc31998?from=manga_search\r\n\r\n关注并转发本动态我们将会在5月27日抽取10位博士赠送【兔兔奇境】周边礼盒一份。 互动抽奖"
) )
async def test_parse_target(bilibili: "Bilibili"):
from nonebot_bison.platform.platform import Platform
res = await bilibili.parse_target(
"https://space.bilibili.com/161775300?from=search&seid=130517740606234234234&spm_id_from=333.337.0.0"
)
assert res == "161775300"
res2 = await bilibili.parse_target(
"space.bilibili.com/161775300?from=search&seid=130517740606234234234&spm_id_from=333.337.0.0"
)
assert res2 == "161775300"
with pytest.raises(Platform.ParseTargetException):
await bilibili.parse_target(
"https://www.bilibili.com/video/BV1qP4y1g738?spm_id_from=333.999.0.0"
)

View File

@ -0,0 +1,44 @@
from datetime import datetime
import feedparser
import pytest
import respx
from httpx import Response
from nonebug.app import App
from pytz import timezone
from .utils import get_file, get_json
@pytest.fixture
def bili_live(app: App):
from nonebot_bison.platform import platform_manager
return platform_manager["bilibili-live"]
@pytest.mark.asyncio
@respx.mock
async def test_fetch_bilibili_live_status(bili_live, dummy_user_subinfo):
mock_bili_live_status = get_json("bili_live_status.json")
bili_live_router = respx.get(
"https://api.bilibili.com/x/space/acc/info?mid=13164144"
)
bili_live_router.mock(return_value=Response(200, json=mock_bili_live_status))
target = "13164144"
res = await bili_live.fetch_new_post(target, [dummy_user_subinfo])
assert bili_live_router.called
assert len(res) == 0
mock_bili_live_status["data"]["live_room"]["liveStatus"] = 1
bili_live_router.mock(return_value=Response(200, json=mock_bili_live_status))
res2 = await bili_live.fetch_new_post(target, [dummy_user_subinfo])
post = res2[0][1][0]
assert post.target_type == "Bilibili直播"
assert post.text == "【Zc】早朝危机合约"
assert post.url == "https://live.bilibili.com/3044248"
assert post.target_name == "魔法Zc目录"
assert post.pics == [
"http://i0.hdslb.com/bfs/live/new_room_cover/cf7d4d3b2f336c6dba299644c3af952c5db82612.jpg"
]
assert post.compress == True

View File

@ -1,4 +1,5 @@
import time import time
import typing
import pytest import pytest
import respx import respx
@ -7,6 +8,9 @@ from nonebug.app import App
from .utils import get_json from .utils import get_json
if typing.TYPE_CHECKING:
from nonebot_bison.platform.ncm_artist import NcmArtist
@pytest.fixture @pytest.fixture
def ncm_artist(app: App): def ncm_artist(app: App):
@ -48,3 +52,16 @@ async def test_fetch_new(ncm_artist, ncm_artist_0, ncm_artist_1, dummy_user_subi
assert post.target_type == "ncm-artist" assert post.target_type == "ncm-artist"
assert post.text == "新专辑发布Y1K" assert post.text == "新专辑发布Y1K"
assert post.url == "https://music.163.com/#/album?id=131074504" assert post.url == "https://music.163.com/#/album?id=131074504"
async def test_parse_target(ncm_artist: "NcmArtist"):
from nonebot_bison.platform.platform import Platform
res = await ncm_artist.parse_target("32540734")
assert res == "32540734"
res = await ncm_artist.parse_target("https://music.163.com/#/artist?id=32540734")
assert res == "32540734"
res = await ncm_artist.parse_target("music.163.com/#/artist?id=32540734")
assert res == "32540734"
with pytest.raises(Platform.ParseTargetException):
await ncm_artist.parse_target("music.163.com/#/rad?id=32540734")

View File

@ -1,4 +1,5 @@
import time import time
import typing
import pytest import pytest
import respx import respx
@ -7,6 +8,9 @@ from nonebug.app import App
from .utils import get_json from .utils import get_json
if typing.TYPE_CHECKING:
from nonebot_bison.platform.ncm_radio import NcmRadio
@pytest.fixture @pytest.fixture
def ncm_radio(app: App): def ncm_radio(app: App):
@ -53,3 +57,14 @@ async def test_fetch_new(ncm_radio, ncm_radio_0, ncm_radio_1, dummy_user_subinfo
"http://p1.music.126.net/H5em5xUNIYXcjJhOmeaSqQ==/109951166647436789.jpg" "http://p1.music.126.net/H5em5xUNIYXcjJhOmeaSqQ==/109951166647436789.jpg"
] ]
assert post.target_name == "《明日方舟》游戏原声OST" assert post.target_name == "《明日方舟》游戏原声OST"
async def test_parse_target(ncm_radio: "NcmRadio"):
res = await ncm_radio.parse_target("https://music.163.com/#/djradio?id=793745436")
assert res == "793745436"
res = await ncm_radio.parse_target("music.163.com/#/djradio?id=793745436")
assert res == "793745436"
res = await ncm_radio.parse_target("793745436")
assert res == "793745436"
with pytest.raises(ncm_radio.ParseTargetException):
await ncm_radio.parse_target("music.163.com/#/alm?id=793745436")

View File

@ -1,3 +1,4 @@
import typing
from datetime import datetime from datetime import datetime
import feedparser import feedparser
@ -9,6 +10,9 @@ from pytz import timezone
from .utils import get_file, get_json from .utils import get_file, get_json
if typing.TYPE_CHECKING:
from nonebot_bison.platform.weibo import Weibo
@pytest.fixture @pytest.fixture
def weibo(app: App): def weibo(app: App):
@ -132,3 +136,16 @@ def test_chaohua_tag(weibo):
tags = weibo.get_tags(test_post) tags = weibo.get_tags(test_post)
assert "刚出生的小羊驼长啥样" in tags assert "刚出生的小羊驼长啥样" in tags
assert "小羊驼三三超话" in tags assert "小羊驼三三超话" in tags
async def test_parse_target(weibo: "Weibo"):
from nonebot_bison.platform.platform import Platform
res = await weibo.parse_target("https://weibo.com/u/6441489862")
assert res == "6441489862"
res = await weibo.parse_target("weibo.com/u/6441489862")
assert res == "6441489862"
res = await weibo.parse_target("6441489862")
assert res == "6441489862"
with pytest.raises(Platform.ParseTargetException):
await weibo.parse_target("https://weibo.com/arknights")

View File

@ -67,6 +67,7 @@ async def test_abort_add_on_id(app: App):
from nonebot_bison.config import Config from nonebot_bison.config import Config
from nonebot_bison.config_manager import add_sub_matcher, common_platform from nonebot_bison.config_manager import add_sub_matcher, common_platform
from nonebot_bison.platform import platform_manager from nonebot_bison.platform import platform_manager
from nonebot_bison.platform.weibo import Weibo
config = Config() config = Config()
config.user_target.truncate() config.user_target.truncate()
@ -103,7 +104,7 @@ async def test_abort_add_on_id(app: App):
ctx.receive_event(bot, event_2) ctx.receive_event(bot, event_2)
ctx.should_call_send( ctx.should_call_send(
event_2, event_2,
Message(BotReply.add_reply_on_id), Message(BotReply.add_reply_on_id(Weibo)),
True, True,
) )
event_abort = fake_group_message_event( event_abort = fake_group_message_event(
@ -127,6 +128,7 @@ async def test_abort_add_on_cats(app: App):
from nonebot_bison.config import Config from nonebot_bison.config import Config
from nonebot_bison.config_manager import add_sub_matcher, common_platform from nonebot_bison.config_manager import add_sub_matcher, common_platform
from nonebot_bison.platform import platform_manager from nonebot_bison.platform import platform_manager
from nonebot_bison.platform.weibo import Weibo
config = Config() config = Config()
config.user_target.truncate() config.user_target.truncate()
@ -167,7 +169,7 @@ async def test_abort_add_on_cats(app: App):
ctx.receive_event(bot, event_2) ctx.receive_event(bot, event_2)
ctx.should_call_send( ctx.should_call_send(
event_2, event_2,
Message(BotReply.add_reply_on_id), Message(BotReply.add_reply_on_id(Weibo)),
True, True,
) )
event_3 = fake_group_message_event( event_3 = fake_group_message_event(
@ -207,6 +209,7 @@ async def test_abort_add_on_tag(app: App):
from nonebot_bison.config import Config from nonebot_bison.config import Config
from nonebot_bison.config_manager import add_sub_matcher, common_platform from nonebot_bison.config_manager import add_sub_matcher, common_platform
from nonebot_bison.platform import platform_manager from nonebot_bison.platform import platform_manager
from nonebot_bison.platform.weibo import Weibo
config = Config() config = Config()
config.user_target.truncate() config.user_target.truncate()
@ -247,7 +250,7 @@ async def test_abort_add_on_tag(app: App):
ctx.receive_event(bot, event_2) ctx.receive_event(bot, event_2)
ctx.should_call_send( ctx.should_call_send(
event_2, event_2,
Message(BotReply.add_reply_on_id), Message(BotReply.add_reply_on_id(Weibo)),
True, True,
) )
event_3 = fake_group_message_event( event_3 = fake_group_message_event(

View File

@ -64,6 +64,7 @@ async def test_add_with_target(app: App):
from nonebot_bison.config import Config from nonebot_bison.config import Config
from nonebot_bison.config_manager import add_sub_matcher, common_platform from nonebot_bison.config_manager import add_sub_matcher, common_platform
from nonebot_bison.platform import platform_manager from nonebot_bison.platform import platform_manager
from nonebot_bison.platform.weibo import Weibo
config = Config() config = Config()
config.user_target.truncate() config.user_target.truncate()
@ -115,7 +116,7 @@ async def test_add_with_target(app: App):
ctx.receive_event(bot, event_3) ctx.receive_event(bot, event_3)
ctx.should_call_send( ctx.should_call_send(
event_3, event_3,
Message(BotReply.add_reply_on_id), Message(BotReply.add_reply_on_id(Weibo)),
True, True,
) )
event_4_err = fake_group_message_event( event_4_err = fake_group_message_event(
@ -181,6 +182,7 @@ async def test_add_with_target_no_cat(app: App):
from nonebot_bison.config import Config from nonebot_bison.config import Config
from nonebot_bison.config_manager import add_sub_matcher, common_platform from nonebot_bison.config_manager import add_sub_matcher, common_platform
from nonebot_bison.platform import platform_manager from nonebot_bison.platform import platform_manager
from nonebot_bison.platform.ncm_artist import NcmArtist
config = Config() config = Config()
config.user_target.truncate() config.user_target.truncate()
@ -208,7 +210,7 @@ async def test_add_with_target_no_cat(app: App):
ctx.receive_event(bot, event_3) ctx.receive_event(bot, event_3)
ctx.should_call_send( ctx.should_call_send(
event_3, event_3,
Message(BotReply.add_reply_on_id), Message(BotReply.add_reply_on_id(NcmArtist)),
True, True,
) )
event_4_ok = fake_group_message_event( event_4_ok = fake_group_message_event(
@ -332,6 +334,7 @@ async def test_add_with_get_id(app: App):
from nonebot_bison.config import Config from nonebot_bison.config import Config
from nonebot_bison.config_manager import add_sub_matcher, common_platform from nonebot_bison.config_manager import add_sub_matcher, common_platform
from nonebot_bison.platform import platform_manager from nonebot_bison.platform import platform_manager
from nonebot_bison.platform.weibo import Weibo
config = Config() config = Config()
config.user_target.truncate() config.user_target.truncate()
@ -373,7 +376,7 @@ async def test_add_with_get_id(app: App):
ctx.receive_event(bot, event_3) ctx.receive_event(bot, event_3)
ctx.should_call_send( ctx.should_call_send(
event_3, event_3,
Message(BotReply.add_reply_on_id), Message(BotReply.add_reply_on_id(Weibo)),
True, True,
) )
event_4_query = fake_group_message_event( event_4_query = fake_group_message_event(
@ -387,8 +390,8 @@ async def test_add_with_get_id(app: App):
True, True,
) )
""" """
关于Message([MessageSegment(*BotReply.add_reply_on_id_input_search())]): 关于Message([MessageSegment(*BotReply.add_reply_on_id_input_search())])
异客知道为什么要在这里这样写 知道为什么要在这里这样写
没有[]的话assert不了(should_call_send使用[MessageSegment(...)]的格式进行比较) 没有[]的话assert不了(should_call_send使用[MessageSegment(...)]的格式进行比较)
不在这里MessageSegment()的话也assert不了(指不能让add_reply_on_id_input_search直接返回一个MessageSegment对象) 不在这里MessageSegment()的话也assert不了(指不能让add_reply_on_id_input_search直接返回一个MessageSegment对象)
amen amen
@ -405,3 +408,125 @@ async def test_add_with_get_id(app: App):
ctx.should_finished() ctx.should_finished()
subs = config.list_subscribe(10000, "group") subs = config.list_subscribe(10000, "group")
assert len(subs) == 0 assert len(subs) == 0
@pytest.mark.asyncio
@respx.mock
async def test_add_with_bilibili_target_parser(app: App):
from nonebot.adapters.onebot.v11.event import Sender
from nonebot.adapters.onebot.v11.message import Message
from nonebot_bison.config import Config
from nonebot_bison.config_manager import add_sub_matcher, common_platform
from nonebot_bison.platform import platform_manager
from nonebot_bison.platform.bilibili import Bilibili
config = Config()
config.user_target.truncate()
ak_list_router = respx.get(
"https://api.bilibili.com/x/space/acc/info?mid=161775300"
)
ak_list_router.mock(
return_value=Response(200, json=get_json("bilibili_arknights_profile.json"))
)
async with app.test_matcher(add_sub_matcher) as ctx:
bot = ctx.create_bot()
event_1 = fake_group_message_event(
message=Message("添加订阅"),
sender=Sender(card="", nickname="test", role="admin"),
to_me=True,
)
ctx.receive_event(bot, event_1)
ctx.should_pass_rule()
ctx.should_call_send(
event_1,
Message(
BotReply.add_reply_on_platform(
platform_manager=platform_manager, common_platform=common_platform
)
),
True,
)
event_2 = fake_group_message_event(
message=Message("全部"), sender=Sender(card="", nickname="test", role="admin")
)
ctx.receive_event(bot, event_2)
ctx.should_rejected()
ctx.should_call_send(
event_2,
BotReply.add_reply_on_platform_input_allplatform(platform_manager),
True,
)
event_3 = fake_group_message_event(
message=Message("bilibili"), sender=fake_admin_user
)
ctx.receive_event(bot, event_3)
assert Bilibili.parse_target_promot
ctx.should_call_send(
event_3,
Message(BotReply.add_reply_on_id(Bilibili)),
True,
)
event_4_err1 = fake_group_message_event(
message=Message(
"https://live.bilibili.com/5555734?broadcast_type=0&is_room_feed=1&spm_id_from=333.999.0.0"
),
sender=fake_admin_user,
)
ctx.receive_event(bot, event_4_err1)
ctx.should_call_send(
event_4_err1, BotReply.add_reply_on_target_parse_input_error, True
)
ctx.should_rejected()
event_4_err1 = fake_group_message_event(
message=Message(
"https://space.bilibili.com/ark161775300?from=search&seid=13051774060625135297&spm_id_from=333.337.0.0"
),
sender=fake_admin_user,
)
ctx.receive_event(bot, event_4_err1)
ctx.should_call_send(
event_4_err1, BotReply.add_reply_on_target_parse_input_error, True
)
ctx.should_rejected()
event_4_ok = fake_group_message_event(
message=Message(
"https://space.bilibili.com/161775300?from=search&seid=13051774060625135297&spm_id_from=333.337.0.0"
),
sender=fake_admin_user,
)
ctx.receive_event(bot, event_4_ok)
ctx.should_call_send(
event_4_ok,
BotReply.add_reply_on_target_confirm("bilibili", "明日方舟", "161775300"),
True,
)
ctx.should_call_send(
event_4_ok,
Message(BotReply.add_reply_on_cats(platform_manager, "bilibili")),
True,
)
event_5_ok = fake_group_message_event(
message=Message("视频"), sender=fake_admin_user
)
ctx.receive_event(bot, event_5_ok)
ctx.should_call_send(event_5_ok, Message(BotReply.add_reply_on_tags), True)
event_6 = fake_group_message_event(
message=Message("全部标签"), sender=fake_admin_user
)
ctx.receive_event(bot, event_6)
ctx.should_call_send(
event_6, BotReply.add_reply_subscribe_success("明日方舟"), True
)
ctx.should_finished()
subs = config.list_subscribe(10000, "group")
assert len(subs) == 1
sub = subs[0]
assert sub["target"] == "161775300"
assert sub["tags"] == []
assert sub["cats"] == [platform_manager["bilibili"].reverse_category["视频"]]
assert sub["target_type"] == "bilibili"
assert sub["target_name"] == "明日方舟"

View File

@ -1,6 +1,5 @@
import pytest import pytest
from nonebug import App from nonebug import App
from nonebug.fixture import nonebug_init
async def test_without_proxy(app: App): async def test_without_proxy(app: App):
@ -8,6 +7,8 @@ async def test_without_proxy(app: App):
c = http_client() c = http_client()
assert not c._mounts assert not c._mounts
req = c.build_request("GET", "http://example.com")
assert "Chrome" in req.headers["User-Agent"]
@pytest.mark.parametrize( @pytest.mark.parametrize(

View File

@ -1,3 +1,4 @@
from ast import Str
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from typing_extensions import Literal from typing_extensions import Literal
@ -130,8 +131,18 @@ class BotReply:
def add_reply_subscribe_success(name): def add_reply_subscribe_success(name):
return "添加 {} 成功".format(name) return "添加 {} 成功".format(name)
@staticmethod
def add_reply_on_id(platform: object) -> Str:
base_text = "请输入订阅用户的id\n查询id获取方法请回复:“查询”"
extra_text = (
("1." + platform.parse_target_promot + "\n2.")
if platform.parse_target_promot
else ""
)
return extra_text + base_text
add_reply_on_id_input_error = "id输入错误" add_reply_on_id_input_error = "id输入错误"
add_reply_on_target_parse_input_error = "不能从你的输入中提取出id请检查你输入的内容是否符合预期"
add_reply_on_platform_input_error = "平台输入错误" add_reply_on_platform_input_error = "平台输入错误"
add_reply_on_id = "请输入订阅用户的id:\n查询id获取方法请回复:“查询”"
add_reply_on_tags = '请输入要订阅的tag订阅所有tag输入"全部标签"' add_reply_on_tags = '请输入要订阅的tag订阅所有tag输入"全部标签"'
add_reply_abort = "已中止订阅" add_reply_abort = "已中止订阅"