koishi-plugin-maibot 1.7.30 → 1.7.32

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
@@ -674,13 +674,26 @@ async function waitForUserReply(session, ctx, timeout) {
674
674
  }
675
675
  /**
676
676
  * 交互式获取二维码文本(qr_text)
677
- * 废弃旧的uid策略,每次都需要新的二维码
678
- * 不再使用binding.qrCode缓存,每次操作都要求用户提供新二维码
677
+ * 支持10分钟内使用上次输入的SGID缓存
678
+ * 如果缓存存在且有效,直接使用;否则提示用户输入
679
679
  */
680
- async function getQrText(session, ctx, api, binding, config, timeout = 60000, promptMessage) {
680
+ async function getQrText(session, ctx, api, binding, config, timeout = 60000, promptMessage, useCache = true // 是否使用缓存(默认启用)
681
+ ) {
681
682
  const logger = ctx.logger('maibot');
682
- // 废弃旧的uid策略,每次都需要新的二维码
683
- // 不再使用binding.qrCode缓存,直接提示用户输入
683
+ // 如果启用缓存且binding存在,检查是否有10分钟内的SGID缓存
684
+ if (useCache && binding && binding.lastQrCode && binding.lastQrCodeTime) {
685
+ const cacheAge = Date.now() - new Date(binding.lastQrCodeTime).getTime();
686
+ const cacheValidDuration = 10 * 60 * 1000; // 10分钟
687
+ if (cacheAge < cacheValidDuration && binding.lastQrCode.startsWith('SGWCMAID')) {
688
+ logger.info(`使用缓存的SGID(${Math.floor(cacheAge / 1000)}秒前输入)`);
689
+ // 直接返回缓存的SGID,不验证(让调用方验证,如果失败再提示输入)
690
+ return { qrText: binding.lastQrCode, fromCache: true };
691
+ }
692
+ else {
693
+ logger.debug(`缓存已过期(${Math.floor(cacheAge / 1000)}秒前输入,超过10分钟)`);
694
+ }
695
+ }
696
+ // 没有有效缓存,提示用户输入
684
697
  const actualTimeout = timeout;
685
698
  const message = promptMessage || `请在${actualTimeout / 1000}秒内发送SGID(长按玩家二维码识别后发送)或公众号提供的网页地址`;
686
699
  try {
@@ -749,12 +762,14 @@ async function getQrText(session, ctx, api, binding, config, timeout = 60000, pr
749
762
  await session.send('❌ 无效或过期的二维码,请重新发送');
750
763
  return { qrText: '', error: '无效或过期的二维码' };
751
764
  }
752
- // 如果binding存在,更新数据库中的qrCode(仅用于记录,不再用于缓存)
765
+ // 如果binding存在,更新数据库中的qrCode和缓存
753
766
  if (binding) {
754
767
  await ctx.database.set('maibot_bindings', { userId: binding.userId }, {
755
768
  qrCode: qrText,
769
+ lastQrCode: qrText, // 更新缓存
770
+ lastQrCodeTime: new Date(), // 更新时间戳
756
771
  });
757
- logger.info(`已更新用户 ${binding.userId} 的qrCode(仅用于记录)`);
772
+ logger.info(`已更新用户 ${binding.userId} 的qrCode和缓存`);
758
773
  }
759
774
  return { qrText: qrText };
760
775
  }
@@ -907,6 +922,8 @@ async function promptForRebind(session, ctx, api, binding, config, timeout = 600
907
922
  bindTime: new Date(),
908
923
  userName,
909
924
  rating,
925
+ lastQrCode: qrCode, // 保存为缓存
926
+ lastQrCodeTime: new Date(), // 保存时间戳
910
927
  });
911
928
  // 发送成功反馈
912
929
  await session.send(`✅ 重新绑定成功!${userName ? `\n用户名: ${userName}` : ''}${rating ? `\nRating: ${rating}` : ''}\n\n⚠️ 为了确保账户安全,请手动撤回群内包含SGID的消息`);
@@ -1670,6 +1687,8 @@ function apply(ctx, config) {
1670
1687
  bindTime: new Date(),
1671
1688
  userName,
1672
1689
  rating,
1690
+ lastQrCode: qrCode, // 保存为缓存
1691
+ lastQrCodeTime: new Date(), // 保存时间戳
1673
1692
  });
1674
1693
  return `✅ 绑定成功!\n` +
1675
1694
  (userName ? `用户名: ${userName}\n` : '') +
@@ -1748,9 +1767,11 @@ function apply(ctx, config) {
1748
1767
  `绑定时间: ${new Date(binding.bindTime).toLocaleString('zh-CN')}\n` +
1749
1768
  `🚨 /maialert查看账号提醒状态\n`;
1750
1769
  // 尝试获取最新状态并更新数据库(需要新二维码)
1770
+ let qrTextResultForCharge = null;
1751
1771
  try {
1752
1772
  // 废弃旧的uid策略,每次都需要新的二维码
1753
1773
  const qrTextResult = await getQrText(session, ctx, api, binding, config, rebindTimeout, '请在60秒内发送SGID(长按玩家二维码识别后发送)或公众号提供的网页地址以查询账号状态');
1774
+ qrTextResultForCharge = qrTextResult;
1754
1775
  if (qrTextResult.error) {
1755
1776
  statusInfo += `\n⚠️ 无法获取最新状态:${qrTextResult.error}`;
1756
1777
  }
@@ -1852,9 +1873,70 @@ function apply(ctx, config) {
1852
1873
  statusInfo += `\n\n🔒 锁定状态: 未锁定\n使用 /mai锁定 可以锁定账号(防止他人登录)`;
1853
1874
  }
1854
1875
  }
1855
- // 显示票券信息
1856
- // @deprecated getCharge功能已在新API中移除,已注释
1857
- statusInfo += `\n\n🎫 票券情况: 此功能已在新API中移除`;
1876
+ // 显示票券信息(使用新的getCharge API)
1877
+ try {
1878
+ if (qrTextResultForCharge && !qrTextResultForCharge.error) {
1879
+ // 在调用API前加入队列
1880
+ await waitForQueue(session);
1881
+ const chargeResult = await api.getCharge(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, qrTextResultForCharge.qrText);
1882
+ if (chargeResult.ChargeStatus && chargeResult.userChargeList) {
1883
+ const now = new Date();
1884
+ const validTickets = [];
1885
+ const expiredTickets = [];
1886
+ for (const ticket of chargeResult.userChargeList) {
1887
+ const validDate = new Date(ticket.validDate);
1888
+ if (validDate > now) {
1889
+ validTickets.push(ticket);
1890
+ }
1891
+ else {
1892
+ expiredTickets.push(ticket);
1893
+ }
1894
+ }
1895
+ // 显示有效票券
1896
+ if (validTickets.length > 0 || (options?.expired && expiredTickets.length > 0)) {
1897
+ statusInfo += `\n\n🎫 票券情况:`;
1898
+ if (validTickets.length > 0) {
1899
+ statusInfo += `\n有效票券:`;
1900
+ for (const ticket of validTickets) {
1901
+ const ticketName = getTicketName(ticket.chargeId);
1902
+ const validDateStr = new Date(ticket.validDate).toLocaleString('zh-CN');
1903
+ statusInfo += `\n ${ticketName}: ${ticket.stock} 张(有效期至 ${validDateStr})`;
1904
+ }
1905
+ }
1906
+ // 如果使用 --expired 选项,显示过期票券
1907
+ if (options?.expired && expiredTickets.length > 0) {
1908
+ statusInfo += `\n过期票券:`;
1909
+ for (const ticket of expiredTickets) {
1910
+ const ticketName = getTicketName(ticket.chargeId);
1911
+ const validDateStr = new Date(ticket.validDate).toLocaleString('zh-CN');
1912
+ statusInfo += `\n ${ticketName}: ${ticket.stock} 张(已过期,过期时间 ${validDateStr})`;
1913
+ }
1914
+ }
1915
+ else if (expiredTickets.length > 0) {
1916
+ statusInfo += `\n(还有 ${expiredTickets.length} 种过期票券,使用 --expired 查看)`;
1917
+ }
1918
+ // 显示免费票券
1919
+ if (chargeResult.userFreeChargeList && chargeResult.userFreeChargeList.length > 0) {
1920
+ statusInfo += `\n免费票券:`;
1921
+ for (const freeTicket of chargeResult.userFreeChargeList) {
1922
+ const ticketName = getTicketName(freeTicket.chargeId);
1923
+ statusInfo += `\n ${ticketName}: ${freeTicket.stock} 张`;
1924
+ }
1925
+ }
1926
+ }
1927
+ else {
1928
+ statusInfo += `\n\n🎫 票券情况: 暂无有效票券`;
1929
+ }
1930
+ }
1931
+ else {
1932
+ statusInfo += `\n\n🎫 票券情况: 获取失败(${chargeResult.ChargeStatus === false ? 'API返回失败' : '数据格式错误'})`;
1933
+ }
1934
+ }
1935
+ }
1936
+ catch (error) {
1937
+ logger.warn('获取票券信息失败:', error);
1938
+ statusInfo += `\n\n🎫 票券情况: 获取失败(${error?.message || '未知错误'})`;
1939
+ }
1858
1940
  return statusInfo;
1859
1941
  }
