koishi-plugin-subscription 0.0.6 → 0.0.8

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.
@@ -0,0 +1,16 @@
1
+ import { Dict, Schema } from 'koishi';
2
+ export interface AppConfig {
3
+ alias: string[];
4
+ themeColor?: string;
5
+ allowedAccounts: {
6
+ id: string;
7
+ name: string;
8
+ alias: string;
9
+ }[];
10
+ }
11
+ export interface Config {
12
+ apps: Dict<AppConfig>;
13
+ minAuthority: number;
14
+ mode: 'noAdmin' | 'and' | 'or';
15
+ }
16
+ export declare const Config: Schema<Config>;
package/lib/index.d.ts CHANGED
@@ -1,69 +1,11 @@
1
- import { Context, Dict, Schema, Service, Fragment } from 'koishi';
2
- interface SubscriptionItem {
3
- id: number;
4
- app: string;
5
- account: string;
6
- groupId: string;
7
- createdAt: Date;
8
- }
9
- interface AppConfig {
10
- alias: string[];
11
- allowedAccounts: {
12
- id: string;
13
- name: string;
14
- alias: string;
15
- }[];
16
- }
17
- export interface Config {
18
- apps: Dict<AppConfig>;
19
- minAuthority: number;
20
- mode: "noAdmin" | "and" | "or";
21
- }
22
- export declare const Config: Schema<Config>;
1
+ import { Context } from 'koishi';
2
+ import { Config } from './config';
3
+ import type { Config as SubscriptionConfig } from './config';
4
+ export { Config };
5
+ export { SubscriptionService } from './service';
23
6
  export declare const name = "subscription";
24
7
  export declare const inject: {
25
8
  required: string[];
26
9
  optional: string[];
27
10
  };
28
- declare module 'koishi' {
29
- interface Context {
30
- subscription: SubscriptionService;
31
- }
32
- interface Tables {
33
- subscription_service: SubscriptionItem;
34
- }
35
- }
36
- declare class SubscriptionService extends Service {
37
- readonly tableName = "subscription_service";
38
- subsConfig: Config;
39
- appMap: Map<string, string>;
40
- accountMap: Map<string, string>;
41
- constructor(ctx: Context, config: Config);
42
- initMap(): void;
43
- private getApp;
44
- getAccount(app: string, account: string): string;
45
- getName(app: string, account: string): string;
46
- getAvailableAccounts(app: string): string[];
47
- getSubscribedGroups(app: string, account: string): Promise<string[]>;
48
- broadcast(app: string, account: string, content: Fragment): Promise<void>;
49
- private getSelfIds;
50
- private getAssignedChannels;
51
- broadcastForward(app: string, account: string, content: Fragment): Promise<any[]>;
52
- addSubscription(app: string, account: string, groupId: string): Promise<{
53
- status: boolean;
54
- msg: string;
55
- }>;
56
- removeSubscription(app: string, account: string, groupId: string): Promise<boolean>;
57
- getGroupSubscriptions(groupId: string): Promise<SubscriptionItem[]>;
58
- getAllSubscriptions(): Promise<SubscriptionItem[]>;
59
- getSubscriptionStats(): Promise<{
60
- app: string;
61
- account: string;
62
- count: number;
63
- }[]>;
64
- private authCheck;
65
- private getChannelId;
66
- private registerCommands;
67
- }
68
- export declare function apply(ctx: Context, config: Config): void;
69
- export {};
11
+ export declare function apply(ctx: Context, config: SubscriptionConfig): void;
package/lib/index.js CHANGED
@@ -21,33 +21,21 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
23
  Config: () => Config,
24
+ SubscriptionService: () => SubscriptionService,
24
25
  apply: () => apply,
25
26
  inject: () => inject,
26
27
  name: () => name
27
28
  });
28
29
  module.exports = __toCommonJS(src_exports);
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
30
 
