55 Commits

Author SHA1 Message Date
suyiiyii 43fb5231b8 🔀 merge 2024-09-21 12:26:36 +08:00
suyiiyii 0aeeb28919 🐛 t 2024-09-21 12:24:45 +08:00
suyiiyii 94daf74359 web api 初步完工 2024-09-20 15:39:22 +08:00
suyiiyii c85e77c801 web api 更新 2024-09-20 00:25:40 +08:00
suyiiyii d890d32bba web api 初稿 2024-09-19 21:16:56 +08:00
suyiiyii 76f271584f web api 初稿 2024-09-19 20:28:16 +08:00
suyiiyii 6cbc6f7d4d 🔀 merge 2024-09-19 10:00:34 +08:00
suyiiyii c784417ecc 初步实现删除cookie的单元测试 2024-09-13 20:01:54 +08:00
suyiiyii dd802a9c17 初步实现添加cookie的单元测试 2024-09-13 19:12:19 +08:00
suyiiyii 2cfd58373f pytest db_config 2024-09-13 14:12:02 +08:00
suyiiyii 4791fb69e0 ♻️ 重构 get_cookie 方法 2024-09-13 11:34:45 +08:00
suyiiyii 4b8d6a9379 🐛 fix 2024-09-13 10:11:54 +08:00
suyiiyii b25fcd9ac2 尝试添加一种可以跳过当前请求的方式 2024-09-13 01:07:34 +08:00
suyiiyii 16331b50d5 根据数据库的修改对应的逻辑,同时移除多余的init_cookie和_check_cookie 2024-09-13 01:00:53 +08:00
suyiiyii af246df222 俺又来改数据库哩 2024-09-13 00:35:14 +08:00
suyiiyii 4f73f8a08c 🐛 弃用_cookie_client_manger_,改用issubclass判断是否为CookieClientManager 2024-09-13 00:26:28 +08:00
suyiiyii 5111baa89c 🐛 应用部分推荐的重命名 2024-09-13 00:11:49 +08:00
suyiiyii f865cef427 🐛 调整日志等级 2024-09-12 23:33:11 +08:00
suyiiyii 318ba8fb3c 🔀 merge 2024-09-09 18:41:01 +08:00
suyiiyii 2c2c9a091c ♻️ del_cookie 2024-09-09 18:36:18 +08:00
suyiiyii 4a5e00c094 :recycles: ddel_cookie_target 2024-09-09 18:32:53 +08:00
suyiiyii 404b1e445c :recycles: add_cookie_target 2024-09-09 18:23:23 +08:00
suyiiyii d43d042618 :recycles: add_cookie 2024-09-09 11:35:39 +08:00
suyiiyii 65a5976897 :recycles: CookieClientManager 2024-09-09 11:06:59 +08:00
suyiiyii f959e3ee08 :recycles: 仿照 platform_manager 添加 site_manager 2024-09-09 11:01:34 +08:00
suyiiyii 4db7e7b911 :recycles: DBConfig中 替换platform_name为site_name 2024-09-08 18:38:38 +08:00
suyiiyii 275bc0cb53 :recycles: 注释掉cookie相关代码,使得bison可以正常运行 2024-09-08 18:21:57 +08:00
suyiiyii ce1f1bbedb 又来改数据库了( 2024-09-08 18:17:06 +08:00
suyiiyii 7c9e191f40 删除cookie 对话 2024-09-08 15:56:44 +08:00
suyiiyii 940301a6fc 取消关联cookie 对话 2024-09-08 15:17:19 +08:00
suyiiyii 61dcf879ce ♻️ 整理代码 2024-09-08 13:03:47 +08:00
suyiiyii a6227828e3 ♻️ 整理代码 2024-09-08 12:59:44 +08:00
suyiiyii bbc5492193 为匿名cookie设置标签 2024-09-08 12:22:06 +08:00
suyiiyii eddd3e42a1 修改_choose_cookie的逻辑以支持no target的Platform 2024-09-06 11:33:39 +08:00
suyiiyii 06079b98f7 关联cookie是不显示匿名cookie 2024-09-06 10:16:24 +08:00
suyiiyii 418a941448 添加不合法cookie的提示 2024-09-06 10:08:08 +08:00
suyiiyii afd1bee762 将get_cookie_friendly_name和valid_cookie移动到ccm内部 2024-09-06 01:08:32 +08:00
suyiiyii b61bde6e3f 关联cookie时,只显示支持的订阅 2024-09-06 00:39:57 +08:00
suyiiyii 6537f01a34 集中判断是否为CookieClientManager 2024-09-06 00:34:52 +08:00
suyiiyii 0ce2893911 匿名cookie和用户cookie一起调度 2024-09-06 00:25:33 +08:00
suyiiyii cf3966e69b 添加cookie只显示支持的Platform 2024-09-06 00:14:35 +08:00
suyiiyii 370fc250f0 数据库Cookie表添加is_universal属性 2024-09-05 19:32:47 +08:00
suyiiyii 3bd0867f0e 数据库Cookie表添加cd属性 2024-09-05 16:07:59 +08:00
suyiiyii 01435eeded 提出assemble_client方法 2024-09-04 01:19:53 +08:00
suyiiyii 498e7d60d4 根据条件选择Cookie 和 状态回写数据库 2024-09-03 13:15:18 +08:00
suyiiyii 055ed6e02a 使用闭包实现client hook 2024-09-03 10:52:20 +08:00
suyiiyii 4ce6b85f79 weibo 带 cookie mvp 2024-09-03 10:00:53 +08:00
suyiiyii 7901b845ea 初步实现携带cookie请求 2024-09-02 23:13:29 +08:00
suyiiyii 1cd778c2e0 ♻️ cookie 组件不再与 user 关联 2024-08-31 23:07:43 +08:00
suyiiyii c828fd94e4 ♻️ cookie 组件不再与 user 关联 2024-08-31 23:04:49 +08:00
suyiiyii ffae6f2ec5 添加cookie的时候显示关联的cookie 2024-08-26 18:09:26 +08:00
suyiiyii 6f20dbf358 支持对话关联cookie到订阅目标 2024-08-26 17:32:36 +08:00
suyiiyii b655eff755 支持对话添加cookie 2024-08-26 10:37:03 +08:00
suyiiyii c264ad374b 🐛 stash 2024-08-22 21:50:25 +08:00
suyiiyii 7913f7485a 添加cookie相关的数据库表 2024-08-22 20:55:39 +08:00
174 changed files with 25141 additions and 6356 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
"features": {
"ghcr.io/devcontainers-contrib/features/poetry:2": {}
},
"postCreateCommand": "poetry config virtualenvs.in-project true && poetry install -E all && poetry run pre-commit install && npm install -g pnpm && pnpm install",
"postCreateCommand": "poetry config virtualenvs.in-project true && poetry install -E all && poetry run pre-commit install && yarn install",
"customizations": {
"vscode": {
"settings": {
-61
View File
@@ -1,61 +0,0 @@
name: 问题报告
description: 我遇到了问题
body:
- type: markdown
id: environment
attributes:
value: |
## 环境
- type: input
id: nonebot_bison_version
attributes:
label: nonebot-bison 版本
description: 请填写 nonebot-bison 的版本
- type: input
id: nonebot_version
attributes:
label: nonebot 版本
description: 请填写 nonebot 的版本
- type: dropdown
id: installation_method
attributes:
label: 安装方式
description: 请选择安装方式
options:
- 通过 nb-cli 安装
- 使用 poetry/pdm 等现代包管理器安装
- 通过 pip install 安装
- 克隆或下载项目直接使用
- type: input
id: operating_system
attributes:
label: 操作系统
description: 请填写操作系统
- type: textarea
id: issue_description
attributes:
label: 问题
description: 请在这里描述你遇到的问题
- type: textarea
id: logs
attributes:
label: 日志
description: 请在这里粘贴你的日志
render: shell
- type: checkboxes
id: confirmations
attributes:
label: 确认
options:
- label: 我搜索过了 issue,但是并没有发现过与我类似的问题
required: true
- label: 我确认在日志中去掉了敏感信息
required: true
+31
View File
@@ -0,0 +1,31 @@
---
name: 问题报告
about: 我遇到了问题
title: ""
labels: ""
assignees: ""
---
## 环境
- nonebot-bison 版本:
- nonebot 版本:
- 安装方式:(以下方式的一种或者其他方式)
1. 通过 nb-cli 安装
2. 使用 poetry/pdm 等现代包管理器安装
3. 通过 pip install 安装
4. 克隆或下载项目直接使用
- 操作系统:
## 问题
请在这里描述你遇到的问题
## 日志
```
请在这里粘贴你的日志
```
- [ ] 我搜索过了 issue,但是并没有发现过与我类似的问题
- [ ] 我确认在日志中去掉了敏感信息
+2 -2
View File
@@ -10,5 +10,5 @@ runs:
- name: Build Frontend
shell: bash
run: |
pnpm install
pnpm docs:build
yarn install
yarn docs:build
+2 -2
View File
@@ -11,5 +11,5 @@ runs:
shell: bash
working-directory: ./admin-frontend
run: |
pnpm install
pnpm build
yarn install
yarn build
+4 -9
View File
@@ -8,16 +8,11 @@ runs:
with:
node-version: "20"
- name: Set Up Pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- id: pnpm-cache-dir-path
run: echo "::set-output name=dir::$(pnpm store path)"
- id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
shell: bash
- uses: actions/cache@v4
with:
path: ${{ steps.pnpm-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
+2 -2
View File
@@ -80,7 +80,7 @@ jobs:
run: poetry run pytest --cov-report xml --cov=./nonebot_bison -k 'not compare and not render' -n auto
- name: Upload coverage report
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
flags: smoke-test
@@ -114,7 +114,7 @@ jobs:
run: poetry run pytest --cov-report xml --cov=./nonebot_bison -k 'not compare' -n auto
- name: Upload coverage report
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
flags: all-test
+1 -6
View File
@@ -19,9 +19,4 @@ jobs:
- uses: actions/checkout@v4
- name: Run Ruff Lint
uses: astral-sh/ruff-action@v2
with:
src: >-
nonebot_bison/
extra_plugins/
tests/
uses: chartboost/ruff-action@v1
+17 -7
View File
@@ -7,13 +7,23 @@ ci:
autoupdate_commit_msg: ":arrow_up: auto update by pre-commit hooks"
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.2
rev: v0.6.3
hooks:
- id: ruff
args: [--fix]
stages: [pre-commit]
- id: ruff-format
stages: [pre-commit]
args: [--fix, --exit-non-zero-on-fix]
stages: [commit]
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
stages: [commit]
- repo: https://github.com/psf/black
rev: 24.8.0
hooks:
- id: black
stages: [commit]
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v4.0.0-alpha.8
@@ -21,10 +31,10 @@ repos:
- id: prettier
types_or: [javascript, jsx, ts, tsx, markdown, yaml, json]
exclude: "admin-frontend/"
stages: [pre-commit]
stages: [commit]
- repo: https://github.com/pre-commit/mirrors-eslint
rev: v9.16.0
rev: v9.9.1
hooks:
- id: eslint
additional_dependencies:
-28
View File
@@ -1,33 +1,5 @@
# Change Log
## 最近更新
### 新功能
- :sparkles: B站获取OPUS格式动态 [@AzideCupric](https://github.com/AzideCupric) ([#651](https://github.com/MountainDash/nonebot-bison/pull/651))
- :sparkles: 添加 Cookie 组件 [@suyiiyii](https://github.com/suyiiyii) ([#633](https://github.com/MountainDash/nonebot-bison/pull/633))
### Bug 修复
- :bug: 修复 cookie 模块 type hint [@suyiiyii](https://github.com/suyiiyii) ([#658](https://github.com/MountainDash/nonebot-bison/pull/658))
- :bug: B站转发动态补充 DeletedItem 类型解析 [@AzideCupric](https://github.com/AzideCupric) ([#659](https://github.com/MountainDash/nonebot-bison/pull/659))
- :bug: 小刻食堂cdn使用https [@phidiaLam](https://github.com/phidiaLam) ([#650](https://github.com/MountainDash/nonebot-bison/pull/650))
## v0.9.5
### 新功能
- :sparkles: 更新默认UA为Windows平台 [@suyiiyii](https://github.com/suyiiyii) ([#643](https://github.com/MountainDash/nonebot-bison/pull/643))
### Bug 修复
- 🐛 修复微博更换长内容接口 [@phidiaLam](https://github.com/phidiaLam) ([#645](https://github.com/MountainDash/nonebot-bison/pull/645))
- :bug: 修复B站获取匿名Cookie逻辑 [@suyiiyii](https://github.com/suyiiyii) ([#644](https://github.com/MountainDash/nonebot-bison/pull/644))
### 文档
- 📝 小刻食堂剪彩文档 [@phidiaLam](https://github.com/phidiaLam) ([#636](https://github.com/MountainDash/nonebot-bison/pull/636))
## v0.9.4
### Bug 修复
+3
View File
@@ -17,6 +17,9 @@ _✨ 通用订阅推送插件 ✨_
<img src="https://img.shields.io/pypi/v/nonebot-bison?logo=python&logoColor=edb641" alt="pypi">
</a>
<img src="https://img.shields.io/badge/python-3.10+-blue?logo=python&logoColor=edb641" alt="python">
<a href="https://github.com/psf/black">
<img src="https://img.shields.io/badge/code%20style-black-000000.svg?logo=python&logoColor=edb641" alt="black">
</a>
<a href="https://github.com/astral-sh/ruff">
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json" alt="ruff">
</a>
+2 -2
View File
@@ -14,7 +14,7 @@
"@testing-library/react": "^16.0.1",
"@testing-library/user-event": "^14.5.2",
"@types/jest": "^29.5.13",
"@types/node": "^22.9.0",
"@types/node": "^20.16.5",
"@types/react": "^18.3.7",
"@types/react-dom": "^18.3.0",
"react": "^18.3.1",
@@ -25,7 +25,7 @@
"redux": "^5.0.1",
"redux-persist": "^6.0.0",
"typescript": "^5.6.2",
"web-vitals": "^4.2.4"
"web-vitals": "^3.5.2"
},
"scripts": {
"start": "react-scripts start",
+69 -70
View File
@@ -36,8 +36,8 @@ importers:
specifier: ^29.5.13
version: 29.5.13
'@types/node':
specifier: ^22.9.0
version: 22.9.0
specifier: ^20.16.5
version: 20.16.5
'@types/react':
specifier: ^18.3.7
version: 18.3.7
@@ -58,7 +58,7 @@ importers:
version: 6.26.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-scripts:
specifier: 5.0.1
version: 5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(@types/babel__core@7.1.20)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)(react@18.3.1)(type-fest@0.21.3)(typescript@5.6.2)
version: 5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(@types/babel__core@7.1.20)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1)(react@18.3.1)(type-fest@0.21.3)(typescript@5.6.2)
redux:
specifier: ^5.0.1
version: 5.0.1
@@ -69,8 +69,8 @@ importers:
specifier: ^5.6.2
version: 5.6.2
web-vitals:
specifier: ^4.2.4
version: 4.2.4
specifier: ^3.5.2
version: 3.5.2
devDependencies:
'@testing-library/jest-dom':
specifier: ^6.5.0
@@ -86,10 +86,10 @@ importers:
version: 8.57.1
eslint-config-airbnb:
specifier: ^19.0.4
version: 19.0.4(eslint-plugin-import@2.30.0)(eslint-plugin-jsx-a11y@6.10.0(eslint@8.57.1))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-react@7.36.1(eslint@8.57.1))(eslint@8.57.1)
version: 19.0.4(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1))(eslint-plugin-jsx-a11y@6.10.0(eslint@8.57.1))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-react@7.36.1(eslint@8.57.1))(eslint@8.57.1)
eslint-config-airbnb-typescript:
specifier: ^18.0.0
version: 18.0.0(@typescript-eslint/eslint-plugin@8.6.0(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-plugin-import@2.30.0)(eslint@8.57.1)
version: 18.0.0(@typescript-eslint/eslint-plugin@8.6.0(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1))(eslint@8.57.1)
eslint-import-resolver-typescript:
specifier: ^3.6.3
version: 3.6.3(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-plugin-import@2.30.0)(eslint@8.57.1)
@@ -1362,8 +1362,8 @@ packages:
'@types/mime@3.0.1':
resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==}
'@types/node@22.9.0':
resolution: {integrity: sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==}
'@types/node@20.16.5':
resolution: {integrity: sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==}
'@types/parse-json@4.0.0':
resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==}
@@ -2827,7 +2827,6 @@ packages:
eslint@8.57.1:
resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
hasBin: true
espree@9.6.1:
@@ -3209,8 +3208,8 @@ packages:
resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==}
engines: {node: '>= 6'}
http-proxy-middleware@2.0.7:
resolution: {integrity: sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==}
http-proxy-middleware@2.0.6:
resolution: {integrity: sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@types/express': ^4.17.13
@@ -5668,8 +5667,8 @@ packages:
wbuf@1.7.3:
resolution: {integrity: sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==}
web-vitals@4.2.4:
resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==}
web-vitals@3.5.2:
resolution: {integrity: sha512-c0rhqNcHXRkY/ogGDJQxZ9Im9D19hDihbzSQJrsioex+KnFgmMzBiy57Z1EjkhX/+OjyBpclDCzz2ITtjokFmg==}
webidl-conversions@4.0.2:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
@@ -6980,7 +6979,7 @@ snapshots:
'@jest/console@27.5.1':
dependencies:
'@jest/types': 27.5.1
'@types/node': 22.9.0
'@types/node': 20.16.5
chalk: 4.1.2
jest-message-util: 27.5.1
jest-util: 27.5.1
@@ -6989,7 +6988,7 @@ snapshots:
'@jest/console@28.1.3':
dependencies:
'@jest/types': 28.1.3
'@types/node': 22.9.0
'@types/node': 20.16.5
chalk: 4.1.2
jest-message-util: 28.1.3
jest-util: 28.1.3
@@ -7002,7 +7001,7 @@ snapshots:
'@jest/test-result': 27.5.1
'@jest/transform': 27.5.1
'@jest/types': 27.5.1
'@types/node': 22.9.0
'@types/node': 20.16.5
ansi-escapes: 4.3.2
chalk: 4.1.2
emittery: 0.8.1
@@ -7036,7 +7035,7 @@ snapshots:
dependencies:
'@jest/fake-timers': 27.5.1
'@jest/types': 27.5.1
'@types/node': 22.9.0
'@types/node': 20.16.5
jest-mock: 27.5.1
'@jest/expect-utils@29.7.0':
@@ -7047,7 +7046,7 @@ snapshots:
dependencies:
'@jest/types': 27.5.1
'@sinonjs/fake-timers': 8.1.0
'@types/node': 22.9.0
'@types/node': 20.16.5
jest-message-util: 27.5.1
jest-mock: 27.5.1
jest-util: 27.5.1
@@ -7065,7 +7064,7 @@ snapshots:
'@jest/test-result': 27.5.1
'@jest/transform': 27.5.1
'@jest/types': 27.5.1
'@types/node': 22.9.0
'@types/node': 20.16.5
chalk: 4.1.2
collect-v8-coverage: 1.0.1
exit: 0.1.2
@@ -7149,7 +7148,7 @@ snapshots:
dependencies:
'@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4
'@types/node': 22.9.0
'@types/node': 20.16.5
'@types/yargs': 16.0.9
chalk: 4.1.2
@@ -7158,7 +7157,7 @@ snapshots:
'@jest/schemas': 28.1.3
'@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4
'@types/node': 22.9.0
'@types/node': 20.16.5
'@types/yargs': 17.0.32
chalk: 4.1.2
@@ -7167,7 +7166,7 @@ snapshots:
'@jest/schemas': 29.6.3
'@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4
'@types/node': 22.9.0
'@types/node': 20.16.5
'@types/yargs': 17.0.32
chalk: 4.1.2
@@ -7436,20 +7435,20 @@ snapshots:
'@types/body-parser@1.19.2':
dependencies:
'@types/connect': 3.4.35
'@types/node': 22.9.0
'@types/node': 20.16.5
'@types/bonjour@3.5.10':
dependencies:
'@types/node': 22.9.0
'@types/node': 20.16.5
'@types/connect-history-api-fallback@1.3.5':
dependencies:
'@types/express-serve-static-core': 4.17.31
'@types/node': 22.9.0
'@types/node': 20.16.5
'@types/connect@3.4.35':
dependencies:
'@types/node': 22.9.0
'@types/node': 20.16.5
'@types/eslint@8.4.10':
dependencies:
@@ -7464,7 +7463,7 @@ snapshots:
'@types/express-serve-static-core@4.17.31':
dependencies:
'@types/node': 22.9.0
'@types/node': 20.16.5
'@types/qs': 6.9.7
'@types/range-parser': 1.2.4
@@ -7477,13 +7476,13 @@ snapshots:
'@types/graceful-fs@4.1.5':
dependencies:
'@types/node': 22.9.0
'@types/node': 20.16.5
'@types/html-minifier-terser@6.1.0': {}
'@types/http-proxy@1.17.9':
dependencies:
'@types/node': 22.9.0
'@types/node': 20.16.5
'@types/istanbul-lib-coverage@2.0.6': {}
@@ -7506,7 +7505,7 @@ snapshots:
'@types/mime@3.0.1': {}
'@types/node@22.9.0':
'@types/node@20.16.5':
dependencies:
undici-types: 6.19.8
@@ -7533,7 +7532,7 @@ snapshots:
'@types/resolve@1.17.1':
dependencies:
'@types/node': 22.9.0
'@types/node': 20.16.5
'@types/retry@0.12.0': {}
@@ -7546,11 +7545,11 @@ snapshots:
'@types/serve-static@1.15.0':
dependencies:
'@types/mime': 3.0.1
'@types/node': 22.9.0
'@types/node': 20.16.5
'@types/sockjs@0.3.33':
dependencies:
'@types/node': 22.9.0
'@types/node': 20.16.5
'@types/stack-utils@2.0.3': {}
@@ -7560,7 +7559,7 @@ snapshots:
'@types/ws@8.5.3':
dependencies:
'@types/node': 22.9.0
'@types/node': 20.16.5
'@types/yargs-parser@21.0.3': {}
@@ -9098,7 +9097,7 @@ snapshots:
optionalDependencies:
source-map: 0.6.1
eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.30.0)(eslint@8.57.1):
eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1))(eslint@8.57.1):
dependencies:
confusing-browser-globals: 1.0.11
eslint: 8.57.1
@@ -9107,19 +9106,19 @@ snapshots:
object.entries: 1.1.7
semver: 6.3.1
eslint-config-airbnb-typescript@18.0.0(@typescript-eslint/eslint-plugin@8.6.0(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-plugin-import@2.30.0)(eslint@8.57.1):
eslint-config-airbnb-typescript@18.0.0(@typescript-eslint/eslint-plugin@8.6.0(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1))(eslint@8.57.1):
dependencies:
'@typescript-eslint/eslint-plugin': 8.6.0(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2)
'@typescript-eslint/parser': 8.6.0(eslint@8.57.1)(typescript@5.6.2)
eslint: 8.57.1
eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.30.0)(eslint@8.57.1)
eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1))(eslint@8.57.1)
transitivePeerDependencies:
- eslint-plugin-import
eslint-config-airbnb@19.0.4(eslint-plugin-import@2.30.0)(eslint-plugin-jsx-a11y@6.10.0(eslint@8.57.1))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-react@7.36.1(eslint@8.57.1))(eslint@8.57.1):
eslint-config-airbnb@19.0.4(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1))(eslint-plugin-jsx-a11y@6.10.0(eslint@8.57.1))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-react@7.36.1(eslint@8.57.1))(eslint@8.57.1):
dependencies:
eslint: 8.57.1
eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.30.0)(eslint@8.57.1)
eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1))(eslint@8.57.1)
eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.1)
eslint-plugin-react: 7.36.1(eslint@8.57.1)
@@ -9127,7 +9126,7 @@ snapshots:
object.assign: 4.1.4
object.entries: 1.1.6
eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)(jest@27.5.1)(typescript@5.6.2):
eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1)(jest@27.5.1)(typescript@5.6.2):
dependencies:
'@babel/core': 7.25.2
'@babel/eslint-parser': 7.19.1(@babel/core@7.25.2)(eslint@8.57.1)
@@ -9138,7 +9137,7 @@ snapshots:
confusing-browser-globals: 1.0.11
eslint: 8.57.1
eslint-plugin-flowtype: 8.0.3(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(eslint@8.57.1)
eslint-plugin-import: 2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
eslint-plugin-import: 2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1)
eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(jest@27.5.1)(typescript@5.6.2)
eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.1)
eslint-plugin-react: 7.36.1(eslint@8.57.1)
@@ -9168,7 +9167,7 @@ snapshots:
debug: 4.3.5
enhanced-resolve: 5.17.1
eslint: 8.57.1
eslint-module-utils: 2.8.1(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
eslint-module-utils: 2.8.1(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1)
fast-glob: 3.3.2
get-tsconfig: 4.8.0
is-bun-module: 1.1.0
@@ -9181,7 +9180,7 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
eslint-module-utils@2.11.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1):
eslint-module-utils@2.11.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1):
dependencies:
debug: 3.2.7
optionalDependencies:
@@ -9192,7 +9191,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
eslint-module-utils@2.11.0(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1):
eslint-module-utils@2.11.0(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1):
dependencies:
debug: 3.2.7
optionalDependencies:
@@ -9203,7 +9202,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
eslint-module-utils@2.8.1(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1):
eslint-module-utils@2.8.1(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1):
dependencies:
debug: 3.2.7
optionalDependencies:
@@ -9221,7 +9220,7 @@ snapshots:
lodash: 4.17.21
string-natural-compare: 3.0.1
eslint-plugin-import@2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1):
eslint-plugin-import@2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.8
@@ -9232,7 +9231,7 @@ snapshots:
doctrine: 2.1.0
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.11.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
eslint-module-utils: 2.11.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1)
hasown: 2.0.2
is-core-module: 2.15.1
is-glob: 4.0.3
@@ -9260,7 +9259,7 @@ snapshots:
doctrine: 2.1.0
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1)
hasown: 2.0.2
is-core-module: 2.15.1
is-glob: 4.0.3
@@ -9862,7 +9861,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
http-proxy-middleware@2.0.7(@types/express@4.17.14):
http-proxy-middleware@2.0.6(@types/express@4.17.14):
dependencies:
'@types/http-proxy': 1.17.9
http-proxy: 1.18.1
@@ -10146,7 +10145,7 @@ snapshots:
'@jest/environment': 27.5.1
'@jest/test-result': 27.5.1
'@jest/types': 27.5.1
'@types/node': 22.9.0
'@types/node': 20.16.5
chalk: 4.1.2
co: 4.6.0
dedent: 0.7.0
@@ -10249,7 +10248,7 @@ snapshots:
'@jest/environment': 27.5.1
'@jest/fake-timers': 27.5.1
'@jest/types': 27.5.1
'@types/node': 22.9.0
'@types/node': 20.16.5
jest-mock: 27.5.1
jest-util: 27.5.1
jsdom: 16.7.0
@@ -10264,7 +10263,7 @@ snapshots:
'@jest/environment': 27.5.1
'@jest/fake-timers': 27.5.1
'@jest/types': 27.5.1
'@types/node': 22.9.0
'@types/node': 20.16.5
jest-mock: 27.5.1
jest-util: 27.5.1
@@ -10276,7 +10275,7 @@ snapshots:
dependencies:
'@jest/types': 27.5.1
'@types/graceful-fs': 4.1.5
'@types/node': 22.9.0
'@types/node': 20.16.5
anymatch: 3.1.2
fb-watchman: 2.0.2
graceful-fs: 4.2.11
@@ -10295,7 +10294,7 @@ snapshots:
'@jest/source-map': 27.5.1
'@jest/test-result': 27.5.1
'@jest/types': 27.5.1
'@types/node': 22.9.0
'@types/node': 20.16.5
chalk: 4.1.2
co: 4.6.0
expect: 27.5.1
@@ -10369,7 +10368,7 @@ snapshots:
jest-mock@27.5.1:
dependencies:
'@jest/types': 27.5.1
'@types/node': 22.9.0
'@types/node': 20.16.5
jest-pnp-resolver@1.2.2(jest-resolve@27.5.1):
optionalDependencies:
@@ -10407,7 +10406,7 @@ snapshots:
'@jest/test-result': 27.5.1
'@jest/transform': 27.5.1
'@jest/types': 27.5.1
'@types/node': 22.9.0
'@types/node': 20.16.5
chalk: 4.1.2
emittery: 0.8.1
graceful-fs: 4.2.11
@@ -10458,7 +10457,7 @@ snapshots:
jest-serializer@27.5.1:
dependencies:
'@types/node': 22.9.0
'@types/node': 20.16.5
graceful-fs: 4.2.11
jest-snapshot@27.5.1:
@@ -10491,7 +10490,7 @@ snapshots:
jest-util@27.5.1:
dependencies:
'@jest/types': 27.5.1
'@types/node': 22.9.0
'@types/node': 20.16.5
chalk: 4.1.2
ci-info: 3.9.0
graceful-fs: 4.2.11
@@ -10500,7 +10499,7 @@ snapshots:
jest-util@28.1.3:
dependencies:
'@jest/types': 28.1.3
'@types/node': 22.9.0
'@types/node': 20.16.5
chalk: 4.1.2
ci-info: 3.9.0
graceful-fs: 4.2.11
@@ -10509,7 +10508,7 @@ snapshots:
jest-util@29.7.0:
dependencies:
'@jest/types': 29.6.3
'@types/node': 22.9.0
'@types/node': 20.16.5
chalk: 4.1.2
ci-info: 3.9.0
graceful-fs: 4.2.11
@@ -10539,7 +10538,7 @@ snapshots:
dependencies:
'@jest/test-result': 27.5.1
'@jest/types': 27.5.1
'@types/node': 22.9.0
'@types/node': 20.16.5
ansi-escapes: 4.3.2
chalk: 4.1.2
jest-util: 27.5.1
@@ -10549,7 +10548,7 @@ snapshots:
dependencies:
'@jest/test-result': 28.1.3
'@jest/types': 28.1.3
'@types/node': 22.9.0
'@types/node': 20.16.5
ansi-escapes: 4.3.2
chalk: 4.1.2
emittery: 0.10.2
@@ -10558,19 +10557,19 @@ snapshots:
jest-worker@26.6.2:
dependencies:
'@types/node': 22.9.0
'@types/node': 20.16.5
merge-stream: 2.0.0
supports-color: 7.2.0
jest-worker@27.5.1:
dependencies:
'@types/node': 22.9.0
'@types/node': 20.16.5
merge-stream: 2.0.0
supports-color: 8.1.1
jest-worker@28.1.3:
dependencies:
'@types/node': 22.9.0
'@types/node': 20.16.5
merge-stream: 2.0.0
supports-color: 8.1.1
@@ -11682,7 +11681,7 @@ snapshots:
'@remix-run/router': 1.19.2
react: 18.3.1
react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(@types/babel__core@7.1.20)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)(react@18.3.1)(type-fest@0.21.3)(typescript@5.6.2):
react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(@types/babel__core@7.1.20)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1)(react@18.3.1)(type-fest@0.21.3)(typescript@5.6.2):
dependencies:
'@babel/core': 7.25.2
'@pmmmwh/react-refresh-webpack-plugin': 0.5.9(react-refresh@0.11.0)(type-fest@0.21.3)(webpack-dev-server@4.11.1(webpack@5.94.0))(webpack@5.94.0)
@@ -11700,7 +11699,7 @@ snapshots:
dotenv: 10.0.0
dotenv-expand: 5.1.0
eslint: 8.57.1
eslint-config-react-app: 7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)(jest@27.5.1)(typescript@5.6.2)
eslint-config-react-app: 7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.6.0(eslint@8.57.1)(typescript@5.6.2))(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1)(jest@27.5.1)(typescript@5.6.2)
eslint-webpack-plugin: 3.2.0(eslint@8.57.1)(webpack@5.94.0)
file-loader: 6.2.0(webpack@5.94.0)
fs-extra: 10.1.0
@@ -12704,7 +12703,7 @@ snapshots:
dependencies:
minimalistic-assert: 1.0.1
web-vitals@4.2.4: {}
web-vitals@3.5.2: {}
webidl-conversions@4.0.2: {}
@@ -12740,7 +12739,7 @@ snapshots:
express: 4.19.2
graceful-fs: 4.2.11
html-entities: 2.3.3
http-proxy-middleware: 2.0.7(@types/express@4.17.14)
http-proxy-middleware: 2.0.6(@types/express@4.17.14)
ipaddr.js: 2.0.1
open: 8.4.0
p-retry: 4.6.2
-9
View File
@@ -9,7 +9,6 @@ import SubscribeManager from './features/subsribeConfigManager/SubscribeManager'
import WeightConfig from './features/weightConfig/WeightManager';
import Home from './pages/Home';
import Unauthed from './pages/Unauthed';
import CookieManager from './features/cookieManager/CookieManager';
function App() {
const dispatch = useAppDispatch();
@@ -47,14 +46,6 @@ function App() {
path: 'weight',
element: <WeightConfig />,
},
{
path: 'cookie',
element: <CookieManager />,
},
{
path: 'cookie/:siteName',
element: <CookieManager />,
},
],
},
], { basename: '/bison' });
+1 -7
View File
@@ -17,7 +17,6 @@ import globalConfReducer from '../features/globalConf/globalConfSlice';
import { subscribeApi } from '../features/subsribeConfigManager/subscribeConfigSlice';
import { targetNameApi } from '../features/targetName/targetNameSlice';
import { weightApi } from '../features/weightConfig/weightConfigSlice';
import { cookieApi, cookieTargetApi } from '../features/cookieManager/cookieConfigSlice';
const rootReducer = combineReducers({
auth: authReducer,
@@ -25,8 +24,6 @@ const rootReducer = combineReducers({
[subscribeApi.reducerPath]: subscribeApi.reducer,
[weightApi.reducerPath]: weightApi.reducer,
[targetNameApi.reducerPath]: targetNameApi.reducer,
[cookieApi.reducerPath]: cookieApi.reducer,
[cookieTargetApi.reducerPath]: cookieTargetApi.reducer,
});
const persistConfig = {
@@ -46,10 +43,7 @@ export const store = configureStore({
})
.concat(subscribeApi.middleware)
.concat(weightApi.middleware)
.concat(targetNameApi.middleware)
.concat(cookieApi.middleware)
.concat(cookieTargetApi.middleware),
.concat(targetNameApi.middleware),
});
export const persistor = persistStore(store);
@@ -1,78 +0,0 @@
import React, { useState } from 'react';
import { Form, Input, Modal } from '@arco-design/web-react';
import { useNewCookieMutation } from './cookieConfigSlice';
import { useAppDispatch } from '../../app/hooks';
import validateCookie from './cookieValidateReq';
interface CookieAddModalProps {
visible: boolean;
setVisible: (arg0: boolean) => void;
siteName: string;
}
function CookieAddModal({ visible, setVisible, siteName }: CookieAddModalProps) {
const FormItem = Form.Item;
const [content, setContent] = useState<string>('');
const [confirmLoading, setConfirmLoading] = useState(false);
const [newCookie] = useNewCookieMutation();
const dispatch = useAppDispatch();
const onSubmit = () => {
const postPromise: ReturnType<typeof newCookie> = newCookie({ siteName, content });
setConfirmLoading(true);
postPromise.then(() => {
setConfirmLoading(false);
setVisible(false);
setContent('');
});
};
return (
<Modal
title="添加 Cookie"
visible={visible}
onCancel={() => setVisible(false)}
confirmLoading={confirmLoading}
onOk={onSubmit}
style={{ maxWidth: '90vw' }}
>
<Form autoComplete="off">
<FormItem label="站点" required>
<Input placeholder="Please enter site name" value={siteName} disabled />
</FormItem>
<FormItem
label="Cookie"
required
field="content"
hasFeedback
rules={[
{
validator: (value, callback) => new Promise<void>((resolve) => {
dispatch(validateCookie(siteName, value))
.then((res) => {
if (res) {
callback();
} else {
callback('Cookie 格式错误');
}
resolve();
});
}),
},
]}
>
<Input.TextArea
placeholder="请输入 Cookie"
value={content}
onChange={setContent}
/>
</FormItem>
</Form>
</Modal>
);
}
export default CookieAddModal;
@@ -1,128 +0,0 @@
import React, { useState } from 'react';
import {
Button, Empty, Form, Input, Modal, Space, Table,
} from '@arco-design/web-react';
import { useDeleteCookieTargetMutation, useGetCookieTargetsQuery } from './cookieConfigSlice';
import { Cookie, CookieTarget } from '../../utils/type';
import CookieTargetModal from '../cookieTargetManager/CookieTargetModal';
interface CookieEditModalProps {
visible: boolean;
setVisible: (arg0: boolean) => void;
cookie: Cookie | null
}
function CookieEditModal({ visible, setVisible, cookie }: CookieEditModalProps) {
if (!cookie) {
return <Empty />;
}
const FormItem = Form.Item;
// const [confirmLoading, setConfirmLoading] = useState(false);
const [deleteCookieTarget] = useDeleteCookieTargetMutation();
// 获取 Cookie Target
const { data: cookieTargets } = useGetCookieTargetsQuery({ cookieId: cookie.id });
// 添加 Cookie Target
const [showAddCookieTargetModal, setShowAddCookieTargetModal] = useState(false);
const handleAddCookieTarget = () => () => {
setShowAddCookieTargetModal(true);
};
// 删除 Cookie Target
const handleDelete = (record: CookieTarget) => () => {
deleteCookieTarget({
cookieId: record.cookie_id,
target: record.target.target,
platformName: record.target.platform_name,
});
};
const columns = [
{
title: '平台名称',
dataIndex: 'target.platform_name',
},
{
title: '订阅名称',
dataIndex: 'target.target_name',
},
{
title: 'Cookie ID',
dataIndex: 'cookie_id',
},
{
title: '操作',
dataIndex: 'op',
render: (_: null, record: CookieTarget) => (
<Space size="small">
<Button type="text" status="danger" onClick={handleDelete(record)}></Button>
</Space>
),
},
];
return (
<>
<Modal
title="编辑 Cookie"
visible={visible}
onCancel={() => setVisible(false)}
// confirmLoading={confirmLoading}
onOk={() => setVisible(false)}
style={{ maxWidth: '90vw', minWidth: '50vw' }}
>
<Form autoComplete="off">
<FormItem label="Cookie ID">
<Input disabled value={cookie.id.toString()} />
</FormItem>
<FormItem label="Cookie 名称">
<Input value={cookie.cookie_name} disabled />
</FormItem>
<FormItem label="所属站点">
<Input value={cookie.site_name} disabled />
</FormItem>
<FormItem label="内容">
<Input.TextArea
value={cookie.content}
disabled
/>
</FormItem>
<FormItem label="标签">
<Input.TextArea
value={JSON.stringify(cookie.tags)}
disabled
/>
</FormItem>
<FormItem label="最后使用时间">
<Input value={cookie.last_usage.toString()} disabled />
</FormItem>
<FormItem label="状态">
<Input value={cookie.status} disabled />
</FormItem>
<FormItem label="冷却时间(毫秒)">
<Input value={cookie.cd_milliseconds.toString()} disabled />
</FormItem>
</Form>
<Button type="primary" onClick={handleAddCookieTarget()}> Cookie</Button>
<Table
columns={columns}
data={cookieTargets}
rowKey={(record: CookieTarget) => `${record.target.platform_name}-${record.target.target}`}
scroll={{ x: true }}
/>
</Modal>
<CookieTargetModal
cookie={cookie}
visible={showAddCookieTargetModal}
setVisible={setShowAddCookieTargetModal}
/>
</>
);
}
export default CookieEditModal;
@@ -1,13 +0,0 @@
.list-actions-icon {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border-radius: 50%;
transition: all 0.1s;
}
.list-actions-icon:hover {
background-color: var(--color-fill-3);
}
@@ -1,111 +0,0 @@
import React from 'react';
import {
Button,
Table, TableColumnProps, Typography, Space, Popconfirm,
} from '@arco-design/web-react';
import { useParams } from 'react-router-dom';
import { useGetCookiesQuery, useDeleteCookieMutation } from './cookieConfigSlice';
import './CookieManager.css';
import { Cookie } from '../../utils/type';
import CookieAddModal from './CookieAddModal';
import CookieEditModal from './CookieEditModal';
export default function CookieManager() {
const { siteName } = useParams();
const { data: cookieDict } = useGetCookiesQuery();
const cookiesList = cookieDict ? Object.values(cookieDict) : [];
// 添加cookie
const [showAddModal, setShowAddModal] = React.useState(false);
const handleAddCookie = () => () => {
setShowAddModal(true);
};
// 删除cookie
const [deleteCookie] = useDeleteCookieMutation();
const handleDelCookie = (cookieId: string) => () => {
deleteCookie({
cookieId,
});
};
// 编辑cookie
const [showEditModal, setShowEditModal] = React.useState(false);
const [editCookie, setEditCookie] = React.useState<Cookie | null>(null);
const handleEditCookie = (cookie: Cookie) => () => {
setEditCookie(cookie);
setShowEditModal(true);
};
let data = [];
if (siteName) {
data = cookiesList.filter((tSite) => tSite.site_name === siteName);
}
const columns: TableColumnProps[] = [
{
title: 'ID',
dataIndex: 'id',
},
{
title: 'Cookie 名称',
dataIndex: 'cookie_name',
},
{
title: '所属站点',
dataIndex: 'site_name',
},
{
title: '最后使用时间',
dataIndex: 'last_usage',
},
{
title: '状态',
dataIndex: 'status',
},
{
title: 'CD',
dataIndex: 'cd_milliseconds',
}, {
title: '操作',
dataIndex: 'op',
render: (_: null, record: Cookie) => (
<Space size="small">
<Popconfirm
title={`确定删除 Cookie ${record.cookie_name} `}
onOk={handleDelCookie(record.id.toString())}
>
<span className="list-actions-icon">
{/* <IconDelete /> */}
<Button type="text" status="danger"></Button>
</span>
</Popconfirm>
<Button type="text" onClick={handleEditCookie(record)}></Button>
</Space>
),
},
];
return (
<>
<div>
<Typography.Title heading={4} style={{ margin: '15px' }}>Cookie </Typography.Title>
<Button
style={{ width: '90px', margin: '20px 10px' }}
type="primary"
onClick={handleAddCookie()}
>
</Button>
</div>
<Table columns={columns} data={data} />
<CookieAddModal visible={showAddModal} setVisible={setShowAddModal} siteName={siteName || ''} />
<CookieEditModal visible={showEditModal} setVisible={setShowEditModal} cookie={editCookie} />
</>
);
}
@@ -1,66 +0,0 @@
import { createApi } from '@reduxjs/toolkit/query/react';
import {
StatusResp, Cookie, NewCookieParam,
DelCookieParam, CookieTarget, NewCookieTargetParam, DelCookieTargetParam,
} from '../../utils/type';
import { baseQueryWithAuth } from '../auth/authQuery';
export const cookieApi = createApi({
reducerPath: 'cookie',
baseQuery: baseQueryWithAuth,
tagTypes: ['Cookie'],
endpoints: (builder) => ({
getCookies: builder.query<Cookie, void>({
query: () => '/cookie',
providesTags: ['Cookie'],
}),
newCookie: builder.mutation<StatusResp, NewCookieParam>({
query: ({ siteName, content }) => ({
method: 'POST',
url: `/cookie?site_name=${siteName}&content=${content}`,
}),
invalidatesTags: ['Cookie'],
}),
deleteCookie: builder.mutation<StatusResp, DelCookieParam>({
query: ({ cookieId }) => ({
method: 'DELETE',
url: `/cookie/${cookieId}`,
}),
invalidatesTags: ['Cookie'],
}),
}),
});
export const {
useGetCookiesQuery, useNewCookieMutation, useDeleteCookieMutation,
} = cookieApi;
export const cookieTargetApi = createApi({
reducerPath: 'cookieTarget',
baseQuery: baseQueryWithAuth,
tagTypes: ['CookieTarget'],
endpoints: (builder) => ({
getCookieTargets: builder.query<CookieTarget[], { cookieId: number }>({
query: ({ cookieId }) => `/cookie_target?cookie_id=${cookieId}`,
providesTags: ['CookieTarget'],
}),
newCookieTarget: builder.mutation<StatusResp, NewCookieTargetParam>({
query: ({ platformName, target, cookieId }) => ({
method: 'POST',
url: `/cookie_target?platform_name=${platformName}&target=${encodeURIComponent(target)}&cookie_id=${cookieId}`,
}),
invalidatesTags: ['CookieTarget'],
}),
deleteCookieTarget: builder.mutation<StatusResp, DelCookieTargetParam>({
query: ({ platformName, target, cookieId }) => ({
method: 'DELETE',
url: `/cookie_target?platform_name=${platformName}&target=${encodeURIComponent(target)}&cookie_id=${cookieId}`,
}),
invalidatesTags: ['CookieTarget'],
}),
}),
});
export const {
useGetCookieTargetsQuery, useNewCookieTargetMutation, useDeleteCookieTargetMutation,
} = cookieTargetApi;
@@ -1,20 +0,0 @@
import { AppThunk } from '../../app/store';
import { baseUrl } from '../../utils/urls';
// eslint-disable-next-line
export const validCookie =
(siteName: string, content: string): AppThunk<Promise<string>> => async (_, getState) => {
const url = `${baseUrl}cookie/validate?site_name=${siteName}&content=${content}`;
const state = getState();
const authToken = state.auth.token;
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${authToken}`,
},
method: 'POST',
});
const resObj = await res.json();
return resObj.ok;
};
export default validCookie;
@@ -1,109 +0,0 @@
import React
from 'react';
import {
Empty, Form, Modal, Select,
} from '@arco-design/web-react';
import { Cookie, SubscribeConfig, SubscribeGroupDetail } from '../../utils/type';
import { useNewCookieTargetMutation } from '../cookieManager/cookieConfigSlice';
import { useGetSubsQuery } from '../subsribeConfigManager/subscribeConfigSlice';
import { useAppSelector } from '../../app/hooks';
import { selectPlatformConf } from '../globalConf/globalConfSlice';
interface SubscribeModalProp {
cookie:Cookie| null
visible: boolean;
setVisible: (arg0: boolean) => void;
}
export default function CookieTargetModal({
cookie, visible, setVisible,
}: SubscribeModalProp) {
if (!cookie) {
return <Empty />;
}
const [newCookieTarget] = useNewCookieTargetMutation();
const FormItem = Form.Item;
// 筛选出当前Cookie支持的平台
const platformConf = useAppSelector(selectPlatformConf);
const platformThatSiteSupport = Object.values(platformConf).reduce((p, c) => {
if (c.siteName in p) {
p[c.siteName].push(c.platformName);
} else {
p[c.siteName] = [c.platformName];
}
return p;
}, {} as Record<string, string[]>);
const supportedPlatform = platformThatSiteSupport[cookie.site_name];
const { data: subs } = useGetSubsQuery();
const pureSubs:SubscribeConfig[] = subs ? Object.values(subs)
.reduce((
pv:Array<SubscribeConfig>,
cv:SubscribeGroupDetail,
) => pv.concat(cv.subscribes), []) : [];
const filteredSubs = pureSubs.filter((sub) => supportedPlatform.includes(sub.platformName));
const [index, setIndex] = React.useState(-1);
const handleSubmit = (idx:number) => {
const postPromise: ReturnType<typeof newCookieTarget> = newCookieTarget({
cookieId: cookie.id,
platformName: filteredSubs[idx].platformName,
target: filteredSubs[idx].target,
});
postPromise.then(() => {
setVisible(false);
});
};
const { Option } = Select;
return (
<Modal
title="关联 Cookie"
visible={visible}
onCancel={() => setVisible(false)}
onOk={() => handleSubmit(index)}
>
<Form>
<FormItem label="平台">
<Select
placeholder="选择要关联的平台"
style={{ width: '100%' }}
onChange={setIndex}
>
{supportedPlatform.length
&& supportedPlatform.map((sub, idx) => (
<Option
key={JSON.stringify(sub)}
value={idx}
>
{sub}
</Option>
))}
</Select>
</FormItem>
<FormItem label="订阅目标" required>
<Select
placeholder="选择要关联的订阅目标"
style={{ width: '100%' }}
onChange={setIndex}
>
{filteredSubs.length
&& filteredSubs.map((sub, idx) => (
<Option
key={JSON.stringify(sub)}
value={idx}
>
{sub.targetName}
</Option>
))}
</Select>
</FormItem>
</Form>
</Modal>
);
}
@@ -6,7 +6,6 @@ import { globalConfUrl } from '../../utils/urls';
const initialState = {
loaded: false,
platformConf: {},
siteConf: {},
} as GlobalConf;
export const loadGlobalConf = createAsyncThunk(
@@ -25,7 +24,6 @@ export const globalConfSlice = createSlice({
builder
.addCase(loadGlobalConf.fulfilled, (state, payload) => {
state.platformConf = payload.payload.platformConf;
state.siteConf = payload.payload.siteConf;
state.loaded = true;
});
},
@@ -35,4 +33,3 @@ export default globalConfSlice.reducer;
export const selectGlobalConfLoaded = (state: RootState) => state.globalConf.loaded;
export const selectPlatformConf = (state: RootState) => state.globalConf.platformConf;
export const selectSiteConf = (state: RootState) => state.globalConf.siteConf;
+4 -50
View File
@@ -1,15 +1,13 @@
import React, { ReactNode, useEffect, useState } from 'react';
import { Breadcrumb, Layout, Menu } from '@arco-design/web-react';
import {
IconRobot, IconDashboard, IconIdcard,
} from '@arco-design/web-react/icon';
import { IconRobot, IconDashboard } from '@arco-design/web-react/icon';
import './Home.css';
// import SubscribeManager from '../features/subsribeConfigManager/SubscribeManager';
import {
Link, Navigate, Outlet, useLocation, useNavigate,
} from 'react-router-dom';
import { useAppSelector } from '../app/hooks';
import { selectIsLogin } from '../features/auth/authSlice';
import { selectSiteConf } from '../features/globalConf/globalConfSlice';
export default function Home() {
const location = useLocation();
@@ -25,12 +23,6 @@ export default function Home() {
if (path !== '/home/groups' && !path.startsWith('/home/groups/') && path !== '/home/weight') {
navigate('/home/groups');
}
if (path === '/home/cookie') {
navigate('/home/cookie');
}
if (path.startsWith('/home/cookie/')) {
navigate(path);
}
}, [path]);
let currentKey = '';
@@ -38,8 +30,6 @@ export default function Home() {
currentKey = 'groups';
} else if (path.startsWith('/home/groups/')) {
currentKey = 'subs';
} else if (path.startsWith('/home/cookie/')) {
currentKey = path.substring(6);
}
const [selectedTab, changeSelectTab] = useState(currentKey);
@@ -50,10 +40,6 @@ export default function Home() {
navigate('/home/groups');
} else if (tab === 'weight') {
navigate('/home/weight');
} else if (tab === 'cookie') {
navigate('/home/cookie');
} else if (tab.startsWith('cookie/')) {
navigate(`/home/${tab}`);
}
};
@@ -94,22 +80,7 @@ export default function Home() {
</Breadcrumb.Item>
</Breadcrumb>
);
} else if (path.startsWith('/home/cookie')) {
breadcrumbContent = (
<Breadcrumb style={{ margin: '16px 0' }}>
<Breadcrumb.Item>
<Link to="/home/cookie">
<IconIdcard />
Cookie
</Link>
</Breadcrumb.Item>
</Breadcrumb>
);
}
const MenuItem = Menu.Item;
const { SubMenu } = Menu;
const siteConf = useAppSelector(selectSiteConf);
return (
<Layout className="layout-collapse-demo">
<Layout.Header>
@@ -124,29 +95,12 @@ export default function Home() {
>
<Menu
defaultSelectedKeys={[selectedTab]}
onClickMenuItem={(key) => {
handleTabSelect(key);
}}
onClickMenuItem={(key) => { handleTabSelect(key); }}
>
<Menu.Item key="groups">
<IconRobot />
</Menu.Item>
<SubMenu
key="cookie"
title={(
<>
<IconIdcard />
Cookie
</>
)}
>
{Object.values(siteConf).filter((site) => site.enable_cookie).map((site) => (
<MenuItem key={`cookie/${site.name}`}>
{site.name}
</MenuItem>
))}
</SubMenu>
<Menu.Item key="weight">
<IconDashboard />
@@ -155,7 +109,7 @@ export default function Home() {
</Layout.Sider>
<Layout.Content style={{ padding: '0 1em' }}>
<Layout style={{ height: '100%' }}>
{breadcrumbContent}
{ breadcrumbContent }
<Layout.Content style={{ margin: '0.5em', padding: '2em' }}>
<Outlet />
</Layout.Content>
-57
View File
@@ -4,10 +4,8 @@ export interface TokenResp {
id: number;
name: string;
}
export interface GlobalConf {
platformConf: AllPlatformConf;
siteConf: AllSiteConf;
loaded: boolean;
}
@@ -15,10 +13,6 @@ export interface AllPlatformConf {
[idx: string]: PlatformConfig;
}
export interface AllSiteConf {
[idx: string]: SiteConfig;
}
export interface CategoryConfig {
[idx: number]: string;
}
@@ -28,15 +22,9 @@ export interface PlatformConfig {
categories: CategoryConfig;
enabledTag: boolean;
platformName: string;
siteName: string;
hasTarget: boolean;
}
export interface SiteConfig {
name: string;
enable_cookie: string;
}
export interface SubscribeConfig {
platformName: string;
target: string;
@@ -81,48 +69,3 @@ export interface PlatformWeightConfigResp {
platform_name: string;
weight: WeightConfig;
}
export interface Target {
platform_name: string;
target_name: string;
target: string;
}
export interface Cookie {
id: number;
site_name: string;
content: string;
cookie_name: string;
last_usage: Date;
status: string;
cd_milliseconds: number;
is_universal: boolean;
is_anonymous: boolean;
tags: { [key: string]: string };
}
export interface CookieTarget {
target: Target;
cookie_id: number;
}
export interface NewCookieParam {
siteName: string;
content: string;
}
export interface DelCookieParam {
cookieId: string;
}
export interface NewCookieTargetParam {
platformName: string;
target: string;
cookieId: number;
}
export interface DelCookieTargetParam {
platformName: string;
target: string;
cookieId: number;
}
+1 -1
View File
@@ -22,7 +22,7 @@ BISON_SKIP_BROWSER_CHECK=false
BISON_USE_PIC_MERGE=0
BISON_RESEND_TIMES=0
BISON_PROXY=
BISON_UA=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0
BISON_UA=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36
BISON_SHOW_NETWORK_WARNING=true
BISON_PLATFORM_THEME='{}'
+2 -3
View File
@@ -1,8 +1,7 @@
FROM node:22.11.0 AS frontend
FROM node:20.17.0 as frontend
ADD . /app
WORKDIR /app/admin-frontend
RUN npm install -g pnpm
RUN pnpm install && pnpm build
RUN yarn && yarn build
FROM python:3.11
RUN python3 -m pip install poetry && poetry config virtualenvs.create false
+4 -3
View File
@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1.10
FROM python:3.11-slim-bullseye AS base
FROM python:3.11-slim-bullseye as base
FROM base AS builder
FROM base as builder
ENV PYTHONFAULTHANDLER=1 \
PYTHONUNBUFFERED=1 \
@@ -12,6 +12,7 @@ ENV PYTHONFAULTHANDLER=1 \
POETRY_NO_INTERACTION=1 \
POETRY_VIRTUALENVS_CREATE=false \
PATH="$PATH:/runtime/bin" \
PYTHONPATH="$PYTHONPATH:/runtime/lib/python3.10/site-packages" \
# Versions:
POETRY_VERSION=1.7.1
RUN apt-get update && apt-get install -y build-essential unzip wget python-dev git
@@ -24,7 +25,7 @@ COPY README.md pyproject.toml poetry.lock /src/
RUN poetry export --only=main,docker --without-hashes --no-interaction --no-ansi -f requirements.txt -o requirements.txt
RUN pip install --prefix=/runtime --force-reinstall -r requirements.txt
FROM base AS runtime
FROM base as runtime
WORKDIR /app
RUN --mount=type=cache,target=/var/cache/apt \
+4 -3
View File
@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1.10
FROM python:3.11-slim-bullseye AS base
FROM python:3.11-slim-bullseye as base
FROM base AS builder
FROM base as builder
ENV PYTHONFAULTHANDLER=1 \
PYTHONUNBUFFERED=1 \
@@ -12,6 +12,7 @@ ENV PYTHONFAULTHANDLER=1 \
POETRY_NO_INTERACTION=1 \
POETRY_VIRTUALENVS_CREATE=false \
PATH="$PATH:/runtime/bin" \
PYTHONPATH="$PYTHONPATH:/runtime/lib/python3.10/site-packages" \
# Versions:
POETRY_VERSION=1.7.1
RUN apt-get update && apt-get install -y build-essential unzip wget python3-dev git
@@ -24,7 +25,7 @@ COPY pyproject.toml poetry.lock /src/
RUN poetry export --only=main,docker --without-hashes --no-interaction --no-ansi -f requirements.txt -o requirements.txt
RUN pip install --prefix=/runtime --force-reinstall -r requirements.txt
FROM base AS runtime
FROM base as runtime
WORKDIR /app
RUN --mount=type=cache,target=/var/cache/apt \
+1 -19
View File
@@ -23,29 +23,11 @@ export default navbar([
link: "",
activeMatch: "^/usage/?$",
},
{
text: "Cookie 使用",
icon: "cookie",
link: "cookie",
},
],
},
{
text: "开发",
icon: "flask",
prefix: "/dev/",
children: [
{
text: "基本开发",
icon: "tools",
link: "",
activeMatch: "^/dev/?$",
},
{
text: "Cookie 开发",
icon: "cookie",
link: "cookie",
},
],
link: "/dev/",
},
]);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

+3 -1
View File
@@ -80,7 +80,6 @@ export default hopeTheme({
sup: true,
tabs: true,
vPre: true,
mermaid: true,
// 在启用之前安装 chart.js
// chart: true,
@@ -102,6 +101,9 @@ export default hopeTheme({
// 在启用之前安装 mathjax-full
// mathjax: true,
// 在启用之前安装 mermaid
// mermaid: true,
// playground: {
// presets: ["ts", "vue"],
// },
+1 -1
View File
@@ -70,7 +70,7 @@ highlights:
details: 一个由玩家创造的幻想世界
link: https://adsrff.web.sdo.com/web1/
- title: 小刻食堂
- title: 小刻食堂 (即将支持)
details: 实时获取鹰角发的动态
link: https://www.ceobecanteen.top
-5
View File
@@ -1,8 +1,3 @@
---
prev: /usage/install
next: /dev/cookie
---
# 基本开发须知
## 语言以及工具
-157
View File
@@ -1,157 +0,0 @@
---
prev: /usage/
#next: /dev/cookie
---
# Cookie 开发须知
本项目将大部分 Cookie 相关逻辑提出到了 Site 及 ClientManger 模块中,只需要继承相关类即可获得使用 Cookie 的能力。
::: tip
在开发 Cookie 功能之前,你应该对[基本开发](/dev/#基本开发)有一定的了解。
:::
## Cookie 相关的基本概念
- `nonebot_bison.config.db_model.Cookie`: 用于存储 Cookie 的实体类,包含了 Cookie 的名称、内容、状态等信息
- `nonebot_bison.config.db_model.CookieTarget`: 用于存储 Cookie 与订阅的关联关系
- `nonebot_bison.utils.site.CookieClientManager`: 添加了 Cookie 功能的 ClientManager,是 Cookie 管理功能的核心,调度 Cookie 的功能就在这里实现
## 快速上手
例如,现在有一个这样子的 Site 类:
```python
class WeiboSite(Site):
name = "weibo.com"
schedule_type = "interval"
schedule_setting = {"seconds": 3}
```
简而言之,要让站点获得 Cookie 能力,只需要:
为 Site 类添加一个`client_mgr`字段,值为`CookieClientManager.from_name(name)`,其中`name`为站点名称,这是默认的 Cookie 管理器。
```python {5}
class WeiboSite(Site):
name = "weibo.com"
schedule_type = "interval"
schedule_setting = {"seconds": 3}
client_mgr = CookieClientManager.from_name(name)
```
至此,站点就可以使用 Cookie 了!
## 更好的体验
为了给用户提供更好的体验,还可以创建自己的 `ClientManager`:继承 `CookieClientManager` 并重写`validate_cookie`和`get_target_name`方法。
- `async def validate_cookie(cls, content: str) -> bool`该方法将会在 Cookie 添加时被调用,可以在这里验证 Cookie 的有效性
- `async def get_cookie_name(cls, content: str) -> str`该方法将会在验证 Cookie 成功后被调用,可以在这里设置 Cookie 的名字并展示给用户
## 自定义 Cookie 调度策略
当默认的 Cookie 调度逻辑无法满足需求时,可以重写`CookieClientManager`的`_choose_cookie`方法。
目前整体的调度逻辑是:
```mermaid
sequenceDiagram
participant Scheduler
participant Platform
participant CookieClientManager
participant DB
participant Internet
Scheduler->>Platform: exec_fetch
Platform->>Platform: do_fetch_new_post(SubUnit)
Platform->>Platform: get_sub_list(Target)
Platform->>CookieClientManager: get_client(Target)
CookieClientManager->>CookieClientManager: _choose_cookie(Target)
CookieClientManager->>DB: get_cookies()
CookieClientManager->>CookieClientManager: _assemble_client(Target, cookie)
CookieClientManager->>Platform: client
Platform->>Internet: client.get(Target)
Internet->>Platform: response
Platform->>CookieClientManager: _response_hook()
CookieClientManager->>DB: update_cookie()
```
目前 CookieClientManager 具有以下方法
- `refresh_anonymous_cookie(cls)` 移除已有的匿名 cookie,添加一个新的匿名 cookie,应该在 CCM 初始化时调用
- `add_user_cookie(cls, content: str)` 添加用户 cookie,在这里对 Cookie 进行检查并获取 cookie_name,写入数据库
- `_generate_hook(self, cookie: Cookie) -> Callable` hook 函数生成器,用于回写请求状态到数据库
- `_choose_cookie(self, target: Target) -> Cookie` 选择 cookie 的具体算法
- `add_user_cookie(cls, content: str, cookie_name: str | None = None) -> Cookie` 对外的接口,添加用户 cookie,内部会调用 Site 的方法进行检查
- `get_client(self, target: Target | None) -> AsyncClient` 对外的接口,获取 client,根据 target 选择 cookie
- `_assemble_client(self, client, cookie) -> AsyncClient` 组装 client,可以自定义 cookie 对象的 content 装配到 client 中的方式
::: details 大致流程
1. `Platfrom` 调用 `CookieClientManager.get_client` 方法,传入 `Target` 对象
2. `CookieClientManager` 根据 `Target` 选择一个 `Cookie` 对象
3. `CookieClientManager` 调用 `CookieClientManager._assemble_client` 方法,将 Cookie 装配到 `Client` 中
4. `Platform` 使用 `Client` 进行请求
:::
简单来说:
- 如果需要修改 Cookie 的默认参数,可以重写`add_user_cookie`方法,这里设置需要的字段
- 如果需要修改选择 Cookie 的逻辑,可以重写`_choose_cookie`方法,使用自己的算法选择合适的 Cookie 并返回
- 如果需要自定义 Cookie 的格式,可以重写`valid_cookie`方法,自定义验证 Cookie 的逻辑,并重写`_assemble_client`方法,自定义将 Cookie 装配到 Client 中的逻辑
- 如果要在请求结束后做一些操作(例如保存此次请求的结果/状态),可以重写`_response_hook`方法,自定义请求结束后的行为
- 如果需要跳过一次请求,可以在 `get_client` 方法中抛出 `SkipRequestException` 异常,调度器会捕获该异常并跳过此次请求
## 实名 Cookie 和匿名 Cookie
部分站点所有接口都需要携带 Cookie,对于匿名用户(未登录)也会发放一个临时 Cookie,本项目称为匿名 Cookie。
在此基础上,我们添加了用户上传 Cookie 的功能,这种 Cookie 本项目称为实名 Cookie。
匿名 Cookie 和实名 Cookie 在同一个框架下统一调度,实名 Cookie 优先级高于匿名 Cookie。为了调度,Cookie 对象有以下字段:
```python
# 最后使用的时刻
last_usage: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime(1970, 1, 1))
# Cookie 当前的状态
status: Mapped[str] = mapped_column(String(20), default="")
# 使用一次之后,需要的冷却时间
cd_milliseconds: Mapped[int] = mapped_column(default=0)
# 是否是通用 Cookie(对所有 Target 都有效)
is_universal: Mapped[bool] = mapped_column(default=False)
# 是否是匿名 Cookie
is_anonymous: Mapped[bool] = mapped_column(default=False)
# 标签,扩展用
tags: Mapped[dict[str, Any]] = mapped_column(JSON().with_variant(JSONB, "postgresql"), default={})
```
其中:
- **is_universal**:用于标记 Cookie 是否为通用 Cookie,即对所有 Target 都有效。可以理解为是一种特殊的 target,添加 Cookie 和获取 Cookie 时通过传入参数进行设置。
- **is_anonymous**:用于标记 Cookie 是否为匿名 Cookie,目前的定义是:可以由程序自动生成的,适用于所有 Target 的 Cookie。目前的逻辑是 bison 启动时,生成一个新的匿名 Cookie 并替换掉原有的匿名 Cookie。
- **无 Target 平台的 Cookie 处理方式**
对于不存在 Target 的平台,如小刻食堂,可以重写 add_user_cookie 方法,为用户 Cookie 设置 is_universal 字段。这样,在获取 Client 时,由于传入的 Target 为空,就只会选择 is_universal 的 cookie。实现了无 Target 平台的用户 Cookie 调度。
## 默认的调度策略
默认的调度策略在 CookieClientManager 的 `_choose_cookie` 方法中实现:
```python
async def _choose_cookie(self, target: Target | None) -> Cookie:
"""选择 cookie 的具体算法"""
cookies = await config.get_cookie(self._site_name, target)
cookies = (cookie for cookie in cookies if cookie.last_usage + cookie.cd < datetime.now())
cookie = min(cookies, key=lambda x: x.last_usage)
return cookie
```
简而言之,会选择最近使用时间最早的 Cookie,且不在冷却时间内的 Cookie。
在默认情况下,匿名 Cookie 的冷却时间为 0,实名 Cookie 的冷却时间为 10 秒。也就是说,调度时,如果没有可用的实名 Cookie,则会选择匿名 Cookie。
+1 -16
View File
@@ -1,6 +1,6 @@
---
prev: /usage/install
next: /usage/cookie
next: /usage/easy-use
---
# 全方位了解 Bison 的自行车
@@ -272,21 +272,6 @@ Bison 在处理每条推送时,会按照以下规则顺序检查推送中的 T
3. **需订阅 Tag** 列表为空
- **发送**该推送到群中,检查结束
#### Cookie 功能
Bison 支持携带 Cookie 进行请求。
目前支持的平台有:
- `rss`: RSS
- `weibo`: 新浪微博
::: warning 使用须知
Cookie 全局生效,这意味着,通过你的 Cookie 获取到的内容,可能会被发给其他用户。
:::
管理员可以通过**命令**或**管理后台**给 Bison 设置 Cookie。
<script setup lang="ts">
import { ref, computed } from 'vue';
-113
View File
@@ -1,113 +0,0 @@
---
prev: /usage/
next: /usage/install
---
# :cookie: Bison 的自行车电助力装置
Bison 支持 Cookie 啦,你可以将 Cookie 关联到订阅以获得更好的体验。
但是,盲目使用 Cookie 功能并不能解决问题,反而可能为你的账号带来风险。请阅读完本文档后再决定是否使用 Cookie 功能。
::: warning 免责声明
Bison 具有良好的风控应对机制,我们会尽力保护你的账户,但是无法保证绝对的安全。
nonebot-bison 开发者及 MountainDash 社区不对因使用 Cookie 导致的任何问题负责。
:::
## :monocle_face: 什么时候需要 Cookie
首先,请确认 Cookie 的使用场景,并了解注意事项。
在绝大多数情况下,Bison 不需要 Cookie 即可正常工作。但是,部分平台只能够获取有限的内容,此时,Cookie 就可以帮助 Bison 获取更多的内容。
例如,微博平台可以设置微博为“仅粉丝可见”,正常情况下 Bison 无法获取到这些内容。如果你的账号是该博主的粉丝,那么你可以将你的 Cookie 关联到 Bison,这样 Bison 就可以获取到这些受限内容。
::: warning 使用须知
Cookie 全局生效,这意味着,通过你的 Cookie 获取到的内容,可能会被共享给其他用户。
当然,Bison 不会将你的 Cookie 透露给其他用户。但是,管理员或其他可以接触的数据库的人员可以看到**所有 Cookie**的内容。所以,在上传 Cookie 之前,请确保管理人员可信。
:::
## :wheelchair: 我该怎么使用 Cookie
首先,需要明确的是,因为 Cookie 具有隐私性,所有与 Cookie 相关的操作,仅支持**管理员**通过**私聊**或者通过**WebUI**进行管理。
目前,使用 Cookie 主要有两个步骤:
- **添加 Cookie**: 将 Cookie 发给 Bison
- **关联 Cookie**: 告诉 Bison,你希望在什么时候使用这个 Cookie
## :nerd_face: 如何获取 Cookie
对于大部分平台,Bison 支持 JSON 格式的 Cookie,你可以通过浏览器的开发者工具获取。
- RSS: 对于各种 RSS 订阅,你需要自行准备需要的 Cookie,以 JSON 格式添加即可
- 微博:Bison 兼容 RSSHub 的 Cookie,以下方法引用自[RSSHub 的文档](https://docs.rsshub.app/zh/deploy/config#%E5%BE%AE%E5%8D%9A)
> 1. 打开并登录 https://m.weibo.cn(确保打开页面为手机版,如果强制跳转电脑端可尝试使用可更改 UserAgent 的浏览器插件)
> 2. 按下 F12 打开控制台,切换至 Network(网络)面板
> 3. 在该网页切换至任意关注分组,并在面板打开最先捕获到的请求(该情形下捕获到的请求路径应包含/feed/group
> 4. 查看该请求的 Headers(请求头), 找到 Cookie 字段并复制内容
- Bilibili: Bison 兼容 RSSHub 的 Cookie,以下方法引用自[RSSHub 的文档](https://docs.rsshub.app/zh/deploy/config#bilibili)
> 1. 打开 https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_new?uid=0&type=8
> 2. 打开控制台,切换到 Network 面板,刷新
> 3. 点击 dynamic_new 请求,找到 Cookie
> 4. 视频和专栏,UP 主粉丝及关注只要求 SESSDATA 字段,动态需复制整段 Cookie
## :sparkles: 给 Bison 添加 Cookie
打开 Bison 的私聊,发送 `添加cookie` 命令,Bison 会开始添加 Cookie 流程。
![add cookie](/images/add-cookie.png)
然后,依次输入平台名称和 Cookie 内容。
![add cookie 2](/images/add-cookie-2.png)
看到 Bison 的回复之后,Cookie 就添加成功啦!
## :children_crossing: 关联 Cookie 到具体的订阅
接下来要关联 Cookie 到一个具体的订阅。
输入 `添加关联cookie` 命令,Bison 就会列出当前所有的订阅。
我们选择一个订阅,Bison 会列出所有的可以选择的 Cookie。
![add-cookie-target.png](/images/add-cookie-target.png)
再选择需要关联的 Cookie。
至此,Bison 便会携带我们的 Cookie 去请求订阅目标啦!
## :stethoscope: 取消关联 Cookie
如果你想取消关联某个 Cookie,可以发送 `取消关联cookie` 命令,Bison 会列出所有已被关联的订阅和 Cookie。
选择需要取消关联的 CookieBison 会取消此 Cookie 对该订阅的关联。
这是 `添加关联cookie` 的逆向操作。
## :wastebasket: 删除 Cookie
如果你想删除某个 Cookie,可以发送 `删除cookie` 命令,Bison 会列出所有已添加的 Cookie。
选择需要删除的 CookieBison 会删除此 Cookie。
::: tip
只能删除未被关联的 Cookie。
也就是说,你需要先取消一个 Cookie 的所有关联,才能删除。
:::
这是 `添加cookie` 的逆向操作。
## :globe_with_meridians: 使用 WebUI 管理 Cookie
同样的,Bison 提供了一个网页管理 Cookie 的功能,即 WebUI,你可以在网页上查看、添加、删除 Cookie。
使用方法参见 [使用网页管理订阅](/usage/easy-use#使用网页管理订阅)。
## :tada: 完成!
至此,你已经掌握了使用 Cookie 的方法。
Congratulations! 🎉
+2 -2
View File
@@ -1,7 +1,7 @@
from nonebot import on_request
from nonebot.adapters.onebot.v11 import Bot
from nonebot.adapters.onebot.v11.event import FriendRequestEvent, GroupRequestEvent
from nonebot.log import logger
from nonebot.adapters.onebot.v11 import Bot
from nonebot.adapters.onebot.v11.event import GroupRequestEvent, FriendRequestEvent
friend_req = on_request(priority=5)
+4 -4
View File
@@ -6,8 +6,8 @@ require("nonebot_plugin_saa")
import nonebot_plugin_saa
from . import admin_page, bootstrap, config, platform, post, scheduler, send, sub_manager, theme, types, utils
from .plugin_config import PlugConfig, plugin_config
from . import post, send, theme, types, utils, config, platform, bootstrap, scheduler, admin_page, sub_manager
__help__version__ = "0.8.2"
nonebot_plugin_saa.enable_auto_select_bot()
@@ -36,12 +36,12 @@ __all__ = [
"admin_page",
"bootstrap",
"config",
"platform",
"sub_manager",
"post",
"scheduler",
"send",
"sub_manager",
"theme",
"platform",
"types",
"utils",
"theme",
]
+6 -7
View File
@@ -2,16 +2,15 @@ import os
from pathlib import Path
from typing import TYPE_CHECKING
from nonebot import get_driver, on_command
from nonebot.adapters.onebot.v11 import Bot
from nonebot.adapters.onebot.v11.event import PrivateMessageEvent
from nonebot.log import logger
from nonebot.rule import to_me
from nonebot.typing import T_State
from nonebot_bison.plugin_config import plugin_config
from nonebot import get_driver, on_command
from nonebot.adapters.onebot.v11 import Bot
from nonebot.adapters.onebot.v11.event import PrivateMessageEvent
from .api import router as api_router
from ..plugin_config import plugin_config
from .token_manager import token_manager as tm
if TYPE_CHECKING:
@@ -22,9 +21,9 @@ STATIC_PATH = (Path(__file__).parent / "dist").resolve()
def init_fastapi(driver: "Driver"):
import socketio
from fastapi.applications import FastAPI
from fastapi.staticfiles import StaticFiles
import socketio
sio = socketio.AsyncServer(async_mode="asgi", cors_allowed_origins="*")
socket_app = socketio.ASGIApp(sio, socketio_path="socket")
@@ -62,7 +61,7 @@ def init_fastapi(driver: "Driver"):
def register_get_token_handler():
get_token = on_command("后台管理", rule=to_me(), priority=5, aliases={"管理后台"}, block=True)
get_token = on_command("后台管理", rule=to_me(), priority=5, aliases={"管理后台"})
@get_token.handle()
async def send_token(bot: "Bot", event: PrivateMessageEvent, state: T_State):
+27 -43
View File
@@ -1,39 +1,36 @@
from typing import cast
from fastapi import status
from fastapi.exceptions import HTTPException
from fastapi.param_functions import Depends
from fastapi.routing import APIRouter
from fastapi.security.oauth2 import OAuth2PasswordBearer
import nonebot
from fastapi import status
from fastapi.routing import APIRouter
from fastapi.param_functions import Depends
from fastapi.exceptions import HTTPException
from nonebot_plugin_saa import TargetQQGroup
from nonebot_plugin_saa.auto_select_bot import get_bot
from fastapi.security.oauth2 import OAuth2PasswordBearer
from nonebot_bison.apis import check_sub_target
from nonebot_bison.config import NoSuchSubscribeException, NoSuchTargetException, NoSuchUserException, config
from nonebot_bison.config.db_config import SubscribeDupException
from nonebot_bison.platform import platform_manager
from nonebot_bison.scheduler import scheduler_dict
from nonebot_bison.types import Target as T_Target
from nonebot_bison.types import WeightConfig
from nonebot_bison.utils.get_bot import get_groups
from nonebot_bison.utils.site import CookieClientManager, is_cookie_client_manager, site_manager
from ..types import WeightConfig
from ..apis import check_sub_target
from .jwt import load_jwt, pack_jwt
from ..types import Target as T_Target
from ..utils.get_bot import get_groups
from .token_manager import token_manager
from ..config.db_config import SubscribeDupException
from ..platform import site_manager, platform_manager
from ..utils.site import CookieClientManager, is_cookie_client_manager
from ..config import NoSuchUserException, NoSuchTargetException, NoSuchSubscribeException, config
from .types import (
AddSubscribeReq,
Cookie,
CookieTarget,
TokenResp,
GlobalConf,
PlatformConfig,
SiteConfig,
StatusResp,
CookieTarget,
SubscribeResp,
PlatformConfig,
AddSubscribeReq,
SubscribeConfig,
SubscribeGroupDetail,
SubscribeResp,
Target,
TokenResp,
)
router = APIRouter(prefix="/api", tags=["api"])
@@ -69,7 +66,7 @@ async def get_global_conf() -> GlobalConf:
platformName=platform_name,
categories=platform.categories,
enabledTag=platform.enable_tag,
siteName=platform.site.name,
site_name=platform.site.name,
name=platform.name,
hasTarget=getattr(platform, "has_target"),
)
@@ -213,13 +210,14 @@ async def update_weigth_config(platformName: str, target: str, weight_config: We
@router.get("/cookie", dependencies=[Depends(check_is_superuser)])
async def get_cookie(site_name: str | None = None, target: str | None = None) -> list[Cookie]:
async def get_cookie(site_name: str = None, target: str = None) -> list[Cookie]:
cookies_in_db = await config.get_cookie(site_name, is_anonymous=False)
client_mgr = cast(CookieClientManager, site_manager[site_name].client_mgr)
friendly_names = [await client_mgr.get_cookie_friendly_name(x) for x in cookies_in_db]
return [
Cookie(
id=cookies_in_db[i].id,
content=cookies_in_db[i].content,
cookie_name=cookies_in_db[i].cookie_name,
friendly_name=friendly_names[i],
site_name=cookies_in_db[i].site_name,
last_usage=cookies_in_db[i].last_usage,
status=cookies_in_db[i].status,
@@ -234,7 +232,7 @@ async def get_cookie(site_name: str | None = None, target: str | None = None) ->
@router.post("/cookie", dependencies=[Depends(check_is_superuser)])
async def add_cookie(site_name: str, content: str) -> StatusResp:
client_mgr = cast(CookieClientManager, scheduler_dict[site_manager[site_name]].client_mgr)
client_mgr = cast(CookieClientManager, site_manager[site_name].client_mgr)
await client_mgr.add_user_cookie(content)
return StatusResp(ok=True, msg="")
@@ -252,12 +250,7 @@ async def get_cookie_target(
cookie_targets = await config.get_cookie_target()
# TODO: filter in SQL
return [
CookieTarget(
target=Target(
platform_name=x.target.platform_name, target_name=x.target.target_name, target=x.target.target
),
cookie_id=x.cookie.id,
)
x
for x in cookie_targets
if (site_name is None or x.cookie.site_name == site_name)
and (target is None or x.target.target == target)
@@ -266,21 +259,12 @@ async def get_cookie_target(
@router.post("/cookie_target", dependencies=[Depends(check_is_superuser)])
async def add_cookie_target(platform_name: str, target: T_Target, cookie_id: int) -> StatusResp:
async def add_cookie_target(platform_name: str, target: str, cookie_id: int) -> StatusResp:
await config.add_cookie_target(target, platform_name, cookie_id)
return StatusResp(ok=True, msg="")
@router.delete("/cookie_target", dependencies=[Depends(check_is_superuser)])
async def del_cookie_target(platform_name: str, target: T_Target, cookie_id: int) -> StatusResp:
async def del_cookie_target(platform_name: str, target: str, cookie_id: int) -> StatusResp:
await config.delete_cookie_target(target, platform_name, cookie_id)
return StatusResp(ok=True, msg="")
@router.post("/cookie/validate", dependencies=[Depends(check_is_superuser)])
async def get_cookie_valid(site_name: str, content: str) -> StatusResp:
client_mgr = cast(CookieClientManager, scheduler_dict[site_manager[site_name]].client_mgr)
if await client_mgr.validate_cookie(content):
return StatusResp(ok=True, msg="")
else:
return StatusResp(ok=False, msg="")
+1 -1
View File
@@ -1,6 +1,6 @@
import datetime
import random
import string
import datetime
import jwt
+1 -1
View File
@@ -1,6 +1,6 @@
from datetime import timedelta
import random
import string
from datetime import timedelta
from expiringdictx import ExpiringDict
+3 -4
View File
@@ -6,7 +6,7 @@ class PlatformConfig(BaseModel):
categories: dict[int, str]
enabledTag: bool
platformName: str
siteName: str
site_name: str
hasTarget: bool
@@ -60,8 +60,8 @@ class StatusResp(BaseModel):
msg: str
from datetime import datetime
from typing import Any
from datetime import datetime
from pydantic import BaseModel
@@ -75,8 +75,7 @@ class Target(BaseModel):
class Cookie(BaseModel):
id: int
site_name: str
content: str
cookie_name: str
friendly_name: str
last_usage: datetime
status: str
cd_milliseconds: int
+2 -2
View File
@@ -1,6 +1,6 @@
from .platform import platform_manager
from .scheduler import scheduler_dict
from .types import Target
from .scheduler import scheduler_dict
from .platform import platform_manager
async def check_sub_target(platform_name: str, target: Target):
+3 -3
View File
@@ -1,10 +1,10 @@
from nonebot.log import logger
from nonebot_plugin_datastore.db import get_engine, post_db_init, pre_db_init
from sqlalchemy import inspect, text
from sqlalchemy import text, inspect
from nonebot_plugin_datastore.db import get_engine, pre_db_init, post_db_init
from .config.config_legacy import start_up as legacy_db_startup
from .config.db_migration import data_migrate
from .scheduler.manager import init_scheduler
from .config.config_legacy import start_up as legacy_db_startup
@pre_db_init
+2 -2
View File
@@ -1,9 +1,9 @@
from typing import Literal, overload
from nonebot.compat import PYDANTIC_V2
from pydantic import BaseModel
from nonebot.compat import PYDANTIC_V2
__all__ = ("model_rebuild", "model_validator")
__all__ = ("model_validator", "model_rebuild")
if PYDANTIC_V2:
+2 -2
View File
@@ -1,4 +1,4 @@
from .db_config import config as config
from .utils import NoSuchSubscribeException as NoSuchSubscribeException
from .utils import NoSuchTargetException as NoSuchTargetException
from .utils import NoSuchUserException as NoSuchUserException
from .utils import NoSuchTargetException as NoSuchTargetException
from .utils import NoSuchSubscribeException as NoSuchSubscribeException
+8 -9
View File
@@ -1,20 +1,19 @@
from collections import defaultdict
from datetime import datetime
import json
import os
import json
from os import path
from pathlib import Path
from datetime import datetime
from collections import defaultdict
from typing import Literal, TypedDict
from nonebot.log import logger
from tinydb import Query, TinyDB
from nonebot_bison.platform import platform_manager
from nonebot_bison.plugin_config import plugin_config
from nonebot_bison.types import Target, User
from nonebot_bison.utils import Singleton
from .utils import NoSuchSubscribeException, NoSuchUserException
from ..utils import Singleton
from ..types import User, Target
from ..platform import platform_manager
from ..plugin_config import plugin_config
from .utils import NoSuchUserException, NoSuchSubscribeException
supported_target_type = platform_manager.keys()
+11 -32
View File
@@ -1,20 +1,20 @@
import asyncio
from collections import defaultdict
from collections.abc import Awaitable, Callable, Sequence
from datetime import datetime, time
from datetime import time, datetime
from collections.abc import Callable, Sequence, Awaitable
from nonebot.compat import model_dump
from nonebot_plugin_datastore import create_session
from nonebot_plugin_saa import PlatformTarget
from sqlalchemy import delete, func, select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import selectinload
from sqlalchemy.exc import IntegrityError
from sqlalchemy import func, delete, select
from nonebot_plugin_saa import PlatformTarget
from nonebot_plugin_datastore import create_session
from nonebot_bison.types import Category, PlatformWeightConfigResp, Tag, TimeWeightConfig, UserSubInfo, WeightConfig
from nonebot_bison.types import Target as T_Target
from .db_model import Cookie, CookieTarget, ScheduleTimeWeight, Subscribe, Target, User
from .utils import DuplicateCookieTargetException, NoSuchTargetException
from ..types import Tag
from ..types import Target as T_Target
from .utils import NoSuchTargetException, DuplicateCookieTargetException
from .db_model import User, Cookie, Target, Subscribe, CookieTarget, ScheduleTimeWeight
from ..types import Category, UserSubInfo, WeightConfig, TimeWeightConfig, PlatformWeightConfigResp
def _get_time():
@@ -285,13 +285,6 @@ class DBConfig:
res = [cookie for cookie in res if cookie.id in ids or cookie.is_universal]
return res
async def get_cookie_by_id(self, cookie_id: int) -> Cookie:
async with create_session() as sess:
cookie = await sess.scalar(select(Cookie).where(Cookie.id == cookie_id))
if not cookie:
raise NoSuchTargetException(f"cookie {cookie_id} not found")
return cookie
async def add_cookie(self, cookie: Cookie) -> int:
async with create_session() as sess:
sess.add(cookie)
@@ -305,7 +298,6 @@ class DBConfig:
if not cookie_in_db:
raise ValueError(f"cookie {cookie.id} not found")
cookie_in_db.content = cookie.content
cookie_in_db.cookie_name = cookie.cookie_name
cookie_in_db.last_usage = cookie.last_usage
cookie_in_db.status = cookie.status
cookie_in_db.tags = cookie.tags
@@ -319,8 +311,6 @@ class DBConfig:
.outerjoin(CookieTarget)
.options(selectinload(Cookie.targets))
)
if not cookie:
raise NoSuchTargetException(f"cookie {cookie_id} not found")
if len(cookie.targets) > 0:
raise Exception(f"cookie {cookie.id} in use")
await sess.execute(delete(Cookie).where(Cookie.id == cookie_id))
@@ -372,16 +362,5 @@ class DBConfig:
res.sort(key=lambda x: (x.target.platform_name, x.cookie_id, x.target_id))
return res
async def clear_db(self):
"""清空数据库,用于单元测试清理环境"""
async with create_session() as sess:
await sess.execute(delete(User))
await sess.execute(delete(Target))
await sess.execute(delete(ScheduleTimeWeight))
await sess.execute(delete(Subscribe))
await sess.execute(delete(Cookie))
await sess.execute(delete(CookieTarget))
await sess.commit()
config = DBConfig()
+3 -3
View File
@@ -1,11 +1,11 @@
from nonebot.compat import model_dump
from nonebot.log import logger
from nonebot.compat import model_dump
from nonebot_plugin_datastore.db import get_engine
from nonebot_plugin_saa import TargetQQGroup, TargetQQPrivate
from sqlalchemy.ext.asyncio.session import AsyncSession
from nonebot_plugin_saa import TargetQQGroup, TargetQQPrivate
from .db_model import User, Target, Subscribe
from .config_legacy import Config, ConfigContent, drop
from .db_model import Subscribe, Target, User
async def data_migrate():
+6 -8
View File
@@ -1,15 +1,15 @@
import datetime
from pathlib import Path
from typing import Any
from pathlib import Path
from nonebot_plugin_saa import PlatformTarget
from sqlalchemy.dialects.postgresql import JSONB
from nonebot.compat import PYDANTIC_V2, ConfigDict
from nonebot_plugin_datastore import get_plugin_data
from nonebot_plugin_saa import PlatformTarget
from sqlalchemy import JSON, DateTime, ForeignKey, String, UniqueConstraint
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm import Mapped, relationship, mapped_column
from sqlalchemy import JSON, String, DateTime, ForeignKey, UniqueConstraint
from nonebot_bison.types import Category, Tag
from ..types import Tag, Category
Model = get_plugin_data().Model
get_plugin_data().set_migration_dir(Path(__file__).parent / "migrations")
@@ -74,8 +74,6 @@ class Cookie(Model):
id: Mapped[int] = mapped_column(primary_key=True)
site_name: Mapped[str] = mapped_column(String(100))
content: Mapped[str] = mapped_column(String(1024))
# Cookie 的友好名字,类似于 Target 的 target_name,用于展示
cookie_name: Mapped[str] = mapped_column(String(1024), default="unnamed cookie")
# 最后使用的时刻
last_usage: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime(1970, 1, 1))
# Cookie 当前的状态
@@ -6,8 +6,8 @@ Create Date: 2022-03-21 19:18:13.762626
"""
from alembic import op
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "0571870f5222"
@@ -6,8 +6,8 @@ Create Date: 2022-03-27 21:50:10.911649
"""
from alembic import op
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "4a46ba54a3f3"
@@ -6,8 +6,8 @@ Create Date: 2022-05-31 22:05:13.235981
"""
from alembic import op
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "5f3370328e44"
@@ -6,8 +6,8 @@ Create Date: 2023-03-20 00:39:30.199915
"""
from alembic import op
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects.postgresql import JSONB
# revision identifiers, used by Alembic.
@@ -6,8 +6,8 @@ Create Date: 2023-03-20 11:08:42.883556
"""
from alembic import op
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects.postgresql import JSONB
# revision identifiers, used by Alembic.
@@ -6,8 +6,8 @@ Create Date: 2023-03-20 15:38:20.220599
"""
from alembic import op
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "8d3863e9d74b"
@@ -6,8 +6,8 @@ Create Date: 2022-03-29 21:01:38.213153
"""
from alembic import op
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "a333d6224193"
@@ -6,10 +6,10 @@ Create Date: 2023-03-20 01:14:42.623789
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.ext.automap import automap_base
from alembic import op
from sqlalchemy.orm import Session
from sqlalchemy.ext.automap import automap_base
# revision identifiers, used by Alembic.
revision = "a5466912fad0"
@@ -6,8 +6,8 @@ Create Date: 2023-03-09 19:10:42.168133
"""
from alembic import op
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "aceef470d69c"
@@ -6,11 +6,11 @@ Create Date: 2023-03-02 14:04:16.492133
"""
from alembic import op
import sqlalchemy as sa
from alembic import op
from sqlalchemy import select
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
from sqlalchemy.ext.automap import automap_base
# revision identifiers, used by Alembic.
revision = "bd92923c218f"
@@ -1,18 +1,18 @@
"""empty message
Revision ID: f90b712557a9
Revision ID: ef796b74b0fe
Revises: f9baef347cc8
Create Date: 2024-09-23 10:03:30.593263
Create Date: 2024-09-13 00:34:08.601438
"""
from alembic import op
import sqlalchemy as sa
from alembic import op
from sqlalchemy import Text
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = "f90b712557a9"
revision = "ef796b74b0fe"
down_revision = "f9baef347cc8"
branch_labels = None
depends_on = None
@@ -25,7 +25,6 @@ def upgrade() -> None:
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("site_name", sa.String(length=100), nullable=False),
sa.Column("content", sa.String(length=1024), nullable=False),
sa.Column("cookie_name", sa.String(length=1024), nullable=False),
sa.Column("last_usage", sa.DateTime(), nullable=False),
sa.Column("status", sa.String(length=20), nullable=False),
sa.Column("cd_milliseconds", sa.Integer(), nullable=False),
@@ -6,10 +6,10 @@ Create Date: 2023-08-25 00:20:51.511329
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.ext.automap import automap_base
from alembic import op
from sqlalchemy.orm import Session
from sqlalchemy.ext.automap import automap_base
# revision identifiers, used by Alembic.
revision = "f9baef347cc8"
@@ -1,6 +1,6 @@
"""nbesf is Nonebot Bison Enchangable Subscribes File!"""
from . import v1, v2, v3
from . import v1, v2
from .base import NBESFBase
__all__ = ["NBESFBase", "v1", "v2", "v3"]
__all__ = ["v1", "v2", "NBESFBase"]
@@ -1,10 +1,10 @@
from abc import ABC
from pydantic import BaseModel
from nonebot.compat import PYDANTIC_V2, ConfigDict
from nonebot_plugin_saa.registries import AllSupportedPlatformTarget as UserInfo
from pydantic import BaseModel
from nonebot_bison.types import Category, Tag
from ....types import Tag, Category
class NBESFBase(BaseModel, ABC):
+10 -11
View File
@@ -1,18 +1,17 @@
"""nbesf is Nonebot Bison Enchangable Subscribes File! ver.1"""
from functools import partial
from typing import Any
from functools import partial
from nonebot.compat import PYDANTIC_V2, ConfigDict, model_dump, type_validate_json, type_validate_python
from nonebot.log import logger
from pydantic import BaseModel
from nonebot_plugin_saa import TargetQQGroup, TargetQQPrivate
from pydantic import BaseModel, Field
from nonebot_bison.config.db_config import SubscribeDupException, config
from nonebot_bison.config.subs_io.utils import NBESFParseErr
from nonebot_bison.types import Category, Tag
from nonebot.compat import PYDANTIC_V2, ConfigDict, model_dump, type_validate_json, type_validate_python
from ..utils import NBESFParseErr
from ....types import Tag, Category
from .base import NBESFBase, SubReceipt
from ...db_config import SubscribeDupException, config
# ===== nbesf 定义格式 ====== #
NBESF_VERSION = 1
@@ -80,7 +79,7 @@ class SubGroup(
"""
version: int = NBESF_VERSION
groups: list[SubPack] = Field(default_factory=list)
groups: list[SubPack] = []
# ======================= #
@@ -109,11 +108,11 @@ async def subs_receipt_gen(nbesf_data: SubGroup):
try:
await config.add_subscribe(receipt.user, **model_dump(receipt, exclude={"user"}))
except SubscribeDupException:
logger.warning(f"!添加订阅条目 {receipt!r} 失败: 相同的订阅已存在")
logger.warning(f"!添加订阅条目 {repr(receipt)} 失败: 相同的订阅已存在")
except Exception as e:
logger.error(f"!添加订阅条目 {receipt!r} 失败: {e!r}")
logger.error(f"!添加订阅条目 {repr(receipt)} 失败: {repr(e)}")
else:
logger.success(f"添加订阅条目 {receipt!r} 成功!")
logger.success(f"添加订阅条目 {repr(receipt)} 成功!")
def nbesf_parser(raw_data: Any) -> SubGroup:
+10 -11
View File
@@ -1,18 +1,17 @@
"""nbesf is Nonebot Bison Enchangable Subscribes File! ver.2"""
from functools import partial
from typing import Any
from functools import partial
from nonebot.compat import PYDANTIC_V2, ConfigDict, model_dump, type_validate_json, type_validate_python
from nonebot.log import logger
from pydantic import BaseModel
from nonebot_plugin_saa.registries import AllSupportedPlatformTarget
from pydantic import BaseModel, Field
from nonebot_bison.config.db_config import SubscribeDupException, config
from nonebot_bison.config.subs_io.utils import NBESFParseErr
from nonebot_bison.types import Category, Tag
from nonebot.compat import PYDANTIC_V2, ConfigDict, model_dump, type_validate_json, type_validate_python
from ..utils import NBESFParseErr
from ....types import Tag, Category
from .base import NBESFBase, SubReceipt
from ...db_config import SubscribeDupException, config
# ===== nbesf 定义格式 ====== #
NBESF_VERSION = 2
@@ -65,7 +64,7 @@ class SubGroup(NBESFBase):
"""
version: int = NBESF_VERSION
groups: list[SubPack] = Field(default_factory=list)
groups: list[SubPack] = []
# ======================= #
@@ -86,11 +85,11 @@ async def subs_receipt_gen(nbesf_data: SubGroup):
try:
await config.add_subscribe(receipt.user, **model_dump(receipt, exclude={"user"}))
except SubscribeDupException:
logger.warning(f"!添加订阅条目 {receipt!r} 失败: 相同的订阅已存在")
logger.warning(f"!添加订阅条目 {repr(receipt)} 失败: 相同的订阅已存在")
except Exception as e:
logger.error(f"!添加订阅条目 {receipt!r} 失败: {e!r}")
logger.error(f"!添加订阅条目 {repr(receipt)} 失败: {repr(e)}")
else:
logger.success(f"添加订阅条目 {receipt!r} 成功!")
logger.success(f"添加订阅条目 {repr(receipt)} 成功!")
def nbesf_parser(raw_data: Any) -> SubGroup:
+11 -42
View File
@@ -1,20 +1,17 @@
"""nbesf is Nonebot Bison Enchangable Subscribes File! ver.2"""
from functools import partial
from typing import Any
from functools import partial
from nonebot.compat import PYDANTIC_V2, ConfigDict, model_dump, type_validate_json, type_validate_python
from nonebot.log import logger
from pydantic import BaseModel
from nonebot_plugin_saa.registries import AllSupportedPlatformTarget
from pydantic import BaseModel, Field
from nonebot_bison.config.db_config import SubscribeDupException, config
from nonebot_bison.config.db_model import Cookie as DBCookie
from nonebot_bison.config.subs_io.utils import NBESFParseErr
from nonebot_bison.types import Category, Tag
from nonebot_bison.types import Target as T_Target
from nonebot.compat import PYDANTIC_V2, ConfigDict, model_dump, type_validate_json, type_validate_python
from ..utils import NBESFParseErr
from ....types import Tag, Category
from .base import NBESFBase, SubReceipt
from ...db_config import SubscribeDupException, config
# ===== nbesf 定义格式 ====== #
NBESF_VERSION = 3
@@ -51,18 +48,6 @@ class SubPayload(BaseModel):
orm_mode = True
class Cookie(BaseModel):
"""Bison的魔法饼干"""
site_name: str
content: str
cookie_name: str
cd_milliseconds: int
is_universal: bool
tags: dict[str, str]
targets: list[Target]
class SubPack(BaseModel):
"""Bison给指定用户派送的快递包"""
@@ -73,21 +58,19 @@ class SubPack(BaseModel):
class SubGroup(NBESFBase):
"""
Bison的全部订单(按用户分组)和魔法饼干
Bison的全部订单(按用户分组)
结构参见`nbesf_model`下的对应版本
"""
version: int = NBESF_VERSION
groups: list[SubPack] = Field(default_factory=list)
cookies: list[Cookie] = Field(default_factory=list)
groups: list[SubPack] = []
# ======================= #
async def subs_receipt_gen(nbesf_data: SubGroup):
logger.info("开始添加订阅流程")
for item in nbesf_data.groups:
sub_receipt = partial(SubReceipt, user=item.user_target)
@@ -102,25 +85,11 @@ async def subs_receipt_gen(nbesf_data: SubGroup):
try:
await config.add_subscribe(receipt.user, **model_dump(receipt, exclude={"user"}))
except SubscribeDupException:
logger.warning(f"!添加订阅条目 {receipt!r} 失败: 相同的订阅已存在")
logger.warning(f"!添加订阅条目 {repr(receipt)} 失败: 相同的订阅已存在")
except Exception as e:
logger.error(f"!添加订阅条目 {receipt!r} 失败: {e!r}")
logger.error(f"!添加订阅条目 {repr(receipt)} 失败: {repr(e)}")
else:
logger.success(f"添加订阅条目 {receipt!r} 成功!")
async def magic_cookie_gen(nbesf_data: SubGroup):
logger.info("开始添加 Cookie 流程")
for cookie in nbesf_data.cookies:
try:
new_cookie = DBCookie(**model_dump(cookie, exclude={"targets"}))
cookie_id = await config.add_cookie(new_cookie)
for target in cookie.targets:
await config.add_cookie_target(T_Target(target.target), target.platform_name, cookie_id)
except Exception as e:
logger.error(f"!添加 Cookie 条目 {cookie!r} 失败: {e!r}")
else:
logger.success(f"添加 Cookie 条目 {cookie!r} 成功!")
logger.success(f"添加订阅条目 {repr(receipt)} 成功!")
def nbesf_parser(raw_data: Any) -> SubGroup:
+15 -53
View File
@@ -1,23 +1,21 @@
from typing import cast
from collections import defaultdict
from collections.abc import Callable
from typing import cast
from nonebot.compat import type_validate_python
from nonebot.log import logger
from nonebot_plugin_datastore.db import create_session
from nonebot_plugin_saa import PlatformTarget
from sqlalchemy import select
from sqlalchemy.orm.strategy_options import selectinload
from nonebot.log import logger
from sqlalchemy.sql.selectable import Select
from nonebot_plugin_saa import PlatformTarget
from nonebot.compat import type_validate_python
from nonebot_plugin_datastore.db import create_session
from sqlalchemy.orm.strategy_options import selectinload
from nonebot_bison.config import config
from nonebot_bison.config.db_model import Cookie, CookieTarget, Subscribe, Target, User
from .nbesf_model import NBESFBase, v1, v2, v3
from .utils import NBESFVerMatchErr, row2dict
from .utils import NBESFVerMatchErr
from ..db_model import User, Subscribe
from .nbesf_model import NBESFBase, v1, v2
async def subscribes_export(selector: Callable[[Select], Select]) -> v3.SubGroup:
async def subscribes_export(selector: Callable[[Select], Select]) -> v2.SubGroup:
"""
将Bison订阅导出为 Nonebot Bison Exchangable Subscribes File 标准格式的 SubGroup 类型数据
@@ -36,54 +34,22 @@ async def subscribes_export(selector: Callable[[Select], Select]) -> v3.SubGroup
user_stmt = cast(Select[tuple[User]], user_stmt)
user_data = await sess.scalars(user_stmt)
groups: list[v3.SubPack] = []
user_id_sub_dict: dict[int, list[v3.SubPayload]] = defaultdict(list)
groups: list[v2.SubPack] = []
user_id_sub_dict: dict[int, list[v2.SubPayload]] = defaultdict(list)
for sub in sub_data:
sub_paylaod = type_validate_python(v3.SubPayload, sub)
sub_paylaod = type_validate_python(v2.SubPayload, sub)
user_id_sub_dict[sub.user_id].append(sub_paylaod)
for user in user_data:
assert isinstance(user, User)
sub_pack = v3.SubPack(
sub_pack = v2.SubPack(
user_target=PlatformTarget.deserialize(user.user_target),
subs=user_id_sub_dict[user.id],
)
groups.append(sub_pack)
async with create_session() as sess:
cookie_target_stmt = (
select(CookieTarget)
.join(Cookie)
.join(Target)
.options(selectinload(CookieTarget.target))
.options(selectinload(CookieTarget.cookie))
)
cookie_target_data = await sess.scalars(cookie_target_stmt)
cookie_target_dict: dict[Cookie, list[v3.Target]] = defaultdict(list)
for cookie_target in cookie_target_data:
target_payload = type_validate_python(v3.Target, cookie_target.target)
cookie_target_dict[cookie_target.cookie].append(target_payload)
def cookie_transform(cookie: Cookie, targets: list[v3.Target]) -> v3.Cookie:
cookie_dict = row2dict(cookie)
cookie_dict["tags"] = cookie.tags
cookie_dict["targets"] = targets
return v3.Cookie(**cookie_dict)
cookies: list[v3.Cookie] = []
cookie_set = set()
for cookie, targets in cookie_target_dict.items():
assert isinstance(cookie, Cookie)
cookies.append(cookie_transform(cookie, targets))
cookie_set.add(cookie.id)
# 添加未关联的cookie
all_cookies = await config.get_cookie(is_anonymous=False)
cookies.extend([cookie_transform(c, []) for c in all_cookies if c.id not in cookie_set])
sub_group = v3.SubGroup(groups=groups, cookies=cookies)
sub_group = v2.SubGroup(groups=groups)
return sub_group
@@ -106,10 +72,6 @@ async def subscribes_import(
case 2:
assert isinstance(nbesf_data, v2.SubGroup)
await v2.subs_receipt_gen(nbesf_data)
case 3:
assert isinstance(nbesf_data, v3.SubGroup)
await v3.subs_receipt_gen(nbesf_data)
await v3.magic_cookie_gen(nbesf_data)
case _:
raise NBESFVerMatchErr(f"不支持的NBESF版本:{nbesf_data.version}")
logger.info("订阅流程结束,请检查所有订阅记录是否全部添加成功")
-13
View File
@@ -1,17 +1,4 @@
from typing import Any
from sqlalchemy.orm import DeclarativeBase
class NBESFVerMatchErr(Exception): ...
class NBESFParseErr(Exception): ...
def row2dict(row: DeclarativeBase) -> dict[str, Any]:
d = {}
for column in row.__table__.columns:
d[column.name] = str(getattr(row, column.name))
return d
+11 -4
View File
@@ -1,10 +1,10 @@
from collections import defaultdict
from importlib import import_module
from pathlib import Path
from pkgutil import iter_modules
from collections import defaultdict
from importlib import import_module
from nonebot_bison.plugin_config import plugin_config
from ..utils import Site
from ..plugin_config import plugin_config
from .platform import Platform, make_no_target_group
_package_dir = str(Path(__file__).resolve().parent)
@@ -36,3 +36,10 @@ def _get_unavailable_platforms() -> dict[str, str]:
# platform => reason for not available
unavailable_paltforms: dict[str, str] = _get_unavailable_platforms()
site_manager: dict[str, type[Site]] = {}
for site in Site.registry:
if not hasattr(site, "name"):
continue
site_manager[site.name] = site
+16 -17
View File
@@ -1,19 +1,18 @@
from functools import partial
import html
import re
from typing import Any, ClassVar
import html
from typing import Any
from functools import partial
from bs4 import BeautifulSoup as bs
from httpx import AsyncClient
from nonebot.compat import type_validate_python
from pydantic import BaseModel, Field
from yarl import URL
from httpx import AsyncClient
from bs4 import BeautifulSoup as bs
from pydantic import Field, BaseModel
from nonebot.compat import type_validate_python
from nonebot_bison.post import Post
from nonebot_bison.post.protocol import HTMLContentSupport
from nonebot_bison.types import Category, RawPost, Target
from nonebot_bison.utils import Site
from ..post import Post
from ..utils import Site
from ..types import Target, RawPost, Category
from ..post.protocol import HTMLContentSupport
from .platform import NewMessage, StatusChange
@@ -59,7 +58,7 @@ class ArkBulletinResponse(ArkResponseBase):
class ArknightsSite(Site):
name = "arknights"
schedule_type = "interval"
schedule_setting: ClassVar[dict] = {"seconds": 30}
schedule_setting = {"seconds": 30}
class ArknightsPost(Post, HTMLContentSupport):
@@ -96,7 +95,7 @@ class ArknightsPost(Post, HTMLContentSupport):
class Arknights(NewMessage):
categories: ClassVar[dict[Category, str]] = {1: "游戏公告"}
categories = {1: "游戏公告"}
platform_name = "arknights"
name = "明日方舟游戏信息"
enable_tag = False
@@ -158,7 +157,7 @@ class Arknights(NewMessage):
class AkVersion(StatusChange):
categories: ClassVar[dict[Category, str]] = {2: "更新信息"}
categories = {2: "更新信息"}
platform_name = "arknights"
name = "明日方舟游戏信息"
enable_tag = False
@@ -203,7 +202,7 @@ class AkVersion(StatusChange):
class MonsterSiren(NewMessage):
categories: ClassVar[dict[Category, str]] = {3: "塞壬唱片新闻"}
categories = {3: "塞壬唱片新闻"}
platform_name = "arknights"
name = "明日方舟游戏信息"
enable_tag = False
@@ -251,7 +250,7 @@ class MonsterSiren(NewMessage):
class TerraHistoricusComic(NewMessage):
categories: ClassVar[dict[Category, str]] = {4: "泰拉记事社漫画"}
categories = {4: "泰拉记事社漫画"}
platform_name = "arknights"
name = "明日方舟游戏信息"
enable_tag = False
+3 -3
View File
@@ -1,7 +1,7 @@
from .platforms import Bilibili as Bilibili
from .platforms import BilibiliBangumi as BilibiliBangumi
from .platforms import Bilibililive as Bilibililive
from .scheduler import BiliBangumiSite as BiliBangumiSite
from .scheduler import BilibiliClientManager as BilibiliClientManager
from .scheduler import BilibiliSite as BilibiliSite
from .scheduler import BililiveSite as BililiveSite
from .platforms import BilibiliBangumi as BilibiliBangumi
from .scheduler import BiliBangumiSite as BiliBangumiSite
from .scheduler import BilibiliClientManager as BilibiliClientManager
+9 -9
View File
@@ -1,22 +1,22 @@
import sys
import asyncio
from collections.abc import AsyncGenerator, Awaitable, Callable, Sequence
from collections.abc import Set as AbstractSet
from dataclasses import dataclass
import inspect
from enum import Enum
from functools import wraps
import inspect
import sys
from dataclasses import dataclass
from collections.abc import Set as AbstractSet
from collections.abc import Callable, Sequence, Awaitable, AsyncGenerator
from typing import (
TYPE_CHECKING,
Any,
Concatenate,
Generic,
NamedTuple,
ParamSpec,
TypeVar,
Protocol,
ParamSpec,
TypeAlias,
TypedDict,
TypeVar,
NamedTuple,
Concatenate,
overload,
runtime_checkable,
)
+4 -12
View File
@@ -1,7 +1,7 @@
from typing import Any, Literal, TypeAlias, TypeVar
from typing import Any, Literal, TypeVar, TypeAlias
from nonebot.compat import PYDANTIC_V2, ConfigDict
from pydantic import BaseModel
from nonebot.compat import PYDANTIC_V2, ConfigDict
from nonebot_bison.compat import model_rebuild
@@ -13,7 +13,7 @@ TBaseModel = TypeVar("TBaseModel", bound=type[BaseModel])
def model_rebuild_recurse(cls: TBaseModel) -> TBaseModel:
"""Recursively rebuild all BaseModel subclasses in the class."""
if not PYDANTIC_V2:
from inspect import getmembers, isclass
from inspect import isclass, getmembers
for _, sub_cls in getmembers(cls, lambda x: isclass(x) and issubclass(x, BaseModel)):
model_rebuild_recurse(sub_cls)
@@ -131,7 +131,7 @@ class PostAPI(APIBase):
basic: "PostAPI.Basic"
id_str: str
modules: "PostAPI.Modules"
orig: "PostAPI.Item | PostAPI.DeletedItem | None" = None
orig: "PostAPI.Item | None" = None
topic: "PostAPI.Topic | None" = None
type: DynamicType
@@ -141,14 +141,6 @@ class PostAPI(APIBase):
modules: "PostAPI.Modules"
type: Literal["DYNAMIC_TYPE_NONE"]
def to_item(self) -> "PostAPI.Item":
return PostAPI.Item(
basic=self.basic,
id_str="",
modules=self.modules,
type=self.type,
)
class Data(Base):
items: "list[PostAPI.Item | PostAPI.DeletedItem] | None" = None
+39 -55
View File
@@ -1,41 +1,40 @@
import re
import json
from copy import deepcopy
from enum import Enum, unique
import json
import re
from typing import ClassVar, NamedTuple
from typing import NamedTuple
from typing_extensions import Self
from httpx import AsyncClient
from nonebot import logger
from nonebot.compat import type_validate_json, type_validate_python
from pydantic import BaseModel, Field, ValidationError
from yarl import URL
from nonebot import logger
from httpx import AsyncClient
from pydantic import Field, BaseModel, ValidationError
from nonebot.compat import type_validate_json, type_validate_python
from nonebot_bison.compat import model_rebuild
from nonebot_bison.platform.platform import CategoryNotRecognize, CategoryNotSupport, NewMessage, StatusChange
from nonebot_bison.post.post import Post
from nonebot_bison.types import ApiError, Category, RawPost, Tag, Target
from nonebot_bison.utils import decode_unicode_escapes, text_similarity
from nonebot_bison.compat import model_rebuild
from nonebot_bison.utils import text_similarity, decode_unicode_escapes
from nonebot_bison.types import Tag, Target, RawPost, ApiError, Category
from .models import (
ArticleMajor,
CommonMajor,
CoursesMajor,
DeletedMajor,
DrawMajor,
DynamicType,
DynRawPost,
LiveMajor,
LiveRecommendMajor,
OPUSMajor,
PGCMajor,
PostAPI,
UnknownMajor,
UserAPI,
VideoMajor,
)
from .retry import ApiCode352Error, retry_for_352
from .scheduler import BiliBangumiSite, BilibiliSite, BililiveSite
from .scheduler import BilibiliSite, BililiveSite, BiliBangumiSite
from ..platform import NewMessage, StatusChange, CategoryNotSupport, CategoryNotRecognize
from .models import (
PostAPI,
UserAPI,
PGCMajor,
DrawMajor,
LiveMajor,
OPUSMajor,
DynRawPost,
VideoMajor,
CommonMajor,
DynamicType,
ArticleMajor,
CoursesMajor,
UnknownMajor,
LiveRecommendMajor,
)
class _ProcessedText(NamedTuple):
@@ -51,7 +50,7 @@ class _ParsedMojarPost(NamedTuple):
class Bilibili(NewMessage):
categories: ClassVar[dict[Category, str]] = {
categories = {
1: "一般动态",
2: "专栏文章",
3: "视频",
@@ -94,7 +93,7 @@ class Bilibili(NewMessage):
@retry_for_352
async def get_sub_list(self, target: Target) -> list[DynRawPost]:
client = await self.ctx.get_client(target)
params = {"host_mid": target, "timezone_offset": -480, "offset": "", "features": "itemOpusStyle"}
params = {"host_mid": target, "timezone_offset": -480, "offset": ""}
res = await client.get(
"https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space",
params=params,
@@ -162,6 +161,7 @@ class Bilibili(NewMessage):
return tags
def _text_process(self, dynamic: str, desc: str, title: str) -> _ProcessedText:
# 计算视频标题和视频描述相似度
title_similarity = 0.0 if len(title) == 0 or len(desc) == 0 else text_similarity(title, desc[: len(title)])
if title_similarity > 0.9:
@@ -243,13 +243,6 @@ class Bilibili(NewMessage):
pics=[courses.cover],
url=URL(courses.jump_url).with_scheme("https").human_repr(),
)
case DeletedMajor(none=none):
return _ParsedMojarPost(
title="",
content=none.tips,
pics=[],
url=None,
)
case UnknownMajor(type=unknown_type):
raise CategoryNotSupport(unknown_type)
case None: # 没有major的情况
@@ -266,13 +259,10 @@ class Bilibili(NewMessage):
parsed_raw_post = self.pre_parse_by_mojar(raw_post)
parsed_raw_repost = None
if self._do_get_category(raw_post.type) == Category(5):
match raw_post.orig:
case PostAPI.Item() as orig:
parsed_raw_repost = self.pre_parse_by_mojar(orig)
case PostAPI.DeletedItem() as orig:
parsed_raw_repost = self.pre_parse_by_mojar(orig.to_item())
case None:
logger.warning(f"转发动态{raw_post.id_str}没有原动态")
if raw_post.orig:
parsed_raw_repost = self.pre_parse_by_mojar(raw_post.orig)
else:
logger.warning(f"转发动态{raw_post.id_str}没有原动态")
post = Post(
self,
@@ -285,14 +275,8 @@ class Bilibili(NewMessage):
nickname=raw_post.modules.module_author.name,
)
if parsed_raw_repost:
match raw_post.orig:
case PostAPI.Item() as orig:
orig = orig
case PostAPI.DeletedItem() as orig:
orig = orig.to_item()
case None:
raise ValueError("转发动态没有原动态")
orig = raw_post.orig
assert orig
post.repost = Post(
self,
content=decode_unicode_escapes(parsed_raw_repost.content),
@@ -307,7 +291,7 @@ class Bilibili(NewMessage):
class Bilibililive(StatusChange):
categories: ClassVar[dict[Category, str]] = {1: "开播提醒", 2: "标题更新提醒", 3: "下播提醒"}
categories = {1: "开播提醒", 2: "标题更新提醒", 3: "下播提醒"}
platform_name = "bilibili-live"
enable_tag = False
enabled = True
@@ -457,7 +441,7 @@ class Bilibililive(StatusChange):
class BilibiliBangumi(StatusChange):
categories: ClassVar[dict[Category, str]] = {}
categories = {}
platform_name = "bilibili-bangumi"
enable_tag = False
enabled = True
+10 -9
View File
@@ -1,20 +1,20 @@
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from datetime import datetime, timedelta
import random
from enum import Enum
from functools import wraps
import random
from dataclasses import dataclass
from datetime import datetime, timedelta
from collections.abc import Callable, Awaitable
from typing_extensions import override, assert_never
from typing import TYPE_CHECKING, Generic, Literal, TypeVar
from typing_extensions import assert_never, override
from httpx import URL as HttpxURL
from nonebot.log import logger
from strenum import StrEnum
from nonebot.log import logger
from httpx import URL as HttpxURL
from nonebot_bison.types import Target
from .fsm import FSM, ActionReturn, Condition, StateGraph, Transition, reset_on_exception
from .models import DynRawPost
from .fsm import FSM, Condition, StateGraph, Transition, ActionReturn, reset_on_exception
if TYPE_CHECKING:
from .platforms import Bilibili
@@ -115,7 +115,8 @@ class RetryAddon(Generic[TBilibili]):
def record_backoff_finish_time(self):
self.backoff_finish_time = (
datetime.now() + self.backoff_timedelta * self.backoff_count**2
datetime.now()
+ self.backoff_timedelta * self.backoff_count**2
# + timedelta(seconds=random.randint(1, 60)) # jitter
)
logger.trace(f"set backoff finish time: {self.backoff_finish_time}")
+30 -35
View File
@@ -1,16 +1,13 @@
from datetime import datetime, timedelta
import json
import random
from typing import TYPE_CHECKING, ClassVar, TypeVar
from typing_extensions import override
from typing import TYPE_CHECKING, TypeVar
from httpx import AsyncClient
from nonebot import logger, require
from playwright.async_api import Cookie
from nonebot_bison.config.db_model import Cookie as CookieModel
from nonebot_bison.utils import Site, http_client
from nonebot_bison.utils.site import CookieClientManager
from nonebot_bison.types import Target
from nonebot_bison.utils import Site, ClientManager, http_client
if TYPE_CHECKING:
from .platforms import Bilibili
@@ -21,47 +18,45 @@ from nonebot_plugin_htmlrender import get_browser
B = TypeVar("B", bound="Bilibili")
class BilibiliClientManager(CookieClientManager):
_default_cookie_cd = timedelta(seconds=120)
class BilibiliClientManager(ClientManager):
_client: AsyncClient
_inited: bool = False
def __init__(self) -> None:
self._client = http_client()
async def _get_cookies(self) -> list[Cookie]:
browser = await get_browser()
async with await browser.new_page() as page:
await page.goto(f"https://space.bilibili.com/{random.randint(1, 1000)}/dynamic")
await page.wait_for_load_state("load") # 等待基本加载完成
await page.wait_for_function('document.cookie.includes("bili_ticket")') # 期望保证 GenWebTicket 请求完成
await page.wait_for_load_state("networkidle") # 期望保证 ExClimbWuzhi 请求完成
await page.wait_for_load_state("load")
cookies = await page.context.cookies()
return cookies
def _gen_json_cookie(self, cookies: list[Cookie]):
cookie_dict = {}
async def _reset_client_cookies(self, cookies: list[Cookie]):
for cookie in cookies:
cookie_dict[cookie.get("name", "")] = cookie.get("value", "")
return cookie_dict
@override
async def _generate_anonymous_cookie(self) -> CookieModel:
cookies = await self._get_cookies()
cookie = CookieModel(
cookie_name=f"{self._site_name} anonymous",
site_name=self._site_name,
content=json.dumps(self._gen_json_cookie(cookies)),
is_universal=True,
is_anonymous=True,
last_usage=datetime.now(),
cd_milliseconds=0,
tags="{}",
status="",
)
return cookie
self._client.cookies.set(
name=cookie.get("name", ""),
value=cookie.get("value", ""),
domain=cookie.get("domain", ""),
path=cookie.get("path", "/"),
)
@override
async def refresh_client(self):
await self._refresh_anonymous_cookie()
cookies = await self._get_cookies()
await self._reset_client_cookies(cookies)
logger.debug("刷新B站客户端的cookie")
@override
async def get_client(self, target: Target | None) -> AsyncClient:
if not self._inited:
logger.debug("初始化B站客户端")
await self.refresh_client()
self._inited = True
return self._client
@override
async def get_client_for_static(self) -> AsyncClient:
return http_client()
@@ -73,7 +68,7 @@ class BilibiliClientManager(CookieClientManager):
class BilibiliSite(Site):
name = "bilibili.com"
schedule_setting: ClassVar[dict] = {"seconds": 60}
schedule_setting = {"seconds": 60}
schedule_type = "interval"
client_mgr = BilibiliClientManager
require_browser = True
@@ -81,11 +76,11 @@ class BilibiliSite(Site):
class BililiveSite(Site):
name = "live.bilibili.com"
schedule_setting: ClassVar[dict] = {"seconds": 5}
schedule_setting = {"seconds": 5}
schedule_type = "interval"
class BiliBangumiSite(Site):
name = "bilibili.com/bangumi"
schedule_setting: ClassVar[dict] = {"seconds": 30}
schedule_setting = {"seconds": 30}
schedule_type = "interval"
+7 -7
View File
@@ -1,16 +1,16 @@
from collections.abc import Callable
from datetime import timedelta
from functools import partial
from types import MappingProxyType
from typing import TypeAlias
from functools import partial
from datetime import timedelta
from types import MappingProxyType
from collections.abc import Callable
from expiringdictx import ExpiringDict, SimpleCache
from hishel import AsyncCacheTransport, AsyncInMemoryStorage, Controller
from httpx import AsyncClient, AsyncHTTPTransport
from expiringdictx import SimpleCache, ExpiringDict
from hishel import Controller, AsyncCacheTransport, AsyncInMemoryStorage
from .const import DATASOURCE_URL
from .models import CeobeSource, CeobeTarget, DataSourceResponse
from .utils import process_response
from .models import CeobeSource, CeobeTarget, DataSourceResponse
cache_transport = AsyncCacheTransport(
AsyncHTTPTransport(),
+1 -1
View File
@@ -1,4 +1,4 @@
DATASOURCE_URL = "https://server.ceobecanteen.top/api/v1/canteen/config/datasource/list"
COMB_ID_URL = "https://server.ceobecanteen.top/api/v1/canteen/user/getDatasourceComb"
COOKIE_ID_URL = "https://cdn.ceobecanteen.top/datasource-comb"
COOKIE_ID_URL = "http://cdn.ceobecanteen.top/datasource-comb"
COOKIES_URL = "https://server-cdn.ceobecanteen.top/api/v1/cdn/cookie/mainList/cookieList"
@@ -1,4 +1,4 @@
from typing import Literal, NamedTuple, TypeVar
from typing import Literal, TypeVar, NamedTuple
from pydantic import BaseModel
+14 -16
View File
@@ -1,23 +1,23 @@
from collections import defaultdict
from datetime import timedelta
from typing import ParamSpec
from functools import partial
from typing import ClassVar, ParamSpec
from datetime import timedelta
from collections import defaultdict
from httpx import AsyncClient
from nonebot import logger, require
from rapidfuzz import fuzz, process
from nonebot_bison.platform.platform import NewMessage
from nonebot_bison.plugin_config import plugin_config
from nonebot_bison.post import Post
from nonebot_bison.types import Category, RawPost, Target
from nonebot_bison.utils import ClientManager, Site, capture_html
from nonebot_bison.plugin_config import plugin_config
from nonebot_bison.types import Target, RawPost, Category
from nonebot_bison.utils import Site, ClientManager, capture_html
from .cache import CeobeCache, CeobeClient, CeobeDataSourceCache
from .const import COMB_ID_URL, COOKIE_ID_URL, COOKIES_URL
from .exception import CeobeSnapshotFailed, CeobeSnapshotSkip
from .models import CeobeCookie, CeobeImage, CeobeTextPic, CombIdResponse, CookieIdResponse, CookiesResponse
from ..platform import NewMessage
from .utils import process_response
from .const import COMB_ID_URL, COOKIES_URL, COOKIE_ID_URL
from .exception import CeobeSnapshotSkip, CeobeSnapshotFailed
from .cache import CeobeCache, CeobeClient, CeobeDataSourceCache
from .models import CeobeImage, CeobeCookie, CeobeTextPic, CombIdResponse, CookiesResponse, CookieIdResponse
P = ParamSpec("P")
@@ -49,7 +49,7 @@ class CeobeCanteenSite(Site):
name = "ceobe_canteen"
schedule_type = "interval"
# lwt の 推荐间隔
schedule_setting: ClassVar[dict] = {"seconds": 15}
schedule_setting = {"seconds": 15}
client_mgr = CeobeCanteenClientManager
@@ -64,7 +64,7 @@ class CeobeCanteen(NewMessage):
use_batch: bool = True
default_theme: str = "ceobecanteen"
categories: ClassVar[dict[Category, str]] = {1: "普通", 2: "转发"}
categories: dict[Category, str] = {1: "普通", 2: "转发"}
data_source_cache = CeobeDataSourceCache()
@@ -213,9 +213,7 @@ class CeobeCanteen(NewMessage):
logger.debug(f"snapshot official website url: {url}")
# /html/body/div[1]/div[1]/div/div[1]/div[1]/div
snapshot_selector = (
"html > body > div:nth-child(1) > div:nth-child(1) > div > div:nth-child(1) > div:nth-child(1) > div"
)
snapshot_selector = "html > body > div:nth-child(1) > div:nth-child(1) > div > div:nth-child(1) > div:nth-child(1) > div" # noqa: E501
# /html/body/div[1]/div[1]/div/div[1]/div[1]/div/div[4]/div/div/div
calculate_selector = "html > body > div:nth-child(1) > div:nth-child(1) > div > div:nth-child(1) > div:nth-child(1) > div > div:nth-child(4) > div > div > div" # noqa: E501
viewport = {"width": 1024, "height": 19990}
+1 -1
View File
@@ -3,7 +3,7 @@ from nonebot import logger
from nonebot.compat import type_validate_python
from .exception import CeobeResponseError
from .models import CookieIdResponse, ResponseModel
from .models import ResponseModel, CookieIdResponse
def process_response(response: Response, parse_model: type[ResponseModel]) -> ResponseModel:
+5 -6
View File
@@ -1,16 +1,15 @@
from typing import Any, ClassVar
from typing import Any
from httpx import AsyncClient
from nonebot_bison.post import Post
from nonebot_bison.types import RawPost, Target
from nonebot_bison.utils import anonymous_site
from ..post import Post
from .platform import NewMessage
from ..utils import anonymous_site
from ..types import Target, RawPost
class FF14(NewMessage):
categories: ClassVar[dict] = {}
categories = {}
platform_name = "ff14"
name = "最终幻想XIV官方公告"
enable_tag = False
+7 -8
View File
@@ -1,23 +1,22 @@
import re
from typing import Any, ClassVar
from typing import Any
from httpx import AsyncClient
from nonebot_bison.post import Post
from nonebot_bison.types import ApiError, Category, RawPost, Target
from nonebot_bison.utils import Site
from ..post import Post
from ..utils import Site
from .platform import NewMessage
from ..types import Target, RawPost, ApiError
class NcmSite(Site):
name = "music.163.com"
schedule_type = "interval"
schedule_setting: ClassVar[dict] = {"minutes": 1}
schedule_setting = {"minutes": 1}
class NcmArtist(NewMessage):
categories: ClassVar[dict[Category, str]] = {}
categories = {}
platform_name = "ncm-artist"
enable_tag = False
enabled = True
@@ -74,7 +73,7 @@ class NcmArtist(NewMessage):
class NcmRadio(NewMessage):
categories: ClassVar[dict[Category, str]] = {}
categories = {}
platform_name = "ncm-radio"
enable_tag = False
enabled = True
+10 -25
View File
@@ -1,22 +1,22 @@
from abc import ABC, abstractmethod
from collections import defaultdict
from collections.abc import Awaitable, Callable, Collection
from dataclasses import dataclass
import json
import ssl
import json
import time
import typing
from typing import Any, ParamSpec, TypeVar
from dataclasses import dataclass
from abc import ABC, abstractmethod
from collections import defaultdict
from typing import Any, TypeVar, ParamSpec
from collections.abc import Callable, Awaitable, Collection
import httpx
from httpx import AsyncClient
from nonebot.log import logger
from nonebot_plugin_saa import PlatformTarget
from nonebot_bison.plugin_config import plugin_config
from nonebot_bison.post import Post
from nonebot_bison.types import Category, RawPost, SubUnit, Tag, Target
from nonebot_bison.utils import ProcessContext, Site
from ..post import Post
from ..utils import Site, ProcessContext
from ..plugin_config import plugin_config
from ..types import Tag, Target, RawPost, SubUnit, Category, RegistryMeta
class CategoryNotSupport(Exception):
@@ -29,21 +29,6 @@ class CategoryNotRecognize(Exception):
"""raise in get_category, when you don't know the category of post"""
class RegistryMeta(type):
def __new__(cls, name, bases, namespace, **kwargs):
return super().__new__(cls, name, bases, namespace)
def __init__(cls, name, bases, namespace, **kwargs):
if kwargs.get("base"):
# this is the base class
cls.registry = []
elif not kwargs.get("abstract"):
# this is the subclass
cls.registry.append(cls)
super().__init__(name, bases, namespace, **kwargs)
P = ParamSpec("P")
R = TypeVar("R")
+11 -11
View File
@@ -1,27 +1,27 @@
import calendar
import time
from typing import Any, ClassVar
import calendar
from typing import Any
from bs4 import BeautifulSoup as bs
import feedparser
from httpx import AsyncClient
from bs4 import BeautifulSoup as bs
from nonebot_bison.post import Post
from nonebot_bison.types import Category, RawPost, Target
from nonebot_bison.utils import text_similarity
from nonebot_bison.utils.site import CookieClientManager, Site
from ..post import Post
from .platform import NewMessage
from ..types import Target, RawPost
from ..utils import Site, text_similarity
from ..utils.site import create_cookie_client_manager
class RssSite(Site):
name = "rss"
schedule_type = "interval"
schedule_setting: ClassVar[dict] = {"seconds": 30}
client_mgr = CookieClientManager.from_name(name)
schedule_setting = {"seconds": 30}
client_mgr = create_cookie_client_manager("rss")
class RssPost(Post):
async def get_plain_content(self) -> str:
soup = bs(self.content, "html.parser")
@@ -38,7 +38,7 @@ class RssPost(Post):
class Rss(NewMessage):
categories: ClassVar[dict[Category, str]] = {}
categories = {}
enable_tag = False
platform_name = "rss"
name = "Rss"
+16 -37
View File
@@ -1,22 +1,20 @@
from datetime import datetime
import json
import re
from typing import Any, ClassVar
from typing_extensions import override
import json
from typing import Any
from datetime import datetime
from urllib.parse import unquote
from bs4 import BeautifulSoup as bs
from httpx import AsyncClient
from lxml.etree import HTML
from nonebot.log import logger
from yarl import URL
from lxml.etree import HTML
from httpx import AsyncClient
from nonebot.log import logger
from bs4 import BeautifulSoup as bs
from nonebot_bison.post import Post
from nonebot_bison.types import ApiError, Category, RawPost, Tag, Target
from nonebot_bison.utils import http_client, text_fletten
from nonebot_bison.utils.site import CookieClientManager, Site
from ..post import Post
from .platform import NewMessage
from ..utils import Site, http_client
from ..utils.site import create_cookie_client_manager
from ..types import Tag, Target, RawPost, ApiError, Category
_HEADER = {
"accept": (
@@ -38,34 +36,15 @@ _HEADER = {
}
class WeiboClientManager(CookieClientManager):
_site_name = "weibo.com"
async def _get_current_user_name(self, cookies: dict) -> str:
url = "https://m.weibo.cn/setup/nick/detail"
async with http_client() as client:
r = await client.get(url, headers=_HEADER, cookies=cookies)
data = json.loads(r.text)
name = data["data"]["user"]["screen_name"]
return name
@override
async def get_cookie_name(self, content: str) -> str:
"""从cookie内容中获取cookie的友好名字,添加cookie时调用,持久化在数据库中"""
name = await self._get_current_user_name(json.loads(content))
return text_fletten(f"weibo: [{name[:10]}]")
class WeiboSite(Site):
name = "weibo.com"
schedule_type = "interval"
schedule_setting: ClassVar[dict] = {"seconds": 3}
client_mgr = WeiboClientManager
schedule_setting = {"seconds": 3}
client_mgr = create_cookie_client_manager(name)
class Weibo(NewMessage):
categories: ClassVar[dict[Category, str]] = {
categories = {
1: "转发",
2: "视频",
3: "图文",
@@ -177,7 +156,7 @@ class Weibo(NewMessage):
try:
client = await self.ctx.get_client()
weibo_info = await client.get(
"https://m.weibo.cn/statuses/extend",
"https://m.weibo.cn/statuses/show",
params={"id": weibo_id},
headers=_HEADER,
)
@@ -191,7 +170,7 @@ class Weibo(NewMessage):
async def _parse_weibo(self, info: dict) -> Post:
if info["isLongText"] or info["pic_num"] > 9:
info["text"] = (await self._get_long_weibo(info["mid"]))["longTextContent"]
info["text"] = (await self._get_long_weibo(info["mid"]))["text"]
parsed_text = self._get_text(info["text"])
raw_pics_list = info.get("pics", [])
pic_urls = [img["large"]["url"] for img in raw_pics_list]
+4 -5
View File
@@ -1,8 +1,8 @@
import nonebot
from nonebot import get_plugin_config
from nonebot.compat import PYDANTIC_V2, ConfigDict
from pydantic import BaseModel, Field
from yarl import URL
from nonebot import get_plugin_config
from pydantic import Field, BaseModel
from nonebot.compat import PYDANTIC_V2, ConfigDict
global_config = nonebot.get_driver().config
PlatformName = str
@@ -36,8 +36,7 @@ class PlugConfig(BaseModel):
bison_resend_times: int = 0
bison_proxy: str | None = None
bison_ua: str = Field(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
" Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
description="默认UA",
)
bison_show_network_warning: bool = True
+4 -4
View File
@@ -1,10 +1,10 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
from abc import ABC, abstractmethod
from nonebot_plugin_saa import MessageFactory, MessageSegmentFactory, Text
from nonebot_plugin_saa import Text, MessageFactory, MessageSegmentFactory
from nonebot_bison.plugin_config import plugin_config
from nonebot_bison.utils import text_to_image
from ..utils import text_to_image
from ..plugin_config import plugin_config
@dataclass(kw_only=True)
+7 -9
View File
@@ -1,22 +1,20 @@
from collections.abc import Sequence
from dataclasses import dataclass, fields
import reprlib
from io import BytesIO
from pathlib import Path
import reprlib
from typing import TYPE_CHECKING
from dataclasses import fields, dataclass
from nonebot.log import logger
from nonebot_plugin_saa import MessageSegmentFactory
from nonebot_bison.plugin_config import plugin_config
from nonebot_bison.theme import theme_manager
from nonebot_bison.theme.types import ThemeRenderError, ThemeRenderUnsupportError
from ..theme import theme_manager
from .abstract_post import AbstractPost
from ..plugin_config import plugin_config
from .protocol import PlainContentSupport
from ..theme.types import ThemeRenderError, ThemeRenderUnsupportError
if TYPE_CHECKING:
from nonebot_bison.platform import Platform
from ..platform import Platform
@dataclass
@@ -32,7 +30,7 @@ class Post(AbstractPost, PlainContentSupport):
"""文本内容"""
title: str | None = None
"""标题"""
images: Sequence[str | bytes | Path | BytesIO] | None = None
images: list[str | bytes | Path | BytesIO] | None = None
"""图片列表"""
timestamp: float | None = None
"""发布/获取时间戳, 秒"""
+2 -2
View File
@@ -1,3 +1,3 @@
from .manager import handle_delete_target, handle_insert_new_target, init_scheduler, scheduler_dict
from .manager import init_scheduler, scheduler_dict, handle_delete_target, handle_insert_new_target
__all__ = ["handle_delete_target", "handle_insert_new_target", "init_scheduler", "scheduler_dict"]
__all__ = ["init_scheduler", "handle_delete_target", "handle_insert_new_target", "scheduler_dict"]
+10 -11
View File
@@ -2,15 +2,14 @@ from typing import cast
from nonebot.log import logger
from nonebot_bison.config import config
from nonebot_bison.config.db_model import Target
from nonebot_bison.platform import platform_manager
from nonebot_bison.plugin_config import plugin_config
from nonebot_bison.types import Target as T_Target
from nonebot_bison.utils import Site
from nonebot_bison.utils.site import CookieClientManager, is_cookie_client_manager
from ..utils import Site
from ..config import config
from .scheduler import Scheduler
from ..config.db_model import Target
from ..types import Target as T_Target
from ..platform import platform_manager
from ..plugin_config import plugin_config
from ..utils.site import CookieClientManager, is_cookie_client_manager
scheduler_dict: dict[type[Site], Scheduler] = {}
@@ -34,6 +33,9 @@ async def init_scheduler():
else:
_schedule_class_platform_dict[site].append(platform_name)
for site, target_list in _schedule_class_dict.items():
if is_cookie_client_manager(site.client_mgr):
client_mgr = cast(CookieClientManager, site.client_mgr)
await client_mgr.refresh_anonymous_cookie()
if not plugin_config.bison_use_browser and site.require_browser:
logger.warning(f"{site.name} requires browser, it will not schedule.")
continue
@@ -45,9 +47,6 @@ async def init_scheduler():
)
platform_name_list = _schedule_class_platform_dict[site]
scheduler_dict[site] = Scheduler(site, schedulable_args, platform_name_list)
if is_cookie_client_manager(site.client_mgr):
client_mgr = cast(CookieClientManager, scheduler_dict[site].client_mgr)
await client_mgr.refresh_client()
config.register_add_target_hook(handle_insert_new_target)
config.register_delete_target_hook(handle_delete_target)
+9 -7
View File
@@ -1,16 +1,18 @@
from collections import defaultdict
from dataclasses import dataclass
from collections import defaultdict
from nonebot.log import logger
from nonebot_plugin_apscheduler import scheduler
from nonebot_plugin_saa.utils.exceptions import NoBotFound
from nonebot_bison.config import config
from nonebot_bison.platform import platform_manager
from nonebot_bison.send import send_msgs
from nonebot_bison.types import SubUnit, Target
from nonebot_bison.utils import ClientManager, ProcessContext, Site
from nonebot_bison.utils.site import SkipRequestException
from nonebot_bison.utils import ClientManager
from ..config import config
from ..send import send_msgs
from ..types import Target, SubUnit
from ..platform import platform_manager
from ..utils import Site, ProcessContext
from ..utils.site import SkipRequestException
@dataclass
+13 -16
View File
@@ -1,26 +1,25 @@
from collections.abc import Callable, Coroutine
from functools import partial, wraps
import importlib
import json
from pathlib import Path
import time
import importlib
from pathlib import Path
from types import ModuleType
from typing import Any, TypeVar
from functools import wraps, partial
from collections.abc import Callable, Coroutine
from anyio import open_file
from nonebot.compat import model_dump
from nonebot.log import logger
from nonebot.compat import model_dump
from nonebot_bison.config.subs_io import subscribes_export, subscribes_import
from nonebot_bison.config.subs_io.nbesf_model import v1, v2, v3
from nonebot_bison.scheduler.manager import init_scheduler
from ..scheduler.manager import init_scheduler
from ..config.subs_io.nbesf_model import v1, v2
from ..config.subs_io import subscribes_export, subscribes_import
try:
from typing_extensions import ParamSpec
import anyio
from anyio import from_thread, to_thread
import click
from anyio import to_thread, from_thread
except ImportError as e: # pragma: no cover
raise ImportError("请使用 `pip install nonebot-bison[cli]` 安装所需依赖") from e
@@ -84,7 +83,7 @@ async def subs_export(path: Path, format: str):
export_file = path / f"bison_subscribes_export_{int(time.time())}.{format}"
logger.info("正在获取订阅信息...")
export_data: v3.SubGroup = await subscribes_export(lambda x: x)
export_data: v2.SubGroup = await subscribes_export(lambda x: x)
with export_file.open("w", encoding="utf-8") as f:
match format:
@@ -128,18 +127,18 @@ async def subs_import(path: str, format: str):
import_file_path = Path(path)
assert import_file_path.is_file(), "该路径不是文件!"
async with await open_file(import_file_path, "r", encoding="utf-8") as f:
with import_file_path.open("r", encoding="utf-8") as f:
match format:
case "yaml" | "yml":
logger.info("正在从yaml导入...")
pyyaml = import_yaml_module()
import_items = pyyaml.safe_load(await f.read())
import_items = pyyaml.safe_load(f)
case "json":
logger.info("正在从json导入...")
import_items = json.loads(await f.read())
import_items = json.load(f)
case _:
raise click.BadParameter(message=f"不支持的导入格式: {format}")
@@ -152,8 +151,6 @@ async def subs_import(path: str, format: str):
nbesf_data = v1.nbesf_parser(import_items)
case 2:
nbesf_data = v2.nbesf_parser(import_items)
case 3:
nbesf_data = v3.nbesf_parser(import_items)
case _:
raise NotImplementedError("不支持的NBESF版本")
+3 -7
View File
@@ -1,10 +1,10 @@
import asyncio
from collections import deque
from nonebot.adapters.onebot.v11.exception import ActionFailed
from nonebot.log import logger
from nonebot_plugin_saa import AggregatedMessageFactory, MessageFactory, PlatformTarget
from nonebot_plugin_saa.auto_select_bot import refresh_bots
from nonebot.adapters.onebot.v11.exception import ActionFailed
from nonebot_plugin_saa import MessageFactory, PlatformTarget, AggregatedMessageFactory
from .plugin_config import plugin_config
@@ -14,8 +14,6 @@ QUEUE: deque[tuple[PlatformTarget, Sendable, int]] = deque()
MESSGE_SEND_INTERVAL = 1.5
_MESSAGE_DISPATCH_TASKS: set[asyncio.Task] = set()
async def _do_send(send_target: PlatformTarget, msg: Sendable):
try:
@@ -61,9 +59,7 @@ async def _send_msgs_dispatch(send_target: PlatformTarget, msg: Sendable):
QUEUE.append((send_target, msg, plugin_config.bison_resend_times))
# len(QUEUE) before append was 0
if len(QUEUE) == 1:
task = asyncio.create_task(do_send_msgs())
_MESSAGE_DISPATCH_TASKS.add(task)
task.add_done_callback(_MESSAGE_DISPATCH_TASKS.discard)
asyncio.create_task(do_send_msgs())
else:
await _do_send(send_target, msg)
+19 -30
View File
@@ -2,25 +2,23 @@ import asyncio
from datetime import datetime
from nonebot import on_command
from nonebot.typing import T_State
from nonebot.matcher import Matcher
from nonebot.rule import Rule, to_me
from nonebot.permission import SUPERUSER
from nonebot_plugin_saa import TargetQQGroup
from nonebot.params import ArgStr, ArgPlainText
from nonebot.adapters import Bot, MessageTemplate
from nonebot.adapters.onebot.v11.event import PrivateMessageEvent
from nonebot.matcher import Matcher
from nonebot.params import ArgPlainText, ArgStr
from nonebot.permission import SUPERUSER
from nonebot.rule import Rule, to_me
from nonebot.typing import T_State
from nonebot_plugin_saa import TargetQQGroup
from .add_cookie import do_add_cookie
from .add_cookie_target import do_add_cookie_target
from .add_sub import do_add_sub
from .del_cookie import do_del_cookie
from .del_cookie_target import do_del_cookie_target
from .del_sub import do_del_sub
from .query_sub import do_query_sub
from .utils import admin_permission, common_platform, configurable_to_me, gen_handle_cancel, set_target_user_info
_COMMAND_DISPATCH_TASKS: set[asyncio.Task] = set()
from .add_cookie import do_add_cookie
from .del_cookie import do_del_cookie
from .add_cookie_target import do_add_cookie_target
from .del_cookie_target import do_del_cookie_target
from .utils import common_platform, admin_permission, gen_handle_cancel, configurable_to_me, set_target_user_info
add_sub_matcher = on_command(
"添加订阅",
@@ -32,12 +30,10 @@ add_sub_matcher = on_command(
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, block=True)
query_sub_matcher.handle()(set_target_user_info)
do_query_sub(query_sub_matcher)
del_sub_matcher = on_command(
"删除订阅",
rule=configurable_to_me,
@@ -151,18 +147,11 @@ async def do_dispatch_command(
else:
do_del_sub(new_matcher)
new_matcher_ins = new_matcher()
task = asyncio.create_task(new_matcher_ins.run(bot, event, state))
_COMMAND_DISPATCH_TASKS.add(task)
task.add_done_callback(_COMMAND_DISPATCH_TASKS.discard)
asyncio.create_task(new_matcher_ins.run(bot, event, state))
no_permission_matcher = on_command(
"添加订阅",
rule=configurable_to_me,
aliases={"删除订阅", "群管理", "管理后台", "添加cookie", "删除cookie", "关联cookie", "取消关联cookie"},
priority=8,
block=True,
"添加订阅", rule=configurable_to_me, aliases={"删除订阅", "群管理"}, priority=8, block=True
)
@@ -172,14 +161,14 @@ async def send_no_permission():
__all__ = [
"add_cookie_matcher",
"add_cookie_target_matcher",
"add_sub_matcher",
"common_platform",
"del_cookie_matcher",
"del_cookie_target_matcher",
"add_sub_matcher",
"query_sub_matcher",
"del_sub_matcher",
"group_manage_matcher",
"no_permission_matcher",
"query_sub_matcher",
"add_cookie_matcher",
"add_cookie_target_matcher",
"del_cookie_target_matcher",
"del_cookie_matcher",
]

Some files were not shown because too many files have changed in this diff Show More