nonebot-plugin-parser 2.0.13__tar.gz → 2.1.0__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.
Files changed (54) hide show
  1. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/PKG-INFO +4 -4
  2. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/pyproject.toml +11 -11
  3. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/__init__.py +1 -1
  4. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/matchers/preprocess.py +38 -37
  5. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/parsers/bilibili/__init__.py +1 -1
  6. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/parsers/data.py +1 -1
  7. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/parsers/nga.py +6 -0
  8. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/renders/default.py +9 -7
  9. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/README.md +0 -0
  10. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/config.py +0 -0
  11. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/constants.py +0 -0
  12. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/download/__init__.py +0 -0
  13. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/download/task.py +0 -0
  14. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/download/ytdlp.py +0 -0
  15. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/exception.py +0 -0
  16. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/helper.py +0 -0
  17. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/matchers/__init__.py +0 -0
  18. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/matchers/filter.py +0 -0
  19. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/parsers/__init__.py +0 -0
  20. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/parsers/acfun.py +0 -0
  21. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/parsers/base.py +0 -0
  22. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/parsers/bilibili/article.py +0 -0
  23. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/parsers/bilibili/common.py +0 -0
  24. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/parsers/bilibili/dynamic.py +0 -0
  25. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/parsers/bilibili/favlist.py +0 -0
  26. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/parsers/bilibili/live.py +0 -0
  27. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/parsers/bilibili/opus.py +0 -0
  28. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/parsers/bilibili/video.py +0 -0
  29. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/parsers/cookie.py +0 -0
  30. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/parsers/douyin/__init__.py +0 -0
  31. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/parsers/douyin/slides.py +0 -0
  32. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/parsers/douyin/video.py +0 -0
  33. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/parsers/kuaishou.py +0 -0
  34. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/parsers/tiktok.py +0 -0
  35. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/parsers/twitter.py +0 -0
  36. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/parsers/weibo.py +0 -0
  37. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/parsers/xiaohongshu.py +0 -0
  38. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/parsers/youtube.py +0 -0
  39. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/renders/__init__.py +0 -0
  40. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/renders/base.py +0 -0
  41. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/renders/common.py +0 -0
  42. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/renders/resources/HYSongYunLangHeiW-1.ttf +0 -0
  43. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/renders/resources/bilibili.png +0 -0
  44. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/renders/resources/douyin.png +0 -0
  45. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/renders/resources/kuaishou.png +0 -0
  46. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/renders/resources/media_button.png +0 -0
  47. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/renders/resources/tiktok.png +0 -0
  48. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/renders/resources/twitter.png +0 -0
  49. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/renders/resources/weibo.png +0 -0
  50. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/renders/resources/xiaohongshu.png +0 -0
  51. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/renders/resources/youtube.png +0 -0
  52. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/renders/templates/weibo.html.jinja +0 -0
  53. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/renders/weibo.py +0 -0
  54. {nonebot_plugin_parser-2.0.13 → nonebot_plugin_parser-2.1.0}/src/nonebot_plugin_parser/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nonebot-plugin-parser
3
- Version: 2.0.13
3
+ Version: 2.1.0
4
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
@@ -8,12 +8,12 @@ Author-email: fllesser <fllessive@gmail.com>
8
8
  Requires-Dist: msgspec>=0.19.0,<1.0.0
9
9
  Requires-Dist: httpx>=0.27.2,<1.0.0
10
10
  Requires-Dist: tqdm>=4.67.1,<5.0.0
11
- Requires-Dist: aiofiles>=24.1.0
11
+ Requires-Dist: aiofiles>=25.1.0
12
12
  Requires-Dist: pillow>=11.0.0
13
13
  Requires-Dist: curl-cffi>=0.13.0,<1.0.0
14
- Requires-Dist: bilibili-api-python>=17.3.0,<18.0.0
14
+ Requires-Dist: bilibili-api-python>=17.4.0,<18.0.0
15
15
  Requires-Dist: beautifulsoup4>=4.12.0,<5.0.0
16
- Requires-Dist: yt-dlp>=2025.9.26
16
+ Requires-Dist: yt-dlp>=2025.10.14
17
17
  Requires-Dist: nonebot2>=2.4.3,<3.0.0
18
18
  Requires-Dist: nonebot-plugin-localstore>=0.7.4,<1.0.0
19
19
  Requires-Dist: nonebot-plugin-apscheduler>=0.5.0,<1.0.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nonebot-plugin-parser"
3
- version = "2.0.13"
3
+ version = "2.1.0"
4
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"
@@ -24,14 +24,14 @@ dependencies = [
24
24
  "msgspec>=0.19.0,<1.0.0",
25
25
  "httpx>=0.27.2,<1.0.0",
26
26
  "tqdm>=4.67.1,<5.0.0",
27
- "aiofiles>=24.1.0",
27
+ "aiofiles>=25.1.0",
28
28
  "pillow>=11.0.0",
29
29
 
30
30
  # 后续改为可选依赖
31
31
  "curl_cffi>=0.13.0,<1.0.0",
32
- "bilibili-api-python>=17.3.0,<18.0.0",
32
+ "bilibili-api-python>=17.4.0,<18.0.0",
33
33
  "beautifulsoup4>=4.12.0,<5.0.0",
34
- "yt-dlp>=2025.9.26",
34
+ "yt-dlp>=2025.10.14",
35
35
 
36
36
  "nonebot2>=2.4.3,<3.0.0",
37
37
  "nonebot-plugin-localstore>=0.7.4,<1.0.0",
@@ -68,18 +68,18 @@ test = [
68
68
  ]
69
69
 
70
70
  telegram = ["nonebot-adapter-telegram>=0.1.0b20"]
71
- pydantic_v1 = ["pydantic<2.0.0"]
72
- pydantic_v2 = ["pydantic>=2.0.0"]
71
+ pydantic-v1 = ["pydantic<2.0.0"] # renovate:ignore
72
+ pydantic-v2 = ["pydantic>=2.0.0"]
73
73
 
74
- all_extras = ["nonebot-plugin-htmlkit>=0.1.0rc3"]
74
+ extras = ["nonebot-plugin-htmlkit>=0.1.0rc3"]
75
75
 
76
76
  [tool.uv]
77
77
  required-version = ">=0.9.2"
78
- default-groups = ["test", "dev", "all_extras"]
78
+ default-groups = ["test", "dev", "extras"]
79
79
  conflicts = [
80
80
  [
81
- { group = "pydantic_v1" },
82
- { group = "pydantic_v2" },
81
+ { group = "pydantic-v1" },
82
+ { group = "pydantic-v2" },
83
83
  { group = "telegram" },
84
84
  ],
85
85
  ]
@@ -195,7 +195,7 @@ build-backend = "uv_build"
195
195
 
196
196
 
197
197
  [tool.bumpversion]
198
- current_version = "2.0.13"
198
+ current_version = "2.1.0"
199
199
  commit = true
200
200
  message = "🔖 release: bump vesion from {current_version} to {new_version}"
201
201
  tag = true
@@ -12,7 +12,7 @@ from .utils import safe_unlink
12
12
 
13
13
  __plugin_meta__ = PluginMetadata(
14
14
  name="链接分享解析 Alconna 版",
15
- description="全新通用媒体卡片渲染(PIL 实现)\n支持 B站/抖音/快手/微博/小红书/youtube/tiktok/twitter/acfun/nga",
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",
@@ -4,7 +4,8 @@ from typing import Any, Literal
4
4
 
5
5
  from nonebot import logger
6
6
  from nonebot.matcher import Matcher
7
- from nonebot.message import event_preprocessor
7
+
8
+ # from nonebot.message import event_preprocessor
8
9
  from nonebot.params import Depends
9
10
  from nonebot.plugin.on import get_matcher_source
10
11
  from nonebot.rule import Rule
@@ -42,30 +43,22 @@ def _kwd_regex_matched(state: T_State) -> re.Match[str] | None:
42
43
  return state.get(PSR_KWD_MATCHED_KEY)
43
44
 
44
45
 
45
- URL_KEY_MAPPING = {
46
- "detail_1": "qqdocurl",
47
- "news": "jumpUrl",
48
- "music": "jumpUrl",
49
- }
50
-
51
- CHAR_REPLACEMENTS = {"&#44;": ",", "\\": "", "&amp;": "&"}
52
-
53
-
54
- def _clean_url(url: str) -> str:
55
- """清理 URL 中的特殊字符
56
-
46
+ def _escape_raw(raw: str) -> str:
47
+ """
48
+ 转义原始字符串中的特殊字符
57
49
  Args:
58
- url: 原始 URL
50
+ raw: 原始字符串
59
51
 
60
52
  Returns:
61
- str: 清理后的 URL
53
+ str: 转义后的字符串
62
54
  """
63
- for old, new in CHAR_REPLACEMENTS.items():
64
- url = url.replace(old, new)
65
- return url
55
+ replacements = [("&#44;", ","), ("\\", ""), ("&amp;", "&")]
56
+ for old, new in replacements:
57
+ raw = raw.replace(old, new)
58
+ return raw
66
59
 
67
60
 
68
- def _extract_json_url(hyper: Hyper) -> str | None:
61
+ def _extract_url(hyper: Hyper) -> str | None:
69
62
  """处理 JSON 类型的消息段,提取 URL
70
63
 
71
64
  Args:
@@ -75,12 +68,12 @@ def _extract_json_url(hyper: Hyper) -> str | None:
75
68
  Optional[str]: 提取的 URL, 如果提取失败则返回 None
76
69
  """
77
70
  data = hyper.data
78
- raw_str = data.get("raw")
71
+ raw_str: str | None = data.get("raw")
79
72
 
80
73
  if raw_str is None:
81
74
  return None
82
- # 处理转义字符
83
- raw_str = raw_str.replace("&#44;", ",")
75
+
76
+ raw_str = _escape_raw(raw_str)
84
77
 
85
78
  try:
86
79
  raw: dict[str, Any] = json.loads(raw_str)
@@ -92,24 +85,23 @@ def _extract_json_url(hyper: Hyper) -> str | None:
92
85
  if not meta:
93
86
  return None
94
87
 
95
- for key1, key2 in URL_KEY_MAPPING.items():
96
- if item := meta.get(key1):
97
- if url := item.get(key2):
98
- return _clean_url(url)
88
+ for key1, key2 in [("detail_1", "qqdocurl"), ("news", "jumpUrl"), ("music", "jumpUrl")]:
89
+ if url := meta.get(key1, {}).get(key2):
90
+ logger.debug(f"extract url from raw:{key1}:{key2}: {url}")
91
+ return url
99
92
  return None
100
93
 
101
94
 
102
- @event_preprocessor
103
- def extract_msg_text(message: UniMsg, state: T_State) -> None:
104
- text: str | None = None
105
-
106
- if hyper := message.get(Hyper, 1):
107
- state[PSR_EXTRACT_KEY] = _extract_json_url(hyper.pop())
108
- return
95
+ # 纪念我写了一个存在了一年没人发现的 bug ()
96
+ # @event_preprocessor
97
+ # async def extract_msg_text(message: UniMsg, state: T_State):
98
+ # if hyper := next(iter(message.get(Hyper, 1)), None):
99
+ # state[PSR_EXTRACT_KEY] = _extract_url(hyper)
100
+ # return
109
101
 
110
- # 提取纯文本
111
- if text := message.extract_plain_text().strip():
112
- state[PSR_EXTRACT_KEY] = text
102
+ # # 提取纯文本
103
+ # if text := message.extract_plain_text().strip():
104
+ # state[PSR_EXTRACT_KEY] = text
113
105
 
114
106
 
115
107
  class KeyPatternList(list[tuple[str, re.Pattern[str]]]):
@@ -138,14 +130,23 @@ class KeywordRegexRule:
138
130
  def __hash__(self) -> int:
139
131
  return hash(frozenset(self.key_pattern_list))
140
132
 
141
- async def __call__(self, state: T_State, text: str = ExtractText()) -> bool:
133
+ async def __call__(self, message: UniMsg, state: T_State) -> bool:
134
+ text: str | None = None
135
+ if hyper := next(iter(message.get(Hyper, 1)), None):
136
+ text = _extract_url(hyper)
137
+
138
+ elif plain_text := message.extract_plain_text().strip():
139
+ text = plain_text
140
+
142
141
  if not text:
143
142
  return False
143
+
144
144
  for keyword, pattern in self.key_pattern_list:
145
145
  if keyword not in text:
146
146
  continue
147
147
  if matched := pattern.search(text):
148
148
  state[PSR_KWD_KEY] = keyword
149
+ state[PSR_EXTRACT_KEY] = text
149
150
  state[PSR_KWD_MATCHED_KEY] = matched
150
151
  return True
151
152
  logger.debug(f"keyword '{keyword}' is in '{text}', but not matched")
@@ -165,7 +165,7 @@ class BilibiliParser(BaseParser):
165
165
  # 判断链接类型并解析
166
166
  logger.debug(f"解析其他类型链接: {url}")
167
167
  # 1. 动态
168
- if "t.bilibili.com" in url:
168
+ if "t.bili" in url or "m.bili" in url:
169
169
  return await self.parse_dynamic(url)
170
170
 
171
171
  # 2.图文动态
@@ -160,7 +160,7 @@ class ParseResult:
160
160
  """渲染图片"""
161
161
 
162
162
  @property
163
- def header(self) -> str:
163
+ def header(self) -> str | None:
164
164
  """头信息 仅用于 default render"""
165
165
  header = self.platform.display_name
166
166
  if self.author:
@@ -38,6 +38,7 @@ class NGAParser(BaseParser):
38
38
  "Upgrade-Insecure-Requests": "1",
39
39
  }
40
40
  self.headers.update(extra_headers)
41
+ self.base_img_url = "https://img.nga.178.com/attachments"
41
42
 
42
43
  @staticmethod
43
44
  def nga_url(tid: str | int) -> str:
@@ -140,9 +141,13 @@ class NGAParser(BaseParser):
140
141
  # 提取文本 - postcontent0
141
142
  text = None
142
143
  content_tag = soup.find(id="postcontent0")
144
+ contents = []
143
145
  if content_tag and isinstance(content_tag, Tag):
144
146
  text = content_tag.get_text("\n", strip=True)
145
147
  # 清理 BBCode 标签并限制长度
148
+ img_urls: list[str] = re.findall(r"\[img\](.*?)\[/img\]", text)
149
+ img_urls = [self.base_img_url + url[1:] for url in img_urls]
150
+ contents.extend(self.create_image_contents(img_urls))
146
151
  text = self.clean_nga_text(text)
147
152
 
148
153
  return self.result(
@@ -150,6 +155,7 @@ class NGAParser(BaseParser):
150
155
  text=text,
151
156
  url=url,
152
157
  author=author,
158
+ contents=contents,
153
159
  timestamp=timestamp,
154
160
  )
155
161
 
@@ -20,20 +20,22 @@ class DefaultRenderer(BaseRenderer):
20
20
  Generator[UniMessage[Any], None, None]: 消息生成器
21
21
  """
22
22
 
23
- texts = (
23
+ texts = [
24
24
  result.header,
25
25
  result.text,
26
26
  result.extra_info,
27
- result.display_url,
28
- result.repost_display_url,
29
- )
27
+ ]
28
+
29
+ if self.append_url:
30
+ texts.extend((result.display_url, result.repost_display_url))
31
+
30
32
  texts = [text for text in texts if text]
31
33
  texts[:-1] = [text + "\n" for text in texts[:-1]]
32
-
33
34
  segs: list[Segment] = [Text(text) for text in texts]
34
- cover_path = await result.cover_path
35
- if cover_path is not None:
35
+
36
+ if cover_path := await result.cover_path:
36
37
  segs.insert(1, UniHelper.img_seg(cover_path))
38
+
37
39
  yield UniMessage(segs)
38
40
 
39
41
  async for message in self.render_contents(result):