(msg: Api.Message)
| 469 | // 解析用户信息 |
| 470 | const userInfoParts: Record<string, string> = {}; |
| 471 | userInfoHeader.split(';').forEach((part: string) => { |
| 472 | const equalsIndex = part.indexOf('='); |
| 473 | if (equalsIndex > 0) userInfoParts[part.substring(0, equalsIndex).trim().toLowerCase()] = part.substring(equalsIndex + 1).trim(); |
| 474 | }); |
| 475 | |
| 476 | const upload = parseInt(userInfoParts.upload || '0'); |
| 477 | const download = parseInt(userInfoParts.download || '0'); |
| 478 | const total = parseInt(userInfoParts.total || '0'); |
| 479 | const expireTs = parseInt(userInfoParts.expire || '0'); |
| 480 | const startTs = parseInt(userInfoParts.starttime || '0'); |
| 481 | |
| 482 | const used = upload + download; |
| 483 | const remain = total > used ? total - used : 0; |
| 484 | const percent = total > 0 ? Math.round((used / total) * 10000) / 100 : 0; |
| 485 | |
| 486 | // 状态判断 |
| 487 | let status = "有效"; |
| 488 | let statusEmoji = "✅"; |
| 489 | if (total > 0 && remain <= 0) { status = "耗尽"; statusEmoji = "⚠️"; } |
| 490 | if (expireTs && Date.now() > expireTs * 1000) { status = "过期"; statusEmoji = "❌"; } |
| 491 | |
| 492 | // 获取节点信息 |
| 493 | try { result.nodeInfo = await getNodeInfo(url); } catch { result.nodeInfo = null; } |
| 494 | |
| 495 | return { |
| 496 | ...result, success: true, status, statusEmoji, upload, download, total, used, remain, percent, expireTs, startTs |
| 497 | }; |
| 498 | |
| 499 | } catch (err: any) { |
| 500 | result.errorMessage = err.message || '未知错误'; |
| 501 | return result; |
| 502 | } |
| 503 | } |
| 504 | |
| 505 | // --- 详细模式处理器 (.subinfo) --- |
| 506 | async handleSubinfo(msg: Api.Message): Promise<void> { |
| 507 | const client = await getGlobalClient(); |
| 508 | await msg.edit({ text: "⏳ 正在准备解析订阅,请稍候..." }); |
| 509 | |
| 510 | const myText = (msg.text ?? '').trim(); |
| 511 | const parts = myText.split(/\s+/).slice(1); |
| 512 | |
| 513 | const isTxtOutput = parts.length > 0 && parts[0].toLowerCase() === 'txt'; |
| 514 | const cleanParts = isTxtOutput ? parts.slice(1) : parts; |
| 515 | |
| 516 | let sourceText = ''; |
| 517 | if (msg.replyToMsgId) { |
| 518 | try { |
| 519 | const replyMsg = await safeGetReplyMessage(msg); |
| 520 | if (replyMsg) sourceText = (replyMsg.text ?? '') + ' ' + ((replyMsg as any).caption ?? ''); |
| 521 | } catch { /* 忽略 */ } |
| 522 | } |
| 523 | if (cleanParts.length > 0) sourceText += ' ' + cleanParts.join(' '); |
| 524 | sourceText = sourceText.trim(); |
| 525 | |
| 526 | if (!sourceText) { |
| 527 | await msg.edit({ text: this.description, parseMode: "html" }); |
| 528 | return; |
nothing calls this directly
no test coverage detected