koishi-plugin-rocom 1.0.1

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.
Files changed (157) hide show
  1. package/lib/client.d.ts +53 -0
  2. package/lib/client.js +473 -0
  3. package/lib/commands/account.d.ts +5 -0
  4. package/lib/commands/account.js +205 -0
  5. package/lib/commands/admin.d.ts +2 -0
  6. package/lib/commands/admin.js +117 -0
  7. package/lib/commands/egg.d.ts +2 -0
  8. package/lib/commands/egg.js +196 -0
  9. package/lib/commands/merchant.d.ts +2 -0
  10. package/lib/commands/merchant.js +242 -0
  11. package/lib/commands/query.d.ts +2 -0
  12. package/lib/commands/query.js +1264 -0
  13. package/lib/commands/wiki.d.ts +2 -0
  14. package/lib/commands/wiki.js +11 -0
  15. package/lib/egg-service.d.ts +229 -0
  16. package/lib/egg-service.js +705 -0
  17. package/lib/index.d.ts +24 -0
  18. package/lib/index.js +3746 -0
  19. package/lib/render-templates/bind-list/index.html +51 -0
  20. package/lib/render-templates/bind-list/style.css +178 -0
  21. package/lib/render-templates/exchange-hall/css/_@astro-renderers.0KDkAyVb.css +1 -0
  22. package/lib/render-templates/exchange-hall/css/index.B3tv56V6.css +1 -0
  23. package/lib/render-templates/exchange-hall/css/index.D2LGPudy.css +1 -0
  24. package/lib/render-templates/exchange-hall/extracted.css +393 -0
  25. package/lib/render-templates/exchange-hall/index.html +99 -0
  26. package/lib/render-templates/exchange-hall/style.css +267 -0
  27. package/lib/render-templates/friendship/index.html +58 -0
  28. package/lib/render-templates/friendship/style.css +182 -0
  29. package/lib/render-templates/home/data/home_item_list.json +122 -0
  30. package/lib/render-templates/home/img/home_icon/100604.png +0 -0
  31. package/lib/render-templates/home/img/home_icon/100604_1.png +0 -0
  32. package/lib/render-templates/home/img/home_icon/100604_2.png +0 -0
  33. package/lib/render-templates/home/img/home_icon/100605.png +0 -0
  34. package/lib/render-templates/home/img/home_icon/100605_1.png +0 -0
  35. package/lib/render-templates/home/img/home_icon/100605_2.png +0 -0
  36. package/lib/render-templates/home/img/home_icon/100606.png +0 -0
  37. package/lib/render-templates/home/img/home_icon/100606_1.png +0 -0
  38. package/lib/render-templates/home/img/home_icon/100606_2.png +0 -0
  39. package/lib/render-templates/home/img/home_icon/100607.png +0 -0
  40. package/lib/render-templates/home/img/home_icon/100607_1.png +0 -0
  41. package/lib/render-templates/home/img/home_icon/100607_2.png +0 -0
  42. package/lib/render-templates/home/img/home_icon/100608.png +0 -0
  43. package/lib/render-templates/home/img/home_icon/100608_1.png +0 -0
  44. package/lib/render-templates/home/img/home_icon/100608_2.png +0 -0
  45. package/lib/render-templates/home/img/home_icon/100622.png +0 -0
  46. package/lib/render-templates/home/img/home_icon/100622_1.png +0 -0
  47. package/lib/render-templates/home/img/home_icon/100622_2.png +0 -0
  48. package/lib/render-templates/home/img/home_icon/100623.png +0 -0
  49. package/lib/render-templates/home/img/home_icon/100623_1.png +0 -0
  50. package/lib/render-templates/home/img/home_icon/100623_2.png +0 -0
  51. package/lib/render-templates/home/img/home_icon/100624.png +0 -0
  52. package/lib/render-templates/home/img/home_icon/100624_1.png +0 -0
  53. package/lib/render-templates/home/img/home_icon/100624_2.png +0 -0
  54. package/lib/render-templates/home/img/home_icon/100627.png +0 -0
  55. package/lib/render-templates/home/img/home_icon/100627_1.png +0 -0
  56. package/lib/render-templates/home/img/home_icon/100627_2.png +0 -0
  57. package/lib/render-templates/home/img/home_icon/100684.png +0 -0
  58. package/lib/render-templates/home/img/home_icon/100684_1.png +0 -0
  59. package/lib/render-templates/home/img/home_icon/100684_2.png +0 -0
  60. package/lib/render-templates/home/img/home_icon/100686.png +0 -0
  61. package/lib/render-templates/home/img/home_icon/100686_1.png +0 -0
  62. package/lib/render-templates/home/img/home_icon/100686_2.png +0 -0
  63. package/lib/render-templates/home/img/home_icon/100687.png +0 -0
  64. package/lib/render-templates/home/img/home_icon/100687_1.png +0 -0
  65. package/lib/render-templates/home/img/home_icon/100687_2.png +0 -0
  66. package/lib/render-templates/home/img/home_icon/100689.png +0 -0
  67. package/lib/render-templates/home/img/home_icon/100689_1.png +0 -0
  68. package/lib/render-templates/home/img/home_icon/100689_2.png +0 -0
  69. package/lib/render-templates/home/img/home_icon/100690.png +0 -0
  70. package/lib/render-templates/home/img/home_icon/100690_1.png +0 -0
  71. package/lib/render-templates/home/img/home_icon/100690_2.png +0 -0
  72. package/lib/render-templates/home/img/home_icon/100691.png +0 -0
  73. package/lib/render-templates/home/img/home_icon/100691_1.png +0 -0
  74. package/lib/render-templates/home/img/home_icon/100691_2.png +0 -0
  75. package/lib/render-templates/home/img/home_icon/100692.png +0 -0
  76. package/lib/render-templates/home/img/home_icon/100692_1.png +0 -0
  77. package/lib/render-templates/home/img/home_icon/100692_2.png +0 -0
  78. package/lib/render-templates/home/img/home_icon/100693.png +0 -0
  79. package/lib/render-templates/home/img/home_icon/100693_1.png +0 -0
  80. package/lib/render-templates/home/img/home_icon/100693_2.png +0 -0
  81. package/lib/render-templates/home/img/home_icon/100694.png +0 -0
  82. package/lib/render-templates/home/img/home_icon/100694_1.png +0 -0
  83. package/lib/render-templates/home/img/home_icon/100694_2.png +0 -0
  84. package/lib/render-templates/home/img/home_icon/100706.png +0 -0
  85. package/lib/render-templates/home/img/home_icon/100706_1.png +0 -0
  86. package/lib/render-templates/home/img/home_icon/100706_2.png +0 -0
  87. package/lib/render-templates/home/img/home_icon/100751.png +0 -0
  88. package/lib/render-templates/home/img/home_icon/100751_1.png +0 -0
  89. package/lib/render-templates/home/img/home_icon/100751_2.png +0 -0
  90. package/lib/render-templates/home/img/home_icon/100755.png +0 -0
  91. package/lib/render-templates/home/img/home_icon/100755_1.png +0 -0
  92. package/lib/render-templates/home/img/home_icon/100755_2.png +0 -0
  93. package/lib/render-templates/home/img/home_icon/100762.png +0 -0
  94. package/lib/render-templates/home/img/home_icon/100762_1.png +0 -0
  95. package/lib/render-templates/home/img/home_icon/100762_2.png +0 -0
  96. package/lib/render-templates/home/img/home_icon/100764.png +0 -0
  97. package/lib/render-templates/home/img/home_icon/100764_1.png +0 -0
  98. package/lib/render-templates/home/img/home_icon/100764_2.png +0 -0
  99. package/lib/render-templates/home/img/home_icon/100869.png +0 -0
  100. package/lib/render-templates/home/img/home_icon/100869_1.png +0 -0
  101. package/lib/render-templates/home/img/home_icon/100869_2.png +0 -0
  102. package/lib/render-templates/home/img/img_HomeVisit_Icon1.png +0 -0
  103. package/lib/render-templates/home/img/img_LevelReward_Bg2.png +0 -0
  104. package/lib/render-templates/home/index.html +139 -0
  105. package/lib/render-templates/home/style.css +537 -0
  106. package/lib/render-templates/ingame-shop/index.html +87 -0
  107. package/lib/render-templates/ingame-shop/style.css +220 -0
  108. package/lib/render-templates/inspect/index.html +47 -0
  109. package/lib/render-templates/inspect/style.css +149 -0
  110. package/lib/render-templates/lineup/index.html +77 -0
  111. package/lib/render-templates/lineup/style.css +255 -0
  112. package/lib/render-templates/lineup-detail/index.html +63 -0
  113. package/lib/render-templates/lineup-detail/style.css +218 -0
  114. package/lib/render-templates/menu/index.html +36 -0
  115. package/lib/render-templates/menu/style.css +126 -0
  116. package/lib/render-templates/package/index.html +115 -0
  117. package/lib/render-templates/package/style.css +352 -0
  118. package/lib/render-templates/personal-card/index.html +292 -0
  119. package/lib/render-templates/personal-card/style.css +2114 -0
  120. package/lib/render-templates/pet-wiki/index.html +118 -0
  121. package/lib/render-templates/pet-wiki/style.css +382 -0
  122. package/lib/render-templates/player-search/index.html +60 -0
  123. package/lib/render-templates/player-search/style.css +192 -0
  124. package/lib/render-templates/record/index.html +86 -0
  125. package/lib/render-templates/record/style.css +322 -0
  126. package/lib/render-templates/searcheggs/Pets.json +104328 -0
  127. package/lib/render-templates/searcheggs/candidates.html +52 -0
  128. package/lib/render-templates/searcheggs/eggs.py +599 -0
  129. package/lib/render-templates/searcheggs/index.html +198 -0
  130. package/lib/render-templates/searcheggs/pair.html +81 -0
  131. package/lib/render-templates/searcheggs/size.html +82 -0
  132. package/lib/render-templates/searcheggs/style.css +586 -0
  133. package/lib/render-templates/searcheggs/want.html +63 -0
  134. package/lib/render-templates/skill-wiki/index.html +68 -0
  135. package/lib/render-templates/skill-wiki/style.css +182 -0
  136. package/lib/render-templates/student/index.html +95 -0
  137. package/lib/render-templates/student/style.css +255 -0
  138. package/lib/render-templates/student-perks/index.html +78 -0
  139. package/lib/render-templates/student-perks/style.css +238 -0
  140. package/lib/render-templates/student-state/index.html +52 -0
  141. package/lib/render-templates/student-state/style.css +157 -0
  142. package/lib/render-templates/yuanxing-shangren/index.html +371 -0
  143. package/lib/render-templates/yuanxing-shangren/style.css +371 -0
  144. package/lib/render.d.ts +11 -0
  145. package/lib/render.js +226 -0
  146. package/lib/role-token.d.ts +27 -0
  147. package/lib/role-token.js +137 -0
  148. package/lib/send-image.d.ts +3 -0
  149. package/lib/send-image.js +135 -0
  150. package/lib/subscription-send.d.ts +8 -0
  151. package/lib/subscription-send.js +48 -0
  152. package/lib/types.d.ts +32 -0
  153. package/lib/types.js +2 -0
  154. package/lib/user.d.ts +67 -0
  155. package/lib/user.js +176 -0
  156. package/package.json +58 -0
  157. package/readme.md +575 -0
