koishi-plugin-rocom 1.0.17 → 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 +49 -9
  6. package/lib/client.js +250 -141
  7. package/lib/commands/egg.js +101 -9
  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 +125 -20
  16. package/lib/egg-service.js +364 -96
  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 +193 -8740
  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,10 +27,9 @@ 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
- private formatEggSearchCard;
33
- private formatEggSearchTextLine;
34
33
  private mergeCardsByName;
35
34
  private mergeSizeCard;
36
35
  private minValue;
@@ -51,8 +50,8 @@ export declare class EggService {
51
50
  range: any[];
52
51
  }, heightDisplay?: string): string;
53
52
  buildSizeSearchTextFromApi(height?: number, weight?: number, results?: any, heightDisplay?: string): string;
54
- buildEggSearchText(heightMeters?: number, weight?: number, results?: any, heightDisplay?: string): string;
55
53
  buildSearchText(pet: any): string;
54
+ buildSearchTextFromWiki(data: any): string;
56
55
  buildCandidatesText(keyword: string, candidates: any[]): string;
57
56
  buildWantPetText(pet: any): string;
58
57
  buildPairText(a: any, b: any): string;
@@ -117,6 +116,48 @@ export declare class EggService {
117
116
  commandHint: string;
118
117
  copyright: string;
119
118
  };
119
+ buildSearchDataFromWiki(pet: any, compatibleByGroup?: Record<string, any[]>): {
120
+ pet_name: any;
121
+ pet_id: any;
122
+ pet_icon: any;
123
+ pet_image: any;
124
+ type_label: string;
125
+ egg_groups_label: string;
126
+ egg_groups: (string | number)[];
127
+ egg_group_labels: {
128
+ [k: string]: string;
129
+ };
130
+ male_rate: number;
131
+ female_rate: number;
132
+ hatch_label: string;
133
+ weight_label: string;
134
+ height_label: string;
135
+ total_compatible: number;
136
+ is_undiscovered: boolean;
137
+ egg_group_sections: {
138
+ id: string | number;
139
+ label: string;
140
+ desc: string;
141
+ count: number;
142
+ members: {
143
+ name: any;
144
+ id: any;
145
+ type_label: string;
146
+ egg_groups_label: string;
147
+ }[];
148
+ has_more: boolean;
149
+ total: number;
150
+ }[];
151
+ total_stats: number;
152
+ egg_details: {
153
+ has_data: boolean;
154
+ };
155
+ commandHint: string;
156
+ copyright: string;
157
+ };
158
+ private wikiEggGroups;
159
+ private wikiTypeLabel;
160
+ private formatWikiMember;
120
161
  buildPairData(a: any, b: any): {
121
162
  commandHint: string;
122
163
  copyright: string;
@@ -142,6 +183,13 @@ export declare class EggService {
142
183
  };
143
184
  buildWantPetData(pet: any): {
144
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;
145
193
  id: any;
146
194
  name: string;
147
195
  icon: string;
@@ -155,15 +203,19 @@ export declare class EggService {
155
203
  weight_min: number;
156
204
  weight_max: number;
157
205
  weight_label: string;
158
- probability: number;
159
- match_count: number;
160
- match_info_label: string;
161
206
  };
162
207
  egg_groups_label: string;
163
208
  female_rate: any;
164
209
  male_rate: any;
165
210
  is_undiscovered: boolean;
166
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;
167
219
  id: any;
168
220
  name: string;
169
221
  icon: string;
@@ -177,9 +229,6 @@ export declare class EggService {
177
229
  weight_min: number;
178
230
  weight_max: number;
179
231
  weight_label: string;
180
- probability: number;
181
- match_count: number;
182
- match_info_label: string;
183
232
  }[];
184
233
  father_count: number;
185
234
  commandHint: string;
@@ -189,6 +238,13 @@ export declare class EggService {
189
238
  keyword: string;
190
239
  count: number;
191
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;
192
248
  id: any;
193
249
  name: string;
194
250
  icon: string;
@@ -202,13 +258,71 @@ export declare class EggService {
202
258
  weight_min: number;
203
259
  weight_max: number;
204
260
  weight_label: string;
205
- probability: number;
206
- 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;
207
284
  match_info_label: string;
208
285
  }[];
209
286
  commandHint: string;
210
287
  copyright: string;
211
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;
212
326
  buildSizeSearchData(height?: number, weight?: number, results?: {
213
327
  perfect: any[];
214
328
  range: any[];
@@ -230,14 +344,5 @@ export declare class EggService {
230
344
  commandHint: string;
231
345
  copyright: string;
232
346
  };
233
- buildEggSearchData(heightMeters?: number, weight?: number, results?: any, heightDisplay?: string): {
234
- query_label: string;
235
- perfect_matches: any;
236
- range_matches: any[];
237
- total_count: any;
238
- has_results: boolean;
239
- commandHint: string;
240
- copyright: string;
241
- };
242
347
  private buildEggDetails;
243
348
  }