koishi-plugin-group-control 0.2.9 → 0.2.11

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/database.d.ts CHANGED
@@ -19,11 +19,16 @@ export interface GroupBotStatus {
19
19
  guildId: string;
20
20
  botEnabled: boolean;
21
21
  }
22
+ export interface SmallGroupWhitelist {
23
+ platform: string;
24
+ guildId: string;
25
+ }
22
26
  declare module 'koishi' {
23
27
  interface Tables {
24
28
  blacklisted_guild: BlacklistedGuild;
25
29
  command_frequency_record: CommandFrequencyRecord;
26
30
  group_bot_status: GroupBotStatus;
31
+ small_group_whitelist: SmallGroupWhitelist;
27
32
  }
28
33
  }
29
34
  export declare const name = "group-control-database";
@@ -38,3 +43,7 @@ export declare function getCommandFrequencyRecord(ctx: Context, platform: string
38
43
  export declare function updateCommandFrequencyRecord(ctx: Context, platform: string, guildId: string, data: Partial<CommandFrequencyRecord>): Promise<void>;
39
44
  export declare function getGroupBotStatus(ctx: Context, platform: string, guildId: string): Promise<GroupBotStatus | null>;
40
45
  export declare function setGroupBotStatus(ctx: Context, platform: string, guildId: string, botEnabled: boolean): Promise<void>;
46
+ export declare function isInSmallGroupWhitelist(ctx: Context, guildId: string): Promise<boolean>;
47
+ export declare function addToSmallGroupWhitelist(ctx: Context, guildId: string): Promise<void>;
48
+ export declare function removeFromSmallGroupWhitelist(ctx: Context, guildId: string): Promise<void>;
49
+ export declare function getAllSmallGroupWhitelist(ctx: Context): Promise<SmallGroupWhitelist[]>;
package/lib/index.js CHANGED
@@ -30,15 +30,19 @@ module.exports = __toCommonJS(src_exports);
30
30
  var database_exports = {};
31
31
  __export(database_exports, {
32
32
  BLACKLIST_PLATFORM: () => BLACKLIST_PLATFORM,
33
+ addToSmallGroupWhitelist: () => addToSmallGroupWhitelist,
33
34
  apply: () => apply,
34
35
  clearBlacklistedGuilds: () => clearBlacklistedGuilds,
35
36
  createBlacklistedGuild: () => createBlacklistedGuild,
36
37
  getAllBlacklistedGuilds: () => getAllBlacklistedGuilds,
38
+ getAllSmallGroupWhitelist: () => getAllSmallGroupWhitelist,
37
39
  getBlacklistedGuild: () => getBlacklistedGuild,
38
40
  getCommandFrequencyRecord: () => getCommandFrequencyRecord,
39
41
  getGroupBotStatus: () => getGroupBotStatus,
42
+ isInSmallGroupWhitelist: () => isInSmallGroupWhitelist,
40
43
  name: () => name,
41
44
  removeBlacklistedGuild: () => removeBlacklistedGuild,
45
+ removeFromSmallGroupWhitelist: () => removeFromSmallGroupWhitelist,
42
46
  setGroupBotStatus: () => setGroupBotStatus,
43
47
  updateCommandFrequencyRecord: () => updateCommandFrequencyRecord
44
48
  });
@@ -64,6 +68,10 @@ function apply(ctx) {
64
68
  guildId: "string",
65
69
  botEnabled: "boolean"
66
70
  }, { primary: ["platform", "guildId"] });
71
+ ctx.model.extend("small_group_whitelist", {
72
+ platform: "string",
73
+ guildId: "string"
74
+ }, { primary: ["platform", "guildId"] });
67
75
  }
68
76
  __name(apply, "apply");
69
77
  var BLACKLIST_PLATFORM = "onebot";
@@ -114,6 +122,23 @@ async function setGroupBotStatus(ctx, platform, guildId, botEnabled) {
114
122
  await ctx.model.upsert("group_bot_status", [{ platform, guildId, botEnabled }]);
115
123
  }
116
124
  __name(setGroupBotStatus, "setGroupBotStatus");
125
+ async function isInSmallGroupWhitelist(ctx, guildId) {
126
+ const records = await ctx.model.get("small_group_whitelist", { platform: BLACKLIST_PLATFORM, guildId });
127
+ return records.length > 0;
128
+ }
129
+ __name(isInSmallGroupWhitelist, "isInSmallGroupWhitelist");
130
+ async function addToSmallGroupWhitelist(ctx, guildId) {
131
+ await ctx.model.upsert("small_group_whitelist", [{ platform: BLACKLIST_PLATFORM, guildId }]);
132
+ }
133
+ __name(addToSmallGroupWhitelist, "addToSmallGroupWhitelist");
134
+ async function removeFromSmallGroupWhitelist(ctx, guildId) {
135
+ await ctx.model.remove("small_group_whitelist", { platform: BLACKLIST_PLATFORM, guildId });
136
+ }
137
+ __name(removeFromSmallGroupWhitelist, "removeFromSmallGroupWhitelist");
138
+ async function getAllSmallGroupWhitelist(ctx) {
139
+ return await ctx.model.get("small_group_whitelist", { platform: BLACKLIST_PLATFORM });
140
+ }
141
+ __name(getAllSmallGroupWhitelist, "getAllSmallGroupWhitelist");
117
142
 
