koishi-plugin-maibot 1.7.32 → 1.7.34

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
@@ -67,6 +67,13 @@ exports.Config = koishi_1.Schema.object({
67
67
  interval: 10000,
68
68
  message: '你正在排队,前面还有 {queuePosition} 人。预计等待 {queueEST} 秒。',
69
69
  }),
70
+ operationLog: koishi_1.Schema.object({
71
+ enabled: koishi_1.Schema.boolean().default(true).description('操作记录开关,开启后记录所有操作'),
72
+ refIdLabel: koishi_1.Schema.string().default('Ref_ID').description('Ref_ID 显示标签(可自定义),默认 "Ref_ID"'),
73
+ }).description('操作记录配置').default({
74
+ enabled: true,
75
+ refIdLabel: 'Ref_ID',
76
+ }),
70
77
  });
71
78
  // 我认识了很多朋友 以下是我认识的好朋友们!
72
79
  // Fracture_Hikaritsu
@@ -959,6 +966,54 @@ function apply(ctx, config) {
959
966
  // 初始化队列系统
960
967
  const queueConfig = config.queue || { enabled: false, interval: 10000, message: '你正在排队,前面还有 {queuePosition} 人。预计等待 {queueEST} 秒。' };
961
968
  const requestQueue = queueConfig.enabled ? new RequestQueue(queueConfig.interval) : null;
969
+ // 操作记录配置
970
+ const operationLogConfig = config.operationLog || { enabled: true, refIdLabel: 'Ref_ID' };
971
+ /**
972
+ * 生成唯一的 ref_id
973
+ */
974
+ function generateRefId() {
975
+ const timestamp = Date.now().toString(36);
976
+ const random = Math.random().toString(36).substring(2, 9);
977
+ return `${timestamp}-${random}`.toUpperCase();
978
+ }
979
+ /**
980
+ * 记录操作日志
981
+ */
982
+ async function logOperation(params) {
983
+ if (!operationLogConfig.enabled) {
984
+ return '';
985
+ }
986
+ const refId = generateRefId();
987
+ try {
988
+ await ctx.database.create('maibot_operation_logs', {
989
+ refId,
990
+ command: params.command,
991
+ userId: params.session.userId || '',
992
+ targetUserId: params.targetUserId,
993
+ guildId: params.session.guildId || undefined,
994
+ channelId: params.session.channelId || undefined,
995
+ status: params.status,
996
+ result: params.result,
997
+ errorMessage: params.errorMessage,
998
+ apiResponse: params.apiResponse ? JSON.stringify(params.apiResponse) : undefined,
999
+ createdAt: new Date(),
1000
+ });
1001
+ }
1002
+ catch (error) {
1003
+ logger.warn(`记录操作日志失败: ${error?.message || '未知错误'}`);
1004
+ }
1005
+ return refId;
1006
+ }
1007
+ /**
1008
+ * 在结果消息中添加 Ref_ID
1009
+ */
1010
+ function appendRefId(message, refId) {
1011
+ if (!refId || !operationLogConfig.enabled) {
1012
+ return message;
1013
+ }
1014
+ const label = operationLogConfig.refIdLabel || 'Ref_ID';
1015
+ return `${message}\n${label}: ${refId}`;
1016
+ }
962
1017
  /**
963
1018
  * 在API调用前加入队列并等待
964
1019
  * 这个函数应该在获取到SGID后、调用API前使用
@@ -1191,7 +1246,7 @@ function apply(ctx, config) {
1191
1246
  logger.debug(`getTargetBinding: 成功获取目标用户 ${targetUserIdRaw} 的绑定`);
1192
1247
  return { binding: bindings[0], isProxy: true, error: null };
1193
1248
  }
1194
- const scheduleB50Notification = (session, taskId) => {
1249
+ const scheduleB50Notification = (session, taskId, initialRefId) => {
1195
1250
  const bot = session.bot;
1196
1251
  const channelId = session.channelId;
1197
1252
  if (!bot || !channelId) {
@@ -1219,7 +1274,17 @@ function apply(ctx, config) {
1219
1274
  const finishTime = detail.alive_task_end_time
1220
1275
  ? `\n完成时间: ${new Date((typeof detail.alive_task_end_time === 'number' ? detail.alive_task_end_time : parseInt(String(detail.alive_task_end_time))) * 1000).toLocaleString('zh-CN')}`
1221
1276
  : '';
1222
- await bot.sendMessage(channelId, `${mention} 水鱼B50任务 ${taskId} 状态更新\n${statusText}${finishTime}`, guildId);
1277
+ // 记录任务完成/失败的操作日志
1278
+ const taskRefId = await logOperation({
1279
+ command: 'mai上传B50-任务完成',
1280
+ session,
1281
+ status: hasError ? 'failure' : 'success',
1282
+ result: `${statusText}${finishTime}`,
1283
+ errorMessage: hasError ? detail.error || '未知错误' : undefined,
1284
+ apiResponse: detail,
1285
+ });
1286
+ const finalMessage = `${mention} 水鱼B50任务 ${taskId} 状态更新\n${statusText}${finishTime}`;
1287
+ await bot.sendMessage(channelId, appendRefId(finalMessage, taskRefId), guildId);
1223
1288
  return;
1224
1289
  }
1225
1290
  // 如果还没完成且没出错,继续轮询(在超时范围内)
@@ -1227,12 +1292,19 @@ function apply(ctx, config) {
1227
1292
  ctx.setTimeout(poll, interval);
1228
1293
  return;
1229
1294
  }
1295
+ // 超时情况
1296
+ const timeoutRefId = await logOperation({
1297
+ command: 'mai上传B50-任务超时',
1298
+ session,
1299
+ status: 'failure',
1300
+ errorMessage: '任务轮询超时(10分钟)',
1301
+ });
1230
1302
  let msg = `${mention} 水鱼B50任务 ${taskId} 上传失败,请稍后再试一次。`;
1231
1303
  const maintenanceMsg = getMaintenanceMessage(maintenanceNotice);
1232
1304
  if (maintenanceMsg) {
1233
1305
  msg += `\n${maintenanceMsg}`;
1234
1306
  }
1235
- await bot.sendMessage(channelId, msg, guildId);
1307
+ await bot.sendMessage(channelId, appendRefId(msg, timeoutRefId), guildId);
1236
1308
  }
1237
1309
  catch (error) {
1238
1310
  logger.warn('轮询B50任务状态失败', error);
@@ -1240,18 +1312,25 @@ function apply(ctx, config) {
1240
1312
  ctx.setTimeout(poll, interval);
1241
1313
  return;
1242
1314
  }
1315
+ // 轮询异常情况
1316
+ const errorRefId = await logOperation({
1317
+ command: 'mai上传B50-轮询异常',
1318
+ session,
1319
+ status: 'error',
1320
+ errorMessage: error instanceof Error ? error.message : '未知错误',
1321
+ });
1243
1322
  let msg = `${mention} 水鱼B50任务 ${taskId} 上传失败,请稍后再试一次。`;
1244
1323
  const maintenanceMsg = getMaintenanceMessage(maintenanceNotice);
1245
1324
  if (maintenanceMsg) {
1246
1325
  msg += `\n${maintenanceMsg}`;
1247
1326
  }
1248
- await bot.sendMessage(channelId, msg, guildId);
1327
+ await bot.sendMessage(channelId, appendRefId(msg, errorRefId), guildId);
1249
1328
  }
1250
1329
  };
1251
1330
  // 首次延迟3秒后开始检查,之后每5秒轮询一次
1252
1331
  ctx.setTimeout(poll, initialDelay);
1253
1332
  };
1254
- const scheduleLxB50Notification = (session, taskId) => {
1333
+ const scheduleLxB50Notification = (session, taskId, initialRefId) => {
1255
1334
  const bot = session.bot;
1256
1335
  const channelId = session.channelId;
1257
1336
  if (!bot || !channelId) {
@@ -1279,7 +1358,17 @@ function apply(ctx, config) {
1279
1358
  const finishTime = detail.alive_task_end_time
1280
1359
  ? `\n完成时间: ${new Date((typeof detail.alive_task_end_time === 'number' ? detail.alive_task_end_time : parseInt(String(detail.alive_task_end_time))) * 1000).toLocaleString('zh-CN')}`
1281
1360
  : '';
1282
- await bot.sendMessage(channelId, `${mention} 落雪B50任务 ${taskId} 状态更新\n${statusText}${finishTime}`, guildId);
1361
+ // 记录任务完成/失败的操作日志
1362
+ const taskRefId = await logOperation({
1363
+ command: 'mai上传落雪b50-任务完成',
1364
+ session,
1365
+ status: hasError ? 'failure' : 'success',
1366
+ result: `${statusText}${finishTime}`,
1367
+ errorMessage: hasError ? detail.error || '未知错误' : undefined,
1368
+ apiResponse: detail,
1369
+ });
1370
+ const finalMessage = `${mention} 落雪B50任务 ${taskId} 状态更新\n${statusText}${finishTime}`;
1371
+ await bot.sendMessage(channelId, appendRefId(finalMessage, taskRefId), guildId);
1283
1372
  return;
1284
1373
  }
1285
1374
  // 如果还没完成且没出错,继续轮询(在超时范围内)
@@ -1287,12 +1376,19 @@ function apply(ctx, config) {
1287
1376
  ctx.setTimeout(poll, interval);
1288
1377
  return;
1289
1378
  }
1379
+ // 超时情况
1380
+ const timeoutRefId = await logOperation({
1381
+ command: 'mai上传落雪b50-任务超时',
1382
+ session,
1383
+ status: 'failure',
1384
+ errorMessage: '任务轮询超时(10分钟)',
1385
+ });
1290
1386
  let msg = `${mention} 落雪B50任务 ${taskId} 上传失败,请稍后再试一次。`;
1291
1387
  const maintenanceMsg = getMaintenanceMessage(maintenanceNotice);
1292
1388
  if (maintenanceMsg) {
1293
1389
  msg += `\n${maintenanceMsg}`;
1294
1390
  }
1295
- await bot.sendMessage(channelId, msg, guildId);
1391
+ await bot.sendMessage(channelId, appendRefId(msg, timeoutRefId), guildId);
1296
1392
  }
1297
1393
  catch (error) {
1298
1394
  logger.warn('轮询落雪B50任务状态失败', error);
@@ -1300,12 +1396,19 @@ function apply(ctx, config) {
1300
1396
  ctx.setTimeout(poll, interval);
1301
1397
  return;
1302
1398
  }
1399
+ // 轮询异常情况
1400
+ const errorRefId = await logOperation({
1401
+ command: 'mai上传落雪b50-轮询异常',
1402
+ session,
1403
+ status: 'error',
1404
+ errorMessage: error instanceof Error ? error.message : '未知错误',
1405
+ });
1303
1406
  let msg = `${mention} 落雪B50任务 ${taskId} 上传失败,请稍后再试一次。`;
1304
1407
  const maintenanceMsg = getMaintenanceMessage(maintenanceNotice);
1305
1408
  if (maintenanceMsg) {
1306
1409
  msg += `\n${maintenanceMsg}`;
1307
1410
  }
1308
- await bot.sendMessage(channelId, msg, guildId);
1411
+ await bot.sendMessage(channelId, appendRefId(msg, errorRefId), guildId);
1309
1412
  }
1310
1413
  };
1311
1414
  // 首次延迟2秒后开始检查,之后每1秒轮询一次
@@ -1669,11 +1772,27 @@ function apply(ctx, config) {
1669
1772
  }
1670
1773
  catch (error) {
1671
1774
  ctx.logger('maibot').error('获取用户预览信息失败:', error);
1672
- return `❌ 绑定失败:无法从二维码获取用户信息\n错误信息: ${error?.message || '未知错误'}`;
1775
+ const errorMessage = `❌ 绑定失败:无法从二维码获取用户信息\n错误信息: ${error?.message || '未知错误'}`;
1776
+ const refId = await logOperation({
1777
+ command: 'mai绑定',
1778
+ session,
1779
+ status: 'error',
1780
+ errorMessage: error?.message || '未知错误',
1781
+ apiResponse: error?.response?.data,
1782
+ });
1783
+ return appendRefId(errorMessage, refId);
1673
1784
  }
1674
1785
  // 检查是否获取成功
1675
1786
  if (previewResult.UserID === -1 || (typeof previewResult.UserID === 'string' && previewResult.UserID === '-1')) {
1676
- return `❌ 绑定失败:无效或过期的二维码`;
1787
+ const errorMessage = `❌ 绑定失败:无效或过期的二维码`;
1788
+ const refId = await logOperation({
1789
+ command: 'mai绑定',
1790
+ session,
1791
+ status: 'failure',
1792
+ errorMessage: '无效或过期的二维码',
1793
+ apiResponse: previewResult,
1794
+ });
1795
+ return appendRefId(errorMessage, refId);
1677
1796
  }
1678
1797
  // UserID在新API中是加密的字符串
1679
1798
  const maiUid = String(previewResult.UserID);
@@ -1690,21 +1809,34 @@ function apply(ctx, config) {
1690
1809
  lastQrCode: qrCode, // 保存为缓存
1691
1810
  lastQrCodeTime: new Date(), // 保存时间戳
1692
1811
  });
1693
- return `✅ 绑定成功!\n` +
1812
+ const successMessage = `✅ 绑定成功!\n` +
1694
1813
  (userName ? `用户名: ${userName}\n` : '') +
1695
1814
  (rating ? `Rating: ${rating}\n` : '') +
1696
1815
  `绑定时间: ${new Date().toLocaleString('zh-CN')}\n\n` +
1697
1816
  `⚠️ 为了确保账户安全,请手动撤回群内包含SGID的消息`;
1817
+ const refId = await logOperation({
1818
+ command: 'mai绑定',
1819
+ session,
1820
+ status: 'success',
1821
+ result: successMessage,
1822
+ });
1823
+ return appendRefId(successMessage, refId);
1698
1824
  }
1699
1825
  catch (error) {
1700
1826
  ctx.logger('maibot').error('绑定失败:', error);
1701
- if (maintenanceMode) {
1702
- return maintenanceMessage;
1703
- }
1704
- if (error?.response) {
1705
- return `❌ API请求失败: ${error.response.status} ${error.response.statusText}\n\n${maintenanceMessage}`;
1706
- }
1707
- return `❌ 绑定失败: ${error?.message || '未知错误'}\n\n${maintenanceMessage}`;
1827
+ const errorMessage = maintenanceMode
1828
+ ? maintenanceMessage
1829
+ : (error?.response
1830
+ ? `❌ API请求失败: ${error.response.status} ${error.response.statusText}\n\n${maintenanceMessage}`
1831
+ : `❌ 绑定失败: ${error?.message || '未知错误'}\n\n${maintenanceMessage}`);
1832
+ const refId = await logOperation({
1833
+ command: 'mai绑定',
1834
+ session,
1835
+ status: 'error',
1836
+ errorMessage: error?.message || '未知错误',
1837
+ apiResponse: error?.response?.data,
1838
+ });
1839
+ return appendRefId(errorMessage, refId);
1708
1840
  }
1709
1841
  });
1710
1842
  /**
@@ -1776,10 +1908,14 @@ function apply(ctx, config) {
1776
1908
  statusInfo += `\n⚠️ 无法获取最新状态:${qrTextResult.error}`;
1777
1909
  }
1778
1910
  else {
1779
- // 在调用API前加入队列
1911
+ // 在调用API前加入队列(只调用一次)
1780
1912
  await waitForQueue(session);
1781
1913
  try {
1782
- const preview = await api.getPreview(machineInfo.clientId, qrTextResult.qrText);
1914
+ // 同时获取 preview getCharge(并行执行,避免重复排队)
1915
+ const [preview, chargeResult] = await Promise.all([
1916
+ api.getPreview(machineInfo.clientId, qrTextResult.qrText),
1917
+ api.getCharge(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, qrTextResult.qrText)
1918
+ ]);
1783
1919
  // 更新数据库中的用户名和Rating
1784
1920
  await ctx.database.set('maibot_bindings', { userId }, {
1785
1921
  userName: preview.UserName,
@@ -1822,6 +1958,9 @@ function apply(ctx, config) {
1822
1958
  (versionInfo ? versionInfo : '') +
1823
1959
  `登录状态: ${preview.IsLogin === true ? '已登录' : '未登录'}\n` +
1824
1960
  `封禁状态: ${preview.BanState === 0 ? '正常' : '已封禁'}\n`;
1961
+ // 保存 chargeResult 供后续使用
1962
+ qrTextResultForCharge = { ...qrTextResult };
1963
+ qrTextResultForCharge.chargeResult = chargeResult;
1825
1964
  }
1826
1965
  catch (error) {
1827
1966
  logger.warn('获取用户预览信息失败:', error);
@@ -1876,9 +2015,17 @@ function apply(ctx, config) {
1876
2015
  // 显示票券信息(使用新的getCharge API)
1877
2016
  try {
1878
2017
  if (qrTextResultForCharge && !qrTextResultForCharge.error) {
1879
- // 在调用API前加入队列
1880
- await waitForQueue(session);
1881
- const chargeResult = await api.getCharge(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, qrTextResultForCharge.qrText);
2018
+ // 如果已经在上面获取了 chargeResult,直接使用;否则重新获取
2019
+ let chargeResult;
2020
+ if (qrTextResultForCharge.chargeResult) {
2021
+ // 已经在上面并行获取了,直接使用
2022
+ chargeResult = qrTextResultForCharge.chargeResult;
2023
+ }
2024
+ else {
2025
+ // 如果上面获取失败,这里重新获取(需要排队)
2026
+ await waitForQueue(session);
2027
+ chargeResult = await api.getCharge(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, qrTextResultForCharge.qrText);
2028
+ }
1882
2029
  if (chargeResult.ChargeStatus && chargeResult.userChargeList) {
1883
2030
  const now = new Date();
1884
2031
  const validTickets = [];
@@ -1937,10 +2084,26 @@ function apply(ctx, config) {
1937
2084
  logger.warn('获取票券信息失败:', error);
1938
2085
  statusInfo += `\n\n🎫 票券情况: 获取失败(${error?.message || '未知错误'})`;
1939
2086
  }
1940
- return statusInfo;
2087
+ const refId = await logOperation({
2088
+ command: 'mai状态',
2089
+ session,
2090
+ targetUserId,
2091
+ status: 'success',
2092
+ result: statusInfo,
2093
+ });
2094
+ return appendRefId(statusInfo, refId);
1941
2095
  }
1942
2096
  catch (error) {
1943
2097
  ctx.logger('maibot').error('查询状态失败:', error);
2098
+ const errorMessage = `❌ 查询状态失败: ${error?.message || '未知错误'}`;
2099
+ const refId = await logOperation({
2100
+ command: 'mai状态',
2101
+ session,
2102
+ targetUserId,
2103
+ status: 'error',
2104
+ errorMessage: error?.message || '未知错误',
2105
+ });
2106
+ return appendRefId(errorMessage, refId);
1944
2107
  if (maintenanceMode) {
1945
2108
  return maintenanceMessage;
1946
2109
  }
@@ -2487,17 +2650,32 @@ function apply(ctx, config) {
2487
2650
  return '❌ 发票失败:服务器返回未成功,请确认是否已在短时间内多次执行发票指令或稍后再试或点击获取二维码刷新账号后再试。';
2488
2651
  }
2489
2652
  }
2490
- return `✅ 已发放 ${multiple} 倍票\n请稍等几分钟在游戏内确认`;
2653
+ const successMessage = `✅ 已发放 ${multiple} 倍票\n请稍等几分钟在游戏内确认`;
2654
+ const refId = await logOperation({
2655
+ command: 'mai发票',
2656
+ session,
2657
+ targetUserId,
2658
+ status: 'success',
2659
+ result: successMessage,
2660
+ });
2661
+ return appendRefId(successMessage, refId);
2491
2662
  }
2492
2663
  catch (error) {
2493
2664
  logger.error('发票失败:', error);
2494
- if (maintenanceMode) {
2495
- return maintenanceMessage;
2496
- }
2497
- if (error?.response) {
2498
- return `❌ API请求失败: ${error.response.status} ${error.response.statusText}\n\n${maintenanceMessage}`;
2499
- }
2500
- return `❌ 发票失败: ${error?.message || '未知错误'}\n\n${maintenanceMessage}`;
2665
+ const errorMessage = maintenanceMode
2666
+ ? maintenanceMessage
2667
+ : (error?.response
2668
+ ? `❌ API请求失败: ${error.response.status} ${error.response.statusText}\n\n${maintenanceMessage}`
2669
+ : `❌ 发票失败: ${error?.message || '未知错误'}\n\n${maintenanceMessage}`);
2670
+ const refId = await logOperation({
2671
+ command: 'mai发票',
2672
+ session,
2673
+ targetUserId,
2674
+ status: 'error',
2675
+ errorMessage: error?.message || '未知错误',
2676
+ apiResponse: error?.response?.data,
2677
+ });
2678
+ return appendRefId(errorMessage, refId);
2501
2679
  }
2502
2680
  });
2503
2681
  /**
@@ -2678,8 +2856,17 @@ function apply(ctx, config) {
2678
2856
  const taskIdInfo = result.task_id ? `\n任务ID: ${result.task_id}` : '';
2679
2857
  return `❌ 上传失败:${result.msg || '未知错误'}${taskIdInfo}`;
2680
2858
  }
2681
- scheduleB50Notification(session, result.task_id);
2682
- return `✅ B50上传任务已提交!\n任务ID: ${result.task_id}\n\n请耐心等待任务完成,预计1-10分钟`;
2859
+ const successMessage = `✅ B50上传任务已提交!\n任务ID: ${result.task_id}\n\n请耐心等待任务完成,预计1-10分钟`;
2860
+ const refId = await logOperation({
2861
+ command: 'mai上传B50',
2862
+ session,
2863
+ targetUserId,
2864
+ status: 'success',
2865
+ result: successMessage,
2866
+ apiResponse: result,
2867
+ });
2868
+ scheduleB50Notification(session, result.task_id, refId);
2869
+ return appendRefId(successMessage, refId);
2683
2870
  }
2684
2871
  return `❌ 获取二维码失败:${qrTextResult.error}`;
2685
2872
  }
@@ -2758,8 +2945,17 @@ function apply(ctx, config) {
2758
2945
  return `❌ 上传失败:${result.msg || '未知错误'}${taskIdInfo}`;
2759
2946
  }
2760
2947
  }
2761
- scheduleB50Notification(session, result.task_id);
2762
- return `✅ B50上传任务已提交!\n任务ID: ${result.task_id}\n\n请耐心等待任务完成,预计1-10分钟`;
2948
+ const successMessage = `✅ B50上传任务已提交!\n任务ID: ${result.task_id}\n\n请耐心等待任务完成,预计1-10分钟`;
2949
+ const refId = await logOperation({
2950
+ command: 'mai上传B50',
2951
+ session,
2952
+ targetUserId,
2953
+ status: 'success',
2954
+ result: successMessage,
2955
+ apiResponse: result,
2956
+ });
2957
+ scheduleB50Notification(session, result.task_id, refId);
2958
+ return appendRefId(successMessage, refId);
2763
2959
  }
2764
2960
  catch (error) {
2765
2961
  ctx.logger('maibot').error('上传B50失败:', error);
@@ -2864,157 +3060,166 @@ function apply(ctx, config) {
2864
3060
  return `❌ 获取二维码失败:${qrTextResult.error}`;
2865
3061
  }
2866
3062
  const results = [];
2867
- // 上传水鱼B50
2868
- const fishAbort = await (async () => {
2869
- try {
3063
+ // 先上传水鱼B50,等待完成后再上传落雪(串行执行,避免同时登录)
3064
+ try {
3065
+ await waitForQueue(session);
3066
+ let fishResult = await api.uploadB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, qrTextResult.qrText, fishToken);
3067
+ // 如果使用了缓存且失败,尝试重新获取SGID
3068
+ if (qrTextResult.fromCache && !fishResult.UploadStatus && (fishResult.msg?.includes('二维码') || fishResult.msg?.includes('qr_text') || fishResult.msg?.includes('无效'))) {
3069
+ logger.info('使用缓存的SGID失败,尝试重新获取SGID');
3070
+ const retryQrText = await getQrText(session, ctx, api, binding, config, rebindTimeout, undefined, false); // 禁用缓存,强制重新输入
3071
+ if (retryQrText.error) {
3072
+ const taskIdInfo = fishResult.task_id ? `\n任务ID: ${fishResult.task_id}` : '';
3073
+ return `🐟 水鱼: ❌ 上传失败:${fishResult.msg || '未知错误'}\n获取新二维码失败:${retryQrText.error}${taskIdInfo}`;
3074
+ }
3075
+ // 在调用API前加入队列
2870
3076
  await waitForQueue(session);
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);
3077
+ fishResult = await api.uploadB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, retryQrText.qrText, fishToken);
3078
+ }
3079
+ if (!fishResult.UploadStatus) {
3080
+ if (fishResult.msg === '该账号下存在未完成的任务') {
3081
+ results.push('🐟 水鱼: ⚠️ 当前账号已有未完成的B50任务,请稍后再试,无需重复上传。');
2884
3082
  }
2885
- if (!fishResult.UploadStatus) {
2886
- if (fishResult.msg === '该账号下存在未完成的任务') {
2887
- results.push('🐟 水鱼: ⚠️ 当前账号已有未完成的B50任务,请稍后再试,无需重复上传。');
2888
- return null;
2889
- }
2890
- if (fishResult.msg?.includes('二维码') || fishResult.msg?.includes('qr_text') || fishResult.msg?.includes('无效')) {
2891
- const rebindResult = await promptForRebind(session, ctx, api, binding, config, rebindTimeout);
2892
- if (rebindResult.success && rebindResult.newBinding) {
2893
- return '✅ 重新绑定成功!请重新执行 /maiua 上传操作。';
2894
- }
2895
- const taskIdInfo = fishResult.task_id ? `\n任务ID: ${fishResult.task_id}` : '';
2896
- return `❌ 水鱼上传失败:${fishResult.msg || '未知错误'}\n重新绑定失败:${rebindResult.error || '未知错误'}${taskIdInfo}`;
3083
+ else if (fishResult.msg?.includes('二维码') || fishResult.msg?.includes('qr_text') || fishResult.msg?.includes('无效')) {
3084
+ const rebindResult = await promptForRebind(session, ctx, api, binding, config, rebindTimeout);
3085
+ if (rebindResult.success && rebindResult.newBinding) {
3086
+ return '✅ 重新绑定成功!请重新执行 /maiua 上传操作。';
2897
3087
  }
3088
+ const taskIdInfo = fishResult.task_id ? `\n任务ID: ${fishResult.task_id}` : '';
3089
+ return `❌ 水鱼上传失败:${fishResult.msg || '未知错误'}\n重新绑定失败:${rebindResult.error || '未知错误'}${taskIdInfo}`;
3090
+ }
3091
+ else {
2898
3092
  const taskIdInfo = fishResult.task_id ? `\n任务ID: ${fishResult.task_id}` : '';
2899
3093
  results.push(`🐟 水鱼: ❌ 上传失败:${fishResult.msg || '未知错误'}${taskIdInfo}`);
2900
- return null;
2901
3094
  }
2902
- scheduleB50Notification(session, fishResult.task_id);
2903
- results.push(`🐟 水鱼: ✅ B50任务已提交!\n任务ID: ${fishResult.task_id}\n请耐心等待任务完成,预计1-10分钟`);
2904
- return null;
2905
3095
  }
2906
- catch (error) {
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
- }
3096
+ else {
3097
+ const successMessage = `🐟 水鱼: ✅ B50任务已提交!\n任务ID: ${fishResult.task_id}\n请耐心等待任务完成,预计1-10分钟`;
3098
+ const refId = await logOperation({
3099
+ command: 'maiua-水鱼B50',
3100
+ session,
3101
+ targetUserId: actualTargetUserId,
3102
+ status: 'success',
3103
+ result: successMessage,
3104
+ apiResponse: fishResult,
3105
+ });
3106
+ scheduleB50Notification(session, fishResult.task_id, refId);
3107
+ results.push(appendRefId(successMessage, refId));
3108
+ }
3109
+ }
3110
+ catch (error) {
3111
+ // 如果使用了缓存且失败,尝试重新获取SGID
3112
+ if (qrTextResult.fromCache) {
3113
+ logger.info('使用缓存的SGID失败,尝试重新获取SGID');
3114
+ const retryQrText = await getQrText(session, ctx, api, binding, config, rebindTimeout, undefined, false); // 禁用缓存,强制重新输入
3115
+ if (retryQrText.error) {
3116
+ return `🐟 水鱼: ❌ 获取二维码失败:${retryQrText.error}`;
3117
+ }
3118
+ // 在调用API前加入队列
3119
+ await waitForQueue(session);
3120
+ try {
3121
+ const fishResult = await api.uploadB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, retryQrText.qrText, fishToken);
3122
+ if (!fishResult.UploadStatus) {
3123
+ if (fishResult.msg === '该账号下存在未完成的任务') {
3124
+ results.push('🐟 水鱼: ⚠️ 当前账号已有未完成的B50任务,请稍后再试,无需重复上传。');
3125
+ }
3126
+ else {
2924
3127
  const taskIdInfo = fishResult.task_id ? `\n任务ID: ${fishResult.task_id}` : '';
2925
- results.push(`🐟 水鱼: ❌ 上传失败:${fishResult.msg || '未知错误'}${taskIdInfo}`);
2926
- return null;
3128
+ return `🐟 水鱼: ❌ 上传失败:${fishResult.msg || '未知错误'}${taskIdInfo}`;
2927
3129
  }
3130
+ }
3131
+ else {
2928
3132
  scheduleB50Notification(session, fishResult.task_id);
2929
3133
  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
3134
  }
2948
3135
  }
2949
- else {
2950
- const failureResult = await handleApiFailure(session, ctx, api, binding, config, error, rebindTimeout);
3136
+ catch (retryError) {
3137
+ const failureResult = await handleApiFailure(session, ctx, api, binding, config, retryError, rebindTimeout);
2951
3138
  if (failureResult.rebindResult && failureResult.rebindResult.success && failureResult.rebindResult.newBinding) {
2952
3139
  return '✅ 重新绑定成功!请重新执行 /maiua 上传操作。';
2953
3140
  }
2954
- if (error?.code === 'ECONNABORTED' || String(error?.message || '').includes('timeout')) {
2955
- results.push('🐟 水鱼: ❌ 上传超时,请稍后再试一次。');
2956
- return null;
3141
+ if (retryError?.code === 'ECONNABORTED' || String(retryError?.message || '').includes('timeout')) {
3142
+ return '🐟 水鱼: ❌ 上传超时,请稍后再试一次。';
2957
3143
  }
2958
- if (error?.response) {
2959
- results.push(`🐟 水鱼: ❌ API请求失败: ${error.response.status} ${error.response.statusText}`);
2960
- return null;
3144
+ if (retryError?.response) {
3145
+ return `🐟 水鱼: ❌ API请求失败: ${retryError.response.status} ${retryError.response.statusText}`;
2961
3146
  }
2962
- results.push(`🐟 水鱼: ❌ 上传失败: ${error?.message || '未知错误'}`);
2963
- return null;
3147
+ return `🐟 水鱼: ❌ 上传失败: ${retryError?.message || '未知错误'}`;
2964
3148
  }
2965
3149
  }
2966
- })();
2967
- if (fishAbort) {
2968
- return fishAbort;
3150
+ else {
3151
+ const failureResult = await handleApiFailure(session, ctx, api, binding, config, error, rebindTimeout);
3152
+ if (failureResult.rebindResult && failureResult.rebindResult.success && failureResult.rebindResult.newBinding) {
3153
+ return '✅ 重新绑定成功!请重新执行 /maiua 上传操作。';
3154
+ }
3155
+ if (error?.code === 'ECONNABORTED' || String(error?.message || '').includes('timeout')) {
3156
+ return '🐟 水鱼: ❌ 上传超时,请稍后再试一次。';
3157
+ }
3158
+ if (error?.response) {
3159
+ return `🐟 水鱼: ❌ API请求失败: ${error.response.status} ${error.response.statusText}`;
3160
+ }
3161
+ return `🐟 水鱼: ❌ 上传失败: ${error?.message || '未知错误'}`;
3162
+ }
2969
3163
  }
3164
+ // 等待水鱼上传完成后再上传落雪(避免同时登录导致失败)
2970
3165
  // 上传落雪B50
2971
- const lxnsAbort = await (async () => {
2972
- try {
2973
- await waitForQueue(session);
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
- }
3166
+ try {
3167
+ await waitForQueue(session);
3168
+ let lxResult = await api.uploadLxB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, qrTextResult.qrText, finalLxnsCode);
3169
+ // 如果使用了缓存且失败,尝试重新获取SGID
3170
+ if (qrTextResult.fromCache && !lxResult.UploadStatus && (lxResult.msg?.includes('二维码') || lxResult.msg?.includes('qr_text') || lxResult.msg?.includes('无效'))) {
3171
+ logger.info('使用缓存的SGID失败,尝试重新获取SGID');
3172
+ const retryQrText = await getQrText(session, ctx, api, binding, config, rebindTimeout, undefined, false); // 禁用缓存,强制重新输入
3173
+ if (retryQrText.error) {
3174
+ const taskIdInfo = lxResult.task_id ? `\n任务ID: ${lxResult.task_id}` : '';
3175
+ results.push(`❄️ 落雪: 上传失败:${lxResult.msg || '未知错误'}\n获取新二维码失败:${retryQrText.error}${taskIdInfo}`);
3176
+ }
3177
+ else {
2984
3178
  // 在调用API前加入队列
2985
3179
  await waitForQueue(session);
2986
3180
  lxResult = await api.uploadLxB50(machineInfo.regionId, machineInfo.clientId, machineInfo.placeId, retryQrText.qrText, finalLxnsCode);
2987
3181
  }
2988
- if (!lxResult.UploadStatus) {
2989
- if (lxResult.msg === '该账号下存在未完成的任务') {
2990
- results.push('❄️ 落雪: ⚠️ 当前账号已有未完成的B50任务,请稍后再试,无需重复上传。');
2991
- return null;
2992
- }
2993
- if (lxResult.msg?.includes('二维码') || lxResult.msg?.includes('qr_text') || lxResult.msg?.includes('无效')) {
2994
- const rebindResult = await promptForRebind(session, ctx, api, binding, config, rebindTimeout);
2995
- if (rebindResult.success && rebindResult.newBinding) {
2996
- return '✅ 重新绑定成功!请重新执行 /maiua 上传操作。';
2997
- }
2998
- const taskIdInfo = lxResult.task_id ? `\n任务ID: ${lxResult.task_id}` : '';
2999
- return `❌ 落雪上传失败:${lxResult.msg || '未知错误'}\n重新绑定失败:${rebindResult.error || '未知错误'}${taskIdInfo}`;
3182
+ }
3183
+ if (!lxResult.UploadStatus) {
3184
+ if (lxResult.msg === '该账号下存在未完成的任务') {
3185
+ results.push('❄️ 落雪: ⚠️ 当前账号已有未完成的B50任务,请稍后再试,无需重复上传。');
3186
+ }
3187
+ else if (lxResult.msg?.includes('二维码') || lxResult.msg?.includes('qr_text') || lxResult.msg?.includes('无效')) {
3188
+ const rebindResult = await promptForRebind(session, ctx, api, binding, config, rebindTimeout);
3189
+ if (rebindResult.success && rebindResult.newBinding) {
3190
+ return '✅ 重新绑定成功!请重新执行 /maiua 上传操作。';
3000
3191
  }
3192
+ const taskIdInfo = lxResult.task_id ? `\n任务ID: ${lxResult.task_id}` : '';
3193
+ return `❌ 落雪上传失败:${lxResult.msg || '未知错误'}\n重新绑定失败:${rebindResult.error || '未知错误'}${taskIdInfo}`;
3194
+ }
3195
+ else {
3001
3196
  const taskIdInfo = lxResult.task_id ? `\n任务ID: ${lxResult.task_id}` : '';
3002
3197
  results.push(`❄️ 落雪: ❌ 上传失败:${lxResult.msg || '未知错误'}${taskIdInfo}`);
3003
- return null;
3004
3198
  }
3005
- scheduleLxB50Notification(session, lxResult.task_id);
3006
- results.push(`❄️ 落雪: ✅ B50任务已提交!\n任务ID: ${lxResult.task_id}\n请耐心等待任务完成,预计1-10分钟`);
3007
- return null;
3008
3199
  }
3009
- catch (error) {
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
- }
3200
+ else {
3201
+ const successMessage = `❄️ 落雪: ✅ B50任务已提交!\n任务ID: ${lxResult.task_id}\n请耐心等待任务完成,预计1-10分钟`;
3202
+ const refId = await logOperation({
3203
+ command: 'maiua-落雪B50',
3204
+ session,
3205
+ targetUserId: actualTargetUserId,
3206
+ status: 'success',
3207
+ result: successMessage,
3208
+ apiResponse: lxResult,
3209
+ });
3210
+ scheduleLxB50Notification(session, lxResult.task_id, refId);
3211
+ results.push(appendRefId(successMessage, refId));
3212
+ }
3213
+ }
3214
+ catch (error) {
3215
+ // 如果使用了缓存且失败,尝试重新获取SGID
3216
+ if (qrTextResult.fromCache) {
3217
+ logger.info('使用缓存的SGID失败,尝试重新获取SGID');
3218
+ const retryQrText = await getQrText(session, ctx, api, binding, config, rebindTimeout, undefined, false); // 禁用缓存,强制重新输入
3219
+ if (retryQrText.error) {
3220
+ results.push(`❄️ 落雪: ❌ 获取二维码失败:${retryQrText.error}`);
3221
+ }
3222
+ else {
3018
3223
  // 在调用API前加入队列
3019
3224
  await waitForQueue(session);
3020
3225
  try {
@@ -3022,15 +3227,25 @@ function apply(ctx, config) {
3022
3227
  if (!lxResult.UploadStatus) {
3023
3228
  if (lxResult.msg === '该账号下存在未完成的任务') {
3024
3229
  results.push('❄️ 落雪: ⚠️ 当前账号已有未完成的B50任务,请稍后再试,无需重复上传。');
3025
- return null;
3026
3230
  }
3027
- const taskIdInfo = lxResult.task_id ? `\n任务ID: ${lxResult.task_id}` : '';
3028
- results.push(`❄️ 落雪: 上传失败:${lxResult.msg || '未知错误'}${taskIdInfo}`);
3029
- return null;
3231
+ else {
3232
+ const taskIdInfo = lxResult.task_id ? `\n任务ID: ${lxResult.task_id}` : '';
3233
+ results.push(`❄️ 落雪: ❌ 上传失败:${lxResult.msg || '未知错误'}${taskIdInfo}`);
3234
+ }
3235
+ }
3236
+ else {
3237
+ const successMessage = `❄️ 落雪: ✅ B50任务已提交!\n任务ID: ${lxResult.task_id}\n请耐心等待任务完成,预计1-10分钟`;
3238
+ const refId = await logOperation({
3239
+ command: 'maiua-落雪B50',
3240
+ session,
3241
+ targetUserId: actualTargetUserId,
3242
+ status: 'success',
3243
+ result: successMessage,
3244
+ apiResponse: lxResult,
3245
+ });
3246
+ scheduleLxB50Notification(session, lxResult.task_id, refId);
3247
+ results.push(appendRefId(successMessage, refId));
3030
3248
  }
3031
- scheduleLxB50Notification(session, lxResult.task_id);
3032
- results.push(`❄️ 落雪: ✅ B50任务已提交!\n任务ID: ${lxResult.task_id}\n请耐心等待任务完成,预计1-10分钟`);
3033
- return null;
3034
3249
  }
3035
3250
  catch (retryError) {
3036
3251
  const failureResult = await handleApiFailure(session, ctx, api, binding, config, retryError, rebindTimeout);
@@ -3039,36 +3254,31 @@ function apply(ctx, config) {
3039
3254
  }
3040
3255
  if (retryError?.code === 'ECONNABORTED' || String(retryError?.message || '').includes('timeout')) {
3041
3256
  results.push('❄️ 落雪: ❌ 上传超时,请稍后再试一次。');
3042
- return null;
3043
3257
  }
3044
- if (retryError?.response) {
3258
+ else if (retryError?.response) {
3045
3259
  results.push(`❄️ 落雪: ❌ API请求失败: ${retryError.response.status} ${retryError.response.statusText}`);
3046
- return null;
3047
3260
  }
3048
- results.push(`❄️ 落雪: ❌ 上传失败: ${retryError?.message || '未知错误'}`);
3049
- return null;
3261
+ else {
3262
+ results.push(`❄️ 落雪: ❌ 上传失败: ${retryError?.message || '未知错误'}`);
3263
+ }
3050
3264
  }
3051
3265
  }
3266
+ }
3267
+ else {
3268
+ const failureResult = await handleApiFailure(session, ctx, api, binding, config, error, rebindTimeout);
3269
+ if (failureResult.rebindResult && failureResult.rebindResult.success && failureResult.rebindResult.newBinding) {
3270
+ return '✅ 重新绑定成功!请重新执行 /maiua 上传操作。';
3271
+ }
3272
+ if (error?.code === 'ECONNABORTED' || String(error?.message || '').includes('timeout')) {
3273
+ results.push('❄️ 落雪: ❌ 上传超时,请稍后再试一次。');
3274
+ }
3275
+ else if (error?.response) {
3276
+ results.push(`❄️ 落雪: ❌ API请求失败: ${error.response.status} ${error.response.statusText}`);
3277
+ }
3052
3278
  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
3279
  results.push(`❄️ 落雪: ❌ 上传失败: ${error?.message || '未知错误'}`);
3066
- return null;
3067
3280
  }
3068
3281
  }
3069
- })();
3070
- if (lxnsAbort) {
3071
- return lxnsAbort;
3072
3282
  }
3073
3283
  if (results.length === 0) {
3074
3284
  return `⚠️ 未能发起上传请求${proxyTip}`;
@@ -3613,11 +3823,11 @@ function apply(ctx, config) {
3613
3823
  if (!whitelistCheck.allowed) {
3614
3824
  return whitelistCheck.message || '本群暂时没有被授权使用本Bot的功能,请添加官方群聊1072033605。';
3615
3825
  }
3826
+ // 解析参数:第一个参数可能是SGID/URL或落雪代码
3827
+ let qrCode;
3828
+ let lxnsCode;
3829
+ let actualTargetUserId = targetUserId;
3616
3830
  try {
3617
- // 解析参数:第一个参数可能是SGID/URL或落雪代码
3618
- let qrCode;
3619
- let lxnsCode;
3620
- let actualTargetUserId = targetUserId;
3621
3831
  // 检查第一个参数是否是SGID或URL
3622
3832
  if (qrCodeOrLxnsCode) {
3623
3833
  const processed = processSGID(qrCodeOrLxnsCode);
@@ -3701,8 +3911,17 @@ function apply(ctx, config) {
3701
3911
  const taskIdInfo = result.task_id ? `\n任务ID: ${result.task_id}` : '';
3702
3912
  return `❌ 上传失败:${result.msg || '未知错误'}${taskIdInfo}`;
3703
3913
  }
3704
- scheduleLxB50Notification(session, result.task_id);
3705
- return `✅ 落雪B50上传任务已提交!\n任务ID: ${result.task_id}\n\n请耐心等待任务完成,预计1-10分钟`;
3914
+ const successMessage = `✅ 落雪B50上传任务已提交!\n任务ID: ${result.task_id}\n\n请耐心等待任务完成,预计1-10分钟`;
3915
+ const refId = await logOperation({
3916
+ command: 'mai上传落雪b50',
3917
+ session,
3918
+ targetUserId: actualTargetUserId,
3919
+ status: 'success',
3920
+ result: successMessage,
3921
+ apiResponse: result,
3922
+ });
3923
+ scheduleLxB50Notification(session, result.task_id, refId);
3924
+ return appendRefId(successMessage, refId);
3706
3925
  }
3707
3926
  return `❌ 获取二维码失败:${qrTextResult.error}`;
3708
3927
  }
@@ -3781,28 +4000,44 @@ function apply(ctx, config) {
3781
4000
  return `❌ 上传失败:${result.msg || '未知错误'}${taskIdInfo}`;
3782
4001
  }
3783
4002
  }
3784
- scheduleLxB50Notification(session, result.task_id);
3785
- return `✅ 落雪B50上传任务已提交!\n任务ID: ${result.task_id}\n\n请耐心等待任务完成,预计1-10分钟`;
4003
+ const successMessage = `✅ 落雪B50上传任务已提交!\n任务ID: ${result.task_id}\n\n请耐心等待任务完成,预计1-10分钟`;
4004
+ const refId = await logOperation({
4005
+ command: 'mai上传落雪b50',
4006
+ session,
4007
+ targetUserId: actualTargetUserId || undefined,
4008
+ status: 'success',
4009
+ result: successMessage,
4010
+ apiResponse: result,
4011
+ });
4012
+ scheduleLxB50Notification(session, result.task_id, refId);
4013
+ return appendRefId(successMessage, refId);
3786
4014
  }
3787
4015
  catch (error) {
3788
4016
  ctx.logger('maibot').error('上传落雪B50失败:', error);
3789
- if (maintenanceMode) {
3790
- return maintenanceMessage;
3791
- }
3792
- // 处理请求超时类错误,统一提示
3793
- if (error?.code === 'ECONNABORTED' || String(error?.message || '').includes('timeout')) {
3794
- let msg = '落雪B50任务 上传失败,请稍后再试一次。';
3795
- const maintenanceMsg = getMaintenanceMessage(maintenanceNotice);
3796
- if (maintenanceMsg) {
3797
- msg += `\n${maintenanceMsg}`;
3798
- }
3799
- msg += `\n\n${maintenanceMessage}`;
3800
- return msg;
3801
- }
3802
- if (error?.response) {
3803
- return `❌ API请求失败: ${error.response.status} ${error.response.statusText}\n\n${maintenanceMessage}`;
3804
- }
3805
- return `❌ 上传失败: ${error?.message || '未知错误'}\n\n${maintenanceMessage}`;
4017
+ const errorMessage = maintenanceMode
4018
+ ? maintenanceMessage
4019
+ : (error?.code === 'ECONNABORTED' || String(error?.message || '').includes('timeout')
4020
+ ? (() => {
4021
+ let msg = '落雪B50任务 上传失败,请稍后再试一次。';
4022
+ const maintenanceMsg = getMaintenanceMessage(maintenanceNotice);
4023
+ if (maintenanceMsg) {
4024
+ msg += `\n${maintenanceMsg}`;
4025
+ }
4026
+ msg += `\n\n${maintenanceMessage}`;
4027
+ return msg;
4028
+ })()
4029
+ : (error?.response
4030
+ ? `❌ API请求失败: ${error.response.status} ${error.response.statusText}\n\n${maintenanceMessage}`
4031
+ : `❌ 上传失败: ${error?.message || '未知错误'}\n\n${maintenanceMessage}`));
4032
+ const refId = await logOperation({
4033
+ command: 'mai上传落雪b50',
4034
+ session,
4035
+ targetUserId: (typeof actualTargetUserId !== 'undefined' ? actualTargetUserId : targetUserId) || undefined,
4036
+ status: 'error',
4037
+ errorMessage: error?.message || '未知错误',
4038
+ apiResponse: error?.response?.data,
4039
+ });
4040
+ return appendRefId(errorMessage, refId);
3806
4041
  }
3807
4042
  });
3808
4043
  // 查询落雪B50任务状态功能已暂时取消
@@ -4628,16 +4863,168 @@ function apply(ctx, config) {
4628
4863
  resultMessage = `ℹ️ 没有需要更新的用户\n所有用户都未开启锁定模式和保护模式`
4629
4864
  }
4630
4865
 
4631
- return resultMessage
4632
- } catch (error: any) {
4633
- logger.error('管理员一键关闭操作失败:', error)
4634
- if (maintenanceMode) {
4635
- return maintenanceMessage
4636
- }
4637
- return `❌ 操作失败: ${error?.message || '未知错误'}\n\n${maintenanceMessage}`
4638
- }
4639
- })
4866
+ const refId = await logOperation({
4867
+ command: 'mai管理员一键关闭',
4868
+ session,
4869
+ status: 'success',
4870
+ result: resultMessage,
4871
+ })
4872
+
4873
+ return appendRefId(resultMessage, refId)
4874
+ } catch (error: any) {
4875
+ logger.error('管理员一键关闭操作失败:', error)
4876
+ const errorMessage = maintenanceMode
4877
+ ? maintenanceMessage
4878
+ : `❌ 操作失败: ${error?.message || '未知错误'}\n\n${maintenanceMessage}`
4879
+
4880
+ const refId = await logOperation({
4881
+ command: 'mai管理员一键关闭',
4882
+ session,
4883
+ status: 'error',
4884
+ errorMessage: error?.message || '未知错误',
4885
+ })
4886
+
4887
+ return appendRefId(errorMessage, refId)
4888
+ }
4889
+ })
4640
4890
 
4891
+ /**
4892
+ * 管理员查询操作记录(通过 ref_id)
4893
+ * 用法: /mai管理员查询操作 <ref_id>
4894
+ */
4895
+ ctx.command('mai管理员查询操作 <refId:text>', '通过 Ref_ID 查询操作详细信息(需要auth等级3以上)')
4896
+ .userFields(['authority'])
4897
+ .action(async ({ session }, refId) => {
4898
+ if (!session) {
4899
+ return '❌ 无法获取会话信息';
4900
+ }
4901
+ if ((session.user?.authority ?? 0) < 3) {
4902
+ return '❌ 权限不足,需要auth等级3以上才能执行此操作';
4903
+ }
4904
+ try {
4905
+ const logs = await ctx.database.get('maibot_operation_logs', { refId: refId.trim() });
4906
+ if (logs.length === 0) {
4907
+ return `❌ 未找到 Ref_ID 为 "${refId}" 的操作记录`;
4908
+ }
4909
+ const log = logs[0];
4910
+ const statusText = {
4911
+ success: '✅ 成功',
4912
+ failure: '❌ 失败',
4913
+ error: '⚠️ 错误',
4914
+ }[log.status] || log.status;
4915
+ let result = `📋 操作记录详情\n\n`;
4916
+ result += `Ref_ID: ${log.refId}\n`;
4917
+ result += `命令: ${log.command}\n`;
4918
+ result += `操作人: ${log.userId}\n`;
4919
+ if (log.targetUserId) {
4920
+ result += `目标用户: ${log.targetUserId}\n`;
4921
+ }
4922
+ result += `状态: ${statusText}\n`;
4923
+ result += `操作时间: ${new Date(log.createdAt).toLocaleString('zh-CN')}\n`;
4924
+ if (log.guildId) {
4925
+ result += `群组ID: ${log.guildId}\n`;
4926
+ }
4927
+ if (log.channelId) {
4928
+ result += `频道ID: ${log.channelId}\n`;
4929
+ }
4930
+ if (log.result) {
4931
+ result += `\n操作结果:\n${log.result}\n`;
4932
+ }
4933
+ if (log.errorMessage) {
4934
+ result += `\n错误信息:\n${log.errorMessage}\n`;
4935
+ }
4936
+ if (log.apiResponse) {
4937
+ try {
4938
+ const apiResp = JSON.parse(log.apiResponse);
4939
+ result += `\nAPI响应:\n${JSON.stringify(apiResp, null, 2)}\n`;
4940
+ }
4941
+ catch {
4942
+ result += `\nAPI响应:\n${log.apiResponse}\n`;
4943
+ }
4944
+ }
4945
+ return result;
4946
+ }
4947
+ catch (error) {
4948
+ logger.error('查询操作记录失败:', error);
4949
+ return `❌ 查询失败: ${error?.message || '未知错误'}`;
4950
+ }
4951
+ });
4952
+ /**
4953
+ * 管理员查看今日命令统计
4954
+ * 用法: /mai管理员统计
4955
+ */
4956
+ ctx.command('mai管理员统计', '查看今日各指令执行次数统计(需要auth等级3以上)')
4957
+ .userFields(['authority'])
4958
+ .action(async ({ session }) => {
4959
+ if (!session) {
4960
+ return '❌ 无法获取会话信息';
4961
+ }
4962
+ if ((session.user?.authority ?? 0) < 3) {
4963
+ return '❌ 权限不足,需要auth等级3以上才能执行此操作';
4964
+ }
4965
+ try {
4966
+ const today = new Date();
4967
+ today.setHours(0, 0, 0, 0);
4968
+ const todayStart = today.getTime();
4969
+ // 获取今日所有操作记录
4970
+ const allLogs = await ctx.database.get('maibot_operation_logs', {});
4971
+ const todayLogs = allLogs.filter(log => new Date(log.createdAt).getTime() >= todayStart);
4972
+ // 统计各命令执行次数
4973
+ // 将任务完成/失败等子命令合并到主命令中
4974
+ const commandStats = {};
4975
+ // 命令名称映射:将子命令合并到主命令
4976
+ const commandMapping = {
4977
+ 'mai上传B50-任务完成': 'mai上传B50',
4978
+ 'mai上传B50-任务超时': 'mai上传B50',
4979
+ 'mai上传B50-轮询异常': 'mai上传B50',
4980
+ 'mai上传落雪b50-任务完成': 'mai上传落雪b50',
4981
+ 'mai上传落雪b50-任务超时': 'mai上传落雪b50',
4982
+ 'mai上传落雪b50-轮询异常': 'mai上传落雪b50',
4983
+ 'maiua-水鱼B50': 'maiua',
4984
+ 'maiua-落雪B50': 'maiua',
4985
+ };
4986
+ for (const log of todayLogs) {
4987
+ // 使用映射后的命令名称,如果没有映射则使用原命令名称
4988
+ const commandName = commandMapping[log.command] || log.command;
4989
+ if (!commandStats[commandName]) {
4990
+ commandStats[commandName] = { total: 0, success: 0, failure: 0, error: 0 };
4991
+ }
4992
+ commandStats[commandName].total++;
4993
+ if (log.status === 'success') {
4994
+ commandStats[commandName].success++;
4995
+ }
4996
+ else if (log.status === 'failure') {
4997
+ commandStats[commandName].failure++;
4998
+ }
4999
+ else if (log.status === 'error') {
5000
+ commandStats[commandName].error++;
5001
+ }
5002
+ }
5003
+ // 按执行次数排序
5004
+ const sortedCommands = Object.entries(commandStats).sort((a, b) => b[1].total - a[1].total);
5005
+ let result = `📊 今日命令执行统计\n\n`;
5006
+ result += `统计时间: ${new Date().toLocaleString('zh-CN')}\n`;
5007
+ result += `总操作数: ${todayLogs.length}\n\n`;
5008
+ if (sortedCommands.length === 0) {
5009
+ result += `ℹ️ 今日暂无操作记录`;
5010
+ }
5011
+ else {
5012
+ result += `各命令执行情况:\n`;
5013
+ for (const [command, stats] of sortedCommands) {
5014
+ result += `\n${command}:\n`;
5015
+ result += ` 总次数: ${stats.total}\n`;
5016
+ result += ` 成功: ${stats.success}\n`;
5017
+ result += ` 失败: ${stats.failure}\n`;
5018
+ result += ` 错误: ${stats.error}\n`;
5019
+ }
5020
+ }
5021
+ return result;
5022
+ }
5023
+ catch (error) {
5024
+ logger.error('查询统计失败:', error);
5025
+ return `❌ 查询失败: ${error?.message || '未知错误'}`;
5026
+ }
5027
+ });
4641
5028
  /**
4642
5029
  * 管理员关闭/开启登录播报功能(全局开关)
4643
5030
  * 用法: /mai管理员关闭登录播报 [on|off]