nonebot-plugin-parsehub 0.1.0__tar.gz → 1.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nonebot_plugin_parsehub-1.0.1/PKG-INFO +219 -0
- nonebot_plugin_parsehub-1.0.1/README.md +190 -0
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/pyproject.toml +13 -3
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/pyproject.toml.orig +11 -3
- nonebot_plugin_parsehub-1.0.1/src/nonebot_plugin_parsehub/SPEC.md +82 -0
- nonebot_plugin_parsehub-1.0.1/src/nonebot_plugin_parsehub/__init__.py +56 -0
- nonebot_plugin_parsehub-1.0.1/src/nonebot_plugin_parsehub/app.py +181 -0
- nonebot_plugin_parsehub-1.0.1/src/nonebot_plugin_parsehub/budget.py +61 -0
- nonebot_plugin_parsehub-1.0.1/src/nonebot_plugin_parsehub/cache.py +475 -0
- nonebot_plugin_parsehub-1.0.1/src/nonebot_plugin_parsehub/card/SPEC.md +46 -0
- nonebot_plugin_parsehub-1.0.1/src/nonebot_plugin_parsehub/card/__init__.py +179 -0
- nonebot_plugin_parsehub-1.0.1/src/nonebot_plugin_parsehub/card/cover.py +135 -0
- nonebot_plugin_parsehub-1.0.1/src/nonebot_plugin_parsehub/card/fonts.py +527 -0
- nonebot_plugin_parsehub-0.1.0/src/nonebot_plugin_parsehub/card.py → nonebot_plugin_parsehub-1.0.1/src/nonebot_plugin_parsehub/card/layout.py +277 -304
- nonebot_plugin_parsehub-1.0.1/src/nonebot_plugin_parsehub/config.py +235 -0
- nonebot_plugin_parsehub-1.0.1/src/nonebot_plugin_parsehub/dedupe.py +128 -0
- nonebot_plugin_parsehub-1.0.1/src/nonebot_plugin_parsehub/delivery.py +552 -0
- nonebot_plugin_parsehub-1.0.1/src/nonebot_plugin_parsehub/errors.py +187 -0
- nonebot_plugin_parsehub-1.0.1/src/nonebot_plugin_parsehub/feedback.py +150 -0
- nonebot_plugin_parsehub-1.0.1/src/nonebot_plugin_parsehub/keys.py +157 -0
- nonebot_plugin_parsehub-1.0.1/src/nonebot_plugin_parsehub/media.py +221 -0
- nonebot_plugin_parsehub-1.0.1/src/nonebot_plugin_parsehub/parsehub_client.py +234 -0
- nonebot_plugin_parsehub-1.0.1/src/nonebot_plugin_parsehub/paths.py +56 -0
- nonebot_plugin_parsehub-1.0.1/src/nonebot_plugin_parsehub/platforms.py +174 -0
- nonebot_plugin_parsehub-0.1.0/src/nonebot_plugin_parsehub/server.py → nonebot_plugin_parsehub-1.0.1/src/nonebot_plugin_parsehub/relay.py +16 -79
- nonebot_plugin_parsehub-1.0.1/src/nonebot_plugin_parsehub/service.py +634 -0
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/src/nonebot_plugin_parsehub/switches.py +42 -42
- nonebot_plugin_parsehub-1.0.1/src/nonebot_plugin_parsehub/triggers.py +289 -0
- nonebot_plugin_parsehub-0.1.0/PKG-INFO +0 -275
- nonebot_plugin_parsehub-0.1.0/README.md +0 -248
- nonebot_plugin_parsehub-0.1.0/src/nonebot_plugin_parsehub/__init__.py +0 -1298
- nonebot_plugin_parsehub-0.1.0/src/nonebot_plugin_parsehub/config.py +0 -95
- nonebot_plugin_parsehub-0.1.0/src/nonebot_plugin_parsehub/core.py +0 -485
- nonebot_plugin_parsehub-0.1.0/src/nonebot_plugin_parsehub/mediacache.py +0 -418
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/LICENSE +0 -0
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/src/nonebot_plugin_parsehub/assets/logos/baidu.png +0 -0
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/src/nonebot_plugin_parsehub/assets/logos/bilibili.png +0 -0
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/src/nonebot_plugin_parsehub/assets/logos/coolapk.png +0 -0
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/src/nonebot_plugin_parsehub/assets/logos/douban.png +0 -0
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/src/nonebot_plugin_parsehub/assets/logos/douyin.png +0 -0
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/src/nonebot_plugin_parsehub/assets/logos/facebook.png +0 -0
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/src/nonebot_plugin_parsehub/assets/logos/instagram.png +0 -0
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/src/nonebot_plugin_parsehub/assets/logos/kuaishou.png +0 -0
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/src/nonebot_plugin_parsehub/assets/logos/medium.png +0 -0
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/src/nonebot_plugin_parsehub/assets/logos/pipix.png +0 -0
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/src/nonebot_plugin_parsehub/assets/logos/snapchat.png +0 -0
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/src/nonebot_plugin_parsehub/assets/logos/threads.png +0 -0
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/src/nonebot_plugin_parsehub/assets/logos/tiktok.png +0 -0
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/src/nonebot_plugin_parsehub/assets/logos/toutiao.png +0 -0
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/src/nonebot_plugin_parsehub/assets/logos/wechat.png +0 -0
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/src/nonebot_plugin_parsehub/assets/logos/weibo.png +0 -0
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/src/nonebot_plugin_parsehub/assets/logos/x.png +0 -0
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/src/nonebot_plugin_parsehub/assets/logos/xiaoheihe.png +0 -0
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/src/nonebot_plugin_parsehub/assets/logos/xiaohongshu.png +0 -0
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/src/nonebot_plugin_parsehub/assets/logos/youtube.png +0 -0
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/src/nonebot_plugin_parsehub/assets/logos/zhihu.png +0 -0
- {nonebot_plugin_parsehub-0.1.0 → nonebot_plugin_parsehub-1.0.1}/src/nonebot_plugin_parsehub/assets/logos/zuiyou.png +0 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nonebot-plugin-parsehub
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: 基于 ParseHub 的社交媒体链接解析插件:群里出现分享链接时自动解析并发送媒体
|
|
5
|
+
Keywords: nonebot,nonebot2,plugin,parsehub,parser,media,onebot
|
|
6
|
+
Author: ByteColtX
|
|
7
|
+
Author-email: ByteColtX <umk@live.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Topic :: Communications :: Chat
|
|
15
|
+
Requires-Dist: httpx>=0.28.0,<1.0.0
|
|
16
|
+
Requires-Dist: nonebot-adapter-onebot>=2.4.6,<3.0.0
|
|
17
|
+
Requires-Dist: nonebot-plugin-localstore>=0.7.4,<1.0.0
|
|
18
|
+
Requires-Dist: nonebot2>=2.5.0,<3.0.0
|
|
19
|
+
Requires-Dist: numpy>=2.0.0,<3.0.0
|
|
20
|
+
Requires-Dist: opencv-python-headless>=4.13.0.92
|
|
21
|
+
Requires-Dist: parsehub>=2.3.10,<3.0.0
|
|
22
|
+
Requires-Dist: pillow>=12.1.0,<13.0.0
|
|
23
|
+
Requires-Dist: pydantic>=2.0.0,<3.0.0
|
|
24
|
+
Requires-Python: >=3.12
|
|
25
|
+
Project-URL: Homepage, https://github.com/ByteColtX/nonebot-plugin-parsehub
|
|
26
|
+
Project-URL: Repository, https://github.com/ByteColtX/nonebot-plugin-parsehub.git
|
|
27
|
+
Project-URL: Issues, https://github.com/ByteColtX/nonebot-plugin-parsehub/issues
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
<div align="center">
|
|
31
|
+
<a href="https://v2.nonebot.dev/store">
|
|
32
|
+
<img src="https://raw.githubusercontent.com/fllesser/nonebot-plugin-template/refs/heads/resource/.docs/NoneBotPlugin.svg" width="310" alt="logo"></a>
|
|
33
|
+
|
|
34
|
+
## ✨ nonebot-plugin-parsehub ✨
|
|
35
|
+
|
|
36
|
+
[](./LICENSE)
|
|
37
|
+
[](https://pypi.org/project/nonebot-plugin-parsehub/)
|
|
38
|
+
[](https://www.python.org)
|
|
39
|
+
[](https://github.com/astral-sh/uv)
|
|
40
|
+
<br/>
|
|
41
|
+
[](https://github.com/astral-sh/ruff)
|
|
42
|
+
[](https://github.com/ByteColtX/nonebot-plugin-parsehub/actions/workflows/ci.yml)
|
|
43
|
+
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
## 📖 介绍
|
|
47
|
+
|
|
48
|
+
基于 [ParseHub](https://github.com/z-mio/ParseHub) 的社交媒体链接解析插件:群里出现受支持的分享链接时自动解析,发回视频 / 图集 / 图文,并配一张 App Store「Today」风格的信息卡片。
|
|
49
|
+
|
|
50
|
+
> 仅支持 **OneBot v11** 适配器(NapCat / SnowLuma / LuckyLilliaBot 等)。
|
|
51
|
+
|
|
52
|
+
- **自动解析**:发链接或分享文案即可,也可用 `/解析` 命令手动触发
|
|
53
|
+
- **按会话开关**:`解析开关` 控制某个群 / 私聊,超管和群管理可操作
|
|
54
|
+
- **24 个平台**:Bilibili、抖音、小红书、微博、快手、知乎、贴吧、豆瓣、今日头条、微信公众号、微信视频号、酷安、皮皮虾、最右、小黑盒、Bangumi、X/Twitter、Instagram、YouTube、TikTok、Threads、Facebook、Snapchat、Medium
|
|
55
|
+
- **卡片 + 媒体**:先发信息卡片,再发视频 / 图集;2 个以上媒体合并转发,单个作品最多 20 个
|
|
56
|
+
- **实况照片**:图片和短视频一起发;视频走 `video` 消息段,不占群文件
|
|
57
|
+
- **表情回应**:给触发消息点表情(收到 / 成功 / 失败),不引用原消息
|
|
58
|
+
- **成本控制**:体积 / 时长上限、会话内去重、并发限制、临时文件自动清理
|
|
59
|
+
- **缓存与投递**:同一作品 24 小时内直接发本地缓存;投递方式 `file` / `http` / `base64` 三选一
|
|
60
|
+
|
|
61
|
+
## 🎨 效果图
|
|
62
|
+
|
|
63
|
+
| B 站视频卡片 | 抖音图集卡片 |
|
|
64
|
+
| :---: | :---: |
|
|
65
|
+
| <img src="https://raw.githubusercontent.com/ByteColtX/nonebot-plugin-parsehub/main/docs/card-bilibili.jpg" width="420" alt="B 站卡片"> | <img src="https://raw.githubusercontent.com/ByteColtX/nonebot-plugin-parsehub/main/docs/card-douyin.jpg" width="420" alt="抖音卡片"> |
|
|
66
|
+
|
|
67
|
+
## 💿 安装
|
|
68
|
+
|
|
69
|
+
使用 nb-cli 安装
|
|
70
|
+
|
|
71
|
+
在 nonebot2 项目的根目录下打开命令行, 输入以下指令即可安装
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
nb plugin install nonebot-plugin-parsehub --upgrade
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
使用 uv 安装
|
|
78
|
+
|
|
79
|
+
在 nonebot2 项目的根目录下, 打开命令行, 输入以下指令即可安装
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
uv add nonebot-plugin-parsehub
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
安装仓库 main 分支
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
uv add git+https://github.com/ByteColtX/nonebot-plugin-parsehub@main
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
用 uv 安装后还需要在 nonebot2 项目根目录的 `pyproject.toml` 里注册插件
|
|
92
|
+
(nb-cli 会自动完成这一步):
|
|
93
|
+
|
|
94
|
+
```toml
|
|
95
|
+
[tool.nonebot]
|
|
96
|
+
plugins = ["nonebot_plugin_parsehub"]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## ⚙️ 配置
|
|
100
|
+
|
|
101
|
+
在 nonebot2 项目的 `.env` 文件中添加下表中的配置, 全部可选(零配置即可加载运行)
|
|
102
|
+
|
|
103
|
+
| 配置项 | 必填 | 默认值 | 说明 |
|
|
104
|
+
| :--- | :---: | :--- | :--- |
|
|
105
|
+
| `PARSEHUB_AUTO` | 否 | `true` | 群里出现链接时自动解析(总开关) |
|
|
106
|
+
| `PARSEHUB_MAX_FILE_MB` | 否 | `100` | 单个媒体文件体积上限(MB),超过直接跳过;`0` = 不限 |
|
|
107
|
+
| `PARSEHUB_MAX_SECONDS` | 否 | `300` | 视频时长上限(秒),超过只发封面 + 一句提示、不下载;`0` = 不限 |
|
|
108
|
+
| `PARSEHUB_PLATFORMS` | 否 | 无 | 平台白名单:只解析这些平台,留空 = 全部。可写平台 id(`xhs`)或中文名(`小红书`) |
|
|
109
|
+
| `PARSEHUB_DEDUPE_SECONDS` | 否 | `120` | 同一会话内同一链接的去重窗口(秒),`0` = 不去重 |
|
|
110
|
+
| `PARSEHUB_CONCURRENCY` | 否 | `2` | 同时处理的任务数 |
|
|
111
|
+
| `PARSEHUB_PROXY` | 否 | 无 | 解析 / 下载代理,如 `http://127.0.0.1:7890` |
|
|
112
|
+
| `PARSEHUB_PROXY_PLATFORMS` | 否 | 海外 8 平台 | 只对这些平台走代理,留空 = 全部走(列表要用合法 JSON) |
|
|
113
|
+
| `PARSEHUB_COOKIE` | 否 | 无 | 兜底 cookie(优先读 parsehub 自己的 `cookies.toml`) |
|
|
114
|
+
| `PARSEHUB_TIMEOUT` | 否 | `180` | 解析 + 下载总超时(秒) |
|
|
115
|
+
| `PARSEHUB_CACHE_TTL_HOURS` | 否 | `24` | 媒体缓存保留小时数,命中就直接发本地那份;`0` = 关闭缓存 |
|
|
116
|
+
| `PARSEHUB_CACHE_MAX_MB` | 否 | `1024` | 媒体缓存总容量上限(MB),超了按最久没用过的删 |
|
|
117
|
+
| `PARSEHUB_CARD` | 否 | `true` | 是否发信息卡片图(失败自动退回文字) |
|
|
118
|
+
| `PARSEHUB_CARD_FONT` | 否 | 无 | 中文字体路径,留空则自动找系统字体 / 下载 |
|
|
119
|
+
| `PARSEHUB_FORWARD_TIMEOUT` | 否 | `60` | 合并转发等待协议端的秒数,超时才逐条发 |
|
|
120
|
+
| `PARSEHUB_SEND_TEXT` | 否 | `true` | 是否发送标题 / 正文 |
|
|
121
|
+
| `PARSEHUB_MAX_CHARS` | 否 | `300` | 正文最多发送多少字符 |
|
|
122
|
+
| `PARSEHUB_FAIL_NOTICE` | 否 | `true` | 视频发送失败时是否额外发文字提示 |
|
|
123
|
+
| `PARSEHUB_DELIVERY` | 否 | `file` | 媒体交给协议端:`file` = 本机路径(要求同机)/ `http` = 中转地址 / `base64` = 内容内联 |
|
|
124
|
+
| `PARSEHUB_RELAY_HOST` | 否 | 无 | 中转服务对外地址,留空自动探测内网 IP(仅 `http` 模式) |
|
|
125
|
+
| `PARSEHUB_RELAY_PORT` | 否 | `21473` | 中转服务端口(仅 `http` 模式) |
|
|
126
|
+
| `PARSEHUB_TASK_TTL_MINUTES` | 否 | `30` | 临时文件保留分钟数 |
|
|
127
|
+
|
|
128
|
+
> 列表类配置必须是合法 JSON(如 `PARSEHUB_PROXY_PLATFORMS=["twitter", "youtube"]`);
|
|
129
|
+
> 数值项有下界校验(如 `PARSEHUB_TIMEOUT >= 5`),写 0 或负数会在启动时直接报错。
|
|
130
|
+
> 哪些群 / 私聊解析由命令 `/解析开关` 控制,没有对应的配置项。
|
|
131
|
+
|
|
132
|
+
需要登录态的平台(X、Instagram、TikTok、豆瓣、微信视频号 等),cookie 优先从 ParseHub 自己的配置读取
|
|
133
|
+
(`ph set cookie <平台>`),也可以用 `PARSEHUB_COOKIE` 兜底。
|
|
134
|
+
|
|
135
|
+
## 🎉 使用
|
|
136
|
+
|
|
137
|
+
### 指令表
|
|
138
|
+
|
|
139
|
+
| 指令 | 权限 | 需要@ | 范围 | 说明 |
|
|
140
|
+
| :--- | :---: | :---: | :---: | :--- |
|
|
141
|
+
| `/解析 <链接或分享文案>`(别名 `/ph`、`/解析链接`) | 群员 | 否 | 群聊/私聊 | 手动解析,不做去重拦截 |
|
|
142
|
+
| `/解析平台` | 群员 | 否 | 群聊/私聊 | 列出支持的平台 |
|
|
143
|
+
| `/解析开关 [开\|关]`、`/解析开`、`/解析关` | 主人/群管 | 否 | 群聊/私聊 | 按会话开关解析(私聊仅主人) |
|
|
144
|
+
| `/解析状态` | 群员 | 否 | 群聊/私聊 | 查看当前会话的开关状态 |
|
|
145
|
+
|
|
146
|
+
> 命令按 NoneBot 的 `COMMAND_START` 匹配(默认只有 `/`)。想让无前缀写法也算命令,把 `COMMAND_START` 加上 `""`。
|
|
147
|
+
|
|
148
|
+
### 触发规则
|
|
149
|
+
|
|
150
|
+
- 群里发链接或分享文案即自动解析,一条消息最多处理 **3 个**链接
|
|
151
|
+
- `/解析关` 后该会话不再自动解析;手动 `/解析` 只对超管 / 群管理有效
|
|
152
|
+
- 不带前缀的「解析 <链接>」**不是**命令,会当作普通消息自动解析(带去重、不额外提示)
|
|
153
|
+
|
|
154
|
+
### 解析开关
|
|
155
|
+
|
|
156
|
+
- 以**会话**为维度(群按群号,私聊按对方 QQ),群和私聊**默认都开**
|
|
157
|
+
- 关掉后该会话不再自动解析,手动解析仅超管 / 群管可用;状态所有人可看
|
|
158
|
+
- 超管与群主 / 管理员可修改;状态存在 `sessions.json`,只记录与默认值不同的会话
|
|
159
|
+
|
|
160
|
+
### 媒体缓存
|
|
161
|
+
|
|
162
|
+
- **按作品跨会话共享**:A 群发过的视频,B 群再发直接发本地那份
|
|
163
|
+
- **命中即跳过**:不解析、不下载、不画卡(卡片图也一起缓存)
|
|
164
|
+
- **保留 24 小时**(命中续期),总量上限 1GB,超了按最久没用过的删
|
|
165
|
+
- **不同写法归一**:`b23.tv/xxx` 与 `bilibili.com/video/BVxxx` 是同一个作品
|
|
166
|
+
- 时间线:120 秒内同会话重复 → 静默;120 秒 ~ 24 小时 → 直接发缓存;超过 24 小时 → 重新解析
|
|
167
|
+
- 手动 `/解析 <链接>` 同样吃缓存(只是不做去重拦截)
|
|
168
|
+
|
|
169
|
+
### 说明
|
|
170
|
+
|
|
171
|
+
- 视频被协议端拒绝时只点失败表情,**不会**退回群文件
|
|
172
|
+
- 投递三选一、**互不兜底**:`file` 发本机路径(协议端同机)/ `http` 发中转地址(跨机)/ `base64` 内容内联(请求体约 +1/3)
|
|
173
|
+
- parsehub 全程同步阻塞,插件把所有调用放进 `asyncio.to_thread`;首次导入懒加载,不影响启动
|
|
174
|
+
- 卡片用 Pillow 画,封面三级兜底(`thumb_url` → 图片媒体 → 视频抽帧),没装 Pillow 或没有中文字体时自动退回文字;
|
|
175
|
+
字体优先 `PARSEHUB_CARD_FONT` → 缓存目录 → 系统字体 → 下载(sha256 校验),缺字用回退字体补,emoji 用 Twemoji 位图字体
|
|
176
|
+
|
|
177
|
+
## 🛠️ 开发
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
uv sync --all-groups # 安装依赖(含测试)
|
|
181
|
+
uv run poe check # 代码检查 + 格式检查 + 测试(CI 同款)
|
|
182
|
+
uv run pytest -q # 只跑测试
|
|
183
|
+
uv run poe live # 真实链路自检(解析 → 下载 → 中转取回)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 代码结构
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
src/nonebot_plugin_parsehub/
|
|
190
|
+
├── triggers.py # matcher / 命令:只决定「处理谁」
|
|
191
|
+
├── service.py # 编排:每个结局都是 Outcome 里的一个值
|
|
192
|
+
├── delivery.py # 三种上传模式 + 合并转发/逐条 + 统一失败记账
|
|
193
|
+
├── feedback.py # 表情回应与用户提示文案
|
|
194
|
+
├── cache.py # 作品级媒体缓存(哈希目录、原子写)
|
|
195
|
+
├── dedupe.py # 会话级去重(claim / commit / release)
|
|
196
|
+
├── relay.py # 只读中转服务(只服务 relay/)
|
|
197
|
+
├── parsehub_client.py # ParseHub 懒加载 + 线程池封装
|
|
198
|
+
├── card/ # fonts / cover / layout + CardRenderer
|
|
199
|
+
└── app.py # 装配与生命周期
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
模块级单例已全部移除:`App` 持有设置、客户端、缓存、去重、开关、中转、投递和卡片,
|
|
203
|
+
测试直接 `App.build(...)` 造独立实例。
|
|
204
|
+
|
|
205
|
+
### 代码风格
|
|
206
|
+
|
|
207
|
+
遵循 [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html):行宽 80,Google 风格文档字符串,
|
|
208
|
+
ruff 强制 `pydocstyle(google)` + `pep8-naming` + `isort`。
|
|
209
|
+
|
|
210
|
+
有意保留的偏差:包内用相对导入(本地插件加载时绝对自引用会 ImportError);NoneBot 按名字导入;
|
|
211
|
+
中文文档字符串以「。」结尾(关闭 `D415`);测试不强制文档字符串。
|
|
212
|
+
|
|
213
|
+
## 📄 许可
|
|
214
|
+
|
|
215
|
+
[MIT](LICENSE)
|
|
216
|
+
|
|
217
|
+
## 🙏 致谢
|
|
218
|
+
|
|
219
|
+
- [z-mio/ParseHub](https://github.com/z-mio/ParseHub):本插件的解析能力全部来自这个项目
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<a href="https://v2.nonebot.dev/store">
|
|
3
|
+
<img src="https://raw.githubusercontent.com/fllesser/nonebot-plugin-template/refs/heads/resource/.docs/NoneBotPlugin.svg" width="310" alt="logo"></a>
|
|
4
|
+
|
|
5
|
+
## ✨ nonebot-plugin-parsehub ✨
|
|
6
|
+
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
[](https://pypi.org/project/nonebot-plugin-parsehub/)
|
|
9
|
+
[](https://www.python.org)
|
|
10
|
+
[](https://github.com/astral-sh/uv)
|
|
11
|
+
<br/>
|
|
12
|
+
[](https://github.com/astral-sh/ruff)
|
|
13
|
+
[](https://github.com/ByteColtX/nonebot-plugin-parsehub/actions/workflows/ci.yml)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
## 📖 介绍
|
|
18
|
+
|
|
19
|
+
基于 [ParseHub](https://github.com/z-mio/ParseHub) 的社交媒体链接解析插件:群里出现受支持的分享链接时自动解析,发回视频 / 图集 / 图文,并配一张 App Store「Today」风格的信息卡片。
|
|
20
|
+
|
|
21
|
+
> 仅支持 **OneBot v11** 适配器(NapCat / SnowLuma / LuckyLilliaBot 等)。
|
|
22
|
+
|
|
23
|
+
- **自动解析**:发链接或分享文案即可,也可用 `/解析` 命令手动触发
|
|
24
|
+
- **按会话开关**:`解析开关` 控制某个群 / 私聊,超管和群管理可操作
|
|
25
|
+
- **24 个平台**:Bilibili、抖音、小红书、微博、快手、知乎、贴吧、豆瓣、今日头条、微信公众号、微信视频号、酷安、皮皮虾、最右、小黑盒、Bangumi、X/Twitter、Instagram、YouTube、TikTok、Threads、Facebook、Snapchat、Medium
|
|
26
|
+
- **卡片 + 媒体**:先发信息卡片,再发视频 / 图集;2 个以上媒体合并转发,单个作品最多 20 个
|
|
27
|
+
- **实况照片**:图片和短视频一起发;视频走 `video` 消息段,不占群文件
|
|
28
|
+
- **表情回应**:给触发消息点表情(收到 / 成功 / 失败),不引用原消息
|
|
29
|
+
- **成本控制**:体积 / 时长上限、会话内去重、并发限制、临时文件自动清理
|
|
30
|
+
- **缓存与投递**:同一作品 24 小时内直接发本地缓存;投递方式 `file` / `http` / `base64` 三选一
|
|
31
|
+
|
|
32
|
+
## 🎨 效果图
|
|
33
|
+
|
|
34
|
+
| B 站视频卡片 | 抖音图集卡片 |
|
|
35
|
+
| :---: | :---: |
|
|
36
|
+
| <img src="https://raw.githubusercontent.com/ByteColtX/nonebot-plugin-parsehub/main/docs/card-bilibili.jpg" width="420" alt="B 站卡片"> | <img src="https://raw.githubusercontent.com/ByteColtX/nonebot-plugin-parsehub/main/docs/card-douyin.jpg" width="420" alt="抖音卡片"> |
|
|
37
|
+
|
|
38
|
+
## 💿 安装
|
|
39
|
+
|
|
40
|
+
使用 nb-cli 安装
|
|
41
|
+
|
|
42
|
+
在 nonebot2 项目的根目录下打开命令行, 输入以下指令即可安装
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
nb plugin install nonebot-plugin-parsehub --upgrade
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
使用 uv 安装
|
|
49
|
+
|
|
50
|
+
在 nonebot2 项目的根目录下, 打开命令行, 输入以下指令即可安装
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
uv add nonebot-plugin-parsehub
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
安装仓库 main 分支
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
uv add git+https://github.com/ByteColtX/nonebot-plugin-parsehub@main
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
用 uv 安装后还需要在 nonebot2 项目根目录的 `pyproject.toml` 里注册插件
|
|
63
|
+
(nb-cli 会自动完成这一步):
|
|
64
|
+
|
|
65
|
+
```toml
|
|
66
|
+
[tool.nonebot]
|
|
67
|
+
plugins = ["nonebot_plugin_parsehub"]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## ⚙️ 配置
|
|
71
|
+
|
|
72
|
+
在 nonebot2 项目的 `.env` 文件中添加下表中的配置, 全部可选(零配置即可加载运行)
|
|
73
|
+
|
|
74
|
+
| 配置项 | 必填 | 默认值 | 说明 |
|
|
75
|
+
| :--- | :---: | :--- | :--- |
|
|
76
|
+
| `PARSEHUB_AUTO` | 否 | `true` | 群里出现链接时自动解析(总开关) |
|
|
77
|
+
| `PARSEHUB_MAX_FILE_MB` | 否 | `100` | 单个媒体文件体积上限(MB),超过直接跳过;`0` = 不限 |
|
|
78
|
+
| `PARSEHUB_MAX_SECONDS` | 否 | `300` | 视频时长上限(秒),超过只发封面 + 一句提示、不下载;`0` = 不限 |
|
|
79
|
+
| `PARSEHUB_PLATFORMS` | 否 | 无 | 平台白名单:只解析这些平台,留空 = 全部。可写平台 id(`xhs`)或中文名(`小红书`) |
|
|
80
|
+
| `PARSEHUB_DEDUPE_SECONDS` | 否 | `120` | 同一会话内同一链接的去重窗口(秒),`0` = 不去重 |
|
|
81
|
+
| `PARSEHUB_CONCURRENCY` | 否 | `2` | 同时处理的任务数 |
|
|
82
|
+
| `PARSEHUB_PROXY` | 否 | 无 | 解析 / 下载代理,如 `http://127.0.0.1:7890` |
|
|
83
|
+
| `PARSEHUB_PROXY_PLATFORMS` | 否 | 海外 8 平台 | 只对这些平台走代理,留空 = 全部走(列表要用合法 JSON) |
|
|
84
|
+
| `PARSEHUB_COOKIE` | 否 | 无 | 兜底 cookie(优先读 parsehub 自己的 `cookies.toml`) |
|
|
85
|
+
| `PARSEHUB_TIMEOUT` | 否 | `180` | 解析 + 下载总超时(秒) |
|
|
86
|
+
| `PARSEHUB_CACHE_TTL_HOURS` | 否 | `24` | 媒体缓存保留小时数,命中就直接发本地那份;`0` = 关闭缓存 |
|
|
87
|
+
| `PARSEHUB_CACHE_MAX_MB` | 否 | `1024` | 媒体缓存总容量上限(MB),超了按最久没用过的删 |
|
|
88
|
+
| `PARSEHUB_CARD` | 否 | `true` | 是否发信息卡片图(失败自动退回文字) |
|
|
89
|
+
| `PARSEHUB_CARD_FONT` | 否 | 无 | 中文字体路径,留空则自动找系统字体 / 下载 |
|
|
90
|
+
| `PARSEHUB_FORWARD_TIMEOUT` | 否 | `60` | 合并转发等待协议端的秒数,超时才逐条发 |
|
|
91
|
+
| `PARSEHUB_SEND_TEXT` | 否 | `true` | 是否发送标题 / 正文 |
|
|
92
|
+
| `PARSEHUB_MAX_CHARS` | 否 | `300` | 正文最多发送多少字符 |
|
|
93
|
+
| `PARSEHUB_FAIL_NOTICE` | 否 | `true` | 视频发送失败时是否额外发文字提示 |
|
|
94
|
+
| `PARSEHUB_DELIVERY` | 否 | `file` | 媒体交给协议端:`file` = 本机路径(要求同机)/ `http` = 中转地址 / `base64` = 内容内联 |
|
|
95
|
+
| `PARSEHUB_RELAY_HOST` | 否 | 无 | 中转服务对外地址,留空自动探测内网 IP(仅 `http` 模式) |
|
|
96
|
+
| `PARSEHUB_RELAY_PORT` | 否 | `21473` | 中转服务端口(仅 `http` 模式) |
|
|
97
|
+
| `PARSEHUB_TASK_TTL_MINUTES` | 否 | `30` | 临时文件保留分钟数 |
|
|
98
|
+
|
|
99
|
+
> 列表类配置必须是合法 JSON(如 `PARSEHUB_PROXY_PLATFORMS=["twitter", "youtube"]`);
|
|
100
|
+
> 数值项有下界校验(如 `PARSEHUB_TIMEOUT >= 5`),写 0 或负数会在启动时直接报错。
|
|
101
|
+
> 哪些群 / 私聊解析由命令 `/解析开关` 控制,没有对应的配置项。
|
|
102
|
+
|
|
103
|
+
需要登录态的平台(X、Instagram、TikTok、豆瓣、微信视频号 等),cookie 优先从 ParseHub 自己的配置读取
|
|
104
|
+
(`ph set cookie <平台>`),也可以用 `PARSEHUB_COOKIE` 兜底。
|
|
105
|
+
|
|
106
|
+
## 🎉 使用
|
|
107
|
+
|
|
108
|
+
### 指令表
|
|
109
|
+
|
|
110
|
+
| 指令 | 权限 | 需要@ | 范围 | 说明 |
|
|
111
|
+
| :--- | :---: | :---: | :---: | :--- |
|
|
112
|
+
| `/解析 <链接或分享文案>`(别名 `/ph`、`/解析链接`) | 群员 | 否 | 群聊/私聊 | 手动解析,不做去重拦截 |
|
|
113
|
+
| `/解析平台` | 群员 | 否 | 群聊/私聊 | 列出支持的平台 |
|
|
114
|
+
| `/解析开关 [开\|关]`、`/解析开`、`/解析关` | 主人/群管 | 否 | 群聊/私聊 | 按会话开关解析(私聊仅主人) |
|
|
115
|
+
| `/解析状态` | 群员 | 否 | 群聊/私聊 | 查看当前会话的开关状态 |
|
|
116
|
+
|
|
117
|
+
> 命令按 NoneBot 的 `COMMAND_START` 匹配(默认只有 `/`)。想让无前缀写法也算命令,把 `COMMAND_START` 加上 `""`。
|
|
118
|
+
|
|
119
|
+
### 触发规则
|
|
120
|
+
|
|
121
|
+
- 群里发链接或分享文案即自动解析,一条消息最多处理 **3 个**链接
|
|
122
|
+
- `/解析关` 后该会话不再自动解析;手动 `/解析` 只对超管 / 群管理有效
|
|
123
|
+
- 不带前缀的「解析 <链接>」**不是**命令,会当作普通消息自动解析(带去重、不额外提示)
|
|
124
|
+
|
|
125
|
+
### 解析开关
|
|
126
|
+
|
|
127
|
+
- 以**会话**为维度(群按群号,私聊按对方 QQ),群和私聊**默认都开**
|
|
128
|
+
- 关掉后该会话不再自动解析,手动解析仅超管 / 群管可用;状态所有人可看
|
|
129
|
+
- 超管与群主 / 管理员可修改;状态存在 `sessions.json`,只记录与默认值不同的会话
|
|
130
|
+
|
|
131
|
+
### 媒体缓存
|
|
132
|
+
|
|
133
|
+
- **按作品跨会话共享**:A 群发过的视频,B 群再发直接发本地那份
|
|
134
|
+
- **命中即跳过**:不解析、不下载、不画卡(卡片图也一起缓存)
|
|
135
|
+
- **保留 24 小时**(命中续期),总量上限 1GB,超了按最久没用过的删
|
|
136
|
+
- **不同写法归一**:`b23.tv/xxx` 与 `bilibili.com/video/BVxxx` 是同一个作品
|
|
137
|
+
- 时间线:120 秒内同会话重复 → 静默;120 秒 ~ 24 小时 → 直接发缓存;超过 24 小时 → 重新解析
|
|
138
|
+
- 手动 `/解析 <链接>` 同样吃缓存(只是不做去重拦截)
|
|
139
|
+
|
|
140
|
+
### 说明
|
|
141
|
+
|
|
142
|
+
- 视频被协议端拒绝时只点失败表情,**不会**退回群文件
|
|
143
|
+
- 投递三选一、**互不兜底**:`file` 发本机路径(协议端同机)/ `http` 发中转地址(跨机)/ `base64` 内容内联(请求体约 +1/3)
|
|
144
|
+
- parsehub 全程同步阻塞,插件把所有调用放进 `asyncio.to_thread`;首次导入懒加载,不影响启动
|
|
145
|
+
- 卡片用 Pillow 画,封面三级兜底(`thumb_url` → 图片媒体 → 视频抽帧),没装 Pillow 或没有中文字体时自动退回文字;
|
|
146
|
+
字体优先 `PARSEHUB_CARD_FONT` → 缓存目录 → 系统字体 → 下载(sha256 校验),缺字用回退字体补,emoji 用 Twemoji 位图字体
|
|
147
|
+
|
|
148
|
+
## 🛠️ 开发
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
uv sync --all-groups # 安装依赖(含测试)
|
|
152
|
+
uv run poe check # 代码检查 + 格式检查 + 测试(CI 同款)
|
|
153
|
+
uv run pytest -q # 只跑测试
|
|
154
|
+
uv run poe live # 真实链路自检(解析 → 下载 → 中转取回)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 代码结构
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
src/nonebot_plugin_parsehub/
|
|
161
|
+
├── triggers.py # matcher / 命令:只决定「处理谁」
|
|
162
|
+
├── service.py # 编排:每个结局都是 Outcome 里的一个值
|
|
163
|
+
├── delivery.py # 三种上传模式 + 合并转发/逐条 + 统一失败记账
|
|
164
|
+
├── feedback.py # 表情回应与用户提示文案
|
|
165
|
+
├── cache.py # 作品级媒体缓存(哈希目录、原子写)
|
|
166
|
+
├── dedupe.py # 会话级去重(claim / commit / release)
|
|
167
|
+
├── relay.py # 只读中转服务(只服务 relay/)
|
|
168
|
+
├── parsehub_client.py # ParseHub 懒加载 + 线程池封装
|
|
169
|
+
├── card/ # fonts / cover / layout + CardRenderer
|
|
170
|
+
└── app.py # 装配与生命周期
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
模块级单例已全部移除:`App` 持有设置、客户端、缓存、去重、开关、中转、投递和卡片,
|
|
174
|
+
测试直接 `App.build(...)` 造独立实例。
|
|
175
|
+
|
|
176
|
+
### 代码风格
|
|
177
|
+
|
|
178
|
+
遵循 [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html):行宽 80,Google 风格文档字符串,
|
|
179
|
+
ruff 强制 `pydocstyle(google)` + `pep8-naming` + `isort`。
|
|
180
|
+
|
|
181
|
+
有意保留的偏差:包内用相对导入(本地插件加载时绝对自引用会 ImportError);NoneBot 按名字导入;
|
|
182
|
+
中文文档字符串以「。」结尾(关闭 `D415`);测试不强制文档字符串。
|
|
183
|
+
|
|
184
|
+
## 📄 许可
|
|
185
|
+
|
|
186
|
+
[MIT](LICENSE)
|
|
187
|
+
|
|
188
|
+
## 🙏 致谢
|
|
189
|
+
|
|
190
|
+
- [z-mio/ParseHub](https://github.com/z-mio/ParseHub):本插件的解析能力全部来自这个项目
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "nonebot-plugin-parsehub"
|
|
3
|
-
version = "0.1
|
|
3
|
+
version = "1.0.1"
|
|
4
4
|
description = "基于 ParseHub 的社交媒体链接解析插件:群里出现分享链接时自动解析并发送媒体"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -27,7 +27,9 @@ dependencies = [
|
|
|
27
27
|
"nonebot-adapter-onebot>=2.4.6,<3.0.0",
|
|
28
28
|
"nonebot-plugin-localstore>=0.7.4,<1.0.0",
|
|
29
29
|
"nonebot2>=2.5.0,<3.0.0",
|
|
30
|
-
"
|
|
30
|
+
"numpy>=2.0.0,<3.0.0",
|
|
31
|
+
"opencv-python-headless>=4.13.0.92",
|
|
32
|
+
"parsehub>=2.3.10,<3.0.0",
|
|
31
33
|
"pillow>=12.1.0,<13.0.0",
|
|
32
34
|
"pydantic>=2.0.0,<3.0.0",
|
|
33
35
|
]
|
|
@@ -57,7 +59,7 @@ test = [
|
|
|
57
59
|
]
|
|
58
60
|
|
|
59
61
|
[build-system]
|
|
60
|
-
requires = ["uv_build>=0.10.0"]
|
|
62
|
+
requires = ["uv_build>=0.10.0,<0.14"]
|
|
61
63
|
build-backend = "uv_build"
|
|
62
64
|
|
|
63
65
|
[tool.nonebot]
|
|
@@ -65,6 +67,14 @@ plugins = ["nonebot_plugin_parsehub"]
|
|
|
65
67
|
|
|
66
68
|
[tool.poe.tasks]
|
|
67
69
|
test = "pytest -q"
|
|
70
|
+
lint = "ruff check ."
|
|
71
|
+
fmt = "ruff format --check ."
|
|
72
|
+
check = [
|
|
73
|
+
"lint",
|
|
74
|
+
"fmt",
|
|
75
|
+
"test",
|
|
76
|
+
]
|
|
77
|
+
live = "python scripts/live_check.py"
|
|
68
78
|
|
|
69
79
|
[tool.pytest.ini_options]
|
|
70
80
|
asyncio_mode = "auto"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "nonebot-plugin-parsehub"
|
|
3
|
-
version = "0.1
|
|
3
|
+
version = "1.0.1"
|
|
4
4
|
description = "基于 ParseHub 的社交媒体链接解析插件:群里出现分享链接时自动解析并发送媒体"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -20,7 +20,10 @@ dependencies = [
|
|
|
20
20
|
"nonebot-adapter-onebot>=2.4.6,<3.0.0",
|
|
21
21
|
"nonebot-plugin-localstore>=0.7.4,<1.0.0",
|
|
22
22
|
"nonebot2>=2.5.0,<3.0.0",
|
|
23
|
-
|
|
23
|
+
# 卡片渐变遮罩与视频抽帧是代码里直接 import 的,必须显式声明
|
|
24
|
+
"numpy>=2.0.0,<3.0.0",
|
|
25
|
+
"opencv-python-headless>=4.13.0.92",
|
|
26
|
+
"parsehub>=2.3.10,<3.0.0",
|
|
24
27
|
"pillow>=12.1.0,<13.0.0",
|
|
25
28
|
"pydantic>=2.0.0,<3.0.0",
|
|
26
29
|
]
|
|
@@ -46,7 +49,7 @@ test = [
|
|
|
46
49
|
]
|
|
47
50
|
|
|
48
51
|
[build-system]
|
|
49
|
-
requires = ["uv_build>=0.10.0"]
|
|
52
|
+
requires = ["uv_build>=0.10.0,<0.14"]
|
|
50
53
|
build-backend = "uv_build"
|
|
51
54
|
|
|
52
55
|
[tool.nonebot]
|
|
@@ -54,6 +57,11 @@ plugins = ["nonebot_plugin_parsehub"]
|
|
|
54
57
|
|
|
55
58
|
[tool.poe.tasks]
|
|
56
59
|
test = "pytest -q"
|
|
60
|
+
lint = "ruff check ."
|
|
61
|
+
fmt = "ruff format --check ."
|
|
62
|
+
# CI 同款:检查 + 格式 + 测试,本地提交前跑这一个就够
|
|
63
|
+
check = ["lint", "fmt", "test"]
|
|
64
|
+
live = "python scripts/live_check.py"
|
|
57
65
|
|
|
58
66
|
[tool.pytest.ini_options]
|
|
59
67
|
asyncio_mode = "auto"
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: parsehub-plugin
|
|
3
|
+
type: module-design
|
|
4
|
+
status: draft
|
|
5
|
+
title: nonebot_plugin_parsehub 模块
|
|
6
|
+
parent: parsehub-architecture
|
|
7
|
+
tags: [nonebot, parsehub, module]
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# nonebot_plugin_parsehub
|
|
11
|
+
|
|
12
|
+
## Responsibility
|
|
13
|
+
|
|
14
|
+
把一个链接变成「发出去的东西」:识别平台、去重、缓存、解析、下载、画卡、投递,
|
|
15
|
+
并把每个结局表达成用户看得见的反馈。
|
|
16
|
+
|
|
17
|
+
## Boundary
|
|
18
|
+
|
|
19
|
+
- **允许依赖**:nonebot2、onebot v11 适配器、parsehub、Pillow、httpx、localstore、
|
|
20
|
+
numpy、opencv-python-headless(后两者只在 `card` 里用)。
|
|
21
|
+
- **禁止**:`triggers` 之外的模块 import matcher / `on_command`;`delivery` 之外的模块
|
|
22
|
+
调用 `sender.send`;`service` import `Bot`。
|
|
23
|
+
- **日志**:一律用 `nonebot.logger`(官方指导),领域模块也不例外;除 logger 之外,
|
|
24
|
+
`cache`、`dedupe`、`relay`、`keys`、`platforms`、`budget`、`media`、`card` 不碰
|
|
25
|
+
nonebot 的 matcher / `Bot` / 事件类型。
|
|
26
|
+
- 包内一律相对 import:插件既可能作为已安装包加载,也可能作为本地插件加载。
|
|
27
|
+
|
|
28
|
+
## Module map
|
|
29
|
+
|
|
30
|
+
| 模块 | 角色 |
|
|
31
|
+
| :--- | :--- |
|
|
32
|
+
| `app.py` | `App` 装配、启动(中转 / 字体 / 预热 / 清扫)、关闭 |
|
|
33
|
+
| `config.py` | `Config`(校验)+ `Settings`(归一化只读快照) |
|
|
34
|
+
| `triggers.py` | matcher 注册、自动规则、四个命令 |
|
|
35
|
+
| `service.py` | 编排流水线与 `Outcome` |
|
|
36
|
+
| `delivery.py` | `Sender` 协议、三种上传模式、合并转发、`MediaReport` |
|
|
37
|
+
| `feedback.py` | `Reaction` 与全部提示文案 |
|
|
38
|
+
| `parsehub_client.py` | 懒加载客户端、cookie 读取 |
|
|
39
|
+
| `cache.py` | 作品级媒体缓存(原子写、TTL、容量) |
|
|
40
|
+
| `dedupe.py` | 会话级去重 |
|
|
41
|
+
| `relay.py` | 只读中转服务 |
|
|
42
|
+
| `paths.py` | cache / relay / data 目录约定 |
|
|
43
|
+
| `keys.py` | 域名白名单、链接提取、作品 key 归一 |
|
|
44
|
+
| `platforms.py` | 平台表、白名单别名、时长单位 |
|
|
45
|
+
| `media.py` | `MediaKind` / `MediaRef` / `MediaFile`、体积探测 |
|
|
46
|
+
| `errors.py` | `ErrorKind` / `ParseFailure` / 异常分类 |
|
|
47
|
+
| `budget.py` | 单次工作的超时预算 |
|
|
48
|
+
| `card/` | 卡片渲染,见 `parsehub-card` |
|
|
49
|
+
|
|
50
|
+
## Contracts
|
|
51
|
+
|
|
52
|
+
- `service.handle(app, sender, event, url, manual=False) -> Result`;`Result.ok` 决定终态
|
|
53
|
+
表情,`Result.notice` 由 `handle` 发送。
|
|
54
|
+
- `Delivery.send_media(...) -> MediaReport`:`sent` / `failed` / `skipped` 三份明细,
|
|
55
|
+
不抛异常;`MediaReport.ok` 是「至少发出去一个且没有失败」。
|
|
56
|
+
- `CardRenderer.render(...) -> Path | None`:任何失败返回 `None`,调用方退回文字。
|
|
57
|
+
- `MediaCache.store(keys, files, card=, caption=, seconds=) -> Path | None`:先写
|
|
58
|
+
`.tmp` 再 `os.replace`,过期条目重新缓存不会混入旧文件。
|
|
59
|
+
- `ParseHubClient` 的所有方法都是异步的,内部 `to_thread`;异常由 `errors.translate`
|
|
60
|
+
在 `service` 边界分类。
|
|
61
|
+
|
|
62
|
+
## Decisions
|
|
63
|
+
|
|
64
|
+
- 不额外包一层 `domain.Work`:`MediaRef` / `MediaFile` / `CacheEntry` 就是领域模型,
|
|
65
|
+
避免复制 parsehub 的结果对象。
|
|
66
|
+
- 平台白名单在 `Settings` 里归一成 id 集合,运行期只做集合查找。
|
|
67
|
+
- 配置键是唯一入口:`Config` 只负责读取与校验,运行期一律用 `Settings`,
|
|
68
|
+
不再散读 `plugin_config`。
|
|
69
|
+
- 命令严格按 `COMMAND_START` 匹配:`should_auto` 不排除命令文本,靠命令匹配器的
|
|
70
|
+
`priority=10` + `block=True` 先收口。裸写的「解析 <链接>」不是命令,按普通消息
|
|
71
|
+
自动解析——默认前缀只有 `/`,若自动规则也跳过它,消息会被静默丢弃。
|
|
72
|
+
- `paths` 把 localstore 当硬依赖(`require` 后 import),不保留临时目录兜底。
|
|
73
|
+
- 日志统一用 `nonebot.logger`:框架的 loguru 已经管住格式、插件标签与 `LOG_LEVEL`,
|
|
74
|
+
在包内再桥接一层 stdlib logging 只是多一个可能失效的中间层。
|
|
75
|
+
- 第三方依赖全是**硬依赖**:parsehub / Pillow / numpy / opencv / localstore 缺了就是
|
|
76
|
+
装错了,代码里不写 `except ImportError` 兜底,也不往别的目录写;降级只发生在产品层
|
|
77
|
+
(卡片→文字、字体→系统字体、体积探测→未知)。
|
|
78
|
+
|
|
79
|
+
## Known limitations
|
|
80
|
+
|
|
81
|
+
- `sessions.json` 带 schema 版本(当前 2);版本不符时按默认值处理。
|
|
82
|
+
- 平台白名单里无法表达「除某平台外全部」。
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Copyright 2026 ByteColtX
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""ParseHub 链接解析插件(OneBot v11)。
|
|
5
|
+
|
|
6
|
+
群里出现受支持的分享链接时自动解析,并把视频 / 图集 / 正文发回群里;也可以用
|
|
7
|
+
命令 ``解析 <链接或分享文案>`` 手动触发。
|
|
8
|
+
|
|
9
|
+
分层:
|
|
10
|
+
|
|
11
|
+
- ``triggers``:matcher 与命令,只负责「该不该处理」和「把链接交给谁」;
|
|
12
|
+
- ``service``:解析编排,每个结局都是 ``Outcome`` 里的一个值;
|
|
13
|
+
- ``delivery`` / ``feedback``:媒体投递与用户可见反馈;
|
|
14
|
+
- ``cache`` / ``dedupe`` / ``relay`` / ``card``:缓存、去重、中转、卡片;
|
|
15
|
+
- ``app``:把上面这些装在一起,由本模块构建一次。
|
|
16
|
+
|
|
17
|
+
解析与下载全部由 ``parsehub`` 完成,插件只负责调度和发送。parsehub 是同步阻塞
|
|
18
|
+
的(内部还用了 requests),因此所有调用都通过 ``asyncio.to_thread`` 执行,不会
|
|
19
|
+
阻塞 NoneBot 的事件循环。
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from nonebot import get_driver
|
|
25
|
+
from nonebot.plugin import PluginMetadata
|
|
26
|
+
|
|
27
|
+
from . import triggers
|
|
28
|
+
from .app import App
|
|
29
|
+
from .config import Config, Settings, plugin_config
|
|
30
|
+
|
|
31
|
+
__plugin_meta__ = PluginMetadata(
|
|
32
|
+
name="ParseHub 链接解析",
|
|
33
|
+
description=(
|
|
34
|
+
"群里出现受支持的分享链接时,用 ParseHub 自动解析并发送媒体"
|
|
35
|
+
"(视频 / 图集 / 正文)。"
|
|
36
|
+
),
|
|
37
|
+
usage=(
|
|
38
|
+
"发链接即可自动解析;命令:/解析 <链接或分享文案>、/解析平台、"
|
|
39
|
+
"/解析开关 [开|关](超管 / 群管理)、/解析状态"
|
|
40
|
+
),
|
|
41
|
+
type="application",
|
|
42
|
+
homepage="https://github.com/ByteColtX/nonebot-plugin-parsehub",
|
|
43
|
+
config=Config,
|
|
44
|
+
supported_adapters={"~onebot.v11"},
|
|
45
|
+
extra={"parsehub": "https://github.com/z-mio/ParseHub"},
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# 进程内唯一的插件实例;测试可以自己 App.build() 一个。
|
|
49
|
+
APP = App.build(Settings.from_config(plugin_config))
|
|
50
|
+
|
|
51
|
+
# matcher 注册(处理函数闭包捕获 APP);注册本身有副作用,不需要保留返回值。
|
|
52
|
+
triggers.create_matchers(APP)
|
|
53
|
+
|
|
54
|
+
driver = get_driver()
|
|
55
|
+
driver.on_startup(APP.startup)
|
|
56
|
+
driver.on_shutdown(APP.shutdown)
|