koishi-plugin-group-control 0.2.11 → 0.2.12

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/config.d.ts CHANGED
@@ -21,6 +21,7 @@ export interface GroupInviteConfig {
21
21
  inviteRequestMessage: string;
22
22
  autoApprove: boolean;
23
23
  showDetailedLog: boolean;
24
+ inviteExpireDays: number;
24
25
  }
25
26
  export interface FrequencyConfig {
26
27
  enabled: boolean;
package/lib/database.d.ts CHANGED
@@ -23,12 +23,22 @@ export interface SmallGroupWhitelist {
23
23
  platform: string;
24
24
  guildId: string;
25
25
  }
26
+ export interface PendingInvite {
27
+ platform: string;
28
+ groupId: string;
29
+ userId: string;
30
+ userName: string;
31
+ groupName: string;
32
+ time: number;
33
+ flag: string;
34
+ }
26
35
  declare module 'koishi' {
27
36
  interface Tables {
28
37
  blacklisted_guild: BlacklistedGuild;
29
38
  command_frequency_record: CommandFrequencyRecord;
30
39
  group_bot_status: GroupBotStatus;
31
40
  small_group_whitelist: SmallGroupWhitelist;
41
+ pending_invite: PendingInvite;
32
42
  }
33
43
  }
34
44
  export declare const name = "group-control-database";
@@ -47,3 +57,8 @@ export declare function isInSmallGroupWhitelist(ctx: Context, guildId: string):
47
57
  export declare function addToSmallGroupWhitelist(ctx: Context, guildId: string): Promise<void>;
48
58
  export declare function removeFromSmallGroupWhitelist(ctx: Context, guildId: string): Promise<void>;
49
59
  export declare function getAllSmallGroupWhitelist(ctx: Context): Promise<SmallGroupWhitelist[]>;
60
+ export declare function getPendingInvite(ctx: Context, groupId: string): Promise<PendingInvite>;
61
+ export declare function addPendingInvite(ctx: Context, inviteUser: Omit<PendingInvite, 'platform'>): Promise<void>;
62
+ export declare function removePendingInvite(ctx: Context, groupId: string): Promise<void>;
63
+ export declare function getAllPendingInvites(ctx: Context): Promise<PendingInvite[]>;
64
+ export declare function clearExpiredPendingInvites(ctx: Context, expireTimeMs: number): Promise<number>;
package/lib/index.js CHANGED
@@ -30,19 +30,24 @@ module.exports = __toCommonJS(src_exports);
30
30
  var database_exports = {};
31
31
  __export(database_exports, {
32
32
  BLACKLIST_PLATFORM: () => BLACKLIST_PLATFORM,
33
+ addPendingInvite: () => addPendingInvite,
33
34
  addToSmallGroupWhitelist: () => addToSmallGroupWhitelist,
34
35
  apply: () => apply,
35
36
  clearBlacklistedGuilds: () => clearBlacklistedGuilds,
37
+ clearExpiredPendingInvites: () => clearExpiredPendingInvites,
36
38
  createBlacklistedGuild: () => createBlacklistedGuild,
37
39
  getAllBlacklistedGuilds: () => getAllBlacklistedGuilds,
40
+ getAllPendingInvites: () => getAllPendingInvites,
38
41
  getAllSmallGroupWhitelist: () => getAllSmallGroupWhitelist,
39
42
  getBlacklistedGuild: () => getBlacklistedGuild,
40
43
  getCommandFrequencyRecord: () => getCommandFrequencyRecord,
41
44
  getGroupBotStatus: () => getGroupBotStatus,
45
+ getPendingInvite: () => getPendingInvite,
42
46
  isInSmallGroupWhitelist: () => isInSmallGroupWhitelist,
43
47
  name: () => name,
44
48
  removeBlacklistedGuild: () => removeBlacklistedGuild,
45
49
  removeFromSmallGroupWhitelist: () => removeFromSmallGroupWhitelist,
50
+ removePendingInvite: () => removePendingInvite,
46
51
  setGroupBotStatus: () => setGroupBotStatus,
47
52
  updateCommandFrequencyRecord: () => updateCommandFrequencyRecord
48
53
  });
@@ -72,6 +77,15 @@ function apply(ctx) {
72
77
  platform: "string",
73
78
  guildId: "string"
74
79
  }, { primary: ["platform", "guildId"] });
