koishi-plugin-bind-bot 2.2.9 → 2.3.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/export-utils.js +2 -1
- package/lib/handlers/binding.handler.js +2 -1
- package/lib/handlers/buid.handler.js +7 -10
- package/lib/handlers/group-request-review.handler.js +0 -6
- package/lib/handlers/mcid.handler.js +10 -9
- package/lib/index.js +10 -294
- package/lib/repositories/mcidbind.repository.d.ts +4 -86
- package/lib/repositories/mcidbind.repository.js +61 -127
- package/lib/services/database.service.js +0 -6
- package/lib/types/config.d.ts +4 -0
- package/lib/types/database.d.ts +0 -6
- package/lib/types/update-data.d.ts +0 -11
- package/lib/utils/helpers.d.ts +1 -9
- package/lib/utils/helpers.js +5 -0
- package/lib/utils/supabase-client.d.ts +14 -0
- package/lib/utils/supabase-client.js +44 -0
- package/package.json +1 -1
package/lib/export-utils.js
CHANGED
|
@@ -38,6 +38,7 @@ const XLSX = __importStar(require("xlsx"));
|
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
40
|
const bind_status_1 = require("./utils/bind-status");
|
|
41
|
+
const helpers_1 = require("./utils/helpers");
|
|
41
42
|
class GroupExporter {
|
|
42
43
|
logger;
|
|
43
44
|
ctx;
|
|
@@ -169,7 +170,7 @@ class GroupExporter {
|
|
|
169
170
|
MC_UUID: binding?.mcUuid || '',
|
|
170
171
|
B站UID: binding?.buidUid || '',
|
|
171
172
|
B站用户名: binding?.buidUsername || '',
|
|
172
|
-
舰长等级: binding?.
|
|
173
|
+
舰长等级: (0, helpers_1.getGuardLevelText)(binding?.guardLevel),
|
|
173
174
|
粉丝牌名称: binding?.medalName || '',
|
|
174
175
|
粉丝牌等级: binding?.medalLevel ? binding.medalLevel.toString() : '',
|
|
175
176
|
荣耀等级: binding?.wealthMedalLevel ? binding.wealthMedalLevel.toString() : '',
|
|
@@ -4,6 +4,7 @@ exports.BindingHandler = void 0;
|
|
|
4
4
|
const koishi_1 = require("koishi");
|
|
5
5
|
const base_handler_1 = require("./base.handler");
|
|
6
6
|
const bind_status_1 = require("../utils/bind-status");
|
|
7
|
+
const helpers_1 = require("../utils/helpers");
|
|
7
8
|
/**
|
|
8
9
|
* 交互式绑定命令处理器
|
|
9
10
|
* 处理 "绑定" 命令,引导用户完成 MC 和 B站双重绑定
|
|
@@ -25,7 +26,7 @@ class BindingHandler extends base_handler_1.BaseHandler {
|
|
|
25
26
|
const displayUsername = bind_status_1.BindStatus.getDisplayMcUsername(bind, '未绑定');
|
|
26
27
|
let bindInfo = `${prefix}\n✅ MC账号: ${displayUsername}\n✅ B站账号: ${bind.buidUsername} (UID: ${bind.buidUid})`;
|
|
27
28
|
if (bind.guardLevel > 0) {
|
|
28
|
-
bindInfo += `\n舰长等级: ${bind.
|
|
29
|
+
bindInfo += `\n舰长等级: ${(0, helpers_1.getGuardLevelText)(bind.guardLevel)}`;
|
|
29
30
|
}
|
|
30
31
|
if (bind.medalName) {
|
|
31
32
|
bindInfo += `\n粉丝牌: ${bind.medalName} Lv.${bind.medalLevel}`;
|
|
@@ -8,6 +8,7 @@ const koishi_1 = require("koishi");
|
|
|
8
8
|
const base_handler_1 = require("./base.handler");
|
|
9
9
|
const axios_1 = __importDefault(require("axios"));
|
|
10
10
|
const bind_status_1 = require("../utils/bind-status");
|
|
11
|
+
const helpers_1 = require("../utils/helpers");
|
|
11
12
|
/**
|
|
12
13
|
* BUID 命令处理器
|
|
13
14
|
* 处理 B站账号相关命令
|
|
@@ -78,13 +79,13 @@ class BuidHandler extends base_handler_1.BaseHandler {
|
|
|
78
79
|
const userInfo = `${target ? `用户 ${bind.qqId} 的` : '您的'}B站账号信息:\nB站UID: ${bind.buidUid}\n用户名: ${bind.buidUsername}`;
|
|
79
80
|
let detailInfo = '';
|
|
80
81
|
if (bind.guardLevel > 0) {
|
|
81
|
-
detailInfo += `\n舰长等级: ${bind.
|
|
82
|
+
detailInfo += `\n舰长等级: ${(0, helpers_1.getGuardLevelText)(bind.guardLevel)} (${bind.guardLevel})`;
|
|
82
83
|
if (bind.maxGuardLevel > 0 && bind.maxGuardLevel < bind.guardLevel) {
|
|
83
|
-
detailInfo += `\n历史最高: ${bind.
|
|
84
|
+
detailInfo += `\n历史最高: ${(0, helpers_1.getGuardLevelText)(bind.maxGuardLevel)} (${bind.maxGuardLevel})`;
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
else if (bind.maxGuardLevel > 0) {
|
|
87
|
-
detailInfo += `\n历史舰长: ${bind.
|
|
88
|
+
detailInfo += `\n历史舰长: ${(0, helpers_1.getGuardLevelText)(bind.maxGuardLevel)} (${bind.maxGuardLevel})`;
|
|
88
89
|
}
|
|
89
90
|
detailInfo += `\n粉丝牌: ${bind.medalName || '无'} Lv.${bind.medalLevel || 0}`;
|
|
90
91
|
detailInfo += `\n荣耀等级: ${bind.wealthMedalLevel || 0}`;
|
|
@@ -161,13 +162,13 @@ class BuidHandler extends base_handler_1.BaseHandler {
|
|
|
161
162
|
// 构建详细信息
|
|
162
163
|
let adminInfo = `B站UID"${bind.buidUid}"绑定信息:\nQQ号: ${bind.qqId}\n用户名: ${bind.buidUsername}`;
|
|
163
164
|
if (bind.guardLevel > 0) {
|
|
164
|
-
adminInfo += `\n舰长等级: ${bind.
|
|
165
|
+
adminInfo += `\n舰长等级: ${(0, helpers_1.getGuardLevelText)(bind.guardLevel)} (${bind.guardLevel})`;
|
|
165
166
|
if (bind.maxGuardLevel > 0 && bind.maxGuardLevel < bind.guardLevel) {
|
|
166
|
-
adminInfo += `\n历史最高: ${bind.
|
|
167
|
+
adminInfo += `\n历史最高: ${(0, helpers_1.getGuardLevelText)(bind.maxGuardLevel)} (${bind.maxGuardLevel})`;
|
|
167
168
|
}
|
|
168
169
|
}
|
|
169
170
|
else if (bind.maxGuardLevel > 0) {
|
|
170
|
-
adminInfo += `\n历史舰长: ${bind.
|
|
171
|
+
adminInfo += `\n历史舰长: ${(0, helpers_1.getGuardLevelText)(bind.maxGuardLevel)} (${bind.maxGuardLevel})`;
|
|
171
172
|
}
|
|
172
173
|
if (bind.medalName) {
|
|
173
174
|
adminInfo += `\n粉丝牌: ${bind.medalName} Lv.${bind.medalLevel}`;
|
|
@@ -374,9 +375,7 @@ class BuidHandler extends base_handler_1.BaseHandler {
|
|
|
374
375
|
buidUid: buidUser.uid,
|
|
375
376
|
buidUsername: buidUser.username,
|
|
376
377
|
guardLevel: buidUser.guard_level || 0,
|
|
377
|
-
guardLevelText: buidUser.guard_level_text || '',
|
|
378
378
|
maxGuardLevel: buidUser.max_guard_level || 0,
|
|
379
|
-
maxGuardLevelText: buidUser.max_guard_level_text || '',
|
|
380
379
|
medalName: buidUser.medal?.name || '',
|
|
381
380
|
medalLevel: buidUser.medal?.level || 0,
|
|
382
381
|
wealthMedalLevel: buidUser.wealthMedalLevel || 0,
|
|
@@ -431,9 +430,7 @@ class BuidHandler extends base_handler_1.BaseHandler {
|
|
|
431
430
|
const updateData = {
|
|
432
431
|
buidUsername: buidUser.username,
|
|
433
432
|
guardLevel: buidUser.guard_level || 0,
|
|
434
|
-
guardLevelText: buidUser.guard_level_text || '',
|
|
435
433
|
maxGuardLevel: buidUser.max_guard_level || 0,
|
|
436
|
-
maxGuardLevelText: buidUser.max_guard_level_text || '',
|
|
437
434
|
medalName: buidUser.medal?.name || '',
|
|
438
435
|
medalLevel: buidUser.medal?.level || 0,
|
|
439
436
|
wealthMedalLevel: buidUser.wealthMedalLevel || 0,
|
|
@@ -638,9 +638,7 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
638
638
|
buidUid: zminfoUser.uid,
|
|
639
639
|
buidUsername: zminfoUser.username,
|
|
640
640
|
guardLevel: zminfoUser.guard_level || 0,
|
|
641
|
-
guardLevelText: zminfoUser.guard_level_text || '',
|
|
642
641
|
maxGuardLevel: zminfoUser.guard_level || 0,
|
|
643
|
-
maxGuardLevelText: zminfoUser.guard_level_text || '',
|
|
644
642
|
medalName: zminfoUser.medal?.name || '',
|
|
645
643
|
medalLevel: zminfoUser.medal?.level || 0,
|
|
646
644
|
wealthMedalLevel: zminfoUser.wealthMedalLevel || 0,
|
|
@@ -659,11 +657,7 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
659
657
|
buidUid: zminfoUser.uid,
|
|
660
658
|
buidUsername: zminfoUser.username,
|
|
661
659
|
guardLevel: zminfoUser.guard_level || 0,
|
|
662
|
-
guardLevelText: zminfoUser.guard_level_text || '',
|
|
663
660
|
maxGuardLevel: Math.max(bind.maxGuardLevel || 0, zminfoUser.guard_level || 0),
|
|
664
|
-
maxGuardLevelText: zminfoUser.guard_level > (bind.maxGuardLevel || 0)
|
|
665
|
-
? zminfoUser.guard_level_text
|
|
666
|
-
: bind.maxGuardLevelText,
|
|
667
661
|
medalName: zminfoUser.medal?.name || '',
|
|
668
662
|
medalLevel: zminfoUser.medal?.level || 0,
|
|
669
663
|
wealthMedalLevel: zminfoUser.wealthMedalLevel || 0,
|
|
@@ -4,6 +4,7 @@ exports.McidCommandHandler = void 0;
|
|
|
4
4
|
const koishi_1 = require("koishi");
|
|
5
5
|
const base_handler_1 = require("./base.handler");
|
|
6
6
|
const bind_status_1 = require("../utils/bind-status");
|
|
7
|
+
const helpers_1 = require("../utils/helpers");
|
|
7
8
|
class McidCommandHandler extends base_handler_1.BaseHandler {
|
|
8
9
|
/**
|
|
9
10
|
* 注册所有MCID命令
|
|
@@ -92,14 +93,14 @@ class McidCommandHandler extends base_handler_1.BaseHandler {
|
|
|
92
93
|
if (refreshedBind) {
|
|
93
94
|
let buidInfo = `该用户尚未绑定MC账号\n\nB站账号信息:\nB站UID: ${refreshedBind.buidUid}\n用户名: ${refreshedBind.buidUsername}`;
|
|
94
95
|
if (refreshedBind.guardLevel > 0) {
|
|
95
|
-
buidInfo += `\n舰长等级: ${refreshedBind.
|
|
96
|
+
buidInfo += `\n舰长等级: ${(0, helpers_1.getGuardLevelText)(refreshedBind.guardLevel)} (${refreshedBind.guardLevel})`;
|
|
96
97
|
if (refreshedBind.maxGuardLevel > 0 &&
|
|
97
98
|
refreshedBind.maxGuardLevel < refreshedBind.guardLevel) {
|
|
98
|
-
buidInfo += `\n历史最高: ${refreshedBind.
|
|
99
|
+
buidInfo += `\n历史最高: ${(0, helpers_1.getGuardLevelText)(refreshedBind.maxGuardLevel)} (${refreshedBind.maxGuardLevel})`;
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
else if (refreshedBind.maxGuardLevel > 0) {
|
|
102
|
-
buidInfo += `\n历史舰长: ${refreshedBind.
|
|
103
|
+
buidInfo += `\n历史舰长: ${(0, helpers_1.getGuardLevelText)(refreshedBind.maxGuardLevel)} (${refreshedBind.maxGuardLevel})`;
|
|
103
104
|
}
|
|
104
105
|
if (refreshedBind.medalName) {
|
|
105
106
|
buidInfo += `\n粉丝牌: ${refreshedBind.medalName} Lv.${refreshedBind.medalLevel}`;
|
|
@@ -132,14 +133,14 @@ class McidCommandHandler extends base_handler_1.BaseHandler {
|
|
|
132
133
|
if (refreshedBind) {
|
|
133
134
|
let buidInfo = `您尚未绑定MC账号\n\nB站账号信息:\nB站UID: ${refreshedBind.buidUid}\n用户名: ${refreshedBind.buidUsername}`;
|
|
134
135
|
if (refreshedBind.guardLevel > 0) {
|
|
135
|
-
buidInfo += `\n舰长等级: ${refreshedBind.
|
|
136
|
+
buidInfo += `\n舰长等级: ${(0, helpers_1.getGuardLevelText)(refreshedBind.guardLevel)} (${refreshedBind.guardLevel})`;
|
|
136
137
|
if (refreshedBind.maxGuardLevel > 0 &&
|
|
137
138
|
refreshedBind.maxGuardLevel < refreshedBind.guardLevel) {
|
|
138
|
-
buidInfo += `\n历史最高: ${refreshedBind.
|
|
139
|
+
buidInfo += `\n历史最高: ${(0, helpers_1.getGuardLevelText)(refreshedBind.maxGuardLevel)} (${refreshedBind.maxGuardLevel})`;
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
142
|
else if (refreshedBind.maxGuardLevel > 0) {
|
|
142
|
-
buidInfo += `\n历史舰长: ${refreshedBind.
|
|
143
|
+
buidInfo += `\n历史舰长: ${(0, helpers_1.getGuardLevelText)(refreshedBind.maxGuardLevel)} (${refreshedBind.maxGuardLevel})`;
|
|
143
144
|
}
|
|
144
145
|
if (refreshedBind.medalName) {
|
|
145
146
|
buidInfo += `\n粉丝牌: ${refreshedBind.medalName} Lv.${refreshedBind.medalLevel}`;
|
|
@@ -217,13 +218,13 @@ class McidCommandHandler extends base_handler_1.BaseHandler {
|
|
|
217
218
|
if (bind.buidUid) {
|
|
218
219
|
buidInfo = `B站账号信息:\nB站UID: ${bind.buidUid}\n用户名: ${bind.buidUsername}`;
|
|
219
220
|
if (bind.guardLevel > 0) {
|
|
220
|
-
buidInfo += `\n舰长等级: ${bind.
|
|
221
|
+
buidInfo += `\n舰长等级: ${(0, helpers_1.getGuardLevelText)(bind.guardLevel)} (${bind.guardLevel})`;
|
|
221
222
|
if (bind.maxGuardLevel > 0 && bind.maxGuardLevel < bind.guardLevel) {
|
|
222
|
-
buidInfo += `\n历史最高: ${bind.
|
|
223
|
+
buidInfo += `\n历史最高: ${(0, helpers_1.getGuardLevelText)(bind.maxGuardLevel)} (${bind.maxGuardLevel})`;
|
|
223
224
|
}
|
|
224
225
|
}
|
|
225
226
|
else if (bind.maxGuardLevel > 0) {
|
|
226
|
-
buidInfo += `\n历史舰长: ${bind.
|
|
227
|
+
buidInfo += `\n历史舰长: ${(0, helpers_1.getGuardLevelText)(bind.maxGuardLevel)} (${bind.maxGuardLevel})`;
|
|
227
228
|
}
|
|
228
229
|
if (bind.medalName) {
|
|
229
230
|
buidInfo += `\n粉丝牌: ${bind.medalName} Lv.${bind.medalLevel}`;
|
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");
|
|
@@ -53,6 +54,12 @@ exports.Config = koishi_1.Schema.object({
|
|
|
53
54
|
forceBindTargetUpUid: koishi_1.Schema.number().description('强制绑定目标UP主UID').default(686127),
|
|
54
55
|
forceBindTargetRoomId: koishi_1.Schema.number().description('强制绑定目标房间号').default(544853),
|
|
55
56
|
forceBindTargetMedalName: koishi_1.Schema.string().description('强制绑定目标粉丝牌名称').default('生态'),
|
|
57
|
+
supabaseUrl: koishi_1.Schema.string()
|
|
58
|
+
.description('Supabase项目URL')
|
|
59
|
+
.default(''),
|
|
60
|
+
supabaseKey: koishi_1.Schema.string()
|
|
61
|
+
.description('Supabase API Key (anon/service_role key)')
|
|
62
|
+
.default(''),
|
|
56
63
|
groupRequestReview: koishi_1.Schema.object({
|
|
57
64
|
enabled: koishi_1.Schema.boolean().description('是否启用入群申请审批功能').default(false),
|
|
58
65
|
targetGroupId: koishi_1.Schema.string()
|
|
@@ -109,7 +116,8 @@ function apply(ctx, config) {
|
|
|
109
116
|
const logger = new koishi_1.Logger('bind-bot');
|
|
110
117
|
const loggerService = new logger_1.LoggerService(logger, config.debugMode);
|
|
111
118
|
// 创建数据仓储实例
|
|
112
|
-
const
|
|
119
|
+
const supabaseClient = new supabase_client_1.SupabaseClient({ url: config.supabaseUrl, key: config.supabaseKey });
|
|
120
|
+
const mcidbindRepo = new mcidbind_repository_1.MCIDBINDRepository(supabaseClient, loggerService);
|
|
113
121
|
const scheduleMuteRepo = new schedule_mute_repository_1.ScheduleMuteRepository(ctx, loggerService);
|
|
114
122
|
// 交互型绑定会话管理
|
|
115
123
|
const bindingSessions = new Map();
|
|
@@ -583,299 +591,7 @@ function apply(ctx, config) {
|
|
|
583
591
|
content.body = 'Internal Server Error';
|
|
584
592
|
}
|
|
585
593
|
});
|
|
586
|
-
//
|
|
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
|
-
};
|
|
594
|
+
// mcidbind 表已迁移至 Supabase,无需在 Koishi 中注册模型
|
|
879
595
|
// 处理用户ID,去除平台前缀,只保留QQ号
|
|
880
596
|
const normalizeQQId = (userId) => {
|
|
881
597
|
// 处理空值情况
|
|
@@ -1,117 +1,35 @@
|
|
|
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
|
-
*
|
|
6
|
+
* 通过 Supabase REST API 操作 user 表
|
|
7
7
|
*/
|
|
8
8
|
export declare class MCIDBINDRepository {
|
|
9
|
-
private
|
|
9
|
+
private supabase;
|
|
10
10
|
private logger;
|
|
11
|
-
constructor(
|
|
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
|
-
*/
|
|
41
16
|
findByBuidUid(buidUid: string): Promise<MCIDBIND | null>;
|
|
42
|
-
/**
|
|
43
|
-
* 获取所有绑定记录
|
|
44
|
-
* @param options 查询选项
|
|
45
|
-
* @returns 绑定记录列表
|
|
46
|
-
*/
|
|
47
17
|
findAll(options?: {
|
|
48
18
|
limit?: number;
|
|
49
19
|
}): Promise<MCIDBIND[]>;
|
|
50
|
-
/**
|
|
51
|
-
* 根据标签查询绑定记录
|
|
52
|
-
* @param tag 标签名称
|
|
53
|
-
* @returns 包含该标签的绑定记录列表
|
|
54
|
-
*/
|
|
55
20
|
findByTag(tag: string): Promise<MCIDBIND[]>;
|
|
56
|
-
/**
|
|
57
|
-
* 创建新的绑定记录
|
|
58
|
-
* @param data 绑定数据
|
|
59
|
-
* @returns 创建的记录
|
|
60
|
-
*/
|
|
61
21
|
create(data: Partial<MCIDBIND> & {
|
|
62
22
|
qqId: string;
|
|
63
23
|
}): Promise<MCIDBIND>;
|
|
64
|
-
/**
|
|
65
|
-
* 更新绑定记录(部分字段)
|
|
66
|
-
* @param qqId QQ号
|
|
67
|
-
* @param data 要更新的字段
|
|
68
|
-
*/
|
|
69
24
|
update(qqId: string, data: Partial<MCIDBIND>): Promise<void>;
|
|
70
|
-
/**
|
|
71
|
-
* 删除绑定记录
|
|
72
|
-
* @param qqId QQ号
|
|
73
|
-
* @returns 删除的记录数
|
|
74
|
-
*/
|
|
75
25
|
delete(qqId: string): Promise<number>;
|
|
76
|
-
/**
|
|
77
|
-
* 删除所有绑定记录
|
|
78
|
-
* @returns 删除的记录数
|
|
79
|
-
*/
|
|
80
26
|
deleteAll(): Promise<number>;
|
|
81
|
-
/**
|
|
82
|
-
* 批量创建绑定记录
|
|
83
|
-
* @param records 绑定记录列表
|
|
84
|
-
*/
|
|
85
27
|
batchCreate(records: Array<Partial<MCIDBIND> & {
|
|
86
28
|
qqId: string;
|
|
87
29
|
}>): Promise<void>;
|
|
88
|
-
/**
|
|
89
|
-
* 为用户添加标签
|
|
90
|
-
* @param qqId QQ号
|
|
91
|
-
* @param tag 标签名称
|
|
92
|
-
*/
|
|
93
30
|
addTag(qqId: string, tag: string): Promise<void>;
|
|
94
|
-
/**
|
|
95
|
-
* 为用户移除标签
|
|
96
|
-
* @param qqId QQ号
|
|
97
|
-
* @param tag 标签名称
|
|
98
|
-
*/
|
|
99
31
|
removeTag(qqId: string, tag: string): Promise<void>;
|
|
100
|
-
/**
|
|
101
|
-
* 为用户添加白名单服务器
|
|
102
|
-
* @param qqId QQ号
|
|
103
|
-
* @param serverId 服务器ID
|
|
104
|
-
*/
|
|
105
32
|
addWhitelist(qqId: string, serverId: string): Promise<void>;
|
|
106
|
-
/**
|
|
107
|
-
* 为用户移除白名单服务器
|
|
108
|
-
* @param qqId QQ号
|
|
109
|
-
* @param serverId 服务器ID
|
|
110
|
-
*/
|
|
111
33
|
removeWhitelist(qqId: string, serverId: string): Promise<void>;
|
|
112
|
-
/**
|
|
113
|
-
* 获取所有管理员
|
|
114
|
-
* @returns 管理员列表
|
|
115
|
-
*/
|
|
116
34
|
findAllAdmins(): Promise<MCIDBIND[]>;
|
|
117
35
|
}
|
|
@@ -2,171 +2,143 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MCIDBINDRepository = void 0;
|
|
4
4
|
const helpers_1 = require("../utils/helpers");
|
|
5
|
+
const TABLE = 'user';
|
|
6
|
+
const DATE_FIELDS = ['lastModified', 'usernameLastChecked', 'lastActiveTime'];
|
|
7
|
+
function deserialize(row) {
|
|
8
|
+
if (!row)
|
|
9
|
+
return row;
|
|
10
|
+
for (const field of DATE_FIELDS) {
|
|
11
|
+
if (row[field] && typeof row[field] === 'string') {
|
|
12
|
+
row[field] = new Date(row[field]);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return row;
|
|
16
|
+
}
|
|
17
|
+
function serializeDates(data) {
|
|
18
|
+
const out = { ...data };
|
|
19
|
+
for (const field of DATE_FIELDS) {
|
|
20
|
+
if (out[field] instanceof Date) {
|
|
21
|
+
out[field] = out[field].toISOString();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
5
26
|
/**
|
|
6
27
|
* MCIDBIND 数据仓储类
|
|
7
|
-
*
|
|
28
|
+
* 通过 Supabase REST API 操作 user 表
|
|
8
29
|
*/
|
|
9
30
|
class MCIDBINDRepository {
|
|
10
|
-
|
|
31
|
+
supabase;
|
|
11
32
|
logger;
|
|
12
|
-
constructor(
|
|
13
|
-
this.
|
|
33
|
+
constructor(supabase, logger) {
|
|
34
|
+
this.supabase = supabase;
|
|
14
35
|
this.logger = logger;
|
|
15
36
|
}
|
|
16
|
-
/**
|
|
17
|
-
* 根据 QQ 号查询绑定信息
|
|
18
|
-
* @param qqId QQ号(已规范化)
|
|
19
|
-
* @returns 绑定信息或 null
|
|
20
|
-
*/
|
|
21
37
|
async findByQQId(qqId) {
|
|
22
38
|
try {
|
|
23
39
|
this.logger.debug('数据库', `查询QQ(${qqId})的绑定信息`);
|
|
24
|
-
const
|
|
25
|
-
return
|
|
40
|
+
const rows = await this.supabase.get(TABLE, `qqId=eq.${qqId}&limit=1`);
|
|
41
|
+
return rows.length > 0 ? deserialize(rows[0]) : null;
|
|
26
42
|
}
|
|
27
43
|
catch (error) {
|
|
28
44
|
this.logger.error('数据库', `查询QQ(${qqId})绑定信息失败: ${error.message}`);
|
|
29
45
|
return null;
|
|
30
46
|
}
|
|
31
47
|
}
|
|
32
|
-
/**
|
|
33
|
-
* 根据 MC 用户名查询绑定信息(精确匹配)
|
|
34
|
-
* @param mcUsername MC用户名
|
|
35
|
-
* @returns 绑定信息或 null
|
|
36
|
-
*/
|
|
37
48
|
async findByMCUsername(mcUsername) {
|
|
38
49
|
try {
|
|
39
50
|
this.logger.debug('数据库', `查询MC用户名(${mcUsername})的绑定信息`);
|
|
40
|
-
const
|
|
41
|
-
return
|
|
51
|
+
const rows = await this.supabase.get(TABLE, `mcUsername=eq.${encodeURIComponent(mcUsername)}&limit=1`);
|
|
52
|
+
return rows.length > 0 ? deserialize(rows[0]) : null;
|
|
42
53
|
}
|
|
43
54
|
catch (error) {
|
|
44
55
|
this.logger.error('数据库', `查询MC用户名(${mcUsername})绑定信息失败: ${error.message}`);
|
|
45
56
|
return null;
|
|
46
57
|
}
|
|
47
58
|
}
|
|
48
|
-
/**
|
|
49
|
-
* 根据 MC 用户名查询绑定信息(不区分大小写)
|
|
50
|
-
* @param mcUsername MC用户名
|
|
51
|
-
* @returns 绑定信息或 null
|
|
52
|
-
*/
|
|
53
59
|
async findByUsernameIgnoreCase(mcUsername) {
|
|
54
60
|
try {
|
|
55
61
|
const normalizedInput = (0, helpers_1.normalizeUsername)(mcUsername, this.logger.getRawLogger());
|
|
56
62
|
this.logger.debug('数据库', `查询MC用户名(${mcUsername} -> ${normalizedInput})的绑定信息(不区分大小写)`);
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
return match || null;
|
|
63
|
+
const rows = await this.supabase.get(TABLE, `mcUsername=ilike.${encodeURIComponent(mcUsername)}`);
|
|
64
|
+
const match = rows.find(bind => bind.mcUsername && (0, helpers_1.normalizeUsername)(bind.mcUsername) === normalizedInput);
|
|
65
|
+
return match ? deserialize(match) : null;
|
|
61
66
|
}
|
|
62
67
|
catch (error) {
|
|
63
68
|
this.logger.error('数据库', `查询MC用户名(${mcUsername})绑定信息失败(不区分大小写): ${error.message}`);
|
|
64
69
|
return null;
|
|
65
70
|
}
|
|
66
71
|
}
|
|
67
|
-
/**
|
|
68
|
-
* 根据 MC UUID 查询绑定信息
|
|
69
|
-
* @param mcUuid MC UUID(可带或不带连字符)
|
|
70
|
-
* @returns 绑定信息或 null
|
|
71
|
-
*/
|
|
72
72
|
async findByUuid(mcUuid) {
|
|
73
73
|
try {
|
|
74
|
-
// 规范化 UUID(移除连字符)
|
|
75
74
|
const cleanUuid = mcUuid.replace(/-/g, '');
|
|
76
75
|
this.logger.debug('数据库', `查询MC UUID(${cleanUuid})的绑定信息`);
|
|
77
|
-
// 先尝试精确匹配
|
|
78
|
-
let binds = await this.ctx.database.get('mcidbind', { mcUuid: cleanUuid });
|
|
79
|
-
if (binds.length > 0)
|
|
80
|
-
return binds[0];
|
|
81
|
-
// 尝试带连字符的格式
|
|
82
76
|
const formattedUuid = `${cleanUuid.substring(0, 8)}-${cleanUuid.substring(8, 12)}-${cleanUuid.substring(12, 16)}-${cleanUuid.substring(16, 20)}-${cleanUuid.substring(20)}`;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
77
|
+
// 用 or 一次查两种格式
|
|
78
|
+
const rows = await this.supabase.get(TABLE, `or=(mcUuid.eq.${cleanUuid},mcUuid.eq.${formattedUuid})&limit=1`);
|
|
79
|
+
if (rows.length > 0)
|
|
80
|
+
return deserialize(rows[0]);
|
|
81
|
+
// 兜底:用 ilike 模糊匹配
|
|
82
|
+
const fallback = await this.supabase.get(TABLE, `mcUuid=ilike.*${cleanUuid}*&limit=1`);
|
|
83
|
+
if (fallback.length > 0)
|
|
84
|
+
return deserialize(fallback[0]);
|
|
85
|
+
return null;
|
|
90
86
|
}
|
|
91
87
|
catch (error) {
|
|
92
88
|
this.logger.error('数据库', `查询MC UUID(${mcUuid})绑定信息失败: ${error.message}`);
|
|
93
89
|
return null;
|
|
94
90
|
}
|
|
95
91
|
}
|
|
96
|
-
/**
|
|
97
|
-
* 根据 B站 UID 查询绑定信息
|
|
98
|
-
* @param buidUid B站UID
|
|
99
|
-
* @returns 绑定信息或 null
|
|
100
|
-
*/
|
|
101
92
|
async findByBuidUid(buidUid) {
|
|
102
93
|
try {
|
|
103
94
|
this.logger.debug('数据库', `查询B站UID(${buidUid})的绑定信息`);
|
|
104
|
-
const
|
|
105
|
-
return
|
|
95
|
+
const rows = await this.supabase.get(TABLE, `buidUid=eq.${buidUid}&limit=1`);
|
|
96
|
+
return rows.length > 0 ? deserialize(rows[0]) : null;
|
|
106
97
|
}
|
|
107
98
|
catch (error) {
|
|
108
99
|
this.logger.error('数据库', `查询B站UID(${buidUid})绑定信息失败: ${error.message}`);
|
|
109
100
|
return null;
|
|
110
101
|
}
|
|
111
102
|
}
|
|
112
|
-
/**
|
|
113
|
-
* 获取所有绑定记录
|
|
114
|
-
* @param options 查询选项
|
|
115
|
-
* @returns 绑定记录列表
|
|
116
|
-
*/
|
|
117
103
|
async findAll(options) {
|
|
118
104
|
try {
|
|
119
105
|
this.logger.debug('数据库', `获取所有绑定记录${options?.limit ? ` (limit: ${options.limit})` : ''}`);
|
|
120
|
-
const
|
|
121
|
-
|
|
106
|
+
const query = options?.limit ? `limit=${options.limit}` : '';
|
|
107
|
+
const rows = await this.supabase.get(TABLE, query);
|
|
108
|
+
return rows.map(deserialize);
|
|
122
109
|
}
|
|
123
110
|
catch (error) {
|
|
124
111
|
this.logger.error('数据库', `获取所有绑定记录失败: ${error.message}`);
|
|
125
112
|
return [];
|
|
126
113
|
}
|
|
127
114
|
}
|
|
128
|
-
/**
|
|
129
|
-
* 根据标签查询绑定记录
|
|
130
|
-
* @param tag 标签名称
|
|
131
|
-
* @returns 包含该标签的绑定记录列表
|
|
132
|
-
*/
|
|
133
115
|
async findByTag(tag) {
|
|
134
116
|
try {
|
|
135
117
|
this.logger.debug('数据库', `查询包含标签"${tag}"的绑定记录`);
|
|
136
|
-
const
|
|
137
|
-
return
|
|
118
|
+
const rows = await this.supabase.get(TABLE, `tags=cs.${encodeURIComponent(JSON.stringify([tag]))}`);
|
|
119
|
+
return rows.map(deserialize);
|
|
138
120
|
}
|
|
139
121
|
catch (error) {
|
|
140
122
|
this.logger.error('数据库', `查询标签"${tag}"的绑定记录失败: ${error.message}`);
|
|
141
123
|
return [];
|
|
142
124
|
}
|
|
143
125
|
}
|
|
144
|
-
/**
|
|
145
|
-
* 创建新的绑定记录
|
|
146
|
-
* @param data 绑定数据
|
|
147
|
-
* @returns 创建的记录
|
|
148
|
-
*/
|
|
149
126
|
async create(data) {
|
|
150
127
|
try {
|
|
151
128
|
this.logger.debug('数据库', `创建QQ(${data.qqId})的绑定记录`);
|
|
152
|
-
const created = await this.
|
|
129
|
+
const created = await this.supabase.post(TABLE, serializeDates(data));
|
|
153
130
|
this.logger.info('数据库', `成功创建QQ(${data.qqId})的绑定记录`, true);
|
|
154
|
-
return created;
|
|
131
|
+
return deserialize(created);
|
|
155
132
|
}
|
|
156
133
|
catch (error) {
|
|
157
134
|
this.logger.error('数据库', `创建QQ(${data.qqId})绑定记录失败: ${error.message}`);
|
|
158
135
|
throw error;
|
|
159
136
|
}
|
|
160
137
|
}
|
|
161
|
-
/**
|
|
162
|
-
* 更新绑定记录(部分字段)
|
|
163
|
-
* @param qqId QQ号
|
|
164
|
-
* @param data 要更新的字段
|
|
165
|
-
*/
|
|
166
138
|
async update(qqId, data) {
|
|
167
139
|
try {
|
|
168
140
|
this.logger.debug('数据库', `更新QQ(${qqId})的绑定记录`);
|
|
169
|
-
await this.
|
|
141
|
+
await this.supabase.patch(TABLE, `qqId=eq.${qqId}`, serializeDates(data));
|
|
170
142
|
this.logger.info('数据库', `成功更新QQ(${qqId})的绑定记录`, true);
|
|
171
143
|
}
|
|
172
144
|
catch (error) {
|
|
@@ -174,49 +146,35 @@ class MCIDBINDRepository {
|
|
|
174
146
|
throw error;
|
|
175
147
|
}
|
|
176
148
|
}
|
|
177
|
-
/**
|
|
178
|
-
* 删除绑定记录
|
|
179
|
-
* @param qqId QQ号
|
|
180
|
-
* @returns 删除的记录数
|
|
181
|
-
*/
|
|
182
149
|
async delete(qqId) {
|
|
183
150
|
try {
|
|
184
151
|
this.logger.debug('数据库', `删除QQ(${qqId})的绑定记录`);
|
|
185
|
-
const
|
|
186
|
-
this.logger.info('数据库', `成功删除QQ(${qqId})的绑定记录(删除${
|
|
187
|
-
return
|
|
152
|
+
const removed = await this.supabase.del(TABLE, `qqId=eq.${qqId}`);
|
|
153
|
+
this.logger.info('数据库', `成功删除QQ(${qqId})的绑定记录(删除${removed}条)`, true);
|
|
154
|
+
return removed;
|
|
188
155
|
}
|
|
189
156
|
catch (error) {
|
|
190
157
|
this.logger.error('数据库', `删除QQ(${qqId})绑定记录失败: ${error.message}`);
|
|
191
158
|
throw error;
|
|
192
159
|
}
|
|
193
160
|
}
|
|
194
|
-
/**
|
|
195
|
-
* 删除所有绑定记录
|
|
196
|
-
* @returns 删除的记录数
|
|
197
|
-
*/
|
|
198
161
|
async deleteAll() {
|
|
199
162
|
try {
|
|
200
163
|
this.logger.debug('数据库', '删除所有绑定记录');
|
|
201
|
-
const
|
|
202
|
-
this.logger.info('数据库', `成功删除所有绑定记录(删除${
|
|
203
|
-
return
|
|
164
|
+
const removed = await this.supabase.del(TABLE, 'qqId=not.is.null');
|
|
165
|
+
this.logger.info('数据库', `成功删除所有绑定记录(删除${removed}条)`, true);
|
|
166
|
+
return removed;
|
|
204
167
|
}
|
|
205
168
|
catch (error) {
|
|
206
169
|
this.logger.error('数据库', `删除所有绑定记录失败: ${error.message}`);
|
|
207
170
|
throw error;
|
|
208
171
|
}
|
|
209
172
|
}
|
|
210
|
-
/**
|
|
211
|
-
* 批量创建绑定记录
|
|
212
|
-
* @param records 绑定记录列表
|
|
213
|
-
*/
|
|
214
173
|
async batchCreate(records) {
|
|
215
174
|
try {
|
|
216
175
|
this.logger.debug('数据库', `批量创建${records.length}条绑定记录`);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
176
|
+
const serialized = records.map(r => serializeDates(r));
|
|
177
|
+
await this.supabase.postMany(TABLE, serialized);
|
|
220
178
|
this.logger.info('数据库', `成功批量创建${records.length}条绑定记录`, true);
|
|
221
179
|
}
|
|
222
180
|
catch (error) {
|
|
@@ -224,11 +182,6 @@ class MCIDBINDRepository {
|
|
|
224
182
|
throw error;
|
|
225
183
|
}
|
|
226
184
|
}
|
|
227
|
-
/**
|
|
228
|
-
* 为用户添加标签
|
|
229
|
-
* @param qqId QQ号
|
|
230
|
-
* @param tag 标签名称
|
|
231
|
-
*/
|
|
232
185
|
async addTag(qqId, tag) {
|
|
233
186
|
try {
|
|
234
187
|
const bind = await this.findByQQId(qqId);
|
|
@@ -247,11 +200,6 @@ class MCIDBINDRepository {
|
|
|
247
200
|
throw error;
|
|
248
201
|
}
|
|
249
202
|
}
|
|
250
|
-
/**
|
|
251
|
-
* 为用户移除标签
|
|
252
|
-
* @param qqId QQ号
|
|
253
|
-
* @param tag 标签名称
|
|
254
|
-
*/
|
|
255
203
|
async removeTag(qqId, tag) {
|
|
256
204
|
try {
|
|
257
205
|
const bind = await this.findByQQId(qqId);
|
|
@@ -271,11 +219,6 @@ class MCIDBINDRepository {
|
|
|
271
219
|
throw error;
|
|
272
220
|
}
|
|
273
221
|
}
|
|
274
|
-
/**
|
|
275
|
-
* 为用户添加白名单服务器
|
|
276
|
-
* @param qqId QQ号
|
|
277
|
-
* @param serverId 服务器ID
|
|
278
|
-
*/
|
|
279
222
|
async addWhitelist(qqId, serverId) {
|
|
280
223
|
try {
|
|
281
224
|
const bind = await this.findByQQId(qqId);
|
|
@@ -294,11 +237,6 @@ class MCIDBINDRepository {
|
|
|
294
237
|
throw error;
|
|
295
238
|
}
|
|
296
239
|
}
|
|
297
|
-
/**
|
|
298
|
-
* 为用户移除白名单服务器
|
|
299
|
-
* @param qqId QQ号
|
|
300
|
-
* @param serverId 服务器ID
|
|
301
|
-
*/
|
|
302
240
|
async removeWhitelist(qqId, serverId) {
|
|
303
241
|
try {
|
|
304
242
|
const bind = await this.findByQQId(qqId);
|
|
@@ -318,15 +256,11 @@ class MCIDBINDRepository {
|
|
|
318
256
|
throw error;
|
|
319
257
|
}
|
|
320
258
|
}
|
|
321
|
-
/**
|
|
322
|
-
* 获取所有管理员
|
|
323
|
-
* @returns 管理员列表
|
|
324
|
-
*/
|
|
325
259
|
async findAllAdmins() {
|
|
326
260
|
try {
|
|
327
261
|
this.logger.debug('数据库', '获取所有管理员');
|
|
328
|
-
const
|
|
329
|
-
return
|
|
262
|
+
const rows = await this.supabase.get(TABLE, 'isAdmin=eq.true');
|
|
263
|
+
return rows.map(deserialize);
|
|
330
264
|
}
|
|
331
265
|
catch (error) {
|
|
332
266
|
this.logger.error('数据库', `获取所有管理员失败: ${error.message}`);
|
|
@@ -274,9 +274,7 @@ class DatabaseService {
|
|
|
274
274
|
buidUid: buidUser.uid.toString(), // 转换为字符串存储
|
|
275
275
|
buidUsername: buidUser.username,
|
|
276
276
|
guardLevel: buidUser.guard_level || 0,
|
|
277
|
-
guardLevelText: buidUser.guard_level_text || '',
|
|
278
277
|
maxGuardLevel: buidUser.max_guard_level || 0,
|
|
279
|
-
maxGuardLevelText: buidUser.max_guard_level_text || '',
|
|
280
278
|
medalName: buidUser.medal?.name || '',
|
|
281
279
|
medalLevel: buidUser.medal?.level || 0,
|
|
282
280
|
wealthMedalLevel: buidUser.wealthMedalLevel || 0,
|
|
@@ -334,9 +332,7 @@ class DatabaseService {
|
|
|
334
332
|
const updateData = {
|
|
335
333
|
buidUsername: buidUser.username,
|
|
336
334
|
guardLevel: buidUser.guard_level || 0,
|
|
337
|
-
guardLevelText: buidUser.guard_level_text || '',
|
|
338
335
|
maxGuardLevel: buidUser.max_guard_level || 0,
|
|
339
|
-
maxGuardLevelText: buidUser.max_guard_level_text || '',
|
|
340
336
|
medalName: buidUser.medal?.name || '',
|
|
341
337
|
medalLevel: buidUser.medal?.level || 0,
|
|
342
338
|
wealthMedalLevel: buidUser.wealthMedalLevel || 0,
|
|
@@ -386,9 +382,7 @@ class DatabaseService {
|
|
|
386
382
|
buidUid: null,
|
|
387
383
|
buidUsername: null,
|
|
388
384
|
guardLevel: 0,
|
|
389
|
-
guardLevelText: '',
|
|
390
385
|
maxGuardLevel: 0,
|
|
391
|
-
maxGuardLevelText: '',
|
|
392
386
|
medalName: '',
|
|
393
387
|
medalLevel: 0,
|
|
394
388
|
wealthMedalLevel: 0,
|
package/lib/types/config.d.ts
CHANGED
|
@@ -53,6 +53,10 @@ export interface Config {
|
|
|
53
53
|
forceBindTargetRoomId: number;
|
|
54
54
|
/** 强制绑定目标粉丝牌名称 */
|
|
55
55
|
forceBindTargetMedalName: string;
|
|
56
|
+
/** Supabase项目URL */
|
|
57
|
+
supabaseUrl: string;
|
|
58
|
+
/** Supabase API Key */
|
|
59
|
+
supabaseKey: string;
|
|
56
60
|
/** 入群申请审批功能配置 */
|
|
57
61
|
groupRequestReview?: GroupRequestReviewConfig;
|
|
58
62
|
}
|
package/lib/types/database.d.ts
CHANGED
|
@@ -24,9 +24,7 @@
|
|
|
24
24
|
* buidUid: '87654321',
|
|
25
25
|
* buidUsername: 'B站用户名',
|
|
26
26
|
* guardLevel: 3,
|
|
27
|
-
* guardLevelText: '舰长',
|
|
28
27
|
* maxGuardLevel: 3,
|
|
29
|
-
* maxGuardLevelText: '舰长',
|
|
30
28
|
* medalName: '粉丝牌',
|
|
31
29
|
* medalLevel: 20,
|
|
32
30
|
* wealthMedalLevel: 15,
|
|
@@ -60,12 +58,8 @@ export interface MCIDBIND {
|
|
|
60
58
|
buidUsername: string | null;
|
|
61
59
|
/** 当前舰长等级 (0=无, 1=总督, 2=提督, 3=舰长) */
|
|
62
60
|
guardLevel: number;
|
|
63
|
-
/** 当前舰长等级文本 (例: '舰长', '提督', '总督') */
|
|
64
|
-
guardLevelText: string;
|
|
65
61
|
/** 历史最高舰长等级 */
|
|
66
62
|
maxGuardLevel: number;
|
|
67
|
-
/** 历史最高舰长等级文本 */
|
|
68
|
-
maxGuardLevelText: string;
|
|
69
63
|
/** 粉丝牌名称 */
|
|
70
64
|
medalName: string;
|
|
71
65
|
/** 粉丝牌等级 */
|
|
@@ -54,9 +54,7 @@ export interface UpdateMcBindData {
|
|
|
54
54
|
* buidUid: '87654321',
|
|
55
55
|
* buidUsername: 'B站用户名',
|
|
56
56
|
* guardLevel: 3,
|
|
57
|
-
* guardLevelText: '舰长',
|
|
58
57
|
* maxGuardLevel: 3,
|
|
59
|
-
* maxGuardLevelText: '舰长',
|
|
60
58
|
* medalName: '粉丝牌',
|
|
61
59
|
* medalLevel: 20,
|
|
62
60
|
* wealthMedalLevel: 15,
|
|
@@ -72,12 +70,8 @@ export interface UpdateBuidBindData {
|
|
|
72
70
|
buidUsername?: string | null;
|
|
73
71
|
/** 当前舰长等级 */
|
|
74
72
|
guardLevel?: number;
|
|
75
|
-
/** 当前舰长等级文本 */
|
|
76
|
-
guardLevelText?: string;
|
|
77
73
|
/** 历史最高舰长等级 */
|
|
78
74
|
maxGuardLevel?: number;
|
|
79
|
-
/** 历史最高舰长等级文本 */
|
|
80
|
-
maxGuardLevelText?: string;
|
|
81
75
|
/** 粉丝牌名称 */
|
|
82
76
|
medalName?: string;
|
|
83
77
|
/** 粉丝牌等级 */
|
|
@@ -104,7 +98,6 @@ export interface UpdateBuidBindData {
|
|
|
104
98
|
* {
|
|
105
99
|
* buidUsername: '新用户名',
|
|
106
100
|
* guardLevel: 3,
|
|
107
|
-
* guardLevelText: '舰长',
|
|
108
101
|
* medalLevel: 21
|
|
109
102
|
* }
|
|
110
103
|
* ```
|
|
@@ -114,12 +107,8 @@ export interface UpdateBuidInfoData {
|
|
|
114
107
|
buidUsername?: string;
|
|
115
108
|
/** 当前舰长等级 */
|
|
116
109
|
guardLevel?: number;
|
|
117
|
-
/** 当前舰长等级文本 */
|
|
118
|
-
guardLevelText?: string;
|
|
119
110
|
/** 历史最高舰长等级 */
|
|
120
111
|
maxGuardLevel?: number;
|
|
121
|
-
/** 历史最高舰长等级文本 */
|
|
122
|
-
maxGuardLevelText?: string;
|
|
123
112
|
/** 粉丝牌名称 */
|
|
124
113
|
medalName?: string;
|
|
125
114
|
/** 粉丝牌等级 */
|
package/lib/utils/helpers.d.ts
CHANGED
|
@@ -89,15 +89,7 @@ export declare function levenshteinDistance(str1: string, str2: string): number;
|
|
|
89
89
|
* @returns 相似度值(0到1之间,1表示完全相同)
|
|
90
90
|
*/
|
|
91
91
|
export declare function calculateSimilarity(str1: string, str2: string): number;
|
|
92
|
-
|
|
93
|
-
* 规范化 Minecraft 用户名(统一小写,用于存储和比较)
|
|
94
|
-
* Minecraft 用户名不区分大小写,但 Mojang 返回的是规范大小写
|
|
95
|
-
* 为避免 "Notch" 和 "notch" 被视为不同用户,统一转小写存储
|
|
96
|
-
*
|
|
97
|
-
* @param username MC 用户名
|
|
98
|
-
* @param logger Koishi Logger实例(用于日志)
|
|
99
|
-
* @returns 规范化后的用户名(小写)
|
|
100
|
-
*/
|
|
92
|
+
export declare function getGuardLevelText(level: number): string;
|
|
101
93
|
export declare function normalizeUsername(username: string, logger?: Logger): string;
|
|
102
94
|
/**
|
|
103
95
|
* 比较两个 Minecraft 用户名是否相同(不区分大小写)
|
package/lib/utils/helpers.js
CHANGED
|
@@ -11,6 +11,7 @@ exports.escapeRegExp = escapeRegExp;
|
|
|
11
11
|
exports.cleanUserInput = cleanUserInput;
|
|
12
12
|
exports.levenshteinDistance = levenshteinDistance;
|
|
13
13
|
exports.calculateSimilarity = calculateSimilarity;
|
|
14
|
+
exports.getGuardLevelText = getGuardLevelText;
|
|
14
15
|
exports.normalizeUsername = normalizeUsername;
|
|
15
16
|
exports.isSameUsername = isSameUsername;
|
|
16
17
|
exports.extractBuidUsernameFromNickname = extractBuidUsernameFromNickname;
|
|
@@ -369,6 +370,10 @@ function calculateSimilarity(str1, str2) {
|
|
|
369
370
|
* @param logger Koishi Logger实例(用于日志)
|
|
370
371
|
* @returns 规范化后的用户名(小写)
|
|
371
372
|
*/
|
|
373
|
+
const GUARD_LEVEL_TEXT = { 1: '总督', 2: '提督', 3: '舰长' };
|
|
374
|
+
function getGuardLevelText(level) {
|
|
375
|
+
return GUARD_LEVEL_TEXT[level] || '';
|
|
376
|
+
}
|
|
372
377
|
function normalizeUsername(username, logger) {
|
|
373
378
|
if (!username) {
|
|
374
379
|
logger?.warn('[用户名规范化] 收到空用户名');
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface SupabaseConfig {
|
|
2
|
+
url: string;
|
|
3
|
+
key: string;
|
|
4
|
+
}
|
|
5
|
+
export declare class SupabaseClient {
|
|
6
|
+
private config;
|
|
7
|
+
private client;
|
|
8
|
+
constructor(config: SupabaseConfig);
|
|
9
|
+
get<T>(table: string, query?: string): Promise<T[]>;
|
|
10
|
+
post<T>(table: string, body: object | object[]): Promise<T>;
|
|
11
|
+
postMany<T>(table: string, body: object[]): Promise<T[]>;
|
|
12
|
+
patch(table: string, query: string, body: object): Promise<void>;
|
|
13
|
+
del(table: string, query: string): Promise<number>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SupabaseClient = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
class SupabaseClient {
|
|
9
|
+
config;
|
|
10
|
+
client;
|
|
11
|
+
constructor(config) {
|
|
12
|
+
this.config = config;
|
|
13
|
+
this.client = axios_1.default.create({
|
|
14
|
+
baseURL: `${config.url}/rest/v1`,
|
|
15
|
+
headers: {
|
|
16
|
+
apikey: config.key,
|
|
17
|
+
Authorization: `Bearer ${config.key}`,
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
Prefer: 'return=representation',
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
async get(table, query = '') {
|
|
24
|
+
const url = query ? `/${table}?${query}` : `/${table}`;
|
|
25
|
+
const { data } = await this.client.get(url);
|
|
26
|
+
return data;
|
|
27
|
+
}
|
|
28
|
+
async post(table, body) {
|
|
29
|
+
const { data } = await this.client.post(`/${table}`, body);
|
|
30
|
+
return Array.isArray(data) ? data[0] : data;
|
|
31
|
+
}
|
|
32
|
+
async postMany(table, body) {
|
|
33
|
+
const { data } = await this.client.post(`/${table}`, body);
|
|
34
|
+
return data;
|
|
35
|
+
}
|
|
36
|
+
async patch(table, query, body) {
|
|
37
|
+
await this.client.patch(`/${table}?${query}`, body);
|
|
38
|
+
}
|
|
39
|
+
async del(table, query) {
|
|
40
|
+
const { data } = await this.client.delete(`/${table}?${query}`);
|
|
41
|
+
return Array.isArray(data) ? data.length : 0;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.SupabaseClient = SupabaseClient;
|