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,22 +1,9 @@
import json
import random
import json
import random
import asyncio
import os
from typing import List
"""随机 JM 码 —— 入口(元数据 + 配置注册)。"""
from nonebot.adapters.onebot.v11 import MessageSegment, PokeNotifyEvent, Message
from nonebot.plugin.on import on_notice, on_command
from nonebot import on_type, on_message, logger
from nonebot.adapters import Event
from nonebot.matcher import Matcher
from nonebot.params import CommandArg
from nonebot.rule import to_me
from nonebot_plugin_alconna import UniMessage, get_target
from nonebot import require
from nonebot.plugin import PluginMetadata
from hexi.plugins.nonebot_plugin_hexi_core.message_utils import send_poke
require("nonebot_plugin_alconna")
__plugin_meta__ = PluginMetadata(
name="随机JM码",
@@ -25,127 +12,7 @@ __plugin_meta__ = PluginMetadata(
type="application",
)
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
FILE_PATH = os.path.join(BASE_DIR, "jm_code.json")
from . import config as _config # noqa: E402
from . import handlers as _handlers # noqa: E402
TEXT_TEMPLATES = [
"给你{code},拿去吧",
"给你{code},记得谢谢我",
"给你{code},注意保养弹簧"
]
BLOCK_CODE = ["350235", "350234"]
WHITE_LIST = ["1019165018"]
jm_handler = on_notice(priority=10, block=False, rule=to_me())
add_jm_code = on_command("jm", rule=to_me())
@add_jm_code.handle()
async def add_more_jm_code(event: Event, matcher: Matcher):
input_code = event.get_message().extract_plain_text().strip()
cmd = matcher.state["_prefix"]["command"][0]
input_code = input_code.replace(cmd, "")
if input_code in BLOCK_CODE:
await UniMessage.at(event.get_user_id()).text(" 不准发石!!").send()
return
await add_new_jm_code(input_code)
await UniMessage.reply(event.get_user_id()).text(" 添加成功").send()
return
@jm_handler.handle()
async def random_jm_code(event: Event, poke_event: PokeNotifyEvent):
user_id = event.get_user_id()
logger.info(f"{user_id}触发了戳一戳")
target = get_target(event)
group_id = target.parent_id or target.id or poke_event.group_id
logger.info(f"触发戳一戳的群聊:{group_id}")
if group_id in WHITE_LIST:
logger.info(f"白名单:{group_id}")
choice = random.randint(0, 100)
logger.info(f"触发概率:{choice}")
if choice >= 50:
await poke_reply(event)
else:
logger.info("不做操作")
else:
logger.info(f"不在白名单:{group_id}")
if target.private:
group_id = None
result = await send_poke(group_id=group_id, user_id=user_id)
logger.info(result)
return
async def poke_reply(event: Event):
user_id = event.get_user_id()
jm_message = await get_random_jm_code()
await UniMessage.at(user_id).text(" " + jm_message).send()
JM_CODES: List[int] = []
_LOADED = False
LOCK = asyncio.Lock()
def _safe_write_json(data, file_path: str):
tmp_path = file_path + ".tmp"
with open(tmp_path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# 原子替换(防止写一半崩溃)
os.replace(tmp_path, file_path)
async def load_codes(file_path: str = FILE_PATH):
global JM_CODES, _LOADED
if _LOADED:
return
async with LOCK:
if _LOADED:
return
if not os.path.exists(file_path):
JM_CODES = []
_safe_write_json(JM_CODES, file_path)
else:
with open(file_path, "r", encoding="utf-8") as f:
try:
JM_CODES = json.load(f)
except json.JSONDecodeError:
# 文件损坏时兜底
JM_CODES = []
_LOADED = True
async def get_random_jm_code() -> str:
await load_codes()
if not JM_CODES:
return "暂无可用代码"
code = random.choice(JM_CODES)
logger.info(f"随机JM:{code}")
template = random.choice(TEXT_TEMPLATES)
return template.format(code=code)
async def add_new_jm_code(new_code: int) -> bool:
await load_codes()
async with LOCK:
if new_code in JM_CODES:
return False # 已存在
JM_CODES.append(new_code)
_safe_write_json(JM_CODES, FILE_PATH)
return True
_config.register_config()
@@ -0,0 +1,54 @@
"""随机 JM 码 —— 配置注册(按《插件配置文件标准》)。
把原本硬编码在 __init__.py 里的文案模板 / 屏蔽码 / 白名单群号
注册为 Web 可读可改的配置项(来源无关,store=本模块)。运行期插件读本模块常量,
Web 保存后经 apply 热更新本模块属性,重启也不丢。
"""
from __future__ import annotations
import sys as _sys
from hexi.config_standard import register_config_items
# plugin_id = NoneBot 插件模块名
_PLUGIN_ID = __package__
# —— 可配置常量(来源为本模块) ——
TEXT_TEMPLATES = [
"给你{code},拿去吧",
"给你{code},记得谢谢我",
"给你{code},注意保养弹簧",
]
BLOCK_CODE = ["350235", "350234"]
WHITE_LIST = ["1019165018"]
def register_config() -> None:
register_config_items(
_PLUGIN_ID,
[
{
"key": "TEXT_TEMPLATES",
"label": "随机文案模板",
"type": "text",
"item_type": "str",
"default": TEXT_TEMPLATES,
},
{
"key": "BLOCK_CODE",
"label": "屏蔽码",
"type": "text",
"item_type": "str",
"default": BLOCK_CODE,
},
{
"key": "WHITE_LIST",
"label": "白名单群号",
"type": "text",
"item_type": "str",
"default": WHITE_LIST,
},
],
store=_sys.modules[__name__],
)
@@ -0,0 +1,3 @@
"""随机 JM 码 —— Trigger 层。"""
from .jm import add_jm_code, jm_handler # noqa: F401
@@ -0,0 +1,72 @@
"""随机 JM 码 —— 触发器(Trigger)层。"""
from __future__ import annotations
import random
from nonebot import logger
from nonebot.adapters import Event
from nonebot.adapters.onebot.v11 import PokeNotifyEvent
from nonebot.matcher import Matcher
from nonebot.plugin import PluginMetadata
from nonebot.plugin.on import on_notice, on_command
from nonebot.rule import to_me
from nonebot_plugin_alconna import UniMessage, get_target
from hexi.plugins.nonebot_plugin_hexi_core.message_utils import send_poke
from .. import config as _cfg
from ..services.store import add_new_jm_code, get_random_jm_code
__plugin_meta__ = PluginMetadata(
name="随机JM码",
description="随机发送JM码,支持添加与屏蔽",
usage="@bot 触发随机JM码;jm 添加JM码",
type="application",
)
jm_handler = on_notice(priority=10, block=False, rule=to_me())
add_jm_code = on_command("jm", rule=to_me())
@add_jm_code.handle()
async def add_more_jm_code(event: Event, matcher: Matcher):
input_code = event.get_message().extract_plain_text().strip()
cmd = matcher.state["_prefix"]["command"][0]
input_code = input_code.replace(cmd, "")
if input_code in _cfg.BLOCK_CODE:
await UniMessage.at(event.get_user_id()).text(" 不准发石!!").send()
return
await add_new_jm_code(input_code)
await UniMessage.reply(event.get_user_id()).text(" 添加成功").send()
return
@jm_handler.handle()
async def random_jm_code(event: Event, poke_event: PokeNotifyEvent):
user_id = event.get_user_id()
logger.info(f"{user_id}触发了戳一戳")
target = get_target(event)
group_id = target.parent_id or target.id or poke_event.group_id
logger.info(f"触发戳一戳的群聊:{group_id}")
if group_id in _cfg.WHITE_LIST:
logger.info(f"白名单:{group_id}")
choice = random.randint(0, 100)
logger.info(f"触发概率:{choice}")
if choice >= 50:
await poke_reply(event)
else:
logger.info("不做操作")
else:
logger.info(f"不在白名单:{group_id}")
if target.private:
group_id = None
result = await send_poke(group_id=group_id, user_id=user_id)
logger.info(result)
return
async def poke_reply(event: Event):
user_id = event.get_user_id()
jm_message = await get_random_jm_code()
await UniMessage.at(user_id).text(" " + jm_message).send()
@@ -1,286 +0,0 @@
[
"219",
"224",
"759",
"877",
"878",
"2057",
"2388",
"2868",
"4333",
"6102",
"6411",
"18212",
"20106",
"20348",
"20902",
"21274",
"21407",
"21994",
"22392",
"27512",
"27701",
"28650",
"33775",
"39509",
"46555",
"47729",
"50514",
"54747",
"55427",
"58267",
"61257",
"62268",
"69354",
"73451",
"74891",
"77778",
"78665",
"80358",
"85055",
"86779",
"89218",
"98509",
"98669",
"101792",
"104738",
"105854",
"107184",
"114445",
"116157",
"122333",
"122427",
"124116",
"124561",
"125237",
"127282",
"135697",
"138809",
"140620",
"142464",
"142568",
"145289",
"148486",
"148624",
"149578",
"149906",
"151607",
"151696",
"181107",
"181611",
"190447",
"192957",
"203446",
"207063",
"209232",
"218796",
"220455",
"220995",
"220996",
"221659",
"223153",
"224879",
"225764",
"230702",
"230806",
"231692",
"235366",
"239642",
"239797",
"241726",
"245189",
"247044",
"248646",
"249121",
"251050",
"255015",
"258778",
"263104",
"265236",
"266615",
"268453",
"269417",
"271249",
"273943",
"278032",
"281142",
"284185",
"285994",
"290391",
"291407",
"293593",
"293867",
"303683",
"305291",
"305428",
"306326",
"306848",
"308472",
"308595",
"310176",
"318730",
"318731",
"322028",
"326357",
"329995",
"333722",
"334554",
"343898",
"346346",
"346862",
"352727",
"355019",
"355659",
"362197",
"362460",
"368639",
"368837",
"369504",
"370634",
"371570",
"371631",
"371875",
"373099",
"374495",
"377532",
"380052",
"382336",
"384297",
"385372",
"389479",
"390353",
"392326",
"394523",
"395280",
"395281",
"397150",
"397478",
"398569",
"401496",
"401574",
"409658",
"409896",
"410841",
"411613",
"412004",
"415513",
"417463",
"427706",
"429322",
"433023",
"434797",
"434801",
"435777",
"441189",
"441594",
"442137",
"443170",
"445794",
"448098",
"451603",
"451610",
"452501",
"452699",
"453931",
"454803",
"465335",
"467110",
"467180",
"468624",
"469738",
"471872",
"472952",
"475378",
"476677",
"477029",
"477990",
"478437",
"479907",
"480142",
"480841",
"481536",
"481979",
"482542",
"484683",
"485647",
"485870",
"486253",
"487798",
"488125",
"495051",
"495256",
"498750",
"501638",
"501838",
"502132",
"505048",
"505241",
"505253",
"505484",
"507975",
"508253",
"509489",
"509834",
"511010",
"513984",
"515569",
"517500",
"523276",
"524360",
"527354",
"529468",
"533458",
"533552",
"537119",
"541285",
"541762",
"542252",
"543247",
"543525",
"543749",
"544360",
"544401",
"545733",
"547590",
"549168",
"550176",
"552312",
"554413",
"563746",
"608258",
"1020570",
"1060990",
"1071423",
"1176067",
"1190464",
"1193587",
"1196804",
"1202415",
"1206636",
"1212262",
"1212643",
"1215962",
"1216628",
"1217450",
"1222299",
"1222320",
"1222393",
"1223363",
"1224091",
"1224493",
"2904551",
"3037141",
"3157283",
"3300062",
"3481682",
"3918941",
"3921012",
"4214502",
"4283473",
"4283484",
"4366352",
"4380002",
"5430811",
"1434077",
"1423811"
]
@@ -0,0 +1,3 @@
"""随机 JM 码 —— Service 层。"""
from .store import add_new_jm_code, get_random_jm_code # noqa: F401
@@ -0,0 +1,74 @@
"""随机 JM 码 —— 数据访问(Service 层)。"""
from __future__ import annotations
import asyncio
import json
import os
import random
from pathlib import Path
from typing import List
from .. import config as _cfg
# 运行时数据统一放 hexi/data
_DATA_DIR = Path(__file__).resolve().parents[3] / "data" / "random_jm_code"
FILE_PATH = _DATA_DIR / "jm_code.json"
_LEGACY_FILE = Path(__file__).resolve().parents[1] / "jm_code.json"
JM_CODES: List[int] = []
_LOADED = False
LOCK = asyncio.Lock()
def _migrate_legacy() -> None:
if not FILE_PATH.exists() and _LEGACY_FILE.exists():
_DATA_DIR.mkdir(parents=True, exist_ok=True)
os.replace(_LEGACY_FILE, FILE_PATH)
def _safe_write_json(data, file_path: Path):
file_path.parent.mkdir(parents=True, exist_ok=True)
tmp_path = file_path.with_suffix(".tmp")
with open(tmp_path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
os.replace(tmp_path, file_path)
async def load_codes(file_path: Path = FILE_PATH):
global JM_CODES, _LOADED
if _LOADED:
return
async with LOCK:
if _LOADED:
return
_migrate_legacy()
if not file_path.exists():
JM_CODES = []
_safe_write_json(JM_CODES, file_path)
else:
with open(file_path, "r", encoding="utf-8") as f:
try:
JM_CODES = json.load(f)
except json.JSONDecodeError:
JM_CODES = []
_LOADED = True
async def get_random_jm_code() -> str:
await load_codes()
if not JM_CODES:
return "暂无可用代码"
code = random.choice(JM_CODES)
template = random.choice(_cfg.TEXT_TEMPLATES)
return template.format(code=code)
async def add_new_jm_code(new_code: int) -> bool:
await load_codes()
async with LOCK:
if new_code in JM_CODES:
return False
JM_CODES.append(new_code)
_safe_write_json(JM_CODES, FILE_PATH)
return True