nonebot-plugin-htmlrender 0.6.2__tar.gz → 0.6.5__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 (56) hide show
  1. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/PKG-INFO +2 -2
  2. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/nonebot_plugin_htmlrender/browser.py +49 -15
  3. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/nonebot_plugin_htmlrender/config.py +25 -12
  4. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/nonebot_plugin_htmlrender/utils.py +13 -0
  5. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/pyproject.toml +2 -2
  6. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/tests/test_browser.py +24 -0
  7. nonebot_plugin_htmlrender-0.6.5/uv.lock +1782 -0
  8. nonebot_plugin_htmlrender-0.6.2/nonebot_plugin_htmlrender/compat.py +0 -24
  9. nonebot_plugin_htmlrender-0.6.2/uv.lock +0 -1729
  10. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/.github/workflows/codecov.yml +0 -0
  11. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/.github/workflows/publish.yml +0 -0
  12. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/.gitignore +0 -0
  13. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/.pre-commit-config.yaml +0 -0
  14. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/.python-version +0 -0
  15. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/LICENSE +0 -0
  16. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/README.md +0 -0
  17. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/docs/example.md +0 -0
  18. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/docs/html2pic.png +0 -0
  19. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/docs/md2pic.png +0 -0
  20. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/docs/template2pic.png +0 -0
  21. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/docs/text2pic.png +0 -0
  22. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/example/.env.dev +0 -0
  23. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/example/bot.py +0 -0
  24. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/example/plugins/render/__init__.py +0 -0
  25. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/example/plugins/render/html2pic.html +0 -0
  26. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/example/plugins/render/templates/markdown.css +0 -0
  27. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/example/plugins/render/templates/mystyle.css +0 -0
  28. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/example/plugins/render/templates/progress.html.jinja2 +0 -0
  29. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/example/plugins/render/templates/text.html +0 -0
  30. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/example/plugins/render/utils.py +0 -0
  31. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/nonebot_plugin_htmlrender/__init__.py +0 -0
  32. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/nonebot_plugin_htmlrender/consts.py +0 -0
  33. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/nonebot_plugin_htmlrender/data_source.py +0 -0
  34. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/nonebot_plugin_htmlrender/install.py +0 -0
  35. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/nonebot_plugin_htmlrender/process.py +0 -0
  36. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/nonebot_plugin_htmlrender/signal.py +0 -0
  37. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/nonebot_plugin_htmlrender/templates/github-markdown-light.css +0 -0
  38. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/nonebot_plugin_htmlrender/templates/katex/katex.min.b64_fonts.css +0 -0
  39. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/nonebot_plugin_htmlrender/templates/katex/katex.min.js +0 -0
  40. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/nonebot_plugin_htmlrender/templates/katex/mathtex-script-type.min.js +0 -0
  41. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/nonebot_plugin_htmlrender/templates/katex/mhchem.min.js +0 -0
  42. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/nonebot_plugin_htmlrender/templates/markdown.html +0 -0
  43. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/nonebot_plugin_htmlrender/templates/pygments-default.css +0 -0
  44. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/nonebot_plugin_htmlrender/templates/text.css +0 -0
  45. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/nonebot_plugin_htmlrender/templates/text.html +0 -0
  46. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/tests/__init__.py +0 -0
  47. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/tests/conftest.py +0 -0
  48. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/tests/resources/test_template_filter.png +0 -0
  49. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/tests/templates/markdown.css +0 -0
  50. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/tests/templates/mystyle.css +0 -0
  51. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/tests/templates/progress.html.jinja2 +0 -0
  52. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/tests/templates/text.html +0 -0
  53. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/tests/test_deprecated_decorator.py +0 -0
  54. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/tests/test_htmlrender.py +0 -0
  55. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/tests/test_install.py +0 -0
  56. {nonebot_plugin_htmlrender-0.6.2 → nonebot_plugin_htmlrender-0.6.5}/tests/test_process.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nonebot-plugin-htmlrender
