nonebot-plugin-parser 2.0.8__tar.gz → 2.0.10__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_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/PKG-INFO +5 -7
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/README.md +2 -4
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/pyproject.toml +7 -7
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/__init__.py +2 -2
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/config.py +6 -5
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/matchers/__init__.py +2 -2
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/matchers/preprocess.py +13 -13
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/parsers/bilibili/__init__.py +19 -31
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/renders/base.py +7 -4
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/renders/common.py +86 -118
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/constants.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/download/__init__.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/download/task.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/download/ytdlp.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/exception.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/helper.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/matchers/filter.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/parsers/__init__.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/parsers/acfun.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/parsers/base.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/parsers/bilibili/article.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/parsers/bilibili/common.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/parsers/bilibili/dynamic.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/parsers/bilibili/favlist.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/parsers/bilibili/live.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/parsers/bilibili/opus.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/parsers/bilibili/video.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/parsers/cookie.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/parsers/data.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/parsers/douyin/__init__.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/parsers/douyin/slides.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/parsers/douyin/video.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/parsers/kuaishou.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/parsers/nga.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/parsers/tiktok.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/parsers/twitter.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/parsers/weibo.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/parsers/xiaohongshu.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/parsers/youtube.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/renders/__init__.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/renders/default.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/renders/resources/HYSongYunLangHeiW-1.ttf +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/renders/resources/bilibili.png +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/renders/resources/douyin.png +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/renders/resources/kuaishou.png +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/renders/resources/media_button.png +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/renders/resources/tiktok.png +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/renders/resources/twitter.png +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/renders/resources/weibo.png +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/renders/resources/xiaohongshu.png +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/renders/resources/youtube.png +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/renders/templates/weibo.html.jinja +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/renders/weibo.py +0 -0
- {nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/utils.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: nonebot-plugin-parser
|
|
3
|
-
Version: 2.0.
|
|
4
|
-
Summary: NoneBot2
|
|
3
|
+
Version: 2.0.10
|
|
4
|
+
Summary: NoneBot2 链接分享解析 Alconna 版, 通用媒体卡片渲染(PIL 实现), 支持 B站/抖音/快手/微博/小红书/youtube/tiktok/twitter/acfun/nga
|
|
5
5
|
Keywords: nonebot,nonebot2,video,bilibili,youtube,tiktok,twitter,kuaishou,acfun,weibo,xiaohongshu,nga,douyin
|
|
6
6
|
Author: fllesser
|
|
7
7
|
Author-email: fllesser <fllessive@gmail.com>
|
|
@@ -19,7 +19,7 @@ Requires-Dist: nonebot-plugin-localstore>=0.7.4,<1.0.0
|
|
|
19
19
|
Requires-Dist: nonebot-plugin-apscheduler>=0.5.0,<1.0.0
|
|
20
20
|
Requires-Dist: nonebot-plugin-alconna>=0.59.4
|
|
21
21
|
Requires-Dist: nonebot-plugin-uninfo>=0.9.0
|
|
22
|
-
Requires-Dist: nonebot-plugin-htmlkit>=0.1.
|
|
22
|
+
Requires-Dist: nonebot-plugin-htmlkit>=0.1.0rc3 ; extra == 'htmlkit'
|
|
23
23
|
Requires-Dist: jinja2>=3.1.6 ; extra == 'htmlkit'
|
|
24
24
|
Requires-Python: >=3.10
|
|
25
25
|
Project-URL: IssueTracker, https://github.com/fllesser/nonebot-plugin-parser/issues
|
|
@@ -52,8 +52,6 @@ Description-Content-Type: text/markdown
|
|
|
52
52
|
|
|
53
53
|
## 📖 介绍
|
|
54
54
|
|
|
55
|
-
[nonebot-plugin-resolver](https://github.com/zhiyu1998/nonebot-plugin-resolver) 重制版
|
|
56
|
-
|
|
57
55
|
| 平台 | 触发的消息形态 | 视频 | 图集 | 音频 |
|
|
58
56
|
| ------- | ------------------------------------- | ---- | ---- | ---- |
|
|
59
57
|
| B站 | BV号/链接(包含短链,BV,av)/卡片/小程序 | ✅ | ✅ | ✅ |
|
|
@@ -69,7 +67,7 @@ Description-Content-Type: text/markdown
|
|
|
69
67
|
支持的链接,可参考 [测试链接](https://github.com/fllesser/nonebot-plugin-parser/blob/master/test_url.md)
|
|
70
68
|
|
|
71
69
|
## 🎨 效果图
|
|
72
|
-
|
|
70
|
+
插件默认启用 PIL 实现的通用媒体卡片渲染,效果图如下
|
|
73
71
|
<div align="center">
|
|
74
72
|
|
|
75
73
|
<img src="https://raw.githubusercontent.com/fllesser/nonebot-plugin-parser/refs/heads/resources/resources/renderdamine/video.png" width="160" />
|
|
@@ -82,7 +80,7 @@ Description-Content-Type: text/markdown
|
|
|
82
80
|
|
|
83
81
|
## 💿 安装
|
|
84
82
|
> [!Warning]
|
|
85
|
-
> **如果你已经在使用 nonebot-plugin-resolver,请在安装此插件前卸载**
|
|
83
|
+
> **如果你已经在使用 nonebot-plugin-resolver[2],请在安装此插件前卸载**
|
|
86
84
|
|
|
87
85
|
<details open>
|
|
88
86
|
<summary>使用 nb-cli 安装/更新</summary>
|
|
@@ -22,8 +22,6 @@
|
|
|
22
22
|
|
|
23
23
|
## 📖 介绍
|
|
24
24
|
|
|
25
|
-
[nonebot-plugin-resolver](https://github.com/zhiyu1998/nonebot-plugin-resolver) 重制版
|
|
26
|
-
|
|
27
25
|
| 平台 | 触发的消息形态 | 视频 | 图集 | 音频 |
|
|
28
26
|
| ------- | ------------------------------------- | ---- | ---- | ---- |
|
|
29
27
|
| B站 | BV号/链接(包含短链,BV,av)/卡片/小程序 | ✅ | ✅ | ✅ |
|
|
@@ -39,7 +37,7 @@
|
|
|
39
37
|
支持的链接,可参考 [测试链接](https://github.com/fllesser/nonebot-plugin-parser/blob/master/test_url.md)
|
|
40
38
|
|
|
41
39
|
## 🎨 效果图
|
|
42
|
-
|
|
40
|
+
插件默认启用 PIL 实现的通用媒体卡片渲染,效果图如下
|
|
43
41
|
<div align="center">
|
|
44
42
|
|
|
45
43
|
<img src="https://raw.githubusercontent.com/fllesser/nonebot-plugin-parser/refs/heads/resources/resources/renderdamine/video.png" width="160" />
|
|
@@ -52,7 +50,7 @@
|
|
|
52
50
|
|
|
53
51
|
## 💿 安装
|
|
54
52
|
> [!Warning]
|
|
55
|
-
> **如果你已经在使用 nonebot-plugin-resolver,请在安装此插件前卸载**
|
|
53
|
+
> **如果你已经在使用 nonebot-plugin-resolver[2],请在安装此插件前卸载**
|
|
56
54
|
|
|
57
55
|
<details open>
|
|
58
56
|
<summary>使用 nb-cli 安装/更新</summary>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "nonebot-plugin-parser"
|
|
3
|
-
version = "2.0.
|
|
4
|
-
description = "NoneBot2
|
|
3
|
+
version = "2.0.10"
|
|
4
|
+
description = "NoneBot2 链接分享解析 Alconna 版, 通用媒体卡片渲染(PIL 实现), 支持 B站/抖音/快手/微博/小红书/youtube/tiktok/twitter/acfun/nga"
|
|
5
5
|
authors = [{ "name" = "fllesser", "email" = "fllessive@gmail.com" }]
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
requires-python = ">=3.10"
|
|
@@ -41,7 +41,7 @@ dependencies = [
|
|
|
41
41
|
]
|
|
42
42
|
|
|
43
43
|
[project.optional-dependencies]
|
|
44
|
-
htmlkit = ["nonebot-plugin-htmlkit>=0.1.
|
|
44
|
+
htmlkit = ["nonebot-plugin-htmlkit>=0.1.0rc3", "jinja2>=3.1.6"]
|
|
45
45
|
|
|
46
46
|
[project.urls]
|
|
47
47
|
Repository = "https://github.com/fllesser/nonebot-plugin-parser"
|
|
@@ -54,7 +54,7 @@ dev = [
|
|
|
54
54
|
"nonebot2[fastapi]>=2.4.3,<3.0.0",
|
|
55
55
|
"nonebot-adapter-telegram>=0.1.0b20",
|
|
56
56
|
"pre-commit>=4.3.0",
|
|
57
|
-
"ruff>=0.
|
|
57
|
+
"ruff>=0.14.0,<1.0.0",
|
|
58
58
|
"bump-my-version>=1.2.4",
|
|
59
59
|
]
|
|
60
60
|
|
|
@@ -72,7 +72,7 @@ test = [
|
|
|
72
72
|
all_extras = ["nonebot-plugin-htmlkit>=0.1.0rc1", "jinja2>=3.1.6"]
|
|
73
73
|
|
|
74
74
|
[tool.uv]
|
|
75
|
-
required-version = ">=0.
|
|
75
|
+
required-version = ">=0.9.2"
|
|
76
76
|
default-groups = ["test", "dev", "all_extras"]
|
|
77
77
|
|
|
78
78
|
[tool.nonebot]
|
|
@@ -99,7 +99,7 @@ addopts = [
|
|
|
99
99
|
[tool.poe.tasks]
|
|
100
100
|
test_others = "pytest tests/others --cov=src --cov-report=xml:coverage1.xml --junitxml=junit1.xml -n auto"
|
|
101
101
|
test_parsers = "pytest tests/parsers --cov=src --cov-report=xml:coverage2.xml --junitxml=junit2.xml -n auto"
|
|
102
|
-
test_render = "pytest tests/render --cov=src --cov-report=xml:coverage3.xml --junitxml=junit3.xml
|
|
102
|
+
test_render = "pytest tests/render --cov=src --cov-report=xml:coverage3.xml --junitxml=junit3.xml"
|
|
103
103
|
bump = "bump-my-version bump"
|
|
104
104
|
show-bump = "bump-my-version show-bump"
|
|
105
105
|
|
|
@@ -186,7 +186,7 @@ build-backend = "uv_build"
|
|
|
186
186
|
|
|
187
187
|
|
|
188
188
|
[tool.bumpversion]
|
|
189
|
-
current_version = "2.0.
|
|
189
|
+
current_version = "2.0.10"
|
|
190
190
|
commit = true
|
|
191
191
|
message = "🔖 release: bump vesion from {current_version} to {new_version}"
|
|
192
192
|
tag = true
|
{nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/__init__.py
RENAMED
|
@@ -11,8 +11,8 @@ from .matchers import clear_result_cache
|
|
|
11
11
|
from .utils import safe_unlink
|
|
12
12
|
|
|
13
13
|
__plugin_meta__ = PluginMetadata(
|
|
14
|
-
name="
|
|
15
|
-
description="
|
|
14
|
+
name="链接分享解析 Alconna 版",
|
|
15
|
+
description="全新通用媒体卡片渲染(PIL 实现), 支持 B站/抖音/快手/微博/小红书/youtube/tiktok/twitter/acfun/nga",
|
|
16
16
|
usage="发送支持平台的(BV号/链接/小程序/卡片)即可",
|
|
17
17
|
type="application",
|
|
18
18
|
homepage="https://github.com/fllesser/nonebot-plugin-parser",
|
{nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/config.py
RENAMED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
|
+
from functools import cached_property
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
from typing import Literal
|
|
4
5
|
|
|
@@ -56,22 +57,22 @@ class Config(BaseModel):
|
|
|
56
57
|
parser_need_forward_contents: bool = True
|
|
57
58
|
"""是否需要转发媒体内容"""
|
|
58
59
|
|
|
59
|
-
@
|
|
60
|
+
@cached_property
|
|
60
61
|
def nickname(self) -> str:
|
|
61
62
|
"""全局名称"""
|
|
62
63
|
return _nickname
|
|
63
64
|
|
|
64
|
-
@
|
|
65
|
+
@cached_property
|
|
65
66
|
def cache_dir(self) -> Path:
|
|
66
67
|
"""插件缓存目录"""
|
|
67
68
|
return _cache_dir
|
|
68
69
|
|
|
69
|
-
@
|
|
70
|
+
@cached_property
|
|
70
71
|
def config_dir(self) -> Path:
|
|
71
72
|
"""插件配置目录"""
|
|
72
73
|
return _config_dir
|
|
73
74
|
|
|
74
|
-
@
|
|
75
|
+
@cached_property
|
|
75
76
|
def data_dir(self) -> Path:
|
|
76
77
|
"""插件数据目录"""
|
|
77
78
|
return _data_dir
|
|
@@ -131,7 +132,7 @@ class Config(BaseModel):
|
|
|
131
132
|
"""是否在解析结果中附加原始URL"""
|
|
132
133
|
return self.parser_append_url
|
|
133
134
|
|
|
134
|
-
@
|
|
135
|
+
@cached_property
|
|
135
136
|
def custom_font(self) -> Path | None:
|
|
136
137
|
"""自定义字体"""
|
|
137
138
|
return (self.data_dir / self.parser_custom_font) if self.parser_custom_font else None
|
|
@@ -12,7 +12,7 @@ from ..config import pconfig
|
|
|
12
12
|
from ..parsers import BaseParser, ParseResult
|
|
13
13
|
from ..renders import get_renderer
|
|
14
14
|
from ..utils import LimitedSizeDict
|
|
15
|
-
from .preprocess import
|
|
15
|
+
from .preprocess import Keyword, KwdRegexMatched, on_keyword_regex
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def _get_enabled_parser_classes() -> list[type[BaseParser]]:
|
|
@@ -60,7 +60,7 @@ parser_matcher = on_keyword_regex(*_get_enabled_patterns())
|
|
|
60
60
|
async def _(
|
|
61
61
|
event: Event,
|
|
62
62
|
keyword: str = Keyword(),
|
|
63
|
-
matched: re.Match[str] =
|
|
63
|
+
matched: re.Match[str] = KwdRegexMatched(),
|
|
64
64
|
):
|
|
65
65
|
"""统一的解析处理器"""
|
|
66
66
|
# 响应用户处理中
|
|
@@ -13,9 +13,9 @@ from nonebot_plugin_alconna.uniseg import Hyper, UniMsg
|
|
|
13
13
|
|
|
14
14
|
from .filter import is_not_in_disabled_groups
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
PSR_KWD_KEY: Literal["_psr_kwd"] = "_psr_kwd"
|
|
17
|
+
PSR_EXTRACT_KEY: Literal["_psr_extract"] = "_psr_extract"
|
|
18
|
+
PSR_KWD_MATCHED_KEY: Literal["_psr_kwd_matched"] = "_psr_kwd_matched"
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def ExtractText() -> str:
|
|
@@ -23,7 +23,7 @@ def ExtractText() -> str:
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def _extract_text(state: T_State) -> str | None:
|
|
26
|
-
return state.get(
|
|
26
|
+
return state.get(PSR_EXTRACT_KEY)
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def Keyword() -> str:
|
|
@@ -31,15 +31,15 @@ def Keyword() -> str:
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
def _keyword(state: T_State) -> str | None:
|
|
34
|
-
return state.get(
|
|
34
|
+
return state.get(PSR_KWD_KEY)
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
def
|
|
38
|
-
return Depends(
|
|
37
|
+
def KwdRegexMatched() -> re.Match[str]:
|
|
38
|
+
return Depends(_kwd_regex_matched)
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
def
|
|
42
|
-
return state.get(
|
|
41
|
+
def _kwd_regex_matched(state: T_State) -> re.Match[str] | None:
|
|
42
|
+
return state.get(PSR_KWD_MATCHED_KEY)
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
URL_KEY_MAPPING = {
|
|
@@ -104,12 +104,12 @@ def extract_msg_text(message: UniMsg, state: T_State) -> None:
|
|
|
104
104
|
text: str | None = None
|
|
105
105
|
|
|
106
106
|
if hyper := message.get(Hyper, 1):
|
|
107
|
-
state[
|
|
107
|
+
state[PSR_EXTRACT_KEY] = _extract_json_url(hyper.pop())
|
|
108
108
|
return
|
|
109
109
|
|
|
110
110
|
# 提取纯文本
|
|
111
111
|
if text := message.extract_plain_text().strip():
|
|
112
|
-
state[
|
|
112
|
+
state[PSR_EXTRACT_KEY] = text
|
|
113
113
|
|
|
114
114
|
|
|
115
115
|
class KeyPatternList(list[tuple[str, re.Pattern[str]]]):
|
|
@@ -145,8 +145,8 @@ class KeywordRegexRule:
|
|
|
145
145
|
if keyword not in text:
|
|
146
146
|
continue
|
|
147
147
|
if matched := pattern.search(text):
|
|
148
|
-
state[
|
|
149
|
-
state[
|
|
148
|
+
state[PSR_KWD_KEY] = keyword
|
|
149
|
+
state[PSR_KWD_MATCHED_KEY] = matched
|
|
150
150
|
return True
|
|
151
151
|
logger.debug(f"keyword '{keyword}' is in '{text}', but not matched")
|
|
152
152
|
return False
|
|
@@ -61,21 +61,23 @@ class BilibiliParser(BaseParser):
|
|
|
61
61
|
"""
|
|
62
62
|
# 从匹配对象中获取原始URL, 视频ID, 页码
|
|
63
63
|
url, video_id, page_num = str(matched.group(0)), str(matched.group(1)), matched.group(2)
|
|
64
|
-
|
|
65
64
|
# 处理短链
|
|
66
65
|
if "b23.tv" in url or "bili2233.cn" in url:
|
|
67
66
|
url = await self.get_redirect_url(url, self.headers)
|
|
68
67
|
|
|
69
68
|
if not video_id:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
elif _matched := re.search(r"av(\d{6,})[^?]*?(?:\?[^#]*?p=(\d{1,3}))?", url):
|
|
74
|
-
video_id = _matched.group(1)
|
|
75
|
-
page_num = _matched.group(2)
|
|
69
|
+
# https://www.bilibili.com/video/BV1584y167sD?a=20&p=40
|
|
70
|
+
if _matched := re.search(r"(?:(BV[\dA-Za-z]{10})|av(\d{6,}))", url):
|
|
71
|
+
video_id = _matched.group(1) or _matched.group(2)
|
|
76
72
|
else:
|
|
77
73
|
return await self.parse_others(url)
|
|
78
74
|
|
|
75
|
+
# 匹配页码参数
|
|
76
|
+
if _matched := re.search(r"(?:&|\?)p=(\d{1,3})", url):
|
|
77
|
+
page_num = _matched.group(1)
|
|
78
|
+
else:
|
|
79
|
+
page_num = None
|
|
80
|
+
|
|
79
81
|
avid, bvid = None, None
|
|
80
82
|
page_num = int(page_num) if page_num and page_num.isdigit() else 1
|
|
81
83
|
|
|
@@ -282,18 +284,11 @@ class BilibiliParser(BaseParser):
|
|
|
282
284
|
current_text = ""
|
|
283
285
|
|
|
284
286
|
for node in opus_data.gen_text_img():
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
current_text.strip(),
|
|
291
|
-
node.alt,
|
|
292
|
-
)
|
|
293
|
-
)
|
|
294
|
-
current_text = ""
|
|
295
|
-
case TextNode():
|
|
296
|
-
current_text += node.text
|
|
287
|
+
if isinstance(node, ImageNode):
|
|
288
|
+
contents.append(self.create_graphics_content(node.url, current_text.strip(), node.alt))
|
|
289
|
+
current_text = ""
|
|
290
|
+
elif isinstance(node, TextNode):
|
|
291
|
+
current_text += node.text
|
|
297
292
|
|
|
298
293
|
return self.result(
|
|
299
294
|
title=opus_data.title,
|
|
@@ -358,18 +353,11 @@ class BilibiliParser(BaseParser):
|
|
|
358
353
|
contents: list[MediaContent] = []
|
|
359
354
|
current_text = ""
|
|
360
355
|
for child in article_info.gen_text_img():
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
current_text.strip(),
|
|
367
|
-
child.alt,
|
|
368
|
-
)
|
|
369
|
-
)
|
|
370
|
-
current_text = ""
|
|
371
|
-
case TextNode():
|
|
372
|
-
current_text += child.text
|
|
356
|
+
if isinstance(child, ImageNode):
|
|
357
|
+
contents.append(self.create_graphics_content(child.url, current_text.strip(), child.alt))
|
|
358
|
+
current_text = ""
|
|
359
|
+
elif isinstance(child, TextNode):
|
|
360
|
+
current_text += child.text
|
|
373
361
|
|
|
374
362
|
author = self.create_author(*article_info.author_info)
|
|
375
363
|
|
|
@@ -44,6 +44,7 @@ class BaseRenderer(ABC):
|
|
|
44
44
|
"""
|
|
45
45
|
failed_count = 0
|
|
46
46
|
forwardable_segs: list[ForwardNodeInner] = []
|
|
47
|
+
dynamic_segs: list[ForwardNodeInner] = []
|
|
47
48
|
|
|
48
49
|
for cont in chain(result.contents, result.repost.contents if result.repost else ()):
|
|
49
50
|
try:
|
|
@@ -65,7 +66,7 @@ class BaseRenderer(ABC):
|
|
|
65
66
|
case ImageContent():
|
|
66
67
|
forwardable_segs.append(UniHelper.img_seg(path))
|
|
67
68
|
case DynamicContent():
|
|
68
|
-
|
|
69
|
+
dynamic_segs.append(UniHelper.video_seg(path))
|
|
69
70
|
case GraphicsContent() as graphics:
|
|
70
71
|
graphics_msg = UniHelper.img_seg(path)
|
|
71
72
|
if graphics.text is not None:
|
|
@@ -76,11 +77,13 @@ class BaseRenderer(ABC):
|
|
|
76
77
|
|
|
77
78
|
if forwardable_segs:
|
|
78
79
|
if pconfig.need_forward_contents or len(forwardable_segs) > 4:
|
|
79
|
-
forward_msg = UniHelper.construct_forward_message(forwardable_segs)
|
|
80
|
+
forward_msg = UniHelper.construct_forward_message(forwardable_segs + dynamic_segs)
|
|
80
81
|
yield UniMessage(forward_msg)
|
|
81
82
|
else:
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
yield UniMessage(forwardable_segs)
|
|
84
|
+
|
|
85
|
+
if dynamic_segs:
|
|
86
|
+
yield UniMessage(UniHelper.construct_forward_message(dynamic_segs))
|
|
84
87
|
|
|
85
88
|
if failed_count > 0:
|
|
86
89
|
message = f"{failed_count} 项媒体下载失败"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
+
from functools import lru_cache
|
|
2
3
|
from io import BytesIO
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from typing import ClassVar
|
|
@@ -19,6 +20,53 @@ class FontInfo:
|
|
|
19
20
|
cjk_width: int
|
|
20
21
|
ascii_width: int
|
|
21
22
|
|
|
23
|
+
def __hash__(self) -> int:
|
|
24
|
+
"""实现哈希方法以支持 @lru_cache"""
|
|
25
|
+
return hash((self.line_height, self.cjk_width, self.ascii_width))
|
|
26
|
+
|
|
27
|
+
@lru_cache(maxsize=100)
|
|
28
|
+
def get_char_width(self, char: str) -> int:
|
|
29
|
+
"""获取字符宽度,使用缓存优化"""
|
|
30
|
+
bbox = self.font.getbbox(char)
|
|
31
|
+
width = int(bbox[2] - bbox[0])
|
|
32
|
+
return width
|
|
33
|
+
|
|
34
|
+
def get_char_width_fast(self, char: str) -> int:
|
|
35
|
+
"""快速获取单个字符宽度"""
|
|
36
|
+
if self._is_cjk_char(char):
|
|
37
|
+
return self.cjk_width
|
|
38
|
+
elif self._is_ascii_char(char):
|
|
39
|
+
return self.ascii_width
|
|
40
|
+
else:
|
|
41
|
+
return self.get_char_width(char)
|
|
42
|
+
|
|
43
|
+
def get_text_width(self, text: str) -> int:
|
|
44
|
+
"""计算文本宽度,使用预计算的字符宽度优化性能
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
text: 要计算宽度的文本
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
文本宽度(像素)
|
|
51
|
+
"""
|
|
52
|
+
if not text:
|
|
53
|
+
return 0
|
|
54
|
+
|
|
55
|
+
total_width = 0
|
|
56
|
+
for char in text:
|
|
57
|
+
total_width += self.get_char_width_fast(char)
|
|
58
|
+
return total_width
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def _is_cjk_char(char: str) -> bool:
|
|
62
|
+
"""判断是否为中日韩字符"""
|
|
63
|
+
return "\u4e00" <= char <= "\u9fff"
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def _is_ascii_char(char: str) -> bool:
|
|
67
|
+
"""判断是否为ASCII字符"""
|
|
68
|
+
return ord(char) < 128
|
|
69
|
+
|
|
22
70
|
|
|
23
71
|
@dataclass(eq=False, frozen=True, slots=True)
|
|
24
72
|
class FontSet:
|
|
@@ -28,6 +76,7 @@ class FontSet:
|
|
|
28
76
|
title_font: FontInfo
|
|
29
77
|
text_font: FontInfo
|
|
30
78
|
extra_font: FontInfo
|
|
79
|
+
indicator_font: FontInfo
|
|
31
80
|
|
|
32
81
|
|
|
33
82
|
@dataclass(eq=False, frozen=True, slots=True)
|
|
@@ -169,9 +218,9 @@ class CommonRenderer(ImageRenderer):
|
|
|
169
218
|
"""转发缩放比例"""
|
|
170
219
|
|
|
171
220
|
# 字体大小和行高
|
|
172
|
-
FONT_SIZES: ClassVar[dict[str, int]] = {"name": 28, "title": 30, "text": 24, "extra": 24}
|
|
221
|
+
FONT_SIZES: ClassVar[dict[str, int]] = {"name": 28, "title": 30, "text": 24, "extra": 24, "indicator": 60}
|
|
173
222
|
"""字体大小"""
|
|
174
|
-
LINE_HEIGHTS: ClassVar[dict[str, int]] = {"name": 32, "title": 36, "text": 28, "extra": 28}
|
|
223
|
+
LINE_HEIGHTS: ClassVar[dict[str, int]] = {"name": 32, "title": 36, "text": 28, "extra": 28, "indicator": 68}
|
|
175
224
|
"""行高"""
|
|
176
225
|
|
|
177
226
|
RESOURCES_DIR: ClassVar[Path] = Path(__file__).parent / "resources"
|
|
@@ -228,6 +277,7 @@ class CommonRenderer(ImageRenderer):
|
|
|
228
277
|
title_font=font_infos["title"],
|
|
229
278
|
text_font=font_infos["text"],
|
|
230
279
|
extra_font=font_infos["extra"],
|
|
280
|
+
indicator_font=font_infos["indicator"],
|
|
231
281
|
)
|
|
232
282
|
|
|
233
283
|
logger.success(f"加载字体「{self.font_path.name}」成功")
|
|
@@ -859,11 +909,11 @@ class CommonRenderer(ImageRenderer):
|
|
|
859
909
|
if section.alt_text:
|
|
860
910
|
y_pos += self.SECTION_SPACING # 图片和alt文本之间的间距
|
|
861
911
|
# 计算文本居中位置
|
|
862
|
-
|
|
863
|
-
text_width =
|
|
912
|
+
extra_font_info = self.fontset.extra_font
|
|
913
|
+
text_width = extra_font_info.get_text_width(section.alt_text)
|
|
864
914
|
text_x = self.PADDING + (content_width - text_width) // 2
|
|
865
|
-
draw.text((text_x, y_pos), section.alt_text, fill=self.EXTRA_COLOR, font=
|
|
866
|
-
y_pos +=
|
|
915
|
+
draw.text((text_x, y_pos), section.alt_text, fill=self.EXTRA_COLOR, font=extra_font_info.font)
|
|
916
|
+
y_pos += extra_font_info.line_height
|
|
867
917
|
|
|
868
918
|
return y_pos + self.SECTION_SPACING
|
|
869
919
|
|
|
@@ -985,19 +1035,14 @@ class CommonRenderer(ImageRenderer):
|
|
|
985
1035
|
|
|
986
1036
|
# 绘制+N文字
|
|
987
1037
|
text = f"+{count}"
|
|
988
|
-
|
|
989
|
-
font_size = min(img_width, img_height) // 4
|
|
990
|
-
font = ImageFont.truetype(self.font_path, font_size)
|
|
991
|
-
|
|
1038
|
+
font_info = self.fontset.indicator_font
|
|
992
1039
|
# 计算文字位置(居中)
|
|
993
|
-
|
|
994
|
-
text_width = bbox[2] - bbox[0]
|
|
995
|
-
text_height = bbox[3] - bbox[1]
|
|
1040
|
+
text_width = font_info.get_text_width(text)
|
|
996
1041
|
text_x = img_x + (img_width - text_width) // 2
|
|
997
|
-
text_y = img_y + (img_height -
|
|
1042
|
+
text_y = img_y + (img_height - font_info.line_height) // 2
|
|
998
1043
|
|
|
999
|
-
#
|
|
1000
|
-
draw.text((text_x, text_y), text, fill=(255, 255, 255
|
|
1044
|
+
# 绘制50%透明白色文字
|
|
1045
|
+
draw.text((text_x, text_y), text, fill=(255, 255, 255), font=font_info.font)
|
|
1001
1046
|
|
|
1002
1047
|
def _draw_rounded_rectangle(
|
|
1003
1048
|
self, image: Image.Image, bbox: tuple[int, int, int, int], fill_color: tuple[int, int, int], radius: int = 8
|
|
@@ -1056,81 +1101,14 @@ class CommonRenderer(ImageRenderer):
|
|
|
1056
1101
|
lines = []
|
|
1057
1102
|
paragraphs = text.split("\n")
|
|
1058
1103
|
|
|
1059
|
-
# 字符宽度缓存
|
|
1060
|
-
char_width_cache = {}
|
|
1061
|
-
|
|
1062
|
-
def get_char_width(char: str) -> int:
|
|
1063
|
-
"""获取字符宽度,使用缓存优化"""
|
|
1064
|
-
if char in char_width_cache:
|
|
1065
|
-
return char_width_cache[char]
|
|
1066
|
-
|
|
1067
|
-
bbox = font_info.font.getbbox(char)
|
|
1068
|
-
width = int(bbox[2] - bbox[0])
|
|
1069
|
-
char_width_cache[char] = width
|
|
1070
|
-
return width
|
|
1071
|
-
|
|
1072
|
-
def is_cjk_char(char: str) -> bool:
|
|
1073
|
-
"""判断是否为中日韩字符"""
|
|
1074
|
-
return "\u4e00" <= char <= "\u9fff"
|
|
1075
|
-
|
|
1076
|
-
def is_ascii_char(char: str) -> bool:
|
|
1077
|
-
"""判断是否为ASCII字符"""
|
|
1078
|
-
return ord(char) < 128
|
|
1079
|
-
|
|
1080
1104
|
def is_punctuation(char: str) -> bool:
|
|
1081
|
-
"""
|
|
1105
|
+
"""判断是否为不能为行首的标点符号"""
|
|
1082
1106
|
# 中文标点符号
|
|
1083
|
-
chinese_punctuation = "
|
|
1107
|
+
chinese_punctuation = ",。!?;:、)】》〉」』〕〗〙〛…—·"
|
|
1084
1108
|
# 英文标点符号
|
|
1085
|
-
english_punctuation = ",.;:!?
|
|
1086
|
-
# Unicode 标点符号类别
|
|
1087
|
-
import unicodedata
|
|
1088
|
-
|
|
1089
|
-
return (
|
|
1090
|
-
char in chinese_punctuation or char in english_punctuation or unicodedata.category(char).startswith("P")
|
|
1091
|
-
)
|
|
1092
|
-
|
|
1093
|
-
def get_text_width_fast(text: str) -> int:
|
|
1094
|
-
"""快速计算文本宽度"""
|
|
1095
|
-
if not text:
|
|
1096
|
-
return 0
|
|
1097
|
-
|
|
1098
|
-
total_width = 0
|
|
1099
|
-
for char in text:
|
|
1100
|
-
if is_cjk_char(char):
|
|
1101
|
-
total_width += font_info.cjk_width
|
|
1102
|
-
elif is_ascii_char(char):
|
|
1103
|
-
total_width += font_info.ascii_width
|
|
1104
|
-
else:
|
|
1105
|
-
total_width += get_char_width(char)
|
|
1106
|
-
return total_width
|
|
1107
|
-
|
|
1108
|
-
def find_break_point(text: str) -> int:
|
|
1109
|
-
"""找到合适的断点位置,避免标点符号在行首"""
|
|
1110
|
-
if not text:
|
|
1111
|
-
return 0
|
|
1112
|
-
|
|
1113
|
-
# 从后往前找断点
|
|
1114
|
-
for i in range(len(text) - 1, 0, -1):
|
|
1115
|
-
char = text[i]
|
|
1116
|
-
|
|
1117
|
-
# 优先在空格处断行
|
|
1118
|
-
if char == " ":
|
|
1119
|
-
return i
|
|
1120
|
-
|
|
1121
|
-
# 对于中文,可以在任意字符处断行
|
|
1122
|
-
if is_cjk_char(char):
|
|
1123
|
-
return i
|
|
1109
|
+
english_punctuation = ",.;:!?)]}"
|
|
1124
1110
|
|
|
1125
|
-
|
|
1126
|
-
if is_punctuation(char):
|
|
1127
|
-
continue
|
|
1128
|
-
|
|
1129
|
-
# 其他字符可以作为断点
|
|
1130
|
-
return i
|
|
1131
|
-
|
|
1132
|
-
# 如果找不到合适的断点,在中间位置断行
|
|
1133
|
-
return max(1, len(text) // 2)
|
|
1111
|
+
return char in chinese_punctuation or char in english_punctuation
|
|
1134
1112
|
|
|
1135
1113
|
for paragraph in paragraphs:
|
|
1136
1114
|
if not paragraph:
|
|
@@ -1138,51 +1116,41 @@ class CommonRenderer(ImageRenderer):
|
|
|
1138
1116
|
continue
|
|
1139
1117
|
|
|
1140
1118
|
current_line = ""
|
|
1119
|
+
current_line_width = 0
|
|
1141
1120
|
remaining_text = paragraph
|
|
1142
1121
|
|
|
1143
1122
|
while remaining_text:
|
|
1123
|
+
next_char = remaining_text[0]
|
|
1124
|
+
char_width = font_info.get_char_width_fast(next_char)
|
|
1125
|
+
|
|
1144
1126
|
# 如果当前行为空,直接添加字符
|
|
1145
1127
|
if not current_line:
|
|
1146
|
-
current_line =
|
|
1128
|
+
current_line = next_char
|
|
1129
|
+
current_line_width = char_width
|
|
1147
1130
|
remaining_text = remaining_text[1:]
|
|
1148
1131
|
continue
|
|
1149
1132
|
|
|
1150
|
-
#
|
|
1151
|
-
|
|
1152
|
-
|
|
1133
|
+
# 如果是标点符号,直接添加到当前行(标点符号不应该单独成行)
|
|
1134
|
+
if is_punctuation(next_char):
|
|
1135
|
+
current_line += next_char
|
|
1136
|
+
current_line_width += char_width
|
|
1137
|
+
remaining_text = remaining_text[1:]
|
|
1138
|
+
continue
|
|
1139
|
+
|
|
1140
|
+
# 测试添加下一个字符后的宽度
|
|
1141
|
+
test_width = current_line_width + char_width
|
|
1153
1142
|
|
|
1154
1143
|
if test_width <= max_width:
|
|
1155
1144
|
# 宽度合适,继续添加
|
|
1156
|
-
current_line
|
|
1145
|
+
current_line += next_char
|
|
1146
|
+
current_line_width = test_width
|
|
1157
1147
|
remaining_text = remaining_text[1:]
|
|
1158
1148
|
else:
|
|
1159
1149
|
# 宽度超限,需要断行
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
remaining_text = remaining_text[1:]
|
|
1165
|
-
else:
|
|
1166
|
-
# 尝试找到合适的断点
|
|
1167
|
-
break_point = find_break_point(current_line)
|
|
1168
|
-
|
|
1169
|
-
# 保存当前行
|
|
1170
|
-
lines.append(current_line[:break_point].rstrip())
|
|
1171
|
-
|
|
1172
|
-
# 开始新行,跳过行首的标点符号
|
|
1173
|
-
current_line = current_line[break_point:].lstrip()
|
|
1174
|
-
|
|
1175
|
-
# 如果新行以标点符号开头,将其移到上一行
|
|
1176
|
-
while current_line and is_punctuation(current_line[0]):
|
|
1177
|
-
if lines:
|
|
1178
|
-
lines[-1] += current_line[0]
|
|
1179
|
-
current_line = current_line[1:]
|
|
1180
|
-
else:
|
|
1181
|
-
break
|
|
1182
|
-
|
|
1183
|
-
if not current_line:
|
|
1184
|
-
current_line = remaining_text[0]
|
|
1185
|
-
remaining_text = remaining_text[1:]
|
|
1150
|
+
lines.append(current_line)
|
|
1151
|
+
current_line = next_char
|
|
1152
|
+
current_line_width = char_width
|
|
1153
|
+
remaining_text = remaining_text[1:]
|
|
1186
1154
|
|
|
1187
1155
|
# 保存最后一行
|
|
1188
1156
|
if current_line:
|
{nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/constants.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/exception.py
RENAMED
|
File without changes
|
{nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/helper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nonebot_plugin_parser-2.0.8 → nonebot_plugin_parser-2.0.10}/src/nonebot_plugin_parser/utils.py
RENAMED
|
File without changes
|