44
- // src/index.tsx
45
- var import_jsx_runtime = require("@satorijs/element/jsx-runtime");
31
+ // src/config.ts
32
+ var import_koishi = require("koishi");
46
33
  var Config = import_koishi.Schema.intersect([
47
34
  import_koishi.Schema.object({
48
35
  apps: import_koishi.Schema.dict(
49
36
  import_koishi.Schema.object({
50
37
  alias: import_koishi.Schema.array(import_koishi.Schema.string()).role("table").description("应用别名,如果重复,后面会覆盖前面的"),
38
+ themeColor: import_koishi.Schema.string().role("color").description("应用主题色,用于订阅列表展示,建议使用十六进制颜色,如 #1d9bf0"),
51
39
  allowedAccounts: import_koishi.Schema.array(
52
40
  import_koishi.Schema.object({
53
41
  id: import_koishi.Schema.string().required().description("账号id"),
@@ -67,17 +55,388 @@ var Config = import_koishi.Schema.intersect([
67
55
  ]).default("or").role("radio").description("权限模式")
68
56
  }).description("权限配置")
69
57
  ]);
70
- var name = "subscription";
71
- var inject = {
72
- required: ["database"],
73
- optional: ["puppeteer"]
74
- };
75
- var SubscriptionService = class extends import_koishi.Service {
58
+
59
+ // src/service.tsx
60
+ var import_koishi2 = require("koishi");
61
+
62
+ // src/render.ts
63
+ function generateSubscriptionHTML(groupId, availableAccounts, subscribedByApp, appThemeColors = {}, options = {}) {
64
+ const apps = createAppViews(availableAccounts, subscribedByApp, appThemeColors);
65
+ const showTitle = options.showTitle ?? true;
66
+ return `
67
+ <!DOCTYPE html>
68
+ <html lang="zh-CN">
69
+ <head>
70
+ <meta charset="UTF-8">
71
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
72
+ <title>订阅状态总览</title>
73
+ <style>
74
+ :root {
75
+ --bg: #f5f7fb;
76
+ --panel: #ffffff;
77
+ --text: #1f2328;
78
+ --muted: #69727f;
79
+ --soft: #f8fafc;
80
+ --line: #d9e0e8;
81
+ --line-soft: #e8edf3;
82
+ --ok: #157347;
83
+ --ok-bg: #e6f4ec;
84
+ --ok-line: #a8d8bd;
85
+ --off: #8a95a3;
86
+ --off-bg: #f2f4f7;
87
+ --accent: #315f8a;
88
+ --app-color: var(--accent);
89
+ --app-color-soft: rgba(49, 95, 138, 0.22);
90
+ --app-color-faint: rgba(49, 95, 138, 0.11);
91
+ --app-color-wash: rgba(49, 95, 138, 0.06);
92
+ --alias-bg: #fff5dc;
93
+ --alias-text: #745416;
94
+ --id-bg: #eef2f7;
95
+ --shadow: rgba(18, 27, 38, 0.05);
96
+ }
97
+ * {
98
+ box-sizing: border-box;
99
+ }
100
+ html,
101
+ body {
102
+ margin: 0;
103
+ min-height: 100%;
104
+ background: var(--bg);
105
+ color: var(--text);
106
+ font-family: Inter, "Segoe UI", "Microsoft YaHei", "Noto Sans CJK SC", sans-serif;
107
+ }
108
+ body {
109
+ padding: 24px;
110
+ }
111
+ .page {
112
+ width: 1392px;
113
+ margin: 0 auto;
114
+ }
115
+ .title {
116
+ margin: 0 0 18px;
117
+ color: #17202b;
118
+ font-size: 32px;
119
+ line-height: 1.2;
120
+ font-weight: 820;
121
+ letter-spacing: 0;
122
+ }
123
+ .app {
124
+ margin-bottom: 22px;
125
+ overflow: hidden;
126
+ }
127
+ .app-head {
128
+ display: flex;
129
+ align-items: center;
130
+ min-height: 34px;
131
+ padding: 0 4px;
132
+ }
133
+ .app-name {
134
+ display: inline-flex;
135
+ align-items: center;
136
+ gap: 10px;
137
+ color: var(--app-color);
138
+ font-size: 26px;
139
+ line-height: 1.2;
140
+ font-weight: 820;
141
+ }
142
+ .app-name::before {
143
+ content: "";
144
+ width: 10px;
145
+ height: 10px;
146
+ border-radius: 999px;
147
+ background: var(--app-color);
148
+ box-shadow: 0 0 0 5px var(--app-color-faint);
149
+ }
150
+ .account-grid {
151
+ display: grid;
152
+ grid-template-columns: repeat(4, minmax(0, 1fr));
153
+ gap: 9px;
154
+ padding: 10px 0 0;
155
+ }
156
+ .account {
157
+ position: relative;
158
+ min-width: 0;
159
+ min-height: 78px;
160
+ padding: 10px 11px 9px 22px;
161
+ border-radius: 13px;
162
+ background: #ffffff;
163
+ display: grid;
164
+ grid-template-rows: auto auto;
165
+ gap: 6px;
166
+ box-shadow: 0 1px 1px var(--shadow);
167
+ }
168
+ .account::before {
169
+ content: "";
170
+ position: absolute;
171
+ inset: 10px auto 10px 8px;
172
+ width: 5px;
173
+ border-radius: 999px;
174
+ background: var(--off);
175
+ }
176
+ .account.subscribed::before {
177
+ background: var(--app-color);
178
+ }
179
+ .account.subscribed {
180
+ background: var(--app-color-wash);
181
+ }
182
+ .account.unsubscribed {
183
+ background: #ffffff;
184
+ color: #687280;
185
+ box-shadow: none;
186
+ }
187
+ .main-line {
188
+ min-width: 0;
189
+ display: grid;
190
+ grid-template-columns: minmax(0, 1fr) auto;
191
+ gap: 10px;
192
+ align-items: start;
193
+ }
194
+ .sub-line {
195
+ min-width: 0;
196
+ display: flex;
197
+ flex-wrap: wrap;
198
+ gap: 5px;
199
+ align-items: center;
200
+ }
201
+ .status {
202
+ display: inline-flex;
203
+ align-items: center;
204
+ justify-content: center;
205
+ width: 22px;
206
+ height: 22px;
207
+ }
208
+ .dot {
209
+ width: 8px;
210
+ height: 8px;
211
+ border-radius: 50%;
212
+ background: var(--off);
213
+ }
214
+ .subscribed .dot {
215
+ background: var(--app-color);
216
+ box-shadow: 0 0 0 3px var(--app-color-soft);
217
+ }
218
+ .subscribed .status {
219
+ color: var(--ok);
220
+ }
221
+ .unsubscribed .status {
222
+ color: var(--off);
223
+ }
224
+ .name {
225
+ min-width: 0;
226
+ overflow-wrap: anywhere;
227
+ display: -webkit-box;
228
+ overflow: hidden;
229
+ -webkit-line-clamp: 2;
230
+ -webkit-box-orient: vertical;
231
+ color: var(--text);
232
+ font-size: 18px;
233
+ line-height: 1.2;
234
+ font-weight: 780;
235
+ }
236
+ .unsubscribed .name {
237
+ color: #4f5b68;
238
+ font-weight: 700;
239
+ }
240
+ .id {
241
+ display: inline;
242
+ overflow-wrap: anywhere;
243
+ padding: 0;
244
+ border-radius: 0;
245
+ background: transparent;
246
+ color: #3f4b59;
247
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
248
+ font-size: 14px;
249
+ line-height: 1.3;
250
+ }
251
+ .id::before {
252
+ content: "@";
253
+ color: var(--app-color);
254
+ font-family: Inter, "Segoe UI", "Microsoft YaHei", "Noto Sans CJK SC", sans-serif;
255
+ font-weight: 760;
256
+ }
257
+ .aliases {
258
+ display: flex;
259
+ flex-wrap: wrap;
260
+ gap: 5px;
261
+ }
262
+ .alias {
263
+ max-width: 100%;
264
+ overflow-wrap: anywhere;
265
+ padding: 0;
266
+ border-radius: 0;
267
+ background: transparent;
268
+ color: var(--app-color);
269
+ font-size: 13px;
270
+ line-height: 1.25;
271
+ font-weight: 720;
272
+ }
273
+ .alias::before {
274
+ content: "#";
275
+ opacity: 1;
276
+ }
277
+ .alias.empty {
278
+ background: transparent;
279
+ color: #9aa3af;
280
+ padding-left: 0;
281
+ font-weight: 520;
282
+ }
283
+ .alias.empty::before {
284
+ content: "";
285
+ }
286
+ .empty-row {
287
+ padding: 18px 4px;
288
+ color: var(--muted);
289
+ font-size: 18px;
290
+ }
291
+ </style>
292
+ </head>
293
+ <body>
294
+ <main class="page">
295
+ ${showTitle ? '<h1 class="title">当前群订阅</h1>' : ""}
296
+ ${apps.map(renderApp).join("")}
297
+ </main>
298
+ </body>
299
+ </html>
300
+ `;
301
+ }
302
+ __name(generateSubscriptionHTML, "generateSubscriptionHTML");
303
+ function createAppViews(availableAccounts, subscribedByApp, appThemeColors) {
304
+ return Object.entries(availableAccounts).map(([app, accounts]) => {
305
+ const subscribed = subscribedByApp[app] || /* @__PURE__ */ new Set();
306
+ return {
307
+ app,
308
+ theme: createTheme(appThemeColors[app], app),
309
+ accounts: accounts.map((account) => ({
310
+ ...account,
311
+ aliases: normalizeAliases(account.alias),
312
+ subscribed: subscribed.has(account.id)
313
+ })).sort((a, b) => Number(b.subscribed) - Number(a.subscribed))
314
+ };
315
+ });
316
+ }
317
+ __name(createAppViews, "createAppViews");
318
+ function normalizeAliases(aliases) {
319
+ return aliases.flatMap((alias) => alias.split(",")).map((alias) => alias.trim()).filter(Boolean);
320
+ }
321
+ __name(normalizeAliases, "normalizeAliases");
322
+ function renderApp(section) {
323
+ return `
324
+ <section class="app" style="${renderThemeStyle(section.theme)}">
325
+ <div class="app-head">
326
+ <div class="app-name">${escapeHtml(section.app)}</div>
327
+ </div>
328
+ ${section.accounts.length ? `<div class="account-grid">
329
+ ${section.accounts.map(renderAccount).join("")}
330
+ </div>` : '<div class="empty-row">暂无可用账号</div>'}
331
+ </section>
332
+ `;
333
+ }
334
+ __name(renderApp, "renderApp");
335
+ function createTheme(input, app) {
336
+ const color = normalizeColor(input, getDefaultThemeColor(app));
337
+ const rgb = hexToRgb(color);
338
+ return {
339
+ color,
340
+ soft: `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.22)`,
341
+ faint: `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.11)`,
342
+ wash: `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.06)`
343
+ };
344
+ }
345
+ __name(createTheme, "createTheme");
346
+ function getDefaultThemeColor(app) {
347
+ switch (app?.toLowerCase()) {
348
+ case "twitter":
349
+ return "#1d9bf0";
350
+ case "bilibili":
351
+ return "#fb7299";
352
+ case "instagram":
353
+ return "#c13584";
354
+ default:
355
+ return "#315f8a";
356
+ }
357
+ }
358
+ __name(getDefaultThemeColor, "getDefaultThemeColor");
359
+ function normalizeColor(input, fallback = "#315f8a") {
360
+ const value = input?.trim();
361
+ if (!value) return fallback;
362
+ if (/^#[0-9a-f]{3}$/i.test(value)) {
363
+ const [, r, g, b] = value;
364
+ return `#${r}${r}${g}${g}${b}${b}`.toLowerCase();
365
+ }
366
+ if (/^#[0-9a-f]{6}$/i.test(value)) {
367
+ return value.toLowerCase();
368
+ }
369
+ return fallback;
370
+ }
371
+ __name(normalizeColor, "normalizeColor");
372
+ function hexToRgb(hex) {
373
+ return {
374
+ r: Number.parseInt(hex.slice(1, 3), 16),
375
+ g: Number.parseInt(hex.slice(3, 5), 16),
376
+ b: Number.parseInt(hex.slice(5, 7), 16)
377
+ };
378
+ }
379
+ __name(hexToRgb, "hexToRgb");
380
+ function renderThemeStyle(theme) {
381
+ return [
382
+ `--app-color: ${theme.color}`,
383
+ `--app-color-soft: ${theme.soft}`,
384
+ `--app-color-faint: ${theme.faint}`,
385
+ `--app-color-wash: ${theme.wash}`
386
+ ].join("; ");
387
+ }
388
+ __name(renderThemeStyle, "renderThemeStyle");
389
+ function renderAccount(account) {
390
+ return `
391
+ <article class="account ${account.subscribed ? "subscribed" : "unsubscribed"}">
392
+ <div class="main-line">
393
+ <div class="name">${escapeHtml(account.name)}</div>
394
+ <span class="status"><span class="dot"></span></span>
395
+ </div>
396
+ <div class="sub-line">
397
+ <span class="id">${escapeHtml(account.id)}</span>
398
+ <span class="aliases">${renderAliases(account.aliases)}</span>
399
+ </div>
400
+ </article>
401
+ `;
402
+ }
403
+ __name(renderAccount, "renderAccount");
404
+ function renderAliases(aliases) {
405
+ return aliases.length ? aliases.map((alias) => `<span class="alias">${escapeHtml(alias)}</span>`).join("") : '<span class="alias empty">无别名</span>';
406
+ }
407
+ __name(renderAliases, "renderAliases");
408
+ function escapeHtml(value) {
409
+ return String(value ?? "").replace(/[&<>"']/g, (char) => ({
410
+ "&": "&amp;",
411
+ "<": "&lt;",
412
+ ">": "&gt;",
413
+ '"': "&quot;",
414
+ "'": "&#39;"
415
+ })[char]);
416
+ }
417
+ __name(escapeHtml, "escapeHtml");
418
+
419
+ // src/retry.ts
420
+ async function retry(retries, fn, delay = 500) {
421
+ try {
422
+ return await fn();
423
+ } catch (err) {
424
+ console.log(`剩余${retries}次尝试`, err);
425
+ if (retries <= 1) throw err;
426
+ await new Promise((r) => setTimeout(r, delay));
427
+ return retry(retries - 1, fn, delay * 2);
428
+ }
429
+ }
430
+ __name(retry, "retry");
431
+
432
+ // src/service.tsx
433
+ var import_jsx_runtime = require("@satorijs/element/jsx-runtime");
434
+ var SubscriptionService = class extends import_koishi2.Service {
76
435
  static {
77
436
  __name(this, "SubscriptionService");
78
437
  }
79
- // 数据表名称
80
- tableName = "subscription_service";
438
+ subTableName = "subscription_service";
439
+ recTableName = "subscription_records";
81
440
  subsConfig;
82
441
  appMap;
83
442
  accountMap;
@@ -85,7 +444,7 @@ var SubscriptionService = class extends import_koishi.Service {
85
444
  super(ctx, "subscription");
86
445
  this.subsConfig = config;
87
446
  this.initMap();
88
- ctx.model.extend(this.tableName, {
447
+ ctx.model.extend(this.subTableName, {
89
448
  id: "unsigned",
90
449
  app: "string",
91
450
  account: "string",
@@ -95,6 +454,14 @@ var SubscriptionService = class extends import_koishi.Service {
95
454
  primary: "id",
96
455
  autoInc: true
97
456
  });
457
+ ctx.model.extend(this.recTableName, {
458
+ app: "string",
459
+ id: "string",
460
+ createdAt: "timestamp"
461
+ }, {
462
+ primary: ["app", "id"],
463
+ autoInc: false
464
+ });
98
465
  this.registerCommands(ctx, config);
99
466
  }
100
467
  initMap() {
@@ -122,25 +489,38 @@ var SubscriptionService = class extends import_koishi.Service {
122
489
  }
123
490
  }
124
491
  getApp(app) {
492
+ if (!app) return;
125
493
  return this.appMap.get(app.toLowerCase());
126
494
  }
495
+ async checkExist(app, id) {
496
+ app = this.getApp(app);
497
+ if ((await this.ctx.database.get(this.recTableName, { app, id })).length > 0) {
498
+ return true;
499
+ }
500
+ await this.ctx.database.upsert(this.recTableName, [{
501
+ app,
502
+ id,
503
+ createdAt: /* @__PURE__ */ new Date()
504
+ }]);
505
+ return false;
506
+ }
127
507
  getAccount(app, account) {
508
+ if (!app || !account) return;
128
509
  return this.accountMap.get((app + account).toLowerCase());
129
510
  }
130
511
  getName(app, account) {
131
512
  app = this.getApp(app);
513
+ if (!app) return;
132
514
  return this.subsConfig.apps[app].allowedAccounts.find((item) => item.id === this.getAccount(app, account))?.name;
133
515
  }
134
- // 获取对应应用可以订阅的账号
135
516
  getAvailableAccounts(app) {
136
517
  app = this.getApp(app);
137
518
  const appConfig = this.subsConfig.apps[app];
138
519
  return appConfig ? appConfig.allowedAccounts.map((item) => item.id) : [];
139
520
  }
140
- // 根据应用和账号获取订阅了这个账号的所有群组
141
521
  async getSubscribedGroups(app, account) {
142
522
  app = this.getApp(app), account = this.getAccount(app, account);
143
- const subscriptions = await this.ctx.database.get(this.tableName, {
523
+ const subscriptions = await this.ctx.database.get(this.subTableName, {
144
524
  app,
145
525
  account
146
526
  });
@@ -178,6 +558,7 @@ var SubscriptionService = class extends import_koishi.Service {
178
558
  if (index < 0) continue;
179
559
  channels.splice(index, 1);
180
560
  }
561
+ ;
181
562
  ((assignMap[platform] ||= {})[assignee] ||= []).push(channel);
182
563
  }
183
564
  if (channels?.length) {
@@ -191,24 +572,35 @@ var SubscriptionService = class extends import_koishi.Service {
191
572
  type: "message",
192
573
  channel: {
193
574
  id: selfChannelId,
194
- type: import_koishi.Universal.Channel.Type.DIRECT
575
+ type: import_koishi2.Universal.Channel.Type.DIRECT
195
576
  },
196
577
  user: { id: bot.selfId }
197
578
  });
198
- const messageIds = Array.isArray(content) ? (await Promise.all(content.map(
199
- (msg) => {
579
+ let messageIds = [];
580
+ if (Array.isArray(content)) {
581
+ for (const msg of content) {
200
582
  try {
201
- return retry(3, () => bot.sendMessage(selfChannelId, msg, void 0, { session }));
583
+ const ids = await retry(
584
+ 3,
585
+ () => bot.sendMessage(selfChannelId, msg, void 0, { session })
586
+ );
587
+ if (Array.isArray(ids)) messageIds.push(...ids);
588
+ else if (ids != null) messageIds.push(ids);
202
589
  } catch (e) {
203
- return Promise.resolve([]);
590
+ continue;
204
591
  }
205
592
  }
206
- ))).flat(1) : await retry(3, () => bot.sendMessage(selfChannelId, content, void 0, { session }));
593
+ } else {
594
+ messageIds = await retry(
595
+ 3,
596
+ () => bot.sendMessage(selfChannelId, content, void 0, { session })
597
+ );
598
+ }
207
599
  const forwardContent = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { forward: true, children: messageIds.map((id) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("message", { id: `${id}` })) });
208
600
  const sessions = targets.map(({ id, guildId, locales }) => {
209
601
  const session2 = bot.session({
210
602
  type: "message",
211
- channel: { id, type: import_koishi.Universal.Channel.Type.TEXT },
603
+ channel: { id, type: import_koishi2.Universal.Channel.Type.TEXT },
212
604
  guild: { id: guildId }
213
605
  });
214
606
  session2.locales = locales;
@@ -221,17 +613,17 @@ var SubscriptionService = class extends import_koishi.Service {
221
613
  }
222
614
  }))).flat(1);
223
615
  }
224
- // 添加订阅
225
616
  async addSubscription(app, account, groupId) {
226
- app = this.getApp(app), account = this.getAccount(app, account);
227
- const appConfig = this.getAvailableAccounts(app);
228
- if (!appConfig) {
617
+ app = this.getApp(app);
618
+ if (!app) {
229
619
  return { status: false, msg: "应用不存在" };
230
620
  }
621
+ account = this.getAccount(app, account);
622
+ const appConfig = this.getAvailableAccounts(app);
231
623
  if (!appConfig.includes(account)) {
232
624
  return { status: false, msg: "账号不在白名单中" };
233
625
  }
234
- const existing = await this.ctx.database.get(this.tableName, {
626
+ const existing = await this.ctx.database.get(this.subTableName, {
235
627
  app,
236
628
  account,
237
629
  groupId
@@ -239,7 +631,7 @@ var SubscriptionService = class extends import_koishi.Service {
239
631
  if (existing.length > 0) {
240
632
  return { status: false, msg: "已订阅该账号" };
241
633
  }
242
- await this.ctx.database.create(this.tableName, {
634
+ await this.ctx.database.create(this.subTableName, {
243
635
  app,
244
636
  account,
245
637
  groupId,
@@ -247,25 +639,24 @@ var SubscriptionService = class extends import_koishi.Service {
247
639
  });
248
640
  return { status: true, msg: "订阅成功" };
249
641
  }
250
- // 删除订阅
251
642
  async removeSubscription(app, account, groupId) {
252
- app = this.appMap.get(app), account = this.accountMap.get(app + account);
253
- const result = await this.ctx.database.remove(this.tableName, {
643
+ app = this.getApp(app);
644
+ if (!app) return false;
645
+ account = this.getAccount(app, account);
646
+ if (!account) return false;
647
+ const result = await this.ctx.database.remove(this.subTableName, {
254
648
  app,
255
649
  account,
256
650
  groupId
257
651
  });
258
652
  return result.removed > 0;
259
653
  }
260
- // 获取群组的所有订阅
261
654
  async getGroupSubscriptions(groupId) {
262
- return await this.ctx.database.get(this.tableName, { groupId });
655
+ return await this.ctx.database.get(this.subTableName, { groupId });
263
656
  }
264
- // 获取所有订阅(用于管理)
265
657
  async getAllSubscriptions() {
266
- return await this.ctx.database.get(this.tableName, {});
658
+ return await this.ctx.database.get(this.subTableName, {});
267
659
  }
268
- // 按应用获取订阅统计
269
660
  async getSubscriptionStats() {
270
661
  const subscriptions = await this.getAllSubscriptions();
271
662
  const stats = {};
@@ -296,50 +687,112 @@ var SubscriptionService = class extends import_koishi.Service {
296
687
  if (!channel) return;
297
688
  return `${channel.platform}:${channel.id}`;
298
689
  }
690
+ async getSubscriptionListView(app, groupId) {
691
+ const subscriptions = await this.getGroupSubscriptions(groupId);
692
+ const subscribedByApp = {};
693
+ subscriptions.forEach((sub) => {
694
+ if (!subscribedByApp[sub.app]) {
695
+ subscribedByApp[sub.app] = /* @__PURE__ */ new Set();
696
+ }
697
+ subscribedByApp[sub.app].add(sub.account);
698
+ });
699
+ const availableAccounts = {};
700
+ const appThemeColors = {};
701
+ const appConfig = this.subsConfig.apps;
702
+ const targetApp = app ? this.getApp(app) : void 0;
703
+ if (app && !targetApp) {
704
+ return `应用不存在:${app}
705
+ 请使用配置中的应用名或应用别名,例如:订阅列表 twitter`;
706
+ }
707
+ for (const appName in appConfig) {
708
+ if (targetApp && appName !== targetApp) continue;
709
+ appThemeColors[appName] = appConfig[appName].themeColor;
710
+ const subscribed = subscribedByApp[appName] || /* @__PURE__ */ new Set();
711
+ availableAccounts[appName] = appConfig[appName].allowedAccounts.filter((account) => targetApp || subscribed.has(account.id)).map((account) => ({
712
+ id: account.id,
713
+ name: account.name,
714
+ alias: account.alias ? account.alias.split(",") : []
715
+ }));
716
+ }
717
+ return {
718
+ targetApp,
719
+ subscribedByApp,
720
+ availableAccounts,
721
+ appThemeColors
722
+ };
723
+ }
724
+ renderSubscriptionListText(view) {
725
+ let result = ``;
726
+ for (const app in view.availableAccounts) {
727
+ result += `【${app}】
728
+ `;
729
+ const subscribed = view.subscribedByApp[app] || /* @__PURE__ */ new Set();
730
+ view.availableAccounts[app].forEach((account) => {
731
+ const isSubscribed = subscribed.has(account.id);
732
+ const status = isSubscribed ? "✅ 已订阅" : "❌ 未订阅";
733
+ const aliases = account.alias.length > 0 ? ` (${account.alias.join(", ")})` : "";
734
+ result += `${status} ${account.name} id:${account.id}
735
+ ${aliases}
736
+ `;
737
+ });
738
+ result += "\n";
739
+ }
740
+ return result;
741
+ }
299
742
  registerCommands(ctx, config) {
300
- const sub = ctx.command("subscription <app:string> [...accounts]", "添加订阅,账号可以使用 用户名 用户id 或是别名,格式:订阅 <应用名> <账号> [...更多账号]").alias("订阅").alias("添加订阅").userFields(["authority"]).example("推特订阅 爱美 伊藤彩沙").example("订阅 twitter aimi_sound").action(async ({ session, options }, app, ...accounts) => {
743
+ const screenshotWidth = 1440;
744
+ const screenshotDeviceScaleFactor = 2;
745
+ const screenshotQuality = 82;
746
+ const sub = ctx.command("subscription <app:string> [...accounts]", "为当前群添加账号订阅。账号可使用 ID、名称或别名,可一次添加多个。").alias("订阅").alias("添加订阅").userFields(["authority"]).example("订阅 twitter aimi_sound ayasa_ito").example("推特订阅 爱美 伊藤彩沙").action(async ({ session, options }, app, ...accounts) => {
301
747
  if (!await this.authCheck(session, config)) {
302
748
  return "权限不足";
303
749
  }
304
750
  if (!app || !accounts || accounts.length == 0) {
305
- return "请指定应用名和账号,格式:订阅 <应用名> <账号> [...更多账号]";
751
+ return "请指定应用名和账号。\n用法:订阅 <应用名> <账号> [...更多账号]\n示例:订阅 twitter aimi_sound ayasa_ito";
752
+ }
753
+ const appName = this.getApp(app);
754
+ if (!appName) {
755
+ return `应用不存在:${app}`;
306
756
  }
307
- accounts;
308
757
  const groupId = this.getChannelId(session.channel);
309
758
  if (!groupId) {
310
759
  return "请在群聊中使用当前指令";
311
760
  }
312
761
  let result = "";
313
762
  for (let account of accounts) {
314
- app = this.getApp(app), account = this.getName(app, account);
315
- const { status, msg } = await this.addSubscription(app, account, groupId);
763
+ const accountName = this.getName(appName, account) || account;
764
+ const { status, msg } = await this.addSubscription(appName, accountName, groupId);
316
765
  if (status) {
317
- result += `成功为群组 ${groupId} 添加订阅:${app} - ${account}
766
+ result += `成功为当前群添加订阅:${appName} - ${accountName}
318
767
  `;
319
768
  } else {
320
- result += `添加订阅 ${app} - ${account} 失败,${msg}
769
+ result += `添加订阅 ${appName} - ${accountName} 失败,${msg}
321
770
  `;
322
771
  }
323
772
  }
324
773
  return result;
325
774
  });
326
- const unsub = ctx.command("subscription.remove <app:string> <account:string>", "删除订阅").alias("删除订阅").alias("取消订阅").userFields(["authority"]).example("取消推特订阅 爱美").example("取消订阅 twitter aimi_sound").action(async ({ session, options }, app, account) => {
775
+ const unsub = ctx.command("subscription.remove <app:string> <account:string>", "从当前群删除一个账号订阅。账号可使用 ID、名称或别名。").alias("删除订阅").alias("取消订阅").userFields(["authority"]).example("取消订阅 twitter aimi_sound").example("取消推特订阅 爱美").action(async ({ session, options }, app, account) => {
327
776
  if (!await this.authCheck(session, config)) {
328
777
  return "权限不足";
329
778
  }
330
779
  if (!app || !account) {
331
- return "请指定应用名和账号,格式:取消订阅 <应用名> <账号>";
780
+ return "请指定应用名和账号。\n用法:取消订阅 <应用名> <账号>\n示例:取消订阅 twitter aimi_sound";
781
+ }
782
+ const appName = this.getApp(app);
783
+ if (!appName) {
784
+ return `应用不存在:${app}`;
332
785
  }
333
- app = this.getApp(app), account = this.getName(app, account);
786
+ const accountName = this.getName(appName, account) || account;
334
787
  const groupId = this.getChannelId(session.channel);
335
788
  if (!groupId) {
336
789
  return "请在群聊中使用当前指令";
337
790
  }
338
- const success = await this.removeSubscription(app, account, groupId);
791
+ const success = await this.removeSubscription(appName, accountName, groupId);
339
792
  if (success) {
340
- return `成功为群组 ${groupId} 删除订阅:${app} - ${account}`;
793
+ return `成功为当前群删除订阅:${appName} - ${accountName}`;
341
794
  } else {
342
- return `删除订阅失败,可能的原因:订阅不存在`;
795
+ return "删除订阅失败,可能的原因:订阅不存在";
343
796
  }
344
797
  });
345
798
  for (const app in config.apps) {
@@ -350,330 +803,50 @@ var SubscriptionService = class extends import_koishi.Service {
350
803
  unsub.alias(`取消${appAlias}订阅`, { args: [app] });
351
804
  }
352
805
  }
353
- ctx.command("subscription.list", "查看当前群组的订阅").alias("订阅列表").userFields(["authority"]).action(async ({ session, options }) => {
354
- if (!await this.authCheck(session, config)) {
806
+ ctx.command("subscription.list [app:string]", "查看当前群订阅。默认只显示已订阅账号;指定应用时显示该应用完整状态。").alias("订阅列表").userFields(["authority"]).example("订阅列表").example("订阅列表 twitter").example("订阅列表 推特").action(async ({ session, options }, app) => {
807
+ const passedAuth = await this.authCheck(session, config);
808
+ if (!passedAuth) {
355
809
  return "权限不足";
356
810
  }
357
811
  const groupId = this.getChannelId(session.channel);
358
812
  if (!groupId) {
359
813
  return "请在群聊中使用当前指令";
360
814
  }
361
- const subscriptions = await this.getGroupSubscriptions(groupId);
362
- const subscribedByApp = {};
363
- subscriptions.forEach((sub2) => {
364
- if (!subscribedByApp[sub2.app]) {
365
- subscribedByApp[sub2.app] = /* @__PURE__ */ new Set();
366
- }
367
- subscribedByApp[sub2.app].add(sub2.account);
368
- });
369
- const availableAccounts = {};
370
- const appConfig = this.subsConfig.apps;
371
- for (const app in appConfig) {
372
- availableAccounts[app] = appConfig[app].allowedAccounts.map((account) => ({
373
- id: account.id,
374
- name: account.name,
375
- alias: account.alias ? account.alias.split(",") : []
376
- }));
377
- }
815
+ const view = await this.getSubscriptionListView(app, groupId);
816
+ if (typeof view === "string") return view;
817
+ const textFallback = /* @__PURE__ */ __name(() => this.renderSubscriptionListText(view), "textFallback");
378
818
  if (!ctx.puppeteer) {
379
- let result = ``;
380
- for (const app in availableAccounts) {
381
- result += `【${app}】
382
- `;
383
- const subscribed = subscribedByApp[app] || /* @__PURE__ */ new Set();
384
- availableAccounts[app].forEach((account) => {
385
- const isSubscribed = subscribed.has(account.id);
386
- const status = isSubscribed ? "✅ 已订阅" : "❌ 未订阅";
387
- const aliases = account.alias.length > 0 ? ` (${account.alias.join(", ")})` : "";
388
- result += `${status} ${account.name} id:${account.id}
389
- ${aliases}
390
- `;
391
- });
392
- result += "\n";
393
- }
394
- return result;
819
+ return textFallback();
395
820
  }
396
- const html = generateSubscriptionHTML(groupId, availableAccounts, subscribedByApp);
397
- const page = await ctx.puppeteer.page();
821
+ const html = generateSubscriptionHTML(groupId, view.availableAccounts, view.subscribedByApp, view.appThemeColors, {
822
+ showTitle: !view.targetApp
823
+ });
824
+ let page;
398
825
  try {
826
+ page = await ctx.puppeteer.page();
827
+ page.setDefaultTimeout?.(15e3);
399
828
  await page.setViewport({
400
- width: 1e3,
401
- height: 800
829
+ width: screenshotWidth,
830
+ height: 1,
831
+ deviceScaleFactor: screenshotDeviceScaleFactor
402
832
  });
403
833
  await page.setContent(html, {
404
- waitUntil: "networkidle0"
834
+ waitUntil: "load"
405
835
  });
406
836
  const image = await page.screenshot({
407
837
  type: "jpeg",
838
+ quality: screenshotQuality,
408
839
  fullPage: true
409
840
  });
410
- return `<image url="data:image/png;base64,${image.toString("base64")}" />`;
841
+ const base64 = image.toString("base64");
842
+ return `<image url="data:image/jpeg;base64,${base64}" />`;
843
+ } catch (error) {
844
+ return textFallback();
411
845
  } finally {
412
- await page.close();
846
+ await page?.close();
413
847
  }
414
848
  });
415
- function generateSubscriptionHTML(groupId, availableAccounts, subscribedByApp) {
416
- return `
417
- <!DOCTYPE html>
418
- <html>
419
- <head>
420
- <meta charset="UTF-8">
421
- <style>
422
- * {
423
- margin: 0;
424
- padding: 0;
425
- box-sizing: border-box;
426
- }
427
- body {
428
- font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
429
- background: #f5f7fa;
430
- min-height: 100vh;
431
- padding: 20px;
432
- }
433
- .container {
434
- max-width: 1000px;
435
- margin: 0 auto;
436
- }
437
- .header {
438
- text-align: center;
439
- margin-bottom: 20px;
440
- color: #2c3e50;
441
- }
442
- .header h1 {
443
- font-size: 36px;
444
- margin-bottom: 12px;
445
- font-weight: 900;
446
- }
447
- .header .subtitle {
448
- font-size: 21px;
449
- color: #7f8c8d;
450
- }
451
- .apps-container {
452
- background: white;
453
- border-radius: 12px;
454
- box-shadow: 0 3px 18px rgba(0,0,0,0.08);
455
- overflow: hidden;
456
- }
457
- .app-section {
458
- border-bottom: 1px solid #eaeaea;
459
- }
460
- .app-section:last-child {
461
- border-bottom: none;
462
- }
463
- .app-title {
464
- font-size: 24px;
465
- font-weight: 600;
466
- color: #34495e;
467
- padding: 18px 24px;
468
- background: #f8f9fa;
469
- border-bottom: 1px solid #eaeaea;
470
- display: flex;
471
- align-items: center;
472
- gap: 12px;
473
- }
474
- .app-title::before {
475
- content: "▸";
476
- font-size: 21px;
477
- color: #3498db;
478
- }
479
- .grid {
480
- display: grid;
481
- grid-template-columns: repeat(2, 1fr);
482
- gap: 0;
483
- }
484
- .account-card {
485
- padding: 21px;
486
- border-right: 1px solid #f0f0f0;
487
- border-bottom: 1px solid #f0f0f0;
488
- transition: all 0.2s ease;
489
- position: relative;
490
- min-height: 120px;
491
- display: flex;
492
- flex-direction: column;
493
- }
494
- .account-card:nth-child(4n+1),
495
- .account-card:nth-child(4n+4) {
496
- background: #f8f9fa;
497
- }
498
- .account-card:nth-child(4n+2),
499
- .account-card:nth-child(4n+3) {
500
- background: #f0f2f5;
501
- }
502
- .account-card:hover {
503
- background: #e6f3ff !important;
504
- }
505
- .account-card:nth-child(2n) {
506
- border-right: none;
507
- }
508
- .account-card:last-child {
509
- border-right: none;
510
- }
511
- .name-id-container {
512
- display: flex;
513
- justify-content: space-between;
514
- align-items: center;
515
- margin-bottom: 12px;
516
- flex-wrap: wrap;
517
- }
518
- .name {
519
- font-size: 21px;
520
- font-weight: 900;
521
- color: #2c3e50;
522
- line-height: 1.3;
523
- max-width: 60%;
524
- overflow: hidden;
525
- text-overflow: ellipsis;
526
- white-space: nowrap;
527
- }
528
- .id {
529
- font-size: 21px;
530
- color: #5a6c7d;
531
- font-family: 'Courier New', monospace;
532
- background: #f8f9fa;
533
- padding: 4.5px 9px;
534
- border-radius: 4.5px;
535
- line-height: 1.3;
536
- max-width: 35%;
537
- overflow: hidden;
538
- text-overflow: ellipsis;
539
- white-space: nowrap;
540
- }
541
- .name-id-container.vertical {
542
- flex-direction: column;
543
- align-items: flex-start;
544
- }
545
- .name-id-container.vertical .name {
546
- max-width: 100%;
547
- margin-bottom: 6px;
548
- }
549
- .name-id-container.vertical .id {
550
- max-width: 100%;
551
- align-self: flex-end;
552
- }
553
- .alias-status-container {
554
- display: flex;
555
- justify-content: space-between;
556
- margin-top: 12px;
557
- flex-grow: 1;
558
- }
559
- .alias-container {
560
- flex: 1;
561
- max-width: calc(100% - 105px);
562
- align-self: flex-start;
563
- }
564
- .alias {
565
- font-size: 20px;
566
- color: #6c757d;
567
- display: flex;
568
- flex-wrap: wrap;
569
- gap: 6px;
570
- align-items: flex-start;
571
- }
572
- .alias-tag {
573
- background: #e8f4fd;
574
- color: #1971c2;
575
- padding: 3px 9px;
576
- border-radius: 15px;
577
- border: 1px solid #a5d8ff;
578
- white-space: nowrap;
579
- margin-bottom: 3px;
580
- }
581
- .no-alias {
582
- font-size: 20px;
583
- color: #adb5bd;
584
- font-style: italic;
585
- }
586
- .status-container {
587
- display: flex;
588
- align-items: flex-end;
589
- margin-left: 12px;
590
- }
591
- .status {
592
- display: inline-flex;
593
- align-items: center;
594
- gap: 9px;
595
- padding: 4.5px 15px;
596
- border-radius: 24px;
597
- font-size: 20px;
598
- font-weight: 900;
599
- white-space: nowrap;
600
- flex-shrink: 0;
601
- }
602
- .subscribed {
603
- background: #d4edda;
604
- color: #155724;
605
- border: 1px solid #c3e6cb;
606
- }
607
- .unsubscribed {
608
- background: #f8d7da;
609
- color: #721c24;
610
- border: 1px solid #f5c6cb;
611
- }
612
- .empty-state {
613
- text-align: center;
614
- color: #6c757d;
615
- font-style: italic;
616
- padding: 45px;
617
- background: #f8f9fa;
618
- grid-column: 1 / -1;
619
- }
620
- </style>
621
- </head>
622
- <body>
623
- <div class="container">
624
- <div class="header">
625
- <h1>订阅状态总览</h1>
626
- </div>
627
-
628
- <div class="apps-container">
629
- ${Object.entries(availableAccounts).map(([app, accounts]) => {
630
- const subscribed = subscribedByApp[app] || /* @__PURE__ */ new Set();
631
- return `
632
- <div class="app-section">
633
- <div class="app-title">${app}</div>
634
- <div class="grid">
635
- ${accounts.length > 0 ? accounts.map((account) => {
636
- const isSubscribed = subscribed.has(account.id);
637
- const needsVerticalLayout = account.name.length > 15 || account.id.length > 20;
638
- return `
639
- <div class="account-card">
640
- <div class="name-id-container ${needsVerticalLayout ? "vertical" : ""}">
641
- <div class="name">${account.name}</div>
642
- <div class="id">${account.id}</div>
643
- </div>
644
- <div class="alias-status-container">
645
- <div class="alias-container">
646
- ${account.alias.length > 0 ? `
647
- <div class="alias">
648
- ${account.alias.map((alias) => `<span class="alias-tag">${alias}</span>`).join("")}
649
- </div>
650
- ` : `<div class="no-alias">无别名</div>`}
651
- </div>
652
- <div class="status-container">
653
- <div class="status ${isSubscribed ? "subscribed" : "unsubscribed"}">
654
- ${isSubscribed ? "✅ 已订阅" : "❌ 未订阅"}
655
- </div>
656
- </div>
657
- </div>
658
- </div>
659
- `;
660
- }).join("") : `
661
- <div class="empty-state">
662
- 暂无可用账号
663
- </div>
664
- `}
665
- </div>
666
- </div>
667
- `;
668
- }).join("")}
669
- </div>
670
- </div>
671
- </body>
672
- </html>
673
- `;
674
- }
675
- __name(generateSubscriptionHTML, "generateSubscriptionHTML");
676
- ctx.command("subscription.clear", "删除群组的所有订阅").alias("清空订阅").userFields(["authority"]).action(async ({ session }) => {
849
+ ctx.command("subscription.clear [app:string]", "清空当前群订阅。可指定应用名或别名;不指定时清空全部。执行前会要求二次确认。").alias("清空订阅").userFields(["authority"]).example("清空订阅").example("清空订阅 twitter").example("清空订阅 推特").action(async ({ session }, app) => {
677
850
  if (!await this.authCheck(session, config)) {
678
851
  return "权限不足";
679
852
  }
@@ -681,12 +854,20 @@ var SubscriptionService = class extends import_koishi.Service {
681
854
  if (!groupId) {
682
855
  return "请在群聊中使用当前指令";
683
856
  }
684
- const subscriptions = await this.getGroupSubscriptions(groupId);
857
+ const targetApp = app ? this.getApp(app) : void 0;
858
+ if (app && !targetApp) {
859
+ return `应用不存在:${app}
860
+ 请使用配置中的应用名或应用别名,例如:清空订阅 twitter`;
861
+ }
862
+ const query = targetApp ? { groupId, app: targetApp } : { groupId };
863
+ const subscriptions = await ctx.database.get(this.subTableName, query);
864
+ const scopeText = targetApp ? `应用 ${targetApp}` : "所有应用";
685
865
  if (subscriptions.length === 0) {
686
- return `群组 ${groupId} 暂无订阅`;
866
+ return `当前群暂无${scopeText}的订阅`;
687
867
  }
688
- const confirm = await session.send(
689
- `确认要清空当前群聊的所有订阅吗?
868
+ await session.send(
869
+ `确认要清空当前群的${scopeText}订阅吗?
870
+ 将删除 ${subscriptions.length} 个订阅。
690
871
  请输入 "是" 或 "确认" 或 "yes" 来确认删除,输入其他内容取消。`
691
872
  );
692
873
  const response = await session.prompt(3e4);
@@ -694,13 +875,13 @@ var SubscriptionService = class extends import_koishi.Service {
694
875
  return "操作已取消(超时)。";
695
876
  }
696
877
  if (["是", "确认", "yes", "y"].includes(response.toLowerCase())) {
697
- await ctx.database.remove("subscription_service", { groupId });
698
- return `已删除群组 ${groupId} 的所有订阅(共 ${subscriptions.length} 个)`;
878
+ await ctx.database.remove(this.subTableName, query);
879
+ return `已删除当前群的${scopeText}订阅(共 ${subscriptions.length} 个)`;
699
880
  } else {
700
881
  return "操作已取消。";
701
882
  }
702
883
  });
703
- ctx.command("subscription.stats", "查看订阅统计", {
884
+ ctx.command("subscription.stats", "查看所有订阅的群组数量统计。", {
704
885
  permissions: [`authority:${config.minAuthority}`],
705
886
  hidden: true
706
887
  }).alias("订阅统计").userFields(["authority"]).action(async ({ session }) => {
@@ -716,6 +897,13 @@ ${statsList}`;
716
897
  });
717
898
  }
718
899
  };
900
+
901
+ // src/index.tsx
902
+ var name = "subscription";
903
+ var inject = {
904
+ required: ["database"],
905
+ optional: ["puppeteer"]
906
+ };
719
907
  function apply(ctx, config) {
720
908
  ctx.plugin(SubscriptionService, config);
721
909
  }
@@ -723,6 +911,7 @@ __name(apply, "apply");
723
911
  // Annotate the CommonJS export names for ESM import in node:
724
912
  0 && (module.exports = {
725
913
  Config,
914
+ SubscriptionService,
726
915
  apply,
727
916
  inject,
728
917
  name
@@ -0,0 +1,14 @@
1
+ import { Dict } from 'koishi';
2
+ interface AccountItem {
3
+ id: string;
4
+ name: string;
5
+ alias: string[];
6
+ }
7
+ interface ThemeInput {
8
+ [app: string]: string | undefined;
9
+ }
10
+ interface RenderOptions {
11
+ showTitle?: boolean;
12
+ }
13
+ export declare function generateSubscriptionHTML(groupId: string, availableAccounts: Dict<AccountItem[]>, subscribedByApp: Dict<Set<string>>, appThemeColors?: ThemeInput, options?: RenderOptions): string;
14
+ export {};
@@ -0,0 +1,60 @@
1
+ import { Context, Service, Fragment } from 'koishi';
2
+ import type { Config } from './config';
3
+ interface RecordsItem {
4
+ app: string;
5
+ id: string;
6
+ createdAt: Date;
7
+ }
8
+ interface SubscriptionItem {
9
+ id: number;
10
+ app: string;
11
+ account: string;
12
+ groupId: string;
13
+ createdAt: Date;
14
+ }
15
+ declare module 'koishi' {
16
+ interface Context {
17
+ subscription: SubscriptionService;
18
+ }
19
+ interface Tables {
20
+ subscription_service: SubscriptionItem;
21
+ subscription_records: RecordsItem;
22
+ }
23
+ }
24
+ export declare class SubscriptionService extends Service {
25
+ readonly subTableName = "subscription_service";
26
+ readonly recTableName = "subscription_records";
27
+ subsConfig: Config;
28
+ appMap: Map<string, string>;
29
+ accountMap: Map<string, string>;
30
+ constructor(ctx: Context, config: Config);
31
+ initMap(): void;
32
+ private getApp;
33
+ checkExist(app: string, id: string): Promise<boolean>;
34
+ getAccount(app: string, account: string): string;
35
+ getName(app: string, account: string): string;
36
+ getAvailableAccounts(app: string): string[];
37
+ getSubscribedGroups(app: string, account: string): Promise<string[]>;
38
+ broadcast(app: string, account: string, content: Fragment): Promise<void>;
39
+ private getSelfIds;
40
+ private getAssignedChannels;
41
+ broadcastForward(app: string, account: string, content: Fragment): Promise<any[]>;
42
+ addSubscription(app: string, account: string, groupId: string): Promise<{
43
+ status: boolean;
44
+ msg: string;
45
+ }>;
46
+ removeSubscription(app: string, account: string, groupId: string): Promise<boolean>;
47
+ getGroupSubscriptions(groupId: string): Promise<SubscriptionItem[]>;
48
+ getAllSubscriptions(): Promise<SubscriptionItem[]>;
49
+ getSubscriptionStats(): Promise<{
50
+ app: string;
51
+ account: string;
52
+ count: number;
53
+ }[]>;
54
+ private authCheck;
55
+ private getChannelId;
56
+ private getSubscriptionListView;
57
+ private renderSubscriptionListText;
58
+ private registerCommands;
59
+ }
60
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-subscription",
3
3
  "description": "给多个应用提供订阅群组管理",
4
- "version": "0.0.6",
4
+ "version": "0.0.8",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [