nonebot-plugin-htmlrender 0.4.0__tar.gz → 0.5.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 (48) hide show
  1. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/PKG-INFO +1 -2
  2. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/nonebot_plugin_htmlrender/__init__.py +3 -3
  3. nonebot_plugin_htmlrender-0.5.0/nonebot_plugin_htmlrender/browser.py +356 -0
  4. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/nonebot_plugin_htmlrender/config.py +6 -1
  5. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/pyproject.toml +2 -2
  6. nonebot_plugin_htmlrender-0.5.0/tests/conftest.py +15 -0
  7. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/uv.lock +5 -5
  8. nonebot_plugin_htmlrender-0.4.0/nonebot_plugin_htmlrender/browser.py +0 -131
  9. nonebot_plugin_htmlrender-0.4.0/tests/conftest.py +0 -10
  10. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/.flake8 +0 -0
  11. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/.github/workflows/codecov.yml +0 -0
  12. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/.github/workflows/publish.yml +0 -0
  13. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/.gitignore +0 -0
  14. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/.python-version +0 -0
  15. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/LICENSE +0 -0
  16. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/README.md +0 -0
  17. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/docs/example.md +0 -0
  18. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/docs/html2pic.png +0 -0
  19. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/docs/md2pic.png +0 -0
  20. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/docs/template2pic.png +0 -0
  21. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/docs/text2pic.png +0 -0
  22. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/example/.env.dev +0 -0
  23. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/example/bot.py +0 -0
  24. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/example/plugins/render/__init__.py +0 -0
  25. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/example/plugins/render/html2pic.html +0 -0
  26. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/example/plugins/render/templates/markdown.css +0 -0
  27. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/example/plugins/render/templates/mystyle.css +0 -0
  28. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/example/plugins/render/templates/progress.html.jinja2 +0 -0
  29. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/example/plugins/render/templates/text.html +0 -0
  30. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/example/plugins/render/utils.py +0 -0
  31. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/nonebot_plugin_htmlrender/data_source.py +0 -0
  32. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/nonebot_plugin_htmlrender/templates/github-markdown-light.css +0 -0
  33. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/nonebot_plugin_htmlrender/templates/katex/katex.min.b64_fonts.css +0 -0
  34. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/nonebot_plugin_htmlrender/templates/katex/katex.min.js +0 -0
  35. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/nonebot_plugin_htmlrender/templates/katex/mathtex-script-type.min.js +0 -0
  36. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/nonebot_plugin_htmlrender/templates/katex/mhchem.min.js +0 -0
  37. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/nonebot_plugin_htmlrender/templates/markdown.html +0 -0
  38. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/nonebot_plugin_htmlrender/templates/pygments-default.css +0 -0
  39. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/nonebot_plugin_htmlrender/templates/text.css +0 -0
  40. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/nonebot_plugin_htmlrender/templates/text.html +0 -0
  41. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/tests/__init__.py +0 -0
  42. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/tests/templates/markdown.css +0 -0
  43. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/tests/templates/mystyle.css +0 -0
  44. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/tests/templates/progress.html.jinja2 +0 -0
  45. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/tests/templates/text.html +0 -0
  46. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/tests/test_htmlrender.py +0 -0
  47. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/tests/test_template_filter.png +0 -0
  48. {nonebot_plugin_htmlrender-0.4.0 → nonebot_plugin_htmlrender-0.5.0}/tests/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nonebot-plugin-htmlrender
3
- Version: 0.4.0
3
+ Version: 0.5.0
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
@@ -26,7 +26,6 @@ License: MIT License
26
26
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
27
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
28
  SOFTWARE.
29
- License-File: LICENSE
30
29
  Requires-Python: <4.0,>=3.9
31
30
  Requires-Dist: aiofiles>=0.8.0
32
31
  Requires-Dist: jinja2>=3.0.3
@@ -6,6 +6,7 @@ from .browser import (
6
6
  get_browser as get_browser,
7
7
  get_new_page as get_new_page,
8
8
  shutdown_browser as shutdown_browser,
9
+ init_browser as init_browser,
9
10
  )
