refactor: restructure plugins per MTSS standard, consolidate assets to res/
Per docs/plugin-audit-report.md (plugins normalized to Trigger(handlers) → Service(services) → Model(repository/models) + utils): - Split monolithic __init__.py into handlers/services/utils across dailywife, deer_pipe, dice, galgame_card, helldivers_tools, huoziyinshua, learning_chat, makeaquote, mc_server_status, ncm_saying, picfinder_take, picstatus, random_jm_code, regif, steam_info, video_analysis, group_tools - Move static assets under res/: deer_pipe font/img, makeaquote font, helldivers img/templates, huoziyinshua HuoZiYinShua - Add config.py + register_config_items to ncm_saying, random_jm_code, group_tools; learning_chat unified config bridge - Remove deprecated: voice_trans plugin, bf_bot/test.py, dead code in dailywife/deer_pipe, empty dirs, debug scripts under helldivers temp - Disable brash_general_supercredits_tools (stub comment only) - bot.py: optional stdout/stderr redirect to log file for Web log viewer, force ANSI colorize on non-TTY sinks - Move runtime data (jm_code.json) out of plugin dir into hexi/data - Docs: plugin-audit-report.md; README reflects removed plugins Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,329 @@
|
||||
"""搜图 —— 触发器(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()
|
||||
Reference in New Issue
Block a user