"""搜图 —— 触发器(Trigger + View)层。""" from __future__ import annotations import re from asyncio import sleep from datetime import datetime, timedelta from nonebot import get_driver, on_message, on_startswith, require from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, PrivateMessageEvent from nonebot.exception import ActionFailed from nonebot.log import logger from nonebot.plugin import PluginMetadata from nonebot.rule import is_type, to_me require("nonebot_plugin_alconna") from nonebot_plugin_alconna import UniMessage from ..config import CHECK, DAILY_LIMIT, IGNORE_STAMP, SAUCENAO_KEY, threshold from ..services.image import check_screenshot, get_image_data_ascii, get_image_data_sauce from ..services.search import picfinder from ..services.state import lmtd, pls __plugin_meta__ = PluginMetadata( name="搜图", description="SauceNAO + ascii2d 二次元搜图", usage=( "(xx为bot名称)\n" "[@bot+图片] 单张/多张搜图\n" "[xx搜图] 进入批量搜图模式\n" "[谢谢xx] 退出批量搜图模式" ), type="application", ) driver_config = get_driver().config SUPERUSERS = driver_config.superusers NICKNAMES = list(driver_config.nickname) if driver_config.nickname else ["竹竹"] NICKNAME = NICKNAMES[0] start_finder = on_startswith(("识图", "搜图", "查图", "找图"), rule=to_me()) picmessage = on_message(rule=is_type(GroupMessageEvent), block=False) replymessage = on_message(rule=is_type(GroupMessageEvent), block=False) thanks = on_startswith("谢谢") pic_private = on_message(rule=is_type(PrivateMessageEvent), block=False) def parse_report(text: str) -> UniMessage: msg = UniMessage() parts = re.split(r"(\[CQ:image,[^\]]*\])", text) for part in parts: if part.startswith("[CQ:image,"): m = re.search(r"file=([^,\]]+)", part) msg += UniMessage.image(url=m.group(1) if m else "") elif part: msg += UniMessage.text(part) return msg def _extract_image(ev) -> tuple[str, str, str | None] | None: for m in ev.message: if m.type == "image": return m.data["file"], m.data["url"], m.data.get("subType") return None @start_finder.handle() async def start_finder_handle(bot: Bot, ev: GroupMessageEvent): uid = ev.user_id gid = ev.group_id mid = ev.message_id if str(uid) not in SUPERUSERS: await UniMessage.text("暂不支持在群聊中使用!").send() return ret = _extract_image(ev) if not ret: if pls.get_on_off_status(gid): if uid == pls.on[gid]: pls.timeout[gid] = datetime.now() + timedelta(seconds=30) await UniMessage.text( "您已经在搜图模式下啦!\n如想退出搜图模式请发送“谢谢竹竹”~" ).send() await start_finder.finish() else: await UniMessage.at(user_id=str(pls.on[gid])).text( "正在搜图,请耐心等待~" ).send() await start_finder.finish() pls.turn_on(gid, uid) await UniMessage.text( f"了解~请发送图片吧!支持批量噢!\n如想退出搜索模式请发送“谢谢{NICKNAME}”" ).send() await sleep(30) ct = 0 while pls.get_on_off_status(gid): if datetime.now() < pls.timeout[gid]: if ct != pls.count[gid]: ct = pls.count[gid] pls.timeout[gid] = datetime.now() + timedelta(seconds=60) else: temp = pls.on[gid] if not pls.count[gid]: await UniMessage.at(user_id=str(temp)).text( " 由于超时,已为您自动退出搜图模式~\n您本次搜索期间未发送任何图片,请检查是否被吞图~" ).send() else: await UniMessage.at(user_id=str(temp)).text( f" 由于超时,已为您自动退出搜图模式,以后要记得说“谢谢{NICKNAME}”来退出搜图模式噢~\n您本次搜索共搜索了{pls.count[gid]}张图片~" ).send() pls.turn_off(ev.group_id) break await sleep(30) return file, url, _ = ret if str(uid) not in SUPERUSERS: if not lmtd.check(uid): await UniMessage.text( f"您今天已经搜过{DAILY_LIMIT}次图了,休息一下明天再来吧~" ).send(at_sender=True) return if CHECK: result = await check_screenshot(bot, file, url) if result: if result == 1: await UniMessage.reply(id=str(mid)).text( "该图似乎是手机截屏,请进行适当裁剪后再尝试搜图~\n*请注意搜索漫画时务必截取一个完整单页进行搜图~" ).send() if result == 2: await UniMessage.reply(id=str(mid)).text( "该图似乎是长图拼接,请进行适当裁剪后再尝试搜图~\n*请注意搜索漫画时务必截取一个完整单页进行搜图~" ).send() return await UniMessage.text("正在搜索,请稍候~").send() await picfinder(bot, ev, url) @picmessage.handle() async def picmessage_handle(bot: Bot, ev: GroupMessageEvent): mid = ev.message_id atcheck = False batchcheck = False for m in ev.message: if m.type == "at" and int(m.data["qq"]) == ev.self_id: atcheck = True if pls.get_on_off_status(ev.group_id): if int(pls.on[ev.group_id]) == int(ev.user_id): batchcheck = True if not (batchcheck or atcheck): return uid = ev.user_id ret = _extract_image(ev) if not ret: return file, url, sbtype = ret if str(uid) not in SUPERUSERS: if not lmtd.check(uid): await UniMessage.text( f"您今天已经搜过{DAILY_LIMIT}次图了,休息一下明天再来吧~" ).send(at_sender=True) if pls.get_on_off_status(ev.group_id): pls.turn_off(ev.group_id) return if pls.get_on_off_status(ev.group_id): pls.count_plus(ev.group_id) if pls.count[ev.group_id] > pls.limit[ev.group_id]: await UniMessage.text( f"您今天已经搜过{DAILY_LIMIT}次图了,休息一下明天再来吧~" ).send(at_sender=True) pls.turn_off(ev.group_id) return if sbtype and IGNORE_STAMP: if sbtype != "0": await UniMessage.reply(id=str(mid)).text( "该图为表情,已忽略~如确需搜索请尝试单发搜索或回复搜索~" ).send() return if CHECK: result = await check_screenshot(bot, file, url) if result: if result == 1: await UniMessage.reply(id=str(mid)).text( "该图似乎是手机截屏,请手动进行适当裁剪后再尝试搜图~\n*请注意搜索漫画时务必截取一个完整单页进行搜图~" ).send() if result == 2: await UniMessage.reply(id=str(mid)).text( "该图似乎是长图拼接,请手动进行适当裁剪后再尝试搜图~\n*请注意搜索漫画时务必截取一个完整单页进行搜图~" ).send() return if "c2cpicdw.qpic.cn/offpic_new/" in url: md5 = file[:-6].upper() url = f"http://gchat.qpic.cn/gchatpic_new/0/0-0-{md5}/0?term=2" await UniMessage.text("正在搜索,请稍候~").send() await picfinder(bot, ev, url) @replymessage.handle() async def replymessage_handle(bot: Bot, ev: GroupMessageEvent): mid = ev.message_id uid = ev.user_id seg = ev.message[0] if seg.type != "reply": return tmid = seg.data["id"] cmd = ev.message.extract_plain_text() flag1 = 0 flag2 = 0 for m in ev.message[2:]: if m.type == "at" and int(m.data["qq"]) == ev.self_id: flag1 = 1 for name in NICKNAMES: if name in cmd: flag1 = 1 break for pfcmd in ["识图", "搜图", "查图", "找图"]: if pfcmd in cmd: flag2 = 1 if not (flag1 and flag2): return if str(uid) not in SUPERUSERS: if not lmtd.check(uid): await UniMessage.text( f"您今天已经搜过{DAILY_LIMIT}次图了,休息一下明天再来吧~" ).send(at_sender=True) try: tmsg = await bot.get_msg(message_id=int(tmid)) except ActionFailed: await UniMessage.text("该消息已过期,请重新转发~").send() await replymessage.finish() ret = re.search(r"\[CQ:image,file=(.*)?,url=(.*)\]", str(tmsg["message"])) if not ret: await UniMessage.text("未找到图片~").send() return file = ret.group(1) url = ret.group(2) if ",subType=" in url: url = url.split(",")[0] elif ",subType=" in file: file = file.split(",")[0] if CHECK: result = await check_screenshot(bot, file, url) if result: if result == 1: await UniMessage.reply(id=str(mid)).text( "该图似乎是手机截屏,请手动进行适当裁剪后再尝试搜图~\n*请注意搜索漫画时务必截取一个完整单页进行搜图~" ).send() if result == 2: await UniMessage.reply(id=str(mid)).text( "该图似乎是长图拼接,请手动进行适当裁剪后再尝试搜图~\n*请注意搜索漫画时务必截取一个完整单页进行搜图~" ).send() return if "c2cpicdw.qpic.cn/offpic_new/" in url: md5 = file[:-6].upper() url = f"http://gchat.qpic.cn/gchatpic_new/0/0-0-{md5}/0?term=2" await UniMessage.text("正在搜索,请稍候~").send() await picfinder(bot, ev, url) @thanks.handle() async def thanks_handle(bot: Bot, ev: GroupMessageEvent): gid = ev.group_id name = ev.message.extract_plain_text().removeprefix("谢谢").strip() if name not in NICKNAMES: return if pls.get_on_off_status(gid): if pls.on[gid] != ev.user_id: await UniMessage.text("不能替别人结束搜图哦~").send() return if not pls.count[gid]: await UniMessage.text("不用谢~\n您本次搜索期间未发送任何图片,请检查是否被吞图~").send() else: await UniMessage.text(f"不用谢~\n您本次搜索共搜索了{pls.count[gid]}张图片~").send() pls.turn_off(gid) return await UniMessage.text("にゃ~").send() @pic_private.handle() async def pic_private_handle(bot: Bot, ev: PrivateMessageEvent): uid = ev.user_id ret = _extract_image(ev) if not ret: flag1 = flag2 = 0 for name in NICKNAMES: if name in str(ev.message): flag1 = 1 break for pfcmd in ["识图", "搜图", "查图", "找图"]: if pfcmd in str(ev.message): flag2 = 1 if flag1 and flag2: await UniMessage.text("私聊搜图请直接发送图片~").send() return file, url, _ = ret if not lmtd.check(uid): await UniMessage.text( f"您今天已经搜过{DAILY_LIMIT}次图了,休息一下明天再来吧~" ).send() return if "c2cpicdw.qpic.cn/offpic_new/" in url: md5 = file.upper() logger.info(f"图片URL{url}") url = f"https://gchat.qpic.cn/gchatpic_new/0/0-0-{md5}/0?term=2" await UniMessage.text("正在搜索,请稍候~").send() result = await get_image_data_sauce(url, SAUCENAO_KEY) image_data_report = result[0] simimax = result[1] if "Index #" in image_data_report: su = next(iter(SUPERUSERS)) await bot.send_private_msg(user_id=int(su), message="发生index解析错误") await bot.send_private_msg(user_id=int(su), message=url) await bot.send_private_msg(user_id=int(su), message=image_data_report) await parse_report(image_data_report).send() if float(simimax) > float(threshold): lmtd.increase(uid) else: if simimax != 0: await UniMessage.text("相似度过低,换用ascii2d检索中…").send() else: logger.error("SauceNao not found imageInfo") await UniMessage.text("SauceNao检索失败,换用ascii2d检索中…").send() image_data_report = await get_image_data_ascii(url) if image_data_report[0]: await parse_report(image_data_report[0]).send() lmtd.increase(uid) if image_data_report[1]: await parse_report(image_data_report[1]).send() if not (image_data_report[0] or image_data_report[1]): logger.error("ascii2d not found imageInfo") await UniMessage.text("ascii2d检索失败…").send()