Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ help_preview.html
__rank_preview.html
__rank_preview.png
.playwright-cli/
# 预渲染回归产物(脚本 + 对比图,纯本地,不入库)
test_output/

# 本地文档 / 项目主页(不入库)
index.html
Expand Down
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@
"files.exclude": {
"**/__pycache__": true
}
}
}
113 changes: 86 additions & 27 deletions TodayWaifu/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from gsuid_core.help.draw_new_plugin_help import get_new_help

from .shared import (
BASE_DIR,
LOG_PREFIX,
HELP_ICON_PATH,
Bot,
Expand All @@ -31,22 +30,44 @@
from .executor import run_blocking
from ..daily_wife_config import DailyWifeShowConfig

# 横幅、背景、分类条与命令卡贴图统一由外部绘图资源包提供,路径在该包内解析。
# 横幅/背景/分类条随风格成套切换,故不固定为模块级常量,而是每次调用时按风格解析;
# 命令卡片贴图与风格无关,是唯一可以全局固定的资源。
from ..todaywaifu_help.get_help import (
HELP_DATA as _HELP_JSON_PATH,
ICON_PATH as _ICON_PATH,
ITEM_PATH as _HELP_ITEM,
DEFAULT_STYLE as _HELP_DEFAULT_STYLE,
HELP_STYLE_NAMES as _HELP_STYLE_NAMES,
HELP_TEXT_COLORS,
HelpButtons,
resolve_style_path,
attach_command_icons,
)

# 资源签名:路径、mtime_ns、大小;缺失时为 None
# 用 mtime_ns 而非秒级 mtime:同秒内的两次写入在秒级精度下无法区分,会漏掉重绘。
PathSignature = tuple[str, int, int] | None
# 帮助缓存键:5 个资源签名 + 列数 + 权限等级(见 _help_cache_key)
# 帮助缓存键:7 个资源签名 + 列数 + 权限等级 + 风格编号(见 _help_cache_key)
# 权限等级参与键是因为帮助内容随权限裁剪,不同权限的用户不能共用同一张图。
# 分类条与命令卡贴图同样计入签名:它们参与渲染,替换后若不换键会长期显示旧图。
# 风格编号显式入键:横幅/背景/分类条按风格成套切换,仅靠路径签名虽也能区分,
# 但一旦两套风格共用某张资源,键就会静默复用另一种风格的旧图。
HelpCacheKey = tuple[
PathSignature, PathSignature, PathSignature, PathSignature, PathSignature, int, int
PathSignature,
PathSignature,
PathSignature,
PathSignature,
PathSignature,
tuple[PathSignature, ...],
PathSignature,
int,
int,
int,
]

# 帮助图缓存容量刻意很小(4):键维度多,不同键的图几乎不会复用,
# 保留过多只会长期占用内存中较大的位图。
_HELP_JSON_PATH = BASE_DIR / 'help.json'
_TEXTURE_DIR = BASE_DIR / 'texture2d'
_BANNER_BG_PATH = _TEXTURE_DIR / 'help_banner.png'
_BG_PATH = _TEXTURE_DIR / 'help_bg.jpg'
_ICON_PATH = _TEXTURE_DIR / 'icons'
_HELP_CACHE_MAX_ENTRIES = 4
_HELP_CACHE: OrderedDict[HelpCacheKey, str] = OrderedDict()
# 在途渲染合并表:同一键的并发请求共享一次渲染,否则同时多人触发帮助会重复起绘图任务。
Expand All @@ -70,6 +91,20 @@ def _show_config_path(key: str) -> Path | None:
return path if path.is_file() else None


def _help_style() -> int:
"""读出配置里的帮助图风格编号,越界或非整数一律回落到默认风格。

风格决定整套横幅/背景/分类条贴图,取值非法时不能静默套用另一套风格,
因此这里先夹到已知编号区间再交由 resolve_style_path 解析目录。
"""
value = DailyWifeShowConfig.get_config('DailyWifeHelpStyle').data
try:
style = int(value)
except (TypeError, ValueError):
style = _HELP_DEFAULT_STYLE
return style if style in _HELP_STYLE_NAMES else _HELP_DEFAULT_STYLE


def _help_column() -> int:
# 列数必须夹在 [1, 10]:绘图布局在超出该范围时会溢出画布或除零,
# 非法配置按默认 3 列处理。
Expand Down Expand Up @@ -97,61 +132,75 @@ def _help_cache_key(
icon_path: Path,
banner_bg_path: Path,
help_bg_path: Path,
text_path: Path,
style: int,
column: int,
pm: int,
) -> HelpCacheKey:
# 五个签名对应五份参与渲染的资源,缺一都会导致「换了图但帮助未更新」。
# 每个签名对应一份参与渲染的资源,缺一都会导致「换了图但帮助未更新」。
return (
_path_signature(_HELP_JSON_PATH),
_path_signature(icon_path),
_path_signature(banner_bg_path),
_path_signature(help_bg_path),
_path_signature(_ICON_PATH),
# 分类条为多张轮换,故整组取签名;命令卡为单张。
tuple(_path_signature(path) for path in sorted(text_path.glob('cag_bg*.png'))),
# 命令卡与风格无关,取共用的那一份。
_path_signature(_HELP_ITEM),
column,
pm,
style,
)


def _build_help_inputs(
plugin_icon_path: Path,
custom_banner_bg_path: Path | None,
custom_help_bg_path: Path | None,
) -> tuple[Image.Image, dict[str, PluginHelp], dict[str, Image.Image | Path]]:
text_path: Path,
) -> tuple[
Image.Image,
dict[str, PluginHelp],
dict[str, Image.Image | Path | list[Image.Image]],
]:
"""在线程中读取 JSON 和 PIL 资源,避免阻塞事件循环。"""
with Image.open(plugin_icon_path) as source:
icon = source.convert('RGBA')