80
+ ctx.model.extend("pending_invite", {
81
+ platform: "string",
82
+ groupId: "string",
83
+ userId: "string",
84
+ userName: "string",
85
+ groupName: "string",
86
+ time: "integer",
87
+ flag: "string"
88
+ }, { primary: ["platform", "groupId"] });
75
89
  }
76
90
  __name(apply, "apply");
77
91
  var BLACKLIST_PLATFORM = "onebot";
@@ -139,6 +153,33 @@ async function getAllSmallGroupWhitelist(ctx) {
139
153
  return await ctx.model.get("small_group_whitelist", { platform: BLACKLIST_PLATFORM });
140
154
  }
141
155
  __name(getAllSmallGroupWhitelist, "getAllSmallGroupWhitelist");
156
+ async function getPendingInvite(ctx, groupId) {
157
+ const records = await ctx.model.get("pending_invite", { platform: BLACKLIST_PLATFORM, groupId });
158
+ return records.length > 0 ? records[0] : null;
159
+ }
160
+ __name(getPendingInvite, "getPendingInvite");
161
+ async function addPendingInvite(ctx, inviteUser) {
162
+ await ctx.model.upsert("pending_invite", [{ platform: BLACKLIST_PLATFORM, ...inviteUser }]);
163
+ }
164
+ __name(addPendingInvite, "addPendingInvite");
165
+ async function removePendingInvite(ctx, groupId) {
166
+ await ctx.model.remove("pending_invite", { platform: BLACKLIST_PLATFORM, groupId });
167
+ }
168
+ __name(removePendingInvite, "removePendingInvite");
169
+ async function getAllPendingInvites(ctx) {
170
+ return await ctx.model.get("pending_invite", { platform: BLACKLIST_PLATFORM });
171
+ }
172
+ __name(getAllPendingInvites, "getAllPendingInvites");
173
+ async function clearExpiredPendingInvites(ctx, expireTimeMs) {
174
+ const cutoff = Date.now() - expireTimeMs;
175
+ const all = await ctx.model.get("pending_invite", { platform: BLACKLIST_PLATFORM });
176
+ const expired = all.filter((r) => r.time < cutoff);
177
+ for (const record of expired) {
178
+ await ctx.model.remove("pending_invite", { platform: BLACKLIST_PLATFORM, groupId: record.groupId });
179
+ }
180
+ return expired.length;
181
+ }
182
+ __name(clearExpiredPendingInvites, "clearExpiredPendingInvites");
142
183
 
143
184
  // src/modules/basic.ts
144
185
  var basic_exports = {};
@@ -183,6 +224,9 @@ async function notifyAdmins(bot, config, message) {
183
224
  }
184
225
  __name(notifyAdmins, "notifyAdmins");
