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/client.d.ts
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import { Context } from 'koishi';
|
|
2
|
+
export interface IngameTaskPollOptions {
|
|
3
|
+
/** 服务端同步等待毫秒(long-poll),这段时间内服务端会尽量直接返回结果而不入队 */
|
|
4
|
+
waitMs?: number;
|
|
5
|
+
/** 进入排队后轮询任务状态的间隔毫秒 */
|
|
6
|
+
intervalMs?: number;
|
|
7
|
+
/** 进入排队后等待任务完成的总超时毫秒 */
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
/** 任务进入排队(拿到 task_id)时回调一次,可用于向用户发送“排队中”提示 */
|
|
10
|
+
onQueued?: (taskId: string) => void | Promise<void>;
|
|
11
|
+
}
|
|
2
12
|
export declare class RocomClient {
|
|
3
13
|
private baseUrl;
|
|
4
14
|
private apiKey;
|
|
@@ -25,6 +35,12 @@ export declare class RocomClient {
|
|
|
25
35
|
private requestWithStatus;
|
|
26
36
|
private requestIngameWithFallback;
|
|
27
37
|
private getIngameTask;
|
|
38
|
+
private static normalizeTaskStatus;
|
|
39
|
+
private static extractTaskId;
|
|
40
|
+
private static isCompletedGatewayPayload;
|
|
41
|
+
private static extractCompletedIngamePayload;
|
|
42
|
+
private static taskErrorMessage;
|
|
43
|
+
private pollIngameTask;
|
|
28
44
|
qqQrLogin(ctx: Context, userIdentifier: string): Promise<any>;
|
|
29
45
|
qqQrStatus(ctx: Context, fwToken: string, userIdentifier: string): Promise<any>;
|
|
30
46
|
wechatQrLogin(ctx: Context, userIdentifier: string): Promise<any>;
|
|
@@ -77,7 +93,7 @@ export declare class RocomClient {
|
|
|
77
93
|
getEggExchangeSubscriptions(ctx: Context, userIdentifier?: string): Promise<any>;
|
|
78
94
|
deleteEggExchangeSubscription(ctx: Context, subscriptionId: string | number, userIdentifier?: string): Promise<any>;
|
|
79
95
|
getEggExchangeEvents(ctx: Context, subscriptionId: string | number, afterEventId?: string, limit?: number, userIdentifier?: string): Promise<any>;
|
|
80
|
-
ingameHomeInfo(ctx: Context, uid: string,
|
|
96
|
+
ingameHomeInfo(ctx: Context, uid: string, options?: IngameTaskPollOptions): Promise<any>;
|
|
81
97
|
ingameMerchantInfo(ctx: Context, shopId: string | number): Promise<any>;
|
|
82
98
|
getFriendship(ctx: Context, fwToken: string, userIds: string, userIdentifier?: string): Promise<any>;
|
|
83
99
|
getStudentState(ctx: Context, fwToken: string, accountType?: number, userIdentifier?: string): Promise<any>;
|
package/lib/client.js
CHANGED
|
@@ -3,6 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.RocomClient = void 0;
|
|
4
4
|
const koishi_1 = require("koishi");
|
|
5
5
|
const logger = new koishi_1.Logger('rocom-client');
|
|
6
|
+
// ingame 异步任务状态分类(来自服务端 /ingame/tasks/{task_id} 返回的 status 字段)
|
|
7
|
+
const INGAME_PENDING_STATUSES = ['queued', 'pending', 'running', 'processing', 'accepted'];
|
|
8
|
+
const INGAME_FAILED_STATUSES = ['failed', 'error', 'timeout', 'cancelled', 'canceled'];
|
|
9
|
+
const INGAME_COMPLETED_STATUSES = ['done', 'success', 'succeeded', 'completed', 'finished'];
|
|
6
10
|
class RocomClient {
|
|
7
11
|
baseUrl;
|
|
8
12
|
apiKey;
|
|
@@ -349,6 +353,112 @@ class RocomClient {
|
|
|
349
353
|
async getIngameTask(ctx, taskId, includeApiKey = true) {
|
|
350
354
|
return this.requestWithStatus(ctx, 'GET', `/api/v1/games/rocom/ingame/tasks/${taskId}`, this.wegameHeaders('', '', '', '', includeApiKey), { acceptedStatuses: [200, 202] });
|
|
351
355
|
}
|
|
356
|
+
static normalizeTaskStatus(value) {
|
|
357
|
+
return String(value ?? '').trim().toLowerCase();
|
|
358
|
+
}
|
|
359
|
+
static extractTaskId(payload) {
|
|
360
|
+
return String(payload?.task_id || payload?.taskId || payload?.taskID || '').trim();
|
|
361
|
+
}
|
|
362
|
+
// 判断一份 payload 是否为“已完成的业务结果”(而非排队占位)。
|
|
363
|
+
// 家园接口完成时返回 rows/home_info;玩家、商店完成时返回 source/title/rows。
|
|
364
|
+
static isCompletedGatewayPayload(payload) {
|
|
365
|
+
if (!payload || typeof payload !== 'object')
|
|
366
|
+
return false;
|
|
367
|
+
if (Array.isArray(payload.rows))
|
|
368
|
+
return true;
|
|
369
|
+
if (payload.home_info !== undefined)
|
|
370
|
+
return true;
|
|
371
|
+
if (payload.source !== undefined)
|
|
372
|
+
return true;
|
|
373
|
+
if (String(payload.title || '').trim())
|
|
374
|
+
return true;
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
static extractCompletedIngamePayload(payload) {
|
|
378
|
+
if (!payload || typeof payload !== 'object')
|
|
379
|
+
return null;
|
|
380
|
+
if (RocomClient.isCompletedGatewayPayload(payload))
|
|
381
|
+
return payload;
|
|
382
|
+
if (RocomClient.isCompletedGatewayPayload(payload.result))
|
|
383
|
+
return payload.result;
|
|
384
|
+
if (RocomClient.isCompletedGatewayPayload(payload.data))
|
|
385
|
+
return payload.data;
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
static taskErrorMessage(payload, fallbackStatus = '') {
|
|
389
|
+
const candidates = [
|
|
390
|
+
payload?.message, payload?.error, payload?.error_message, payload?.reason,
|
|
391
|
+
payload?.result?.message, payload?.result?.error, payload?.result?.error_message,
|
|
392
|
+
payload?.data?.message, payload?.data?.error,
|
|
393
|
+
];
|
|
394
|
+
for (const candidate of candidates) {
|
|
395
|
+
const text = String(candidate ?? '').trim();
|
|
396
|
+
if (text && text.toLowerCase() !== 'failed')
|
|
397
|
+
return text;
|
|
398
|
+
}
|
|
399
|
+
return String(payload?.status || fallbackStatus || '').trim() || '任务执行失败';
|
|
400
|
+
}
|
|
401
|
+
// 排队等候轮询:把 ingame 接口提交后返回的 202(task_id/status=queued)轮询到出结果或超时。
|
|
402
|
+
// 入参 first 是首请求拿到的 { status, data, usedApiKey }。
|
|
403
|
+
async pollIngameTask(ctx, first, options, labels) {
|
|
404
|
+
const { status, data, usedApiKey } = first;
|
|
405
|
+
// 服务端在 wait_ms 内已直接返回业务结果,或任务壳里已经带了完成结果。
|
|
406
|
+
const initialCompletedPayload = RocomClient.extractCompletedIngamePayload(data);
|
|
407
|
+
if (status !== null && initialCompletedPayload)
|
|
408
|
+
return initialCompletedPayload;
|
|
409
|
+
const taskId = RocomClient.extractTaskId(data);
|
|
410
|
+
if (!taskId) {
|
|
411
|
+
if (status === 202)
|
|
412
|
+
this.setLastError(labels.queuedNoTaskId);
|
|
413
|
+
// 既不是任务也不是已知完成结构:把原始 data 透传给上层兼容旧逻辑。
|
|
414
|
+
return status === null ? null : data;
|
|
415
|
+
}
|
|
416
|
+
// 已进入队列,回调通知“排队中”。
|
|
417
|
+
if (typeof options.onQueued === 'function') {
|
|
418
|
+
try {
|
|
419
|
+
await options.onQueued(taskId);
|
|
420
|
+
}
|
|
421
|
+
catch (e) {
|
|
422
|
+
logger.warn(`ingame 排队提示回调失败: ${e}`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
const intervalMs = Math.max(300, Number(options.intervalMs) || 3000);
|
|
426
|
+
const timeoutMs = Math.max(intervalMs, Number(options.timeoutMs) || 180000);
|
|
427
|
+
const startedAt = Date.now();
|
|
428
|
+
let lastStatus = RocomClient.normalizeTaskStatus(data?.status);
|
|
429
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
430
|
+
if (lastStatus && INGAME_FAILED_STATUSES.includes(lastStatus)) {
|
|
431
|
+
this.setLastError(RocomClient.taskErrorMessage(data, lastStatus));
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
await new Promise(resolve => setTimeout(resolve, intervalMs));
|
|
435
|
+
const task = await this.getIngameTask(ctx, taskId, usedApiKey);
|
|
436
|
+
if (task.status === null)
|
|
437
|
+
return null;
|
|
438
|
+
const completedPayload = RocomClient.extractCompletedIngamePayload(task.data);
|
|
439
|
+
if (completedPayload)
|
|
440
|
+
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
|
+
// 状态标记完成,但结果可能嵌套在 result/data 中。
|
|
447
|
+
if (INGAME_COMPLETED_STATUSES.includes(lastStatus)) {
|
|
448
|
+
this.setLastError(`Ingame 任务已完成但未返回可解析结果:${taskId}`);
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
if (lastStatus && !INGAME_PENDING_STATUSES.includes(lastStatus)) {
|
|
452
|
+
this.setLastError(`Ingame 任务状态异常:${lastStatus}`);
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
// 既无 task_id 也无状态,说明拿到的就是裸结果。
|
|
456
|
+
if (!RocomClient.extractTaskId(task.data) && !lastStatus)
|
|
457
|
+
return task.data;
|
|
458
|
+
}
|
|
459
|
+
this.setLastError(labels.stillQueued(taskId));
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
352
462
|
async qqQrLogin(ctx, userIdentifier) {
|
|
353
463
|
const params = { client_type: 'bot', client_id: 'koishi', provider: 'rocom' };
|
|
354
464
|
if (userIdentifier)
|
|
@@ -496,7 +606,10 @@ class RocomClient {
|
|
|
496
606
|
return this.get(ctx, '/api/v1/games/rocom/exchange/posters', this.wegameHeaders(fwToken, userIdentifier), params);
|
|
497
607
|
}
|
|
498
608
|
async getMerchantInfo(ctx, refresh = false) {
|
|
499
|
-
return this.get(ctx, '/api/v1/games/rocom/merchant/info', this.wegameHeaders(), {
|
|
609
|
+
return this.get(ctx, '/api/v1/games/rocom/merchant/info', this.wegameHeaders(), {
|
|
610
|
+
refresh: refresh ? 'true' : 'false',
|
|
611
|
+
random_goods: 'all',
|
|
612
|
+
});
|
|
500
613
|
}
|
|
501
614
|
async queryPetSize(ctx, diameter, weight, sameRideEgg = false, userIdentifier = '') {
|
|
502
615
|
const params = this.scopedParams({ diameter, weight }, userIdentifier);
|
|
@@ -562,33 +675,20 @@ class RocomClient {
|
|
|
562
675
|
params.after_event_id = afterEventId;
|
|
563
676
|
return this.get(ctx, '/api/v1/games/rocom/community/egg-exchange-events', this.wegameHeaders('', userIdentifier, 'bot', 'koishi'), this.scopedParams(params, userIdentifier));
|
|
564
677
|
}
|
|
565
|
-
async ingameHomeInfo(ctx, uid,
|
|
678
|
+
async ingameHomeInfo(ctx, uid, options = {}) {
|
|
566
679
|
const sanitizedUid = this.sanitizeUid(uid);
|
|
567
680
|
if (!sanitizedUid) {
|
|
568
681
|
this.setLastError('UID 不能为空');
|
|
569
682
|
return null;
|
|
570
683
|
}
|
|
684
|
+
const waitMs = Number(options.waitMs) || 5000;
|
|
571
685
|
const path = '/api/v1/games/rocom/ingame/home/info';
|
|
572
686
|
const payload = { uid: sanitizedUid, wait_ms: waitMs };
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
if (status === 202)
|
|
579
|
-
this.setLastError('家园查询任务已入队,但未返回 task_id');
|
|
580
|
-
return null;
|
|
581
|
-
}
|
|
582
|
-
for (let i = 0; i < 10; i++) {
|
|
583
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
584
|
-
const task = await this.getIngameTask(ctx, taskId, usedApiKey);
|
|
585
|
-
if (task.status === 200)
|
|
586
|
-
return task.data;
|
|
587
|
-
if (task.status === null)
|
|
588
|
-
return null;
|
|
589
|
-
}
|
|
590
|
-
this.setLastError(`Home query task is still queued, please retry later (task_id: ${taskId})`);
|
|
591
|
-
return null;
|
|
687
|
+
const first = await this.requestIngameWithFallback(ctx, path, payload);
|
|
688
|
+
return this.pollIngameTask(ctx, first, options, {
|
|
689
|
+
queuedNoTaskId: '家园查询任务已入队,但未返回 task_id',
|
|
690
|
+
stillQueued: (taskId) => `家园查询任务仍在排队,请稍后重试(task_id: ${taskId})`,
|
|
691
|
+
});
|
|
592
692
|
}
|
|
593
693
|
async ingameMerchantInfo(ctx, shopId) {
|
|
594
694
|
const params = { shop_id: shopId };
|