3
- Version: 0.6.2
3
+ Version: 0.6.5
4
4
  Summary: 通过浏览器渲染图片
5
5
  Project-URL: Homepage, https://github.com/kexue-z/nonebot-plugin-htmlrender
6
6
  Project-URL: Bug Tracker, https://github.com/kexue-z/nonebot-plugin-htmlrender/issues
@@ -31,7 +31,7 @@ Requires-Python: <4.0,>=3.9
31
31
  Requires-Dist: aiofiles>=0.8.0
32
32
  Requires-Dist: jinja2>=3.0.3
33
33
  Requires-Dist: markdown>=3.3.6
34
- Requires-Dist: nonebot2>=2.2.0
34
+ Requires-Dist: nonebot2>=2.4.2
35
35
  Requires-Dist: playwright>=1.48.0
36
36
  Requires-Dist: pygments>=2.10.0
37
37
  Requires-Dist: pymdown-extensions>=9.1
@@ -13,7 +13,7 @@ from playwright.async_api import (
13
13
 
14
14
  from nonebot_plugin_htmlrender.config import plugin_config
15
15
  from nonebot_plugin_htmlrender.install import install_browser
16
- from nonebot_plugin_htmlrender.utils import proxy_settings, suppress_and_log
16
+ from nonebot_plugin_htmlrender.utils import proxy_settings, suppress_and_log, with_lock
17
17
 
18
18
  _browser: Optional[Browser] = None
19
19
  _playwright: Optional[Playwright] = None
@@ -53,8 +53,6 @@ async def init_browser(**kwargs) -> Browser:
53
53
  Raises:
54
54
  RuntimeError: 如果浏览器无法启动或安装失败。
55
55
  """
56
- await install_browser() # update playwright when start
57
- await check_playwright_env()
58
56
  return await start_browser(**kwargs)
59
57
 
60
58
 
@@ -72,12 +70,11 @@ async def get_new_page(device_scale_factor: float = 2, **kwargs) -> AsyncIterato
72
70
  """
73
71
  ctx = await get_browser()
74
72
  page = await ctx.new_page(device_scale_factor=device_scale_factor, **kwargs)
75
- try:
73
+ async with page:
76
74
  yield page
77
- finally:
78
- await page.close()
79
75
 
80
76
 
77
+ @with_lock
81
78
  async def get_browser(**kwargs) -> Browser:
82
79
  """
83
80
  获取浏览器实例。
@@ -117,6 +114,32 @@ async def _connect_via_cdp(**kwargs) -> Browser:
117
114
  raise RuntimeError("Playwright 未初始化")
118
115
 
119
116
 
117
+ async def _connect(browser_type: str, **kwargs) -> Browser:
118
+ """
119
+ 通过 Playwright 协议连接浏览器。
120
+
121
+ Args:
122
+ browser_type (str): 浏览器类型。
123
+ **kwargs: 传递给`playwright.connect`的关键字参数。
124
+
125
+ Returns:
126
+ Browser: 启动的浏览器实例。
127
+
128
+ Raises:
129
+ RuntimeError: 如果 Playwright 未初始化。
130
+ """
131
+ _browser_cls: BrowserType = getattr(_playwright, browser_type)
132
+ kwargs["ws_endpoint"] = plugin_config.htmlrender_connect
133
+ logger.info(
134
+ f"正在使用 Playwright 协议连接 {browser_type}({plugin_config.htmlrender_connect})"
135
+ )
136
+ if _playwright is not None:
137
+ return await _browser_cls.connect(**kwargs)
138
+ else:
139
+ raise RuntimeError("Playwright 未初始化")
140
+
141
+
142
+ @with_lock
120
143
  async def start_browser(**kwargs) -> Browser:
121
144
  """
122
145
  启动 Playwright 浏览器实例。
@@ -128,24 +151,35 @@ async def start_browser(**kwargs) -> Browser:
128
151
  Browser: 启动的浏览器实例。
129
152
  """
130
153
  global _browser, _playwright
154
+
155
+ await shutdown_browser()
131
156
  _playwright = await async_playwright().start()
132
157
 
133
158
  if (
134
159
  plugin_config.htmlrender_browser == "chromium"
135
160
  and plugin_config.htmlrender_connect_over_cdp
136
161
  ):
137
- return await _connect_via_cdp(**kwargs)
162
+ _browser = await _connect_via_cdp(**kwargs)
163
+ elif plugin_config.htmlrender_connect:
164
+ _browser = await _connect(plugin_config.htmlrender_browser, **kwargs)
165
+ else:
166
+ if plugin_config.htmlrender_browser_channel:
167
+ kwargs["channel"] = plugin_config.htmlrender_browser_channel
138
168
 
139
- if plugin_config.htmlrender_browser_channel:
140
- kwargs["channel"] = plugin_config.htmlrender_browser_channel
169
+ if plugin_config.htmlrender_proxy_host:
170
+ kwargs["proxy"] = proxy_settings(plugin_config.htmlrender_proxy_host)
141
171
 
142
- if plugin_config.htmlrender_proxy_host:
143
- kwargs["proxy"] = proxy_settings(plugin_config.htmlrender_proxy_host)
172
+ if plugin_config.htmlrender_browser_executable_path:
173
+ kwargs["executable_path"] = plugin_config.htmlrender_browser_executable_path
174
+ else:
175
+ try:
176
+ await check_playwright_env()
177
+ except RuntimeError:
178
+ await install_browser()
179
+ await check_playwright_env()
144
180
 
145
- if plugin_config.htmlrender_browser_executable_path:
146
- kwargs["executable_path"] = plugin_config.htmlrender_browser_executable_path
181
+ _browser = await _launch(plugin_config.htmlrender_browser, **kwargs)
147
182
 
148
- _browser = await _launch(plugin_config.htmlrender_browser, **kwargs)
149
183
  return _browser
150
184
 
151
185
 
@@ -164,7 +198,7 @@ async def check_playwright_env():
164
198
  logger.info("Checking Playwright environment...")
165
199
  try:
166
200
  async with async_playwright() as p:
167
- await p.chromium.launch()
201
+ await getattr(p, plugin_config.htmlrender_browser).launch()
168
202
  except Exception as e:
169
203
  raise RuntimeError(
170
204
  "Playwright environment is not set up correctly. "
@@ -1,10 +1,9 @@
1
- from typing import Optional
2
- from typing_extensions import Self
1
+ from typing import Any, Optional
3
2
 
4
3
  from nonebot import get_driver, get_plugin_config
4
+ from nonebot.compat import model_validator
5
5
  from pydantic import BaseModel, Field
6
6
 
7
- from nonebot_plugin_htmlrender.compat import model_validator
8
7
  from nonebot_plugin_htmlrender.consts import BROWSER_CHANNEL_TYPES, BROWSER_ENGINE_TYPES
9
8
 
10
9
 
@@ -36,25 +35,39 @@ class Config(BaseModel):
36
35
  htmlrender_connect_over_cdp: Optional[str] = Field(
37
36
  default=None, description="通过 CDP 连接Playwright浏览器的端点地址。"
38
37
  )
38
+ htmlrender_connect: Optional[str] = Field(
39
+ default=None, description="通过Playwright协议连接Playwright浏览器的端点地址。"
40
+ )
39
41
 
40
42
  @model_validator(mode="after")
41
- def check_browser_channel(self) -> Self:
42
- if (
43
- self.htmlrender_browser_channel is not None
44
- and self.htmlrender_browser_channel not in BROWSER_CHANNEL_TYPES
45
- ):
43
+ @classmethod
44
+ def check_browser_channel(cls, data: Any) -> Any:
45
+ browser_channel = (
46
+ data.get("htmlrender_browser_channel")
47
+ if isinstance(data, dict)
48
+ else getattr(data, "htmlrender_browser_channel", None)
49
+ )
50
+
51
+ if browser_channel is not None and browser_channel not in BROWSER_CHANNEL_TYPES:
46
52
  raise ValueError(
47
53
  f"Invalid browser channel type. Must be one of {BROWSER_CHANNEL_TYPES}"
48
54
  )
49
- return self
55
+ return data
50
56
 
51
57
  @model_validator(mode="after")
52
- def check_browser(self) -> Self:
53
- if self.htmlrender_browser not in BROWSER_ENGINE_TYPES:
58
+ @classmethod
59
+ def check_browser(cls, data: Any) -> Any:
60
+ browser = (
61
+ data.get("htmlrender_browser", "chromium")
62
+ if isinstance(data, dict)
63
+ else getattr(data, "htmlrender_browser", "chromium")
64
+ )
65
+
66
+ if browser not in BROWSER_ENGINE_TYPES:
54
67
  raise ValueError(
55
68
  f"Invalid browser type. Must be one of {BROWSER_ENGINE_TYPES}"
56
69
  )
57
- return self
70
+ return data
58
71
 
59
72
 
60
73
  global_config = get_driver().config
@@ -1,3 +1,5 @@
1
+ from asyncio import Lock
2
+ from collections.abc import Awaitable
1
3
  from contextlib import contextmanager
2
4
  from functools import wraps
3
5
  import re
@@ -146,3 +148,14 @@ def proxy_settings(proxy_host: Optional[str]) -> Optional[dict]:
146
148
  proxy["bypass"] = plugin_config.htmlrender_proxy_host_bypass
147
149
 
148
150
  return proxy
151
+
152
+
153
+ def with_lock(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[R]]:
154
+ lock = Lock()
155
+
156
+ @wraps(func)
157
+ async def wrapper(*args, **kwargs) -> R:
158
+ async with lock:
159
+ return await func(*args, **kwargs)
160
+
161
+ return wrapper
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nonebot-plugin-htmlrender"
3
- version = "0.6.2"
3
+ version = "0.6.5"
4
4
  description = "通过浏览器渲染图片"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "kexue", email = "x@kexue-cloud.cn" }]
@@ -10,7 +10,7 @@ dependencies = [
10
10
  "aiofiles>=0.8.0",
11
11
  "jinja2>=3.0.3",
12
12
  "markdown>=3.3.6",
13
- "nonebot2>=2.2.0",
13
+ "nonebot2>=2.4.2",
14
14
  "playwright>=1.48.0",
15
15
  "pygments>=2.10.0",
16
16
  "pymdown-extensions>=9.1",
@@ -39,6 +39,7 @@ def browser_config() -> dict[str, str]:
39
39
  return {
40
40
  "browser": "chromium",
41
41
  "cdp": "ws://localhost:9222",
42
+ "pwp": "ws://localhost:3000/chromium/playwright",
42
43
  }
43
44
 
44
45
 
@@ -169,6 +170,29 @@ async def test_connect_via_cdp(
169
170
  assert browser == mock_browser
170
171
 
171
172
 
173
+ @pytest.mark.asyncio
174
+ async def test_connect(
175
+ mocker: MockerFixture, mock_browser: Browser, browser_config: dict[str, str]
176
+ ) -> None:
177
+ """测试通过Playwright协议连接浏览器"""
178
+ from nonebot_plugin_htmlrender.browser import start_browser
179
+
180
+ mocker.patch(
181
+ "nonebot_plugin_htmlrender.browser._connect", return_value=mock_browser
182
+ )
183
+ mocker.patch(
184
+ "nonebot_plugin_htmlrender.browser.plugin_config.htmlrender_browser",
185
+ browser_config["browser"],
186
+ )
187
+ mocker.patch(
188
+ "nonebot_plugin_htmlrender.browser.plugin_config.htmlrender_connect",
189
+ browser_config["pwp"],
190
+ )
191
+
192
+ browser = await start_browser()
193
+ assert browser == mock_browser
194
+
195
+
172
196
  @pytest.mark.parametrize(
173
197
  ("proxy_url", "expected"),
174
198
  [