koishi-plugin-subscription 0.0.5 → 0.0.7

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/index.d.ts CHANGED
@@ -1,4 +1,9 @@
1
1
  import { Context, Dict, Schema, Service, Fragment } from 'koishi';
2
+ interface RecordsItem {
3
+ app: string;
4
+ id: string;
5
+ createdAt: Date;
6
+ }
2
7
  interface SubscriptionItem {
3
8
  id: number;
4
9
  app: string;
@@ -31,17 +36,21 @@ declare module 'koishi' {
31
36
  }
32
37
  interface Tables {
33
38
  subscription_service: SubscriptionItem;
39
+ subscription_records: RecordsItem;
34
40
  }
35
41
  }
36
42
  declare class SubscriptionService extends Service {
37
- readonly tableName = "subscription_service";
43
+ readonly subTableName = "subscription_service";
44
+ readonly recTableName = "subscription_records";
38
45
  subsConfig: Config;
39
46
  appMap: Map<string, string>;
40
47
  accountMap: Map<string, string>;
41
48
  constructor(ctx: Context, config: Config);
42
49
  initMap(): void;
43
50
  private getApp;
44
- private getAccount;
51
+ checkExist(app: string, id: string): Promise<boolean>;
52
+ getAccount(app: string, account: string): string;
53
+ getName(app: string, account: string): string;
45
54
  getAvailableAccounts(app: string): string[];
46
55
  getSubscribedGroups(app: string, account: string): Promise<string[]>;
47
56
  broadcast(app: string, account: string, content: Fragment): Promise<void>;
package/lib/index.js CHANGED
@@ -27,6 +27,21 @@ __export(src_exports, {
27
27
  });
28
28
  module.exports = __toCommonJS(src_exports);
29
29
  var import_koishi = require("koishi");
30
+
31
+ // src/retry.ts
32
+ async function retry(retries, fn, delay = 500) {
33
+ try {
34
+ return await fn();
35
+ } catch (err) {
36
+ console.log(`剩余${retries}次尝试`, err);
37
+ if (retries <= 1) throw err;
38
+ await new Promise((r) => setTimeout(r, delay));
39
+ return retry(retries - 1, fn, delay * 2);
40
+ }
41
+ }
42
+ __name(retry, "retry");
43
+
44
+ // src/index.tsx
30
45
  var import_jsx_runtime = require("@satorijs/element/jsx-runtime");
31
46
  var Config = import_koishi.Schema.intersect([
32
47
  import_koishi.Schema.object({
@@ -62,7 +77,8 @@ var SubscriptionService = class extends import_koishi.Service {
62
77
  __name(this, "SubscriptionService");
63
78
  }
64
79
  // 数据表名称
65
- tableName = "subscription_service";
80
+ subTableName = "subscription_service";
81
+ recTableName = "subscription_records";
66
82
  subsConfig;
67
83
  appMap;
68
84
  accountMap;
@@ -70,7 +86,7 @@ var SubscriptionService = class extends import_koishi.Service {
70
86
  super(ctx, "subscription");
71
87
  this.subsConfig = config;
72
88
  this.initMap();
73
- ctx.model.extend(this.tableName, {
89
+ ctx.model.extend(this.subTableName, {
74
90
  id: "unsigned",
75
91
  app: "string",
76
92
  account: "string",
@@ -80,6 +96,14 @@ var SubscriptionService = class extends import_koishi.Service {
80
96
  primary: "id",
81
97
  autoInc: true
82
98
  });
99
+ ctx.model.extend(this.recTableName, {
100
+ app: "string",
101
+ id: "string",
102
+ createdAt: "timestamp"
103
+ }, {
104
+ primary: ["app", "id"],
105
+ autoInc: false
106
+ });
83
107
  this.registerCommands(ctx, config);
84
108
  }
85
109
  initMap() {
@@ -109,9 +133,25 @@ var SubscriptionService = class extends import_koishi.Service {
109
133
  getApp(app) {
110
134
  return this.appMap.get(app.toLowerCase());
111
135
  }
136
+ async checkExist(app, id) {
137
+ app = this.getApp(app);
138
+ if ((await this.ctx.database.get(this.recTableName, { app, id })).length > 0) {
139
+ return true;
140
+ }
141
+ await this.ctx.database.upsert(this.recTableName, [{
142
+ app,
143
+ id,
144
+ createdAt: /* @__PURE__ */ new Date()
145
+ }]);
146
+ return false;
147
+ }
112
148
  getAccount(app, account) {
113
149
  return this.accountMap.get((app + account).toLowerCase());
114
150
  }
151
+ getName(app, account) {
152
+ app = this.getApp(app);
153
+ return this.subsConfig.apps[app].allowedAccounts.find((item) => item.id === this.getAccount(app, account))?.name;
154
+ }
115
155
  // 获取对应应用可以订阅的账号
116
156
  getAvailableAccounts(app) {
117
157
  app = this.getApp(app);
@@ -121,7 +161,7 @@ var SubscriptionService = class extends import_koishi.Service {
121
161
  // 根据应用和账号获取订阅了这个账号的所有群组
122
162
  async getSubscribedGroups(app, account) {
123
163
  app = this.getApp(app), account = this.getAccount(app, account);
124
- const subscriptions = await this.ctx.database.get(this.tableName, {
164
+ const subscriptions = await this.ctx.database.get(this.subTableName, {
125
165
  app,
126
166
  account
127
167
  });
@@ -167,18 +207,50 @@ var SubscriptionService = class extends import_koishi.Service {
167
207
  return (await Promise.all(this.ctx.bots.map(async (bot) => {
168
208
  const targets = assignMap[bot.platform]?.[bot.selfId];
169
209
  if (!targets) return Promise.resolve([]);
170
- const messageIds = await bot.sendPrivateMessage(bot.selfId, content);
171
- const forwardContent = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { forward: true, children: messageIds.map((id) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { id: `${id}` })) });
172
- const sessions = targets.map(({ id, guildId, locales }) => {
173
- const session = bot.session({
210
+ try {
211
+ const selfChannelId = `private:${bot.selfId}`, session = bot.session({
174
212
  type: "message",
175
- channel: { id, type: import_koishi.Universal.Channel.Type.TEXT },
176
- guild: { id: guildId }
213
+ channel: {
214
+ id: selfChannelId,
215
+ type: import_koishi.Universal.Channel.Type.DIRECT
216
+ },
217
+ user: { id: bot.selfId }
177
218
  });
178
- session.locales = locales;
179
- return session;
180
- });
181
- return bot.broadcast(sessions, forwardContent);
219
+ let messageIds = [];
220
+ if (Array.isArray(content)) {
221
+ for (const msg of content) {
222
+ try {
223
+ const ids = await retry(
224
+ 3,
225
+ () => bot.sendMessage(selfChannelId, msg, void 0, { session })
226
+ );
227
+ if (Array.isArray(ids)) messageIds.push(...ids);
228
+ else if (ids != null) messageIds.push(ids);
229
+ } catch (e) {
230
+ continue;
231
+ }
232
+ }
233
+ } else {
234
+ messageIds = await retry(
235
+ 3,
236
+ () => bot.sendMessage(selfChannelId, content, void 0, { session })
237
+ );
238
+ }
239
+ const forwardContent = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { forward: true, children: messageIds.map((id) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { id: `${id}` })) });
240
+ const sessions = targets.map(({ id, guildId, locales }) => {
241
+ const session2 = bot.session({
242
+ type: "message",
243
+ channel: { id, type: import_koishi.Universal.Channel.Type.TEXT },
244
+ guild: { id: guildId }
245
+ });
246
+ session2.locales = locales;
247
+ return session2;
248
+ });
249
+ return bot.broadcast(sessions, forwardContent);
250
+ } catch (e) {
251
+ this.ctx.logger("broadcastForward").warn("broadcast error:", bot.selfId, Array.isArray(content) ? content.length : 1, e);
252
+ return Promise.resolve([]);
253
+ }
182
254
  }))).flat(1);
183
255
  }
184
256
  // 添加订阅
@@ -191,7 +263,7 @@ var SubscriptionService = class extends import_koishi.Service {
191
263
  if (!appConfig.includes(account)) {
192
264
  return { status: false, msg: "账号不在白名单中" };
193
265
  }
194
- const existing = await this.ctx.database.get(this.tableName, {
266
+ const existing = await this.ctx.database.get(this.subTableName, {
195
267
  app,
196
268
  account,
197
269
  groupId
@@ -199,7 +271,7 @@ var SubscriptionService = class extends import_koishi.Service {
199
271
  if (existing.length > 0) {
200
272
  return { status: false, msg: "已订阅该账号" };
201
273
  }
202
- await this.ctx.database.create(this.tableName, {
274
+ await this.ctx.database.create(this.subTableName, {
203
275
  app,
204
276
  account,
205
277
  groupId,
@@ -209,8 +281,8 @@ var SubscriptionService = class extends import_koishi.Service {
209
281
  }
210
282
  // 删除订阅
211
283
  async removeSubscription(app, account, groupId) {
212
- app = this.appMap.get(app), account = this.accountMap.get(app + account);
213
- const result = await this.ctx.database.remove(this.tableName, {
284
+ app = this.appMap.get(app), account = this.getAccount(app, account);
285
+ const result = await this.ctx.database.remove(this.subTableName, {
214
286
  app,
215
287
  account,
216
288
  groupId
@@ -219,11 +291,11 @@ var SubscriptionService = class extends import_koishi.Service {
219
291
  }
220
292
  // 获取群组的所有订阅
221
293
  async getGroupSubscriptions(groupId) {
222
- return await this.ctx.database.get(this.tableName, { groupId });
294
+ return await this.ctx.database.get(this.subTableName, { groupId });
223
295
  }
224
296
  // 获取所有订阅(用于管理)
225
297
  async getAllSubscriptions() {
226
- return await this.ctx.database.get(this.tableName, {});
298
+ return await this.ctx.database.get(this.subTableName, {});
227
299
  }
228
300
  // 按应用获取订阅统计
229
301
  async getSubscriptionStats() {
@@ -257,19 +329,21 @@ var SubscriptionService = class extends import_koishi.Service {
257
329
  return `${channel.platform}:${channel.id}`;
258
330
  }
259
331
  registerCommands(ctx, config) {
260
- const sub = ctx.command("subscription <app:string> [...accounts]", "添加订阅,格式:订阅 <应用名> <账号>").alias("订阅").alias("添加订阅").userFields(["authority"]).example("推特订阅 爱美").example("订阅 twitter aimi_sound").action(async ({ session, options }, app, ...accounts) => {
332
+ const sub = ctx.command("subscription <app:string> [...accounts]", "添加订阅,账号可以使用 用户名 用户id 或是别名,格式:订阅 <应用名> <账号> [...更多账号]").alias("订阅").alias("添加订阅").userFields(["authority"]).example("推特订阅 爱美 伊藤彩沙").example("订阅 twitter aimi_sound").action(async ({ session, options }, app, ...accounts) => {
261
333
  if (!await this.authCheck(session, config)) {
262
334
  return "权限不足";
263
335
  }
264
336
  if (!app || !accounts || accounts.length == 0) {
265
337
  return "请指定应用名和账号,格式:订阅 <应用名> <账号> [...更多账号]";
266
338
  }
339
+ accounts;
267
340
  const groupId = this.getChannelId(session.channel);
268
341
  if (!groupId) {
269
342
  return "请在群聊中使用当前指令";
270
343
  }
271
344
  let result = "";
272
- for (const account of accounts) {
345
+ for (let account of accounts) {
346
+ app = this.getApp(app), account = this.getName(app, account);
273
347
  const { status, msg } = await this.addSubscription(app, account, groupId);
274
348
  if (status) {
275
349
  result += `成功为群组 ${groupId} 添加订阅:${app} - ${account}
@@ -288,6 +362,7 @@ var SubscriptionService = class extends import_koishi.Service {
288
362
  if (!app || !account) {
289
363
  return "请指定应用名和账号,格式:取消订阅 <应用名> <账号>";
290
364
  }
365
+ app = this.getApp(app), account = this.getName(app, account);
291
366
  const groupId = this.getChannelId(session.channel);
292
367
  if (!groupId) {
293
368
  return "请在群聊中使用当前指令";
@@ -351,7 +426,23 @@ var SubscriptionService = class extends import_koishi.Service {
351
426
  return result;
352
427
  }
353
428
  const html = generateSubscriptionHTML(groupId, availableAccounts, subscribedByApp);
354
- return await ctx.puppeteer.render(html);
429
+ const page = await ctx.puppeteer.page();
430
+ try {
431
+ await page.setViewport({
432
+ width: 1e3,
433
+ height: 800
434
+ });
435
+ await page.setContent(html, {
436
+ waitUntil: "networkidle0"
437
+ });
438
+ const image = await page.screenshot({
439
+ type: "jpeg",
440
+ fullPage: true
441
+ });
442
+ return `<image url="data:image/png;base64,${image.toString("base64")}" />`;
443
+ } finally {
444
+ await page.close();
445
+ }
355
446
  });
356
447
  function generateSubscriptionHTML(groupId, availableAccounts, subscribedByApp) {
357
448
  return `
@@ -372,7 +463,7 @@ var SubscriptionService = class extends import_koishi.Service {
372
463
  padding: 20px;
373
464
  }
374
465
  .container {
375
- max-width: 1200px;
466
+ max-width: 1000px;
376
467
  margin: 0 auto;
377
468
  }
378
469
  .header {
@@ -381,18 +472,18 @@ var SubscriptionService = class extends import_koishi.Service {
381
472
  color: #2c3e50;
382
473
  }
383
474
  .header h1 {
384
- font-size: 24px;
385
- margin-bottom: 8px;
386
- font-weight: 600;
475
+ font-size: 36px;
476
+ margin-bottom: 12px;
477
+ font-weight: 900;
387
478
  }
388
479
  .header .subtitle {
389
- font-size: 14px;
480
+ font-size: 21px;
390
481
  color: #7f8c8d;
391
482
  }
392
483
  .apps-container {
393
484
  background: white;
394
- border-radius: 8px;
395
- box-shadow: 0 2px 12px rgba(0,0,0,0.08);
485
+ border-radius: 12px;
486
+ box-shadow: 0 3px 18px rgba(0,0,0,0.08);
396
487
  overflow: hidden;
397
488
  }
398
489
  .app-section {
@@ -402,46 +493,48 @@ var SubscriptionService = class extends import_koishi.Service {
402
493
  border-bottom: none;
403
494
  }
404
495
  .app-title {
405
- font-size: 16px;
496
+ font-size: 24px;
406
497
  font-weight: 600;
407
498
  color: #34495e;
408
- padding: 12px 16px;
499
+ padding: 18px 24px;
409
500
  background: #f8f9fa;
410
501
  border-bottom: 1px solid #eaeaea;
411
502
  display: flex;
412
503
  align-items: center;
413
- gap: 8px;
504
+ gap: 12px;
414
505
  }
415
506
  .app-title::before {
416
507
  content: "▸";
417
- font-size: 14px;
508
+ font-size: 21px;
418
509
  color: #3498db;
419
510
  }
420
511
  .grid {
421
512
  display: grid;
422
- grid-template-columns: repeat(3, 1fr);
513
+ grid-template-columns: repeat(2, 1fr);
423
514
  gap: 0;
424
515
  }
425
516
  .account-card {
426
- padding: 14px;
517
+ padding: 21px;
427
518
  border-right: 1px solid #f0f0f0;
428
519
  border-bottom: 1px solid #f0f0f0;
429
520
  transition: all 0.2s ease;
430
521
  position: relative;
431
- min-height: 80px;
522
+ min-height: 120px;
432
523
  display: flex;
433
524
  flex-direction: column;
434
525
  }
435
- .account-card:nth-child(odd) {
526
+ .account-card:nth-child(4n+1),
527
+ .account-card:nth-child(4n+4) {
436
528
  background: #f8f9fa;
437
529
  }
438
- .account-card:nth-child(even) {
530
+ .account-card:nth-child(4n+2),
531
+ .account-card:nth-child(4n+3) {
439
532
  background: #f0f2f5;
440
533
  }
441
534
  .account-card:hover {
442
535
  background: #e6f3ff !important;
443
536
  }
444
- .account-card:nth-child(3n) {
537
+ .account-card:nth-child(2n) {
445
538
  border-right: none;
446
539
  }
447
540
  .account-card:last-child {
@@ -451,12 +544,12 @@ var SubscriptionService = class extends import_koishi.Service {
451
544
  display: flex;
452
545
  justify-content: space-between;
453
546
  align-items: center;
454
- margin-bottom: 8px;
547
+ margin-bottom: 12px;
455
548
  flex-wrap: wrap;
456
549
  }
457
550
  .name {
458
- font-size: 14px;
459
- font-weight: 600;
551
+ font-size: 21px;
552
+ font-weight: 900;
460
553
  color: #2c3e50;
461
554
  line-height: 1.3;
462
555
  max-width: 60%;
@@ -465,12 +558,12 @@ var SubscriptionService = class extends import_koishi.Service {
465
558
  white-space: nowrap;
466
559
  }
467
560
  .id {
468
- font-size: 12px;
561
+ font-size: 21px;
469
562
  color: #5a6c7d;
470
563
  font-family: 'Courier New', monospace;
471
564
  background: #f8f9fa;
472
- padding: 3px 6px;
473
- border-radius: 3px;
565
+ padding: 4.5px 9px;
566
+ border-radius: 4.5px;
474
567
  line-height: 1.3;
475
568
  max-width: 35%;
476
569
  overflow: hidden;
@@ -483,7 +576,7 @@ var SubscriptionService = class extends import_koishi.Service {
483
576
  }
484
577
  .name-id-container.vertical .name {
485
578
  max-width: 100%;
486
- margin-bottom: 4px;
579
+ margin-bottom: 6px;
487
580
  }
488
581
  .name-id-container.vertical .id {
489
582
  max-width: 100%;
@@ -492,49 +585,49 @@ var SubscriptionService = class extends import_koishi.Service {
492
585
  .alias-status-container {
493
586
  display: flex;
494
587
  justify-content: space-between;
495
- margin-top: 8px;
588
+ margin-top: 12px;
496
589
  flex-grow: 1;
497
590
  }
498
591
  .alias-container {
499
592
  flex: 1;
500
- max-width: calc(100% - 70px);
593
+ max-width: calc(100% - 105px);
501
594
  align-self: flex-start;
502
595
  }
503
596
  .alias {
504
- font-size: 11px;
597
+ font-size: 20px;
505
598
  color: #6c757d;
506
599
  display: flex;
507
600
  flex-wrap: wrap;
508
- gap: 4px;
601
+ gap: 6px;
509
602
  align-items: flex-start;
510
603
  }
511
604
  .alias-tag {
512
605
  background: #e8f4fd;
513
606
  color: #1971c2;
514
- padding: 2px 6px;
515
- border-radius: 10px;
607
+ padding: 3px 9px;
608
+ border-radius: 15px;
516
609
  border: 1px solid #a5d8ff;
517
610
  white-space: nowrap;
518
- margin-bottom: 2px;
611
+ margin-bottom: 3px;
519
612
  }
520
613
  .no-alias {
521
- font-size: 11px;
614
+ font-size: 20px;
522
615
  color: #adb5bd;
523
616
  font-style: italic;
524
617
  }
525
618
  .status-container {
526
619
  display: flex;
527
620
  align-items: flex-end;
528
- margin-left: 8px;
621
+ margin-left: 12px;
529
622
  }
530
623
  .status {
531
624
  display: inline-flex;
532
625
  align-items: center;
533
- gap: 6px;
534
- padding: 3px 10px;
535
- border-radius: 16px;
536
- font-size: 11px;
537
- font-weight: 600;
626
+ gap: 9px;
627
+ padding: 4.5px 15px;
628
+ border-radius: 24px;
629
+ font-size: 20px;
630
+ font-weight: 900;
538
631
  white-space: nowrap;
539
632
  flex-shrink: 0;
540
633
  }
@@ -552,7 +645,7 @@ var SubscriptionService = class extends import_koishi.Service {
552
645
  text-align: center;
553
646
  color: #6c757d;
554
647
  font-style: italic;
555
- padding: 30px;
648
+ padding: 45px;
556
649
  background: #f8f9fa;
557
650
  grid-column: 1 / -1;
558
651
  }
@@ -624,8 +717,20 @@ var SubscriptionService = class extends import_koishi.Service {
624
717
  if (subscriptions.length === 0) {
625
718
  return `群组 ${groupId} 暂无订阅`;
626
719
  }
627
- await ctx.database.remove("subscription_service", { groupId });
628
- return `已删除群组 ${groupId} 的所有订阅(共 ${subscriptions.length} 个)`;
720
+ const confirm = await session.send(
721
+ `确认要清空当前群聊的所有订阅吗?
722
+ 请输入 "是" 或 "确认" 或 "yes" 来确认删除,输入其他内容取消。`
723
+ );
724
+ const response = await session.prompt(3e4);
725
+ if (!response) {
726
+ return "操作已取消(超时)。";
727
+ }
728
+ if (["是", "确认", "yes", "y"].includes(response.toLowerCase())) {
729
+ await ctx.database.remove("subscription_service", { groupId });
730
+ return `已删除群组 ${groupId} 的所有订阅(共 ${subscriptions.length} 个)`;
731
+ } else {
732
+ return "操作已取消。";
733
+ }
629
734
  });
630
735
  ctx.command("subscription.stats", "查看订阅统计", {
631
736
  permissions: [`authority:${config.minAuthority}`],
package/lib/retry.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function retry<T>(retries: number, fn: () => Promise<T> | T, delay?: number): Promise<T>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-subscription",
3
3
  "description": "给多个应用提供订阅群组管理",
4
- "version": "0.0.5",
4
+ "version": "0.0.7",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [