feat:初版

This commit is contained in:
2025-12-03 15:48:44 +08:00
commit b4df26f61d
199 changed files with 23434 additions and 0 deletions

218
plugins/RandomImage/main.py Normal file
View File

@@ -0,0 +1,218 @@
"""
随机图片插件
支持命令触发和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)}"}