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:
@@ -1,17 +1,6 @@
|
||||
import base64
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from random import choice
|
||||
from tkinter import Image
|
||||
"""今日老婆 —— 入口(元数据 + 依赖)。"""
|
||||
|
||||
from PIL import *
|
||||
from .utils import *
|
||||
|
||||
import httpx
|
||||
from nonebot import on_command, logger
|
||||
from nonebot.adapters.onebot.v11 import MessageEvent, Bot, MessageSegment
|
||||
from nonebot import on_command
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
@@ -21,106 +10,4 @@ __plugin_meta__ = PluginMetadata(
|
||||
type="application",
|
||||
)
|
||||
|
||||
wife = on_command("今日老婆", aliases={"今日老婆"})
|
||||
|
||||
|
||||
def get_member_list(all_list):
|
||||
id_list = []
|
||||
for member_list in all_list:
|
||||
id_list.append(member_list['user_id'])
|
||||
return id_list
|
||||
|
||||
|
||||
async def download_avatar(user_id: str) -> bytes:
|
||||
url = f"http://q1.qlogo.cn/g?b=qq&nk={user_id}&s=640"
|
||||
data = await download_url(url)
|
||||
if not data or hashlib.md5(data).hexdigest() == "acef72340ac0e914090bd35799f5594e":
|
||||
url = f"http://q1.qlogo.cn/g?b=qq&nk={user_id}&s=100"
|
||||
data = await download_url(url)
|
||||
return data
|
||||
|
||||
|
||||
async def download_url(url: str) -> bytes:
|
||||
async with httpx.AsyncClient() as client:
|
||||
for i in range(3):
|
||||
try:
|
||||
resp = await client.get(url)
|
||||
if resp.status_code != 200:
|
||||
continue
|
||||
return resp.content
|
||||
except Exception as e:
|
||||
logger.error(f"Error downloading {url}, retry {i}/3: {str(e)}")
|
||||
|
||||
|
||||
async def get_wife_info(member_info, qq_id):
|
||||
img = await download_avatar(qq_id)
|
||||
avatar = Image.open(BytesIO(img)).convert('RGBA')
|
||||
avatar = png_resize(avatar, new_width=145, new_height=145)
|
||||
|
||||
b_io = BytesIO()
|
||||
avatar.save(b_io, format="PNG")
|
||||
avatar_str = 'base64://' + base64.b64encode(b_io.getvalue()).decode()
|
||||
member_name = (member_info["card"] or member_info["nickname"])
|
||||
msg = (MessageSegment.text('你今天的群友老婆是:'), MessageSegment.image(avatar_str),
|
||||
MessageSegment.text(f'{member_name}({qq_id})'))
|
||||
return msg
|
||||
|
||||
|
||||
def load_group_config(group_id: str) -> int:
|
||||
filename = os.path.join(os.path.dirname(__file__), 'config', f'{group_id}.json')
|
||||
try:
|
||||
with open(filename, encoding='utf8') as f:
|
||||
config = json.load(f)
|
||||
return config
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
def write_group_config(group_id: str, link_id: str, wife_id: str, date: str, config) -> int:
|
||||
config_file = os.path.join(os.path.dirname(__file__), 'config', f'{group_id}.json')
|
||||
if config is not None:
|
||||
config[link_id] = [wife_id, date]
|
||||
else:
|
||||
config = {link_id: [wife_id, date]}
|
||||
with open(config_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(config, f, ensure_ascii=False)
|
||||
|
||||
|
||||
@wife.handle()
|
||||
async def wife_handle(bot: Bot, ev: MessageEvent):
|
||||
group_id = ev.group_id
|
||||
user_id = ev.user_id
|
||||
bot_id = ev.self_id
|
||||
wife_id = None
|
||||
today = str(datetime.date.today())
|
||||
config = load_group_config(group_id)
|
||||
|
||||
# if priv.check_priv(ev, priv.SUPERUSER):
|
||||
# wife_id = bot_id
|
||||
if config is not None:
|
||||
if str(user_id) in list(config):
|
||||
if config[str(user_id)][1] == today:
|
||||
wife_id = config[str(user_id)][0]
|
||||
else:
|
||||
del config[str(user_id)]
|
||||
|
||||
if wife_id is None:
|
||||
all_list = await bot.get_group_member_list(group_id=group_id)
|
||||
id_list = get_member_list(all_list)
|
||||
id_list.remove(bot_id)
|
||||
id_list.remove(user_id)
|
||||
if config is not None:
|
||||
for record_id in list(config):
|
||||
if config[record_id][1] != today:
|
||||
del config[record_id]
|
||||
else:
|
||||
try:
|
||||
id_list.remove(int(config[record_id][0]))
|
||||
except:
|
||||
del config[record_id]
|
||||
wife_id = choice(id_list)
|
||||
|
||||
write_group_config(group_id, user_id, wife_id, today, config)
|
||||
member_info = await bot.get_group_member_info(group_id=group_id, user_id=wife_id)
|
||||
result = await get_wife_info(member_info, wife_id)
|
||||
await bot.send(ev, result, at_sender=True)
|
||||
from .handlers import wife # noqa: E402,F401
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
"""今日老婆 —— Trigger 层。"""
|
||||
|
||||
from .wife import wife # noqa: F401
|
||||
@@ -0,0 +1,78 @@
|
||||
"""今日老婆 —— 触发器(Trigger + View)层。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import datetime
|
||||
from io import BytesIO
|
||||
from random import choice
|
||||
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters.onebot.v11 import MessageEvent, Bot, MessageSegment
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from PIL import Image
|
||||
|
||||
from ..services.store import get_member_list, load_group_config, write_group_config
|
||||
from ..utils import download_avatar, png_resize
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="今日老婆",
|
||||
description="随机抓取群友作为老婆",
|
||||
usage="发送【今日老婆】",
|
||||
type="application",
|
||||
)
|
||||
|
||||
wife = on_command("今日老婆", aliases={"今日老婆"})
|
||||
|
||||
|
||||
async def get_wife_info(member_info, qq_id):
|
||||
img = await download_avatar(qq_id)
|
||||
avatar = Image.open(BytesIO(img)).convert("RGBA")
|
||||
avatar = png_resize(avatar, new_width=145, new_height=145)
|
||||
b_io = BytesIO()
|
||||
avatar.save(b_io, format="PNG")
|
||||
avatar_str = "base64://" + base64.b64encode(b_io.getvalue()).decode()
|
||||
member_name = member_info["card"] or member_info["nickname"]
|
||||
return (
|
||||
MessageSegment.text("你今天的群友老婆是:"),
|
||||
MessageSegment.image(avatar_str),
|
||||
MessageSegment.text(f"{member_name}({qq_id})"),
|
||||
)
|
||||
|
||||
|
||||
@wife.handle()
|
||||
async def wife_handle(bot: Bot, ev: MessageEvent):
|
||||
group_id = ev.group_id
|
||||
user_id = ev.user_id
|
||||
bot_id = ev.self_id
|
||||
wife_id = None
|
||||
today = str(datetime.date.today())
|
||||
config = load_group_config(group_id)
|
||||
|
||||
if config is not None:
|
||||
if str(user_id) in list(config):
|
||||
if config[str(user_id)][1] == today:
|
||||
wife_id = config[str(user_id)][0]
|
||||
else:
|
||||
del config[str(user_id)]
|
||||
|
||||
if wife_id is None:
|
||||
all_list = await bot.get_group_member_list(group_id=group_id)
|
||||
id_list = get_member_list(all_list)
|
||||
id_list.remove(bot_id)
|
||||
id_list.remove(user_id)
|
||||
if config is not None:
|
||||
for record_id in list(config):
|
||||
if config[record_id][1] != today:
|
||||
del config[record_id]
|
||||
else:
|
||||
try:
|
||||
id_list.remove(int(config[record_id][0]))
|
||||
except Exception:
|
||||
del config[record_id]
|
||||
wife_id = choice(id_list)
|
||||
|
||||
write_group_config(group_id, user_id, wife_id, today, config)
|
||||
member_info = await bot.get_group_member_info(group_id=group_id, user_id=wife_id)
|
||||
result = await get_wife_info(member_info, wife_id)
|
||||
await bot.send(ev, result, at_sender=True)
|
||||
@@ -0,0 +1,3 @@
|
||||
"""今日老婆 —— Service 层。"""
|
||||
|
||||
from .store import get_member_list, load_group_config, write_group_config # noqa: F401
|
||||
@@ -0,0 +1,35 @@
|
||||
"""今日老婆 —— 数据访问(Service 层)。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
_CONFIG_DIR = Path(__file__).resolve().parents[3] / "data" / "dailywife" / "config"
|
||||
|
||||
|
||||
def get_member_list(all_list):
|
||||
return [member["user_id"] for member in all_list]
|
||||
|
||||
|
||||
def load_group_config(group_id: str) -> dict | None:
|
||||
filename = _CONFIG_DIR / f"{group_id}.json"
|
||||
try:
|
||||
with open(filename, encoding="utf8") as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def write_group_config(group_id: str, link_id: str, wife_id: str, date: str, config) -> None:
|
||||
_CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
||||
config_file = _CONFIG_DIR / f"{group_id}.json"
|
||||
if config is not None:
|
||||
config[link_id] = [wife_id, date]
|
||||
else:
|
||||
config = {link_id: [wife_id, date]}
|
||||
tmp = config_file.with_suffix(".tmp")
|
||||
with open(tmp, "w", encoding="utf-8") as f:
|
||||
json.dump(config, f, ensure_ascii=False)
|
||||
os.replace(tmp, config_file)
|
||||
@@ -1,50 +0,0 @@
|
||||
import hashlib
|
||||
import json
|
||||
from nonebot.log import logger as sv
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
from io import BytesIO
|
||||
|
||||
import aiohttp
|
||||
import qrcode
|
||||
import requests
|
||||
import requests.exceptions
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
|
||||
# PNG重绘大小
|
||||
def png_resize(source_file, new_width=0, new_height=0, resample="LANCZOS", ref_file=''):
|
||||
"""
|
||||
PNG缩放透明度处理
|
||||
:param source_file: 源文件(Image.open())
|
||||
:param new_width: 设置的宽度
|
||||
:param new_height: 设置的高度
|
||||
:param resample: 抗锯齿
|
||||
:param ref_file: 参考文件
|
||||
:return:
|
||||
"""
|
||||
img = source_file
|
||||
img = img.convert("RGBA")
|
||||
width, height = img.size
|
||||
|
||||
if ref_file != '':
|
||||
imgRef = Image.open(ref_file)
|
||||
new_width, new_height = imgRef.size
|
||||
else:
|
||||
if new_height == 0:
|
||||
new_height = new_width * width / height
|
||||
|
||||
bands = img.split()
|
||||
resample_map = {
|
||||
"NEAREST": Image.NEAREST,
|
||||
"BILINEAR": Image.BILINEAR,
|
||||
"BICUBIC": Image.BICUBIC,
|
||||
"LANCZOS": Image.LANCZOS
|
||||
}
|
||||
resample_method = resample_map.get(resample, Image.LANCZOS) # 默认使用 LANCZOS
|
||||
|
||||
bands = [b.resize((new_width, new_height), resample=resample_method) for b in bands]
|
||||
resized_file = Image.merge('RGBA', bands)
|
||||
|
||||
return resized_file
|
||||
@@ -0,0 +1,3 @@
|
||||
"""今日老婆 —— 纯工具层。"""
|
||||
|
||||
from .avatar import download_avatar, download_url, png_resize # noqa: F401
|
||||
@@ -0,0 +1,56 @@
|
||||
"""今日老婆 —— 头像工具(纯工具 + 网络请求)。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
from io import BytesIO
|
||||
|
||||
import httpx
|
||||
from PIL import Image
|
||||
|
||||
|
||||
# PNG 重绘大小
|
||||
def png_resize(source_file, new_width=0, new_height=0, resample="LANCZOS", ref_file=""):
|
||||
img = source_file
|
||||
img = img.convert("RGBA")
|
||||
width, height = img.size
|
||||
if ref_file != "":
|
||||
imgRef = Image.open(ref_file)
|
||||
new_width, new_height = imgRef.size
|
||||
else:
|
||||
if new_height == 0:
|
||||
new_height = new_width * width / height
|
||||
bands = img.split()
|
||||
resample_map = {
|
||||
"NEAREST": Image.NEAREST,
|
||||
"BILINEAR": Image.BILINEAR,
|
||||
"BICUBIC": Image.BICUBIC,
|
||||
"LANCZOS": Image.LANCZOS,
|
||||
}
|
||||
resample_method = resample_map.get(resample, Image.LANCZOS)
|
||||
bands = [b.resize((new_width, new_height), resample=resample_method) for b in bands]
|
||||
return Image.merge("RGBA", bands)
|
||||
|
||||
|
||||
async def download_url(url: str) -> bytes:
|
||||
from nonebot import logger
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
for i in range(3):
|
||||
try:
|
||||
resp = await client.get(url)
|
||||
if resp.status_code != 200:
|
||||
continue
|
||||
return resp.content
|
||||
except Exception as e:
|
||||
logger.error(f"Error downloading {url}, retry {i}/3: {str(e)}")
|
||||
return b""
|
||||
|
||||
|
||||
async def download_avatar(user_id: str) -> bytes:
|
||||
url = f"http://q1.qlogo.cn/g?b=qq&nk={user_id}&s=640"
|
||||
data = await download_url(url)
|
||||
if not data or hashlib.md5(data).hexdigest() == "acef72340ac0e914090bd35799f5594e":
|
||||
url = f"http://q1.qlogo.cn/g?b=qq&nk={user_id}&s=100"
|
||||
data = await download_url(url)
|
||||
return data
|
||||
Reference in New Issue
Block a user