Files
WechatHookBot/plugins/RandomImage/main.py
2025-12-03 15:48:44 +08:00

219 lines
8.7 KiB
Python

"""
随机图片插件
支持命令触发和LLM工具调用
"""
import asyncio
import aiohttp
import uuid
from pathlib import Path
from datetime import datetime
from typing import List, Optional
from loguru import logger
from utils.plugin_base import PluginBase
from utils.decorators import on_text_message
from WechatHook import WechatHookClient
class RandomImage(PluginBase):
"""随机图片插件"""
description = "随机图片插件 - 支持黑丝、白丝、原神等图片"
author = "Assistant"
version = "1.0.0"
def __init__(self):
super().__init__()
self.images_dir = None
async def async_init(self):
"""异步初始化"""
self.images_dir = Path(__file__).parent / "images"
self.images_dir.mkdir(exist_ok=True)
logger.success("随机图片插件初始化完成")
async def _download_image(self, url: str) -> Optional[tuple]:
"""下载图片到本地,返回(本地路径, 原始URL)"""
try:
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=30)) as session:
async with session.get(url) as response:
if response.status == 200:
content = await response.read()
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
uid = uuid.uuid4().hex[:8]
file_path = self.images_dir / f"random_{ts}_{uid}.jpg"
with open(file_path, "wb") as f:
f.write(content)
logger.info(f"图片下载成功: {file_path}")
return (str(file_path), url)
except Exception as e:
logger.error(f"下载图片失败: {e}")
return None
async def _fetch_random_image(self) -> Optional[str]:
"""从三个接口随机选一个,获取一张图片,返回本地路径"""
import random
# 三个随机图片接口
api_urls = [
"https://v2.xxapi.cn/api/heisi",
"https://v2.xxapi.cn/api/meinvpic",
"https://v2.xxapi.cn/api/yscos"
]
# 随机选择一个接口
url = random.choice(api_urls)
logger.info(f"随机选择接口: {url}")
try:
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=10)) as session:
async with session.get(url) as response:
if response.status == 200:
data = await response.json()
if data.get("code") == 200 and "data" in data:
image_url = data["data"]
result = await self._download_image(image_url)
if result:
return result[0] # 返回本地路径
except Exception as e:
logger.error(f"获取随机图片失败: {e}")
return None
async def _send_as_chat_record(self, bot: WechatHookClient, from_wxid: str, title: str, images: List[tuple]):
"""将图片以聊天记录格式发送"""
try:
import time
import hashlib
import xml.etree.ElementTree as ET
is_group = from_wxid.endswith("@chatroom")
recordinfo = ET.Element("recordinfo")
ET.SubElement(recordinfo, "info").text = title
ET.SubElement(recordinfo, "isChatRoom").text = "1" if is_group else "0"
datalist = ET.SubElement(recordinfo, "datalist")
datalist.set("count", str(len(images)))
ET.SubElement(recordinfo, "desc").text = title
ET.SubElement(recordinfo, "fromscene").text = "3"
for i, (local_path, img_url) in enumerate(images):
di = ET.SubElement(datalist, "dataitem")
di.set("datatype", "5")
di.set("dataid", uuid.uuid4().hex)
src_local_id = str((int(time.time() * 1000) % 90000) + 10000)
new_msg_id = str(int(time.time() * 1000) + i)
create_time = str(int(time.time()) - len(images) + i)
ET.SubElement(di, "srcMsgLocalid").text = src_local_id
ET.SubElement(di, "sourcetime").text = time.strftime("%Y-%m-%d %H:%M", time.localtime(int(create_time)))
ET.SubElement(di, "fromnewmsgid").text = new_msg_id
ET.SubElement(di, "srcMsgCreateTime").text = create_time
ET.SubElement(di, "sourcename").text = "图片助手"
ET.SubElement(di, "sourceheadurl").text = ""
ET.SubElement(di, "datatitle").text = f"图片 {i+1}"
ET.SubElement(di, "datadesc").text = "点击查看"
ET.SubElement(di, "datafmt").text = "url"
ET.SubElement(di, "link").text = img_url
ET.SubElement(di, "ischatroom").text = "1" if is_group else "0"
weburlitem = ET.SubElement(di, "weburlitem")
ET.SubElement(weburlitem, "thumburl").text = img_url
ET.SubElement(weburlitem, "link").text = img_url
ET.SubElement(weburlitem, "title").text = f"图片 {i+1}"
ET.SubElement(weburlitem, "desc").text = "点击查看"
dataitemsource = ET.SubElement(di, "dataitemsource")
ET.SubElement(dataitemsource, "hashusername").text = hashlib.sha256(from_wxid.encode("utf-8")).hexdigest()
record_xml = ET.tostring(recordinfo, encoding="unicode")
appmsg_xml = "".join([
"<appmsg appid=\"\" sdkver=\"0\">",
f"<title>{title}</title>",
f"<des>{title}</des>",
"<type>19</type>",
"<url>https://support.weixin.qq.com/cgi-bin/mmsupport-bin/readtemplate?t=page/favorite_record__w_unsupport</url>",
"<appattach><cdnthumbaeskey></cdnthumbaeskey><aeskey></aeskey></appattach>",
f"<recorditem><![CDATA[{record_xml}]]></recorditem>",
"<percent>0</percent>",
"</appmsg>"
])
await bot._send_data_async(11214, {"to_wxid": from_wxid, "content": appmsg_xml})
logger.success(f"已发送聊天记录: {title}")
except Exception as e:
logger.error(f"发送聊天记录失败: {e}")
@on_text_message(priority=70)
async def handle_message(self, bot: WechatHookClient, message: dict):
"""处理文本消息"""
content = message.get("Content", "").strip()
from_wxid = message.get("FromWxid", "")
# 精确匹配命令
if content not in ["随机图片", "图来", "黑丝", "白丝"]:
return True
logger.info(f"收到随机图片请求: {content}")
try:
# 获取一张随机图片
image_path = await self._fetch_random_image()
if image_path:
# 直接发送图片
await bot.send_image(from_wxid, image_path)
logger.success(f"已发送随机图片: {image_path}")
else:
await bot.send_text(from_wxid, "❌ 获取图片失败,请稍后重试")
except Exception as e:
logger.error(f"处理失败: {e}")
await bot.send_text(from_wxid, f"❌ 处理失败: {str(e)}")
return False
def get_llm_tools(self) -> List[dict]:
"""返回LLM工具定义"""
return [{
"type": "function",
"function": {
"name": "get_random_image",
"description": "获取随机图片,从三个接口中随机选择一个返回一张图片",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
}
}]
async def execute_llm_tool(self, tool_name: str, arguments: dict, bot: WechatHookClient, from_wxid: str) -> dict:
"""执行LLM工具调用"""
if tool_name != "get_random_image":
return None
try:
logger.info(f"LLM工具调用获取随机图片")
# 获取一张随机图片
image_path = await self._fetch_random_image()
if image_path:
# 直接发送图片
await bot.send_image(from_wxid, image_path)
return {
"success": True,
"message": "已发送随机图片",
"no_reply": True # 不需要 AI 再回复
}
else:
return {"success": False, "message": "获取图片失败"}
except Exception as e:
logger.error(f"LLM工具执行失败: {e}")
return {"success": False, "message": f"执行失败: {str(e)}"}