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.
Files changed (57) hide show
  1. package/lib/announcement-service.d.ts +21 -0
  2. package/lib/announcement-service.js +63 -0
  3. package/lib/async-control.d.ts +3 -0
  4. package/lib/async-control.js +48 -0
  5. package/lib/client.d.ts +48 -7
  6. package/lib/client.js +244 -125
  7. package/lib/commands/egg.js +66 -4
  8. package/lib/commands/merchant.js +144 -146
  9. package/lib/commands/query.js +148 -117
  10. package/lib/commands/ranking.d.ts +2 -0
  11. package/lib/commands/ranking.js +31 -0
  12. package/lib/commands/share-code.d.ts +2 -0
  13. package/lib/commands/share-code.js +66 -0
  14. package/lib/commands/tools.js +47 -59
  15. package/lib/egg-service.d.ts +82 -8
  16. package/lib/egg-service.js +235 -19
  17. package/lib/img//345/274/202/350/211/262.png +0 -0
  18. package/lib/img//347/202/253/345/275/251.png +0 -0
  19. package/lib/index.d.ts +5 -0
  20. package/lib/index.js +16 -3
  21. package/lib/merchant-clock.d.ts +22 -0
  22. package/lib/merchant-clock.js +95 -0
  23. package/lib/merchant-service.d.ts +24 -0
  24. package/lib/merchant-service.js +148 -0
  25. package/lib/permissions.d.ts +16 -0
  26. package/lib/permissions.js +66 -0
  27. package/lib/pet-data-service.js +7 -9
  28. package/lib/pet-size.d.ts +13 -0
  29. package/lib/pet-size.js +54 -0
  30. package/lib/player-service.d.ts +37 -0
  31. package/lib/player-service.js +201 -0
  32. package/lib/ranking-service.d.ts +46 -0
  33. package/lib/ranking-service.js +137 -0
  34. package/lib/render-templates/announcement/detail.html +8 -0
  35. package/lib/render-templates/announcement/list.html +5 -0
  36. package/lib/render-templates/announcement/style.css +1 -0
  37. package/lib/render-templates/pet-ranking/index.html +99 -0
  38. package/lib/render-templates/pet-ranking/style.css +338 -0
  39. package/lib/render-templates/player-search/index.html +7 -0
  40. package/lib/render-templates/searcheggs/index.html +1 -1
  41. package/lib/render-templates/searcheggs/size.html +70 -68
  42. package/lib/render-templates/share-code-team/index.html +105 -0
  43. package/lib/render-templates/share-code-team/style.css +345 -0
  44. package/lib/render.d.ts +15 -2
  45. package/lib/render.js +120 -37
  46. package/lib/send-image.d.ts +1 -1
  47. package/lib/send-image.js +12 -1
  48. package/lib/share-code-service.d.ts +32 -0
  49. package/lib/share-code-service.js +184 -0
  50. package/lib/subscription-runner.d.ts +6 -0
  51. package/lib/subscription-runner.js +21 -0
  52. package/lib/subscription-send.d.ts +4 -2
  53. package/lib/subscription-send.js +45 -20
  54. package/lib/types.d.ts +5 -0
  55. package/lib/user.d.ts +5 -0
  56. package/package.json +4 -1
  57. package/readme.md +145 -124
@@ -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(/&nbsp;/g, ' ')
19
- .replace(/&amp;/g, '&')
20
- .replace(/&lt;/g, '<')
21
- .replace(/&gt;/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 = !session?.guildId;
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
- const latest = await client.getLatestAnnouncement(ctx, { category_id: 99 });
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 = [`【洛克王国新公告】`, buildAnnouncementDetailText(latest)].join('\n');
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.sendScheduledMessage)(ctx, {
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
- return buildAnnouncementListText(data, currentPage);
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
- return buildAnnouncementDetailText(data);
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
- return buildAnnouncementDetailText(data);
198
+ await sendDetail(session, data);
214
199
  });
215
- ctx.command('订阅洛克公告', '订阅洛克王国新公告推送')
200
+ ctx.command('订阅洛克公告', '订阅洛克王国新公告推送').userFields(['authority'])
216
201
  .action(async ({ session }) => {
217
- const privateChat = !session?.guildId;
218
- if (!privateChat && !config.adminUserIds.includes(session?.userId || ''))
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 = !session?.guildId;
240
- if (!privateChat && !config.adminUserIds.includes(session?.userId || ''))
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 远端配置')
@@ -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
- probability: number;
246
- match_count: number;
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[];