94 Commits

Author SHA1 Message Date
suyiiyii 074fe3ff58 ♻️ _site_name 和 _default_cookie_cd 内置 CookieClientManager 2024-10-29 21:28:35 +08:00
suyiiyii 8a8a48aef6 ♻️ 移除CookieSite 2024-10-29 21:17:07 +08:00
suyiiyii c5dea7e252 ♻️ 初步移除CookieSite 2024-10-29 21:12:50 +08:00
suyiiyii 07190a7f64 ♻️ 改回 _site_name 2024-10-29 20:53:23 +08:00
suyiiyii 62386931d7 ♻️ 移除 bilibili 的 _site_name 属性 2024-10-29 14:43:16 +08:00
suyiiyii 32c237015f ♻️ 更新部分方法使用 scheduler_dict 进行cookie操作 2024-10-29 14:15:15 +08:00
suyiiyii 29c2eb456d ♻️ 更新部分方法使用 scheduler_dict 进行cookie操作 2024-10-29 12:24:31 +08:00
suyiiyii 9d985eb3c8 ♻️ bilibili 移除 _client 属性 2024-10-29 12:23:50 +08:00
suyiiyii 4805d0d77c ♻️ 在 CookieClientManager 中添加对对应的 Site 的引用 2024-10-29 11:33:28 +08:00
suyiiyii 81e53419a3 ♻️ 将 refresh_anonymous_cookie 改为内部方法, 同时外部使用 refresh_client 方法进行刷新匿名 cookie 2024-10-29 09:38:08 +08:00
suyiiyii b130627d7e ♻️ 还原对 bilibili platform 的修改 2024-10-29 00:48:35 +08:00
suyiiyii 4dd4555fc7 📝 更新文档 2024-10-29 00:44:58 +08:00
suyiiyii e6c45e5b1b ♻️ 为 row2dict 添加类型注解 2024-10-28 23:56:10 +08:00
suyiiyii b60ba566de ♻️ 将 default_cd 重命名为 default_cookie_cd 2024-10-28 23:54:12 +08:00
suyiiyii ac794efd18 :refactor: 将 cookie default_cd 移动到 CookieSite 内部 2024-10-28 23:50:04 +08:00
suyiiyii cdd671b15f 🐛 修复「管理后台」重复发送消息 2024-10-28 23:06:54 +08:00
suyiiyii 6e53c6f4b2 🐛 优化对 bilibili cookie 的 mock 2024-10-28 22:48:11 +08:00
suyiiyii 3a0f95b712 🐛 优化对 weibo get_cookie_name 的 mock 2024-10-28 22:47:31 +08:00
suyiiyii 641cc44a12 🔀 merge 2024-10-28 22:07:20 +08:00
suyiiyii 42cc56ac24 🐛 优化对 bilibili cookie 的 mock 2024-10-28 22:06:12 +08:00
suyiiyii 0deb406692 🐛 更新判断superuser的私聊消息的逻辑 2024-10-28 22:00:15 +08:00
suyiiyii eb64eab14a 添加cookie命令的无权限提示 2024-10-28 13:46:22 +08:00
suyiiyii 3043817de4 🐛 为cookie相关操作添加非私聊拒绝提示 2024-10-28 13:29:10 +08:00
suyiiyii 0985705c22 🐛 添加 Cookie 时,显示 「无法获取cookie_name」 的错误提示 2024-10-28 13:17:36 +08:00
suyiiyii 438b23a0b9 🐛 从 Site 中移除 「无效的 Cookie」 文本 2024-10-28 12:44:51 +08:00
suyiiyii dcd32f0662 📝 更新文档顶栏 2024-10-28 12:37:17 +08:00
suyiiyii 60cee1bcc1 🐛 修复B站获取匿名Cookie逻辑-ExClimbWuzhi 2024-10-24 21:40:32 +08:00
suyiiyii e64fb09145 🐛 修复B站获取匿名Cookie逻辑 2024-10-24 17:43:13 +08:00
github-actions[bot] 08ba7b0e3b 📝 Update changelog 2024-10-24 08:56:51 +00:00
suyiiyii 1a0eca4a38 更新默认UA为Windows平台 (#643) 2024-10-24 16:56:24 +08:00
suyiiyii d2c33feff8 mock生成匿名Cookie的逻辑 2024-10-23 10:53:11 +08:00
suyiiyii ef52339337 移除用于测试的 token 2024-10-22 21:54:04 +08:00
suyiiyii d614d8bf64 为 bilibili 初步适配Cookie功能 2024-10-22 21:15:37 +08:00
suyiiyii e84894cd9e CookieClientManager中提出_generate_anonymous_cookie方法 2024-10-22 21:13:31 +08:00
suyiiyii ff1c3c3159 WebUI 中,允许查看 Cookie 的 content 2024-10-21 20:58:21 +08:00
suyiiyii 8d32145ea8 ✏️ 改改变量名 2024-10-21 20:43:08 +08:00
suyiiyii f5d30b998c 📝 添加 获取Cookie 的文档 2024-10-21 13:44:46 +08:00
suyiiyii a090ff2127 Merge branch 'MountainDash:main' into cookie 2024-10-21 13:21:44 +08:00
suyiiyii 6474504c30 (admin) 添加验证Cookie有效性 2024-10-21 11:22:54 +08:00
suyiiyii a78bb73281 (admin) 添加验证Cookie有效性接口 2024-10-21 10:04:00 +08:00
suyiiyii c3289671d8 📝 关于 Cookie 的开发指南 2024-10-19 13:25:07 +08:00
suyiiyii 0f4b0aab86 📝 好像还是要加上sass-embedded这个库 2024-10-16 18:15:07 +08:00
suyiiyii 69ef94bcf5 📝 破防了,不整zenuml了 2024-10-16 17:14:59 +08:00
suyiiyii 3c090db0cb 📝 似乎可以了? 2024-10-16 10:41:36 +08:00
suyiiyii 56627753fc 📝 怎么前端编译报错了( 2024-10-16 09:35:59 +08:00
suyiiyii 7359b3ef9d 📝 怎么前端编译报错了( 2024-10-16 09:33:59 +08:00
suyiiyii 7622d3da1e 📝 好像可以用 zenuml 了 ) 2024-10-15 21:43:25 +08:00
suyiiyii 3773c77864 📝 试试zenuml ( 2024-10-15 18:12:53 +08:00
suyiiyii cdc8de9619 📝 试试流程图) 2024-10-14 15:35:29 +08:00
suyiiyii 7aac134d5d 📝 测试mermaid画图 2024-10-14 12:32:59 +08:00
suyiiyii b2b20ab7c5 📝 添加cookie模块使用文档 2024-10-14 01:14:03 +08:00
suyiiyii 2093622672 🐛 改改单测 2024-10-13 21:51:47 +08:00
suyiiyii b6ba904a68 🐛 又忘记改单测了( 2024-10-13 21:30:08 +08:00
suyiiyii f3d8b7d5bc 添加 cookie 时自动使用用户名命名(weibo) 2024-10-13 20:44:37 +08:00
suyiiyii b1b8d37171 (admin) console.log()忘记删了 ( 2024-10-12 13:41:28 +08:00
suyiiyii 271e3e7a72 🗑️ 删除旧的CookieTargetManager 2024-10-12 13:37:17 +08:00
suyiiyii a941d77257 (admin) 支持删除cookieTarget( 2024-10-12 13:35:53 +08:00
suyiiyii 6d8de1b59e (admin) 支持添加cookieTarget 2024-10-12 13:14:04 +08:00
suyiiyii 8174ec895d (admin) 支持查看cookie详情 2024-10-11 13:41:00 +08:00
suyiiyii 10bb6179ae (admin) 支持查看cookie详情 2024-10-11 13:40:31 +08:00
suyiiyii 7030758ac9 (admin) 支持删除cookie 2024-10-11 12:05:28 +08:00
suyiiyii a18b2e4f7b (admin) 支持添加cookie 2024-10-11 11:54:30 +08:00
suyiiyii 3f3cc2d25e 🐛 更改命名siteName以符合小驼峰规范 2024-10-11 11:37:18 +08:00
suyiiyii 5b8d0440ee (admin) 完善 将site选择移动到侧边栏 2024-10-11 11:12:15 +08:00
suyiiyii 24251c2728 (admin) 将site选择移动到侧边栏 2024-10-09 23:23:43 +08:00
suyiiyii 19345cf960 移动validate_cookie到CookieSite中 2024-10-09 23:18:43 +08:00
suyiiyii 1346d07982 修改cookie_site.get_cookie_name为异步 2024-10-09 23:14:17 +08:00
renovate[bot] ce1c902905 ⬆️ Update dependency pre-commit to v4 (#637)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-09 14:19:06 +08:00
suyiiyii 6c0efdddfc 🔀 merge main 2024-10-08 17:24:34 +08:00
洛初 40f9bc817f feat(ISSUE_TEMPLATE): 修改问题报告模板 (#630)
test-build / Build Frontend (push) Has been cancelled
test-build / Smoke-test Coverage (macos-latest, 3.10) (push) Has been cancelled
test-build / Smoke-test Coverage (macos-latest, 3.11) (push) Has been cancelled
test-build / Smoke-test Coverage (macos-latest, 3.12) (push) Has been cancelled
test-build / Smoke-test Coverage (ubuntu-latest, 3.10) (push) Has been cancelled
test-build / Smoke-test Coverage (ubuntu-latest, 3.11) (push) Has been cancelled
test-build / Smoke-test Coverage (ubuntu-latest, 3.12) (push) Has been cancelled
test-build / Smoke-test Coverage (windows-latest, 3.10) (push) Has been cancelled
test-build / Smoke-test Coverage (windows-latest, 3.11) (push) Has been cancelled
test-build / Smoke-test Coverage (windows-latest, 3.12) (push) Has been cancelled
test-build / All-test Coverage (macos-latest, 3.10) (push) Has been cancelled
test-build / All-test Coverage (macos-latest, 3.11) (push) Has been cancelled
test-build / All-test Coverage (macos-latest, 3.12) (push) Has been cancelled
test-build / All-test Coverage (ubuntu-latest, 3.10) (push) Has been cancelled
test-build / All-test Coverage (ubuntu-latest, 3.11) (push) Has been cancelled
test-build / All-test Coverage (ubuntu-latest, 3.12) (push) Has been cancelled
test-build / All-test Coverage (windows-latest, 3.10) (push) Has been cancelled
test-build / All-test Coverage (windows-latest, 3.11) (push) Has been cancelled
test-build / All-test Coverage (windows-latest, 3.12) (push) Has been cancelled
pydantic1-compat-test / pydantic1 test (ubuntu-latest, 3.11) (push) Has been cancelled
Ruff Lint / Ruff Lint (push) Has been cancelled
test-build / Docker main (push) Has been cancelled
test-build / Docker main sentry (push) Has been cancelled
*  feat(ISSUE_TEMPLATE): 修改问题报告模板 

  feat(ISSUE_TEMPLATE): 修改问题报告模板

* 💄 auto fix by pre-commit hooks

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-10-03 10:31:47 +08:00
github-actions[bot] f416b249f7 📝 Update changelog 2024-10-03 02:31:09 +00:00
suyiiyii ec6fe2679d 👷 将自动化工具中的 yarn 替换为 pnpm (#634)
* 👷 将自动化工具中的 yarn 替换为 pnpm

* 🐛 写串台了艹

* 🐛 最讨厌调试 action 了 (
2024-10-03 10:29:57 +08:00
github-actions[bot] 19b25552d0 📝 Update changelog 2024-10-01 07:38:45 +00:00
洛梧藤 85b5ab3868 📝 小刻食堂剪彩文档 (#636) 2024-10-01 15:38:16 +08:00
suyiiyii 1d1b9f6574 🐛 config 中添加 clear_db 方法,用于清空数据库内容;添加clear_db fixture,用于在单测前后清空数据库 2024-09-28 16:51:22 +08:00
suyiiyii aa897939d9 为什么 eslint 的warning都不让我过编译 ( 2024-09-27 09:51:19 +08:00
suyiiyii 7d3193d958 为什么前端 pnpm 和 yarn 的行为还不一样( 2024-09-27 01:18:59 +08:00
suyiiyii f31a798326 修修单测 2024-09-27 00:45:45 +08:00
pre-commit-ci[bot] 6b6bf9d8f8 💄 auto fix by pre-commit hooks 2024-09-26 11:41:12 +00:00
suyiiyii 54d37e254d 📝 尝试添加文档 2024-09-26 19:38:43 +08:00
suyiiyii 8fa456bc4e 📝 尝试添加文档 2024-09-26 19:37:06 +08:00
suyiiyii b04cbc2ced 导入导出 cookie 优化代码 2024-09-23 13:49:39 +08:00
suyiiyii 5d160dcadc 导入导出支持 cookie 2024-09-23 11:44:33 +08:00
suyiiyii 8742de6cd1 在 cookie 中添加 cookie_name 字段 2024-09-23 10:29:56 +08:00
suyiiyii 59d42531a3 听劝,修改部分样式 XD 2024-09-22 19:54:17 +08:00
suyiiyii cc31ef88ef 听劝,修改部分样式 XD 2024-09-22 19:49:22 +08:00
suyiiyii 0083f0311a 支持删除 CookieTarget 2024-09-22 17:59:01 +08:00
suyiiyii 6990f04a74 支持删除 CookieTarget 2024-09-22 17:22:14 +08:00
suyiiyii bc44e40f56 支持删除 CookieTarget 2024-09-22 17:12:52 +08:00
suyiiyii bb63529fe8 支持添加 CookieTarget 2024-09-22 17:02:10 +08:00
suyiiyii 75a55c009a 添加 CookieTarget 页面 2024-09-22 14:33:56 +08:00
suyiiyii 4c20e47399 添加 Cookie 增删查 功能 2024-09-22 13:52:51 +08:00
suyiiyii db4b848e3f 添加 Cookie 界面 2024-09-22 13:09:16 +08:00
suyiiyii 54c9020c36 添加cookie页面 2024-09-22 00:28:26 +08:00
74 changed files with 4974 additions and 1028 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 && yarn install",
"postCreateCommand": "poetry config virtualenvs.in-project true && poetry install -E all && poetry run pre-commit install && npm install -g pnpm && pnpm install",
"customizations": {
"vscode": {
"settings": {
+61
View File
@@ -0,0 +1,61 @@
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
@@ -1,31 +0,0 @@
---
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: |
yarn install
yarn docs:build
pnpm install
pnpm docs:build
+2 -2
View File
@@ -11,5 +11,5 @@ runs:
shell: bash
working-directory: ./admin-frontend
run: |
yarn install
yarn build
pnpm install
pnpm build
+9 -4
View File
@@ -8,11 +8,16 @@ runs:
with:
node-version: "20"
- id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- 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)"
shell: bash
- uses: actions/cache@v4
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
path: ${{ steps.pnpm-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
+10
View File
@@ -1,5 +1,15 @@
# Change Log
## 最近更新
### 新功能
- :sparkles: 更新默认UA为Windows平台 [@suyiiyii](https://github.com/suyiiyii) ([#643](https://github.com/MountainDash/nonebot-bison/pull/643))
### 文档
- 📝 小刻食堂剪彩文档 [@phidiaLam](https://github.com/phidiaLam) ([#636](https://github.com/MountainDash/nonebot-bison/pull/636))
## v0.9.4
### Bug 修复
+9
View File
@@ -9,6 +9,7 @@ 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();
@@ -46,6 +47,14 @@ function App() {
path: 'weight',
element: <WeightConfig />,
},
{
path: 'cookie',
element: <CookieManager />,
},
{
path: 'cookie/:siteName',
element: <CookieManager />,
},
],
},
], { basename: '/bison' });
+7 -1
View File
@@ -17,6 +17,7 @@ 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,
@@ -24,6 +25,8 @@ 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 = {
@@ -43,7 +46,10 @@ export const store = configureStore({
})
.concat(subscribeApi.middleware)
.concat(weightApi.middleware)
.concat(targetNameApi.middleware),
.concat(targetNameApi.middleware)
.concat(cookieApi.middleware)
.concat(cookieTargetApi.middleware),
});
export const persistor = persistStore(store);
@@ -0,0 +1,78 @@
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;
@@ -0,0 +1,128 @@
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;
@@ -0,0 +1,13 @@
.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);
}
@@ -0,0 +1,111 @@
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} />
</>
);
}
@@ -0,0 +1,66 @@
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;
@@ -0,0 +1,20 @@
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;
@@ -0,0 +1,109 @@
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,6 +6,7 @@ import { globalConfUrl } from '../../utils/urls';
const initialState = {
loaded: false,
platformConf: {},
siteConf: {},
} as GlobalConf;
export const loadGlobalConf = createAsyncThunk(
@@ -24,6 +25,7 @@ export const globalConfSlice = createSlice({
builder
.addCase(loadGlobalConf.fulfilled, (state, payload) => {
state.platformConf = payload.payload.platformConf;
state.siteConf = payload.payload.siteConf;
state.loaded = true;
});
},
@@ -33,3 +35,4 @@ 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;
+49 -3
View File
@@ -1,13 +1,15 @@
import React, { ReactNode, useEffect, useState } from 'react';
import { Breadcrumb, Layout, Menu } from '@arco-design/web-react';
import { IconRobot, IconDashboard } from '@arco-design/web-react/icon';
import {
IconRobot, IconDashboard, IconIdcard,
} 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();
@@ -23,6 +25,12 @@ 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 = '';
@@ -30,6 +38,8 @@ 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);
@@ -40,6 +50,10 @@ 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}`);
}
};
@@ -80,7 +94,22 @@ 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>
@@ -95,12 +124,29 @@ 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 />
+57
View File
@@ -4,8 +4,10 @@ export interface TokenResp {
id: number;
name: string;
}
export interface GlobalConf {
platformConf: AllPlatformConf;
siteConf: AllSiteConf;
loaded: boolean;
}
@@ -13,6 +15,10 @@ export interface AllPlatformConf {
[idx: string]: PlatformConfig;
}
export interface AllSiteConf {
[idx: string]: SiteConfig;
}
export interface CategoryConfig {
[idx: number]: string;
}
@@ -22,9 +28,15 @@ 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;
@@ -69,3 +81,48 @@ 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 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36
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_SHOW_NETWORK_WARNING=true
BISON_PLATFORM_THEME='{}'
+2 -1
View File
@@ -1,7 +1,8 @@
FROM node:20.17.0 as frontend
ADD . /app
WORKDIR /app/admin-frontend
RUN yarn && yarn build
RUN npm install -g pnpm
RUN pnpm install && pnpm build
FROM python:3.11
RUN python3 -m pip install poetry && poetry config virtualenvs.create false
+19 -1
View File
@@ -23,11 +23,29 @@ export default navbar([
link: "",
activeMatch: "^/usage/?$",
},
{
text: "Cookie 使用",
icon: "cookie",
link: "cookie",
},
],
},
{
text: "开发",
icon: "flask",
link: "/dev/",
prefix: "/dev/",
children: [
{
text: "基本开发",
icon: "tools",
link: "",
activeMatch: "^/dev/?$",
},
{
text: "Cookie 开发",
icon: "cookie",
link: "cookie",
},
],
},
]);
Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

+1 -3
View File
@@ -80,6 +80,7 @@ export default hopeTheme({
sup: true,
tabs: true,
vPre: true,
mermaid: true,
// 在启用之前安装 chart.js
// chart: true,
@@ -101,9 +102,6 @@ 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,3 +1,8 @@
---
prev: /usage/install
next: /dev/cookie
---
# 基本开发须知
## 语言以及工具
+168
View File
@@ -0,0 +1,168 @@
---
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 的功能就在这里实现
- `nonebot_bison.utils.site.CookieSite`: 添加了 Cookie 功能的 Site 类,根据需求添加了和 Site 强相关的 Cookie 功能实现方法
## 快速上手
例如,你现在有一个这样子的 Site 类:
```python
class WeiboSite(Site):
name = "weibo.com"
schedule_type = "interval"
schedule_setting = {"seconds": 3}
```
简而言之,要让你的站点获得 Cookie 能力,只需要:
1. 将父类从`Site`改为`CookieSite`
```python {1}
class WeiboSite(CookieSite):
name = "weibo.com"
schedule_type = "interval"
schedule_setting = {"seconds": 3}
```
2. 为你的 Site 类添加一个`client_mgr`属性,值为`create_cookie_client_manager(name)`,其中`name`为你的站点名称,这是默认的 Cookie 管理器。
```python {5}
class WeiboSite(CookieSite):
name = "weibo.com"
schedule_type = "interval"
schedule_setting = {"seconds": 3}
client_mgr = create_cookie_client_manager(name)
```
至此,你的站点就可以使用 Cookie 了!
## 更好的体验
为了给用户提供更好的体验,你还可以为你的 Site 重写`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 中的方式
CookieSite 的方法见上文
::: 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`方法,自定义请求结束后的行为
## 实名 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。
+16 -1
View File
@@ -1,6 +1,6 @@
---
prev: /usage/install
next: /usage/easy-use
next: /usage/cookie
---
# 全方位了解 Bison 的自行车
@@ -272,6 +272,21 @@ 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
@@ -0,0 +1,113 @@
---
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! 🎉
+1 -1
View File
@@ -61,7 +61,7 @@ def init_fastapi(driver: "Driver"):
def register_get_token_handler():
get_token = on_command("后台管理", rule=to_me(), priority=5, aliases={"管理后台"})
get_token = on_command("后台管理", rule=to_me(), priority=5, aliases={"管理后台"}, block=True)
@get_token.handle()
async def send_token(bot: "Bot", event: PrivateMessageEvent, state: T_State):
+14 -5
View File
@@ -12,6 +12,7 @@ from fastapi.security.oauth2 import OAuth2PasswordBearer
from ..types import WeightConfig
from ..apis import check_sub_target
from .jwt import load_jwt, pack_jwt
from ..scheduler import scheduler_dict
from ..types import Target as T_Target
from ..utils.get_bot import get_groups
from .token_manager import token_manager
@@ -66,7 +67,7 @@ async def get_global_conf() -> GlobalConf:
platformName=platform_name,
categories=platform.categories,
enabledTag=platform.enable_tag,
site_name=platform.site.name,
siteName=platform.site.name,
name=platform.name,
hasTarget=getattr(platform, "has_target"),
)
@@ -212,12 +213,11 @@ 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, 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,
friendly_name=friendly_names[i],
content=cookies_in_db[i].content,
cookie_name=cookies_in_db[i].cookie_name,
site_name=cookies_in_db[i].site_name,
last_usage=cookies_in_db[i].last_usage,
status=cookies_in_db[i].status,
@@ -232,7 +232,7 @@ async def get_cookie(site_name: str = None, target: str = None) -> list[Cookie]:
@router.post("/cookie", dependencies=[Depends(check_is_superuser)])
async def add_cookie(site_name: str, content: str) -> StatusResp:
client_mgr = cast(CookieClientManager, site_manager[site_name].client_mgr)
client_mgr = cast(CookieClientManager, scheduler_dict[site_manager[site_name]].client_mgr)
await client_mgr.add_user_cookie(content)
return StatusResp(ok=True, msg="")
@@ -268,3 +268,12 @@ async def add_cookie_target(platform_name: str, target: str, cookie_id: int) ->
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="")
+3 -2
View File
@@ -6,7 +6,7 @@ class PlatformConfig(BaseModel):
categories: dict[int, str]
enabledTag: bool
platformName: str
site_name: str
siteName: str
hasTarget: bool
@@ -75,7 +75,8 @@ class Target(BaseModel):
class Cookie(BaseModel):
id: int
site_name: str
friendly_name: str
content: str
cookie_name: str
last_usage: datetime
status: str
cd_milliseconds: int
+17
View File
@@ -285,6 +285,11 @@ 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))
return cookie
async def add_cookie(self, cookie: Cookie) -> int:
async with create_session() as sess:
sess.add(cookie)
@@ -298,6 +303,7 @@ 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
@@ -362,5 +368,16 @@ 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()
+2
View File
@@ -74,6 +74,8 @@ 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 当前的状态
@@ -1,8 +1,8 @@
"""empty message
Revision ID: ef796b74b0fe
Revision ID: f90b712557a9
Revises: f9baef347cc8
Create Date: 2024-09-13 00:34:08.601438
Create Date: 2024-09-23 10:03:30.593263
"""
@@ -12,7 +12,7 @@ from sqlalchemy import Text
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = "ef796b74b0fe"
revision = "f90b712557a9"
down_revision = "f9baef347cc8"
branch_labels = None
depends_on = None
@@ -25,6 +25,7 @@ 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),
@@ -1,6 +1,6 @@
"""nbesf is Nonebot Bison Enchangable Subscribes File!"""
from . import v1, v2
from . import v1, v2, v3
from .base import NBESFBase
__all__ = ["v1", "v2", "NBESFBase"]
__all__ = ["v1", "v2", "v3", "NBESFBase"]
+30 -1
View File
@@ -11,6 +11,7 @@ from nonebot.compat import PYDANTIC_V2, ConfigDict, model_dump, type_validate_js
from ..utils import NBESFParseErr
from ....types import Tag, Category
from .base import NBESFBase, SubReceipt
from ...db_model import Cookie as DBCookie
from ...db_config import SubscribeDupException, config
# ===== nbesf 定义格式 ====== #
@@ -48,6 +49,18 @@ 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给指定用户派送的快递包"""
@@ -58,19 +71,21 @@ class SubPack(BaseModel):
class SubGroup(NBESFBase):
"""
Bison的全部订单(按用户分组)
Bison的全部订单(按用户分组)和魔法饼干
结构参见`nbesf_model`下的对应版本
"""
version: int = NBESF_VERSION
groups: list[SubPack] = []
cookies: list[Cookie] = []
# ======================= #
async def subs_receipt_gen(nbesf_data: SubGroup):
logger.info("开始添加订阅流程")
for item in nbesf_data.groups:
sub_receipt = partial(SubReceipt, user=item.user_target)
@@ -92,6 +107,20 @@ async def subs_receipt_gen(nbesf_data: SubGroup):
logger.success(f"添加订阅条目 {repr(receipt)} 成功!")
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(target.target, target.platform_name, cookie_id)
except Exception as e:
logger.error(f"!添加 Cookie 条目 {repr(cookie)} 失败: {repr(e)}")
else:
logger.success(f"添加 Cookie 条目 {repr(cookie)} 成功!")
def nbesf_parser(raw_data: Any) -> SubGroup:
try:
if isinstance(raw_data, str):
+46 -9
View File
@@ -10,12 +10,13 @@ from nonebot.compat import type_validate_python
from nonebot_plugin_datastore.db import create_session
from sqlalchemy.orm.strategy_options import selectinload
from .utils import NBESFVerMatchErr
from ..db_model import User, Subscribe
from .nbesf_model import NBESFBase, v1, v2
from .. import config
from .utils import NBESFVerMatchErr, row2dict
from .nbesf_model import NBESFBase, v1, v2, v3
from ..db_model import User, Cookie, Target, Subscribe, CookieTarget
async def subscribes_export(selector: Callable[[Select], Select]) -> v2.SubGroup:
async def subscribes_export(selector: Callable[[Select], Select]) -> v3.SubGroup:
"""
将Bison订阅导出为 Nonebot Bison Exchangable Subscribes File 标准格式的 SubGroup 类型数据
@@ -34,22 +35,54 @@ async def subscribes_export(selector: Callable[[Select], Select]) -> v2.SubGroup
user_stmt = cast(Select[tuple[User]], user_stmt)
user_data = await sess.scalars(user_stmt)
groups: list[v2.SubPack] = []
user_id_sub_dict: dict[int, list[v2.SubPayload]] = defaultdict(list)
groups: list[v3.SubPack] = []
user_id_sub_dict: dict[int, list[v3.SubPayload]] = defaultdict(list)
for sub in sub_data:
sub_paylaod = type_validate_python(v2.SubPayload, sub)
sub_paylaod = type_validate_python(v3.SubPayload, sub)
user_id_sub_dict[sub.user_id].append(sub_paylaod)
for user in user_data:
assert isinstance(user, User)
sub_pack = v2.SubPack(
sub_pack = v3.SubPack(
user_target=PlatformTarget.deserialize(user.user_target),
subs=user_id_sub_dict[user.id],
)
groups.append(sub_pack)
sub_group = v2.SubGroup(groups=groups)
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: [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)
return sub_group
@@ -72,6 +105,10 @@ 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("订阅流程结束,请检查所有订阅记录是否全部添加成功")
+11
View File
@@ -1,4 +1,15 @@
from ..db_model import Model
class NBESFVerMatchErr(Exception): ...
class NBESFParseErr(Exception): ...
def row2dict(row: Model) -> dict:
d = {}
for column in row.__table__.columns:
d[column.name] = str(getattr(row, column.name))
return d
+33 -24
View File
@@ -1,13 +1,17 @@
import json
import random
from typing_extensions import override
from datetime import datetime, timedelta
from typing import TYPE_CHECKING, TypeVar
from httpx import AsyncClient
from nonebot import logger, require
from playwright.async_api import Cookie
from nonebot_bison.types import Target
from nonebot_bison.utils import Site, ClientManager, http_client
from nonebot_bison.utils import Site, http_client
from ...utils.site import CookieClientManager
from ...config.db_model import Cookie as CookieModel
if TYPE_CHECKING:
from .platforms import Bilibili
@@ -18,45 +22,50 @@ from nonebot_plugin_htmlrender import get_browser
B = TypeVar("B", bound="Bilibili")
class BilibiliClientManager(ClientManager):
_client: AsyncClient
_inited: bool = False
class BilibiliClientManager(CookieClientManager):
def __init__(self) -> None:
self._client = http_client()
_default_cookie_cd: int = timedelta(seconds=120)
@classmethod
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_load_state("load") # 等待基本加载完成
await page.wait_for_function('document.cookie.includes("bili_ticket")') # 期望保证 GenWebTicket 请求完成
await page.wait_for_load_state("networkidle") # 期望保证 ExClimbWuzhi 请求完成
cookies = await page.context.cookies()
return cookies
async def _reset_client_cookies(self, cookies: list[Cookie]):
@classmethod
def _gen_json_cookie(self, cookies: list[Cookie]):
cookie_dict = {}
for cookie in cookies:
self._client.cookies.set(
name=cookie.get("name", ""),
value=cookie.get("value", ""),
domain=cookie.get("domain", ""),
path=cookie.get("path", "/"),
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
@override
async def refresh_client(self):
cookies = await self._get_cookies()
await self._reset_client_cookies(cookies)
await self._refresh_anonymous_cookie()
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()
+3 -3
View File
@@ -9,15 +9,15 @@ from bs4 import BeautifulSoup as bs
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
from ..utils import text_similarity
from ..utils.site import Site, create_cookie_client_manager
class RssSite(Site):
name = "rss"
schedule_type = "interval"
schedule_setting = {"seconds": 30}
client_mgr = create_cookie_client_manager("rss")
client_mgr = create_cookie_client_manager(name)
class RssPost(Post):
+23 -4
View File
@@ -1,7 +1,7 @@
import re
import json
from typing import Any
from datetime import datetime
from typing import Any, override
from urllib.parse import unquote
from yarl import URL
@@ -12,8 +12,8 @@ from bs4 import BeautifulSoup as bs
from ..post import Post
from .platform import NewMessage
from ..utils import Site, http_client
from ..utils.site import create_cookie_client_manager
from ..utils import http_client, text_fletten
from ..utils.site import Site, CookieClientManager
from ..types import Tag, Target, RawPost, ApiError, Category
_HEADER = {
@@ -36,11 +36,30 @@ _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 = {"seconds": 3}
client_mgr = create_cookie_client_manager(name)
client_mgr = WeiboClientManager
class Weibo(NewMessage):
+2 -1
View File
@@ -36,7 +36,8 @@ class PlugConfig(BaseModel):
bison_resend_times: int = 0
bison_proxy: str | None = None
bison_ua: str = Field(
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
"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",
description="默认UA",
)
bison_show_network_warning: bool = True
+3 -3
View File
@@ -33,9 +33,6 @@ 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
@@ -47,6 +44,9 @@ 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)
+1 -1
View File
@@ -40,8 +40,8 @@ class Scheduler:
logger.error(f"scheduler config [{self.name}] not found, exiting")
raise RuntimeError(f"{self.name} not found")
self.scheduler_config = scheduler_config
self.client_mgr = scheduler_config.client_mgr()
self.scheduler_config_obj = self.scheduler_config()
self.client_mgr = scheduler_config.client_mgr()
self.schedulable_list = []
self.batch_platform_name_targets_cache = defaultdict(list)
+3 -1
View File
@@ -11,7 +11,7 @@ from nonebot.log import logger
from nonebot.compat import model_dump
from ..scheduler.manager import init_scheduler
from ..config.subs_io.nbesf_model import v1, v2
from ..config.subs_io.nbesf_model import v1, v2, v3
from ..config.subs_io import subscribes_export, subscribes_import
try:
@@ -151,6 +151,8 @@ 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版本")
+5 -1
View File
@@ -151,7 +151,11 @@ async def do_dispatch_command(
no_permission_matcher = on_command(
"添加订阅", rule=configurable_to_me, aliases={"删除订阅", "群管理"}, priority=8, block=True
"添加订阅",
rule=configurable_to_me,
aliases={"删除订阅", "群管理", "管理后台", "添加cookie", "删除cookie", "关联cookie", "取消关联cookie"},
priority=8,
block=True,
)
+23 -10
View File
@@ -1,20 +1,25 @@
from typing import cast
from json import JSONDecodeError
from nonebot.log import logger
from nonebot.typing import T_State
from nonebot.matcher import Matcher
from nonebot.params import Arg, ArgPlainText
from nonebot.adapters.onebot.v11 import MessageEvent
from nonebot.adapters import Message, MessageTemplate
from ..scheduler import scheduler_dict
from ..platform import platform_manager
from .utils import common_platform, gen_handle_cancel
from ..utils.site import CookieClientManager, is_cookie_client_manager
from .utils import common_platform, gen_handle_cancel, only_allow_private
def do_add_cookie(add_cookie: type[Matcher]):
handle_cancel = gen_handle_cancel(add_cookie, "已中止添加cookie")
@add_cookie.handle()
async def init_promote(state: T_State):
async def init_promote(state: T_State, event: MessageEvent):
await only_allow_private(event, add_cookie)
state["_prompt"] = (
"请输入想要添加 Cookie 的平台,目前支持,请输入冒号左边的名称:\n"
+ "".join(
@@ -42,7 +47,6 @@ def do_add_cookie(add_cookie: type[Matcher]):
await add_cookie.finish("已中止添加cookie")
elif platform in platform_manager:
state["platform"] = platform
state["site"] = platform_manager[platform].site
else:
await add_cookie.reject("平台输入错误")
@@ -52,18 +56,27 @@ def do_add_cookie(add_cookie: type[Matcher]):
@add_cookie.got("cookie", MessageTemplate("{_prompt}"), [handle_cancel])
async def got_cookie(state: T_State, cookie: Message = Arg()):
client_mgr: type[CookieClientManager] = cast(
type[CookieClientManager], platform_manager[state["platform"]].site.client_mgr
)
client_mgr = cast(CookieClientManager, scheduler_dict[platform_manager[state["platform"]].site].client_mgr)
cookie_text = cookie.extract_plain_text()
if not await client_mgr.validate_cookie(cookie_text):
await add_cookie.reject(state["site"].cookie_format_prompt)
await add_cookie.reject(
"无效的 Cookie,请检查后重新输入,详情见https://nonebot-bison.netlify.app/usage/cookie.html"
)
try:
cookie_name = await client_mgr.get_cookie_name(cookie_text)
state["cookie"] = cookie_text
state["cookie_name"] = cookie_name
except JSONDecodeError as e:
logger.error("获取 Cookie 名称失败" + str(e))
await add_cookie.reject(
"获取 Cookie 名称失败,请检查后重新输入,详情见https://nonebot-bison.netlify.app/usage/cookie.html"
)
@add_cookie.handle()
async def add_cookie_process(state: T_State):
client_mgr = cast(CookieClientManager, platform_manager[state["platform"]].site.client_mgr)
await client_mgr.add_user_cookie(state["cookie"])
client_mgr = cast(CookieClientManager, scheduler_dict[platform_manager[state["platform"]].site].client_mgr)
new_cookie = await client_mgr.add_user_cookie(state["cookie"], state["cookie_name"])
await add_cookie.finish(
f"已添加 Cookie: {state['cookie']} 到平台 {state['platform']}" + "\n请使用“关联cookie”为 Cookie 关联订阅"
f"已添加 Cookie: {new_cookie.cookie_name} 到平台 {state['platform']}"
+ "\n请使用“关联cookie”为 Cookie 关联订阅"
)
+6 -10
View File
@@ -1,23 +1,22 @@
from typing import cast
from nonebot.typing import T_State
from nonebot.matcher import Matcher
from nonebot.params import ArgPlainText
from nonebot_plugin_saa import MessageFactory
from nonebot.adapters.onebot.v11 import MessageEvent
from nonebot.internal.adapter import MessageTemplate
from ..config import config
from ..utils import parse_text
from ..platform import platform_manager
from ..utils.site import CookieClientManager
from .utils import gen_handle_cancel, generate_sub_list_text
from .utils import gen_handle_cancel, only_allow_private, generate_sub_list_text
def do_add_cookie_target(add_cookie_target_matcher: type[Matcher]):
handle_cancel = gen_handle_cancel(add_cookie_target_matcher, "已中止关联 cookie")
@add_cookie_target_matcher.handle()
async def init_promote(state: T_State):
async def init_promote(state: T_State, event: MessageEvent):
await only_allow_private(event, add_cookie_target_matcher)
res = await generate_sub_list_text(
add_cookie_target_matcher, state, is_index=True, is_show_cookie=True, is_hide_no_cookie_platfrom=True
)
@@ -51,9 +50,8 @@ def do_add_cookie_target(add_cookie_target_matcher: type[Matcher]):
)
state["cookies"] = cookies
client_mgr = cast(CookieClientManager, state["site"].client_mgr)
state["_prompt"] = "请选择一个 Cookie,已关联的 Cookie 不会显示\n" + "\n".join(
[f"{idx}. {await client_mgr.get_cookie_friendly_name(cookie)}" for idx, cookie in enumerate(cookies, 1)]
[f"{idx}. {cookie.cookie_name}" for idx, cookie in enumerate(cookies, 1)]
)
@add_cookie_target_matcher.got("cookie_idx", MessageTemplate("{_prompt}"), [handle_cancel])
@@ -68,8 +66,6 @@ def do_add_cookie_target(add_cookie_target_matcher: type[Matcher]):
async def add_cookie_target_process(state: T_State):
await config.add_cookie_target(state["target"]["target"], state["target"]["platform_name"], state["cookie"].id)
cookie = state["cookie"]
client_mgr = cast(CookieClientManager, state["site"].client_mgr)
await add_cookie_target_matcher.finish(
f"已关联 Cookie: {await client_mgr.get_cookie_friendly_name(cookie)} "
f"到订阅 {state['site'].name} {state['target']['target']}"
f"已关联 Cookie: {cookie.cookie_name} " f"到订阅 {state['site'].name} {state['target']['target']}"
)
+5 -6
View File
@@ -2,18 +2,19 @@ from nonebot.typing import T_State
from nonebot.matcher import Matcher
from nonebot.params import EventPlainText
from nonebot_plugin_saa import MessageFactory
from nonebot.adapters.onebot.v11 import MessageEvent
from ..config import config
from ..utils import parse_text
from ..platform import site_manager
from .utils import gen_handle_cancel
from .utils import gen_handle_cancel, only_allow_private
def do_del_cookie(del_cookie: type[Matcher]):
handle_cancel = gen_handle_cancel(del_cookie, "删除中止")
@del_cookie.handle()
async def send_list(state: T_State):
async def send_list(state: T_State, event: MessageEvent):
await only_allow_private(event, del_cookie)
cookies = await config.get_cookie(is_anonymous=False)
if not cookies:
await del_cookie.finish("暂无已添加的 Cookie\n请使用“添加cookie”命令添加")
@@ -21,9 +22,7 @@ def do_del_cookie(del_cookie: type[Matcher]):
state["cookie_table"] = {}
for index, cookie in enumerate(cookies, 1):
state["cookie_table"][index] = cookie
client_mgr = site_manager[cookie.site_name].client_mgr
friendly_name = await client_mgr.get_cookie_friendly_name(cookie)
res += f"{index} {cookie.site_name} {friendly_name} {len(cookie.targets)}个关联\n"
res += f"{index} {cookie.site_name} {cookie.cookie_name} {len(cookie.targets)}个关联\n"
if res[-1] != "\n":
res += "\n"
res += "请输入要删除的 Cookie 的序号\n输入'取消'中止"
@@ -1,30 +1,27 @@
from typing import cast
from nonebot.typing import T_State
from nonebot.matcher import Matcher
from nonebot.params import EventPlainText
from nonebot_plugin_saa import MessageFactory
from nonebot.adapters.onebot.v11 import MessageEvent
from ..config import config
from ..utils import parse_text
from .utils import gen_handle_cancel
from ..platform import platform_manager
from ..utils.site import CookieClientManager
from .utils import gen_handle_cancel, only_allow_private
def do_del_cookie_target(del_cookie_target: type[Matcher]):
handle_cancel = gen_handle_cancel(del_cookie_target, "取消关联中止")
@del_cookie_target.handle()
async def send_list(state: T_State):
async def send_list(state: T_State, event: MessageEvent):
await only_allow_private(event, del_cookie_target)
cookie_targets = await config.get_cookie_target()
if not cookie_targets:
await del_cookie_target.finish("暂无已关联 Cookie\n请使用“添加cookie”命令添加关联")
res = "已关联的 Cookie 为:\n"
state["cookie_target_table"] = {}
for index, cookie_target in enumerate(cookie_targets, 1):
client_mgr = cast(CookieClientManager, platform_manager[cookie_target.target.platform_name].site.client_mgr)
friendly_name = await client_mgr.get_cookie_friendly_name(cookie_target.cookie)
friendly_name = cookie_target.cookie.cookie_name
state["cookie_target_table"][index] = {
"platform_name": cookie_target.target.platform_name,
"target": cookie_target.target,
+13 -5
View File
@@ -1,7 +1,7 @@
import contextlib
from typing import Annotated
from itertools import groupby
from operator import attrgetter
from typing import Annotated, cast
from nonebot.rule import Rule
from nonebot.adapters import Event
@@ -13,9 +13,9 @@ from nonebot_plugin_saa import PlatformTarget, extract_target
from ..config import config
from ..types import Category
from ..platform import platform_manager
from ..plugin_config import plugin_config
from ..platform import site_manager, platform_manager
from ..utils.site import CookieClientManager, is_cookie_client_manager
from ..utils.site import is_cookie_client_manager
def _configurable_to_me(to_me: bool = EventToMe()):
@@ -114,10 +114,18 @@ async def generate_sub_list_text(
if target_cookies:
res += " 关联的 Cookie\n"
for cookie in target_cookies:
client_mgr = cast(CookieClientManager, site_manager[platform.site.name].client_mgr)
res += f" \t{await client_mgr.get_cookie_friendly_name(cookie)}\n"
res += f" \t{cookie.cookie_name}\n"
else:
res += f" (平台 {sub.target.platform_name} 已失效,请删除此订阅)"
return res
async def only_allow_private(
event: Event,
matcher: type[Matcher],
):
# if not issubclass(PrivateMessageEvent, event.__class__):
if event.message_type != "private":
await matcher.finish("请在私聊中使用此命令")
+10 -10
View File
@@ -1,25 +1,25 @@
import difflib
import re
import sys
import difflib
import nonebot
from nonebot.plugin import require
from bs4 import BeautifulSoup as bs
from nonebot.log import logger, default_format
from nonebot.plugin import require
from nonebot_plugin_saa import Text, Image, MessageSegmentFactory
from .context import ProcessContext as ProcessContext
from .site import Site as Site
from ..plugin_config import plugin_config
from .image import pic_merge as pic_merge
from .http import http_client as http_client
from .image import capture_html as capture_html
from .image import is_pics_mergable as is_pics_mergable
from .image import pic_merge as pic_merge
from .image import pic_url_to_image as pic_url_to_image
from .image import text_to_image as text_to_image
from .site import ClientManager as ClientManager
from .site import DefaultClientManager as DefaultClientManager
from .site import Site as Site
from .image import text_to_image as text_to_image
from .site import anonymous_site as anonymous_site
from ..plugin_config import plugin_config
from .context import ProcessContext as ProcessContext
from .image import is_pics_mergable as is_pics_mergable
from .image import pic_url_to_image as pic_url_to_image
from .site import DefaultClientManager as DefaultClientManager
class Singleton(type):
+52 -39
View File
@@ -43,31 +43,52 @@ class DefaultClientManager(ClientManager):
class CookieClientManager(ClientManager):
_site_name: str
_default_cd: int = timedelta(seconds=10)
_default_cookie_cd: int = timedelta(seconds=15)
_site_name: str = ""
@classmethod
async def refresh_anonymous_cookie(cls):
"""移除已有的匿名cookie,添加一个新的匿名cookie"""
anonymous_cookies = await config.get_cookie(cls._site_name, is_anonymous=True)
anonymous_cookie = Cookie(site_name=cls._site_name, content="{}", is_universal=True, is_anonymous=True)
for cookie in anonymous_cookies:
if not cookie.is_anonymous:
continue
await config.delete_cookie_by_id(cookie.id)
anonymous_cookie.id = cookie.id # 保持原有的id
anonymous_cookie.last_usage = datetime.now() # 使得第一次请求优先使用用户 cookie
await config.add_cookie(anonymous_cookie)
async def _generate_anonymous_cookie(self) -> Cookie:
return Cookie(
cookie_name=f"{self._site_name} anonymous",
site_name=self._site_name,
content="{}",
is_universal=True,
is_anonymous=True,
last_usage=datetime.now(),
cd_milliseconds=0,
tags="{}",
status="",
)
@classmethod
async def add_user_cookie(cls, content: str):
async def _refresh_anonymous_cookie(self):
"""更新已有的匿名cookie,若不存在则添加"""
existing_anonymous_cookies = await config.get_cookie(self._site_name, is_anonymous=True)
if existing_anonymous_cookies:
for cookie in existing_anonymous_cookies:
new_anonymous_cookie = await self._generate_anonymous_cookie()
new_anonymous_cookie.id = cookie.id # 保持原有的id
await config.update_cookie(new_anonymous_cookie)
else:
new_anonymous_cookie = await self._generate_anonymous_cookie()
await config.add_cookie(new_anonymous_cookie)
async def add_user_cookie(self, content: str, cookie_name: str | None = None) -> Cookie:
"""添加用户 cookie"""
cookie = Cookie(site_name=cls._site_name, content=content)
cookie.cd = cls._default_cd
await config.add_cookie(cookie)
@classmethod
async def validate_cookie(cls, content: str) -> bool:
if not await self.validate_cookie(content):
raise ValueError()
cookie = Cookie(site_name=self._site_name, content=content)
cookie.cookie_name = cookie_name if cookie_name else await self.get_cookie_name(content)
cookie.cd = self._default_cookie_cd
cookie_id = await config.add_cookie(cookie)
return await config.get_cookie_by_id(cookie_id)
async def get_cookie_name(self, content: str) -> str:
"""从cookie内容中获取cookie的友好名字,添加cookie时调用,持久化在数据库中"""
from . import text_fletten
return text_fletten(f"{self._site_name} [{content[:10]}]")
async def validate_cookie(self, content: str) -> bool:
"""验证 cookie 内容是否有效,添加 cookie 时用,可根据平台的具体情况进行重写"""
try:
data = json.loads(content)
@@ -77,13 +98,6 @@ class CookieClientManager(ClientManager):
return False
return True
@classmethod
async def get_cookie_friendly_name(cls, cookie: Cookie) -> str:
"""获取 cookie 的友好名字,用于展示"""
from . import text_fletten
return text_fletten(f"{cookie.site_name} [{cookie.content[:10]}]")
def _generate_hook(self, cookie: Cookie) -> callable:
"""hook 函数生成器,用于回写请求状态到数据库"""
@@ -133,22 +147,13 @@ class CookieClientManager(ClientManager):
return http_client()
async def refresh_client(self):
pass
await self._refresh_anonymous_cookie()
def is_cookie_client_manager(manger: type[ClientManager]) -> bool:
return issubclass(manger, CookieClientManager)
def create_cookie_client_manager(site_name: str) -> type[CookieClientManager]:
"""创建一个平台特化的 CookieClientManger"""
return type(
"CookieClientManager",
(CookieClientManager,),
{"_site_name": site_name},
)
class Site(metaclass=RegistryMeta, base=True):
schedule_type: Literal["date", "interval", "cron"]
schedule_setting: dict
@@ -156,12 +161,20 @@ class Site(metaclass=RegistryMeta, base=True):
client_mgr: type[ClientManager] = DefaultClientManager
require_browser: bool = False
registry: list[type["Site"]]
cookie_format_prompt = "无效的 Cookie,请检查后重新输入,详情见<待添加的文档>"
def __str__(self):
return f"[{self.name}]-{self.name}-{self.schedule_setting}"
def create_cookie_client_manager(site_name: str) -> type[CookieClientManager]:
"""创建一个平台特化的 CookieClientManger"""
return type(
"CookieClientManager",
(CookieClientManager,),
{"_site_name": site_name},
)
def anonymous_site(schedule_type: Literal["date", "interval", "cron"], schedule_setting: dict) -> type[Site]:
return type(
"AnonymousSite",
+6 -3
View File
@@ -11,9 +11,12 @@
"docs:update-package": "pnpm dlx vp-update"
},
"devDependencies": {
"@vuepress/bundler-vite": "2.0.0-rc.15",
"@vuepress/bundler-vite": "2.0.0-rc.17",
"mermaid": "^11.3.0",
"sass-embedded": "^1.79.5",
"vue": "^3.5.6",
"vuepress": "2.0.0-rc.15",
"vuepress-theme-hope": "2.0.0-rc.52"
"vuepress": "2.0.0-rc.17",
"vuepress-plugin-md-enhance": "2.0.0-rc.57",
"vuepress-theme-hope": "2.0.0-rc.58"
}
}
+2942 -730
View File
File diff suppressed because it is too large Load Diff
Generated
+4 -4
View File
@@ -3149,13 +3149,13 @@ reference = "offical-source"
[[package]]
name = "pre-commit"
version = "3.8.0"
version = "4.0.1"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
optional = false
python-versions = ">=3.9"
files = [
{file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"},
{file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"},
{file = "pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878"},
{file = "pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2"},
]
[package.dependencies]
@@ -5129,4 +5129,4 @@ yaml = []
[metadata]
lock-version = "2.0"
python-versions = ">=3.10,<4.0.0"
content-hash = "02cf0461830f2012ad533f01d152e7b4abb9e8bd94158956a73369f4f91c96b6"
content-hash = "49633514954cd83e3973b4539baa86ca5518d8582bfdb08df85247e5dcabc70f"
+1 -1
View File
@@ -49,7 +49,7 @@ ipdb = "^0.13.13"
isort = "^5.13.2"
nonemoji = "^0.1.4"
nb-cli = "^1.4.2"
pre-commit = "^3.8.0"
pre-commit = "^4.0.1"
ruff = "^0.6.5"
[tool.poetry.group.test.dependencies]
+5 -3
View File
@@ -6,11 +6,13 @@ import pytest
from nonebug import App
@pytest.mark.usefixtures("_patch_weibo_get_cookie_name")
async def test_cookie(app: App, init_scheduler):
from nonebot_plugin_saa import TargetQQGroup
from nonebot_bison.platform import site_manager
from nonebot_bison.config.db_config import config
from nonebot_bison.scheduler import scheduler_dict
from nonebot_bison.types import Target as T_Target
from nonebot_bison.utils.site import CookieClientManager
from nonebot_bison.config.utils import DuplicateCookieTargetException
@@ -26,14 +28,13 @@ async def test_cookie(app: App, init_scheduler):
tags=[],
)
site = site_manager["weibo.com"]
client_mgr = cast(CookieClientManager, site.client_mgr)
client_mgr = cast(CookieClientManager, scheduler_dict[site].client_mgr)
# 刷新匿名cookie
await client_mgr.refresh_anonymous_cookie()
await client_mgr.refresh_client()
cookies = await config.get_cookie(site_name=site.name)
assert len(cookies) == 1
# 添加用户cookie
await client_mgr.add_user_cookie(json.dumps({"test_cookie": "1"}))
await client_mgr.add_user_cookie(json.dumps({"test_cookie": "2"}))
@@ -67,6 +68,7 @@ async def test_cookie(app: App, init_scheduler):
cats=[],
tags=[],
)
await client_mgr.add_user_cookie(json.dumps({"test_cookie": "3"}))
cookies = await config.get_cookie(site_name=site.name, is_anonymous=False)
+33
View File
@@ -29,6 +29,16 @@ def load_adapters(nonebug_init: None):
return driver
def patch_refresh_bilibili_anonymous_cookie(mocker: MockerFixture):
# patch 掉bilibili的匿名cookie生成函数,避免真实请求
from nonebot_bison.platform.bilibili.scheduler import BilibiliClientManager
mocker.patch.object(
BilibiliClientManager, "_get_cookies", return_value=[{"name": "test anonymous", "content": "test"}]
)
@pytest.fixture
async def app(tmp_path: Path, request: pytest.FixtureRequest, mocker: MockerFixture):
sys.path.append(str(Path(__file__).parent.parent / "src" / "plugins"))
@@ -51,6 +61,10 @@ async def app(tmp_path: Path, request: pytest.FixtureRequest, mocker: MockerFixt
param: AppReq = getattr(request, "param", AppReq())
# 如果在 app 前调用会报错“无法找到调用者”
# 而在后面调用又来不及mock,所以只能在中间mock
patch_refresh_bilibili_anonymous_cookie(mocker)
if not param.get("no_init_db"):
await init_db()
# if not param.get("refresh_bot"):
@@ -123,3 +137,22 @@ async def _no_browser(app: App, mocker: MockerFixture):
mocker.patch.object(plugin_config, "bison_use_browser", False)
mocker.patch("nonebot_bison.platform.unavailable_paltforms", _get_unavailable_platforms())
@pytest.fixture
async def _clear_db(app: App):
from nonebot_bison.config import config
await config.clear_db()
yield
await config.clear_db()
return
@pytest.fixture
def _patch_weibo_get_cookie_name(app: App, mocker: MockerFixture):
from nonebot_bison.platform import weibo
mocker.patch.object(weibo.WeiboClientManager, "_get_current_user_name", return_value="test_name")
yield
mocker.stopall()
+138
View File
@@ -0,0 +1,138 @@
{
"ok": 1,
"data": {
"config": {
"code": 11000,
"text": "<p>非微博会员不可多次修改昵称。自2024年1月1日至今,您已成功修改1次,目前无法继续修改。如需继续改名可开通微博会员。</p>",
"guide": {
"title": "开通微博会员",
"desc": "<p>本年度<span>增加最多8次</span>改名机会</p>",
"button_text": "开通会员",
"button_url": "https://m.weibo.cn/c/upgrade"
}
},
"data": {
"config": {
"title": "修改昵称次数扣除明细"
}
},
"user": {
"id": 114514,
"idstr": "114514",
"class": 1,
"screen_name": "suyiiyii",
"name": "suyiiyii",
"province": "44",
"city": "1",
"location": "广东 广州",
"description": "",
"url": "",
"profile_image_url": "https://tvax1.sinaimg.cn/default/images/default_avatar_male_50.gif?KID=imgbed,tva&Expires=1728833531&ssig=jURhYal3%2BR",
"light_ring": false,
"profile_url": "u/114514",
"domain": "",
"weihao": "",
"gender": "m",
"followers_count": 1,
"followers_count_str": "1",
"friends_count": 6,
"pagefriends_count": 0,
"statuses_count": 1,
"video_status_count": 0,
"video_play_count": 0,
"super_topic_not_syn_count": 0,
"favourites_count": 0,
"created_at": "Tue Sep 03 00:07:59 +0800 2024",
"following": false,
"allow_all_act_msg": false,
"geo_enabled": true,
"verified": false,
"verified_type": -1,
"remark": "",
"insecurity": {
"sexual_content": false
},
"ptype": 0,
"allow_all_comment": true,
"avatar_large": "https://tvax1.sinaimg.cn/default/images/default_avatar_male_180.gif?KID=imgbed,tva&Expires=1728833531&ssig=cornnikInk",
"avatar_hd": "https://tvax1.sinaimg.cn/default/images/default_avatar_male_180.gif?KID=imgbed,tva&Expires=1728833531&ssig=cornnikInk",
"verified_reason": "",
"verified_trade": "",
"verified_reason_url": "",
"verified_source": "",
"verified_source_url": "",
"follow_me": false,
"like": false,
"like_me": false,
"online_status": 0,
"bi_followers_count": 0,
"lang": "zh-cn",
"star": 0,
"mbtype": 0,
"mbrank": 0,
"svip": 0,
"vvip": 0,
"mb_expire_time": 0,
"block_word": 0,
"block_app": 0,
"chaohua_ability": 0,
"brand_ability": 0,
"nft_ability": 0,
"vplus_ability": 0,
"wenda_ability": 0,
"live_ability": 0,
"gongyi_ability": 0,
"paycolumn_ability": 0,
"newbrand_ability": 0,
"ecommerce_ability": 0,
"hardfan_ability": 0,
"wbcolumn_ability": 0,
"interaction_user": 0,
"audio_ability": 0,
"place_ability": 0,
"credit_score": 80,
"user_ability": 0,
"urank": 0,
"story_read_state": -1,
"vclub_member": 0,
"is_teenager": 0,
"is_guardian": 0,
"is_teenager_list": 0,
"pc_new": 0,
"special_follow": false,
"planet_video": 0,
"video_mark": 0,
"live_status": 0,
"user_ability_extend": 0,
"status_total_counter": {
"total_cnt": 0,
"repost_cnt": 0,
"comment_cnt": 0,
"like_cnt": 0,
"comment_like_cnt": 0
},
"video_total_counter": {
"play_cnt": -1
},
"brand_account": 0,
"hongbaofei": 0,
"green_mode": 0,
"urisk": 524288,
"unfollowing_recom_switch": 1,
"block": 0,
"block_me": 0,
"avatar_type": 0,
"is_big": 0,
"auth_status": 1,
"auth_realname": null,
"auth_career": null,
"auth_career_name": null,
"show_auth": 0,
"is_auth": 0,
"is_punish": 0,
"like_display": 0
},
"submit": true,
"having_count": 0
}
}
+11
View File
@@ -217,3 +217,14 @@ async def test_parse_target(weibo: "Weibo"):
assert res == "6441489862"
with pytest.raises(Platform.ParseTargetException):
await weibo.parse_target("https://weibo.com/arknights")
@respx.mock
async def test_get_cookie_name(weibo: "Weibo"):
from nonebot_bison.platform.weibo import WeiboClientManager
router = respx.get("https://m.weibo.cn/setup/nick/detail")
router.mock(return_value=Response(200, json=get_json("weibo_get-cookie-name.json")))
weibo_client_mgr = WeiboClientManager()
name = await weibo_client_mgr.get_cookie_name("{}")
assert name == "weibo: [suyiiyii]"
+4
View File
@@ -5,6 +5,8 @@ from unittest.mock import AsyncMock
from nonebug import App
from pytest_mock import MockerFixture
from tests.conftest import patch_refresh_bilibili_anonymous_cookie
if typing.TYPE_CHECKING:
from nonebot_bison.utils import Site
@@ -199,6 +201,7 @@ async def test_scheduler_skip_browser(mocker: MockerFixture):
site = MockSite
mocker.patch.dict(platform_manager, {"mock_platform": MockPlatform})
patch_refresh_bilibili_anonymous_cookie(mocker)
await init_scheduler()
@@ -229,6 +232,7 @@ async def test_scheduler_no_skip_not_require_browser(mocker: MockerFixture):
site = MockSite
mocker.patch.dict(platform_manager, {"mock_platform": MockPlatform})
patch_refresh_bilibili_anonymous_cookie(mocker)
await init_scheduler()
+15 -8
View File
@@ -1,5 +1,6 @@
import json
import pytest
from nonebug.app import App
from pytest_mock import MockerFixture
@@ -30,7 +31,8 @@ async def test_add_cookie_rule(app: App, mocker: MockerFixture):
ctx.should_pass_permission()
async def test_add_cookie_target_no_cookie(app: App, mocker: MockerFixture):
@pytest.mark.usefixtures("_clear_db")
async def test_add_cookie_target_no_cookie(app: App):
from nonebot.adapters.onebot.v11.bot import Bot
from nonebot.adapters.onebot.v11.message import Message
@@ -80,7 +82,9 @@ async def test_add_cookie_target_no_cookie(app: App, mocker: MockerFixture):
)
async def test_add_cookie(app: App, mocker: MockerFixture):
@pytest.mark.usefixtures("_clear_db")
@pytest.mark.usefixtures("_patch_weibo_get_cookie_name")
async def test_add_cookie(app: App):
from nonebot.adapters.onebot.v11.bot import Bot
from nonebot.adapters.onebot.v11.message import Message
@@ -90,7 +94,7 @@ async def test_add_cookie(app: App, mocker: MockerFixture):
async with app.test_matcher(add_cookie_matcher) as ctx:
bot = ctx.create_bot(base=Bot)
event_1 = fake_private_message_event(
message=Message("添加Cookie"), sender=fake_superuser, to_me=True, user_id=fake_superuser.user_id
message=Message("添加cookie"), sender=fake_superuser, to_me=True, user_id=fake_superuser.user_id
)
ctx.receive_event(bot, event_1)
ctx.should_pass_rule()
@@ -120,7 +124,11 @@ async def test_add_cookie(app: App, mocker: MockerFixture):
message=Message("test"), sender=fake_superuser, to_me=True, user_id=fake_superuser.user_id
)
ctx.receive_event(bot, event_4_err)
ctx.should_call_send(event_4_err, "无效的 Cookie,请检查后重新输入,详情见<待添加的文档>", True)
ctx.should_call_send(
event_4_err,
"无效的 Cookie,请检查后重新输入,详情见https://nonebot-bison.netlify.app/usage/cookie.html",
True,
)
ctx.should_rejected()
event_4_ok = fake_private_message_event(
message=Message(json.dumps({"cookie": "test"})),
@@ -131,7 +139,7 @@ async def test_add_cookie(app: App, mocker: MockerFixture):
ctx.receive_event(bot, event_4_ok)
ctx.should_pass_rule()
ctx.should_call_send(
event_4_ok, '已添加 Cookie: {"cookie": "test"} 到平台 weibo\n请使用“关联cookie”为 Cookie 关联订阅', True
event_4_ok, "已添加 Cookie: weibo: [test_name] 到平台 weibo\n请使用“关联cookie”为 Cookie 关联订阅", True
)
async with app.test_matcher(add_cookie_target_matcher) as ctx:
@@ -176,7 +184,7 @@ async def test_add_cookie(app: App, mocker: MockerFixture):
)
ctx.receive_event(bot, event_2_ok)
ctx.should_pass_rule()
ctx.should_call_send(event_2_ok, '请选择一个 Cookie,已关联的 Cookie 不会显示\n1. weibo.com [{"cookie":]', True)
ctx.should_call_send(event_2_ok, "请选择一个 Cookie,已关联的 Cookie 不会显示\n1. weibo: [test_name]", True)
event_3_err = fake_private_message_event(
message=Message("2"), sender=fake_superuser, to_me=True, user_id=fake_superuser.user_id
)
@@ -188,11 +196,10 @@ async def test_add_cookie(app: App, mocker: MockerFixture):
)
ctx.receive_event(bot, event_3_ok)
ctx.should_pass_rule()
ctx.should_call_send(event_3_ok, '已关联 Cookie: weibo.com [{"cookie":] 到订阅 weibo.com weibo_id', True)
ctx.should_call_send(event_3_ok, "已关联 Cookie: weibo: [test_name] 到订阅 weibo.com weibo_id", True)
async def test_add_cookie_target_no_target(app: App, mocker: MockerFixture):
from nonebot.adapters.onebot.v11.bot import Bot
from nonebot.adapters.onebot.v11.message import Message
+64 -61
View File
@@ -1,10 +1,73 @@
import json
import pytest
from nonebug.app import App
from ..utils import fake_superuser, fake_private_message_event
@pytest.mark.usefixtures("_clear_db")
async def test_del_cookie(app: App):
from nonebug_saa import should_send_saa
from nonebot.adapters.onebot.v11.bot import Bot
from nonebot.adapters.onebot.v11.message import Message
from nonebot_plugin_saa import TargetQQGroup, MessageFactory
from nonebot_bison.config import config
from nonebot_bison.config.db_model import Cookie
from nonebot_bison.types import Target as T_Target
from nonebot_bison.sub_manager import del_cookie_matcher
async with app.test_matcher(del_cookie_matcher) as ctx:
bot = ctx.create_bot(base=Bot)
event = fake_private_message_event(
message=Message("删除cookie"), sender=fake_superuser, to_me=True, user_id=fake_superuser.user_id
)
ctx.receive_event(bot, event)
ctx.should_pass_rule()
ctx.should_pass_permission()
ctx.should_call_send(event, "暂无已添加的 Cookie\n请使用“添加cookie”命令添加", True)
async with app.test_matcher(del_cookie_matcher) as ctx:
bot = ctx.create_bot(base=Bot)
target = T_Target("weibo_id")
platform_name = "weibo"
await config.add_subscribe(
TargetQQGroup(group_id=123),
target=target,
target_name="weibo_name",
platform_name=platform_name,
cats=[],
tags=[],
)
await config.add_cookie(Cookie(content=json.dumps({"cookie": "test"}), site_name="weibo.com"))
event_1 = fake_private_message_event(
message=Message("删除cookie"), sender=fake_superuser, to_me=True, user_id=fake_superuser.user_id
)
ctx.receive_event(bot, event_1)
ctx.should_pass_rule()
ctx.should_pass_permission()
should_send_saa(
ctx,
MessageFactory(
"已添加的 Cookie 为:\n1 weibo.com unnamed cookie"
" 0个关联\n请输入要删除的 Cookie 的序号\n输入'取消'中止"
),
bot,
event=event_1,
)
event_2 = fake_private_message_event(
message=Message("1"), sender=fake_superuser, to_me=True, user_id=fake_superuser.user_id
)
ctx.receive_event(bot, event_2)
ctx.should_pass_rule()
ctx.should_pass_permission()
ctx.should_call_send(event_2, "删除成功", True)
@pytest.mark.usefixtures("_clear_db")
@pytest.mark.usefixtures("_patch_weibo_get_cookie_name")
async def test_del_cookie_err(app: App):
from nonebug_saa import should_send_saa
from nonebot.adapters.onebot.v11.bot import Bot
@@ -51,8 +114,7 @@ async def test_del_cookie_err(app: App):
should_send_saa(
ctx,
MessageFactory(
'已添加的 Cookie 为:\n1 weibo.com weibo.com [{"cookie":] '
"1个关联\n请输入要删除的 Cookie 的序号\n输入'取消'中止"
"已添加的 Cookie 为:\n1 weibo.com unnamed cookie 1个关联\n请输入要删除的 Cookie 的序号\n输入'取消'中止"
),
bot,
event=event_1,
@@ -72,62 +134,3 @@ async def test_del_cookie_err(app: App):
ctx.should_call_send(event_2, "只能删除未关联的 Cookie,请使用“取消关联cookie”命令取消关联", True)
ctx.should_call_send(event_2, "删除错误", True)
ctx.should_rejected()
async def test_del_cookie(app: App):
from nonebug_saa import should_send_saa
from nonebot.adapters.onebot.v11.bot import Bot
from nonebot.adapters.onebot.v11.message import Message
from nonebot_plugin_saa import TargetQQGroup, MessageFactory
from nonebot_bison.config import config
from nonebot_bison.config.db_model import Cookie
from nonebot_bison.types import Target as T_Target
from nonebot_bison.sub_manager import del_cookie_matcher
async with app.test_matcher(del_cookie_matcher) as ctx:
bot = ctx.create_bot(base=Bot)
event = fake_private_message_event(
message=Message("删除cookie"), sender=fake_superuser, to_me=True, user_id=fake_superuser.user_id
)
ctx.receive_event(bot, event)
ctx.should_pass_rule()
ctx.should_pass_permission()
ctx.should_call_send(event, "暂无已添加的 Cookie\n请使用“添加cookie”命令添加", True)
async with app.test_matcher(del_cookie_matcher) as ctx:
bot = ctx.create_bot(base=Bot)
target = T_Target("weibo_id")
platform_name = "weibo"
await config.add_subscribe(
TargetQQGroup(group_id=123),
target=target,
target_name="weibo_name",
platform_name=platform_name,
cats=[],
tags=[],
)
await config.add_cookie(Cookie(content=json.dumps({"cookie": "test"}), site_name="weibo.com"))
event_1 = fake_private_message_event(
message=Message("删除cookie"), sender=fake_superuser, to_me=True, user_id=fake_superuser.user_id
)
ctx.receive_event(bot, event_1)
ctx.should_pass_rule()
ctx.should_pass_permission()
should_send_saa(
ctx,
MessageFactory(
'已添加的 Cookie 为:\n1 weibo.com weibo.com [{"cookie":]'
" 0个关联\n请输入要删除的 Cookie 的序号\n输入'取消'中止"
),
bot,
event=event_1,
)
event_2 = fake_private_message_event(
message=Message("1"), sender=fake_superuser, to_me=True, user_id=fake_superuser.user_id
)
ctx.receive_event(bot, event_2)
ctx.should_pass_rule()
ctx.should_pass_permission()
ctx.should_call_send(event_2, "删除成功", True)
+69
View File
@@ -0,0 +1,69 @@
{
"version": 3,
"groups": [
{
"user_target": {
"platform_type": "QQ Group",
"group_id": 1232
},
"subs": [
{
"categories": [],
"tags": [],
"target": {
"target_name": "weibo_name",
"target": "weibo_id",
"platform_name": "weibo",
"default_schedule_weight": 10
}
}
]
},
{
"user_target": {
"platform_type": "QQ Group",
"group_id": 2342
},
"subs": [
{
"categories": [],
"tags": ["kaltsit", "amiya"],
"target": {
"target_name": "weibo_name",
"target": "weibo_id",
"platform_name": "weibo",
"default_schedule_weight": 10
}
},
{
"categories": [1, 2],
"tags": [],
"target": {
"target_name": "bilibili_name",
"target": "bilibili_id",
"platform_name": "bilibili",
"default_schedule_weight": 10
}
}
]
}
],
"cookies": [
{
"site_name": "weibo.com",
"content": "{\"cookie\": \"test\"}",
"cookie_name": "test cookie",
"cd_milliseconds": 0,
"is_universal": false,
"tags": {},
"targets": [
{
"target_name": "weibo_name",
"target": "weibo_id",
"platform_name": "weibo",
"default_schedule_weight": 10
}
]
}
]
}
+48
View File
@@ -0,0 +1,48 @@
version: 3
groups:
- subs:
- categories: []
tags: []
target:
default_schedule_weight: 10
platform_name: weibo
target: weibo_id
target_name: weibo_name
user_target:
platform_type: QQ Group
group_id: 123552
- subs:
- categories: []
tags:
- kaltsit
- amiya
target:
default_schedule_weight: 10
platform_name: weibo
target: weibo_id
target_name: weibo_name
- categories:
- 1
- 2
tags: []
target:
default_schedule_weight: 10
platform_name: bilibili
target: bilibili_id
target_name: bilibili_name
user_target:
platform_type: QQ Group
group_id: 234662
cookies:
- site_name: weibo.com
content: '{"cookie": "test"}'
cookie_name: test cookie
cd_milliseconds: 0
is_universal: false
tags: {}
targets:
- target_name: weibo_name
target: weibo_id
platform_name: weibo
default_schedule_weight: 10
@@ -0,0 +1,103 @@
{
"version": 2,
"groups": [
{
"user_target": {
"platform_type": "QQ Group",
"group_id": 123
},
"subs": [
{
"categories": [],
"tags": [],
"target": {
"target_name": "weibo_name",
"target": "weibo_id",
"platform_name": "weibo",
"default_schedule_weight": 10
}
}
]
},
{
"user_target": {
"platform_type": "QQ Group",
"group_id": 234
},
"subs": [
{
"tags": ["kaltsit", "amiya"],
"target": {
"target_name": "weibo_name",
"target": "weibo_id",
"platform_name": "weibo",
"default_schedule_weight": 10
}
},
{
"categories": [1, 2],
"tags": [],
"target": [
{
"target_name": "bilibili_name",
"target": "bilibili_id",
"platform_name": "bilibili",
"default_schedule_weight": 10
}
]
}
]
},
{
"user_target": {
"platform_type": "QQ Group",
"group_id": 123
},
"subs": {
"categories": [],
"tags": [],
"target": {
"target_name": "weibo_name2",
"target": "weibo_id2",
"platform_name": "weibo",
"default_schedule_weight": 10
}
}
},
{
"user_target": {
"platform_type": "QQ Group",
"group_id": 123
},
"subs": [
{
"categories": [],
"tags": [],
"target": {
"target_name": "weibo_name2",
"platform_name": "weibo",
"default_schedule_weight": 10
}
}
]
}
],
"cookies": [
{
"site_name": "weibo.com1111",
"content": "{\"cookie\": 111}",
"cookie_name": "test cookie1",
"cd_milliseconds": -1,
"is_universal": false,
"tags": {},
"targets": [
{
"target_name": "weibo_name",
"target": "weibo_id",
"platform_name": "weibo",
"default_schedule_weight": 10
}
]
}
]
}
@@ -0,0 +1,97 @@
{
"version": 3,
"groups": [
{
"user_target": {
"platform_type": "QQ Group",
"group_id": 1232
},
"subs": [
{
"categories": [],
"tags": [],
"target": {
"target_name": "weibo_name",
"target": "weibo_id",
"platform_name": "weibo",
"default_schedule_weight": 10
}
}
]
},
{
"user_target": {
"platform_type": "QQ Group",
"group_id": 2342
},
"subs": [
{
"categories": [],
"tags": ["kaltsit", "amiya"],
"target": {
"target_name": "weibo_name",
"target": "weibo_id",
"platform_name": "weibo",
"default_schedule_weight": 10
}
},
{
"categories": [1, 2],
"tags": [],
"target": {
"target_name": "bilibili_name",
"target": "bilibili_id",
"platform_name": "bilibili",
"default_schedule_weight": 10
}
}
]
},
{
"user_target": {
"platform_type": "QQ Group",
"group_id": 1232
},
"subs": [
{
"categories": [],
"tags": [],
"target": {
"target_name": "weibo_name",
"target": "weibo_id",
"platform_name": "weibo",
"default_schedule_weight": 10
}
},
{
"categories": [2, 6],
"tags": ["poca"],
"target": {
"target_name": "weibo_name2",
"target": "weibo_id2",
"platform_name": "weibo",
"default_schedule_weight": 10
}
}
]
}
],
"cookies": [
{
"site_name": "weibo.com",
"content": "{\"cookie\": \"test\"}",
"cookie_name": "test cookie",
"cd_milliseconds": 0,
"is_universal": false,
"tags": {},
"targets": [
{
"target_name": "weibo_name",
"target": "weibo_id",
"platform_name": "weibo",
"default_schedule_weight": 10
}
]
}
]
}
+15 -3
View File
@@ -40,6 +40,7 @@ def test_cli_help(app: App):
async def test_subs_export(app: App, tmp_path: Path):
from nonebot_plugin_saa import TargetQQGroup
from nonebot_bison.config.db_model import Cookie
from nonebot_bison.config.db_config import config
from nonebot_bison.types import Target as TTarget
from nonebot_bison.script.cli import cli, run_sync
@@ -70,6 +71,14 @@ async def test_subs_export(app: App, tmp_path: Path):
cats=[1, 2],
tags=[],
)
cookie_id = await config.add_cookie(
Cookie(
site_name="weibo.com",
content='{"cookie": "test"}',
cookie_name="test cookie",
)
)
await config.add_cookie_target("weibo_id", "weibo", cookie_id)
assert len(await config.list_subs_with_all_info()) == 3
@@ -84,8 +93,9 @@ async def test_subs_export(app: App, tmp_path: Path):
assert result.exit_code == 0
file_path = Path.cwd() / "bison_subscribes_export_1.json"
assert file_path.exists()
assert '"version": 2' in file_path.read_text()
assert '"version": 3' in file_path.read_text()
assert '"group_id": 123' in file_path.read_text()
assert '"content": "{\\"cookie\\": \\"test\\"}",\n' in file_path.read_text()
# 是否导出到指定已存在文件夹
data_dir = tmp_path / "data"
@@ -94,8 +104,9 @@ async def test_subs_export(app: App, tmp_path: Path):
assert result.exit_code == 0
file_path2 = data_dir / "bison_subscribes_export_1.json"
assert file_path2.exists()
assert '"version": 2' in file_path2.read_text()
assert '"version": 3' in file_path2.read_text()
assert '"group_id": 123' in file_path2.read_text()
assert '"content": "{\\"cookie\\": \\"test\\"}",\n' in file_path.read_text()
# 是否拒绝导出到不存在的文件夹
result = await run_sync(runner.invoke)(cli, ["export", "-p", str(tmp_path / "data2")])
@@ -106,9 +117,10 @@ async def test_subs_export(app: App, tmp_path: Path):
assert result.exit_code == 0
file_path3 = tmp_path / "bison_subscribes_export_1.yaml"
assert file_path3.exists()
assert "version: 2" in file_path3.read_text()
assert "version: 3" in file_path3.read_text()
assert "group_id: 123" in file_path3.read_text()
assert "platform_type: QQ Group" in file_path3.read_text()
assert '"content": "{\\"cookie\\": \\"test\\"}",\n' in file_path.read_text()
# 是否允许以未支持的格式导出
result = await run_sync(runner.invoke)(cli, ["export", "-p", str(tmp_path), "--format", "toml"])
+34 -8
View File
@@ -5,12 +5,13 @@ from nonebot.compat import model_dump
from .utils import get_json
@pytest.mark.usefixtures("_clear_db")
async def test_subs_export(app: App, init_scheduler):
from nonebot_plugin_saa import TargetQQGroup
from nonebot_bison.config.db_model import User
from nonebot_bison.config.db_config import config
from nonebot_bison.types import Target as TTarget
from nonebot_bison.config.db_model import User, Cookie
from nonebot_bison.config.subs_io import subscribes_export
await config.add_subscribe(
@@ -37,12 +38,20 @@ async def test_subs_export(app: App, init_scheduler):
cats=[1, 2],
tags=[],
)
cookie_id = await config.add_cookie(
Cookie(
site_name="weibo.com",
content='{"cookie": "test"}',
cookie_name="test cookie",
)
)
await config.add_cookie_target("weibo_id", "weibo", cookie_id)
data = await config.list_subs_with_all_info()
assert len(data) == 3
nbesf_data = await subscribes_export(lambda x: x)
assert model_dump(nbesf_data) == get_json("v2/subs_export.json")
assert model_dump(nbesf_data) == get_json("v3/subs_export.json")
nbesf_data_user_234 = await subscribes_export(
lambda stmt: stmt.where(User.user_target == {"platform_type": "QQ Group", "group_id": 2342})
@@ -102,16 +111,30 @@ async def test_subs_import_dup_err(app: App, init_scheduler):
async def test_subs_import_version_disorder(app: App, init_scheduler):
from nonebot_bison.config.subs_io import subscribes_import
from nonebot_bison.config.subs_io.nbesf_model import v1, v2
from nonebot_bison.config.subs_io.utils import NBESFParseErr
# use v2 parse v1
with pytest.raises(NBESFParseErr):
v1.nbesf_parser(get_json("v2/subs_export_has_subdup_err.json"))
from nonebot_bison.config.subs_io.nbesf_model import v1, v2, v3
# use v1 parse v2
with pytest.raises(NBESFParseErr):
v1.nbesf_parser(get_json("v2/subs_export_has_subdup_err.json"))
# use v1 parse v3
with pytest.raises(NBESFParseErr):
v1.nbesf_parser(get_json("v3/subs_export_has_subdup_err.json"))
# use v2 parse v1
with pytest.raises(NBESFParseErr):
v2.nbesf_parser(get_json("v1/subs_export_has_subdup_err.json"))
# # use v2 parse v3
# with pytest.raises(NBESFParseErr):
# v2.nbesf_parser(get_json("v3/subs_export_has_subdup_err.json"))
# use v3 parse v1
with pytest.raises(NBESFParseErr):
v3.nbesf_parser(get_json("v1/subs_export_has_subdup_err.json"))
# # use v3 parse v2
# with pytest.raises(NBESFParseErr):
# v3.nbesf_parser(get_json("v2/subs_export_has_subdup_err.json"))
# TODO: v3 parse v2 不会报错,但是v3 parse v1 会报错,似乎是有问题 (
with pytest.raises(AssertionError): # noqa: PT012
nbesf_data = v2.nbesf_parser(get_json("v2/subs_export_has_subdup_err.json"))
@@ -121,7 +144,7 @@ async def test_subs_import_version_disorder(app: App, init_scheduler):
async def test_subs_import_all_fail(app: App, init_scheduler):
"""只要文件格式有任何一个错误, 都不会进行订阅"""
from nonebot_bison.config.subs_io.nbesf_model import v1, v2
from nonebot_bison.config.subs_io.nbesf_model import v1, v2, v3
from nonebot_bison.config.subs_io.nbesf_model.v1 import NBESFParseErr
with pytest.raises(NBESFParseErr):
@@ -129,3 +152,6 @@ async def test_subs_import_all_fail(app: App, init_scheduler):
with pytest.raises(NBESFParseErr):
v2.nbesf_parser(get_json("v2/subs_export_all_illegal.json"))
with pytest.raises(NBESFParseErr):
v3.nbesf_parser(get_json("v3/subs_export_all_illegal.json"))
+3 -2
View File
@@ -17,7 +17,8 @@ async def test_http_error(app: App):
assert ctx.gen_req_records() == [
"https://example.com Headers({'host': 'example.com', 'accept': '*/*', 'accept-encoding': 'gzip, deflate',"
" 'connection': 'keep-alive', 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like"
" Gecko) Chrome/51.0.2704.103 Safari/537.36'}) | [403] Headers({'content-length': '15', 'content-type':"
" 'connection': 'keep-alive', 'user-agent': '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'}) | [403] Headers({'content-length': '"
"15', 'content-type':"
' \'application/json\'}) {"error": "gg"}'
]