Files
abot/plugins/fanhao_search/main.py
2025-12-11 16:01:14 +08:00

287 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from typing import Dict, Any, List, Optional, Tuple
import re
from loguru import logger
from base.plugin_common.message_plugin_interface import MessagePluginInterface
from base.plugin_common.plugin_interface import PluginStatus
from utils.decorator.plugin_decorators import plugin_stats_decorator
from utils.decorator.points_decorator import plugin_points_cost
from utils.robot_cmd.robot_command import Feature, PermissionStatus, GroupBotManager
from wechat_ipad import WechatAPIClient
class FanhaoSearchPlugin(MessagePluginInterface):
"""番号查询插件"""
FEATURE_KEY = "FANHAO"
FEATURE_DESCRIPTION = "🔎 番号查询功能 [番号]"
@property
def name(self) -> str:
return "番号查询"
@property
def version(self) -> str:
return "1.0.0"
@property
def description(self) -> str:
return "提供基于MongoDB的番号搜索功能支持两个集合查询"
@property
def author(self) -> str:
return "liu.wei"
@property
def command_prefix(self) -> Optional[str]:
return ""
@property
def commands(self) -> List[str]:
return self._commands
@property
def feature_key(self) -> Optional[str]:
return self.FEATURE_KEY
@property
def feature_description(self) -> Optional[str]:
return self.FEATURE_DESCRIPTION
def __init__(self):
super().__init__()
self.feature = self.register_feature()
self.mongo_client = None
self.mongo_db = None
def initialize(self, context: Dict[str, Any]) -> bool:
self.LOG = logger
self.LOG.info(f"正在初始化 {self.name} 插件...")
self.event_system = context.get("event_system")
cfg = self._config.get("FanhaoSearch", {})
self._commands = cfg.get("command", ["番号"]) # 例:"番号 FNS-109"
self.command_format = cfg.get("command-format", "番号 番号编号 例如:番号 FNS-109")
self.enable = cfg.get("enable", True)
self.mongo_uri = cfg.get(
"mongo_uri",
"mongodb+srv://readonly:cS9NSuiJ1ebHnUL0@cluster0.8mosa.mongodb.net/sehuatang?retryWrites=true&w=majority",
)
self.mongo_db_name = cfg.get("db", "sehuatang")
self.collections = cfg.get(
"collections", ["hd_chinese_subtitles", "asia_codeless_originate", "asia_mosaic_originate", "4k_video"]
)
self.search_fields = cfg.get("search_fields", ["number"]) # 可能的字段名
# 延迟连接,在首次查询时连接,避免初始化阻塞
self.LOG.info(f"[{self.name}] 插件初始化完成,指令:{self._commands}")
return True
def start(self) -> bool:
self.LOG.info(f"[{self.name}] 插件已启动")
self.status = PluginStatus.RUNNING
return True
def stop(self) -> bool:
try:
if self.mongo_client:
self.mongo_client.close()
except Exception:
pass
self.LOG.info(f"[{self.name}] 插件已停止")
self.status = PluginStatus.STOPPED
return True
def can_process(self, message: Dict[str, Any]) -> bool:
if not self.enable:
return False
content = str(message.get("content", "")).strip()
command = content.split(" ")[0]
return command in self._commands
def _redact_mongo_uri(self, uri: str) -> str:
try:
# 隐藏用户名密码,仅保留协议和主机段
return re.sub(r"(mongodb\+srv://)(.*?@)", r"\\1***@", uri)
except Exception:
return "***"
def _ensure_mongo(self):
if self.mongo_client:
return
from pymongo import MongoClient
self.LOG.info(
f"[{self.name}] 准备连接MongoDB uri={self._redact_mongo_uri(self.mongo_uri)} db={self.mongo_db_name}"
)
try:
self.mongo_client = MongoClient(self.mongo_uri, serverSelectionTimeoutMS=5000)
# 探活
self.mongo_client.admin.command("ping")
self.mongo_db = self.mongo_client.get_database(self.mongo_db_name)
# 打印可见的数据库
try:
dbs = self.mongo_client.list_database_names()
self.LOG.info(f"[{self.name}] 可见数据库={dbs}")
except Exception as e:
self.LOG.warning(f"[{self.name}] 获取数据库列表失败: {e}")
try:
colls = self.mongo_db.list_collection_names()
except Exception as e:
colls = []
self.LOG.warning(f"[{self.name}] 获取集合列表失败: {e}")
self.LOG.info(f"[{self.name}] MongoDB连接成功集合={colls}")
# 对配置集合进行计数探测
for cname in self.collections:
try:
c = self.mongo_db.get_collection(cname)
# 尝试快速计数(可能返回估算值,但足够判断可见性)
cnt = c.estimated_document_count()
self.LOG.info(f"[{self.name}] 集合探测 {self.mongo_db_name}.{cname} 文档数≈{cnt}")
except Exception as e:
self.LOG.warning(f"[{self.name}] 集合探测失败 {self.mongo_db_name}.{cname}: {e}")
except Exception as e:
self.LOG.error(f"[{self.name}] MongoDB连接失败: {e}")
raise
def _normalize_code(self, text: str) -> str:
# 1. 基础清理:判空、去首尾空格、转大写
text = (text or "").strip().upper()
# 用户输入 处理后 说明
# IPzz108 IPZZ-108 目标场景:自动补全了横杠
# ipzz108 IPZZ-108 全小写自动转大写并补全
# IPZZ-108 IPZZ-108 已经有横杠,正则不匹配(字母后是横杠不是数字),保持原样
# ipzz-108 IPZZ-108 小写带横杠,仅转大写,保持原样
# ipzz108 IPZZ-108 去除前后空格
# A1 A-1 极短代码也能兼容
# 2. 核心逻辑:使用正则查找“字母后面紧跟数字”的情况,并在中间插入横杠
# r'([A-Z])(\d)' 含义捕获组1是任意大写字母捕获组2是任意数字
# r'\1-\2' 含义将匹配到的内容替换为“组1 + 横杠 + 组2”
return re.sub(r'([A-Z])(\d)', r'\1-\2', text)
def _build_queries(self, code_upper: str) -> List[Dict[str, Any]]:
# 精确匹配查询
or_exact = [{field: code_upper} for field in self.search_fields]
exact_query = {"$or": or_exact}
# 回退:大小写不敏感的等值匹配
or_regex = [
{field: {"$regex": f"^{re.escape(code_upper)}$", "$options": "i"}}
for field in self.search_fields
]
regex_query = {"$or": or_regex}
return [exact_query, regex_query]
def _query_collections(self, code_upper: str) -> Optional[Dict[str, Any]]:
self._ensure_mongo()
queries = self._build_queries(code_upper)
self.LOG.debug(f"[{self.name}] 标准化番号={code_upper},查询字段={self.search_fields}")
for idx, query in enumerate(queries):
self.LOG.debug(f"[{self.name}] 执行查询({idx + 1}/{len(queries)}): {query}")
for coll_name in self.collections:
try:
coll = self.mongo_db.get_collection(coll_name)
self.LOG.debug(f"[{self.name}] 在集合 {coll_name} 查找…")
doc = coll.find_one(query)
if doc:
doc["_collection"] = coll_name
self.LOG.info(f"[{self.name}] 命中集合 {coll_name}")
return doc
else:
self.LOG.debug(f"[{self.name}] 集合 {coll_name} 未命中")
except Exception as e:
self.LOG.error(f"[{self.name}] 查询集合 {coll_name} 出错: {e}")
continue
return None
def _format_result(self, doc: Dict[str, Any]) -> str:
def pick(d: Dict[str, Any], keys: List[str]) -> str:
for k in keys:
v = d.get(k)
if v:
return str(v)
return ""
code = pick(doc, self.search_fields)
title = pick(doc, ["title", "name", "标题"]) or "未提供"
actress = pick(doc, ["actress", "actors", "performer", "女优", "演员"]) # 可为空
date_val = pick(doc, ["date", "publish_date", "发行日"]) # 例如2025-09-10
post_time = pick(doc, ["post_time"]) # 例如2025-09-10 10:42:04
magnet = pick(doc, ["magnet"]) # 磁力
magnet_115 = pick(doc, ["magnet_115"]) # 115专用磁力
lines = [
f"✅ 查询成功:{code}",
f"标题:{title}",
]
if actress:
lines.append(f"演员:{actress}")
if date_val and post_time:
lines.append(f"日期:{date_val}(发帖:{post_time}")
elif date_val:
lines.append(f"日期:{date_val}")
elif post_time:
lines.append(f"发帖:{post_time}")
if magnet:
lines.append(f"磁力:{magnet}")
if magnet_115:
lines.append(f"115磁力{magnet_115}")
return "\n".join(lines)
@plugin_stats_decorator(plugin_name="番号查询")
@plugin_points_cost(10, "番号查询消耗积分", FEATURE_KEY)
async def process_message(self, message: Dict[str, Any]) -> Tuple[bool, Optional[str]]:
content = str(message.get("content", "")).strip()
self.LOG.debug(f"插件执行: {self.name}{content}")
command = content.split(" ")[0]
sender = message.get("sender")
roomid = message.get("roomid", "")
gbm: GroupBotManager = message.get("gbm")
bot: WechatAPIClient = message.get("bot")
# 参数检查
parts = content.split(" ")
if len(parts) < 2:
await bot.send_text_message((roomid if roomid else sender), f"❌命令格式错误!\n{self.command_format}",
sender)
return False, "命令格式错误"
if roomid and gbm.get_group_permission(roomid, self.feature) == PermissionStatus.DISABLED:
return False, "没有权限"
raw_code = content[len(command):].strip()
user_code = self._normalize_code(raw_code)
self.LOG.info(
f"[{self.name}] 收到查询 command={command} raw='{raw_code}' normalized='{user_code}' db={self.mongo_db_name} collections={self.collections}"
)
if not user_code:
await bot.send_text_message((roomid if roomid else sender), f"❌命令格式错误!\n{self.command_format}",
sender)
return False, "命令格式错误"
try:
doc = self._query_collections(user_code)
target = roomid if roomid else sender
if not doc:
self.LOG.warning(f"[{self.name}] 未找到番号:{user_code}")
await bot.send_text_message(target, f"未找到番号:{user_code}", sender)
return False, "未找到"
text = self._format_result(doc)
await bot.send_text_message(target, text, sender)
return True, "查询成功"
except Exception as e:
self.LOG.exception(f"处理番号查询出错: {e}")
return False, f"处理出错: {e}"
def get_plugin():
return FanhaoSearchPlugin()