extra: dict[str, Image.Image | Path] = {}
banner_bg_path = custom_banner_bg_path or _BANNER_BG_PATH
extra: dict[str, Image.Image | Path | list[Image.Image]] = {}
# text_path 是当前风格的内置贴图目录,仅在用户未上传自定义图时才作为回落。
banner_bg_path = custom_banner_bg_path or text_path / 'banner_bg.jpg'
if banner_bg_path.is_file():
with Image.open(banner_bg_path) as source:
banner = source.convert('RGBA')
if custom_banner_bg_path is None:
# 内置横幅只取顶部 40%:原图是整幅插画,直接使用会让横幅比例失真。
width, height = banner.size
extra['banner_bg'] = banner.crop((0, 0, width, int(height * 0.40)))
else:
# 用户自定义横幅已按横幅比例制作,原样使用,不做裁切。
extra['banner_bg'] = banner.copy()

help_bg_path = custom_help_bg_path or _BG_PATH
# 横幅已按横幅比例制作,内置与自定义都不再裁切。
extra['banner_bg'] = source.convert('RGBA')

help_bg_path = custom_help_bg_path or text_path / 'bg.jpg'
if help_bg_path.is_file():
with Image.open(help_bg_path) as source:
# 背景按画布尺寸等比覆盖裁切,不做纯色填充(浅色主题下会露出色带)
extra['help_bg'] = source.convert('RGBA').copy()
extra['help_bg'] = source.convert('RGBA')

# 图标以目录形式传入而非逐个打开:绘图工具按需读取,避免一次性载入全部图标位图。
if _ICON_PATH.is_dir():
extra['icon_path'] = _ICON_PATH

return icon, _load_help_data(), extra
# 分类条与命令卡按卡片式版式绘制:分类条取自当前风格目录并多张轮换,
# 命令卡为两风格共用的单张。
buttons = HelpButtons(text_path)
extra['cag_bg'] = buttons.cag_bg
extra['item_bg'] = buttons.item

# 逐条命令补上图标:help.json 只写命令名,图标需按别名表回查资源目录。
return icon, attach_command_icons(_load_help_data(), _ICON_PATH), extra


