koishi-plugin-rocom 1.0.12 → 1.0.13
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/client.d.ts +17 -1
- package/lib/client.js +121 -21
- package/lib/commands/merchant.js +340 -89
- package/lib/commands/query.js +22 -6
- package/lib/index.d.ts +4 -0
- package/lib/index.js +480 -122
- package/lib/render-templates/home/img/rocomuid/a_title.png +0 -0
- package/lib/render-templates/home/img/rocomuid/banner_bg.png +0 -0
- package/lib/render-templates/home/img/rocomuid/bg.jpg +0 -0
- package/lib/render-templates/home/img/rocomuid/img_head.png +0 -0
- package/lib/render-templates/home/img/rocomuid/jindu_bar.png +0 -0
- package/lib/render-templates/home/img/rocomuid/jindu_tc.png +0 -0
- package/lib/render-templates/home/img/rocomuid/level_icon.png +0 -0
- package/lib/render-templates/home/img/rocomuid/pet_bg.png +0 -0
- package/lib/render-templates/home/img/rocomuid/plant_bg.png +0 -0
- package/lib/render-templates/home/img/rocomuid/star_1.png +0 -0
- package/lib/render-templates/home/img/rocomuid/star_8.png +0 -0
- package/lib/render-templates/home/img/rocomuid/star_9.png +0 -0
- package/lib/render-templates/home/img/rocomuid/title_fg.png +0 -0
- package/lib/render-templates/home/img/rocomuid/top_bg.png +0 -0
- package/lib/render-templates/home/img/rocomuid/touxiang_mask.png +0 -0
- package/lib/render-templates/home/index.html +81 -97
- package/lib/render-templates/home/style.css +316 -375
- package/lib/render-templates/yuanxing-shangren/CHANGELOG.md +138 -0
- package/lib/render-templates/yuanxing-shangren/README.md +86 -0
- package/lib/render-templates/yuanxing-shangren/img/coin.png +0 -0
- package/lib/render-templates/yuanxing-shangren/img//345/272/225/351/203/250/344/277/241/346/201/257/346/241/206.png +0 -0
- package/lib/render-templates/yuanxing-shangren/img//346/240/207/351/242/230.png +0 -0
- package/lib/render-templates/yuanxing-shangren/img//347/203/255/351/224/200.png +0 -0
- package/lib/render-templates/yuanxing-shangren/img//347/274/226/345/217/267/350/203/214/346/231/257/346/241/206.png +0 -0
- package/lib/render-templates/yuanxing-shangren/index.html +198 -170
- package/lib/render-templates/yuanxing-shangren/index.html.backup +356 -0
- package/lib/render-templates/yuanxing-shangren/merchant.html +461 -0
- package/lib/render-templates/yuanxing-shangren/style.css +201 -256
- package/lib/render-templates/yuanxing-shangren/today.html +461 -0
- package/lib/render-templates/yuanxing-shangren/today.style.css +366 -0
- package/lib/render.js +11 -4
- package/lib/subscription-send.js +4 -5
- package/lib/ttf/rocom_skill_origin.ttf +0 -0
- package/lib/ttf//351/200/240/345/255/227/345/267/245/346/210/277/345/220/257/351/273/221/344/275/223.ttf +0 -0
- package/lib/types.d.ts +4 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -43,7 +43,10 @@ var import_node_path5 = __toESM(require("node:path"));
|
|
|
43
43
|
// src/client.ts
|
|
44
44
|
var import_koishi = require("koishi");
|
|
45
45
|
var logger = new import_koishi.Logger("rocom-client");
|
|
46
|
-
var
|
|
46
|
+
var INGAME_PENDING_STATUSES = ["queued", "pending", "running", "processing", "accepted"];
|
|
47
|
+
var INGAME_FAILED_STATUSES = ["failed", "error", "timeout", "cancelled", "canceled"];
|
|
48
|
+
var INGAME_COMPLETED_STATUSES = ["done", "success", "succeeded", "completed", "finished"];
|
|
49
|
+
var RocomClient = class _RocomClient {
|
|
47
50
|
static {
|
|
48
51
|
__name(this, "RocomClient");
|
|
49
52
|
}
|
|
@@ -362,6 +365,97 @@ ${this.stringifyForLog(details)}`);
|
|
|
362
365
|
{ acceptedStatuses: [200, 202] }
|
|
363
366
|
);
|
|
364
367
|
}
|
|
368
|
+
static normalizeTaskStatus(value) {
|
|
369
|
+
return String(value ?? "").trim().toLowerCase();
|
|
370
|
+
}
|
|
371
|
+
static extractTaskId(payload) {
|
|
372
|
+
return String(payload?.task_id || payload?.taskId || payload?.taskID || "").trim();
|
|
373
|
+
}
|
|
374
|
+
// 判断一份 payload 是否为“已完成的业务结果”(而非排队占位)。
|
|
375
|
+
// 家园接口完成时返回 rows/home_info;玩家、商店完成时返回 source/title/rows。
|
|
376
|
+
static isCompletedGatewayPayload(payload) {
|
|
377
|
+
if (!payload || typeof payload !== "object") return false;
|
|
378
|
+
if (Array.isArray(payload.rows)) return true;
|
|
379
|
+
if (payload.home_info !== void 0) return true;
|
|
380
|
+
if (payload.source !== void 0) return true;
|
|
381
|
+
if (String(payload.title || "").trim()) return true;
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
static extractCompletedIngamePayload(payload) {
|
|
385
|
+
if (!payload || typeof payload !== "object") return null;
|
|
386
|
+
if (_RocomClient.isCompletedGatewayPayload(payload)) return payload;
|
|
387
|
+
if (_RocomClient.isCompletedGatewayPayload(payload.result)) return payload.result;
|
|
388
|
+
if (_RocomClient.isCompletedGatewayPayload(payload.data)) return payload.data;
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
static taskErrorMessage(payload, fallbackStatus = "") {
|
|
392
|
+
const candidates = [
|
|
393
|
+
payload?.message,
|
|
394
|
+
payload?.error,
|
|
395
|
+
payload?.error_message,
|
|
396
|
+
payload?.reason,
|
|
397
|
+
payload?.result?.message,
|
|
398
|
+
payload?.result?.error,
|
|
399
|
+
payload?.result?.error_message,
|
|
400
|
+
payload?.data?.message,
|
|
401
|
+
payload?.data?.error
|
|
402
|
+
];
|
|
403
|
+
for (const candidate of candidates) {
|
|
404
|
+
const text = String(candidate ?? "").trim();
|
|
405
|
+
if (text && text.toLowerCase() !== "failed") return text;
|
|
406
|
+
}
|
|
407
|
+
return String(payload?.status || fallbackStatus || "").trim() || "任务执行失败";
|
|
408
|
+
}
|
|
409
|
+
// 排队等候轮询:把 ingame 接口提交后返回的 202(task_id/status=queued)轮询到出结果或超时。
|
|
410
|
+
// 入参 first 是首请求拿到的 { status, data, usedApiKey }。
|
|
411
|
+
async pollIngameTask(ctx, first, options, labels) {
|
|
412
|
+
const { status, data, usedApiKey } = first;
|
|
413
|
+
const initialCompletedPayload = _RocomClient.extractCompletedIngamePayload(data);
|
|
414
|
+
if (status !== null && initialCompletedPayload) return initialCompletedPayload;
|
|
415
|
+
const taskId = _RocomClient.extractTaskId(data);
|
|
416
|
+
if (!taskId) {
|
|
417
|
+
if (status === 202) this.setLastError(labels.queuedNoTaskId);
|
|
418
|
+
return status === null ? null : data;
|
|
419
|
+
}
|
|
420
|
+
if (typeof options.onQueued === "function") {
|
|
421
|
+
try {
|
|
422
|
+
await options.onQueued(taskId);
|
|
423
|
+
} catch (e) {
|
|
424
|
+
logger.warn(`ingame 排队提示回调失败: ${e}`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
const intervalMs = Math.max(300, Number(options.intervalMs) || 3e3);
|
|
428
|
+
const timeoutMs = Math.max(intervalMs, Number(options.timeoutMs) || 18e4);
|
|
429
|
+
const startedAt = Date.now();
|
|
430
|
+
let lastStatus = _RocomClient.normalizeTaskStatus(data?.status);
|
|
431
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
432
|
+
if (lastStatus && INGAME_FAILED_STATUSES.includes(lastStatus)) {
|
|
433
|
+
this.setLastError(_RocomClient.taskErrorMessage(data, lastStatus));
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
437
|
+
const task = await this.getIngameTask(ctx, taskId, usedApiKey);
|
|
438
|
+
if (task.status === null) return null;
|
|
439
|
+
const completedPayload = _RocomClient.extractCompletedIngamePayload(task.data);
|
|
440
|
+
if (completedPayload) return completedPayload;
|
|
441
|
+
lastStatus = _RocomClient.normalizeTaskStatus(task.data?.status);
|
|
442
|
+
if (INGAME_FAILED_STATUSES.includes(lastStatus)) {
|
|
443
|
+
this.setLastError(_RocomClient.taskErrorMessage(task.data, lastStatus));
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
if (INGAME_COMPLETED_STATUSES.includes(lastStatus)) {
|
|
447
|
+
this.setLastError(`Ingame 任务已完成但未返回可解析结果:${taskId}`);
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
450
|
+
if (lastStatus && !INGAME_PENDING_STATUSES.includes(lastStatus)) {
|
|
451
|
+
this.setLastError(`Ingame 任务状态异常:${lastStatus}`);
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
if (!_RocomClient.extractTaskId(task.data) && !lastStatus) return task.data;
|
|
455
|
+
}
|
|
456
|
+
this.setLastError(labels.stillQueued(taskId));
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
365
459
|
async qqQrLogin(ctx, userIdentifier) {
|
|
366
460
|
const params = { client_type: "bot", client_id: "koishi", provider: "rocom" };
|
|
367
461
|
if (userIdentifier) params.user_identifier = this.sanitizeUid(userIdentifier);
|
|
@@ -503,7 +597,10 @@ ${this.stringifyForLog(details)}`);
|
|
|
503
597
|
return this.get(ctx, "/api/v1/games/rocom/exchange/posters", this.wegameHeaders(fwToken, userIdentifier), params);
|
|
504
598
|
}
|
|
505
599
|
async getMerchantInfo(ctx, refresh = false) {
|
|
506
|
-
return this.get(ctx, "/api/v1/games/rocom/merchant/info", this.wegameHeaders(), {
|
|
600
|
+
return this.get(ctx, "/api/v1/games/rocom/merchant/info", this.wegameHeaders(), {
|
|
601
|
+
refresh: refresh ? "true" : "false",
|
|
602
|
+
random_goods: "all"
|
|
603
|
+
});
|
|
507
604
|
}
|
|
508
605
|
async queryPetSize(ctx, diameter, weight, sameRideEgg = false, userIdentifier = "") {
|
|
509
606
|
const params = this.scopedParams({ diameter, weight }, userIdentifier);
|
|
@@ -661,29 +758,20 @@ ${this.stringifyForLog(details)}`);
|
|
|
661
758
|
this.scopedParams(params, userIdentifier)
|
|
662
759
|
);
|
|
663
760
|
}
|
|
664
|
-
async ingameHomeInfo(ctx, uid,
|
|
761
|
+
async ingameHomeInfo(ctx, uid, options = {}) {
|
|
665
762
|
const sanitizedUid = this.sanitizeUid(uid);
|
|
666
763
|
if (!sanitizedUid) {
|
|
667
764
|
this.setLastError("UID 不能为空");
|
|
668
765
|
return null;
|
|
669
766
|
}
|
|
767
|
+
const waitMs = Number(options.waitMs) || 5e3;
|
|
670
768
|
const path6 = "/api/v1/games/rocom/ingame/home/info";
|
|
671
769
|
const payload = { uid: sanitizedUid, wait_ms: waitMs };
|
|
672
|
-
const
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
return null;
|
|
678
|
-
}
|
|
679
|
-
for (let i = 0; i < 10; i++) {
|
|
680
|
-
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
681
|
-
const task = await this.getIngameTask(ctx, taskId, usedApiKey);
|
|
682
|
-
if (task.status === 200) return task.data;
|
|
683
|
-
if (task.status === null) return null;
|
|
684
|
-
}
|
|
685
|
-
this.setLastError(`Home query task is still queued, please retry later (task_id: ${taskId})`);
|
|
686
|
-
return null;
|
|
770
|
+
const first = await this.requestIngameWithFallback(ctx, path6, payload);
|
|
771
|
+
return this.pollIngameTask(ctx, first, options, {
|
|
772
|
+
queuedNoTaskId: "家园查询任务已入队,但未返回 task_id",
|
|
773
|
+
stillQueued: /* @__PURE__ */ __name((taskId) => `家园查询任务仍在排队,请稍后重试(task_id: ${taskId})`, "stillQueued")
|
|
774
|
+
});
|
|
687
775
|
}
|
|
688
776
|
async ingameMerchantInfo(ctx, shopId) {
|
|
689
777
|
const params = { shop_id: shopId };
|
|
@@ -1634,6 +1722,11 @@ var TEMPLATE_CAPTURE_PADDING = {
|
|
|
1634
1722
|
var TEMPLATE_VIEWPORTS = {
|
|
1635
1723
|
activities: { width: 1600, height: 1200, deviceScaleFactor: 2 }
|
|
1636
1724
|
};
|
|
1725
|
+
var VISUAL_BOUNDS_TEMPLATES = /* @__PURE__ */ new Set([
|
|
1726
|
+
"home",
|
|
1727
|
+
"yuanxing-shangren/merchant",
|
|
1728
|
+
"yuanxing-shangren/today"
|
|
1729
|
+
]);
|
|
1637
1730
|
var DEFAULT_SCREENSHOT_OPTIONS = {
|
|
1638
1731
|
type: "jpeg",
|
|
1639
1732
|
quality: 82
|
|
@@ -1760,6 +1853,7 @@ var Renderer = class {
|
|
|
1760
1853
|
".student-perks-page",
|
|
1761
1854
|
".student-page",
|
|
1762
1855
|
".merchant-page",
|
|
1856
|
+
".page",
|
|
1763
1857
|
".home-page",
|
|
1764
1858
|
".pet-panel-page",
|
|
1765
1859
|
".pet-detail-page",
|
|
@@ -1776,16 +1870,17 @@ var Renderer = class {
|
|
|
1776
1870
|
if (target) {
|
|
1777
1871
|
const box = await target.boundingBox();
|
|
1778
1872
|
if (box && box.width > 0 && box.height > 0) {
|
|
1779
|
-
const
|
|
1873
|
+
const useVisualBounds = VISUAL_BOUNDS_TEMPLATES.has(templateName);
|
|
1874
|
+
const elementMetrics = await page.evaluate((el, visualBounds) => {
|
|
1780
1875
|
const rect = el.getBoundingClientRect();
|
|
1781
1876
|
const element = el;
|
|
1782
1877
|
return {
|
|
1783
1878
|
x: rect.left + window.scrollX,
|
|
1784
1879
|
y: rect.top + window.scrollY,
|
|
1785
|
-
width: Math.max(rect.width, element.scrollWidth, element.offsetWidth),
|
|
1786
|
-
height: Math.max(rect.height, element.scrollHeight, element.offsetHeight)
|
|
1880
|
+
width: visualBounds ? rect.width : Math.max(rect.width, element.scrollWidth, element.offsetWidth),
|
|
1881
|
+
height: visualBounds ? rect.height : Math.max(rect.height, element.scrollHeight, element.offsetHeight)
|
|
1787
1882
|
};
|
|
1788
|
-
}, target);
|
|
1883
|
+
}, target, useVisualBounds);
|
|
1789
1884
|
const capturePadding = TEMPLATE_CAPTURE_PADDING[templateName] || { left: 0, right: 0, top: 0, bottom: 0 };
|
|
1790
1885
|
await page.setViewport({
|
|
1791
1886
|
width: Math.max(Math.ceil(elementMetrics.x + elementMetrics.width + capturePadding.right) + 8, 200),
|
|
@@ -2434,15 +2529,14 @@ async function sendScheduledMessage(ctx, target, message) {
|
|
|
2434
2529
|
}
|
|
2435
2530
|
__name(sendScheduledMessage, "sendScheduledMessage");
|
|
2436
2531
|
async function sendScheduledImageWithFallback(ctx, target, image, fallbackText, mentionAll = false) {
|
|
2437
|
-
const
|
|
2532
|
+
const buildContent = /* @__PURE__ */ __name((body) => mentionAll ? (0, import_koishi7.h)("message", {}, (0, import_koishi7.h)("at", { type: "all" }), import_koishi7.h.text("\n"), body) : body, "buildContent");
|
|
2438
2533
|
if (!image) {
|
|
2439
|
-
return sendScheduledMessage(ctx, target,
|
|
2534
|
+
return sendScheduledMessage(ctx, target, buildContent(import_koishi7.h.text(fallbackText)));
|
|
2440
2535
|
}
|
|
2441
2536
|
const imageSegment = import_koishi7.h.image(image, detectImageMime(image));
|
|
2442
|
-
const
|
|
2443
|
-
const sent = await sendScheduledMessage(ctx, target, content);
|
|
2537
|
+
const sent = await sendScheduledMessage(ctx, target, buildContent(imageSegment));
|
|
2444
2538
|
if (sent) return true;
|
|
2445
|
-
return sendScheduledMessage(ctx, target,
|
|
2539
|
+
return sendScheduledMessage(ctx, target, buildContent(import_koishi7.h.text(fallbackText)));
|
|
2446
2540
|
}
|
|
2447
2541
|
__name(sendScheduledImageWithFallback, "sendScheduledImageWithFallback");
|
|
2448
2542
|
|
|
@@ -3224,7 +3318,7 @@ function panelPetDetailData(uid, pet, updatedAtText) {
|
|
|
3224
3318
|
}
|
|
3225
3319
|
__name(panelPetDetailData, "panelPetDetailData");
|
|
3226
3320
|
async function refreshPanelPets(deps, uid, userId = "") {
|
|
3227
|
-
const res = await deps.client.ingameHomeInfo(deps.ctx, uid, 2e4);
|
|
3321
|
+
const res = await deps.client.ingameHomeInfo(deps.ctx, uid, { waitMs: 2e4, timeoutMs: 3e4 });
|
|
3228
3322
|
if (res) {
|
|
3229
3323
|
const homeInfo = homeInfoPayload(res);
|
|
3230
3324
|
const pets = buildPanelPetList(homeInfo);
|
|
@@ -3256,7 +3350,7 @@ function formatPanelUpdatedAt(ts) {
|
|
|
3256
3350
|
return new Date(value * 1e3).toLocaleString("zh-CN");
|
|
3257
3351
|
}
|
|
3258
3352
|
__name(formatPanelUpdatedAt, "formatPanelUpdatedAt");
|
|
3259
|
-
function extractHomePet(raw, index, guard = false) {
|
|
3353
|
+
function extractHomePet(deps, raw, index, guard = false) {
|
|
3260
3354
|
if (!raw || typeof raw !== "object") return null;
|
|
3261
3355
|
const homePet = raw.home_pet_info && typeof raw.home_pet_info === "object" ? raw.home_pet_info : raw;
|
|
3262
3356
|
const display = raw.display_info && typeof raw.display_info === "object" ? raw.display_info : {};
|
|
@@ -3268,6 +3362,7 @@ function extractHomePet(raw, index, guard = false) {
|
|
|
3268
3362
|
const eggReady = hasEgg || predictedEggTime > 0 && nowTs >= predictedEggTime;
|
|
3269
3363
|
const feedRound = Number(homePet.feed_round || raw.feed_round || 0) || 0;
|
|
3270
3364
|
const gender = Number(display.gender || raw.gender || 0) || 0;
|
|
3365
|
+
const mutationType = Number(display.mutation_type || raw.mutation_type || homePet.mutation_type || 0) || 0;
|
|
3271
3366
|
const isMale = gender === 1;
|
|
3272
3367
|
const status = homePet.status ?? raw.status;
|
|
3273
3368
|
const isGuard = guard || Boolean(raw.is_guard || raw.guard) || ["2", "guard", "守卫"].includes(String(status).toLowerCase());
|
|
@@ -3295,7 +3390,9 @@ function extractHomePet(raw, index, guard = false) {
|
|
|
3295
3390
|
name: String(homePet.name || homePet.pet_name || raw.name || raw.pet_name || `精灵 ${petId || ""}`),
|
|
3296
3391
|
level: display.level || raw.level || homePet.level || "--",
|
|
3297
3392
|
iconUrl: homePetIcon(petId, raw.icon_url || raw.pet_img_url || raw.petIcon || ""),
|
|
3393
|
+
starIconUrl: [1, 8, 9].includes(mutationType) ? deps.renderer.resourceUrl(`render-templates/home/img/rocomuid/star_${mutationType}.png`) : "",
|
|
3298
3394
|
badge: isGuard ? "守" : hasEgg ? "蛋" : "",
|
|
3395
|
+
mutationType,
|
|
3299
3396
|
isGuard,
|
|
3300
3397
|
statusText,
|
|
3301
3398
|
statusClass,
|
|
@@ -3410,13 +3507,13 @@ function buildHomeRenderData(deps, res, uid) {
|
|
|
3410
3507
|
const indoorPets = [];
|
|
3411
3508
|
const guardPets = [];
|
|
3412
3509
|
indoorSources.forEach((raw, index) => {
|
|
3413
|
-
const item = extractHomePet(raw, index);
|
|
3510
|
+
const item = extractHomePet(deps, raw, index);
|
|
3414
3511
|
if (!item) return;
|
|
3415
3512
|
if (item.isGuard) guardPets.push(item);
|
|
3416
3513
|
else indoorPets.push(item);
|
|
3417
3514
|
});
|
|
3418
3515
|
guardSources.forEach((raw, index) => {
|
|
3419
|
-
const item = extractHomePet(raw, index, true);
|
|
3516
|
+
const item = extractHomePet(deps, raw, index, true);
|
|
3420
3517
|
if (item) guardPets.push(item);
|
|
3421
3518
|
});
|
|
3422
3519
|
indoorPets.sort((a, b) => {
|
|
@@ -3645,7 +3742,7 @@ async function checkHomeSubscriptions(deps) {
|
|
|
3645
3742
|
if (!sub.uid || !["garden", "inspiration"].includes(sub.kind)) continue;
|
|
3646
3743
|
checkedCount++;
|
|
3647
3744
|
if (!cache.has(sub.uid)) {
|
|
3648
|
-
cache.set(sub.uid, await deps.client.ingameHomeInfo(deps.ctx, sub.uid));
|
|
3745
|
+
cache.set(sub.uid, await deps.client.ingameHomeInfo(deps.ctx, sub.uid, { timeoutMs: 3e4 }));
|
|
3649
3746
|
}
|
|
3650
3747
|
const res = cache.get(sub.uid);
|
|
3651
3748
|
if (!res) continue;
|
|
@@ -4083,7 +4180,17 @@ function register2(deps) {
|
|
|
4083
4180
|
targetUid = String(binding?.role_id || "");
|
|
4084
4181
|
}
|
|
4085
4182
|
if (!targetUid) return "请提供玩家 UID,或先完成绑定后使用 洛克.家园。";
|
|
4086
|
-
|
|
4183
|
+
let queuedNotified = false;
|
|
4184
|
+
const res = await client.ingameHomeInfo(ctx, targetUid, {
|
|
4185
|
+
waitMs: deps.config.homeQueryWaitMs,
|
|
4186
|
+
intervalMs: deps.config.homeQueryPollIntervalMs,
|
|
4187
|
+
timeoutMs: deps.config.homeQueryTimeoutMs,
|
|
4188
|
+
onQueued: /* @__PURE__ */ __name(async () => {
|
|
4189
|
+
if (queuedNotified) return;
|
|
4190
|
+
queuedNotified = true;
|
|
4191
|
+
await session?.send?.(`UID ${targetUid} 的家园查询已进入队列,正在等待游戏侧返回,请稍候…`);
|
|
4192
|
+
}, "onQueued")
|
|
4193
|
+
});
|
|
4087
4194
|
if (!res) return `家园查询失败:${client.getLastErrorBrief()}`;
|
|
4088
4195
|
await sendImage(deps, session, "home", buildHomeRenderData(deps, res, targetUid), `【洛克家园】UID ${targetUid}`);
|
|
4089
4196
|
});
|
|
@@ -4187,6 +4294,7 @@ var import_koishi9 = require("koishi");
|
|
|
4187
4294
|
var logger9 = new import_koishi9.Logger("rocom-merchant");
|
|
4188
4295
|
var TEXT = {
|
|
4189
4296
|
merchant: "远行商人",
|
|
4297
|
+
todayMerchant: "今日远行商人",
|
|
4190
4298
|
subscribe: "订阅远行商人",
|
|
4191
4299
|
unsubscribe: "取消订阅远行商人",
|
|
4192
4300
|
viewSubscribe: "查看远行商人订阅",
|
|
@@ -4201,6 +4309,12 @@ var CATEGORY_LABELS = {
|
|
|
4201
4309
|
round: "常规商品",
|
|
4202
4310
|
weekend: "周末限定"
|
|
4203
4311
|
};
|
|
4312
|
+
var ROUND_WINDOWS = [
|
|
4313
|
+
{ id: 1, label: "08:00-12:00", startHour: 8, endHour: 12 },
|
|
4314
|
+
{ id: 2, label: "12:00-16:00", startHour: 12, endHour: 16 },
|
|
4315
|
+
{ id: 3, label: "16:00-20:00", startHour: 16, endHour: 20 },
|
|
4316
|
+
{ id: 4, label: "20:00-24:00", startHour: 20, endHour: 24 }
|
|
4317
|
+
];
|
|
4204
4318
|
var CHINA_TIMEZONE = "Asia/Shanghai";
|
|
4205
4319
|
var chinaPartsFormatter = new Intl.DateTimeFormat("zh-CN", {
|
|
4206
4320
|
timeZone: CHINA_TIMEZONE,
|
|
@@ -4209,19 +4323,39 @@ var chinaPartsFormatter = new Intl.DateTimeFormat("zh-CN", {
|
|
|
4209
4323
|
day: "2-digit",
|
|
4210
4324
|
hour: "2-digit",
|
|
4211
4325
|
minute: "2-digit",
|
|
4326
|
+
second: "2-digit",
|
|
4212
4327
|
hour12: false
|
|
4213
4328
|
});
|
|
4214
|
-
function getChinaParts(
|
|
4329
|
+
function getChinaParts(input = Date.now()) {
|
|
4215
4330
|
const parts = {};
|
|
4216
|
-
|
|
4331
|
+
const date = input instanceof Date ? input : new Date(input);
|
|
4332
|
+
for (const item of chinaPartsFormatter.formatToParts(date)) {
|
|
4217
4333
|
if (item.type !== "literal") parts[item.type] = item.value;
|
|
4218
4334
|
}
|
|
4219
4335
|
return {
|
|
4336
|
+
year: Number(parts.year || "0"),
|
|
4337
|
+
month: Number(parts.month || "0"),
|
|
4338
|
+
day: Number(parts.day || "0"),
|
|
4220
4339
|
hour: Number(parts.hour || "0"),
|
|
4221
|
-
minute: Number(parts.minute || "0")
|
|
4340
|
+
minute: Number(parts.minute || "0"),
|
|
4341
|
+
second: Number(parts.second || "0")
|
|
4222
4342
|
};
|
|
4223
4343
|
}
|
|
4224
4344
|
__name(getChinaParts, "getChinaParts");
|
|
4345
|
+
function padNumber(value) {
|
|
4346
|
+
return String(value).padStart(2, "0");
|
|
4347
|
+
}
|
|
4348
|
+
__name(padNumber, "padNumber");
|
|
4349
|
+
function getChinaDateText(input = Date.now()) {
|
|
4350
|
+
const parts = getChinaParts(input);
|
|
4351
|
+
return `${parts.year}-${padNumber(parts.month)}-${padNumber(parts.day)}`;
|
|
4352
|
+
}
|
|
4353
|
+
__name(getChinaDateText, "getChinaDateText");
|
|
4354
|
+
function getChinaDayStartMs(input = Date.now()) {
|
|
4355
|
+
const parts = getChinaParts(input);
|
|
4356
|
+
return (/* @__PURE__ */ new Date(`${parts.year}-${padNumber(parts.month)}-${padNumber(parts.day)}T00:00:00+08:00`)).getTime();
|
|
4357
|
+
}
|
|
4358
|
+
__name(getChinaDayStartMs, "getChinaDayStartMs");
|
|
4225
4359
|
function classifyMerchantItem(item) {
|
|
4226
4360
|
const start = normalizeTimestamp(item?.start_time);
|
|
4227
4361
|
const end = normalizeTimestamp(item?.end_time);
|
|
@@ -4248,16 +4382,12 @@ function formatProductWindow(product) {
|
|
|
4248
4382
|
const end = normalizeTimestamp(product?.end_time);
|
|
4249
4383
|
if (!start && !end) return "";
|
|
4250
4384
|
const formatDate2 = /* @__PURE__ */ __name((timestamp) => {
|
|
4251
|
-
const
|
|
4252
|
-
|
|
4253
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
4254
|
-
return `${month}-${day}`;
|
|
4385
|
+
const parts = getChinaParts(timestamp);
|
|
4386
|
+
return `${padNumber(parts.month)}-${padNumber(parts.day)}`;
|
|
4255
4387
|
}, "formatDate");
|
|
4256
4388
|
const formatTime = /* @__PURE__ */ __name((timestamp) => {
|
|
4257
|
-
const
|
|
4258
|
-
|
|
4259
|
-
const minute = String(date.getMinutes()).padStart(2, "0");
|
|
4260
|
-
return `${hour}:${minute}`;
|
|
4389
|
+
const parts = getChinaParts(timestamp);
|
|
4390
|
+
return `${padNumber(parts.hour)}:${padNumber(parts.minute)}`;
|
|
4261
4391
|
}, "formatTime");
|
|
4262
4392
|
if (start && end) {
|
|
4263
4393
|
const datePart = formatDate2(start);
|
|
@@ -4272,7 +4402,7 @@ function getMerchantActivity(res) {
|
|
|
4272
4402
|
return activities[0] || {};
|
|
4273
4403
|
}
|
|
4274
4404
|
__name(getMerchantActivity, "getMerchantActivity");
|
|
4275
|
-
function
|
|
4405
|
+
function getMerchantProducts(res) {
|
|
4276
4406
|
const activity = getMerchantActivity(res);
|
|
4277
4407
|
const groups = [];
|
|
4278
4408
|
if (Array.isArray(activity?.products)) groups.push(activity.products);
|
|
@@ -4282,12 +4412,10 @@ function getActiveProducts(res) {
|
|
|
4282
4412
|
if (Array.isArray(activity?.get_pets)) groups.push(activity.get_pets);
|
|
4283
4413
|
const merged = [];
|
|
4284
4414
|
const seen = /* @__PURE__ */ new Set();
|
|
4285
|
-
const now = Date.now();
|
|
4286
4415
|
for (const list of groups) {
|
|
4287
4416
|
for (const item of list) {
|
|
4288
4417
|
const start = normalizeTimestamp(item?.start_time) ?? 0;
|
|
4289
4418
|
const end = normalizeTimestamp(item?.end_time) ?? Infinity;
|
|
4290
|
-
if (now < start || now >= end) continue;
|
|
4291
4419
|
const key = `${item?.id ?? ""}|${item?.name ?? ""}|${start}|${end === Infinity ? "inf" : end}`;
|
|
4292
4420
|
if (seen.has(key)) continue;
|
|
4293
4421
|
seen.add(key);
|
|
@@ -4296,35 +4424,60 @@ function getActiveProducts(res) {
|
|
|
4296
4424
|
}
|
|
4297
4425
|
return merged;
|
|
4298
4426
|
}
|
|
4299
|
-
__name(
|
|
4300
|
-
function
|
|
4301
|
-
const
|
|
4302
|
-
const
|
|
4303
|
-
const
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
const
|
|
4427
|
+
__name(getMerchantProducts, "getMerchantProducts");
|
|
4428
|
+
function isMerchantItemActive(item, now = Date.now()) {
|
|
4429
|
+
const nowMs = now instanceof Date ? now.getTime() : now;
|
|
4430
|
+
const start = normalizeTimestamp(item?.start_time);
|
|
4431
|
+
const end = normalizeTimestamp(item?.end_time);
|
|
4432
|
+
return (start === null || nowMs >= start) && (end === null || nowMs < end);
|
|
4433
|
+
}
|
|
4434
|
+
__name(isMerchantItemActive, "isMerchantItemActive");
|
|
4435
|
+
function isMerchantItemToday(item, now = Date.now()) {
|
|
4436
|
+
const start = normalizeTimestamp(item?.start_time);
|
|
4437
|
+
const end = normalizeTimestamp(item?.end_time);
|
|
4438
|
+
if (start === null || end === null) return true;
|
|
4439
|
+
const startOfDay = getChinaDayStartMs(now);
|
|
4440
|
+
const endOfDay = startOfDay + 24 * 60 * 60 * 1e3;
|
|
4441
|
+
return start < endOfDay && end > startOfDay;
|
|
4442
|
+
}
|
|
4443
|
+
__name(isMerchantItemToday, "isMerchantItemToday");
|
|
4444
|
+
function getRoundForItem(item, now = Date.now()) {
|
|
4445
|
+
const start = normalizeTimestamp(item?.start_time);
|
|
4446
|
+
if (start === null) return null;
|
|
4447
|
+
const startParts = getChinaParts(start);
|
|
4448
|
+
const nowParts = getChinaParts(now);
|
|
4449
|
+
if (startParts.year !== nowParts.year || startParts.month !== nowParts.month || startParts.day !== nowParts.day) {
|
|
4450
|
+
return null;
|
|
4451
|
+
}
|
|
4452
|
+
const startHour = startParts.hour + startParts.minute / 60;
|
|
4453
|
+
const round = ROUND_WINDOWS.find((win) => startHour >= win.startHour && startHour < win.endHour);
|
|
4454
|
+
return round?.id ?? null;
|
|
4455
|
+
}
|
|
4456
|
+
__name(getRoundForItem, "getRoundForItem");
|
|
4457
|
+
function getCurrentMerchantRound(now = /* @__PURE__ */ new Date()) {
|
|
4458
|
+
const parts = getChinaParts(now);
|
|
4459
|
+
const secondsOfDay = parts.hour * 3600 + parts.minute * 60 + parts.second;
|
|
4460
|
+
const marketStartSeconds = 8 * 3600;
|
|
4461
|
+
const marketEndSeconds = 24 * 3600;
|
|
4462
|
+
const datePart = getChinaDateText(now);
|
|
4463
|
+
if (secondsOfDay < marketStartSeconds || secondsOfDay >= marketEndSeconds) {
|
|
4464
|
+
return {
|
|
4465
|
+
current: null,
|
|
4466
|
+
total: ROUND_WINDOWS.length,
|
|
4467
|
+
countdown: TEXT.notOpen,
|
|
4468
|
+
is_open: false,
|
|
4469
|
+
round_id: `${datePart}-closed`
|
|
4470
|
+
};
|
|
4471
|
+
}
|
|
4472
|
+
const currentWindow = ROUND_WINDOWS.find((win) => parts.hour >= win.startHour && parts.hour < win.endHour);
|
|
4473
|
+
const currentRound = currentWindow?.id ?? null;
|
|
4474
|
+
const roundEndSeconds = (currentWindow?.endHour ?? 24) * 3600;
|
|
4475
|
+
const diff = Math.max(0, (roundEndSeconds - secondsOfDay) * 1e3);
|
|
4318
4476
|
const hours = Math.floor(diff / 36e5);
|
|
4319
4477
|
const mins = Math.floor(diff % 36e5 / 6e4);
|
|
4320
|
-
const datePart = [
|
|
4321
|
-
now.getFullYear(),
|
|
4322
|
-
String(now.getMonth() + 1).padStart(2, "0"),
|
|
4323
|
-
String(now.getDate()).padStart(2, "0")
|
|
4324
|
-
].join("-");
|
|
4325
4478
|
return {
|
|
4326
4479
|
current: currentRound,
|
|
4327
|
-
total:
|
|
4480
|
+
total: ROUND_WINDOWS.length,
|
|
4328
4481
|
countdown: `${hours}小时${mins}分钟`,
|
|
4329
4482
|
is_open: currentRound !== null,
|
|
4330
4483
|
round_id: `${datePart}-${currentRound || "closed"}`
|
|
@@ -4365,66 +4518,258 @@ function sameStringArray(left, right) {
|
|
|
4365
4518
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
4366
4519
|
}
|
|
4367
4520
|
__name(sameStringArray, "sameStringArray");
|
|
4368
|
-
function
|
|
4369
|
-
const
|
|
4370
|
-
const
|
|
4371
|
-
const
|
|
4372
|
-
const
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4521
|
+
function getRandomGoodsMaps(res) {
|
|
4522
|
+
const priceMap = /* @__PURE__ */ new Map();
|
|
4523
|
+
const limitMap = /* @__PURE__ */ new Map();
|
|
4524
|
+
const randomGoods = Array.isArray(res?.random_goods) ? res.random_goods : Array.isArray(res?.randomGoods) ? res.randomGoods : [];
|
|
4525
|
+
for (const item of randomGoods) {
|
|
4526
|
+
const name2 = String(item?.goods_name || item?.name || "").trim();
|
|
4527
|
+
if (!name2) continue;
|
|
4528
|
+
if (item?.price !== void 0 && item?.price !== null && item.price !== "") priceMap.set(name2, item.price);
|
|
4529
|
+
if (item?.buy_limit_num !== void 0 && item?.buy_limit_num !== null && item.buy_limit_num !== "") {
|
|
4530
|
+
limitMap.set(name2, item.buy_limit_num);
|
|
4531
|
+
}
|
|
4532
|
+
}
|
|
4533
|
+
return { priceMap, limitMap };
|
|
4534
|
+
}
|
|
4535
|
+
__name(getRandomGoodsMaps, "getRandomGoodsMaps");
|
|
4536
|
+
function normalizeMerchantProducts(res, now = /* @__PURE__ */ new Date()) {
|
|
4537
|
+
return getMerchantProducts(res).map((item) => {
|
|
4538
|
+
const name2 = String(item?.name || item?.goods_name || TEXT.unknown).trim() || TEXT.unknown;
|
|
4539
|
+
return {
|
|
4540
|
+
name: name2,
|
|
4541
|
+
image: String(item?.icon_url || item?.iconUrl || ""),
|
|
4542
|
+
time_label: formatProductWindow(item),
|
|
4543
|
+
category: classifyMerchantItem(item),
|
|
4544
|
+
round_id: getRoundForItem(item, now),
|
|
4545
|
+
is_active: isMerchantItemActive(item, now),
|
|
4546
|
+
start_time: normalizeTimestamp(item?.start_time),
|
|
4547
|
+
end_time: normalizeTimestamp(item?.end_time)
|
|
4548
|
+
};
|
|
4549
|
+
});
|
|
4550
|
+
}
|
|
4551
|
+
__name(normalizeMerchantProducts, "normalizeMerchantProducts");
|
|
4552
|
+
function buildMerchantCardItems(products, res, options) {
|
|
4553
|
+
const { priceMap, limitMap } = getRandomGoodsMaps(res);
|
|
4554
|
+
const catOrder = { round: 0, normal: 1, weekend: 2 };
|
|
4555
|
+
const startY = 592;
|
|
4556
|
+
const cardHeight = 308;
|
|
4557
|
+
const gap = 43;
|
|
4558
|
+
const goodsAll = products.map((product) => {
|
|
4559
|
+
const limit = limitMap.get(product.name);
|
|
4560
|
+
const limitText = limit === void 0 || limit === null || limit === "" ? "--" : String(limit);
|
|
4561
|
+
const isHot = product.category !== "round";
|
|
4562
|
+
const isEnded = options.includeEnded ? !product.is_active : false;
|
|
4563
|
+
const roundPrefix = product.round_id ? `第${product.round_id}轮·` : "";
|
|
4564
|
+
const remainingStr = product.category === "normal" ? `本日限购${limitText}个` : product.category === "weekend" ? `活动期间限购${limitText}个` : `${isEnded ? roundPrefix : ""}本轮限购${limitText}个`;
|
|
4565
|
+
return {
|
|
4566
|
+
goods_name: product.name,
|
|
4567
|
+
iconUrl: product.image,
|
|
4568
|
+
price: priceMap.get(product.name) ?? 0,
|
|
4569
|
+
num: "",
|
|
4570
|
+
category: product.category,
|
|
4571
|
+
roundId: product.round_id || 0,
|
|
4572
|
+
isHot,
|
|
4573
|
+
isEnded,
|
|
4574
|
+
remainingStr,
|
|
4575
|
+
top: 0
|
|
4576
|
+
};
|
|
4577
|
+
});
|
|
4578
|
+
goodsAll.sort((a, b) => {
|
|
4579
|
+
if (options.includeEnded && a.isEnded !== b.isEnded) return a.isEnded ? 1 : -1;
|
|
4580
|
+
if (a.category !== b.category) return catOrder[a.category] - catOrder[b.category];
|
|
4581
|
+
return Number(b.price || 0) - Number(a.price || 0);
|
|
4582
|
+
});
|
|
4583
|
+
const goods = goodsAll.map((item, index) => ({
|
|
4584
|
+
...item,
|
|
4585
|
+
num: String(index + 1).padStart(2, "0"),
|
|
4586
|
+
top: startY + index * (cardHeight + gap)
|
|
4377
4587
|
}));
|
|
4378
|
-
const
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4588
|
+
const lastCardTop = goods.length > 0 ? goods[goods.length - 1].top : startY;
|
|
4589
|
+
const bottomFrameTop = lastCardTop + 287;
|
|
4590
|
+
const pageHeight = bottomFrameTop + 160;
|
|
4591
|
+
return { goods, bottomFrameTop, pageHeight };
|
|
4592
|
+
}
|
|
4593
|
+
__name(buildMerchantCardItems, "buildMerchantCardItems");
|
|
4594
|
+
function getCurrentMerchantProducts(products, roundInfo) {
|
|
4595
|
+
return products.filter((product) => {
|
|
4596
|
+
if (!product.is_active) return false;
|
|
4597
|
+
if (product.category === "round") return !!roundInfo.current && product.round_id === roundInfo.current;
|
|
4598
|
+
return true;
|
|
4599
|
+
});
|
|
4600
|
+
}
|
|
4601
|
+
__name(getCurrentMerchantProducts, "getCurrentMerchantProducts");
|
|
4602
|
+
function buildMerchantFallbackText(title, products, roundInfo) {
|
|
4603
|
+
const fallbackLines = [title];
|
|
4604
|
+
if (roundInfo) {
|
|
4605
|
+
fallbackLines.push(`轮次:第 ${roundInfo.current || TEXT.notOpen} / ${roundInfo.total} 轮`);
|
|
4606
|
+
fallbackLines.push(`剩余:${roundInfo.countdown}`);
|
|
4607
|
+
}
|
|
4608
|
+
fallbackLines.push("");
|
|
4609
|
+
if (!products.length) {
|
|
4610
|
+
fallbackLines.push(roundInfo ? "当前轮次暂无商品。" : "今日暂无已公布的远行商人商品。");
|
|
4611
|
+
return fallbackLines.join("\n").trimEnd();
|
|
4612
|
+
}
|
|
4613
|
+
for (const key of CATEGORY_ORDER) {
|
|
4614
|
+
const group = products.filter((product) => product.category === key);
|
|
4615
|
+
if (!group.length) continue;
|
|
4616
|
+
fallbackLines.push(`【${CATEGORY_LABELS[key]}】`);
|
|
4617
|
+
group.forEach((product, index) => {
|
|
4618
|
+
const tail = product.time_label ? ` (${product.time_label})` : "";
|
|
4619
|
+
fallbackLines.push(` ${index + 1}. ${product.name}${tail}`);
|
|
4620
|
+
});
|
|
4621
|
+
fallbackLines.push("");
|
|
4385
4622
|
}
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4623
|
+
return fallbackLines.join("\n").trimEnd();
|
|
4624
|
+
}
|
|
4625
|
+
__name(buildMerchantFallbackText, "buildMerchantFallbackText");
|
|
4626
|
+
function getMerchantActivityTitle(res) {
|
|
4627
|
+
const activity = getMerchantActivity(res);
|
|
4628
|
+
return String(activity?.name || TEXT.merchant).trim() || TEXT.merchant;
|
|
4629
|
+
}
|
|
4630
|
+
__name(getMerchantActivityTitle, "getMerchantActivityTitle");
|
|
4631
|
+
function getMerchantActivitySubtitle(res) {
|
|
4632
|
+
const activity = getMerchantActivity(res);
|
|
4633
|
+
return String(activity?.start_date || "每日 08:00 / 12:00 / 16:00 / 20:00 刷新").trim();
|
|
4634
|
+
}
|
|
4635
|
+
__name(getMerchantActivitySubtitle, "getMerchantActivitySubtitle");
|
|
4636
|
+
function getLegacyRoundGroups(now, roundInfo) {
|
|
4637
|
+
return ROUND_WINDOWS.map((win) => ({
|
|
4638
|
+
round_id: win.id,
|
|
4639
|
+
label: `${padNumber(win.startHour)}:00 - ${padNumber(win.endHour)}:00`,
|
|
4640
|
+
is_current: roundInfo.current === win.id,
|
|
4641
|
+
products: []
|
|
4390
4642
|
}));
|
|
4643
|
+
}
|
|
4644
|
+
__name(getLegacyRoundGroups, "getLegacyRoundGroups");
|
|
4645
|
+
function buildLegacyCategoryGroups(products, roundGroups) {
|
|
4646
|
+
return CATEGORY_ORDER.map((key) => {
|
|
4647
|
+
const groups = roundGroups.map((group) => ({
|
|
4648
|
+
round_id: group.round_id,
|
|
4649
|
+
label: group.label,
|
|
4650
|
+
is_current: group.is_current,
|
|
4651
|
+
products: group.products.filter((product) => product.category === key)
|
|
4652
|
+
})).filter((group) => group.products.length > 0);
|
|
4653
|
+
return {
|
|
4654
|
+
key,
|
|
4655
|
+
label: CATEGORY_LABELS[key],
|
|
4656
|
+
roundGroups: groups,
|
|
4657
|
+
product_count: groups.reduce((sum, group) => sum + group.products.length, 0)
|
|
4658
|
+
};
|
|
4659
|
+
}).filter((category) => category.product_count > 0);
|
|
4660
|
+
}
|
|
4661
|
+
__name(buildLegacyCategoryGroups, "buildLegacyCategoryGroups");
|
|
4662
|
+
function getMerchantDateStr(now = /* @__PURE__ */ new Date()) {
|
|
4663
|
+
const parts = getChinaParts(now);
|
|
4664
|
+
return `${parts.month}.${parts.day}`;
|
|
4665
|
+
}
|
|
4666
|
+
__name(getMerchantDateStr, "getMerchantDateStr");
|
|
4667
|
+
function buildMerchantRenderPayload(res, now = /* @__PURE__ */ new Date()) {
|
|
4668
|
+
const allProducts = normalizeMerchantProducts(res, now);
|
|
4669
|
+
const roundInfo = getCurrentMerchantRound(now);
|
|
4670
|
+
const products = getCurrentMerchantProducts(allProducts, roundInfo);
|
|
4671
|
+
const currentWindow = ROUND_WINDOWS.find((win) => win.id === roundInfo.current);
|
|
4672
|
+
const timeRange = currentWindow ? `${padNumber(currentWindow.startHour)}:00-${padNumber(currentWindow.endHour)}:00` : "--:--~--:--";
|
|
4673
|
+
const data = {
|
|
4674
|
+
dateStr: getMerchantDateStr(now),
|
|
4675
|
+
timeRange,
|
|
4676
|
+
...buildMerchantCardItems(products, res, { includeEnded: false })
|
|
4677
|
+
};
|
|
4678
|
+
const fallback = buildMerchantFallbackText(TEXT.merchant, products, roundInfo);
|
|
4679
|
+
return { products, roundInfo, data, fallback };
|
|
4680
|
+
}
|
|
4681
|
+
__name(buildMerchantRenderPayload, "buildMerchantRenderPayload");
|
|
4682
|
+
function buildLegacyMerchantRenderPayload(res, now = /* @__PURE__ */ new Date()) {
|
|
4683
|
+
const allProducts = normalizeMerchantProducts(res, now);
|
|
4684
|
+
const roundInfo = getCurrentMerchantRound(now);
|
|
4685
|
+
const products = getCurrentMerchantProducts(allProducts, roundInfo);
|
|
4686
|
+
const roundGroups = getLegacyRoundGroups(now, roundInfo);
|
|
4687
|
+
const currentGroup = roundGroups.find((group) => group.round_id === roundInfo.current) || roundGroups[0];
|
|
4688
|
+
currentGroup.products.push(...products);
|
|
4689
|
+
const categories = buildLegacyCategoryGroups(products, roundGroups);
|
|
4391
4690
|
const data = {
|
|
4392
4691
|
background: "",
|
|
4393
|
-
title:
|
|
4394
|
-
subtitle:
|
|
4395
|
-
titleIcon: true,
|
|
4396
|
-
product_count: renderedProducts.length,
|
|
4397
|
-
round_info: roundInfo,
|
|
4692
|
+
title: getMerchantActivityTitle(res),
|
|
4693
|
+
subtitle: getMerchantActivitySubtitle(res),
|
|
4398
4694
|
categories,
|
|
4399
|
-
|
|
4695
|
+
roundGroups,
|
|
4696
|
+
total_products: products.length
|
|
4400
4697
|
};
|
|
4401
|
-
const
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
}
|
|
4417
|
-
const fallback =
|
|
4418
|
-
return { products
|
|
4698
|
+
const fallback = buildMerchantFallbackText(TEXT.merchant, products, roundInfo);
|
|
4699
|
+
return { products, roundInfo, data, fallback };
|
|
4700
|
+
}
|
|
4701
|
+
__name(buildLegacyMerchantRenderPayload, "buildLegacyMerchantRenderPayload");
|
|
4702
|
+
function buildTodayMerchantRenderPayload(res, now = /* @__PURE__ */ new Date()) {
|
|
4703
|
+
const products = normalizeMerchantProducts(res, now).filter((product) => {
|
|
4704
|
+
const source = {
|
|
4705
|
+
start_time: product.start_time,
|
|
4706
|
+
end_time: product.end_time
|
|
4707
|
+
};
|
|
4708
|
+
return isMerchantItemToday(source, now);
|
|
4709
|
+
});
|
|
4710
|
+
const data = {
|
|
4711
|
+
dateStr: getMerchantDateStr(now),
|
|
4712
|
+
...buildMerchantCardItems(products, res, { includeEnded: true })
|
|
4713
|
+
};
|
|
4714
|
+
const fallback = buildMerchantFallbackText(`今日远行商人 (${getChinaDateText(now)})`, products);
|
|
4715
|
+
return { products, data, fallback };
|
|
4716
|
+
}
|
|
4717
|
+
__name(buildTodayMerchantRenderPayload, "buildTodayMerchantRenderPayload");
|
|
4718
|
+
function buildLegacyTodayMerchantRenderPayload(res, now = /* @__PURE__ */ new Date()) {
|
|
4719
|
+
const allProducts = normalizeMerchantProducts(res, now).filter((product) => {
|
|
4720
|
+
const source = {
|
|
4721
|
+
start_time: product.start_time,
|
|
4722
|
+
end_time: product.end_time
|
|
4723
|
+
};
|
|
4724
|
+
return isMerchantItemToday(source, now);
|
|
4725
|
+
});
|
|
4726
|
+
const roundInfo = getCurrentMerchantRound(now);
|
|
4727
|
+
const roundGroups = getLegacyRoundGroups(now, roundInfo);
|
|
4728
|
+
for (const product of allProducts) {
|
|
4729
|
+
const roundId = product.category === "round" ? product.round_id : null;
|
|
4730
|
+
const group = roundId ? roundGroups.find((item) => item.round_id === roundId) : roundGroups[0];
|
|
4731
|
+
if (group) group.products.push(product);
|
|
4732
|
+
}
|
|
4733
|
+
const categories = buildLegacyCategoryGroups(allProducts, roundGroups);
|
|
4734
|
+
const data = {
|
|
4735
|
+
background: "",
|
|
4736
|
+
title: TEXT.todayMerchant,
|
|
4737
|
+
subtitle: `${getChinaDateText(now)} · 每日 08:00 / 12:00 / 16:00 / 20:00 刷新`,
|
|
4738
|
+
categories,
|
|
4739
|
+
roundGroups,
|
|
4740
|
+
total_products: allProducts.length
|
|
4741
|
+
};
|
|
4742
|
+
const fallback = buildMerchantFallbackText(`今日远行商人 (${getChinaDateText(now)})`, allProducts);
|
|
4743
|
+
return { products: allProducts, data, fallback };
|
|
4419
4744
|
}
|
|
4420
|
-
__name(
|
|
4745
|
+
__name(buildLegacyTodayMerchantRenderPayload, "buildLegacyTodayMerchantRenderPayload");
|
|
4746
|
+
function useLegacyMerchantUi(deps) {
|
|
4747
|
+
return deps.config.merchantUiStyle === "old";
|
|
4748
|
+
}
|
|
4749
|
+
__name(useLegacyMerchantUi, "useLegacyMerchantUi");
|
|
4750
|
+
function buildConfiguredMerchantRenderPayload(deps, res, now = /* @__PURE__ */ new Date()) {
|
|
4751
|
+
const payload = useLegacyMerchantUi(deps) ? buildLegacyMerchantRenderPayload(res, now) : buildMerchantRenderPayload(res, now);
|
|
4752
|
+
return {
|
|
4753
|
+
...payload,
|
|
4754
|
+
templateName: useLegacyMerchantUi(deps) ? "yuanxing-shangren" : "yuanxing-shangren/merchant"
|
|
4755
|
+
};
|
|
4756
|
+
}
|
|
4757
|
+
__name(buildConfiguredMerchantRenderPayload, "buildConfiguredMerchantRenderPayload");
|
|
4758
|
+
function buildConfiguredTodayMerchantRenderPayload(deps, res, now = /* @__PURE__ */ new Date()) {
|
|
4759
|
+
const payload = useLegacyMerchantUi(deps) ? buildLegacyTodayMerchantRenderPayload(res, now) : buildTodayMerchantRenderPayload(res, now);
|
|
4760
|
+
return {
|
|
4761
|
+
...payload,
|
|
4762
|
+
templateName: useLegacyMerchantUi(deps) ? "yuanxing-shangren" : "yuanxing-shangren/today"
|
|
4763
|
+
};
|
|
4764
|
+
}
|
|
4765
|
+
__name(buildConfiguredTodayMerchantRenderPayload, "buildConfiguredTodayMerchantRenderPayload");
|
|
4421
4766
|
async function checkMerchantSubscriptions(deps) {
|
|
4422
4767
|
const { ctx, client, merchantSubMgr, renderer, config } = deps;
|
|
4423
4768
|
const res = await client.getMerchantInfo(ctx, true);
|
|
4424
4769
|
if (!res) return { subscriptions: 0, matched: 0, pushed: 0 };
|
|
4425
|
-
const { products, roundInfo, data, fallback } =
|
|
4770
|
+
const { products, roundInfo, data, fallback, templateName } = buildConfiguredMerchantRenderPayload(deps, res);
|
|
4426
4771
|
const productNames = products.map((p) => p.name || "").filter(Boolean);
|
|
4427
|
-
const rendered = await renderer.renderHtml(ctx,
|
|
4772
|
+
const rendered = await renderer.renderHtml(ctx, templateName, data);
|
|
4428
4773
|
const renderedImage = rendered ? compressPngImage(rendered, config) : null;
|
|
4429
4774
|
const subs = merchantSubMgr.getAll();
|
|
4430
4775
|
let matchedCount = 0;
|
|
@@ -4474,10 +4819,17 @@ function register3(deps) {
|
|
|
4474
4819
|
ctx.command(TEXT.merchant, "查看远行商人商品").action(async ({ session }) => {
|
|
4475
4820
|
const res = await client.getMerchantInfo(ctx, true);
|
|
4476
4821
|
if (!res) return `获取远行商人数据失败:${client.getLastErrorBrief()}`;
|
|
4477
|
-
const { data, fallback } =
|
|
4478
|
-
const png = await deps.renderer.renderHtml(ctx,
|
|
4822
|
+
const { data, fallback, templateName } = buildConfiguredMerchantRenderPayload(deps, res);
|
|
4823
|
+
const png = await deps.renderer.renderHtml(ctx, templateName, data);
|
|
4479
4824
|
await sendImageWithFallback(session, png, fallback, "merchant:yuanxing-shangren", deps.config);
|
|
4480
4825
|
});
|
|
4826
|
+
ctx.command(TEXT.todayMerchant, "查看今日远行商人全部商品").action(async ({ session }) => {
|
|
4827
|
+
const res = await client.getMerchantInfo(ctx, true);
|
|
4828
|
+
if (!res) return `获取今日远行商人数据失败:${client.getLastErrorBrief()}`;
|
|
4829
|
+
const { data, fallback, templateName } = buildConfiguredTodayMerchantRenderPayload(deps, res);
|
|
4830
|
+
const png = await deps.renderer.renderHtml(ctx, templateName, data);
|
|
4831
|
+
await sendImageWithFallback(session, png, fallback, "merchant:yuanxing-shangren:today", deps.config);
|
|
4832
|
+
});
|
|
4481
4833
|
ctx.command(`${TEXT.subscribe} [args:text]`, "订阅远行商人商品提醒").action(async ({ session }, args) => {
|
|
4482
4834
|
const target = getSubscriptionTarget(session);
|
|
4483
4835
|
if (!target.privateChat && !isBotAdmin2(session, config.adminUserIds)) return "此指令仅限管理员使用。";
|
|
@@ -5374,13 +5726,19 @@ var Config = import_koishi12.Schema.intersect([
|
|
|
5374
5726
|
import_koishi12.Schema.object({
|
|
5375
5727
|
merchantSubscriptionEnabled: import_koishi12.Schema.boolean().default(true).description("启用远行商人订阅"),
|
|
5376
5728
|
merchantSubscriptionItems: import_koishi12.Schema.array(String).default(["国王球", "棱镜球", "炫彩精灵蛋"]).description("默认订阅商品"),
|
|
5729
|
+
merchantUiStyle: import_koishi12.Schema.union(["new", "old"]).default("new").description("远行商人 UI 样式:new 为新版大图卡片,old 为旧版列表卡片"),
|
|
5377
5730
|
merchantCheckMode: import_koishi12.Schema.union(["interval", "times"]).default("interval").description("商人检查模式:interval 定期轮询 / times 指定时间点"),
|
|
5378
5731
|
merchantCheckInterval: import_koishi12.Schema.number().default(3e5).description("商人检查间隔,单位毫秒(仅 interval 模式生效)"),
|
|
5379
5732
|
merchantCheckTimes: import_koishi12.Schema.array(String).default([]).description("商人定时检查时间点(HH:MM 格式,如 08:00,仅 times 模式生效)"),
|
|
5380
5733
|
merchantPrivateSubscriptionEnabled: import_koishi12.Schema.boolean().default(true).description("允许个人私聊订阅远行商人推送"),
|
|
5381
5734
|
homeSubscriptionEnabled: import_koishi12.Schema.boolean().default(true).description("启用家园菜园和灵感订阅推送"),
|
|
5382
5735
|
homeSubscriptionIntervalMinutes: import_koishi12.Schema.number().default(5).description("家园订阅检查间隔,单位分钟")
|
|
5383
|
-
}).description("订阅推送设置")
|
|
5736
|
+
}).description("订阅推送设置"),
|
|
5737
|
+
import_koishi12.Schema.object({
|
|
5738
|
+
homeQueryWaitMs: import_koishi12.Schema.number().default(5e3).description("家园查询服务端同步等待毫秒(long-poll,超过此时间未出结果则转入排队)"),
|
|
5739
|
+
homeQueryPollIntervalMs: import_koishi12.Schema.number().default(3e3).description("家园查询进入排队后的轮询间隔,单位毫秒"),
|
|
5740
|
+
homeQueryTimeoutMs: import_koishi12.Schema.number().default(18e4).description("家园查询排队等候的总超时,单位毫秒,超时后提示稍后重试")
|
|
5741
|
+
}).description("家园查询排队设置")
|
|
5384
5742
|
]);
|
|
5385
5743
|
function apply(ctx, config) {
|
|
5386
5744
|
setupRoleTokenModel(ctx);
|