koishi-plugin-rocom 1.0.18 → 1.0.19
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.
- package/lib/announcement-service.d.ts +21 -0
- package/lib/announcement-service.js +63 -0
- package/lib/async-control.d.ts +3 -0
- package/lib/async-control.js +48 -0
- package/lib/client.d.ts +48 -7
- package/lib/client.js +244 -125
- package/lib/commands/egg.js +66 -4
- package/lib/commands/merchant.js +144 -146
- package/lib/commands/query.js +148 -117
- package/lib/commands/ranking.d.ts +2 -0
- package/lib/commands/ranking.js +31 -0
- package/lib/commands/share-code.d.ts +2 -0
- package/lib/commands/share-code.js +66 -0
- package/lib/commands/tools.js +47 -59
- package/lib/egg-service.d.ts +82 -8
- package/lib/egg-service.js +235 -19
- package/lib/img//345/274/202/350/211/262.png +0 -0
- package/lib/img//347/202/253/345/275/251.png +0 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +16 -3
- package/lib/merchant-clock.d.ts +22 -0
- package/lib/merchant-clock.js +95 -0
- package/lib/merchant-service.d.ts +24 -0
- package/lib/merchant-service.js +148 -0
- package/lib/permissions.d.ts +16 -0
- package/lib/permissions.js +66 -0
- package/lib/pet-data-service.js +7 -9
- package/lib/pet-size.d.ts +13 -0
- package/lib/pet-size.js +54 -0
- package/lib/player-service.d.ts +37 -0
- package/lib/player-service.js +201 -0
- package/lib/ranking-service.d.ts +46 -0
- package/lib/ranking-service.js +137 -0
- package/lib/render-templates/announcement/detail.html +8 -0
- package/lib/render-templates/announcement/list.html +5 -0
- package/lib/render-templates/announcement/style.css +1 -0
- package/lib/render-templates/pet-ranking/index.html +99 -0
- package/lib/render-templates/pet-ranking/style.css +338 -0
- package/lib/render-templates/player-search/index.html +7 -0
- package/lib/render-templates/searcheggs/index.html +1 -1
- package/lib/render-templates/searcheggs/size.html +70 -68
- package/lib/render-templates/share-code-team/index.html +105 -0
- package/lib/render-templates/share-code-team/style.css +345 -0
- package/lib/render.d.ts +15 -2
- package/lib/render.js +120 -37
- package/lib/send-image.d.ts +1 -1
- package/lib/send-image.js +12 -1
- package/lib/share-code-service.d.ts +32 -0
- package/lib/share-code-service.js +184 -0
- package/lib/subscription-runner.d.ts +6 -0
- package/lib/subscription-runner.js +21 -0
- package/lib/subscription-send.d.ts +4 -2
- package/lib/subscription-send.js +45 -20
- package/lib/types.d.ts +5 -0
- package/lib/user.d.ts +5 -0
- package/package.json +4 -1
- package/readme.md +145 -124
package/lib/commands/query.js
CHANGED
|
@@ -9,63 +9,18 @@ const account_1 = require("./account");
|
|
|
9
9
|
const send_image_1 = require("../send-image");
|
|
10
10
|
const subscription_send_1 = require("../subscription-send");
|
|
11
11
|
const pet_data_service_1 = require("../pet-data-service");
|
|
12
|
+
const player_service_1 = require("../player-service");
|
|
13
|
+
const merchant_service_1 = require("../merchant-service");
|
|
14
|
+
const permissions_1 = require("../permissions");
|
|
15
|
+
const render_1 = require("../render");
|
|
16
|
+
const subscription_runner_1 = require("../subscription-runner");
|
|
12
17
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
13
18
|
const node_path_1 = __importDefault(require("node:path"));
|
|
14
19
|
const logger = new koishi_1.Logger('rocom-query');
|
|
15
|
-
async function sendImage(deps, session, templateName, data, fallback) {
|
|
16
|
-
const png = await deps.renderer.
|
|
20
|
+
async function sendImage(deps, session, templateName, data, fallback, options) {
|
|
21
|
+
const png = await deps.renderer.renderPages(deps.ctx, templateName, data, options);
|
|
17
22
|
await (0, send_image_1.sendImageWithFallback)(session, png, fallback, `query:${templateName}`, deps.config);
|
|
18
23
|
}
|
|
19
|
-
function cleanPlayerFieldValue(field, value) {
|
|
20
|
-
const text = String(value ?? '').trim().replace(/^'+|'+$/g, '');
|
|
21
|
-
if (!text || ['<0B>', '<0b>', '<0B >', '<0b >'].includes(text))
|
|
22
|
-
return '未设置';
|
|
23
|
-
if (['is_online', 'online', 'chat_top_unlock', 'is_friend', 'is_black', 'is_black_role', 'is_chat_node_unlock'].includes(field)) {
|
|
24
|
-
return ['1', 'true', 'True', '是'].includes(text) ? '是' : '否';
|
|
25
|
-
}
|
|
26
|
-
if (['sex', 'gender'].includes(field)) {
|
|
27
|
-
return { '0': '未知', '1': '男', '2': '女' }[text] || text;
|
|
28
|
-
}
|
|
29
|
-
if (field === 'friend_type') {
|
|
30
|
-
return { '0': '默认', '1': '特殊' }[text] || text;
|
|
31
|
-
}
|
|
32
|
-
if (field === 'battle_state') {
|
|
33
|
-
return { '0': '空闲', '1': '对战中' }[text] || text;
|
|
34
|
-
}
|
|
35
|
-
return text;
|
|
36
|
-
}
|
|
37
|
-
function parseIngamePlayerPayload(payload, uid) {
|
|
38
|
-
const rows = payload?.rows || [];
|
|
39
|
-
const rowMap = {};
|
|
40
|
-
const labelMap = {};
|
|
41
|
-
for (const row of rows) {
|
|
42
|
-
const field = String(row.field || '');
|
|
43
|
-
if (!field)
|
|
44
|
-
continue;
|
|
45
|
-
rowMap[field] = String(row.value ?? '');
|
|
46
|
-
labelMap[field] = String(row.label || field);
|
|
47
|
-
}
|
|
48
|
-
const playerUid = cleanPlayerFieldValue('uin', rowMap.uin || uid);
|
|
49
|
-
const signature = cleanPlayerFieldValue('signature', rowMap.signature || '');
|
|
50
|
-
return {
|
|
51
|
-
title: String(payload?.title || '玩家搜索'),
|
|
52
|
-
nickname: cleanPlayerFieldValue('name', rowMap.name || '-'),
|
|
53
|
-
uid: playerUid,
|
|
54
|
-
level: cleanPlayerFieldValue('level', rowMap.level || '-'),
|
|
55
|
-
signature: signature === '未设置' ? '' : signature,
|
|
56
|
-
rowMap,
|
|
57
|
-
labelMap,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
function playerField(parsed, field, defaultValue = '未设置') {
|
|
61
|
-
if (!parsed)
|
|
62
|
-
return defaultValue;
|
|
63
|
-
const raw = parsed.rowMap[field];
|
|
64
|
-
if (raw == null || raw === '')
|
|
65
|
-
return defaultValue;
|
|
66
|
-
const value = cleanPlayerFieldValue(field, raw);
|
|
67
|
-
return value && value !== '-' && value !== '未设置' ? value : defaultValue;
|
|
68
|
-
}
|
|
69
24
|
function tryParseJson(text) {
|
|
70
25
|
try {
|
|
71
26
|
return JSON.parse(text);
|
|
@@ -1112,8 +1067,8 @@ function buildHomeRenderData(deps, res, uid) {
|
|
|
1112
1067
|
updatedAt: new Date(createdAt ? createdAt * 1000 : Date.now()).toLocaleString('zh-CN'),
|
|
1113
1068
|
};
|
|
1114
1069
|
}
|
|
1115
|
-
function buildPlayerSearchRenderData(payload, uid) {
|
|
1116
|
-
const parsed = parseIngamePlayerPayload(payload, uid);
|
|
1070
|
+
function buildPlayerSearchRenderData(payload, uid, cardImageUrl = '') {
|
|
1071
|
+
const parsed = (0, player_service_1.parseIngamePlayerPayload)(payload, uid);
|
|
1117
1072
|
const pack = (title, pairs) => {
|
|
1118
1073
|
const items = pairs
|
|
1119
1074
|
.filter(([, value]) => value && value !== '-' && value !== '未设置')
|
|
@@ -1123,27 +1078,27 @@ function buildPlayerSearchRenderData(payload, uid) {
|
|
|
1123
1078
|
const sections = [
|
|
1124
1079
|
pack('核心档案', [
|
|
1125
1080
|
['等级', parsed.level],
|
|
1126
|
-
['在线状态', playerField(parsed, 'online', playerField(parsed, 'is_online'))],
|
|
1127
|
-
['性别', playerField(parsed, 'gender', playerField(parsed, 'sex'))],
|
|
1128
|
-
['世界等级', playerField(parsed, 'world_level')],
|
|
1129
|
-
['图鉴收集', playerField(parsed, 'card_handbook_collect_num')],
|
|
1130
|
-
['最后离线', playerField(parsed, 'last_logout_time')],
|
|
1081
|
+
['在线状态', (0, player_service_1.playerField)(parsed, 'online', (0, player_service_1.playerField)(parsed, 'is_online'))],
|
|
1082
|
+
['性别', (0, player_service_1.playerField)(parsed, 'gender', (0, player_service_1.playerField)(parsed, 'sex'))],
|
|
1083
|
+
['世界等级', (0, player_service_1.playerField)(parsed, 'world_level')],
|
|
1084
|
+
['图鉴收集', (0, player_service_1.playerField)(parsed, 'card_handbook_collect_num')],
|
|
1085
|
+
['最后离线', (0, player_service_1.playerField)(parsed, 'last_logout_time')],
|
|
1131
1086
|
]),
|
|
1132
1087
|
pack('家园信息', [
|
|
1133
|
-
['家园名称', playerField(parsed, 'home_name')],
|
|
1134
|
-
['家园等级', playerField(parsed, 'home_level')],
|
|
1135
|
-
['家园经验', playerField(parsed, 'home_experience')],
|
|
1136
|
-
['舒适度', playerField(parsed, 'home_comfort_level')],
|
|
1137
|
-
['访客数量', playerField(parsed, 'visitor_num')],
|
|
1088
|
+
['家园名称', (0, player_service_1.playerField)(parsed, 'home_name')],
|
|
1089
|
+
['家园等级', (0, player_service_1.playerField)(parsed, 'home_level')],
|
|
1090
|
+
['家园经验', (0, player_service_1.playerField)(parsed, 'home_experience')],
|
|
1091
|
+
['舒适度', (0, player_service_1.playerField)(parsed, 'home_comfort_level')],
|
|
1092
|
+
['访客数量', (0, player_service_1.playerField)(parsed, 'visitor_num')],
|
|
1138
1093
|
]),
|
|
1139
1094
|
].filter(Boolean);
|
|
1140
1095
|
const summaryCards = [
|
|
1141
1096
|
{ label: '等级', value: parsed.level },
|
|
1142
|
-
{ label: '在线状态', value: playerField(parsed, 'online', playerField(parsed, 'is_online')) },
|
|
1143
|
-
{ label: '世界等级', value: playerField(parsed, 'world_level') },
|
|
1144
|
-
{ label: '图鉴收集', value: playerField(parsed, 'card_handbook_collect_num') },
|
|
1145
|
-
{ label: '家园等级', value: playerField(parsed, 'home_level') },
|
|
1146
|
-
{ label: '舒适度', value: playerField(parsed, 'home_comfort_level') },
|
|
1097
|
+
{ label: '在线状态', value: (0, player_service_1.playerField)(parsed, 'online', (0, player_service_1.playerField)(parsed, 'is_online')) },
|
|
1098
|
+
{ label: '世界等级', value: (0, player_service_1.playerField)(parsed, 'world_level') },
|
|
1099
|
+
{ label: '图鉴收集', value: (0, player_service_1.playerField)(parsed, 'card_handbook_collect_num') },
|
|
1100
|
+
{ label: '家园等级', value: (0, player_service_1.playerField)(parsed, 'home_level') },
|
|
1101
|
+
{ label: '舒适度', value: (0, player_service_1.playerField)(parsed, 'home_comfort_level') },
|
|
1147
1102
|
].filter(item => item.value && item.value !== '-');
|
|
1148
1103
|
const signature = parsed.signature && parsed.signature !== '未设置' ? parsed.signature : '';
|
|
1149
1104
|
return {
|
|
@@ -1155,7 +1110,10 @@ function buildPlayerSearchRenderData(payload, uid) {
|
|
|
1155
1110
|
signature,
|
|
1156
1111
|
showSignature: Boolean(signature),
|
|
1157
1112
|
sections,
|
|
1158
|
-
|
|
1113
|
+
cardImageUrl: cardImageUrl || '',
|
|
1114
|
+
collectedShining: (0, player_service_1.playerField)(parsed, 'collected_shining_pet_count', '未知'),
|
|
1115
|
+
collectedGlass: (0, player_service_1.playerField)(parsed, 'collected_glass_pet_count', '未知'),
|
|
1116
|
+
commandHint: '洛克.玩家 [UID]',
|
|
1159
1117
|
copyright: 'Koishi & WeGame Locke Kingdom Plugin',
|
|
1160
1118
|
};
|
|
1161
1119
|
}
|
|
@@ -1163,7 +1121,27 @@ function buildShopRenderData(payload, shopId) {
|
|
|
1163
1121
|
const sections = [];
|
|
1164
1122
|
const detailItems = [];
|
|
1165
1123
|
const summaryCards = [{ label: '商店 ID', value: shopId }];
|
|
1166
|
-
|
|
1124
|
+
const shop = payload?.shop || {};
|
|
1125
|
+
if (Array.isArray(payload?.goods)) {
|
|
1126
|
+
const items = (0, merchant_service_1.normalizeLiveMerchant)(payload);
|
|
1127
|
+
summaryCards.push({ label: '商品数量', value: String(items.length) });
|
|
1128
|
+
const shopName = String(shop.name || shop.title || '').trim();
|
|
1129
|
+
if (shopName)
|
|
1130
|
+
summaryCards.push({ label: '商店名称', value: shopName });
|
|
1131
|
+
sections.push({
|
|
1132
|
+
title: '商品列表',
|
|
1133
|
+
cards: items.map((item) => ({
|
|
1134
|
+
title: item.name,
|
|
1135
|
+
image: item.icon,
|
|
1136
|
+
meta: [
|
|
1137
|
+
item.price !== null ? { label: '价格', value: String(item.price) } : null,
|
|
1138
|
+
item.limit !== null ? { label: '限购', value: String(item.limit) } : null,
|
|
1139
|
+
item.active ? null : { label: '状态', value: '非在售' },
|
|
1140
|
+
].filter(Boolean),
|
|
1141
|
+
})),
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
else if (Array.isArray(payload?.rows)) {
|
|
1167
1145
|
detailItems.push(...payload.rows.filter((row) => Number(row.level || 0) === 0).map((row) => ({
|
|
1168
1146
|
label: row.label || row.field || '-',
|
|
1169
1147
|
value: stringifyInspectValue(row.value),
|
|
@@ -1171,6 +1149,8 @@ function buildShopRenderData(payload, shopId) {
|
|
|
1171
1149
|
}
|
|
1172
1150
|
else {
|
|
1173
1151
|
for (const [key, value] of Object.entries(payload || {})) {
|
|
1152
|
+
if (['_source', 'goods', 'shop', 'meta', '_goods_mapping', 'goods_mapping'].includes(key))
|
|
1153
|
+
continue;
|
|
1174
1154
|
if (Array.isArray(value)) {
|
|
1175
1155
|
summaryCards.push({ label: key, value: String(value.length) });
|
|
1176
1156
|
sections.push({
|
|
@@ -1194,12 +1174,12 @@ function buildShopRenderData(payload, shopId) {
|
|
|
1194
1174
|
title: '洛克商店',
|
|
1195
1175
|
subtitle: `shop_id = ${shopId}`,
|
|
1196
1176
|
heroTitle: '商店查询',
|
|
1197
|
-
heroValue: detailItems.find(item => ['name', 'title', '名称', '标题'].includes(item.label))?.value || shopId,
|
|
1177
|
+
heroValue: String(shop.name || shop.title || '') || detailItems.find(item => ['name', 'title', '名称', '标题'].includes(item.label))?.value || shopId,
|
|
1198
1178
|
heroSubvalue: `shop_id = ${shopId}`,
|
|
1199
1179
|
summaryCards: summaryCards.slice(0, 3),
|
|
1200
1180
|
sections,
|
|
1201
1181
|
detailItems: detailItems.slice(0, 18),
|
|
1202
|
-
commandHint: '洛克.商店
|
|
1182
|
+
commandHint: '洛克.商店 [shop_id]',
|
|
1203
1183
|
copyright: 'Koishi & WeGame Locke Kingdom Plugin',
|
|
1204
1184
|
};
|
|
1205
1185
|
}
|
|
@@ -1274,7 +1254,7 @@ function sessionTarget(session) {
|
|
|
1274
1254
|
return {
|
|
1275
1255
|
platform: session?.platform || session?.bot?.platform || '',
|
|
1276
1256
|
channelId: session?.channelId || session?.guildId || '',
|
|
1277
|
-
userId: session
|
|
1257
|
+
userId: (0, permissions_1.isDirectSession)(session) ? (session?.userId || '') : '',
|
|
1278
1258
|
};
|
|
1279
1259
|
}
|
|
1280
1260
|
function homeSubscriptionKey(session, uid, kind) {
|
|
@@ -1284,6 +1264,9 @@ function homeSubscriptionKey(session, uid, kind) {
|
|
|
1284
1264
|
function isBotAdmin(session, adminUserIds) {
|
|
1285
1265
|
return adminUserIds.includes(session?.userId || '');
|
|
1286
1266
|
}
|
|
1267
|
+
function canManageGroupSubscription(deps, session) {
|
|
1268
|
+
return (0, permissions_1.canManageSubscription)(deps.config, session);
|
|
1269
|
+
}
|
|
1287
1270
|
async function resolveHomeUid(deps, session, uid = '') {
|
|
1288
1271
|
const targetUid = String(uid || '').trim();
|
|
1289
1272
|
if (targetUid)
|
|
@@ -1312,22 +1295,27 @@ const HOME_SUB_TEXT = {
|
|
|
1312
1295
|
};
|
|
1313
1296
|
async function subscribeHome(deps, session, uid, kind) {
|
|
1314
1297
|
const target = sessionTarget(session);
|
|
1315
|
-
if (!target.userId && !
|
|
1316
|
-
return '
|
|
1298
|
+
if (!target.userId && !canManageGroupSubscription(deps, session))
|
|
1299
|
+
return '此指令仅限群管理员或机器人管理员使用。';
|
|
1317
1300
|
const targetUid = await resolveHomeUid(deps, session, uid);
|
|
1318
1301
|
if (!targetUid)
|
|
1319
1302
|
return HOME_SUB_TEXT[kind].needUidHint;
|
|
1320
1303
|
const key = homeSubscriptionKey(session, targetUid, kind);
|
|
1304
|
+
const own = deps.userMgr.getUserBindings(session?.userId || '').find(binding => binding.role_id === targetUid);
|
|
1305
|
+
const existing = deps.homeSubMgr.getAll()[key];
|
|
1321
1306
|
deps.homeSubMgr.upsert(key, {
|
|
1322
1307
|
key,
|
|
1323
1308
|
kind,
|
|
1324
1309
|
uid: targetUid,
|
|
1310
|
+
notify_user_id: own ? session.userId : '',
|
|
1311
|
+
nickname: own?.nickname || '',
|
|
1325
1312
|
platform: target.platform,
|
|
1313
|
+
self_id: session?.selfId || session?.bot?.selfId || '',
|
|
1326
1314
|
channel_id: target.channelId,
|
|
1327
1315
|
guild_id: session?.guildId || '',
|
|
1328
1316
|
user_id: target.userId,
|
|
1329
1317
|
updated_by: session?.userId || '',
|
|
1330
|
-
notify_state: {},
|
|
1318
|
+
notify_state: existing?.notify_state || {},
|
|
1331
1319
|
updated_at: Math.floor(Date.now() / 1000),
|
|
1332
1320
|
});
|
|
1333
1321
|
return `已订阅 UID ${targetUid} ${HOME_SUB_TEXT[kind].subscribedHint}`;
|
|
@@ -1359,18 +1347,24 @@ function homeSubscriptionMessage(uid, kind, level, totalCount, readyItems, names
|
|
|
1359
1347
|
names.length ? `已完成:${names.slice(0, 8).join('、')}` : '',
|
|
1360
1348
|
].filter(Boolean).join('\n');
|
|
1361
1349
|
}
|
|
1362
|
-
async function checkHomeSubscriptions(deps) {
|
|
1350
|
+
async function checkHomeSubscriptions(deps, signal) {
|
|
1363
1351
|
const subs = deps.homeSubMgr.getAll();
|
|
1364
1352
|
const cache = new Map();
|
|
1365
1353
|
let checkedCount = 0;
|
|
1366
1354
|
let pushedCount = 0;
|
|
1367
1355
|
for (const [key, sub] of Object.entries(subs)) {
|
|
1356
|
+
if (signal?.aborted)
|
|
1357
|
+
break;
|
|
1368
1358
|
if (!sub.uid || !['garden', 'inspiration', 'egg'].includes(sub.kind))
|
|
1369
1359
|
continue;
|
|
1370
1360
|
checkedCount++;
|
|
1371
1361
|
if (!cache.has(sub.uid)) {
|
|
1372
|
-
cache.set(sub.uid, await deps.client.ingameHomeInfo(deps.ctx, sub.uid, { timeoutMs: 30000 }));
|
|
1362
|
+
cache.set(sub.uid, await deps.client.ingameHomeInfo(deps.ctx, sub.uid, { timeoutMs: 30000, signal }));
|
|
1373
1363
|
}
|
|
1364
|
+
if (signal?.aborted)
|
|
1365
|
+
break;
|
|
1366
|
+
if (JSON.stringify(deps.homeSubMgr.getAll()[key]) !== JSON.stringify(sub))
|
|
1367
|
+
continue;
|
|
1374
1368
|
const res = cache.get(sub.uid);
|
|
1375
1369
|
if (!res)
|
|
1376
1370
|
continue;
|
|
@@ -1379,7 +1373,7 @@ async function checkHomeSubscriptions(deps) {
|
|
|
1379
1373
|
const totalCount = items.length;
|
|
1380
1374
|
if (totalCount <= 0)
|
|
1381
1375
|
continue;
|
|
1382
|
-
const notifyState = sub.notify_state
|
|
1376
|
+
const notifyState = { ...sub.notify_state };
|
|
1383
1377
|
const pushLevels = [];
|
|
1384
1378
|
if (!readyItems.length) {
|
|
1385
1379
|
notifyState.first = false;
|
|
@@ -1397,15 +1391,19 @@ async function checkHomeSubscriptions(deps) {
|
|
|
1397
1391
|
deps.homeSubMgr.upsert(key, { ...sub, notify_state: notifyState });
|
|
1398
1392
|
continue;
|
|
1399
1393
|
}
|
|
1400
|
-
const
|
|
1394
|
+
const owned = deps.userMgr.getUserBindings(sub.updated_by).find(binding => binding.role_id === sub.uid);
|
|
1395
|
+
const label = owned?.nickname || sub.uid;
|
|
1396
|
+
const notifyUser = owned && sub.notify_user_id === sub.updated_by ? sub.notify_user_id : '';
|
|
1397
|
+
const messages = pushLevels.map(level => homeSubscriptionMessage(label, sub.kind, level, totalCount, readyItems, names));
|
|
1401
1398
|
try {
|
|
1402
1399
|
const sent = await (0, subscription_send_1.sendScheduledMessage)(deps.ctx, {
|
|
1403
1400
|
platform: sub.platform,
|
|
1404
1401
|
channelId: sub.channel_id || sub.guild_id || sub.user_id || '',
|
|
1405
1402
|
guildId: sub.guild_id || '',
|
|
1406
1403
|
userId: sub.user_id || '',
|
|
1407
|
-
|
|
1408
|
-
|
|
1404
|
+
selfId: sub.self_id || '',
|
|
1405
|
+
}, !sub.user_id && notifyUser ? [koishi_1.h.at(notifyUser), koishi_1.h.text('\n' + messages.join('\n\n'))] : messages.join('\n\n'), signal);
|
|
1406
|
+
if (!sent || signal?.aborted || JSON.stringify(deps.homeSubMgr.getAll()[key]) !== JSON.stringify(sub))
|
|
1409
1407
|
continue;
|
|
1410
1408
|
}
|
|
1411
1409
|
catch (e) {
|
|
@@ -1421,6 +1419,7 @@ async function checkHomeSubscriptions(deps) {
|
|
|
1421
1419
|
}
|
|
1422
1420
|
function register(deps) {
|
|
1423
1421
|
const { ctx, client } = deps;
|
|
1422
|
+
const homeRunner = (0, subscription_runner_1.createRunner)(ctx);
|
|
1424
1423
|
ctx.command('洛克').subcommand('.档案', '查看个人档案')
|
|
1425
1424
|
.alias('洛克档案')
|
|
1426
1425
|
.action(async ({ session }) => {
|
|
@@ -1444,28 +1443,30 @@ function register(deps) {
|
|
|
1444
1443
|
const cl = collRes || {};
|
|
1445
1444
|
const bo = boRes || {};
|
|
1446
1445
|
const recentBattle = blRes?.battles?.[0];
|
|
1447
|
-
const
|
|
1448
|
-
const
|
|
1446
|
+
const auth = { fwToken, userIdentifier };
|
|
1447
|
+
const playerSearchRes = role?.id ? await client.ingamePlayerSearch(ctx, String(role.id), { auth }) : null;
|
|
1448
|
+
const playerCardRes = role?.id ? await client.ingamePlayerCard(ctx, String(role.id), { auth }).catch(() => null) : null;
|
|
1449
|
+
const playerSearchData = (0, player_service_1.parseIngamePlayerPayload)((0, player_service_1.mergePlayerPayloads)(playerSearchRes, playerCardRes), String(role.id || ''));
|
|
1449
1450
|
const profileSignature = playerSearchData?.signature || '';
|
|
1450
1451
|
const profileHeadTags = playerSearchData ? [
|
|
1451
|
-
{ label: '在线', value: playerField(playerSearchData, 'online', '未知') },
|
|
1452
|
-
{ label: '性别', value: playerField(playerSearchData, 'gender', playerField(playerSearchData, 'sex', '未知')) },
|
|
1453
|
-
{ label: '世界等级', value: playerField(playerSearchData, 'world_level') },
|
|
1454
|
-
{ label: '家园等级', value: playerField(playerSearchData, 'home_level') },
|
|
1452
|
+
{ label: '在线', value: (0, player_service_1.playerField)(playerSearchData, 'online', '未知') },
|
|
1453
|
+
{ label: '性别', value: (0, player_service_1.playerField)(playerSearchData, 'gender', (0, player_service_1.playerField)(playerSearchData, 'sex', '未知')) },
|
|
1454
|
+
{ label: '世界等级', value: (0, player_service_1.playerField)(playerSearchData, 'world_level') },
|
|
1455
|
+
{ label: '家园等级', value: (0, player_service_1.playerField)(playerSearchData, 'home_level') },
|
|
1455
1456
|
].filter((item) => item.value && item.value !== '-' && item.value !== '未设置').slice(0, 4) : [];
|
|
1456
1457
|
const profileHomeItems = playerSearchData ? [
|
|
1457
|
-
{ label: '家园名称', value: playerField(playerSearchData, 'home_name') },
|
|
1458
|
-
{ label: '家园等级', value: playerField(playerSearchData, 'home_level') },
|
|
1459
|
-
{ label: '家园经验', value: playerField(playerSearchData, 'home_experience') },
|
|
1460
|
-
{ label: '舒适度', value: playerField(playerSearchData, 'home_comfort_level') },
|
|
1461
|
-
{ label: '访客数量', value: playerField(playerSearchData, 'visitor_num') },
|
|
1458
|
+
{ label: '家园名称', value: (0, player_service_1.playerField)(playerSearchData, 'home_name') },
|
|
1459
|
+
{ label: '家园等级', value: (0, player_service_1.playerField)(playerSearchData, 'home_level') },
|
|
1460
|
+
{ label: '家园经验', value: (0, player_service_1.playerField)(playerSearchData, 'home_experience') },
|
|
1461
|
+
{ label: '舒适度', value: (0, player_service_1.playerField)(playerSearchData, 'home_comfort_level') },
|
|
1462
|
+
{ label: '访客数量', value: (0, player_service_1.playerField)(playerSearchData, 'visitor_num') },
|
|
1462
1463
|
].filter((item) => item.value && item.value !== '-' && item.value !== '未设置') : [];
|
|
1463
1464
|
const profileCardItems = playerSearchData ? [
|
|
1464
|
-
{ label: '名片皮肤', value: playerField(playerSearchData, 'card_skin_selected') },
|
|
1465
|
-
{ label: '名片头像', value: playerField(playerSearchData, 'card_icon_selected') },
|
|
1465
|
+
{ label: '名片皮肤', value: (0, player_service_1.playerField)(playerSearchData, 'card_skin_selected') },
|
|
1466
|
+
{ label: '名片头像', value: (0, player_service_1.playerField)(playerSearchData, 'card_icon_selected') },
|
|
1466
1467
|
].filter((item) => item.value && item.value !== '-' && item.value !== '未设置') : [];
|
|
1467
|
-
const profileCardImage = playerSearchData ? playerField(playerSearchData, 'card_bussiness_card_url', '') : '';
|
|
1468
|
-
const profileStatusText = playerSearchData ? playerField(playerSearchData, 'online', '未知') : '未知';
|
|
1468
|
+
const profileCardImage = playerSearchData ? (0, player_service_1.resourceUrl)((0, player_service_1.playerField)(playerSearchData, 'card_bussiness_card_url', ''), deps.config.apiBaseUrl) : '';
|
|
1469
|
+
const profileStatusText = playerSearchData ? (0, player_service_1.playerField)(playerSearchData, 'online', '未知') : '未知';
|
|
1469
1470
|
const hasExtraProfileData = Boolean(profileSignature || profileHomeItems.length || profileCardItems.length || profileCardImage);
|
|
1470
1471
|
let degraded = false;
|
|
1471
1472
|
if (!sm) {
|
|
@@ -1838,15 +1839,33 @@ function register(deps) {
|
|
|
1838
1839
|
};
|
|
1839
1840
|
await sendImage(deps, session, 'exchange-hall', data, `【交换大厅】第${page}页`);
|
|
1840
1841
|
});
|
|
1841
|
-
ctx.command('洛克').subcommand('.玩家
|
|
1842
|
+
ctx.command('洛克').subcommand('.玩家 [uid:string]', '通过 ingame 接口查询玩家资料')
|
|
1842
1843
|
.alias('洛克玩家')
|
|
1843
|
-
.action(async ({ session }, uid) => {
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
const
|
|
1847
|
-
|
|
1844
|
+
.action(async ({ session }, uid = '') => {
|
|
1845
|
+
const userId = session.userId;
|
|
1846
|
+
const explicitUid = String(uid || '').trim();
|
|
1847
|
+
const binding = deps.userMgr.getPrimaryBinding(userId);
|
|
1848
|
+
const targetUid = explicitUid || String(binding?.role_id || '');
|
|
1849
|
+
const fwToken = await (0, account_1.getPrimaryToken)(deps, userId);
|
|
1850
|
+
if (!targetUid && !fwToken) {
|
|
1851
|
+
return '请提供玩家 UID,或先使用“洛克.QQ登录 / 洛克.微信登录”完成绑定。用法:洛克.玩家 [UID]';
|
|
1852
|
+
}
|
|
1853
|
+
const auth = { fwToken, userIdentifier: userId };
|
|
1854
|
+
const search = await client.ingamePlayerSearch(ctx, targetUid, { auth });
|
|
1855
|
+
if (!search)
|
|
1848
1856
|
return `玩家搜索失败:${client.getLastErrorBrief()}`;
|
|
1849
|
-
|
|
1857
|
+
// 显式 UID 与搜索返回 UID 不一致时不合并名片,避免混用两个角色数据。
|
|
1858
|
+
const returnedUid = String(search?.player_info?.uin ?? search?.player_info?.uid ?? search?.uin ?? search?.uid ?? '');
|
|
1859
|
+
const uidConsistent = !explicitUid || !returnedUid || returnedUid === explicitUid;
|
|
1860
|
+
let card = uidConsistent
|
|
1861
|
+
? await client.ingamePlayerCard(ctx, targetUid, { auth }).catch(() => null)
|
|
1862
|
+
: null;
|
|
1863
|
+
const cardUid = (0, player_service_1.playerPayloadUid)(card);
|
|
1864
|
+
if (cardUid && (targetUid || returnedUid) && cardUid !== (targetUid || returnedUid))
|
|
1865
|
+
card = null;
|
|
1866
|
+
const view = (0, player_service_1.buildPlayerView)(search, card, targetUid, deps.config.apiBaseUrl);
|
|
1867
|
+
const merged = (0, player_service_1.mergePlayerPayloads)(search, card);
|
|
1868
|
+
await sendImage(deps, session, 'player-search', buildPlayerSearchRenderData(merged, targetUid || view.parsed.uid, view.cardImageUrl), (0, player_service_1.buildPlayerText)(merged, targetUid || view.parsed.uid));
|
|
1850
1869
|
});
|
|
1851
1870
|
ctx.command('洛克').subcommand('.家园 [uid:string]', '通过 UID 查询家园菜园、守卫和室内精灵')
|
|
1852
1871
|
.alias('洛克家园')
|
|
@@ -1856,10 +1875,12 @@ function register(deps) {
|
|
|
1856
1875
|
const binding = deps.userMgr.getPrimaryBinding(session.userId);
|
|
1857
1876
|
targetUid = String(binding?.role_id || '');
|
|
1858
1877
|
}
|
|
1859
|
-
|
|
1878
|
+
const fwToken = await (0, account_1.getPrimaryToken)(deps, session.userId);
|
|
1879
|
+
if (!targetUid && !fwToken)
|
|
1860
1880
|
return '请提供玩家 UID,或先完成绑定后使用 洛克.家园。';
|
|
1861
1881
|
let queuedNotified = false;
|
|
1862
1882
|
const res = await client.ingameHomeInfo(ctx, targetUid, {
|
|
1883
|
+
auth: { fwToken, userIdentifier: session.userId },
|
|
1863
1884
|
waitMs: deps.config.homeQueryWaitMs,
|
|
1864
1885
|
intervalMs: deps.config.homeQueryPollIntervalMs,
|
|
1865
1886
|
timeoutMs: deps.config.homeQueryTimeoutMs,
|
|
@@ -1958,7 +1979,10 @@ function register(deps) {
|
|
|
1958
1979
|
fallbackLines.push('');
|
|
1959
1980
|
fallbackLines.push('若服务器带宽过小导致生成超时,请在配置项打开低带宽模式。');
|
|
1960
1981
|
fallbackLines.push('低带宽模式开启后,家园详情将不再加载技能图标。');
|
|
1961
|
-
await sendImage(deps, session, 'pet-data', data, fallbackLines.join('\n')
|
|
1982
|
+
await sendImage(deps, session, 'pet-data', data, fallbackLines.join('\n'), {
|
|
1983
|
+
...(lowBandwidth ? render_1.lowBandwidthRenderOptions : {}),
|
|
1984
|
+
timeoutMs: deps.config.renderTimeout || 30000,
|
|
1985
|
+
});
|
|
1962
1986
|
});
|
|
1963
1987
|
ctx.command('洛克').subcommand('.刷新面板 [uid:string]', '刷新指定 UID 的精灵面板缓存')
|
|
1964
1988
|
.alias('洛克刷新面板')
|
|
@@ -2041,15 +2065,16 @@ function register(deps) {
|
|
|
2041
2065
|
const fallback = panelPetDetailFallback(targetUid, selected, updatedAtText);
|
|
2042
2066
|
await sendImage(deps, session, 'pet-panel-detail', data, fallback);
|
|
2043
2067
|
});
|
|
2044
|
-
ctx.command('洛克').subcommand('.商店
|
|
2068
|
+
ctx.command('洛克').subcommand('.商店 [shopId:string]', '通过 ingame 接口查询商店信息,默认商店 3009')
|
|
2045
2069
|
.alias('洛克商店')
|
|
2046
|
-
.action(async ({ session }, shopId) => {
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2070
|
+
.action(async ({ session }, shopId = '3009') => {
|
|
2071
|
+
const targetShopId = String(shopId || '3009').trim() || '3009';
|
|
2072
|
+
if (!/^\d+$/.test(targetShopId))
|
|
2073
|
+
return '商店 ID 必须是数字。用法:洛克.商店 [shop_id],默认 3009';
|
|
2074
|
+
const res = await client.ingameMerchantInfo(ctx, targetShopId);
|
|
2050
2075
|
if (!res)
|
|
2051
2076
|
return `商店查询失败:${client.getLastErrorBrief()}`;
|
|
2052
|
-
await sendImage(deps, session, 'ingame-shop', buildShopRenderData(res,
|
|
2077
|
+
await sendImage(deps, session, 'ingame-shop', buildShopRenderData(res, targetShopId), `【洛克商店】shop_id=${targetShopId}`);
|
|
2053
2078
|
});
|
|
2054
2079
|
ctx.command('洛克').subcommand('.好友关系 <userIds:string>', '查询好友关系')
|
|
2055
2080
|
.alias('洛克好友关系')
|
|
@@ -2065,16 +2090,20 @@ function register(deps) {
|
|
|
2065
2090
|
await sendImage(deps, session, 'friendship', buildFriendshipRenderData(res, userIds), `【好友关系】${userIds}`);
|
|
2066
2091
|
});
|
|
2067
2092
|
ctx.command('订阅家园菜园 [uid:string]', '订阅指定 UID 的家园菜园成熟提醒')
|
|
2093
|
+
.userFields(['authority'])
|
|
2068
2094
|
.action(async ({ session }, uid = '') => subscribeHome(deps, session, uid, 'garden'));
|
|
2069
2095
|
ctx.command('订阅家园灵感 [uid:string]', '订阅指定 UID 的家园精灵灵感完成提醒')
|
|
2096
|
+
.userFields(['authority'])
|
|
2070
2097
|
.action(async ({ session }, uid = '') => subscribeHome(deps, session, uid, 'inspiration'));
|
|
2071
2098
|
ctx.command('订阅家园生蛋 [uid:string]', '订阅指定 UID 的家园精灵生蛋提醒')
|
|
2099
|
+
.userFields(['authority'])
|
|
2072
2100
|
.action(async ({ session }, uid = '') => subscribeHome(deps, session, uid, 'egg'));
|
|
2073
2101
|
ctx.command('取消订阅家园 [kind:string] [uid:string]', '取消当前会话的家园订阅')
|
|
2102
|
+
.userFields(['authority'])
|
|
2074
2103
|
.action(async ({ session }, kind = '全部', uid = '') => {
|
|
2075
2104
|
const target = sessionTarget(session);
|
|
2076
|
-
if (!target.userId && !
|
|
2077
|
-
return '
|
|
2105
|
+
if (!target.userId && !canManageGroupSubscription(deps, session))
|
|
2106
|
+
return '此指令仅限群管理员或机器人管理员使用。';
|
|
2078
2107
|
const kindMap = { '菜园': 'garden', '灵感': 'inspiration', '生蛋': 'egg', '全部': '', all: '', garden: 'garden', inspiration: 'inspiration', egg: 'egg' };
|
|
2079
2108
|
const deleted = deps.homeSubMgr.deleteMatching(target, kindMap[String(kind || '全部')] ?? '', String(uid || '').trim());
|
|
2080
2109
|
return deleted ? `已取消 ${deleted} 条家园订阅。` : '当前会话没有匹配的家园订阅。';
|
|
@@ -2084,10 +2113,12 @@ function register(deps) {
|
|
|
2084
2113
|
.action(async ({ session }) => {
|
|
2085
2114
|
if (!isBotAdmin(session, deps.config.adminUserIds))
|
|
2086
2115
|
return '此指令仅限管理员使用。';
|
|
2087
|
-
const result = await checkHomeSubscriptions(deps);
|
|
2116
|
+
const result = await homeRunner(signal => checkHomeSubscriptions(deps, signal));
|
|
2117
|
+
if (!result)
|
|
2118
|
+
return '订阅检查正在运行或插件已停止。';
|
|
2088
2119
|
return `家园订阅检查完成:订阅 ${result.subscriptions} 条,检查 ${result.checked} 条,推送 ${result.pushed} 档提醒。`;
|
|
2089
2120
|
});
|
|
2090
2121
|
if (deps.config.homeSubscriptionEnabled) {
|
|
2091
|
-
ctx.setInterval(() => checkHomeSubscriptions(deps).catch(err => logger.warn(`家园订阅检查失败: ${err}`)), Math.max(1, deps.config.homeSubscriptionIntervalMinutes || 5) * 60000);
|
|
2122
|
+
ctx.setInterval(() => homeRunner(signal => checkHomeSubscriptions(deps, signal)).catch(err => logger.warn(`家园订阅检查失败: ${err}`)), Math.max(1, deps.config.homeSubscriptionIntervalMinutes || 5) * 60000);
|
|
2092
2123
|
}
|
|
2093
2124
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.register = register;
|
|
4
|
+
const send_image_1 = require("../send-image");
|
|
5
|
+
const ranking_service_1 = require("../ranking-service");
|
|
6
|
+
function register(deps) {
|
|
7
|
+
const { ctx, client, userMgr, renderer, config } = deps;
|
|
8
|
+
for (const [label, kind] of [['异色', 'shining'], ['炫彩', 'glass']]) {
|
|
9
|
+
ctx.command(`${label}排行榜 [args:text]`, `查询${label}精灵收集排行榜`)
|
|
10
|
+
.alias(`${label}榜`)
|
|
11
|
+
.alias(`洛克${label}排行榜`)
|
|
12
|
+
.action(async ({ session }, args = '') => {
|
|
13
|
+
if (!session)
|
|
14
|
+
return;
|
|
15
|
+
const primaryUid = String(userMgr.getPrimaryBinding(session.userId)?.role_id || '');
|
|
16
|
+
let parsed;
|
|
17
|
+
try {
|
|
18
|
+
parsed = (0, ranking_service_1.parseRankingArgs)(args, primaryUid);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
return error.message;
|
|
22
|
+
}
|
|
23
|
+
const payload = await client.getPetCollectionRanking(ctx, kind, parsed.limit, parsed.uid, { userIdentifier: session.userId });
|
|
24
|
+
if (payload === null)
|
|
25
|
+
return `排行榜查询失败:${client.getLastErrorBrief()}`;
|
|
26
|
+
const data = (0, ranking_service_1.buildRankingView)(payload, kind, config.apiBaseUrl.replace(/\/$/, ''), parsed.uid);
|
|
27
|
+
const image = await renderer.renderPages(ctx, 'pet-ranking', data, { maxPageHeight: 3500 });
|
|
28
|
+
await (0, send_image_1.sendImageWithFallback)(session, image, (0, ranking_service_1.buildRankingText)(data), 'ranking', config);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.register = register;
|
|
4
|
+
const send_image_1 = require("../send-image");
|
|
5
|
+
const share_code_service_1 = require("../share-code-service");
|
|
6
|
+
function register(deps) {
|
|
7
|
+
const { ctx, client, renderer, config } = deps;
|
|
8
|
+
const baseUrl = config.apiBaseUrl.replace(/\/$/, '');
|
|
9
|
+
const send = async (session, template, data, fallback) => {
|
|
10
|
+
const image = await renderer.renderPages(ctx, template, data);
|
|
11
|
+
await (0, send_image_1.sendImageWithFallback)(session, image, fallback, `share-code:${template}`, config);
|
|
12
|
+
};
|
|
13
|
+
const command = ctx.command('阵容码', '阵容分享码工具');
|
|
14
|
+
command.subcommand('解析 <input:text>', '解析分享码或链接')
|
|
15
|
+
.alias('阵容码解析')
|
|
16
|
+
.action(async ({ session }, input) => {
|
|
17
|
+
if (!session)
|
|
18
|
+
return;
|
|
19
|
+
let code = '';
|
|
20
|
+
try {
|
|
21
|
+
code = (0, share_code_service_1.extractShareCode)(input || '');
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
return error.message;
|
|
25
|
+
}
|
|
26
|
+
if (!code)
|
|
27
|
+
return '请提供分享码或包含 shareData 的链接。用法:阵容码 解析 <分享码或链接>';
|
|
28
|
+
const payload = await client.parseShareCode(ctx, code, session.userId);
|
|
29
|
+
if (!payload)
|
|
30
|
+
return `分享码解析失败:${client.getLastErrorBrief()}`;
|
|
31
|
+
const data = (0, share_code_service_1.buildShareCodeView)(payload, 'parse', undefined, baseUrl);
|
|
32
|
+
if (!data.teams.length)
|
|
33
|
+
return '分享码中没有可展示的精灵数据。';
|
|
34
|
+
await send(session, 'share-code-team', data, (0, share_code_service_1.buildShareCodeText)(data));
|
|
35
|
+
});
|
|
36
|
+
command.subcommand('查询 <input:text>', '查询分享码历史记录')
|
|
37
|
+
.alias('阵容码查询')
|
|
38
|
+
.action(async ({ session }, input) => {
|
|
39
|
+
if (!session)
|
|
40
|
+
return;
|
|
41
|
+
let code = '';
|
|
42
|
+
try {
|
|
43
|
+
code = (0, share_code_service_1.extractShareCode)(input || '');
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
return error.message;
|
|
47
|
+
}
|
|
48
|
+
if (!code)
|
|
49
|
+
return '请提供分享码。用法:阵容码 查询 <分享码>';
|
|
50
|
+
const res = await client.getShareCodeRecords(ctx, { shareCode: code, pageNo: 1, pageSize: 1 }, session.userId);
|
|
51
|
+
if (!res)
|
|
52
|
+
return `分享码记录查询失败:${client.getLastErrorBrief()}`;
|
|
53
|
+
const record = Array.isArray(res.items) ? res.items[0] : null;
|
|
54
|
+
if (!record)
|
|
55
|
+
return '未找到该分享码的解析记录,请先使用:阵容码 解析 <分享码或链接>';
|
|
56
|
+
let data = (0, share_code_service_1.buildShareCodeView)(record, 'record', record, baseUrl);
|
|
57
|
+
if (!data.teams.length) {
|
|
58
|
+
const parsed = await client.parseShareCode(ctx, code, session.userId);
|
|
59
|
+
if (parsed)
|
|
60
|
+
data = (0, share_code_service_1.buildShareCodeView)(parsed, 'record', record, baseUrl);
|
|
61
|
+
}
|
|
62
|
+
if (!data.teams.length)
|
|
63
|
+
return '已找到分享码记录,但无法还原阵容详情,请稍后重试。';
|
|
64
|
+
await send(session, 'share-code-team', data, (0, share_code_service_1.buildShareCodeText)(data));
|
|
65
|
+
});
|
|
66
|
+
}
|