10
11
  from .data_source import (
11
12
  capture_element as capture_element,
@@ -35,9 +36,8 @@ async def init(**kwargs):
35
36
  Returns:
36
37
  Browser: Browser
37
38
  """
38
- browser = await get_browser(**kwargs)
39
- logger.info("Browser Started.")
40
- return browser
39
+ logger.info("Browser Starting.")
40
+ return await init_browser(**kwargs)
41
41
 
42
42
 
43
43
  @driver.on_shutdown
@@ -0,0 +1,356 @@
1
+ import asyncio
2
+ import os
3
+ import socket
4
+ import sys
5
+ from contextlib import asynccontextmanager, contextmanager
6
+ from dataclasses import dataclass
7
+ from enum import Enum
8
+ from typing import Optional, Literal, AsyncIterator, TextIO
9
+ from urllib.parse import urlparse
10
+
11
+ from nonebot.log import logger
12
+ from playwright.async_api import Browser, Error, Page, Playwright, async_playwright
13
+
14
+ from .config import plugin_config
15
+
16
+ _browser: Optional[Browser] = None
17
+ _playwright: Optional[Playwright] = None
18
+ BrowserType = Literal["chromium", "firefox", "webkit"]
19
+
20
+
21
+ @contextmanager
22
+ def _suppress_and_log():
23
+ """抑制并记录关闭期间的任何异常。"""
24
+ try:
25
+ yield
26
+ except Exception as e:
27
+ logger.opt(exception=e).warning("关闭 playwright 时发生错误。")
28
+
29
+
30
+ async def init_browser(**kwargs) -> Browser:
31
+ """初始化全局 playwright 浏览器实例并返回浏览器实例。"""
32
+ try:
33
+ return await start_browser(**kwargs)
34
+ except Error:
35
+ await install_browser()
36
+ return await start_browser(**kwargs)
37
+
38
+
39
+ async def start_browser(**kwargs) -> Browser:
40
+ """
41
+ 启动 playwright 浏览器实例。
42
+ Args:
43
+ **kwargs (Any): 传递给 `playwright.launch` 的关键字参数。
44
+
45
+ Returns:
46
+ Browser: 浏览器实例。
47
+ """
48
+ global _browser, _playwright
49
+ browser_type = (
50
+ plugin_config.htmlrender_browser.lower()
51
+ if plugin_config.htmlrender_browser
52
+ else "chromium"
53
+ )
54
+
55
+ if plugin_config.htmlrender_browser_channel:
56
+ kwargs["channel"] = plugin_config.htmlrender_browser_channel
57
+
58
+ if plugin_config.htmlrender_proxy_host:
59
+ kwargs["proxy"] = {
60
+ "server": plugin_config.htmlrender_proxy_host,
61
+ }
62
+
63
+ _playwright = await async_playwright().start()
64
+
65
+ match browser_type:
66
+ case "firefox":
67
+ logger.info("使用 Firefox 启动中...")
68
+ _browser = await _playwright.firefox.launch(**kwargs)
69
+ logger.debug(f"Firefox 路径: {_playwright.firefox.executable_path}")
70
+
71
+ case "webkit":
72
+ logger.info("使用 WebKit 启动中...")
73
+ _browser = await _playwright.webkit.launch(**kwargs)
74
+ logger.debug(f"WebKit 路径: {_playwright.webkit.executable_path}")
75
+
76
+ case "chromium":
77
+ if plugin_config.htmlrender_connect_over_cdp:
78
+ logger.info("使用 Chromium CDP 连接中...")
79
+ return await _playwright.chromium.connect_over_cdp(**kwargs)
80
+
81
+ logger.info("使用 Chromium 启动中...")
82
+ _browser = await _playwright.chromium.launch(**kwargs)
83
+ logger.debug(f"Chromium 路径: {_playwright.chromium.executable_path}")
84
+
85
+ case _:
86
+ raise ValueError(f"不支持的浏览器类型: {browser_type}")
87
+
88
+ return _browser
89
+
90
+
91
+ def get_browser() -> Browser:
92
+ """"""
93
+ if not _browser:
94
+ raise RuntimeError("playwright is not initalized")
95
+ return _browser
96
+
97
+
98
+ @asynccontextmanager
99
+ async def get_new_page(device_scale_factor: float = 2, **kwargs) -> AsyncIterator[Page]:
100
+ """
101
+ 获取一个新的页面的上下文管理器。
102
+ Args:
103
+ device_scale_factor (float): 设备缩放因子。
104
+ **kwargs (Any): 传递给 `browser.new_context` 的关键字参数。
105
+
106
+ Returns:
107
+ AsyncIterator[Page]: 页面对象。
108
+ """
109
+ ctx = get_browser()
110
+ page = await ctx.new_page(device_scale_factor=device_scale_factor, **kwargs)
111
+ try:
112
+ yield page
113
+ finally:
114
+ await page.close()
115
+
116
+
117
+ async def shutdown_browser() -> None:
118
+ """关闭浏览器实例。"""
119
+ if _browser:
120
+ with _suppress_and_log():
121
+ await _browser.close()
122
+ if _playwright:
123
+ with _suppress_and_log():
124
+ await _playwright.stop()
125
+
126
+
127
+ @dataclass
128
+ class MirrorSource:
129
+ name: str
130
+ url: str
131
+ priority: int
132
+
133
+
134
+ async def check_mirror_connectivity(
135
+ mirrors: list[MirrorSource], timeout: int = 5
136
+ ) -> Optional[MirrorSource]:
137
+ """
138
+ 检查镜像源的可用性并返回最佳镜像源。
139
+ Args:
140
+ mirrors (list[MirrorSource]): 镜像源列表。
141
+ timeout (int): 连接超时时间。
142
+
143
+ Returns:
144
+ Optional[MirrorSource]: 最佳镜像源。
145
+ """
146
+
147
+ async def _check_single_mirror(mirror: MirrorSource) -> tuple[MirrorSource, float]:
148
+ """
149
+ 检查单个镜像源的可用性。
150
+ Args:
151
+ mirror (MirrorSource): 镜像源。
152
+
153
+ Returns:
154
+ tuple[MirrorSource, float]: 镜像源和连接耗时。
155
+ """
156
+ try:
157
+ parsed_url = urlparse(mirror.url)
158
+ host = parsed_url.hostname
159
+ port = parsed_url.port or (443 if parsed_url.scheme == "https" else 80)
160
+
161
+ start_time = asyncio.get_event_loop().time()
162
+
163
+ _, _ = await asyncio.wait_for(
164
+ asyncio.open_connection(host, port), timeout=timeout
165
+ )
166
+
167
+ elapsed = asyncio.get_event_loop().time() - start_time
168
+ return mirror, elapsed
169
+
170
+ except (asyncio.TimeoutError, ConnectionRefusedError, socket.gaierror) as e:
171
+ logger.debug(f"镜像源 {mirror.name} 连接失败: {str(e)}")
172
+ return mirror, float("inf")
173
+
174
+ tasks = [_check_single_mirror(mirror) for mirror in mirrors]
175
+ results = await asyncio.gather(*tasks)
176
+
177
+ available_mirrors = [(m, t) for m, t in results if t != float("inf")]
178
+ if not available_mirrors:
179
+ return None
180
+
181
+ return min(available_mirrors, key=lambda x: (x[1], -x[0].priority))[0]
182
+
183
+
184
+ @asynccontextmanager
185
+ async def download_host_context(mirrors: list[MirrorSource]) -> AsyncIterator[None]:
186
+ """
187
+ 为下载设置镜像源上下文管理器。
188
+ Args:
189
+ mirrors (list[MirrorSource]): 镜像源列表。
190
+
191
+ Returns:
192
+ AsyncIterator[None]: 上下文管理器。
193
+ """
194
+ had_original = "PLAYWRIGHT_DOWNLOAD_HOST" in os.environ
195
+ original_host = os.environ.get("PLAYWRIGHT_DOWNLOAD_HOST")
196
+
197
+ try:
198
+ best_mirror = await check_mirror_connectivity(mirrors)
199
+
200
+ if plugin_config.htmlrender_download_host:
201
+ logger.info(f"使用配置的下载源: {plugin_config.htmlrender_download_host}")
202
+ os.environ["PLAYWRIGHT_DOWNLOAD_HOST"] = (
203
+ plugin_config.htmlrender_download_host
204
+ )
205
+ elif best_mirror:
206
+ logger.info(f"使用镜像源: {best_mirror.name} ({best_mirror.url})")
207
+ os.environ["PLAYWRIGHT_DOWNLOAD_HOST"] = best_mirror.url
208
+ else:
209
+ logger.info("无可用镜像源,使用默认源")
210
+
211
+ yield
212
+ finally:
213
+ if had_original and original_host is not None:
214
+ os.environ["PLAYWRIGHT_DOWNLOAD_HOST"] = original_host
215
+ elif "PLAYWRIGHT_DOWNLOAD_HOST" in os.environ:
216
+ del os.environ["PLAYWRIGHT_DOWNLOAD_HOST"]
217
+
218
+
219
+ class MessageType(Enum):
220
+ PROGRESS = "progress"
221
+ DOWNLOAD = "download"
222
+ INFO = "info"
223
+
224
+
225
+ async def read_stream(stream, out: TextIO = sys.stdout) -> None:
226
+ """实时读取并输出流内容
227
+
228
+ Args:
229
+ stream: 要读取的流
230
+ out: 输出流,默认为 sys.stdout
231
+ """
232
+ last_progress = ""
233
+
234
+ def write_progress(_: str) -> None:
235
+ """写入进度条"""
236
+ out.write(f"\r{_}")
237
+ out.flush()
238
+
239
+ def ensure_newline() -> None:
240
+ """确保进度条后换行"""
241
+ nonlocal last_progress
242
+ if last_progress:
243
+ out.write("\n")
244
+ out.flush()
245
+ last_progress = ""
246
+
247
+ def get_message_type(_: str) -> MessageType:
248
+ """获取消息类型"""
249
+ if "|" in _ and "%" in _ and ("MiB" in _ or "KiB" in _):
250
+ return MessageType.PROGRESS
251
+ elif _.startswith("Downloading "):
252
+ return MessageType.DOWNLOAD
253
+ return MessageType.INFO
254
+
255
+ while True:
256
+ line = await stream.readline()
257
+ if not line:
258
+ break
259
+
260
+ text = line.decode().strip()
261
+ if not text:
262
+ continue
263
+
264
+ msg_type = get_message_type(text)
265
+
266
+ match msg_type:
267
+ case MessageType.PROGRESS:
268
+ progress_text = text.split("|", 1)[1].strip()
269
+ write_progress(progress_text)
270
+ last_progress = text
271
+ case _:
272
+ ensure_newline()
273
+ logger.info(text)
274
+
275
+
276
+ async def execute_install_command(
277
+ install_cmd: list[str], timeout: int
278
+ ) -> tuple[bool, str]:
279
+ """
280
+ 执行安装命令并返回结果。
281
+ Args:
282
+ install_cmd (list[str]): 安装命令。
283
+ timeout (int): 等待超时时间。
284
+
285
+ Returns:
286
+ tuple[bool, str]: 是否安装成功和消息。
287
+ """
288
+ process = await asyncio.create_subprocess_exec(
289
+ *install_cmd,
290
+ stdout=asyncio.subprocess.PIPE,
291
+ stderr=asyncio.subprocess.PIPE,
292
+ )
293
+
294
+ try:
295
+ await asyncio.wait_for(
296
+ asyncio.gather(
297
+ read_stream(process.stdout),
298
+ read_stream(process.stderr),
299
+ ),
300
+ timeout=timeout,
301
+ )
302
+
303
+ await process.wait()
304
+ return (
305
+ process.returncode == 0,
306
+ "安装完成" if process.returncode == 0 else "安装失败",
307
+ )
308
+ except asyncio.TimeoutError:
309
+ return False, "安装超时"
310
+ finally:
311
+ if process and process.returncode is None:
312
+ process.kill()
313
+ await process.wait()
314
+
315
+
316
+ async def install_browser(
317
+ browser_type: str = "chromium", timeout: int = 300, retries: int = 2
318
+ ) -> bool:
319
+ """
320
+ 安装用于 HTML 渲染的浏览器。
321
+
322
+ Args:
323
+ browser_type (str): 要安装的浏览器类型 ("chromium"、"firefox" 或 "webkit")。
324
+ timeout (int): 安装过程中等待的最大秒数。
325
+ retries (int): 安装失败时的最大重试次数。
326
+
327
+ Returns:
328
+ bool: 如果安装成功返回 True,否则返回 False
329
+ """
330
+ mirrors = [
331
+ MirrorSource("淘宝", "https://npmmirror.com/mirrors/playwright/", 1),
332
+ ]
333
+ async with download_host_context(mirrors):
334
+ install_cmd = ["playwright", "install", "--with-deps", browser_type]
335
+
336
+ for attempt in range(1, retries + 1):
337
+ try:
338
+ logger.info(
339
+ f"正在安装 {browser_type}(第 {attempt}/{retries} 次尝试)..."
340
+ )
341
+ success, message = await execute_install_command(install_cmd, timeout)
342
+
343
+ if success:
344
+ logger.success(f"{browser_type} 安装成功")
345
+ return True
346
+ else:
347
+ logger.error(f"安装失败:{message}")
348
+
349
+ except Exception as e:
350
+ logger.error(f"安装过程中出现异常:{str(e)}")
351
+
352
+ if attempt < retries:
353
+ await asyncio.sleep(2)
354
+
355
+ logger.error(f"在 {retries} 次尝试后安装失败")
356
+ return False
@@ -1,6 +1,7 @@
1
1
  from typing import Optional
2
2
 
3
3
  from pydantic import BaseModel, Field
4
+ from nonebot import get_driver, get_plugin_config
4
5
 
5
6
 
6
7
  class Config(BaseModel):
@@ -9,4 +10,8 @@ class Config(BaseModel):
9
10
  htmlrender_proxy_host: Optional[str] = Field(default=None)
10
11
  htmlrender_browser_channel: Optional[str] = Field(default=None)
11
12
  htmlrender_browser_executable_path: Optional[str] = Field(default=None)
12
- htmlrender_connect_over_cdp: Optional[str] = Field(default=False)
13
+ htmlrender_connect_over_cdp: Optional[str] = Field(default=None)
14
+
15
+
16
+ global_config = get_driver().config
17
+ plugin_config = get_plugin_config(Config)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nonebot-plugin-htmlrender"
3
- version = "0.4.0"
3
+ version = "0.5.0"
4
4
  description = "通过浏览器渲染图片"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "kexue", email = "x@kexue-cloud.cn" }]
@@ -30,7 +30,7 @@ asyncio_mode = "auto"
30
30
  dev = [
31
31
  "nonebot-adapter-onebot>=2.4.6",
32
32
  "nonebot2[fastapi]>=2.3.3",
33
- "nonebug>=0.4.1",
33
+ "nonebug>=0.4.2",
34
34
  "pillow>=11.0.0",
35
35
  "pytest-asyncio>=0.24.0",
36
36
  ]
@@ -0,0 +1,15 @@
1
+ import nonebot
2
+ import pytest
3
+ from pytest_asyncio import is_async_test
4
+
5
+
6
+ def pytest_collection_modifyitems(items: list[pytest.Item]):
7
+ pytest_asyncio_tests = (item for item in items if is_async_test(item))
8
+ session_scope_marker = pytest.mark.asyncio(loop_scope="session")
9
+ for async_test in pytest_asyncio_tests:
10
+ async_test.add_marker(session_scope_marker, append=False)
11
+
12
+
13
+ @pytest.fixture(scope="session", autouse=True)
14
+ async def after_nonebot_init(after_nonebot_init: None):
15
+ nonebot.require("nonebot_plugin_htmlrender")
@@ -612,7 +612,7 @@ wheels = [
612
612
 
613
613
  [[package]]
614
614
  name = "nonebot-plugin-htmlrender"
615
- version = "0.4.0"
615
+ version = "0.5.0"
616
616
  source = { editable = "." }
617
617
  dependencies = [
618
618
  { name = "aiofiles" },
@@ -650,7 +650,7 @@ requires-dist = [
650
650
  dev = [
651
651
  { name = "nonebot-adapter-onebot", specifier = ">=2.4.6" },
652
652
  { name = "nonebot2", extras = ["fastapi"], specifier = ">=2.3.3" },
653
- { name = "nonebug", specifier = ">=0.4.1" },
653
+ { name = "nonebug", specifier = ">=0.4.2" },
654
654
  { name = "pillow", specifier = ">=11.0.0" },
655
655
  { name = "pytest-asyncio", specifier = ">=0.24.0" },
656
656
  ]
@@ -681,7 +681,7 @@ fastapi = [
681
681
 
682
682
  [[package]]
683
683
  name = "nonebug"
684
- version = "0.4.1"
684
+ version = "0.4.2"
685
685
  source = { registry = "https://pypi.org/simple" }
686
686
  dependencies = [
687
687
  { name = "asgiref" },
@@ -690,9 +690,9 @@ dependencies = [
690
690
  { name = "pytest" },
691
691
  { name = "typing-extensions" },
692
692
  ]
693
- sdist = { url = "https://files.pythonhosted.org/packages/d1/57/4300262bbf6f8b7b69a75e8d827a32b1503fe9322210440ad785fd6bf3d0/nonebug-0.4.1.tar.gz", hash = "sha256:1bb96d715ec415351f2c7b871a86c64fdba533fb1a105e9a01cc1e250d37d52b", size = 11789 }
693
+ sdist = { url = "https://files.pythonhosted.org/packages/29/56/01eb465dc135787e463423bf4762b883159a639d02c3a577ab4efc8f9d56/nonebug-0.4.2.tar.gz", hash = "sha256:002dac00fed1fbff97574f3dffab2237fc41473b3bf3e73d2730ae34895bc137", size = 12118 }
694
694
  wheels = [
695
- { url = "https://files.pythonhosted.org/packages/ce/e0/e8d6e0db6cbff3edbb034284bd9451e9e380dde6c580fd22f1d6c2a91687/nonebug-0.4.1-py3-none-any.whl", hash = "sha256:553bfc408337fb33a19fcc936e24f4cc43200e4550a828f21e7f4406e93b62db", size = 14659 },
695
+ { url = "https://files.pythonhosted.org/packages/a0/4a/7592e899cd5074a9b70df007afe9b6f8bbaa02b8e9c1ce7f5c5498634cb7/nonebug-0.4.2-py3-none-any.whl", hash = "sha256:83d9229ec899819e9f5786eabc0c27b9dd0cd5d6e2e7438f59923ab822d5c555", size = 15030 },
696
696
  ]
697
697
 
698
698
  [[package]]
@@ -1,131 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- @Author : yanyongyu
5
- @Date : 2021-03-12 13:42:43
6
- @LastEditors : yanyongyu
7
- @LastEditTime : 2021-11-01 14:05:41
8
- @Description : None
9
- @GitHub : https://github.com/yanyongyu
10
- """
11
-
12
- __author__ = "yanyongyu"
13
-
14
- from contextlib import asynccontextmanager
15
- from typing import AsyncIterator, Optional
16
-
17
- from nonebot import get_plugin_config
18
- from nonebot.log import logger
19
- from playwright.async_api import Browser, Error, Page, Playwright, async_playwright
20
-
21
- from .config import Config
22
-
23
- config = get_plugin_config(Config)
24
-
25
- _browser: Optional[Browser] = None
26
- _playwright: Optional[Playwright] = None
27
-
28
-
29
- async def init(**kwargs) -> Browser:
30
- global _browser
31
- global _playwright
32
- _playwright = await async_playwright().start()
33
- try:
34
- _browser = await launch_browser(**kwargs)
35
- except Error:
36
- await install_browser()
37
- _browser = await launch_browser(**kwargs)
38
- return _browser
39
-
40
-
41
- async def launch_browser(**kwargs) -> Browser:
42
- assert _playwright is not None, "Playwright 未安装"
43
-
44
- if config.htmlrender_browser_channel:
45
- kwargs["channel"] = config.htmlrender_browser_channel
46
-
47
- if config.htmlrender_proxy_host:
48
- kwargs["proxy"] = {
49
- "server": config.htmlrender_proxy_host,
50
- }
51
-
52
- if config.htmlrender_browser_executable_path:
53
- kwargs["executable_path"] = config.htmlrender_browser_executable_path
54
-
55
- if config.htmlrender_browser:
56
- if config.htmlrender_browser.lower() == "firefox":
57
- logger.info("使用 Firefox 启动")
58
- return await _playwright.firefox.launch(**kwargs)
59
- elif config.htmlrender_browser.lower() == "webkit":
60
- logger.info("使用 Webkit 启动")
61
- return await _playwright.webkit.launch(**kwargs)
62
- else:
63
- logger.warning("浏览器参数错误,无 {} 浏览器核心可用,使用 Chromium 启动")
64
-
65
- if config.htmlrender_connect_over_cdp:
66
- logger.info("使用 chromium connect_over_cdp 启动")
67
- return await _playwright.chromium.connect_over_cdp(**kwargs)
68
-
69
- # 默认使用 chromium
70
- logger.info("使用 Chromium 启动")
71
- return await _playwright.chromium.launch(**kwargs)
72
-
73
-
74
- async def get_browser(**kwargs) -> Browser:
75
- return _browser if _browser and _browser.is_connected() else await init(**kwargs)
76
-
77
-
78
- @asynccontextmanager
79
- async def get_new_page(device_scale_factor: float = 2, **kwargs) -> AsyncIterator[Page]:
80
- browser = await get_browser()
81
- page = await browser.new_page(device_scale_factor=device_scale_factor, **kwargs)
82
- try:
83
- yield page
84
- finally:
85
- await page.close()
86
-
87
-
88
- async def shutdown_browser():
89
- global _browser
90
- global _playwright
91
- if _browser:
92
- if _browser.is_connected():
93
- await _browser.close()
94
- _browser = None
95
- if _playwright:
96
- # await _playwright.stop()
97
- _playwright = None
98
-
99
-
100
- async def install_browser():
101
- import os
102
- import sys
103
-
104
- from playwright.__main__ import main
105
-
106
- if host := config.htmlrender_download_host:
107
- logger.info("使用配置源进行下载")
108
- os.environ["PLAYWRIGHT_DOWNLOAD_HOST"] = host
109
- else:
110
- logger.info("使用镜像源进行下载")
111
- os.environ["PLAYWRIGHT_DOWNLOAD_HOST"] = (
112
- "https://npmmirror.com/mirrors/playwright/"
113
- )
114
- success = False
115
-
116
- if config.htmlrender_browser == "firefox":
117
- logger.info("正在安装 firefox")
118
- sys.argv = ["", "install", "firefox"]
119
- else:
120
- # 默认使用 chromium
121
- logger.info("正在安装 chromium")
122
- sys.argv = ["", "install", "chromium"]
123
- try:
124
- logger.info("正在安装依赖")
125
- os.system("playwright install-deps") # noqa: ASYNC102, S605, S607
126
- main()
127
- except SystemExit as e:
128
- if e.code == 0:
129
- success = True
130
- if not success:
131
- logger.error("浏览器更新失败, 请检查网络连通性")
@@ -1,10 +0,0 @@
1
- import nonebot
2
- import pytest
3
- from nonebug import App
4
-
5
-
6
- @pytest.fixture
7
- def app():
8
- nonebot.require("nonebot_plugin_htmlrender")
9
-
10
- yield App()