koishi-plugin-bind-bot 2.2.8 → 2.2.9
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/handlers/binding.handler.d.ts +28 -0
- package/lib/handlers/binding.handler.js +168 -107
- package/lib/handlers/group-request-review.handler.d.ts +6 -1
- package/lib/handlers/group-request-review.handler.js +92 -84
- package/lib/index.js +4 -4
- package/lib/types/config.d.ts +2 -2
- package/lib/utils/bind-status.d.ts +14 -0
- package/lib/utils/bind-status.js +22 -0
- package/package.json +1 -1
|
@@ -8,6 +8,34 @@ import { LoggerService } from '../utils/logger';
|
|
|
8
8
|
export declare class BindingHandler extends BaseHandler {
|
|
9
9
|
private readonly BINDING_SESSION_TIMEOUT;
|
|
10
10
|
constructor(ctx: Context, config: any, logger: LoggerService, repos: Repositories, deps: HandlerDependencies);
|
|
11
|
+
/**
|
|
12
|
+
* 格式化绑定信息
|
|
13
|
+
* @param bind 绑定记录
|
|
14
|
+
* @param prefix 前缀文本
|
|
15
|
+
* @returns 格式化后的绑定信息
|
|
16
|
+
*/
|
|
17
|
+
private formatBindInfo;
|
|
18
|
+
/**
|
|
19
|
+
* 处理已完成全部绑定的场景
|
|
20
|
+
*/
|
|
21
|
+
private handleCompletedBinds;
|
|
22
|
+
/**
|
|
23
|
+
* 处理只绑定MC未绑定B站的场景
|
|
24
|
+
*/
|
|
25
|
+
private handleMcOnlyBind;
|
|
26
|
+
/**
|
|
27
|
+
* 处理只绑定B站未绑定MC的场景
|
|
28
|
+
*/
|
|
29
|
+
private handleBuidOnlyBind;
|
|
30
|
+
/**
|
|
31
|
+
* 处理都未绑定的场景
|
|
32
|
+
*/
|
|
33
|
+
private handleNoBind;
|
|
34
|
+
/**
|
|
35
|
+
* 处理绑定流程
|
|
36
|
+
* @param ctx 绑定流程上下文
|
|
37
|
+
*/
|
|
38
|
+
private handleBindingFlow;
|
|
11
39
|
/**
|
|
12
40
|
* 注册交互式绑定命令
|
|
13
41
|
*/
|
|
@@ -15,6 +15,156 @@ class BindingHandler extends base_handler_1.BaseHandler {
|
|
|
15
15
|
// 从配置中获取会话超时时间,默认3分钟
|
|
16
16
|
this.BINDING_SESSION_TIMEOUT = 3 * 60 * 1000;
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* 格式化绑定信息
|
|
20
|
+
* @param bind 绑定记录
|
|
21
|
+
* @param prefix 前缀文本
|
|
22
|
+
* @returns 格式化后的绑定信息
|
|
23
|
+
*/
|
|
24
|
+
formatBindInfo(bind, prefix = '') {
|
|
25
|
+
const displayUsername = bind_status_1.BindStatus.getDisplayMcUsername(bind, '未绑定');
|
|
26
|
+
let bindInfo = `${prefix}\n✅ MC账号: ${displayUsername}\n✅ B站账号: ${bind.buidUsername} (UID: ${bind.buidUid})`;
|
|
27
|
+
if (bind.guardLevel > 0) {
|
|
28
|
+
bindInfo += `\n舰长等级: ${bind.guardLevelText}`;
|
|
29
|
+
}
|
|
30
|
+
if (bind.medalName) {
|
|
31
|
+
bindInfo += `\n粉丝牌: ${bind.medalName} Lv.${bind.medalLevel}`;
|
|
32
|
+
}
|
|
33
|
+
return bindInfo;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 处理已完成全部绑定的场景
|
|
37
|
+
*/
|
|
38
|
+
async handleCompletedBinds(ctx, bind) {
|
|
39
|
+
const { session, userId, isAdminAction } = ctx;
|
|
40
|
+
this.logger.info('交互绑定', `QQ(${userId})已完成全部绑定`, true);
|
|
41
|
+
const prefix = isAdminAction
|
|
42
|
+
? `用户 ${userId} 已完成全部账号绑定:`
|
|
43
|
+
: '您已完成全部账号绑定:';
|
|
44
|
+
let bindInfo = this.formatBindInfo(bind, prefix);
|
|
45
|
+
// 用户自己绑定时显示修改提示
|
|
46
|
+
if (!isAdminAction) {
|
|
47
|
+
bindInfo += `\n\n如需修改绑定信息,请使用:\n- ${this.deps.formatCommand('mcid change <新用户名>')} 修改MC账号\n- ${this.deps.formatCommand('buid bind <新UID>')} 修改B站账号`;
|
|
48
|
+
}
|
|
49
|
+
await this.deps.sendMessage(session, [koishi_1.h.text(bindInfo)]);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 处理只绑定MC未绑定B站的场景
|
|
53
|
+
*/
|
|
54
|
+
async handleMcOnlyBind(ctx, bind) {
|
|
55
|
+
const { session, userId, rawUserId, channelId, isAdminAction } = ctx;
|
|
56
|
+
this.logger.info('交互绑定', `QQ(${userId})已绑定MC,进入B站绑定流程`, true);
|
|
57
|
+
const displayUsername = bind_status_1.BindStatus.getDisplayMcUsername(bind, '未绑定');
|
|
58
|
+
if (isAdminAction) {
|
|
59
|
+
// 管理员操作:更新会话状态并@目标用户
|
|
60
|
+
this.deps.updateBindingSession(rawUserId, channelId, {
|
|
61
|
+
state: 'waiting_buid',
|
|
62
|
+
mcUsername: bind.mcUsername,
|
|
63
|
+
mcUuid: bind.mcUuid
|
|
64
|
+
});
|
|
65
|
+
await this.deps.sendMessage(session, [
|
|
66
|
+
koishi_1.h.at(userId),
|
|
67
|
+
koishi_1.h.text(` 管理员为您启动了B站绑定流程\n🎮 已绑定MC: ${displayUsername}\n🔗 请发送您的B站UID`)
|
|
68
|
+
]);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// 用户自己操作:创建带超时的会话
|
|
72
|
+
const timeout = setTimeout(() => {
|
|
73
|
+
this.deps.bindingSessions.delete(`${userId}_${channelId}`);
|
|
74
|
+
this.ctx.bots.forEach(bot => {
|
|
75
|
+
bot
|
|
76
|
+
.sendMessage(channelId, [
|
|
77
|
+
koishi_1.h.at(userId),
|
|
78
|
+
koishi_1.h.text(' 绑定会话已超时,请重新开始绑定流程\n\n⚠️ 温馨提醒:若在管理员多次提醒后仍不配合绑定账号信息,将按群规进行相应处理。')
|
|
79
|
+
])
|
|
80
|
+
.catch(() => { });
|
|
81
|
+
});
|
|
82
|
+
this.logger.info('交互绑定', `QQ(${userId})的绑定会话因超时被清理`, true);
|
|
83
|
+
}, this.BINDING_SESSION_TIMEOUT);
|
|
84
|
+
const sessionData = {
|
|
85
|
+
userId: rawUserId,
|
|
86
|
+
channelId: channelId,
|
|
87
|
+
state: 'waiting_buid',
|
|
88
|
+
startTime: Date.now(),
|
|
89
|
+
timeout: timeout,
|
|
90
|
+
mcUsername: bind.mcUsername,
|
|
91
|
+
mcUuid: bind.mcUuid
|
|
92
|
+
};
|
|
93
|
+
this.deps.bindingSessions.set(`${userId}_${channelId}`, sessionData);
|
|
94
|
+
await this.deps.sendMessage(session, [
|
|
95
|
+
koishi_1.h.text(`🎮 已绑定MC: ${displayUsername}\n🔗 请发送您的B站UID`)
|
|
96
|
+
]);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 处理只绑定B站未绑定MC的场景
|
|
101
|
+
*/
|
|
102
|
+
async handleBuidOnlyBind(ctx, bind) {
|
|
103
|
+
const { session, userId, rawUserId, channelId, isAdminAction } = ctx;
|
|
104
|
+
this.logger.info('交互绑定', `QQ(${userId})${isAdminAction ? '已' : '只'}绑定${isAdminAction ? 'B站' : '了B站'},进入MC绑定流程`, true);
|
|
105
|
+
if (isAdminAction) {
|
|
106
|
+
// 管理员操作:更新会话状态并@目标用户
|
|
107
|
+
this.deps.updateBindingSession(rawUserId, channelId, {
|
|
108
|
+
state: 'waiting_mc_username',
|
|
109
|
+
buidUid: bind.buidUid,
|
|
110
|
+
buidUsername: bind.buidUsername
|
|
111
|
+
});
|
|
112
|
+
await this.deps.sendMessage(session, [
|
|
113
|
+
koishi_1.h.at(userId),
|
|
114
|
+
koishi_1.h.text(` 管理员为您启动了MC绑定流程\n✅ 已绑定B站: ${bind.buidUsername}\n🎮 请发送您的MC用户名,或发送"跳过"保持当前状态`)
|
|
115
|
+
]);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// 用户自己操作:创建会话并更新状态
|
|
119
|
+
this.deps.createBindingSession(rawUserId, channelId, 'waiting_mc_username');
|
|
120
|
+
const bindingSession = this.deps.getBindingSession(rawUserId, channelId);
|
|
121
|
+
bindingSession.state = 'waiting_mc_username';
|
|
122
|
+
await this.deps.sendMessage(session, [
|
|
123
|
+
koishi_1.h.text(`✅ 已绑定B站: ${bind.buidUsername}\n🎮 请发送您的MC用户名,或发送"跳过"保持当前状态`)
|
|
124
|
+
]);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 处理都未绑定的场景
|
|
129
|
+
*/
|
|
130
|
+
async handleNoBind(ctx) {
|
|
131
|
+
const { session, userId, isAdminAction } = ctx;
|
|
132
|
+
const messageElements = isAdminAction
|
|
133
|
+
? [
|
|
134
|
+
koishi_1.h.at(userId),
|
|
135
|
+
koishi_1.h.text(' 管理员为您启动了账号绑定流程\n📋 请选择绑定方式:\n1. 发送您的B站UID进行B站绑定\n2. 发送"跳过"仅绑定MC账号')
|
|
136
|
+
]
|
|
137
|
+
: [koishi_1.h.text('📋 请选择绑定方式:\n1. 发送您的B站UID进行B站绑定\n2. 发送"跳过"仅绑定MC账号')];
|
|
138
|
+
await this.deps.sendMessage(session, messageElements);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* 处理绑定流程
|
|
142
|
+
* @param ctx 绑定流程上下文
|
|
143
|
+
*/
|
|
144
|
+
async handleBindingFlow(ctx) {
|
|
145
|
+
const { userId, rawUserId, channelId, bind, isAdminAction } = ctx;
|
|
146
|
+
// 场景1: 已完成全部绑定
|
|
147
|
+
if (bind_status_1.BindStatus.hasCompletedAllBinds(bind)) {
|
|
148
|
+
return this.handleCompletedBinds(ctx, bind);
|
|
149
|
+
}
|
|
150
|
+
// 创建绑定会话(用户自己绑定时在后续场景中创建)
|
|
151
|
+
if (isAdminAction) {
|
|
152
|
+
this.deps.createBindingSession(rawUserId, channelId, 'waiting_buid');
|
|
153
|
+
}
|
|
154
|
+
// 场景2: 只绑定MC → 进入B站绑定流程
|
|
155
|
+
if (bind_status_1.BindStatus.hasOnlyMcBind(bind)) {
|
|
156
|
+
return this.handleMcOnlyBind(ctx, bind);
|
|
157
|
+
}
|
|
158
|
+
// 场景3: 只绑定B站 → 进入MC绑定流程
|
|
159
|
+
if (bind_status_1.BindStatus.hasOnlyBuidBind(bind)) {
|
|
160
|
+
return this.handleBuidOnlyBind(ctx, bind);
|
|
161
|
+
}
|
|
162
|
+
// 场景4: 都未绑定 → 显示选择菜单
|
|
163
|
+
if (!isAdminAction) {
|
|
164
|
+
this.deps.createBindingSession(rawUserId, channelId, 'waiting_buid');
|
|
165
|
+
}
|
|
166
|
+
return this.handleNoBind(ctx);
|
|
167
|
+
}
|
|
18
168
|
/**
|
|
19
169
|
* 注册交互式绑定命令
|
|
20
170
|
*/
|
|
@@ -58,48 +208,16 @@ class BindingHandler extends base_handler_1.BaseHandler {
|
|
|
58
208
|
koishi_1.h.text(`用户 ${normalizedTargetId} 已有进行中的绑定会话`)
|
|
59
209
|
]);
|
|
60
210
|
}
|
|
61
|
-
//
|
|
211
|
+
// 检查目标用户当前绑定状态并处理绑定流程
|
|
62
212
|
const targetBind = await this.deps.databaseService.getMcBindByQQId(normalizedTargetId);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
if (targetBind.medalName) {
|
|
73
|
-
bindInfo += `\n粉丝牌: ${targetBind.medalName} Lv.${targetBind.medalLevel}`;
|
|
74
|
-
}
|
|
75
|
-
return this.deps.sendMessage(session, [koishi_1.h.text(bindInfo)]);
|
|
76
|
-
}
|
|
77
|
-
// 为目标用户创建绑定会话
|
|
78
|
-
this.deps.createBindingSession(target, channelId, 'waiting_buid');
|
|
79
|
-
// 如果已绑定MC但未绑定B站,直接进入B站绑定流程
|
|
80
|
-
if (bind_status_1.BindStatus.hasValidMcBind(targetBind) && !bind_status_1.BindStatus.hasValidBuidBind(targetBind)) {
|
|
81
|
-
this.logger.info('交互绑定', `QQ(${normalizedTargetId})已绑定MC,进入B站绑定流程`, true);
|
|
82
|
-
// 更新会话状态
|
|
83
|
-
this.deps.updateBindingSession(target, channelId, {
|
|
84
|
-
state: 'waiting_buid',
|
|
85
|
-
mcUsername: bind_status_1.BindStatus.hasValidMcBind(targetBind)
|
|
86
|
-
? targetBind.mcUsername
|
|
87
|
-
: null,
|
|
88
|
-
mcUuid: targetBind.mcUuid
|
|
89
|
-
});
|
|
90
|
-
// 向目标用户发送提示(@他们)
|
|
91
|
-
const displayUsername = bind_status_1.BindStatus.getDisplayMcUsername(targetBind, '未绑定');
|
|
92
|
-
await this.deps.sendMessage(session, [
|
|
93
|
-
koishi_1.h.at(normalizedTargetId),
|
|
94
|
-
koishi_1.h.text(` 管理员为您启动了B站绑定流程\n🎮 已绑定MC: ${displayUsername}\n🔗 请发送您的B站UID`)
|
|
95
|
-
]);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
// 向目标用户发送提示(@他们)
|
|
99
|
-
await this.deps.sendMessage(session, [
|
|
100
|
-
koishi_1.h.at(normalizedTargetId),
|
|
101
|
-
koishi_1.h.text(' 管理员为您启动了账号绑定流程\n📋 请选择绑定方式:\n1. 发送您的B站UID进行B站绑定\n2. 发送"跳过"仅绑定MC账号')
|
|
102
|
-
]);
|
|
213
|
+
await this.handleBindingFlow({
|
|
214
|
+
session,
|
|
215
|
+
userId: normalizedTargetId,
|
|
216
|
+
rawUserId: target,
|
|
217
|
+
channelId,
|
|
218
|
+
bind: targetBind,
|
|
219
|
+
isAdminAction: true
|
|
220
|
+
});
|
|
103
221
|
return;
|
|
104
222
|
}
|
|
105
223
|
// 为自己启动绑定流程
|
|
@@ -112,73 +230,16 @@ class BindingHandler extends base_handler_1.BaseHandler {
|
|
|
112
230
|
koishi_1.h.text('您已有进行中的绑定会话,请先完成当前绑定或等待会话超时')
|
|
113
231
|
]);
|
|
114
232
|
}
|
|
115
|
-
//
|
|
233
|
+
// 检查用户当前绑定状态并处理绑定流程
|
|
116
234
|
const existingBind = await this.deps.databaseService.getMcBindByQQId(normalizedUserId);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
if (existingBind.medalName) {
|
|
127
|
-
bindInfo += `\n粉丝牌: ${existingBind.medalName} Lv.${existingBind.medalLevel}`;
|
|
128
|
-
}
|
|
129
|
-
bindInfo += `\n\n如需修改绑定信息,请使用:\n- ${this.deps.formatCommand('mcid change <新用户名>')} 修改MC账号\n- ${this.deps.formatCommand('buid bind <新UID>')} 修改B站账号`;
|
|
130
|
-
return this.deps.sendMessage(session, [koishi_1.h.text(bindInfo)]);
|
|
131
|
-
}
|
|
132
|
-
// 如果已绑定MC(且不是temp用户名)但未绑定B站,直接进入B站绑定流程
|
|
133
|
-
if (existingBind && bind_status_1.BindStatus.hasValidMcBind(existingBind) && !existingBind.buidUid) {
|
|
134
|
-
this.logger.info('交互绑定', `QQ(${normalizedUserId})已绑定MC,进入B站绑定流程`, true);
|
|
135
|
-
// 创建绑定会话,状态直接设为等待B站UID
|
|
136
|
-
const timeout = setTimeout(() => {
|
|
137
|
-
this.deps.bindingSessions.delete(`${normalizedUserId}_${channelId}`);
|
|
138
|
-
this.ctx.bots.forEach(bot => {
|
|
139
|
-
bot
|
|
140
|
-
.sendMessage(channelId, [
|
|
141
|
-
koishi_1.h.at(normalizedUserId),
|
|
142
|
-
koishi_1.h.text(' 绑定会话已超时,请重新开始绑定流程\n\n⚠️ 温馨提醒:若在管理员多次提醒后仍不配合绑定账号信息,将按群规进行相应处理。')
|
|
143
|
-
])
|
|
144
|
-
.catch(() => { });
|
|
145
|
-
});
|
|
146
|
-
this.logger.info('交互绑定', `QQ(${normalizedUserId})的绑定会话因超时被清理`, true);
|
|
147
|
-
}, this.BINDING_SESSION_TIMEOUT);
|
|
148
|
-
const sessionData = {
|
|
149
|
-
userId: session.userId,
|
|
150
|
-
channelId: channelId,
|
|
151
|
-
state: 'waiting_buid',
|
|
152
|
-
startTime: Date.now(),
|
|
153
|
-
timeout: timeout,
|
|
154
|
-
mcUsername: existingBind.mcUsername,
|
|
155
|
-
mcUuid: existingBind.mcUuid
|
|
156
|
-
};
|
|
157
|
-
this.deps.bindingSessions.set(`${normalizedUserId}_${channelId}`, sessionData);
|
|
158
|
-
return this.deps.sendMessage(session, [
|
|
159
|
-
koishi_1.h.text(`🎮 已绑定MC: ${existingBind.mcUsername}\n🔗 请发送您的B站UID`)
|
|
160
|
-
]);
|
|
161
|
-
}
|
|
162
|
-
// 如果只绑定了B站(MC是temp用户名),提醒绑定MC账号
|
|
163
|
-
if (existingBind &&
|
|
164
|
-
existingBind.buidUid &&
|
|
165
|
-
existingBind.buidUsername &&
|
|
166
|
-
!bind_status_1.BindStatus.hasValidMcBind(existingBind)) {
|
|
167
|
-
this.logger.info('交互绑定', `QQ(${normalizedUserId})只绑定了B站,进入MC绑定流程`, true);
|
|
168
|
-
// 创建绑定会话,状态设为等待MC用户名
|
|
169
|
-
this.deps.createBindingSession(session.userId, channelId, 'waiting_mc_username');
|
|
170
|
-
const bindingSession = this.deps.getBindingSession(session.userId, channelId);
|
|
171
|
-
bindingSession.state = 'waiting_mc_username';
|
|
172
|
-
return this.deps.sendMessage(session, [
|
|
173
|
-
koishi_1.h.text(`✅ 已绑定B站: ${existingBind.buidUsername}\n🎮 请发送您的MC用户名,或发送"跳过"保持当前状态`)
|
|
174
|
-
]);
|
|
175
|
-
}
|
|
176
|
-
// 如果未绑定账号,让用户选择绑定方式,优先B站绑定
|
|
177
|
-
this.deps.createBindingSession(session.userId, channelId, 'waiting_buid');
|
|
178
|
-
// 发送绑定选项提示
|
|
179
|
-
return this.deps.sendMessage(session, [
|
|
180
|
-
koishi_1.h.text('📋 请选择绑定方式:\n1. 发送您的B站UID进行B站绑定\n2. 发送"跳过"仅绑定MC账号')
|
|
181
|
-
]);
|
|
235
|
+
await this.handleBindingFlow({
|
|
236
|
+
session,
|
|
237
|
+
userId: normalizedUserId,
|
|
238
|
+
rawUserId: session.userId,
|
|
239
|
+
channelId,
|
|
240
|
+
bind: existingBind,
|
|
241
|
+
isAdminAction: false
|
|
242
|
+
});
|
|
182
243
|
}
|
|
183
244
|
catch (error) {
|
|
184
245
|
const normalizedUserId = this.deps.normalizeQQId(session.userId);
|
|
@@ -87,9 +87,14 @@ export declare class GroupRequestReviewHandler extends BaseHandler {
|
|
|
87
87
|
*/
|
|
88
88
|
private notifyAdmin;
|
|
89
89
|
/**
|
|
90
|
-
*
|
|
90
|
+
* 执行自动绑定(仅数据库操作,不设置昵称)
|
|
91
|
+
* @returns 绑定信息,包含B站用户名和MC用户名
|
|
91
92
|
*/
|
|
92
93
|
private performAutoBind;
|
|
94
|
+
/**
|
|
95
|
+
* 用户进群后设置群昵称
|
|
96
|
+
*/
|
|
97
|
+
private setGroupNicknameAfterJoin;
|
|
93
98
|
/**
|
|
94
99
|
* 定时清理过期记录
|
|
95
100
|
*/
|
|
@@ -292,7 +292,7 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
292
292
|
else {
|
|
293
293
|
elements.push(koishi_1.h.text(`⚠️ 未提供有效的 B 站 UID\n\n`));
|
|
294
294
|
}
|
|
295
|
-
elements.push(koishi_1.h.text('请点击表情回应:\n'), koishi_1.h.text('
|
|
295
|
+
elements.push(koishi_1.h.text('请点击表情回应:\n'), koishi_1.h.text('/赞 - 通过并自动绑定\n'), koishi_1.h.text('/OK - 通过并交互式绑定\n'), koishi_1.h.text('/NO - 拒绝申请'));
|
|
296
296
|
try {
|
|
297
297
|
const result = await session.bot.sendMessage(this.reviewConfig.reviewGroupId, elements);
|
|
298
298
|
// result 通常是数组,第一个元素是消息ID
|
|
@@ -366,21 +366,23 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
366
366
|
return;
|
|
367
367
|
}
|
|
368
368
|
this.logger.info('入群审批', `开始自动绑定 - QQ: ${pendingReq.applicantQQ}, UID: ${uid}`);
|
|
369
|
-
// 2.
|
|
370
|
-
await this.performAutoBind(pendingReq.applicantQQ, uid, session.bot);
|
|
369
|
+
// 2. 先执行绑定(在批准入群前,仅数据库操作,不设置昵称)
|
|
370
|
+
const bindResult = await this.performAutoBind(pendingReq.applicantQQ, uid, session.bot);
|
|
371
371
|
this.logger.info('入群审批', `预绑定完成 - QQ: ${pendingReq.applicantQQ}, UID: ${uid}`);
|
|
372
372
|
// 3. 批准入群
|
|
373
373
|
await session.bot.handleGuildMemberRequest(pendingReq.requestFlag, true, '欢迎加入!');
|
|
374
374
|
this.logger.info('入群审批', `已批准入群 - QQ: ${pendingReq.applicantQQ}`, true);
|
|
375
375
|
// 4. 等待用户进群
|
|
376
376
|
const joined = await this.waitForUserJoin(pendingReq.applicantQQ, pendingReq.targetGroupId, 10000);
|
|
377
|
-
if (
|
|
378
|
-
|
|
379
|
-
pendingReq.
|
|
380
|
-
|
|
377
|
+
if (joined) {
|
|
378
|
+
// 5. 用户进群后再设置群昵称
|
|
379
|
+
await this.setGroupNicknameAfterJoin(pendingReq.applicantQQ, bindResult.buidUsername, bindResult.mcUsername, session.bot);
|
|
380
|
+
// 6. 通知管理员
|
|
381
|
+
await this.notifyAdmin(operatorId, session, `✅ 已批准 ${pendingReq.applicantQQ} 入群并完成自动绑定\nUID: ${uid}`);
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
await this.notifyAdmin(operatorId, session, `⚠️ 已完成绑定并批准 ${pendingReq.applicantQQ} 入群,但用户未在10秒内进群\n昵称将在用户进群后由系统自动设置`);
|
|
381
385
|
}
|
|
382
|
-
// 5. 通知管理员
|
|
383
|
-
await this.notifyAdmin(operatorId, session, `✅ 已批准 ${pendingReq.applicantQQ} 入群并完成自动绑定\nUID: ${uid}`);
|
|
384
386
|
pendingReq.status = 'approved';
|
|
385
387
|
}
|
|
386
388
|
catch (error) {
|
|
@@ -605,86 +607,92 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
605
607
|
}
|
|
606
608
|
}
|
|
607
609
|
/**
|
|
608
|
-
*
|
|
610
|
+
* 执行自动绑定(仅数据库操作,不设置昵称)
|
|
611
|
+
* @returns 绑定信息,包含B站用户名和MC用户名
|
|
609
612
|
*/
|
|
610
613
|
async performAutoBind(qq, uid, bot) {
|
|
614
|
+
// 1. 使用强制绑定模式获取最新用户信息(避免频率限制)
|
|
615
|
+
this.logger.debug('入群审批', `开始获取 B站 UID ${uid} 的信息`);
|
|
616
|
+
// 使用强制绑定模式获取完整信息(与 bind -f 保持一致)
|
|
617
|
+
this.logger.debug('入群审批', '正在使用强制绑定模式获取B站用户信息...');
|
|
618
|
+
const enhancedUser = await this.deps.forceBinder.forceBindUser(uid);
|
|
619
|
+
const zminfoUser = this.deps.forceBinder.convertToZminfoUser(enhancedUser);
|
|
620
|
+
if (!zminfoUser) {
|
|
621
|
+
throw new Error(`无法验证B站UID: ${uid}`);
|
|
622
|
+
}
|
|
623
|
+
this.logger.info('入群审批', `✅ 获取到用户名: "${zminfoUser.username}"`, true);
|
|
624
|
+
// 2. 检查是否已被其他人绑定
|
|
625
|
+
const existingBind = await this.repos.mcidbind.findByBuidUid(uid);
|
|
626
|
+
if (existingBind && existingBind.qqId !== qq) {
|
|
627
|
+
throw new Error(`UID ${uid} 已被其他用户绑定`);
|
|
628
|
+
}
|
|
629
|
+
// 3. 获取或创建绑定记录
|
|
630
|
+
let bind = await this.repos.mcidbind.findByQQId(qq);
|
|
631
|
+
let mcUsername = null;
|
|
632
|
+
if (!bind) {
|
|
633
|
+
// 创建新绑定(不使用临时MC用户名)
|
|
634
|
+
bind = await this.repos.mcidbind.create({
|
|
635
|
+
qqId: qq,
|
|
636
|
+
mcUsername: null,
|
|
637
|
+
mcUuid: null,
|
|
638
|
+
buidUid: zminfoUser.uid,
|
|
639
|
+
buidUsername: zminfoUser.username,
|
|
640
|
+
guardLevel: zminfoUser.guard_level || 0,
|
|
641
|
+
guardLevelText: zminfoUser.guard_level_text || '',
|
|
642
|
+
maxGuardLevel: zminfoUser.guard_level || 0,
|
|
643
|
+
maxGuardLevelText: zminfoUser.guard_level_text || '',
|
|
644
|
+
medalName: zminfoUser.medal?.name || '',
|
|
645
|
+
medalLevel: zminfoUser.medal?.level || 0,
|
|
646
|
+
wealthMedalLevel: zminfoUser.wealthMedalLevel || 0,
|
|
647
|
+
lastActiveTime: new Date(),
|
|
648
|
+
lastModified: new Date(),
|
|
649
|
+
hasMcBind: false,
|
|
650
|
+
hasBuidBind: true
|
|
651
|
+
});
|
|
652
|
+
this.logger.info('入群审批', `已创建新绑定 - QQ: ${qq}, UID: ${uid}`, true);
|
|
653
|
+
}
|
|
654
|
+
else {
|
|
655
|
+
// 保存现有的MC用户名
|
|
656
|
+
mcUsername = bind_status_1.BindStatus.getDisplayMcUsername(bind, null);
|
|
657
|
+
// 更新现有绑定
|
|
658
|
+
await this.repos.mcidbind.update(qq, {
|
|
659
|
+
buidUid: zminfoUser.uid,
|
|
660
|
+
buidUsername: zminfoUser.username,
|
|
661
|
+
guardLevel: zminfoUser.guard_level || 0,
|
|
662
|
+
guardLevelText: zminfoUser.guard_level_text || '',
|
|
663
|
+
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
|
+
medalName: zminfoUser.medal?.name || '',
|
|
668
|
+
medalLevel: zminfoUser.medal?.level || 0,
|
|
669
|
+
wealthMedalLevel: zminfoUser.wealthMedalLevel || 0,
|
|
670
|
+
lastActiveTime: new Date(),
|
|
671
|
+
lastModified: new Date(),
|
|
672
|
+
hasBuidBind: true
|
|
673
|
+
});
|
|
674
|
+
this.logger.info('入群审批', `已更新绑定 - QQ: ${qq}, UID: ${uid}`, true);
|
|
675
|
+
}
|
|
676
|
+
this.logger.info('入群审批', `自动绑定完成 - QQ: ${qq}, UID: ${uid}, 用户名: ${zminfoUser.username}`, true);
|
|
677
|
+
// 返回绑定信息供后续使用(昵称设置在用户进群后执行)
|
|
678
|
+
return {
|
|
679
|
+
buidUsername: zminfoUser.username,
|
|
680
|
+
mcUsername: mcUsername
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* 用户进群后设置群昵称
|
|
685
|
+
*/
|
|
686
|
+
async setGroupNicknameAfterJoin(qq, buidUsername, mcUsername, bot) {
|
|
611
687
|
try {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
const zminfoUser = this.deps.forceBinder.convertToZminfoUser(enhancedUser);
|
|
618
|
-
if (!zminfoUser) {
|
|
619
|
-
throw new Error(`无法验证B站UID: ${uid}`);
|
|
620
|
-
}
|
|
621
|
-
this.logger.info('入群审批', `✅ 获取到用户名: "${zminfoUser.username}"`, true);
|
|
622
|
-
// 2. 检查是否已被其他人绑定
|
|
623
|
-
const existingBind = await this.repos.mcidbind.findByBuidUid(uid);
|
|
624
|
-
if (existingBind && existingBind.qqId !== qq) {
|
|
625
|
-
throw new Error(`UID ${uid} 已被其他用户绑定`);
|
|
626
|
-
}
|
|
627
|
-
// 3. 获取或创建绑定记录
|
|
628
|
-
let bind = await this.repos.mcidbind.findByQQId(qq);
|
|
629
|
-
if (!bind) {
|
|
630
|
-
// 创建新绑定(不使用临时MC用户名)
|
|
631
|
-
bind = await this.repos.mcidbind.create({
|
|
632
|
-
qqId: qq,
|
|
633
|
-
mcUsername: null,
|
|
634
|
-
mcUuid: null,
|
|
635
|
-
buidUid: zminfoUser.uid,
|
|
636
|
-
buidUsername: zminfoUser.username,
|
|
637
|
-
guardLevel: zminfoUser.guard_level || 0,
|
|
638
|
-
guardLevelText: zminfoUser.guard_level_text || '',
|
|
639
|
-
maxGuardLevel: zminfoUser.guard_level || 0,
|
|
640
|
-
maxGuardLevelText: zminfoUser.guard_level_text || '',
|
|
641
|
-
medalName: zminfoUser.medal?.name || '',
|
|
642
|
-
medalLevel: zminfoUser.medal?.level || 0,
|
|
643
|
-
wealthMedalLevel: zminfoUser.wealthMedalLevel || 0,
|
|
644
|
-
lastActiveTime: new Date(),
|
|
645
|
-
lastModified: new Date(),
|
|
646
|
-
hasMcBind: false,
|
|
647
|
-
hasBuidBind: true
|
|
648
|
-
});
|
|
649
|
-
this.logger.info('入群审批', `已创建新绑定 - QQ: ${qq}, UID: ${uid}`, true);
|
|
650
|
-
}
|
|
651
|
-
else {
|
|
652
|
-
// 更新现有绑定
|
|
653
|
-
await this.repos.mcidbind.update(qq, {
|
|
654
|
-
buidUid: zminfoUser.uid,
|
|
655
|
-
buidUsername: zminfoUser.username,
|
|
656
|
-
guardLevel: zminfoUser.guard_level || 0,
|
|
657
|
-
guardLevelText: zminfoUser.guard_level_text || '',
|
|
658
|
-
maxGuardLevel: Math.max(bind.maxGuardLevel || 0, zminfoUser.guard_level || 0),
|
|
659
|
-
maxGuardLevelText: zminfoUser.guard_level > (bind.maxGuardLevel || 0)
|
|
660
|
-
? zminfoUser.guard_level_text
|
|
661
|
-
: bind.maxGuardLevelText,
|
|
662
|
-
medalName: zminfoUser.medal?.name || '',
|
|
663
|
-
medalLevel: zminfoUser.medal?.level || 0,
|
|
664
|
-
wealthMedalLevel: zminfoUser.wealthMedalLevel || 0,
|
|
665
|
-
lastActiveTime: new Date(),
|
|
666
|
-
lastModified: new Date(),
|
|
667
|
-
hasBuidBind: true
|
|
668
|
-
});
|
|
669
|
-
this.logger.info('入群审批', `已更新绑定 - QQ: ${qq}, UID: ${uid}`, true);
|
|
670
|
-
}
|
|
671
|
-
// 4. 更新群昵称(使用标准格式)
|
|
672
|
-
try {
|
|
673
|
-
const groupId = this.reviewConfig.targetGroupId;
|
|
674
|
-
const mcInfo = bind_status_1.BindStatus.getDisplayMcUsername(bind, '未绑定');
|
|
675
|
-
const nickname = `${zminfoUser.username}(ID:${mcInfo})`;
|
|
676
|
-
await bot.internal.setGroupCard(groupId, qq, nickname);
|
|
677
|
-
this.logger.info('入群审批', `已更新群昵称 - QQ: ${qq}, 昵称: ${nickname}`);
|
|
678
|
-
}
|
|
679
|
-
catch (error) {
|
|
680
|
-
this.logger.warn('入群审批', `更新群昵称失败: ${error.message}`);
|
|
681
|
-
// 昵称更新失败不影响绑定
|
|
682
|
-
}
|
|
683
|
-
this.logger.info('入群审批', `自动绑定完成 - QQ: ${qq}, UID: ${uid}, 用户名: ${zminfoUser.username}`, true);
|
|
688
|
+
const groupId = this.reviewConfig.targetGroupId;
|
|
689
|
+
const mcInfo = mcUsername || '未绑定';
|
|
690
|
+
const nickname = `${buidUsername}(ID:${mcInfo})`;
|
|
691
|
+
await bot.internal.setGroupCard(groupId, qq, nickname);
|
|
692
|
+
this.logger.info('入群审批', `已更新群昵称 - QQ: ${qq}, 昵称: ${nickname}`);
|
|
684
693
|
}
|
|
685
694
|
catch (error) {
|
|
686
|
-
this.logger.
|
|
687
|
-
throw error;
|
|
695
|
+
this.logger.warn('入群审批', `更新群昵称失败: ${error.message}`);
|
|
688
696
|
}
|
|
689
697
|
}
|
|
690
698
|
/**
|
package/lib/index.js
CHANGED
|
@@ -60,11 +60,11 @@ exports.Config = koishi_1.Schema.object({
|
|
|
60
60
|
reviewGroupId: koishi_1.Schema.string()
|
|
61
61
|
.description('管理员审批操作所在的群ID(播报群)'),
|
|
62
62
|
approveAutoBindEmoji: koishi_1.Schema.string()
|
|
63
|
-
.description('批准并自动绑定的表情ID
|
|
64
|
-
.default('
|
|
63
|
+
.description('批准并自动绑定的表情ID(/赞)')
|
|
64
|
+
.default('76'),
|
|
65
65
|
approveInteractiveBindEmoji: koishi_1.Schema.string()
|
|
66
|
-
.description('批准并交互式绑定的表情ID
|
|
67
|
-
.default('
|
|
66
|
+
.description('批准并交互式绑定的表情ID(/OK)')
|
|
67
|
+
.default('124'),
|
|
68
68
|
rejectEmoji: koishi_1.Schema.string().description('拒绝申请的表情ID(/NO)').default('123'),
|
|
69
69
|
autoCleanupHours: koishi_1.Schema.number()
|
|
70
70
|
.description('待审批记录自动清理时间(小时)')
|
package/lib/types/config.d.ts
CHANGED
|
@@ -134,9 +134,9 @@ export interface GroupRequestReviewConfig {
|
|
|
134
134
|
targetGroupId: string;
|
|
135
135
|
/** 管理员审批操作所在的群ID(播报群) */
|
|
136
136
|
reviewGroupId: string;
|
|
137
|
-
/** 批准并自动绑定的表情ID(默认:
|
|
137
|
+
/** 批准并自动绑定的表情ID(默认:76 /赞) */
|
|
138
138
|
approveAutoBindEmoji: string;
|
|
139
|
-
/** 批准并交互式绑定的表情ID(默认:
|
|
139
|
+
/** 批准并交互式绑定的表情ID(默认:124 /OK) */
|
|
140
140
|
approveInteractiveBindEmoji: string;
|
|
141
141
|
/** 拒绝申请的表情ID(默认:123 /NO) */
|
|
142
142
|
rejectEmoji: string;
|
|
@@ -79,6 +79,20 @@ export declare class BindStatus {
|
|
|
79
79
|
* ```
|
|
80
80
|
*/
|
|
81
81
|
static hasCompletedAllBinds(bind: MCIDBIND | null | undefined): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* 检查是否只绑定了MC(未绑定B站)
|
|
84
|
+
*
|
|
85
|
+
* @param bind - 绑定记录(可为 null 或 undefined)
|
|
86
|
+
* @returns true 表示已绑定MC但未绑定B站
|
|
87
|
+
*/
|
|
88
|
+
static hasOnlyMcBind(bind: MCIDBIND | null | undefined): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* 检查是否只绑定了B站(未绑定MC)
|
|
91
|
+
*
|
|
92
|
+
* @param bind - 绑定记录(可为 null 或 undefined)
|
|
93
|
+
* @returns true 表示已绑定B站但未绑定MC
|
|
94
|
+
*/
|
|
95
|
+
static hasOnlyBuidBind(bind: MCIDBIND | null | undefined): boolean;
|
|
82
96
|
/**
|
|
83
97
|
* 获取绑定状态摘要信息
|
|
84
98
|
*
|
package/lib/utils/bind-status.js
CHANGED
|
@@ -111,6 +111,28 @@ class BindStatus {
|
|
|
111
111
|
static hasCompletedAllBinds(bind) {
|
|
112
112
|
return this.hasValidMcBind(bind) && this.hasValidBuidBind(bind);
|
|
113
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* 检查是否只绑定了MC(未绑定B站)
|
|
116
|
+
*
|
|
117
|
+
* @param bind - 绑定记录(可为 null 或 undefined)
|
|
118
|
+
* @returns true 表示已绑定MC但未绑定B站
|
|
119
|
+
*/
|
|
120
|
+
static hasOnlyMcBind(bind) {
|
|
121
|
+
return this.hasValidMcBind(bind) && !this.hasValidBuidBind(bind);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* 检查是否只绑定了B站(未绑定MC)
|
|
125
|
+
*
|
|
126
|
+
* @param bind - 绑定记录(可为 null 或 undefined)
|
|
127
|
+
* @returns true 表示已绑定B站但未绑定MC
|
|
128
|
+
*/
|
|
129
|
+
static hasOnlyBuidBind(bind) {
|
|
130
|
+
return (bind !== null &&
|
|
131
|
+
bind !== undefined &&
|
|
132
|
+
!!bind.buidUid &&
|
|
133
|
+
!!bind.buidUsername &&
|
|
134
|
+
!this.hasValidMcBind(bind));
|
|
135
|
+
}
|
|
114
136
|
/**
|
|
115
137
|
* 获取绑定状态摘要信息
|
|
116
138
|
*
|