koishi-plugin-rocom 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.d.ts +53 -0
- package/lib/client.js +473 -0
- package/lib/commands/account.d.ts +5 -0
- package/lib/commands/account.js +205 -0
- package/lib/commands/admin.d.ts +2 -0
- package/lib/commands/admin.js +117 -0
- package/lib/commands/egg.d.ts +2 -0
- package/lib/commands/egg.js +196 -0
- package/lib/commands/merchant.d.ts +2 -0
- package/lib/commands/merchant.js +242 -0
- package/lib/commands/query.d.ts +2 -0
- package/lib/commands/query.js +1264 -0
- package/lib/commands/wiki.d.ts +2 -0
- package/lib/commands/wiki.js +11 -0
- package/lib/egg-service.d.ts +229 -0
- package/lib/egg-service.js +705 -0
- package/lib/index.d.ts +24 -0
- package/lib/index.js +3746 -0
- package/lib/render-templates/bind-list/index.html +51 -0
- package/lib/render-templates/bind-list/style.css +178 -0
- package/lib/render-templates/exchange-hall/css/_@astro-renderers.0KDkAyVb.css +1 -0
- package/lib/render-templates/exchange-hall/css/index.B3tv56V6.css +1 -0
- package/lib/render-templates/exchange-hall/css/index.D2LGPudy.css +1 -0
- package/lib/render-templates/exchange-hall/extracted.css +393 -0
- package/lib/render-templates/exchange-hall/index.html +99 -0
- package/lib/render-templates/exchange-hall/style.css +267 -0
- package/lib/render-templates/friendship/index.html +58 -0
- package/lib/render-templates/friendship/style.css +182 -0
- package/lib/render-templates/home/data/home_item_list.json +122 -0
- package/lib/render-templates/home/img/home_icon/100604.png +0 -0
- package/lib/render-templates/home/img/home_icon/100604_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100604_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100605.png +0 -0
- package/lib/render-templates/home/img/home_icon/100605_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100605_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100606.png +0 -0
- package/lib/render-templates/home/img/home_icon/100606_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100606_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100607.png +0 -0
- package/lib/render-templates/home/img/home_icon/100607_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100607_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100608.png +0 -0
- package/lib/render-templates/home/img/home_icon/100608_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100608_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100622.png +0 -0
- package/lib/render-templates/home/img/home_icon/100622_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100622_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100623.png +0 -0
- package/lib/render-templates/home/img/home_icon/100623_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100623_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100624.png +0 -0
- package/lib/render-templates/home/img/home_icon/100624_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100624_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100627.png +0 -0
- package/lib/render-templates/home/img/home_icon/100627_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100627_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100684.png +0 -0
- package/lib/render-templates/home/img/home_icon/100684_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100684_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100686.png +0 -0
- package/lib/render-templates/home/img/home_icon/100686_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100686_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100687.png +0 -0
- package/lib/render-templates/home/img/home_icon/100687_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100687_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100689.png +0 -0
- package/lib/render-templates/home/img/home_icon/100689_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100689_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100690.png +0 -0
- package/lib/render-templates/home/img/home_icon/100690_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100690_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100691.png +0 -0
- package/lib/render-templates/home/img/home_icon/100691_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100691_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100692.png +0 -0
- package/lib/render-templates/home/img/home_icon/100692_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100692_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100693.png +0 -0
- package/lib/render-templates/home/img/home_icon/100693_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100693_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100694.png +0 -0
- package/lib/render-templates/home/img/home_icon/100694_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100694_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100706.png +0 -0
- package/lib/render-templates/home/img/home_icon/100706_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100706_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100751.png +0 -0
- package/lib/render-templates/home/img/home_icon/100751_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100751_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100755.png +0 -0
- package/lib/render-templates/home/img/home_icon/100755_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100755_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100762.png +0 -0
- package/lib/render-templates/home/img/home_icon/100762_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100762_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100764.png +0 -0
- package/lib/render-templates/home/img/home_icon/100764_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100764_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100869.png +0 -0
- package/lib/render-templates/home/img/home_icon/100869_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100869_2.png +0 -0
- package/lib/render-templates/home/img/img_HomeVisit_Icon1.png +0 -0
- package/lib/render-templates/home/img/img_LevelReward_Bg2.png +0 -0
- package/lib/render-templates/home/index.html +139 -0
- package/lib/render-templates/home/style.css +537 -0
- package/lib/render-templates/ingame-shop/index.html +87 -0
- package/lib/render-templates/ingame-shop/style.css +220 -0
- package/lib/render-templates/inspect/index.html +47 -0
- package/lib/render-templates/inspect/style.css +149 -0
- package/lib/render-templates/lineup/index.html +77 -0
- package/lib/render-templates/lineup/style.css +255 -0
- package/lib/render-templates/lineup-detail/index.html +63 -0
- package/lib/render-templates/lineup-detail/style.css +218 -0
- package/lib/render-templates/menu/index.html +36 -0
- package/lib/render-templates/menu/style.css +126 -0
- package/lib/render-templates/package/index.html +115 -0
- package/lib/render-templates/package/style.css +352 -0
- package/lib/render-templates/personal-card/index.html +292 -0
- package/lib/render-templates/personal-card/style.css +2114 -0
- package/lib/render-templates/pet-wiki/index.html +118 -0
- package/lib/render-templates/pet-wiki/style.css +382 -0
- package/lib/render-templates/player-search/index.html +60 -0
- package/lib/render-templates/player-search/style.css +192 -0
- package/lib/render-templates/record/index.html +86 -0
- package/lib/render-templates/record/style.css +322 -0
- package/lib/render-templates/searcheggs/Pets.json +104328 -0
- package/lib/render-templates/searcheggs/candidates.html +52 -0
- package/lib/render-templates/searcheggs/eggs.py +599 -0
- package/lib/render-templates/searcheggs/index.html +198 -0
- package/lib/render-templates/searcheggs/pair.html +81 -0
- package/lib/render-templates/searcheggs/size.html +82 -0
- package/lib/render-templates/searcheggs/style.css +586 -0
- package/lib/render-templates/searcheggs/want.html +63 -0
- package/lib/render-templates/skill-wiki/index.html +68 -0
- package/lib/render-templates/skill-wiki/style.css +182 -0
- package/lib/render-templates/student/index.html +95 -0
- package/lib/render-templates/student/style.css +255 -0
- package/lib/render-templates/student-perks/index.html +78 -0
- package/lib/render-templates/student-perks/style.css +238 -0
- package/lib/render-templates/student-state/index.html +52 -0
- package/lib/render-templates/student-state/style.css +157 -0
- package/lib/render-templates/yuanxing-shangren/index.html +371 -0
- package/lib/render-templates/yuanxing-shangren/style.css +371 -0
- package/lib/render.d.ts +11 -0
- package/lib/render.js +226 -0
- package/lib/role-token.d.ts +27 -0
- package/lib/role-token.js +137 -0
- package/lib/send-image.d.ts +3 -0
- package/lib/send-image.js +135 -0
- package/lib/subscription-send.d.ts +8 -0
- package/lib/subscription-send.js +48 -0
- package/lib/types.d.ts +32 -0
- package/lib/types.js +2 -0
- package/lib/user.d.ts +67 -0
- package/lib/user.js +176 -0
- package/package.json +58 -0
- package/readme.md +575 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,3746 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name2 in all)
|
|
10
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
Config: () => Config,
|
|
34
|
+
apply: () => apply,
|
|
35
|
+
inject: () => inject,
|
|
36
|
+
name: () => name
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(src_exports);
|
|
39
|
+
var import_koishi11 = require("koishi");
|
|
40
|
+
var import_node_path5 = __toESM(require("node:path"));
|
|
41
|
+
|
|
42
|
+
// src/client.ts
|
|
43
|
+
var import_koishi = require("koishi");
|
|
44
|
+
var logger = new import_koishi.Logger("rocom-client");
|
|
45
|
+
var RocomClient = class {
|
|
46
|
+
static {
|
|
47
|
+
__name(this, "RocomClient");
|
|
48
|
+
}
|
|
49
|
+
baseUrl;
|
|
50
|
+
apiKey;
|
|
51
|
+
timeout;
|
|
52
|
+
lastError = "接口异常";
|
|
53
|
+
constructor(baseUrl, apiKey, timeout = 15e3) {
|
|
54
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
55
|
+
this.apiKey = apiKey;
|
|
56
|
+
this.timeout = timeout;
|
|
57
|
+
}
|
|
58
|
+
sanitizeUid(uid) {
|
|
59
|
+
if (!uid) return "";
|
|
60
|
+
return uid.trim().replace(/[^a-zA-Z0-9_\- \u4e00-\u9fa5]/g, "").trim();
|
|
61
|
+
}
|
|
62
|
+
wegameHeaders(fwToken = "", userIdentifier = "", clientType = "", clientId = "") {
|
|
63
|
+
const headers = {};
|
|
64
|
+
if (this.apiKey) headers["X-API-Key"] = this.apiKey;
|
|
65
|
+
if (fwToken) headers["X-Framework-Token"] = fwToken;
|
|
66
|
+
if (userIdentifier) headers["X-User-Identifier"] = this.sanitizeUid(userIdentifier);
|
|
67
|
+
if (clientType) headers["X-Client-Type"] = clientType;
|
|
68
|
+
if (clientId) headers["X-Client-ID"] = clientId;
|
|
69
|
+
return headers;
|
|
70
|
+
}
|
|
71
|
+
rocomHeaders(fwToken, userIdentifier = "") {
|
|
72
|
+
const headers = { "X-Framework-Token": fwToken };
|
|
73
|
+
if (this.apiKey) headers["X-API-Key"] = this.apiKey;
|
|
74
|
+
if (userIdentifier) headers["X-User-Identifier"] = this.sanitizeUid(userIdentifier);
|
|
75
|
+
return headers;
|
|
76
|
+
}
|
|
77
|
+
formatHttpError(e) {
|
|
78
|
+
const err = e;
|
|
79
|
+
const response = err?.response;
|
|
80
|
+
if (response) {
|
|
81
|
+
const body = response.data;
|
|
82
|
+
const bodyMessage = body && typeof body === "object" ? body.message || body.msg || body.error || JSON.stringify(body) : body;
|
|
83
|
+
const prefix = response.status ? `HTTP ${response.status}` : "HTTP error";
|
|
84
|
+
return bodyMessage ? `${prefix}: ${bodyMessage}` : `${prefix}: ${response.statusText || err?.message || "unknown"}`;
|
|
85
|
+
}
|
|
86
|
+
return err?.message || String(e);
|
|
87
|
+
}
|
|
88
|
+
isSensitiveLogKey(key) {
|
|
89
|
+
return /api[-_]?key|authorization|cookie|framework[-_]?token|password|secret|ticket|token/i.test(key);
|
|
90
|
+
}
|
|
91
|
+
maskSensitiveValue(value) {
|
|
92
|
+
if (typeof value !== "string") return value;
|
|
93
|
+
if (!value) return value;
|
|
94
|
+
if (value.length <= 8) return "***";
|
|
95
|
+
return `${value.slice(0, 4)}...${value.slice(-4)}`;
|
|
96
|
+
}
|
|
97
|
+
sanitizeForLog(value, key = "") {
|
|
98
|
+
if (this.isSensitiveLogKey(key)) return this.maskSensitiveValue(value);
|
|
99
|
+
if (!value || typeof value !== "object") return value;
|
|
100
|
+
if (Array.isArray(value)) return value.map((item) => this.sanitizeForLog(item));
|
|
101
|
+
const result = {};
|
|
102
|
+
for (const [entryKey, entryValue] of Object.entries(value)) {
|
|
103
|
+
result[entryKey] = this.sanitizeForLog(entryValue, entryKey);
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
headersForLog(headers) {
|
|
108
|
+
if (!headers || typeof headers !== "object") return headers;
|
|
109
|
+
const iterableHeaders = headers;
|
|
110
|
+
if (typeof iterableHeaders.forEach === "function") {
|
|
111
|
+
const result = {};
|
|
112
|
+
iterableHeaders.forEach((value, key) => {
|
|
113
|
+
result[key] = this.sanitizeForLog(value, key);
|
|
114
|
+
});
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
return this.sanitizeForLog(headers);
|
|
118
|
+
}
|
|
119
|
+
stringifyForLog(value) {
|
|
120
|
+
try {
|
|
121
|
+
return JSON.stringify(value, null, 2);
|
|
122
|
+
} catch {
|
|
123
|
+
return String(value);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
logRequestFailureDetails(method, path6, headers, params, body, errorOrResponse) {
|
|
127
|
+
const err = errorOrResponse;
|
|
128
|
+
const response = err?.response;
|
|
129
|
+
const details = {
|
|
130
|
+
request: {
|
|
131
|
+
method,
|
|
132
|
+
url: `${this.baseUrl}${path6}`,
|
|
133
|
+
path: path6,
|
|
134
|
+
params: this.sanitizeForLog(params),
|
|
135
|
+
body: this.sanitizeForLog(body),
|
|
136
|
+
headers: this.sanitizeForLog(headers),
|
|
137
|
+
timeout: this.timeout
|
|
138
|
+
},
|
|
139
|
+
response: response ? {
|
|
140
|
+
status: response.status,
|
|
141
|
+
statusText: response.statusText,
|
|
142
|
+
headers: this.headersForLog(response.headers),
|
|
143
|
+
body: this.sanitizeForLog(response.data)
|
|
144
|
+
} : void 0,
|
|
145
|
+
apiResponse: response ? void 0 : this.sanitizeForLog(errorOrResponse),
|
|
146
|
+
error: response || err?.message || err?.stack ? {
|
|
147
|
+
name: err?.name,
|
|
148
|
+
message: err?.message,
|
|
149
|
+
code: err?.code,
|
|
150
|
+
stack: err?.stack
|
|
151
|
+
} : void 0
|
|
152
|
+
};
|
|
153
|
+
console.warn(`[rocom-client] ${method} ${path6} detailed failure
|
|
154
|
+
${this.stringifyForLog(details)}`);
|
|
155
|
+
}
|
|
156
|
+
async get(ctx, path6, headers, params, options) {
|
|
157
|
+
try {
|
|
158
|
+
const resp = await ctx.http.get(`${this.baseUrl}${path6}`, {
|
|
159
|
+
headers,
|
|
160
|
+
params,
|
|
161
|
+
timeout: this.timeout
|
|
162
|
+
});
|
|
163
|
+
if (resp?.code !== 0) {
|
|
164
|
+
this.setLastError(resp?.message || "接口返回异常");
|
|
165
|
+
logger.warn(path6 + " error: " + (resp?.message || "unknown"));
|
|
166
|
+
if (!options?.silentFailureDetails) {
|
|
167
|
+
this.logRequestFailureDetails("GET", path6, headers, params, void 0, resp);
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
return resp?.data ?? {};
|
|
172
|
+
} catch (e) {
|
|
173
|
+
const message = this.formatHttpError(e);
|
|
174
|
+
this.setLastError(message);
|
|
175
|
+
if (!options?.silentFailureDetails) {
|
|
176
|
+
this.logRequestFailureDetails("GET", path6, headers, params, void 0, e);
|
|
177
|
+
}
|
|
178
|
+
const err = e;
|
|
179
|
+
if (err?.response) {
|
|
180
|
+
logger.warn("GET " + path6 + " failed: " + message);
|
|
181
|
+
} else {
|
|
182
|
+
logger.error("GET " + path6 + " failed: " + message);
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async post(ctx, path6, headers, json, params) {
|
|
188
|
+
try {
|
|
189
|
+
const resp = await ctx.http.post(`${this.baseUrl}${path6}`, json, {
|
|
190
|
+
headers,
|
|
191
|
+
params,
|
|
192
|
+
timeout: this.timeout
|
|
193
|
+
});
|
|
194
|
+
if (resp?.code !== 0) {
|
|
195
|
+
this.setLastError(resp?.message || "接口返回异常");
|
|
196
|
+
logger.warn(path6 + " error: " + (resp?.message || "unknown"));
|
|
197
|
+
this.logRequestFailureDetails("POST", path6, headers, params, json, resp);
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
return resp?.data ?? {};
|
|
201
|
+
} catch (e) {
|
|
202
|
+
const message = this.formatHttpError(e);
|
|
203
|
+
this.setLastError(message);
|
|
204
|
+
this.logRequestFailureDetails("POST", path6, headers, params, json, e);
|
|
205
|
+
const err = e;
|
|
206
|
+
if (err?.response) {
|
|
207
|
+
logger.warn("POST " + path6 + " failed: " + message);
|
|
208
|
+
} else {
|
|
209
|
+
logger.error("POST " + path6 + " failed: " + message);
|
|
210
|
+
}
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async delete(ctx, path6, headers) {
|
|
215
|
+
try {
|
|
216
|
+
const resp = await ctx.http("DELETE", `${this.baseUrl}${path6}`, { headers, timeout: this.timeout });
|
|
217
|
+
if (resp?.code !== 0) {
|
|
218
|
+
this.setLastError(resp?.message || "接口返回异常");
|
|
219
|
+
logger.warn(path6 + " error: " + (resp?.message || "unknown"));
|
|
220
|
+
this.logRequestFailureDetails("DELETE", path6, headers, void 0, void 0, resp);
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
return resp?.data ?? {};
|
|
224
|
+
} catch (e) {
|
|
225
|
+
const message = this.formatHttpError(e);
|
|
226
|
+
this.setLastError(message);
|
|
227
|
+
this.logRequestFailureDetails("DELETE", path6, headers, void 0, void 0, e);
|
|
228
|
+
const err = e;
|
|
229
|
+
if (err?.response) {
|
|
230
|
+
logger.warn("DELETE " + path6 + " failed: " + message);
|
|
231
|
+
} else {
|
|
232
|
+
logger.error("DELETE " + path6 + " failed: " + message);
|
|
233
|
+
}
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// Login and binding APIs.
|
|
238
|
+
async requestWithStatus(ctx, method, path6, headers, options = {}) {
|
|
239
|
+
const acceptedStatuses = options.acceptedStatuses || [200];
|
|
240
|
+
try {
|
|
241
|
+
const response = await ctx.http(method, `${this.baseUrl}${path6}`, {
|
|
242
|
+
headers,
|
|
243
|
+
params: options.params,
|
|
244
|
+
data: options.json,
|
|
245
|
+
timeout: this.timeout,
|
|
246
|
+
validateStatus: /* @__PURE__ */ __name(() => true, "validateStatus")
|
|
247
|
+
});
|
|
248
|
+
const status = Number(response?.status ?? response?.statusCode ?? 200);
|
|
249
|
+
const body = response?.data !== void 0 ? response.data : response;
|
|
250
|
+
if (body?.code !== void 0 && body.code !== 0) {
|
|
251
|
+
this.setLastError(body.message || body.msg || "接口返回异常");
|
|
252
|
+
this.logRequestFailureDetails(method, path6, headers, options.params, options.json, body);
|
|
253
|
+
return { status: null, data: null };
|
|
254
|
+
}
|
|
255
|
+
const data = body?.code !== void 0 ? body.data ?? {} : body ?? {};
|
|
256
|
+
if (!acceptedStatuses.includes(status)) {
|
|
257
|
+
this.setLastError(`HTTP ${status}`);
|
|
258
|
+
this.logRequestFailureDetails(method, path6, headers, options.params, options.json, response);
|
|
259
|
+
}
|
|
260
|
+
return { status, data };
|
|
261
|
+
} catch (e) {
|
|
262
|
+
const message = this.formatHttpError(e);
|
|
263
|
+
this.setLastError(message);
|
|
264
|
+
this.logRequestFailureDetails(method, path6, headers, options.params, options.json, e);
|
|
265
|
+
return { status: null, data: null };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
async getIngameTask(ctx, taskId) {
|
|
269
|
+
return this.requestWithStatus(
|
|
270
|
+
ctx,
|
|
271
|
+
"GET",
|
|
272
|
+
`/api/v1/games/rocom/ingame/tasks/${taskId}`,
|
|
273
|
+
this.wegameHeaders(),
|
|
274
|
+
{ acceptedStatuses: [200, 202] }
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
async qqQrLogin(ctx, userIdentifier) {
|
|
278
|
+
const params = { client_type: "bot", client_id: "koishi", provider: "rocom" };
|
|
279
|
+
if (userIdentifier) params.user_identifier = this.sanitizeUid(userIdentifier);
|
|
280
|
+
return this.get(ctx, "/api/v1/login/wegame/qr", this.wegameHeaders("", userIdentifier, "bot", "koishi"), params);
|
|
281
|
+
}
|
|
282
|
+
async qqQrStatus(ctx, fwToken, userIdentifier) {
|
|
283
|
+
const params = {};
|
|
284
|
+
if (userIdentifier) params.user_identifier = this.sanitizeUid(userIdentifier);
|
|
285
|
+
return this.get(ctx, "/api/v1/login/wegame/status", this.wegameHeaders(fwToken, userIdentifier, "bot", "koishi"), params);
|
|
286
|
+
}
|
|
287
|
+
async wechatQrLogin(ctx, userIdentifier) {
|
|
288
|
+
const params = { client_type: "bot", client_id: "koishi", provider: "rocom" };
|
|
289
|
+
if (userIdentifier) params.user_identifier = this.sanitizeUid(userIdentifier);
|
|
290
|
+
return this.get(ctx, "/api/v1/login/wegame/wechat/qr", this.wegameHeaders("", userIdentifier, "bot", "koishi"), params);
|
|
291
|
+
}
|
|
292
|
+
async wechatQrStatus(ctx, fwToken, userIdentifier) {
|
|
293
|
+
const params = {};
|
|
294
|
+
if (userIdentifier) params.user_identifier = this.sanitizeUid(userIdentifier);
|
|
295
|
+
return this.get(ctx, "/api/v1/login/wegame/wechat/status", this.wegameHeaders(fwToken, userIdentifier, "bot", "koishi"), params);
|
|
296
|
+
}
|
|
297
|
+
async importToken(ctx, tgpId, tgpTicket, userIdentifier) {
|
|
298
|
+
const body = { tgp_id: tgpId, tgp_ticket: tgpTicket, provider: "rocom", client_type: "bot", client_id: "koishi" };
|
|
299
|
+
if (userIdentifier) body.user_identifier = this.sanitizeUid(userIdentifier);
|
|
300
|
+
return this.post(ctx, "/api/v1/login/wegame/token", this.wegameHeaders("", userIdentifier, "bot", "koishi"), body);
|
|
301
|
+
}
|
|
302
|
+
async createBinding(ctx, fwToken, userIdentifier) {
|
|
303
|
+
const payload = { framework_token: fwToken, user_identifier: this.sanitizeUid(userIdentifier), client_type: "bot", client_id: "koishi" };
|
|
304
|
+
return this.post(ctx, "/api/v1/user/bindings", this.wegameHeaders("", userIdentifier, "bot", "koishi"), payload);
|
|
305
|
+
}
|
|
306
|
+
async refreshBinding(ctx, bindingId, userIdentifier) {
|
|
307
|
+
return this.post(ctx, `/api/v1/user/bindings/${bindingId}/refresh`, this.wegameHeaders("", userIdentifier), {});
|
|
308
|
+
}
|
|
309
|
+
async deleteBinding(ctx, bindingId, userIdentifier) {
|
|
310
|
+
const res = await this.delete(ctx, `/api/v1/user/bindings/${bindingId}`, this.wegameHeaders("", userIdentifier));
|
|
311
|
+
return res !== null;
|
|
312
|
+
}
|
|
313
|
+
// 洛克王国游戏数据接口
|
|
314
|
+
async getRole(ctx, fwToken, accountType, userIdentifier = "") {
|
|
315
|
+
const params = {};
|
|
316
|
+
if (accountType) params.account_type = accountType;
|
|
317
|
+
return this.get(ctx, "/api/v1/games/rocom/profile/role", this.rocomHeaders(fwToken, userIdentifier), params);
|
|
318
|
+
}
|
|
319
|
+
async getEvaluation(ctx, fwToken, userIdentifier = "") {
|
|
320
|
+
return this.get(
|
|
321
|
+
ctx,
|
|
322
|
+
"/api/v1/games/rocom/profile/evaluation",
|
|
323
|
+
this.rocomHeaders(fwToken, userIdentifier),
|
|
324
|
+
void 0,
|
|
325
|
+
{ silentFailureDetails: true }
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
getLastError(defaultMessage = "接口异常") {
|
|
329
|
+
return this.lastError || defaultMessage;
|
|
330
|
+
}
|
|
331
|
+
setLastError(message) {
|
|
332
|
+
this.lastError = message || "接口异常";
|
|
333
|
+
}
|
|
334
|
+
async getPetSummary(ctx, fwToken, userIdentifier = "") {
|
|
335
|
+
return this.get(ctx, "/api/v1/games/rocom/profile/pet-summary", this.rocomHeaders(fwToken, userIdentifier));
|
|
336
|
+
}
|
|
337
|
+
async getCollection(ctx, fwToken, userIdentifier = "") {
|
|
338
|
+
return this.get(ctx, "/api/v1/games/rocom/profile/collection", this.rocomHeaders(fwToken, userIdentifier));
|
|
339
|
+
}
|
|
340
|
+
async getBattleOverview(ctx, fwToken, userIdentifier = "") {
|
|
341
|
+
return this.get(ctx, "/api/v1/games/rocom/profile/battle-overview", this.rocomHeaders(fwToken, userIdentifier));
|
|
342
|
+
}
|
|
343
|
+
async getBattleList(ctx, fwToken, pageSize = 4, afterTime = "", userIdentifier = "") {
|
|
344
|
+
const params = { page_size: pageSize };
|
|
345
|
+
if (afterTime) params.after_time = afterTime;
|
|
346
|
+
return this.get(ctx, "/api/v1/games/rocom/battle/list", this.rocomHeaders(fwToken, userIdentifier), params);
|
|
347
|
+
}
|
|
348
|
+
isIngamePlayerPayload(data) {
|
|
349
|
+
if (!data || typeof data !== "object") return false;
|
|
350
|
+
return Boolean(
|
|
351
|
+
Array.isArray(data.rows) || Array.isArray(data.notes) || data.title || data.nickname || data.uid
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
async ingamePlayerSearch(ctx, uid) {
|
|
355
|
+
const sanitizedUid = this.sanitizeUid(uid);
|
|
356
|
+
if (!sanitizedUid) {
|
|
357
|
+
this.setLastError("UID 涓嶈兘涓虹┖");
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
const path6 = "/api/v1/games/rocom/ingame/player/search";
|
|
361
|
+
const headers = this.wegameHeaders();
|
|
362
|
+
const payload = { uid: sanitizedUid, wait_ms: 5e3 };
|
|
363
|
+
let { status, data } = await this.requestWithStatus(ctx, "POST", path6, headers, {
|
|
364
|
+
json: payload,
|
|
365
|
+
acceptedStatuses: [200, 202]
|
|
366
|
+
});
|
|
367
|
+
if (status === 200 && data && this.isIngamePlayerPayload(data)) return data;
|
|
368
|
+
if (status === null) {
|
|
369
|
+
const fallback = await this.requestWithStatus(ctx, "GET", path6, headers, {
|
|
370
|
+
params: payload,
|
|
371
|
+
acceptedStatuses: [200, 202]
|
|
372
|
+
});
|
|
373
|
+
status = fallback.status;
|
|
374
|
+
data = fallback.data;
|
|
375
|
+
if (status === 200 && data && this.isIngamePlayerPayload(data)) return data;
|
|
376
|
+
}
|
|
377
|
+
if (!data) return null;
|
|
378
|
+
if (this.isIngamePlayerPayload(data)) return data;
|
|
379
|
+
const taskId = data.task_id || data.taskId || data.taskID;
|
|
380
|
+
if (!taskId) {
|
|
381
|
+
if (status === 202) this.setLastError("玩家搜索任务已入队,但未返回 task_id");
|
|
382
|
+
return data;
|
|
383
|
+
}
|
|
384
|
+
for (let i = 0; i < 8; i++) {
|
|
385
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
386
|
+
const task = await this.getIngameTask(ctx, taskId);
|
|
387
|
+
if (task.status === 200) return task.data;
|
|
388
|
+
if (task.status === null) return null;
|
|
389
|
+
}
|
|
390
|
+
this.setLastError("Player search task is still queued, please retry later (task_id: " + taskId + ")");
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
async getPets(ctx, fwToken, petSubset = 0, pageNo = 1, pageSize = 10, userIdentifier = "") {
|
|
394
|
+
const params = { pet_subset: petSubset, page_no: pageNo, page_size: pageSize };
|
|
395
|
+
return this.get(ctx, "/api/v1/games/rocom/battle/pets", this.rocomHeaders(fwToken, userIdentifier), params);
|
|
396
|
+
}
|
|
397
|
+
async getLineupList(ctx, fwToken, pageNo = 1, category = "", userIdentifier = "") {
|
|
398
|
+
const params = { page_no: pageNo };
|
|
399
|
+
if (category) params.category = category;
|
|
400
|
+
return this.get(ctx, "/api/v1/games/rocom/lineup/list", this.rocomHeaders(fwToken, userIdentifier), params);
|
|
401
|
+
}
|
|
402
|
+
async getExchangePosters(ctx, fwToken, pageNo = 1, userIdentifier = "") {
|
|
403
|
+
const params = { page_no: Math.max(pageNo, 1), refresh: "false" };
|
|
404
|
+
return this.get(ctx, "/api/v1/games/rocom/exchange/posters", this.wegameHeaders(fwToken, userIdentifier), params);
|
|
405
|
+
}
|
|
406
|
+
async getMerchantInfo(ctx, refresh = false) {
|
|
407
|
+
return this.get(ctx, "/api/v1/games/rocom/merchant/info", this.wegameHeaders(), { refresh: refresh ? "true" : "false" });
|
|
408
|
+
}
|
|
409
|
+
async queryPetSize(ctx, diameter, weight) {
|
|
410
|
+
return this.get(ctx, "/api/v1/games/rocom/pet/size-query", this.wegameHeaders(), { diameter, weight });
|
|
411
|
+
}
|
|
412
|
+
async ingameHomeInfo(ctx, uid, waitMs = 5e3) {
|
|
413
|
+
const sanitizedUid = this.sanitizeUid(uid);
|
|
414
|
+
if (!sanitizedUid) {
|
|
415
|
+
this.setLastError("UID 涓嶈兘涓虹┖");
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
const path6 = "/api/v1/games/rocom/ingame/home/info";
|
|
419
|
+
const headers = this.wegameHeaders();
|
|
420
|
+
const payload = { uid: sanitizedUid, wait_ms: waitMs };
|
|
421
|
+
let { status, data } = await this.requestWithStatus(ctx, "POST", path6, headers, {
|
|
422
|
+
json: payload,
|
|
423
|
+
acceptedStatuses: [200, 202]
|
|
424
|
+
});
|
|
425
|
+
if (status === 200 && data && !(data.task_id || data.taskId || data.taskID)) return data;
|
|
426
|
+
if (status === null) {
|
|
427
|
+
const fallback = await this.requestWithStatus(ctx, "GET", path6, headers, {
|
|
428
|
+
params: payload,
|
|
429
|
+
acceptedStatuses: [200, 202]
|
|
430
|
+
});
|
|
431
|
+
status = fallback.status;
|
|
432
|
+
data = fallback.data;
|
|
433
|
+
if (status === 200 && data && !(data.task_id || data.taskId || data.taskID)) return data;
|
|
434
|
+
}
|
|
435
|
+
const taskId = data?.task_id || data?.taskId || data?.taskID;
|
|
436
|
+
if (!taskId) {
|
|
437
|
+
if (status === 202) this.setLastError("家园查询任务已入队,但未返回 task_id");
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
for (let i = 0; i < 10; i++) {
|
|
441
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
442
|
+
const task = await this.getIngameTask(ctx, taskId);
|
|
443
|
+
if (task.status === 200) return task.data;
|
|
444
|
+
if (task.status === null) return null;
|
|
445
|
+
}
|
|
446
|
+
this.setLastError(`Home query task is still queued, please retry later (task_id: ${taskId})`);
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
async ingameMerchantInfo(ctx, shopId) {
|
|
450
|
+
const params = { shop_id: shopId };
|
|
451
|
+
const data = await this.get(
|
|
452
|
+
ctx,
|
|
453
|
+
"/api/v1/games/rocom/ingame/merchant/info",
|
|
454
|
+
this.wegameHeaders(),
|
|
455
|
+
params,
|
|
456
|
+
{ silentFailureDetails: true }
|
|
457
|
+
);
|
|
458
|
+
if (data) return data;
|
|
459
|
+
return this.post(ctx, "/api/v1/games/rocom/ingame/merchant/info", this.wegameHeaders(), params);
|
|
460
|
+
}
|
|
461
|
+
async getFriendship(ctx, fwToken, userIds, userIdentifier = "") {
|
|
462
|
+
return this.get(
|
|
463
|
+
ctx,
|
|
464
|
+
"/api/v1/games/rocom/social/friendship",
|
|
465
|
+
this.rocomHeaders(fwToken, userIdentifier),
|
|
466
|
+
{ user_ids: userIds }
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
async getStudentState(ctx, fwToken, accountType, userIdentifier = "") {
|
|
470
|
+
const params = {};
|
|
471
|
+
if (accountType !== void 0) params.account_type = accountType;
|
|
472
|
+
return this.get(ctx, "/api/v1/games/rocom/activity/student-state", this.rocomHeaders(fwToken, userIdentifier), params);
|
|
473
|
+
}
|
|
474
|
+
async getStudentPerks(ctx, fwToken, area, accountType, userIdentifier = "") {
|
|
475
|
+
const params = {};
|
|
476
|
+
if (area !== void 0) params.area = area;
|
|
477
|
+
if (accountType !== void 0) params.account_type = accountType;
|
|
478
|
+
return this.get(ctx, "/api/v1/games/rocom/activity/perks", this.rocomHeaders(fwToken, userIdentifier), params);
|
|
479
|
+
}
|
|
480
|
+
async searchWikiPet(ctx, query, limit = 10) {
|
|
481
|
+
return this.get(ctx, "/api/v1/games/rocom/wiki/pet", this.wegameHeaders(), { q: query, limit });
|
|
482
|
+
}
|
|
483
|
+
async searchWikiSkill(ctx, query, limit = 10) {
|
|
484
|
+
return this.get(ctx, "/api/v1/games/rocom/wiki/skill", this.wegameHeaders(), { q: query, limit });
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
// src/user.ts
|
|
489
|
+
var import_koishi2 = require("koishi");
|
|
490
|
+
var import_node_fs = __toESM(require("node:fs"));
|
|
491
|
+
var import_node_path = __toESM(require("node:path"));
|
|
492
|
+
var logger2 = new import_koishi2.Logger("rocom-user");
|
|
493
|
+
var JsonStore = class {
|
|
494
|
+
static {
|
|
495
|
+
__name(this, "JsonStore");
|
|
496
|
+
}
|
|
497
|
+
filePath;
|
|
498
|
+
data;
|
|
499
|
+
constructor(dataDir, filename, defaultData) {
|
|
500
|
+
if (!import_node_fs.default.existsSync(dataDir)) import_node_fs.default.mkdirSync(dataDir, { recursive: true });
|
|
501
|
+
this.filePath = import_node_path.default.join(dataDir, filename);
|
|
502
|
+
this.data = this.load(defaultData);
|
|
503
|
+
}
|
|
504
|
+
load(defaultData) {
|
|
505
|
+
if (import_node_fs.default.existsSync(this.filePath)) {
|
|
506
|
+
try {
|
|
507
|
+
return JSON.parse(import_node_fs.default.readFileSync(this.filePath, "utf-8"));
|
|
508
|
+
} catch (e) {
|
|
509
|
+
logger2.error(`加载 ${this.filePath} 失败: ${e}`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return JSON.parse(JSON.stringify(defaultData));
|
|
513
|
+
}
|
|
514
|
+
save() {
|
|
515
|
+
try {
|
|
516
|
+
const tmp = this.filePath + ".tmp";
|
|
517
|
+
import_node_fs.default.writeFileSync(tmp, JSON.stringify(this.data, null, 2), "utf-8");
|
|
518
|
+
import_node_fs.default.renameSync(tmp, this.filePath);
|
|
519
|
+
} catch (e) {
|
|
520
|
+
logger2.error(`保存 ${this.filePath} 失败: ${e}`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
get() {
|
|
524
|
+
return this.data;
|
|
525
|
+
}
|
|
526
|
+
set(data) {
|
|
527
|
+
this.data = data;
|
|
528
|
+
this.save();
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
var UserManager = class {
|
|
532
|
+
static {
|
|
533
|
+
__name(this, "UserManager");
|
|
534
|
+
}
|
|
535
|
+
store;
|
|
536
|
+
constructor(dataDir) {
|
|
537
|
+
this.store = new JsonStore(dataDir, "rocom_bindings.json", {});
|
|
538
|
+
}
|
|
539
|
+
getUserBindings(userId) {
|
|
540
|
+
return [...this.store.get()[userId] || []];
|
|
541
|
+
}
|
|
542
|
+
getPrimaryBinding(userId) {
|
|
543
|
+
const bindings = this.getUserBindings(userId);
|
|
544
|
+
return bindings.find((b) => b.is_primary) || bindings[0] || null;
|
|
545
|
+
}
|
|
546
|
+
saveUserBindings(userId, bindings) {
|
|
547
|
+
const seen = /* @__PURE__ */ new Set();
|
|
548
|
+
const cleaned = bindings.map((binding) => {
|
|
549
|
+
const { framework_token: _frameworkToken, ...rest } = binding;
|
|
550
|
+
return rest;
|
|
551
|
+
}).filter((binding) => {
|
|
552
|
+
const id = binding.binding_id || binding.role_id;
|
|
553
|
+
if (seen.has(id)) return false;
|
|
554
|
+
seen.add(id);
|
|
555
|
+
return true;
|
|
556
|
+
});
|
|
557
|
+
if (cleaned.length > 0 && !cleaned.some((b) => b.is_primary)) {
|
|
558
|
+
cleaned[0].is_primary = true;
|
|
559
|
+
}
|
|
560
|
+
const data = this.store.get();
|
|
561
|
+
data[userId] = cleaned;
|
|
562
|
+
this.store.set(data);
|
|
563
|
+
}
|
|
564
|
+
addBinding(userId, binding) {
|
|
565
|
+
const existing = this.getUserBindings(userId);
|
|
566
|
+
existing.forEach((b) => b.is_primary = false);
|
|
567
|
+
binding.is_primary = true;
|
|
568
|
+
existing.push(binding);
|
|
569
|
+
this.saveUserBindings(userId, existing);
|
|
570
|
+
}
|
|
571
|
+
deleteUserBinding(userId, index) {
|
|
572
|
+
const bindings = this.getUserBindings(userId);
|
|
573
|
+
if (index < 1 || index > bindings.length) return null;
|
|
574
|
+
const removed = bindings.splice(index - 1, 1)[0];
|
|
575
|
+
this.saveUserBindings(userId, bindings);
|
|
576
|
+
return removed;
|
|
577
|
+
}
|
|
578
|
+
switchPrimary(userId, index) {
|
|
579
|
+
const bindings = this.getUserBindings(userId);
|
|
580
|
+
if (index < 1 || index > bindings.length) return false;
|
|
581
|
+
bindings.forEach((b, i) => b.is_primary = i + 1 === index);
|
|
582
|
+
this.saveUserBindings(userId, bindings);
|
|
583
|
+
return true;
|
|
584
|
+
}
|
|
585
|
+
removeBindingById(userId, bindingId) {
|
|
586
|
+
const data = this.store.get();
|
|
587
|
+
const bindings = data[userId] || [];
|
|
588
|
+
const filtered = bindings.filter((b) => b.binding_id !== bindingId);
|
|
589
|
+
if (filtered.length < bindings.length) {
|
|
590
|
+
data[userId] = filtered;
|
|
591
|
+
this.store.set(data);
|
|
592
|
+
return true;
|
|
593
|
+
}
|
|
594
|
+
return false;
|
|
595
|
+
}
|
|
596
|
+
getAllUsersBindings() {
|
|
597
|
+
return JSON.parse(JSON.stringify(this.store.get()));
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
var MerchantSubscriptionManager = class {
|
|
601
|
+
static {
|
|
602
|
+
__name(this, "MerchantSubscriptionManager");
|
|
603
|
+
}
|
|
604
|
+
store;
|
|
605
|
+
constructor(dataDir) {
|
|
606
|
+
this.store = new JsonStore(dataDir, "rocom_merchant_subscriptions.json", {});
|
|
607
|
+
}
|
|
608
|
+
upsert(key, sub) {
|
|
609
|
+
const data = this.store.get();
|
|
610
|
+
data[key] = { ...sub };
|
|
611
|
+
this.store.set(data);
|
|
612
|
+
}
|
|
613
|
+
get(key) {
|
|
614
|
+
return this.store.get()[key] || null;
|
|
615
|
+
}
|
|
616
|
+
delete(key) {
|
|
617
|
+
const data = this.store.get();
|
|
618
|
+
if (!(key in data)) return false;
|
|
619
|
+
delete data[key];
|
|
620
|
+
this.store.set(data);
|
|
621
|
+
return true;
|
|
622
|
+
}
|
|
623
|
+
getAll() {
|
|
624
|
+
return JSON.parse(JSON.stringify(this.store.get()));
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
var HomeSubscriptionManager = class {
|
|
628
|
+
static {
|
|
629
|
+
__name(this, "HomeSubscriptionManager");
|
|
630
|
+
}
|
|
631
|
+
store;
|
|
632
|
+
constructor(dataDir) {
|
|
633
|
+
this.store = new JsonStore(dataDir, "rocom_home_subscriptions.json", {});
|
|
634
|
+
}
|
|
635
|
+
upsert(key, sub) {
|
|
636
|
+
const data = this.store.get();
|
|
637
|
+
data[key] = { ...sub };
|
|
638
|
+
this.store.set(data);
|
|
639
|
+
}
|
|
640
|
+
deleteMatching(target, kind = "", uid = "") {
|
|
641
|
+
const data = this.store.get();
|
|
642
|
+
let deleted = 0;
|
|
643
|
+
for (const [key, sub] of Object.entries(data)) {
|
|
644
|
+
const sameTarget = sub.platform === target.platform && (sub.channel_id || "") === (target.channelId || "") && (sub.user_id || "") === (target.userId || "");
|
|
645
|
+
if (!sameTarget) continue;
|
|
646
|
+
if (kind && sub.kind !== kind) continue;
|
|
647
|
+
if (uid && sub.uid !== uid) continue;
|
|
648
|
+
delete data[key];
|
|
649
|
+
deleted++;
|
|
650
|
+
}
|
|
651
|
+
if (deleted) this.store.set(data);
|
|
652
|
+
return deleted;
|
|
653
|
+
}
|
|
654
|
+
getAll() {
|
|
655
|
+
return JSON.parse(JSON.stringify(this.store.get()));
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
// src/egg-service.ts
|
|
660
|
+
var import_koishi3 = require("koishi");
|
|
661
|
+
var import_node_fs2 = __toESM(require("node:fs"));
|
|
662
|
+
var import_node_path2 = __toESM(require("node:path"));
|
|
663
|
+
var logger3 = new import_koishi3.Logger("rocom-egg");
|
|
664
|
+
var EGG_GROUP_META = {
|
|
665
|
+
1: { label: "未发现", desc: "不能和任何精灵生蛋" },
|
|
666
|
+
2: { label: "怪兽", desc: "像怪兽一样的动物" },
|
|
667
|
+
3: { label: "两栖", desc: "两栖动物和水边生活的多栖动物" },
|
|
668
|
+
4: { label: "虫", desc: "看起来像虫子的精灵" },
|
|
669
|
+
5: { label: "飞行", desc: "会飞的精灵" },
|
|
670
|
+
6: { label: "陆上", desc: "生活在陆地上的精灵" },
|
|
671
|
+
7: { label: "妖精", desc: "可爱的小动物" },
|
|
672
|
+
8: { label: "植物", desc: "看起来像植物的精灵" },
|
|
673
|
+
9: { label: "人型", desc: "看起来像人的精灵" },
|
|
674
|
+
10: { label: "软体", desc: "看起来软软的精灵" },
|
|
675
|
+
11: { label: "矿物", desc: "身体由矿物组成的精灵" },
|
|
676
|
+
12: { label: "不定形", desc: "没有固定形态的精灵" },
|
|
677
|
+
13: { label: "鱼", desc: "看起来像鱼的精灵" },
|
|
678
|
+
14: { label: "龙", desc: "看起来像龙的精灵" },
|
|
679
|
+
15: { label: "机械", desc: "身体由机械组成的精灵" }
|
|
680
|
+
};
|
|
681
|
+
function getEggGroupLabel(id) {
|
|
682
|
+
return EGG_GROUP_META[id]?.label || `蛋组${id}`;
|
|
683
|
+
}
|
|
684
|
+
__name(getEggGroupLabel, "getEggGroupLabel");
|
|
685
|
+
function formatEggGroups(ids) {
|
|
686
|
+
if (!ids?.length) return "暂无蛋组数据";
|
|
687
|
+
return ids.map(getEggGroupLabel).join(" / ");
|
|
688
|
+
}
|
|
689
|
+
__name(formatEggGroups, "formatEggGroups");
|
|
690
|
+
function petName(p) {
|
|
691
|
+
return p?.localized?.zh?.name || p?.name || "???";
|
|
692
|
+
}
|
|
693
|
+
__name(petName, "petName");
|
|
694
|
+
function petType(p) {
|
|
695
|
+
const parts = [];
|
|
696
|
+
const mt = p?.main_type?.localized?.zh;
|
|
697
|
+
if (mt) parts.push(mt);
|
|
698
|
+
const st = p?.sub_type?.localized?.zh;
|
|
699
|
+
if (st) parts.push(st);
|
|
700
|
+
return parts.join(" / ") || "未知";
|
|
701
|
+
}
|
|
702
|
+
__name(petType, "petType");
|
|
703
|
+
function fmtDur(s) {
|
|
704
|
+
if (!s || s <= 0) return "暂无数据";
|
|
705
|
+
if (s % 86400 === 0) return `${s / 86400} 天`;
|
|
706
|
+
const h3 = s / 3600;
|
|
707
|
+
return h3 === Math.floor(h3) ? `${h3} 小时` : `${h3.toFixed(1)} 小时`;
|
|
708
|
+
}
|
|
709
|
+
__name(fmtDur, "fmtDur");
|
|
710
|
+
function wt(v) {
|
|
711
|
+
return v != null ? Math.round(v / 1e3 * 10) / 10 : null;
|
|
712
|
+
}
|
|
713
|
+
__name(wt, "wt");
|
|
714
|
+
function ht(v) {
|
|
715
|
+
return v != null ? Math.round(v) / 100 : null;
|
|
716
|
+
}
|
|
717
|
+
__name(ht, "ht");
|
|
718
|
+
function fmtRange(lo, hi, u) {
|
|
719
|
+
if (lo == null && hi == null) return "暂无数据";
|
|
720
|
+
if (lo != null && hi != null) return lo === hi ? `${lo}${u}` : `${lo}-${hi}${u}`;
|
|
721
|
+
return `${lo ?? hi}${u}`;
|
|
722
|
+
}
|
|
723
|
+
__name(fmtRange, "fmtRange");
|
|
724
|
+
function num(value) {
|
|
725
|
+
const parsed = Number(value);
|
|
726
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
727
|
+
}
|
|
728
|
+
__name(num, "num");
|
|
729
|
+
function formatNumber(value, digits = 2) {
|
|
730
|
+
const parsed = num(value);
|
|
731
|
+
if (parsed == null) return "";
|
|
732
|
+
return String(Number(parsed.toFixed(digits)));
|
|
733
|
+
}
|
|
734
|
+
__name(formatNumber, "formatNumber");
|
|
735
|
+
function assetPetId(petId) {
|
|
736
|
+
const n = Number(petId);
|
|
737
|
+
if (isNaN(n)) return null;
|
|
738
|
+
return n >= 3e3 ? n : n + 3e3;
|
|
739
|
+
}
|
|
740
|
+
__name(assetPetId, "assetPetId");
|
|
741
|
+
function petIconUrl(petId) {
|
|
742
|
+
const aid = assetPetId(petId);
|
|
743
|
+
return aid ? `https://game.gtimg.cn/images/rocom/rocodata/jingling/${aid}/icon.png` : "";
|
|
744
|
+
}
|
|
745
|
+
__name(petIconUrl, "petIconUrl");
|
|
746
|
+
function petImageUrl(petId) {
|
|
747
|
+
const aid = assetPetId(petId);
|
|
748
|
+
return aid ? `https://game.gtimg.cn/images/rocom/rocodata/jingling/${aid}/image.png` : "";
|
|
749
|
+
}
|
|
750
|
+
__name(petImageUrl, "petImageUrl");
|
|
751
|
+
var EggService = class {
|
|
752
|
+
static {
|
|
753
|
+
__name(this, "EggService");
|
|
754
|
+
}
|
|
755
|
+
pets = [];
|
|
756
|
+
byId = /* @__PURE__ */ new Map();
|
|
757
|
+
byZh = /* @__PURE__ */ new Map();
|
|
758
|
+
byEn = /* @__PURE__ */ new Map();
|
|
759
|
+
constructor(dataDir) {
|
|
760
|
+
this.load(dataDir);
|
|
761
|
+
}
|
|
762
|
+
load(dataDir) {
|
|
763
|
+
const filePath = import_node_path2.default.join(dataDir, "Pets.json");
|
|
764
|
+
if (!import_node_fs2.default.existsSync(filePath)) {
|
|
765
|
+
logger3.error(`Pets.json 不存在: ${filePath}`);
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
try {
|
|
769
|
+
const raw = JSON.parse(import_node_fs2.default.readFileSync(filePath, "utf-8"));
|
|
770
|
+
this.pets = Array.isArray(raw) ? raw : [];
|
|
771
|
+
for (const p of this.pets) {
|
|
772
|
+
this.byId.set(p.id, p);
|
|
773
|
+
const zh = p.localized?.zh?.name;
|
|
774
|
+
if (zh) this.byZh.set(zh, p);
|
|
775
|
+
const en = p.name?.toLowerCase();
|
|
776
|
+
if (en) this.byEn.set(en, p);
|
|
777
|
+
}
|
|
778
|
+
logger3.info(`加载 ${this.pets.length} 只精灵`);
|
|
779
|
+
} catch (e) {
|
|
780
|
+
logger3.error(`加载 Pets.json 失败: ${e}`);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
getEggGroups(pet) {
|
|
784
|
+
return pet?.breeding_profile?.egg_groups || [];
|
|
785
|
+
}
|
|
786
|
+
search(keyword) {
|
|
787
|
+
const kw = keyword.trim();
|
|
788
|
+
if (!kw) return { matchType: "not_found" };
|
|
789
|
+
if (this.byZh.has(kw)) return { matchType: "exact", pet: this.byZh.get(kw) };
|
|
790
|
+
const pid = Number(kw);
|
|
791
|
+
if (!isNaN(pid) && this.byId.has(pid)) return { matchType: "exact", pet: this.byId.get(pid) };
|
|
792
|
+
if (this.byEn.has(kw.toLowerCase())) return { matchType: "exact", pet: this.byEn.get(kw.toLowerCase()) };
|
|
793
|
+
const kwLower = kw.toLowerCase();
|
|
794
|
+
const hits = this.pets.filter((p) => {
|
|
795
|
+
const zh = p.localized?.zh?.name || "";
|
|
796
|
+
const en = p.name || "";
|
|
797
|
+
return zh.toLowerCase().includes(kwLower) || en.toLowerCase().includes(kwLower);
|
|
798
|
+
});
|
|
799
|
+
if (hits.length === 1) return { matchType: "fuzzy", pet: hits[0] };
|
|
800
|
+
if (hits.length > 1) return { matchType: "multi", candidates: hits.slice(0, 20) };
|
|
801
|
+
return { matchType: "not_found" };
|
|
802
|
+
}
|
|
803
|
+
searchBySize(height, weight) {
|
|
804
|
+
const perfect = [], range = [];
|
|
805
|
+
for (const p of this.pets) {
|
|
806
|
+
const br = p.breeding || {};
|
|
807
|
+
let hMatch = null, wMatch = null;
|
|
808
|
+
if (height != null) {
|
|
809
|
+
const hLo = br.height_low, hHi = br.height_high;
|
|
810
|
+
if (hLo != null && hHi != null) {
|
|
811
|
+
if (hLo <= height && height <= hHi) hMatch = "perfect";
|
|
812
|
+
else if (hLo * 0.85 <= height && height <= hHi * 1.15) hMatch = "range";
|
|
813
|
+
else hMatch = "none";
|
|
814
|
+
} else hMatch = "none";
|
|
815
|
+
}
|
|
816
|
+
if (weight != null) {
|
|
817
|
+
const wLo = br.weight_low, wHi = br.weight_high;
|
|
818
|
+
if (wLo != null && wHi != null) {
|
|
819
|
+
const wKgLo = wLo / 1e3, wKgHi = wHi / 1e3;
|
|
820
|
+
if (wKgLo <= weight && weight <= wKgHi) wMatch = "perfect";
|
|
821
|
+
else if (wKgLo * 0.85 <= weight && weight <= wKgHi * 1.15) wMatch = "range";
|
|
822
|
+
else wMatch = "none";
|
|
823
|
+
} else wMatch = "none";
|
|
824
|
+
}
|
|
825
|
+
if (height != null && weight != null) {
|
|
826
|
+
if (hMatch === "perfect" && wMatch === "perfect") perfect.push(p);
|
|
827
|
+
else if (hMatch !== "none" && wMatch !== "none") range.push(p);
|
|
828
|
+
} else if (height != null) {
|
|
829
|
+
if (hMatch === "perfect") perfect.push(p);
|
|
830
|
+
else if (hMatch === "range") range.push(p);
|
|
831
|
+
} else if (weight != null) {
|
|
832
|
+
if (wMatch === "perfect") perfect.push(p);
|
|
833
|
+
else if (wMatch === "range") range.push(p);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return { perfect: perfect.slice(0, 20), range: range.slice(0, 20) };
|
|
837
|
+
}
|
|
838
|
+
formatMatchSummary(probability, matchCount) {
|
|
839
|
+
const parts = [];
|
|
840
|
+
if (probability != null) parts.push(`匹配率 ${formatNumber(probability)}%`);
|
|
841
|
+
if (matchCount != null) parts.push(`命中次数 ${formatNumber(matchCount, 0)}`);
|
|
842
|
+
return parts.join(" / ");
|
|
843
|
+
}
|
|
844
|
+
calcLocalMatchInfo(queryHeight, queryWeight, heightMin, heightMax, weightMin, weightMax) {
|
|
845
|
+
const scores = [];
|
|
846
|
+
if (queryHeight != null) {
|
|
847
|
+
const score = this.rangeMatchScore(ht(queryHeight), heightMin, heightMax);
|
|
848
|
+
if (score != null) scores.push(score);
|
|
849
|
+
}
|
|
850
|
+
if (queryWeight != null) {
|
|
851
|
+
const score = this.rangeMatchScore(queryWeight, weightMin, weightMax);
|
|
852
|
+
if (score != null) scores.push(score);
|
|
853
|
+
}
|
|
854
|
+
if (!scores.length) return { probability: null, matchCount: null };
|
|
855
|
+
return {
|
|
856
|
+
probability: scores.reduce((sum, score) => sum + score, 0) / scores.length,
|
|
857
|
+
matchCount: scores.length
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
rangeMatchScore(value, low, high) {
|
|
861
|
+
const valueNum = num(value);
|
|
862
|
+
const lowNum = num(low);
|
|
863
|
+
const highNum = num(high);
|
|
864
|
+
if (valueNum == null || lowNum == null || highNum == null) return null;
|
|
865
|
+
if (lowNum <= valueNum && valueNum <= highNum) return 100;
|
|
866
|
+
let tolerance;
|
|
867
|
+
let distance;
|
|
868
|
+
if (valueNum < lowNum) {
|
|
869
|
+
tolerance = Math.max(lowNum * 0.15, 1e-4);
|
|
870
|
+
distance = lowNum - valueNum;
|
|
871
|
+
} else {
|
|
872
|
+
tolerance = Math.max(highNum * 0.15, 1e-4);
|
|
873
|
+
distance = valueNum - highNum;
|
|
874
|
+
}
|
|
875
|
+
if (distance > tolerance) return 0;
|
|
876
|
+
return Math.max(0, 100 * (1 - distance / tolerance));
|
|
877
|
+
}
|
|
878
|
+
formatPetCard(pet, queryHeight, queryWeight) {
|
|
879
|
+
const br = pet?.breeding || {};
|
|
880
|
+
const eggGroups = this.getEggGroups(pet);
|
|
881
|
+
const heightMin = ht(br.height_low);
|
|
882
|
+
const heightMax = ht(br.height_high);
|
|
883
|
+
const weightMin = wt(br.weight_low);
|
|
884
|
+
const weightMax = wt(br.weight_high);
|
|
885
|
+
const { probability, matchCount } = this.calcLocalMatchInfo(
|
|
886
|
+
queryHeight,
|
|
887
|
+
queryWeight,
|
|
888
|
+
heightMin,
|
|
889
|
+
heightMax,
|
|
890
|
+
weightMin,
|
|
891
|
+
weightMax
|
|
892
|
+
);
|
|
893
|
+
return {
|
|
894
|
+
id: pet?.id,
|
|
895
|
+
name: petName(pet),
|
|
896
|
+
icon: petIconUrl(pet?.id),
|
|
897
|
+
image: petImageUrl(pet?.id),
|
|
898
|
+
type_label: petType(pet),
|
|
899
|
+
egg_group_ids: eggGroups,
|
|
900
|
+
egg_groups_label: formatEggGroups(eggGroups),
|
|
901
|
+
height_min: heightMin,
|
|
902
|
+
height_max: heightMax,
|
|
903
|
+
height_label: fmtRange(heightMin, heightMax, "m"),
|
|
904
|
+
weight_min: weightMin,
|
|
905
|
+
weight_max: weightMax,
|
|
906
|
+
weight_label: fmtRange(weightMin, weightMax, "kg"),
|
|
907
|
+
probability,
|
|
908
|
+
match_count: matchCount,
|
|
909
|
+
match_info_label: this.formatMatchSummary(probability, matchCount)
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
formatSizeApiCard(item) {
|
|
913
|
+
const probability = num(item?.probability);
|
|
914
|
+
const matchCount = num(item?.matchCount);
|
|
915
|
+
const heightMin = num(item?.diameterMin);
|
|
916
|
+
const heightMax = num(item?.diameterMax);
|
|
917
|
+
const weightMin = num(item?.weightMin);
|
|
918
|
+
const weightMax = num(item?.weightMax);
|
|
919
|
+
return {
|
|
920
|
+
id: item?.petId || "-",
|
|
921
|
+
name: item?.pet || "未知精灵",
|
|
922
|
+
icon: item?.petIcon || petIconUrl(item?.petId),
|
|
923
|
+
image: item?.petImage || petImageUrl(item?.petId),
|
|
924
|
+
type_label: "后端未提供",
|
|
925
|
+
egg_group_ids: [],
|
|
926
|
+
egg_groups_label: "后端未提供",
|
|
927
|
+
height_min: heightMin,
|
|
928
|
+
height_max: heightMax,
|
|
929
|
+
height_label: fmtRange(heightMin, heightMax, "m"),
|
|
930
|
+
weight_min: weightMin,
|
|
931
|
+
weight_max: weightMax,
|
|
932
|
+
weight_label: fmtRange(weightMin, weightMax, "kg"),
|
|
933
|
+
probability,
|
|
934
|
+
match_count: matchCount,
|
|
935
|
+
match_info_label: this.formatMatchSummary(probability, matchCount)
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
mergeCardsByName(perfect, ranged) {
|
|
939
|
+
const perfectMap = /* @__PURE__ */ new Map();
|
|
940
|
+
const rangedMap = /* @__PURE__ */ new Map();
|
|
941
|
+
const keyOf = /* @__PURE__ */ __name((item) => String(item?.name || item?.id || "").replace(/\s+/g, ""), "keyOf");
|
|
942
|
+
const add = /* @__PURE__ */ __name((target, item) => {
|
|
943
|
+
const key = keyOf(item);
|
|
944
|
+
if (!key) return;
|
|
945
|
+
target.set(key, target.has(key) ? this.mergeSizeCard(target.get(key), item) : item);
|
|
946
|
+
}, "add");
|
|
947
|
+
for (const item of perfect) add(perfectMap, item);
|
|
948
|
+
for (const item of ranged) {
|
|
949
|
+
const key = keyOf(item);
|
|
950
|
+
if (key && perfectMap.has(key)) {
|
|
951
|
+
perfectMap.set(key, this.mergeSizeCard(perfectMap.get(key), item));
|
|
952
|
+
} else {
|
|
953
|
+
add(rangedMap, item);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return [[...perfectMap.values()], [...rangedMap.values()]];
|
|
957
|
+
}
|
|
958
|
+
mergeSizeCard(left, right) {
|
|
959
|
+
const unique = /* @__PURE__ */ __name((values) => [...new Set(values.filter((value) => value != null && value !== "").map((value) => String(value)))], "unique");
|
|
960
|
+
const ids = unique([...String(left?.id || "").split("/"), ...String(right?.id || "").split("/")].map((value) => value.trim().replace(/^#/, "")));
|
|
961
|
+
const eggGroupIds = unique([...left?.egg_group_ids || [], ...right?.egg_group_ids || []]).map(Number);
|
|
962
|
+
const probabilityValues = [num(left?.probability), num(right?.probability)].filter((value) => value != null);
|
|
963
|
+
const matchCountValues = [num(left?.match_count), num(right?.match_count)].filter((value) => value != null);
|
|
964
|
+
const heightMin = this.minValue(left?.height_min, right?.height_min);
|
|
965
|
+
const heightMax = this.maxValue(left?.height_max, right?.height_max);
|
|
966
|
+
const weightMin = this.minValue(left?.weight_min, right?.weight_min);
|
|
967
|
+
const weightMax = this.maxValue(left?.weight_max, right?.weight_max);
|
|
968
|
+
const probability = probabilityValues.length ? probabilityValues.reduce((sum, value) => sum + value, 0) : null;
|
|
969
|
+
const matchCount = matchCountValues.length ? matchCountValues.reduce((sum, value) => sum + value, 0) : null;
|
|
970
|
+
return {
|
|
971
|
+
...left,
|
|
972
|
+
id: ids.join("/"),
|
|
973
|
+
egg_group_ids: eggGroupIds,
|
|
974
|
+
egg_groups_label: eggGroupIds.length ? formatEggGroups(eggGroupIds) : unique([left?.egg_groups_label, right?.egg_groups_label]).join(" / ") || left?.egg_groups_label,
|
|
975
|
+
probability,
|
|
976
|
+
match_count: matchCount,
|
|
977
|
+
match_info_label: this.formatMatchSummary(probability, matchCount),
|
|
978
|
+
height_min: heightMin,
|
|
979
|
+
height_max: heightMax,
|
|
980
|
+
height_label: fmtRange(heightMin, heightMax, "m"),
|
|
981
|
+
weight_min: weightMin,
|
|
982
|
+
weight_max: weightMax,
|
|
983
|
+
weight_label: fmtRange(weightMin, weightMax, "kg")
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
minValue(...values) {
|
|
987
|
+
const numbers = values.map(num).filter((value) => value != null);
|
|
988
|
+
return numbers.length ? Math.min(...numbers) : null;
|
|
989
|
+
}
|
|
990
|
+
maxValue(...values) {
|
|
991
|
+
const numbers = values.map(num).filter((value) => value != null);
|
|
992
|
+
return numbers.length ? Math.max(...numbers) : null;
|
|
993
|
+
}
|
|
994
|
+
getCompatiblePets(pet) {
|
|
995
|
+
const groups = new Set(this.getEggGroups(pet));
|
|
996
|
+
if (!groups.size || groups.has(1)) return [];
|
|
997
|
+
return this.pets.filter((o) => {
|
|
998
|
+
if (o.id === pet.id) return false;
|
|
999
|
+
const og = new Set(this.getEggGroups(o));
|
|
1000
|
+
if (!og.size || og.has(1)) return false;
|
|
1001
|
+
for (const g of groups) if (og.has(g)) return true;
|
|
1002
|
+
return false;
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
getBreedingParents(pet) {
|
|
1006
|
+
const eggGroups = new Set(this.getEggGroups(pet));
|
|
1007
|
+
if (!eggGroups.size || eggGroups.has(1)) return [];
|
|
1008
|
+
return this.pets.filter((o) => {
|
|
1009
|
+
if (o.id === pet.id) return false;
|
|
1010
|
+
const og = new Set(this.getEggGroups(o));
|
|
1011
|
+
if (!og.size || og.has(1)) return false;
|
|
1012
|
+
for (const g of eggGroups) if (og.has(g)) return true;
|
|
1013
|
+
return false;
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
evaluatePair(a, b) {
|
|
1017
|
+
const ga = new Set(this.getEggGroups(a)), gb = new Set(this.getEggGroups(b));
|
|
1018
|
+
const shared = [...ga].filter((g) => gb.has(g)).sort();
|
|
1019
|
+
const reasons = [];
|
|
1020
|
+
if (!ga.size) reasons.push(`${petName(a)} 暂无蛋组数据`);
|
|
1021
|
+
if (!gb.size) reasons.push(`${petName(b)} 暂无蛋组数据`);
|
|
1022
|
+
if (ga.has(1)) reasons.push(`${petName(a)} 属于「未发现」蛋组`);
|
|
1023
|
+
if (gb.has(1)) reasons.push(`${petName(b)} 属于「未发现」蛋组`);
|
|
1024
|
+
if (!shared.length && !reasons.length) reasons.push("蛋组不相同,无法配种");
|
|
1025
|
+
const br = a.breeding || {};
|
|
1026
|
+
return {
|
|
1027
|
+
compatible: !reasons.length && shared.length > 0,
|
|
1028
|
+
reasons,
|
|
1029
|
+
shared_egg_groups: shared,
|
|
1030
|
+
shared_egg_group_labels: shared.map(getEggGroupLabel),
|
|
1031
|
+
hatch_label: fmtDur(br.hatch_data),
|
|
1032
|
+
weight_label: fmtRange(wt(br.weight_low), wt(br.weight_high), "kg"),
|
|
1033
|
+
height_label: fmtRange(br.height_low, br.height_high, "cm")
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
// ─── 文本构建 ───
|
|
1037
|
+
buildSizeSearchText(height, weight, results, heightDisplay) {
|
|
1038
|
+
const cond = [];
|
|
1039
|
+
if (height != null) cond.push(`身高=${heightDisplay || fmtRange(ht(height), ht(height), "m")}`);
|
|
1040
|
+
if (weight != null) cond.push(`体重=${weight}kg`);
|
|
1041
|
+
const condStr = cond.join(" + ");
|
|
1042
|
+
if (!results || !results.perfect.length && !results.range.length) return `❌ 未找到符合 ${condStr} 的精灵。`;
|
|
1043
|
+
const lines = [];
|
|
1044
|
+
if (results.perfect.length) {
|
|
1045
|
+
lines.push(`✅ 完美匹配 ${condStr} 的精灵(共 ${results.perfect.length} 只):`);
|
|
1046
|
+
results.perfect.slice(0, 10).forEach((p, i) => {
|
|
1047
|
+
const br = p.breeding || {};
|
|
1048
|
+
lines.push(` ${i + 1}. ${petName(p)} (#${p.id}) — ${fmtRange(ht(br.height_low), ht(br.height_high), "m")} / ${fmtRange(wt(br.weight_low), wt(br.weight_high), "kg")} · ${formatEggGroups(this.getEggGroups(p))}`);
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
if (results.range.length) {
|
|
1052
|
+
if (lines.length) lines.push("");
|
|
1053
|
+
lines.push(`🔍 范围匹配 ${condStr} 的精灵(共 ${results.range.length} 只,容差±15%):`);
|
|
1054
|
+
results.range.slice(0, 10).forEach((p, i) => {
|
|
1055
|
+
const br = p.breeding || {};
|
|
1056
|
+
lines.push(` ${i + 1}. ${petName(p)} (#${p.id}) — ${fmtRange(ht(br.height_low), ht(br.height_high), "m")} / ${fmtRange(wt(br.weight_low), wt(br.weight_high), "kg")} · ${formatEggGroups(this.getEggGroups(p))}`);
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
return lines.join("\n");
|
|
1060
|
+
}
|
|
1061
|
+
buildSizeSearchTextFromApi(height, weight, results, heightDisplay) {
|
|
1062
|
+
const cond = [];
|
|
1063
|
+
if (height != null) cond.push(`身高=${heightDisplay || fmtRange(ht(height), ht(height), "m")}`);
|
|
1064
|
+
if (weight != null) cond.push(`体重=${weight}kg`);
|
|
1065
|
+
const condStr = cond.join(" + ") || "当前条件";
|
|
1066
|
+
const exact = results?.exactResults || [];
|
|
1067
|
+
const candidates = results?.candidates || [];
|
|
1068
|
+
if (!exact.length && !candidates.length) return `❌ 未找到符合 ${condStr} 的精灵。`;
|
|
1069
|
+
const lines = [];
|
|
1070
|
+
if (exact.length) {
|
|
1071
|
+
lines.push(`✅ 完美匹配 ${condStr} 的精灵(共 ${exact.length} 只):`);
|
|
1072
|
+
exact.slice(0, 10).forEach((item, i) => {
|
|
1073
|
+
const card = this.formatSizeApiCard(item);
|
|
1074
|
+
lines.push(` ${i + 1}. ${card.name} (#${card.id}) — ${card.height_label} / ${card.weight_label}`);
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
if (candidates.length) {
|
|
1078
|
+
if (lines.length) lines.push("");
|
|
1079
|
+
lines.push(`🔍 范围匹配 ${condStr} 的精灵(共 ${candidates.length} 只):`);
|
|
1080
|
+
candidates.slice(0, 10).forEach((item, i) => {
|
|
1081
|
+
const card = this.formatSizeApiCard(item);
|
|
1082
|
+
lines.push(` ${i + 1}. ${card.name} (#${card.id}) — ${card.height_label} / ${card.weight_label}`);
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
return lines.join("\n");
|
|
1086
|
+
}
|
|
1087
|
+
buildSearchText(pet) {
|
|
1088
|
+
const egs = this.getEggGroups(pet);
|
|
1089
|
+
const compat = this.getCompatiblePets(pet);
|
|
1090
|
+
const lines = [
|
|
1091
|
+
`🥚 ${petName(pet)} (#${pet.id})`,
|
|
1092
|
+
`属性:${petType(pet)}`,
|
|
1093
|
+
`蛋组:${formatEggGroups(egs)}`,
|
|
1094
|
+
`可配种精灵数:${compat.length}`
|
|
1095
|
+
];
|
|
1096
|
+
if (egs.includes(1)) lines.push("⚠️ 该精灵属于「未发现」蛋组,无法配种。");
|
|
1097
|
+
return lines.join("\n");
|
|
1098
|
+
}
|
|
1099
|
+
buildCandidatesText(keyword, candidates) {
|
|
1100
|
+
const lines = [`🔍 「${keyword}」匹配到 ${candidates.length} 只精灵,请精确输入:`];
|
|
1101
|
+
candidates.slice(0, 10).forEach((p, i) => {
|
|
1102
|
+
lines.push(` ${i + 1}. ${petName(p)} (#${p.id}) — ${petType(p)} · ${formatEggGroups(this.getEggGroups(p))}`);
|
|
1103
|
+
});
|
|
1104
|
+
if (candidates.length > 10) lines.push(` ... 还有 ${candidates.length - 10} 个结果`);
|
|
1105
|
+
return lines.join("\n");
|
|
1106
|
+
}
|
|
1107
|
+
buildWantPetText(pet) {
|
|
1108
|
+
const zh = petName(pet);
|
|
1109
|
+
const egs = this.getEggGroups(pet);
|
|
1110
|
+
const lines = [`🥚 想要孵出「${zh}」:`, `蛋组:${formatEggGroups(egs)}`];
|
|
1111
|
+
if (egs.includes(1)) {
|
|
1112
|
+
lines.push("⚠️ 该精灵属于「未发现」蛋组,无法通过配种获得。");
|
|
1113
|
+
return lines.join("\n");
|
|
1114
|
+
}
|
|
1115
|
+
lines.push(`
|
|
1116
|
+
📌 母体必须是「${zh}」(孵蛋结果跟随母体)`);
|
|
1117
|
+
const fathers = this.getBreedingParents(pet);
|
|
1118
|
+
if (fathers.length) {
|
|
1119
|
+
lines.push(`
|
|
1120
|
+
🔗 可选父体(共 ${fathers.length} 只):`);
|
|
1121
|
+
fathers.slice(0, 15).forEach((f, i) => {
|
|
1122
|
+
lines.push(` ${i + 1}. ${petName(f)} — ${formatEggGroups(this.getEggGroups(f))}`);
|
|
1123
|
+
});
|
|
1124
|
+
if (fathers.length > 15) lines.push(` ... 还有 ${fathers.length - 15} 只`);
|
|
1125
|
+
} else {
|
|
1126
|
+
lines.push("\n❌ 未找到可配种的父体精灵。");
|
|
1127
|
+
}
|
|
1128
|
+
return lines.join("\n");
|
|
1129
|
+
}
|
|
1130
|
+
buildPairText(a, b) {
|
|
1131
|
+
const ev = this.evaluatePair(a, b);
|
|
1132
|
+
const ma = petName(a), fa = petName(b);
|
|
1133
|
+
if (ev.compatible) {
|
|
1134
|
+
return `✅ 父体 ${fa} × 母体 ${ma} 可以配种!
|
|
1135
|
+
共享蛋组:${ev.shared_egg_group_labels.join(" / ")}
|
|
1136
|
+
孵出结果:${ma}(跟随母体)
|
|
1137
|
+
孵化时长:${ev.hatch_label}`;
|
|
1138
|
+
}
|
|
1139
|
+
return `❌ ${fa} × ${ma} 无法配种。
|
|
1140
|
+
原因:${ev.reasons.join(";")}`;
|
|
1141
|
+
}
|
|
1142
|
+
// ─── 渲染数据构建 ───
|
|
1143
|
+
buildSearchData(pet) {
|
|
1144
|
+
const egs = this.getEggGroups(pet);
|
|
1145
|
+
const compat = this.getCompatiblePets(pet);
|
|
1146
|
+
const gmap = {};
|
|
1147
|
+
for (const gid of egs) if (gid !== 1) gmap[gid] = [];
|
|
1148
|
+
for (const c of compat) {
|
|
1149
|
+
for (const gid of egs) {
|
|
1150
|
+
if (gid !== 1 && this.getEggGroups(c).includes(gid)) {
|
|
1151
|
+
gmap[gid] = gmap[gid] || [];
|
|
1152
|
+
gmap[gid].push(c);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
const sections = egs.map((gid) => {
|
|
1157
|
+
const meta = EGG_GROUP_META[gid];
|
|
1158
|
+
const members = gmap[gid] || [];
|
|
1159
|
+
return {
|
|
1160
|
+
id: gid,
|
|
1161
|
+
label: meta?.label || `蛋组${gid}`,
|
|
1162
|
+
desc: meta?.desc || "",
|
|
1163
|
+
count: members.length,
|
|
1164
|
+
members: members.slice(0, 30).map((m) => ({
|
|
1165
|
+
name: petName(m),
|
|
1166
|
+
id: m.id,
|
|
1167
|
+
type_label: petType(m),
|
|
1168
|
+
egg_groups_label: formatEggGroups(this.getEggGroups(m))
|
|
1169
|
+
})),
|
|
1170
|
+
has_more: members.length > 30,
|
|
1171
|
+
total: members.length
|
|
1172
|
+
};
|
|
1173
|
+
});
|
|
1174
|
+
const br = pet.breeding || {};
|
|
1175
|
+
const bp = pet.breeding_profile || {};
|
|
1176
|
+
const eggDetails = this.buildEggDetails(br);
|
|
1177
|
+
return {
|
|
1178
|
+
pet_name: petName(pet),
|
|
1179
|
+
pet_id: pet.id,
|
|
1180
|
+
pet_icon: petIconUrl(pet.id),
|
|
1181
|
+
pet_image: petImageUrl(pet.id),
|
|
1182
|
+
type_label: petType(pet),
|
|
1183
|
+
egg_groups_label: formatEggGroups(egs),
|
|
1184
|
+
egg_groups: egs,
|
|
1185
|
+
egg_group_labels: Object.fromEntries(egs.map((gid) => [gid, getEggGroupLabel(gid)])),
|
|
1186
|
+
male_rate: bp.male_rate ?? null,
|
|
1187
|
+
female_rate: bp.female_rate ?? null,
|
|
1188
|
+
hatch_label: fmtDur(br.hatch_data),
|
|
1189
|
+
weight_label: fmtRange(wt(br.weight_low), wt(br.weight_high), "kg"),
|
|
1190
|
+
height_label: fmtRange(br.height_low, br.height_high, "cm"),
|
|
1191
|
+
total_compatible: compat.length,
|
|
1192
|
+
is_undiscovered: egs.includes(1),
|
|
1193
|
+
egg_group_sections: sections,
|
|
1194
|
+
total_stats: ["base_hp", "base_phy_atk", "base_mag_atk", "base_phy_def", "base_mag_def", "base_spd"].reduce((sum, k) => sum + (pet[k] || 0), 0),
|
|
1195
|
+
egg_details: eggDetails,
|
|
1196
|
+
commandHint: "洛克查蛋 <名称> | 洛克查蛋 身高25 体重1.5 | 洛克配种 <父> <母>",
|
|
1197
|
+
copyright: "Koishi & WeGame 洛克王国插件"
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
buildPairData(a, b) {
|
|
1201
|
+
const ev = this.evaluatePair(a, b);
|
|
1202
|
+
const makePetCard = /* @__PURE__ */ __name((p) => ({
|
|
1203
|
+
name: petName(p),
|
|
1204
|
+
id: p.id,
|
|
1205
|
+
type_label: petType(p),
|
|
1206
|
+
egg_groups_label: formatEggGroups(this.getEggGroups(p))
|
|
1207
|
+
}), "makePetCard");
|
|
1208
|
+
return {
|
|
1209
|
+
mother: makePetCard(a),
|
|
1210
|
+
father: makePetCard(b),
|
|
1211
|
+
...ev,
|
|
1212
|
+
commandHint: "默认前父后母,孵蛋结果跟随母体 | 洛克配种 <精灵名> 查怎么孵",
|
|
1213
|
+
copyright: "Koishi & WeGame 洛克王国插件"
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
buildWantPetData(pet) {
|
|
1217
|
+
const fathers = this.getBreedingParents(pet);
|
|
1218
|
+
const bp = pet.breeding_profile || {};
|
|
1219
|
+
const eggGroups = this.getEggGroups(pet);
|
|
1220
|
+
return {
|
|
1221
|
+
target: this.formatPetCard(pet),
|
|
1222
|
+
egg_groups_label: formatEggGroups(eggGroups),
|
|
1223
|
+
female_rate: bp.female_rate ?? null,
|
|
1224
|
+
male_rate: bp.male_rate ?? null,
|
|
1225
|
+
is_undiscovered: eggGroups.includes(1),
|
|
1226
|
+
fathers: fathers.slice(0, 30).map((p) => this.formatPetCard(p)),
|
|
1227
|
+
father_count: fathers.length,
|
|
1228
|
+
commandHint: "洛克配种 <父体> <母体> 查看详细结果",
|
|
1229
|
+
copyright: "Koishi & WeGame 洛克王国插件"
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
buildCandidatesRenderData(keyword, candidates) {
|
|
1233
|
+
return {
|
|
1234
|
+
keyword,
|
|
1235
|
+
count: candidates.length,
|
|
1236
|
+
candidates: candidates.map((p) => this.formatPetCard(p)),
|
|
1237
|
+
commandHint: "请使用更精确的名称重新查询",
|
|
1238
|
+
copyright: "Koishi & WeGame 洛克王国插件"
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
buildSizeSearchData(height, weight, results, heightDisplay) {
|
|
1242
|
+
const conditions = [];
|
|
1243
|
+
if (height != null) conditions.push(`身高 ${heightDisplay || fmtRange(ht(height), ht(height), "m")}`);
|
|
1244
|
+
if (weight != null) conditions.push(`体重 ${weight} kg`);
|
|
1245
|
+
const [perfect, ranged] = this.mergeCardsByName(
|
|
1246
|
+
(results?.perfect || []).map((p) => this.formatPetCard(p, height, weight)),
|
|
1247
|
+
(results?.range || []).map((p) => this.formatPetCard(p, height, weight))
|
|
1248
|
+
);
|
|
1249
|
+
return {
|
|
1250
|
+
query_label: conditions.join(" / ") || "尺寸反查",
|
|
1251
|
+
perfect_matches: perfect,
|
|
1252
|
+
range_matches: ranged,
|
|
1253
|
+
total_count: perfect.length + ranged.length,
|
|
1254
|
+
has_results: !!(perfect.length || ranged.length),
|
|
1255
|
+
commandHint: "洛克查蛋 <精灵名> | 洛克查蛋 身高25 体重1.5",
|
|
1256
|
+
copyright: "Koishi & WeGame 洛克王国插件"
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
buildSizeSearchDataFromApi(height, weight, results, heightDisplay) {
|
|
1260
|
+
const conditions = [];
|
|
1261
|
+
if (height != null) conditions.push(`身高 ${heightDisplay || fmtRange(ht(height), ht(height), "m")}`);
|
|
1262
|
+
if (weight != null) conditions.push(`体重 ${weight} kg`);
|
|
1263
|
+
const [perfect, ranged] = this.mergeCardsByName(
|
|
1264
|
+
(results?.exactResults || []).map((item) => this.formatSizeApiCard(item)),
|
|
1265
|
+
(results?.candidates || []).map((item) => this.formatSizeApiCard(item))
|
|
1266
|
+
);
|
|
1267
|
+
const searchMode = results?.searchMode || "";
|
|
1268
|
+
const queryLabel = `${conditions.join(" / ") || "尺寸反查"}${searchMode ? ` · 模式 ${searchMode}` : ""}`;
|
|
1269
|
+
return {
|
|
1270
|
+
query_label: queryLabel,
|
|
1271
|
+
perfect_matches: perfect,
|
|
1272
|
+
range_matches: ranged,
|
|
1273
|
+
total_count: perfect.length + ranged.length,
|
|
1274
|
+
has_results: !!(perfect.length || ranged.length),
|
|
1275
|
+
commandHint: "洛克查蛋 <精灵名> | 洛克查蛋 0.18m 1.5kg | 洛克查蛋 0.18",
|
|
1276
|
+
copyright: "Koishi & WeGame 洛克王国插件"
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
buildEggDetails(breeding) {
|
|
1280
|
+
if (!breeding) return { has_data: false };
|
|
1281
|
+
const baseProb = breeding.egg_base_glass_prob_array;
|
|
1282
|
+
const addProb = breeding.egg_add_glass_prob_array;
|
|
1283
|
+
const preciousMap = {
|
|
1284
|
+
1: "迪莫蛋",
|
|
1285
|
+
2: "星辰蛋",
|
|
1286
|
+
3: "彩虹蛋",
|
|
1287
|
+
4: "梦幻蛋",
|
|
1288
|
+
5: "传说蛋",
|
|
1289
|
+
6: "神秘蛋",
|
|
1290
|
+
7: "特殊蛋"
|
|
1291
|
+
};
|
|
1292
|
+
const variants = (breeding.variants || []).map((v) => ({
|
|
1293
|
+
id: v.id,
|
|
1294
|
+
name: v.name || "",
|
|
1295
|
+
hatch_label: fmtDur(v.hatch_data),
|
|
1296
|
+
weight_label: fmtRange(wt(v.weight_low), wt(v.weight_high), "kg"),
|
|
1297
|
+
height_label: fmtRange(v.height_low, v.height_high, "cm"),
|
|
1298
|
+
precious_egg_type: v.precious_egg_type,
|
|
1299
|
+
precious_egg_label: preciousMap[v.precious_egg_type] || "普通蛋",
|
|
1300
|
+
base_prob_str: v.egg_base_glass_prob_array?.length === 2 ? `${v.egg_base_glass_prob_array[0]}/${v.egg_base_glass_prob_array[1]}` : "暂无"
|
|
1301
|
+
}));
|
|
1302
|
+
return {
|
|
1303
|
+
has_data: true,
|
|
1304
|
+
base_prob_str: baseProb?.length === 2 ? `${baseProb[0]}/${baseProb[1]}` : "暂无数据",
|
|
1305
|
+
base_prob_pct: baseProb?.length === 2 ? baseProb[0] / baseProb[1] * 100 : null,
|
|
1306
|
+
add_prob_str: addProb?.length === 2 ? `${addProb[0]}/${addProb[1]}` : "暂无数据",
|
|
1307
|
+
add_prob_pct: addProb?.length === 2 ? addProb[0] / addProb[1] * 100 : null,
|
|
1308
|
+
is_contact_add_glass: breeding.is_contact_add_glass_prob,
|
|
1309
|
+
is_contact_add_shining: breeding.is_contact_add_shining_prob,
|
|
1310
|
+
precious_egg_type: breeding.precious_egg_type,
|
|
1311
|
+
precious_egg_label: preciousMap[breeding.precious_egg_type] || "普通蛋",
|
|
1312
|
+
variants,
|
|
1313
|
+
variant_count: variants.length
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
};
|
|
1317
|
+
|
|
1318
|
+
// src/render.ts
|
|
1319
|
+
var import_koishi4 = require("koishi");
|
|
1320
|
+
var template = __toESM(require("art-template"));
|
|
1321
|
+
var import_node_fs3 = __toESM(require("node:fs"));
|
|
1322
|
+
var import_node_os = __toESM(require("node:os"));
|
|
1323
|
+
var import_node_path3 = __toESM(require("node:path"));
|
|
1324
|
+
var import_node_url = require("node:url");
|
|
1325
|
+
var logger4 = new import_koishi4.Logger("rocom-render");
|
|
1326
|
+
var TEMPLATE_CAPTURE_PADDING = {
|
|
1327
|
+
package: { left: 0, right: 0, top: 0, bottom: 0 }
|
|
1328
|
+
};
|
|
1329
|
+
function toDirectoryFileUrl(dirPath) {
|
|
1330
|
+
const href = (0, import_node_url.pathToFileURL)(dirPath).href;
|
|
1331
|
+
return href.endsWith("/") ? href : `${href}/`;
|
|
1332
|
+
}
|
|
1333
|
+
__name(toDirectoryFileUrl, "toDirectoryFileUrl");
|
|
1334
|
+
function normalizeTemplateResourcePaths(content) {
|
|
1335
|
+
return content.replace(/\{\{(_res_path|pluResPath)\}\}render\//g, "{{$1}}render-templates/");
|
|
1336
|
+
}
|
|
1337
|
+
__name(normalizeTemplateResourcePaths, "normalizeTemplateResourcePaths");
|
|
1338
|
+
var Renderer = class {
|
|
1339
|
+
constructor(resPath) {
|
|
1340
|
+
this.resPath = resPath;
|
|
1341
|
+
}
|
|
1342
|
+
static {
|
|
1343
|
+
__name(this, "Renderer");
|
|
1344
|
+
}
|
|
1345
|
+
resourceUrl(relativePath) {
|
|
1346
|
+
return (0, import_node_url.pathToFileURL)(import_node_path3.default.join(this.getResourceRoot(), relativePath)).href;
|
|
1347
|
+
}
|
|
1348
|
+
getResourceRoot() {
|
|
1349
|
+
const builtRoot = import_node_path3.default.join(this.resPath, "lib");
|
|
1350
|
+
if (import_node_fs3.default.existsSync(import_node_path3.default.join(builtRoot, "render-templates"))) return builtRoot;
|
|
1351
|
+
return import_node_path3.default.join(this.resPath, "src");
|
|
1352
|
+
}
|
|
1353
|
+
getTemplateRoot() {
|
|
1354
|
+
return import_node_path3.default.join(this.getResourceRoot(), "render-templates");
|
|
1355
|
+
}
|
|
1356
|
+
getTemplatePath(templateName) {
|
|
1357
|
+
const directHtmlPath = import_node_path3.default.join(this.getTemplateRoot(), `${templateName}.html`);
|
|
1358
|
+
if (import_node_fs3.default.existsSync(directHtmlPath)) return directHtmlPath;
|
|
1359
|
+
return import_node_path3.default.join(this.getTemplateRoot(), templateName, "index.html");
|
|
1360
|
+
}
|
|
1361
|
+
getStylePath(templateName) {
|
|
1362
|
+
return import_node_path3.default.join(this.getTemplateRoot(), templateName, "style.css");
|
|
1363
|
+
}
|
|
1364
|
+
async renderHtml(ctx, templateName, data) {
|
|
1365
|
+
try {
|
|
1366
|
+
const templatePath = this.getTemplatePath(templateName);
|
|
1367
|
+
if (!import_node_fs3.default.existsSync(templatePath)) {
|
|
1368
|
+
logger4.error(`template file missing: ${templatePath}`);
|
|
1369
|
+
return null;
|
|
1370
|
+
}
|
|
1371
|
+
const templateContent = import_node_fs3.default.readFileSync(templatePath, "utf-8");
|
|
1372
|
+
const normalizedTemplateContent = normalizeTemplateResourcePaths(templateContent);
|
|
1373
|
+
const resPathUrl = toDirectoryFileUrl(this.getResourceRoot());
|
|
1374
|
+
const renderData = { ...data, _res_path: resPathUrl, pluResPath: resPathUrl };
|
|
1375
|
+
const html = template.render(normalizedTemplateContent, renderData);
|
|
1376
|
+
if (!ctx.puppeteer?.page) {
|
|
1377
|
+
logger4.error("puppeteer service is unavailable");
|
|
1378
|
+
return null;
|
|
1379
|
+
}
|
|
1380
|
+
const page = await ctx.puppeteer.page();
|
|
1381
|
+
const tempDir = import_node_fs3.default.mkdtempSync(import_node_path3.default.join(import_node_os.default.tmpdir(), "rocom-render-"));
|
|
1382
|
+
const tempHtmlPath = import_node_path3.default.join(tempDir, `${templateName.replace(/[\\/]/g, "_")}.html`);
|
|
1383
|
+
try {
|
|
1384
|
+
await page.setCacheEnabled(false);
|
|
1385
|
+
import_node_fs3.default.writeFileSync(tempHtmlPath, html, "utf-8");
|
|
1386
|
+
await page.setViewport({ width: 1280, height: 768, deviceScaleFactor: 2 });
|
|
1387
|
+
try {
|
|
1388
|
+
await page.goto((0, import_node_url.pathToFileURL)(tempHtmlPath).href, {
|
|
1389
|
+
waitUntil: "networkidle0",
|
|
1390
|
+
timeout: 15e3
|
|
1391
|
+
});
|
|
1392
|
+
} catch (err) {
|
|
1393
|
+
logger4.warn(`page.goto failed for ${templateName}: ${err}`);
|
|
1394
|
+
}
|
|
1395
|
+
try {
|
|
1396
|
+
await page.evaluate(async () => {
|
|
1397
|
+
const images = Array.from(document.images);
|
|
1398
|
+
await Promise.all(images.map((img) => {
|
|
1399
|
+
if (img.complete) return Promise.resolve();
|
|
1400
|
+
return new Promise((resolve) => {
|
|
1401
|
+
img.onload = () => resolve();
|
|
1402
|
+
img.onerror = () => resolve();
|
|
1403
|
+
});
|
|
1404
|
+
}));
|
|
1405
|
+
const fonts = document.fonts;
|
|
1406
|
+
if (fonts?.ready) {
|
|
1407
|
+
await fonts.ready;
|
|
1408
|
+
}
|
|
1409
|
+
});
|
|
1410
|
+
} catch (err) {
|
|
1411
|
+
logger4.warn(`asset wait failed for ${templateName}: ${err}`);
|
|
1412
|
+
}
|
|
1413
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1414
|
+
const selectors = [
|
|
1415
|
+
".exchange-page",
|
|
1416
|
+
".record-page",
|
|
1417
|
+
".package-cont",
|
|
1418
|
+
".searcheggs-cont",
|
|
1419
|
+
".bwiki-shell",
|
|
1420
|
+
".skill-shell",
|
|
1421
|
+
".lineup-page",
|
|
1422
|
+
".lineup-detail-page",
|
|
1423
|
+
".page-section-main",
|
|
1424
|
+
".stats-cont",
|
|
1425
|
+
".inspect-page",
|
|
1426
|
+
".player-search-page",
|
|
1427
|
+
".ingame-shop-page",
|
|
1428
|
+
".friendship-page",
|
|
1429
|
+
".student-state-page",
|
|
1430
|
+
".student-perks-page",
|
|
1431
|
+
".student-page",
|
|
1432
|
+
".home-page"
|
|
1433
|
+
];
|
|
1434
|
+
let target = null;
|
|
1435
|
+
for (const selector of selectors) {
|
|
1436
|
+
target = await page.$(selector);
|
|
1437
|
+
if (target) break;
|
|
1438
|
+
}
|
|
1439
|
+
if (!target) {
|
|
1440
|
+
target = await page.$("body");
|
|
1441
|
+
}
|
|
1442
|
+
if (target) {
|
|
1443
|
+
const box = await target.boundingBox();
|
|
1444
|
+
if (box && box.width > 0 && box.height > 0) {
|
|
1445
|
+
const elementMetrics = await page.evaluate((el) => {
|
|
1446
|
+
const rect = el.getBoundingClientRect();
|
|
1447
|
+
const element = el;
|
|
1448
|
+
return {
|
|
1449
|
+
x: rect.left + window.scrollX,
|
|
1450
|
+
y: rect.top + window.scrollY,
|
|
1451
|
+
width: Math.max(rect.width, element.scrollWidth, element.offsetWidth),
|
|
1452
|
+
height: Math.max(rect.height, element.scrollHeight, element.offsetHeight)
|
|
1453
|
+
};
|
|
1454
|
+
}, target);
|
|
1455
|
+
const capturePadding = TEMPLATE_CAPTURE_PADDING[templateName] || { left: 0, right: 0, top: 0, bottom: 0 };
|
|
1456
|
+
await page.setViewport({
|
|
1457
|
+
width: Math.max(Math.ceil(elementMetrics.x + elementMetrics.width + capturePadding.right) + 8, 200),
|
|
1458
|
+
height: Math.max(Math.ceil(elementMetrics.y + elementMetrics.height + capturePadding.bottom) + 8, 200),
|
|
1459
|
+
deviceScaleFactor: 2
|
|
1460
|
+
});
|
|
1461
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1462
|
+
const hasOverflow = elementMetrics.width > box.width + 0.5 || elementMetrics.height > box.height + 0.5;
|
|
1463
|
+
if (capturePadding.left || capturePadding.right || capturePadding.top || capturePadding.bottom || hasOverflow) {
|
|
1464
|
+
const clipX = Math.max(0, elementMetrics.x - capturePadding.left);
|
|
1465
|
+
const clipY = Math.max(0, elementMetrics.y - capturePadding.top);
|
|
1466
|
+
const clipWidth = elementMetrics.width + capturePadding.left + capturePadding.right;
|
|
1467
|
+
const clipHeight = elementMetrics.height + capturePadding.top + capturePadding.bottom;
|
|
1468
|
+
const screenshot3 = await page.screenshot({
|
|
1469
|
+
type: "png",
|
|
1470
|
+
clip: {
|
|
1471
|
+
x: clipX,
|
|
1472
|
+
y: clipY,
|
|
1473
|
+
width: clipWidth,
|
|
1474
|
+
height: clipHeight
|
|
1475
|
+
}
|
|
1476
|
+
});
|
|
1477
|
+
return Buffer.isBuffer(screenshot3) ? screenshot3 : Buffer.from(screenshot3);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
const screenshot2 = await target.screenshot({ type: "png" });
|
|
1481
|
+
return Buffer.isBuffer(screenshot2) ? screenshot2 : Buffer.from(screenshot2);
|
|
1482
|
+
}
|
|
1483
|
+
const screenshot = await page.screenshot({ fullPage: true });
|
|
1484
|
+
return Buffer.isBuffer(screenshot) ? screenshot : Buffer.from(screenshot);
|
|
1485
|
+
} finally {
|
|
1486
|
+
try {
|
|
1487
|
+
await page.close();
|
|
1488
|
+
} catch {
|
|
1489
|
+
}
|
|
1490
|
+
import_node_fs3.default.rmSync(tempDir, { recursive: true, force: true });
|
|
1491
|
+
}
|
|
1492
|
+
} catch (e) {
|
|
1493
|
+
logger4.error(`render failed: ${e}`);
|
|
1494
|
+
return null;
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
};
|
|
1498
|
+
|
|
1499
|
+
// src/role-token.ts
|
|
1500
|
+
function normalize(value) {
|
|
1501
|
+
return String(value ?? "").trim();
|
|
1502
|
+
}
|
|
1503
|
+
__name(normalize, "normalize");
|
|
1504
|
+
function pickLatest(rows) {
|
|
1505
|
+
if (!rows.length) return null;
|
|
1506
|
+
return [...rows].sort((a, b) => {
|
|
1507
|
+
const aTime = new Date(a.updatedAt).getTime() || 0;
|
|
1508
|
+
const bTime = new Date(b.updatedAt).getTime() || 0;
|
|
1509
|
+
return bTime - aTime;
|
|
1510
|
+
})[0] ?? null;
|
|
1511
|
+
}
|
|
1512
|
+
__name(pickLatest, "pickLatest");
|
|
1513
|
+
function pickLegacyToken(binding) {
|
|
1514
|
+
return normalize(binding.framework_token);
|
|
1515
|
+
}
|
|
1516
|
+
__name(pickLegacyToken, "pickLegacyToken");
|
|
1517
|
+
function setupRoleTokenModel(ctx) {
|
|
1518
|
+
;
|
|
1519
|
+
ctx.model.extend("roleToken", {
|
|
1520
|
+
UserId: "string",
|
|
1521
|
+
fwt: "string",
|
|
1522
|
+
bindingId: "string",
|
|
1523
|
+
roleId: "string",
|
|
1524
|
+
loginType: "string",
|
|
1525
|
+
updatedAt: "timestamp"
|
|
1526
|
+
}, {
|
|
1527
|
+
primary: "UserId",
|
|
1528
|
+
indexes: ["bindingId"]
|
|
1529
|
+
});
|
|
1530
|
+
}
|
|
1531
|
+
__name(setupRoleTokenModel, "setupRoleTokenModel");
|
|
1532
|
+
async function upsertRoleToken(ctx, payload) {
|
|
1533
|
+
const userId = normalize(payload.userId);
|
|
1534
|
+
const fwt = normalize(payload.fwt);
|
|
1535
|
+
if (!userId || !fwt) return;
|
|
1536
|
+
await ctx.database.remove("roleToken", { UserId: userId });
|
|
1537
|
+
await ctx.database.upsert(
|
|
1538
|
+
"roleToken",
|
|
1539
|
+
() => [{
|
|
1540
|
+
UserId: userId,
|
|
1541
|
+
fwt,
|
|
1542
|
+
bindingId: normalize(payload.bindingId),
|
|
1543
|
+
roleId: normalize(payload.roleId),
|
|
1544
|
+
loginType: normalize(payload.loginType),
|
|
1545
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1546
|
+
}],
|
|
1547
|
+
"UserId"
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1550
|
+
__name(upsertRoleToken, "upsertRoleToken");
|
|
1551
|
+
async function getRoleToken(ctx, userId) {
|
|
1552
|
+
const normalizedUserId = normalize(userId);
|
|
1553
|
+
if (!normalizedUserId) return null;
|
|
1554
|
+
const rows = await ctx.database.get("roleToken", { UserId: normalizedUserId });
|
|
1555
|
+
if (!Array.isArray(rows) || !rows.length) return null;
|
|
1556
|
+
return pickLatest(rows);
|
|
1557
|
+
}
|
|
1558
|
+
__name(getRoleToken, "getRoleToken");
|
|
1559
|
+
async function removeRoleToken(ctx, userId) {
|
|
1560
|
+
const normalizedUserId = normalize(userId);
|
|
1561
|
+
if (!normalizedUserId) return;
|
|
1562
|
+
await ctx.database.remove("roleToken", { UserId: normalizedUserId });
|
|
1563
|
+
}
|
|
1564
|
+
__name(removeRoleToken, "removeRoleToken");
|
|
1565
|
+
async function migrateRoleTokensToUserId(ctx) {
|
|
1566
|
+
const rows = await ctx.database.get("roleToken", {});
|
|
1567
|
+
if (!Array.isArray(rows) || !rows.length) return 0;
|
|
1568
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1569
|
+
for (const row of rows) {
|
|
1570
|
+
const userId = normalize(row?.UserId);
|
|
1571
|
+
if (!userId) continue;
|
|
1572
|
+
const bucket = groups.get(userId) || [];
|
|
1573
|
+
bucket.push(row);
|
|
1574
|
+
groups.set(userId, bucket);
|
|
1575
|
+
}
|
|
1576
|
+
let migrated = 0;
|
|
1577
|
+
for (const [userId, items] of groups) {
|
|
1578
|
+
if (items.length <= 1) continue;
|
|
1579
|
+
const latest = pickLatest(items);
|
|
1580
|
+
if (!latest?.fwt) continue;
|
|
1581
|
+
await removeRoleToken(ctx, userId);
|
|
1582
|
+
await upsertRoleToken(ctx, {
|
|
1583
|
+
userId,
|
|
1584
|
+
fwt: latest.fwt,
|
|
1585
|
+
bindingId: latest.bindingId,
|
|
1586
|
+
roleId: latest.roleId,
|
|
1587
|
+
loginType: latest.loginType
|
|
1588
|
+
});
|
|
1589
|
+
migrated++;
|
|
1590
|
+
}
|
|
1591
|
+
return migrated;
|
|
1592
|
+
}
|
|
1593
|
+
__name(migrateRoleTokensToUserId, "migrateRoleTokensToUserId");
|
|
1594
|
+
async function migrateLegacyFrameworkTokens(ctx, userMgr) {
|
|
1595
|
+
const allUsers = userMgr.getAllUsersBindings();
|
|
1596
|
+
let migrated = 0;
|
|
1597
|
+
for (const [userId, bindings] of Object.entries(allUsers)) {
|
|
1598
|
+
const normalizedUserId = normalize(userId);
|
|
1599
|
+
if (!normalizedUserId) continue;
|
|
1600
|
+
const existing = await getRoleToken(ctx, normalizedUserId);
|
|
1601
|
+
const bindingsWithToken = bindings.filter((binding) => pickLegacyToken(binding));
|
|
1602
|
+
const chosen = bindingsWithToken.find((binding) => binding.is_primary) || bindingsWithToken[0];
|
|
1603
|
+
if (!existing?.fwt && chosen) {
|
|
1604
|
+
const legacyToken = pickLegacyToken(chosen);
|
|
1605
|
+
if (legacyToken) {
|
|
1606
|
+
await upsertRoleToken(ctx, {
|
|
1607
|
+
userId: normalizedUserId,
|
|
1608
|
+
fwt: legacyToken,
|
|
1609
|
+
bindingId: chosen.binding_id,
|
|
1610
|
+
roleId: chosen.role_id,
|
|
1611
|
+
loginType: chosen.login_type
|
|
1612
|
+
});
|
|
1613
|
+
migrated++;
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
if (bindingsWithToken.length > 0) {
|
|
1617
|
+
let needsSave = false;
|
|
1618
|
+
for (const binding of bindings) {
|
|
1619
|
+
if (!binding.framework_token) continue;
|
|
1620
|
+
delete binding.framework_token;
|
|
1621
|
+
needsSave = true;
|
|
1622
|
+
}
|
|
1623
|
+
if (needsSave) {
|
|
1624
|
+
userMgr.saveUserBindings(normalizedUserId, bindings);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
return migrated;
|
|
1629
|
+
}
|
|
1630
|
+
__name(migrateLegacyFrameworkTokens, "migrateLegacyFrameworkTokens");
|
|
1631
|
+
|
|
1632
|
+
// src/commands/account.ts
|
|
1633
|
+
var import_koishi6 = require("koishi");
|
|
1634
|
+
|
|
1635
|
+
// src/send-image.ts
|
|
1636
|
+
var import_koishi5 = require("koishi");
|
|
1637
|
+
var import_node_zlib = __toESM(require("node:zlib"));
|
|
1638
|
+
var logger5 = new import_koishi5.Logger("rocom-send");
|
|
1639
|
+
var PNG_SIGNATURE = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
1640
|
+
function formatSessionContext(session) {
|
|
1641
|
+
if (!session) return "session=<null>";
|
|
1642
|
+
const platform = session.platform ?? "unknown";
|
|
1643
|
+
const userId = session.userId ?? "unknown";
|
|
1644
|
+
const guildId = session.guildId ?? "private";
|
|
1645
|
+
const channelId = session.channelId ?? "unknown";
|
|
1646
|
+
return `platform=${platform} user=${userId} guild=${guildId} channel=${channelId}`;
|
|
1647
|
+
}
|
|
1648
|
+
__name(formatSessionContext, "formatSessionContext");
|
|
1649
|
+
function hasSendResult(result) {
|
|
1650
|
+
if (result == null) return false;
|
|
1651
|
+
if (typeof result === "string") return result.length > 0;
|
|
1652
|
+
if (Array.isArray(result)) return result.length > 0;
|
|
1653
|
+
return true;
|
|
1654
|
+
}
|
|
1655
|
+
__name(hasSendResult, "hasSendResult");
|
|
1656
|
+
function crc32(buffer) {
|
|
1657
|
+
let crc = 4294967295;
|
|
1658
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
1659
|
+
crc ^= buffer[i];
|
|
1660
|
+
for (let bit = 0; bit < 8; bit++) {
|
|
1661
|
+
crc = crc >>> 1 ^ 3988292384 & -(crc & 1);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
return (crc ^ 4294967295) >>> 0;
|
|
1665
|
+
}
|
|
1666
|
+
__name(crc32, "crc32");
|
|
1667
|
+
function pngChunk(type, data) {
|
|
1668
|
+
const typeBuffer = Buffer.from(type, "ascii");
|
|
1669
|
+
const lengthBuffer = Buffer.allocUnsafe(4);
|
|
1670
|
+
lengthBuffer.writeUInt32BE(data.length, 0);
|
|
1671
|
+
const crcBuffer = Buffer.allocUnsafe(4);
|
|
1672
|
+
crcBuffer.writeUInt32BE(crc32(Buffer.concat([typeBuffer, data])), 0);
|
|
1673
|
+
return Buffer.concat([lengthBuffer, typeBuffer, data, crcBuffer]);
|
|
1674
|
+
}
|
|
1675
|
+
__name(pngChunk, "pngChunk");
|
|
1676
|
+
function isPng(buffer) {
|
|
1677
|
+
return buffer.length > PNG_SIGNATURE.length && buffer.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE);
|
|
1678
|
+
}
|
|
1679
|
+
__name(isPng, "isPng");
|
|
1680
|
+
function compressPngImage(image, config) {
|
|
1681
|
+
if (!config.imageCompressionEnabled) return image;
|
|
1682
|
+
if (!isPng(image)) return image;
|
|
1683
|
+
if (image.length < Math.max(0, config.imageCompressionMinBytes || 0)) return image;
|
|
1684
|
+
try {
|
|
1685
|
+
const chunks = [];
|
|
1686
|
+
const idatChunks = [];
|
|
1687
|
+
let offset = PNG_SIGNATURE.length;
|
|
1688
|
+
while (offset + 12 <= image.length) {
|
|
1689
|
+
const length = image.readUInt32BE(offset);
|
|
1690
|
+
const type = image.subarray(offset + 4, offset + 8).toString("ascii");
|
|
1691
|
+
const dataStart = offset + 8;
|
|
1692
|
+
const dataEnd = dataStart + length;
|
|
1693
|
+
const nextOffset = dataEnd + 4;
|
|
1694
|
+
if (dataEnd > image.length || nextOffset > image.length) return image;
|
|
1695
|
+
const data = image.subarray(dataStart, dataEnd);
|
|
1696
|
+
chunks.push({ type, data });
|
|
1697
|
+
if (type === "IDAT") idatChunks.push(data);
|
|
1698
|
+
offset = nextOffset;
|
|
1699
|
+
if (type === "IEND") break;
|
|
1700
|
+
}
|
|
1701
|
+
if (!idatChunks.length) return image;
|
|
1702
|
+
const rawImageData = import_node_zlib.default.inflateSync(Buffer.concat(idatChunks));
|
|
1703
|
+
const level = Math.max(0, Math.min(9, Math.floor(config.imageCompressionLevel ?? 9)));
|
|
1704
|
+
const compressedIdat = import_node_zlib.default.deflateSync(rawImageData, { level });
|
|
1705
|
+
const rebuiltChunks = [];
|
|
1706
|
+
let wroteIdat = false;
|
|
1707
|
+
for (const chunk of chunks) {
|
|
1708
|
+
if (chunk.type === "IDAT") {
|
|
1709
|
+
if (!wroteIdat) {
|
|
1710
|
+
rebuiltChunks.push(pngChunk("IDAT", compressedIdat));
|
|
1711
|
+
wroteIdat = true;
|
|
1712
|
+
}
|
|
1713
|
+
continue;
|
|
1714
|
+
}
|
|
1715
|
+
rebuiltChunks.push(pngChunk(chunk.type, chunk.data));
|
|
1716
|
+
}
|
|
1717
|
+
const compressed = Buffer.concat([PNG_SIGNATURE, ...rebuiltChunks]);
|
|
1718
|
+
if (compressed.length < image.length) return compressed;
|
|
1719
|
+
} catch (err) {
|
|
1720
|
+
logger5.warn(`PNG compression failed, sending original image: ${err}`);
|
|
1721
|
+
}
|
|
1722
|
+
return image;
|
|
1723
|
+
}
|
|
1724
|
+
__name(compressPngImage, "compressPngImage");
|
|
1725
|
+
async function sendImageWithFallback(session, image, fallbackText, scene, compressionConfig) {
|
|
1726
|
+
const ctxInfo = formatSessionContext(session);
|
|
1727
|
+
if (!image) {
|
|
1728
|
+
logger5.warn(`[${scene}] render returned empty image, fallback to text | ${ctxInfo}`);
|
|
1729
|
+
try {
|
|
1730
|
+
await session.send(fallbackText);
|
|
1731
|
+
} catch (e) {
|
|
1732
|
+
logger5.error(`[${scene}] text fallback send failed | ${ctxInfo} | ${e}`);
|
|
1733
|
+
}
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
const outputImage = compressionConfig ? compressPngImage(image, compressionConfig) : image;
|
|
1737
|
+
if (outputImage.length < image.length) {
|
|
1738
|
+
logger5.info(`[${scene}] compressed image ${image.length}B -> ${outputImage.length}B | ${ctxInfo}`);
|
|
1739
|
+
}
|
|
1740
|
+
try {
|
|
1741
|
+
const result = await session.send(import_koishi5.h.image(outputImage, "image/png"));
|
|
1742
|
+
if (!hasSendResult(result)) {
|
|
1743
|
+
logger5.warn(`[${scene}] image send returned empty result | size=${outputImage.length}B | ${ctxInfo}`);
|
|
1744
|
+
}
|
|
1745
|
+
} catch (e) {
|
|
1746
|
+
logger5.error(`[${scene}] image send failed | size=${outputImage.length}B | ${ctxInfo} | ${e}`);
|
|
1747
|
+
try {
|
|
1748
|
+
await session.send(fallbackText);
|
|
1749
|
+
} catch (fallbackErr) {
|
|
1750
|
+
logger5.error(`[${scene}] text fallback send failed after image send error | ${ctxInfo} | ${fallbackErr}`);
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
__name(sendImageWithFallback, "sendImageWithFallback");
|
|
1755
|
+
|
|
1756
|
+
// src/commands/account.ts
|
|
1757
|
+
async function getPrimaryToken(deps, userId) {
|
|
1758
|
+
const token = await getRoleToken(deps.ctx, userId);
|
|
1759
|
+
return token?.fwt || "";
|
|
1760
|
+
}
|
|
1761
|
+
__name(getPrimaryToken, "getPrimaryToken");
|
|
1762
|
+
function notLoggedInHint() {
|
|
1763
|
+
return "您尚未绑定洛克王国账号,请先使用“洛克.QQ登录”或“洛克.微信登录”进行绑定。";
|
|
1764
|
+
}
|
|
1765
|
+
__name(notLoggedInHint, "notLoggedInHint");
|
|
1766
|
+
function formatBindTime(bindTime) {
|
|
1767
|
+
if (!bindTime || bindTime <= 0) return "未知";
|
|
1768
|
+
const date = new Date(bindTime);
|
|
1769
|
+
const pad = /* @__PURE__ */ __name((value) => String(value).padStart(2, "0"), "pad");
|
|
1770
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
1771
|
+
}
|
|
1772
|
+
__name(formatBindTime, "formatBindTime");
|
|
1773
|
+
function formatLoginType(loginType) {
|
|
1774
|
+
const typeMap = {
|
|
1775
|
+
qq: "QQ",
|
|
1776
|
+
wechat: "微信",
|
|
1777
|
+
manual: "手动导入"
|
|
1778
|
+
};
|
|
1779
|
+
return typeMap[loginType] || loginType || "未知";
|
|
1780
|
+
}
|
|
1781
|
+
__name(formatLoginType, "formatLoginType");
|
|
1782
|
+
async function saveBindingWithRoleInfo(deps, session, fwToken, loginType, userId) {
|
|
1783
|
+
const { ctx, client, userMgr } = deps;
|
|
1784
|
+
await session.send("登录成功,正在调用绑定接口...");
|
|
1785
|
+
const bindRes = await client.createBinding(ctx, fwToken, userId);
|
|
1786
|
+
const bindingId = String(bindRes?.binding?.id || fwToken || "").trim();
|
|
1787
|
+
if (!bindRes?.binding || !bindingId) {
|
|
1788
|
+
await session.send("绑定接口调用失败,请稍后重试。");
|
|
1789
|
+
return;
|
|
1790
|
+
}
|
|
1791
|
+
await session.send("绑定成功,正在获取角色信息...");
|
|
1792
|
+
const roleRes = await client.getRole(ctx, fwToken, void 0, userId);
|
|
1793
|
+
if (!roleRes?.role) {
|
|
1794
|
+
await session.send("绑定成功,但获取角色信息失败,请尝试重新登录。");
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
const role = roleRes.role;
|
|
1798
|
+
const binding = {
|
|
1799
|
+
binding_id: bindingId,
|
|
1800
|
+
login_type: loginType,
|
|
1801
|
+
role_id: role.id || "unknown",
|
|
1802
|
+
nickname: role.name || "洛克",
|
|
1803
|
+
bind_time: Date.now(),
|
|
1804
|
+
is_primary: true
|
|
1805
|
+
};
|
|
1806
|
+
userMgr.addBinding(userId, binding);
|
|
1807
|
+
await upsertRoleToken(ctx, {
|
|
1808
|
+
userId,
|
|
1809
|
+
fwt: fwToken,
|
|
1810
|
+
bindingId,
|
|
1811
|
+
roleId: binding.role_id,
|
|
1812
|
+
loginType
|
|
1813
|
+
});
|
|
1814
|
+
await session.send(`绑定成功!当前账号:${binding.nickname} (ID: ${binding.role_id})`);
|
|
1815
|
+
}
|
|
1816
|
+
__name(saveBindingWithRoleInfo, "saveBindingWithRoleInfo");
|
|
1817
|
+
function register(deps) {
|
|
1818
|
+
const { ctx, client, userMgr } = deps;
|
|
1819
|
+
ctx.command("洛克").subcommand(".QQ登录", "QQ 扫码登录").action(async ({ session }) => {
|
|
1820
|
+
const userId = session.userId;
|
|
1821
|
+
const qrData = await client.qqQrLogin(ctx, userId);
|
|
1822
|
+
if (!qrData?.qr_image) return "获取 QQ 二维码失败。";
|
|
1823
|
+
const fwToken = qrData.frameworkToken;
|
|
1824
|
+
const qrB64 = qrData.qr_image;
|
|
1825
|
+
const imgData = qrB64.includes(",") ? qrB64.split(",")[1] : qrB64;
|
|
1826
|
+
await session.send((0, import_koishi6.h)(
|
|
1827
|
+
"message",
|
|
1828
|
+
{},
|
|
1829
|
+
import_koishi6.h.at(userId),
|
|
1830
|
+
import_koishi6.h.text("\n请使用 QQ 扫描二维码登录(有效时间 2 分钟)\n注意需要双设备扫码。\n"),
|
|
1831
|
+
import_koishi6.h.image(`data:image/png;base64,${imgData}`)
|
|
1832
|
+
));
|
|
1833
|
+
const startTime = Date.now();
|
|
1834
|
+
while (Date.now() - startTime < 115e3) {
|
|
1835
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
1836
|
+
const status = await client.qqQrStatus(ctx, fwToken, userId);
|
|
1837
|
+
if (!status) continue;
|
|
1838
|
+
if (status.status === "done") {
|
|
1839
|
+
await saveBindingWithRoleInfo(deps, session, fwToken, "qq", userId);
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
if (["expired", "failed", "canceled"].includes(status.status)) break;
|
|
1843
|
+
}
|
|
1844
|
+
return "登录超时或失败,请重试。";
|
|
1845
|
+
});
|
|
1846
|
+
ctx.command("洛克").subcommand(".微信登录", "微信扫码登录").action(async ({ session }) => {
|
|
1847
|
+
const userId = session.userId;
|
|
1848
|
+
const qrData = await client.wechatQrLogin(ctx, userId);
|
|
1849
|
+
if (!qrData?.qr_image) return "获取微信登录链接失败。";
|
|
1850
|
+
const fwToken = qrData.frameworkToken;
|
|
1851
|
+
const qrUrl = qrData.qr_image;
|
|
1852
|
+
await session.send(`请使用微信打开以下链接扫码登录(有效时间 2 分钟)
|
|
1853
|
+
注意需要双设备扫码。
|
|
1854
|
+
${qrUrl}`);
|
|
1855
|
+
const startTime = Date.now();
|
|
1856
|
+
while (Date.now() - startTime < 115e3) {
|
|
1857
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
1858
|
+
const status = await client.wechatQrStatus(ctx, fwToken, userId);
|
|
1859
|
+
if (!status) continue;
|
|
1860
|
+
if (status.status === "done") {
|
|
1861
|
+
await saveBindingWithRoleInfo(deps, session, fwToken, "wechat", userId);
|
|
1862
|
+
return;
|
|
1863
|
+
}
|
|
1864
|
+
if (["expired", "failed"].includes(status.status)) break;
|
|
1865
|
+
}
|
|
1866
|
+
return "登录超时或失败,请重试。";
|
|
1867
|
+
});
|
|
1868
|
+
ctx.command("洛克").subcommand(".导入 <tgpId:string> <tgpTicket:string>", "导入 WeGame 凭证").action(async ({ session }, tgpId, tgpTicket) => {
|
|
1869
|
+
if (!tgpId || !tgpTicket) return "用法:洛克.导入 <tgp_id> <tgp_ticket>";
|
|
1870
|
+
const userId = session.userId;
|
|
1871
|
+
const res = await client.importToken(ctx, tgpId, tgpTicket, userId);
|
|
1872
|
+
if (!res?.frameworkToken) return "凭证导入失败。";
|
|
1873
|
+
await saveBindingWithRoleInfo(deps, session, res.frameworkToken, "manual", userId);
|
|
1874
|
+
});
|
|
1875
|
+
ctx.command("洛克").subcommand(".绑定列表", "查看已绑定账号").action(async ({ session }) => {
|
|
1876
|
+
const bindings = userMgr.getUserBindings(session.userId);
|
|
1877
|
+
if (!bindings.length) return "暂无绑定账号。";
|
|
1878
|
+
const bindItems = bindings.map((binding, index) => ({
|
|
1879
|
+
index: index + 1,
|
|
1880
|
+
nickname: binding.nickname || "未知",
|
|
1881
|
+
isPrimary: Boolean(binding.is_primary),
|
|
1882
|
+
role_id: binding.role_id || "未知",
|
|
1883
|
+
type_label: formatLoginType(binding.login_type),
|
|
1884
|
+
created_at: formatBindTime(binding.bind_time)
|
|
1885
|
+
}));
|
|
1886
|
+
const data = {
|
|
1887
|
+
title: "绑定账号列表",
|
|
1888
|
+
subtitle: `共找到 ${bindings.length} 个有效绑定账号`,
|
|
1889
|
+
bindings: bindItems,
|
|
1890
|
+
commandHint: "洛克.切换 <序号> 切换主账号 | 洛克.解绑 <序号> 移除绑定",
|
|
1891
|
+
copyright: "Koishi & WeGame 洛克王国插件"
|
|
1892
|
+
};
|
|
1893
|
+
const fallbackLines = ["【绑定账号列表】"];
|
|
1894
|
+
bindItems.forEach((binding) => {
|
|
1895
|
+
const mark = binding.isPrimary ? "(主账号)" : "";
|
|
1896
|
+
fallbackLines.push(`[${binding.index}] ${binding.nickname} (ID: ${binding.role_id}) ${binding.type_label}${mark} · ${binding.created_at}`);
|
|
1897
|
+
});
|
|
1898
|
+
const png = await deps.renderer.renderHtml(ctx, "bind-list", data);
|
|
1899
|
+
await sendImageWithFallback(session, png, fallbackLines.join("\n"), "account:bind-list", deps.config);
|
|
1900
|
+
});
|
|
1901
|
+
ctx.command("洛克").subcommand(".切换 <index:number>", "切换主账号").action(async ({ session }, index) => {
|
|
1902
|
+
if (!index) return "用法:洛克.切换 <序号>";
|
|
1903
|
+
return userMgr.switchPrimary(session.userId, index) ? `成功切换到序号 ${index} 账号。` : "序号无效。";
|
|
1904
|
+
});
|
|
1905
|
+
ctx.command("洛克").subcommand(".解绑 <index:number>", "解绑账号").action(async ({ session }, index) => {
|
|
1906
|
+
if (!index) return "用法:洛克.解绑 <序号>";
|
|
1907
|
+
const removed = userMgr.deleteUserBinding(session.userId, index);
|
|
1908
|
+
if (!removed) return "序号无效。";
|
|
1909
|
+
if (removed.binding_id) {
|
|
1910
|
+
try {
|
|
1911
|
+
await client.deleteBinding(ctx, removed.binding_id, session.userId);
|
|
1912
|
+
} catch {
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
if (userMgr.getUserBindings(session.userId).length === 0) {
|
|
1916
|
+
await removeRoleToken(ctx, session.userId);
|
|
1917
|
+
}
|
|
1918
|
+
return `已解绑账号:${removed.nickname}`;
|
|
1919
|
+
});
|
|
1920
|
+
ctx.command("洛克").subcommand(".刷新", "刷新当前主账号凭证").action(async ({ session }) => {
|
|
1921
|
+
const userId = session.userId;
|
|
1922
|
+
const binding = userMgr.getPrimaryBinding(userId);
|
|
1923
|
+
if (!binding) return notLoggedInHint();
|
|
1924
|
+
if (!binding.binding_id) return "绑定 ID 无效,请重新绑定账号。";
|
|
1925
|
+
await session.send("正在刷新凭证,服务端会自动处理,无需手动操作。");
|
|
1926
|
+
const res = await client.refreshBinding(ctx, binding.binding_id, userId);
|
|
1927
|
+
if (res?.framework_token) {
|
|
1928
|
+
await upsertRoleToken(ctx, {
|
|
1929
|
+
userId,
|
|
1930
|
+
fwt: res.framework_token,
|
|
1931
|
+
bindingId: binding.binding_id,
|
|
1932
|
+
roleId: binding.role_id,
|
|
1933
|
+
loginType: binding.login_type
|
|
1934
|
+
});
|
|
1935
|
+
return "当前账号凭证刷新成功。";
|
|
1936
|
+
}
|
|
1937
|
+
return "凭证刷新失败,可能已过期或不支持刷新(仅 QQ 扫码支持)。";
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
__name(register, "register");
|
|
1941
|
+
|
|
1942
|
+
// src/commands/query.ts
|
|
1943
|
+
var import_koishi8 = require("koishi");
|
|
1944
|
+
|
|
1945
|
+
// src/subscription-send.ts
|
|
1946
|
+
var import_koishi7 = require("koishi");
|
|
1947
|
+
var logger6 = new import_koishi7.Logger("rocom-subscription-send");
|
|
1948
|
+
function findBot(ctx, platform = "") {
|
|
1949
|
+
if (!ctx.bots?.length) return null;
|
|
1950
|
+
if (!platform) return ctx.bots[0];
|
|
1951
|
+
return ctx.bots.find((bot) => bot.platform === platform) || ctx.bots[0];
|
|
1952
|
+
}
|
|
1953
|
+
__name(findBot, "findBot");
|
|
1954
|
+
async function sendScheduledMessage(ctx, target, message) {
|
|
1955
|
+
const platform = target.platform || "";
|
|
1956
|
+
const channelId = target.channelId || target.guildId || "";
|
|
1957
|
+
const userId = target.userId || "";
|
|
1958
|
+
const bot = findBot(ctx, platform);
|
|
1959
|
+
if (!bot) {
|
|
1960
|
+
logger6.warn("no available bot, skip scheduled push");
|
|
1961
|
+
return false;
|
|
1962
|
+
}
|
|
1963
|
+
try {
|
|
1964
|
+
if (userId && !target.guildId) {
|
|
1965
|
+
if (typeof bot.sendPrivateMessage === "function") {
|
|
1966
|
+
await bot.sendPrivateMessage(userId, message);
|
|
1967
|
+
return true;
|
|
1968
|
+
}
|
|
1969
|
+
if (typeof bot.sendMessage === "function") {
|
|
1970
|
+
await bot.sendMessage(userId, message);
|
|
1971
|
+
return true;
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
if (platform && channelId) {
|
|
1975
|
+
await ctx.broadcast([`${platform}:${channelId}`], message);
|
|
1976
|
+
return true;
|
|
1977
|
+
}
|
|
1978
|
+
if (channelId && typeof bot.sendMessage === "function") {
|
|
1979
|
+
await bot.sendMessage(channelId, message, target.guildId);
|
|
1980
|
+
return true;
|
|
1981
|
+
}
|
|
1982
|
+
} catch (err) {
|
|
1983
|
+
logger6.warn(`scheduled push failed: ${err}`);
|
|
1984
|
+
return false;
|
|
1985
|
+
}
|
|
1986
|
+
logger6.warn(`scheduled push target is incomplete: ${JSON.stringify(target)}`);
|
|
1987
|
+
return false;
|
|
1988
|
+
}
|
|
1989
|
+
__name(sendScheduledMessage, "sendScheduledMessage");
|
|
1990
|
+
|
|
1991
|
+
// src/commands/query.ts
|
|
1992
|
+
var import_node_fs4 = __toESM(require("node:fs"));
|
|
1993
|
+
var import_node_path4 = __toESM(require("node:path"));
|
|
1994
|
+
var logger7 = new import_koishi8.Logger("rocom-query");
|
|
1995
|
+
async function sendImage(deps, session, templateName, data, fallback) {
|
|
1996
|
+
const png = await deps.renderer.renderHtml(deps.ctx, templateName, data);
|
|
1997
|
+
await sendImageWithFallback(session, png, fallback, `query:${templateName}`, deps.config);
|
|
1998
|
+
}
|
|
1999
|
+
__name(sendImage, "sendImage");
|
|
2000
|
+
function cleanPlayerFieldValue(field, value) {
|
|
2001
|
+
const text = String(value ?? "").trim().replace(/^'+|'+$/g, "");
|
|
2002
|
+
if (!text || ["<0B>", "<0b>", "<0B >", "<0b >"].includes(text)) return "未设置";
|
|
2003
|
+
if (["is_online", "online", "chat_top_unlock", "is_friend", "is_black", "is_black_role", "is_chat_node_unlock"].includes(field)) {
|
|
2004
|
+
return ["1", "true", "True", "是"].includes(text) ? "是" : "否";
|
|
2005
|
+
}
|
|
2006
|
+
if (["sex", "gender"].includes(field)) {
|
|
2007
|
+
return { "0": "未知", "1": "男", "2": "女" }[text] || text;
|
|
2008
|
+
}
|
|
2009
|
+
if (field === "friend_type") {
|
|
2010
|
+
return { "0": "默认", "1": "特殊" }[text] || text;
|
|
2011
|
+
}
|
|
2012
|
+
if (field === "battle_state") {
|
|
2013
|
+
return { "0": "空闲", "1": "对战中" }[text] || text;
|
|
2014
|
+
}
|
|
2015
|
+
return text;
|
|
2016
|
+
}
|
|
2017
|
+
__name(cleanPlayerFieldValue, "cleanPlayerFieldValue");
|
|
2018
|
+
function parseIngamePlayerPayload(payload, uid) {
|
|
2019
|
+
const rows = payload?.rows || [];
|
|
2020
|
+
const rowMap = {};
|
|
2021
|
+
const labelMap = {};
|
|
2022
|
+
for (const row of rows) {
|
|
2023
|
+
const field = String(row.field || "");
|
|
2024
|
+
if (!field) continue;
|
|
2025
|
+
rowMap[field] = String(row.value ?? "");
|
|
2026
|
+
labelMap[field] = String(row.label || field);
|
|
2027
|
+
}
|
|
2028
|
+
const playerUid = cleanPlayerFieldValue("uin", rowMap.uin || uid);
|
|
2029
|
+
const signature = cleanPlayerFieldValue("signature", rowMap.signature || "");
|
|
2030
|
+
return {
|
|
2031
|
+
title: String(payload?.title || "玩家搜索"),
|
|
2032
|
+
nickname: cleanPlayerFieldValue("name", rowMap.name || "-"),
|
|
2033
|
+
uid: playerUid,
|
|
2034
|
+
level: cleanPlayerFieldValue("level", rowMap.level || "-"),
|
|
2035
|
+
signature: signature === "未设置" ? "" : signature,
|
|
2036
|
+
rowMap,
|
|
2037
|
+
labelMap
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
__name(parseIngamePlayerPayload, "parseIngamePlayerPayload");
|
|
2041
|
+
function playerField(parsed, field, defaultValue = "未设置") {
|
|
2042
|
+
if (!parsed) return defaultValue;
|
|
2043
|
+
const raw = parsed.rowMap[field];
|
|
2044
|
+
if (raw == null || raw === "") return defaultValue;
|
|
2045
|
+
const value = cleanPlayerFieldValue(field, raw);
|
|
2046
|
+
return value && value !== "-" && value !== "未设置" ? value : defaultValue;
|
|
2047
|
+
}
|
|
2048
|
+
__name(playerField, "playerField");
|
|
2049
|
+
function tryParseJson(text) {
|
|
2050
|
+
try {
|
|
2051
|
+
return JSON.parse(text);
|
|
2052
|
+
} catch {
|
|
2053
|
+
return null;
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
__name(tryParseJson, "tryParseJson");
|
|
2057
|
+
function extractTopLevelJsonSegments(text) {
|
|
2058
|
+
const segments = [];
|
|
2059
|
+
let start = -1;
|
|
2060
|
+
let depth = 0;
|
|
2061
|
+
let quote = "";
|
|
2062
|
+
let escaped = false;
|
|
2063
|
+
for (let i = 0; i < text.length; i++) {
|
|
2064
|
+
const ch = text[i];
|
|
2065
|
+
if (quote) {
|
|
2066
|
+
if (escaped) {
|
|
2067
|
+
escaped = false;
|
|
2068
|
+
continue;
|
|
2069
|
+
}
|
|
2070
|
+
if (ch === "\\") {
|
|
2071
|
+
escaped = true;
|
|
2072
|
+
continue;
|
|
2073
|
+
}
|
|
2074
|
+
if (ch === quote) quote = "";
|
|
2075
|
+
continue;
|
|
2076
|
+
}
|
|
2077
|
+
if (ch === '"' || ch === "'") {
|
|
2078
|
+
quote = ch;
|
|
2079
|
+
continue;
|
|
2080
|
+
}
|
|
2081
|
+
if (ch === "{" || ch === "[") {
|
|
2082
|
+
if (depth === 0) start = i;
|
|
2083
|
+
depth++;
|
|
2084
|
+
continue;
|
|
2085
|
+
}
|
|
2086
|
+
if (ch === "}" || ch === "]") {
|
|
2087
|
+
if (depth <= 0) continue;
|
|
2088
|
+
depth--;
|
|
2089
|
+
if (depth === 0 && start >= 0) {
|
|
2090
|
+
segments.push(text.slice(start, i + 1));
|
|
2091
|
+
start = -1;
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
return segments;
|
|
2096
|
+
}
|
|
2097
|
+
__name(extractTopLevelJsonSegments, "extractTopLevelJsonSegments");
|
|
2098
|
+
function collectExchangeItemNames(input, output) {
|
|
2099
|
+
if (input == null) return;
|
|
2100
|
+
if (Array.isArray(input)) {
|
|
2101
|
+
for (const item of input) collectExchangeItemNames(item, output);
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
if (typeof input === "object") {
|
|
2105
|
+
const record = input;
|
|
2106
|
+
const preferredKeys = ["name", "item_name", "title", "label", "text", "value"];
|
|
2107
|
+
let consumedPreferred = false;
|
|
2108
|
+
for (const key of preferredKeys) {
|
|
2109
|
+
if (record[key] == null) continue;
|
|
2110
|
+
consumedPreferred = true;
|
|
2111
|
+
collectExchangeItemNames(record[key], output);
|
|
2112
|
+
}
|
|
2113
|
+
if (!consumedPreferred) {
|
|
2114
|
+
for (const value of Object.values(record)) collectExchangeItemNames(value, output);
|
|
2115
|
+
}
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
if (typeof input === "string") {
|
|
2119
|
+
const text2 = input.trim();
|
|
2120
|
+
if (!text2) return;
|
|
2121
|
+
const direct = tryParseJson(text2);
|
|
2122
|
+
if (direct != null) {
|
|
2123
|
+
collectExchangeItemNames(direct, output);
|
|
2124
|
+
return;
|
|
2125
|
+
}
|
|
2126
|
+
const unescaped = text2.replace(/\\"/g, '"');
|
|
2127
|
+
if (unescaped !== text2) {
|
|
2128
|
+
const escapedParsed = tryParseJson(unescaped);
|
|
2129
|
+
if (escapedParsed != null) {
|
|
2130
|
+
collectExchangeItemNames(escapedParsed, output);
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
const chunks = extractTopLevelJsonSegments(text2);
|
|
2135
|
+
let parsedFromChunks = false;
|
|
2136
|
+
for (const chunk of chunks) {
|
|
2137
|
+
const parsed = tryParseJson(chunk);
|
|
2138
|
+
if (parsed == null) continue;
|
|
2139
|
+
parsedFromChunks = true;
|
|
2140
|
+
collectExchangeItemNames(parsed, output);
|
|
2141
|
+
}
|
|
2142
|
+
if (parsedFromChunks) return;
|
|
2143
|
+
const nameRegex = /["']name["']\s*:\s*["']([^"']+)["']/g;
|
|
2144
|
+
let matched = false;
|
|
2145
|
+
for (let match = nameRegex.exec(text2); match; match = nameRegex.exec(text2)) {
|
|
2146
|
+
const candidate = match[1]?.trim();
|
|
2147
|
+
if (!candidate) continue;
|
|
2148
|
+
output.add(candidate);
|
|
2149
|
+
matched = true;
|
|
2150
|
+
}
|
|
2151
|
+
if (matched) return;
|
|
2152
|
+
output.add(text2);
|
|
2153
|
+
return;
|
|
2154
|
+
}
|
|
2155
|
+
const text = String(input).trim();
|
|
2156
|
+
if (text) output.add(text);
|
|
2157
|
+
}
|
|
2158
|
+
__name(collectExchangeItemNames, "collectExchangeItemNames");
|
|
2159
|
+
function parseExchangeItems(raw) {
|
|
2160
|
+
const names = /* @__PURE__ */ new Set();
|
|
2161
|
+
collectExchangeItemNames(raw, names);
|
|
2162
|
+
return [...names];
|
|
2163
|
+
}
|
|
2164
|
+
__name(parseExchangeItems, "parseExchangeItems");
|
|
2165
|
+
function parseExchangeWantText(raw) {
|
|
2166
|
+
const names = parseExchangeItems(raw);
|
|
2167
|
+
return names[0] || "交友";
|
|
2168
|
+
}
|
|
2169
|
+
__name(parseExchangeWantText, "parseExchangeWantText");
|
|
2170
|
+
function normalizeLineupLookupId(rawValue) {
|
|
2171
|
+
const text = String(rawValue ?? "").trim();
|
|
2172
|
+
if (!text) return "";
|
|
2173
|
+
const match = text.match(/\d+/);
|
|
2174
|
+
return match ? match[0] : text;
|
|
2175
|
+
}
|
|
2176
|
+
__name(normalizeLineupLookupId, "normalizeLineupLookupId");
|
|
2177
|
+
function isTargetLineup(lineup, lineupId) {
|
|
2178
|
+
const target = normalizeLineupLookupId(lineupId);
|
|
2179
|
+
if (!target) return false;
|
|
2180
|
+
const candidates = /* @__PURE__ */ new Set([
|
|
2181
|
+
normalizeLineupLookupId(lineup?.id),
|
|
2182
|
+
normalizeLineupLookupId(lineup?.code),
|
|
2183
|
+
normalizeLineupLookupId(lineup?.lineup_code)
|
|
2184
|
+
]);
|
|
2185
|
+
candidates.delete("");
|
|
2186
|
+
return candidates.has(target);
|
|
2187
|
+
}
|
|
2188
|
+
__name(isTargetLineup, "isTargetLineup");
|
|
2189
|
+
function stringifyInspectValue(value) {
|
|
2190
|
+
if (value === null || value === void 0) return "-";
|
|
2191
|
+
if (typeof value === "boolean") return value ? "是" : "否";
|
|
2192
|
+
if (Array.isArray(value)) {
|
|
2193
|
+
if (!value.length) return "-";
|
|
2194
|
+
if (value.every((item) => item === null || typeof item !== "object")) return value.map(String).join("、");
|
|
2195
|
+
return `共 ${value.length} 项`;
|
|
2196
|
+
}
|
|
2197
|
+
if (typeof value === "object") {
|
|
2198
|
+
const pairs = Object.entries(value).slice(0, 4).map(([key, item]) => `${key}: ${stringifyInspectValue(item)}`);
|
|
2199
|
+
return pairs.length ? `${pairs.join(" | ")}${Object.keys(value).length > 4 ? " | ..." : ""}` : "-";
|
|
2200
|
+
}
|
|
2201
|
+
return String(value);
|
|
2202
|
+
}
|
|
2203
|
+
__name(stringifyInspectValue, "stringifyInspectValue");
|
|
2204
|
+
function accountTypeText(accountType) {
|
|
2205
|
+
return { 0: "自动", 1: "QQ", 2: "微信" }[accountType] || String(accountType);
|
|
2206
|
+
}
|
|
2207
|
+
__name(accountTypeText, "accountTypeText");
|
|
2208
|
+
function normalizeEpochSeconds(value) {
|
|
2209
|
+
const ts = Number(value);
|
|
2210
|
+
if (!Number.isFinite(ts)) return 0;
|
|
2211
|
+
if (ts > 1e13) return Math.floor(ts / 1e6);
|
|
2212
|
+
if (ts > 1e10) return Math.floor(ts / 1e3);
|
|
2213
|
+
return Math.floor(ts);
|
|
2214
|
+
}
|
|
2215
|
+
__name(normalizeEpochSeconds, "normalizeEpochSeconds");
|
|
2216
|
+
function normalizeDurationSeconds(value) {
|
|
2217
|
+
const seconds = Number(value);
|
|
2218
|
+
if (!Number.isFinite(seconds)) return 0;
|
|
2219
|
+
if (seconds > 1e9) return Math.floor(seconds / 1e6);
|
|
2220
|
+
if (seconds > 1e6) return Math.floor(seconds / 1e3);
|
|
2221
|
+
return Math.floor(seconds);
|
|
2222
|
+
}
|
|
2223
|
+
__name(normalizeDurationSeconds, "normalizeDurationSeconds");
|
|
2224
|
+
function formatHomeRemaining(targetTs, nowTs = Math.floor(Date.now() / 1e3)) {
|
|
2225
|
+
if (!targetTs) return "未开始";
|
|
2226
|
+
const remain = Math.max(0, targetTs - nowTs);
|
|
2227
|
+
if (remain <= 0) return "已完成";
|
|
2228
|
+
const hours = Math.floor(remain / 3600);
|
|
2229
|
+
const minutes = Math.floor(remain % 3600 / 60);
|
|
2230
|
+
if (hours >= 24) return `${Math.floor(hours / 24)}天${hours % 24}小时`;
|
|
2231
|
+
if (hours > 0) return `${hours}小时${minutes}分钟`;
|
|
2232
|
+
return `${minutes}分钟`;
|
|
2233
|
+
}
|
|
2234
|
+
__name(formatHomeRemaining, "formatHomeRemaining");
|
|
2235
|
+
function homeInfoPayload(res) {
|
|
2236
|
+
const payload = res || {};
|
|
2237
|
+
if (payload.result?.home_info) return payload.result.home_info;
|
|
2238
|
+
if (payload.home_info) return payload.home_info;
|
|
2239
|
+
if (payload.data?.result?.home_info) return payload.data.result.home_info;
|
|
2240
|
+
if (payload.data?.home_info) return payload.data.home_info;
|
|
2241
|
+
return payload && typeof payload === "object" ? payload : {};
|
|
2242
|
+
}
|
|
2243
|
+
__name(homeInfoPayload, "homeInfoPayload");
|
|
2244
|
+
function homeBriefInfo(homeInfo) {
|
|
2245
|
+
return homeInfo?.friend_home_brief_info || homeInfo?.home_brief_info || homeInfo || {};
|
|
2246
|
+
}
|
|
2247
|
+
__name(homeBriefInfo, "homeBriefInfo");
|
|
2248
|
+
function homeCellInfo(homeInfo) {
|
|
2249
|
+
return homeInfo?.friend_cell_home_brief_info || homeInfo?.cell_home_brief_info || {};
|
|
2250
|
+
}
|
|
2251
|
+
__name(homeCellInfo, "homeCellInfo");
|
|
2252
|
+
function homePetIcon(petId, iconUrl = "") {
|
|
2253
|
+
if (iconUrl) return iconUrl;
|
|
2254
|
+
let assetId = Number(String(petId || "0"));
|
|
2255
|
+
if (!Number.isFinite(assetId) || assetId <= 0) return "";
|
|
2256
|
+
if (assetId < 3e3) assetId += 3e3;
|
|
2257
|
+
return `https://game.gtimg.cn/images/rocom/rocodata/jingling/${assetId}/icon.png`;
|
|
2258
|
+
}
|
|
2259
|
+
__name(homePetIcon, "homePetIcon");
|
|
2260
|
+
function extractHomePet(raw, index, guard = false) {
|
|
2261
|
+
if (!raw || typeof raw !== "object") return null;
|
|
2262
|
+
const homePet = raw.home_pet_info && typeof raw.home_pet_info === "object" ? raw.home_pet_info : raw;
|
|
2263
|
+
const display = raw.display_info && typeof raw.display_info === "object" ? raw.display_info : {};
|
|
2264
|
+
const petId = homePet.pet_cfg_id || homePet.pet_id || homePet.pet_base_id || raw.pet_cfg_id || raw.pet_id || raw.id;
|
|
2265
|
+
if (["", "0"].includes(String(petId || "0")) && !guard) return null;
|
|
2266
|
+
const feedInfo = homePet.feed_info && typeof homePet.feed_info === "object" ? homePet.feed_info : {};
|
|
2267
|
+
const beginTime = normalizeEpochSeconds(feedInfo.begin_time);
|
|
2268
|
+
const timeCost = normalizeDurationSeconds(feedInfo.time_cost);
|
|
2269
|
+
let readyAt = normalizeEpochSeconds(homePet.pet_rip_time || raw.pet_rip_time || raw.rip_time);
|
|
2270
|
+
if (!readyAt && beginTime && timeCost) readyAt = beginTime + timeCost;
|
|
2271
|
+
const nowTs = Math.floor(Date.now() / 1e3);
|
|
2272
|
+
const hasInspiration = Boolean(readyAt);
|
|
2273
|
+
const inspireReady = hasInspiration && nowTs >= readyAt;
|
|
2274
|
+
const isGuard = guard || Boolean(raw.is_guard || raw.guard) || ["2", "guard", "守卫"].includes(String(raw.status).toLowerCase());
|
|
2275
|
+
const statusText = isGuard && !hasInspiration ? "守卫中" : inspireReady ? "灵感已完成" : hasInspiration ? "灵感收集中" : "未喂食";
|
|
2276
|
+
const statusClass = isGuard && !hasInspiration ? "guard" : inspireReady ? "ready" : hasInspiration ? "progress" : "idle";
|
|
2277
|
+
return {
|
|
2278
|
+
id: String(petId || ""),
|
|
2279
|
+
pos: raw.pos || raw.position || index + 1,
|
|
2280
|
+
name: String(homePet.name || homePet.pet_name || raw.name || raw.pet_name || `精灵 ${petId || ""}`),
|
|
2281
|
+
level: display.level || raw.level || homePet.level || "--",
|
|
2282
|
+
iconUrl: homePetIcon(petId, raw.icon_url || raw.pet_img_url || raw.petIcon || ""),
|
|
2283
|
+
badge: isGuard ? "守" : "",
|
|
2284
|
+
isGuard,
|
|
2285
|
+
statusText,
|
|
2286
|
+
statusClass,
|
|
2287
|
+
note: hasInspiration ? formatHomeRemaining(readyAt, nowTs) : isGuard ? "家园守卫位" : "暂无灵感倒计时",
|
|
2288
|
+
inspireReady,
|
|
2289
|
+
readyAt
|
|
2290
|
+
};
|
|
2291
|
+
}
|
|
2292
|
+
__name(extractHomePet, "extractHomePet");
|
|
2293
|
+
function homePetSources(homeInfo) {
|
|
2294
|
+
const cell = homeCellInfo(homeInfo);
|
|
2295
|
+
const indoorSources = [];
|
|
2296
|
+
const guardSources = [];
|
|
2297
|
+
if (Array.isArray(homeInfo?.home_pets)) indoorSources.push(...homeInfo.home_pets);
|
|
2298
|
+
if (Array.isArray(cell?.home_pets)) {
|
|
2299
|
+
for (const pet of cell.home_pets) {
|
|
2300
|
+
const homePet = pet?.home_pet_info || {};
|
|
2301
|
+
if (String(homePet.pet_cfg_id || "0") === "0" && (homePet.name || homePet.pet_name)) guardSources.push(pet);
|
|
2302
|
+
else indoorSources.push(pet);
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
const petInfo = cell?.home_pet_info || {};
|
|
2306
|
+
if (Array.isArray(petInfo.home_pet_list)) indoorSources.push(...petInfo.home_pet_list);
|
|
2307
|
+
for (const key of ["guard_pets", "home_guard_pets", "guard_pet_list"]) {
|
|
2308
|
+
if (Array.isArray(homeInfo?.[key])) guardSources.push(...homeInfo[key]);
|
|
2309
|
+
if (Array.isArray(cell?.[key])) guardSources.push(...cell[key]);
|
|
2310
|
+
}
|
|
2311
|
+
for (const key of ["guard_pet", "home_guard_pet", "guard_pet_info", "home_guard_pet_info", "defend_pet", "defend_pet_info", "protect_pet", "protect_pet_info"]) {
|
|
2312
|
+
if (homeInfo?.[key] && typeof homeInfo[key] === "object") guardSources.push(homeInfo[key]);
|
|
2313
|
+
if (cell?.[key] && typeof cell[key] === "object") guardSources.push(cell[key]);
|
|
2314
|
+
}
|
|
2315
|
+
return { indoorSources, guardSources };
|
|
2316
|
+
}
|
|
2317
|
+
__name(homePetSources, "homePetSources");
|
|
2318
|
+
function homePlantIcon(deps, iconId) {
|
|
2319
|
+
if (!iconId) return "";
|
|
2320
|
+
const text = String(iconId);
|
|
2321
|
+
if (/^(https?:|data:)/.test(text)) return text;
|
|
2322
|
+
return deps.renderer.resourceUrl(`render-templates/home/img/home_icon/${text}_2.png`);
|
|
2323
|
+
}
|
|
2324
|
+
__name(homePlantIcon, "homePlantIcon");
|
|
2325
|
+
var homePlantMapCache = null;
|
|
2326
|
+
function loadHomePlantMap() {
|
|
2327
|
+
if (homePlantMapCache) return homePlantMapCache;
|
|
2328
|
+
const filePath = import_node_path4.default.resolve(__dirname, "..", "render-templates", "home", "data", "home_item_list.json");
|
|
2329
|
+
try {
|
|
2330
|
+
const data = JSON.parse(import_node_fs4.default.readFileSync(filePath, "utf-8"));
|
|
2331
|
+
homePlantMapCache = data && typeof data === "object" ? data : {};
|
|
2332
|
+
} catch (err) {
|
|
2333
|
+
logger7.warn(`加载家园作物映射失败: ${err}`);
|
|
2334
|
+
homePlantMapCache = {};
|
|
2335
|
+
}
|
|
2336
|
+
return homePlantMapCache;
|
|
2337
|
+
}
|
|
2338
|
+
__name(loadHomePlantMap, "loadHomePlantMap");
|
|
2339
|
+
function extractHomePlants(deps, homeInfo) {
|
|
2340
|
+
const cell = homeCellInfo(homeInfo);
|
|
2341
|
+
const plantSources = [];
|
|
2342
|
+
const plantMap = loadHomePlantMap();
|
|
2343
|
+
if (Array.isArray(homeInfo?.home_plants)) plantSources.push(...homeInfo.home_plants);
|
|
2344
|
+
const plantInfo = cell?.home_plant_info || {};
|
|
2345
|
+
for (const land of Array.isArray(plantInfo.home_plant_land_list) ? plantInfo.home_plant_land_list : []) {
|
|
2346
|
+
for (const item of land.home_plant_list || []) plantSources.push({ ...item, land_index: land.land_index });
|
|
2347
|
+
}
|
|
2348
|
+
const nowTs = Math.floor(Date.now() / 1e3);
|
|
2349
|
+
return plantSources.map((raw, index) => {
|
|
2350
|
+
const plantData = raw.plant_info && typeof raw.plant_info === "object" ? raw.plant_info : raw;
|
|
2351
|
+
const plantId = raw.plant_seed_id || raw.plant_cfg_id || raw.plant_id || plantData.id;
|
|
2352
|
+
if (["", "0"].includes(String(plantId || "0"))) return null;
|
|
2353
|
+
const mappedPlant = plantMap[String(plantId)] || {};
|
|
2354
|
+
const iconId = plantData.icon_url || plantData.iconUrl || raw.icon_url || raw.iconUrl || plantData.iconid || raw.iconid || raw.icon_id || mappedPlant.iconid;
|
|
2355
|
+
let readyAt = normalizeEpochSeconds(raw.plant_rip_time || raw.rip_time || raw.end_time);
|
|
2356
|
+
const leftTime = Number(raw.left_time || 0);
|
|
2357
|
+
if (!readyAt && leftTime > 0) readyAt = nowTs + leftTime;
|
|
2358
|
+
const ready = Boolean(readyAt && nowTs >= readyAt) || ["2", "ready", "mature"].includes(String(raw.status));
|
|
2359
|
+
const total = Number(raw.time_cost || raw.total_time || 0);
|
|
2360
|
+
const progress = total && readyAt ? Math.max(0, Math.min(100, Math.floor((total - Math.max(0, readyAt - nowTs)) / total * 100))) : ready ? 100 : 35;
|
|
2361
|
+
const harvestNum = raw.plant_harvest_num;
|
|
2362
|
+
const stealAccount = raw.plant_steal_account;
|
|
2363
|
+
const canStealAccount = raw.plant_can_steal_account;
|
|
2364
|
+
return {
|
|
2365
|
+
id: String(plantId),
|
|
2366
|
+
landIndex: raw.slot_index || raw.land_index || index + 1,
|
|
2367
|
+
plantName: plantData.name || raw.name || mappedPlant.name || `种子 ${plantId}`,
|
|
2368
|
+
iconUrl: homePlantIcon(deps, iconId),
|
|
2369
|
+
stateType: ready ? "ready" : "warning",
|
|
2370
|
+
statusText: ready ? "已成熟" : "成长中",
|
|
2371
|
+
leftTimeText: ready ? "可收获" : formatHomeRemaining(readyAt, nowTs),
|
|
2372
|
+
progress,
|
|
2373
|
+
ready,
|
|
2374
|
+
readyAt,
|
|
2375
|
+
harvestText: harvestNum !== void 0 && harvestNum !== "" ? `产量 ${harvestNum}` : "",
|
|
2376
|
+
stealText: stealAccount !== void 0 && canStealAccount !== void 0 ? `可偷 ${stealAccount}/${canStealAccount}` : ""
|
|
2377
|
+
};
|
|
2378
|
+
}).filter(Boolean);
|
|
2379
|
+
}
|
|
2380
|
+
__name(extractHomePlants, "extractHomePlants");
|
|
2381
|
+
function buildHomeRenderData(deps, res, uid) {
|
|
2382
|
+
const homeInfo = homeInfoPayload(res);
|
|
2383
|
+
const brief = homeBriefInfo(homeInfo);
|
|
2384
|
+
const { indoorSources, guardSources } = homePetSources(homeInfo);
|
|
2385
|
+
const indoorPets = [];
|
|
2386
|
+
const guardPets = [];
|
|
2387
|
+
indoorSources.forEach((raw, index) => {
|
|
2388
|
+
const item = extractHomePet(raw, index);
|
|
2389
|
+
if (!item) return;
|
|
2390
|
+
if (item.isGuard) guardPets.push(item);
|
|
2391
|
+
else indoorPets.push(item);
|
|
2392
|
+
});
|
|
2393
|
+
guardSources.forEach((raw, index) => {
|
|
2394
|
+
const item = extractHomePet(raw, index, true);
|
|
2395
|
+
if (item) guardPets.push(item);
|
|
2396
|
+
});
|
|
2397
|
+
const gardenPlots = extractHomePlants(deps, homeInfo);
|
|
2398
|
+
const createdAt = normalizeEpochSeconds(res?.meta?.created_at);
|
|
2399
|
+
return {
|
|
2400
|
+
title: "洛克家园",
|
|
2401
|
+
subtitle: "Home Information",
|
|
2402
|
+
homeName: brief.home_name || brief.name || `${uid} 的小屋`,
|
|
2403
|
+
uid,
|
|
2404
|
+
summaryCards: [
|
|
2405
|
+
{ label: "房间等级", value: brief.room_level || "--" },
|
|
2406
|
+
{ label: "家园等级", value: brief.home_level || "--" },
|
|
2407
|
+
{ label: "家园经验", value: brief.home_experience || "--" },
|
|
2408
|
+
{ label: "舒适度", value: brief.home_comfort_level || "--" }
|
|
2409
|
+
],
|
|
2410
|
+
gardenPlots,
|
|
2411
|
+
guardPets,
|
|
2412
|
+
indoorPets,
|
|
2413
|
+
gardenCount: gardenPlots.length,
|
|
2414
|
+
guardCount: guardPets.length,
|
|
2415
|
+
indoorCount: indoorPets.length,
|
|
2416
|
+
guardEmptyText: "后端当前返回中没有守卫精灵字段",
|
|
2417
|
+
updatedAt: new Date(createdAt ? createdAt * 1e3 : Date.now()).toLocaleString("zh-CN")
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
__name(buildHomeRenderData, "buildHomeRenderData");
|
|
2421
|
+
function buildPlayerSearchRenderData(payload, uid) {
|
|
2422
|
+
const parsed = parseIngamePlayerPayload(payload, uid);
|
|
2423
|
+
const pack = /* @__PURE__ */ __name((title, pairs) => {
|
|
2424
|
+
const items = pairs.filter(([, value]) => value && value !== "-" && value !== "未设置").map(([label, value]) => ({ label, value }));
|
|
2425
|
+
return items.length ? { title, items } : null;
|
|
2426
|
+
}, "pack");
|
|
2427
|
+
const sections = [
|
|
2428
|
+
pack("核心档案", [
|
|
2429
|
+
["等级", parsed.level],
|
|
2430
|
+
["在线状态", playerField(parsed, "online", playerField(parsed, "is_online"))],
|
|
2431
|
+
["性别", playerField(parsed, "gender", playerField(parsed, "sex"))],
|
|
2432
|
+
["世界等级", playerField(parsed, "world_level")],
|
|
2433
|
+
["图鉴收集", playerField(parsed, "card_handbook_collect_num")],
|
|
2434
|
+
["最后离线", playerField(parsed, "last_logout_time")]
|
|
2435
|
+
]),
|
|
2436
|
+
pack("家园信息", [
|
|
2437
|
+
["家园名称", playerField(parsed, "home_name")],
|
|
2438
|
+
["家园等级", playerField(parsed, "home_level")],
|
|
2439
|
+
["家园经验", playerField(parsed, "home_experience")],
|
|
2440
|
+
["舒适度", playerField(parsed, "home_comfort_level")],
|
|
2441
|
+
["访客数量", playerField(parsed, "visitor_num")]
|
|
2442
|
+
]),
|
|
2443
|
+
pack("名片信息", [
|
|
2444
|
+
["名片皮肤", playerField(parsed, "card_skin_selected")],
|
|
2445
|
+
["名片头像", playerField(parsed, "card_icon_selected")],
|
|
2446
|
+
["首标签", playerField(parsed, "card_label_first_selected")],
|
|
2447
|
+
["尾标签", playerField(parsed, "card_label_last_selected")]
|
|
2448
|
+
])
|
|
2449
|
+
].filter(Boolean);
|
|
2450
|
+
const summaryCards = [
|
|
2451
|
+
{ label: "等级", value: parsed.level },
|
|
2452
|
+
{ label: "在线状态", value: playerField(parsed, "online", playerField(parsed, "is_online")) },
|
|
2453
|
+
{ label: "世界等级", value: playerField(parsed, "world_level") },
|
|
2454
|
+
{ label: "图鉴收集", value: playerField(parsed, "card_handbook_collect_num") },
|
|
2455
|
+
{ label: "家园等级", value: playerField(parsed, "home_level") },
|
|
2456
|
+
{ label: "舒适度", value: playerField(parsed, "home_comfort_level") }
|
|
2457
|
+
].filter((item) => item.value && item.value !== "-");
|
|
2458
|
+
const signature = parsed.signature && parsed.signature !== "未设置" ? parsed.signature : "";
|
|
2459
|
+
return {
|
|
2460
|
+
title: "洛克玩家",
|
|
2461
|
+
subtitle: parsed.title,
|
|
2462
|
+
heroTitle: "玩家信息",
|
|
2463
|
+
heroValue: parsed.nickname,
|
|
2464
|
+
heroSubvalue: `UID ${parsed.uid}`,
|
|
2465
|
+
summaryCards,
|
|
2466
|
+
signature,
|
|
2467
|
+
showSignature: Boolean(signature),
|
|
2468
|
+
sections,
|
|
2469
|
+
commandHint: "洛克.玩家 <UID>",
|
|
2470
|
+
copyright: "Koishi & WeGame Locke Kingdom Plugin"
|
|
2471
|
+
};
|
|
2472
|
+
}
|
|
2473
|
+
__name(buildPlayerSearchRenderData, "buildPlayerSearchRenderData");
|
|
2474
|
+
function buildShopRenderData(payload, shopId) {
|
|
2475
|
+
const sections = [];
|
|
2476
|
+
const detailItems = [];
|
|
2477
|
+
const summaryCards = [{ label: "商店 ID", value: shopId }];
|
|
2478
|
+
if (Array.isArray(payload?.rows)) {
|
|
2479
|
+
detailItems.push(...payload.rows.filter((row) => Number(row.level || 0) === 0).map((row) => ({
|
|
2480
|
+
label: row.label || row.field || "-",
|
|
2481
|
+
value: stringifyInspectValue(row.value)
|
|
2482
|
+
})));
|
|
2483
|
+
} else {
|
|
2484
|
+
for (const [key, value] of Object.entries(payload || {})) {
|
|
2485
|
+
if (Array.isArray(value)) {
|
|
2486
|
+
summaryCards.push({ label: key, value: String(value.length) });
|
|
2487
|
+
sections.push({
|
|
2488
|
+
title: key.replace(/_/g, " "),
|
|
2489
|
+
cards: value.slice(0, 24).map((item, index) => ({
|
|
2490
|
+
title: item?.name || item?.title || item?.item_name || `${key} #${index + 1}`,
|
|
2491
|
+
image: item?.icon || item?.icon_url || item?.image || item?.image_url || "",
|
|
2492
|
+
meta: Object.entries(item || {}).filter(([metaKey, metaValue]) => !["name", "title", "item_name", "icon", "icon_url", "image", "image_url"].includes(metaKey) && (metaValue === null || typeof metaValue !== "object")).slice(0, 6).map(([metaKey, metaValue]) => ({ label: metaKey.replace(/_/g, " "), value: stringifyInspectValue(metaValue) }))
|
|
2493
|
+
}))
|
|
2494
|
+
});
|
|
2495
|
+
} else if (value === null || typeof value !== "object") {
|
|
2496
|
+
detailItems.push({ label: key.replace(/_/g, " "), value: stringifyInspectValue(value) });
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
return {
|
|
2501
|
+
title: "洛克商店",
|
|
2502
|
+
subtitle: `shop_id = ${shopId}`,
|
|
2503
|
+
heroTitle: "商店查询",
|
|
2504
|
+
heroValue: detailItems.find((item) => ["name", "title", "名称", "标题"].includes(item.label))?.value || shopId,
|
|
2505
|
+
heroSubvalue: `shop_id = ${shopId}`,
|
|
2506
|
+
summaryCards: summaryCards.slice(0, 3),
|
|
2507
|
+
sections,
|
|
2508
|
+
detailItems: detailItems.slice(0, 18),
|
|
2509
|
+
commandHint: "洛克.商店 <shop_id>",
|
|
2510
|
+
copyright: "Koishi & WeGame Locke Kingdom Plugin"
|
|
2511
|
+
};
|
|
2512
|
+
}
|
|
2513
|
+
__name(buildShopRenderData, "buildShopRenderData");
|
|
2514
|
+
function buildFriendshipRenderData(payload, userIds) {
|
|
2515
|
+
const result = payload?.result || {};
|
|
2516
|
+
const users = payload?.user_list || payload?.userList || [];
|
|
2517
|
+
const userCards = users.map((user, index) => {
|
|
2518
|
+
const statusCode = user.status;
|
|
2519
|
+
return {
|
|
2520
|
+
title: `用户 ${index + 1}`,
|
|
2521
|
+
userId: String(user.user_id || user.userId || "-"),
|
|
2522
|
+
statusCode: stringifyInspectValue(statusCode),
|
|
2523
|
+
statusText: String(statusCode) === "0" ? "状态正常" : `状态码 ${statusCode}`,
|
|
2524
|
+
statusDesc: "接口已返回该用户状态,但后端当前没有提供更具体的关系类型说明。"
|
|
2525
|
+
};
|
|
2526
|
+
});
|
|
2527
|
+
return {
|
|
2528
|
+
title: "好友关系",
|
|
2529
|
+
subtitle: `查询 ID:${userIds}`,
|
|
2530
|
+
summaryCards: [
|
|
2531
|
+
{ label: "查询对象", value: String(userCards.length || userIds.split(",").length) },
|
|
2532
|
+
{ label: "接口状态", value: Number(result.error_code || 0) === 0 ? "成功" : "异常" },
|
|
2533
|
+
{ label: "上游返回", value: result.error_message || "OK" }
|
|
2534
|
+
],
|
|
2535
|
+
userCards,
|
|
2536
|
+
resultCode: stringifyInspectValue(result.error_code || 0),
|
|
2537
|
+
resultDesc: "当前接口只返回 status 字段,尚未提供“好友/非好友/黑名单”等可读关系类型。",
|
|
2538
|
+
commandHint: "洛克.好友关系 <id1,id2>",
|
|
2539
|
+
copyright: "Koishi & WeGame Locke Kingdom Plugin"
|
|
2540
|
+
};
|
|
2541
|
+
}
|
|
2542
|
+
__name(buildFriendshipRenderData, "buildFriendshipRenderData");
|
|
2543
|
+
function buildStudentRenderData(statePayload, perksPayload, area, accountType) {
|
|
2544
|
+
const school = statePayload?.school || statePayload?.school_name || "未返回";
|
|
2545
|
+
const certified = String(statePayload?.certified) === "1";
|
|
2546
|
+
const cards = perksPayload?.cards || [];
|
|
2547
|
+
return {
|
|
2548
|
+
title: "洛克学生",
|
|
2549
|
+
subtitle: `大区:${area} 账号类型:${accountTypeText(accountType)}`,
|
|
2550
|
+
heroTitle: "学生信息总览",
|
|
2551
|
+
heroValue: certified ? "已通过" : "未认证",
|
|
2552
|
+
heroSubvalue: school,
|
|
2553
|
+
summaryCards: [
|
|
2554
|
+
{ label: "认证状态", value: certified ? "已认证" : "未认证" },
|
|
2555
|
+
{ label: "学校", value: school },
|
|
2556
|
+
{ label: "奖励数量", value: String(cards.length) }
|
|
2557
|
+
],
|
|
2558
|
+
stateItems: [
|
|
2559
|
+
{ label: "学生认证", value: certified ? "是" : "否" },
|
|
2560
|
+
{ label: "游戏内认证", value: String(statePayload?.game_certified) === "1" ? "是" : "否" },
|
|
2561
|
+
{ label: "学校", value: school },
|
|
2562
|
+
{ label: "上游状态", value: statePayload?.result?.error_message || "WG_COMM_SUCC" },
|
|
2563
|
+
{ label: "上游错误码", value: stringifyInspectValue(statePayload?.result?.error_code || 0) }
|
|
2564
|
+
],
|
|
2565
|
+
perkCards: cards.map((card) => ({
|
|
2566
|
+
name: card.name || `奖励 #${card.id || "-"}`,
|
|
2567
|
+
count: card.count || 0,
|
|
2568
|
+
desc: card.desc || "暂无说明",
|
|
2569
|
+
icon: card.icon || "",
|
|
2570
|
+
id: stringifyInspectValue(card.id),
|
|
2571
|
+
stateText: `状态码 ${stringifyInspectValue(card.state)}`
|
|
2572
|
+
})),
|
|
2573
|
+
detailItems: Object.entries(perksPayload || {}).filter(([key, value]) => !["cards", "result"].includes(key) && (value === null || typeof value !== "object")).map(([key, value]) => ({ label: key.replace(/_/g, " "), value: stringifyInspectValue(value) })),
|
|
2574
|
+
stateResult: statePayload?.result?.error_message || "WG_COMM_SUCC",
|
|
2575
|
+
perksResult: perksPayload?.result?.error_message || "WG_COMM_SUCC",
|
|
2576
|
+
commandHint: "洛克.学生 [area] [account_type]",
|
|
2577
|
+
copyright: "Koishi & WeGame Locke Kingdom Plugin"
|
|
2578
|
+
};
|
|
2579
|
+
}
|
|
2580
|
+
__name(buildStudentRenderData, "buildStudentRenderData");
|
|
2581
|
+
function sessionTarget(session) {
|
|
2582
|
+
return {
|
|
2583
|
+
platform: session?.platform || session?.bot?.platform || "",
|
|
2584
|
+
channelId: session?.channelId || session?.guildId || "",
|
|
2585
|
+
userId: session?.guildId ? "" : session?.userId || ""
|
|
2586
|
+
};
|
|
2587
|
+
}
|
|
2588
|
+
__name(sessionTarget, "sessionTarget");
|
|
2589
|
+
function homeSubscriptionKey(session, uid, kind) {
|
|
2590
|
+
const target = sessionTarget(session);
|
|
2591
|
+
return [target.platform, target.channelId || "private", target.userId || session?.guildId || "", uid, kind].join(":");
|
|
2592
|
+
}
|
|
2593
|
+
__name(homeSubscriptionKey, "homeSubscriptionKey");
|
|
2594
|
+
function isBotAdmin(session, adminUserIds) {
|
|
2595
|
+
return adminUserIds.includes(session?.userId || "");
|
|
2596
|
+
}
|
|
2597
|
+
__name(isBotAdmin, "isBotAdmin");
|
|
2598
|
+
async function resolveHomeUid(deps, session, uid = "") {
|
|
2599
|
+
const targetUid = String(uid || "").trim();
|
|
2600
|
+
if (targetUid) return targetUid;
|
|
2601
|
+
return String(deps.userMgr.getPrimaryBinding(session?.userId || "")?.role_id || "");
|
|
2602
|
+
}
|
|
2603
|
+
__name(resolveHomeUid, "resolveHomeUid");
|
|
2604
|
+
async function subscribeHome(deps, session, uid, kind) {
|
|
2605
|
+
const target = sessionTarget(session);
|
|
2606
|
+
if (!target.userId && !isBotAdmin(session, deps.config.adminUserIds)) return "此指令仅限管理员使用。";
|
|
2607
|
+
const targetUid = await resolveHomeUid(deps, session, uid);
|
|
2608
|
+
if (!targetUid) return kind === "garden" ? "请提供玩家 UID,或先完成绑定后再订阅家园菜园。" : "请提供玩家 UID,或先完成绑定后再订阅家园灵感。";
|
|
2609
|
+
const key = homeSubscriptionKey(session, targetUid, kind);
|
|
2610
|
+
deps.homeSubMgr.upsert(key, {
|
|
2611
|
+
key,
|
|
2612
|
+
kind,
|
|
2613
|
+
uid: targetUid,
|
|
2614
|
+
platform: target.platform,
|
|
2615
|
+
channel_id: target.channelId,
|
|
2616
|
+
guild_id: session?.guildId || "",
|
|
2617
|
+
user_id: target.userId,
|
|
2618
|
+
updated_by: session?.userId || "",
|
|
2619
|
+
notify_state: {},
|
|
2620
|
+
updated_at: Math.floor(Date.now() / 1e3)
|
|
2621
|
+
});
|
|
2622
|
+
return kind === "garden" ? `已订阅 UID ${targetUid} 的家园菜园提醒:首个成熟和全部成熟时各推送一次。` : `已订阅 UID ${targetUid} 的家园精灵灵感提醒:首个完成和全部完成时各推送一次。`;
|
|
2623
|
+
}
|
|
2624
|
+
__name(subscribeHome, "subscribeHome");
|
|
2625
|
+
function homeSubscriptionState(data, kind) {
|
|
2626
|
+
if (kind === "garden") {
|
|
2627
|
+
const items2 = data.gardenPlots || [];
|
|
2628
|
+
const readyItems2 = items2.filter((item) => item.ready);
|
|
2629
|
+
const names2 = readyItems2.map((item) => `田地${item.landIndex} ${item.plantName}`);
|
|
2630
|
+
return { items: items2, readyItems: readyItems2, names: names2 };
|
|
2631
|
+
}
|
|
2632
|
+
const items = [...data.indoorPets || [], ...data.guardPets || []].filter((item) => item.readyAt);
|
|
2633
|
+
const readyItems = items.filter((item) => item.inspireReady);
|
|
2634
|
+
const names = readyItems.map((item) => item.name || "未知精灵");
|
|
2635
|
+
return { items, readyItems, names };
|
|
2636
|
+
}
|
|
2637
|
+
__name(homeSubscriptionState, "homeSubscriptionState");
|
|
2638
|
+
function homeSubscriptionMessage(uid, kind, level, totalCount, readyItems, names) {
|
|
2639
|
+
const kindText = kind === "garden" ? "菜园作物" : "精灵灵感";
|
|
2640
|
+
const actionText = kind === "garden" ? "成熟" : "完成";
|
|
2641
|
+
const levelText = level === "first" ? "首个" : "全部";
|
|
2642
|
+
return [
|
|
2643
|
+
`家园${kindText}${levelText}${actionText}提醒:${uid}`,
|
|
2644
|
+
`进度:${readyItems.length}/${totalCount}`,
|
|
2645
|
+
names.length ? `已完成:${names.slice(0, 8).join("、")}` : ""
|
|
2646
|
+
].filter(Boolean).join("\n");
|
|
2647
|
+
}
|
|
2648
|
+
__name(homeSubscriptionMessage, "homeSubscriptionMessage");
|
|
2649
|
+
async function checkHomeSubscriptions(deps) {
|
|
2650
|
+
const subs = deps.homeSubMgr.getAll();
|
|
2651
|
+
const cache = /* @__PURE__ */ new Map();
|
|
2652
|
+
let checkedCount = 0;
|
|
2653
|
+
let pushedCount = 0;
|
|
2654
|
+
for (const [key, sub] of Object.entries(subs)) {
|
|
2655
|
+
if (!sub.uid || !["garden", "inspiration"].includes(sub.kind)) continue;
|
|
2656
|
+
checkedCount++;
|
|
2657
|
+
if (!cache.has(sub.uid)) {
|
|
2658
|
+
cache.set(sub.uid, await deps.client.ingameHomeInfo(deps.ctx, sub.uid));
|
|
2659
|
+
}
|
|
2660
|
+
const res = cache.get(sub.uid);
|
|
2661
|
+
if (!res) continue;
|
|
2662
|
+
const data = buildHomeRenderData(deps, res, sub.uid);
|
|
2663
|
+
const { items, readyItems, names } = homeSubscriptionState(data, sub.kind);
|
|
2664
|
+
const totalCount = items.length;
|
|
2665
|
+
if (totalCount <= 0) continue;
|
|
2666
|
+
const notifyState = sub.notify_state || {};
|
|
2667
|
+
const pushLevels = [];
|
|
2668
|
+
if (!readyItems.length) {
|
|
2669
|
+
notifyState.first = false;
|
|
2670
|
+
notifyState.all = false;
|
|
2671
|
+
} else {
|
|
2672
|
+
if (!notifyState.first) pushLevels.push("first");
|
|
2673
|
+
if (readyItems.length >= totalCount && !notifyState.all) pushLevels.push("all");
|
|
2674
|
+
if (readyItems.length < totalCount) notifyState.all = false;
|
|
2675
|
+
}
|
|
2676
|
+
if (!pushLevels.length) {
|
|
2677
|
+
deps.homeSubMgr.upsert(key, { ...sub, notify_state: notifyState });
|
|
2678
|
+
continue;
|
|
2679
|
+
}
|
|
2680
|
+
const messages = pushLevels.map((level) => homeSubscriptionMessage(sub.uid, sub.kind, level, totalCount, readyItems, names));
|
|
2681
|
+
try {
|
|
2682
|
+
const sent = await sendScheduledMessage(deps.ctx, {
|
|
2683
|
+
platform: sub.platform,
|
|
2684
|
+
channelId: sub.channel_id || sub.guild_id || sub.user_id || "",
|
|
2685
|
+
guildId: sub.guild_id || "",
|
|
2686
|
+
userId: sub.user_id || ""
|
|
2687
|
+
}, messages.join("\n\n"));
|
|
2688
|
+
if (!sent) continue;
|
|
2689
|
+
} catch (e) {
|
|
2690
|
+
logger7.warn(`家园订阅推送失败: ${e}`);
|
|
2691
|
+
continue;
|
|
2692
|
+
}
|
|
2693
|
+
for (const level of pushLevels) notifyState[level] = true;
|
|
2694
|
+
pushedCount += pushLevels.length;
|
|
2695
|
+
deps.homeSubMgr.upsert(key, { ...sub, notify_state: notifyState, last_push_time: Math.floor(Date.now() / 1e3) });
|
|
2696
|
+
}
|
|
2697
|
+
return { subscriptions: Object.keys(subs).length, checked: checkedCount, pushed: pushedCount };
|
|
2698
|
+
}
|
|
2699
|
+
__name(checkHomeSubscriptions, "checkHomeSubscriptions");
|
|
2700
|
+
function register2(deps) {
|
|
2701
|
+
const { ctx, client } = deps;
|
|
2702
|
+
ctx.command("洛克").subcommand(".档案", "查看个人档案").action(async ({ session }) => {
|
|
2703
|
+
const fwToken = await getPrimaryToken(deps, session.userId);
|
|
2704
|
+
if (!fwToken) return notLoggedInHint();
|
|
2705
|
+
const userIdentifier = session.userId;
|
|
2706
|
+
await session.send("正在获取洛克王国数据...");
|
|
2707
|
+
const [roleRes, evalRes, sumRes, collRes, boRes, blRes] = await Promise.all([
|
|
2708
|
+
client.getRole(ctx, fwToken, void 0, userIdentifier),
|
|
2709
|
+
client.getEvaluation(ctx, fwToken, userIdentifier),
|
|
2710
|
+
client.getPetSummary(ctx, fwToken, userIdentifier),
|
|
2711
|
+
client.getCollection(ctx, fwToken, userIdentifier),
|
|
2712
|
+
client.getBattleOverview(ctx, fwToken, userIdentifier),
|
|
2713
|
+
client.getBattleList(ctx, fwToken, 1, "", userIdentifier)
|
|
2714
|
+
]);
|
|
2715
|
+
if (!roleRes?.role) return "获取角色档案失败,凭据可能已过期,请重新登录。";
|
|
2716
|
+
const role = roleRes.role;
|
|
2717
|
+
const ev = evalRes || {};
|
|
2718
|
+
const sm = sumRes || {};
|
|
2719
|
+
const cl = collRes || {};
|
|
2720
|
+
const bo = boRes || {};
|
|
2721
|
+
const recentBattle = blRes?.battles?.[0];
|
|
2722
|
+
const playerSearchRes = role?.id ? await client.ingamePlayerSearch(ctx, String(role.id)) : null;
|
|
2723
|
+
const playerSearchData = parseIngamePlayerPayload(playerSearchRes, String(role.id || ""));
|
|
2724
|
+
const profileSignature = playerSearchData?.signature || "";
|
|
2725
|
+
const profileHeadTags = playerSearchData ? [
|
|
2726
|
+
{ label: "在线", value: playerField(playerSearchData, "online", "未知") },
|
|
2727
|
+
{ label: "性别", value: playerField(playerSearchData, "gender", playerField(playerSearchData, "sex", "未知")) },
|
|
2728
|
+
{ label: "世界等级", value: playerField(playerSearchData, "world_level") },
|
|
2729
|
+
{ label: "家园等级", value: playerField(playerSearchData, "home_level") }
|
|
2730
|
+
].filter((item) => item.value && item.value !== "-" && item.value !== "未设置").slice(0, 4) : [];
|
|
2731
|
+
const profileHomeItems = playerSearchData ? [
|
|
2732
|
+
{ label: "家园名称", value: playerField(playerSearchData, "home_name") },
|
|
2733
|
+
{ label: "家园等级", value: playerField(playerSearchData, "home_level") },
|
|
2734
|
+
{ label: "家园经验", value: playerField(playerSearchData, "home_experience") },
|
|
2735
|
+
{ label: "舒适度", value: playerField(playerSearchData, "home_comfort_level") },
|
|
2736
|
+
{ label: "访客数量", value: playerField(playerSearchData, "visitor_num") }
|
|
2737
|
+
].filter((item) => item.value && item.value !== "-" && item.value !== "未设置") : [];
|
|
2738
|
+
const profileCardItems = playerSearchData ? [
|
|
2739
|
+
{ label: "名片皮肤", value: playerField(playerSearchData, "card_skin_selected") },
|
|
2740
|
+
{ label: "名片头像", value: playerField(playerSearchData, "card_icon_selected") }
|
|
2741
|
+
].filter((item) => item.value && item.value !== "-" && item.value !== "未设置") : [];
|
|
2742
|
+
const profileCardImage = playerSearchData ? playerField(playerSearchData, "card_bussiness_card_url", "") : "";
|
|
2743
|
+
const profileStatusText = playerSearchData ? playerField(playerSearchData, "online", "未知") : "未知";
|
|
2744
|
+
const hasExtraProfileData = Boolean(profileSignature || profileHomeItems.length || profileCardItems.length || profileCardImage);
|
|
2745
|
+
let degraded = false;
|
|
2746
|
+
if (!sm) {
|
|
2747
|
+
logger7.warn("[Rocom] 洛克.档案:pet-summary 接口不可用,已降级为基础档案渲染");
|
|
2748
|
+
degraded = true;
|
|
2749
|
+
}
|
|
2750
|
+
if (!ev) {
|
|
2751
|
+
logger7.warn("[Rocom] 洛克.档案:evaluation 接口不可用,已降级为基础档案渲染");
|
|
2752
|
+
degraded = true;
|
|
2753
|
+
}
|
|
2754
|
+
if (!cl) {
|
|
2755
|
+
logger7.warn("[Rocom] 洛克.档案:collection 接口不可用,已降级为基础档案渲染");
|
|
2756
|
+
degraded = true;
|
|
2757
|
+
}
|
|
2758
|
+
if (!bo) {
|
|
2759
|
+
logger7.warn("[Rocom] 洛克.档案:battle-overview 接口不可用,已降级为基础档案渲染");
|
|
2760
|
+
degraded = true;
|
|
2761
|
+
}
|
|
2762
|
+
if (degraded) {
|
|
2763
|
+
try {
|
|
2764
|
+
await session.send("AI评分接口暂不可用,已降级为基础档案渲染。");
|
|
2765
|
+
} catch {
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
const radarData = {
|
|
2769
|
+
strength: ev.strength || 0,
|
|
2770
|
+
collection: ev.collection || 0,
|
|
2771
|
+
capture: ev.capture || 0,
|
|
2772
|
+
progression: ev.progression || 0
|
|
2773
|
+
};
|
|
2774
|
+
const cx = 130;
|
|
2775
|
+
const cy = 130;
|
|
2776
|
+
const r = 90;
|
|
2777
|
+
const dims = [
|
|
2778
|
+
{ label: "战力", value: radarData.strength, angle: -90 },
|
|
2779
|
+
{ label: "收藏", value: radarData.collection, angle: 0 },
|
|
2780
|
+
{ label: "捕捉", value: radarData.capture, angle: 90 },
|
|
2781
|
+
{ label: "推进", value: radarData.progression, angle: 180 }
|
|
2782
|
+
];
|
|
2783
|
+
const toXY = /* @__PURE__ */ __name((angle, radius) => {
|
|
2784
|
+
const rad = angle * Math.PI / 180;
|
|
2785
|
+
return { x: Math.round(cx + radius * Math.cos(rad)), y: Math.round(cy + radius * Math.sin(rad)) };
|
|
2786
|
+
}, "toXY");
|
|
2787
|
+
const radarPolygons = [1, 0.66, 0.33].map((scale) => {
|
|
2788
|
+
const pts = dims.map((dim) => toXY(dim.angle, r * scale));
|
|
2789
|
+
return pts.map((point) => `${point.x},${point.y}`).join(" ");
|
|
2790
|
+
});
|
|
2791
|
+
const radarAxes = dims.map((dim) => toXY(dim.angle, r));
|
|
2792
|
+
const radarAreaPoints = dims.map((dim) => toXY(dim.angle, r * Math.min(dim.value, 100) / 100)).map((point) => `${point.x},${point.y}`).join(" ");
|
|
2793
|
+
const radarAxisLabels = dims.map((dim) => {
|
|
2794
|
+
const point = toXY(dim.angle, r + 28);
|
|
2795
|
+
return { x: point.x, y: point.y, name: dim.label, anchor: "middle" };
|
|
2796
|
+
});
|
|
2797
|
+
const radarValueBadges = dims.map((dim) => {
|
|
2798
|
+
const point = toXY(dim.angle, r + 14);
|
|
2799
|
+
return { x: point.x - 20, y: point.y + 8, width: 40, value: dim.value };
|
|
2800
|
+
});
|
|
2801
|
+
const radarDots = dims.map((dim) => toXY(dim.angle, r * Math.min(dim.value, 100) / 100));
|
|
2802
|
+
const data = {
|
|
2803
|
+
userName: role.name || "洛克",
|
|
2804
|
+
userLevel: role.level || 1,
|
|
2805
|
+
userUid: role.id || "",
|
|
2806
|
+
userAvatarDisplay: role.avatar_url || "",
|
|
2807
|
+
enrollDays: role.enroll_days || 0,
|
|
2808
|
+
starName: role.star_name || "魔法学徒",
|
|
2809
|
+
backgroundUrl: role.background_url || "",
|
|
2810
|
+
hasAiProfileData: !!sm.best_pet_name,
|
|
2811
|
+
bestPetName: sm.best_pet_name || "",
|
|
2812
|
+
summaryTitleParts: String(sm.summary_title || "未 知").split(" "),
|
|
2813
|
+
bestPetImageDisplay: sm.best_pet_img_url || "",
|
|
2814
|
+
fallbackPetImage: "{{_res_path}}img/roco_icon.png",
|
|
2815
|
+
scoreText: ev.score || "0.0",
|
|
2816
|
+
aiCommentText: sm.summary_content || "暂无点评",
|
|
2817
|
+
centerX: cx,
|
|
2818
|
+
centerY: cy,
|
|
2819
|
+
radarPolygons,
|
|
2820
|
+
radarAxes,
|
|
2821
|
+
radarAreaPoints,
|
|
2822
|
+
radarAxisLabels,
|
|
2823
|
+
radarValueBadges,
|
|
2824
|
+
radarDots,
|
|
2825
|
+
currentCollectionCount: cl.current_collection_count || 0,
|
|
2826
|
+
totalCollectionCount: cl.total_collection_count || 0,
|
|
2827
|
+
amazingSpriteCount: cl.amazing_sprite_count || 0,
|
|
2828
|
+
shinySpriteCount: cl.shiny_sprite_count || 0,
|
|
2829
|
+
colorfulSpriteCount: cl.colorful_sprite_count || 0,
|
|
2830
|
+
fashionCollectionCount: cl.fashion_collection_count || 0,
|
|
2831
|
+
itemCount: cl.item_count || 0,
|
|
2832
|
+
collectionHint: "查看收藏详情",
|
|
2833
|
+
hasBattleData: bo.total_match > 0,
|
|
2834
|
+
tierBadgeUrl: bo.tier_icon_url || "",
|
|
2835
|
+
winRate: `${bo.win_rate || 0}%`,
|
|
2836
|
+
totalMatch: bo.total_match || 0,
|
|
2837
|
+
matchResult: "",
|
|
2838
|
+
leftTeamPets: [],
|
|
2839
|
+
rightTeamPets: [],
|
|
2840
|
+
opponentName: "",
|
|
2841
|
+
opponentAvatarDisplay: "",
|
|
2842
|
+
hasExtraProfileData,
|
|
2843
|
+
profileSignature,
|
|
2844
|
+
showProfileSignature: Boolean(profileSignature),
|
|
2845
|
+
profileHeadTags,
|
|
2846
|
+
profileHomeItems,
|
|
2847
|
+
profileCardItems,
|
|
2848
|
+
profileCardImage,
|
|
2849
|
+
profileStatusText,
|
|
2850
|
+
profileStatusClass: profileStatusText === "是" ? "online" : "offline",
|
|
2851
|
+
commandHint: "洛克.背包 <筛选> <页码> | 洛克.战绩 <页码> | 洛克 查看菜单",
|
|
2852
|
+
copyright: "AstrBot & WeGame Locke Kingdom Plugin"
|
|
2853
|
+
};
|
|
2854
|
+
if (recentBattle) {
|
|
2855
|
+
data.hasBattleData = true;
|
|
2856
|
+
data.matchResult = recentBattle.result === 1 ? "fail" : "win";
|
|
2857
|
+
data.opponentName = recentBattle.enemy_nickname || "";
|
|
2858
|
+
data.opponentAvatarDisplay = recentBattle.enemy_avatar_url || "";
|
|
2859
|
+
data.leftTeamPets = (recentBattle.pet_base_info || []).map((pet) => ({
|
|
2860
|
+
icon: pet.pet_img_url?.replace("/image.png", "/icon.png") || ""
|
|
2861
|
+
}));
|
|
2862
|
+
data.rightTeamPets = (recentBattle.enemy_pet_base_info || []).map((pet) => ({
|
|
2863
|
+
icon: pet.pet_img_url?.replace("/image.png", "/icon.png") || ""
|
|
2864
|
+
}));
|
|
2865
|
+
}
|
|
2866
|
+
const fallback = `【${role.name}的档案】Lv.${role.level} UID:${role.id}
|
|
2867
|
+
评分:${ev.score || "0"} 收藏:${cl.current_collection_count || 0}/${cl.total_collection_count || 0}`;
|
|
2868
|
+
await sendImage(deps, session, "personal-card", data, fallback);
|
|
2869
|
+
});
|
|
2870
|
+
ctx.command("洛克").subcommand(".战绩 [page:number]", "查看对战战绩").action(async ({ session }, _page = 1) => {
|
|
2871
|
+
const fwToken = await getPrimaryToken(deps, session.userId);
|
|
2872
|
+
if (!fwToken) return notLoggedInHint();
|
|
2873
|
+
const userIdentifier = session.userId;
|
|
2874
|
+
const [roleRes, boRes, blRes] = await Promise.all([
|
|
2875
|
+
client.getRole(ctx, fwToken, void 0, userIdentifier),
|
|
2876
|
+
client.getBattleOverview(ctx, fwToken, userIdentifier),
|
|
2877
|
+
client.getBattleList(ctx, fwToken, 4, "", userIdentifier)
|
|
2878
|
+
]);
|
|
2879
|
+
if (!roleRes?.role) return "获取战绩数据失败。";
|
|
2880
|
+
const role = roleRes.role;
|
|
2881
|
+
const bo = boRes || {};
|
|
2882
|
+
const battles = (blRes?.battles || []).map((battle) => {
|
|
2883
|
+
const result = battle.result === 1 ? "fail" : "win";
|
|
2884
|
+
const battleTime = battle.battle_time ? new Date(battle.battle_time) : null;
|
|
2885
|
+
return {
|
|
2886
|
+
time: battleTime ? battleTime.toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" }) : "?",
|
|
2887
|
+
date: battleTime ? battleTime.toLocaleDateString("zh-CN") : "?",
|
|
2888
|
+
result,
|
|
2889
|
+
leftName: battle.nickname || "",
|
|
2890
|
+
leftAvatar: battle.avatar_url || "",
|
|
2891
|
+
leftPets: (battle.pet_base_info || []).map((pet) => ({
|
|
2892
|
+
icon: pet.pet_img_url?.replace("/image.png", "/icon.png") || ""
|
|
2893
|
+
})),
|
|
2894
|
+
rightName: battle.enemy_nickname || "",
|
|
2895
|
+
rightAvatar: battle.enemy_avatar_url || "",
|
|
2896
|
+
rightPets: (battle.enemy_pet_base_info || []).map((pet) => ({
|
|
2897
|
+
icon: pet.pet_img_url?.replace("/image.png", "/icon.png") || ""
|
|
2898
|
+
}))
|
|
2899
|
+
};
|
|
2900
|
+
});
|
|
2901
|
+
const data = {
|
|
2902
|
+
userAvatarDisplay: role.avatar_url || "",
|
|
2903
|
+
userName: role.name,
|
|
2904
|
+
userLevel: role.level || 1,
|
|
2905
|
+
userUid: role.id || "",
|
|
2906
|
+
winRate: `${bo.win_rate || 0}%`,
|
|
2907
|
+
totalMatch: bo.total_match || 0,
|
|
2908
|
+
battles,
|
|
2909
|
+
commandHint: "洛克.战绩 <页码>",
|
|
2910
|
+
copyright: "Koishi & WeGame 洛克王国插件"
|
|
2911
|
+
};
|
|
2912
|
+
await sendImage(deps, session, "record", data, `【${role.name}的战绩】胜率:${bo.win_rate || 0}% 场次:${bo.total_match || 0}`);
|
|
2913
|
+
});
|
|
2914
|
+
ctx.command("洛克").subcommand(".背包 [arg1:string] [arg2:string]", "查看精灵背包").action(async ({ session }, arg1, arg2) => {
|
|
2915
|
+
const fwToken = await getPrimaryToken(deps, session.userId);
|
|
2916
|
+
if (!fwToken) return notLoggedInHint();
|
|
2917
|
+
const catMap = { "全部": 0, "了不起": 1, "异色": 2, "炫彩": 3 };
|
|
2918
|
+
let category = "全部";
|
|
2919
|
+
let pageNo = 1;
|
|
2920
|
+
for (const arg of [arg1, arg2]) {
|
|
2921
|
+
if (!arg) continue;
|
|
2922
|
+
if (/^\d+$/.test(arg)) pageNo = parseInt(arg);
|
|
2923
|
+
else if (arg in catMap) category = arg;
|
|
2924
|
+
else if (arg.replace("精灵", "") in catMap) category = arg.replace("精灵", "");
|
|
2925
|
+
}
|
|
2926
|
+
const userIdentifier = session.userId;
|
|
2927
|
+
const petSubset = catMap[category] ?? 0;
|
|
2928
|
+
const [roleRes, petRes] = await Promise.all([
|
|
2929
|
+
client.getRole(ctx, fwToken, void 0, userIdentifier),
|
|
2930
|
+
client.getPets(ctx, fwToken, petSubset, pageNo, 10, userIdentifier)
|
|
2931
|
+
]);
|
|
2932
|
+
if (!roleRes?.role || !petRes?.pets) return "获取背包数据失败。";
|
|
2933
|
+
const role = roleRes.role;
|
|
2934
|
+
const data = {
|
|
2935
|
+
userName: role.name || "洛克",
|
|
2936
|
+
userLevel: role.level || 1,
|
|
2937
|
+
userUid: role.id || "",
|
|
2938
|
+
userAvatar: role.avatar_url || "",
|
|
2939
|
+
pageTitle: `背包 - ${category}精灵`,
|
|
2940
|
+
tabs: [
|
|
2941
|
+
{ text: "全部精灵", active: category === "全部" },
|
|
2942
|
+
{ text: "了不起精灵", active: category === "了不起" },
|
|
2943
|
+
{ text: "异色精灵", active: category === "异色" },
|
|
2944
|
+
{ text: "炫彩精灵", active: category === "炫彩" }
|
|
2945
|
+
],
|
|
2946
|
+
currentTab: `${category}精灵`,
|
|
2947
|
+
totalCount: petRes.total || 0,
|
|
2948
|
+
accountLabel: role.name || "",
|
|
2949
|
+
pets: (petRes.pets || []).map((pet) => ({
|
|
2950
|
+
name: pet.pet_name?.split("&")[0] || "?",
|
|
2951
|
+
custom_name: pet.pet_name?.includes("&") ? pet.pet_name.split("&")[1] : void 0,
|
|
2952
|
+
level: pet.pet_level || 1,
|
|
2953
|
+
pet_img_url: pet.pet_img_url || "",
|
|
2954
|
+
elementIcons: (pet.pet_types_info || []).map((petType2) => ({ src: petType2.icon || "", name: petType2.name || "" })),
|
|
2955
|
+
badgeImage: ""
|
|
2956
|
+
})),
|
|
2957
|
+
emptySlots: [],
|
|
2958
|
+
currentPage: pageNo,
|
|
2959
|
+
totalPages: Math.max(1, Math.ceil((petRes.total || 0) / 10)),
|
|
2960
|
+
pageSize: 10,
|
|
2961
|
+
commandHint: "洛克.背包 <全部/异色/了不起/炫彩> <页码>",
|
|
2962
|
+
fallbackPetImage: ""
|
|
2963
|
+
};
|
|
2964
|
+
await sendImage(deps, session, "package", data, `【背包 - ${category}精灵】共${petRes.total || 0}只`);
|
|
2965
|
+
});
|
|
2966
|
+
ctx.command("洛克").subcommand(".阵容 [arg1:string] [arg2:string]", "查看阵容推荐").action(async ({ session }, arg1, arg2) => {
|
|
2967
|
+
const fwToken = await getPrimaryToken(deps, session.userId);
|
|
2968
|
+
if (!fwToken) return notLoggedInHint();
|
|
2969
|
+
const userIdentifier = session.userId;
|
|
2970
|
+
let category = "";
|
|
2971
|
+
let pageNo = 1;
|
|
2972
|
+
for (const arg of [arg1, arg2]) {
|
|
2973
|
+
if (!arg) continue;
|
|
2974
|
+
if (/^\d+$/.test(arg)) pageNo = parseInt(arg);
|
|
2975
|
+
else category = arg;
|
|
2976
|
+
}
|
|
2977
|
+
const res = await client.getLineupList(ctx, fwToken, pageNo, category, userIdentifier);
|
|
2978
|
+
if (!res?.lineups) return "获取阵容数据失败。";
|
|
2979
|
+
const data = {
|
|
2980
|
+
category: category || "热门推荐",
|
|
2981
|
+
lineups: (res.lineups || []).map((lineup) => ({
|
|
2982
|
+
name: lineup.name || "",
|
|
2983
|
+
tags: lineup.tags || [],
|
|
2984
|
+
likes: lineup.likes || 0,
|
|
2985
|
+
author_name: lineup.author_name || "?",
|
|
2986
|
+
lineup_code: String(lineup.id || ""),
|
|
2987
|
+
pets: (lineup.lineup?.pets || []).map((pet) => ({
|
|
2988
|
+
pet_name: pet.pet_name || "",
|
|
2989
|
+
pet_img_url: pet.pet_img_url || "",
|
|
2990
|
+
skills_info: (pet.skills_info || []).map((skill) => ({ skill_img_url: skill.skill_img_url || "" }))
|
|
2991
|
+
}))
|
|
2992
|
+
})),
|
|
2993
|
+
page_no: res.page_no || pageNo,
|
|
2994
|
+
total_pages: res.total_pages || 1,
|
|
2995
|
+
fallbackPetImage: "",
|
|
2996
|
+
commandHint: "洛克.阵容 <分类> <页码>"
|
|
2997
|
+
};
|
|
2998
|
+
await sendImage(deps, session, "lineup", data, `【阵容推荐】${category || "热门"} 第${pageNo}页`);
|
|
2999
|
+
});
|
|
3000
|
+
ctx.command("查看阵容 <lineupId:string>", "查看阵容详情").action(async ({ session }, lineupId) => {
|
|
3001
|
+
const normalizedLineupId = normalizeLineupLookupId(lineupId);
|
|
3002
|
+
if (!normalizedLineupId) return "请提供有效的阵容码。用法:查看阵容 <阵容码>";
|
|
3003
|
+
const fwToken = await getPrimaryToken(deps, session.userId);
|
|
3004
|
+
if (!fwToken) return notLoggedInHint();
|
|
3005
|
+
const userIdentifier = session.userId;
|
|
3006
|
+
const firstPageRes = await client.getLineupList(ctx, fwToken, 1, "", userIdentifier);
|
|
3007
|
+
if (!firstPageRes?.lineups) return "获取阵容数据失败。";
|
|
3008
|
+
let targetLineup = (firstPageRes.lineups || []).find((lineup) => isTargetLineup(lineup, normalizedLineupId));
|
|
3009
|
+
if (!targetLineup) {
|
|
3010
|
+
const totalPages = Math.max(1, Number(firstPageRes.total_pages) || 1);
|
|
3011
|
+
const maxSearchPage = Math.min(totalPages, 10);
|
|
3012
|
+
for (let page = 2; page <= maxSearchPage; page++) {
|
|
3013
|
+
const pageRes = await client.getLineupList(ctx, fwToken, page, "", userIdentifier);
|
|
3014
|
+
const lineups = pageRes?.lineups || [];
|
|
3015
|
+
targetLineup = lineups.find((lineup) => isTargetLineup(lineup, normalizedLineupId));
|
|
3016
|
+
if (targetLineup) break;
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
if (!targetLineup) return `未找到阵容码为 ${normalizedLineupId} 的阵容。`;
|
|
3020
|
+
const lineupData = targetLineup.lineup || {};
|
|
3021
|
+
const processedPets = (lineupData.pets || []).map((pet) => ({
|
|
3022
|
+
pet_name: pet.pet_name || "",
|
|
3023
|
+
pet_img_url: pet.pet_img_url || "",
|
|
3024
|
+
skills: (pet.skills_info || []).map((skill) => skill.skill_img_url || "").filter(Boolean),
|
|
3025
|
+
bloodline: Boolean(pet.bloodline_info),
|
|
3026
|
+
bloodline_icon: pet.bloodline_info?.icon || ""
|
|
3027
|
+
}));
|
|
3028
|
+
const data = {
|
|
3029
|
+
lineup: {
|
|
3030
|
+
name: targetLineup.name || "",
|
|
3031
|
+
tags: targetLineup.tags || [],
|
|
3032
|
+
pets: processedPets,
|
|
3033
|
+
author_name: targetLineup.author_name || "",
|
|
3034
|
+
author_avatar: targetLineup.author_avatar || "",
|
|
3035
|
+
likes: targetLineup.likes || 0,
|
|
3036
|
+
lineup_code: normalizedLineupId
|
|
3037
|
+
},
|
|
3038
|
+
fallbackPetImage: "{{_res_path}}img/roco_icon.png"
|
|
3039
|
+
};
|
|
3040
|
+
const fallback = `【阵容详情】${targetLineup.name || "未知阵容"} | 阵容码: ${normalizedLineupId}`;
|
|
3041
|
+
await sendImage(deps, session, "lineup-detail", data, fallback);
|
|
3042
|
+
});
|
|
3043
|
+
ctx.command("洛克").subcommand(".交换大厅 [page:number]", "查看交换大厅").action(async ({ session }, page = 1) => {
|
|
3044
|
+
const fwToken = await getPrimaryToken(deps, session.userId);
|
|
3045
|
+
if (!fwToken) return notLoggedInHint();
|
|
3046
|
+
const userIdentifier = session.userId;
|
|
3047
|
+
const res = await client.getExchangePosters(ctx, fwToken, page, userIdentifier);
|
|
3048
|
+
if (!res?.posters) return "获取交换大厅数据失败。";
|
|
3049
|
+
const data = {
|
|
3050
|
+
filterLabel: "全部",
|
|
3051
|
+
posts: (res.posters || []).map((poster) => {
|
|
3052
|
+
const user = poster.user_info || {};
|
|
3053
|
+
const provideItems = parseExchangeItems(
|
|
3054
|
+
poster.offer_items ?? poster.offer_item_names ?? poster.offerItems ?? []
|
|
3055
|
+
);
|
|
3056
|
+
return {
|
|
3057
|
+
userName: user.nickname || "?",
|
|
3058
|
+
userLevel: user.level || 0,
|
|
3059
|
+
isOnline: user.online_status === 1,
|
|
3060
|
+
avatarUrl: user.avatar_url || "",
|
|
3061
|
+
userId: user.role_id || "",
|
|
3062
|
+
wantText: parseExchangeWantText(
|
|
3063
|
+
poster.want_item_name ?? poster.want_item ?? poster.wantText ?? "交友"
|
|
3064
|
+
),
|
|
3065
|
+
wantBadgeUrl: "",
|
|
3066
|
+
isExpired: false,
|
|
3067
|
+
provideItems: provideItems.length > 0 ? provideItems : ["暂无"],
|
|
3068
|
+
timeLabel: poster.create_time ? new Date(Number(poster.create_time) * 1e3).toLocaleString("zh-CN", {
|
|
3069
|
+
month: "2-digit",
|
|
3070
|
+
day: "2-digit",
|
|
3071
|
+
hour: "2-digit",
|
|
3072
|
+
minute: "2-digit"
|
|
3073
|
+
}) : "?"
|
|
3074
|
+
};
|
|
3075
|
+
}),
|
|
3076
|
+
currentPage: page,
|
|
3077
|
+
totalPages: res.total_pages || 1,
|
|
3078
|
+
commandHint: "洛克.交换大厅 <页码>"
|
|
3079
|
+
};
|
|
3080
|
+
await sendImage(deps, session, "exchange-hall", data, `【交换大厅】第${page}页`);
|
|
3081
|
+
});
|
|
3082
|
+
ctx.command("洛克").subcommand(".玩家 <uid:string>", "通过 ingame 接口查询玩家基础资料").action(async ({ session }, uid) => {
|
|
3083
|
+
if (!uid) return "请提供玩家 UID。用法:洛克.玩家 <UID>";
|
|
3084
|
+
const res = await client.ingamePlayerSearch(ctx, uid);
|
|
3085
|
+
if (!res) return `玩家搜索失败:${client.getLastError()}`;
|
|
3086
|
+
await sendImage(deps, session, "player-search", buildPlayerSearchRenderData(res, uid), `【洛克玩家】UID ${uid}`);
|
|
3087
|
+
});
|
|
3088
|
+
ctx.command("洛克").subcommand(".家园 [uid:string]", "通过 UID 查询家园菜园、守卫和室内精灵").action(async ({ session }, uid = "") => {
|
|
3089
|
+
let targetUid = String(uid || "").trim();
|
|
3090
|
+
if (!targetUid) {
|
|
3091
|
+
const binding = deps.userMgr.getPrimaryBinding(session.userId);
|
|
3092
|
+
targetUid = String(binding?.role_id || "");
|
|
3093
|
+
}
|
|
3094
|
+
if (!targetUid) return "请提供玩家 UID,或先完成绑定后使用 洛克.家园。";
|
|
3095
|
+
const res = await client.ingameHomeInfo(ctx, targetUid);
|
|
3096
|
+
if (!res) return `家园查询失败:${client.getLastError()}`;
|
|
3097
|
+
await sendImage(deps, session, "home", buildHomeRenderData(deps, res, targetUid), `【洛克家园】UID ${targetUid}`);
|
|
3098
|
+
});
|
|
3099
|
+
ctx.command("洛克").subcommand(".商店 <shopId:string>", "通过 ingame 接口查询商店信息").action(async ({ session }, shopId) => {
|
|
3100
|
+
if (!shopId) return "请提供商店 ID。用法:洛克.商店 <shop_id>";
|
|
3101
|
+
const res = await client.ingameMerchantInfo(ctx, shopId);
|
|
3102
|
+
if (!res) return `商店查询失败:${client.getLastError()}`;
|
|
3103
|
+
await sendImage(deps, session, "ingame-shop", buildShopRenderData(res, shopId), `【洛克商店】shop_id=${shopId}`);
|
|
3104
|
+
});
|
|
3105
|
+
ctx.command("洛克").subcommand(".好友关系 <userIds:string>", "查询好友关系").action(async ({ session }, userIds) => {
|
|
3106
|
+
if (!userIds) return "请提供要查询的用户 ID 列表。用法:洛克.好友关系 <id1,id2>";
|
|
3107
|
+
const fwToken = await getPrimaryToken(deps, session.userId);
|
|
3108
|
+
if (!fwToken) return notLoggedInHint();
|
|
3109
|
+
const res = await client.getFriendship(ctx, fwToken, userIds, session.userId);
|
|
3110
|
+
if (!res) return `好友关系查询失败:${client.getLastError()}`;
|
|
3111
|
+
await sendImage(deps, session, "friendship", buildFriendshipRenderData(res, userIds), `【好友关系】${userIds}`);
|
|
3112
|
+
});
|
|
3113
|
+
ctx.command("洛克").subcommand(".学生 [area:number] [accountType:number]", "查询学生认证状态与学生活动福利").action(async ({ session }, area = 101, accountType = 0) => {
|
|
3114
|
+
const fwToken = await getPrimaryToken(deps, session.userId);
|
|
3115
|
+
if (!fwToken) return notLoggedInHint();
|
|
3116
|
+
const userIdentifier = session.userId;
|
|
3117
|
+
const [stateRes, perksRes] = await Promise.all([
|
|
3118
|
+
client.getStudentState(ctx, fwToken, accountType, userIdentifier),
|
|
3119
|
+
client.getStudentPerks(ctx, fwToken, area, accountType, userIdentifier)
|
|
3120
|
+
]);
|
|
3121
|
+
if (!stateRes) return `学生认证状态查询失败:${client.getLastError()}`;
|
|
3122
|
+
if (!perksRes) return `学生活动福利查询失败:${client.getLastError()}`;
|
|
3123
|
+
await sendImage(deps, session, "student", buildStudentRenderData(stateRes, perksRes, area, accountType), "【洛克学生】认证与福利信息");
|
|
3124
|
+
});
|
|
3125
|
+
ctx.command("订阅家园菜园 [uid:string]", "订阅指定 UID 的家园菜园成熟提醒").action(async ({ session }, uid = "") => subscribeHome(deps, session, uid, "garden"));
|
|
3126
|
+
ctx.command("订阅家园灵感 [uid:string]", "订阅指定 UID 的家园精灵灵感完成提醒").action(async ({ session }, uid = "") => subscribeHome(deps, session, uid, "inspiration"));
|
|
3127
|
+
ctx.command("取消订阅家园 [kind:string] [uid:string]", "取消当前会话的家园订阅").action(async ({ session }, kind = "全部", uid = "") => {
|
|
3128
|
+
const target = sessionTarget(session);
|
|
3129
|
+
if (!target.userId && !isBotAdmin(session, deps.config.adminUserIds)) return "此指令仅限管理员使用。";
|
|
3130
|
+
const kindMap = { "菜园": "garden", "灵感": "inspiration", "全部": "", all: "", garden: "garden", inspiration: "inspiration" };
|
|
3131
|
+
const deleted = deps.homeSubMgr.deleteMatching(target, kindMap[String(kind || "全部")] ?? "", String(uid || "").trim());
|
|
3132
|
+
return deleted ? `已取消 ${deleted} 条家园订阅。` : "当前会话没有匹配的家园订阅。";
|
|
3133
|
+
});
|
|
3134
|
+
ctx.command("洛克").subcommand(".调试家园订阅", "立即执行一次家园订阅检查").action(async ({ session }) => {
|
|
3135
|
+
if (!isBotAdmin(session, deps.config.adminUserIds)) return "此指令仅限管理员使用。";
|
|
3136
|
+
const result = await checkHomeSubscriptions(deps);
|
|
3137
|
+
return `家园订阅检查完成:订阅 ${result.subscriptions} 条,检查 ${result.checked} 条,推送 ${result.pushed} 档提醒。`;
|
|
3138
|
+
});
|
|
3139
|
+
if (deps.config.homeSubscriptionEnabled) {
|
|
3140
|
+
ctx.setInterval(() => checkHomeSubscriptions(deps).catch((err) => logger7.warn(`家园订阅检查失败: ${err}`)), Math.max(1, deps.config.homeSubscriptionIntervalMinutes || 5) * 6e4);
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
__name(register2, "register");
|
|
3144
|
+
|
|
3145
|
+
// src/commands/merchant.ts
|
|
3146
|
+
var import_koishi9 = require("koishi");
|
|
3147
|
+
var logger8 = new import_koishi9.Logger("rocom-merchant");
|
|
3148
|
+
var TEXT = {
|
|
3149
|
+
merchant: "远行商人",
|
|
3150
|
+
subscribe: "订阅远行商人",
|
|
3151
|
+
unsubscribe: "取消订阅远行商人",
|
|
3152
|
+
viewSubscribe: "查看远行商人订阅",
|
|
3153
|
+
unknown: "未知",
|
|
3154
|
+
notOpen: "未开放",
|
|
3155
|
+
defaultSource: "默认",
|
|
3156
|
+
customSource: "自定义"
|
|
3157
|
+
};
|
|
3158
|
+
function normalizeTimestamp(value) {
|
|
3159
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
3160
|
+
const timestamp = Number(value);
|
|
3161
|
+
if (!Number.isFinite(timestamp)) return null;
|
|
3162
|
+
return timestamp < 1e11 ? timestamp * 1e3 : timestamp;
|
|
3163
|
+
}
|
|
3164
|
+
__name(normalizeTimestamp, "normalizeTimestamp");
|
|
3165
|
+
function formatProductWindow(product) {
|
|
3166
|
+
const start = normalizeTimestamp(product?.start_time);
|
|
3167
|
+
const end = normalizeTimestamp(product?.end_time);
|
|
3168
|
+
if (!start && !end) return "";
|
|
3169
|
+
const formatTime = /* @__PURE__ */ __name((timestamp) => {
|
|
3170
|
+
const date = new Date(timestamp);
|
|
3171
|
+
const hour = String(date.getHours()).padStart(2, "0");
|
|
3172
|
+
const minute = String(date.getMinutes()).padStart(2, "0");
|
|
3173
|
+
return `${hour}:${minute}`;
|
|
3174
|
+
}, "formatTime");
|
|
3175
|
+
if (start && end) return `${formatTime(start)}-${formatTime(end)}`;
|
|
3176
|
+
if (start) return `${formatTime(start)}+`;
|
|
3177
|
+
return `${formatTime(end)}-`;
|
|
3178
|
+
}
|
|
3179
|
+
__name(formatProductWindow, "formatProductWindow");
|
|
3180
|
+
function getMerchantActivity(res) {
|
|
3181
|
+
const activities = res?.merchantActivities || res?.merchant_activities || [];
|
|
3182
|
+
return activities[0] || {};
|
|
3183
|
+
}
|
|
3184
|
+
__name(getMerchantActivity, "getMerchantActivity");
|
|
3185
|
+
function getActiveProducts(res) {
|
|
3186
|
+
const activity = getMerchantActivity(res);
|
|
3187
|
+
const products = activity?.products || activity?.product_list || activity?.get_props || [];
|
|
3188
|
+
return products.filter((p) => {
|
|
3189
|
+
const now = Date.now();
|
|
3190
|
+
const start = normalizeTimestamp(p.start_time) ?? 0;
|
|
3191
|
+
const end = normalizeTimestamp(p.end_time) ?? Infinity;
|
|
3192
|
+
return now >= start && now < end;
|
|
3193
|
+
});
|
|
3194
|
+
}
|
|
3195
|
+
__name(getActiveProducts, "getActiveProducts");
|
|
3196
|
+
function getCurrentMerchantRound() {
|
|
3197
|
+
const now = /* @__PURE__ */ new Date();
|
|
3198
|
+
const hour = now.getHours();
|
|
3199
|
+
const rounds = [8, 12, 16, 20];
|
|
3200
|
+
let currentRound = null;
|
|
3201
|
+
let nextRound = null;
|
|
3202
|
+
for (let i = 0; i < rounds.length; i++) {
|
|
3203
|
+
if (hour >= rounds[i] && (i === rounds.length - 1 || hour < rounds[i + 1])) {
|
|
3204
|
+
currentRound = i + 1;
|
|
3205
|
+
nextRound = i < rounds.length - 1 ? rounds[i + 1] : rounds[0];
|
|
3206
|
+
break;
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
if (currentRound === null) nextRound = rounds[0];
|
|
3210
|
+
const nextTime = new Date(now);
|
|
3211
|
+
if (nextRound <= hour) nextTime.setDate(nextTime.getDate() + 1);
|
|
3212
|
+
nextTime.setHours(nextRound, 0, 0, 0);
|
|
3213
|
+
const diff = nextTime.getTime() - now.getTime();
|
|
3214
|
+
const hours = Math.floor(diff / 36e5);
|
|
3215
|
+
const mins = Math.floor(diff % 36e5 / 6e4);
|
|
3216
|
+
const datePart = [
|
|
3217
|
+
now.getFullYear(),
|
|
3218
|
+
String(now.getMonth() + 1).padStart(2, "0"),
|
|
3219
|
+
String(now.getDate()).padStart(2, "0")
|
|
3220
|
+
].join("-");
|
|
3221
|
+
return {
|
|
3222
|
+
current: currentRound,
|
|
3223
|
+
countdown: `${hours}小时${mins}分钟`,
|
|
3224
|
+
is_open: currentRound !== null,
|
|
3225
|
+
round_id: `${datePart}-${currentRound || "closed"}`
|
|
3226
|
+
};
|
|
3227
|
+
}
|
|
3228
|
+
__name(getCurrentMerchantRound, "getCurrentMerchantRound");
|
|
3229
|
+
function parseMerchantSubscriptionArgs(args, defaultItems) {
|
|
3230
|
+
const parts = (args || "").split(/\s+/).filter(Boolean);
|
|
3231
|
+
let mentionAll = false;
|
|
3232
|
+
if (parts[0] === "1" || parts[0] === "0") {
|
|
3233
|
+
mentionAll = parts.shift() === "1";
|
|
3234
|
+
}
|
|
3235
|
+
const items = parts.length ? parts : defaultItems;
|
|
3236
|
+
return {
|
|
3237
|
+
mention_all: mentionAll,
|
|
3238
|
+
items,
|
|
3239
|
+
source: parts.length ? TEXT.customSource : TEXT.defaultSource
|
|
3240
|
+
};
|
|
3241
|
+
}
|
|
3242
|
+
__name(parseMerchantSubscriptionArgs, "parseMerchantSubscriptionArgs");
|
|
3243
|
+
function getSubscriptionTarget(session) {
|
|
3244
|
+
const platform = session.platform || session.bot?.platform || "";
|
|
3245
|
+
const privateChat = !session.guildId;
|
|
3246
|
+
const channelId = session.channelId || session.guildId || session.userId || "";
|
|
3247
|
+
const key = privateChat ? `private_${session.userId}` : session.guildId;
|
|
3248
|
+
return { key, platform, channelId, privateChat };
|
|
3249
|
+
}
|
|
3250
|
+
__name(getSubscriptionTarget, "getSubscriptionTarget");
|
|
3251
|
+
function isBotAdmin2(session, adminUserIds) {
|
|
3252
|
+
return adminUserIds.includes(session?.userId || "");
|
|
3253
|
+
}
|
|
3254
|
+
__name(isBotAdmin2, "isBotAdmin");
|
|
3255
|
+
function sameStringArray(left, right) {
|
|
3256
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
3257
|
+
}
|
|
3258
|
+
__name(sameStringArray, "sameStringArray");
|
|
3259
|
+
async function checkMerchantSubscriptions(deps) {
|
|
3260
|
+
const { ctx, client, merchantSubMgr } = deps;
|
|
3261
|
+
const res = await client.getMerchantInfo(ctx, true);
|
|
3262
|
+
if (!res) return { subscriptions: 0, matched: 0, pushed: 0 };
|
|
3263
|
+
const products = getActiveProducts(res);
|
|
3264
|
+
const productNames = products.map((p) => p.name || "").filter(Boolean);
|
|
3265
|
+
const roundInfo = getCurrentMerchantRound();
|
|
3266
|
+
const subs = merchantSubMgr.getAll();
|
|
3267
|
+
let matchedCount = 0;
|
|
3268
|
+
let pushedCount = 0;
|
|
3269
|
+
for (const [key, sub] of Object.entries(subs)) {
|
|
3270
|
+
const matched = sub.items.filter((item) => productNames.some((n) => n.includes(item)));
|
|
3271
|
+
if (!matched.length) continue;
|
|
3272
|
+
matchedCount++;
|
|
3273
|
+
if (sub.last_push_round === roundInfo.round_id && sameStringArray(matched, sub.last_matched_items || [])) continue;
|
|
3274
|
+
const msg = `🔔 远行商人刷新提醒
|
|
3275
|
+
当前商品:${productNames.join("、")}
|
|
3276
|
+
匹配订阅:${matched.join("、")}`;
|
|
3277
|
+
const platform = sub.platform || ctx.bots[0]?.platform;
|
|
3278
|
+
const channelId = sub.channel_id || sub.group_id || sub.user_id || key;
|
|
3279
|
+
if (!platform || !channelId) {
|
|
3280
|
+
logger8.warn(`推送失败 ${key}: 无法确定平台或频道`);
|
|
3281
|
+
continue;
|
|
3282
|
+
}
|
|
3283
|
+
const sent = await sendScheduledMessage(ctx, {
|
|
3284
|
+
platform,
|
|
3285
|
+
channelId,
|
|
3286
|
+
guildId: sub.group_id || "",
|
|
3287
|
+
userId: sub.user_id || ""
|
|
3288
|
+
}, sub.mention_all ? `@全体
|
|
3289
|
+
${msg}` : msg);
|
|
3290
|
+
if (!sent) continue;
|
|
3291
|
+
pushedCount++;
|
|
3292
|
+
merchantSubMgr.upsert(key, {
|
|
3293
|
+
...sub,
|
|
3294
|
+
last_push_round: roundInfo.round_id,
|
|
3295
|
+
last_matched_items: matched
|
|
3296
|
+
});
|
|
3297
|
+
}
|
|
3298
|
+
return { subscriptions: Object.keys(subs).length, matched: matchedCount, pushed: pushedCount };
|
|
3299
|
+
}
|
|
3300
|
+
__name(checkMerchantSubscriptions, "checkMerchantSubscriptions");
|
|
3301
|
+
function register3(deps) {
|
|
3302
|
+
const { ctx, config, client, merchantSubMgr } = deps;
|
|
3303
|
+
ctx.command(TEXT.merchant, "查看远行商人商品").action(async ({ session }) => {
|
|
3304
|
+
const res = await client.getMerchantInfo(ctx, true);
|
|
3305
|
+
if (!res) return "获取远行商人数据失败。";
|
|
3306
|
+
const products = getActiveProducts(res);
|
|
3307
|
+
const roundInfo = getCurrentMerchantRound();
|
|
3308
|
+
const activity = getMerchantActivity(res);
|
|
3309
|
+
const data = {
|
|
3310
|
+
background: "",
|
|
3311
|
+
title: activity.name || TEXT.merchant,
|
|
3312
|
+
subtitle: activity.start_date || "每日 08:00 / 12:00 / 16:00 / 20:00 刷新",
|
|
3313
|
+
titleIcon: "",
|
|
3314
|
+
product_count: products.length,
|
|
3315
|
+
round_info: roundInfo,
|
|
3316
|
+
products: products.map((p) => ({
|
|
3317
|
+
name: p.name || TEXT.unknown,
|
|
3318
|
+
image: p.icon_url || "",
|
|
3319
|
+
time_label: formatProductWindow(p)
|
|
3320
|
+
}))
|
|
3321
|
+
};
|
|
3322
|
+
const fallback = products.length ? `远行商人当前商品:${products.map((p) => p.name || TEXT.unknown).join("、")}
|
|
3323
|
+
轮次:${roundInfo.current || TEXT.notOpen}
|
|
3324
|
+
剩余:${roundInfo.countdown}` : "当前远行商人暂无商品。";
|
|
3325
|
+
const png = await deps.renderer.renderHtml(ctx, "yuanxing-shangren", data);
|
|
3326
|
+
await sendImageWithFallback(session, png, fallback, "merchant:yuanxing-shangren", deps.config);
|
|
3327
|
+
});
|
|
3328
|
+
ctx.command(`${TEXT.subscribe} [args:text]`, "订阅远行商人商品提醒").action(async ({ session }, args) => {
|
|
3329
|
+
const target = getSubscriptionTarget(session);
|
|
3330
|
+
if (!target.privateChat && !isBotAdmin2(session, config.adminUserIds)) return "此指令仅限管理员使用。";
|
|
3331
|
+
if (target.privateChat && !config.merchantPrivateSubscriptionEnabled) return "个人私聊订阅功能已被禁用,请联系机器人管理员。";
|
|
3332
|
+
const parsed = parseMerchantSubscriptionArgs(args, config.merchantSubscriptionItems);
|
|
3333
|
+
const existing = merchantSubMgr.get(target.key);
|
|
3334
|
+
merchantSubMgr.upsert(target.key, {
|
|
3335
|
+
group_id: session.guildId || "",
|
|
3336
|
+
user_id: target.privateChat ? session.userId : "",
|
|
3337
|
+
type: target.privateChat ? "个人订阅" : "群订阅",
|
|
3338
|
+
channel_id: target.channelId,
|
|
3339
|
+
platform: target.platform,
|
|
3340
|
+
items: parsed.items,
|
|
3341
|
+
mention_all: target.privateChat ? false : parsed.mention_all,
|
|
3342
|
+
last_push_round: existing?.last_push_round ?? null,
|
|
3343
|
+
last_matched_items: existing?.last_matched_items ?? [],
|
|
3344
|
+
updated_by: session.userId
|
|
3345
|
+
});
|
|
3346
|
+
return `✅ 已订阅远行商人商品:${parsed.items.join("、")}(${parsed.source});${target.privateChat ? "个人订阅" : parsed.mention_all ? "命中后会 @全体" : "命中后不 @全体"}`;
|
|
3347
|
+
});
|
|
3348
|
+
ctx.command(TEXT.viewSubscribe, "查看当前会话的远行商人订阅").action(async ({ session }) => {
|
|
3349
|
+
const target = getSubscriptionTarget(session);
|
|
3350
|
+
if (!target.privateChat && !isBotAdmin2(session, config.adminUserIds)) return "此指令仅限管理员使用。";
|
|
3351
|
+
const sub = merchantSubMgr.get(target.key);
|
|
3352
|
+
const scopeName = target.privateChat ? "你" : "当前群组";
|
|
3353
|
+
if (!sub) return `${scopeName}未订阅远行商人。
|
|
3354
|
+
用法:${TEXT.subscribe} [1/0] [商品名1] [商品名2] ...`;
|
|
3355
|
+
return `${scopeName}订阅商品:${sub.items.join("、")}
|
|
3356
|
+
提醒方式:${target.privateChat ? "私聊提醒" : sub.mention_all ? "@全体" : "普通提醒"}`;
|
|
3357
|
+
});
|
|
3358
|
+
ctx.command(TEXT.unsubscribe, "取消远行商人订阅").action(async ({ session }) => {
|
|
3359
|
+
const target = getSubscriptionTarget(session);
|
|
3360
|
+
if (!target.privateChat && !isBotAdmin2(session, config.adminUserIds)) return "此指令仅限管理员使用。";
|
|
3361
|
+
merchantSubMgr.delete(target.key);
|
|
3362
|
+
return "✅ 已取消远行商人订阅。";
|
|
3363
|
+
});
|
|
3364
|
+
ctx.command("洛克").subcommand(".调试远行商人订阅", "立即执行一次远行商人订阅检查").action(async ({ session }) => {
|
|
3365
|
+
if (!isBotAdmin2(session, config.adminUserIds)) return "此指令仅限管理员使用。";
|
|
3366
|
+
const result = await checkMerchantSubscriptions(deps);
|
|
3367
|
+
return `远行商人订阅检查完成:订阅 ${result.subscriptions} 条,命中 ${result.matched} 条,推送 ${result.pushed} 条。`;
|
|
3368
|
+
});
|
|
3369
|
+
if (config.merchantSubscriptionEnabled) {
|
|
3370
|
+
ctx.setInterval(async () => {
|
|
3371
|
+
await checkMerchantSubscriptions(deps);
|
|
3372
|
+
}, config.merchantCheckInterval);
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
__name(register3, "register");
|
|
3376
|
+
|
|
3377
|
+
// src/commands/wiki.ts
|
|
3378
|
+
var WIKI_CLOSED_MESSAGE = "该功能暂时关闭";
|
|
3379
|
+
function register4(deps) {
|
|
3380
|
+
const { ctx } = deps;
|
|
3381
|
+
ctx.command("洛克").subcommand(".wiki <name:text>", "查询精灵 wiki").action(() => WIKI_CLOSED_MESSAGE);
|
|
3382
|
+
ctx.command("洛克").subcommand(".技能 <name:text>", "查询技能 wiki").action(() => WIKI_CLOSED_MESSAGE);
|
|
3383
|
+
}
|
|
3384
|
+
__name(register4, "register");
|
|
3385
|
+
|
|
3386
|
+
// src/commands/egg.ts
|
|
3387
|
+
function petName2(p) {
|
|
3388
|
+
return p?.localized?.zh?.name || p?.name || "未知精灵";
|
|
3389
|
+
}
|
|
3390
|
+
__name(petName2, "petName");
|
|
3391
|
+
function parseHeightValue(raw) {
|
|
3392
|
+
const text = String(raw ?? "").trim().toLowerCase().replace(/^(身高|高度|h)\s*/i, "").trim();
|
|
3393
|
+
const match = text.match(/^([0-9]+(?:\.[0-9]+)?)(?:\s*(m|米))?$/);
|
|
3394
|
+
if (!match) return null;
|
|
3395
|
+
const meterValue = Number(match[1]);
|
|
3396
|
+
if (!Number.isFinite(meterValue)) return null;
|
|
3397
|
+
return {
|
|
3398
|
+
dataValue: meterValue * 100,
|
|
3399
|
+
meterValue,
|
|
3400
|
+
display: `${formatNumber2(meterValue)} m`
|
|
3401
|
+
};
|
|
3402
|
+
}
|
|
3403
|
+
__name(parseHeightValue, "parseHeightValue");
|
|
3404
|
+
function parseWeightValue(raw) {
|
|
3405
|
+
const text = String(raw ?? "").trim().toLowerCase().replace(/^(体重|重量|w)\s*/i, "").trim();
|
|
3406
|
+
const match = text.match(/^([0-9]+(?:\.[0-9]+)?)(?:\s*(kg|千克|公斤))?$/);
|
|
3407
|
+
if (!match) return null;
|
|
3408
|
+
const value = Number(match[1]);
|
|
3409
|
+
return Number.isFinite(value) ? value : null;
|
|
3410
|
+
}
|
|
3411
|
+
__name(parseWeightValue, "parseWeightValue");
|
|
3412
|
+
function formatNumber2(value) {
|
|
3413
|
+
return Number.isInteger(value) ? String(value) : String(Number(value.toFixed(4)));
|
|
3414
|
+
}
|
|
3415
|
+
__name(formatNumber2, "formatNumber");
|
|
3416
|
+
function searchResultCandidates(result) {
|
|
3417
|
+
return result.candidates || [];
|
|
3418
|
+
}
|
|
3419
|
+
__name(searchResultCandidates, "searchResultCandidates");
|
|
3420
|
+
async function sendEggImage(deps, session, templateName, data, fallback) {
|
|
3421
|
+
const png = await deps.renderer.renderHtml(deps.ctx, templateName, data);
|
|
3422
|
+
await sendImageWithFallback(session, png, fallback, `egg:${templateName}`, deps.config);
|
|
3423
|
+
}
|
|
3424
|
+
__name(sendEggImage, "sendEggImage");
|
|
3425
|
+
function register5(deps) {
|
|
3426
|
+
const { ctx, client, eggService } = deps;
|
|
3427
|
+
ctx.command("洛克").subcommand(".查蛋 [arg1:string] [arg2:string]", "查询精灵蛋组").action(async ({ session }, arg1, arg2) => {
|
|
3428
|
+
if (!arg1) {
|
|
3429
|
+
return [
|
|
3430
|
+
"查蛋用法:",
|
|
3431
|
+
" 洛克.查蛋 <精灵名>",
|
|
3432
|
+
" 洛克.查蛋 0.18 1.5",
|
|
3433
|
+
" 洛克.查蛋 0.18m 1.5kg",
|
|
3434
|
+
" 洛克.查蛋 身高0.18m 体重1.5kg"
|
|
3435
|
+
].join("\n");
|
|
3436
|
+
}
|
|
3437
|
+
let height;
|
|
3438
|
+
let heightMeters;
|
|
3439
|
+
let heightDisplay;
|
|
3440
|
+
let weight;
|
|
3441
|
+
const nameParts = [];
|
|
3442
|
+
const numericArgs = [];
|
|
3443
|
+
for (const rawArg of [arg1, arg2]) {
|
|
3444
|
+
if (!rawArg) continue;
|
|
3445
|
+
const arg = String(rawArg).trim();
|
|
3446
|
+
const explicitHeight = /^(身高|高度|h)/i.test(arg);
|
|
3447
|
+
const explicitWeight = /^(体重|重量|w)/i.test(arg);
|
|
3448
|
+
if (explicitHeight) {
|
|
3449
|
+
const parsed = parseHeightValue(arg);
|
|
3450
|
+
if (parsed) {
|
|
3451
|
+
height = parsed.dataValue;
|
|
3452
|
+
heightMeters = parsed.meterValue;
|
|
3453
|
+
heightDisplay = parsed.display;
|
|
3454
|
+
continue;
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
if (explicitWeight) {
|
|
3458
|
+
const parsed = parseWeightValue(arg);
|
|
3459
|
+
if (parsed != null) {
|
|
3460
|
+
weight = parsed;
|
|
3461
|
+
continue;
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
const heightCandidate = parseHeightValue(arg);
|
|
3465
|
+
const weightCandidate = parseWeightValue(arg);
|
|
3466
|
+
if (heightCandidate || weightCandidate != null) {
|
|
3467
|
+
numericArgs.push({ height: heightCandidate, weight: weightCandidate });
|
|
3468
|
+
} else {
|
|
3469
|
+
nameParts.push(arg);
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
if (numericArgs.length) {
|
|
3473
|
+
if (height == null && numericArgs[0]?.height) {
|
|
3474
|
+
height = numericArgs[0].height.dataValue;
|
|
3475
|
+
heightMeters = numericArgs[0].height.meterValue;
|
|
3476
|
+
heightDisplay = numericArgs[0].height.display;
|
|
3477
|
+
}
|
|
3478
|
+
if (weight == null && numericArgs[1]?.weight != null) {
|
|
3479
|
+
weight = numericArgs[1].weight;
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
if (height != null || weight != null) {
|
|
3483
|
+
let data2 = null;
|
|
3484
|
+
let fallback = "";
|
|
3485
|
+
if (height != null && weight != null) {
|
|
3486
|
+
const backendResults = await client.queryPetSize(ctx, heightMeters ?? height / 100, weight);
|
|
3487
|
+
if (backendResults) {
|
|
3488
|
+
data2 = eggService.buildSizeSearchDataFromApi(height, weight, backendResults, heightDisplay);
|
|
3489
|
+
fallback = eggService.buildSizeSearchTextFromApi(height, weight, backendResults, heightDisplay);
|
|
3490
|
+
}
|
|
3491
|
+
}
|
|
3492
|
+
if (!data2) {
|
|
3493
|
+
const results = eggService.searchBySize(height, weight);
|
|
3494
|
+
data2 = eggService.buildSizeSearchData(height, weight, results, heightDisplay);
|
|
3495
|
+
fallback = eggService.buildSizeSearchText(height, weight, results, heightDisplay);
|
|
3496
|
+
}
|
|
3497
|
+
await sendEggImage(deps, session, "searcheggs/size", data2, fallback);
|
|
3498
|
+
return;
|
|
3499
|
+
}
|
|
3500
|
+
const name2 = nameParts.join(" ");
|
|
3501
|
+
if (!name2) return "请输入精灵名称。用法:洛克.查蛋 <精灵名>";
|
|
3502
|
+
const sr = eggService.search(name2);
|
|
3503
|
+
if (sr.matchType === "multi") {
|
|
3504
|
+
const candidates = searchResultCandidates(sr);
|
|
3505
|
+
const data2 = eggService.buildCandidatesRenderData(name2, candidates);
|
|
3506
|
+
await sendEggImage(deps, session, "searcheggs/candidates", data2, eggService.buildCandidatesText(name2, candidates));
|
|
3507
|
+
return;
|
|
3508
|
+
}
|
|
3509
|
+
if (sr.matchType === "not_found") return `未找到名为「${name2}」的精灵,请检查名称后重试。`;
|
|
3510
|
+
const pet = sr.pet;
|
|
3511
|
+
const data = eggService.buildSearchData(pet);
|
|
3512
|
+
data.commandHint = "洛克.查蛋 <名称> | 洛克.查蛋 身高0.18m 体重1.5kg | 洛克.配种 <父体> <母体>";
|
|
3513
|
+
data.copyright = "Koishi & WeGame 洛克王国插件";
|
|
3514
|
+
const hint = sr.matchType === "fuzzy" ? `模糊匹配到「${petName2(pet)}」
|
|
3515
|
+
` : "";
|
|
3516
|
+
await sendEggImage(deps, session, "searcheggs", data, hint + eggService.buildSearchText(pet));
|
|
3517
|
+
});
|
|
3518
|
+
ctx.command("洛克").subcommand(".配种 <nameA:string> [nameB:string]", "配种查询").action(async ({ session }, nameA, nameB) => {
|
|
3519
|
+
if (!nameA) {
|
|
3520
|
+
return [
|
|
3521
|
+
"配种用法:",
|
|
3522
|
+
" 洛克.配种 <精灵名>",
|
|
3523
|
+
" 查询想孵出这个精灵时可选哪些父体。",
|
|
3524
|
+
" 洛克.配种 <父体> <母体>",
|
|
3525
|
+
" 判断两只精灵是否可以配种,默认前父后母,孵蛋结果跟随母体。",
|
|
3526
|
+
"示例:",
|
|
3527
|
+
" 洛克.配种 喵喵",
|
|
3528
|
+
" 洛克.配种 父体名称 母体名称"
|
|
3529
|
+
].join("\n");
|
|
3530
|
+
}
|
|
3531
|
+
if (!nameB) {
|
|
3532
|
+
const sr = eggService.search(nameA);
|
|
3533
|
+
if (sr.matchType === "multi") {
|
|
3534
|
+
const candidates = searchResultCandidates(sr);
|
|
3535
|
+
const data3 = eggService.buildCandidatesRenderData(nameA, candidates);
|
|
3536
|
+
await sendEggImage(deps, session, "searcheggs/candidates", data3, eggService.buildCandidatesText(nameA, candidates));
|
|
3537
|
+
return;
|
|
3538
|
+
}
|
|
3539
|
+
if (sr.matchType === "not_found") return `未找到名为「${nameA}」的精灵。`;
|
|
3540
|
+
const data2 = eggService.buildWantPetData(sr.pet);
|
|
3541
|
+
await sendEggImage(deps, session, "searcheggs/want", data2, eggService.buildWantPetText(sr.pet));
|
|
3542
|
+
return;
|
|
3543
|
+
}
|
|
3544
|
+
const srA = eggService.search(nameA);
|
|
3545
|
+
if (srA.matchType === "multi") {
|
|
3546
|
+
const candidates = searchResultCandidates(srA);
|
|
3547
|
+
const data2 = eggService.buildCandidatesRenderData(nameA, candidates);
|
|
3548
|
+
await sendEggImage(deps, session, "searcheggs/candidates", data2, eggService.buildCandidatesText(nameA, candidates));
|
|
3549
|
+
return;
|
|
3550
|
+
}
|
|
3551
|
+
if (srA.matchType === "not_found") return `未找到名为「${nameA}」的精灵。`;
|
|
3552
|
+
const srB = eggService.search(nameB);
|
|
3553
|
+
if (srB.matchType === "multi") {
|
|
3554
|
+
const candidates = searchResultCandidates(srB);
|
|
3555
|
+
const data2 = eggService.buildCandidatesRenderData(nameB, candidates);
|
|
3556
|
+
await sendEggImage(deps, session, "searcheggs/candidates", data2, eggService.buildCandidatesText(nameB, candidates));
|
|
3557
|
+
return;
|
|
3558
|
+
}
|
|
3559
|
+
if (srB.matchType === "not_found") return `未找到名为「${nameB}」的精灵。`;
|
|
3560
|
+
const data = eggService.buildPairData(srB.pet, srA.pet);
|
|
3561
|
+
data.commandHint = "默认前父后母,孵蛋结果跟随母体 | 洛克.配种 <精灵名> 查询怎么孵";
|
|
3562
|
+
data.copyright = "Koishi & WeGame 洛克王国插件";
|
|
3563
|
+
await sendEggImage(deps, session, "searcheggs/pair", data, eggService.buildPairText(srB.pet, srA.pet));
|
|
3564
|
+
});
|
|
3565
|
+
}
|
|
3566
|
+
__name(register5, "register");
|
|
3567
|
+
|
|
3568
|
+
// src/commands/admin.ts
|
|
3569
|
+
var import_koishi10 = require("koishi");
|
|
3570
|
+
var logger9 = new import_koishi10.Logger("rocom-admin");
|
|
3571
|
+
function register6(deps) {
|
|
3572
|
+
const { ctx, config, client, userMgr } = deps;
|
|
3573
|
+
function isAdmin(userId) {
|
|
3574
|
+
return config.adminUserIds.includes(userId);
|
|
3575
|
+
}
|
|
3576
|
+
__name(isAdmin, "isAdmin");
|
|
3577
|
+
ctx.command("洛克").subcommand(".刷新所有凭证", "刷新所有用户凭证(管理员)").action(async ({ session }) => {
|
|
3578
|
+
if (!isAdmin(session.userId)) return "此指令仅限管理员使用。";
|
|
3579
|
+
await session.send("正在刷新所有用户的凭证...");
|
|
3580
|
+
const allUsers = userMgr.getAllUsersBindings();
|
|
3581
|
+
let success = 0;
|
|
3582
|
+
let fail = 0;
|
|
3583
|
+
for (const [userId] of Object.entries(allUsers)) {
|
|
3584
|
+
const binding = userMgr.getPrimaryBinding(userId);
|
|
3585
|
+
if (!binding || binding.login_type !== "qq" || !binding.binding_id) continue;
|
|
3586
|
+
try {
|
|
3587
|
+
const res = await client.refreshBinding(ctx, binding.binding_id, userId);
|
|
3588
|
+
if (res?.framework_token) {
|
|
3589
|
+
await upsertRoleToken(ctx, {
|
|
3590
|
+
userId,
|
|
3591
|
+
fwt: res.framework_token,
|
|
3592
|
+
bindingId: binding.binding_id,
|
|
3593
|
+
roleId: binding.role_id,
|
|
3594
|
+
loginType: binding.login_type
|
|
3595
|
+
});
|
|
3596
|
+
success++;
|
|
3597
|
+
} else {
|
|
3598
|
+
fail++;
|
|
3599
|
+
}
|
|
3600
|
+
} catch {
|
|
3601
|
+
fail++;
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3604
|
+
return `刷新完成:成功 ${success},失败 ${fail}`;
|
|
3605
|
+
});
|
|
3606
|
+
ctx.command("洛克").subcommand(".删除失效绑定", "清理失效绑定(管理员)").action(async ({ session }) => {
|
|
3607
|
+
if (!isAdmin(session.userId)) return "此指令仅限管理员使用。";
|
|
3608
|
+
await session.send("正在检查所有用户的绑定有效性...");
|
|
3609
|
+
const allUsers = userMgr.getAllUsersBindings();
|
|
3610
|
+
let totalInvalid = 0;
|
|
3611
|
+
let totalValid = 0;
|
|
3612
|
+
for (const [userId, bindings] of Object.entries(allUsers)) {
|
|
3613
|
+
const token = await getRoleToken(ctx, userId);
|
|
3614
|
+
if (!token?.fwt) {
|
|
3615
|
+
logger9.warn(`用户 ${userId} 没有可用的 fwt,跳过失效检测`);
|
|
3616
|
+
continue;
|
|
3617
|
+
}
|
|
3618
|
+
const fwToken = token.fwt;
|
|
3619
|
+
const valid = [];
|
|
3620
|
+
for (const binding of bindings) {
|
|
3621
|
+
const roleRes = await client.getRole(ctx, fwToken, void 0, userId);
|
|
3622
|
+
if (roleRes?.role) {
|
|
3623
|
+
valid.push(binding);
|
|
3624
|
+
totalValid++;
|
|
3625
|
+
continue;
|
|
3626
|
+
}
|
|
3627
|
+
if (binding.binding_id) {
|
|
3628
|
+
try {
|
|
3629
|
+
await client.deleteBinding(ctx, binding.binding_id, userId);
|
|
3630
|
+
} catch (e) {
|
|
3631
|
+
logger9.warn(`删除用户 ${userId} 服务端绑定 ${binding.binding_id} 失败:${e}`);
|
|
3632
|
+
}
|
|
3633
|
+
}
|
|
3634
|
+
totalInvalid++;
|
|
3635
|
+
}
|
|
3636
|
+
userMgr.saveUserBindings(userId, valid);
|
|
3637
|
+
if (valid.length === 0) {
|
|
3638
|
+
await removeRoleToken(ctx, userId);
|
|
3639
|
+
}
|
|
3640
|
+
}
|
|
3641
|
+
return totalInvalid > 0 ? `清理完成:移除 ${totalInvalid} 个无效绑定,剩余 ${totalValid} 个有效绑定。` : `所有绑定均有效,无需清理。共 ${totalValid} 个有效绑定。`;
|
|
3642
|
+
});
|
|
3643
|
+
if (config.autoRefreshEnabled) {
|
|
3644
|
+
ctx.setInterval(async () => {
|
|
3645
|
+
const now = /* @__PURE__ */ new Date();
|
|
3646
|
+
const timeStr = `${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`;
|
|
3647
|
+
if (!config.autoRefreshTime.includes(timeStr)) return;
|
|
3648
|
+
const allUsers = userMgr.getAllUsersBindings();
|
|
3649
|
+
for (const [userId] of Object.entries(allUsers)) {
|
|
3650
|
+
const binding = userMgr.getPrimaryBinding(userId);
|
|
3651
|
+
if (!binding || binding.login_type !== "qq" || !binding.binding_id) continue;
|
|
3652
|
+
try {
|
|
3653
|
+
const res = await client.refreshBinding(ctx, binding.binding_id, userId);
|
|
3654
|
+
if (res?.framework_token) {
|
|
3655
|
+
await upsertRoleToken(ctx, {
|
|
3656
|
+
userId,
|
|
3657
|
+
fwt: res.framework_token,
|
|
3658
|
+
bindingId: binding.binding_id,
|
|
3659
|
+
roleId: binding.role_id,
|
|
3660
|
+
loginType: binding.login_type
|
|
3661
|
+
});
|
|
3662
|
+
}
|
|
3663
|
+
} catch (e) {
|
|
3664
|
+
logger9.warn(`自动刷新用户 ${userId} 失败: ${e}`);
|
|
3665
|
+
}
|
|
3666
|
+
}
|
|
3667
|
+
}, 6e4);
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
__name(register6, "register");
|
|
3671
|
+
|
|
3672
|
+
// src/index.ts
|
|
3673
|
+
var name = "rocom";
|
|
3674
|
+
var inject = { required: ["puppeteer", "database"] };
|
|
3675
|
+
var Config = import_koishi11.Schema.intersect([
|
|
3676
|
+
import_koishi11.Schema.object({
|
|
3677
|
+
apiBaseUrl: import_koishi11.Schema.string().default("https://wegame.shallow.ink").description("API 基础地址"),
|
|
3678
|
+
wegameApiKey: import_koishi11.Schema.string().default("").description("WeGame API Key"),
|
|
3679
|
+
qqLoginDebugMode: import_koishi11.Schema.boolean().default(false).description("QQ 扫码登录调试模式:扫码成功后输出用户 fwt(敏感凭证,请仅调试时开启)"),
|
|
3680
|
+
adminUserIds: import_koishi11.Schema.array(String).default([]).description("管理员用户 ID 列表(仅用于管理员命令)"),
|
|
3681
|
+
autoRefreshEnabled: import_koishi11.Schema.boolean().default(false).description("启用自动刷新凭证"),
|
|
3682
|
+
autoRefreshTime: import_koishi11.Schema.array(String).default(["00:00", "12:00"]).description("自动刷新时间")
|
|
3683
|
+
}).description("基础设置"),
|
|
3684
|
+
import_koishi11.Schema.object({
|
|
3685
|
+
imageCompressionEnabled: import_koishi11.Schema.boolean().default(true).description("发送图片前启用 PNG 无损压缩。失败或压缩后不更小时会发送原图。"),
|
|
3686
|
+
imageCompressionMinBytes: import_koishi11.Schema.number().default(262144).description("触发压缩的最小图片大小,单位字节。低于此大小直接发送原图。"),
|
|
3687
|
+
imageCompressionLevel: import_koishi11.Schema.number().min(0).max(9).default(9).description("PNG zlib 压缩等级,0-9,数值越大越慢但通常更小。")
|
|
3688
|
+
}).description("图片压缩设置"),
|
|
3689
|
+
import_koishi11.Schema.object({
|
|
3690
|
+
merchantSubscriptionEnabled: import_koishi11.Schema.boolean().default(true).description("启用远行商人订阅"),
|
|
3691
|
+
merchantSubscriptionItems: import_koishi11.Schema.array(String).default(["国王球", "棱镜球", "炫彩精灵蛋"]).description("默认订阅商品"),
|
|
3692
|
+
merchantCheckInterval: import_koishi11.Schema.number().default(3e5).description("商人检查间隔(ms)"),
|
|
3693
|
+
merchantPrivateSubscriptionEnabled: import_koishi11.Schema.boolean().default(true).description("允许个人私聊订阅远行商人推送"),
|
|
3694
|
+
homeSubscriptionEnabled: import_koishi11.Schema.boolean().default(true).description("启用家园菜园和灵感订阅推送"),
|
|
3695
|
+
homeSubscriptionIntervalMinutes: import_koishi11.Schema.number().default(5).description("家园订阅检查间隔(分钟)")
|
|
3696
|
+
}).description("订阅推送设置")
|
|
3697
|
+
]);
|
|
3698
|
+
function apply(ctx, config) {
|
|
3699
|
+
setupRoleTokenModel(ctx);
|
|
3700
|
+
const client = new RocomClient(config.apiBaseUrl, config.wegameApiKey);
|
|
3701
|
+
const dataDir = import_node_path5.default.join(ctx.baseDir, "data", "rocom");
|
|
3702
|
+
const userMgr = new UserManager(dataDir);
|
|
3703
|
+
const merchantSubMgr = new MerchantSubscriptionManager(dataDir);
|
|
3704
|
+
const homeSubMgr = new HomeSubscriptionManager(dataDir);
|
|
3705
|
+
const resPath = import_node_path5.default.resolve(__dirname, "..");
|
|
3706
|
+
const renderer = new Renderer(resPath);
|
|
3707
|
+
const searcheggsDir = import_node_path5.default.join(resPath, "src", "render-templates", "searcheggs");
|
|
3708
|
+
const eggService = new EggService(searcheggsDir);
|
|
3709
|
+
const deps = { ctx, config, client, userMgr, merchantSubMgr, homeSubMgr, eggService, renderer };
|
|
3710
|
+
ctx.on("ready", () => {
|
|
3711
|
+
migrateRoleTokensToUserId(ctx).catch((err) => {
|
|
3712
|
+
console.warn(`[rocom] role token migration failed: ${err}`);
|
|
3713
|
+
});
|
|
3714
|
+
migrateLegacyFrameworkTokens(ctx, userMgr).catch((err) => {
|
|
3715
|
+
console.warn(`[rocom] legacy framework token migration failed: ${err}`);
|
|
3716
|
+
});
|
|
3717
|
+
});
|
|
3718
|
+
ctx.command("洛克", "洛克王国帮助菜单").action(async () => [
|
|
3719
|
+
"【洛克王国插件】",
|
|
3720
|
+
"",
|
|
3721
|
+
"📋 账号管理:",
|
|
3722
|
+
" 洛克.QQ登录 / 洛克.微信登录 / 洛克.导入",
|
|
3723
|
+
" 洛克.绑定列表 / 洛克.切换 / 洛克.解绑 / 洛克.刷新",
|
|
3724
|
+
"",
|
|
3725
|
+
"📊 数据查询:",
|
|
3726
|
+
" 洛克.档案 / 洛克.战绩 / 洛克.背包 / 洛克.阵容",
|
|
3727
|
+
" 洛克.交换大厅 / 远行商人 / 订阅远行商人",
|
|
3728
|
+
"",
|
|
3729
|
+
"📖 百科查询:",
|
|
3730
|
+
" 洛克.wiki / 洛克.技能 / 洛克.查蛋 / 洛克.配种"
|
|
3731
|
+
].join("\n"));
|
|
3732
|
+
register(deps);
|
|
3733
|
+
register2(deps);
|
|
3734
|
+
register3(deps);
|
|
3735
|
+
register4(deps);
|
|
3736
|
+
register5(deps);
|
|
3737
|
+
register6(deps);
|
|
3738
|
+
}
|
|
3739
|
+
__name(apply, "apply");
|
|
3740
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3741
|
+
0 && (module.exports = {
|
|
3742
|
+
Config,
|
|
3743
|
+
apply,
|
|
3744
|
+
inject,
|
|
3745
|
+
name
|
|
3746
|
+
});
|