import asyncio import io import json import os import re from datetime import datetime from typing import Optional, Union import base64 from PIL import Image from playwright.async_api import async_playwright from nonebot.adapters.onebot.v11 import MessageEvent, MessageSegment from nonebot import logger basic_path = os.path.dirname(__file__) save_path = os.path.join(basic_path, "temp") headers = { "User-Agent": "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1.6) ", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "zh-cn" } def gen_ms_img(image: Union[bytes, Image.Image]) -> MessageSegment: if isinstance(image, bytes): return MessageSegment.image( pic2b64(Image.open(io.BytesIO(image))) ) else: return MessageSegment.image( pic2b64(image) ) def get_present_time() -> int: return int(datetime.timestamp(datetime.now())) async def screen_shot(url: str, time_present: int) -> Optional[str or bool]: async with async_playwright() as p: browser = await p.chromium.launch() page = await browser.new_page() try: # 访问页面 await page.goto(url) # 等待页面加载完成 await page.wait_for_load_state('domcontentloaded') await page.wait_for_load_state('networkidle') # 1. 获取卡片数量 card_selector = "div.flex.cursor-pointer[style*='width: 320px;']" # 等待至少一个卡片元素出现 await page.wait_for_selector(card_selector, state='visible') # 获取卡片数量 card_count = await page.eval_on_selector_all(card_selector, "els => els.length") logger.info(f"获取到的星球卡片数量:{card_count}") # 2. 单个卡片高度(固定为 270) card_height = 270 extra_padding = 100 # 防止 margin、padding、gap # 3. 计算所需 viewport 高度 if card_count == 0 or card_count <= 15: logger.warning("卡片元素数量未超过设定值,使用默认视口高度。") required_height = 1080 # 默认高度 else: required_height = 1080 + (card_count - 15) / 5 * card_height + extra_padding viewport_width = 1920 # 固定宽度 await page.set_viewport_size({"width": viewport_width, "height": required_height}) # 记录时间 time_start = get_present_time() # 4. 加载替换文本的脚本 with open(f'{basic_path}/data/plantes_mix.json', 'r', encoding='utf-8') as file: replacements = json.load(file) # 构建替换脚本 replacement_script = "" for keyword, replacement in replacements.items(): escaped_keyword = json.dumps(keyword) escaped_replacement = json.dumps(replacement) replacement_script += f""" document.body.outerHTML = document.body.outerHTML.replace(new RegExp({escaped_keyword}, 'g'), {escaped_replacement}); """ # 执行替换脚本 await page.evaluate(replacement_script) # 结束时间 time_end = get_present_time() duration = time_end - time_start logger.info(f"截图文本替换耗时:{duration}s") # 等待页面更新 await asyncio.sleep(1) # 保存截图 logger.info("正在保存图片...") img_path = os.path.join(save_path, f'{time_present}.png') await page.screenshot( path=img_path, full_page=True ) # 压缩图片 logger.info("正在压缩图片...") img_convert = Image.open(img_path) img_convert.save(img_path, quality=80) logger.info("图片保存成功!") except Exception as e: logger.error(f"访问网站异常:{type(e)} `{e}`") return f"访问网站异常:{type(e)} `{e}`" finally: await browser.close() return "success" async def screen_shot_2(url: str, time_present: int) -> Optional[str or bool]: async with async_playwright() as p: browser = await p.chromium.launch() page = await browser.new_page() try: # 设置视口大小 await page.set_viewport_size({"width": 1920, "height": 1080}) await page.goto(url) await page.wait_for_load_state('networkidle') with open(f'{basic_path}/data/plantes_mix.json', 'r', encoding='utf-8') as file: replacements = json.load(file) # 遍历字典,构建替换脚本 replacement_script = "" for keyword, replacement in replacements.items(): escaped_keyword = json.dumps(keyword) escaped_replacement = json.dumps(replacement) replacement_script += f""" document.body.outerHTML = document.body.outerHTML.replace(new RegExp({escaped_keyword}, 'g'), {escaped_replacement}); """ # 在页面上执行替换脚本 await page.evaluate(replacement_script) except Exception as e: return f"访问网站异常{type(e)}`{e}`" await asyncio.sleep(1) logger.info("正在保存图片...") img_path = os.path.join(save_path, f'{time_present}.png') await page.screenshot( path=img_path, full_page=True ) logger.info("正在压缩图片...") img_convert = Image.open(img_path) img_convert.save(img_path, quality=80) logger.info("图片保存成功!") await browser.close() return "success" def pic2b64(pic: Image) -> str: buf = io.BytesIO() pic.save(buf, format='PNG') base64_str = base64.b64encode(buf.getvalue()).decode() return 'base64://' + base64_str