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:
2026-09-03 00:44:38 +08:00
co-authored by Claude
parent 25bc30611f
commit 9371a28e35
2389 changed files with 5100 additions and 10127 deletions
@@ -1,7 +1,7 @@
"""统一配置注册:把插件配置按文档标准接入 hexi.web_config(Web 可读可改)。
- temp 清理配置(env pydantic) → register_model_config
- S3 存储配置(原硬编码在 storage/s3.py) → register_config_items(store=s3 模块)
- S3 存储配置(原硬编码在 services/storage/s3.py) → register_config_items(store=s3 模块)
- 群分组配置(data/list.json) → register_config_items(type=json, nosave)
"""
@@ -10,7 +10,7 @@ from __future__ import annotations
from hexi.config_standard import register_config_items, register_model_config
from . import cleanup, list_proc # noqa: F401
from .storage import s3 as _s3mod
from .services.storage import s3 as _s3mod
# 插件模块名 = plugin_id(与 NoneBot 模块名一致)
_PLUGIN_ID = __package__
@@ -1,6 +1,5 @@
"""抖音视频/图文解析编排层"""
import os
import re
import urllib.parse
from pathlib import Path
@@ -9,19 +8,15 @@ from typing import Optional, Union
import httpx
from nonebot import logger
from ..fetchers.douyin_api import fetch_douyin_content
from ..fetchers.douyin_ssr import MOBILE_UA, fetch_douyin_note_ssr
from ..services.fetchers.douyin_api import fetch_douyin_content
from ..services.fetchers.douyin_ssr import MOBILE_UA, fetch_douyin_note_ssr
from ..models import DouyinFetchError
from ..utils import parse_netscape_cookies
from ..utils import get_data_dir, parse_netscape_cookies
from .sender import PendingMedia, _as_paths
SHORT_LINK_PATTERN = re.compile(r"(v\.douyin\.com/[A-Za-z0-9_\-]+)")
def _get_data_dir() -> str:
return os.path.join(os.path.dirname(__file__), "..", "data")
async def _resolve_short_link(url: str) -> Optional[str]:
"""短链重定向:httpx 取 Location(最多 3 跳),失败返回 None"""
try:
@@ -83,7 +78,7 @@ async def parse_douyin(
try:
is_img_post = False
cookies_path = os.path.join(_get_data_dir(), "cookies.txt")
cookies_path = get_data_dir() / "cookies.txt"
cookies = parse_netscape_cookies(cookies_path)
# 先试 SSR(秒级、免浏览器),失败一律回退 playwright 链路。
# 2026-08-13 起抖音对 iesdouyin SSR 端点整体降级(登录态也拿不到
@@ -3,7 +3,6 @@
from __future__ import annotations
import html
import os
import re
from typing import Optional
@@ -13,16 +12,13 @@ from nonebot.rule import to_me
from nonebot_plugin_alconna import UniMessage
from nonebot_plugin_alconna.uniseg import get_target
from ..fetchers.bilibili_content import fetch_bilibili_content, resolve_short_link
from ..fetchers.rednote_content import fetch_rednote_content
from ..services.fetchers.bilibili_content import fetch_bilibili_content, resolve_short_link
from ..services.fetchers.rednote_content import fetch_rednote_content
from .douyin import parse_douyin, process_douyin_res
from .sender import PendingMedia, send_pending_media
from .universal import handle_universal
from ..list_proc import AUTO_LINK_KEYWORDS, get_group_auto_link, verify_user
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
FILE_PATH = os.path.join(BASE_DIR, "data", "list.json")
URL_PATTERN = re.compile(r"(https?://\S+)")
XCX_PATTERN = r"QQ小程序(?:&amp;#93;|&#93;|\])"
@@ -15,7 +15,7 @@ from typing import Optional, Union
from nonebot import logger
from nonebot_plugin_alconna import UniMessage
from ..storage.s3 import upload_with_plan
from ..services.storage.s3 import upload_with_plan
@dataclass
@@ -6,7 +6,7 @@ from typing import Optional
from nonebot import logger
from nonebot_plugin_alconna import UniMessage
from ..fetchers.video_downloader import download_video
from ..services.fetchers.video_downloader import download_video
from .sender import PendingMedia, _as_paths
@@ -0,0 +1 @@
# video_analysis —— Service 层。
@@ -21,10 +21,10 @@ from typing import Optional, Union
from nonebot import logger
from ..models import ContentFetchError
from ..utils import parse_netscape_cookies, slugify
from ...models import ContentFetchError
from ...utils import get_data_dir, parse_netscape_cookies, slugify
DATA_DIR = Path(__file__).resolve().parent.parent / "data"
DATA_DIR = get_data_dir()
BILI_UA = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
@@ -9,8 +9,8 @@ import httpx
from nonebot import logger
from playwright.async_api import async_playwright
from ..models import DouyinFetchError
from ..utils import ensure_unique_path, get_temp_root
from ...models import DouyinFetchError
from ...utils import ensure_unique_path, get_temp_root
from .douyin_parser import (
ParsedDouyinContent,
extract_trailing_digits,
@@ -9,8 +9,8 @@ from typing import Dict, List, Optional
from nonebot import logger
from ..models import DouyinFetchError
from ..utils import slugify
from ...models import DouyinFetchError
from ...utils import slugify
# ============================= 数据结构 =============================
@@ -19,8 +19,8 @@ from typing import List, Optional
import httpx
from nonebot import logger
from ..models import DouyinFetchError
from ..utils import get_temp_root
from ...models import DouyinFetchError
from ...utils import get_temp_root
from .douyin_api import _process_animated_note, _process_note_with_parsed
from .douyin_parser import parse_douyin_response, parse_note_images, parse_ssr_page
@@ -27,10 +27,10 @@ from typing import Optional, Union
from nonebot import logger
from ..models import ContentFetchError
from ..utils import get_temp_root, parse_netscape_cookies, slugify
from ...models import ContentFetchError
from ...utils import get_data_dir, get_temp_root, parse_netscape_cookies, slugify
DATA_DIR = Path(__file__).resolve().parent.parent / "data"
DATA_DIR = get_data_dir()
REDNOTE_UA = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
@@ -14,7 +14,7 @@ from nonebot import logger
from yt_dlp import YoutubeDL
from yt_dlp.utils import DownloadError
from ..utils import get_temp_root, slugify, ensure_unique_path
from ...utils import get_data_dir, get_temp_root, slugify, ensure_unique_path
def detect_platform(url: str) -> str:
@@ -49,11 +49,6 @@ def get_ffmpeg_path() -> str:
return "ffmpeg"
def _get_data_dir() -> Path:
"""获取 data/ 目录路径"""
return Path(__file__).resolve().parent.parent / "data"
async def _retry_download(
loop: asyncio.AbstractEventLoop,
url: str,
@@ -154,7 +149,7 @@ async def download_video(url: str) -> Optional[Path]:
"ffmpeg_location": get_ffmpeg_path(),
}
cookie_path = _get_data_dir() / "cookies.txt"
cookie_path = get_data_dir() / "cookies.txt"
if platform in ("bilibili", "twitter", "youtube"):
if not cookie_path.exists():
@@ -5,6 +5,15 @@ from time import strftime, localtime
from typing import List
def get_data_dir() -> Path:
"""插件 data 目录:.../nonebot_plugin_video_analysis/data
注意 utils.py 位于插件根目录,.parent 即插件根,因此这里写死回到
插件自身 data/(cookies.txt / list.json / temp 都在此处)。
"""
return Path(__file__).resolve().parent / "data"
def get_temp_root(sub: str = "") -> Path:
"""插件媒体临时目录:data/temp[/sub] — 下载的媒体先进这里