@@ -0,0 +1,1264 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.register = register;
7
+ const koishi_1 = require("koishi");
8
+ const account_1 = require("./account");
9
+ const send_image_1 = require("../send-image");
10
+ const subscription_send_1 = require("../subscription-send");
11
+ const node_fs_1 = __importDefault(require("node:fs"));
12
+ const node_path_1 = __importDefault(require("node:path"));
13
+ const logger = new koishi_1.Logger('rocom-query');
14
+ async function sendImage(deps, session, templateName, data, fallback) {
15
+ const png = await deps.renderer.renderHtml(deps.ctx, templateName, data);
16
+ await (0, send_image_1.sendImageWithFallback)(session, png, fallback, `query:${templateName}`, deps.config);
17
+ }
18
+ function cleanPlayerFieldValue(field, value) {
19
+ const text = String(value ?? '').trim().replace(/^'+|'+$/g, '');
20
+ if (!text || ['<0B>', '<0b>', '<0B >', '<0b >'].includes(text))
21
+ return '未设置';
22
+ if (['is_online', 'online', 'chat_top_unlock', 'is_friend', 'is_black', 'is_black_role', 'is_chat_node_unlock'].includes(field)) {
23
+ return ['1', 'true', 'True', '是'].includes(text) ? '是' : '否';
24
+ }
25
+ if (['sex', 'gender'].includes(field)) {
26
+ return { '0': '未知', '1': '男', '2': '女' }[text] || text;
27
+ }
28
+ if (field === 'friend_type') {
29
+ return { '0': '默认', '1': '特殊' }[text] || text;
30
+ }
31
+ if (field === 'battle_state') {
32
+ return { '0': '空闲', '1': '对战中' }[text] || text;
33
+ }
34
+ return text;
35
+ }
36
+ function parseIngamePlayerPayload(payload, uid) {
37
+ const rows = payload?.rows || [];
38
+ const rowMap = {};
39
+ const labelMap = {};
40
+ for (const row of rows) {
41
+ const field = String(row.field || '');
42
+ if (!field)
43
+ continue;
44
+ rowMap[field] = String(row.value ?? '');
45
+ labelMap[field] = String(row.label || field);
46
+ }
47
+ const playerUid = cleanPlayerFieldValue('uin', rowMap.uin || uid);
48
+ const signature = cleanPlayerFieldValue('signature', rowMap.signature || '');
49
+ return {
50
+ title: String(payload?.title || '玩家搜索'),
51
+ nickname: cleanPlayerFieldValue('name', rowMap.name || '-'),
52
+ uid: playerUid,
53
+ level: cleanPlayerFieldValue('level', rowMap.level || '-'),
54
+ signature: signature === '未设置' ? '' : signature,
55
+ rowMap,
56
+ labelMap,
57
+ };
58
+ }
59
+ function playerField(parsed, field, defaultValue = '未设置') {
60
+ if (!parsed)
61
+ return defaultValue;
62
+ const raw = parsed.rowMap[field];
63
+ if (raw == null || raw === '')
64
+ return defaultValue;
65
+ const value = cleanPlayerFieldValue(field, raw);
66
+ return value && value !== '-' && value !== '未设置' ? value : defaultValue;
67
+ }
68
+ function tryParseJson(text) {
69
+ try {
70
+ return JSON.parse(text);
71
+ }
72
+ catch {
73
+ return null;
74
+ }
75
+ }
76
+ function extractTopLevelJsonSegments(text) {
77
+ const segments = [];
78
+ let start = -1;
79
+ let depth = 0;
80
+ let quote = '';
81
+ let escaped = false;
82
+ for (let i = 0; i < text.length; i++) {
83
+ const ch = text[i];
84
+ if (quote) {
85
+ if (escaped) {
86
+ escaped = false;
87
+ continue;
88
+ }
89
+ if (ch === '\\') {
90
+ escaped = true;
91
+ continue;
92
+ }
93
+ if (ch === quote)
94
+ quote = '';
95
+ continue;
96
+ }
97
+ if (ch === '"' || ch === '\'') {
98
+ quote = ch;
99
+ continue;
100
+ }
101
+ if (ch === '{' || ch === '[') {
102
+ if (depth === 0)
103
+ start = i;
104
+ depth++;
105
+ continue;
106
+ }
107
+ if (ch === '}' || ch === ']') {
108
+ if (depth <= 0)
109
+ continue;
110
+ depth--;
111
+ if (depth === 0 && start >= 0) {
112
+ segments.push(text.slice(start, i + 1));
113
+ start = -1;
114
+ }
115
+ }
116
+ }
117
+ return segments;
118
+ }
119
+ function collectExchangeItemNames(input, output) {
120
+ if (input == null)
121
+ return;
122
+ if (Array.isArray(input)) {
123
+ for (const item of input)
124
+ collectExchangeItemNames(item, output);
125
+ return;
126
+ }
127
+ if (typeof input === 'object') {
128
+ const record = input;
129
+ const preferredKeys = ['name', 'item_name', 'title', 'label', 'text', 'value'];
130
+ let consumedPreferred = false;
131
+ for (const key of preferredKeys) {
132
+ if (record[key] == null)
133
+ continue;
134
+ consumedPreferred = true;
135
+ collectExchangeItemNames(record[key], output);
136
+ }
137
+ if (!consumedPreferred) {
138
+ for (const value of Object.values(record))
139
+ collectExchangeItemNames(value, output);
140
+ }
141
+ return;
142
+ }
143
+ if (typeof input === 'string') {
144
+ const text = input.trim();
145
+ if (!text)
146
+ return;
147
+ const direct = tryParseJson(text);
148
+ if (direct != null) {
149
+ collectExchangeItemNames(direct, output);
150
+ return;
151
+ }
152
+ const unescaped = text.replace(/\\"/g, '"');
153
+ if (unescaped !== text) {
154
+ const escapedParsed = tryParseJson(unescaped);
155
+ if (escapedParsed != null) {
156
+ collectExchangeItemNames(escapedParsed, output);
157
+ return;
158
+ }
159
+ }
160
+ const chunks = extractTopLevelJsonSegments(text);
161
+ let parsedFromChunks = false;
162
+ for (const chunk of chunks) {
163
+ const parsed = tryParseJson(chunk);
164
+ if (parsed == null)
165
+ continue;
166
+ parsedFromChunks = true;
167
+ collectExchangeItemNames(parsed, output);
168
+ }
169
+ if (parsedFromChunks)
170
+ return;
171
+ const nameRegex = /["']name["']\s*:\s*["']([^"']+)["']/g;
172
+ let matched = false;
173
+ for (let match = nameRegex.exec(text); match; match = nameRegex.exec(text)) {
174
+ const candidate = match[1]?.trim();
175
+ if (!candidate)
176
+ continue;
177
+ output.add(candidate);
178
+ matched = true;
179
+ }
180
+ if (matched)
181
+ return;
182
+ output.add(text);
183
+ return;
184
+ }
185
+ const text = String(input).trim();
186
+ if (text)
187
+ output.add(text);
188
+ }
189
+ function parseExchangeItems(raw) {
190
+ const names = new Set();
191
+ collectExchangeItemNames(raw, names);
192
+ return [...names];
193
+ }
194
+ function parseExchangeWantText(raw) {
195
+ const names = parseExchangeItems(raw);
196
+ return names[0] || '交友';
197
+ }
198
+ function normalizeLineupLookupId(rawValue) {
199
+ const text = String(rawValue ?? '').trim();
200
+ if (!text)
201
+ return '';
202
+ const match = text.match(/\d+/);
203
+ return match ? match[0] : text;
204
+ }
205
+ function isTargetLineup(lineup, lineupId) {
206
+ const target = normalizeLineupLookupId(lineupId);
207
+ if (!target)
208
+ return false;
209
+ const candidates = new Set([
210
+ normalizeLineupLookupId(lineup?.id),
211
+ normalizeLineupLookupId(lineup?.code),
212
+ normalizeLineupLookupId(lineup?.lineup_code),
213
+ ]);
214
+ candidates.delete('');
215
+ return candidates.has(target);
216
+ }
217
+ function stringifyInspectValue(value) {
218
+ if (value === null || value === undefined)
219
+ return '-';
220
+ if (typeof value === 'boolean')
221
+ return value ? '是' : '否';
222
+ if (Array.isArray(value)) {
223
+ if (!value.length)
224
+ return '-';
225
+ if (value.every(item => item === null || typeof item !== 'object'))
226
+ return value.map(String).join('、');
227
+ return `共 ${value.length} 项`;
228
+ }
229
+ if (typeof value === 'object') {
230
+ const pairs = Object.entries(value).slice(0, 4).map(([key, item]) => `${key}: ${stringifyInspectValue(item)}`);
231
+ return pairs.length ? `${pairs.join(' | ')}${Object.keys(value).length > 4 ? ' | ...' : ''}` : '-';
232
+ }
233
+ return String(value);
234
+ }
235
+ function accountTypeText(accountType) {
236
+ return { 0: '自动', 1: 'QQ', 2: '微信' }[accountType] || String(accountType);
237
+ }
238
+ function normalizeEpochSeconds(value) {
239
+ const ts = Number(value);
240
+ if (!Number.isFinite(ts))
241
+ return 0;
242
+ if (ts > 10000000000000)
243
+ return Math.floor(ts / 1000000);
244
+ if (ts > 10000000000)
245
+ return Math.floor(ts / 1000);
246
+ return Math.floor(ts);
247
+ }
248
+ function normalizeDurationSeconds(value) {
249
+ const seconds = Number(value);
250
+ if (!Number.isFinite(seconds))
251
+ return 0;
252
+ if (seconds > 1000000000)
253
+ return Math.floor(seconds / 1000000);
254
+ if (seconds > 1000000)
255
+ return Math.floor(seconds / 1000);
256
+ return Math.floor(seconds);
257
+ }
258
+ function formatHomeRemaining(targetTs, nowTs = Math.floor(Date.now() / 1000)) {
259
+ if (!targetTs)
260
+ return '未开始';
261
+ const remain = Math.max(0, targetTs - nowTs);
262
+ if (remain <= 0)
263
+ return '已完成';
264
+ const hours = Math.floor(remain / 3600);
265
+ const minutes = Math.floor((remain % 3600) / 60);
266
+ if (hours >= 24)
267
+ return `${Math.floor(hours / 24)}天${hours % 24}小时`;
268
+ if (hours > 0)
269
+ return `${hours}小时${minutes}分钟`;
270
+ return `${minutes}分钟`;
271
+ }
272
+ function homeInfoPayload(res) {
273
+ const payload = res || {};
274
+ if (payload.result?.home_info)
275
+ return payload.result.home_info;
276
+ if (payload.home_info)
277
+ return payload.home_info;
278
+ if (payload.data?.result?.home_info)
279
+ return payload.data.result.home_info;
280
+ if (payload.data?.home_info)
281
+ return payload.data.home_info;
282
+ return payload && typeof payload === 'object' ? payload : {};
283
+ }
284
+ function homeBriefInfo(homeInfo) {
285
+ return homeInfo?.friend_home_brief_info || homeInfo?.home_brief_info || homeInfo || {};
286
+ }
287
+ function homeCellInfo(homeInfo) {
288
+ return homeInfo?.friend_cell_home_brief_info || homeInfo?.cell_home_brief_info || {};
289
+ }
290
+ function homePetIcon(petId, iconUrl = '') {
291
+ if (iconUrl)
292
+ return iconUrl;
293
+ let assetId = Number(String(petId || '0'));
294
+ if (!Number.isFinite(assetId) || assetId <= 0)
295
+ return '';
296
+ if (assetId < 3000)
297
+ assetId += 3000;
298
+ return `https://game.gtimg.cn/images/rocom/rocodata/jingling/${assetId}/icon.png`;
299
+ }
300
+ function extractHomePet(raw, index, guard = false) {
301
+ if (!raw || typeof raw !== 'object')
302
+ return null;
303
+ const homePet = raw.home_pet_info && typeof raw.home_pet_info === 'object' ? raw.home_pet_info : raw;
304
+ const display = raw.display_info && typeof raw.display_info === 'object' ? raw.display_info : {};
305
+ const petId = homePet.pet_cfg_id || homePet.pet_id || homePet.pet_base_id || raw.pet_cfg_id || raw.pet_id || raw.id;
306
+ if (['', '0'].includes(String(petId || '0')) && !guard)
307
+ return null;
308
+ const feedInfo = homePet.feed_info && typeof homePet.feed_info === 'object' ? homePet.feed_info : {};
309
+ const beginTime = normalizeEpochSeconds(feedInfo.begin_time);
310
+ const timeCost = normalizeDurationSeconds(feedInfo.time_cost);
311
+ let readyAt = normalizeEpochSeconds(homePet.pet_rip_time || raw.pet_rip_time || raw.rip_time);
312
+ if (!readyAt && beginTime && timeCost)
313
+ readyAt = beginTime + timeCost;
314
+ const nowTs = Math.floor(Date.now() / 1000);
315
+ const hasInspiration = Boolean(readyAt);
316
+ const inspireReady = hasInspiration && nowTs >= readyAt;
317
+ const isGuard = guard || Boolean(raw.is_guard || raw.guard) || ['2', 'guard', '守卫'].includes(String(raw.status).toLowerCase());
318
+ const statusText = isGuard && !hasInspiration ? '守卫中' : inspireReady ? '灵感已完成' : hasInspiration ? '灵感收集中' : '未喂食';
319
+ const statusClass = isGuard && !hasInspiration ? 'guard' : inspireReady ? 'ready' : hasInspiration ? 'progress' : 'idle';
320
+ return {
321
+ id: String(petId || ''),
322
+ pos: raw.pos || raw.position || index + 1,
323
+ name: String(homePet.name || homePet.pet_name || raw.name || raw.pet_name || `精灵 ${petId || ''}`),
324
+ level: display.level || raw.level || homePet.level || '--',
325
+ iconUrl: homePetIcon(petId, raw.icon_url || raw.pet_img_url || raw.petIcon || ''),
326
+ badge: isGuard ? '守' : '',
327
+ isGuard,
328
+ statusText,
329
+ statusClass,
330
+ note: hasInspiration ? formatHomeRemaining(readyAt, nowTs) : (isGuard ? '家园守卫位' : '暂无灵感倒计时'),
331
+ inspireReady,
332
+ readyAt,
333
+ };
334
+ }
335
+ function homePetSources(homeInfo) {
336
+ const cell = homeCellInfo(homeInfo);
337
+ const indoorSources = [];
338
+ const guardSources = [];
339
+ if (Array.isArray(homeInfo?.home_pets))
340
+ indoorSources.push(...homeInfo.home_pets);
341
+ if (Array.isArray(cell?.home_pets)) {
342
+ for (const pet of cell.home_pets) {
343
+ const homePet = pet?.home_pet_info || {};
344
+ if (String(homePet.pet_cfg_id || '0') === '0' && (homePet.name || homePet.pet_name))
345
+ guardSources.push(pet);
346
+ else
347
+ indoorSources.push(pet);
348
+ }
349
+ }
350
+ const petInfo = cell?.home_pet_info || {};
351
+ if (Array.isArray(petInfo.home_pet_list))
352
+ indoorSources.push(...petInfo.home_pet_list);
353
+ for (const key of ['guard_pets', 'home_guard_pets', 'guard_pet_list']) {
354
+ if (Array.isArray(homeInfo?.[key]))
355
+ guardSources.push(...homeInfo[key]);
356
+ if (Array.isArray(cell?.[key]))
357
+ guardSources.push(...cell[key]);
358
+ }
359
+ for (const key of ['guard_pet', 'home_guard_pet', 'guard_pet_info', 'home_guard_pet_info', 'defend_pet', 'defend_pet_info', 'protect_pet', 'protect_pet_info']) {
360
+ if (homeInfo?.[key] && typeof homeInfo[key] === 'object')
361
+ guardSources.push(homeInfo[key]);
362
+ if (cell?.[key] && typeof cell[key] === 'object')
363
+ guardSources.push(cell[key]);
364
+ }
365
+ return { indoorSources, guardSources };
366
+ }
367
+ function homePlantIcon(deps, iconId) {
368
+ if (!iconId)
369
+ return '';
370
+ const text = String(iconId);
371
+ if (/^(https?:|data:)/.test(text))
372
+ return text;
373
+ return deps.renderer.resourceUrl(`render-templates/home/img/home_icon/${text}_2.png`);
374
+ }
375
+ let homePlantMapCache = null;
376
+ function loadHomePlantMap() {
377
+ if (homePlantMapCache)
378
+ return homePlantMapCache;
379
+ const filePath = node_path_1.default.resolve(__dirname, '..', 'render-templates', 'home', 'data', 'home_item_list.json');
380
+ try {
381
+ const data = JSON.parse(node_fs_1.default.readFileSync(filePath, 'utf-8'));
382
+ homePlantMapCache = data && typeof data === 'object' ? data : {};
383
+ }
384
+ catch (err) {
385
+ logger.warn(`加载家园作物映射失败: ${err}`);
386
+ homePlantMapCache = {};
387
+ }
388
+ return homePlantMapCache;
389
+ }
390
+ function extractHomePlants(deps, homeInfo) {
391
+ const cell = homeCellInfo(homeInfo);
392
+ const plantSources = [];
393
+ const plantMap = loadHomePlantMap();
394
+ if (Array.isArray(homeInfo?.home_plants))
395
+ plantSources.push(...homeInfo.home_plants);
396
+ const plantInfo = cell?.home_plant_info || {};
397
+ for (const land of Array.isArray(plantInfo.home_plant_land_list) ? plantInfo.home_plant_land_list : []) {
398
+ for (const item of land.home_plant_list || [])
399
+ plantSources.push({ ...item, land_index: land.land_index });
400
+ }
401
+ const nowTs = Math.floor(Date.now() / 1000);
402
+ return plantSources.map((raw, index) => {
403
+ const plantData = raw.plant_info && typeof raw.plant_info === 'object' ? raw.plant_info : raw;
404
+ const plantId = raw.plant_seed_id || raw.plant_cfg_id || raw.plant_id || plantData.id;
405
+ if (['', '0'].includes(String(plantId || '0')))
406
+ return null;
407
+ const mappedPlant = plantMap[String(plantId)] || {};
408
+ const iconId = plantData.icon_url || plantData.iconUrl || raw.icon_url || raw.iconUrl || plantData.iconid || raw.iconid || raw.icon_id || mappedPlant.iconid;
409
+ let readyAt = normalizeEpochSeconds(raw.plant_rip_time || raw.rip_time || raw.end_time);
410
+ const leftTime = Number(raw.left_time || 0);
411
+ if (!readyAt && leftTime > 0)
412
+ readyAt = nowTs + leftTime;
413
+ const ready = Boolean(readyAt && nowTs >= readyAt) || ['2', 'ready', 'mature'].includes(String(raw.status));
414
+ const total = Number(raw.time_cost || raw.total_time || 0);
415
+ const progress = total && readyAt ? Math.max(0, Math.min(100, Math.floor(((total - Math.max(0, readyAt - nowTs)) / total) * 100))) : (ready ? 100 : 35);
416
+ const harvestNum = raw.plant_harvest_num;
417
+ const stealAccount = raw.plant_steal_account;
418
+ const canStealAccount = raw.plant_can_steal_account;
419
+ return {
420
+ id: String(plantId),
421
+ landIndex: raw.slot_index || raw.land_index || index + 1,
422
+ plantName: plantData.name || raw.name || mappedPlant.name || `种子 ${plantId}`,
423
+ iconUrl: homePlantIcon(deps, iconId),
424
+ stateType: ready ? 'ready' : 'warning',
425
+ statusText: ready ? '已成熟' : '成长中',
426
+ leftTimeText: ready ? '可收获' : formatHomeRemaining(readyAt, nowTs),
427
+ progress,
428
+ ready,
429
+ readyAt,
430
+ harvestText: harvestNum !== undefined && harvestNum !== '' ? `产量 ${harvestNum}` : '',
431
+ stealText: stealAccount !== undefined && canStealAccount !== undefined ? `可偷 ${stealAccount}/${canStealAccount}` : '',
432
+ };
433
+ }).filter(Boolean);
434
+ }
435
+ function buildHomeRenderData(deps, res, uid) {
436
+ const homeInfo = homeInfoPayload(res);
437
+ const brief = homeBriefInfo(homeInfo);
438
+ const { indoorSources, guardSources } = homePetSources(homeInfo);
439
+ const indoorPets = [];
440
+ const guardPets = [];
441
+ indoorSources.forEach((raw, index) => {
442
+ const item = extractHomePet(raw, index);
443
+ if (!item)
444
+ return;
445
+ if (item.isGuard)
446
+ guardPets.push(item);
447
+ else
448
+ indoorPets.push(item);
449
+ });
450
+ guardSources.forEach((raw, index) => {
451
+ const item = extractHomePet(raw, index, true);
452
+ if (item)
453
+ guardPets.push(item);
454
+ });
455
+ const gardenPlots = extractHomePlants(deps, homeInfo);
456
+ const createdAt = normalizeEpochSeconds(res?.meta?.created_at);
457
+ return {
458
+ title: '洛克家园',
459
+ subtitle: 'Home Information',
460
+ homeName: brief.home_name || brief.name || `${uid} 的小屋`,
461
+ uid,
462
+ summaryCards: [
463
+ { label: '房间等级', value: brief.room_level || '--' },
464
+ { label: '家园等级', value: brief.home_level || '--' },
465
+ { label: '家园经验', value: brief.home_experience || '--' },
466
+ { label: '舒适度', value: brief.home_comfort_level || '--' },
467
+ ],
468
+ gardenPlots,
469
+ guardPets,
470
+ indoorPets,
471
+ gardenCount: gardenPlots.length,
472
+ guardCount: guardPets.length,
473
+ indoorCount: indoorPets.length,
474
+ guardEmptyText: '后端当前返回中没有守卫精灵字段',
475
+ updatedAt: new Date(createdAt ? createdAt * 1000 : Date.now()).toLocaleString('zh-CN'),
476
+ };
477
+ }
478
+ function buildPlayerSearchRenderData(payload, uid) {
479
+ const parsed = parseIngamePlayerPayload(payload, uid);
480
+ const pack = (title, pairs) => {
481
+ const items = pairs
482
+ .filter(([, value]) => value && value !== '-' && value !== '未设置')
483
+ .map(([label, value]) => ({ label, value }));
484
+ return items.length ? { title, items } : null;
485
+ };
486
+ const sections = [
487
+ pack('核心档案', [
488
+ ['等级', parsed.level],
489
+ ['在线状态', playerField(parsed, 'online', playerField(parsed, 'is_online'))],
490
+ ['性别', playerField(parsed, 'gender', playerField(parsed, 'sex'))],
491
+ ['世界等级', playerField(parsed, 'world_level')],
492
+ ['图鉴收集', playerField(parsed, 'card_handbook_collect_num')],
493
+ ['最后离线', playerField(parsed, 'last_logout_time')],
494
+ ]),
495
+ pack('家园信息', [
496
+ ['家园名称', playerField(parsed, 'home_name')],
497
+ ['家园等级', playerField(parsed, 'home_level')],
498
+ ['家园经验', playerField(parsed, 'home_experience')],
499
+ ['舒适度', playerField(parsed, 'home_comfort_level')],
500
+ ['访客数量', playerField(parsed, 'visitor_num')],
501
+ ]),
502
+ pack('名片信息', [
503
+ ['名片皮肤', playerField(parsed, 'card_skin_selected')],
504
+ ['名片头像', playerField(parsed, 'card_icon_selected')],
505
+ ['首标签', playerField(parsed, 'card_label_first_selected')],
506
+ ['尾标签', playerField(parsed, 'card_label_last_selected')],
507
+ ]),
508
+ ].filter(Boolean);
509
+ const summaryCards = [
510
+ { label: '等级', value: parsed.level },
511
+ { label: '在线状态', value: playerField(parsed, 'online', playerField(parsed, 'is_online')) },
512
+ { label: '世界等级', value: playerField(parsed, 'world_level') },
513
+ { label: '图鉴收集', value: playerField(parsed, 'card_handbook_collect_num') },
514
+ { label: '家园等级', value: playerField(parsed, 'home_level') },
515
+ { label: '舒适度', value: playerField(parsed, 'home_comfort_level') },
516
+ ].filter(item => item.value && item.value !== '-');
517
+ const signature = parsed.signature && parsed.signature !== '未设置' ? parsed.signature : '';
518
+ return {
519
+ title: '洛克玩家',
520
+ subtitle: parsed.title,
521
+ heroTitle: '玩家信息',
522
+ heroValue: parsed.nickname,
523
+ heroSubvalue: `UID ${parsed.uid}`,
524
+ summaryCards,
525
+ signature,
526
+ showSignature: Boolean(signature),
527
+ sections,
528
+ commandHint: '洛克.玩家 <UID>',
529
+ copyright: 'Koishi & WeGame Locke Kingdom Plugin',
530
+ };
531
+ }
532
+ function buildShopRenderData(payload, shopId) {
533
+ const sections = [];
534
+ const detailItems = [];
535
+ const summaryCards = [{ label: '商店 ID', value: shopId }];
536
+ if (Array.isArray(payload?.rows)) {
537
+ detailItems.push(...payload.rows.filter((row) => Number(row.level || 0) === 0).map((row) => ({
538
+ label: row.label || row.field || '-',
539
+ value: stringifyInspectValue(row.value),
540
+ })));
541
+ }
542
+ else {
543
+ for (const [key, value] of Object.entries(payload || {})) {
544
+ if (Array.isArray(value)) {
545
+ summaryCards.push({ label: key, value: String(value.length) });
546
+ sections.push({
547
+ title: key.replace(/_/g, ' '),
548
+ cards: value.slice(0, 24).map((item, index) => ({
549
+ title: item?.name || item?.title || item?.item_name || `${key} #${index + 1}`,
550
+ image: item?.icon || item?.icon_url || item?.image || item?.image_url || '',
551
+ meta: Object.entries(item || {})
552
+ .filter(([metaKey, metaValue]) => !['name', 'title', 'item_name', 'icon', 'icon_url', 'image', 'image_url'].includes(metaKey) && (metaValue === null || typeof metaValue !== 'object'))
553
+ .slice(0, 6)
554
+ .map(([metaKey, metaValue]) => ({ label: metaKey.replace(/_/g, ' '), value: stringifyInspectValue(metaValue) })),
555
+ })),
556
+ });
557
+ }
558
+ else if (value === null || typeof value !== 'object') {
559
+ detailItems.push({ label: key.replace(/_/g, ' '), value: stringifyInspectValue(value) });
560
+ }
561
+ }
562
+ }
563
+ return {
564
+ title: '洛克商店',
565
+ subtitle: `shop_id = ${shopId}`,
566
+ heroTitle: '商店查询',
567
+ heroValue: detailItems.find(item => ['name', 'title', '名称', '标题'].includes(item.label))?.value || shopId,
568
+ heroSubvalue: `shop_id = ${shopId}`,
569
+ summaryCards: summaryCards.slice(0, 3),
570
+ sections,
571
+ detailItems: detailItems.slice(0, 18),
572
+ commandHint: '洛克.商店 <shop_id>',
573
+ copyright: 'Koishi & WeGame Locke Kingdom Plugin',
574
+ };
575
+ }
576
+ function buildFriendshipRenderData(payload, userIds) {
577
+ const result = payload?.result || {};
578
+ const users = payload?.user_list || payload?.userList || [];
579
+ const userCards = users.map((user, index) => {
580
+ const statusCode = user.status;
581
+ return {
582
+ title: `用户 ${index + 1}`,
583
+ userId: String(user.user_id || user.userId || '-'),
584
+ statusCode: stringifyInspectValue(statusCode),
585
+ statusText: String(statusCode) === '0' ? '状态正常' : `状态码 ${statusCode}`,
586
+ statusDesc: '接口已返回该用户状态,但后端当前没有提供更具体的关系类型说明。',
587
+ };
588
+ });
589
+ return {
590
+ title: '好友关系',
591
+ subtitle: `查询 ID:${userIds}`,
592
+ summaryCards: [
593
+ { label: '查询对象', value: String(userCards.length || userIds.split(',').length) },
594
+ { label: '接口状态', value: Number(result.error_code || 0) === 0 ? '成功' : '异常' },
595
+ { label: '上游返回', value: result.error_message || 'OK' },
596
+ ],
597
+ userCards,
598
+ resultCode: stringifyInspectValue(result.error_code || 0),
599
+ resultDesc: '当前接口只返回 status 字段,尚未提供“好友/非好友/黑名单”等可读关系类型。',
600
+ commandHint: '洛克.好友关系 <id1,id2>',
601
+ copyright: 'Koishi & WeGame Locke Kingdom Plugin',
602
+ };
603
+ }
604
+ function buildStudentRenderData(statePayload, perksPayload, area, accountType) {
605
+ const school = statePayload?.school || statePayload?.school_name || '未返回';
606
+ const certified = String(statePayload?.certified) === '1';
607
+ const cards = perksPayload?.cards || [];
608
+ return {
609
+ title: '洛克学生',
610
+ subtitle: `大区:${area} 账号类型:${accountTypeText(accountType)}`,
611
+ heroTitle: '学生信息总览',
612
+ heroValue: certified ? '已通过' : '未认证',
613
+ heroSubvalue: school,
614
+ summaryCards: [
615
+ { label: '认证状态', value: certified ? '已认证' : '未认证' },
616
+ { label: '学校', value: school },
617
+ { label: '奖励数量', value: String(cards.length) },
618
+ ],
619
+ stateItems: [
620
+ { label: '学生认证', value: certified ? '是' : '否' },
621
+ { label: '游戏内认证', value: String(statePayload?.game_certified) === '1' ? '是' : '否' },
622
+ { label: '学校', value: school },
623
+ { label: '上游状态', value: statePayload?.result?.error_message || 'WG_COMM_SUCC' },
624
+ { label: '上游错误码', value: stringifyInspectValue(statePayload?.result?.error_code || 0) },
625
+ ],
626
+ perkCards: cards.map((card) => ({
627
+ name: card.name || `奖励 #${card.id || '-'}`,
628
+ count: card.count || 0,
629
+ desc: card.desc || '暂无说明',
630
+ icon: card.icon || '',
631
+ id: stringifyInspectValue(card.id),
632
+ stateText: `状态码 ${stringifyInspectValue(card.state)}`,
633
+ })),
634
+ detailItems: Object.entries(perksPayload || {})
635
+ .filter(([key, value]) => !['cards', 'result'].includes(key) && (value === null || typeof value !== 'object'))
636
+ .map(([key, value]) => ({ label: key.replace(/_/g, ' '), value: stringifyInspectValue(value) })),
637
+ stateResult: statePayload?.result?.error_message || 'WG_COMM_SUCC',
638
+ perksResult: perksPayload?.result?.error_message || 'WG_COMM_SUCC',
639
+ commandHint: '洛克.学生 [area] [account_type]',
640
+ copyright: 'Koishi & WeGame Locke Kingdom Plugin',
641
+ };
642
+ }
643
+ function sessionTarget(session) {
644
+ return {
645
+ platform: session?.platform || session?.bot?.platform || '',
646
+ channelId: session?.channelId || session?.guildId || '',
647
+ userId: session?.guildId ? '' : (session?.userId || ''),
648
+ };
649
+ }
650
+ function homeSubscriptionKey(session, uid, kind) {
651
+ const target = sessionTarget(session);
652
+ return [target.platform, target.channelId || 'private', target.userId || session?.guildId || '', uid, kind].join(':');
653
+ }
654
+ function isBotAdmin(session, adminUserIds) {
655
+ return adminUserIds.includes(session?.userId || '');
656
+ }
657
+ async function resolveHomeUid(deps, session, uid = '') {
658
+ const targetUid = String(uid || '').trim();
659
+ if (targetUid)
660
+ return targetUid;
661
+ return String(deps.userMgr.getPrimaryBinding(session?.userId || '')?.role_id || '');
662
+ }
663
+ async function subscribeHome(deps, session, uid, kind) {
664
+ const target = sessionTarget(session);
665
+ if (!target.userId && !isBotAdmin(session, deps.config.adminUserIds))
666
+ return '此指令仅限管理员使用。';
667
+ const targetUid = await resolveHomeUid(deps, session, uid);
668
+ if (!targetUid)
669
+ return kind === 'garden' ? '请提供玩家 UID,或先完成绑定后再订阅家园菜园。' : '请提供玩家 UID,或先完成绑定后再订阅家园灵感。';
670
+ const key = homeSubscriptionKey(session, targetUid, kind);
671
+ deps.homeSubMgr.upsert(key, {
672
+ key,
673
+ kind,
674
+ uid: targetUid,
675
+ platform: target.platform,
676
+ channel_id: target.channelId,
677
+ guild_id: session?.guildId || '',
678
+ user_id: target.userId,
679
+ updated_by: session?.userId || '',
680
+ notify_state: {},
681
+ updated_at: Math.floor(Date.now() / 1000),
682
+ });
683
+ return kind === 'garden'
684
+ ? `已订阅 UID ${targetUid} 的家园菜园提醒:首个成熟和全部成熟时各推送一次。`
685
+ : `已订阅 UID ${targetUid} 的家园精灵灵感提醒:首个完成和全部完成时各推送一次。`;
686
+ }
687
+ function homeSubscriptionState(data, kind) {
688
+ if (kind === 'garden') {
689
+ const items = data.gardenPlots || [];
690
+ const readyItems = items.filter((item) => item.ready);
691
+ const names = readyItems.map((item) => `田地${item.landIndex} ${item.plantName}`);
692
+ return { items, readyItems, names };
693
+ }
694
+ const items = [...(data.indoorPets || []), ...(data.guardPets || [])].filter((item) => item.readyAt);
695
+ const readyItems = items.filter((item) => item.inspireReady);
696
+ const names = readyItems.map((item) => item.name || '未知精灵');
697
+ return { items, readyItems, names };
698
+ }
699
+ function homeSubscriptionMessage(uid, kind, level, totalCount, readyItems, names) {
700
+ const kindText = kind === 'garden' ? '菜园作物' : '精灵灵感';
701
+ const actionText = kind === 'garden' ? '成熟' : '完成';
702
+ const levelText = level === 'first' ? '首个' : '全部';
703
+ return [
704
+ `家园${kindText}${levelText}${actionText}提醒:${uid}`,
705
+ `进度:${readyItems.length}/${totalCount}`,
706
+ names.length ? `已完成:${names.slice(0, 8).join('、')}` : '',
707
+ ].filter(Boolean).join('\n');
708
+ }
709
+ async function checkHomeSubscriptions(deps) {
710
+ const subs = deps.homeSubMgr.getAll();
711
+ const cache = new Map();
712
+ let checkedCount = 0;
713
+ let pushedCount = 0;
714
+ for (const [key, sub] of Object.entries(subs)) {
715
+ if (!sub.uid || !['garden', 'inspiration'].includes(sub.kind))
716
+ continue;
717
+ checkedCount++;
718
+ if (!cache.has(sub.uid)) {
719
+ cache.set(sub.uid, await deps.client.ingameHomeInfo(deps.ctx, sub.uid));
720
+ }
721
+ const res = cache.get(sub.uid);
722
+ if (!res)
723
+ continue;
724
+ const data = buildHomeRenderData(deps, res, sub.uid);
725
+ const { items, readyItems, names } = homeSubscriptionState(data, sub.kind);
726
+ const totalCount = items.length;
727
+ if (totalCount <= 0)
728
+ continue;
729
+ const notifyState = sub.notify_state || {};
730
+ const pushLevels = [];
731
+ if (!readyItems.length) {
732
+ notifyState.first = false;
733
+ notifyState.all = false;
734
+ }
735
+ else {
736
+ if (!notifyState.first)
737
+ pushLevels.push('first');
738
+ if (readyItems.length >= totalCount && !notifyState.all)
739
+ pushLevels.push('all');
740
+ if (readyItems.length < totalCount)
741
+ notifyState.all = false;
742
+ }
743
+ if (!pushLevels.length) {
744
+ deps.homeSubMgr.upsert(key, { ...sub, notify_state: notifyState });
745
+ continue;
746
+ }
747
+ const messages = pushLevels.map(level => homeSubscriptionMessage(sub.uid, sub.kind, level, totalCount, readyItems, names));
748
+ try {
749
+ const sent = await (0, subscription_send_1.sendScheduledMessage)(deps.ctx, {
750
+ platform: sub.platform,
751
+ channelId: sub.channel_id || sub.guild_id || sub.user_id || '',
752
+ guildId: sub.guild_id || '',
753
+ userId: sub.user_id || '',
754
+ }, messages.join('\n\n'));
755
+ if (!sent)
756
+ continue;
757
+ }
758
+ catch (e) {
759
+ logger.warn(`家园订阅推送失败: ${e}`);
760
+ continue;
761
+ }
762
+ for (const level of pushLevels)
763
+ notifyState[level] = true;
764
+ pushedCount += pushLevels.length;
765
+ deps.homeSubMgr.upsert(key, { ...sub, notify_state: notifyState, last_push_time: Math.floor(Date.now() / 1000) });
766
+ }
767
+ return { subscriptions: Object.keys(subs).length, checked: checkedCount, pushed: pushedCount };
768
+ }
769
+ function register(deps) {
770
+ const { ctx, client } = deps;
771
+ ctx.command('洛克').subcommand('.档案', '查看个人档案')
772
+ .action(async ({ session }) => {
773
+ const fwToken = await (0, account_1.getPrimaryToken)(deps, session.userId);
774
+ if (!fwToken)
775
+ return (0, account_1.notLoggedInHint)();
776
+ const userIdentifier = session.userId;
777
+ await session.send('正在获取洛克王国数据...');
778
+ const [roleRes, evalRes, sumRes, collRes, boRes, blRes] = await Promise.all([
779
+ client.getRole(ctx, fwToken, undefined, userIdentifier),
780
+ client.getEvaluation(ctx, fwToken, userIdentifier),
781
+ client.getPetSummary(ctx, fwToken, userIdentifier),
782
+ client.getCollection(ctx, fwToken, userIdentifier),
783
+ client.getBattleOverview(ctx, fwToken, userIdentifier),
784
+ client.getBattleList(ctx, fwToken, 1, '', userIdentifier),
785
+ ]);
786
+ if (!roleRes?.role)
787
+ return '获取角色档案失败,凭据可能已过期,请重新登录。';
788
+ const role = roleRes.role;
789
+ const ev = evalRes || {};
790
+ const sm = sumRes || {};
791
+ const cl = collRes || {};
792
+ const bo = boRes || {};
793
+ const recentBattle = blRes?.battles?.[0];
794
+ const playerSearchRes = role?.id ? await client.ingamePlayerSearch(ctx, String(role.id)) : null;
795
+ const playerSearchData = parseIngamePlayerPayload(playerSearchRes, String(role.id || ''));
796
+ const profileSignature = playerSearchData?.signature || '';
797
+ const profileHeadTags = playerSearchData ? [
798
+ { label: '在线', value: playerField(playerSearchData, 'online', '未知') },
799
+ { label: '性别', value: playerField(playerSearchData, 'gender', playerField(playerSearchData, 'sex', '未知')) },
800
+ { label: '世界等级', value: playerField(playerSearchData, 'world_level') },
801
+ { label: '家园等级', value: playerField(playerSearchData, 'home_level') },
802
+ ].filter((item) => item.value && item.value !== '-' && item.value !== '未设置').slice(0, 4) : [];
803
+ const profileHomeItems = playerSearchData ? [
804
+ { label: '家园名称', value: playerField(playerSearchData, 'home_name') },
805
+ { label: '家园等级', value: playerField(playerSearchData, 'home_level') },
806
+ { label: '家园经验', value: playerField(playerSearchData, 'home_experience') },
807
+ { label: '舒适度', value: playerField(playerSearchData, 'home_comfort_level') },
808
+ { label: '访客数量', value: playerField(playerSearchData, 'visitor_num') },
809
+ ].filter((item) => item.value && item.value !== '-' && item.value !== '未设置') : [];
810
+ const profileCardItems = playerSearchData ? [
811
+ { label: '名片皮肤', value: playerField(playerSearchData, 'card_skin_selected') },
812
+ { label: '名片头像', value: playerField(playerSearchData, 'card_icon_selected') },
813
+ ].filter((item) => item.value && item.value !== '-' && item.value !== '未设置') : [];
814
+ const profileCardImage = playerSearchData ? playerField(playerSearchData, 'card_bussiness_card_url', '') : '';
815
+ const profileStatusText = playerSearchData ? playerField(playerSearchData, 'online', '未知') : '未知';
816
+ const hasExtraProfileData = Boolean(profileSignature || profileHomeItems.length || profileCardItems.length || profileCardImage);
817
+ let degraded = false;
818
+ if (!sm) {
819
+ logger.warn('[Rocom] 洛克.档案:pet-summary 接口不可用,已降级为基础档案渲染');
820
+ degraded = true;
821
+ }
822
+ if (!ev) {
823
+ logger.warn('[Rocom] 洛克.档案:evaluation 接口不可用,已降级为基础档案渲染');
824
+ degraded = true;
825
+ }
826
+ if (!cl) {
827
+ logger.warn('[Rocom] 洛克.档案:collection 接口不可用,已降级为基础档案渲染');
828
+ degraded = true;
829
+ }
830
+ if (!bo) {
831
+ logger.warn('[Rocom] 洛克.档案:battle-overview 接口不可用,已降级为基础档案渲染');
832
+ degraded = true;
833
+ }
834
+ if (degraded) {
835
+ try {
836
+ await session.send('AI评分接口暂不可用,已降级为基础档案渲染。');
837
+ }
838
+ catch {
839
+ // Ignore prompt delivery failure and continue rendering the card.
840
+ }
841
+ }
842
+ const radarData = {
843
+ strength: ev.strength || 0,
844
+ collection: ev.collection || 0,
845
+ capture: ev.capture || 0,
846
+ progression: ev.progression || 0,
847
+ };
848
+ const cx = 130;
849
+ const cy = 130;
850
+ const r = 90;
851
+ const dims = [
852
+ { label: '战力', value: radarData.strength, angle: -90 },
853
+ { label: '收藏', value: radarData.collection, angle: 0 },
854
+ { label: '捕捉', value: radarData.capture, angle: 90 },
855
+ { label: '推进', value: radarData.progression, angle: 180 },
856
+ ];
857
+ const toXY = (angle, radius) => {
858
+ const rad = angle * Math.PI / 180;
859
+ return { x: Math.round(cx + radius * Math.cos(rad)), y: Math.round(cy + radius * Math.sin(rad)) };
860
+ };
861
+ const radarPolygons = [1, 0.66, 0.33].map((scale) => {
862
+ const pts = dims.map(dim => toXY(dim.angle, r * scale));
863
+ return pts.map(point => `${point.x},${point.y}`).join(' ');
864
+ });
865
+ const radarAxes = dims.map(dim => toXY(dim.angle, r));
866
+ const radarAreaPoints = dims
867
+ .map(dim => toXY(dim.angle, r * Math.min(dim.value, 100) / 100))
868
+ .map(point => `${point.x},${point.y}`)
869
+ .join(' ');
870
+ const radarAxisLabels = dims.map(dim => {
871
+ const point = toXY(dim.angle, r + 28);
872
+ return { x: point.x, y: point.y, name: dim.label, anchor: 'middle' };
873
+ });
874
+ const radarValueBadges = dims.map(dim => {
875
+ const point = toXY(dim.angle, r + 14);
876
+ return { x: point.x - 20, y: point.y + 8, width: 40, value: dim.value };
877
+ });
878
+ const radarDots = dims.map(dim => toXY(dim.angle, r * Math.min(dim.value, 100) / 100));
879
+ const data = {
880
+ userName: role.name || '洛克',
881
+ userLevel: role.level || 1,
882
+ userUid: role.id || '',
883
+ userAvatarDisplay: role.avatar_url || '',
884
+ enrollDays: role.enroll_days || 0,
885
+ starName: role.star_name || '魔法学徒',
886
+ backgroundUrl: role.background_url || '',
887
+ hasAiProfileData: !!sm.best_pet_name,
888
+ bestPetName: sm.best_pet_name || '',
889
+ summaryTitleParts: String(sm.summary_title || '未 知').split(' '),
890
+ bestPetImageDisplay: sm.best_pet_img_url || '',
891
+ fallbackPetImage: '{{_res_path}}img/roco_icon.png',
892
+ scoreText: ev.score || '0.0',
893
+ aiCommentText: sm.summary_content || '暂无点评',
894
+ centerX: cx,
895
+ centerY: cy,
896
+ radarPolygons,
897
+ radarAxes,
898
+ radarAreaPoints,
899
+ radarAxisLabels,
900
+ radarValueBadges,
901
+ radarDots,
902
+ currentCollectionCount: cl.current_collection_count || 0,
903
+ totalCollectionCount: cl.total_collection_count || 0,
904
+ amazingSpriteCount: cl.amazing_sprite_count || 0,
905
+ shinySpriteCount: cl.shiny_sprite_count || 0,
906
+ colorfulSpriteCount: cl.colorful_sprite_count || 0,
907
+ fashionCollectionCount: cl.fashion_collection_count || 0,
908
+ itemCount: cl.item_count || 0,
909
+ collectionHint: '查看收藏详情',
910
+ hasBattleData: bo.total_match > 0,
911
+ tierBadgeUrl: bo.tier_icon_url || '',
912
+ winRate: `${bo.win_rate || 0}%`,
913
+ totalMatch: bo.total_match || 0,
914
+ matchResult: '',
915
+ leftTeamPets: [],
916
+ rightTeamPets: [],
917
+ opponentName: '',
918
+ opponentAvatarDisplay: '',
919
+ hasExtraProfileData,
920
+ profileSignature,
921
+ showProfileSignature: Boolean(profileSignature),
922
+ profileHeadTags,
923
+ profileHomeItems,
924
+ profileCardItems,
925
+ profileCardImage,
926
+ profileStatusText,
927
+ profileStatusClass: profileStatusText === '是' ? 'online' : 'offline',
928
+ commandHint: '洛克.背包 <筛选> <页码> | 洛克.战绩 <页码> | 洛克 查看菜单',
929
+ copyright: 'AstrBot & WeGame Locke Kingdom Plugin',
930
+ };
931
+ if (recentBattle) {
932
+ data.hasBattleData = true;
933
+ data.matchResult = recentBattle.result === 1 ? 'fail' : 'win';
934
+ data.opponentName = recentBattle.enemy_nickname || '';
935
+ data.opponentAvatarDisplay = recentBattle.enemy_avatar_url || '';
936
+ data.leftTeamPets = (recentBattle.pet_base_info || []).map((pet) => ({
937
+ icon: pet.pet_img_url?.replace('/image.png', '/icon.png') || '',
938
+ }));
939
+ data.rightTeamPets = (recentBattle.enemy_pet_base_info || []).map((pet) => ({
940
+ icon: pet.pet_img_url?.replace('/image.png', '/icon.png') || '',
941
+ }));
942
+ }
943
+ const fallback = `【${role.name}的档案】Lv.${role.level} UID:${role.id}\n评分:${ev.score || '0'} 收藏:${cl.current_collection_count || 0}/${cl.total_collection_count || 0}`;
944
+ await sendImage(deps, session, 'personal-card', data, fallback);
945
+ });
946
+ ctx.command('洛克').subcommand('.战绩 [page:number]', '查看对战战绩')
947
+ .action(async ({ session }, _page = 1) => {
948
+ const fwToken = await (0, account_1.getPrimaryToken)(deps, session.userId);
949
+ if (!fwToken)
950
+ return (0, account_1.notLoggedInHint)();
951
+ const userIdentifier = session.userId;
952
+ const [roleRes, boRes, blRes] = await Promise.all([
953
+ client.getRole(ctx, fwToken, undefined, userIdentifier),
954
+ client.getBattleOverview(ctx, fwToken, userIdentifier),
955
+ client.getBattleList(ctx, fwToken, 4, '', userIdentifier),
956
+ ]);
957
+ if (!roleRes?.role)
958
+ return '获取战绩数据失败。';
959
+ const role = roleRes.role;
960
+ const bo = boRes || {};
961
+ const battles = (blRes?.battles || []).map((battle) => {
962
+ const result = battle.result === 1 ? 'fail' : 'win';
963
+ const battleTime = battle.battle_time ? new Date(battle.battle_time) : null;
964
+ return {
965
+ time: battleTime ? battleTime.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }) : '?',
966
+ date: battleTime ? battleTime.toLocaleDateString('zh-CN') : '?',
967
+ result,
968
+ leftName: battle.nickname || '',
969
+ leftAvatar: battle.avatar_url || '',
970
+ leftPets: (battle.pet_base_info || []).map((pet) => ({
971
+ icon: pet.pet_img_url?.replace('/image.png', '/icon.png') || '',
972
+ })),
973
+ rightName: battle.enemy_nickname || '',
974
+ rightAvatar: battle.enemy_avatar_url || '',
975
+ rightPets: (battle.enemy_pet_base_info || []).map((pet) => ({
976
+ icon: pet.pet_img_url?.replace('/image.png', '/icon.png') || '',
977
+ })),
978
+ };
979
+ });
980
+ const data = {
981
+ userAvatarDisplay: role.avatar_url || '',
982
+ userName: role.name,
983
+ userLevel: role.level || 1,
984
+ userUid: role.id || '',
985
+ winRate: `${bo.win_rate || 0}%`,
986
+ totalMatch: bo.total_match || 0,
987
+ battles,
988
+ commandHint: '洛克.战绩 <页码>',
989
+ copyright: 'Koishi & WeGame 洛克王国插件',
990
+ };
991
+ await sendImage(deps, session, 'record', data, `【${role.name}的战绩】胜率:${bo.win_rate || 0}% 场次:${bo.total_match || 0}`);
992
+ });
993
+ ctx.command('洛克').subcommand('.背包 [arg1:string] [arg2:string]', '查看精灵背包')
994
+ .action(async ({ session }, arg1, arg2) => {
995
+ const fwToken = await (0, account_1.getPrimaryToken)(deps, session.userId);
996
+ if (!fwToken)
997
+ return (0, account_1.notLoggedInHint)();
998
+ const catMap = { '全部': 0, '了不起': 1, '异色': 2, '炫彩': 3 };
999
+ let category = '全部';
1000
+ let pageNo = 1;
1001
+ for (const arg of [arg1, arg2]) {
1002
+ if (!arg)
1003
+ continue;
1004
+ if (/^\d+$/.test(arg))
1005
+ pageNo = parseInt(arg);
1006
+ else if (arg in catMap)
1007
+ category = arg;
1008
+ else if (arg.replace('精灵', '') in catMap)
1009
+ category = arg.replace('精灵', '');
1010
+ }
1011
+ const userIdentifier = session.userId;
1012
+ const petSubset = catMap[category] ?? 0;
1013
+ const [roleRes, petRes] = await Promise.all([
1014
+ client.getRole(ctx, fwToken, undefined, userIdentifier),
1015
+ client.getPets(ctx, fwToken, petSubset, pageNo, 10, userIdentifier),
1016
+ ]);
1017
+ if (!roleRes?.role || !petRes?.pets)
1018
+ return '获取背包数据失败。';
1019
+ const role = roleRes.role;
1020
+ const data = {
1021
+ userName: role.name || '洛克',
1022
+ userLevel: role.level || 1,
1023
+ userUid: role.id || '',
1024
+ userAvatar: role.avatar_url || '',
1025
+ pageTitle: `背包 - ${category}精灵`,
1026
+ tabs: [
1027
+ { text: '全部精灵', active: category === '全部' },
1028
+ { text: '了不起精灵', active: category === '了不起' },
1029
+ { text: '异色精灵', active: category === '异色' },
1030
+ { text: '炫彩精灵', active: category === '炫彩' },
1031
+ ],
1032
+ currentTab: `${category}精灵`,
1033
+ totalCount: petRes.total || 0,
1034
+ accountLabel: role.name || '',
1035
+ pets: (petRes.pets || []).map((pet) => ({
1036
+ name: pet.pet_name?.split('&')[0] || '?',
1037
+ custom_name: pet.pet_name?.includes('&') ? pet.pet_name.split('&')[1] : undefined,
1038
+ level: pet.pet_level || 1,
1039
+ pet_img_url: pet.pet_img_url || '',
1040
+ elementIcons: (pet.pet_types_info || []).map((petType) => ({ src: petType.icon || '', name: petType.name || '' })),
1041
+ badgeImage: '',
1042
+ })),
1043
+ emptySlots: [],
1044
+ currentPage: pageNo,
1045
+ totalPages: Math.max(1, Math.ceil((petRes.total || 0) / 10)),
1046
+ pageSize: 10,
1047
+ commandHint: '洛克.背包 <全部/异色/了不起/炫彩> <页码>',
1048
+ fallbackPetImage: '',
1049
+ };
1050
+ await sendImage(deps, session, 'package', data, `【背包 - ${category}精灵】共${petRes.total || 0}只`);
1051
+ });
1052
+ ctx.command('洛克').subcommand('.阵容 [arg1:string] [arg2:string]', '查看阵容推荐')
1053
+ .action(async ({ session }, arg1, arg2) => {
1054
+ const fwToken = await (0, account_1.getPrimaryToken)(deps, session.userId);
1055
+ if (!fwToken)
1056
+ return (0, account_1.notLoggedInHint)();
1057
+ const userIdentifier = session.userId;
1058
+ let category = '';
1059
+ let pageNo = 1;
1060
+ for (const arg of [arg1, arg2]) {
1061
+ if (!arg)
1062
+ continue;
1063
+ if (/^\d+$/.test(arg))
1064
+ pageNo = parseInt(arg);
1065
+ else
1066
+ category = arg;
1067
+ }
1068
+ const res = await client.getLineupList(ctx, fwToken, pageNo, category, userIdentifier);
1069
+ if (!res?.lineups)
1070
+ return '获取阵容数据失败。';
1071
+ const data = {
1072
+ category: category || '热门推荐',
1073
+ lineups: (res.lineups || []).map((lineup) => ({
1074
+ name: lineup.name || '',
1075
+ tags: lineup.tags || [],
1076
+ likes: lineup.likes || 0,
1077
+ author_name: lineup.author_name || '?',
1078
+ lineup_code: String(lineup.id || ''),
1079
+ pets: (lineup.lineup?.pets || []).map((pet) => ({
1080
+ pet_name: pet.pet_name || '',
1081
+ pet_img_url: pet.pet_img_url || '',
1082
+ skills_info: (pet.skills_info || []).map((skill) => ({ skill_img_url: skill.skill_img_url || '' })),
1083
+ })),
1084
+ })),
1085
+ page_no: res.page_no || pageNo,
1086
+ total_pages: res.total_pages || 1,
1087
+ fallbackPetImage: '',
1088
+ commandHint: '洛克.阵容 <分类> <页码>',
1089
+ };
1090
+ await sendImage(deps, session, 'lineup', data, `【阵容推荐】${category || '热门'} 第${pageNo}页`);
1091
+ });
1092
+ ctx.command('查看阵容 <lineupId:string>', '查看阵容详情')
1093
+ .action(async ({ session }, lineupId) => {
1094
+ const normalizedLineupId = normalizeLineupLookupId(lineupId);
1095
+ if (!normalizedLineupId)
1096
+ return '请提供有效的阵容码。用法:查看阵容 <阵容码>';
1097
+ const fwToken = await (0, account_1.getPrimaryToken)(deps, session.userId);
1098
+ if (!fwToken)
1099
+ return (0, account_1.notLoggedInHint)();
1100
+ const userIdentifier = session.userId;
1101
+ const firstPageRes = await client.getLineupList(ctx, fwToken, 1, '', userIdentifier);
1102
+ if (!firstPageRes?.lineups)
1103
+ return '获取阵容数据失败。';
1104
+ let targetLineup = (firstPageRes.lineups || []).find((lineup) => isTargetLineup(lineup, normalizedLineupId));
1105
+ if (!targetLineup) {
1106
+ const totalPages = Math.max(1, Number(firstPageRes.total_pages) || 1);
1107
+ const maxSearchPage = Math.min(totalPages, 10);
1108
+ for (let page = 2; page <= maxSearchPage; page++) {
1109
+ const pageRes = await client.getLineupList(ctx, fwToken, page, '', userIdentifier);
1110
+ const lineups = pageRes?.lineups || [];
1111
+ targetLineup = lineups.find((lineup) => isTargetLineup(lineup, normalizedLineupId));
1112
+ if (targetLineup)
1113
+ break;
1114
+ }
1115
+ }
1116
+ if (!targetLineup)
1117
+ return `未找到阵容码为 ${normalizedLineupId} 的阵容。`;
1118
+ const lineupData = targetLineup.lineup || {};
1119
+ const processedPets = (lineupData.pets || []).map((pet) => ({
1120
+ pet_name: pet.pet_name || '',
1121
+ pet_img_url: pet.pet_img_url || '',
1122
+ skills: (pet.skills_info || []).map((skill) => skill.skill_img_url || '').filter(Boolean),
1123
+ bloodline: Boolean(pet.bloodline_info),
1124
+ bloodline_icon: pet.bloodline_info?.icon || '',
1125
+ }));
1126
+ const data = {
1127
+ lineup: {
1128
+ name: targetLineup.name || '',
1129
+ tags: targetLineup.tags || [],
1130
+ pets: processedPets,
1131
+ author_name: targetLineup.author_name || '',
1132
+ author_avatar: targetLineup.author_avatar || '',
1133
+ likes: targetLineup.likes || 0,
1134
+ lineup_code: normalizedLineupId,
1135
+ },
1136
+ fallbackPetImage: '{{_res_path}}img/roco_icon.png',
1137
+ };
1138
+ const fallback = `【阵容详情】${targetLineup.name || '未知阵容'} | 阵容码: ${normalizedLineupId}`;
1139
+ await sendImage(deps, session, 'lineup-detail', data, fallback);
1140
+ });
1141
+ ctx.command('洛克').subcommand('.交换大厅 [page:number]', '查看交换大厅')
1142
+ .action(async ({ session }, page = 1) => {
1143
+ const fwToken = await (0, account_1.getPrimaryToken)(deps, session.userId);
1144
+ if (!fwToken)
1145
+ return (0, account_1.notLoggedInHint)();
1146
+ const userIdentifier = session.userId;
1147
+ const res = await client.getExchangePosters(ctx, fwToken, page, userIdentifier);
1148
+ if (!res?.posters)
1149
+ return '获取交换大厅数据失败。';
1150
+ const data = {
1151
+ filterLabel: '全部',
1152
+ posts: (res.posters || []).map((poster) => {
1153
+ const user = poster.user_info || {};
1154
+ const provideItems = parseExchangeItems(poster.offer_items ?? poster.offer_item_names ?? poster.offerItems ?? []);
1155
+ return {
1156
+ userName: user.nickname || '?',
1157
+ userLevel: user.level || 0,
1158
+ isOnline: user.online_status === 1,
1159
+ avatarUrl: user.avatar_url || '',
1160
+ userId: user.role_id || '',
1161
+ wantText: parseExchangeWantText(poster.want_item_name ?? poster.want_item ?? poster.wantText ?? '交友'),
1162
+ wantBadgeUrl: '',
1163
+ isExpired: false,
1164
+ provideItems: provideItems.length > 0 ? provideItems : ['暂无'],
1165
+ timeLabel: poster.create_time
1166
+ ? new Date(Number(poster.create_time) * 1000).toLocaleString('zh-CN', {
1167
+ month: '2-digit',
1168
+ day: '2-digit',
1169
+ hour: '2-digit',
1170
+ minute: '2-digit',
1171
+ })
1172
+ : '?',
1173
+ };
1174
+ }),
1175
+ currentPage: page,
1176
+ totalPages: res.total_pages || 1,
1177
+ commandHint: '洛克.交换大厅 <页码>',
1178
+ };
1179
+ await sendImage(deps, session, 'exchange-hall', data, `【交换大厅】第${page}页`);
1180
+ });
1181
+ ctx.command('洛克').subcommand('.玩家 <uid:string>', '通过 ingame 接口查询玩家基础资料')
1182
+ .action(async ({ session }, uid) => {
1183
+ if (!uid)
1184
+ return '请提供玩家 UID。用法:洛克.玩家 <UID>';
1185
+ const res = await client.ingamePlayerSearch(ctx, uid);
1186
+ if (!res)
1187
+ return `玩家搜索失败:${client.getLastError()}`;
1188
+ await sendImage(deps, session, 'player-search', buildPlayerSearchRenderData(res, uid), `【洛克玩家】UID ${uid}`);
1189
+ });
1190
+ ctx.command('洛克').subcommand('.家园 [uid:string]', '通过 UID 查询家园菜园、守卫和室内精灵')
1191
+ .action(async ({ session }, uid = '') => {
1192
+ let targetUid = String(uid || '').trim();
1193
+ if (!targetUid) {
1194
+ const binding = deps.userMgr.getPrimaryBinding(session.userId);
1195
+ targetUid = String(binding?.role_id || '');
1196
+ }
1197
+ if (!targetUid)
1198
+ return '请提供玩家 UID,或先完成绑定后使用 洛克.家园。';
1199
+ const res = await client.ingameHomeInfo(ctx, targetUid);
1200
+ if (!res)
1201
+ return `家园查询失败:${client.getLastError()}`;
1202
+ await sendImage(deps, session, 'home', buildHomeRenderData(deps, res, targetUid), `【洛克家园】UID ${targetUid}`);
1203
+ });
1204
+ ctx.command('洛克').subcommand('.商店 <shopId:string>', '通过 ingame 接口查询商店信息')
1205
+ .action(async ({ session }, shopId) => {
1206
+ if (!shopId)
1207
+ return '请提供商店 ID。用法:洛克.商店 <shop_id>';
1208
+ const res = await client.ingameMerchantInfo(ctx, shopId);
1209
+ if (!res)
1210
+ return `商店查询失败:${client.getLastError()}`;
1211
+ await sendImage(deps, session, 'ingame-shop', buildShopRenderData(res, shopId), `【洛克商店】shop_id=${shopId}`);
1212
+ });
1213
+ ctx.command('洛克').subcommand('.好友关系 <userIds:string>', '查询好友关系')
1214
+ .action(async ({ session }, userIds) => {
1215
+ if (!userIds)
1216
+ return '请提供要查询的用户 ID 列表。用法:洛克.好友关系 <id1,id2>';
1217
+ const fwToken = await (0, account_1.getPrimaryToken)(deps, session.userId);
1218
+ if (!fwToken)
1219
+ return (0, account_1.notLoggedInHint)();
1220
+ const res = await client.getFriendship(ctx, fwToken, userIds, session.userId);
1221
+ if (!res)
1222
+ return `好友关系查询失败:${client.getLastError()}`;
1223
+ await sendImage(deps, session, 'friendship', buildFriendshipRenderData(res, userIds), `【好友关系】${userIds}`);
1224
+ });
1225
+ ctx.command('洛克').subcommand('.学生 [area:number] [accountType:number]', '查询学生认证状态与学生活动福利')
1226
+ .action(async ({ session }, area = 101, accountType = 0) => {
1227
+ const fwToken = await (0, account_1.getPrimaryToken)(deps, session.userId);
1228
+ if (!fwToken)
1229
+ return (0, account_1.notLoggedInHint)();
1230
+ const userIdentifier = session.userId;
1231
+ const [stateRes, perksRes] = await Promise.all([
1232
+ client.getStudentState(ctx, fwToken, accountType, userIdentifier),
1233
+ client.getStudentPerks(ctx, fwToken, area, accountType, userIdentifier),
1234
+ ]);
1235
+ if (!stateRes)
1236
+ return `学生认证状态查询失败:${client.getLastError()}`;
1237
+ if (!perksRes)
1238
+ return `学生活动福利查询失败:${client.getLastError()}`;
1239
+ await sendImage(deps, session, 'student', buildStudentRenderData(stateRes, perksRes, area, accountType), '【洛克学生】认证与福利信息');
1240
+ });
1241
+ ctx.command('订阅家园菜园 [uid:string]', '订阅指定 UID 的家园菜园成熟提醒')
1242
+ .action(async ({ session }, uid = '') => subscribeHome(deps, session, uid, 'garden'));
1243
+ ctx.command('订阅家园灵感 [uid:string]', '订阅指定 UID 的家园精灵灵感完成提醒')
1244
+ .action(async ({ session }, uid = '') => subscribeHome(deps, session, uid, 'inspiration'));
1245
+ ctx.command('取消订阅家园 [kind:string] [uid:string]', '取消当前会话的家园订阅')
1246
+ .action(async ({ session }, kind = '全部', uid = '') => {
1247
+ const target = sessionTarget(session);
1248
+ if (!target.userId && !isBotAdmin(session, deps.config.adminUserIds))
1249
+ return '此指令仅限管理员使用。';
1250
+ const kindMap = { '菜园': 'garden', '灵感': 'inspiration', '全部': '', all: '', garden: 'garden', inspiration: 'inspiration' };
1251
+ const deleted = deps.homeSubMgr.deleteMatching(target, kindMap[String(kind || '全部')] ?? '', String(uid || '').trim());
1252
+ return deleted ? `已取消 ${deleted} 条家园订阅。` : '当前会话没有匹配的家园订阅。';
1253
+ });
1254
+ ctx.command('洛克').subcommand('.调试家园订阅', '立即执行一次家园订阅检查')
1255
+ .action(async ({ session }) => {
1256
+ if (!isBotAdmin(session, deps.config.adminUserIds))
1257
+ return '此指令仅限管理员使用。';
1258
+ const result = await checkHomeSubscriptions(deps);
1259
+ return `家园订阅检查完成:订阅 ${result.subscriptions} 条,检查 ${result.checked} 条,推送 ${result.pushed} 档提醒。`;
1260
+ });
1261
+ if (deps.config.homeSubscriptionEnabled) {
1262
+ ctx.setInterval(() => checkHomeSubscriptions(deps).catch(err => logger.warn(`家园订阅检查失败: ${err}`)), Math.max(1, deps.config.homeSubscriptionIntervalMinutes || 5) * 60000);
1263
+ }
1264
+ }