219 lines
8.7 KiB
Python
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)}"}
|