import os.path from os import getcwd from pathlib import Path from typing import Literal, Optional, Union import aiofiles import jinja2 import markdown from nonebot.log import logger from .browser import get_new_page TEMPLATES_PATH = str(Path(__file__).parent / "templates") env = jinja2.Environment( # noqa: S701 extensions=["jinja2.ext.loopcontrols"], loader=jinja2.FileSystemLoader(TEMPLATES_PATH), enable_async=True, ) async def text_to_pic( text: str, css_path: str = "", width: int = 500, type: Literal["jpeg", "png"] = "png", # noqa: A002 quality: Union[int, None] = None, device_scale_factor: float = 2, ) -> bytes: """多行文本转图片 Args: text (str): 纯文本, 可多行 css_path (str, optional): css文件 width (int, optional): 图片宽度,默认为 500 type (Literal["jpeg", "png"]): 图片类型, 默认 png quality (int, optional): 图片质量 0-100 当为`png`时无效 device_scale_factor: 缩放比例,类型为float,值越大越清晰(真正想让图片清晰更优先请调整此选项) Returns: bytes: 图片, 可直接发送 """ template = env.get_template("text.html") return await html_to_pic( template_path=f"file://{css_path if css_path else TEMPLATES_PATH}", html=await template.render_async( text=text, css=await read_file(css_path) if css_path else await read_tpl("text.css"), ), viewport={"width": width, "height": 10}, type=type, quality=quality, device_scale_factor=device_scale_factor, ) async def md_to_pic( md: str = "", md_path: str = "", css_path: str = "", width: int = 500, type: Literal["jpeg", "png"] = "png", # noqa: A002 quality: Union[int, None] = None, device_scale_factor: float = 2, ) -> bytes: """markdown 转 图片 Args: md (str, optional): markdown 格式文本 md_path (str, optional): markdown 文件路径 css_path (str, optional): css文件路径. Defaults to None. width (int, optional): 图片宽度,默认为 500 type (Literal["jpeg", "png"]): 图片类型, 默认 png quality (int, optional): 图片质量 0-100 当为`png`时无效 device_scale_factor: 缩放比例,类型为float,值越大越清晰(真正想让图片清晰更优先请调整此选项) Returns: bytes: 图片, 可直接发送 """ template = env.get_template("markdown.html") if not md: if md_path: md = await read_file(md_path) else: raise Exception("必须输入 md 或 md_path") logger.debug(md) md = markdown.markdown( md, extensions=[ "pymdownx.tasklist", "tables", "fenced_code", "codehilite", "mdx_math", "pymdownx.tilde", ], extension_configs={"mdx_math": {"enable_dollar_delimiter": True}}, ) logger.debug(md) extra = "" if "math/tex" in md: katex_css = await read_tpl("katex/katex.min.b64_fonts.css") katex_js = await read_tpl("katex/katex.min.js") mathtex_js = await read_tpl("katex/mathtex-script-type.min.js") extra = ( f'' f"" f"" ) if css_path: css = await read_file(css_path) else: css = await read_tpl("github-markdown-light.css") + await read_tpl( "pygments-default.css", ) return await html_to_pic( template_path=f"file://{css_path if css_path else TEMPLATES_PATH}", html=await template.render_async(md=md, css=css, extra=extra), viewport={"width": width, "height": 10}, type=type, quality=quality, device_scale_factor=device_scale_factor, ) # async def read_md(md_path: str) -> str: # async with aiofiles.open(str(Path(md_path).resolve()), mode="r") as f: # md = await f.read() # return markdown.markdown(md) async def read_file(path: str) -> str: async with aiofiles.open(path, mode="r") as f: return await f.read() async def read_tpl(path: str) -> str: return await read_file(f"{TEMPLATES_PATH}/{path}") async def template_to_html( template_path: str, template_name: str, **kwargs, ) -> str: """使用jinja2模板引擎通过html生成图片 Args: template_path (str): 模板路径 template_name (str): 模板名 **kwargs: 模板内容 Returns: str: html """ template_env = jinja2.Environment( # noqa: S701 loader=jinja2.FileSystemLoader(template_path), enable_async=True, ) template = template_env.get_template(template_name) return await template.render_async(**kwargs) async def html_to_pic( html: str, wait: int = 0, template_path: str = f"file://{getcwd()}", # noqa: PTH109 type: Literal["jpeg", "png"] = "png", # noqa: A002 quality: Union[int, None] = None, device_scale_factor: float = 2, **kwargs, ) -> bytes: """html转图片 Args: html (str): html文本 wait (int, optional): 等待时间. Defaults to 0. template_path (str, optional): 模板路径 如 "file:///path/to/template/" type (Literal["jpeg", "png"]): 图片类型, 默认 png quality (int, optional): 图片质量 0-100 当为`png`时无效 device_scale_factor: 缩放比例,类型为float,值越大越清晰(真正想让图片清晰更优先请调整此选项) **kwargs: 传入 page 的参数 Returns: bytes: 图片, 可直接发送 """ # logger.debug(f"html:\n{html}") if "file:" not in template_path: raise Exception("template_path 应该为 file:///path/to/template") async with get_new_page(device_scale_factor, **kwargs) as page: await page.goto(template_path) await page.set_content(html, wait_until="networkidle") await page.wait_for_timeout(wait) return await page.screenshot( full_page=True, type=type, quality=quality, ) async def template_to_pic( template_path: str, templates: dict, pages: Optional[dict] = None, wait: int = 0, type: Literal["jpeg", "png"] = "png", # noqa: A002 quality: Union[int, None] = None, device_scale_factor: float = 2, ) -> bytes: """使用jinja2模板引擎通过html生成图片 Args: template_path (str): 模板路径 templates (dict): 模板内参数 如: {"name": "abc"} pages (dict): 网页参数 Defaults to {"base_url": 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`时无效 device_scale_factor: 缩放比例,类型为float,值越大越清晰(真正想让图片清晰更优先请调整此选项) Returns: bytes: 图片 可直接发送 """ if pages is None: pages = { "viewport": {"width": 500, "height": 10}, "base_url": f"file://{getcwd()}", # noqa: PTH109 } template_env = jinja2.Environment( # noqa: S701 loader=jinja2.FileSystemLoader(os.path.dirname(template_path)), enable_async=True, ) template = template_env.get_template(os.path.basename(template_path)) return await html_to_pic( template_path=f"file://{template_path}", html=await template.render_async(**templates), wait=wait, type=type, quality=quality, device_scale_factor=device_scale_factor, **pages, ) async def capture_element( url: str, element: str, timeout: float = 0, type: Literal["jpeg", "png"] = "png", # noqa: A002 quality: Union[int, None] = None, **kwargs, ) -> bytes: async with get_new_page(**kwargs) as page: await page.goto(url, timeout=timeout) return await page.locator(element).screenshot( type=type, quality=quality, )