118
143
  // src/modules/basic.ts
119
144
  var basic_exports = {};
@@ -208,11 +233,32 @@ var ADMIN_COMMANDS = /* @__PURE__ */ new Set([
208
233
  "clear-blacklist",
209
234
  "approve",
210
235
  "reject",
211
- "pending-invites"
236
+ "pending-invites",
237
+ "allow-small-group",
238
+ "disallow-small-group",
239
+ "view-small-group-whitelist"
212
240
  ]);
213
241
 
242
+ // src/state.ts
243
+ var approvedGroups = /* @__PURE__ */ new Set();
244
+
214
245
  // src/modules/basic.ts
215
246
  var name2 = "group-control-basic";
247
+ async function getGroupName(bot, guildId) {
248
+ try {
249
+ const info = await bot.internal?.getGroupInfo?.(parseInt(guildId));
250
+ if (info?.group_name) return info.group_name;
251
+ } catch {
252
+ }
253
+ try {
254
+ const info = await bot.getGuild(guildId);
255
+ if (info?.name) return info.name;
256
+ if (info?.group_name) return info.group_name;
257
+ } catch {
258
+ }
259
+ return "未知";
260
+ }
261
+ __name(getGroupName, "getGroupName");
216
262
  function apply2(ctx, config) {
217
263
  const quittingGuilds = /* @__PURE__ */ new Map();
218
264
  const processedKicks = /* @__PURE__ */ new Map();
@@ -245,54 +291,67 @@ function apply2(ctx, config) {
245
291
  }
246
292
  }
