nonebot-plugin-parser 2.2.4__tar.gz → 2.3.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.2.4 → nonebot_plugin_parser-2.3.0}/PKG-INFO +6 -2
  2. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/README.md +4 -0
  3. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/pyproject.toml +7 -7
  4. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/config.py +8 -1
  5. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/download/__init__.py +1 -1
  6. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/renders/common.py +68 -68
  7. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/__init__.py +0 -0
  8. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/constants.py +0 -0
  9. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/download/task.py +0 -0
  10. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/download/ytdlp.py +0 -0
  11. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/exception.py +0 -0
  12. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/helper.py +0 -0
  13. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/matchers/__init__.py +0 -0
  14. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/matchers/filter.py +0 -0
  15. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/matchers/rule.py +0 -0
  16. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/parsers/__init__.py +0 -0
  17. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/parsers/acfun.py +0 -0
  18. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/parsers/base.py +0 -0
  19. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/parsers/bilibili/__init__.py +0 -0
  20. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/parsers/bilibili/article.py +0 -0
  21. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/parsers/bilibili/common.py +0 -0
  22. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/parsers/bilibili/dynamic.py +0 -0
  23. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/parsers/bilibili/favlist.py +0 -0
  24. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/parsers/bilibili/live.py +0 -0
  25. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/parsers/bilibili/opus.py +0 -0
  26. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/parsers/bilibili/video.py +0 -0
  27. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/parsers/cookie.py +0 -0
  28. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/parsers/data.py +0 -0
  29. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/parsers/douyin/__init__.py +0 -0
  30. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/parsers/douyin/slides.py +0 -0
  31. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/parsers/douyin/video.py +0 -0
  32. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/parsers/kuaishou.py +0 -0
  33. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/parsers/nga.py +0 -0
  34. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/parsers/tiktok.py +0 -0
  35. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/parsers/twitter.py +0 -0
  36. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/parsers/weibo.py +0 -0
  37. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/parsers/xiaohongshu.py +0 -0
  38. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/parsers/youtube.py +0 -0
  39. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/renders/__init__.py +0 -0
  40. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/renders/base.py +0 -0
  41. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/renders/default.py +0 -0
  42. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/renders/resources/HYSongYunLangHeiW-1.ttf +0 -0
  43. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/renders/resources/bilibili.png +0 -0
  44. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/renders/resources/douyin.png +0 -0
  45. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/renders/resources/kuaishou.png +0 -0
  46. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/renders/resources/media_button.png +0 -0
  47. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/renders/resources/tiktok.png +0 -0
  48. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/renders/resources/twitter.png +0 -0
  49. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/renders/resources/weibo.png +0 -0
  50. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/renders/resources/xiaohongshu.png +0 -0
  51. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/renders/resources/youtube.png +0 -0
  52. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/renders/templates/weibo.html.jinja +0 -0
  53. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.0}/src/nonebot_plugin_parser/renders/weibo.py +0 -0
  54. {nonebot_plugin_parser-2.2.4 → nonebot_plugin_parser-2.3.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.2.4
3
+ Version: 2.3.0
4
4
  Summary: NoneBot2 链接分享解析 Alconna 版, 通用媒体卡片渲染(PIL 实现), 支持 B站/抖音/快手/微博/小红书/youtube/tiktok/twitter/acfun/nga
5
5
  Keywords: acfun,bilibili,douyin,kuaishou,nga,nonebot,nonebot2,tiktok,twitter,video,weibo,xiaohongshu,youtube
6
6
  Author: fllesser
@@ -21,12 +21,12 @@ Classifier: Topic :: Internet :: WWW/HTTP
21
21
  Classifier: Topic :: Multimedia :: Video
22
22
  Requires-Dist: nonebot2>=2.4.3,<3.0.0
23
23
  Requires-Dist: pillow>=11.0.0
24
- Requires-Dist: pilmoji-for-parser>=0.1.3
25
24
  Requires-Dist: tqdm>=4.67.1,<5.0.0
26
25
  Requires-Dist: aiofiles>=25.1.0
27
26
  Requires-Dist: curl-cffi>=0.13.0,<1.0.0
28
27
  Requires-Dist: httpx>=0.27.2,<1.0.0
29
28
  Requires-Dist: msgspec>=0.20.0,<1.0.0
29
+ Requires-Dist: apilmoji[tqdm]>=0.1.2,<1.0.0
30
30
  Requires-Dist: beautifulsoup4>=4.12.0,<5.0.0
31
31
  Requires-Dist: bilibili-api-python>=17.4.0,<18.0.0
32
32
  Requires-Dist: nonebot-plugin-alconna>=0.59.4,<1.0.0
@@ -258,6 +258,10 @@ parser_custom_font="LXGWZhenKaiGB-Regular.ttf"
258
258
  # [可选] 是否需要转发媒体内容(超过 4 项时始终使用合并转发)
259
259
  parser_need_forward_contents=True
260
260
 
261
+ # [可选] emoji 渲染 CDN
262
+ # 例如 ELK_SH_CDN = "https://emojicdn.elk.sh", MQRIO_DEV_CDN = "https://emoji-cdn.mqrio.dev"
263
+ parser_emoji_cdn="https://emojicdn.elk.sh"
264
+
261
265
  # [可选] emoji 渲染样式 "apple", "google", "twitter", "facebook"(默认)
262
266
  parser_emoji_style="facebook"
263
267
  ```
@@ -210,6 +210,10 @@ parser_custom_font="LXGWZhenKaiGB-Regular.ttf"
210
210
  # [可选] 是否需要转发媒体内容(超过 4 项时始终使用合并转发)
211
211
  parser_need_forward_contents=True
212
212
 
213
+ # [可选] emoji 渲染 CDN
214
+ # 例如 ELK_SH_CDN = "https://emojicdn.elk.sh", MQRIO_DEV_CDN = "https://emoji-cdn.mqrio.dev"
215
+ parser_emoji_cdn="https://emojicdn.elk.sh"
216
+
213
217
  # [可选] emoji 渲染样式 "apple", "google", "twitter", "facebook"(默认)
214
218
  parser_emoji_style="facebook"
215
219
  ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nonebot-plugin-parser"
3
- version = "2.2.4"
3
+ version = "2.3.0"
4
4
  description = "NoneBot2 链接分享解析 Alconna 版, 通用媒体卡片渲染(PIL 实现), 支持 B站/抖音/快手/微博/小红书/youtube/tiktok/twitter/acfun/nga"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -41,12 +41,12 @@ classifiers = [
41
41
  dependencies = [
42
42
  "nonebot2>=2.4.3,<3.0.0",
43
43
  "pillow>=11.0.0",
44
- "pilmoji-for-parser>=0.1.3",
45
44
  "tqdm>=4.67.1,<5.0.0",
46
45
  "aiofiles>=25.1.0",
47
46
  "curl_cffi>=0.13.0,<1.0.0",
48
47
  "httpx>=0.27.2,<1.0.0",
49
48
  "msgspec>=0.20.0,<1.0.0",
49
+ "apilmoji[tqdm]>=0.1.2,<1.0.0",
50
50
  "beautifulsoup4>=4.12.0,<5.0.0",
51
51
  "bilibili-api-python>=17.4.0,<18.0.0",
52
52
  "nonebot-plugin-alconna>=0.59.4,<1.0.0",
@@ -105,12 +105,12 @@ conflicts = [
105
105
  ]
106
106
 
107
107
  [tool.uv.sources]
108
- nonebug = { git = "https://github.com/fllesser/nonebug", rev = "dev/migrate-uv" }
108
+ nonebug = { git = "https://github.com/nonebot/nonebug" }
109
109
 
110
110
  [tool.bumpversion]
111
111
  tag = true
112
112
  commit = true
113
- current_version = "2.2.4"
113
+ current_version = "2.3.0"
114
114
  message = "release: bump vesion from {current_version} to {new_version}"
115
115
 
116
116
  [[tool.bumpversion.files]]
@@ -136,9 +136,9 @@ adapters = [
136
136
  plugins = ["nonebot_plugin_parser"]
137
137
 
138
138
  [tool.poe.tasks]
139
- test_others = "pytest tests/others --cov=src --cov-report=xml --junitxml=junit.xml -n auto"
140
- test_parsers = "pytest tests/parsers --cov=src --cov-report=xml --junitxml=junit.xml -n auto"
141
- test_render = "pytest tests/render --cov=src --cov-report=xml --junitxml=junit.xml"
139
+ test-other = "pytest tests/others --cov=src --cov-report=xml --junitxml=junit.xml -n auto"
140
+ test-parser = "pytest tests/parsers --cov=src --cov-report=xml --junitxml=junit.xml -n auto"
141
+ test-render = "pytest tests/renders --cov=src --cov-report=xml --junitxml=junit.xml"
142
142
  bump = "bump-my-version bump"
143
143
  show-bump = "bump-my-version show-bump"
144
144
 
@@ -2,7 +2,7 @@ from enum import Enum
2
2
  from pathlib import Path
3
3
 
4
4
  from nonebot import require, get_driver, get_plugin_config
5
- from pilmoji import EmojiStyle
5
+ from apilmoji import ELK_SH_CDN, EmojiStyle
6
6
  from pydantic import BaseModel
7
7
  from bilibili_api.video import VideoCodecs, VideoQuality
8
8
 
@@ -56,6 +56,8 @@ class Config(BaseModel):
56
56
  """自定义字体"""
57
57
  parser_need_forward_contents: bool = True
58
58
  """是否需要转发媒体内容"""
59
+ parser_emoji_cdn: str = ELK_SH_CDN
60
+ """Pilmoji 表情 CDN"""
59
61
  parser_emoji_style: EmojiStyle = EmojiStyle.FACEBOOK
60
62
  """Pilmoji 表情样式"""
61
63
 
@@ -149,6 +151,11 @@ class Config(BaseModel):
149
151
  """是否需要转发媒体内容"""
150
152
  return self.parser_need_forward_contents
151
153
 
154
+ @property
155
+ def emoji_cdn(self) -> str:
156
+ """Pilmoji 表情 CDN"""
157
+ return self.parser_emoji_cdn
158
+
152
159
  @property
153
160
  def emoji_style(self) -> EmojiStyle:
154
161
  """Pilmoji 表情样式"""
@@ -63,7 +63,7 @@ class StreamDownloader:
63
63
  raise ZeroSizeException
64
64
 
65
65
  if (file_size := content_length / 1024 / 1024) > pconfig.max_size:
66
- logger.warning(f"媒体 url: {url} 大小 {file_size:.2f} MB 超过 {{pconfig.max_size}} MB, 取消下载")
66
+ logger.warning(f"媒体 url: {url} 大小 {file_size:.2f} MB 超过 {pconfig.max_size} MB, 取消下载")
67
67
  raise SizeLimitException
68
68
 
69
69
  with self.get_progress_bar(file_name, content_length) as bar:
@@ -8,7 +8,7 @@ from typing_extensions import override
8
8
 
9
9
  from PIL import Image, ImageDraw, ImageFont
10
10
  from nonebot import logger
11
- from pilmoji import Pilmoji, EmojiCDNSource
11
+ from apilmoji import Apilmoji, EmojiCDNSource
12
12
 
13
13
  from .base import ParseResult, ImageRenderer
14
14
  from ..config import pconfig
@@ -299,9 +299,12 @@ class CommonRenderer(ImageRenderer):
299
299
  DEFAULT_VIDEO_BUTTON_PATH: ClassVar[Path] = RESOURCES_DIR / _BUTTON_FILENAME
300
300
  """默认视频按钮路径"""
301
301
  EMOJI_SOURCE: ClassVar[EmojiCDNSource] = EmojiCDNSource(
302
- style=pconfig.emoji_style, cache_dir=pconfig.cache_dir / _EMOJIS
302
+ base_url=pconfig.emoji_cdn,
303
+ style=pconfig.emoji_style,
304
+ cache_dir=pconfig.cache_dir / _EMOJIS,
305
+ enable_tqdm=True,
303
306
  )
304
- PILMOJI: ClassVar[Pilmoji] = Pilmoji(source=EMOJI_SOURCE)
307
+ """Emoji Source"""
305
308
 
306
309
  @classmethod
307
310
  def load_resources(cls):
@@ -346,13 +349,22 @@ class CommonRenderer(ImageRenderer):
346
349
  async def text(
347
350
  cls,
348
351
  ctx: RenderContext,
349
- text: str,
350
352
  xy: tuple[int, int],
353
+ lines: list[str],
354
+ font: FontInfo,
351
355
  fill: Color,
352
- font: ImageFont.FreeTypeFont,
353
- ):
356
+ ) -> int:
354
357
  """绘制文本"""
355
- await cls.PILMOJI.text(ctx.image, xy, text, fill=fill, font=font)
358
+ await Apilmoji.text(
359
+ ctx.image,
360
+ xy,
361
+ lines,
362
+ font.font,
363
+ fill=fill,
364
+ line_height=font.line_height,
365
+ source=cls.EMOJI_SOURCE,
366
+ )
367
+ return font.line_height * len(lines)
356
368
 
357
369
  @override
358
370
  async def render_image(self, result: ParseResult) -> bytes:
@@ -931,28 +943,24 @@ class CommonRenderer(ImageRenderer):
931
943
  text_y = text_start_y
932
944
 
933
945
  # 发布者名称(蓝色)
934
- for line in section.name_lines:
935
- await self.text(
936
- ctx,
937
- line,
938
- (text_x, text_y),
939
- self.HEADER_COLOR,
940
- self.fontset.name_font.font,
941
- )
942
- text_y += self.fontset.name_font.line_height
946
+ text_y += await self.text(
947
+ ctx,
948
+ (text_x, text_y),
949
+ section.name_lines,
950
+ self.fontset.name_font,
951
+ fill=self.HEADER_COLOR,
952
+ )
943
953
 
944
954
  # 时间(灰色)
945
955
  if section.time_lines:
946
956
  text_y += self.NAME_TIME_GAP
947
- for line in section.time_lines:
948
- await self.text(
949
- ctx,
950
- line,
951
- (text_x, text_y),
952
- self.EXTRA_COLOR,
953
- self.fontset.extra_font.font,
954
- )
955
- text_y += self.fontset.extra_font.line_height
957
+ text_y += await self.text(
958
+ ctx,
959
+ (text_x, text_y),
960
+ section.time_lines,
961
+ self.fontset.extra_font,
962
+ fill=self.EXTRA_COLOR,
963
+ )
956
964
 
957
965
  # 在右侧绘制平台 logo(仅在非转发内容时绘制)
958
966
  if ctx.not_repost:
@@ -969,15 +977,14 @@ class CommonRenderer(ImageRenderer):
969
977
 
970
978
  async def _draw_title(self, ctx: RenderContext, lines: list[str]) -> None:
971
979
  """绘制标题"""
972
- for line in lines:
973
- await self.text(
974
- ctx,
975
- line,
976
- (self.PADDING, ctx.y_pos),
977
- self.TEXT_COLOR,
978
- self.fontset.title_font.font,
979
- )
980
- ctx.y_pos += self.fontset.title_font.line_height
980
+ ctx.y_pos += await self.text(
981
+ ctx,
982
+ (self.PADDING, ctx.y_pos),
983
+ lines,
984
+ self.fontset.title_font,
985
+ self.TEXT_COLOR,
986
+ )
987
+
981
988
  ctx.y_pos += self.SECTION_SPACING
982
989
 
983
990
  def _draw_cover(self, ctx: RenderContext, cover_img: PILImage) -> None:
@@ -1000,30 +1007,26 @@ class CommonRenderer(ImageRenderer):
1000
1007
 
1001
1008
  async def _draw_text(self, ctx: RenderContext, lines: list[str]) -> None:
1002
1009
  """绘制文本内容"""
1003
- for line in lines:
1004
- await self.text(
1005
- ctx,
1006
- line,
1007
- (self.PADDING, ctx.y_pos),
1008
- self.TEXT_COLOR,
1009
- self.fontset.text_font.font,
1010
- )
1011
- ctx.y_pos += self.fontset.text_font.line_height
1010
+ ctx.y_pos += await self.text(
1011
+ ctx,
1012
+ (self.PADDING, ctx.y_pos),
1013
+ lines,
1014
+ self.fontset.text_font,
1015
+ fill=self.TEXT_COLOR,
1016
+ )
1012
1017
  ctx.y_pos += self.SECTION_SPACING
1013
1018
 
1014
1019
  async def _draw_graphics(self, ctx: RenderContext, section: GraphicsSectionData) -> None:
1015
1020
  """绘制图文内容"""
1016
1021
  # 绘制文本内容(如果有)
1017
1022
  if section.text_lines:
1018
- for line in section.text_lines:
1019
- await self.text(
1020
- ctx,
1021
- line,
1022
- (self.PADDING, ctx.y_pos),
1023
- self.TEXT_COLOR,
1024
- self.fontset.text_font.font,
1025
- )
1026
- ctx.y_pos += self.fontset.text_font.line_height
1023
+ ctx.y_pos += await self.text(
1024
+ ctx,
1025
+ (self.PADDING, ctx.y_pos),
1026
+ section.text_lines,
1027
+ self.fontset.text_font,
1028
+ fill=self.TEXT_COLOR,
1029
+ )
1027
1030
  ctx.y_pos += self.SECTION_SPACING # 文本和图片之间的间距
1028
1031
 
1029
1032
  # 绘制图片(居中)
@@ -1038,28 +1041,25 @@ class CommonRenderer(ImageRenderer):
1038
1041
  extra_font_info = self.fontset.extra_font
1039
1042
  text_width = extra_font_info.get_text_width(section.alt_text)
1040
1043
  text_x = self.PADDING + (ctx.content_width - text_width) // 2
1041
- await self.text(
1044
+ ctx.y_pos += await self.text(
1042
1045
  ctx,
1043
- section.alt_text,
1044
1046
  (text_x, ctx.y_pos),
1045
- self.EXTRA_COLOR,
1046
- extra_font_info.font,
1047
+ [section.alt_text],
1048
+ self.fontset.extra_font,
1049
+ fill=self.EXTRA_COLOR,
1047
1050
  )
1048
- ctx.y_pos += extra_font_info.line_height
1049
1051
 
1050
1052
  ctx.y_pos += self.SECTION_SPACING
1051
1053
 
1052
1054
  async def _draw_extra(self, ctx: RenderContext, lines: list[str]) -> None:
1053
1055
  """绘制额外信息"""
1054
- for line in lines:
1055
- await self.text(
1056
- ctx,
1057
- line,
1058
- (self.PADDING, ctx.y_pos),
1059
- self.EXTRA_COLOR,
1060
- self.fontset.extra_font.font,
1061
- )
1062
- ctx.y_pos += self.fontset.extra_font.line_height
1056
+ ctx.y_pos += await self.text(
1057
+ ctx,
1058
+ (self.PADDING, ctx.y_pos),
1059
+ lines,
1060
+ self.fontset.extra_font,
1061
+ fill=self.EXTRA_COLOR,
1062
+ )
1063
1063
 
1064
1064
  def _draw_repost(self, ctx: RenderContext, section: RepostSectionData) -> None:
1065
1065
  """绘制转发内容"""
@@ -1273,8 +1273,8 @@ class CommonRenderer(ImageRenderer):
1273
1273
  if not text:
1274
1274
  return []
1275
1275
 
1276
- lines = []
1277
- paragraphs = text.split("\n")
1276
+ lines: list[str] = []
1277
+ paragraphs = text.splitlines()
1278
1278
 
1279
1279
  def is_punctuation(char: str) -> bool:
1280
1280
  """判断是否为不能为行首的标点符号"""