mirror of
https://github.com/suyiiyii/nonebot-bison.git
synced 2025-06-06 20:06:12 +08:00
Merge branch 'main' into feat/db
This commit is contained in:
commit
861441eb2b
14
.github/actions/build-docs/action.yml
vendored
Normal file
14
.github/actions/build-docs/action.yml
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
name: Build Docs
|
||||
description: Build Docs
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Setup Node Environment
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
- name: Build Frontend
|
||||
shell: bash
|
||||
run: |
|
||||
yarn install
|
||||
yarn docs:build
|
15
.github/actions/build-frontend/action.yml
vendored
Normal file
15
.github/actions/build-frontend/action.yml
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
name: Build Frontend
|
||||
description: Build Frontend
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Setup Node Environment
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
- name: Build Frontend
|
||||
shell: bash
|
||||
working-directory: ./admin-frontend
|
||||
run: |
|
||||
yarn install
|
||||
yarn build
|
18
.github/actions/setup-node/action.yml
vendored
Normal file
18
.github/actions/setup-node/action.yml
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
name: Setup Node
|
||||
description: Setup Node
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "16"
|
||||
|
||||
- id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
shell: bash
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
40
.github/actions/setup-python/action.yml
vendored
Normal file
40
.github/actions/setup-python/action.yml
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
name: Setup Python
|
||||
description: Setup Python
|
||||
|
||||
inputs:
|
||||
python-version:
|
||||
description: Python version
|
||||
required: false
|
||||
default: "3.9"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
|
||||
- name: Install poetry
|
||||
uses: Gr1N/setup-poetry@v7
|
||||
|
||||
- name: Cache Windows dependencies
|
||||
uses: actions/cache@v2
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
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
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
with:
|
||||
path: ~/.cache/pypoetry/virtualenvs
|
||||
key: ${{ runner.os }}-poetry-${{ inputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
|
||||
|
||||
- name: Cache macOS dependencies
|
||||
uses: actions/cache@v2
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
with:
|
||||
path: ~/Library/Caches/pypoetry/virtualenvs
|
||||
key: ${{ runner.os }}-poetry-${{ inputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
|
36
.github/release-drafter.yml
vendored
Normal file
36
.github/release-drafter.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
template: $CHANGES
|
||||
category-template: "### $TITLE"
|
||||
name-template: "Release v$RESOLVED_VERSION 🌈"
|
||||
tag-template: "v$RESOLVED_VERSION"
|
||||
change-template: "- $TITLE [@$AUTHOR](https://github.com/$AUTHOR) ([#$NUMBER]($URL))"
|
||||
change-title-escapes: '\<&'
|
||||
exclude-labels:
|
||||
- "dependencies"
|
||||
- "skip-changelog"
|
||||
categories:
|
||||
- title: "破坏性变更"
|
||||
labels:
|
||||
- "Breaking"
|
||||
- title: "新功能"
|
||||
labels:
|
||||
- "feature"
|
||||
- "enhancement"
|
||||
- title: "Bug 修复"
|
||||
labels:
|
||||
- "fix"
|
||||
- "bugfix"
|
||||
- "bug"
|
||||
- title: "文档"
|
||||
labels:
|
||||
- "documentation"
|
||||
version-resolver:
|
||||
major:
|
||||
labels:
|
||||
- "major"
|
||||
minor:
|
||||
labels:
|
||||
- "minor"
|
||||
patch:
|
||||
labels:
|
||||
- "patch"
|
||||
default: patch
|
137
.github/workflows/main.yml
vendored
Normal file
137
.github/workflows/main.yml
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
name: test-build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-frontend:
|
||||
name: Build Frontend
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Build Frontend
|
||||
uses: ./.github/actions/build-frontend
|
||||
|
||||
- name: Upload dist
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: frontend
|
||||
path: ./admin-frontend/build/
|
||||
test:
|
||||
name: Test Coverage
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: build-frontend
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.9", "3.10"]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
fail-fast: false
|
||||
env:
|
||||
OS: ${{ matrix.os }}
|
||||
PYTHON_VERSION: ${{ matrix.python-version }}
|
||||
|
||||
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: Setup Python environment
|
||||
uses: ./.github/actions/setup-python
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install prerequisites
|
||||
run: poetry install
|
||||
|
||||
- name: Run Pytest
|
||||
run: poetry run pytest --cov-report xml --cov=./src/plugins/nonebot_bison -k 'not compare and not render'
|
||||
|
||||
- name: Upload coverage report
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
env_vars: OS,PYTHON_VERSION
|
||||
docker-main:
|
||||
name: Docker main
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-frontend, test]
|
||||
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
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
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-to: type=gha,mode=max
|
39
.github/workflows/release-drafter.yml
vendored
Normal file
39
.github/workflows/release-drafter.yml
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
name: Release Drafter
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
branches:
|
||||
- main
|
||||
types:
|
||||
- closed
|
||||
|
||||
jobs:
|
||||
update-release-drafter:
|
||||
runs-on: ubuntu-latest
|
||||
concurrency:
|
||||
group: pull-request-changelog
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: release-drafter/release-drafter@v5
|
||||
id: release-drafter
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Update Changelog
|
||||
uses: docker://ghcr.io/nonebot/auto-changelog:master
|
||||
with:
|
||||
latest_changes_position: '# Change Log\n\n'
|
||||
latest_changes_title: '## 最近更新'
|
||||
replace_regex: '(?<=## 最近更新\n)[\s\S]*?(?=\n## )'
|
||||
changelog_body: ${{ steps.release-drafter.outputs.body }}
|
||||
commit_and_push: false
|
||||
|
||||
- name: Commit and Push
|
||||
run: |
|
||||
git config user.name github-actions[bot]
|
||||
git config user.email github-actions[bot]@users.noreply.github.com
|
||||
git add .
|
||||
git diff-index --quiet HEAD || git commit -m ":memo: Update changelog"
|
||||
git push
|
35
.github/workflows/release-trigger.yml
vendored
Normal file
35
.github/workflows/release-trigger.yml
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
name: Trigger Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
archive:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: main
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
|
||||
- name: Setup Python environment
|
||||
uses: ./.github/actions/setup-python
|
||||
|
||||
- run: echo "TAG_NAME=v$(poetry version -s)" >> $GITHUB_ENV
|
||||
|
||||
- name: Archive Changelog
|
||||
uses: docker://ghcr.io/nonebot/auto-changelog:master
|
||||
with:
|
||||
archive_regex: '(?<=## )最近更新(?=\n)'
|
||||
archive_title: ${{ env.TAG_NAME }}
|
||||
commit_and_push: false
|
||||
|
||||
- name: Push Tag
|
||||
run: |
|
||||
git config user.name github-actions[bot]
|
||||
git config user.email github-actions[bot]@users.noreply.github.com
|
||||
git add .
|
||||
git commit -m ":bookmark: Release $(poetry version -s)"
|
||||
git tag ${{ env.TAG_NAME }}
|
||||
git push && git push --tags
|
128
.github/workflows/release.yml
vendored
Normal file
128
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
jobs:
|
||||
build-frontend:
|
||||
name: Build Frontend
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Build Frontend
|
||||
uses: ./.github/actions/build-frontend
|
||||
|
||||
- name: Upload dist
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: frontend
|
||||
path: ./admin-frontend/build/
|
||||
publish-pypi-github:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-frontend
|
||||
|
||||
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: Setup Python environment
|
||||
uses: ./.github/actions/setup-python
|
||||
|
||||
- run: |
|
||||
echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
|
||||
- uses: release-drafter/release-drafter@v5
|
||||
with:
|
||||
name: Release ${{ env.TAG_NAME }}
|
||||
tag: ${{ env.TAG_NAME }}
|
||||
publish: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Publish PyPI and Github
|
||||
run: |
|
||||
poetry publish --build -u ${{ secrets.PYPI_USERNAME }} -p ${{ secrets.PYPI_PASSWORD }} || echo "Already pushed to pypi"
|
||||
gh release upload --clobber ${{ env.TAG_NAME }} dist/*
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
publish-docker:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-frontend
|
||||
|
||||
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
|
||||
|
||||
- run: |
|
||||
echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
|
||||
- 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
|
||||
push: true
|
||||
tags: |
|
||||
felinae98/nonebot-bison:latest
|
||||
felinae98/nonebot-bison:${{ env.TAG_NAME }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Build Sentry and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile_with_frontend_sentry
|
||||
push: true
|
||||
tags: felinae98/nonebot-bison:${{ env.TAG_NAME }}-sentry
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
deploy-web:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Build Docs
|
||||
uses: ./.github/actions/build-docs
|
||||
|
||||
- name: Deploy to Netlify
|
||||
uses: nwtgck/actions-netlify@v1
|
||||
with:
|
||||
publish-dir: "./docs/.vuepress/dist"
|
||||
production-deploy: true
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
deploy-message: "Deploy ${{ env.TAG_NAME }}"
|
||||
enable-commit-comment: false
|
||||
alias: ${{ env.TAG_NAME }}
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
|
36
.github/workflows/website-preview.yml
vendored
Normal file
36
.github/workflows/website-preview.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
name: Site Deploy(Preview)
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
|
||||
jobs:
|
||||
preview:
|
||||
runs-on: ubuntu-latest
|
||||
concurrency:
|
||||
group: pull-request-preview-${{ github.event.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Build Docs
|
||||
uses: ./.github/actions/build-docs
|
||||
|
||||
- name: Get Deploy Name
|
||||
run: |
|
||||
echo "DEPLOY_NAME=deploy-preview-${{ github.event.number }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Deploy to Netlify
|
||||
uses: nwtgck/actions-netlify@v1
|
||||
with:
|
||||
publish-dir: "./docs/.vuepress/dist"
|
||||
production-deploy: false
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
deploy-message: "Deploy ${{ env.DEPLOY_NAME }}@${{ github.sha }}"
|
||||
enable-commit-comment: false
|
||||
alias: ${{ env.DEPLOY_NAME }}
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
|
@ -20,3 +20,4 @@ repos:
|
||||
hooks:
|
||||
- id: prettier
|
||||
types_or: [markdown, ts, tsx]
|
||||
exclude: 'CHANGELOG.md'
|
||||
|
105
CHANGELOG.md
105
CHANGELOG.md
@ -1,47 +1,46 @@
|
||||
# Change Log
|
||||
|
||||
## [0.2.11] - 2021-06-17
|
||||
## 最近更新
|
||||
|
||||
- 增加了简单的单元测试
|
||||
- 增加了管理员直接管理订阅的能力
|
||||
### 新功能
|
||||
|
||||
## [0.3.0] - 2021-07-06
|
||||
- 增加代理设置 [@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))
|
||||
|
||||
- 微博 tag 支持
|
||||
- 修复 bug
|
||||
- 增加微博超话和纯文字支持
|
||||
- 更改浏览器配置
|
||||
- 将“来源”移动到文末
|
||||
- 使用组合来构建新的 platform,新增状态改变类型订阅
|
||||
## v0.5.3
|
||||
|
||||
## [0.3.1] - 2021-07-10
|
||||
- on_command 设置 block=True (#63) @MeetWq
|
||||
- 修复 issue#51 存在的问题并修改相应的单元测试 (#52) @AzideCupric
|
||||
- 增添关于添加订阅命令中中止订阅的相关文档 (#45) @AzideCupric
|
||||
|
||||
- 修复不发送来源
|
||||
- 发送 RSS 订阅的 title
|
||||
- 修复浏览器渲染问题
|
||||
### Bug 修复
|
||||
|
||||
## [0.3.2] - 2021-09-28
|
||||
- 修复#66 (#69) @felinae98
|
||||
|
||||
- 增加 NoTargetGroup
|
||||
- 增加 1x3 拼图的支持
|
||||
- 增加网易云
|
||||
## [0.5.2]
|
||||
|
||||
## [0.3.3] - 2021-09-28
|
||||
- 修复了微博获取全文时接口失效的问题
|
||||
- 修复了 bilibili 空列表时的报错
|
||||
|
||||
- 修复拼图问题
|
||||
## [0.5.1]
|
||||
|
||||
## [0.4.0] - 2021-11-18
|
||||
- 使用了新的在私聊中进行群管理的方式:从`管理-*`替换为`群管理`命令
|
||||
- 默认关闭自动重发功能
|
||||
- 添加了 [推送消息合并转发功能](https://nonebot-bison.vercel.app/usage/#%E9%85%8D%E7%BD%AE)
|
||||
- 添加了`添加订阅`命令事件的中途取消功能
|
||||
- 优化了`添加订阅`命令的聊天处理逻辑
|
||||
|
||||
- 项目更名为 nonebot-bison
|
||||
## [0.5.0]
|
||||
|
||||
## [0.4.1] - 2021-11-31
|
||||
- 添加了 FF14
|
||||
- 去掉了自己维护的 playwright,转向[nonebot-plugin-htmlrender](https://github.com/kexue-z/nonebot-plugin-htmlrender)
|
||||
- 支持了 nonebot 2.0.0beta
|
||||
|
||||
- 加入了管理后台
|
||||
## [0.4.4]
|
||||
|
||||
## [0.4.2]
|
||||
|
||||
并没有做什么只是为了修复前端文件没有正确打包的问题开了个新的版本号
|
||||
推上 pypi
|
||||
- 又双叒叕重构了一下
|
||||
- 修复了 Docker 中 Playwright 下载的浏览器版本不正确问题
|
||||
- 加入了猴子补丁,使 Windows 里能运行 Playwright
|
||||
|
||||
## [0.4.3]
|
||||
|
||||
@ -52,27 +51,47 @@
|
||||
- 前端可以刷新了(之前居然不可以)
|
||||
- 在镜像里塞进了浏览器(导致镜像体积起飞)
|
||||
|
||||
## [0.4.4]
|
||||
## [0.4.2]
|
||||
|
||||
- 又双叒叕重构了一下
|
||||
- 修复了 Docker 中 Playwright 下载的浏览器版本不正确问题
|
||||
- 加入了猴子补丁,使 Windows 里能运行 Playwright
|
||||
并没有做什么只是为了修复前端文件没有正确打包的问题开了个新的版本号
|
||||
推上 pypi
|
||||
|
||||
## [0.5.0]
|
||||
## [0.4.1] - 2021-11-31
|
||||
|
||||
- 添加了 FF14
|
||||
- 去掉了自己维护的 playwright,转向[nonebot-plugin-htmlrender](https://github.com/kexue-z/nonebot-plugin-htmlrender)
|
||||
- 支持了 nonebot 2.0.0beta
|
||||
- 加入了管理后台
|
||||
|
||||
## [0.5.1]
|
||||
## [0.4.0] - 2021-11-18
|
||||
|
||||
- 使用了新的在私聊中进行群管理的方式:从`管理-*`替换为`群管理`命令
|
||||
- 默认关闭自动重发功能
|
||||
- 添加了 [推送消息合并转发功能](https://nonebot-bison.vercel.app/usage/#%E9%85%8D%E7%BD%AE)
|
||||
- 添加了`添加订阅`命令事件的中途取消功能
|
||||
- 优化了`添加订阅`命令的聊天处理逻辑
|
||||
- 项目更名为 nonebot-bison
|
||||
|
||||
## [0.5.2]
|
||||
## [0.3.3] - 2021-09-28
|
||||
|
||||
- 修复了微博获取全文时接口失效的问题
|
||||
- 修复了 bilibili 空列表时的报错
|
||||
- 修复拼图问题
|
||||
|
||||
## [0.3.2] - 2021-09-28
|
||||
|
||||
- 增加 NoTargetGroup
|
||||
- 增加 1x3 拼图的支持
|
||||
- 增加网易云
|
||||
|
||||
## [0.3.1] - 2021-07-10
|
||||
|
||||
- 修复不发送来源
|
||||
- 发送 RSS 订阅的 title
|
||||
- 修复浏览器渲染问题
|
||||
|
||||
## [0.3.0] - 2021-07-06
|
||||
|
||||
- 微博 tag 支持
|
||||
- 修复 bug
|
||||
- 增加微博超话和纯文字支持
|
||||
- 更改浏览器配置
|
||||
- 将“来源”移动到文末
|
||||
- 使用组合来构建新的 platform,新增状态改变类型订阅
|
||||
|
||||
## [0.2.11] - 2021-06-17
|
||||
|
||||
- 增加了简单的单元测试
|
||||
- 增加了管理员直接管理订阅的能力
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
[](https://pypi.org/project/nonebot-bison/)
|
||||
[](https://github.com/felinae98/nonebot-bison/blob/main/LICENSE)
|
||||
[](https://circleci.com/gh/felinae98/nonebot-bison)
|
||||
[](https://github.com/felinae98/nonebot-bison/actions/workflows/main.yml)
|
||||
[](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)
|
||||
@ -31,6 +31,7 @@
|
||||
- 图文
|
||||
- 视频
|
||||
- 纯文字
|
||||
- 转发
|
||||
- Bilibili
|
||||
- 视频
|
||||
- 图文
|
||||
@ -44,6 +45,7 @@
|
||||
- 塞壬唱片新闻
|
||||
- 游戏内公告
|
||||
- 版本更新等通知
|
||||
- 泰拉记事社漫画
|
||||
- 网易云音乐
|
||||
- 歌手发布新专辑
|
||||
- 电台更新
|
||||
|
@ -22,7 +22,7 @@
|
||||
"react-dom": "^17.0.2",
|
||||
"react-redux": "^7.2.6",
|
||||
"react-router-dom": "^5.3.0",
|
||||
"react-scripts": "4.0.3",
|
||||
"react-scripts": "^5.0.0",
|
||||
"typescript": "^4.1.2",
|
||||
"web-vitals": "^1.0.1"
|
||||
},
|
||||
|
13681
admin-frontend/yarn.lock
13681
admin-frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -136,6 +136,8 @@ sidebar: auto
|
||||
启用此功能时,可能会因为待推送图片过大/过多而导致文字消息与合并转发图片消息推送间隔过大(选择模式`1`时),请谨慎考虑开启。或者选择模式`2`,使图文消息一同合并转发(可能会使消息推送延迟过长)
|
||||
:::
|
||||
|
||||
- `BISON_PROXY`: 使用的代理连接,形如`http://<ip>:<port>`(可选)
|
||||
|
||||
## 使用
|
||||
|
||||
::: warning
|
||||
|
1070
poetry.lock
generated
1070
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "nonebot-bison"
|
||||
version = "0.5.2"
|
||||
version = "0.5.3"
|
||||
description = "Subscribe message from social medias"
|
||||
authors = ["felinae98 <felinae225@qq.com>"]
|
||||
license = "MIT"
|
||||
@ -25,11 +25,11 @@ classifiers = [
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.9"
|
||||
nonebot2 = "^2.0.0-beta.2"
|
||||
httpx = ">=0.16.1 <1.0.0"
|
||||
httpx = ">=0.16.1"
|
||||
bs4 = "^0.0.1"
|
||||
tinydb = "^4.3.0"
|
||||
feedparser = "^6.0.2"
|
||||
pillow = "^8.1.0"
|
||||
pillow = ">=8.1,<10.0"
|
||||
apscheduler = "^3.7.0"
|
||||
expiringdict = "^1.2.1"
|
||||
pyjwt = "^2.1.0"
|
||||
|
@ -129,8 +129,7 @@ def register_router_fastapi(driver: Driver, socketio):
|
||||
|
||||
def init():
|
||||
driver = get_driver()
|
||||
if driver.type == "fastapi":
|
||||
assert isinstance(driver, Driver)
|
||||
if isinstance(driver, Driver):
|
||||
register_router_fastapi(driver, socket_app)
|
||||
else:
|
||||
logger.warning(f"Driver {driver.type} not supported")
|
||||
|
@ -9,7 +9,6 @@ from nonebot.adapters.onebot.v11.message import Message
|
||||
from nonebot.adapters.onebot.v11.permission import GROUP_ADMIN, GROUP_OWNER
|
||||
from nonebot.internal.params import ArgStr
|
||||
from nonebot.internal.rule import Rule
|
||||
from nonebot.log import logger
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.params import Depends, EventPlainText, EventToMe
|
||||
from nonebot.permission import SUPERUSER
|
||||
@ -17,7 +16,7 @@ from nonebot.rule import to_me
|
||||
from nonebot.typing import T_State
|
||||
|
||||
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 .types import Category, Target, User
|
||||
from .utils import parse_text
|
||||
@ -108,8 +107,13 @@ def do_add_sub(add_sub: Type[Matcher]):
|
||||
"platform", _gen_prompt_template("{_prompt}"), [Depends(parse_platform)]
|
||||
)
|
||||
async def init_id(state: T_State):
|
||||
if platform_manager[state["platform"]].has_target:
|
||||
state["_prompt"] = "请输入订阅用户的id:\n查询id获取方法请回复:“查询”"
|
||||
cur_platform = platform_manager[state["platform"]]
|
||||
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:
|
||||
state["id"] = "default"
|
||||
state["name"] = await platform_manager[state["platform"]].get_target_name(
|
||||
@ -125,6 +129,8 @@ def do_add_sub(add_sub: Type[Matcher]):
|
||||
raise LookupError
|
||||
if target == "取消":
|
||||
raise KeyboardInterrupt
|
||||
platform = platform_manager[state["platform"]]
|
||||
target = await platform.parse_target(target)
|
||||
name = await check_sub_target(state["platform"], target)
|
||||
if not name:
|
||||
raise ValueError
|
||||
@ -141,6 +147,8 @@ def do_add_sub(add_sub: Type[Matcher]):
|
||||
await add_sub.finish("已中止订阅")
|
||||
except (ValueError):
|
||||
await add_sub.reject("id输入错误")
|
||||
except (Platform.ParseTargetException):
|
||||
await add_sub.reject("不能从你的输入中提取出id,请检查你输入的内容是否符合预期")
|
||||
else:
|
||||
await add_sub.send(
|
||||
"即将订阅的用户为:{} {} {}\n如有错误请输入“取消”重新订阅".format(
|
||||
@ -244,41 +252,46 @@ def do_del_sub(del_sub: Type[Matcher]):
|
||||
async def send_list(bot: Bot, event: Event, state: T_State):
|
||||
user_info = state["target_user_info"]
|
||||
assert isinstance(user_info, User)
|
||||
sub_list = await config.list_subscribe(
|
||||
# state.get("_user_id") or event.group_id, "group"
|
||||
user_info.user,
|
||||
user_info.user_type,
|
||||
)
|
||||
res = "订阅的帐号为:\n"
|
||||
state["sub_table"] = {}
|
||||
for index, sub in enumerate(sub_list, 1):
|
||||
state["sub_table"][index] = {
|
||||
"platform_name": sub.target.platform_name,
|
||||
"target": sub.target.target,
|
||||
}
|
||||
res += "{} {} {} {}\n".format(
|
||||
index,
|
||||
sub.target.platform_name,
|
||||
sub.target.target_name,
|
||||
sub.target.target,
|
||||
try:
|
||||
sub_list = await config.list_subscribe(
|
||||
# state.get("_user_id") or event.group_id, "group"
|
||||
user_info.user,
|
||||
user_info.user_type,
|
||||
)
|
||||
platform = platform_manager[sub.target.platform_name]
|
||||
if platform.categories:
|
||||
res += " [{}]".format(
|
||||
", ".join(
|
||||
map(lambda x: platform.categories[Category(x)], sub.categories)
|
||||
)
|
||||
assert sub_list
|
||||
except AssertionError:
|
||||
await del_sub.finish("暂无已订阅账号\n请使用“添加订阅”命令添加订阅")
|
||||
else:
|
||||
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"]
|
||||
)
|
||||
if platform.enable_tag:
|
||||
res += " {}".format(", ".join(sub.tags))
|
||||
res += "\n"
|
||||
res += "请输入要删除的订阅的序号"
|
||||
await bot.send(event=event, message=Message(await parse_text(res)))
|
||||
platform = platform_manager[sub["target_type"]]
|
||||
if platform.categories:
|
||||
res += " [{}]".format(
|
||||
", ".join(
|
||||
map(lambda x: platform.categories[Category(x)], sub["cats"])
|
||||
)
|
||||
)
|
||||
if platform.enable_tag:
|
||||
res += " {}".format(", ".join(sub["tags"]))
|
||||
res += "\n"
|
||||
res += "请输入要删除的订阅的序号\n输入'取消'中止"
|
||||
await bot.send(event=event, message=Message(await parse_text(res)))
|
||||
|
||||
@del_sub.receive()
|
||||
async def do_del(event: Event, state: T_State):
|
||||
user_msg = str(event.get_message()).strip()
|
||||
if user_msg == "取消":
|
||||
await del_sub.finish("删除中止")
|
||||
try:
|
||||
index = int(str(event.get_message()).strip())
|
||||
index = int(user_msg)
|
||||
user_info = state["target_user_info"]
|
||||
assert isinstance(user_info, User)
|
||||
await config.del_subscribe(
|
||||
@ -299,12 +312,13 @@ add_sub_matcher = on_command(
|
||||
rule=configurable_to_me,
|
||||
permission=GROUP_ADMIN | GROUP_OWNER | SUPERUSER,
|
||||
priority=5,
|
||||
block=True,
|
||||
)
|
||||
add_sub_matcher.handle()(set_target_user_info)
|
||||
do_add_sub(add_sub_matcher)
|
||||
|
||||
|
||||
query_sub_matcher = on_command("查询订阅", rule=configurable_to_me, priority=5)
|
||||
query_sub_matcher = on_command("查询订阅", rule=configurable_to_me, priority=5, block=True)
|
||||
query_sub_matcher.handle()(set_target_user_info)
|
||||
do_query_sub(query_sub_matcher)
|
||||
|
||||
@ -314,11 +328,14 @@ del_sub_matcher = on_command(
|
||||
rule=configurable_to_me,
|
||||
permission=GROUP_ADMIN | GROUP_OWNER | SUPERUSER,
|
||||
priority=5,
|
||||
block=True,
|
||||
)
|
||||
del_sub_matcher.handle()(set_target_user_info)
|
||||
do_del_sub(del_sub_matcher)
|
||||
|
||||
group_manage_matcher = on_command("群管理", rule=to_me(), permission=SUPERUSER, priority=4)
|
||||
group_manage_matcher = on_command(
|
||||
"群管理", rule=to_me(), permission=SUPERUSER, priority=4, block=True
|
||||
)
|
||||
|
||||
|
||||
@group_manage_matcher.handle()
|
||||
|
@ -1,12 +1,12 @@
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from bs4 import BeautifulSoup as bs
|
||||
from nonebot.plugin import require
|
||||
|
||||
from ..post import Post
|
||||
from ..types import Category, RawPost, Target
|
||||
from ..utils import http_client
|
||||
from .platform import CategoryNotSupport, NewMessage, StatusChange
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@ class Arknights(NewMessage):
|
||||
return "明日方舟游戏信息"
|
||||
|
||||
async def get_sub_list(self, _) -> list[RawPost]:
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with http_client() as client:
|
||||
raw_data = await client.get(
|
||||
"https://ak-conf.hypergryph.com/config/prod/announce_meta/IOS/announcement.meta.json"
|
||||
)
|
||||
@ -44,7 +44,7 @@ class Arknights(NewMessage):
|
||||
async def parse(self, raw_post: RawPost) -> Post:
|
||||
announce_url = raw_post["webUrl"]
|
||||
text = ""
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with http_client() as client:
|
||||
raw_html = await client.get(announce_url)
|
||||
soup = bs(raw_html.text, "html.parser")
|
||||
pics = []
|
||||
@ -99,7 +99,7 @@ class AkVersion(StatusChange):
|
||||
return "明日方舟游戏信息"
|
||||
|
||||
async def get_status(self, _):
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with http_client() as client:
|
||||
res_ver = await client.get(
|
||||
"https://ak-conf.hypergryph.com/config/prod/official/IOS/version"
|
||||
)
|
||||
@ -155,7 +155,7 @@ class MonsterSiren(NewMessage):
|
||||
return "明日方舟游戏信息"
|
||||
|
||||
async def get_sub_list(self, _) -> list[RawPost]:
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with http_client() as client:
|
||||
raw_data = await client.get("https://monster-siren.hypergryph.com/api/news")
|
||||
return raw_data.json()["data"]["list"]
|
||||
|
||||
@ -170,7 +170,7 @@ 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:
|
||||
async with http_client() as client:
|
||||
res = await client.get(
|
||||
f'https://monster-siren.hypergryph.com/api/news/{raw_post["cid"]}'
|
||||
)
|
||||
@ -189,3 +189,47 @@ class MonsterSiren(NewMessage):
|
||||
compress=True,
|
||||
override_use_pic=False,
|
||||
)
|
||||
|
||||
|
||||
class TerraHistoricusComic(NewMessage):
|
||||
|
||||
categories = {4: "泰拉记事社漫画"}
|
||||
platform_name = "arknights"
|
||||
name = "明日方舟游戏信息"
|
||||
enable_tag = False
|
||||
enabled = True
|
||||
is_common = False
|
||||
schedule_type = "interval"
|
||||
schedule_kw = {"seconds": 30}
|
||||
has_target = False
|
||||
|
||||
async def get_target_name(self, _: Target) -> str:
|
||||
return "明日方舟游戏信息"
|
||||
|
||||
async def get_sub_list(self, _) -> list[RawPost]:
|
||||
async with http_client() as client:
|
||||
raw_data = await client.get(
|
||||
"https://terra-historicus.hypergryph.com/api/recentUpdate"
|
||||
)
|
||||
return raw_data.json()["data"]
|
||||
|
||||
def get_id(self, post: RawPost) -> Any:
|
||||
return f'{post["comicCid"]}/{post["episodeCid"]}'
|
||||
|
||||
def get_date(self, _) -> None:
|
||||
return None
|
||||
|
||||
def get_category(self, _) -> Category:
|
||||
return Category(4)
|
||||
|
||||
async def parse(self, raw_post: RawPost) -> Post:
|
||||
url = f'https://terra-historicus.hypergryph.com/comic/{raw_post["comicCid"]}/episode/{raw_post["episodeCid"]}'
|
||||
return Post(
|
||||
"terra-historicus",
|
||||
text=f'{raw_post["title"]} - {raw_post["episodeShortTitle"]}',
|
||||
pics=[raw_post["coverUrl"]],
|
||||
url=url,
|
||||
target_name="泰拉记事社漫画",
|
||||
compress=True,
|
||||
override_use_pic=False,
|
||||
)
|
||||
|
@ -1,10 +1,10 @@
|
||||
import json
|
||||
import re
|
||||
from typing import Any, Optional
|
||||
|
||||
import httpx
|
||||
|
||||
from ..post import Post
|
||||
from ..types import Category, RawPost, Tag, Target
|
||||
from ..utils import http_client
|
||||
from .platform import CategoryNotSupport, NewMessage
|
||||
|
||||
|
||||
@ -26,9 +26,10 @@ class Bilibili(NewMessage):
|
||||
schedule_kw = {"seconds": 10}
|
||||
name = "B站"
|
||||
has_target = True
|
||||
parse_target_promot = "请输入用户主页的链接"
|
||||
|
||||
async def get_target_name(self, target: Target) -> Optional[str]:
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with http_client() as client:
|
||||
res = await client.get(
|
||||
"https://api.bilibili.com/x/space/acc/info", params={"mid": target}
|
||||
)
|
||||
@ -37,8 +38,18 @@ class Bilibili(NewMessage):
|
||||
return None
|
||||
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 with httpx.AsyncClient() as client:
|
||||
async with http_client() 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",
|
||||
|
@ -1,9 +1,8 @@
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
from ..post import Post
|
||||
from ..types import RawPost, Target
|
||||
from ..utils import http_client
|
||||
from .platform import NewMessage
|
||||
|
||||
|
||||
@ -23,7 +22,7 @@ class FF14(NewMessage):
|
||||
return "最终幻想XIV官方公告"
|
||||
|
||||
async def get_sub_list(self, _) -> list[RawPost]:
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with http_client() as client:
|
||||
raw_data = await client.get(
|
||||
"https://ff.web.sdo.com/inc/newdata.ashx?url=List?gameCode=ff&category=5309,5310,5311,5312,5313&pageIndex=0&pageSize=5"
|
||||
)
|
||||
|
@ -1,9 +1,9 @@
|
||||
import re
|
||||
from typing import Any, Optional
|
||||
|
||||
import httpx
|
||||
|
||||
from ..post import Post
|
||||
from ..types import RawPost, Target
|
||||
from ..utils import http_client
|
||||
from .platform import NewMessage
|
||||
|
||||
|
||||
@ -18,9 +18,10 @@ class NcmArtist(NewMessage):
|
||||
schedule_kw = {"minutes": 1}
|
||||
name = "网易云-歌手"
|
||||
has_target = True
|
||||
parse_target_promot = "请输入歌手主页(包含数字ID)的链接"
|
||||
|
||||
async def get_target_name(self, target: Target) -> Optional[str]:
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with http_client() as client:
|
||||
res = await client.get(
|
||||
"https://music.163.com/api/artist/albums/{}".format(target),
|
||||
headers={"Referer": "https://music.163.com/"},
|
||||
@ -30,8 +31,18 @@ class NcmArtist(NewMessage):
|
||||
return
|
||||
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 with httpx.AsyncClient() as client:
|
||||
async with http_client() as client:
|
||||
res = await client.get(
|
||||
"https://music.163.com/api/artist/albums/{}".format(target),
|
||||
headers={"Referer": "https://music.163.com/"},
|
||||
|
@ -1,9 +1,9 @@
|
||||
import re
|
||||
from typing import Any, Optional
|
||||
|
||||
import httpx
|
||||
|
||||
from ..post import Post
|
||||
from ..types import RawPost, Target
|
||||
from ..utils import http_client
|
||||
from .platform import NewMessage
|
||||
|
||||
|
||||
@ -18,9 +18,10 @@ class NcmRadio(NewMessage):
|
||||
schedule_kw = {"minutes": 10}
|
||||
name = "网易云-电台"
|
||||
has_target = True
|
||||
parse_target_promot = "请输入主播电台主页(包含数字ID)的链接"
|
||||
|
||||
async def get_target_name(self, target: Target) -> Optional[str]:
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with http_client() as client:
|
||||
res = await client.post(
|
||||
"http://music.163.com/api/dj/program/byradio",
|
||||
headers={"Referer": "https://music.163.com/"},
|
||||
@ -31,8 +32,18 @@ class NcmRadio(NewMessage):
|
||||
return
|
||||
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 with httpx.AsyncClient() as client:
|
||||
async with http_client() as client:
|
||||
res = await client.post(
|
||||
"http://music.163.com/api/dj/program/byradio",
|
||||
headers={"Referer": "https://music.163.com/"},
|
||||
|
@ -47,6 +47,7 @@ class Platform(metaclass=RegistryABCMeta, base=True):
|
||||
enable_tag: bool
|
||||
store: dict[Target, Any]
|
||||
platform_name: str
|
||||
parse_target_promot: Optional[str] = None
|
||||
|
||||
@abstractmethod
|
||||
async def get_target_name(self, target: Target) -> Optional[str]:
|
||||
@ -73,6 +74,12 @@ class Platform(metaclass=RegistryABCMeta, base=True):
|
||||
self.reverse_category[val] = key
|
||||
self.store = dict()
|
||||
|
||||
class ParseTargetException(Exception):
|
||||
pass
|
||||
|
||||
async def parse_target(self, target_string: str) -> Target:
|
||||
return Target(target_string)
|
||||
|
||||
@abstractmethod
|
||||
def get_tags(self, raw_post: RawPost) -> Optional[Collection[Tag]]:
|
||||
"Return Tag list of given RawPost"
|
||||
|
@ -2,11 +2,11 @@ import calendar
|
||||
from typing import Any, Optional
|
||||
|
||||
import feedparser
|
||||
import httpx
|
||||
from bs4 import BeautifulSoup as bs
|
||||
|
||||
from ..post import Post
|
||||
from ..types import RawPost, Target
|
||||
from ..utils import http_client
|
||||
from .platform import NewMessage
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ class Rss(NewMessage):
|
||||
has_target = True
|
||||
|
||||
async def get_target_name(self, target: Target) -> Optional[str]:
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with http_client() as client:
|
||||
res = await client.get(target, timeout=10.0)
|
||||
feed = feedparser.parse(res.text)
|
||||
return feed["feed"]["title"]
|
||||
@ -35,7 +35,7 @@ class Rss(NewMessage):
|
||||
return post.id
|
||||
|
||||
async def get_sub_list(self, target: Target) -> list[RawPost]:
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with http_client() as client:
|
||||
res = await client.get(target, timeout=10.0)
|
||||
feed = feedparser.parse(res)
|
||||
entries = feed.entries
|
||||
|
@ -3,12 +3,12 @@ import re
|
||||
from datetime import datetime
|
||||
from typing import Any, Optional
|
||||
|
||||
import httpx
|
||||
from bs4 import BeautifulSoup as bs
|
||||
from nonebot.log import logger
|
||||
|
||||
from ..post import Post
|
||||
from ..types import *
|
||||
from ..utils import http_client
|
||||
from .platform import NewMessage
|
||||
|
||||
|
||||
@ -28,9 +28,10 @@ class Weibo(NewMessage):
|
||||
schedule_type = "interval"
|
||||
schedule_kw = {"seconds": 3}
|
||||
has_target = True
|
||||
parse_target_promot = "请输入用户主页(包含数字UID)的链接"
|
||||
|
||||
async def get_target_name(self, target: Target) -> Optional[str]:
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with http_client() as client:
|
||||
param = {"containerid": "100505" + target}
|
||||
res = await client.get(
|
||||
"https://m.weibo.cn/api/container/getIndex", params=param
|
||||
@ -41,8 +42,17 @@ class Weibo(NewMessage):
|
||||
else:
|
||||
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 with httpx.AsyncClient() as client:
|
||||
async with http_client() as client:
|
||||
params = {"containerid": "107603" + target}
|
||||
res = await client.get(
|
||||
"https://m.weibo.cn/api/container/getIndex?", params=params, timeout=4.0
|
||||
@ -123,15 +133,19 @@ class Weibo(NewMessage):
|
||||
"Mobile Safari/537.36",
|
||||
}
|
||||
info = raw_post["mblog"]
|
||||
if info["isLongText"] or info["pic_num"] > 9:
|
||||
async with httpx.AsyncClient() as client:
|
||||
retweeted = False
|
||||
if info.get("retweeted_status"):
|
||||
retweeted = True
|
||||
pic_num = info["retweeted_status"]["pic_num"] if retweeted else info["pic_num"]
|
||||
if info["isLongText"] or pic_num > 9:
|
||||
async with http_client() as client:
|
||||
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+"call"', res.text
|
||||
).group(1)
|
||||
match = re.search(r'"status": ([\s\S]+),\s+"call"', res.text)
|
||||
assert match
|
||||
full_json_text = match.group(1)
|
||||
info = json.loads(full_json_text)
|
||||
except:
|
||||
logger.info(
|
||||
@ -140,7 +154,12 @@ class Weibo(NewMessage):
|
||||
)
|
||||
)
|
||||
parsed_text = self._get_text(info["text"])
|
||||
pic_urls = [img["large"]["url"] for img in info.get("pics", [])]
|
||||
raw_pics_list = (
|
||||
info["retweeted_status"].get("pics", [])
|
||||
if retweeted
|
||||
else info.get("pics", [])
|
||||
)
|
||||
pic_urls = [img["large"]["url"] for img in raw_pics_list]
|
||||
detail_url = "https://weibo.com/{}/{}".format(info["user"]["id"], info["bid"])
|
||||
# return parsed_text, detail_url, pic_urls
|
||||
return Post(
|
||||
|
@ -1,3 +1,5 @@
|
||||
from typing import Optional
|
||||
|
||||
import nonebot
|
||||
from pydantic import BaseSettings
|
||||
|
||||
@ -12,9 +14,10 @@ class PlugConfig(BaseSettings):
|
||||
bison_filter_log: bool = False
|
||||
bison_to_me: bool = True
|
||||
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以及以上:所有消息全部合并转发
|
||||
bison_resend_times: int = 0
|
||||
bison_proxy: Optional[str]
|
||||
|
||||
class Config:
|
||||
extra = "ignore"
|
||||
|
@ -3,13 +3,12 @@ from functools import reduce
|
||||
from io import BytesIO
|
||||
from typing import Optional, Union
|
||||
|
||||
import httpx
|
||||
from nonebot.adapters.onebot.v11.message import Message, MessageSegment
|
||||
from nonebot.log import logger
|
||||
from PIL import Image
|
||||
|
||||
from .plugin_config import plugin_config
|
||||
from .utils import parse_text
|
||||
from .utils import http_client, parse_text
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -34,7 +33,7 @@ class Post:
|
||||
async def _pic_url_to_image(self, data: Union[str, bytes]) -> Image.Image:
|
||||
pic_buffer = BytesIO()
|
||||
if isinstance(data, str):
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with http_client() as client:
|
||||
res = await client.get(data)
|
||||
pic_buffer.write(res.content)
|
||||
else:
|
||||
|
93
src/plugins/nonebot_bison/scheduler/old_sched.py
Normal file
93
src/plugins/nonebot_bison/scheduler/old_sched.py
Normal file
@ -0,0 +1,93 @@
|
||||
import logging
|
||||
|
||||
import nonebot
|
||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
from nonebot import get_driver
|
||||
from nonebot.adapters.onebot.v11.bot import Bot
|
||||
from nonebot.log import LoguruHandler, logger
|
||||
|
||||
from .config import config
|
||||
from .platform import platform_manager
|
||||
from .plugin_config import plugin_config
|
||||
from .send import do_send_msgs, send_msgs
|
||||
from .types import UserSubInfo
|
||||
|
||||
scheduler = AsyncIOScheduler(timezone="Asia/Shanghai")
|
||||
|
||||
|
||||
@get_driver().on_startup
|
||||
async def _start():
|
||||
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}"
|
||||
)
|
||||
scheduler.add_job(
|
||||
fetch_and_send,
|
||||
platform.schedule_type,
|
||||
**platform.schedule_kw,
|
||||
args=(platform_name,),
|
||||
)
|
||||
|
||||
scheduler.configure({"apscheduler.timezone": "Asia/Shanghai"})
|
||||
scheduler.start()
|
||||
|
||||
|
||||
# get_driver().on_startup(_start)
|
||||
|
||||
|
||||
async def fetch_and_send(target_type: str):
|
||||
target = config.get_next_target(target_type)
|
||||
if not target:
|
||||
return
|
||||
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,
|
||||
)
|
||||
)
|
||||
to_send = await platform_manager[target_type].fetch_new_post(
|
||||
target, send_userinfo_list
|
||||
)
|
||||
if not to_send:
|
||||
return
|
||||
bot = nonebot.get_bot()
|
||||
assert isinstance(bot, Bot)
|
||||
for user, send_list in to_send:
|
||||
for send_post in send_list:
|
||||
logger.info("send to {}: {}".format(user, send_post))
|
||||
if not bot:
|
||||
logger.warning("no bot connected")
|
||||
else:
|
||||
await send_msgs(
|
||||
bot, user.user, user.user_type, await send_post.generate_messages()
|
||||
)
|
||||
|
||||
|
||||
class CustomLogHandler(LoguruHandler):
|
||||
def filter(self, record: logging.LogRecord):
|
||||
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)
|
||||
|
||||
aps_logger = logging.getLogger("apscheduler")
|
||||
aps_logger.setLevel(30)
|
||||
aps_logger.handlers.clear()
|
||||
aps_logger.addHandler(CustomLogHandler())
|
@ -8,7 +8,10 @@ from nonebot.adapters.onebot.v11.message import MessageSegment
|
||||
from nonebot.log import default_format, logger
|
||||
from nonebot.plugin import require
|
||||
|
||||
from .plugin_config import plugin_config
|
||||
from ..plugin_config import plugin_config
|
||||
from .http import http_client
|
||||
|
||||
__all__ = ["http_client", "Singleton", "parse_text", "html_to_text"]
|
||||
|
||||
|
||||
class Singleton(type):
|
12
src/plugins/nonebot_bison/utils/http.py
Normal file
12
src/plugins/nonebot_bison/utils/http.py
Normal file
@ -0,0 +1,12 @@
|
||||
import functools
|
||||
|
||||
import httpx
|
||||
|
||||
from ..plugin_config import plugin_config
|
||||
|
||||
if plugin_config.bison_proxy:
|
||||
http_client = functools.partial(
|
||||
httpx.AsyncClient, proxies=plugin_config.bison_proxy
|
||||
)
|
||||
else:
|
||||
http_client = httpx.AsyncClient
|
1
tests/platforms/static/bilibili_arknights_profile.json
Normal file
1
tests/platforms/static/bilibili_arknights_profile.json
Normal 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}}
|
1
tests/platforms/static/terra-hist-0.json
Normal file
1
tests/platforms/static/terra-hist-0.json
Normal file
@ -0,0 +1 @@
|
||||
{"code":0,"msg":"","data":[{"coverUrl":"https://web.hycdn.cn/comic/pic/20220507/c4da4fb95587101f1867a30fe85cb557.png","comicCid":"6253","title":"123罗德岛!?","subtitle":"你可能不知道的罗德岛小剧场!","episodeCid":"5771","episodeType":1,"episodeShortTitle":"「流明」篇","updateTime":1652025600},{"coverUrl":"https://web.hycdn.cn/comic/pic/20220505/5c296539f5dc808603f20dda86879c9c.png","comicCid":"0696","title":"A1行动预备组","subtitle":"","episodeCid":"2897","episodeType":1,"episodeShortTitle":"01","updateTime":1651852800},{"coverUrl":"https://web.hycdn.cn/comic/pic/20220428/f4dc1e91420d18eccb80d60bba322d42.png","comicCid":"6253","title":"123罗德岛!?","subtitle":"你可能不知道的罗德岛小剧场!","episodeCid":"6346","episodeType":1,"episodeShortTitle":"「艾丽妮」篇","updateTime":1651507200}]}
|
1
tests/platforms/static/terra-hist-1.json
Normal file
1
tests/platforms/static/terra-hist-1.json
Normal file
@ -0,0 +1 @@
|
||||
{"code":0,"msg":"","data":[{"coverUrl":"https://web.hycdn.cn/comic/pic/20220507/ab8a2ff408ec7d587775aed70b178ec0.png","comicCid":"6253","title":"123罗德岛!?","subtitle":"你可能不知道的罗德岛小剧场!","episodeCid":"4938","episodeType":1,"episodeShortTitle":"「掠风」篇","updateTime":1652112000},{"coverUrl":"https://web.hycdn.cn/comic/pic/20220507/c4da4fb95587101f1867a30fe85cb557.png","comicCid":"6253","title":"123罗德岛!?","subtitle":"你可能不知道的罗德岛小剧场!","episodeCid":"5771","episodeType":1,"episodeShortTitle":"「流明」篇","updateTime":1652025600},{"coverUrl":"https://web.hycdn.cn/comic/pic/20220505/5c296539f5dc808603f20dda86879c9c.png","comicCid":"0696","title":"A1行动预备组","subtitle":"","episodeCid":"2897","episodeType":1,"episodeShortTitle":"01","updateTime":1651852800},{"coverUrl":"https://web.hycdn.cn/comic/pic/20220428/f4dc1e91420d18eccb80d60bba322d42.png","comicCid":"6253","title":"123罗德岛!?","subtitle":"你可能不知道的罗德岛小剧场!","episodeCid":"6346","episodeType":1,"episodeShortTitle":"「艾丽妮」篇","updateTime":1651507200}]}
|
@ -61,6 +61,7 @@ async def test_fetch_new(
|
||||
"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")
|
||||
terra_list = respx.get("https://terra-historicus.hypergryph.com/api/recentUpdate")
|
||||
ak_list_router.mock(return_value=Response(200, json=arknights_list__1))
|
||||
detail_router.mock(
|
||||
return_value=Response(200, text=get_file("arknights-detail-807"))
|
||||
@ -72,6 +73,7 @@ async def test_fetch_new(
|
||||
return_value=Response(200, json=get_json("arknights-pre-0.json"))
|
||||
)
|
||||
monster_siren_router.mock(return_value=Response(200, json=monster_siren_list_0))
|
||||
terra_list.mock(return_value=Response(200, json=get_json("terra-hist-0.json")))
|
||||
target = ""
|
||||
res = await arknights.fetch_new_post(target, [dummy_user_subinfo])
|
||||
assert ak_list_router.called
|
||||
@ -90,7 +92,17 @@ async def test_fetch_new(
|
||||
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()
|
||||
await post.generate_messages()
|
||||
terra_list.mock(return_value=Response(200, json=get_json("terra-hist-1.json")))
|
||||
res = await arknights.fetch_new_post(target, [dummy_user_subinfo])
|
||||
assert len(res) == 1
|
||||
post = res[0][1][0]
|
||||
assert post.target_type == "terra-historicus"
|
||||
assert post.text == "123罗德岛!? - 「掠风」篇"
|
||||
assert post.url == "https://terra-historicus.hypergryph.com/comic/6253/episode/4938"
|
||||
assert post.pics == [
|
||||
"https://web.hycdn.cn/comic/pic/20220507/ab8a2ff408ec7d587775aed70b178ec0.png"
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.render
|
||||
@ -116,6 +128,7 @@ async def test_send_with_render(
|
||||
"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")
|
||||
terra_list = respx.get("https://terra-historicus.hypergryph.com/api/recentUpdate")
|
||||
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"))
|
||||
@ -127,6 +140,7 @@ async def test_send_with_render(
|
||||
return_value=Response(200, json=get_json("arknights-pre-0.json"))
|
||||
)
|
||||
monster_siren_router.mock(return_value=Response(200, json=monster_siren_list_0))
|
||||
terra_list.mock(return_value=Response(200, json=get_json("terra-hist-0.json")))
|
||||
target = ""
|
||||
res = await arknights.fetch_new_post(target, [dummy_user_subinfo])
|
||||
assert ak_list_router.called
|
||||
|
@ -1,3 +1,5 @@
|
||||
import typing
|
||||
|
||||
import pytest
|
||||
from httpx import Response
|
||||
from nonebug.app import App
|
||||
@ -10,6 +12,10 @@ def bing_dy_list():
|
||||
return get_json("bilibili_bing_list.json")["data"]["cards"]
|
||||
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from nonebot_bison.platform.bilibili import Bilibili
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def bilibili(app: App):
|
||||
from nonebot_bison.platform import platform_manager
|
||||
@ -46,3 +52,20 @@ async def test_dynamic_forward(bilibili, bing_dy_list):
|
||||
+ "\n--------------\n"
|
||||
+ "#明日方舟#\n【新增服饰】\n//殿堂上的游禽 - 星极\n塞壬唱片偶像企划《闪耀阶梯》特供服饰/殿堂上的游禽。星极自费参加了这项企划,尝试着用大众能接受的方式演绎天空之上的故事。\n\n_____________\n谦逊留给观众,骄傲发自歌喉,此夜,唯我璀璨。 "
|
||||
)
|
||||
|
||||
|
||||
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"
|
||||
)
|
||||
|
@ -1,4 +1,5 @@
|
||||
import time
|
||||
import typing
|
||||
|
||||
import pytest
|
||||
import respx
|
||||
@ -7,6 +8,9 @@ from nonebug.app import App
|
||||
|
||||
from .utils import get_json
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from nonebot_bison.platform.ncm_artist import NcmArtist
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
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.text == "新专辑发布:Y1K"
|
||||
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")
|
||||
|
@ -1,4 +1,5 @@
|
||||
import time
|
||||
import typing
|
||||
|
||||
import pytest
|
||||
import respx
|
||||
@ -7,6 +8,9 @@ from nonebug.app import App
|
||||
|
||||
from .utils import get_json
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from nonebot_bison.platform.ncm_radio import NcmRadio
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
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"
|
||||
]
|
||||
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")
|
||||
|
@ -1,3 +1,4 @@
|
||||
import typing
|
||||
from datetime import datetime
|
||||
|
||||
import feedparser
|
||||
@ -9,6 +10,9 @@ from pytz import timezone
|
||||
|
||||
from .utils import get_file, get_json
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from nonebot_bison.platform.weibo import Weibo
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def weibo(app: App):
|
||||
@ -23,7 +27,14 @@ def weibo_ak_list_1():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@respx.mock
|
||||
async def test_get_name(weibo):
|
||||
profile_router = respx.get(
|
||||
"https://m.weibo.cn/api/container/getIndex?containerid=1005056279793937"
|
||||
)
|
||||
profile_router.mock(
|
||||
return_value=Response(200, json=get_json("weibo_ak_profile.json"))
|
||||
)
|
||||
name = await weibo.get_target_name("6279793937")
|
||||
assert name == "明日方舟Arknights"
|
||||
|
||||
@ -125,3 +136,16 @@ def test_chaohua_tag(weibo):
|
||||
tags = weibo.get_tags(test_post)
|
||||
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")
|
||||
|
@ -62,6 +62,7 @@ async def test_abort_add_on_id(app: App, db_migration):
|
||||
from nonebot.adapters.onebot.v11.message import Message
|
||||
from nonebot_bison.config_manager import add_sub_matcher, common_platform
|
||||
from nonebot_bison.platform import platform_manager
|
||||
from nonebot_bison.platform.weibo import Weibo
|
||||
|
||||
ak_list_router = respx.get(
|
||||
"https://m.weibo.cn/api/container/getIndex?containerid=1005056279793937"
|
||||
@ -95,7 +96,7 @@ async def test_abort_add_on_id(app: App, db_migration):
|
||||
ctx.receive_event(bot, event_2)
|
||||
ctx.should_call_send(
|
||||
event_2,
|
||||
Message(BotReply.add_reply_on_id),
|
||||
Message(BotReply.add_reply_on_id(Weibo)),
|
||||
True,
|
||||
)
|
||||
event_abort = fake_group_message_event(
|
||||
@ -118,6 +119,7 @@ async def test_abort_add_on_cats(app: App, db_migration):
|
||||
from nonebot.adapters.onebot.v11.message import Message
|
||||
from nonebot_bison.config_manager import add_sub_matcher, common_platform
|
||||
from nonebot_bison.platform import platform_manager
|
||||
from nonebot_bison.platform.weibo import Weibo
|
||||
|
||||
ak_list_router = respx.get(
|
||||
"https://m.weibo.cn/api/container/getIndex?containerid=1005056279793937"
|
||||
@ -155,7 +157,7 @@ async def test_abort_add_on_cats(app: App, db_migration):
|
||||
ctx.receive_event(bot, event_2)
|
||||
ctx.should_call_send(
|
||||
event_2,
|
||||
Message(BotReply.add_reply_on_id),
|
||||
Message(BotReply.add_reply_on_id(Weibo)),
|
||||
True,
|
||||
)
|
||||
event_3 = fake_group_message_event(
|
||||
@ -194,6 +196,7 @@ async def test_abort_add_on_tag(app: App, db_migration):
|
||||
from nonebot.adapters.onebot.v11.message import Message
|
||||
from nonebot_bison.config_manager import add_sub_matcher, common_platform
|
||||
from nonebot_bison.platform import platform_manager
|
||||
from nonebot_bison.platform.weibo import Weibo
|
||||
|
||||
ak_list_router = respx.get(
|
||||
"https://m.weibo.cn/api/container/getIndex?containerid=1005056279793937"
|
||||
@ -231,7 +234,7 @@ async def test_abort_add_on_tag(app: App, db_migration):
|
||||
ctx.receive_event(bot, event_2)
|
||||
ctx.should_call_send(
|
||||
event_2,
|
||||
Message(BotReply.add_reply_on_id),
|
||||
Message(BotReply.add_reply_on_id(Weibo)),
|
||||
True,
|
||||
)
|
||||
event_3 = fake_group_message_event(
|
||||
@ -265,3 +268,49 @@ async def test_abort_add_on_tag(app: App, db_migration):
|
||||
True,
|
||||
)
|
||||
ctx.should_finished()
|
||||
|
||||
|
||||
# 删除订阅阶段中止
|
||||
@pytest.mark.asyncio
|
||||
async def test_abort_del_sub(app: App):
|
||||
from nonebot.adapters.onebot.v11.bot import Bot
|
||||
from nonebot.adapters.onebot.v11.message import Message
|
||||
from nonebot_bison.config import Config
|
||||
from nonebot_bison.config_manager import del_sub_matcher
|
||||
from nonebot_bison.platform import platform_manager
|
||||
|
||||
config = Config()
|
||||
config.user_target.truncate()
|
||||
config.add_subscribe(
|
||||
10000,
|
||||
"group",
|
||||
"6279793937",
|
||||
"明日方舟Arknights",
|
||||
"weibo",
|
||||
[platform_manager["weibo"].reverse_category["图文"]],
|
||||
["明日方舟"],
|
||||
)
|
||||
async with app.test_matcher(del_sub_matcher) as ctx:
|
||||
bot = ctx.create_bot(base=Bot)
|
||||
assert isinstance(bot, Bot)
|
||||
event = fake_group_message_event(
|
||||
message=Message("删除订阅"), to_me=True, sender=fake_admin_user
|
||||
)
|
||||
ctx.receive_event(bot, event)
|
||||
ctx.should_pass_rule()
|
||||
ctx.should_pass_permission()
|
||||
ctx.should_call_send(
|
||||
event,
|
||||
Message(
|
||||
"订阅的帐号为:\n1 weibo 明日方舟Arknights 6279793937\n [图文] 明日方舟\n请输入要删除的订阅的序号\n输入'取消'中止"
|
||||
),
|
||||
True,
|
||||
)
|
||||
event_abort = fake_group_message_event(
|
||||
message=Message("取消"), sender=fake_admin_user
|
||||
)
|
||||
ctx.receive_event(bot, event_abort)
|
||||
ctx.should_call_send(event_abort, "删除中止", True)
|
||||
ctx.should_finished()
|
||||
subs = config.list_subscribe(10000, "group")
|
||||
assert subs
|
||||
|
@ -64,6 +64,7 @@ async def test_add_with_target(app: App, db_migration):
|
||||
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.weibo import Weibo
|
||||
|
||||
ak_list_router = respx.get(
|
||||
"https://m.weibo.cn/api/container/getIndex?containerid=1005056279793937"
|
||||
@ -112,7 +113,7 @@ async def test_add_with_target(app: App, db_migration):
|
||||
ctx.receive_event(bot, event_3)
|
||||
ctx.should_call_send(
|
||||
event_3,
|
||||
Message(BotReply.add_reply_on_id),
|
||||
Message(BotReply.add_reply_on_id(Weibo)),
|
||||
True,
|
||||
)
|
||||
event_4_err = fake_group_message_event(
|
||||
@ -178,6 +179,7 @@ async def test_add_with_target_no_cat(app: App, db_migration):
|
||||
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.ncm_artist import NcmArtist
|
||||
|
||||
ncm_router = respx.get("https://music.163.com/api/artist/albums/32540734")
|
||||
ncm_router.mock(return_value=Response(200, json=get_json("ncm_siren.json")))
|
||||
@ -202,7 +204,7 @@ async def test_add_with_target_no_cat(app: App, db_migration):
|
||||
ctx.receive_event(bot, event_3)
|
||||
ctx.should_call_send(
|
||||
event_3,
|
||||
Message(BotReply.add_reply_on_id),
|
||||
Message(BotReply.add_reply_on_id(NcmArtist)),
|
||||
True,
|
||||
)
|
||||
event_4_ok = fake_group_message_event(
|
||||
@ -320,6 +322,7 @@ async def test_add_with_get_id(app: App, db_migration):
|
||||
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.weibo import Weibo
|
||||
|
||||
ak_list_router = respx.get(
|
||||
"https://m.weibo.cn/api/container/getIndex?containerid=1005056279793937"
|
||||
@ -358,7 +361,7 @@ async def test_add_with_get_id(app: App, db_migration):
|
||||
ctx.receive_event(bot, event_3)
|
||||
ctx.should_call_send(
|
||||
event_3,
|
||||
Message(BotReply.add_reply_on_id),
|
||||
Message(BotReply.add_reply_on_id(Weibo)),
|
||||
True,
|
||||
)
|
||||
event_4_query = fake_group_message_event(
|
||||
@ -372,7 +375,7 @@ async def test_add_with_get_id(app: App, db_migration):
|
||||
True,
|
||||
)
|
||||
"""
|
||||
line 362:
|
||||
关于:Message([MessageSegment(*BotReply.add_reply_on_id_input_search())])
|
||||
鬼知道为什么要在这里这样写,
|
||||
没有[]的话assert不了(should_call_send使用[MessageSegment(...)]的格式进行比较)
|
||||
不在这里MessageSegment()的话也assert不了(指不能让add_reply_on_id_input_search直接返回一个MessageSegment对象)
|
||||
@ -390,3 +393,125 @@ async def test_add_with_get_id(app: App, db_migration):
|
||||
ctx.should_finished()
|
||||
subs = await config.list_subscribe(10000, "group")
|
||||
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"] == "明日方舟"
|
||||
|
@ -65,7 +65,7 @@ async def test_del_sub(app: App, db_migration):
|
||||
ctx.should_call_send(
|
||||
event,
|
||||
Message(
|
||||
"订阅的帐号为:\n1 weibo 明日方舟Arknights 6279793937\n [图文] 明日方舟\n请输入要删除的订阅的序号"
|
||||
"订阅的帐号为:\n1 weibo 明日方舟Arknights 6279793937\n [图文] 明日方舟\n请输入要删除的订阅的序号\n输入'取消'中止"
|
||||
),
|
||||
True,
|
||||
)
|
||||
@ -83,3 +83,30 @@ async def test_del_sub(app: App, db_migration):
|
||||
ctx.should_finished()
|
||||
subs = await config.list_subscribe(10000, "group")
|
||||
assert len(subs) == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_del_empty_sub(app: App):
|
||||
from nonebot.adapters.onebot.v11.bot import Bot
|
||||
from nonebot.adapters.onebot.v11.message import Message
|
||||
from nonebot_bison.config import Config
|
||||
from nonebot_bison.config_manager import del_sub_matcher
|
||||
from nonebot_bison.platform import platform_manager
|
||||
|
||||
config = Config()
|
||||
config.user_target.truncate()
|
||||
async with app.test_matcher(del_sub_matcher) as ctx:
|
||||
bot = ctx.create_bot(base=Bot)
|
||||
assert isinstance(bot, Bot)
|
||||
event = fake_group_message_event(
|
||||
message=Message("删除订阅"), to_me=True, sender=fake_admin_user
|
||||
)
|
||||
ctx.receive_event(bot, event)
|
||||
ctx.should_pass_rule()
|
||||
ctx.should_pass_permission()
|
||||
ctx.should_finished()
|
||||
ctx.should_call_send(
|
||||
event,
|
||||
"暂无已订阅账号\n请使用“添加订阅”命令添加订阅",
|
||||
True,
|
||||
)
|
||||
|
20
tests/test_proxy.py
Normal file
20
tests/test_proxy.py
Normal file
@ -0,0 +1,20 @@
|
||||
import pytest
|
||||
from nonebug import App
|
||||
from nonebug.fixture import nonebug_init
|
||||
|
||||
|
||||
async def test_without_proxy(app: App):
|
||||
from nonebot_bison.utils import http_client
|
||||
|
||||
c = http_client()
|
||||
assert not c._mounts
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"nonebug_init", [{"bison_proxy": "http://example.com"}], indirect=True
|
||||
)
|
||||
async def test_with_proxy(app: App):
|
||||
from nonebot_bison.utils import http_client
|
||||
|
||||
c = http_client()
|
||||
assert c._mounts
|
@ -1,3 +1,4 @@
|
||||
from ast import Str
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from typing_extensions import Literal
|
||||
@ -130,8 +131,18 @@ class BotReply:
|
||||
def add_reply_subscribe_success(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_target_parse_input_error = "不能从你的输入中提取出id,请检查你输入的内容是否符合预期"
|
||||
add_reply_on_platform_input_error = "平台输入错误"
|
||||
add_reply_on_id = "请输入订阅用户的id:\n查询id获取方法请回复:“查询”"
|
||||
add_reply_on_tags = '请输入要订阅的tag,订阅所有tag输入"全部标签"'
|
||||
add_reply_abort = "已中止订阅"
|
||||
|
@ -414,9 +414,9 @@
|
||||
vue-demi "*"
|
||||
|
||||
ansi-regex@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.nlark.com/ansi-regex/download/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
|
||||
integrity sha1-OIU59VF5vzkznIGvMKZU1p+Hy3U=
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
|
||||
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
|
||||
|
||||
ansi-styles@^4.1.0:
|
||||
version "4.3.0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user