番号插件新增封面全图模糊,发送前自动遮罩处理

变更项:

1. 在 fanhao_search 插件中新增全图高斯模糊处理函数,发送封面前统一执行。

2. 增加 cover_blur_enable 与 cover_blur_radius 配置项,支持开关和模糊强度调节。

3. 封面处理失败时回退原图并记录日志,保证主流程可用。

4. 初始化日志补充模糊配置参数,便于排查线上行为。
This commit is contained in:
liuwei
2026-04-22 12:29:11 +08:00
parent 62f7a9f2d4
commit 3e52ba2a82
2 changed files with 43 additions and 3 deletions

View File

@@ -20,9 +20,14 @@ http_proxy = ""
allow_download_link = true allow_download_link = true
# 是否发送封面预览图(默认关闭) # 是否发送封面预览图(默认关闭)
allow_preview_cover = false allow_preview_cover = true
# 当开启磁力时,是否优先返回“带字幕”磁力 # 当开启磁力时,是否优先返回“带字幕”磁力
prefer_subtitle_magnet = true prefer_subtitle_magnet = true
# 封面图是否开启全图模糊(建议开启)
cover_blur_enable = true
# 全图模糊强度(高斯半径,数值越大越模糊)
cover_blur_radius = 18

View File

@@ -1,10 +1,12 @@
from typing import Any, Dict, List, Optional, Tuple from typing import Any, Dict, List, Optional, Tuple
import re import re
from urllib.parse import urljoin from urllib.parse import urljoin
import io
import aiohttp import aiohttp
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from loguru import logger from loguru import logger
from PIL import Image, ImageFilter
from base.plugin_common.message_plugin_interface import MessagePluginInterface from base.plugin_common.message_plugin_interface import MessagePluginInterface
from base.plugin_common.plugin_interface import PluginStatus from base.plugin_common.plugin_interface import PluginStatus
@@ -73,6 +75,9 @@ class FanhaoSearchPlugin(MessagePluginInterface):
self.allow_download_link = False self.allow_download_link = False
self.allow_preview_cover = False self.allow_preview_cover = False
self.prefer_subtitle_magnet = True self.prefer_subtitle_magnet = True
# 封面图降敏配置:默认开启全图模糊,降低群内图片内容风险。
self.cover_blur_enable = True
self.cover_blur_radius = 18
def initialize(self, context: Dict[str, Any]) -> bool: def initialize(self, context: Dict[str, Any]) -> bool:
"""初始化插件配置。""" """初始化插件配置。"""
@@ -90,11 +95,14 @@ class FanhaoSearchPlugin(MessagePluginInterface):
self.allow_download_link = bool(cfg.get("allow_download_link", False)) self.allow_download_link = bool(cfg.get("allow_download_link", False))
self.allow_preview_cover = bool(cfg.get("allow_preview_cover", False)) self.allow_preview_cover = bool(cfg.get("allow_preview_cover", False))
self.prefer_subtitle_magnet = bool(cfg.get("prefer_subtitle_magnet", True)) self.prefer_subtitle_magnet = bool(cfg.get("prefer_subtitle_magnet", True))
self.cover_blur_enable = bool(cfg.get("cover_blur_enable", True))
self.cover_blur_radius = max(1, int(cfg.get("cover_blur_radius", 18) or 18))
self.LOG.info( self.LOG.info(
f"[{self.name}] 初始化完成: enable={self.enable}, commands={self._commands}, " f"[{self.name}] 初始化完成: enable={self.enable}, commands={self._commands}, "
f"base_url={self.javbus_base_url}, allow_download_link={self.allow_download_link}, " f"base_url={self.javbus_base_url}, allow_download_link={self.allow_download_link}, "
f"allow_preview_cover={self.allow_preview_cover}, timeout={self.request_timeout_seconds}s" f"allow_preview_cover={self.allow_preview_cover}, cover_blur_enable={self.cover_blur_enable}, "
f"cover_blur_radius={self.cover_blur_radius}, timeout={self.request_timeout_seconds}s"
) )
return True return True
@@ -176,6 +184,32 @@ class FanhaoSearchPlugin(MessagePluginInterface):
raise RuntimeError(f"图片下载失败 status={resp.status}, url={url}") raise RuntimeError(f"图片下载失败 status={resp.status}, url={url}")
return await resp.read() return await resp.read()
def _apply_full_blur_to_image_bytes(self, image_bytes: bytes) -> bytes:
"""对图片执行全图高斯模糊。
设计说明:
1. 这里使用 Pillow 的 GaussianBlur 对整图处理,不做局部区域判断;
2. 输出统一转为 JPEG避免源图格式差异导致发送失败
3. 若处理异常则回退原图,保证主流程可用性。
"""
if not image_bytes:
return image_bytes
if not self.cover_blur_enable:
return image_bytes
try:
with Image.open(io.BytesIO(image_bytes)) as image:
# 转为 RGB 统一颜色空间,避免 RGBA 保存 JPEG 报错。
if image.mode in ("RGBA", "P"):
image = image.convert("RGB")
# 全图高斯模糊,半径可在配置中调整。
blurred = image.filter(ImageFilter.GaussianBlur(radius=float(self.cover_blur_radius)))
output = io.BytesIO()
blurred.save(output, format="JPEG", quality=88)
return output.getvalue()
except Exception as e:
self.LOG.warning(f"[{self.name}] 封面全图模糊失败,回退原图: error={e}")
return image_bytes
@staticmethod @staticmethod
def _extract_plain_value_from_info_p(info_p) -> str: def _extract_plain_value_from_info_p(info_p) -> str:
"""从详情页 <p> 节点中提取纯文本值。 """从详情页 <p> 节点中提取纯文本值。
@@ -462,6 +496,8 @@ class FanhaoSearchPlugin(MessagePluginInterface):
if cover_url: if cover_url:
try: try:
cover_bytes = await self._http_get_bytes(cover_url, referer=detail_url) cover_bytes = await self._http_get_bytes(cover_url, referer=detail_url)
# 发送前执行全图模糊,避免直接发送原始封面。
cover_bytes = self._apply_full_blur_to_image_bytes(cover_bytes)
await bot.send_image_message(target, cover_bytes) await bot.send_image_message(target, cover_bytes)
except Exception as cover_error: except Exception as cover_error:
self.LOG.warning(f"[{self.name}] 封面发送失败: code={normalized_code}, error={cover_error}") self.LOG.warning(f"[{self.name}] 封面发送失败: code={normalized_code}, error={cover_error}")
@@ -475,4 +511,3 @@ class FanhaoSearchPlugin(MessagePluginInterface):
def get_plugin(): def get_plugin():
"""返回插件实例。""" """返回插件实例。"""
return FanhaoSearchPlugin() return FanhaoSearchPlugin()