koishi-plugin-maibot 1.7.11 → 1.7.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -338,31 +338,13 @@ async function extractQRCodeFromSession(session, ctx) {
338
338
  }
339
339
  /**
340
340
  * 交互式获取二维码文本(qr_text)
341
- * 如果binding存在且包含qrCode,优先使用;否则提示用户输入
342
- * 如果API调用失败,会尝试重新绑定
341
+ * 废弃旧的uid策略,每次都需要新的二维码
342
+ * 不再使用binding.qrCode缓存,每次操作都要求用户提供新二维码
343
343
  */
344
344
  async function getQrText(session, ctx, api, binding, config, timeout = 60000, promptMessage) {
345
345
  const logger = ctx.logger('maibot');
346
- // 如果绑定存在且有qrCode,先验证是否有效
347
- if (binding && binding.qrCode) {
348
- // 验证qrCode是否仍然有效(可选,如果验证失败再提示重新输入)
349
- try {
350
- const preview = await api.getPreview(config.machineInfo.clientId, binding.qrCode);
351
- if (preview.UserID !== -1 && (typeof preview.UserID !== 'string' || preview.UserID !== '-1')) {
352
- // qrCode有效,直接返回
353
- return { qrText: binding.qrCode };
354
- }
355
- // qrCode无效,需要重新绑定
356
- logger.warn(`用户 ${binding.userId} 的qrCode已失效,需要重新绑定`);
357
- return { qrText: '', error: '二维码已失效', needRebind: true };
358
- }
359
- catch (error) {
360
- // 验证失败,可能需要重新绑定,但先尝试使用现有qrCode
361
- logger.warn(`验证qrCode失败,将使用现有qrCode: ${error}`);
362
- return { qrText: binding.qrCode };
363
- }
364
- }
365
- // 否则提示用户输入
346
+ // 废弃旧的uid策略,每次都需要新的二维码
347
+ // 不再使用binding.qrCode缓存,直接提示用户输入
366
348
  const actualTimeout = timeout;
367
349
  const message = promptMessage || `请在${actualTimeout / 1000}秒内直接发送SGID(长按玩家二维码识别后发送)`;
368
350
  try {
@@ -375,33 +357,37 @@ async function getQrText(session, ctx, api, binding, config, timeout = 60000, pr
375
357
  }
376
358
  const trimmed = promptText.trim();
377
359
  logger.debug(`收到用户输入: ${trimmed.substring(0, 50)}`);
378
- // 检查是否为SGID格式
379
- if (!trimmed.startsWith('SGWCMAID')) {
380
- await session.send('⚠️ 未识别到有效的SGID格式,请发送SGID文本(SGWCMAID开头)');
381
- return { qrText: '', error: '无效的二维码格式,必须以 SGWCMAID 开头' };
382
- }
383
- // 验证二维码格式
384
- if (trimmed.length < 48 || trimmed.length > 128) {
360
+ // 链接直接传给API,不提取qrtext参数
361
+ const qrText = trimmed;
362
+ // 检查是否为SGID格式或二维码链接格式
363
+ const isLink = trimmed.includes('https://wq.wahlap.net/qrcode/req/');
364
+ const isSGID = trimmed.startsWith('SGWCMAID');
365
+ if (!isLink && !isSGID) {
366
+ await session.send('⚠️ 未识别到有效的SGID格式或二维码链接,请发送SGID文本(SGWCMAID开头)或二维码链接(https://wq.wahlap.net/qrcode/req/...)');
367
+ return { qrText: '', error: '无效的二维码格式,必须是SGID文本或二维码链接' };
368
+ }
369
+ // 如果是SGID格式,验证长度
370
+ if (isSGID && (qrText.length < 48 || qrText.length > 128)) {
385
371
  await session.send('❌ SGID长度错误,应在48-128字符之间');
386
372
  return { qrText: '', error: '二维码长度错误,应在48-128字符之间' };
387
373
  }
388
- logger.info(`✅ 接收到SGID: ${trimmed.substring(0, 20)}...`);
389
- await session.send('⏳ 正在处理SGID,请稍候...');
390
- // 验证qrCode是否有效
374
+ logger.info(`✅ 接收到${isLink ? '二维码链接' : 'SGID'}: ${qrText.substring(0, 50)}...`);
375
+ await session.send('⏳ 正在处理,请稍候...');
376
+ // 验证qrCode是否有效(如果是SGID格式,需要验证;如果是链接,直接传给API验证)
391
377
  try {
392
- const preview = await api.getPreview(config.machineInfo.clientId, trimmed);
378
+ const preview = await api.getPreview(config.machineInfo.clientId, qrText);
393
379
  if (preview.UserID === -1 || (typeof preview.UserID === 'string' && preview.UserID === '-1')) {
394
380
  await session.send('❌ 无效或过期的二维码,请重新发送');
395
381
  return { qrText: '', error: '无效或过期的二维码' };
396
382
  }
397
- // 如果binding存在,更新数据库中的qrCode
383
+ // 如果binding存在,更新数据库中的qrCode(仅用于记录,不再用于缓存)
398
384
  if (binding) {
399
385
  await ctx.database.set('maibot_bindings', { userId: binding.userId }, {
400
- qrCode: trimmed,
386
+ qrCode: qrText,
401
387
  });
402
- logger.info(`已更新用户 ${binding.userId} 的qrCode`);
388
+ logger.info(`已更新用户 ${binding.userId} 的qrCode(仅用于记录)`);
403
389
  }
404
- return { qrText: trimmed };
390
+ return { qrText: qrText };
405
391
  }
406
392
  catch (error) {
407
393
  logger.error('验证qrCode失败:', error);
@@ -469,23 +455,24 @@ async function promptForRebind(session, ctx, api, binding, config, timeout = 600
469
455
  }
470
456
  const trimmed = promptText.trim();
471
457
  logger.debug(`收到用户输入: ${trimmed.substring(0, 50)}`);
472
- // 检查是否为SGID格式
473
- if (!trimmed.startsWith('SGWCMAID')) {
474
- await session.send('⚠️ 未识别到有效的SGID格式,请发送SGID文本(SGWCMAID开头)');
475
- return { success: false, error: '无效的二维码格式,必须以 SGWCMAID 开头', messageId: promptMessageId };
476
- }
458
+ // 链接直接传给API,不提取qrtext参数
477
459
  const qrCode = trimmed;
478
- logger.info(`✅ 接收到SGID: ${qrCode.substring(0, 20)}...`);
479
- // 发送识别中反馈
480
- await session.send('⏳ 正在处理SGID,请稍候...');
481
- // 验证二维码格式
482
- if (qrCode.length < 48 || qrCode.length > 128) {
460
+ // 检查是否为SGID格式或二维码链接格式
461
+ const isLink = trimmed.includes('https://wq.wahlap.net/qrcode/req/');
462
+ const isSGID = trimmed.startsWith('SGWCMAID');
463
+ if (!isLink && !isSGID) {
464
+ await session.send('⚠️ 未识别到有效的SGID格式或二维码链接,请发送SGID文本(SGWCMAID开头)或二维码链接(https://wq.wahlap.net/qrcode/req/...)');
465
+ return { success: false, error: '无效的二维码格式,必须是SGID文本或二维码链接', messageId: promptMessageId };
466
+ }
467
+ // 如果是SGID格式,验证长度
468
+ if (isSGID && (qrCode.length < 48 || qrCode.length > 128)) {
483
469
  await session.send('❌ 识别失败:SGID长度错误,应在48-128字符之间');
484
470
  return { success: false, error: '二维码长度错误,应在48-128字符之间', messageId: promptMessageId };
485
471
  }
486
- // 使用新API获取用户信息(需要client_id)
487
- // 注意:这里需要从配置中获取client_id,但为了兼容性,我们先尝试使用getPreview
488
- // 如果失败,可能需要提示用户输入client_id或从配置中获取
472
+ logger.info(`✅ 接收到${isLink ? '二维码链接' : 'SGID'}: ${qrCode.substring(0, 50)}...`);
473
+ // 发送识别中反馈
474
+ await session.send('⏳ 正在处理,请稍候...');
475
+ // 使用新API获取用户信息(链接或SGID都直接传给API)
489
476
  const machineInfo = config.machineInfo;
490
477
  let previewResult;
491
478
  try {
@@ -522,7 +509,7 @@ async function promptForRebind(session, ctx, api, binding, config, timeout = 600
522
509
  rating,
523
510
  });
524
511
  // 发送成功反馈
525
- await session.send(`✅ 重新绑定成功!\n用户ID: ${maskUserId(maiUid)}${userName ? `\n用户名: ${userName}` : ''}${rating ? `\nRating: ${rating}` : ''}\n\n⚠️ 为了确保账户安全,请手动撤回群内包含SGID的消息`);
512
+ await session.send(`✅ 重新绑定成功!${userName ? `\n用户名: ${userName}` : ''}${rating ? `\nRating: ${rating}` : ''}\n\n⚠️ 为了确保账户安全,请手动撤回群内包含SGID的消息`);
526
513
  // 获取更新后的绑定
527
514
  const updated = await ctx.database.get('maibot_bindings', { userId: binding.userId });
528
515
  if (updated.length > 0) {
@@ -733,20 +720,20 @@ function apply(ctx, config) {
733
720
  }
734
721
  const mention = buildMention(session);
735
722
  const guildId = session.guildId;
736
- const maxAttempts = 20;
737
- const interval = 1000; // 减少到5秒轮询一次,更快响应
738
- const initialDelay = 2000; // 首次延迟3秒后开始检查
723
+ const maxAttempts = 300; // 10分钟超时:300次 * 2秒 = 600秒 = 10分钟
724
+ const interval = 2000; // 每2秒轮询一次
725
+ const initialDelay = 2000; // 首次延迟2秒后开始检查
739
726
  let attempts = 0;
740
727
  const poll = async () => {
741
728
  attempts += 1;
742
729
  try {
743
730
  const detail = await api.getB50TaskById(taskId);
744
- if (!detail.done && attempts < maxAttempts) {
745
- ctx.setTimeout(poll, interval);
746
- return;
747
- }
748
- if (detail.done) {
749
- const statusText = detail.error
731
+ // 检测 done === true 或者 error is not none 就停止
732
+ const hasError = detail.error !== null && detail.error !== undefined && detail.error !== '';
733
+ const isDone = detail.done === true;
734
+ if (isDone || hasError) {
735
+ // 任务完成或出错,发送通知并停止
736
+ const statusText = hasError
750
737
  ? `❌ 任务失败:${detail.error}`
751
738
  : '✅ 任务已完成';
752
739
  const finishTime = detail.alive_task_end_time
@@ -755,6 +742,11 @@ function apply(ctx, config) {
755
742
  await bot.sendMessage(channelId, `${mention} 水鱼B50任务 ${taskId} 状态更新\n${statusText}${finishTime}`, guildId);
756
743
  return;
757
744
  }
745
+ // 如果还没完成且没出错,继续轮询(在超时范围内)
746
+ if (attempts < maxAttempts) {
747
+ ctx.setTimeout(poll, interval);
748
+ return;
749
+ }
758
750
  let msg = `${mention} 水鱼B50任务 ${taskId} 上传失败,请稍后再试一次。`;
759
751
  const maintenanceMsg = getMaintenanceMessage(maintenanceNotice);
760
752
  if (maintenanceMsg) {
@@ -788,20 +780,20 @@ function apply(ctx, config) {
788
780
  }
789
781
  const mention = buildMention(session);
790
782
  const guildId = session.guildId;
791
- const maxAttempts = 20;
792
- const interval = 1000; // 1秒轮询一次,更快响应
783
+ const maxAttempts = 300; // 10分钟超时:300次 * 2秒 = 600秒 = 10分钟
784
+ const interval = 2000; // 每2秒轮询一次
793
785
  const initialDelay = 2000; // 首次延迟2秒后开始检查
794
786
  let attempts = 0;
795
787
  const poll = async () => {
796
788
  attempts += 1;
797
789
  try {
798
790
  const detail = await api.getLxB50TaskById(taskId);
799
- if (!detail.done && attempts < maxAttempts) {
800
- ctx.setTimeout(poll, interval);
801
- return;
802
- }
803
- if (detail.done) {
804
- const statusText = detail.error
791
+ // 检测 done === true 或者 error is not none 就停止
792
+ const hasError = detail.error !== null && detail.error !== undefined && detail.error !== '';
793
+ const isDone = detail.done === true;
794
+ if (isDone || hasError) {
795
+ // 任务完成或出错,发送通知并停止
796
+ const statusText = hasError
805
797
  ? `❌ 任务失败:${detail.error}`
806
798
  : '✅ 任务已完成';
807
799
  const finishTime = detail.alive_task_end_time
@@ -810,6 +802,11 @@ function apply(ctx, config) {
810
802
  await bot.sendMessage(channelId, `${mention} 落雪B50任务 ${taskId} 状态更新\n${statusText}${finishTime}`, guildId);
811
803
  return;
812
804
  }
805
+ // 如果还没完成且没出错,继续轮询(在超时范围内)
806
+ if (attempts < maxAttempts) {
807
+ ctx.setTimeout(poll, interval);
808
+ return;
809
+ }
813
810
  let msg = `${mention} 落雪B50任务 ${taskId} 上传失败,请稍后再试一次。`;
814
811
  const maintenanceMsg = getMaintenanceMessage(maintenanceNotice);
815
812
  if (maintenanceMsg) {
@@ -840,65 +837,154 @@ function apply(ctx, config) {
840
837
  */
841
838
  ctx.command('mai [help:text]', '查看所有可用指令')
842
839
  .alias('mai帮助')
840
+ .userFields(['authority'])
843
841
  .action(async ({ session }) => {
844
842
  if (!session) {
845
843
  return '❌ 无法获取会话信息';
846
844
  }
847
- const helpText = `📖 舞萌DX机器人指令帮助
845
+ // 获取用户权限
846
+ const userAuthority = session.user?.authority ?? 0;
847
+ const canProxy = userAuthority >= authLevelForProxy;
848
+ let helpText = `📖 舞萌DX机器人指令帮助
848
849
 
849
850
  🔐 账号管理:
850
- /mai绑定 <SGWCMAID...> - 绑定舞萌DX账号
851
+ /mai绑定 - 绑定舞萌DX账号(支持SGID文本或二维码链接)
851
852
  /mai解绑 - 解绑舞萌DX账号
852
- /mai状态 [@用户] - 查询绑定状态(可查看他人,需要权限)
853
-
854
- 🔒 账号锁定:
855
- /mai锁定 [@用户] - 锁定账号,防止他人登录
856
- /mai解锁 [@用户] - 解锁账号(仅限通过mai锁定指令锁定的账号)
857
- /mai逃离 - 解锁账号的别名
853
+ /mai状态 - 查询绑定状态
854
+ /mai ping - 测试机台连接`;
855
+ // 有权限的代操作命令
856
+ if (canProxy) {
857
+ helpText += `
858
+ /mai状态 [@用户] - 查询他人绑定状态(需要auth等级${authLevelForProxy}以上)`;
859
+ }
860
+ helpText += `
858
861
 
859
862
  🐟 水鱼B50:
860
- /mai绑定水鱼 <token> [@用户] - 绑定水鱼Token用于B50上传
861
- /mai解绑水鱼 [@用户] - 解绑水鱼Token
862
- /mai上传B50 [@用户] - 上传B50数据到水鱼
863
- /mai查询B50 [@用户] - 查询B50上传任务状态
863
+ /mai绑定水鱼 <token> - 绑定水鱼Token用于B50上传
864
+ /mai解绑水鱼 - 解绑水鱼Token
865
+ /mai上传B50 - 上传B50数据到水鱼`;
866
+ if (canProxy) {
867
+ helpText += `
868
+ /mai绑定水鱼 <token> [@用户] - 为他人绑定水鱼Token(需要auth等级${authLevelForProxy}以上)
869
+ /mai解绑水鱼 [@用户] - 解绑他人的水鱼Token(需要auth等级${authLevelForProxy}以上)
870
+ /mai上传B50 [@用户] - 为他人上传B50(需要auth等级${authLevelForProxy}以上)`;
871
+ }
872
+ helpText += `
864
873
 
865
874
  ❄️ 落雪B50:
866
- /mai绑定落雪 <lxns_code> [@用户] - 绑定落雪代码用于B50上传
867
- /mai解绑落雪 [@用户] - 解绑落雪代码
868
- /mai上传落雪b50 [lxns_code] [@用户] - 上传B50数据到落雪
869
- /mai查询落雪B50 [@用户] - 查询落雪B50上传任务状态
875
+ /mai绑定落雪 <lxns_code> - 绑定落雪代码用于B50上传
876
+ /mai解绑落雪 - 解绑落雪代码
877
+ /mai上传落雪b50 [lxns_code] - 上传B50数据到落雪`;
878
+ if (canProxy) {
879
+ helpText += `
880
+ /mai绑定落雪 <lxns_code> [@用户] - 为他人绑定落雪代码(需要auth等级${authLevelForProxy}以上)
881
+ /mai解绑落雪 [@用户] - 解绑他人的落雪代码(需要auth等级${authLevelForProxy}以上)
882
+ /mai上传落雪b50 [lxns_code] [@用户] - 为他人上传落雪B50(需要auth等级${authLevelForProxy}以上)`;
883
+ }
884
+ helpText += `
870
885
 
871
886
  🎫 票券管理:
872
- /mai发票 [倍数] [@用户] - 为账号发放功能票(2-6倍,默认2倍)
873
- /mai清票 [@用户] - 清空账号的所有功能票
887
+ /mai发票 [倍数] - 为账号发放功能票(2-6倍,默认2倍)
888
+ /mai清票 - 清空账号的所有功能票`;
889
+ if (canProxy) {
890
+ helpText += `
891
+ /mai发票 [倍数] [@用户] - 为他人发放功能票(需要auth等级${authLevelForProxy}以上)
892
+ /mai清票 [@用户] - 清空他人的功能票(需要auth等级${authLevelForProxy}以上)`;
893
+ }
894
+ helpText += `
874
895
 
875
896
  🎮 游戏功能:
876
- /mai舞里程 <里程数> [@用户] - 为账号发放舞里程(必须是1000的倍数)
877
- /mai发收藏品 [@用户] - 发放收藏品(交互式选择)
878
- /mai清收藏品 [@用户] - 清空收藏品(交互式选择)
879
- /mai上传乐曲成绩 [@用户] - 上传游戏乐曲成绩(交互式输入)
897
+ /mai舞里程 <里程数> - 为账号发放舞里程(必须是1000的倍数)`;
898
+ if (canProxy) {
899
+ helpText += `
900
+ /mai舞里程 <里程数> [@用户] - 为他人发放舞里程(需要auth等级${authLevelForProxy}以上)`;
901
+ }
902
+ helpText += `
880
903
 
881
904
  🔔 提醒功能:
882
- /maialert [on|off] - 开关账号状态播报功能
883
- /maialert set <用户ID> [on|off] - 设置他人的播报状态(需要auth等级3以上)
905
+ /maialert [on|off] - 开关账号状态播报功能`;
906
+ if (canProxy) {
907
+ helpText += `
908
+ /maialert set <用户ID> [on|off] - 设置他人的播报状态(需要auth等级${authLevelForProxy}以上)`;
909
+ }
910
+ // 隐藏锁定和保护模式功能(如果hideLockAndProtection为true)
911
+ if (!hideLockAndProtection) {
912
+ helpText += `
913
+
914
+ 🔒 账号锁定:
915
+ /mai锁定 - 锁定账号,防止他人登录
916
+ /mai解锁 - 解锁账号(仅限通过mai锁定指令锁定的账号)
917
+ /mai逃离 - 解锁账号的别名`;
918
+ if (canProxy) {
919
+ helpText += `
920
+ /mai锁定 [@用户] - 锁定他人账号(需要auth等级${authLevelForProxy}以上)
921
+ /mai解锁 [@用户] - 解锁他人账号(需要auth等级${authLevelForProxy}以上)`;
922
+ }
923
+ helpText += `
884
924
 
885
925
  🛡️ 保护模式:
886
- /mai保护模式 [on|off] [@用户] - 开关账号保护模式(自动锁定已下线的账号)
926
+ /mai保护模式 [on|off] - 开关账号保护模式(自动锁定已下线的账号)`;
927
+ if (canProxy) {
928
+ helpText += `
929
+ /mai保护模式 [on|off] [@用户] - 设置他人的保护模式(需要auth等级${authLevelForProxy}以上)`;
930
+ }
931
+ }
932
+ if (canProxy) {
933
+ helpText += `
887
934
 
888
935
  👑 管理员指令:
889
- /mai管理员关闭所有锁定和保护 - 一键关闭所有人的锁定模式和保护模式(需要auth等级3以上)
890
- /mai管理员关闭登录播报 - 关闭/开启登录播报功能(需要auth等级3以上)
891
- /mai管理员关闭所有播报 - 强制关闭所有人的maialert状态(需要auth等级3以上)
936
+ /mai管理员关闭所有锁定和保护 - 一键关闭所有人的锁定模式和保护模式(需要auth等级${authLevelForProxy}以上)
937
+ /mai管理员关闭登录播报 - 关闭/开启登录播报功能(需要auth等级${authLevelForProxy}以上)
938
+ /mai管理员关闭所有播报 - 强制关闭所有人的maialert状态(需要auth等级${authLevelForProxy}以上)`;
939
+ }
940
+ helpText += `
892
941
 
893
942
  💬 交流与反馈:
894
943
  如有问题或建议,请前往QQ群: 1072033605
895
944
 
896
945
  📝 说明:
897
- - 所有指令支持 [@用户] 参数进行代操作(需要权限)
946
+ - 绑定账号支持SGID文本或二维码链接(https://wq.wahlap.net/qrcode/req/?qrtext=...)`;
947
+ if (canProxy) {
948
+ helpText += `
949
+ - 支持 [@用户] 参数进行代操作(需要auth等级${authLevelForProxy}以上)`;
950
+ }
951
+ helpText += `
898
952
  - 部分指令支持 -bypass 参数绕过确认
899
953
  - 使用 /mai状态 --expired 可查看过期票券`;
900
954
  return helpText;
901
955
  });
956
+ /**
957
+ * Ping功能
958
+ * 用法: /mai ping 或 /mai ping机台
959
+ */
960
+ ctx.command('mai ping [target:text]', '测试机台连接')
961
+ .alias('mai ping机台')
962
+ .action(async ({ session }) => {
963
+ if (!session) {
964
+ return '❌ 无法获取会话信息';
965
+ }
966
+ try {
967
+ await session.send('⏳ 正在测试机台连接...');
968
+ const result = await api.maiPing();
969
+ if (result.returnCode === 1 && result.serverTime) {
970
+ const serverTime = new Date(result.serverTime * 1000).toLocaleString('zh-CN');
971
+ return `✅ 机台连接正常\n服务器时间: ${serverTime}`;
972
+ }
973
+ else if (result.result === 'down') {
974
+ return '❌ 机台连接失败,机台可能已下线';
975
+ }
976
+ else {
977
+ return `⚠️ 机台状态未知\n返回结果: ${JSON.stringify(result)}`;
978
+ }
979
+ }
980
+ catch (error) {
981
+ ctx.logger('maibot').error('Ping机台失败:', error);
982
+ if (maintenanceMode) {
983
+ return maintenanceMessage;
984
+ }
985
+ return `❌ Ping失败: ${error?.message || '未知错误'}\n\n${maintenanceMessage}`;
986
+ }
987
+ });
902
988
  // 这个 Fracture_Hikaritsu 不给我吃KFC,故挂在此处。 我很生气。
903
989
  /**
904
990
  * 绑定用户
@@ -914,7 +1000,7 @@ function apply(ctx, config) {
914
1000
  // 检查是否已绑定
915
1001
  const existing = await ctx.database.get('maibot_bindings', { userId });
916
1002
  if (existing.length > 0) {
917
- return `❌ 您已经绑定了账号\n用户ID: ${maskUserId(existing[0].maiUid)}\n绑定时间: ${new Date(existing[0].bindTime).toLocaleString('zh-CN')}\n\n如需重新绑定,请先使用 /mai解绑`;
1003
+ return `❌ 您已经绑定了账号\n绑定时间: ${new Date(existing[0].bindTime).toLocaleString('zh-CN')}\n\n如需重新绑定,请先使用 /mai解绑`;
918
1004
  }
919
1005
  // 如果没有提供SGID,提示用户输入
920
1006
  if (!qrCode) {
@@ -941,16 +1027,18 @@ function apply(ctx, config) {
941
1027
  }
942
1028
  const trimmed = promptText.trim();
943
1029
  logger.debug(`收到用户输入: ${trimmed.substring(0, 50)}`);
944
- // 检查是否为SGID格式
945
- if (!trimmed.startsWith('SGWCMAID')) {
946
- // 如果用户发送了内容但不是SGID,提示并继续等待(但prompt已经返回了,所以这里提示错误)
947
- await session.send('⚠️ 未识别到有效的SGID格式,请发送SGID文本(SGWCMAID开头)');
948
- throw new Error('无效的二维码格式,必须以 SGWCMAID 开头');
949
- }
1030
+ // 链接直接传给API,不提取qrtext参数
950
1031
  qrCode = trimmed;
951
- logger.info(`✅ 接收到SGID: ${qrCode.substring(0, 20)}...`);
1032
+ // 检查是否为SGID格式或二维码链接格式
1033
+ const isLink = trimmed.includes('https://wq.wahlap.net/qrcode/req/');
1034
+ const isSGID = trimmed.startsWith('SGWCMAID');
1035
+ if (!isLink && !isSGID) {
1036
+ await session.send('⚠️ 未识别到有效的SGID格式或二维码链接,请发送SGID文本(SGWCMAID开头)或二维码链接(https://wq.wahlap.net/qrcode/req/...)');
1037
+ throw new Error('无效的二维码格式,必须是SGID文本或二维码链接');
1038
+ }
1039
+ logger.info(`✅ 接收到${isLink ? '二维码链接' : 'SGID'}: ${qrCode.substring(0, 50)}...`);
952
1040
  // 发送识别中反馈
953
- await session.send('⏳ 正在处理SGID,请稍候...');
1041
+ await session.send('⏳ 正在处理,请稍候...');
954
1042
  }
955
1043
  catch (error) {
956
1044
  logger.error(`等待用户输入二维码失败: ${error?.message}`, error);
@@ -965,11 +1053,15 @@ function apply(ctx, config) {
965
1053
  return `❌ 绑定失败:${error?.message || '未知错误'}`;
966
1054
  }
967
1055
  }
968
- // 验证二维码格式
969
- if (!qrCode.startsWith('SGWCMAID')) {
970
- return '❌ 二维码格式错误,必须以 SGWCMAID 开头';
1056
+ // 链接直接传给API,不提取qrtext参数
1057
+ // 检查是否为SGID格式或二维码链接格式
1058
+ const isLink = qrCode.includes('https://wq.wahlap.net/qrcode/req/');
1059
+ const isSGID = qrCode.startsWith('SGWCMAID');
1060
+ if (!isLink && !isSGID) {
1061
+ return '❌ 二维码格式错误,必须是SGID文本(SGWCMAID开头)或二维码链接(https://wq.wahlap.net/qrcode/req/...)';
971
1062
  }
972
- if (qrCode.length < 48 || qrCode.length > 128) {
1063
+ // 如果是SGID格式,验证长度
1064
+ if (isSGID && (qrCode.length < 48 || qrCode.length > 128)) {
973
1065
  return '❌ 二维码长度错误,应在48-128字符之间';
974
1066
  }
975
1067
  // 使用新API获取用户信息(需要client_id)
@@ -1000,7 +1092,6 @@ function apply(ctx, config) {
1000
1092
  rating,
1001
1093
  });
1002
1094
  return `✅ 绑定成功!\n` +
1003
- `用户ID: ${maskUserId(maiUid)}\n` +
1004
1095
  (userName ? `用户名: ${userName}\n` : '') +
1005
1096
  (rating ? `Rating: ${rating}\n` : '') +
1006
1097
  `绑定时间: ${new Date().toLocaleString('zh-CN')}\n\n` +
@@ -1035,7 +1126,7 @@ function apply(ctx, config) {
1035
1126
  }
1036
1127
  // 删除绑定记录
1037
1128
  await ctx.database.remove('maibot_bindings', { userId });
1038
- return `✅ 解绑成功!\n已解绑的用户ID: ${maskUserId(bindings[0].maiUid)}`;
1129
+ return `✅ 解绑成功!`;
1039
1130
  }
1040
1131
  catch (error) {
1041
1132
  ctx.logger('maibot').error('解绑失败:', error);
@@ -1064,18 +1155,18 @@ function apply(ctx, config) {
1064
1155
  }
1065
1156
  const userId = binding.userId;
1066
1157
  let statusInfo = `✅ 已绑定账号\n\n` +
1067
- `用户ID: ${maskUserId(binding.maiUid)}\n` +
1068
1158
  `绑定时间: ${new Date(binding.bindTime).toLocaleString('zh-CN')}\n` +
1069
1159
  `🚨 /maialert查看账号提醒状态\n`;
1070
- // 尝试获取最新状态并更新数据库
1160
+ // 尝试获取最新状态并更新数据库(需要新二维码)
1071
1161
  try {
1072
- // 使用新API获取用户信息(需要qr_text)
1073
- if (!binding.qrCode) {
1074
- logger.warn(`用户 ${userId} 没有qrCode,无法更新用户信息`);
1162
+ // 废弃旧的uid策略,每次都需要新的二维码
1163
+ const qrTextResult = await getQrText(session, ctx, api, binding, config, rebindTimeout, '请在60秒内发送SGID以查询账号状态(长按玩家二维码识别后发送)');
1164
+ if (qrTextResult.error) {
1165
+ statusInfo += `\n⚠️ 无法获取最新状态:${qrTextResult.error}`;
1075
1166
  }
1076
1167
  else {
1077
1168
  try {
1078
- const preview = await api.getPreview(machineInfo.clientId, binding.qrCode);
1169
+ const preview = await api.getPreview(machineInfo.clientId, qrTextResult.qrText);
1079
1170
  // 更新数据库中的用户名和Rating
1080
1171
  await ctx.database.set('maibot_bindings', { userId }, {
1081
1172
  userName: preview.UserName,
@@ -1121,6 +1212,7 @@ function apply(ctx, config) {
1121
1212
  }
1122
1213
  catch (error) {
1123
1214
  logger.warn('获取用户预览信息失败:', error);
1215
+ statusInfo += `\n⚠️ 无法获取最新状态,请检查API服务`;
1124
1216
  }
1125
1217
  }
1126
1218
  }
@@ -1219,7 +1311,7 @@ function apply(ctx, config) {
1219
1311
 
1220
1312
  // 确认操作
1221
1313
  if (!options?.bypass) {
1222
- const confirm = await promptYesLocal(session, `⚠️ 即将锁定账号 ${maskUserId(binding.maiUid)}\n锁定后账号将保持登录状态,防止他人登录\n确认继续?`)
1314
+ const confirm = await promptYesLocal(session, `⚠️ 即将锁定账号\n锁定后账号将保持登录状态,防止他人登录\n确认继续?`)
1223
1315
  if (!confirm) {
1224
1316
  return '操作已取消'
1225
1317
  }
@@ -1259,7 +1351,6 @@ function apply(ctx, config) {
1259
1351
  await ctx.database.set('maibot_bindings', { userId }, updateData)
1260
1352
 
1261
1353
  let message = `✅ 账号已锁定\n` +
1262
- `用户ID: ${maskUserId(binding.maiUid)}\n` +
1263
1354
  `锁定时间: ${new Date().toLocaleString('zh-CN')}\n\n`
1264
1355
 
1265
1356
  if (binding.alertEnabled === true) {
@@ -1322,7 +1413,7 @@ function apply(ctx, config) {
1322
1413
  // 确认操作
1323
1414
  if (!options?.bypass) {
1324
1415
  const proxyTip = isProxy ? `(代操作用户 ${userId})` : ''
1325
- const confirm = await promptYesLocal(session, `⚠️ 即将解锁账号 ${maskUserId(binding.maiUid)}${proxyTip}\n确认继续?`)
1416
+ const confirm = await promptYesLocal(session, `⚠️ 即将解锁账号${proxyTip}\n确认继续?`)
1326
1417
  if (!confirm) {
1327
1418
  return '操作已取消'
1328
1419
  }
@@ -1350,7 +1441,6 @@ function apply(ctx, config) {
1350
1441
  })
1351
1442
 
1352
1443
  let message = `✅ 账号已解锁\n` +
1353
- `用户ID: ${maskUserId(binding.maiUid)}\n` +
1354
1444
  `建议稍等片刻再登录`
1355
1445
 
1356
1446
  // 如果开启了保护模式,提示用户保护模式会继续监控
@@ -1373,21 +1463,14 @@ function apply(ctx, config) {
1373
1463
  */
1374
1464
  /**
1375
1465
  * 绑定水鱼Token
1376
- * 用法: /mai绑定水鱼 <fishToken>
1466
+ * 用法: /mai绑定水鱼 [fishToken]
1377
1467
  */
1378
- ctx.command('mai绑定水鱼 <fishToken:text> [targetUserId:text]', '绑定水鱼Token用于B50上传')
1468
+ ctx.command('mai绑定水鱼 [fishToken:text] [targetUserId:text]', '绑定水鱼Token用于B50上传')
1379
1469
  .userFields(['authority'])
1380
1470
  .action(async ({ session }, fishToken, targetUserId) => {
1381
1471
  if (!session) {
1382
1472
  return '❌ 无法获取会话信息';
1383
1473
  }
1384
- if (!fishToken) {
1385
- return '请提供水鱼Token\n用法:/mai绑定水鱼 <token>\n\nToken长度应在127-132字符之间';
1386
- }
1387
- // 验证Token长度
1388
- if (fishToken.length < 127 || fishToken.length > 132) {
1389
- return '❌ Token长度错误,应在127-132字符之间';
1390
- }
1391
1474
  try {
1392
1475
  // 获取目标用户绑定
1393
1476
  const { binding, isProxy, error } = await getTargetBinding(session, targetUserId);
@@ -1395,6 +1478,29 @@ function apply(ctx, config) {
1395
1478
  return error || '❌ 获取用户绑定失败';
1396
1479
  }
1397
1480
  const userId = binding.userId;
1481
+ // 如果没有提供Token,提示用户交互式输入
1482
+ if (!fishToken) {
1483
+ const actualTimeout = rebindTimeout;
1484
+ try {
1485
+ await session.send(`请在${actualTimeout / 1000}秒内发送水鱼Token(长度应在127-132字符之间)`);
1486
+ const promptText = await session.prompt(actualTimeout);
1487
+ if (!promptText || !promptText.trim()) {
1488
+ return `❌ 输入超时(${actualTimeout / 1000}秒),绑定已取消`;
1489
+ }
1490
+ fishToken = promptText.trim();
1491
+ }
1492
+ catch (error) {
1493
+ logger.error(`等待用户输入水鱼Token失败: ${error?.message}`, error);
1494
+ if (error.message?.includes('超时') || error.message?.includes('timeout') || error.message?.includes('未收到响应')) {
1495
+ return `❌ 输入超时(${actualTimeout / 1000}秒),绑定已取消`;
1496
+ }
1497
+ return `❌ 绑定失败:${error?.message || '未知错误'}`;
1498
+ }
1499
+ }
1500
+ // 验证Token长度
1501
+ if (fishToken.length < 127 || fishToken.length > 132) {
1502
+ return '❌ Token长度错误,应在127-132字符之间';
1503
+ }
1398
1504
  // 更新水鱼Token
1399
1505
  await ctx.database.set('maibot_bindings', { userId }, {
1400
1506
  fishToken,
@@ -1446,21 +1552,14 @@ function apply(ctx, config) {
1446
1552
  });
1447
1553
  /**
1448
1554
  * 绑定落雪代码
1449
- * 用法: /mai绑定落雪 <lxnsCode>
1555
+ * 用法: /mai绑定落雪 [lxnsCode]
1450
1556
  */
1451
- ctx.command('mai绑定落雪 <lxnsCode:text> [targetUserId:text]', '绑定落雪代码用于B50上传')
1557
+ ctx.command('mai绑定落雪 [lxnsCode:text] [targetUserId:text]', '绑定落雪代码用于B50上传')
1452
1558
  .userFields(['authority'])
1453
1559
  .action(async ({ session }, lxnsCode, targetUserId) => {
1454
1560
  if (!session) {
1455
1561
  return '❌ 无法获取会话信息';
1456
1562
  }
1457
- if (!lxnsCode) {
1458
- return '请提供落雪代码\n用法:/mai绑定落雪 <lxns_code>\n\n落雪代码长度必须为15';
1459
- }
1460
- // 验证代码长度
1461
- if (lxnsCode.length !== 15) {
1462
- return '❌ 落雪代码长度错误,必须为15个字符';
1463
- }
1464
1563
  try {
1465
1564
  // 获取目标用户绑定
1466
1565
  const { binding, isProxy, error } = await getTargetBinding(session, targetUserId);
@@ -1468,6 +1567,29 @@ function apply(ctx, config) {
1468
1567
  return error || '❌ 获取用户绑定失败';
1469
1568
  }
1470
1569
  const userId = binding.userId;
1570
+ // 如果没有提供落雪代码,提示用户交互式输入
1571
+ if (!lxnsCode) {
1572
+ const actualTimeout = rebindTimeout;
1573
+ try {
1574
+ await session.send(`请在${actualTimeout / 1000}秒内发送落雪代码(长度必须为15个字符)`);
1575
+ const promptText = await session.prompt(actualTimeout);
1576
+ if (!promptText || !promptText.trim()) {
1577
+ return `❌ 输入超时(${actualTimeout / 1000}秒),绑定已取消`;
1578
+ }
1579
+ lxnsCode = promptText.trim();
1580
+ }
1581
+ catch (error) {
1582
+ logger.error(`等待用户输入落雪代码失败: ${error?.message}`, error);
1583
+ if (error.message?.includes('超时') || error.message?.includes('timeout') || error.message?.includes('未收到响应')) {
1584
+ return `❌ 输入超时(${actualTimeout / 1000}秒),绑定已取消`;
1585
+ }
1586
+ return `❌ 绑定失败:${error?.message || '未知错误'}`;
1587
+ }
1588
+ }
1589
+ // 验证代码长度
1590
+ if (lxnsCode.length !== 15) {
1591
+ return '❌ 落雪代码长度错误,必须为15个字符';
1592
+ }
1471
1593
  // 更新落雪代码
1472
1594
  await ctx.database.set('maibot_bindings', { userId }, {
1473
1595
  lxnsCode,
@@ -1542,7 +1664,7 @@ function apply(ctx, config) {
1542
1664
  const proxyTip = isProxy ? `(代操作用户 ${userId})` : '';
1543
1665
  // 确认操作(如果未使用 -bypass)
1544
1666
  if (!options?.bypass) {
1545
- const baseTip = `⚠️ 即将为 ${maskUserId(binding.maiUid)} 发放 ${multiple} 倍票${proxyTip}`;
1667
+ const baseTip = `⚠️ 即将发放 ${multiple} 倍票${proxyTip}`;
1546
1668
  const confirmFirst = await promptYesLocal(session, `${baseTip}\n操作具有风险,请谨慎`);
1547
1669
  if (!confirmFirst) {
1548
1670
  return '操作已取消(第一次确认未通过)';
@@ -1578,7 +1700,7 @@ function apply(ctx, config) {
1578
1700
  if (!ticketResult.TicketStatus || !ticketResult.LoginStatus || !ticketResult.LogoutStatus) {
1579
1701
  return '❌ 发放功能票失败:服务器返回未成功,请稍后再试';
1580
1702
  }
1581
- return `✅ 已为 ${maskUserId(updatedBinding.maiUid)} 发放 ${multiple} 倍票\n请稍等几分钟在游戏内确认`;
1703
+ return `✅ 已发放 ${multiple} 倍票\n请稍等几分钟在游戏内确认`;
1582
1704
  }
1583
1705
  return `❌ 获取二维码失败:${qrTextResult.error}`;
1584
1706
  }
@@ -1614,7 +1736,7 @@ function apply(ctx, config) {
1614
1736
  }
1615
1737
  return '❌ 发票失败:服务器返回未成功,请确认是否已在短时间内多次执行发票指令或稍后再试或点击获取二维码刷新账号后再试。';
1616
1738
  }
1617
- return `✅ 已为 ${maskUserId(binding.maiUid)} 发放 ${multiple} 倍票\n请稍等几分钟在游戏内确认`;
1739
+ return `✅ 已发放 ${multiple} 倍票\n请稍等几分钟在游戏内确认`;
1618
1740
  }
1619
1741
  catch (error) {
1620
1742
  logger.error('发票失败:', error);
@@ -1759,12 +1881,12 @@ function apply(ctx, config) {
1759
1881
  const result = await api.uploadB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, retryQrText.qrText, binding.fishToken);
1760
1882
  if (!result.UploadStatus) {
1761
1883
  if (result.msg === '该账号下存在未完成的任务') {
1762
- return '⚠️ 当前账号已有未完成的水鱼B50任务,请稍后使用 /mai查询B50 查看任务状态,无需重复上传。';
1884
+ return '⚠️ 当前账号已有未完成的水鱼B50任务,请稍后再试,无需重复上传。';
1763
1885
  }
1764
1886
  return `❌ 上传失败:${result.msg || '未知错误'}`;
1765
1887
  }
1766
1888
  scheduleB50Notification(session, result.task_id);
1767
- return `✅ B50上传任务已提交!\n任务ID: ${result.task_id}\n\n使用 /mai查询B50 查看任务状态`;
1889
+ return `✅ B50上传任务已提交!\n任务ID: ${result.task_id}`;
1768
1890
  }
1769
1891
  return `❌ 获取二维码失败:${qrTextResult.error}`;
1770
1892
  }
@@ -1942,64 +2064,7 @@ function apply(ctx, config) {
1942
2064
  }
1943
2065
  })
1944
2066
  */
1945
- /**
1946
- * 查询B50任务状态
1947
- * 用法: /mai查询B50
1948
- */
1949
- ctx.command('mai查询B50 [targetUserId:text]', '查询B50上传任务状态')
1950
- .userFields(['authority'])
1951
- .action(async ({ session }, targetUserId) => {
1952
- if (!session) {
1953
- return '❌ 无法获取会话信息';
1954
- }
1955
- try {
1956
- // 获取目标用户绑定
1957
- const { binding, isProxy, error } = await getTargetBinding(session, targetUserId);
1958
- if (error || !binding) {
1959
- return error || '❌ 获取用户绑定失败';
1960
- }
1961
- const userId = binding.userId;
1962
- // 查询任务状态
1963
- const taskStatus = await api.getB50TaskStatus(binding.maiUid);
1964
- if (taskStatus.code !== 0 || !taskStatus.alive_task_id) {
1965
- return 'ℹ️ 当前没有正在进行的B50上传任务';
1966
- }
1967
- // 查询任务详情
1968
- const taskDetail = await api.getB50TaskById(String(taskStatus.alive_task_id));
1969
- const startTime = typeof taskStatus.alive_task_time === 'number'
1970
- ? taskStatus.alive_task_time
1971
- : parseInt(String(taskStatus.alive_task_time));
1972
- let statusInfo = `📊 B50上传任务状态\n\n` +
1973
- `任务ID: ${taskStatus.alive_task_id}\n` +
1974
- `开始时间: ${new Date(startTime * 1000).toLocaleString('zh-CN')}\n`;
1975
- if (taskDetail.done) {
1976
- statusInfo += `状态: ✅ 已完成\n`;
1977
- if (taskDetail.alive_task_end_time) {
1978
- const endTime = typeof taskDetail.alive_task_end_time === 'number'
1979
- ? taskDetail.alive_task_end_time
1980
- : parseInt(String(taskDetail.alive_task_end_time));
1981
- statusInfo += `完成时间: ${new Date(endTime * 1000).toLocaleString('zh-CN')}\n`;
1982
- }
1983
- if (taskDetail.error) {
1984
- statusInfo += `错误信息: ${taskDetail.error}\n`;
1985
- }
1986
- }
1987
- else {
1988
- statusInfo += `状态: ⏳ 进行中\n`;
1989
- if (taskDetail.error) {
1990
- statusInfo += `错误信息: ${taskDetail.error}\n`;
1991
- }
1992
- }
1993
- return statusInfo;
1994
- }
1995
- catch (error) {
1996
- ctx.logger('maibot').error('查询B50任务状态失败:', error);
1997
- if (maintenanceMode) {
1998
- return maintenanceMessage;
1999
- }
2000
- return `❌ 查询失败: ${error?.message || '未知错误'}\n\n${maintenanceMessage}`;
2001
- }
2002
- });
2067
+ // 查询B50任务状态功能已暂时取消
2003
2068
  /**
2004
2069
  * 发收藏品
2005
2070
  * 用法: /mai发收藏品
@@ -2389,12 +2454,12 @@ function apply(ctx, config) {
2389
2454
  const result = await api.uploadLxB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, retryQrText.qrText, finalLxnsCode);
2390
2455
  if (!result.UploadStatus) {
2391
2456
  if (result.msg === '该账号下存在未完成的任务') {
2392
- return '⚠️ 当前账号已有未完成的落雪B50任务,请稍后使用 /mai查询落雪B50 查看任务状态,无需重复上传。';
2457
+ return '⚠️ 当前账号已有未完成的落雪B50任务,请稍后再试,无需重复上传。';
2393
2458
  }
2394
2459
  return `❌ 上传失败:${result.msg || '未知错误'}`;
2395
2460
  }
2396
2461
  scheduleLxB50Notification(session, result.task_id);
2397
- return `✅ 落雪B50上传任务已提交!\n任务ID: ${result.task_id}\n\n使用 /mai查询落雪B50 查看任务状态`;
2462
+ return `✅ 落雪B50上传任务已提交!\n任务ID: ${result.task_id}`;
2398
2463
  }
2399
2464
  return `❌ 获取二维码失败:${qrTextResult.error}`;
2400
2465
  }
@@ -2462,64 +2527,7 @@ function apply(ctx, config) {
2462
2527
  return `❌ 上传失败: ${error?.message || '未知错误'}\n\n${maintenanceMessage}`;
2463
2528
  }
2464
2529
  });
2465
- /**
2466
- * 查询落雪B50任务状态
2467
- * 用法: /mai查询落雪B50
2468
- */
2469
- ctx.command('mai查询落雪B50 [targetUserId:text]', '查询落雪B50上传任务状态')
2470
- .userFields(['authority'])
2471
- .action(async ({ session }, targetUserId) => {
2472
- if (!session) {
2473
- return '❌ 无法获取会话信息';
2474
- }
2475
- try {
2476
- // 获取目标用户绑定
2477
- const { binding, isProxy, error } = await getTargetBinding(session, targetUserId);
2478
- if (error || !binding) {
2479
- return error || '❌ 获取用户绑定失败';
2480
- }
2481
- const userId = binding.userId;
2482
- // 查询任务状态
2483
- const taskStatus = await api.getLxB50TaskStatus(binding.maiUid);
2484
- if (taskStatus.code !== 0 || !taskStatus.alive_task_id) {
2485
- return 'ℹ️ 当前没有正在进行的落雪B50上传任务';
2486
- }
2487
- // 查询任务详情
2488
- const taskDetail = await api.getLxB50TaskById(String(taskStatus.alive_task_id));
2489
- const startTime = typeof taskStatus.alive_task_time === 'number'
2490
- ? taskStatus.alive_task_time
2491
- : parseInt(String(taskStatus.alive_task_time));
2492
- let statusInfo = `📊 落雪B50上传任务状态\n\n` +
2493
- `任务ID: ${taskStatus.alive_task_id}\n` +
2494
- `开始时间: ${new Date(startTime * 1000).toLocaleString('zh-CN')}\n`;
2495
- if (taskDetail.done) {
2496
- statusInfo += `状态: ✅ 已完成\n`;
2497
- if (taskDetail.alive_task_end_time) {
2498
- const endTime = typeof taskDetail.alive_task_end_time === 'number'
2499
- ? taskDetail.alive_task_end_time
2500
- : parseInt(String(taskDetail.alive_task_end_time));
2501
- statusInfo += `完成时间: ${new Date(endTime * 1000).toLocaleString('zh-CN')}\n`;
2502
- }
2503
- if (taskDetail.error) {
2504
- statusInfo += `错误信息: ${taskDetail.error}\n`;
2505
- }
2506
- }
2507
- else {
2508
- statusInfo += `状态: ⏳ 进行中\n`;
2509
- if (taskDetail.error) {
2510
- statusInfo += `错误信息: ${taskDetail.error}\n`;
2511
- }
2512
- }
2513
- return statusInfo;
2514
- }
2515
- catch (error) {
2516
- ctx.logger('maibot').error('查询落雪B50任务状态失败:', error);
2517
- if (maintenanceMode) {
2518
- return maintenanceMessage;
2519
- }
2520
- return `❌ 查询失败: ${error?.message || '未知错误'}\n\n${maintenanceMessage}`;
2521
- }
2522
- });
2530
+ // 查询落雪B50任务状态功能已暂时取消
2523
2531
  /**
2524
2532
  * 查询选项文件(OPT)
2525
2533
  * 用法: /mai查询opt <title_ver>
@@ -2630,87 +2638,10 @@ function apply(ctx, config) {
2630
2638
  const lastSavedStatus = current.lastLoginStatus;
2631
2639
  logger.debug(`用户 ${binding.userId} 数据库中保存的上一次状态: ${lastSavedStatus} (类型: ${typeof lastSavedStatus})`);
2632
2640
  // 获取当前登录状态
2633
- // 使用新API获取用户信息(需要qr_text)
2634
- if (!binding.qrCode) {
2635
- logger.warn(`用户 ${binding.userId} 没有qrCode,跳过状态检查`);
2636
- return;
2637
- }
2638
- const preview = await api.getPreview(machineInfo.clientId, binding.qrCode);
2639
- const currentLoginStatus = preview.IsLogin === true;
2640
- logger.info(`用户 ${binding.userId} 当前API返回的登录状态: ${currentLoginStatus} (IsLogin: ${preview.IsLogin})`);
2641
- // 比较数据库中的上一次状态和当前状态(在更新数据库之前比较)
2642
- // 如果 lastSavedStatus 是 undefined,说明是首次检查,不发送消息
2643
- const statusChanged = lastSavedStatus !== undefined && lastSavedStatus !== currentLoginStatus;
2644
- if (statusChanged) {
2645
- logger.info(`🔔 检测到用户 ${binding.userId} 状态变化: ${lastSavedStatus} -> ${currentLoginStatus}`);
2646
- }
2647
- // 更新数据库中的状态和用户名(每次检查都更新)
2648
- // 再次检查账号状态,确保在更新前账号仍然启用播报且未被锁定
2649
- const verifyBinding = await ctx.database.get('maibot_bindings', { userId: binding.userId });
2650
- if (verifyBinding.length > 0 && verifyBinding[0].alertEnabled && !verifyBinding[0].isLocked) {
2651
- const updateData = {
2652
- lastLoginStatus: currentLoginStatus,
2653
- };
2654
- if (preview.UserName) {
2655
- updateData.userName = preview.UserName;
2656
- }
2657
- await ctx.database.set('maibot_bindings', { userId: binding.userId }, updateData);
2658
- logger.debug(`已更新用户 ${binding.userId} 的状态到数据库: ${currentLoginStatus}`);
2659
- }
2660
- // 如果状态发生变化,发送提醒消息
2661
- // 再次检查账号状态,确保在发送消息前账号仍然启用播报且未被锁定
2662
- if (statusChanged) {
2663
- const finalCheck = await ctx.database.get('maibot_bindings', { userId: binding.userId });
2664
- if (finalCheck.length === 0 || !finalCheck[0].alertEnabled || finalCheck[0].isLocked) {
2665
- logger.debug(`用户 ${binding.userId} 在检查过程中播报已关闭或账号已锁定,取消发送消息`);
2666
- return;
2667
- }
2668
- // 发送提醒消息
2669
- if (finalCheck[0].guildId && finalCheck[0].channelId) {
2670
- logger.debug(`准备发送消息到 guildId: ${binding.guildId}, channelId: ${binding.channelId}`);
2671
- // 尝试使用第一个可用的bot发送消息
2672
- let sent = false;
2673
- for (const bot of ctx.bots) {
2674
- try {
2675
- const mention = `<at id="${binding.userId}"/>`;
2676
- // 获取玩家名(优先使用最新的,否则使用缓存的)
2677
- const playerName = preview.UserName || binding.userName || '玩家';
2678
- // 获取消息模板
2679
- const messageTemplate = currentLoginStatus
2680
- ? alertMessages.loginMessage
2681
- : alertMessages.logoutMessage;
2682
- // 替换占位符
2683
- const message = messageTemplate
2684
- .replace(/{playerid}/g, playerName)
2685
- .replace(/{at}/g, mention);
2686
- logger.debug(`尝试使用 bot ${bot.selfId} 发送消息: ${message}`);
2687
- await bot.sendMessage(finalCheck[0].channelId, message, finalCheck[0].guildId);
2688
- logger.info(`✅ 已发送状态提醒给用户 ${binding.userId} (${playerName}): ${currentLoginStatus ? '上线' : '下线'}`);
2689
- sent = true;
2690
- break; // 成功发送后退出循环
2691
- }
2692
- catch (error) {
2693
- logger.warn(`bot ${bot.selfId} 发送消息失败:`, error);
2694
- // 如果这个bot失败,尝试下一个
2695
- continue;
2696
- }
2697
- }
2698
- if (!sent) {
2699
- logger.error(`❌ 所有bot都无法发送消息给用户 ${binding.userId}`);
2700
- }
2701
- }
2702
- else {
2703
- logger.warn(`用户 ${binding.userId} 缺少群组信息 (guildId: ${finalCheck[0].guildId}, channelId: ${finalCheck[0].channelId}),无法发送提醒`);
2704
- }
2705
- }
2706
- else {
2707
- if (lastSavedStatus === undefined) {
2708
- logger.debug(`用户 ${binding.userId} 首次检查,初始化状态为: ${currentLoginStatus},不发送消息`);
2709
- }
2710
- else {
2711
- logger.debug(`用户 ${binding.userId} 状态未变化 (${lastSavedStatus} == ${currentLoginStatus}),跳过`);
2712
- }
2713
- }
2641
+ // 废弃旧的uid策略,后台任务无法交互式获取二维码,跳过检查
2642
+ // 注意:由于废弃了uid策略,后台状态检查功能已禁用
2643
+ logger.warn(`用户 ${binding.userId} 状态检查:由于废弃uid策略,后台任务无法获取新二维码,跳过检查`);
2644
+ return;
2714
2645
  }
2715
2646
  catch (error) {
2716
2647
  logger.error(`检查用户 ${binding.userId} 状态失败:`, error);
@@ -2936,15 +2867,29 @@ function apply(ctx, config) {
2936
2867
  logger.debug(`保护模式:检查用户 ${binding.userId} (maiUid: ${maskUserId(binding.maiUid)}) 的登录状态`)
2937
2868
 
2938
2869
  // 获取当前登录状态
2939
- // 使用新API获取用户信息(需要qr_text)
2940
- if (!binding.qrCode) {
2941
- logger.warn(`用户 ${binding.userId} 没有qrCode,跳过状态检查`)
2942
- return
2943
- }
2944
- const preview = await api.getPreview(machineInfo.clientId, binding.qrCode)
2945
- const currentLoginStatus = preview.IsLogin === true
2946
- logger.debug(`用户 ${binding.userId} 当前登录状态: ${currentLoginStatus}`)
2870
+ // 废弃旧的uid策略,后台任务无法交互式获取二维码,跳过检查
2871
+ // 注意:由于废弃了uid策略,后台保护模式检查功能已禁用
2872
+ logger.warn(`用户 ${binding.userId} 保护模式检查:由于废弃uid策略,后台任务无法获取新二维码,跳过检查`)
2873
+ return
2874
+ } catch (error) {
2875
+ logger.error(`保护模式检查用户 ${binding.userId} 状态失败:`, error)
2876
+ }
2877
+ }
2947
2878
 
2879
+ /**
2880
+ * 锁定账号刷新功能(后台任务)
2881
+ */
2882
+ const refreshLockedAccounts = async () => {
2883
+ // 查找所有已锁定的账号
2884
+ // ... (删除所有后续代码,因为保护模式功能已禁用)
2885
+ return;
2886
+ };
2887
+ // 启动定时任务(已禁用,因为废弃了uid策略)
2888
+ // ctx.setInterval(refreshLockedAccounts, lockRefreshInterval)
2889
+ // 禁用保护模式定时检查(已禁用,因为废弃了uid策略)
2890
+ // ctx.setInterval(checkProtectionMode, protectionCheckInterval)
2891
+ // 以下代码已删除,因为废弃了uid策略导致后台任务无法获取新二维码
2892
+ /*
2948
2893
  // 如果账号已下线,尝试自动锁定
2949
2894
  if (!currentLoginStatus) {
2950
2895
  logger.info(`保护模式:检测到用户 ${binding.userId} 账号已下线,尝试自动锁定`)
@@ -3124,19 +3069,10 @@ function apply(ctx, config) {
3124
3069
  }
3125
3070
  await ctx.database.set('maibot_bindings', { userId }, updateData);
3126
3071
  // 如果是首次开启,初始化登录状态
3072
+ // 废弃旧的uid策略,无法使用缓存的qrCode或maiUid初始化状态
3127
3073
  if (newState && binding.lastLoginStatus === undefined) {
3128
- try {
3129
- logger.debug(`初始化用户 ${userId} 的登录状态...`);
3130
- const preview = await api.preview(binding.maiUid);
3131
- const loginStatus = parseLoginStatus(preview.IsLogin);
3132
- await ctx.database.set('maibot_bindings', { userId }, {
3133
- lastLoginStatus: loginStatus,
3134
- });
3135
- logger.info(`用户 ${userId} 初始登录状态: ${loginStatus} (IsLogin原始值: "${preview.IsLogin}")`);
3136
- }
3137
- catch (error) {
3138
- logger.warn(`初始化用户 ${userId} 登录状态失败:`, error);
3139
- }
3074
+ logger.warn(`用户 ${userId} 状态初始化:由于废弃uid策略,无法使用缓存的qrCode或maiUid初始化状态,跳过初始化`);
3075
+ // 设置为undefined,等待用户下次使用指令时通过新二维码获取状态
3140
3076
  }
3141
3077
  let resultMessage = `✅ 播报功能已${newState ? '开启' : '关闭'}`;
3142
3078
  if (newState) {
@@ -3203,24 +3139,10 @@ function apply(ctx, config) {
3203
3139
  }
3204
3140
  await ctx.database.set('maibot_bindings', { userId: targetUserId }, updateData);
3205
3141
  // 如果是首次开启,初始化登录状态
3142
+ // 废弃旧的uid策略,无法使用缓存的qrCode初始化状态
3206
3143
  if (newState && binding.lastLoginStatus === undefined) {
3207
- try {
3208
- logger.debug(`初始化用户 ${targetUserId} 的登录状态...`);
3209
- // 使用新API获取用户信息(需要qr_text)
3210
- if (!binding.qrCode) {
3211
- logger.warn(`用户 ${targetUserId} 没有qrCode,跳过状态初始化`);
3212
- return;
3213
- }
3214
- const preview = await api.getPreview(machineInfo.clientId, binding.qrCode);
3215
- const loginStatus = preview.IsLogin === true;
3216
- await ctx.database.set('maibot_bindings', { userId: targetUserId }, {
3217
- lastLoginStatus: loginStatus,
3218
- });
3219
- logger.info(`用户 ${targetUserId} 初始登录状态: ${loginStatus} (IsLogin: ${preview.IsLogin})`);
3220
- }
3221
- catch (error) {
3222
- logger.warn(`初始化用户 ${targetUserId} 登录状态失败:`, error);
3223
- }
3144
+ logger.warn(`用户 ${targetUserId} 状态初始化:由于废弃uid策略,无法使用缓存的qrCode初始化状态,跳过初始化`);
3145
+ // 设置为undefined,等待用户下次使用指令时通过新二维码获取状态
3224
3146
  }
3225
3147
  let resultMessage = `✅ 已${newState ? '开启' : '关闭'}用户 ${targetUserId} 的播报功能`;
3226
3148
  if (newState && (!guildId || !channelId)) {