Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"[^>]*>([^<]+)</);
const valueMatch = block.match(/data-slot="usage-value"[\s\S]*?<!--\$-->\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);
Expand Down Expand Up @@ -214,25 +214,32 @@ 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(/<!--[\s\S]*?-->/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.
*/
function parseDurationToSec(phrase) {
if (!phrase) return 0;
const p = phrase.replace(/<!--[\s\S]*?-->/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);
Expand All @@ -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);
}
Expand Down
14 changes: 9 additions & 5 deletions lib/types/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,15 @@ export declare function fetchViaCookie(cfg: OcgoConfig): Promise<Omit<Normalized
*/
export declare function fromSSRHTML(html: string): Omit<NormalizedUsage, 'updatedAt'>;
/**
* 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.
*/
Expand Down
2 changes: 1 addition & 1 deletion lib/types/api.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 28 additions & 7 deletions lib/types/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"[^>]*>([^<]+)</);
const valueMatch = block.match(/data-slot="usage-value"[\s\S]*?<!--\$-->\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() ?? '';
Expand All @@ -128,24 +130,36 @@ 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. */
function stripHtmlComments(s) {
return s.replace(/<!--[\s\S]*?-->/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.
*/
Expand All @@ -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);
Expand All @@ -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;
}
Expand Down
45 changes: 45 additions & 0 deletions src/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = `
<div data-slot="usage-item">
<span data-slot="usage-label">滚动用量</span>
<span data-slot="usage-value"><!--$-->27<!--/-->%</span>
<span data-slot="reset-time"><!--$-->重置于<!--/-->3 小时 37 分钟<!--/--></span>
</div>
<div data-slot="usage-item">
<span data-slot="usage-label">每周用量</span>
<span data-slot="usage-value"><!--$-->15<!--/-->%</span>
<span data-slot="reset-time"><!--$-->重置于<!--/-->6 天 15 小时<!--/--></span>
</div>
<div data-slot="usage-item">
<span data-slot="usage-label">每月用量</span>
<span data-slot="usage-value"><!--$-->20<!--/-->%</span>
<span data-slot="reset-time"><!--$-->重置于<!--/-->15 天 17 小时<!--/--></span>
</div>`
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', () => {
Expand All @@ -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) => {
Expand Down
32 changes: 25 additions & 7 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,10 @@ export function fromSSRHTML(html: string): Omit<NormalizedUsage, 'updatedAt'> {
const block = html.slice(starts[i], starts[i + 1] ?? html.length)
const labelMatch = block.match(/data-slot="usage-label"[^>]*>([^<]+)</)
const valueMatch = block.match(/data-slot="usage-value"[\s\S]*?<!--\$-->\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() ?? ''
Expand Down Expand Up @@ -155,9 +157,14 @@ export function fromSSRHTML(html: string): Omit<NormalizedUsage, 'updatedAt'> {

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
}

Expand All @@ -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.
*/
Expand All @@ -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)
Expand All @@ -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
}
Expand Down