koishi-plugin-bind-bot 2.2.9 → 2.4.0

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.js CHANGED
@@ -11,6 +11,7 @@ const rate_limiter_1 = require("./utils/rate-limiter");
11
11
  const helpers_1 = require("./utils/helpers");
12
12
  const error_utils_1 = require("./utils/error-utils");
13
13
  const mcidbind_repository_1 = require("./repositories/mcidbind.repository");
14
+ const supabase_client_1 = require("./utils/supabase-client");
14
15
  const schedule_mute_repository_1 = require("./repositories/schedule-mute.repository");
15
16
  const handlers_1 = require("./handlers");
16
17
  const service_container_1 = require("./services/service-container");
@@ -40,9 +41,6 @@ exports.Config = koishi_1.Schema.object({
40
41
  showMcSkin: koishi_1.Schema.boolean()
41
42
  .description('是否使用MC皮肤渲染图(需要先开启showAvatar)')
42
43
  .default(false),
43
- zminfoApiUrl: koishi_1.Schema.string()
44
- .description('ZMINFO API地址')
45
- .default('https://zminfo-api.wittf.com'),
46
44
  enableLotteryBroadcast: koishi_1.Schema.boolean().description('是否启用天选开奖播报功能').default(false),
47
45
  lotteryTargetGroupId: koishi_1.Schema.string().description('天选开奖播报目标群ID'),
48
46
  lotteryTargetPrivateId: koishi_1.Schema.string().description('天选开奖播报私聊目标ID(格式:private:QQ号)'),
@@ -53,6 +51,12 @@ exports.Config = koishi_1.Schema.object({
53
51
  forceBindTargetUpUid: koishi_1.Schema.number().description('强制绑定目标UP主UID').default(686127),
54
52
  forceBindTargetRoomId: koishi_1.Schema.number().description('强制绑定目标房间号').default(544853),
55
53
  forceBindTargetMedalName: koishi_1.Schema.string().description('强制绑定目标粉丝牌名称').default('生态'),
54
+ supabaseUrl: koishi_1.Schema.string()
55
+ .description('Supabase项目URL')
56
+ .default(''),
57
+ supabaseKey: koishi_1.Schema.string()
58
+ .description('Supabase API Key (anon/service_role key)')
59
+ .default(''),
56
60
  groupRequestReview: koishi_1.Schema.object({
57
61
  enabled: koishi_1.Schema.boolean().description('是否启用入群申请审批功能').default(false),
58
62
  targetGroupId: koishi_1.Schema.string()
@@ -109,7 +113,8 @@ function apply(ctx, config) {
109
113
  const logger = new koishi_1.Logger('bind-bot');
110
114
  const loggerService = new logger_1.LoggerService(logger, config.debugMode);
111
115
  // 创建数据仓储实例
112
- const mcidbindRepo = new mcidbind_repository_1.MCIDBINDRepository(ctx, loggerService);
116
+ const supabaseClient = new supabase_client_1.SupabaseClient({ url: config.supabaseUrl, key: config.supabaseKey });
117
+ const mcidbindRepo = new mcidbind_repository_1.MCIDBINDRepository(supabaseClient, loggerService);
113
118
  const scheduleMuteRepo = new schedule_mute_repository_1.ScheduleMuteRepository(ctx, loggerService);
114
119
  // 交互型绑定会话管理
115
120
  const bindingSessions = new Map();
@@ -583,299 +588,7 @@ function apply(ctx, config) {
583
588
  content.body = 'Internal Server Error';
584
589
  }
585
590
  });
586
- // 在数据库中创建MCIDBIND表
587
- ctx.model.extend('mcidbind', {
588
- qqId: {
589
- type: 'string'
590
- },
591
- mcUsername: {
592
- type: 'string',
593
- initial: null
594
- },
595
- mcUuid: {
596
- type: 'string',
597
- initial: null
598
- },
599
- lastModified: {
600
- type: 'timestamp',
601
- initial: null
602
- },
603
- isAdmin: {
604
- type: 'boolean',
605
- initial: false
606
- },
607
- whitelist: {
608
- type: 'json',
609
- initial: []
610
- },
611
- tags: {
612
- type: 'json',
613
- initial: []
614
- },
615
- // BUID相关字段
616
- buidUid: {
617
- type: 'string',
618
- initial: ''
619
- },
620
- buidUsername: {
621
- type: 'string',
622
- initial: ''
623
- },
624
- guardLevel: {
625
- type: 'integer',
626
- initial: 0
627
- },
628
- guardLevelText: {
629
- type: 'string',
630
- initial: ''
631
- },
632
- maxGuardLevel: {
633
- type: 'integer',
634
- initial: 0
635
- },
636
- maxGuardLevelText: {
637
- type: 'string',
638
- initial: ''
639
- },
640
- medalName: {
641
- type: 'string',
642
- initial: ''
643
- },
644
- medalLevel: {
645
- type: 'integer',
646
- initial: 0
647
- },
648
- wealthMedalLevel: {
649
- type: 'integer',
650
- initial: 0
651
- },
652
- lastActiveTime: {
653
- type: 'timestamp',
654
- initial: null
655
- },
656
- reminderCount: {
657
- type: 'integer',
658
- initial: 0
659
- },
660
- usernameLastChecked: {
661
- type: 'timestamp',
662
- initial: null
663
- },
664
- usernameCheckFailCount: {
665
- type: 'integer',
666
- initial: 0
667
- },
668
- // 绑定状态标志字段
669
- hasMcBind: {
670
- type: 'boolean',
671
- initial: false
672
- },
673
- hasBuidBind: {
674
- type: 'boolean',
675
- initial: false
676
- }
677
- }, {
678
- // 设置主键为qqId
679
- primary: 'qqId',
680
- // 添加索引
681
- unique: [['mcUsername'], ['buidUid']],
682
- // 添加isAdmin索引,提高查询效率
683
- indexes: [['isAdmin'], ['buidUid']]
684
- });
685
- // 检查表结构是否包含旧字段
686
- const checkTableStructure = async () => {
687
- try {
688
- // 尝试获取一条记录来检查字段
689
- const records = await mcidbindRepo.findAll({ limit: 1 });
690
- // 如果没有记录,不需要迁移
691
- if (!records || records.length === 0)
692
- return false;
693
- // 检查记录中是否包含id或userId字段,或缺少whitelist字段
694
- const record = records[0];
695
- return 'id' in record || 'userId' in record || !('whitelist' in record);
696
- }
697
- catch (error) {
698
- logger.error(`[初始化] 检查表结构失败: ${error.message}`);
699
- return false;
700
- }
701
- };
702
- // 添加缺失字段
703
- const addMissingFields = async () => {
704
- try {
705
- // 获取所有记录
706
- const records = await mcidbindRepo.findAll();
707
- let updatedCount = 0;
708
- // 更新每个缺少字段的记录
709
- for (const record of records) {
710
- let needUpdate = false;
711
- const updateData = {};
712
- const qqId = record.qqId; // 提前提取 qqId,避免类型推断问题
713
- // 检查并添加whitelist字段
714
- if (!record.whitelist) {
715
- updateData.whitelist = [];
716
- needUpdate = true;
717
- }
718
- // 检查并添加tags字段
719
- if (!record.tags) {
720
- updateData.tags = [];
721
- needUpdate = true;
722
- }
723
- // 检查并添加maxGuardLevel字段
724
- if (!('maxGuardLevel' in record)) {
725
- updateData.maxGuardLevel = 0;
726
- needUpdate = true;
727
- }
728
- // 检查并添加maxGuardLevelText字段
729
- if (!('maxGuardLevelText' in record)) {
730
- updateData.maxGuardLevelText = '';
731
- needUpdate = true;
732
- }
733
- // 检查并添加reminderCount字段
734
- if (!('reminderCount' in record)) {
735
- updateData.reminderCount = 0;
736
- needUpdate = true;
737
- }
738
- // 检查并修复hasMcBind字段(数据迁移 + 数据一致性检查)
739
- const currentHasMcBind = record.hasMcBind;
740
- const mcUsername = record.mcUsername;
741
- const hasValidMc = !!(mcUsername && !mcUsername.startsWith('_temp_'));
742
- // 情况1:字段不存在,需要添加
743
- if (currentHasMcBind === undefined || currentHasMcBind === null) {
744
- updateData.hasMcBind = hasValidMc;
745
- needUpdate = true;
746
- logger.debug(`[数据迁移] 添加hasMcBind字段 QQ(${qqId}): ${hasValidMc}`);
747
- }
748
- // 情况2:字段存在但值不正确,需要修复
749
- else if (currentHasMcBind !== hasValidMc) {
750
- updateData.hasMcBind = hasValidMc;
751
- needUpdate = true;
752
- logger.info(`[数据修复] 修正hasMcBind QQ(${qqId}): ${currentHasMcBind} -> ${hasValidMc}`);
753
- }
754
- // 清理临时用户名(无论hasMcBind字段是否存在)
755
- if (!hasValidMc && mcUsername && mcUsername.startsWith('_temp_')) {
756
- updateData.mcUsername = null;
757
- updateData.mcUuid = null;
758
- updateData.whitelist = [];
759
- needUpdate = true;
760
- logger.info(`[数据清理] 清理QQ(${qqId})的临时用户名: ${mcUsername}`);
761
- }
762
- // 检查并修复hasBuidBind字段(数据迁移 + 数据一致性检查)
763
- const currentHasBuidBind = record.hasBuidBind;
764
- const buidUid = record.buidUid;
765
- const hasValidBuid = !!(buidUid && buidUid.length > 0);
766
- // 情况1:字段不存在,需要添加
767
- if (currentHasBuidBind === undefined || currentHasBuidBind === null) {
768
- updateData.hasBuidBind = hasValidBuid;
769
- needUpdate = true;
770
- logger.debug(`[数据迁移] 添加hasBuidBind字段 QQ(${qqId}): ${hasValidBuid}`);
771
- }
772
- // 情况2:字段存在但值不正确,需要修复
773
- else if (currentHasBuidBind !== hasValidBuid) {
774
- updateData.hasBuidBind = hasValidBuid;
775
- needUpdate = true;
776
- logger.info(`[数据修复] 修正hasBuidBind QQ(${qqId}): ${currentHasBuidBind} -> ${hasValidBuid}`);
777
- }
778
- // 如果需要更新,执行更新操作
779
- if (needUpdate) {
780
- await mcidbindRepo.update(qqId, updateData);
781
- updatedCount++;
782
- }
783
- }
784
- if (updatedCount > 0) {
785
- logger.info(`[初始化] 成功为${updatedCount}条记录添加缺失字段`);
786
- }
787
- else {
788
- logger.info('[初始化] 所有记录都包含必要字段,无需更新');
789
- }
790
- return true;
791
- }
792
- catch (error) {
793
- logger.error(`[初始化] 添加缺失字段失败: ${error.message}`);
794
- return false;
795
- }
796
- };
797
- // 在插件启动时执行数据迁移(放在函数定义之后)
798
- ctx.on('ready', async () => {
799
- logger.info('[初始化] 开始数据迁移和一致性检查...');
800
- await addMissingFields();
801
- });
802
- // 重建MCIDBIND表
803
- const rebuildMcidBindTable = async () => {
804
- try {
805
- // 备份现有数据
806
- const oldRecords = await mcidbindRepo.findAll();
807
- logger.info(`[初始化] 成功备份${oldRecords.length}条记录`);
808
- // 创建数据备份(用于恢复)
809
- const backupData = JSON.parse(JSON.stringify(oldRecords));
810
- try {
811
- // 提取有效数据
812
- const validRecords = oldRecords
813
- .map(record => {
814
- // 确保qqId存在
815
- if (!record.qqId) {
816
- // 如果没有qqId但有userId,尝试从userId提取
817
- if ('userId' in record && record.userId) {
818
- record.qqId = normalizeQQId(String(record.userId));
819
- }
820
- else {
821
- // 既没有qqId也没有userId,跳过此记录
822
- return null;
823
- }
824
- }
825
- return {
826
- qqId: record.qqId,
827
- mcUsername: record.mcUsername || '',
828
- mcUuid: record.mcUuid || '',
829
- lastModified: record.lastModified || new Date(),
830
- isAdmin: record.isAdmin || false,
831
- whitelist: record.whitelist || [],
832
- tags: record.tags || []
833
- };
834
- })
835
- .filter(record => record !== null);
836
- // 删除现有表
837
- await mcidbindRepo.deleteAll();
838
- logger.info('[初始化] 成功删除旧表数据');
839
- // 重新创建记录
840
- let successCount = 0;
841
- let errorCount = 0;
842
- for (const record of validRecords) {
843
- try {
844
- await mcidbindRepo.create(record);
845
- successCount++;
846
- }
847
- catch (e) {
848
- errorCount++;
849
- logger.warn(`[初始化] 重建记录失败 (QQ=${record.qqId}): ${e.message}`);
850
- }
851
- }
852
- logger.info(`[初始化] 成功重建了${successCount}条记录,失败${errorCount}条`);
853
- return true;
854
- }
855
- catch (migrationError) {
856
- // 迁移过程出错,尝试恢复
857
- logger.error(`[初始化] 表重建过程失败,尝试恢复数据: ${migrationError.message}`);
858
- try {
859
- // 清空表以避免重复数据
860
- await mcidbindRepo.deleteAll();
861
- // 恢复原始数据
862
- for (const record of backupData) {
863
- await mcidbindRepo.create(record);
864
- }
865
- logger.info(`[初始化] 成功恢复${backupData.length}条原始记录`);
866
- }
867
- catch (recoveryError) {
868
- logger.error(`[初始化] 数据恢复失败,可能导致数据丢失: ${recoveryError.message}`);
869
- throw new Error('数据迁移失败且无法恢复');
870
- }
871
- throw migrationError;
872
- }
873
- }
874
- catch (error) {
875
- logger.error(`[初始化] 重建表失败: ${error.message}`);
876
- throw error;
877
- }
878
- };
591
+ // mcidbind 表已迁移至 Supabase,无需在 Koishi 中注册模型
879
592
  // 处理用户ID,去除平台前缀,只保留QQ号
880
593
  const normalizeQQId = (userId) => {
881
594
  // 处理空值情况
@@ -1143,7 +856,6 @@ function apply(ctx, config) {
1143
856
  // 创建 ForceBinder 实例
1144
857
  const forceBindConfig = {
1145
858
  SESSDATA: config.forceBindSessdata,
1146
- zminfoApiUrl: config.zminfoApiUrl,
1147
859
  targetUpUid: config.forceBindTargetUpUid,
1148
860
  targetRoomId: config.forceBindTargetRoomId,
1149
861
  targetMedalName: config.forceBindTargetMedalName,
@@ -1763,8 +1475,8 @@ function apply(ctx, config) {
1763
1475
  ]);
1764
1476
  return;
1765
1477
  }
1766
- // 验证UID是否存在
1767
- const buidUser = await services.api.validateBUID(actualUid);
1478
+ // 验证UID是否存在(通过Supabase events表查询)
1479
+ const buidUser = await mcidbindRepo.validateBuidUid(actualUid);
1768
1480
  if (!buidUser) {
1769
1481
  logger.warn(`[交互绑定] QQ(${normalizedUserId})输入的B站UID"${actualUid}"不存在`);
1770
1482
  await sendMessage(session, [
@@ -1801,16 +1513,6 @@ function apply(ctx, config) {
1801
1513
  }
1802
1514
  // 发送完整的绑定成功消息
1803
1515
  const buidInfo = `B站UID: ${buidUser.uid}\n用户名: ${buidUser.username}`;
1804
- let extraInfo = '';
1805
- if (buidUser.guard_level > 0) {
1806
- extraInfo += `\n舰长等级: ${buidUser.guard_level_text} (${buidUser.guard_level})`;
1807
- }
1808
- if (buidUser.medal) {
1809
- extraInfo += `\n粉丝牌: ${buidUser.medal.name} Lv.${buidUser.medal.level}`;
1810
- }
1811
- if (buidUser.wealthMedalLevel > 0) {
1812
- extraInfo += `\n荣耀等级: ${buidUser.wealthMedalLevel}`;
1813
- }
1814
1516
  // 准备完成消息
1815
1517
  const displayMcName = bindingSession.mcUsername || null;
1816
1518
  const mcInfo = displayMcName ? `MC: ${displayMcName}` : 'MC: 未绑定';
@@ -1820,7 +1522,7 @@ function apply(ctx, config) {
1820
1522
  extraTip = `\n\n💡 您可以随时使用 ${formatCommand('mcid bind <用户名>')} 绑定MC账号`;
1821
1523
  }
1822
1524
  await sendMessage(session, [
1823
- koishi_1.h.text(`🎉 绑定完成!\n${mcInfo}\nB站: ${buidUser.username}${extraInfo}${extraTip}`),
1525
+ koishi_1.h.text(`🎉 绑定完成!\n${mcInfo}\nB站: ${buidUser.username}${extraTip}`),
1824
1526
  ...(config?.showAvatar
1825
1527
  ? [koishi_1.h.image(`https://workers.vrp.moe/bilibili/avatar/${buidUser.uid}?size=160`)]
1826
1528
  : [])
@@ -1,117 +1,39 @@
1
- import { Context } from 'koishi';
2
1
  import { LoggerService } from '../utils/logger';
2
+ import { SupabaseClient } from '../utils/supabase-client';
3
3
  import type { MCIDBIND } from '../types';
4
4
  /**
5
5
  * MCIDBIND 数据仓储类
6
- * 封装所有 MCIDBIND 表的数据库操作
6
+ * 通过 Supabase REST API 操作 user 表
7
7
  */
8
8
  export declare class MCIDBINDRepository {
9
- private ctx;
9
+ private supabase;
10
10
  private logger;
11
- constructor(ctx: Context, logger: LoggerService);
12
- /**
13
- * 根据 QQ 号查询绑定信息
14
- * @param qqId QQ号(已规范化)
15
- * @returns 绑定信息或 null
16
- */
11
+ constructor(supabase: SupabaseClient, logger: LoggerService);
17
12
  findByQQId(qqId: string): Promise<MCIDBIND | null>;
18
- /**
19
- * 根据 MC 用户名查询绑定信息(精确匹配)
20
- * @param mcUsername MC用户名
21
- * @returns 绑定信息或 null
22
- */
23
13
  findByMCUsername(mcUsername: string): Promise<MCIDBIND | null>;
24
- /**
25
- * 根据 MC 用户名查询绑定信息(不区分大小写)
26
- * @param mcUsername MC用户名
27
- * @returns 绑定信息或 null
28
- */
29
14
  findByUsernameIgnoreCase(mcUsername: string): Promise<MCIDBIND | null>;
30
- /**
31
- * 根据 MC UUID 查询绑定信息
32
- * @param mcUuid MC UUID(可带或不带连字符)
33
- * @returns 绑定信息或 null
34
- */
35
15
  findByUuid(mcUuid: string): Promise<MCIDBIND | null>;
36
- /**
37
- * 根据 B站 UID 查询绑定信息
38
- * @param buidUid B站UID
39
- * @returns 绑定信息或 null
40
- */
16
+ validateBuidUid(uid: string): Promise<{
17
+ uid: string;
18
+ username: string;
19
+ } | null>;
41
20
  findByBuidUid(buidUid: string): Promise<MCIDBIND | null>;
42
- /**
43
- * 获取所有绑定记录
44
- * @param options 查询选项
45
- * @returns 绑定记录列表
46
- */
47
21
  findAll(options?: {
48
22
  limit?: number;
49
23
  }): Promise<MCIDBIND[]>;
50
- /**
51
- * 根据标签查询绑定记录
52
- * @param tag 标签名称
53
- * @returns 包含该标签的绑定记录列表
54
- */
55
24
  findByTag(tag: string): Promise<MCIDBIND[]>;
56
- /**
57
- * 创建新的绑定记录
58
- * @param data 绑定数据
59
- * @returns 创建的记录
60
- */
61
25
  create(data: Partial<MCIDBIND> & {
62
26
  qqId: string;
63
27
  }): Promise<MCIDBIND>;
64
- /**
65
- * 更新绑定记录(部分字段)
66
- * @param qqId QQ号
67
- * @param data 要更新的字段
68
- */
69
28
  update(qqId: string, data: Partial<MCIDBIND>): Promise<void>;
70
- /**
71
- * 删除绑定记录
72
- * @param qqId QQ号
73
- * @returns 删除的记录数
74
- */
75
29
  delete(qqId: string): Promise<number>;
76
- /**
77
- * 删除所有绑定记录
78
- * @returns 删除的记录数
79
- */
80
30
  deleteAll(): Promise<number>;
81
- /**
82
- * 批量创建绑定记录
83
- * @param records 绑定记录列表
84
- */
85
31
  batchCreate(records: Array<Partial<MCIDBIND> & {
86
32
  qqId: string;
87
33
  }>): Promise<void>;
88
- /**
89
- * 为用户添加标签
90
- * @param qqId QQ号
91
- * @param tag 标签名称
92
- */
93
34
  addTag(qqId: string, tag: string): Promise<void>;
94
- /**
95
- * 为用户移除标签
96
- * @param qqId QQ号
97
- * @param tag 标签名称
98
- */
99
35
  removeTag(qqId: string, tag: string): Promise<void>;
100
- /**
101
- * 为用户添加白名单服务器
102
- * @param qqId QQ号
103
- * @param serverId 服务器ID
104
- */
105
36
  addWhitelist(qqId: string, serverId: string): Promise<void>;
106
- /**
107
- * 为用户移除白名单服务器
108
- * @param qqId QQ号
109
- * @param serverId 服务器ID
110
- */
111
37
  removeWhitelist(qqId: string, serverId: string): Promise<void>;
112
- /**
113
- * 获取所有管理员
114
- * @returns 管理员列表
115
- */
116
38
  findAllAdmins(): Promise<MCIDBIND[]>;
117
39
  }