ErisPulse-HelpNext 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- erispulse_helpnext-0.1.0/.github/assets/ErisPulseLogo.png +0 -0
- erispulse_helpnext-0.1.0/.github/workflows/python-publish.yml +71 -0
- erispulse_helpnext-0.1.0/HelpNext/Core.py +349 -0
- erispulse_helpnext-0.1.0/HelpNext/Templates.py +293 -0
- erispulse_helpnext-0.1.0/HelpNext/Visualizer.py +498 -0
- erispulse_helpnext-0.1.0/HelpNext/__init__.py +3 -0
- erispulse_helpnext-0.1.0/HelpNext/assets/icon.png +0 -0
- erispulse_helpnext-0.1.0/LICENSE +7 -0
- erispulse_helpnext-0.1.0/PKG-INFO +148 -0
- erispulse_helpnext-0.1.0/README.md +127 -0
- erispulse_helpnext-0.1.0/pyproject.toml +42 -0
|
Binary file
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# This workflow will upload a Python Package to PyPI when a release is created
|
|
2
|
+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
|
|
3
|
+
|
|
4
|
+
# This workflow uses actions that are not certified by GitHub.
|
|
5
|
+
# They are provided by a third-party and are governed by
|
|
6
|
+
# separate terms of service, privacy policy, and support
|
|
7
|
+
# documentation.
|
|
8
|
+
|
|
9
|
+
name: Upload Python Package
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
workflow_dispatch:
|
|
13
|
+
release:
|
|
14
|
+
types: [published]
|
|
15
|
+
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
release-build:
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
|
|
26
|
+
- uses: actions/setup-python@v5
|
|
27
|
+
with:
|
|
28
|
+
python-version: "3.x"
|
|
29
|
+
|
|
30
|
+
- name: Build release distributions
|
|
31
|
+
run: |
|
|
32
|
+
# NOTE: put your own distribution build steps here.
|
|
33
|
+
python -m pip install build
|
|
34
|
+
python -m build
|
|
35
|
+
|
|
36
|
+
- name: Upload distributions
|
|
37
|
+
uses: actions/upload-artifact@v4
|
|
38
|
+
with:
|
|
39
|
+
name: release-dists
|
|
40
|
+
path: dist/
|
|
41
|
+
|
|
42
|
+
pypi-publish:
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
needs:
|
|
45
|
+
- release-build
|
|
46
|
+
permissions:
|
|
47
|
+
# IMPORTANT: this permission is mandatory for trusted publishing
|
|
48
|
+
id-token: write
|
|
49
|
+
|
|
50
|
+
# Dedicated environments with protections for publishing are strongly recommended.
|
|
51
|
+
# For more information, see: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#deployment-protection-rules
|
|
52
|
+
environment:
|
|
53
|
+
name: pypi
|
|
54
|
+
# OPTIONAL: uncomment and update to include your PyPI project URL in the deployment status:
|
|
55
|
+
# url: https://pypi.org/p/YOURPROJECT
|
|
56
|
+
#
|
|
57
|
+
# ALTERNATIVE: if your GitHub Release name is the PyPI project version string
|
|
58
|
+
# ALTERNATIVE: exactly, uncomment the following line instead:
|
|
59
|
+
# url: https://pypi.org/project/YOURPROJECT/${{ github.event.release.name }}
|
|
60
|
+
|
|
61
|
+
steps:
|
|
62
|
+
- name: Retrieve release distributions
|
|
63
|
+
uses: actions/download-artifact@v4
|
|
64
|
+
with:
|
|
65
|
+
name: release-dists
|
|
66
|
+
path: dist/
|
|
67
|
+
|
|
68
|
+
- name: Publish release distributions to PyPI
|
|
69
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
70
|
+
with:
|
|
71
|
+
packages-dir: dist/
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Dict, List, Optional, Tuple
|
|
3
|
+
|
|
4
|
+
from ErisPulse import sdk
|
|
5
|
+
from ErisPulse.Core.Bases import BaseConfig, BaseI18n, BaseModule, I18nKey
|
|
6
|
+
from ErisPulse.Core.Event import command
|
|
7
|
+
|
|
8
|
+
from .Templates import HelpTemplates
|
|
9
|
+
from .Visualizer import Visualizer
|
|
10
|
+
|
|
11
|
+
class Main(BaseModule):
|
|
12
|
+
"""ErisPulse modern help module (Takumi-rendered, i18n-aware)."""
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class ConfigClass(BaseConfig):
|
|
16
|
+
show_hidden_commands: bool = field(
|
|
17
|
+
default=False,
|
|
18
|
+
metadata={
|
|
19
|
+
"description": {"i18n": "HelpNext.cfg_show_hidden", "default": "显示隐藏命令"},
|
|
20
|
+
"ui": {"widget": "switch", "group": "basic", "order": 1},
|
|
21
|
+
},
|
|
22
|
+
)
|
|
23
|
+
group_commands: bool = field(
|
|
24
|
+
default=True,
|
|
25
|
+
metadata={
|
|
26
|
+
"description": {"i18n": "HelpNext.cfg_group_commands", "default": "按分组显示命令"},
|
|
27
|
+
"ui": {"widget": "switch", "group": "basic", "order": 2},
|
|
28
|
+
},
|
|
29
|
+
)
|
|
30
|
+
theme: str = field(
|
|
31
|
+
default="auto",
|
|
32
|
+
metadata={
|
|
33
|
+
"description": {"i18n": "HelpNext.cfg_theme", "default": "图片主题"},
|
|
34
|
+
"ui": {
|
|
35
|
+
"widget": "select", "group": "render", "order": 3,
|
|
36
|
+
"options": [
|
|
37
|
+
{"label": {"i18n": "HelpNext.theme_auto", "default": "自动(跟随时间)"}, "value": "auto"},
|
|
38
|
+
{"label": {"i18n": "HelpNext.theme_light", "default": "浅色"}, "value": "light"},
|
|
39
|
+
{"label": {"i18n": "HelpNext.theme_dark", "default": "深色"}, "value": "dark"},
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
)
|
|
44
|
+
utc_offset: int = field(
|
|
45
|
+
default=8,
|
|
46
|
+
metadata={
|
|
47
|
+
"description": {"i18n": "HelpNext.cfg_utc_offset", "default": "UTC 时区偏移(用于昼夜切换)"},
|
|
48
|
+
"min": -12, "max": 14,
|
|
49
|
+
"ui": {"widget": "number", "group": "render", "order": 4},
|
|
50
|
+
},
|
|
51
|
+
)
|
|
52
|
+
show_logo: bool = field(
|
|
53
|
+
default=True,
|
|
54
|
+
metadata={
|
|
55
|
+
"description": {"i18n": "HelpNext.cfg_show_logo", "default": "头部显示 ErisPulse 图标"},
|
|
56
|
+
"ui": {"widget": "switch", "group": "header", "order": 5},
|
|
57
|
+
},
|
|
58
|
+
)
|
|
59
|
+
header_title: str = field(
|
|
60
|
+
default="",
|
|
61
|
+
metadata={
|
|
62
|
+
"description": {"i18n": "HelpNext.cfg_header_title", "default": "自定义头部标题(留空使用默认)"},
|
|
63
|
+
"ui": {"widget": "text", "group": "header", "order": 6},
|
|
64
|
+
},
|
|
65
|
+
)
|
|
66
|
+
header_subtitle: str = field(
|
|
67
|
+
default="",
|
|
68
|
+
metadata={
|
|
69
|
+
"description": {"i18n": "HelpNext.cfg_header_subtitle", "default": "自定义头部副标题(留空使用默认)"},
|
|
70
|
+
"ui": {"widget": "text", "group": "header", "order": 7},
|
|
71
|
+
},
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
_schema_meta = {
|
|
75
|
+
"group_labels": {
|
|
76
|
+
"basic": {"i18n": "HelpNext.group_basic", "default": "基本"},
|
|
77
|
+
"render": {"i18n": "HelpNext.group_render", "default": "渲染"},
|
|
78
|
+
"header": {"i18n": "HelpNext.group_header", "default": "头部"},
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
class I18nClass(BaseI18n):
|
|
83
|
+
title: I18nKey = I18nKey(default="Command Help", zh_CN="命令帮助", zh_TW="命令幫助", en="Command Help", ja="コマンドヘルプ", ru="Справка по командам")
|
|
84
|
+
detail_title: I18nKey = I18nKey(default="Command Detail", zh_CN="命令详情", zh_TW="命令詳情", en="Command Detail", ja="コマンド詳細", ru="Подробности команды")
|
|
85
|
+
group_default: I18nKey = I18nKey(default="General", zh_CN="通用命令", zh_TW="一般命令", en="General", ja="一般", ru="Общие")
|
|
86
|
+
no_description: I18nKey = I18nKey(default="No description", zh_CN="暂无描述", zh_TW="暫無描述", en="No description", ja="説明なし", ru="Нет описания")
|
|
87
|
+
usage_hint: I18nKey = I18nKey(default="Use {prefix}help <index> for details", zh_CN="使用 {prefix}help <序号> 查看命令详情", zh_TW="使用 {prefix}help <序號> 查看命令詳情", en="Use {prefix}help <index> for details", ja="{prefix}help <番号> で詳細を表示", ru="Введите {prefix}help <номер> для подробностей")
|
|
88
|
+
command_count: I18nKey = I18nKey(default="{count} commands available", zh_CN="共 {count} 个可用命令", zh_TW="共 {count} 個可用命令", en="{count} commands available", ja="利用可能なコマンド {count} 件", ru="Доступно команд: {count}")
|
|
89
|
+
other_prefixes: I18nKey = I18nKey(default="Other prefixes", zh_CN="其他触发前缀", zh_TW="其他觸發前綴", en="Other prefixes", ja="その他のプレフィックス", ru="Другие префиксы")
|
|
90
|
+
|
|
91
|
+
label_description: I18nKey = I18nKey(default="Description", zh_CN="描述", zh_TW="描述", en="Description", ja="説明", ru="Описание")
|
|
92
|
+
label_aliases: I18nKey = I18nKey(default="Aliases", zh_CN="别名", zh_TW="別名", en="Aliases", ja="エイリアス", ru="Псевдонимы")
|
|
93
|
+
label_usage: I18nKey = I18nKey(default="Usage", zh_CN="用法", zh_TW="用法", en="Usage", ja="使い方", ru="Использование")
|
|
94
|
+
label_permission: I18nKey = I18nKey(default="Permission", zh_CN="权限", zh_TW="權限", en="Permission", ja="権限", ru="Права")
|
|
95
|
+
label_group: I18nKey = I18nKey(default="Group", zh_CN="分组", zh_TW="分組", en="Group", ja="グループ", ru="Группа")
|
|
96
|
+
permission_required: I18nKey = I18nKey(default="Requires permission", zh_CN="需要特殊权限", zh_TW="需要特殊權限", en="Requires permission", ja="特殊権限が必要", ru="Требуются права")
|
|
97
|
+
|
|
98
|
+
chip_commands: I18nKey = I18nKey(default="Commands", zh_CN="命令", zh_TW="命令", en="Commands", ja="コマンド", ru="Команды")
|
|
99
|
+
chip_groups: I18nKey = I18nKey(default="Groups", zh_CN="分组", zh_TW="分組", en="Groups", ja="グループ", ru="Группы")
|
|
100
|
+
|
|
101
|
+
err_out_of_range: I18nKey = I18nKey(default="Index out of range", zh_CN="序号超出范围", zh_TW="序號超出範圍", en="Index out of range", ja="番号が範囲外", ru="Номер вне диапазона")
|
|
102
|
+
err_range_hint: I18nKey = I18nKey(default="Please enter a number between 1 and {count}", zh_CN="请输入 1-{count} 之间的序号", zh_TW="請輸入 1-{count} 之間的序號", en="Please enter a number between 1 and {count}", ja="1〜{count} の番号を入力してください", ru="Введите номер от 1 до {count}")
|
|
103
|
+
err_invalid_arg: I18nKey = I18nKey(default="Invalid argument", zh_CN="参数错误", zh_TW="參數錯誤", en="Invalid argument", ja="引数エラー", ru="Неверный аргумент")
|
|
104
|
+
err_invalid_hint: I18nKey = I18nKey(default="Please enter a valid number", zh_CN="请输入有效的序号", zh_TW="請輸入有效的序號", en="Please enter a valid number", ja="有効な番号を入力してください", ru="Введите корректный номер")
|
|
105
|
+
err_fmt: I18nKey = I18nKey(default="Invalid output format: {fmt}", zh_CN="无效的输出格式:{fmt}", zh_TW="無效的輸出格式:{fmt}", en="Invalid output format: {fmt}", ja="出力形式が無効です: {fmt}", ru="Недопустимый формат вывода: {fmt}")
|
|
106
|
+
err_unknown: I18nKey = I18nKey(default="Unknown argument: {arg}", zh_CN="未知参数:{arg}", zh_TW="未知參數:{arg}", en="Unknown argument: {arg}", ja="不明な引数: {arg}", ru="Неизвестный аргумент: {arg}")
|
|
107
|
+
err_img_unavailable: I18nKey = I18nKey(default="Image output unavailable, falling back to text", zh_CN="图片输出不可用,已回退到文本", zh_TW="圖片輸出不可用,已回退到文字", en="Image output unavailable, falling back to text", ja="画像出力は利用できません。テキストにフォールバックします", ru="Вывод изображений недоступен, используется текст")
|
|
108
|
+
|
|
109
|
+
cfg_show_hidden: I18nKey = I18nKey(key="HelpNext.cfg_show_hidden", default="Show hidden commands", zh_CN="显示隐藏命令", zh_TW="顯示隱藏命令", en="Show hidden commands", ja="非表示コマンドを表示", ru="Показывать скрытые команды")
|
|
110
|
+
cfg_group_commands: I18nKey = I18nKey(key="HelpNext.cfg_group_commands", default="Group commands by category", zh_CN="按分组显示命令", zh_TW="按分組顯示命令", en="Group commands by category", ja="カテゴリ別にグループ化", ru="Группировать команды")
|
|
111
|
+
cfg_theme: I18nKey = I18nKey(key="HelpNext.cfg_theme", default="Image theme", zh_CN="图片主题", zh_TW="圖片主題", en="Image theme", ja="画像テーマ", ru="Тема изображения")
|
|
112
|
+
cfg_utc_offset: I18nKey = I18nKey(key="HelpNext.cfg_utc_offset", default="UTC offset for day/night switching", zh_CN="UTC 时区偏移(用于昼夜切换)", zh_TW="UTC 時區偏移(用於晝夜切換)", en="UTC offset (day/night switching)", ja="UTCオフセット(昼夜切替用)", ru="Смещение UTC (для смены темы)")
|
|
113
|
+
cfg_show_logo: I18nKey = I18nKey(key="HelpNext.cfg_show_logo", default="Show ErisPulse icon in header", zh_CN="头部显示 ErisPulse 图标", zh_TW="頭部顯示 ErisPulse 圖示", en="Show ErisPulse icon in header", ja="ヘッダーに ErisPulse アイコンを表示", ru="Показывать иконку ErisPulse в шапке")
|
|
114
|
+
cfg_header_title: I18nKey = I18nKey(key="HelpNext.cfg_header_title", default="Custom header title (empty for default)", zh_CN="自定义头部标题(留空使用默认)", zh_TW="自訂頭部標題(留空使用預設)", en="Custom header title (empty for default)", ja="カスタムヘッダータイトル(空でデフォルト)", ru="Свой заголовок шапки (пусто = по умолчанию)")
|
|
115
|
+
cfg_header_subtitle: I18nKey = I18nKey(key="HelpNext.cfg_header_subtitle", default="Custom header subtitle (empty for default)", zh_CN="自定义头部副标题(留空使用默认)", zh_TW="自訂頭部副標題(留空使用預設)", en="Custom header subtitle (empty for default)", ja="カスタムヘッダーサブタイトル(空でデフォルト)", ru="Свой подзаголовок шапки (пусто = по умолчанию)")
|
|
116
|
+
|
|
117
|
+
theme_auto: I18nKey = I18nKey(key="HelpNext.theme_auto", default="Auto (by time)", zh_CN="自动(跟随时间)", zh_TW="自動(跟隨時間)", en="Auto (by time)", ja="自動(時間帯)", ru="Авто (по времени)")
|
|
118
|
+
theme_light: I18nKey = I18nKey(key="HelpNext.theme_light", default="Light", zh_CN="浅色", zh_TW="淺色", en="Light", ja="ライト", ru="Светлая")
|
|
119
|
+
theme_dark: I18nKey = I18nKey(key="HelpNext.theme_dark", default="Dark", zh_CN="深色", zh_TW="深色", en="Dark", ja="ダーク", ru="Тёмная")
|
|
120
|
+
group_basic: I18nKey = I18nKey(key="HelpNext.group_basic", default="Basic", zh_CN="基本", zh_TW="基本", en="Basic", ja="基本", ru="Основные")
|
|
121
|
+
group_render: I18nKey = I18nKey(key="HelpNext.group_render", default="Rendering", zh_CN="渲染", zh_TW="渲染", en="Rendering", ja="レンダリング", ru="Отрисовка")
|
|
122
|
+
group_header: I18nKey = I18nKey(key="HelpNext.group_header", default="Header", zh_CN="头部", zh_TW="頭部", en="Header", ja="ヘッダー", ru="Шапка")
|
|
123
|
+
|
|
124
|
+
def __init__(self):
|
|
125
|
+
self.sdk = sdk
|
|
126
|
+
self.logger = sdk.logger.get_child("HelpNext")
|
|
127
|
+
self.command_map: Dict[int, Dict] = {}
|
|
128
|
+
self.visualizer = Visualizer(sdk, {})
|
|
129
|
+
self._help_handler = None
|
|
130
|
+
|
|
131
|
+
@staticmethod
|
|
132
|
+
def get_load_strategy():
|
|
133
|
+
from ErisPulse.loaders import ModuleLoadStrategy
|
|
134
|
+
return ModuleLoadStrategy(
|
|
135
|
+
lazy_load=False,
|
|
136
|
+
priority=60,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
async def on_load(self, event):
|
|
140
|
+
self._help_handler = self._make_handler()
|
|
141
|
+
command(
|
|
142
|
+
"help",
|
|
143
|
+
aliases=["h", "帮助"],
|
|
144
|
+
help="显示帮助信息",
|
|
145
|
+
usage="help [序号] [--format <image|html|markdown|text>]",
|
|
146
|
+
)(self._help_handler)
|
|
147
|
+
self.logger.info("HelpNext 已加载")
|
|
148
|
+
|
|
149
|
+
async def on_unload(self, event):
|
|
150
|
+
if self._help_handler is not None:
|
|
151
|
+
command.unregister(self._help_handler)
|
|
152
|
+
self.logger.info("HelpNext 已卸载")
|
|
153
|
+
return True
|
|
154
|
+
|
|
155
|
+
def _cfg_view(self) -> Dict:
|
|
156
|
+
cfg = self.cfg
|
|
157
|
+
return {
|
|
158
|
+
"show_hidden_commands": cfg.show_hidden_commands,
|
|
159
|
+
"group_commands": cfg.group_commands,
|
|
160
|
+
"theme": cfg.theme,
|
|
161
|
+
"utc_offset": cfg.utc_offset,
|
|
162
|
+
"show_logo": cfg.show_logo,
|
|
163
|
+
"header_title": cfg.header_title,
|
|
164
|
+
"header_subtitle": cfg.header_subtitle,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
def _all_prefixes(self) -> List[str]:
|
|
168
|
+
# 读取框架命令处理器维护的前缀(随配置热更新,与命令解析保持一致)
|
|
169
|
+
prefix = command.prefix
|
|
170
|
+
if isinstance(prefix, list):
|
|
171
|
+
return [str(p) for p in prefix] if prefix else ["/"]
|
|
172
|
+
return [str(prefix)] if prefix else ["/"]
|
|
173
|
+
|
|
174
|
+
def _build_command_list(self, event) -> List[Dict]:
|
|
175
|
+
cfg = self._cfg_view()
|
|
176
|
+
show_hidden = cfg["show_hidden_commands"]
|
|
177
|
+
result: List[Dict] = []
|
|
178
|
+
# 会话感知:按作用域过滤当前会话不可用模块的命令(与框架静默语义一致)
|
|
179
|
+
names = command.get_commands(event=event) if show_hidden else command.get_visible_commands(event=event)
|
|
180
|
+
for name in names:
|
|
181
|
+
info = command.get_command(name, event=event)
|
|
182
|
+
if info and name == info.get("main_name"):
|
|
183
|
+
result.append({"name": name, "info": info})
|
|
184
|
+
return result
|
|
185
|
+
|
|
186
|
+
def _make_handler(self):
|
|
187
|
+
async def help_command(event):
|
|
188
|
+
await self._handle(event)
|
|
189
|
+
return help_command
|
|
190
|
+
|
|
191
|
+
async def _handle(self, event) -> None:
|
|
192
|
+
try:
|
|
193
|
+
args = event.get_command_args()
|
|
194
|
+
commands = self._build_command_list(event)
|
|
195
|
+
prefixes = self._all_prefixes()
|
|
196
|
+
prefix = prefixes[0] if prefixes else "/"
|
|
197
|
+
cfg = self._cfg_view()
|
|
198
|
+
self.visualizer.config = cfg
|
|
199
|
+
|
|
200
|
+
self._index_commands(commands, cfg["group_commands"])
|
|
201
|
+
|
|
202
|
+
index, fmt, err = self._parse_args(args)
|
|
203
|
+
|
|
204
|
+
if err:
|
|
205
|
+
if err[0] == "format":
|
|
206
|
+
msg = HelpTemplates._t("err_fmt", fmt=err[1] or "?")
|
|
207
|
+
else:
|
|
208
|
+
msg = HelpTemplates._t("err_unknown", arg=err[1])
|
|
209
|
+
title = HelpTemplates._t("err_invalid_arg")
|
|
210
|
+
await self._send(event, HelpTemplates.build_error(title, msg), None, "auto-text")
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
image = None
|
|
214
|
+
templates = None
|
|
215
|
+
want_image = fmt in ("auto", "image")
|
|
216
|
+
|
|
217
|
+
if index is not None:
|
|
218
|
+
if index in self.command_map:
|
|
219
|
+
cmd = self.command_map[index]
|
|
220
|
+
if want_image:
|
|
221
|
+
image = self.visualizer.render_command_detail(cmd, prefix, prefixes)
|
|
222
|
+
templates = HelpTemplates.build_command_detail(cmd, prefix, prefixes)
|
|
223
|
+
else:
|
|
224
|
+
title = HelpTemplates._t("err_out_of_range")
|
|
225
|
+
msg = HelpTemplates._t("err_range_hint", count=len(commands))
|
|
226
|
+
if want_image:
|
|
227
|
+
image = self.visualizer.render_error(title, msg)
|
|
228
|
+
templates = HelpTemplates.build_error(title, msg)
|
|
229
|
+
else:
|
|
230
|
+
if want_image:
|
|
231
|
+
image = self.visualizer.render_help_list(
|
|
232
|
+
commands, self.command_map, prefix, cfg["group_commands"], prefixes,
|
|
233
|
+
)
|
|
234
|
+
templates = HelpTemplates.build_help_list(
|
|
235
|
+
commands, self.command_map, prefix, cfg["group_commands"], prefixes,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
await self._send(event, templates, image, fmt)
|
|
239
|
+
except Exception as e:
|
|
240
|
+
self.logger.error(f"处理帮助命令出错: {e}", exc_info=True)
|
|
241
|
+
|
|
242
|
+
@staticmethod
|
|
243
|
+
def _normalize_format(v) -> Optional[str]:
|
|
244
|
+
v = str(v).lower().strip()
|
|
245
|
+
return {
|
|
246
|
+
"image": "image", "img": "image",
|
|
247
|
+
"html": "html", "h5": "html",
|
|
248
|
+
"markdown": "markdown", "md": "markdown",
|
|
249
|
+
"text": "text", "txt": "text",
|
|
250
|
+
}.get(v)
|
|
251
|
+
|
|
252
|
+
@staticmethod
|
|
253
|
+
def _parse_args(args: List[str]) -> Tuple[Optional[int], str, Optional[Tuple[str, str]]]:
|
|
254
|
+
index = None
|
|
255
|
+
fmt = "auto"
|
|
256
|
+
err = None
|
|
257
|
+
i, n = 0, len(args)
|
|
258
|
+
while i < n:
|
|
259
|
+
low = str(args[i]).lower()
|
|
260
|
+
if low.isdigit():
|
|
261
|
+
if index is None:
|
|
262
|
+
index = int(low)
|
|
263
|
+
else:
|
|
264
|
+
err = ("unknown", args[i])
|
|
265
|
+
break
|
|
266
|
+
elif low == "--format":
|
|
267
|
+
if i + 1 < n:
|
|
268
|
+
v = Main._normalize_format(args[i + 1])
|
|
269
|
+
if v is None:
|
|
270
|
+
err = ("format", str(args[i + 1]))
|
|
271
|
+
break
|
|
272
|
+
fmt = v
|
|
273
|
+
i += 1
|
|
274
|
+
else:
|
|
275
|
+
err = ("format", "")
|
|
276
|
+
break
|
|
277
|
+
elif low.startswith("--format="):
|
|
278
|
+
v = Main._normalize_format(low.split("=", 1)[1])
|
|
279
|
+
if v is None:
|
|
280
|
+
err = ("format", low.split("=", 1)[1])
|
|
281
|
+
break
|
|
282
|
+
fmt = v
|
|
283
|
+
else:
|
|
284
|
+
err = ("unknown", args[i])
|
|
285
|
+
break
|
|
286
|
+
i += 1
|
|
287
|
+
return index, fmt, err
|
|
288
|
+
|
|
289
|
+
def _index_commands(self, commands: List[Dict], group_commands: bool) -> None:
|
|
290
|
+
self.command_map = {}
|
|
291
|
+
grouped: Dict[str, List[Dict]] = {}
|
|
292
|
+
if group_commands:
|
|
293
|
+
for cmd in commands:
|
|
294
|
+
g = cmd["info"].get("group") or "default"
|
|
295
|
+
grouped.setdefault(g, []).append(cmd)
|
|
296
|
+
else:
|
|
297
|
+
grouped["default"] = list(commands)
|
|
298
|
+
idx = 1
|
|
299
|
+
for _, cmds in grouped.items():
|
|
300
|
+
for cmd in cmds:
|
|
301
|
+
self.command_map[idx] = cmd
|
|
302
|
+
idx += 1
|
|
303
|
+
|
|
304
|
+
@staticmethod
|
|
305
|
+
def _supports(event, method: str) -> bool:
|
|
306
|
+
try:
|
|
307
|
+
return event.supports(method)
|
|
308
|
+
except Exception:
|
|
309
|
+
return False
|
|
310
|
+
|
|
311
|
+
def _select_text_format(self, event, templates) -> Tuple[str, str]:
|
|
312
|
+
if self._supports(event, "Html"):
|
|
313
|
+
return ("Html", templates["html"])
|
|
314
|
+
if self._supports(event, "Markdown"):
|
|
315
|
+
return ("Markdown", templates["markdown"])
|
|
316
|
+
return ("Text", templates["text"])
|
|
317
|
+
|
|
318
|
+
async def _send(self, event, templates, image: Optional[bytes], fmt: str) -> None:
|
|
319
|
+
try:
|
|
320
|
+
if fmt == "image":
|
|
321
|
+
if image and self._supports(event, "Image"):
|
|
322
|
+
await event.reply(image, method="Image")
|
|
323
|
+
return
|
|
324
|
+
note = HelpTemplates._t("err_img_unavailable")
|
|
325
|
+
await event.reply(note)
|
|
326
|
+
return
|
|
327
|
+
|
|
328
|
+
if fmt in ("html", "markdown", "text"):
|
|
329
|
+
await event.reply(templates[fmt], method=fmt.capitalize())
|
|
330
|
+
return
|
|
331
|
+
|
|
332
|
+
if fmt == "auto" and image and self._supports(event, "Image"):
|
|
333
|
+
try:
|
|
334
|
+
await event.reply(image, method="Image")
|
|
335
|
+
return
|
|
336
|
+
except Exception as e:
|
|
337
|
+
self.logger.warning(f"图片发送失败,回退到文本: {e}")
|
|
338
|
+
|
|
339
|
+
method, content = self._select_text_format(event, templates)
|
|
340
|
+
try:
|
|
341
|
+
await event.reply(content, method=method)
|
|
342
|
+
except Exception:
|
|
343
|
+
await event.reply(templates["text"])
|
|
344
|
+
except Exception as e:
|
|
345
|
+
self.logger.error(f"发送帮助出错: {e}")
|
|
346
|
+
try:
|
|
347
|
+
await event.reply(templates["text"])
|
|
348
|
+
except Exception:
|
|
349
|
+
pass
|