1860
1942
  catch (error) {
@@ -2285,19 +2367,24 @@ function apply(ctx, config) {
2285
2367
  const proxyTip = isProxy ? `(代操作用户 ${userId})` : '';
2286
2368
  // 确认操作(如果未使用 -bypass)
2287
2369
  if (!options?.bypass) {
2288
- const baseTip = `⚠️ 即将发放 ${multiple} 倍票${proxyTip}`;
2289
- const confirmFirst = await promptYesLocal(session, `${baseTip}\n操作具有风险,请谨慎`);
2290
- if (!confirmFirst) {
2291
- return '操作已取消(第一次确认未通过)';
2292
- }
2293
- const confirmSecond = await promptYesLocal(session, '二次确认:若理解风险,请再次输入 Y 执行');
2294
- if (!confirmSecond) {
2295
- return '操作已取消(第二次确认未通过)';
2370
+ if (multiple >= 4) {
2371
+ // 4-6倍:提示失败风险并二次确认
2372
+ const baseTip = `⚠️ 即将发放 ${multiple} 倍票${proxyTip}\n\n⚠️ 警告:4倍及以上票券极有可能失败,请谨慎操作!`;
2373
+ const confirmFirst = await promptYesLocal(session, `${baseTip}\n操作具有风险,请谨慎`);
2374
+ if (!confirmFirst) {
2375
+ return '操作已取消(第一次确认未通过)';
2376
+ }
2377
+ const confirmSecond = await promptYesLocal(session, `二次确认:${multiple}倍票券失败风险极高,确定要继续吗?\n若理解风险,请再次输入 Y 执行`);
2378
+ if (!confirmSecond) {
2379
+ return '操作已取消(第二次确认未通过)';
2380
+ }
2296
2381
  }
2297
- if (multiple >= 3) {
2298
- const confirmThird = await promptYesLocal(session, '第三次确认:3倍及以上票券风险更高,确定继续?');
2299
- if (!confirmThird) {
2300
- return '操作已取消(第三次确认未通过)';
2382
+ else {
2383
+ // 2-3倍:一次确认
2384
+ const baseTip = `⚠️ 即将发放 ${multiple} 倍票${proxyTip}`;
2385
+ const confirmFirst = await promptYesLocal(session, `${baseTip}\n操作具有风险,请谨慎\n确认继续?`);
2386
+ if (!confirmFirst) {
2387
+ return '操作已取消(确认未通过)';
2301
2388
  }
2302
2389
  }
2303
2390
  }
@@ -2332,34 +2419,73 @@ function apply(ctx, config) {
2332
2419
  await session.send('请求成功提交,请等待服务器响应。(通常需要2-3分钟)');
2333
2420
  // 使用新API获取功能票(需要qr_text)
2334
2421
  let ticketResult;
2422
+ let usedCache = qrTextResult.fromCache === true;
2335
2423
  try {
2336
2424
  ticketResult = await api.getTicket(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, multiple, qrTextResult.qrText);
2337
2425
  }
2338
2426
  catch (error) {
2339
- // 如果API返回失败,可能需要重新绑定
2340
- const failureResult = await handleApiFailure(session, ctx, api, binding, config, error, rebindTimeout);
2341
- if (failureResult.rebindResult && failureResult.rebindResult.success && failureResult.rebindResult.newBinding) {
2342
- // 重新绑定成功,重试获取功能票
2343
- const retryQrText = await getQrText(session, ctx, api, failureResult.rebindResult.newBinding, config, rebindTimeout);
2427
+ // 如果使用了缓存且失败,尝试重新获取SGID
2428
+ if (usedCache) {
2429
+ logger.info('使用缓存的SGID失败,尝试重新获取SGID');
2430
+ const retryQrText = await getQrText(session, ctx, api, binding, config, rebindTimeout, undefined, false); // 禁用缓存,强制重新输入
2344
2431
  if (retryQrText.error) {
2345
- return `❌ 重新绑定后获取二维码失败:${retryQrText.error}`;
2432
+ return `❌ 获取二维码失败:${retryQrText.error}`;
2346
2433
  }
2434
+ // 在调用API前加入队列
2435
+ await waitForQueue(session);
2347
2436
  ticketResult = await api.getTicket(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, multiple, retryQrText.qrText);
2348
2437
  }
2349
2438
  else {
2350
- throw error;
2439
+ // 如果API返回失败,可能需要重新绑定
2440
+ const failureResult = await handleApiFailure(session, ctx, api, binding, config, error, rebindTimeout);
2441
+ if (failureResult.rebindResult && failureResult.rebindResult.success && failureResult.rebindResult.newBinding) {
2442
+ // 重新绑定成功,重试获取功能票
2443
+ const retryQrText = await getQrText(session, ctx, api, failureResult.rebindResult.newBinding, config, rebindTimeout);
2444
+ if (retryQrText.error) {
2445
+ return `❌ 重新绑定后获取二维码失败:${retryQrText.error}`;
2446
+ }
2447
+ // 在调用API前加入队列
2448
+ await waitForQueue(session);
2449
+ ticketResult = await api.getTicket(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, multiple, retryQrText.qrText);
2450
+ }
2451
+ else {
2452
+ throw error;
2453
+ }
2351
2454
  }
2352
2455
  }
2353
2456
  if (!ticketResult.TicketStatus || !ticketResult.LoginStatus || !ticketResult.LogoutStatus) {
2354
- // 如果返回失败,可能需要重新绑定
2355
- if (!ticketResult.QrStatus || ticketResult.LoginStatus === false) {
2356
- const rebindResult = await promptForRebind(session, ctx, api, binding, config, rebindTimeout);
2357
- if (rebindResult.success && rebindResult.newBinding) {
2358
- return `✅ 重新绑定成功!请重新执行发票操作。`;
2457
+ // 如果使用了缓存且失败,尝试重新获取SGID
2458
+ if (usedCache && (!ticketResult.QrStatus || ticketResult.LoginStatus === false)) {
2459
+ logger.info('使用缓存的SGID失败,尝试重新获取SGID');
2460
+ const retryQrText = await getQrText(session, ctx, api, binding, config, rebindTimeout, undefined, false); // 禁用缓存,强制重新输入
2461
+ if (retryQrText.error) {
2462
+ return `❌ 获取二维码失败:${retryQrText.error}`;
2359
2463
  }
2360
- return `❌ 发放功能票失败:服务器返回未成功\n重新绑定失败:${rebindResult.error || '未知错误'}`;
2464
+ // 在调用API前加入队列
2465
+ await waitForQueue(session);
2466
+ ticketResult = await api.getTicket(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, multiple, retryQrText.qrText);
2467
+ if (!ticketResult.TicketStatus || !ticketResult.LoginStatus || !ticketResult.LogoutStatus) {
2468
+ if (!ticketResult.QrStatus || ticketResult.LoginStatus === false) {
2469
+ const rebindResult = await promptForRebind(session, ctx, api, binding, config, rebindTimeout);
2470
+ if (rebindResult.success && rebindResult.newBinding) {
2471
+ return `✅ 重新绑定成功!请重新执行发票操作。`;
2472
+ }
2473
+ return `❌ 发放功能票失败:服务器返回未成功\n重新绑定失败:${rebindResult.error || '未知错误'}`;
2474
+ }
2475
+ return '❌ 发票失败:服务器返回未成功,请确认是否已在短时间内多次执行发票指令或稍后再试或点击获取二维码刷新账号后再试。';
2476
+ }
2477
+ }
2478
+ else {
2479
+ // 如果返回失败,可能需要重新绑定
2480
+ if (!ticketResult.QrStatus || ticketResult.LoginStatus === false) {
2481
+ const rebindResult = await promptForRebind(session, ctx, api, binding, config, rebindTimeout);
2482
+ if (rebindResult.success && rebindResult.newBinding) {
2483
+ return `✅ 重新绑定成功!请重新执行发票操作。`;
2484
+ }
2485
+ return `❌ 发放功能票失败:服务器返回未成功\n重新绑定失败:${rebindResult.error || '未知错误'}`;
2486
+ }
2487
+ return '❌ 发票失败:服务器返回未成功,请确认是否已在短时间内多次执行发票指令或稍后再试或点击获取二维码刷新账号后再试。';
2361
2488
  }
2362
- return '❌ 发票失败:服务器返回未成功,请确认是否已在短时间内多次执行发票指令或稍后再试或点击获取二维码刷新账号后再试。';
2363
2489
  }
2364
2490
  return `✅ 已发放 ${multiple} 倍票\n请稍等几分钟在游戏内确认`;
2365
2491
  }
@@ -2561,41 +2687,76 @@ function apply(ctx, config) {
2561
2687
  await waitForQueue(session);
2562
2688
  // 上传B50(使用新API,需要qr_text)
2563
2689
  let result;
2690
+ let usedCache = qrTextResult.fromCache === true;
2564
2691
  try {
2565
2692
  result = await api.uploadB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, qrTextResult.qrText, binding.fishToken);
2566
2693
  }
2567
2694
  catch (error) {
2568
- // 如果API返回失败,可能需要重新绑定
2569
- const failureResult = await handleApiFailure(session, ctx, api, binding, config, error, rebindTimeout);
2570
- if (failureResult.rebindResult && failureResult.rebindResult.success && failureResult.rebindResult.newBinding) {
2571
- // 重新绑定成功,重试上传
2572
- const retryQrText = await getQrText(session, ctx, api, failureResult.rebindResult.newBinding, config, rebindTimeout);
2695
+ // 如果使用了缓存且失败,尝试重新获取SGID
2696
+ if (usedCache) {
2697
+ logger.info('使用缓存的SGID失败,尝试重新获取SGID');
2698
+ const retryQrText = await getQrText(session, ctx, api, binding, config, rebindTimeout, undefined, false); // 禁用缓存,强制重新输入
2573
2699
  if (retryQrText.error) {
2574
- return `❌ 重新绑定后获取二维码失败:${retryQrText.error}`;
2700
+ return `❌ 获取二维码失败:${retryQrText.error}`;
2575
2701
  }
2576
2702
  // 在调用API前加入队列
2577
2703
  await waitForQueue(session);
2578
2704
  result = await api.uploadB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, retryQrText.qrText, binding.fishToken);
2579
2705
  }
2580
2706
  else {
2581
- throw error;
2707
+ // 如果API返回失败,可能需要重新绑定
2708
+ const failureResult = await handleApiFailure(session, ctx, api, binding, config, error, rebindTimeout);
2709
+ if (failureResult.rebindResult && failureResult.rebindResult.success && failureResult.rebindResult.newBinding) {
2710
+ // 重新绑定成功,重试上传
2711
+ const retryQrText = await getQrText(session, ctx, api, failureResult.rebindResult.newBinding, config, rebindTimeout);
2712
+ if (retryQrText.error) {
2713
+ return `❌ 重新绑定后获取二维码失败:${retryQrText.error}`;
2714
+ }
2715
+ // 在调用API前加入队列
2716
+ await waitForQueue(session);
2717
+ result = await api.uploadB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, retryQrText.qrText, binding.fishToken);
2718
+ }
2719
+ else {
2720
+ throw error;
2721
+ }
2582
2722
  }
2583
2723
  }
2584
2724
  if (!result.UploadStatus) {
2585
- if (result.msg === '该账号下存在未完成的任务') {
2586
- return '⚠️ 当前账号已有未完成的水鱼B50任务,请耐心等待任务完成,预计1-10分钟,无需重复上传。';
2725
+ // 如果使用了缓存且失败,尝试重新获取SGID
2726
+ if (usedCache && (result.msg?.includes('二维码') || result.msg?.includes('qr_text') || result.msg?.includes('无效'))) {
2727
+ logger.info('使用缓存的SGID失败,尝试重新获取SGID');
2728
+ const retryQrText = await getQrText(session, ctx, api, binding, config, rebindTimeout, undefined, false); // 禁用缓存,强制重新输入
2729
+ if (retryQrText.error) {
2730
+ const taskIdInfo = result.task_id ? `\n任务ID: ${result.task_id}` : '';
2731
+ return `❌ 上传失败:${result.msg || '未知错误'}\n获取新二维码失败:${retryQrText.error}${taskIdInfo}`;
2732
+ }
2733
+ // 在调用API前加入队列
2734
+ await waitForQueue(session);
2735
+ result = await api.uploadB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, retryQrText.qrText, binding.fishToken);
2736
+ if (!result.UploadStatus) {
2737
+ if (result.msg === '该账号下存在未完成的任务') {
2738
+ return '⚠️ 当前账号已有未完成的水鱼B50任务,请耐心等待任务完成,预计1-10分钟,无需重复上传。';
2739
+ }
2740
+ const taskIdInfo = result.task_id ? `\n任务ID: ${result.task_id}` : '';
2741
+ return `❌ 上传失败:${result.msg || '未知错误'}${taskIdInfo}`;
2742
+ }
2587
2743
  }
2588
- // 如果返回失败,可能需要重新绑定
2589
- if (result.msg?.includes('二维码') || result.msg?.includes('qr_text') || result.msg?.includes('无效')) {
2590
- const rebindResult = await promptForRebind(session, ctx, api, binding, config, rebindTimeout);
2591
- if (rebindResult.success && rebindResult.newBinding) {
2592
- return `✅ 重新绑定成功!请重新执行上传操作。`;
2744
+ else {
2745
+ if (result.msg === '该账号下存在未完成的任务') {
2746
+ return '⚠️ 当前账号已有未完成的水鱼B50任务,请耐心等待任务完成,预计1-10分钟,无需重复上传。';
2747
+ }
2748
+ // 如果返回失败,可能需要重新绑定
2749
+ if (result.msg?.includes('二维码') || result.msg?.includes('qr_text') || result.msg?.includes('无效')) {
2750
+ const rebindResult = await promptForRebind(session, ctx, api, binding, config, rebindTimeout);
2751
+ if (rebindResult.success && rebindResult.newBinding) {
2752
+ return `✅ 重新绑定成功!请重新执行上传操作。`;
2753
+ }
2754
+ const taskIdInfo = result.task_id ? `\n任务ID: ${result.task_id}` : '';
2755
+ return `❌ 上传失败:${result.msg || '未知错误'}\n重新绑定失败:${rebindResult.error || '未知错误'}${taskIdInfo}`;
2593
2756
  }
2594
2757
  const taskIdInfo = result.task_id ? `\n任务ID: ${result.task_id}` : '';
2595
- return `❌ 上传失败:${result.msg || '未知错误'}\n重新绑定失败:${rebindResult.error || '未知错误'}${taskIdInfo}`;
2758
+ return `❌ 上传失败:${result.msg || '未知错误'}${taskIdInfo}`;
2596
2759
  }
2597
- const taskIdInfo = result.task_id ? `\n任务ID: ${result.task_id}` : '';
2598
- return `❌ 上传失败:${result.msg || '未知错误'}${taskIdInfo}`;
2599
2760
  }
2600
2761
  scheduleB50Notification(session, result.task_id);
2601
2762
  return `✅ B50上传任务已提交!\n任务ID: ${result.task_id}\n\n请耐心等待任务完成,预计1-10分钟`;
@@ -2707,7 +2868,20 @@ function apply(ctx, config) {
2707
2868
  const fishAbort = await (async () => {
2708
2869
  try {
2709
2870
  await waitForQueue(session);
2710
- const fishResult = await api.uploadB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, qrTextResult.qrText, fishToken);
2871
+ let fishResult = await api.uploadB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, qrTextResult.qrText, fishToken);
2872
+ // 如果使用了缓存且失败,尝试重新获取SGID
2873
+ if (qrTextResult.fromCache && !fishResult.UploadStatus && (fishResult.msg?.includes('二维码') || fishResult.msg?.includes('qr_text') || fishResult.msg?.includes('无效'))) {
2874
+ logger.info('使用缓存的SGID失败,尝试重新获取SGID');
2875
+ const retryQrText = await getQrText(session, ctx, api, binding, config, rebindTimeout, undefined, false); // 禁用缓存,强制重新输入
2876
+ if (retryQrText.error) {
2877
+ const taskIdInfo = fishResult.task_id ? `\n任务ID: ${fishResult.task_id}` : '';
2878
+ results.push(`🐟 水鱼: ❌ 上传失败:${fishResult.msg || '未知错误'}\n获取新二维码失败:${retryQrText.error}${taskIdInfo}`);
2879
+ return null;
2880
+ }
2881
+ // 在调用API前加入队列
2882
+ await waitForQueue(session);
2883
+ fishResult = await api.uploadB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, retryQrText.qrText, fishToken);
2884
+ }
2711
2885
  if (!fishResult.UploadStatus) {
2712
2886
  if (fishResult.msg === '该账号下存在未完成的任务') {
2713
2887
  results.push('🐟 水鱼: ⚠️ 当前账号已有未完成的B50任务,请稍后再试,无需重复上传。');
@@ -2730,20 +2904,64 @@ function apply(ctx, config) {
2730
2904
  return null;
2731
2905
  }
2732
2906
  catch (error) {
2733
- const failureResult = await handleApiFailure(session, ctx, api, binding, config, error, rebindTimeout);
2734
- if (failureResult.rebindResult && failureResult.rebindResult.success && failureResult.rebindResult.newBinding) {
2735
- return '✅ 重新绑定成功!请重新执行 /maiua 上传操作。';
2736
- }
2737
- if (error?.code === 'ECONNABORTED' || String(error?.message || '').includes('timeout')) {
2738
- results.push('🐟 水鱼: ❌ 上传超时,请稍后再试一次。');
2739
- return null;
2907
+ // 如果使用了缓存且失败,尝试重新获取SGID
2908
+ if (qrTextResult.fromCache) {
2909
+ logger.info('使用缓存的SGID失败,尝试重新获取SGID');
2910
+ const retryQrText = await getQrText(session, ctx, api, binding, config, rebindTimeout, undefined, false); // 禁用缓存,强制重新输入
2911
+ if (retryQrText.error) {
2912
+ results.push(`🐟 水鱼: ❌ 获取二维码失败:${retryQrText.error}`);
2913
+ return null;
2914
+ }
2915
+ // 在调用API前加入队列
2916
+ await waitForQueue(session);
2917
+ try {
2918
+ const fishResult = await api.uploadB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, retryQrText.qrText, fishToken);
2919
+ if (!fishResult.UploadStatus) {
2920
+ if (fishResult.msg === '该账号下存在未完成的任务') {
2921
+ results.push('🐟 水鱼: ⚠️ 当前账号已有未完成的B50任务,请稍后再试,无需重复上传。');
2922
+ return null;
2923
+ }
2924
+ const taskIdInfo = fishResult.task_id ? `\n任务ID: ${fishResult.task_id}` : '';
2925
+ results.push(`🐟 水鱼: ❌ 上传失败:${fishResult.msg || '未知错误'}${taskIdInfo}`);
2926
+ return null;
2927
+ }
2928
+ scheduleB50Notification(session, fishResult.task_id);
2929
+ results.push(`🐟 水鱼: ✅ B50任务已提交!\n任务ID: ${fishResult.task_id}\n请耐心等待任务完成,预计1-10分钟`);
2930
+ return null;
2931
+ }
2932
+ catch (retryError) {
2933
+ const failureResult = await handleApiFailure(session, ctx, api, binding, config, retryError, rebindTimeout);
2934
+ if (failureResult.rebindResult && failureResult.rebindResult.success && failureResult.rebindResult.newBinding) {
2935
+ return '✅ 重新绑定成功!请重新执行 /maiua 上传操作。';
2936
+ }
2937
+ if (retryError?.code === 'ECONNABORTED' || String(retryError?.message || '').includes('timeout')) {
2938
+ results.push('🐟 水鱼: ❌ 上传超时,请稍后再试一次。');
2939
+ return null;
2940
+ }
2941
+ if (retryError?.response) {
2942
+ results.push(`🐟 水鱼: ❌ API请求失败: ${retryError.response.status} ${retryError.response.statusText}`);
2943
+ return null;
2944
+ }
2945
+ results.push(`🐟 水鱼: ❌ 上传失败: ${retryError?.message || '未知错误'}`);
2946
+ return null;
2947
+ }
2740
2948
  }
2741
- if (error?.response) {
2742
- results.push(`🐟 水鱼: API请求失败: ${error.response.status} ${error.response.statusText}`);
2949
+ else {
2950
+ const failureResult = await handleApiFailure(session, ctx, api, binding, config, error, rebindTimeout);
2951
+ if (failureResult.rebindResult && failureResult.rebindResult.success && failureResult.rebindResult.newBinding) {
2952
+ return '✅ 重新绑定成功!请重新执行 /maiua 上传操作。';
2953
+ }
2954
+ if (error?.code === 'ECONNABORTED' || String(error?.message || '').includes('timeout')) {
2955
+ results.push('🐟 水鱼: ❌ 上传超时,请稍后再试一次。');
2956
+ return null;
2957
+ }
2958
+ if (error?.response) {
2959
+ results.push(`🐟 水鱼: ❌ API请求失败: ${error.response.status} ${error.response.statusText}`);
2960
+ return null;
2961
+ }
2962
+ results.push(`🐟 水鱼: ❌ 上传失败: ${error?.message || '未知错误'}`);
2743
2963
  return null;
2744
2964
  }
2745
- results.push(`🐟 水鱼: ❌ 上传失败: ${error?.message || '未知错误'}`);
2746
- return null;
2747
2965
  }
2748
2966
  })();
2749
2967
  if (fishAbort) {
@@ -2753,7 +2971,20 @@ function apply(ctx, config) {
2753
2971
  const lxnsAbort = await (async () => {
2754
2972
  try {
2755
2973
  await waitForQueue(session);
2756
- const lxResult = await api.uploadLxB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, qrTextResult.qrText, finalLxnsCode);
2974
+ let lxResult = await api.uploadLxB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, qrTextResult.qrText, finalLxnsCode);
2975
+ // 如果使用了缓存且失败,尝试重新获取SGID
2976
+ if (qrTextResult.fromCache && !lxResult.UploadStatus && (lxResult.msg?.includes('二维码') || lxResult.msg?.includes('qr_text') || lxResult.msg?.includes('无效'))) {
2977
+ logger.info('使用缓存的SGID失败,尝试重新获取SGID');
2978
+ const retryQrText = await getQrText(session, ctx, api, binding, config, rebindTimeout, undefined, false); // 禁用缓存,强制重新输入
2979
+ if (retryQrText.error) {
2980
+ const taskIdInfo = lxResult.task_id ? `\n任务ID: ${lxResult.task_id}` : '';
2981
+ results.push(`❄️ 落雪: ❌ 上传失败:${lxResult.msg || '未知错误'}\n获取新二维码失败:${retryQrText.error}${taskIdInfo}`);
2982
+ return null;
2983
+ }
2984
+ // 在调用API前加入队列
2985
+ await waitForQueue(session);
2986
+ lxResult = await api.uploadLxB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, retryQrText.qrText, finalLxnsCode);
2987
+ }
2757
2988
  if (!lxResult.UploadStatus) {
2758
2989
  if (lxResult.msg === '该账号下存在未完成的任务') {
2759
2990
  results.push('❄️ 落雪: ⚠️ 当前账号已有未完成的B50任务,请稍后再试,无需重复上传。');
@@ -2776,20 +3007,64 @@ function apply(ctx, config) {
2776
3007
  return null;
2777
3008
  }
2778
3009
  catch (error) {
2779
- const failureResult = await handleApiFailure(session, ctx, api, binding, config, error, rebindTimeout);
2780
- if (failureResult.rebindResult && failureResult.rebindResult.success && failureResult.rebindResult.newBinding) {
2781
- return '✅ 重新绑定成功!请重新执行 /maiua 上传操作。';
2782
- }
2783
- if (error?.code === 'ECONNABORTED' || String(error?.message || '').includes('timeout')) {
2784
- results.push('❄️ 落雪: ❌ 上传超时,请稍后再试一次。');
2785
- return null;
3010
+ // 如果使用了缓存且失败,尝试重新获取SGID
3011
+ if (qrTextResult.fromCache) {
3012
+ logger.info('使用缓存的SGID失败,尝试重新获取SGID');
3013
+ const retryQrText = await getQrText(session, ctx, api, binding, config, rebindTimeout, undefined, false); // 禁用缓存,强制重新输入
3014
+ if (retryQrText.error) {
3015
+ results.push(`❄️ 落雪: ❌ 获取二维码失败:${retryQrText.error}`);
3016
+ return null;
3017
+ }
3018
+ // 在调用API前加入队列
3019
+ await waitForQueue(session);
3020
+ try {
3021
+ const lxResult = await api.uploadLxB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, retryQrText.qrText, finalLxnsCode);
3022
+ if (!lxResult.UploadStatus) {
3023
+ if (lxResult.msg === '该账号下存在未完成的任务') {
3024
+ results.push('❄️ 落雪: ⚠️ 当前账号已有未完成的B50任务,请稍后再试,无需重复上传。');
3025
+ return null;
3026
+ }
3027
+ const taskIdInfo = lxResult.task_id ? `\n任务ID: ${lxResult.task_id}` : '';
3028
+ results.push(`❄️ 落雪: ❌ 上传失败:${lxResult.msg || '未知错误'}${taskIdInfo}`);
3029
+ return null;
3030
+ }
3031
+ scheduleLxB50Notification(session, lxResult.task_id);
3032
+ results.push(`❄️ 落雪: ✅ B50任务已提交!\n任务ID: ${lxResult.task_id}\n请耐心等待任务完成,预计1-10分钟`);
3033
+ return null;
3034
+ }
3035
+ catch (retryError) {
3036
+ const failureResult = await handleApiFailure(session, ctx, api, binding, config, retryError, rebindTimeout);
3037
+ if (failureResult.rebindResult && failureResult.rebindResult.success && failureResult.rebindResult.newBinding) {
3038
+ return '✅ 重新绑定成功!请重新执行 /maiua 上传操作。';
3039
+ }
3040
+ if (retryError?.code === 'ECONNABORTED' || String(retryError?.message || '').includes('timeout')) {
3041
+ results.push('❄️ 落雪: ❌ 上传超时,请稍后再试一次。');
3042
+ return null;
3043
+ }
3044
+ if (retryError?.response) {
3045
+ results.push(`❄️ 落雪: ❌ API请求失败: ${retryError.response.status} ${retryError.response.statusText}`);
3046
+ return null;
3047
+ }
3048
+ results.push(`❄️ 落雪: ❌ 上传失败: ${retryError?.message || '未知错误'}`);
3049
+ return null;
3050
+ }
2786
3051
  }
2787
- if (error?.response) {
2788
- results.push(`❄️ 落雪: API请求失败: ${error.response.status} ${error.response.statusText}`);
3052
+ else {
3053
+ const failureResult = await handleApiFailure(session, ctx, api, binding, config, error, rebindTimeout);
3054
+ if (failureResult.rebindResult && failureResult.rebindResult.success && failureResult.rebindResult.newBinding) {
3055
+ return '✅ 重新绑定成功!请重新执行 /maiua 上传操作。';
3056
+ }
3057
+ if (error?.code === 'ECONNABORTED' || String(error?.message || '').includes('timeout')) {
3058
+ results.push('❄️ 落雪: ❌ 上传超时,请稍后再试一次。');
3059
+ return null;
3060
+ }
3061
+ if (error?.response) {
3062
+ results.push(`❄️ 落雪: ❌ API请求失败: ${error.response.status} ${error.response.statusText}`);
3063
+ return null;
3064
+ }
3065
+ results.push(`❄️ 落雪: ❌ 上传失败: ${error?.message || '未知错误'}`);
2789
3066
  return null;
2790
3067
  }
2791
- results.push(`❄️ 落雪: ❌ 上传失败: ${error?.message || '未知错误'}`);
2792
- return null;
2793
3068
  }
2794
3069
  })();
2795
3070
  if (lxnsAbort) {
@@ -2925,90 +3200,158 @@ function apply(ctx, config) {
2925
3200
  /**
2926
3201
  * 发收藏品
2927
3202
  * 用法: /mai发收藏品
2928
- * @deprecated 发收藏品功能已在新API中移除,已注释
2929
3203
  */
2930
- /*
2931
3204
  ctx.command('mai发收藏品 [targetUserId:text]', '发放收藏品')
2932
- .userFields(['authority'])
2933
- .option('bypass', '-bypass 绕过确认')
2934
- .action(async ({ session, options }, targetUserId) => {
3205
+ .userFields(['authority'])
3206
+ .option('bypass', '-bypass 绕过确认')
3207
+ .action(async ({ session, options }, targetUserId) => {
2935
3208
  if (!session) {
2936
- return '❌ 无法获取会话信息'
3209
+ return '❌ 无法获取会话信息';
3210
+ }
3211
+ // 检查白名单
3212
+ const whitelistCheck = checkWhitelist(session, config);
3213
+ if (!whitelistCheck.allowed) {
3214
+ return whitelistCheck.message || '本群暂时没有被授权使用本Bot的功能,请添加官方群聊1072033605。';
2937
3215
  }
2938
-
2939
3216
  try {
2940
- // 获取目标用户绑定
2941
- const { binding, isProxy, error } = await getTargetBinding(session, targetUserId)
2942
- if (error || !binding) {
2943
- return error || '❌ 获取用户绑定失败'
2944
- }
2945
-
2946
- const userId = binding.userId
2947
-
2948
- // 交互式选择收藏品类别
2949
- const itemKind = await promptCollectionType(session)
2950
- if (itemKind === null) {
2951
- return '操作已取消'
2952
- }
2953
-
2954
- const selectedType = COLLECTION_TYPE_OPTIONS.find(opt => opt.value === itemKind)
2955
- await session.send(
2956
- `已选择:${selectedType?.label} (${itemKind})\n\n` +
2957
- `请输入收藏品ID(数字)\n` +
2958
- `如果不知道收藏品ID,请前往 https://sdgb.lemonno.xyz/ 查询\n` +
2959
- `乐曲解禁请输入乐曲ID\n\n` +
2960
- `输入0取消操作`
2961
- )
2962
-
2963
- const itemIdInput = await session.prompt(60000)
2964
- if (!itemIdInput || itemIdInput.trim() === '0') {
2965
- return '操作已取消'
2966
- }
2967
-
2968
- const itemId = itemIdInput.trim()
2969
- // 验证ID是否为数字
2970
- if (!/^\d+$/.test(itemId)) {
2971
- return '❌ ID必须是数字,请重新输入'
2972
- }
2973
-
2974
- const confirm = await promptYesLocal(
2975
- session,
2976
- `⚠️ 即将为 ${maskUserId(binding.maiUid)} 发放收藏品\n类型: ${selectedType?.label} (${itemKind})\nID: ${itemId}\n确认继续?`
2977
- )
2978
- if (!confirm) {
2979
- return '操作已取消'
2980
- }
2981
-
2982
- await session.send('请求成功提交,请等待服务器响应。(通常需要2-3分钟)')
2983
-
2984
- const result = await api.getItem(
2985
- binding.maiUid,
2986
- itemId,
2987
- itemKind.toString(),
2988
- machineInfo.clientId,
2989
- machineInfo.regionId,
2990
- machineInfo.placeId,
2991
- machineInfo.placeName,
2992
- machineInfo.regionName,
2993
- )
2994
-
2995
- if (result.ItemStatus === false || result.LoginStatus === false || result.LogoutStatus === false) {
2996
- return '❌ 发放失败:服务器未返回成功状态,请稍后再试或点击获取二维码刷新账号后再试。'
2997
- }
2998
-
2999
- return `✅ 已为 ${maskUserId(binding.maiUid)} 发放收藏品\n类型: ${selectedType?.label}\nID: ${itemId}`
3000
- } catch (error: any) {
3001
- logger.error('发收藏品失败:', error)
3002
- if (maintenanceMode) {
3003
- return maintenanceMessage
3004
- }
3005
- if (error?.response) {
3006
- return `❌ API请求失败: ${error.response.status} ${error.response.statusText}\n\n${maintenanceMessage}`
3007
- }
3008
- return `❌ 发放失败: ${error?.message || '未知错误'}\n\n${maintenanceMessage}`
3217
+ // 获取目标用户绑定
3218
+ const { binding, isProxy, error } = await getTargetBinding(session, targetUserId);
3219
+ if (error || !binding) {
3220
+ return error || '❌ 获取用户绑定失败';
3221
+ }
3222
+ const userId = binding.userId;
3223
+ const proxyTip = isProxy ? `(代操作用户 ${userId})` : '';
3224
+ // 交互式选择收藏品类别
3225
+ const itemKind = await promptCollectionType(session);
3226
+ if (itemKind === null) {
3227
+ return '操作已取消';
3228
+ }
3229
+ const selectedType = COLLECTION_TYPE_OPTIONS.find(opt => opt.value === itemKind);
3230
+ await session.send(`已选择:${selectedType?.label} (${itemKind})\n\n` +
3231
+ `请输入收藏品ID(数字)\n` +
3232
+ `如果不知道收藏品ID,请前往 https://sdgb.lemonno.xyz/ 查询\n` +
3233
+ `乐曲解禁请输入乐曲ID\n\n` +
3234
+ `输入0取消操作`);
3235
+ const promptSession = await waitForUserReply(session, ctx, 60000);
3236
+ const itemIdInput = promptSession?.content?.trim() || '';
3237
+ if (!itemIdInput || itemIdInput === '0') {
3238
+ return '操作已取消';
3239
+ }
3240
+ const itemId = itemIdInput.trim();
3241
+ // 验证ID是否为数字
3242
+ if (!/^\d+$/.test(itemId)) {
3243
+ return '❌ ID必须是数字,请重新输入';
3244
+ }
3245
+ // 确认操作(如果未使用 -bypass)
3246
+ if (!options?.bypass) {
3247
+ const confirm = await promptYesLocal(session, `⚠️ 即将为 ${maskUserId(binding.maiUid)} 发放收藏品${proxyTip}\n类型: ${selectedType?.label} (${itemKind})\nID: ${itemId}\n确认继续?`);
3248
+ if (!confirm) {
3249
+ return '操作已取消';
3250
+ }
3251
+ }
3252
+ // 获取qr_text(交互式或从绑定中获取)
3253
+ const qrTextResult = await getQrText(session, ctx, api, binding, config, rebindTimeout);
3254
+ if (qrTextResult.error) {
3255
+ if (qrTextResult.needRebind) {
3256
+ const rebindResult = await promptForRebind(session, ctx, api, binding, config, rebindTimeout);
3257
+ if (!rebindResult.success) {
3258
+ return `❌ 重新绑定失败:${rebindResult.error || '未知错误'}\n请使用 /mai绑定 重新绑定二维码`;
3259
+ }
3260
+ return '✅ 重新绑定成功!请重新执行 /mai发收藏品 操作。';
3261
+ }
3262
+ return `❌ 获取二维码失败:${qrTextResult.error}`;
3263
+ }
3264
+ // 在调用API前加入队列
3265
+ await waitForQueue(session);
3266
+ await session.send('请求成功提交,请等待服务器响应。(通常需要2-3分钟)');
3267
+ // 使用新API获取收藏品(需要qr_text)
3268
+ const machineInfo = config.machineInfo;
3269
+ let result;
3270
+ let usedCache = qrTextResult.fromCache === true;
3271
+ try {
3272
+ result = await api.getItem(machineInfo.regionId, machineInfo.regionName, machineInfo.clientId, machineInfo.placeId, machineInfo.placeName, parseInt(itemId, 10), itemKind, 1, // item_stock: 1
3273
+ qrTextResult.qrText);
3274
+ }
3275
+ catch (error) {
3276
+ // 如果使用了缓存且失败,尝试重新获取SGID
3277
+ if (usedCache) {
3278
+ logger.info('使用缓存的SGID失败,尝试重新获取SGID');
3279
+ const retryQrText = await getQrText(session, ctx, api, binding, config, rebindTimeout, undefined, false); // 禁用缓存,强制重新输入
3280
+ if (retryQrText.error) {
3281
+ return `❌ 获取二维码失败:${retryQrText.error}`;
3282
+ }
3283
+ // 在调用API前加入队列
3284
+ await waitForQueue(session);
3285
+ result = await api.getItem(machineInfo.regionId, machineInfo.regionName, machineInfo.clientId, machineInfo.placeId, machineInfo.placeName, parseInt(itemId, 10), itemKind, 1, // item_stock: 1
3286
+ retryQrText.qrText);
3287
+ }
3288
+ else {
3289
+ // 如果API返回失败,可能需要重新绑定
3290
+ const failureResult = await handleApiFailure(session, ctx, api, binding, config, error, rebindTimeout);
3291
+ if (failureResult.rebindResult && failureResult.rebindResult.success && failureResult.rebindResult.newBinding) {
3292
+ // 重新绑定成功,重试获取收藏品
3293
+ const retryQrText = await getQrText(session, ctx, api, failureResult.rebindResult.newBinding, config, rebindTimeout);
3294
+ if (retryQrText.error) {
3295
+ return `❌ 重新绑定后获取二维码失败:${retryQrText.error}`;
3296
+ }
3297
+ // 在调用API前加入队列
3298
+ await waitForQueue(session);
3299
+ result = await api.getItem(machineInfo.regionId, machineInfo.regionName, machineInfo.clientId, machineInfo.placeId, machineInfo.placeName, parseInt(itemId, 10), itemKind, 1, // item_stock: 1
3300
+ retryQrText.qrText);
3301
+ }
3302
+ else {
3303
+ throw error;
3304
+ }
3305
+ }
3306
+ }
3307
+ if (!result.UserAllStatus || !result.LoginStatus || !result.LogoutStatus) {
3308
+ // 如果使用了缓存且失败,尝试重新获取SGID
3309
+ if (usedCache && (!result.QrStatus || result.LoginStatus === false)) {
3310
+ logger.info('使用缓存的SGID失败,尝试重新获取SGID');
3311
+ const retryQrText = await getQrText(session, ctx, api, binding, config, rebindTimeout, undefined, false); // 禁用缓存,强制重新输入
3312
+ if (retryQrText.error) {
3313
+ return `❌ 获取二维码失败:${retryQrText.error}`;
3314
+ }
3315
+ // 在调用API前加入队列
3316
+ await waitForQueue(session);
3317
+ result = await api.getItem(machineInfo.regionId, machineInfo.regionName, machineInfo.clientId, machineInfo.placeId, machineInfo.placeName, parseInt(itemId, 10), itemKind, 1, // item_stock: 1
3318
+ retryQrText.qrText);
3319
+ if (!result.UserAllStatus || !result.LoginStatus || !result.LogoutStatus) {
3320
+ if (!result.QrStatus || result.LoginStatus === false) {
3321
+ const rebindResult = await promptForRebind(session, ctx, api, binding, config, rebindTimeout);
3322
+ if (rebindResult.success && rebindResult.newBinding) {
3323
+ return `✅ 重新绑定成功!请重新执行发收藏品操作。`;
3324
+ }
3325
+ return `❌ 发放收藏品失败:服务器返回未成功\n重新绑定失败:${rebindResult.error || '未知错误'}`;
3326
+ }
3327
+ return '❌ 发放收藏品失败:服务器返回未成功,请确认是否已在短时间内多次执行发收藏品指令或稍后再试或点击获取二维码刷新账号后再试。';
3328
+ }
3329
+ }
3330
+ else {
3331
+ // 如果返回失败,可能需要重新绑定
3332
+ if (!result.QrStatus || result.LoginStatus === false) {
3333
+ const rebindResult = await promptForRebind(session, ctx, api, binding, config, rebindTimeout);
3334
+ if (rebindResult.success && rebindResult.newBinding) {
3335
+ return `✅ 重新绑定成功!请重新执行发收藏品操作。`;
3336
+ }
3337
+ return `❌ 发放收藏品失败:服务器返回未成功\n重新绑定失败:${rebindResult.error || '未知错误'}`;
3338
+ }
3339
+ return '❌ 发放收藏品失败:服务器返回未成功,请确认是否已在短时间内多次执行发收藏品指令或稍后再试或点击获取二维码刷新账号后再试。';
3340
+ }
3341
+ }
3342
+ return `✅ 已为 ${maskUserId(binding.maiUid)} 发放收藏品${proxyTip}\n类型: ${selectedType?.label}\nID: ${itemId}`;
3009
3343
  }
3010
- })
3011
- */
3344
+ catch (error) {
3345
+ logger.error('发收藏品失败:', error);
3346
+ if (maintenanceMode) {
3347
+ return maintenanceMessage;
3348
+ }
3349
+ if (error?.response) {
3350
+ return `❌ API请求失败: ${error.response.status} ${error.response.statusText}\n\n${maintenanceMessage}`;
3351
+ }
3352
+ return `❌ 发放失败: ${error?.message || '未知错误'}\n\n${maintenanceMessage}`;
3353
+ }
3354
+ });
3012
3355
  /**
3013
3356
  * 清收藏品
3014
3357
  * 用法: /mai清收藏品
@@ -3367,41 +3710,76 @@ function apply(ctx, config) {
3367
3710
  await waitForQueue(session);
3368
3711
  // 上传落雪B50(使用新API,需要qr_text)
3369
3712
  let result;
3713
+ let usedCache = qrTextResult.fromCache === true;
3370
3714
  try {
3371
3715
  result = await api.uploadLxB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, qrTextResult.qrText, finalLxnsCode);
3372
3716
  }
3373
3717
  catch (error) {
3374
- // 如果API返回失败,可能需要重新绑定
3375
- const failureResult = await handleApiFailure(session, ctx, api, binding, config, error, rebindTimeout);
3376
- if (failureResult.rebindResult && failureResult.rebindResult.success && failureResult.rebindResult.newBinding) {
3377
- // 重新绑定成功,重试上传
3378
- const retryQrText = await getQrText(session, ctx, api, failureResult.rebindResult.newBinding, config, rebindTimeout);
3718
+ // 如果使用了缓存且失败,尝试重新获取SGID
3719
+ if (usedCache) {
3720
+ logger.info('使用缓存的SGID失败,尝试重新获取SGID');
3721
+ const retryQrText = await getQrText(session, ctx, api, binding, config, rebindTimeout, undefined, false); // 禁用缓存,强制重新输入
3379
3722
  if (retryQrText.error) {
3380
- return `❌ 重新绑定后获取二维码失败:${retryQrText.error}`;
3723
+ return `❌ 获取二维码失败:${retryQrText.error}`;
3381
3724
  }
3382
3725
  // 在调用API前加入队列
3383
3726
  await waitForQueue(session);
3384
3727
  result = await api.uploadLxB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, retryQrText.qrText, finalLxnsCode);
3385
3728
  }
3386
3729
  else {
3387
- throw error;
3730
+ // 如果API返回失败,可能需要重新绑定
3731
+ const failureResult = await handleApiFailure(session, ctx, api, binding, config, error, rebindTimeout);
3732
+ if (failureResult.rebindResult && failureResult.rebindResult.success && failureResult.rebindResult.newBinding) {
3733
+ // 重新绑定成功,重试上传
3734
+ const retryQrText = await getQrText(session, ctx, api, failureResult.rebindResult.newBinding, config, rebindTimeout);
3735
+ if (retryQrText.error) {
3736
+ return `❌ 重新绑定后获取二维码失败:${retryQrText.error}`;
3737
+ }
3738
+ // 在调用API前加入队列
3739
+ await waitForQueue(session);
3740
+ result = await api.uploadLxB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, retryQrText.qrText, finalLxnsCode);
3741
+ }
3742
+ else {
3743
+ throw error;
3744
+ }
3388
3745
  }
3389
3746
  }
3390
3747
  if (!result.UploadStatus) {
3391
- if (result.msg === '该账号下存在未完成的任务') {
3392
- return '⚠️ 当前账号已有未完成的落雪B50任务,请耐心等待任务完成,预计1-10分钟,无需重复上传。';
3748
+ // 如果使用了缓存且失败,尝试重新获取SGID
3749
+ if (usedCache && (result.msg?.includes('二维码') || result.msg?.includes('qr_text') || result.msg?.includes('无效'))) {
3750
+ logger.info('使用缓存的SGID失败,尝试重新获取SGID');
3751
+ const retryQrText = await getQrText(session, ctx, api, binding, config, rebindTimeout, undefined, false); // 禁用缓存,强制重新输入
3752
+ if (retryQrText.error) {
3753
+ const taskIdInfo = result.task_id ? `\n任务ID: ${result.task_id}` : '';
3754
+ return `❌ 上传失败:${result.msg || '未知错误'}\n获取新二维码失败:${retryQrText.error}${taskIdInfo}`;
3755
+ }
3756
+ // 在调用API前加入队列
3757
+ await waitForQueue(session);
3758
+ result = await api.uploadLxB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, retryQrText.qrText, finalLxnsCode);
3759
+ if (!result.UploadStatus) {
3760
+ if (result.msg === '该账号下存在未完成的任务') {
3761
+ return '⚠️ 当前账号已有未完成的落雪B50任务,请耐心等待任务完成,预计1-10分钟,无需重复上传。';
3762
+ }
3763
+ const taskIdInfo = result.task_id ? `\n任务ID: ${result.task_id}` : '';
3764
+ return `❌ 上传失败:${result.msg || '未知错误'}${taskIdInfo}`;
3765
+ }
3393
3766
  }
3394
- // 如果返回失败,可能需要重新绑定
3395
- if (result.msg?.includes('二维码') || result.msg?.includes('qr_text') || result.msg?.includes('无效')) {
3396
- const rebindResult = await promptForRebind(session, ctx, api, binding, config, rebindTimeout);
3397
- if (rebindResult.success && rebindResult.newBinding) {
3398
- return `✅ 重新绑定成功!请重新执行上传操作。`;
3767
+ else {
3768
+ if (result.msg === '该账号下存在未完成的任务') {
3769
+ return '⚠️ 当前账号已有未完成的落雪B50任务,请耐心等待任务完成,预计1-10分钟,无需重复上传。';
3770
+ }
3771
+ // 如果返回失败,可能需要重新绑定
3772
+ if (result.msg?.includes('二维码') || result.msg?.includes('qr_text') || result.msg?.includes('无效')) {
3773
+ const rebindResult = await promptForRebind(session, ctx, api, binding, config, rebindTimeout);
3774
+ if (rebindResult.success && rebindResult.newBinding) {
3775
+ return `✅ 重新绑定成功!请重新执行上传操作。`;
3776
+ }
3777
+ const taskIdInfo = result.task_id ? `\n任务ID: ${result.task_id}` : '';
3778
+ return `❌ 上传失败:${result.msg || '未知错误'}\n重新绑定失败:${rebindResult.error || '未知错误'}${taskIdInfo}`;
3399
3779
  }
3400
3780
  const taskIdInfo = result.task_id ? `\n任务ID: ${result.task_id}` : '';
3401
- return `❌ 上传失败:${result.msg || '未知错误'}\n重新绑定失败:${rebindResult.error || '未知错误'}${taskIdInfo}`;
3781
+ return `❌ 上传失败:${result.msg || '未知错误'}${taskIdInfo}`;
3402
3782
  }
3403
- const taskIdInfo = result.task_id ? `\n任务ID: ${result.task_id}` : '';
3404
- return `❌ 上传失败:${result.msg || '未知错误'}${taskIdInfo}`;
3405
3783
  }
3406
3784
  scheduleLxB50Notification(session, result.task_id);
3407
3785
  return `✅ 落雪B50上传任务已提交!\n任务ID: ${result.task_id}\n\n请耐心等待任务完成,预计1-10分钟`;