diff --git a/lib/index.js b/lib/index.js index 82f77b8..a47113b 100644 --- a/lib/index.js +++ b/lib/index.js @@ -185,7 +185,7 @@ function fromSSRHTML(html) { const block = html.slice(starts[i], starts[i + 1] ?? html.length); const labelMatch = block.match(/data-slot="usage-label"[^>]*>([^<]+)\s*(\d+)\s*/); - const resetMatch = block.match(/data-slot="reset-time"[\s\S]*?Resets in(?:\s*)?([\s\S]*?)(?:|<\/span>)/); + const resetMatch = block.match(/data-slot="reset-time"[\s\S]*?(?:Resets in|重置于)(?:\s*)?([\s\S]*?)(?:|<\/span>)/); if (!labelMatch || !valueMatch) continue; const label = labelMatch[1]?.trim() ?? ""; const percent = Number.parseInt(valueMatch[1] ?? "0", 10); @@ -214,17 +214,24 @@ function labelToKind(label) { if (lower.startsWith("rolling")) return "rolling"; if (lower.startsWith("weekly")) return "weekly"; if (lower.startsWith("monthly")) return "monthly"; + if (lower.startsWith("滚动")) return "rolling"; + if (lower.startsWith("每周")) return "weekly"; + if (lower.startsWith("每月")) return "monthly"; } /** Strip SolidStart HTML comments `` from a string. */ function stripHtmlComments(s) { return s.replace(//g, "").trim(); } /** -* Parse a human duration phrase into seconds. Examples: -* "2 hours 29 minutes" → 8940 -* "45 minutes" → 2700 -* "5 days" → 432000 -* "30 seconds" → 30 +* Parse a human duration phrase into seconds. Examples (English plus the +* Chinese renderings used by the zh locale): +* "2 hours 29 minutes" → 8940 "2 小时 29 分钟" → 8940 +* "45 minutes" → 2700 "45 分钟" → 2700 +* "5 days" → 432000 "5 天" → 432000 +* "30 seconds" → 30 "30 秒" → 30 +* "1 week" → 604800 "1 周" → 604800 +* "1 month" → 2592000 "1 个月" → 2592000 +* "1 year" → 31536000 "1 年" → 31536000 * * Returns 0 on unrecognized input. */ @@ -232,7 +239,7 @@ function parseDurationToSec(phrase) { if (!phrase) return 0; const p = phrase.replace(//g, " ").trim().replace(/\s+/g, " ").toLowerCase(); if (!p) return 0; - const re = /(\d+)\s*(second|minute|hour|day|week|month|year)s?/g; + const re = /(\d+)\s*(?:个\s*)?(second|minute|hour|day|week|month|year|秒|分钟|小时|天|周|月|年)s?/g; let total = 0; let matched = false; let m = re.exec(p); @@ -242,24 +249,31 @@ function parseDurationToSec(phrase) { matched = true; switch (unit) { case "second": + case "秒": total += n; break; case "minute": + case "分钟": total += n * 60; break; case "hour": + case "小时": total += n * 3600; break; case "day": + case "天": total += n * 86400; break; case "week": + case "周": total += n * 604800; break; case "month": + case "月": total += n * 2592e3; break; - case "year": total += n * 31536e3; + case "year": + case "年": total += n * 31536e3; } m = re.exec(p); } diff --git a/lib/types/api.d.ts b/lib/types/api.d.ts index 3f821d4..22c39d5 100644 --- a/lib/types/api.d.ts +++ b/lib/types/api.d.ts @@ -29,11 +29,15 @@ export declare function fetchViaCookie(cfg: OcgoConfig): Promise; /** - * Parse a human duration phrase into seconds. Examples: - * "2 hours 29 minutes" → 8940 - * "45 minutes" → 2700 - * "5 days" → 432000 - * "30 seconds" → 30 + * Parse a human duration phrase into seconds. Examples (English plus the + * Chinese renderings used by the zh locale): + * "2 hours 29 minutes" → 8940 "2 小时 29 分钟" → 8940 + * "45 minutes" → 2700 "45 分钟" → 2700 + * "5 days" → 432000 "5 天" → 432000 + * "30 seconds" → 30 "30 秒" → 30 + * "1 week" → 604800 "1 周" → 604800 + * "1 month" → 2592000 "1 个月" → 2592000 + * "1 year" → 31536000 "1 年" → 31536000 * * Returns 0 on unrecognized input. */ diff --git a/lib/types/api.d.ts.map b/lib/types/api.d.ts.map index 2ca3c70..f0f3292 100644 --- a/lib/types/api.d.ts.map +++ b/lib/types/api.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAgC,MAAM,YAAY,CAAA;AAM3F,iFAAiF;AACjF,qBAAa,UAAW,SAAQ,KAAK;aAIjB,IAAI,EAAE,MAAM;IAH9B,SAAkB,IAAI,gBAAe;gBAEnC,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,MAAM;CAI/B;AAmDD,6EAA6E;AAC7E,wBAAsB,cAAc,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,eAAe,EAAE,WAAW,CAAC,CAAC,CAkBjG;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC,eAAe,EAAE,WAAW,CAAC,CA6C5E;AAeD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA0CzD;AAMD;;;GAGG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,eAAe,CAAC,CAG1E"} \ No newline at end of file +{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAgC,MAAM,YAAY,CAAA;AAM3F,iFAAiF;AACjF,qBAAa,UAAW,SAAQ,KAAK;aAIjB,IAAI,EAAE,MAAM;IAH9B,SAAkB,IAAI,gBAAe;gBAEnC,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,MAAM;CAI/B;AAmDD,6EAA6E;AAC7E,wBAAsB,cAAc,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,eAAe,EAAE,WAAW,CAAC,CAAC,CAkBjG;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC,eAAe,EAAE,WAAW,CAAC,CA+C5E;AAoBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAiDzD;AAMD;;;GAGG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,eAAe,CAAC,CAG1E"} \ No newline at end of file diff --git a/lib/types/api.js b/lib/types/api.js index d0ed217..0269e62 100644 --- a/lib/types/api.js +++ b/lib/types/api.js @@ -104,7 +104,9 @@ export function fromSSRHTML(html) { const block = html.slice(starts[i], starts[i + 1] ?? html.length); const labelMatch = block.match(/data-slot="usage-label"[^>]*>([^<]+)\s*(\d+)\s*/); - const resetMatch = block.match(/data-slot="reset-time"[\s\S]*?Resets in(?:\s*)?([\s\S]*?)(?:|<\/span>)/); + // The page renders the reset phrase in the UI locale — "Resets in" + // (en) or "重置于" (zh) — so accept both. + const resetMatch = block.match(/data-slot="reset-time"[\s\S]*?(?:Resets in|重置于)(?:\s*)?([\s\S]*?)(?:|<\/span>)/); if (!labelMatch || !valueMatch) continue; const label = labelMatch[1]?.trim() ?? ''; @@ -128,12 +130,20 @@ export function fromSSRHTML(html) { } function labelToKind(label) { const lower = label.toLowerCase(); + // English labels ("Rolling Usage", "Weekly Usage", "Monthly Usage"). if (lower.startsWith('rolling')) return 'rolling'; if (lower.startsWith('weekly')) return 'weekly'; if (lower.startsWith('monthly')) return 'monthly'; + // Chinese labels rendered for zh locale ("滚动用量", "每周用量", "每月用量"). + if (lower.startsWith('滚动')) + return 'rolling'; + if (lower.startsWith('每周')) + return 'weekly'; + if (lower.startsWith('每月')) + return 'monthly'; return undefined; } /** Strip SolidStart HTML comments `` from a string. */ @@ -141,11 +151,15 @@ function stripHtmlComments(s) { return s.replace(//g, '').trim(); } /** - * Parse a human duration phrase into seconds. Examples: - * "2 hours 29 minutes" → 8940 - * "45 minutes" → 2700 - * "5 days" → 432000 - * "30 seconds" → 30 + * Parse a human duration phrase into seconds. Examples (English plus the + * Chinese renderings used by the zh locale): + * "2 hours 29 minutes" → 8940 "2 小时 29 分钟" → 8940 + * "45 minutes" → 2700 "45 分钟" → 2700 + * "5 days" → 432000 "5 天" → 432000 + * "30 seconds" → 30 "30 秒" → 30 + * "1 week" → 604800 "1 周" → 604800 + * "1 month" → 2592000 "1 个月" → 2592000 + * "1 year" → 31536000 "1 年" → 31536000 * * Returns 0 on unrecognized input. */ @@ -158,7 +172,7 @@ export function parseDurationToSec(phrase) { const p = cleaned.trim().replace(/\s+/g, ' ').toLowerCase(); if (!p) return 0; - const re = /(\d+)\s*(second|minute|hour|day|week|month|year)s?/g; + const re = /(\d+)\s*(?:个\s*)?(second|minute|hour|day|week|month|year|秒|分钟|小时|天|周|月|年)s?/g; let total = 0; let matched = false; let m = re.exec(p); @@ -168,24 +182,31 @@ export function parseDurationToSec(phrase) { matched = true; switch (unit) { case 'second': + case '秒': total += n; break; case 'minute': + case '分钟': total += n * 60; break; case 'hour': + case '小时': total += n * 3600; break; case 'day': + case '天': total += n * 86400; break; case 'week': + case '周': total += n * 604800; break; case 'month': + case '月': total += n * 2592000; // 30 days; coarse but adequate for display break; case 'year': + case '年': total += n * 31536000; break; } diff --git a/src/api.test.ts b/src/api.test.ts index 72bbf33..21ffb58 100644 --- a/src/api.test.ts +++ b/src/api.test.ts @@ -98,6 +98,44 @@ describe('fromSSRHTML', () => { const parsed = fromSSRHTML(page) expect(parsed.monthly?.percent).toBe(100) }) + + it('parses a zh-locale page (Chinese labels and reset phrases)', () => { + const zhPage = ` +
+ 滚动用量 + 27% + 重置于3 小时 37 分钟 +
+
+ 每周用量 + 15% + 重置于6 天 15 小时 +
+
+ 每月用量 + 20% + 重置于15 天 17 小时 +
` + const parsed = fromSSRHTML(zhPage) + expect(parsed.rolling).toEqual({ + kind: 'rolling', + percent: 27, + resetInSec: 3 * 3600 + 37 * 60, + status: 'ok', + }) + expect(parsed.weekly).toEqual({ + kind: 'weekly', + percent: 15, + resetInSec: 6 * 86400 + 15 * 3600, + status: 'ok', + }) + expect(parsed.monthly).toEqual({ + kind: 'monthly', + percent: 20, + resetInSec: 15 * 86400 + 17 * 3600, + status: 'ok', + }) + }) }) describe('parseDurationToSec', () => { @@ -109,6 +147,13 @@ describe('parseDurationToSec', () => { ['1 week', 604800], ['1 month', 2592000], ['1 year', 31536000], + ['2 小时 29 分钟', 2 * 3600 + 29 * 60], + ['45 分钟', 45 * 60], + ['5 天', 5 * 86400], + ['30 秒', 30], + ['1 周', 604800], + ['1 个月', 2592000], + ['1 年', 31536000], ['', 0], ['garbage text', 0], ])('parses %j → %i', (phrase, expected) => { diff --git a/src/api.ts b/src/api.ts index bef21d0..042b47f 100644 --- a/src/api.ts +++ b/src/api.ts @@ -125,8 +125,10 @@ export function fromSSRHTML(html: string): Omit { const block = html.slice(starts[i], starts[i + 1] ?? html.length) const labelMatch = block.match(/data-slot="usage-label"[^>]*>([^<]+)\s*(\d+)\s*/) + // The page renders the reset phrase in the UI locale — "Resets in" + // (en) or "重置于" (zh) — so accept both. const resetMatch = block.match( - /data-slot="reset-time"[\s\S]*?Resets in(?:\s*)?([\s\S]*?)(?:|<\/span>)/, + /data-slot="reset-time"[\s\S]*?(?:Resets in|重置于)(?:\s*)?([\s\S]*?)(?:|<\/span>)/, ) if (!labelMatch || !valueMatch) continue const label = labelMatch[1]?.trim() ?? '' @@ -155,9 +157,14 @@ export function fromSSRHTML(html: string): Omit { function labelToKind(label: string): UsageWindowKind | undefined { const lower = label.toLowerCase() + // English labels ("Rolling Usage", "Weekly Usage", "Monthly Usage"). if (lower.startsWith('rolling')) return 'rolling' if (lower.startsWith('weekly')) return 'weekly' if (lower.startsWith('monthly')) return 'monthly' + // Chinese labels rendered for zh locale ("滚动用量", "每周用量", "每月用量"). + if (lower.startsWith('滚动')) return 'rolling' + if (lower.startsWith('每周')) return 'weekly' + if (lower.startsWith('每月')) return 'monthly' return undefined } @@ -167,11 +174,15 @@ function stripHtmlComments(s: string): string { } /** - * Parse a human duration phrase into seconds. Examples: - * "2 hours 29 minutes" → 8940 - * "45 minutes" → 2700 - * "5 days" → 432000 - * "30 seconds" → 30 + * Parse a human duration phrase into seconds. Examples (English plus the + * Chinese renderings used by the zh locale): + * "2 hours 29 minutes" → 8940 "2 小时 29 分钟" → 8940 + * "45 minutes" → 2700 "45 分钟" → 2700 + * "5 days" → 432000 "5 天" → 432000 + * "30 seconds" → 30 "30 秒" → 30 + * "1 week" → 604800 "1 周" → 604800 + * "1 month" → 2592000 "1 个月" → 2592000 + * "1 year" → 31536000 "1 年" → 31536000 * * Returns 0 on unrecognized input. */ @@ -183,7 +194,7 @@ export function parseDurationToSec(phrase: string): number { const p = cleaned.trim().replace(/\s+/g, ' ').toLowerCase() if (!p) return 0 - const re = /(\d+)\s*(second|minute|hour|day|week|month|year)s?/g + const re = /(\d+)\s*(?:个\s*)?(second|minute|hour|day|week|month|year|秒|分钟|小时|天|周|月|年)s?/g let total = 0 let matched = false let m = re.exec(p) @@ -193,24 +204,31 @@ export function parseDurationToSec(phrase: string): number { matched = true switch (unit) { case 'second': + case '秒': total += n break case 'minute': + case '分钟': total += n * 60 break case 'hour': + case '小时': total += n * 3600 break case 'day': + case '天': total += n * 86400 break case 'week': + case '周': total += n * 604800 break case 'month': + case '月': total += n * 2592000 // 30 days; coarse but adequate for display break case 'year': + case '年': total += n * 31536000 break }