185
226
  async function hasPermission(session, config) {
227
+ if (config.invite.adminQQs?.includes(session.userId)) {
228
+ return true;
229
+ }
186
230
  if (config.permission.mode === "koishi") {
187
231
  try {
188
232
  const user = session.user;
@@ -194,9 +238,6 @@ async function hasPermission(session, config) {
194
238
  return false;
195
239
  }
196
240
  const userId = session.userId;
197
- if (config.invite.adminQQs?.includes(userId)) {
198
- return true;
199
- }
200
241
  try {
201
242
  const member = await session.bot.getGuildMember(session.guildId, userId);
202
243
  const roles = member?.roles || member?.role;
@@ -223,20 +264,31 @@ async function hasPermission(session, config) {
223
264
  return false;
224
265
  }
225
266
  __name(hasPermission, "hasPermission");
267
+ function isGlobalAdmin(session, config) {
268
+ if (config.invite.adminQQs?.includes(session.userId)) {
269
+ return true;
270
+ }
271
+ const user = session.user;
272
+ if (config.permission.mode === "koishi" && user && typeof user.authority === "number") {
273
+ return user.authority >= 4;
274
+ }
275
+ return false;
276
+ }
277
+ __name(isGlobalAdmin, "isGlobalAdmin");
226
278
  var ADMIN_COMMANDS = /* @__PURE__ */ new Set([
227
279
  "bot-on",
228
280
  "bot-off",
229
281
  "quit",
230
- "view-blacklist",
231
- "remove-from-blacklist",
232
- "add-to-blacklist",
233
- "clear-blacklist",
282
+ "banlist",
283
+ "unban",
284
+ "ban",
285
+ "clearban",
234
286
  "approve",
235
287
  "reject",
236
- "pending-invites",
237
- "allow-small-group",
238
- "disallow-small-group",
239
- "view-small-group-whitelist"
288
+ "pending",
289
+ "sg-add",
290
+ "sg-rm",
291
+ "sg-list"
240
292
  ]);
241
293
 
242
294
  // src/state.ts
@@ -427,19 +479,17 @@ __export(invite_exports, {
427
479
  var name3 = "group-control-invite";
428
480
  function apply3(ctx, config) {
429
481
  if (!config.invite.enabled) return;
430
- const pendingInvites = /* @__PURE__ */ new Map();
431
- const INVITE_TIMEOUT = 10 * 60 * 1e3;
432
- setInterval(() => {
433
- const now = Date.now();
434
- for (const [key, invite] of pendingInvites) {
435
- if (now - invite.time > INVITE_TIMEOUT) {
436
- pendingInvites.delete(key);
437
- if (config.invite.showDetailedLog) {
438
- console.log(`邀请超时已清理: 群号=${invite.groupId}, 邀请者=${invite.userId}`);
439
- }
482
+ setInterval(async () => {
483
+ const expireMs = config.invite.inviteExpireDays * 24 * 60 * 60 * 1e3;
484
+ try {
485
+ const count = await clearExpiredPendingInvites(ctx, expireMs);
486
+ if (count > 0 && config.invite.showDetailedLog) {
487
+ console.log(`已自动清理 ${count} 个过期邀请`);
440
488
  }
489
+ } catch (error) {
490
+ console.error("清理过期邀请失败:", error);
441
491
  }
442
- }, 60 * 1e3);
492
+ }, 60 * 60 * 1e3);
443
493
  ctx.on("guild-request", async (session) => {
444
494
  const raw = session.original || session.raw || session.event?._data || {};
445
495
  const flag = raw.flag || session.flag || session.messageId;
@@ -486,7 +536,7 @@ function apply3(ctx, config) {
486
536
  }
487
537
  return;
488
538
  }
489
- pendingInvites.set(rawGroupId, {
539
+ await addPendingInvite(ctx, {
490
540
  groupId: rawGroupId,
491
541
  userId: rawUserId,
492
542
  userName,
@@ -524,14 +574,15 @@ function apply3(ctx, config) {
524
574
  console.warn("群邀请请求发送失败:未配置通知群且管理员私聊发送失败");
525
575
  }
526
576
  });
527
- ctx.command("approve <groupId:string>", "同意群聊邀请", { authority: 4 }).action(async ({ session }, groupId) => {
577
+ ctx.command("approve <groupId:string>", "同意群聊邀请").action(async ({ session }, groupId) => {
528
578
  if (!groupId) return "请指定群号。用法:approve <群号>";
529
579
  if (!config.invite.adminQQs.includes(session.userId)) {
530
580
  return "权限不足,只有管理员可以审核邀请。";
531
581
  }
532
- const inviteData = pendingInvites.get(groupId);
582
+ const inviteData = await getPendingInvite(ctx, groupId);
533
583
  if (!inviteData) {
534
- return `未找到群号 ${groupId} 的待处理邀请。当前待处理邀请:${pendingInvites.size > 0 ? Array.from(pendingInvites.values()).map((i) => `${i.groupId}(${i.groupName})`).join(", ") : "无"}`;
584
+ const allInvites = await getAllPendingInvites(ctx);
585
+ return `未找到群号 ${groupId} 的待处理邀请。当前待处理邀请:${allInvites.length > 0 ? allInvites.map((i) => `${i.groupId}(${i.groupName})`).join(", ") : "无"}`;
535
586
  }
536
587
  try {
537
588
  await session.bot.internal.setGroupAddRequest(inviteData.flag, "invite", true, "");
@@ -541,21 +592,22 @@ function apply3(ctx, config) {
541
592
  } catch (error) {
542
593
  console.error("通知邀请者失败:", error);
543
594
  }
544
- pendingInvites.delete(groupId);
595
+ await removePendingInvite(ctx, groupId);
545
596
  return `已同意加入群 ${groupId}(${inviteData.groupName}),邀请者:${inviteData.userName}`;
546
597
  } catch (error) {
547
598
  console.error("处理同意邀请失败:", error);
548
599
  return `处理同意邀请失败: ${error.message}`;
549
600
  }
550
601
  });
551
- ctx.command("reject <groupId:string>", "拒绝群聊邀请", { authority: 4 }).action(async ({ session }, groupId) => {
602
+ ctx.command("reject <groupId:string>", "拒绝群聊邀请").action(async ({ session }, groupId) => {
552
603
  if (!groupId) return "请指定群号。用法:reject <群号>";
553
604
  if (!config.invite.adminQQs.includes(session.userId)) {
554
605
  return "权限不足,只有管理员可以审核邀请。";
555
606
  }
556
- const inviteData = pendingInvites.get(groupId);
607
+ const inviteData = await getPendingInvite(ctx, groupId);
557
608
  if (!inviteData) {
558
- return `未找到群号 ${groupId} 的待处理邀请。当前待处理邀请:${pendingInvites.size > 0 ? Array.from(pendingInvites.values()).map((i) => `${i.groupId}(${i.groupName})`).join(", ") : "无"}`;
609
+ const allInvites = await getAllPendingInvites(ctx);
610
+ return `未找到群号 ${groupId} 的待处理邀请。当前待处理邀请:${allInvites.length > 0 ? allInvites.map((i) => `${i.groupId}(${i.groupName})`).join(", ") : "无"}`;
559
611
  }
560
612
  try {
561
613
  await session.bot.internal.setGroupAddRequest(inviteData.flag, "invite", false, "已拒绝");
@@ -564,22 +616,23 @@ function apply3(ctx, config) {
564
616
  } catch (error) {
565
617
  console.error("通知邀请者失败:", error);
566
618
  }
567
- pendingInvites.delete(groupId);
619
+ await removePendingInvite(ctx, groupId);
568
620
  return `已拒绝加入群 ${groupId}(${inviteData.groupName}),邀请者:${inviteData.userName}`;
569
621
  } catch (error) {
570
622
  console.error("处理拒绝邀请失败:", error);
571
623
  return `处理拒绝邀请失败: ${error.message}`;
572
624
  }
573
625
  });
574
- ctx.command("pending-invites", "查看待处理的群聊邀请", { authority: 4 }).action(async ({ session }) => {
626
+ ctx.command("pending", "查看待处理的群聊邀请").action(async ({ session }) => {
575
627
  if (!config.invite.adminQQs.includes(session.userId)) {
576
628
  return "权限不足,只有管理员可以查看待处理邀请。";
577
629
  }
578
- if (pendingInvites.size === 0) {
630
+ const allInvites = await getAllPendingInvites(ctx);
631
+ if (allInvites.length === 0) {
579
632
  return "当前没有待处理的群聊邀请。";
580
633
  }
581
634
  const lines = ["待处理的群聊邀请列表:"];
582
- for (const [, invite] of pendingInvites) {
635
+ for (const invite of allInvites) {
583
636
  const elapsed = Math.floor((Date.now() - invite.time) / 1e3 / 60);
584
637
  lines.push(`- 群:${invite.groupName}(${invite.groupId})`);
585
638
  lines.push(` 邀请者:${invite.userName}(${invite.userId})`);
@@ -673,7 +726,8 @@ __export(commands_exports, {
673
726
  });
674
727
  var name5 = "group-control-commands";
675
728
  function apply5(ctx, config) {
676
- async function viewBlacklist() {
729
+ async function viewBlacklist({ session }) {
730
+ if (!isGlobalAdmin(session, config)) return "权限不足,只有全局管理员可以执行此操作。";
677
731
  const errorMsg = isBlacklistEnabled(config.basic);
678
732
  if (errorMsg) return errorMsg;
679
733
  const records = await getAllBlacklistedGuilds(ctx);
@@ -681,8 +735,9 @@ function apply5(ctx, config) {
681
735
  return "黑名单列表:\n" + records.map((r) => `- ${r.guildId} (时间: ${formatDate(r.timestamp)})`).join("\n");
682
736
  }
683
737
  __name(viewBlacklist, "viewBlacklist");
684
- ctx.command("view-blacklist", "查看被拉黑的群聊列表", { authority: 4 }).action(viewBlacklist);
685
- async function removeFromBlacklist({}, input) {
738
+ ctx.command("banlist", "查看被拉黑的群聊列表").action(viewBlacklist);
739
+ async function removeFromBlacklist({ session }, input) {
740
+ if (!isGlobalAdmin(session, config)) return "权限不足,只有全局管理员可以执行此操作。";
686
741
  const errorMsg = isBlacklistEnabled(config.basic);
687
742
  if (errorMsg) return errorMsg;
688
743
  const guildId = parseGuildId(input);
@@ -691,8 +746,9 @@ function apply5(ctx, config) {
691
746
  return removed ? `已移除群聊 ${guildId}` : `群聊 ${guildId} 不在黑名单中。`;
692
747
  }
693
748
  __name(removeFromBlacklist, "removeFromBlacklist");
694
- ctx.command("remove-from-blacklist <groupId:text>", "从黑名单移除指定群聊", { authority: 4 }).action(removeFromBlacklist);
695
- async function addToBlacklist({}, input) {
749
+ ctx.command("unban <groupId:text>", "从黑名单移除指定群聊").action(removeFromBlacklist);
750
+ async function addToBlacklist({ session }, input) {
751
+ if (!isGlobalAdmin(session, config)) return "权限不足,只有全局管理员可以执行此操作。";
696
752
  const errorMsg = isBlacklistEnabled(config.basic);
697
753
  if (errorMsg) return errorMsg;
698
754
  const guildId = parseGuildId(input);
@@ -703,8 +759,9 @@ function apply5(ctx, config) {
703
759
  return `已添加群聊 ${guildId} 到黑名单。`;
704
760
  }
705
761
  __name(addToBlacklist, "addToBlacklist");
706
- ctx.command("add-to-blacklist <groupId:text>", "手动添加群聊到黑名单", { authority: 4 }).action(addToBlacklist);
707
- async function clearBlacklist() {
762
+ ctx.command("ban <groupId:text>", "手动添加群聊到黑名单").action(addToBlacklist);
763
+ async function clearBlacklist({ session }) {
764
+ if (!isGlobalAdmin(session, config)) return "权限不足,只有全局管理员可以执行此操作。";
708
765
  const errorMsg = isBlacklistEnabled(config.basic);
709
766
  if (errorMsg) return errorMsg;
710
767
  const records = await getAllBlacklistedGuilds(ctx);
@@ -713,8 +770,9 @@ function apply5(ctx, config) {
713
770
  return `已清空黑名单,共移除 ${records.length} 个群聊。`;
714
771
  }
715
772
  __name(clearBlacklist, "clearBlacklist");
716
- ctx.command("clear-blacklist", "清空黑名单", { authority: 4 }).action(clearBlacklist);
717
- ctx.command("allow-small-group <groupId:text>", "解除指定群聊的小群人数限制", { authority: 4 }).action(async ({}, input) => {
773
+ ctx.command("clearban", "清空黑名单").action(clearBlacklist);
774
+ ctx.command("sg-add <groupId:text>", "解除指定群聊的小群人数限制").action(async ({ session }, input) => {
775
+ if (!isGlobalAdmin(session, config)) return "权限不足,只有全局管理员可以执行此操作。";
718
776
  const guildId = parseGuildId(input);
719
777
  if (!guildId) return "输入格式错误,请输入群号。";
720
778
  const exists = await isInSmallGroupWhitelist(ctx, guildId);
@@ -722,7 +780,8 @@ function apply5(ctx, config) {
722
780
  await addToSmallGroupWhitelist(ctx, guildId);
723
781
  return `已将群聊 ${guildId} 加入小群白名单,该群不再受小群人数限制。`;
724
782
  });
725
- ctx.command("disallow-small-group <groupId:text>", "恢复指定群聊的小群人数限制", { authority: 4 }).action(async ({}, input) => {
783
+ ctx.command("sg-rm <groupId:text>", "恢复指定群聊的小群人数限制").action(async ({ session }, input) => {
784
+ if (!isGlobalAdmin(session, config)) return "权限不足,只有全局管理员可以执行此操作。";
726
785
  const guildId = parseGuildId(input);
727
786
  if (!guildId) return "输入格式错误,请输入群号。";
728
787
  const exists = await isInSmallGroupWhitelist(ctx, guildId);
@@ -730,7 +789,8 @@ function apply5(ctx, config) {
730
789
  await removeFromSmallGroupWhitelist(ctx, guildId);
731
790
  return `已将群聊 ${guildId} 从小群白名单移除,该群将恢复小群人数限制。`;
732
791
  });
733
- ctx.command("view-small-group-whitelist", "查看小群白名单", { authority: 4 }).action(async () => {
792
+ ctx.command("sg-list", "查看小群白名单").action(async ({ session }) => {
793
+ if (!isGlobalAdmin(session, config)) return "权限不足,只有全局管理员可以执行此操作。";
734
794
  const records = await getAllSmallGroupWhitelist(ctx);
735
795
  if (records.length === 0) return "小群白名单为空。";
736
796
  return "小群白名单列表(以下群不受小群人数限制):\n" + records.map((r) => `- ${r.guildId}`).join("\n");
@@ -871,7 +931,8 @@ var Config = import_koishi.Schema.intersect([
871
931
  inviteWaitMessage: import_koishi.Schema.string().default("已收到您的群聊邀请,正在等待管理员审核,请耐心等待。").description("发送给邀请者的等待审核提示消息"),
872
932
  inviteRequestMessage: import_koishi.Schema.string().default("收到新的群聊邀请请求:\n群名称:{groupName}\n群号:{groupId}\n邀请者:{userName} (QQ: {userId})\n\n请管理员使用指令 approve {groupId} 同意或 reject {groupId} 拒绝。").description("发送给管理员的邀请请求消息模板,支持变量{groupName}, {groupId}, {userName}, {userId}"),
873
933
  autoApprove: import_koishi.Schema.boolean().default(false).description("是否自动同意邀请(仅在没有指定管理员时)"),
874
- showDetailedLog: import_koishi.Schema.boolean().default(false).description("是否显示详细日志")
934
+ showDetailedLog: import_koishi.Schema.boolean().default(false).description("是否显示详细日志"),
935
+ inviteExpireDays: import_koishi.Schema.number().default(3).description("邀请记录过期时间(天),超过此时间未处理的邀请将被自动清理")
875
936
  }).description("群聊邀请审核")
876
937
  }),
877
938
  import_koishi.Schema.object({
package/lib/utils.d.ts CHANGED
@@ -10,5 +10,10 @@ export declare function notifyAdmins(bot: any, config: Config, message: string):
10
10
  * - builtin 模式: 检查用户是否为群管理员/群主,或在管理员QQ列表中
11
11
  */
12
12
  export declare function hasPermission(session: Session, config: Config): Promise<boolean>;
13
+ /**
14
+ * 检查当前用户是否拥有全局管理权限
15
+ * (供独立于群聊的全局指令(如黑名单、白名单)使用)
16
+ */
17
+ export declare function isGlobalAdmin(session: Session, config: Config): boolean;
13
18
  /** 管理指令列表 - 这些指令始终不受 bot-off 影响 */
14
19
  export declare const ADMIN_COMMANDS: Set<string>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-group-control",
3
3
  "description": "Koishi 插件,一个多功能的群聊自管理工具。支持被踢出自动拉黑、刷屏自动屏蔽、开关控制等功能。(仅支持 OneBot 适配器)",
4
- "version": "0.2.11",
4
+ "version": "0.2.12",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [