koishi-plugin-rocom 1.0.18 → 1.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/announcement-service.d.ts +21 -0
- package/lib/announcement-service.js +63 -0
- package/lib/async-control.d.ts +3 -0
- package/lib/async-control.js +48 -0
- package/lib/client.d.ts +48 -7
- package/lib/client.js +244 -125
- package/lib/commands/egg.js +66 -4
- package/lib/commands/merchant.js +144 -146
- package/lib/commands/query.js +148 -117
- package/lib/commands/ranking.d.ts +2 -0
- package/lib/commands/ranking.js +31 -0
- package/lib/commands/share-code.d.ts +2 -0
- package/lib/commands/share-code.js +66 -0
- package/lib/commands/tools.js +47 -59
- package/lib/egg-service.d.ts +82 -8
- package/lib/egg-service.js +235 -19
- package/lib/img//345/274/202/350/211/262.png +0 -0
- package/lib/img//347/202/253/345/275/251.png +0 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +16 -3
- package/lib/merchant-clock.d.ts +22 -0
- package/lib/merchant-clock.js +95 -0
- package/lib/merchant-service.d.ts +24 -0
- package/lib/merchant-service.js +148 -0
- package/lib/permissions.d.ts +16 -0
- package/lib/permissions.js +66 -0
- package/lib/pet-data-service.js +7 -9
- package/lib/pet-size.d.ts +13 -0
- package/lib/pet-size.js +54 -0
- package/lib/player-service.d.ts +37 -0
- package/lib/player-service.js +201 -0
- package/lib/ranking-service.d.ts +46 -0
- package/lib/ranking-service.js +137 -0
- package/lib/render-templates/announcement/detail.html +8 -0
- package/lib/render-templates/announcement/list.html +5 -0
- package/lib/render-templates/announcement/style.css +1 -0
- package/lib/render-templates/pet-ranking/index.html +99 -0
- package/lib/render-templates/pet-ranking/style.css +338 -0
- package/lib/render-templates/player-search/index.html +7 -0
- package/lib/render-templates/searcheggs/index.html +1 -1
- package/lib/render-templates/searcheggs/size.html +70 -68
- package/lib/render-templates/share-code-team/index.html +105 -0
- package/lib/render-templates/share-code-team/style.css +345 -0
- package/lib/render.d.ts +15 -2
- package/lib/render.js +120 -37
- package/lib/send-image.d.ts +1 -1
- package/lib/send-image.js +12 -1
- package/lib/share-code-service.d.ts +32 -0
- package/lib/share-code-service.js +184 -0
- package/lib/subscription-runner.d.ts +6 -0
- package/lib/subscription-runner.js +21 -0
- package/lib/subscription-send.d.ts +4 -2
- package/lib/subscription-send.js +45 -20
- package/lib/types.d.ts +5 -0
- package/lib/user.d.ts +5 -0
- package/package.json +4 -1
- package/readme.md +145 -124
package/lib/commands/tools.js
CHANGED
|
@@ -5,23 +5,14 @@ const koishi_1 = require("koishi");
|
|
|
5
5
|
const activities_service_1 = require("../activities-service");
|
|
6
6
|
const send_image_1 = require("../send-image");
|
|
7
7
|
const subscription_send_1 = require("../subscription-send");
|
|
8
|
+
const permissions_1 = require("../permissions");
|
|
9
|
+
const subscription_runner_1 = require("../subscription-runner");
|
|
10
|
+
const announcement_service_1 = require("../announcement-service");
|
|
8
11
|
const logger = new koishi_1.Logger('rocom-tools');
|
|
9
12
|
const activitiesService = new activities_service_1.ActivitiesService();
|
|
10
13
|
function trimText(value) {
|
|
11
14
|
return String(value ?? '').trim();
|
|
12
15
|
}
|
|
13
|
-
function stripHtml(value) {
|
|
14
|
-
return trimText(value)
|
|
15
|
-
.replace(/<br\s*\/?>/gi, '\n')
|
|
16
|
-
.replace(/<\/p>/gi, '\n')
|
|
17
|
-
.replace(/<[^>]+>/g, '')
|
|
18
|
-
.replace(/ /g, ' ')
|
|
19
|
-
.replace(/&/g, '&')
|
|
20
|
-
.replace(/</g, '<')
|
|
21
|
-
.replace(/>/g, '>')
|
|
22
|
-
.replace(/\n{3,}/g, '\n\n')
|
|
23
|
-
.trim();
|
|
24
|
-
}
|
|
25
16
|
function formatDate(value) {
|
|
26
17
|
const text = trimText(value);
|
|
27
18
|
if (!text)
|
|
@@ -72,37 +63,52 @@ function announcementTimestamp(item) {
|
|
|
72
63
|
return 0;
|
|
73
64
|
}
|
|
74
65
|
function announcementSubscriptionKey(session) {
|
|
75
|
-
const privateChat =
|
|
66
|
+
const privateChat = (0, permissions_1.isDirectSession)(session);
|
|
76
67
|
return [session?.platform || '', session?.channelId || 'private', privateChat ? (session?.userId || '') : '', 'announcement'].join(':');
|
|
77
68
|
}
|
|
78
69
|
// 上游 v3.2.0:轮询最新公告,向订阅会话推送新公告提醒。
|
|
79
|
-
async function checkAnnouncementSubscriptions(deps) {
|
|
70
|
+
async function checkAnnouncementSubscriptions(deps, signal) {
|
|
80
71
|
const { ctx, client, announcementSubMgr } = deps;
|
|
81
72
|
const subs = announcementSubMgr.getAll();
|
|
82
73
|
const subEntries = Object.entries(subs);
|
|
83
74
|
if (!subEntries.length)
|
|
84
75
|
return { subscriptions: 0, pushed: 0 };
|
|
85
|
-
|
|
76
|
+
if (signal?.aborted)
|
|
77
|
+
return { subscriptions: 0, pushed: 0 };
|
|
78
|
+
const latest = await client.getLatestAnnouncement(ctx, { category_id: 99 }, '', signal);
|
|
79
|
+
if (signal?.aborted)
|
|
80
|
+
return { subscriptions: 0, pushed: 0 };
|
|
86
81
|
const item = latest?.detail || latest?.announcement || latest;
|
|
87
82
|
const latestId = announcementId(item);
|
|
88
83
|
if (!latestId)
|
|
89
84
|
return { subscriptions: subEntries.length, pushed: 0 };
|
|
90
85
|
const latestTs = announcementTimestamp(item);
|
|
86
|
+
const view = (0, announcement_service_1.buildAnnouncementView)(latest, deps.config.apiBaseUrl);
|
|
87
|
+
let image;
|
|
91
88
|
let pushed = 0;
|
|
92
89
|
for (const [key, sub] of subEntries) {
|
|
90
|
+
if (signal?.aborted)
|
|
91
|
+
break;
|
|
92
|
+
if (JSON.stringify(announcementSubMgr.getAll()[key]) !== JSON.stringify(sub))
|
|
93
|
+
continue;
|
|
93
94
|
if (latestId === String(sub.last_id || ''))
|
|
94
95
|
continue;
|
|
95
96
|
if (latestTs && sub.since_ts && latestTs <= sub.since_ts)
|
|
96
97
|
continue;
|
|
97
|
-
const message =
|
|
98
|
+
const message = (0, announcement_service_1.announcementViewText)(view);
|
|
99
|
+
if (image === undefined)
|
|
100
|
+
image = await deps.renderer.renderPages(ctx, 'announcement/detail', view, { signal });
|
|
101
|
+
if (signal?.aborted)
|
|
102
|
+
break;
|
|
98
103
|
try {
|
|
99
|
-
const sent = await (0, subscription_send_1.
|
|
104
|
+
const sent = await (0, subscription_send_1.sendScheduledImageWithFallback)(ctx, {
|
|
100
105
|
platform: sub.platform,
|
|
106
|
+
selfId: sub.self_id,
|
|
101
107
|
channelId: sub.channel_id || sub.guild_id || sub.user_id || '',
|
|
102
108
|
guildId: sub.guild_id || '',
|
|
103
109
|
userId: sub.user_id || '',
|
|
104
|
-
}, message);
|
|
105
|
-
if (!sent)
|
|
110
|
+
}, image, message, false, signal);
|
|
111
|
+
if (!sent || signal?.aborted || JSON.stringify(announcementSubMgr.getAll()[key]) !== JSON.stringify(sub))
|
|
106
112
|
continue;
|
|
107
113
|
}
|
|
108
114
|
catch (e) {
|
|
@@ -141,36 +147,14 @@ function buildAnnouncementListText(data, page) {
|
|
|
141
147
|
lines.push('详情:洛克.公告详情 <公告ID>');
|
|
142
148
|
return lines.join('\n');
|
|
143
149
|
}
|
|
144
|
-
function buildAnnouncementDetailText(data) {
|
|
145
|
-
const item = data?.detail || data?.announcement || data;
|
|
146
|
-
if (!item || typeof item !== 'object')
|
|
147
|
-
return '公告详情为空。';
|
|
148
|
-
const title = trimText(item?.title) || '未命名公告';
|
|
149
|
-
const id = announcementId(item);
|
|
150
|
-
const summary = trimText(item?.summary);
|
|
151
|
-
const content = stripHtml(item?.content?.text || item?.text || item?.content);
|
|
152
|
-
const lines = [
|
|
153
|
-
`公告详情${id ? ` #${id}` : ''}`,
|
|
154
|
-
title,
|
|
155
|
-
`发布时间:${formatDate(item?.publishAt || item?.published_at || item?.createdAt)}`,
|
|
156
|
-
];
|
|
157
|
-
if (summary)
|
|
158
|
-
lines.push(`摘要:${summary}`);
|
|
159
|
-
if (content) {
|
|
160
|
-
lines.push('');
|
|
161
|
-
lines.push(content.length > 900 ? `${content.slice(0, 900)}...` : content);
|
|
162
|
-
}
|
|
163
|
-
const images = Array.isArray(item?.content?.indexes)
|
|
164
|
-
? item.content.indexes.flatMap((entry) => Array.isArray(entry?.imageUrl) ? entry.imageUrl : [])
|
|
165
|
-
: [];
|
|
166
|
-
if (images.length) {
|
|
167
|
-
lines.push('');
|
|
168
|
-
lines.push(`图片资源:${images.slice(0, 3).join('\n')}`);
|
|
169
|
-
}
|
|
170
|
-
return lines.join('\n');
|
|
171
|
-
}
|
|
172
150
|
function register(deps) {
|
|
173
151
|
const { ctx, client, config, renderer } = deps;
|
|
152
|
+
const runner = (0, subscription_runner_1.createRunner)(ctx);
|
|
153
|
+
const sendDetail = async (session, data) => {
|
|
154
|
+
const view = (0, announcement_service_1.buildAnnouncementView)(data, config.apiBaseUrl);
|
|
155
|
+
const pages = await renderer.renderPages(ctx, 'announcement/detail', view);
|
|
156
|
+
await (0, send_image_1.sendImageWithFallback)(session, pages, (0, announcement_service_1.announcementViewText)(view), 'announcement:detail', config);
|
|
157
|
+
};
|
|
174
158
|
ctx.command('洛克').subcommand('.日历 [mode:string]', '查看洛克活动日历')
|
|
175
159
|
.alias('洛克日历')
|
|
176
160
|
.action(async ({ session }, mode = '') => {
|
|
@@ -191,7 +175,8 @@ function register(deps) {
|
|
|
191
175
|
const data = await client.getAnnouncementList(ctx, { category_id: 99, page: currentPage, limit: 10 }, session?.userId || '');
|
|
192
176
|
if (!data)
|
|
193
177
|
return `公告列表查询失败:${client.getLastErrorBrief()}`;
|
|
194
|
-
|
|
178
|
+
const pages = await renderer.renderPages(ctx, 'announcement/list', (0, announcement_service_1.buildAnnouncementListView)(data, currentPage));
|
|
179
|
+
await (0, send_image_1.sendImageWithFallback)(session, pages, buildAnnouncementListText(data, currentPage), 'announcement:list', config);
|
|
195
180
|
});
|
|
196
181
|
ctx.command('洛克').subcommand('.最新公告', '查看最新洛克公告')
|
|
197
182
|
.alias('洛克最新公告')
|
|
@@ -199,7 +184,7 @@ function register(deps) {
|
|
|
199
184
|
const data = await client.getLatestAnnouncement(ctx, { category_id: 99 }, session?.userId || '');
|
|
200
185
|
if (!data)
|
|
201
186
|
return `最新公告查询失败:${client.getLastErrorBrief()}`;
|
|
202
|
-
|
|
187
|
+
await sendDetail(session, data);
|
|
203
188
|
});
|
|
204
189
|
ctx.command('洛克').subcommand('.公告详情 <threadId:string>', '查看公告详情')
|
|
205
190
|
.alias('洛克公告详情')
|
|
@@ -210,20 +195,23 @@ function register(deps) {
|
|
|
210
195
|
const data = await client.getAnnouncementDetail(ctx, id, session?.userId || '');
|
|
211
196
|
if (!data)
|
|
212
197
|
return `公告详情查询失败:${client.getLastErrorBrief()}`;
|
|
213
|
-
|
|
198
|
+
await sendDetail(session, data);
|
|
214
199
|
});
|
|
215
|
-
ctx.command('订阅洛克公告', '订阅洛克王国新公告推送')
|
|
200
|
+
ctx.command('订阅洛克公告', '订阅洛克王国新公告推送').userFields(['authority'])
|
|
216
201
|
.action(async ({ session }) => {
|
|
217
|
-
const privateChat =
|
|
218
|
-
if (!
|
|
219
|
-
return '
|
|
202
|
+
const privateChat = (0, permissions_1.isDirectSession)(session);
|
|
203
|
+
if (!(0, permissions_1.canManageSubscription)(config, session))
|
|
204
|
+
return '此指令仅限群管理员或机器人管理员使用。';
|
|
220
205
|
const key = announcementSubscriptionKey(session);
|
|
221
206
|
// 以当前最新公告为基线,只推送之后的新公告。
|
|
222
207
|
const latest = await client.getLatestAnnouncement(ctx, { category_id: 99 }, session?.userId || '');
|
|
208
|
+
if (!latest)
|
|
209
|
+
return '获取公告基线失败,请稍后重新订阅。';
|
|
223
210
|
const item = latest?.detail || latest?.announcement || latest;
|
|
224
211
|
deps.announcementSubMgr.upsert(key, {
|
|
225
212
|
key,
|
|
226
213
|
platform: session?.platform || '',
|
|
214
|
+
self_id: session?.selfId || session?.bot?.selfId || '',
|
|
227
215
|
channel_id: session?.channelId || '',
|
|
228
216
|
guild_id: session?.guildId || '',
|
|
229
217
|
user_id: privateChat ? (session?.userId || '') : '',
|
|
@@ -234,11 +222,11 @@ function register(deps) {
|
|
|
234
222
|
});
|
|
235
223
|
return '已订阅洛克王国新公告推送,检测到新公告时会在当前会话提醒。';
|
|
236
224
|
});
|
|
237
|
-
ctx.command('取消订阅洛克公告', '取消洛克王国新公告推送')
|
|
225
|
+
ctx.command('取消订阅洛克公告', '取消洛克王国新公告推送').userFields(['authority'])
|
|
238
226
|
.action(async ({ session }) => {
|
|
239
|
-
const privateChat =
|
|
240
|
-
if (!
|
|
241
|
-
return '
|
|
227
|
+
const privateChat = (0, permissions_1.isDirectSession)(session);
|
|
228
|
+
if (!(0, permissions_1.canManageSubscription)(config, session))
|
|
229
|
+
return '此指令仅限群管理员或机器人管理员使用。';
|
|
242
230
|
const deleted = deps.announcementSubMgr.deleteMatching({
|
|
243
231
|
platform: session?.platform || '',
|
|
244
232
|
channelId: session?.channelId || '',
|
|
@@ -248,7 +236,7 @@ function register(deps) {
|
|
|
248
236
|
});
|
|
249
237
|
if (config.announcementSubscriptionEnabled) {
|
|
250
238
|
ctx.setInterval(() => {
|
|
251
|
-
checkAnnouncementSubscriptions(deps).catch(err => logger.warn(`公告订阅检查失败: ${err}`));
|
|
239
|
+
runner(signal => checkAnnouncementSubscriptions(deps, signal)).catch(err => logger.warn(`公告订阅检查失败: ${err}`));
|
|
252
240
|
}, Math.max(1, config.announcementPollIntervalMinutes || 10) * 60000);
|
|
253
241
|
}
|
|
254
242
|
ctx.command('洛克').subcommand('.同步配置', '手动同步 RoCom 远端配置')
|
package/lib/egg-service.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export declare class EggService {
|
|
|
27
27
|
private rangeMatchScore;
|
|
28
28
|
private formatPetCard;
|
|
29
29
|
private formatSizeApiCard;
|
|
30
|
+
private rangePair;
|
|
30
31
|
private splitNewSizeApiItems;
|
|
31
32
|
private sizeApiResultGroups;
|
|
32
33
|
private mergeCardsByName;
|
|
@@ -182,6 +183,13 @@ export declare class EggService {
|
|
|
182
183
|
};
|
|
183
184
|
buildWantPetData(pet: any): {
|
|
184
185
|
target: {
|
|
186
|
+
probability: number;
|
|
187
|
+
match_count: number;
|
|
188
|
+
match_info_label: string;
|
|
189
|
+
size_variant: string;
|
|
190
|
+
size_variant_label: string;
|
|
191
|
+
size_variant_class: string;
|
|
192
|
+
size_variant_hint: string;
|
|
185
193
|
id: any;
|
|
186
194
|
name: string;
|
|
187
195
|
icon: string;
|
|
@@ -195,15 +203,19 @@ export declare class EggService {
|
|
|
195
203
|
weight_min: number;
|
|
196
204
|
weight_max: number;
|
|
197
205
|
weight_label: string;
|
|
198
|
-
probability: number;
|
|
199
|
-
match_count: number;
|
|
200
|
-
match_info_label: string;
|
|
201
206
|
};
|
|
202
207
|
egg_groups_label: string;
|
|
203
208
|
female_rate: any;
|
|
204
209
|
male_rate: any;
|
|
205
210
|
is_undiscovered: boolean;
|
|
206
211
|
fathers: {
|
|
212
|
+
probability: number;
|
|
213
|
+
match_count: number;
|
|
214
|
+
match_info_label: string;
|
|
215
|
+
size_variant: string;
|
|
216
|
+
size_variant_label: string;
|
|
217
|
+
size_variant_class: string;
|
|
218
|
+
size_variant_hint: string;
|
|
207
219
|
id: any;
|
|
208
220
|
name: string;
|
|
209
221
|
icon: string;
|
|
@@ -217,9 +229,6 @@ export declare class EggService {
|
|
|
217
229
|
weight_min: number;
|
|
218
230
|
weight_max: number;
|
|
219
231
|
weight_label: string;
|
|
220
|
-
probability: number;
|
|
221
|
-
match_count: number;
|
|
222
|
-
match_info_label: string;
|
|
223
232
|
}[];
|
|
224
233
|
father_count: number;
|
|
225
234
|
commandHint: string;
|
|
@@ -229,6 +238,13 @@ export declare class EggService {
|
|
|
229
238
|
keyword: string;
|
|
230
239
|
count: number;
|
|
231
240
|
candidates: {
|
|
241
|
+
probability: number;
|
|
242
|
+
match_count: number;
|
|
243
|
+
match_info_label: string;
|
|
244
|
+
size_variant: string;
|
|
245
|
+
size_variant_label: string;
|
|
246
|
+
size_variant_class: string;
|
|
247
|
+
size_variant_hint: string;
|
|
232
248
|
id: any;
|
|
233
249
|
name: string;
|
|
234
250
|
icon: string;
|
|
@@ -242,13 +258,71 @@ export declare class EggService {
|
|
|
242
258
|
weight_min: number;
|
|
243
259
|
weight_max: number;
|
|
244
260
|
weight_label: string;
|
|
245
|
-
|
|
246
|
-
|
|
261
|
+
}[];
|
|
262
|
+
commandHint: string;
|
|
263
|
+
copyright: string;
|
|
264
|
+
};
|
|
265
|
+
buildCandidatesFromEggApi(keyword: string, candidates: any[]): {
|
|
266
|
+
keyword: string;
|
|
267
|
+
count: number;
|
|
268
|
+
candidates: {
|
|
269
|
+
id: any;
|
|
270
|
+
name: string;
|
|
271
|
+
icon: any;
|
|
272
|
+
image: any;
|
|
273
|
+
type_label: string;
|
|
274
|
+
egg_group_ids: any[];
|
|
275
|
+
egg_groups_label: string;
|
|
276
|
+
height_min: number;
|
|
277
|
+
height_max: number;
|
|
278
|
+
height_label: string;
|
|
279
|
+
weight_min: number;
|
|
280
|
+
weight_max: number;
|
|
281
|
+
weight_label: string;
|
|
282
|
+
probability: any;
|
|
283
|
+
match_count: any;
|
|
247
284
|
match_info_label: string;
|
|
248
285
|
}[];
|
|
249
286
|
commandHint: string;
|
|
250
287
|
copyright: string;
|
|
251
288
|
};
|
|
289
|
+
buildSearchDataFromEggApi(pet: any, compatibleByGroup?: Record<string, any>): {
|
|
290
|
+
pet_name: string;
|
|
291
|
+
pet_id: any;
|
|
292
|
+
pet_icon: any;
|
|
293
|
+
pet_image: any;
|
|
294
|
+
type_label: string;
|
|
295
|
+
egg_groups_label: string;
|
|
296
|
+
egg_groups: number[];
|
|
297
|
+
egg_group_labels: Record<number, string>;
|
|
298
|
+
male_rate: any;
|
|
299
|
+
female_rate: any;
|
|
300
|
+
hatch_label: string;
|
|
301
|
+
weight_label: string;
|
|
302
|
+
height_label: string;
|
|
303
|
+
total_compatible: number;
|
|
304
|
+
compatible_total_known: boolean;
|
|
305
|
+
is_undiscovered: boolean;
|
|
306
|
+
egg_group_sections: any[];
|
|
307
|
+
total_stats: string;
|
|
308
|
+
egg_details: {
|
|
309
|
+
has_data: boolean;
|
|
310
|
+
};
|
|
311
|
+
commandHint: string;
|
|
312
|
+
copyright: string;
|
|
313
|
+
};
|
|
314
|
+
private formatEggApiCard;
|
|
315
|
+
private formatEggApiMember;
|
|
316
|
+
private eggApiGroups;
|
|
317
|
+
private eggGroupId;
|
|
318
|
+
private eggGroupLabel;
|
|
319
|
+
private eggGroupDesc;
|
|
320
|
+
private isUndiscoveredEggGroup;
|
|
321
|
+
private eggPetDisplayName;
|
|
322
|
+
private eggApiTypeLabel;
|
|
323
|
+
private eggGroupsLabel;
|
|
324
|
+
private eggHeightRangeM;
|
|
325
|
+
private eggWeightRangeKg;
|
|
252
326
|
buildSizeSearchData(height?: number, weight?: number, results?: {
|
|
253
327
|
perfect: any[];
|
|
254
328
|
range: any[];
|