async def _render_help(
key: HelpCacheKey,
plugin_icon_path: Path,
custom_banner_bg_path: Path | None,
custom_help_bg_path: Path | None,
text_path: Path,
column: int,
pm: int,
) -> str:
Expand All @@ -163,6 +212,7 @@ async def render() -> str:
plugin_icon_path,
custom_banner_bg_path,
custom_help_bg_path,
text_path,
)
return await get_new_help(
plugin_name='TodayWaifu',
Expand All @@ -177,6 +227,8 @@ async def render() -> str:
enable_cache=False,
column=column,
pm=pm,
# 浅色立绘底上默认灰字几乎不可见,这里显式指定深色文字。
**HELP_TEXT_COLORS,
**extra,
)

Expand Down Expand Up @@ -220,15 +272,21 @@ async def daily_wife_help(bot: Bot, ev: Event) -> list[str] | None:

custom_banner_bg_path = _show_config_path('DailyWifeHelpBannerBgUpload')
custom_help_bg_path = _show_config_path('DailyWifeHelpBgUpload')
# 风格决定内置横幅/背景/分类条取自哪一套贴图;先解析出目录再据此拼路径,
# 这样键中登记的路径与 _build_help_inputs 的实际取值必然一致。
style = _help_style()
text_path = resolve_style_path(style)
# 键中登记的是实际参与渲染的路径(自定义或内置),与 _build_help_inputs 的选择
# 逻辑保持一致,否则自定义图切换后缓存键不变,帮助图不会更新。
banner_bg_path = custom_banner_bg_path or _BANNER_BG_PATH
help_bg_path = custom_help_bg_path or _BG_PATH
banner_bg_path = custom_banner_bg_path or text_path / 'banner_bg.jpg'
help_bg_path = custom_help_bg_path or text_path / 'bg.jpg'
column = _help_column()
key = _help_cache_key(
plugin_icon_path,
banner_bg_path,
help_bg_path,
text_path,
style,
column,
int(ev.user_pm),
)
Expand All @@ -239,6 +297,7 @@ async def daily_wife_help(bot: Bot, ev: Event) -> list[str] | None:
plugin_icon_path,
custom_banner_bg_path,
custom_help_bg_path,
text_path,
column,
int(ev.user_pm),
)
Expand Down
35 changes: 14 additions & 21 deletions config_default.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,13 @@
),
'DailyWifePgrImageSource': GsStrConfig(
'战双老婆图片数据源',
'选择 local 只使用本地战双图库目录;'
'选择 gallery 优先使用远程图库接口,接口不可用时回退本地。默认 gallery',
'选择 local 只使用本地战双图库目录;选择 gallery 优先使用远程图库接口,接口不可用时回退本地。默认 gallery',
'gallery',
options=['local', 'gallery'],
),
'DailyLoliImageSource': GsStrConfig(
'萝莉图片数据源',
'选择 local 只使用本地萝莉图库;'
'选择 gallery 优先使用远程图库接口,接口不可用时回退本地。默认 gallery',
'选择 local 只使用本地萝莉图库;选择 gallery 优先使用远程图库接口,接口不可用时回退本地。默认 gallery',
'gallery',
options=['local', 'gallery'],
),
Expand Down Expand Up @@ -122,7 +120,6 @@
'允许使用本插件全部图片上传功能的用户 ID。机器人主人无需加入白名单',
[],
),

'_DividerNteWife': GsDivider('异环老婆', ''),
'DailyWifeNteEnabled': GsBoolConfig(
'启用今日异环老婆',
Expand Down Expand Up @@ -197,18 +194,14 @@
),
'DailyWifeShowUserId': GsBoolConfig(
'显示触发者 ID',
(
'开启后文字说明额外附带一行“你的ID:xxx”,'
'方便 QQ 官方机器人等平台的群友复制 OpenID 进行抢老婆/送老婆'
),
('开启后文字说明额外附带一行“你的ID:xxx”,方便 QQ 官方机器人等平台的群友复制 OpenID 进行抢老婆/送老婆'),
False,
),
'DailyWifeDebugMode': GsBoolConfig(
'主人 Debug 模式',
'开启后机器人主人每次抽取都会临时随机重抽,不读取或写入当天记录,便于调试',
False,
),

'_DividerAssignWife': GsDivider('分配老婆', ''),
'DailyWifeAssignWhitelist': GsListStrConfig(
'分配老婆白名单',
Expand All @@ -220,14 +213,12 @@
'允许使用指定老婆功能的用户 ID。机器人主人无需加入白名单;功能开关和权限可在“今日老婆-指定老婆”服务中配置',
[],
),

