diff --git a/README.md b/README.md index 4d092bd..c374677 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,73 @@ htmlrender_connect_over_cdp = "http://127.0.0.1:9222" htmlrender_connect="ws://playwright:3000" ``` +### 远程浏览器使用说明 + +当使用远程浏览器(通过 `htmlrender_connect` 或 `htmlrender_connect_over_cdp` 配置)时,本地文件系统的 `file://` 协议将无法访问。此时需要: + +#### 1. 使用 `html_to_pic` 函数 + +可以直接传入不同的 URL 协议: + +```python +from nonebot_plugin_htmlrender import html_to_pic + +# 使用 about:blank(推荐用于纯 HTML 内容) +pic = await html_to_pic( + html="

Hello

", + template_path="about:blank" +) + +# 使用 data URL +pic = await html_to_pic( + html="

Hello

", + template_path="data:text/html," +) + +# 使用 HTTP URL(需要有可访问的 Web 服务器提供资源) +pic = await html_to_pic( + html="

Hello

", + template_path="http://your-server.com/base/" +) +``` + +#### 2. 使用 `template_to_pic` 函数 + +需要通过 `pages` 参数的 `base_url` 字段指定浏览器使用的基础 URL: + +```python +from nonebot_plugin_htmlrender import template_to_pic +from pathlib import Path + +template_path = str(Path(__file__).parent / "templates") + +# 对于远程浏览器,使用 about:blank 或 HTTP URL +pic = await template_to_pic( + template_path=template_path, # 本地模板路径(用于 jinja2 加载) + template_name="my_template.html", + templates={"data": "value"}, + pages={ + "viewport": {"width": 600, "height": 300}, + "base_url": "about:blank", # 浏览器使用的基础 URL + }, +) + +# 如果模板中有相对路径的资源(如图片、CSS),需要使用 HTTP URL +pic = await template_to_pic( + template_path=template_path, + template_name="my_template.html", + templates={"data": "value"}, + pages={ + "viewport": {"width": 600, "height": 300}, + "base_url": "http://your-server.com/static/", + }, +) +``` + +**注意**: +- `template_path` 参数始终是本地文件系统路径,用于 jinja2 模板引擎加载模板文件 +- `base_url` 参数是浏览器使用的基础 URL,用于解析 HTML 中的相对路径资源 + ## 部署 ### (建议)使用 docker compose 进行部署 diff --git a/nonebot_plugin_htmlrender/data_source.py b/nonebot_plugin_htmlrender/data_source.py index b53218d..49003eb 100644 --- a/nonebot_plugin_htmlrender/data_source.py +++ b/nonebot_plugin_htmlrender/data_source.py @@ -198,7 +198,11 @@ async def html_to_pic( screenshot_timeout (float, optional): 截图超时时间,默认30000ms html (str): html文本 wait (int, optional): 等待时间. Defaults to 0. - template_path (str, optional): 模板路径 如 "file:///path/to/template/" + template_path (str, optional): 模板路径,支持多种URL协议: + - file:// 本地文件路径 (如 "file:///path/to/template/") + - http:// 或 https:// 远程URL (用于远程浏览器) + - data: Data URL + - about:blank 空白页面 type (Literal["jpeg", "png"]): 图片类型, 默认 png quality (int, optional): 图片质量 0-100 当为`png`时无效 device_scale_factor: 缩放比例,类型为float,值越大越清晰 @@ -208,8 +212,6 @@ async def html_to_pic( bytes: 图片, 可直接发送 """ # logger.debug(f"html:\n{html}") - if "file:" not in template_path: - raise Exception("template_path should be file:///path/to/template") async with get_new_page(device_scale_factor, **kwargs) as page: page.on("console", lambda msg: logger.debug(f"[Browser Console]: {msg.text}")) await page.goto(template_path) @@ -239,12 +241,16 @@ async def template_to_pic( Args: screenshot_timeout (float, optional): 截图超时时间,默认30000ms - template_path (str): 模板路径 + template_path (str): 模板文件的本地路径 (用于jinja2加载模板) template_name (str): 模板名 templates (Dict[Any, Any]): 模板内参数 如: {"name": "abc"} filters (Optional[Dict[str, Any]]): 自定义过滤器 - pages (Optional[Dict[Any, Any]]): 网页参数 Defaults to - {"base_url": f"file://{getcwd()}", "viewport": {"width": 500, "height": 10}} + pages (Optional[Dict[Any, Any]]): 网页参数,支持以下字段: + - base_url: 浏览器页面的基础URL,用于解析相对路径。 + 对于本地浏览器使用 file:// 协议, + 对于远程浏览器可使用 http:// 或 https:// 协议。 + 默认为 f"file://{getcwd()}" + - viewport: 视口大小,默认为 {"width": 500, "height": 10} wait (int, optional): 网页载入等待时间. Defaults to 0. type (Literal["jpeg", "png"]): 图片类型, 默认 png quality (int, optional): 图片质量 0-100 当为`png`时无效 @@ -270,8 +276,13 @@ async def template_to_pic( template = template_env.get_template(template_name) + # Use base_url from pages if provided, otherwise use file:// URL from template_path + # This allows remote browsers to use http/https URLs + # while local browsers use file:// URLs + page_base_url = pages.get("base_url", f"file://{template_path}") + return await html_to_pic( - template_path=f"file://{template_path}", + template_path=page_base_url, html=await template.render_async(**templates), wait=wait, type=type, diff --git a/tests/test_htmlrender.py b/tests/test_htmlrender.py index ab72ac4..4f6c75c 100644 --- a/tests/test_htmlrender.py +++ b/tests/test_htmlrender.py @@ -80,6 +80,26 @@ async def test_html_to_pic(app: App, browser: None) -> None: assert isinstance(img, bytes) +@pytest.mark.asyncio +async def test_html_to_pic_with_different_url_schemes(app: App, browser: None) -> None: + """测试 html_to_pic 支持不同的 URL 协议""" + from nonebot_plugin_htmlrender import html_to_pic + + # Test with about:blank + img = await html_to_pic( + "

Test with about:blank

", + template_path="about:blank", + ) + assert isinstance(img, bytes) + + # Test with data URL + img = await html_to_pic( + "

Test with data URL

", + template_path="data:text/html,", + ) + assert isinstance(img, bytes) + + @pytest.mark.asyncio @pytest.mark.parametrize("template_resources", ["text"], indirect=True) async def test_template_to_pic( @@ -103,6 +123,31 @@ async def test_template_to_pic( assert isinstance(img, bytes) +@pytest.mark.asyncio +@pytest.mark.parametrize("template_resources", ["text"], indirect=True) +async def test_template_to_pic_with_different_base_url( + app: App, + browser: None, + template_resources: tuple[str, str, list[str]], +) -> None: + """测试 template_to_pic 使用不同的 base_url""" + from nonebot_plugin_htmlrender import template_to_pic + + template_path, template_name, text_list = template_resources + + # Test with about:blank as base_url + img = await template_to_pic( + template_path=template_path, + template_name=template_name, + templates={"text_list": text_list}, + pages={ + "viewport": {"width": 600, "height": 300}, + "base_url": "about:blank", + }, + ) + assert isinstance(img, bytes) + + @pytest.mark.asyncio async def test_template_filter( app: App,