koishi-plugin-maibot 1.7.24 → 1.7.25
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.d.ts.map +1 -1
- package/lib/index.js +713 -605
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -367,9 +367,11 @@ class RequestQueue {
|
|
|
367
367
|
}
|
|
368
368
|
/**
|
|
369
369
|
* 加入队列并等待处理
|
|
370
|
+
* @param userId 用户ID
|
|
371
|
+
* @param channelId 频道ID
|
|
370
372
|
* @returns Promise<number>,当轮到处理时resolve,返回加入队列时的位置(0表示直接执行,没有排队)
|
|
371
373
|
*/
|
|
372
|
-
async enqueue() {
|
|
374
|
+
async enqueue(userId, channelId) {
|
|
373
375
|
// 如果队列为空且距离上次处理已过间隔时间,直接执行
|
|
374
376
|
if (this.queue.length === 0 && !this.processing) {
|
|
375
377
|
const now = Date.now();
|
|
@@ -387,6 +389,8 @@ class RequestQueue {
|
|
|
387
389
|
resolve: () => resolve(queuePosition),
|
|
388
390
|
reject,
|
|
389
391
|
timestamp: Date.now(),
|
|
392
|
+
userId,
|
|
393
|
+
channelId,
|
|
390
394
|
});
|
|
391
395
|
// 启动处理循环(如果还没启动)
|
|
392
396
|
if (!this.processing) {
|
|
@@ -424,6 +428,12 @@ class RequestQueue {
|
|
|
424
428
|
getQueuePosition() {
|
|
425
429
|
return this.queue.length;
|
|
426
430
|
}
|
|
431
|
+
/**
|
|
432
|
+
* 检查是否正在处理
|
|
433
|
+
*/
|
|
434
|
+
isProcessing() {
|
|
435
|
+
return this.processing;
|
|
436
|
+
}
|
|
427
437
|
/**
|
|
428
438
|
* 获取预计等待时间(秒)
|
|
429
439
|
*/
|
|
@@ -432,6 +442,46 @@ class RequestQueue {
|
|
|
432
442
|
const waitTime = position * (this.interval / 1000);
|
|
433
443
|
return Math.ceil(waitTime);
|
|
434
444
|
}
|
|
445
|
+
/**
|
|
446
|
+
* 获取用户在队列中的位置
|
|
447
|
+
* @param userId 用户ID
|
|
448
|
+
* @param channelId 频道ID(可选,用于更精确的匹配)
|
|
449
|
+
* @returns 用户在队列中的位置(0表示正在处理或不在队列中,>0表示前面还有多少人)
|
|
450
|
+
*/
|
|
451
|
+
getUserQueuePosition(userId, channelId) {
|
|
452
|
+
for (let i = 0; i < this.queue.length; i++) {
|
|
453
|
+
const task = this.queue[i];
|
|
454
|
+
if (task.userId === userId && (channelId === undefined || task.channelId === channelId)) {
|
|
455
|
+
// 返回位置(前面的人数),索引0表示第一个等待的人
|
|
456
|
+
return i + 1;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
// 如果用户不在队列中,检查是否正在处理
|
|
460
|
+
if (this.processing && this.queue.length > 0) {
|
|
461
|
+
const firstTask = this.queue[0];
|
|
462
|
+
if (firstTask.userId === userId && (channelId === undefined || firstTask.channelId === channelId)) {
|
|
463
|
+
return 0; // 正在处理
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return -1; // 不在队列中
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* 获取用户预计等待时间(秒)
|
|
470
|
+
* @param userId 用户ID
|
|
471
|
+
* @param channelId 频道ID(可选)
|
|
472
|
+
* @returns 预计等待时间(秒),-1表示不在队列中
|
|
473
|
+
*/
|
|
474
|
+
getUserEstimatedWaitTime(userId, channelId) {
|
|
475
|
+
const position = this.getUserQueuePosition(userId, channelId);
|
|
476
|
+
if (position < 0) {
|
|
477
|
+
return -1;
|
|
478
|
+
}
|
|
479
|
+
if (position === 0) {
|
|
480
|
+
return 0; // 正在处理
|
|
481
|
+
}
|
|
482
|
+
const waitTime = position * (this.interval / 1000);
|
|
483
|
+
return Math.ceil(waitTime);
|
|
484
|
+
}
|
|
435
485
|
}
|
|
436
486
|
/**
|
|
437
487
|
* 处理并转换SGID(从URL或直接SGID)
|
|
@@ -793,27 +843,43 @@ function apply(ctx, config) {
|
|
|
793
843
|
const queueConfig = config.queue || { enabled: false, interval: 10000, message: '你正在排队,前面还有 {queuePosition} 人。预计等待 {queueEST} 秒。' };
|
|
794
844
|
const requestQueue = queueConfig.enabled ? new RequestQueue(queueConfig.interval) : null;
|
|
795
845
|
/**
|
|
796
|
-
*
|
|
846
|
+
* 在API调用前加入队列并等待
|
|
847
|
+
* 这个函数应该在获取到SGID后、调用API前使用
|
|
797
848
|
*/
|
|
798
|
-
async function
|
|
849
|
+
async function waitForQueue(session) {
|
|
799
850
|
if (!requestQueue) {
|
|
800
|
-
//
|
|
801
|
-
return
|
|
851
|
+
// 队列未启用,直接返回
|
|
852
|
+
return;
|
|
802
853
|
}
|
|
803
|
-
//
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
854
|
+
// 检查必要的 session 属性
|
|
855
|
+
if (!session.userId || !session.channelId) {
|
|
856
|
+
logger.warn('无法加入队列:缺少 userId 或 channelId');
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
// 先获取当前队列位置(不等待)
|
|
860
|
+
const currentQueueLength = requestQueue.getQueuePosition();
|
|
861
|
+
const isProcessing = requestQueue.isProcessing();
|
|
862
|
+
// 检查是否需要排队(如果队列不为空或正在处理,需要排队)
|
|
863
|
+
// 注意:即使队列为空,如果正在处理,也需要排队等待
|
|
864
|
+
const needsQueue = currentQueueLength > 0 || isProcessing;
|
|
865
|
+
// 如果需要排队,立即发送队列提示消息(在加入队列前发送,确保及时性)
|
|
866
|
+
if (needsQueue) {
|
|
867
|
+
// 计算队列位置(当前队列长度 + 1,因为用户即将加入)
|
|
868
|
+
const queuePosition = currentQueueLength + 1;
|
|
869
|
+
// 计算预计等待时间(基于队列位置)
|
|
809
870
|
const estimatedWait = Math.ceil(queuePosition * (queueConfig.interval / 1000));
|
|
810
871
|
const queueMessage = queueConfig.message
|
|
811
872
|
.replace(/{queuePosition}/g, String(queuePosition))
|
|
812
873
|
.replace(/{queueEST}/g, String(estimatedWait));
|
|
813
|
-
|
|
874
|
+
// 立即发送队列提示消息(不等待,使用 fire-and-forget 模式确保及时性)
|
|
875
|
+
// 使用 void 确保不等待 Promise 完成,同时捕获错误避免未处理的 Promise rejection
|
|
876
|
+
void session.send(queueMessage).catch(err => {
|
|
877
|
+
logger.warn('发送队列提示消息失败:', err);
|
|
878
|
+
});
|
|
814
879
|
}
|
|
815
|
-
//
|
|
816
|
-
|
|
880
|
+
// 加入队列并等待处理
|
|
881
|
+
// 注意:即使发送了队列消息,这里仍然会等待队列处理完成
|
|
882
|
+
await requestQueue.enqueue(session.userId, session.channelId);
|
|
817
883
|
}
|
|
818
884
|
// 监听用户消息,尝试自动撤回包含SGID、水鱼token或落雪代码的消息
|
|
819
885
|
if (config.autoRecall !== false) {
|
|
@@ -1315,6 +1381,42 @@ function apply(ctx, config) {
|
|
|
1315
1381
|
}
|
|
1316
1382
|
});
|
|
1317
1383
|
// 这个 Fracture_Hikaritsu 不给我吃KFC,故挂在此处。 我很生气。
|
|
1384
|
+
/**
|
|
1385
|
+
* 查询队列位置
|
|
1386
|
+
* 用法: /maiqueue
|
|
1387
|
+
*/
|
|
1388
|
+
ctx.command('maiqueue', '查询当前队列位置')
|
|
1389
|
+
.action(async ({ session }) => {
|
|
1390
|
+
if (!session) {
|
|
1391
|
+
return '❌ 无法获取会话信息';
|
|
1392
|
+
}
|
|
1393
|
+
// 检查白名单
|
|
1394
|
+
const whitelistCheck = checkWhitelist(session, config);
|
|
1395
|
+
if (!whitelistCheck.allowed) {
|
|
1396
|
+
return whitelistCheck.message || '本群暂时没有被授权使用本Bot的功能,请添加官方群聊1072033605。';
|
|
1397
|
+
}
|
|
1398
|
+
// 检查队列是否启用
|
|
1399
|
+
if (!requestQueue) {
|
|
1400
|
+
return 'ℹ️ 队列系统未启用';
|
|
1401
|
+
}
|
|
1402
|
+
// 检查必要的 session 属性
|
|
1403
|
+
if (!session.userId || !session.channelId) {
|
|
1404
|
+
return '❌ 无法查询队列:缺少用户信息';
|
|
1405
|
+
}
|
|
1406
|
+
// 查询用户在队列中的位置
|
|
1407
|
+
const position = requestQueue.getUserQueuePosition(session.userId, session.channelId);
|
|
1408
|
+
const estimatedWait = requestQueue.getUserEstimatedWaitTime(session.userId, session.channelId);
|
|
1409
|
+
const totalQueue = requestQueue.getQueuePosition();
|
|
1410
|
+
if (position < 0) {
|
|
1411
|
+
return `ℹ️ 您当前不在队列中\n队列总长度: ${totalQueue}`;
|
|
1412
|
+
}
|
|
1413
|
+
else if (position === 0) {
|
|
1414
|
+
return `✅ 您的请求正在处理中\n队列总长度: ${totalQueue}`;
|
|
1415
|
+
}
|
|
1416
|
+
else {
|
|
1417
|
+
return `⏳ 您当前在队列中的位置: 第 ${position} 位\n预计等待时间: ${estimatedWait} 秒\n队列总长度: ${totalQueue}`;
|
|
1418
|
+
}
|
|
1419
|
+
});
|
|
1318
1420
|
/**
|
|
1319
1421
|
* 绑定用户
|
|
1320
1422
|
* 用法: /mai绑定 [SGWCMAID...]
|
|
@@ -1330,158 +1432,158 @@ function apply(ctx, config) {
|
|
|
1330
1432
|
return whitelistCheck.message || '本群暂时没有被授权使用本Bot的功能,请添加官方群聊1072033605。';
|
|
1331
1433
|
}
|
|
1332
1434
|
// 使用队列系统
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
promptMessageId = sentMessage;
|
|
1349
|
-
}
|
|
1350
|
-
else if (sentMessage && sentMessage.messageId) {
|
|
1351
|
-
promptMessageId = sentMessage.messageId;
|
|
1352
|
-
}
|
|
1435
|
+
const userId = session.userId;
|
|
1436
|
+
try {
|
|
1437
|
+
// 检查是否已绑定
|
|
1438
|
+
const existing = await ctx.database.get('maibot_bindings', { userId });
|
|
1439
|
+
if (existing.length > 0) {
|
|
1440
|
+
return `❌ 您已经绑定了账号\n绑定时间: ${new Date(existing[0].bindTime).toLocaleString('zh-CN')}\n\n如需重新绑定,请先使用 /mai解绑`;
|
|
1441
|
+
}
|
|
1442
|
+
// 如果没有提供SGID,提示用户输入
|
|
1443
|
+
if (!qrCode) {
|
|
1444
|
+
const actualTimeout = rebindTimeout;
|
|
1445
|
+
let promptMessageId;
|
|
1446
|
+
try {
|
|
1447
|
+
const sentMessage = await session.send(`请在${actualTimeout / 1000}秒内发送SGID(长按玩家二维码识别后发送)或公众号提供的网页地址`);
|
|
1448
|
+
if (typeof sentMessage === 'string') {
|
|
1449
|
+
promptMessageId = sentMessage;
|
|
1353
1450
|
}
|
|
1354
|
-
|
|
1355
|
-
|
|
1451
|
+
else if (sentMessage && sentMessage.messageId) {
|
|
1452
|
+
promptMessageId = sentMessage.messageId;
|
|
1356
1453
|
}
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
}
|
|
1454
|
+
}
|
|
1455
|
+
catch (error) {
|
|
1456
|
+
ctx.logger('maibot').warn('发送提示消息失败:', error);
|
|
1457
|
+
}
|
|
1458
|
+
try {
|
|
1459
|
+
logger.info(`开始等待用户 ${session.userId} 输入SGID,超时时间: ${actualTimeout}ms`);
|
|
1460
|
+
// 使用session.prompt等待用户输入SGID文本
|
|
1461
|
+
const promptText = await session.prompt(actualTimeout);
|
|
1462
|
+
if (!promptText || !promptText.trim()) {
|
|
1463
|
+
throw new Error('超时未收到响应');
|
|
1464
|
+
}
|
|
1465
|
+
const trimmed = promptText.trim();
|
|
1466
|
+
logger.debug(`收到用户输入: ${trimmed.substring(0, 50)}`);
|
|
1467
|
+
qrCode = trimmed;
|
|
1468
|
+
// 检查是否为公众号网页地址格式(https://wq.wahlap.net/qrcode/req/)
|
|
1469
|
+
const isLink = trimmed.includes('https://wq.wahlap.net/qrcode/req/');
|
|
1470
|
+
const isSGID = trimmed.startsWith('SGWCMAID');
|
|
1471
|
+
// 如果是网页地址,提取MAID并转换为SGWCMAID格式
|
|
1472
|
+
if (isLink) {
|
|
1473
|
+
try {
|
|
1474
|
+
// 从URL中提取MAID部分:https://wq.wahlap.net/qrcode/req/MAID2601...55.html?...
|
|
1475
|
+
// 匹配 /qrcode/req/ 后面的 MAID 开头的内容(到 .html 或 ? 之前)
|
|
1476
|
+
const match = trimmed.match(/qrcode\/req\/(MAID[^?\.]+)/i);
|
|
1477
|
+
if (match && match[1]) {
|
|
1478
|
+
const maid = match[1];
|
|
1479
|
+
// 在前面加上 SGWC 变成 SGWCMAID...
|
|
1480
|
+
qrCode = 'SGWC' + maid;
|
|
1481
|
+
logger.info(`从网页地址提取MAID并转换: ${maid.substring(0, 20)}... -> ${qrCode.substring(0, 24)}...`);
|
|
1386
1482
|
}
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
throw new Error('网页地址格式错误');
|
|
1483
|
+
else {
|
|
1484
|
+
await session.send('⚠️ 无法从网页地址中提取MAID,请发送SGID文本(SGWCMAID开头)或公众号提供的网页地址');
|
|
1485
|
+
throw new Error('无法从网页地址中提取MAID');
|
|
1391
1486
|
}
|
|
1392
1487
|
}
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
// 验证SGID格式和长度
|
|
1398
|
-
if (!qrCode.startsWith('SGWCMAID')) {
|
|
1399
|
-
await session.send('⚠️ 未识别到有效的SGID格式,请发送SGID文本(SGWCMAID开头)或公众号提供的网页地址');
|
|
1400
|
-
throw new Error('无效的二维码格式,必须以 SGWCMAID 开头');
|
|
1401
|
-
}
|
|
1402
|
-
if (qrCode.length < 48 || qrCode.length > 128) {
|
|
1403
|
-
await session.send('❌ SGID长度错误,应在48-128字符之间');
|
|
1404
|
-
throw new Error('二维码长度错误,应在48-128字符之间');
|
|
1488
|
+
catch (error) {
|
|
1489
|
+
logger.warn('解析网页地址失败:', error);
|
|
1490
|
+
await session.send('⚠️ 网页地址格式错误,请发送SGID文本(SGWCMAID开头)或公众号提供的网页地址');
|
|
1491
|
+
throw new Error('网页地址格式错误');
|
|
1405
1492
|
}
|
|
1406
|
-
logger.info(`✅ 接收到${isLink ? '网页地址(已转换)' : 'SGID'}: ${qrCode.substring(0, 50)}...`);
|
|
1407
|
-
// 发送识别中反馈
|
|
1408
|
-
await session.send('⏳ 正在处理,请稍候...');
|
|
1409
1493
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
await session.send(`❌ 绑定超时(${actualTimeout / 1000}秒),请稍后使用 /mai绑定 重新绑定`);
|
|
1414
|
-
return '❌ 超时未收到响应,绑定已取消';
|
|
1415
|
-
}
|
|
1416
|
-
if (error.message?.includes('无效的二维码')) {
|
|
1417
|
-
return `❌ 绑定失败:${error.message}`;
|
|
1418
|
-
}
|
|
1419
|
-
await session.send(`❌ 绑定过程中发生错误:${error?.message || '未知错误'}`);
|
|
1420
|
-
return `❌ 绑定失败:${error?.message || '未知错误'}`;
|
|
1494
|
+
else if (!isSGID) {
|
|
1495
|
+
await session.send('⚠️ 未识别到有效的SGID格式或网页地址,请发送SGID文本(SGWCMAID开头)或公众号提供的网页地址(https://wq.wahlap.net/qrcode/req/...)');
|
|
1496
|
+
throw new Error('无效的二维码格式,必须是SGID文本或网页地址');
|
|
1421
1497
|
}
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
if (qrCode && !qrCode.startsWith('SGWCMAID')) {
|
|
1427
|
-
// 如果qrCode不是SGWCMAID格式,可能是原始输入,需要处理
|
|
1428
|
-
await tryRecallMessage(session, ctx, config);
|
|
1429
|
-
// 处理并转换SGID(从URL或直接SGID)
|
|
1430
|
-
const processed = processSGID(qrCode);
|
|
1431
|
-
if (!processed) {
|
|
1432
|
-
return '❌ 二维码格式错误,必须是SGID文本(SGWCMAID开头)或公众号提供的网页地址(https://wq.wahlap.net/qrcode/req/...)';
|
|
1498
|
+
// 验证SGID格式和长度
|
|
1499
|
+
if (!qrCode.startsWith('SGWCMAID')) {
|
|
1500
|
+
await session.send('⚠️ 未识别到有效的SGID格式,请发送SGID文本(SGWCMAID开头)或公众号提供的网页地址');
|
|
1501
|
+
throw new Error('无效的二维码格式,必须以 SGWCMAID 开头');
|
|
1433
1502
|
}
|
|
1434
|
-
qrCode
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
// 使用新API获取用户信息(需要client_id)
|
|
1442
|
-
const machineInfo = config.machineInfo;
|
|
1443
|
-
let previewResult;
|
|
1444
|
-
try {
|
|
1445
|
-
previewResult = await api.getPreview(machineInfo.clientId, qrCode);
|
|
1503
|
+
if (qrCode.length < 48 || qrCode.length > 128) {
|
|
1504
|
+
await session.send('❌ SGID长度错误,应在48-128字符之间');
|
|
1505
|
+
throw new Error('二维码长度错误,应在48-128字符之间');
|
|
1506
|
+
}
|
|
1507
|
+
logger.info(`✅ 接收到${isLink ? '网页地址(已转换)' : 'SGID'}: ${qrCode.substring(0, 50)}...`);
|
|
1508
|
+
// 发送识别中反馈
|
|
1509
|
+
await session.send('⏳ 正在处理,请稍候...');
|
|
1446
1510
|
}
|
|
1447
1511
|
catch (error) {
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1512
|
+
logger.error(`等待用户输入二维码失败: ${error?.message}`, error);
|
|
1513
|
+
if (error.message?.includes('超时') || error.message?.includes('timeout') || error.message?.includes('未收到响应')) {
|
|
1514
|
+
await session.send(`❌ 绑定超时(${actualTimeout / 1000}秒),请稍后使用 /mai绑定 重新绑定`);
|
|
1515
|
+
return '❌ 超时未收到响应,绑定已取消';
|
|
1516
|
+
}
|
|
1517
|
+
if (error.message?.includes('无效的二维码')) {
|
|
1518
|
+
return `❌ 绑定失败:${error.message}`;
|
|
1519
|
+
}
|
|
1520
|
+
await session.send(`❌ 绑定过程中发生错误:${error?.message || '未知错误'}`);
|
|
1521
|
+
return `❌ 绑定失败:${error?.message || '未知错误'}`;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
// 如果直接提供了qrCode参数,尝试撤回并处理
|
|
1525
|
+
// 注意:如果qrCode是通过交互式输入获取的,已经在getQrText中处理过了
|
|
1526
|
+
// 这里只处理直接通过参数提供的qrCode
|
|
1527
|
+
if (qrCode && !qrCode.startsWith('SGWCMAID')) {
|
|
1528
|
+
// 如果qrCode不是SGWCMAID格式,可能是原始输入,需要处理
|
|
1529
|
+
await tryRecallMessage(session, ctx, config);
|
|
1530
|
+
// 处理并转换SGID(从URL或直接SGID)
|
|
1531
|
+
const processed = processSGID(qrCode);
|
|
1532
|
+
if (!processed) {
|
|
1533
|
+
return '❌ 二维码格式错误,必须是SGID文本(SGWCMAID开头)或公众号提供的网页地址(https://wq.wahlap.net/qrcode/req/...)';
|
|
1534
|
+
}
|
|
1535
|
+
qrCode = processed.qrText;
|
|
1536
|
+
logger.info(`从参数中提取并转换SGID: ${qrCode.substring(0, 50)}...`);
|
|
1537
|
+
}
|
|
1538
|
+
else if (qrCode && qrCode.startsWith('SGWCMAID')) {
|
|
1539
|
+
// 如果已经是SGWCMAID格式,说明可能是直接参数传入的,尝试撤回
|
|
1540
|
+
await tryRecallMessage(session, ctx, config);
|
|
1541
|
+
}
|
|
1542
|
+
// 在调用API前加入队列
|
|
1543
|
+
await waitForQueue(session);
|
|
1544
|
+
// 使用新API获取用户信息(需要client_id)
|
|
1545
|
+
const machineInfo = config.machineInfo;
|
|
1546
|
+
let previewResult;
|
|
1547
|
+
try {
|
|
1548
|
+
previewResult = await api.getPreview(machineInfo.clientId, qrCode);
|
|
1473
1549
|
}
|
|
1474
1550
|
catch (error) {
|
|
1475
|
-
ctx.logger('maibot').error('
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1551
|
+
ctx.logger('maibot').error('获取用户预览信息失败:', error);
|
|
1552
|
+
return `❌ 绑定失败:无法从二维码获取用户信息\n错误信息: ${error?.message || '未知错误'}`;
|
|
1553
|
+
}
|
|
1554
|
+
// 检查是否获取成功
|
|
1555
|
+
if (previewResult.UserID === -1 || (typeof previewResult.UserID === 'string' && previewResult.UserID === '-1')) {
|
|
1556
|
+
return `❌ 绑定失败:无效或过期的二维码`;
|
|
1557
|
+
}
|
|
1558
|
+
// UserID在新API中是加密的字符串
|
|
1559
|
+
const maiUid = String(previewResult.UserID);
|
|
1560
|
+
const userName = previewResult.UserName;
|
|
1561
|
+
const rating = previewResult.Rating ? String(previewResult.Rating) : undefined;
|
|
1562
|
+
// 存储到数据库
|
|
1563
|
+
await ctx.database.create('maibot_bindings', {
|
|
1564
|
+
userId,
|
|
1565
|
+
maiUid,
|
|
1566
|
+
qrCode,
|
|
1567
|
+
bindTime: new Date(),
|
|
1568
|
+
userName,
|
|
1569
|
+
rating,
|
|
1570
|
+
});
|
|
1571
|
+
return `✅ 绑定成功!\n` +
|
|
1572
|
+
(userName ? `用户名: ${userName}\n` : '') +
|
|
1573
|
+
(rating ? `Rating: ${rating}\n` : '') +
|
|
1574
|
+
`绑定时间: ${new Date().toLocaleString('zh-CN')}\n\n` +
|
|
1575
|
+
`⚠️ 为了确保账户安全,请手动撤回群内包含SGID的消息`;
|
|
1576
|
+
}
|
|
1577
|
+
catch (error) {
|
|
1578
|
+
ctx.logger('maibot').error('绑定失败:', error);
|
|
1579
|
+
if (maintenanceMode) {
|
|
1580
|
+
return maintenanceMessage;
|
|
1483
1581
|
}
|
|
1484
|
-
|
|
1582
|
+
if (error?.response) {
|
|
1583
|
+
return `❌ API请求失败: ${error.response.status} ${error.response.statusText}\n\n${maintenanceMessage}`;
|
|
1584
|
+
}
|
|
1585
|
+
return `❌ 绑定失败: ${error?.message || '未知错误'}\n\n${maintenanceMessage}`;
|
|
1586
|
+
}
|
|
1485
1587
|
});
|
|
1486
1588
|
/**
|
|
1487
1589
|
* 解绑用户
|
|
@@ -1532,134 +1634,133 @@ function apply(ctx, config) {
|
|
|
1532
1634
|
if (!whitelistCheck.allowed) {
|
|
1533
1635
|
return whitelistCheck.message || '本群暂时没有被授权使用本Bot的功能,请添加官方群聊1072033605。';
|
|
1534
1636
|
}
|
|
1535
|
-
|
|
1536
|
-
|
|
1637
|
+
try {
|
|
1638
|
+
// 获取目标用户绑定
|
|
1639
|
+
const { binding, isProxy, error } = await getTargetBinding(session, targetUserId);
|
|
1640
|
+
if (error || !binding) {
|
|
1641
|
+
return error || '❌ 获取用户绑定失败';
|
|
1642
|
+
}
|
|
1643
|
+
const userId = binding.userId;
|
|
1644
|
+
let statusInfo = `✅ 已绑定账号\n\n` +
|
|
1645
|
+
`绑定时间: ${new Date(binding.bindTime).toLocaleString('zh-CN')}\n` +
|
|
1646
|
+
`🚨 /maialert查看账号提醒状态\n`;
|
|
1647
|
+
// 尝试获取最新状态并更新数据库(需要新二维码)
|
|
1537
1648
|
try {
|
|
1538
|
-
//
|
|
1539
|
-
const
|
|
1540
|
-
if (error
|
|
1541
|
-
|
|
1542
|
-
}
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
//
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
//
|
|
1569
|
-
const
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
//
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
//
|
|
1578
|
-
|
|
1579
|
-
// 转换为数字,如 "09" -> 9, "9" -> 9
|
|
1580
|
-
const versionNumber = parseInt(digitsStr, 10);
|
|
1581
|
-
// 验证转换是否正确
|
|
1582
|
-
if (!isNaN(versionNumber) && versionNumber >= 1) {
|
|
1583
|
-
// 01 -> A, 02 -> B, ..., 09 -> I, 10 -> J, ..., 26 -> Z
|
|
1584
|
-
// 使用模运算确保在 A-Z 范围内循环(27 -> A, 28 -> B, ...)
|
|
1585
|
-
const letterIndex = ((versionNumber - 1) % 26) + 1;
|
|
1586
|
-
// 转换为大写字母:A=65, B=66, ..., Z=90
|
|
1587
|
-
dataVersionLetter = String.fromCharCode(64 + letterIndex).toUpperCase();
|
|
1588
|
-
}
|
|
1649
|
+
// 废弃旧的uid策略,每次都需要新的二维码
|
|
1650
|
+
const qrTextResult = await getQrText(session, ctx, api, binding, config, rebindTimeout, '请在60秒内发送SGID(长按玩家二维码识别后发送)或公众号提供的网页地址以查询账号状态');
|
|
1651
|
+
if (qrTextResult.error) {
|
|
1652
|
+
statusInfo += `\n⚠️ 无法获取最新状态:${qrTextResult.error}`;
|
|
1653
|
+
}
|
|
1654
|
+
else {
|
|
1655
|
+
// 在调用API前加入队列
|
|
1656
|
+
await waitForQueue(session);
|
|
1657
|
+
try {
|
|
1658
|
+
const preview = await api.getPreview(machineInfo.clientId, qrTextResult.qrText);
|
|
1659
|
+
// 更新数据库中的用户名和Rating
|
|
1660
|
+
await ctx.database.set('maibot_bindings', { userId }, {
|
|
1661
|
+
userName: preview.UserName,
|
|
1662
|
+
rating: preview.Rating ? String(preview.Rating) : undefined,
|
|
1663
|
+
});
|
|
1664
|
+
// 格式化版本信息
|
|
1665
|
+
let versionInfo = '';
|
|
1666
|
+
if (preview.RomVersion && preview.DataVersion) {
|
|
1667
|
+
// 机台版本:取前两个数字,如 1.52.00 -> 1.52
|
|
1668
|
+
const romVersionMatch = preview.RomVersion.match(/^(\d+\.\d+)/);
|
|
1669
|
+
const romVersion = romVersionMatch ? romVersionMatch[1] : preview.RomVersion;
|
|
1670
|
+
// 数据版本:取前两个数字 + 最后两个数字转换为字母,如 1.50.09 -> 1.50 - I
|
|
1671
|
+
const dataVersionPrefixMatch = preview.DataVersion.match(/^(\d+\.\d+)/);
|
|
1672
|
+
const dataVersionPrefix = dataVersionPrefixMatch ? dataVersionPrefixMatch[1] : preview.DataVersion;
|
|
1673
|
+
// 从版本号末尾提取最后两位数字,如 "1.50.01" -> "01", "1.50.09" -> "09"
|
|
1674
|
+
// 匹配最后一个点后的数字(确保只匹配版本号末尾)
|
|
1675
|
+
let dataVersionLetter = '';
|
|
1676
|
+
// 匹配最后一个点后的1-2位数字
|
|
1677
|
+
const dataVersionMatch = preview.DataVersion.match(/\.(\d{1,2})$/);
|
|
1678
|
+
if (dataVersionMatch) {
|
|
1679
|
+
// 提取数字字符串,如 "09" 或 "9"
|
|
1680
|
+
const digitsStr = dataVersionMatch[1];
|
|
1681
|
+
// 转换为数字,如 "09" -> 9, "9" -> 9
|
|
1682
|
+
const versionNumber = parseInt(digitsStr, 10);
|
|
1683
|
+
// 验证转换是否正确
|
|
1684
|
+
if (!isNaN(versionNumber) && versionNumber >= 1) {
|
|
1685
|
+
// 01 -> A, 02 -> B, ..., 09 -> I, 10 -> J, ..., 26 -> Z
|
|
1686
|
+
// 使用模运算确保在 A-Z 范围内循环(27 -> A, 28 -> B, ...)
|
|
1687
|
+
const letterIndex = ((versionNumber - 1) % 26) + 1;
|
|
1688
|
+
// 转换为大写字母:A=65, B=66, ..., Z=90
|
|
1689
|
+
dataVersionLetter = String.fromCharCode(64 + letterIndex).toUpperCase();
|
|
1589
1690
|
}
|
|
1590
|
-
versionInfo = `机台版本: ${romVersion}\n` +
|
|
1591
|
-
`数据版本: ${dataVersionPrefix} - ${dataVersionLetter}\n`;
|
|
1592
1691
|
}
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
`Rating: ${preview.Rating || '未知'}\n` +
|
|
1596
|
-
(versionInfo ? versionInfo : '') +
|
|
1597
|
-
`登录状态: ${preview.IsLogin === true ? '已登录' : '未登录'}\n` +
|
|
1598
|
-
`封禁状态: ${preview.BanState === 0 ? '正常' : '已封禁'}\n`;
|
|
1599
|
-
}
|
|
1600
|
-
catch (error) {
|
|
1601
|
-
logger.warn('获取用户预览信息失败:', error);
|
|
1602
|
-
statusInfo += `\n⚠️ 无法获取最新状态,请检查API服务`;
|
|
1692
|
+
versionInfo = `机台版本: ${romVersion}\n` +
|
|
1693
|
+
`数据版本: ${dataVersionPrefix} - ${dataVersionLetter}\n`;
|
|
1603
1694
|
}
|
|
1695
|
+
statusInfo += `\n📊 账号信息:\n` +
|
|
1696
|
+
`用户名: ${preview.UserName || '未知'}\n` +
|
|
1697
|
+
`Rating: ${preview.Rating || '未知'}\n` +
|
|
1698
|
+
(versionInfo ? versionInfo : '') +
|
|
1699
|
+
`登录状态: ${preview.IsLogin === true ? '已登录' : '未登录'}\n` +
|
|
1700
|
+
`封禁状态: ${preview.BanState === 0 ? '正常' : '已封禁'}\n`;
|
|
1604
1701
|
}
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
if (binding.userName) {
|
|
1609
|
-
statusInfo += `\n📊 账号信息(缓存):\n` +
|
|
1610
|
-
`用户名: ${binding.userName}\n` +
|
|
1611
|
-
(binding.rating ? `Rating: ${binding.rating}\n` : '');
|
|
1702
|
+
catch (error) {
|
|
1703
|
+
logger.warn('获取用户预览信息失败:', error);
|
|
1704
|
+
statusInfo += `\n⚠️ 无法获取最新状态,请检查API服务`;
|
|
1612
1705
|
}
|
|
1613
|
-
statusInfo += `\n⚠️ 无法获取最新状态,请检查API服务`;
|
|
1614
1706
|
}
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1707
|
+
}
|
|
1708
|
+
catch (error) {
|
|
1709
|
+
// 如果获取失败,使用缓存的信息
|
|
1710
|
+
if (binding.userName) {
|
|
1711
|
+
statusInfo += `\n📊 账号信息(缓存):\n` +
|
|
1712
|
+
`用户名: ${binding.userName}\n` +
|
|
1713
|
+
(binding.rating ? `Rating: ${binding.rating}\n` : '');
|
|
1714
|
+
}
|
|
1715
|
+
statusInfo += `\n⚠️ 无法获取最新状态,请检查API服务`;
|
|
1716
|
+
}
|
|
1717
|
+
// 显示水鱼Token绑定状态
|
|
1718
|
+
if (binding.fishToken) {
|
|
1719
|
+
statusInfo += `\n\n🐟 水鱼Token: 已绑定`;
|
|
1720
|
+
}
|
|
1721
|
+
else {
|
|
1722
|
+
statusInfo += `\n\n🐟 水鱼Token: 未绑定\n使用 /mai绑定水鱼 <token> 进行绑定`;
|
|
1723
|
+
}
|
|
1724
|
+
// 显示落雪代码绑定状态
|
|
1725
|
+
if (binding.lxnsCode) {
|
|
1726
|
+
statusInfo += `\n\n❄️ 落雪代码: 已绑定`;
|
|
1727
|
+
}
|
|
1728
|
+
else {
|
|
1729
|
+
statusInfo += `\n\n❄️ 落雪代码: 未绑定\n使用 /mai绑定落雪 <lxns_code> 进行绑定`;
|
|
1730
|
+
}
|
|
1731
|
+
// 显示保护模式状态(如果未隐藏)
|
|
1732
|
+
if (!hideLockAndProtection) {
|
|
1733
|
+
if (binding.protectionMode) {
|
|
1734
|
+
statusInfo += `\n\n🛡️ 保护模式: 已开启\n使用 /mai保护模式 off 关闭`;
|
|
1618
1735
|
}
|
|
1619
1736
|
else {
|
|
1620
|
-
statusInfo += `\n\n
|
|
1737
|
+
statusInfo += `\n\n🛡️ 保护模式: 未开启\n使用 /mai保护模式 on 开启(自动锁定已下线的账号)`;
|
|
1621
1738
|
}
|
|
1622
|
-
//
|
|
1623
|
-
if (binding.
|
|
1624
|
-
|
|
1739
|
+
// 显示锁定状态(不显示LoginId)
|
|
1740
|
+
if (binding.isLocked) {
|
|
1741
|
+
const lockTime = binding.lockTime
|
|
1742
|
+
? new Date(binding.lockTime).toLocaleString('zh-CN')
|
|
1743
|
+
: '未知';
|
|
1744
|
+
statusInfo += `\n\n🔒 锁定状态: 已锁定`;
|
|
1745
|
+
statusInfo += `\n锁定时间: ${lockTime}`;
|
|
1746
|
+
statusInfo += `\n使用 /mai解锁 可以解锁账号`;
|
|
1625
1747
|
}
|
|
1626
1748
|
else {
|
|
1627
|
-
statusInfo += `\n\n
|
|
1628
|
-
}
|
|
1629
|
-
// 显示保护模式状态(如果未隐藏)
|
|
1630
|
-
if (!hideLockAndProtection) {
|
|
1631
|
-
if (binding.protectionMode) {
|
|
1632
|
-
statusInfo += `\n\n🛡️ 保护模式: 已开启\n使用 /mai保护模式 off 关闭`;
|
|
1633
|
-
}
|
|
1634
|
-
else {
|
|
1635
|
-
statusInfo += `\n\n🛡️ 保护模式: 未开启\n使用 /mai保护模式 on 开启(自动锁定已下线的账号)`;
|
|
1636
|
-
}
|
|
1637
|
-
// 显示锁定状态(不显示LoginId)
|
|
1638
|
-
if (binding.isLocked) {
|
|
1639
|
-
const lockTime = binding.lockTime
|
|
1640
|
-
? new Date(binding.lockTime).toLocaleString('zh-CN')
|
|
1641
|
-
: '未知';
|
|
1642
|
-
statusInfo += `\n\n🔒 锁定状态: 已锁定`;
|
|
1643
|
-
statusInfo += `\n锁定时间: ${lockTime}`;
|
|
1644
|
-
statusInfo += `\n使用 /mai解锁 可以解锁账号`;
|
|
1645
|
-
}
|
|
1646
|
-
else {
|
|
1647
|
-
statusInfo += `\n\n🔒 锁定状态: 未锁定\n使用 /mai锁定 可以锁定账号(防止他人登录)`;
|
|
1648
|
-
}
|
|
1749
|
+
statusInfo += `\n\n🔒 锁定状态: 未锁定\n使用 /mai锁定 可以锁定账号(防止他人登录)`;
|
|
1649
1750
|
}
|
|
1650
|
-
// 显示票券信息
|
|
1651
|
-
// @deprecated getCharge功能已在新API中移除,已注释
|
|
1652
|
-
statusInfo += `\n\n🎫 票券情况: 此功能已在新API中移除`;
|
|
1653
|
-
return statusInfo;
|
|
1654
1751
|
}
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1752
|
+
// 显示票券信息
|
|
1753
|
+
// @deprecated getCharge功能已在新API中移除,已注释
|
|
1754
|
+
statusInfo += `\n\n🎫 票券情况: 此功能已在新API中移除`;
|
|
1755
|
+
return statusInfo;
|
|
1756
|
+
}
|
|
1757
|
+
catch (error) {
|
|
1758
|
+
ctx.logger('maibot').error('查询状态失败:', error);
|
|
1759
|
+
if (maintenanceMode) {
|
|
1760
|
+
return maintenanceMessage;
|
|
1661
1761
|
}
|
|
1662
|
-
|
|
1762
|
+
return `❌ 查询失败: ${error?.message || '未知错误'}\n\n${maintenanceMessage}`;
|
|
1763
|
+
}
|
|
1663
1764
|
});
|
|
1664
1765
|
/**
|
|
1665
1766
|
* 锁定账号(登录保持)
|
|
@@ -2057,103 +2158,104 @@ function apply(ctx, config) {
|
|
|
2057
2158
|
if (!Number.isInteger(multiple) || multiple < 2 || multiple > 6) {
|
|
2058
2159
|
return '❌ 倍数必须是2-6之间的整数\n例如:/mai发票 3\n例如:/mai发票 6 @userid';
|
|
2059
2160
|
}
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
if (
|
|
2082
|
-
|
|
2083
|
-
if (!confirmThird) {
|
|
2084
|
-
return '操作已取消(第三次确认未通过)';
|
|
2085
|
-
}
|
|
2086
|
-
}
|
|
2087
|
-
}
|
|
2088
|
-
// 获取qr_text(交互式或从绑定中获取)
|
|
2089
|
-
const qrTextResult = await getQrText(session, ctx, api, binding, config, rebindTimeout);
|
|
2090
|
-
if (qrTextResult.error) {
|
|
2091
|
-
if (qrTextResult.needRebind) {
|
|
2092
|
-
const rebindResult = await promptForRebind(session, ctx, api, binding, config, rebindTimeout);
|
|
2093
|
-
if (!rebindResult.success) {
|
|
2094
|
-
return `❌ 重新绑定失败:${rebindResult.error || '未知错误'}\n请使用 /mai绑定 重新绑定二维码`;
|
|
2095
|
-
}
|
|
2096
|
-
// 重新绑定成功后,使用新的binding
|
|
2097
|
-
const updatedBinding = rebindResult.newBinding || binding;
|
|
2098
|
-
const retryQrText = await getQrText(session, ctx, api, updatedBinding, config, rebindTimeout);
|
|
2099
|
-
if (retryQrText.error) {
|
|
2100
|
-
return `❌ 获取二维码失败:${retryQrText.error}`;
|
|
2101
|
-
}
|
|
2102
|
-
// 使用新的qrText继续
|
|
2103
|
-
await session.send('请求成功提交,请等待服务器响应。(通常需要2-3分钟)');
|
|
2104
|
-
const ticketResult = await api.getTicket(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, multiple, retryQrText.qrText);
|
|
2105
|
-
if (!ticketResult.TicketStatus || !ticketResult.LoginStatus || !ticketResult.LogoutStatus) {
|
|
2106
|
-
return '❌ 发放功能票失败:服务器返回未成功,请稍后再试';
|
|
2107
|
-
}
|
|
2108
|
-
return `✅ 已发放 ${multiple} 倍票\n请稍等几分钟在游戏内确认`;
|
|
2161
|
+
try {
|
|
2162
|
+
// 获取目标用户绑定
|
|
2163
|
+
const { binding, isProxy, error } = await getTargetBinding(session, targetUserId);
|
|
2164
|
+
if (error || !binding) {
|
|
2165
|
+
return error || '❌ 获取用户绑定失败';
|
|
2166
|
+
}
|
|
2167
|
+
const userId = binding.userId;
|
|
2168
|
+
const proxyTip = isProxy ? `(代操作用户 ${userId})` : '';
|
|
2169
|
+
// 确认操作(如果未使用 -bypass)
|
|
2170
|
+
if (!options?.bypass) {
|
|
2171
|
+
const baseTip = `⚠️ 即将发放 ${multiple} 倍票${proxyTip}`;
|
|
2172
|
+
const confirmFirst = await promptYesLocal(session, `${baseTip}\n操作具有风险,请谨慎`);
|
|
2173
|
+
if (!confirmFirst) {
|
|
2174
|
+
return '操作已取消(第一次确认未通过)';
|
|
2175
|
+
}
|
|
2176
|
+
const confirmSecond = await promptYesLocal(session, '二次确认:若理解风险,请再次输入 Y 执行');
|
|
2177
|
+
if (!confirmSecond) {
|
|
2178
|
+
return '操作已取消(第二次确认未通过)';
|
|
2179
|
+
}
|
|
2180
|
+
if (multiple >= 3) {
|
|
2181
|
+
const confirmThird = await promptYesLocal(session, '第三次确认:3倍及以上票券风险更高,确定继续?');
|
|
2182
|
+
if (!confirmThird) {
|
|
2183
|
+
return '操作已取消(第三次确认未通过)';
|
|
2109
2184
|
}
|
|
2110
|
-
return `❌ 获取二维码失败:${qrTextResult.error}`;
|
|
2111
|
-
}
|
|
2112
|
-
await session.send('请求成功提交,请等待服务器响应。(通常需要2-3分钟)');
|
|
2113
|
-
// 使用新API获取功能票(需要qr_text)
|
|
2114
|
-
let ticketResult;
|
|
2115
|
-
try {
|
|
2116
|
-
ticketResult = await api.getTicket(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, multiple, qrTextResult.qrText);
|
|
2117
2185
|
}
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
}
|
|
2127
|
-
ticketResult = await api.getTicket(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, multiple, retryQrText.qrText);
|
|
2186
|
+
}
|
|
2187
|
+
// 获取qr_text(交互式或从绑定中获取)
|
|
2188
|
+
const qrTextResult = await getQrText(session, ctx, api, binding, config, rebindTimeout);
|
|
2189
|
+
if (qrTextResult.error) {
|
|
2190
|
+
if (qrTextResult.needRebind) {
|
|
2191
|
+
const rebindResult = await promptForRebind(session, ctx, api, binding, config, rebindTimeout);
|
|
2192
|
+
if (!rebindResult.success) {
|
|
2193
|
+
return `❌ 重新绑定失败:${rebindResult.error || '未知错误'}\n请使用 /mai绑定 重新绑定二维码`;
|
|
2128
2194
|
}
|
|
2129
|
-
|
|
2130
|
-
|
|
2195
|
+
// 重新绑定成功后,使用新的binding
|
|
2196
|
+
const updatedBinding = rebindResult.newBinding || binding;
|
|
2197
|
+
const retryQrText = await getQrText(session, ctx, api, updatedBinding, config, rebindTimeout);
|
|
2198
|
+
if (retryQrText.error) {
|
|
2199
|
+
return `❌ 获取二维码失败:${retryQrText.error}`;
|
|
2131
2200
|
}
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
//
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
}
|
|
2140
|
-
return `❌ 发放功能票失败:服务器返回未成功\n重新绑定失败:${rebindResult.error || '未知错误'}`;
|
|
2201
|
+
// 在调用API前加入队列
|
|
2202
|
+
await waitForQueue(session);
|
|
2203
|
+
// 使用新的qrText继续
|
|
2204
|
+
await session.send('请求成功提交,请等待服务器响应。(通常需要2-3分钟)');
|
|
2205
|
+
const ticketResult = await api.getTicket(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, multiple, retryQrText.qrText);
|
|
2206
|
+
if (!ticketResult.TicketStatus || !ticketResult.LoginStatus || !ticketResult.LogoutStatus) {
|
|
2207
|
+
return '❌ 发放功能票失败:服务器返回未成功,请稍后再试';
|
|
2141
2208
|
}
|
|
2142
|
-
return
|
|
2209
|
+
return `✅ 已发放 ${multiple} 倍票\n请稍等几分钟在游戏内确认`;
|
|
2143
2210
|
}
|
|
2144
|
-
return
|
|
2211
|
+
return `❌ 获取二维码失败:${qrTextResult.error}`;
|
|
2212
|
+
}
|
|
2213
|
+
// 在调用API前加入队列
|
|
2214
|
+
await waitForQueue(session);
|
|
2215
|
+
await session.send('请求成功提交,请等待服务器响应。(通常需要2-3分钟)');
|
|
2216
|
+
// 使用新API获取功能票(需要qr_text)
|
|
2217
|
+
let ticketResult;
|
|
2218
|
+
try {
|
|
2219
|
+
ticketResult = await api.getTicket(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, multiple, qrTextResult.qrText);
|
|
2145
2220
|
}
|
|
2146
2221
|
catch (error) {
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2222
|
+
// 如果API返回失败,可能需要重新绑定
|
|
2223
|
+
const failureResult = await handleApiFailure(session, ctx, api, binding, config, error, rebindTimeout);
|
|
2224
|
+
if (failureResult.rebindResult && failureResult.rebindResult.success && failureResult.rebindResult.newBinding) {
|
|
2225
|
+
// 重新绑定成功,重试获取功能票
|
|
2226
|
+
const retryQrText = await getQrText(session, ctx, api, failureResult.rebindResult.newBinding, config, rebindTimeout);
|
|
2227
|
+
if (retryQrText.error) {
|
|
2228
|
+
return `❌ 重新绑定后获取二维码失败:${retryQrText.error}`;
|
|
2229
|
+
}
|
|
2230
|
+
ticketResult = await api.getTicket(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, multiple, retryQrText.qrText);
|
|
2150
2231
|
}
|
|
2151
|
-
|
|
2152
|
-
|
|
2232
|
+
else {
|
|
2233
|
+
throw error;
|
|
2153
2234
|
}
|
|
2154
|
-
return `❌ 发票失败: ${error?.message || '未知错误'}\n\n${maintenanceMessage}`;
|
|
2155
2235
|
}
|
|
2156
|
-
|
|
2236
|
+
if (!ticketResult.TicketStatus || !ticketResult.LoginStatus || !ticketResult.LogoutStatus) {
|
|
2237
|
+
// 如果返回失败,可能需要重新绑定
|
|
2238
|
+
if (!ticketResult.QrStatus || ticketResult.LoginStatus === false) {
|
|
2239
|
+
const rebindResult = await promptForRebind(session, ctx, api, binding, config, rebindTimeout);
|
|
2240
|
+
if (rebindResult.success && rebindResult.newBinding) {
|
|
2241
|
+
return `✅ 重新绑定成功!请重新执行发票操作。`;
|
|
2242
|
+
}
|
|
2243
|
+
return `❌ 发放功能票失败:服务器返回未成功\n重新绑定失败:${rebindResult.error || '未知错误'}`;
|
|
2244
|
+
}
|
|
2245
|
+
return '❌ 发票失败:服务器返回未成功,请确认是否已在短时间内多次执行发票指令或稍后再试或点击获取二维码刷新账号后再试。';
|
|
2246
|
+
}
|
|
2247
|
+
return `✅ 已发放 ${multiple} 倍票\n请稍等几分钟在游戏内确认`;
|
|
2248
|
+
}
|
|
2249
|
+
catch (error) {
|
|
2250
|
+
logger.error('发票失败:', error);
|
|
2251
|
+
if (maintenanceMode) {
|
|
2252
|
+
return maintenanceMessage;
|
|
2253
|
+
}
|
|
2254
|
+
if (error?.response) {
|
|
2255
|
+
return `❌ API请求失败: ${error.response.status} ${error.response.statusText}\n\n${maintenanceMessage}`;
|
|
2256
|
+
}
|
|
2257
|
+
return `❌ 发票失败: ${error?.message || '未知错误'}\n\n${maintenanceMessage}`;
|
|
2258
|
+
}
|
|
2157
2259
|
});
|
|
2158
2260
|
/**
|
|
2159
2261
|
* 舞里程发放 / 签到
|
|
@@ -2259,145 +2361,148 @@ function apply(ctx, config) {
|
|
|
2259
2361
|
if (!whitelistCheck.allowed) {
|
|
2260
2362
|
return whitelistCheck.message || '本群暂时没有被授权使用本Bot的功能,请添加官方群聊1072033605。';
|
|
2261
2363
|
}
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
if (
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
await tryRecallMessage(session, ctx, config);
|
|
2274
|
-
qrCode = processed.qrText;
|
|
2275
|
-
}
|
|
2276
|
-
else {
|
|
2277
|
-
// 不是SGID,可能是targetUserId
|
|
2278
|
-
targetUserId = qrCodeOrTarget;
|
|
2279
|
-
}
|
|
2280
|
-
}
|
|
2281
|
-
// 获取目标用户绑定
|
|
2282
|
-
const { binding, isProxy, error } = await getTargetBinding(session, targetUserId);
|
|
2283
|
-
if (error || !binding) {
|
|
2284
|
-
return error || '❌ 获取用户绑定失败';
|
|
2285
|
-
}
|
|
2286
|
-
const userId = binding.userId;
|
|
2287
|
-
// 检查是否已绑定水鱼Token
|
|
2288
|
-
if (!binding.fishToken) {
|
|
2289
|
-
return '❌ 请先绑定水鱼Token\n使用 /mai绑定水鱼 <token> 进行绑定';
|
|
2290
|
-
}
|
|
2291
|
-
// 维护时间内直接提示,不发起上传请求
|
|
2292
|
-
const maintenanceMsg = getMaintenanceMessage(maintenanceNotice);
|
|
2293
|
-
if (maintenanceMsg) {
|
|
2294
|
-
return maintenanceMsg;
|
|
2295
|
-
}
|
|
2296
|
-
// 获取qr_text(如果提供了SGID参数则直接使用,否则交互式获取)
|
|
2297
|
-
let qrTextResult;
|
|
2298
|
-
if (qrCode) {
|
|
2299
|
-
// 验证qrCode是否有效
|
|
2300
|
-
try {
|
|
2301
|
-
const preview = await api.getPreview(config.machineInfo.clientId, qrCode);
|
|
2302
|
-
if (preview.UserID === -1 || (typeof preview.UserID === 'string' && preview.UserID === '-1')) {
|
|
2303
|
-
return '❌ 无效或过期的二维码,请重新发送';
|
|
2304
|
-
}
|
|
2305
|
-
qrTextResult = { qrText: qrCode };
|
|
2306
|
-
}
|
|
2307
|
-
catch (error) {
|
|
2308
|
-
return `❌ 验证二维码失败:${error?.message || '未知错误'}`;
|
|
2309
|
-
}
|
|
2364
|
+
try {
|
|
2365
|
+
// 解析参数:可能是SGID或targetUserId
|
|
2366
|
+
let qrCode;
|
|
2367
|
+
let targetUserId;
|
|
2368
|
+
// 检查第一个参数是否是SGID或URL
|
|
2369
|
+
if (qrCodeOrTarget) {
|
|
2370
|
+
const processed = processSGID(qrCodeOrTarget);
|
|
2371
|
+
if (processed) {
|
|
2372
|
+
// 是SGID或URL,尝试撤回
|
|
2373
|
+
await tryRecallMessage(session, ctx, config);
|
|
2374
|
+
qrCode = processed.qrText;
|
|
2310
2375
|
}
|
|
2311
2376
|
else {
|
|
2312
|
-
//
|
|
2313
|
-
|
|
2314
|
-
}
|
|
2315
|
-
if (qrTextResult.error) {
|
|
2316
|
-
if (qrTextResult.needRebind) {
|
|
2317
|
-
const rebindResult = await promptForRebind(session, ctx, api, binding, config, rebindTimeout);
|
|
2318
|
-
if (!rebindResult.success) {
|
|
2319
|
-
return `❌ 重新绑定失败:${rebindResult.error || '未知错误'}\n请使用 /mai绑定 重新绑定二维码`;
|
|
2320
|
-
}
|
|
2321
|
-
// 重新绑定成功后,使用新的binding
|
|
2322
|
-
const updatedBinding = rebindResult.newBinding || binding;
|
|
2323
|
-
const retryQrText = await getQrText(session, ctx, api, updatedBinding, config, rebindTimeout);
|
|
2324
|
-
if (retryQrText.error) {
|
|
2325
|
-
return `❌ 获取二维码失败:${retryQrText.error}`;
|
|
2326
|
-
}
|
|
2327
|
-
// 使用新的qrText继续
|
|
2328
|
-
const result = await api.uploadB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, retryQrText.qrText, binding.fishToken);
|
|
2329
|
-
if (!result.UploadStatus) {
|
|
2330
|
-
if (result.msg === '该账号下存在未完成的任务') {
|
|
2331
|
-
return '⚠️ 当前账号已有未完成的水鱼B50任务,请稍后再试,无需重复上传。';
|
|
2332
|
-
}
|
|
2333
|
-
const taskIdInfo = result.task_id ? `\n任务ID: ${result.task_id}` : '';
|
|
2334
|
-
return `❌ 上传失败:${result.msg || '未知错误'}${taskIdInfo}`;
|
|
2335
|
-
}
|
|
2336
|
-
scheduleB50Notification(session, result.task_id);
|
|
2337
|
-
return `✅ B50上传任务已提交!\n任务ID: ${result.task_id}\n\n请耐心等待任务完成,预计1-10分钟`;
|
|
2338
|
-
}
|
|
2339
|
-
return `❌ 获取二维码失败:${qrTextResult.error}`;
|
|
2377
|
+
// 不是SGID,可能是targetUserId
|
|
2378
|
+
targetUserId = qrCodeOrTarget;
|
|
2340
2379
|
}
|
|
2341
|
-
|
|
2342
|
-
|
|
2380
|
+
}
|
|
2381
|
+
// 获取目标用户绑定
|
|
2382
|
+
const { binding, isProxy, error } = await getTargetBinding(session, targetUserId);
|
|
2383
|
+
if (error || !binding) {
|
|
2384
|
+
return error || '❌ 获取用户绑定失败';
|
|
2385
|
+
}
|
|
2386
|
+
const userId = binding.userId;
|
|
2387
|
+
// 检查是否已绑定水鱼Token
|
|
2388
|
+
if (!binding.fishToken) {
|
|
2389
|
+
return '❌ 请先绑定水鱼Token\n使用 /mai绑定水鱼 <token> 进行绑定';
|
|
2390
|
+
}
|
|
2391
|
+
// 维护时间内直接提示,不发起上传请求
|
|
2392
|
+
const maintenanceMsg = getMaintenanceMessage(maintenanceNotice);
|
|
2393
|
+
if (maintenanceMsg) {
|
|
2394
|
+
return maintenanceMsg;
|
|
2395
|
+
}
|
|
2396
|
+
// 获取qr_text(如果提供了SGID参数则直接使用,否则交互式获取)
|
|
2397
|
+
let qrTextResult;
|
|
2398
|
+
if (qrCode) {
|
|
2399
|
+
// 验证qrCode是否有效
|
|
2343
2400
|
try {
|
|
2344
|
-
|
|
2401
|
+
const preview = await api.getPreview(config.machineInfo.clientId, qrCode);
|
|
2402
|
+
if (preview.UserID === -1 || (typeof preview.UserID === 'string' && preview.UserID === '-1')) {
|
|
2403
|
+
return '❌ 无效或过期的二维码,请重新发送';
|
|
2404
|
+
}
|
|
2405
|
+
qrTextResult = { qrText: qrCode };
|
|
2345
2406
|
}
|
|
2346
2407
|
catch (error) {
|
|
2347
|
-
|
|
2348
|
-
const failureResult = await handleApiFailure(session, ctx, api, binding, config, error, rebindTimeout);
|
|
2349
|
-
if (failureResult.rebindResult && failureResult.rebindResult.success && failureResult.rebindResult.newBinding) {
|
|
2350
|
-
// 重新绑定成功,重试上传
|
|
2351
|
-
const retryQrText = await getQrText(session, ctx, api, failureResult.rebindResult.newBinding, config, rebindTimeout);
|
|
2352
|
-
if (retryQrText.error) {
|
|
2353
|
-
return `❌ 重新绑定后获取二维码失败:${retryQrText.error}`;
|
|
2354
|
-
}
|
|
2355
|
-
result = await api.uploadB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, retryQrText.qrText, binding.fishToken);
|
|
2356
|
-
}
|
|
2357
|
-
else {
|
|
2358
|
-
throw error;
|
|
2359
|
-
}
|
|
2408
|
+
return `❌ 验证二维码失败:${error?.message || '未知错误'}`;
|
|
2360
2409
|
}
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2410
|
+
}
|
|
2411
|
+
else {
|
|
2412
|
+
// 交互式获取
|
|
2413
|
+
qrTextResult = await getQrText(session, ctx, api, binding, config, rebindTimeout);
|
|
2414
|
+
}
|
|
2415
|
+
if (qrTextResult.error) {
|
|
2416
|
+
if (qrTextResult.needRebind) {
|
|
2417
|
+
const rebindResult = await promptForRebind(session, ctx, api, binding, config, rebindTimeout);
|
|
2418
|
+
if (!rebindResult.success) {
|
|
2419
|
+
return `❌ 重新绑定失败:${rebindResult.error || '未知错误'}\n请使用 /mai绑定 重新绑定二维码`;
|
|
2420
|
+
}
|
|
2421
|
+
// 重新绑定成功后,使用新的binding
|
|
2422
|
+
const updatedBinding = rebindResult.newBinding || binding;
|
|
2423
|
+
const retryQrText = await getQrText(session, ctx, api, updatedBinding, config, rebindTimeout);
|
|
2424
|
+
if (retryQrText.error) {
|
|
2425
|
+
return `❌ 获取二维码失败:${retryQrText.error}`;
|
|
2364
2426
|
}
|
|
2365
|
-
//
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2427
|
+
// 在调用API前加入队列
|
|
2428
|
+
await waitForQueue(session);
|
|
2429
|
+
// 使用新的qrText继续
|
|
2430
|
+
const result = await api.uploadB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, retryQrText.qrText, binding.fishToken);
|
|
2431
|
+
if (!result.UploadStatus) {
|
|
2432
|
+
if (result.msg === '该账号下存在未完成的任务') {
|
|
2433
|
+
return '⚠️ 当前账号已有未完成的水鱼B50任务,请稍后再试,无需重复上传。';
|
|
2370
2434
|
}
|
|
2371
2435
|
const taskIdInfo = result.task_id ? `\n任务ID: ${result.task_id}` : '';
|
|
2372
|
-
return `❌ 上传失败:${result.msg || '未知错误'}
|
|
2436
|
+
return `❌ 上传失败:${result.msg || '未知错误'}${taskIdInfo}`;
|
|
2373
2437
|
}
|
|
2374
|
-
|
|
2375
|
-
return
|
|
2438
|
+
scheduleB50Notification(session, result.task_id);
|
|
2439
|
+
return `✅ B50上传任务已提交!\n任务ID: ${result.task_id}\n\n请耐心等待任务完成,预计1-10分钟`;
|
|
2376
2440
|
}
|
|
2377
|
-
|
|
2378
|
-
|
|
2441
|
+
return `❌ 获取二维码失败:${qrTextResult.error}`;
|
|
2442
|
+
}
|
|
2443
|
+
// 在调用API前加入队列
|
|
2444
|
+
await waitForQueue(session);
|
|
2445
|
+
// 上传B50(使用新API,需要qr_text)
|
|
2446
|
+
let result;
|
|
2447
|
+
try {
|
|
2448
|
+
result = await api.uploadB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, qrTextResult.qrText, binding.fishToken);
|
|
2379
2449
|
}
|
|
2380
2450
|
catch (error) {
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
const maintenanceMsg = getMaintenanceMessage(maintenanceNotice);
|
|
2389
|
-
if (maintenanceMsg) {
|
|
2390
|
-
msg += `\n${maintenanceMsg}`;
|
|
2451
|
+
// 如果API返回失败,可能需要重新绑定
|
|
2452
|
+
const failureResult = await handleApiFailure(session, ctx, api, binding, config, error, rebindTimeout);
|
|
2453
|
+
if (failureResult.rebindResult && failureResult.rebindResult.success && failureResult.rebindResult.newBinding) {
|
|
2454
|
+
// 重新绑定成功,重试上传
|
|
2455
|
+
const retryQrText = await getQrText(session, ctx, api, failureResult.rebindResult.newBinding, config, rebindTimeout);
|
|
2456
|
+
if (retryQrText.error) {
|
|
2457
|
+
return `❌ 重新绑定后获取二维码失败:${retryQrText.error}`;
|
|
2391
2458
|
}
|
|
2392
|
-
|
|
2393
|
-
|
|
2459
|
+
// 在调用API前加入队列
|
|
2460
|
+
await waitForQueue(session);
|
|
2461
|
+
result = await api.uploadB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, retryQrText.qrText, binding.fishToken);
|
|
2394
2462
|
}
|
|
2395
|
-
|
|
2396
|
-
|
|
2463
|
+
else {
|
|
2464
|
+
throw error;
|
|
2397
2465
|
}
|
|
2398
|
-
return `❌ 上传失败: ${error?.message || '未知错误'}\n\n${maintenanceMessage}`;
|
|
2399
2466
|
}
|
|
2400
|
-
|
|
2467
|
+
if (!result.UploadStatus) {
|
|
2468
|
+
if (result.msg === '该账号下存在未完成的任务') {
|
|
2469
|
+
return '⚠️ 当前账号已有未完成的水鱼B50任务,请耐心等待任务完成,预计1-10分钟,无需重复上传。';
|
|
2470
|
+
}
|
|
2471
|
+
// 如果返回失败,可能需要重新绑定
|
|
2472
|
+
if (result.msg?.includes('二维码') || result.msg?.includes('qr_text') || result.msg?.includes('无效')) {
|
|
2473
|
+
const rebindResult = await promptForRebind(session, ctx, api, binding, config, rebindTimeout);
|
|
2474
|
+
if (rebindResult.success && rebindResult.newBinding) {
|
|
2475
|
+
return `✅ 重新绑定成功!请重新执行上传操作。`;
|
|
2476
|
+
}
|
|
2477
|
+
const taskIdInfo = result.task_id ? `\n任务ID: ${result.task_id}` : '';
|
|
2478
|
+
return `❌ 上传失败:${result.msg || '未知错误'}\n重新绑定失败:${rebindResult.error || '未知错误'}${taskIdInfo}`;
|
|
2479
|
+
}
|
|
2480
|
+
const taskIdInfo = result.task_id ? `\n任务ID: ${result.task_id}` : '';
|
|
2481
|
+
return `❌ 上传失败:${result.msg || '未知错误'}${taskIdInfo}`;
|
|
2482
|
+
}
|
|
2483
|
+
scheduleB50Notification(session, result.task_id);
|
|
2484
|
+
return `✅ B50上传任务已提交!\n任务ID: ${result.task_id}\n\n请耐心等待任务完成,预计1-10分钟`;
|
|
2485
|
+
}
|
|
2486
|
+
catch (error) {
|
|
2487
|
+
ctx.logger('maibot').error('上传B50失败:', error);
|
|
2488
|
+
if (maintenanceMode) {
|
|
2489
|
+
return maintenanceMessage;
|
|
2490
|
+
}
|
|
2491
|
+
// 处理请求超时类错误,统一提示
|
|
2492
|
+
if (error?.code === 'ECONNABORTED' || String(error?.message || '').includes('timeout')) {
|
|
2493
|
+
let msg = '水鱼B50任务 上传失败,请稍后再试一次。';
|
|
2494
|
+
const maintenanceMsg = getMaintenanceMessage(maintenanceNotice);
|
|
2495
|
+
if (maintenanceMsg) {
|
|
2496
|
+
msg += `\n${maintenanceMsg}`;
|
|
2497
|
+
}
|
|
2498
|
+
msg += `\n\n${maintenanceMessage}`;
|
|
2499
|
+
return msg;
|
|
2500
|
+
}
|
|
2501
|
+
if (error?.response) {
|
|
2502
|
+
return `❌ API请求失败: ${error.response.status} ${error.response.statusText}\n\n${maintenanceMessage}`;
|
|
2503
|
+
}
|
|
2504
|
+
return `❌ 上传失败: ${error?.message || '未知错误'}\n\n${maintenanceMessage}`;
|
|
2505
|
+
}
|
|
2401
2506
|
});
|
|
2402
2507
|
/**
|
|
2403
2508
|
* 清空功能票
|
|
@@ -2858,159 +2963,162 @@ function apply(ctx, config) {
|
|
|
2858
2963
|
if (!whitelistCheck.allowed) {
|
|
2859
2964
|
return whitelistCheck.message || '本群暂时没有被授权使用本Bot的功能,请添加官方群聊1072033605。';
|
|
2860
2965
|
}
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
if (
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
await tryRecallMessage(session, ctx, config);
|
|
2874
|
-
qrCode = processed.qrText;
|
|
2875
|
-
}
|
|
2876
|
-
else if (qrCodeOrLxnsCode.length === 15) {
|
|
2877
|
-
// 可能是落雪代码(15个字符)
|
|
2878
|
-
lxnsCode = qrCodeOrLxnsCode;
|
|
2879
|
-
}
|
|
2880
|
-
else {
|
|
2881
|
-
// 可能是targetUserId
|
|
2882
|
-
actualTargetUserId = qrCodeOrLxnsCode;
|
|
2883
|
-
}
|
|
2884
|
-
}
|
|
2885
|
-
// 获取目标用户绑定
|
|
2886
|
-
const { binding, isProxy, error } = await getTargetBinding(session, actualTargetUserId);
|
|
2887
|
-
if (error || !binding) {
|
|
2888
|
-
return error || '❌ 获取用户绑定失败';
|
|
2889
|
-
}
|
|
2890
|
-
const userId = binding.userId;
|
|
2891
|
-
// 确定使用的落雪代码
|
|
2892
|
-
let finalLxnsCode;
|
|
2893
|
-
if (lxnsCode) {
|
|
2894
|
-
// 如果提供了参数,使用参数
|
|
2895
|
-
finalLxnsCode = lxnsCode;
|
|
2896
|
-
}
|
|
2897
|
-
else {
|
|
2898
|
-
// 如果没有提供参数,使用绑定的代码
|
|
2899
|
-
if (!binding.lxnsCode) {
|
|
2900
|
-
return '❌ 请先绑定落雪代码或提供落雪代码参数\n使用 /mai绑定落雪 <lxns_code> 进行绑定\n或使用 /mai上传落雪b50 <lxns_code> 直接提供代码';
|
|
2901
|
-
}
|
|
2902
|
-
finalLxnsCode = binding.lxnsCode;
|
|
2903
|
-
}
|
|
2904
|
-
// 维护时间内直接提示,不发起上传请求
|
|
2905
|
-
const maintenanceMsg = getMaintenanceMessage(maintenanceNotice);
|
|
2906
|
-
if (maintenanceMsg) {
|
|
2907
|
-
return maintenanceMsg;
|
|
2966
|
+
try {
|
|
2967
|
+
// 解析参数:第一个参数可能是SGID/URL或落雪代码
|
|
2968
|
+
let qrCode;
|
|
2969
|
+
let lxnsCode;
|
|
2970
|
+
let actualTargetUserId = targetUserId;
|
|
2971
|
+
// 检查第一个参数是否是SGID或URL
|
|
2972
|
+
if (qrCodeOrLxnsCode) {
|
|
2973
|
+
const processed = processSGID(qrCodeOrLxnsCode);
|
|
2974
|
+
if (processed) {
|
|
2975
|
+
// 是SGID或URL,尝试撤回
|
|
2976
|
+
await tryRecallMessage(session, ctx, config);
|
|
2977
|
+
qrCode = processed.qrText;
|
|
2908
2978
|
}
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
// 验证qrCode是否有效
|
|
2913
|
-
try {
|
|
2914
|
-
const preview = await api.getPreview(config.machineInfo.clientId, qrCode);
|
|
2915
|
-
if (preview.UserID === -1 || (typeof preview.UserID === 'string' && preview.UserID === '-1')) {
|
|
2916
|
-
return '❌ 无效或过期的二维码,请重新发送';
|
|
2917
|
-
}
|
|
2918
|
-
qrTextResult = { qrText: qrCode };
|
|
2919
|
-
}
|
|
2920
|
-
catch (error) {
|
|
2921
|
-
return `❌ 验证二维码失败:${error?.message || '未知错误'}`;
|
|
2922
|
-
}
|
|
2979
|
+
else if (qrCodeOrLxnsCode.length === 15) {
|
|
2980
|
+
// 可能是落雪代码(15个字符)
|
|
2981
|
+
lxnsCode = qrCodeOrLxnsCode;
|
|
2923
2982
|
}
|
|
2924
2983
|
else {
|
|
2925
|
-
//
|
|
2926
|
-
|
|
2984
|
+
// 可能是targetUserId
|
|
2985
|
+
actualTargetUserId = qrCodeOrLxnsCode;
|
|
2927
2986
|
}
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
}
|
|
2946
|
-
const taskIdInfo = result.task_id ? `\n任务ID: ${result.task_id}` : '';
|
|
2947
|
-
return `❌ 上传失败:${result.msg || '未知错误'}${taskIdInfo}`;
|
|
2948
|
-
}
|
|
2949
|
-
scheduleLxB50Notification(session, result.task_id);
|
|
2950
|
-
return `✅ 落雪B50上传任务已提交!\n任务ID: ${result.task_id}\n\n请耐心等待任务完成,预计1-10分钟`;
|
|
2951
|
-
}
|
|
2952
|
-
return `❌ 获取二维码失败:${qrTextResult.error}`;
|
|
2987
|
+
}
|
|
2988
|
+
// 获取目标用户绑定
|
|
2989
|
+
const { binding, isProxy, error } = await getTargetBinding(session, actualTargetUserId);
|
|
2990
|
+
if (error || !binding) {
|
|
2991
|
+
return error || '❌ 获取用户绑定失败';
|
|
2992
|
+
}
|
|
2993
|
+
const userId = binding.userId;
|
|
2994
|
+
// 确定使用的落雪代码
|
|
2995
|
+
let finalLxnsCode;
|
|
2996
|
+
if (lxnsCode) {
|
|
2997
|
+
// 如果提供了参数,使用参数
|
|
2998
|
+
finalLxnsCode = lxnsCode;
|
|
2999
|
+
}
|
|
3000
|
+
else {
|
|
3001
|
+
// 如果没有提供参数,使用绑定的代码
|
|
3002
|
+
if (!binding.lxnsCode) {
|
|
3003
|
+
return '❌ 请先绑定落雪代码或提供落雪代码参数\n使用 /mai绑定落雪 <lxns_code> 进行绑定\n或使用 /mai上传落雪b50 <lxns_code> 直接提供代码';
|
|
2953
3004
|
}
|
|
2954
|
-
|
|
2955
|
-
|
|
3005
|
+
finalLxnsCode = binding.lxnsCode;
|
|
3006
|
+
}
|
|
3007
|
+
// 维护时间内直接提示,不发起上传请求
|
|
3008
|
+
const maintenanceMsg = getMaintenanceMessage(maintenanceNotice);
|
|
3009
|
+
if (maintenanceMsg) {
|
|
3010
|
+
return maintenanceMsg;
|
|
3011
|
+
}
|
|
3012
|
+
// 获取qr_text(如果提供了SGID参数则直接使用,否则交互式获取)
|
|
3013
|
+
let qrTextResult;
|
|
3014
|
+
if (qrCode) {
|
|
3015
|
+
// 验证qrCode是否有效
|
|
2956
3016
|
try {
|
|
2957
|
-
|
|
3017
|
+
const preview = await api.getPreview(config.machineInfo.clientId, qrCode);
|
|
3018
|
+
if (preview.UserID === -1 || (typeof preview.UserID === 'string' && preview.UserID === '-1')) {
|
|
3019
|
+
return '❌ 无效或过期的二维码,请重新发送';
|
|
3020
|
+
}
|
|
3021
|
+
qrTextResult = { qrText: qrCode };
|
|
2958
3022
|
}
|
|
2959
3023
|
catch (error) {
|
|
2960
|
-
|
|
2961
|
-
const failureResult = await handleApiFailure(session, ctx, api, binding, config, error, rebindTimeout);
|
|
2962
|
-
if (failureResult.rebindResult && failureResult.rebindResult.success && failureResult.rebindResult.newBinding) {
|
|
2963
|
-
// 重新绑定成功,重试上传
|
|
2964
|
-
const retryQrText = await getQrText(session, ctx, api, failureResult.rebindResult.newBinding, config, rebindTimeout);
|
|
2965
|
-
if (retryQrText.error) {
|
|
2966
|
-
return `❌ 重新绑定后获取二维码失败:${retryQrText.error}`;
|
|
2967
|
-
}
|
|
2968
|
-
result = await api.uploadLxB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, retryQrText.qrText, finalLxnsCode);
|
|
2969
|
-
}
|
|
2970
|
-
else {
|
|
2971
|
-
throw error;
|
|
2972
|
-
}
|
|
3024
|
+
return `❌ 验证二维码失败:${error?.message || '未知错误'}`;
|
|
2973
3025
|
}
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
3026
|
+
}
|
|
3027
|
+
else {
|
|
3028
|
+
// 交互式获取
|
|
3029
|
+
qrTextResult = await getQrText(session, ctx, api, binding, config, rebindTimeout);
|
|
3030
|
+
}
|
|
3031
|
+
if (qrTextResult.error) {
|
|
3032
|
+
if (qrTextResult.needRebind) {
|
|
3033
|
+
const rebindResult = await promptForRebind(session, ctx, api, binding, config, rebindTimeout);
|
|
3034
|
+
if (!rebindResult.success) {
|
|
3035
|
+
return `❌ 重新绑定失败:${rebindResult.error || '未知错误'}\n请使用 /mai绑定 重新绑定二维码`;
|
|
2977
3036
|
}
|
|
2978
|
-
//
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
3037
|
+
// 重新绑定成功后,使用新的binding
|
|
3038
|
+
const updatedBinding = rebindResult.newBinding || binding;
|
|
3039
|
+
const retryQrText = await getQrText(session, ctx, api, updatedBinding, config, rebindTimeout);
|
|
3040
|
+
if (retryQrText.error) {
|
|
3041
|
+
return `❌ 获取二维码失败:${retryQrText.error}`;
|
|
3042
|
+
}
|
|
3043
|
+
// 在调用API前加入队列
|
|
3044
|
+
await waitForQueue(session);
|
|
3045
|
+
// 使用新的qrText继续
|
|
3046
|
+
const result = await api.uploadLxB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, retryQrText.qrText, finalLxnsCode);
|
|
3047
|
+
if (!result.UploadStatus) {
|
|
3048
|
+
if (result.msg === '该账号下存在未完成的任务') {
|
|
3049
|
+
return '⚠️ 当前账号已有未完成的落雪B50任务,请稍后再试,无需重复上传。';
|
|
2983
3050
|
}
|
|
2984
3051
|
const taskIdInfo = result.task_id ? `\n任务ID: ${result.task_id}` : '';
|
|
2985
|
-
return `❌ 上传失败:${result.msg || '未知错误'}
|
|
3052
|
+
return `❌ 上传失败:${result.msg || '未知错误'}${taskIdInfo}`;
|
|
2986
3053
|
}
|
|
2987
|
-
|
|
2988
|
-
return
|
|
3054
|
+
scheduleLxB50Notification(session, result.task_id);
|
|
3055
|
+
return `✅ 落雪B50上传任务已提交!\n任务ID: ${result.task_id}\n\n请耐心等待任务完成,预计1-10分钟`;
|
|
2989
3056
|
}
|
|
2990
|
-
|
|
2991
|
-
|
|
3057
|
+
return `❌ 获取二维码失败:${qrTextResult.error}`;
|
|
3058
|
+
}
|
|
3059
|
+
// 在调用API前加入队列
|
|
3060
|
+
await waitForQueue(session);
|
|
3061
|
+
// 上传落雪B50(使用新API,需要qr_text)
|
|
3062
|
+
let result;
|
|
3063
|
+
try {
|
|
3064
|
+
result = await api.uploadLxB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, qrTextResult.qrText, finalLxnsCode);
|
|
2992
3065
|
}
|
|
2993
3066
|
catch (error) {
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3067
|
+
// 如果API返回失败,可能需要重新绑定
|
|
3068
|
+
const failureResult = await handleApiFailure(session, ctx, api, binding, config, error, rebindTimeout);
|
|
3069
|
+
if (failureResult.rebindResult && failureResult.rebindResult.success && failureResult.rebindResult.newBinding) {
|
|
3070
|
+
// 重新绑定成功,重试上传
|
|
3071
|
+
const retryQrText = await getQrText(session, ctx, api, failureResult.rebindResult.newBinding, config, rebindTimeout);
|
|
3072
|
+
if (retryQrText.error) {
|
|
3073
|
+
return `❌ 重新绑定后获取二维码失败:${retryQrText.error}`;
|
|
3074
|
+
}
|
|
3075
|
+
// 在调用API前加入队列
|
|
3076
|
+
await waitForQueue(session);
|
|
3077
|
+
result = await api.uploadLxB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, retryQrText.qrText, finalLxnsCode);
|
|
3078
|
+
}
|
|
3079
|
+
else {
|
|
3080
|
+
throw error;
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
if (!result.UploadStatus) {
|
|
3084
|
+
if (result.msg === '该账号下存在未完成的任务') {
|
|
3085
|
+
return '⚠️ 当前账号已有未完成的落雪B50任务,请耐心等待任务完成,预计1-10分钟,无需重复上传。';
|
|
3086
|
+
}
|
|
3087
|
+
// 如果返回失败,可能需要重新绑定
|
|
3088
|
+
if (result.msg?.includes('二维码') || result.msg?.includes('qr_text') || result.msg?.includes('无效')) {
|
|
3089
|
+
const rebindResult = await promptForRebind(session, ctx, api, binding, config, rebindTimeout);
|
|
3090
|
+
if (rebindResult.success && rebindResult.newBinding) {
|
|
3091
|
+
return `✅ 重新绑定成功!请重新执行上传操作。`;
|
|
3004
3092
|
}
|
|
3005
|
-
|
|
3006
|
-
return msg
|
|
3093
|
+
const taskIdInfo = result.task_id ? `\n任务ID: ${result.task_id}` : '';
|
|
3094
|
+
return `❌ 上传失败:${result.msg || '未知错误'}\n重新绑定失败:${rebindResult.error || '未知错误'}${taskIdInfo}`;
|
|
3007
3095
|
}
|
|
3008
|
-
|
|
3009
|
-
|
|
3096
|
+
const taskIdInfo = result.task_id ? `\n任务ID: ${result.task_id}` : '';
|
|
3097
|
+
return `❌ 上传失败:${result.msg || '未知错误'}${taskIdInfo}`;
|
|
3098
|
+
}
|
|
3099
|
+
scheduleLxB50Notification(session, result.task_id);
|
|
3100
|
+
return `✅ 落雪B50上传任务已提交!\n任务ID: ${result.task_id}\n\n请耐心等待任务完成,预计1-10分钟`;
|
|
3101
|
+
}
|
|
3102
|
+
catch (error) {
|
|
3103
|
+
ctx.logger('maibot').error('上传落雪B50失败:', error);
|
|
3104
|
+
if (maintenanceMode) {
|
|
3105
|
+
return maintenanceMessage;
|
|
3106
|
+
}
|
|
3107
|
+
// 处理请求超时类错误,统一提示
|
|
3108
|
+
if (error?.code === 'ECONNABORTED' || String(error?.message || '').includes('timeout')) {
|
|
3109
|
+
let msg = '落雪B50任务 上传失败,请稍后再试一次。';
|
|
3110
|
+
const maintenanceMsg = getMaintenanceMessage(maintenanceNotice);
|
|
3111
|
+
if (maintenanceMsg) {
|
|
3112
|
+
msg += `\n${maintenanceMsg}`;
|
|
3010
3113
|
}
|
|
3011
|
-
|
|
3114
|
+
msg += `\n\n${maintenanceMessage}`;
|
|
3115
|
+
return msg;
|
|
3012
3116
|
}
|
|
3013
|
-
|
|
3117
|
+
if (error?.response) {
|
|
3118
|
+
return `❌ API请求失败: ${error.response.status} ${error.response.statusText}\n\n${maintenanceMessage}`;
|
|
3119
|
+
}
|
|
3120
|
+
return `❌ 上传失败: ${error?.message || '未知错误'}\n\n${maintenanceMessage}`;
|
|
3121
|
+
}
|
|
3014
3122
|
});
|
|
3015
3123
|
// 查询落雪B50任务状态功能已暂时取消
|
|
3016
3124
|
/**
|