'_DividerDailyWife': GsDivider('今日老婆', ''),
'DailyWifeTextTemplate': GsStrConfig(
'今日老婆文字模板',
'今日老婆的文字说明模板,可用变量:{name} 角色名,{role_id} 数字 ID',
'你今天的老婆是{name}',
),

'_DividerGroupMember': GsDivider('群友玩法', ''),
'DailyWifeEnableGroupMember': GsBoolConfig(
'今日老婆概率抽群友',
Expand All @@ -254,7 +245,6 @@
'「娶群友」命令的文字说明模板,可用变量:{name} 群友昵称,{user_id} 群友 QQ',
'你娶到的群友是{name}',
),

'_DividerDailyHusband': GsDivider('今日老公', ''),
'DailyWifeHusbandEnabled': GsBoolConfig(
'启用今日老公',
Expand All @@ -266,14 +256,12 @@
'今日老公的文字说明模板,可用变量:{name} 角色名,{role_id} 数字 ID',
'你今天的老公是{name}',
),

'_DividerDailyLoli': GsDivider('今日萝莉', ''),
'DailyLoliEnabled': GsBoolConfig(
'启用今日萝莉',
'开启后可使用「今日萝莉」命令;关闭后命令不生效。图片内容风险请自行承担',
True,
),

'_DividerDailyShota': GsDivider('今日正太', ''),
'DailyShotaEnabled': GsBoolConfig(
'启用今日正太',
Expand All @@ -285,7 +273,6 @@
'今日正太的文字说明模板',
'你今天的正太来啦!',
),

'_DividerRob': GsDivider('抢夺设置', ''),
'DailyWifeRobEnabled': GsBoolConfig(
'启用抢老婆',
Expand Down Expand Up @@ -342,7 +329,6 @@
'可用变量:{name} 名称,{role_id} 图片标识,{target} 被抢用户 ID',
'抢正太成功!你把对方今天的正太抢过来了!',
),

'_DividerGift': GsDivider('赠送设置', ''),
'DailyWifeGiftEnabled': GsBoolConfig(
'启用送老婆',
Expand Down Expand Up @@ -411,10 +397,17 @@
'help_icon',
'png',
),
'DailyWifeHelpStyle': GsIntConfig(
'帮助图风格',
'控制帮助图整体配色:1=二次元风格(立绘横幅+渐变分类条),2=手绘风格(柔和手绘底+爱心分类条)。默认 2',
2,
2,
options=[1, 2],
),
'DailyWifeHelpColumn': GsIntConfig(
'帮助展示行数',
'控制帮助图每组展示数量,默认 4,可按需要调整',
4,
10,
'帮助图宽度(每行列数)',
'控制帮助图一行放几条命令:4 更紧凑,3 留白更多。数值越大画布越宽;上限 5,超过会溢出画布',
3,
5,
),
}
6 changes: 5 additions & 1 deletion tests/compatibility_manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -324,9 +324,13 @@
"key": "DailyWifeHelpIconUpload",
"expr": "GsImageConfig('帮助头像', '自定义「今日老婆帮助」左上角头像,建议使用方形图片', str(SHOW_CONFIG_PATH / 'help_icon.png'), str(SHOW_CONFIG_PATH), 'help_icon', 'png')"
},
{
"key": "DailyWifeHelpStyle",
"expr": "GsIntConfig('帮助图风格', '控制帮助图整体配色:1=二次元风格(立绘横幅+渐变分类条),2=手绘风格(柔和手绘底+爱心分类条)。默认 2', 2, 2, options=[1, 2])"
},
{
"key": "DailyWifeHelpColumn",
"expr": "GsIntConfig('帮助展示行数', '控制帮助图每组展示数量,默认 4,可按需要调整', 4, 10)"
"expr": "GsIntConfig('帮助图宽度(每行列数)', '控制帮助图一行放几条命令:4 更紧凑,3 留白更多。数值越大画布越宽;上限 5,超过会溢出画布', 4, 5)"
}
]
},
Expand Down
Loading
Loading