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,