247
293
  if (config.basic.smallGroupAutoQuit) {
248
- const delay = config.basic.smallGroupCheckDelay || 3e3;
249
- setTimeout(async () => {
250
- try {
251
- let memberCount = 0;
294
+ const inWhitelist = await isInSmallGroupWhitelist(ctx, guildId);
295
+ const wasApproved = approvedGroups.has(guildId);
296
+ if (wasApproved) approvedGroups.delete(guildId);
297
+ if (inWhitelist || wasApproved) {
298
+ } else {
299
+ const delay = config.basic.smallGroupCheckDelay || 3e3;
300
+ setTimeout(async () => {
252
301
  try {
253
- const groupInfo = await session.bot.internal?.getGroupInfo?.(parseInt(guildId));
254
- memberCount = groupInfo?.member_count || 0;
255
- } catch {
256
- }
257
- if (memberCount === 0) {
302
+ let memberCount = 0;
303
+ let groupName = "未知";
258
304
  try {
259
- const guildInfo = await session.bot.getGuild(guildId);
260
- memberCount = guildInfo?.member_count || guildInfo?.memberCount || 0;
305
+ const groupInfo = await session.bot.internal?.getGroupInfo?.(parseInt(guildId));
306
+ memberCount = groupInfo?.member_count || 0;
307
+ if (groupInfo?.group_name) groupName = groupInfo.group_name;
261
308
  } catch {
262
309
  }
263
- }
264
- if (memberCount === 0) {
265
- try {
266
- const memberList = await session.bot.getGuildMemberList(guildId);
267
- memberCount = memberList?.data?.length || 0;
268
- } catch {
310
+ if (memberCount === 0) {
311
+ try {
312
+ const guildInfo = await session.bot.getGuild(guildId);
313
+ memberCount = guildInfo?.member_count || guildInfo?.memberCount || 0;
314
+ if (guildInfo?.name) groupName = guildInfo.name;
315
+ } catch {
316
+ }
269
317
  }
270
- }
271
- if (memberCount > 0 && memberCount <= config.basic.smallGroupThreshold) {
272
- const quitMsg = config.basic.smallGroupQuitMessage.replace("{memberCount}", memberCount.toString()).replace("{threshold}", config.basic.smallGroupThreshold.toString());
273
- try {
274
- await session.bot.sendMessage(guildId, quitMsg, platform);
275
- } catch (e) {
318
+ if (memberCount === 0) {
319
+ try {
320
+ const memberList = await session.bot.getGuildMemberList(guildId);
321
+ memberCount = memberList?.data?.length || 0;
322
+ } catch {
323
+ }
276
324
  }
277
- if (config.basic.smallGroupNotifyAdmin) {
278
- const adminMsg = `小群自动退群
325
+ if (groupName === "未知") {
326
+ groupName = await getGroupName(session.bot, guildId);
327
+ }
328
+ if (memberCount > 0 && memberCount <= config.basic.smallGroupThreshold) {
329
+ const quitMsg = config.basic.smallGroupQuitMessage.replaceAll("{memberCount}", memberCount.toString()).replaceAll("{threshold}", config.basic.smallGroupThreshold.toString()).replaceAll("{groupName}", groupName).replaceAll("{groupId}", guildId);
330
+ try {
331
+ await session.bot.sendMessage(guildId, quitMsg, platform);
332
+ } catch (e) {
333
+ }
334
+ if (config.basic.smallGroupNotifyAdmin) {
335
+ const adminMsg = `小群自动退群
336
+ 群名称:${groupName}
279
337
  群号:${guildId}
280
338
  群成员数:${memberCount}人(阈值:${config.basic.smallGroupThreshold}人)
281
339
  机器人已自动退出该群。`;
282
- await notifyAdmins(session.bot, config, adminMsg);
283
- }
284
- quittingGuilds.set(`${platform}:${guildId}`, Date.now());
285
- try {
286
- await session.bot.internal.setGroupLeave(parseInt(guildId));
287
- } catch (e) {
288
- console.error(`小群自动退群失败 (群号: ${guildId}):`, e);
289
- quittingGuilds.delete(`${platform}:${guildId}`);
340
+ await notifyAdmins(session.bot, config, adminMsg);
341
+ }
342
+ quittingGuilds.set(`${platform}:${guildId}`, Date.now());
343
+ try {
344
+ await session.bot.internal.setGroupLeave(parseInt(guildId));
345
+ } catch (e) {
346
+ console.error(`小群自动退群失败 (群号: ${guildId}):`, e);
347
+ quittingGuilds.delete(`${platform}:${guildId}`);
348
+ }
290
349
  }
350
+ } catch (error) {
351
+ console.error(`小群自动退群检测失败 (群号: ${guildId}):`, error);
291
352
  }
292
- } catch (error) {
293
- console.error(`小群自动退群检测失败 (群号: ${guildId}):`, error);
294
- }
295
- }, delay);
353
+ }, delay);
354
+ }
296
355
  }
297
356
  if (config.basic.welcomeMessage) {
298
357
  try {
@@ -311,6 +370,7 @@ function apply2(ctx, config) {
311
370
  return;
312
371
  }
313
372
  processedKicks.set(quittingKey, Date.now());
373
+ const groupName = await getGroupName(session.bot, guildId);
314
374
  if (config.basic.enableBlacklist) {
315
375
  await ctx.model.upsert("blacklisted_guild", [{
316
376
  platform,
@@ -320,7 +380,7 @@ function apply2(ctx, config) {
320
380
  }]);
321
381
  }
322
382
  if (config.basic.notifyAdminOnKick) {
323
- const kickMsg = config.basic.kickNotificationMessage.replace("{groupId}", guildId);
383
+ const kickMsg = config.basic.kickNotificationMessage.replaceAll("{groupId}", guildId).replaceAll("{groupName}", groupName);
324
384
  await notifyAdmins(session.bot, config, kickMsg);
325
385
  }
326
386
  });
@@ -336,7 +396,9 @@ function apply2(ctx, config) {
336
396
  if (!hasPerm) return "权限不足,只有群管理员可以使用此指令。";
337
397
  }
338
398
  const { guildId, platform, userId } = session;
399
+ const groupName = await getGroupName(session.bot, guildId);
339
400
  const adminMsg = `收到来自 ${userId} 的退群指令
401
+ 群名称:${groupName}
340
402
  群号:${guildId}`;
341
403
  await notifyAdmins(session.bot, config, adminMsg);
342
404
  quittingGuilds.set(`${platform}:${guildId}`, Date.now());
@@ -414,6 +476,7 @@ function apply3(ctx, config) {
414
476
  if (config.invite.autoApprove) {
415
477
  try {
416
478
  await session.bot.internal.setGroupAddRequest(flag, "invite", true, "");
479
+ approvedGroups.add(rawGroupId);
417
480
  if (config.invite.showDetailedLog) {
418
481
  console.log(`自动同意群聊邀请: 群号 ${rawGroupId}, 邀请者 ${rawUserId}`);
419
482
  }
@@ -472,6 +535,7 @@ function apply3(ctx, config) {
472
535
  }
473
536
  try {
474
537
  await session.bot.internal.setGroupAddRequest(inviteData.flag, "invite", true, "");
538
+ approvedGroups.add(groupId);
475
539
  try {
476
540
  await session.bot.sendPrivateMessage(inviteData.userId, `您的群聊邀请已通过管理员审核,机器人已加入群聊。`);
477
541
  } catch (error) {
@@ -650,6 +714,27 @@ function apply5(ctx, config) {
650
714
  }
651
715
  __name(clearBlacklist, "clearBlacklist");
652
716
  ctx.command("clear-blacklist", "清空黑名单", { authority: 4 }).action(clearBlacklist);
717
+ ctx.command("allow-small-group <groupId:text>", "解除指定群聊的小群人数限制", { authority: 4 }).action(async ({}, input) => {
718
+ const guildId = parseGuildId(input);
719
+ if (!guildId) return "输入格式错误,请输入群号。";
720
+ const exists = await isInSmallGroupWhitelist(ctx, guildId);
721
+ if (exists) return `群聊 ${guildId} 已在小群白名单中。`;
722
+ await addToSmallGroupWhitelist(ctx, guildId);
723
+ return `已将群聊 ${guildId} 加入小群白名单,该群不再受小群人数限制。`;
724
+ });
725
+ ctx.command("disallow-small-group <groupId:text>", "恢复指定群聊的小群人数限制", { authority: 4 }).action(async ({}, input) => {
726
+ const guildId = parseGuildId(input);
727
+ if (!guildId) return "输入格式错误,请输入群号。";
728
+ const exists = await isInSmallGroupWhitelist(ctx, guildId);
729
+ if (!exists) return `群聊 ${guildId} 不在小群白名单中。`;
730
+ await removeFromSmallGroupWhitelist(ctx, guildId);
731
+ return `已将群聊 ${guildId} 从小群白名单移除,该群将恢复小群人数限制。`;
732
+ });
733
+ ctx.command("view-small-group-whitelist", "查看小群白名单", { authority: 4 }).action(async () => {
734
+ const records = await getAllSmallGroupWhitelist(ctx);
735
+ if (records.length === 0) return "小群白名单为空。";
736
+ return "小群白名单列表(以下群不受小群人数限制):\n" + records.map((r) => `- ${r.guildId}`).join("\n");
737
+ });
653
738
  }
654
739
  __name(apply5, "apply");
655
740
 
@@ -757,10 +842,10 @@ var Config = import_koishi.Schema.intersect([
757
842
  enableBlacklist: import_koishi.Schema.boolean().default(true).description('启用"被踢出自动拉黑"功能'),
758
843
  quitCommandEnabled: import_koishi.Schema.boolean().default(true).description("启用quit"),
759
844
  notifyAdminOnKick: import_koishi.Schema.boolean().default(true).description("被踢出群时通知管理员(需要在群聊邀请审核中配置管理员QQ)"),
760
- kickNotificationMessage: import_koishi.Schema.string().default("机器人已被踢出群聊\n群号:{groupId}\n该群已被自动加入黑名单。").description("被踢出群通知消息模板,支持变量{groupId}"),
845
+ kickNotificationMessage: import_koishi.Schema.string().default("机器人已被踢出群聊\n群名称:{groupName}\n群号:{groupId}\n该群已被自动加入黑名单。").description("被踢出群通知消息模板,支持变量{groupId}, {groupName}"),
761
846
  smallGroupAutoQuit: import_koishi.Schema.boolean().default(false).description("启用小群自动退群功能"),
762
847
  smallGroupThreshold: import_koishi.Schema.number().default(30).description("小群人数阈值(群成员数小于等于此值时自动退群)"),
763
- smallGroupQuitMessage: import_koishi.Schema.string().default("该群人数过少({memberCount}人),不满足最低人数要求({threshold}人),机器人将自动退出。").description("小群自动退群时在群内发送的提示,支持变量{memberCount}, {threshold}"),
848
+ smallGroupQuitMessage: import_koishi.Schema.string().default("该群人数过少({memberCount}人),不满足最低人数要求({threshold}人),机器人将自动退出。").description("小群自动退群时在群内发送的提示,支持变量{memberCount}, {threshold}, {groupName}, {groupId}"),
764
849
  smallGroupNotifyAdmin: import_koishi.Schema.boolean().default(true).description("小群自动退群时通知管理员"),
765
850
  smallGroupCheckDelay: import_koishi.Schema.number().default(3e3).description("小群检测延迟(毫秒),加入群聊后等待一段时间再获取群信息以确保数据准确")
766
851
  }).description("基础群组管理")
package/lib/state.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * 跨模块共享状态
3
+ * 用于在 invite 和 basic 等模块间传递信息
4
+ */
5
+ /** 管理员已审核通过的群号集合(approve 指令通过后添加) */
6
+ export declare const approvedGroups: 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.9",
4
+ "version": "0.2.11",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [