Skip to content

Commit 511a986

Browse files
cyfung1031Copilot
andauthored
♻️ 优化 prettyUrl 功能 (#1244)
* 修正 prettyUrl 不完整 * Update src/pkg/utils/url-utils.test.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/pkg/utils/punycode.test.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/pkg/utils/url-utils.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * replace 錯了 --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 1fe8d20 commit 511a986

6 files changed

Lines changed: 488 additions & 35 deletions

File tree

src/pages/install/App.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@ import { intervalExecution, timeoutExecution } from "@App/pkg/utils/timer";
3131
import { useSearchParams } from "react-router-dom";
3232
import { CACHE_KEY_SCRIPT_INFO } from "@App/app/cache_key";
3333
import { cacheInstance } from "@App/app/cache";
34-
import { formatBytes, prettyUrl } from "@App/pkg/utils/utils";
34+
import { formatBytes } from "@App/pkg/utils/utils";
3535
import { ScriptIcons } from "../options/routes/utils";
3636
import { bytesDecode, detectEncoding } from "@App/pkg/utils/encoding";
37+
import { prettyUrl } from "@App/pkg/utils/url-utils";
3738

3839
const backgroundPromptShownKey = "background_prompt_shown";
3940

src/pkg/utils/punycode.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { describe, it, expect } from "vitest";
2+
import { decodePunycode } from "./punycode";
3+
4+
const getPunycode = (x: string) => {
5+
return new URL(`http://${x}.io`).hostname.slice(0, -3);
6+
};
7+
8+
describe.concurrent("punycode - decode only", () => {
9+
it.concurrent("basic", () => {
10+
expect(decodePunycode("xn--viertelvergngen-bwb")).toBe("viertelvergnügen");
11+
expect(decodePunycode("xn--maana-pta")).toBe("mañana");
12+
expect(decodePunycode("xn--bcher-kva")).toBe("b\xFCcher");
13+
expect(decodePunycode("xn--caf-dma")).toBe("caf\xE9");
14+
expect(decodePunycode("xn----dqo34k")).toBe("\u2603-\u2318");
15+
expect(decodePunycode("xn----dqo34kn65z")).toBe("\uD400\u2603-\u2318");
16+
expect(decodePunycode("xn--ls8h")).toBe("\uD83D\uDCA9");
17+
expect(decodePunycode("xn--p-8sbkgc5ag7bhce")).toBe("джpумлатест");
18+
expect(decodePunycode("xn--ba-lmcq")).toBe("bрфa");
19+
20+
const codes = {
21+
"为什么选择scriptcat-脚本猫": "xn--scriptcat--xx2pif85dpx1n4mn5i9brrzc1f2c",
22+
"scriptcat脚本猫完全兼容油猴脚本-同时提供后台脚本运行框架-丰富的api扩展-让你的浏览体验更出色":
23+
"xn--scriptcat--api--803xq5lxg84bn7x9law6gwxrftatbw476a18aopi2ky56dycj3jr6wh00beah022clsg5u6cb6en53e7ka292jha3303jiai546nxt1eksp1sw6k3c251h",
24+
"为什么选择scriptcat-基于油猴的设计理念-完全兼容油猴脚本-提供更多丰富的api让脚本能够完成更多强大的功能":
25+
"xn--scriptcat---api-903xremky1ci2a25kznmfnap9t8q1beegea3l0js49dka54lbs864gsvf33vulhm9i656aja69tka9912dga2675eha679g784aral7387kja72mma6139lpna01bz17n",
26+
"为什么选择scriptcat-基于油猴的设计理念-完全兼容油猴脚本-提供更多丰富的api-脚本猫不仅兼容油猴脚本-还支持后台脚本运行-功能更强大-覆盖范围广-安装脚本管理器-寻找适合的脚本一键安装即可":
27+
"xn--scriptcat---api-------ql07anix3aghnnp9dxybze30yj7pksaja010heqhtxe3a13grg250g79e2zm98lzqas99fea7iu6hla65gc3exv9ccdi39j2m0b43e9pbw40acmuh6ysa365alaefkf5967ggar9528i6rahat638mlb3788ara0bz70d1o7id3te56bnaignj0264b5n7g88ioa140pgu0cyicj71n8vat3kmrap232b",
28+
"asmdksmklcmdsk-寻找-lmklamdkjqdenakjc-njkqelnuiconwerj-ksfnvcslkjdmc-jweasjkndjk-sandkjasnjxksakjkxnjaksn适合的-xj-kqwnjkxnqjas-nxsjkanxjksnjxansjk-cnajskn-cjkaxjksn-kxjasnjkxansjk-xnasjkxnksaj-cnjkdcnjksdncjsdnjcsdjkc-nmckj脚本":
29+
"xn--asmdksmklcmdsk--lmklamdkjqdenakjc-njkqelnuiconwerj-ksfnvcslkjdmc-jweasjkndjk-sandkjasnjxksakjkxnjaksn-xj-kqwnjkxnqjas-nxsjkanxjksnjxansjk-cnajskn-cjkaxjksn-kxjasnjkxansjk-xnasjkxnksaj-cnjkdcnjksdncjsdnjcsdjkc-nmckj-0g768an264bok8jtmrh2e00as1gp9lgw",
30+
};
31+
32+
let testRaw: keyof typeof codes;
33+
34+
testRaw = "为什么选择scriptcat-脚本猫";
35+
36+
expect(codes[testRaw]).toBe(getPunycode(testRaw));
37+
expect(decodePunycode(codes[testRaw])).toBe(testRaw);
38+
39+
testRaw = "scriptcat脚本猫完全兼容油猴脚本-同时提供后台脚本运行框架-丰富的api扩展-让你的浏览体验更出色";
40+
41+
expect(codes[testRaw]).toBe(getPunycode(testRaw));
42+
expect(decodePunycode(codes[testRaw])).toBe(testRaw);
43+
44+
testRaw = "为什么选择scriptcat-基于油猴的设计理念-完全兼容油猴脚本-提供更多丰富的api让脚本能够完成更多强大的功能";
45+
46+
expect(codes[testRaw]).toBe(getPunycode(testRaw));
47+
expect(decodePunycode(codes[testRaw])).toBe(testRaw);
48+
49+
testRaw =
50+
"为什么选择scriptcat-基于油猴的设计理念-完全兼容油猴脚本-提供更多丰富的api-脚本猫不仅兼容油猴脚本-还支持后台脚本运行-功能更强大-覆盖范围广-安装脚本管理器-寻找适合的脚本一键安装即可";
51+
52+
expect(codes[testRaw]).toBe(getPunycode(testRaw));
53+
expect(decodePunycode(codes[testRaw])).toBe(testRaw);
54+
55+
testRaw =
56+
"asmdksmklcmdsk-寻找-lmklamdkjqdenakjc-njkqelnuiconwerj-ksfnvcslkjdmc-jweasjkndjk-sandkjasnjxksakjkxnjaksn适合的-xj-kqwnjkxnqjas-nxsjkanxjksnjxansjk-cnajskn-cjkaxjksn-kxjasnjkxansjk-xnasjkxnksaj-cnjkdcnjksdncjsdnjcsdjkc-nmckj脚本";
57+
58+
expect(codes[testRaw]).toBe(getPunycode(testRaw));
59+
expect(decodePunycode(codes[testRaw])).toBe(testRaw);
60+
});
61+
});

src/pkg/utils/punycode.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
const maxInt = 2147483647; // 2^31-1
2+
const base = 36;
3+
const tMin = 1;
4+
const tMax = 26;
5+
const skew = 38;
6+
const damp = 700;
7+
const initialBias = 72;
8+
const initialN = 128;
9+
const delimiter = "-";
10+
const adaptD = base - tMin; // 35
11+
const adaptL = ((adaptD + 1) * tMax) >>> 1; // 468
12+
13+
const ERR = {
14+
OVERFLOW: "Overflow: input needs wider integers",
15+
INVALID: "Invalid Punycode input",
16+
} as const;
17+
18+
const error = (t: string) => {
19+
throw new RangeError(t);
20+
};
21+
22+
const floor = Math.floor;
23+
24+
const adapt = (delta: number, numPoints: number, firstTime: boolean) => {
25+
delta = firstTime ? floor(delta / damp) : delta >>> 1;
26+
delta += floor(delta / numPoints);
27+
let k = 0;
28+
for (; delta > adaptL; k += base) delta = floor(delta / adaptD);
29+
return k + floor(((adaptD + 1) * delta) / (delta + skew));
30+
};
31+
32+
/**
33+
* Decodes Punycode (RFC 3492)
34+
* npm package "punycode" is too large. We just need a simple and robust one.
35+
* Punycode is case-insensitive; decodePunycode handle labels individually without dot split
36+
*/
37+
export const decodePunycode = (input: string) => {
38+
input = input.toLowerCase();
39+
input = input.startsWith("xn--") ? input.slice(4) : input;
40+
if (!input || input.length > 251) error(ERR.INVALID);
41+
const output: number[] = [];
42+
const len = input.length;
43+
let i = 0;
44+
let n = initialN;
45+
let bias = initialBias;
46+
47+
const k = input.lastIndexOf(delimiter);
48+
49+
let j = 0;
50+
for (; j < k; ++j) {
51+
const cp = input.codePointAt(j)!;
52+
if (cp >= 0x80) error(ERR.INVALID);
53+
output.push(cp);
54+
}
55+
56+
if (j > 0) j++;
57+
58+
if (j >= len) error(ERR.INVALID);
59+
60+
while (j < len) {
61+
const oldi = i;
62+
let w = 1;
63+
64+
for (let k = base; ; k += base) {
65+
if (j >= len) error(ERR.INVALID);
66+
const cp = input.codePointAt(j++)!;
67+
68+
let digit = -1;
69+
// 0-9 / A-Z / a-z
70+
if (cp >= 0x30 && cp < 0x3a) digit = 26 + (cp - 0x30);
71+
else if (cp >= 0x41 && cp < 0x5b) digit = cp - 0x41;
72+
else if (cp >= 0x61 && cp < 0x7b) digit = cp - 0x61;
73+
else error(ERR.INVALID);
74+
75+
i += digit * w;
76+
if (i >= maxInt) error(ERR.OVERFLOW);
77+
78+
const t = k <= bias ? tMin : k >= bias + tMax ? tMax : k - bias;
79+
80+
if (digit < t) break;
81+
82+
const baseMinusT = base - t;
83+
w *= baseMinusT;
84+
if (w >= maxInt) error(ERR.OVERFLOW);
85+
}
86+
87+
const out = output.length + 1;
88+
bias = adapt(i - oldi, out, oldi === 0);
89+
if (bias > 198) error(ERR.OVERFLOW); // 198 is the theoretical max for 251 bytes decoding to ~0x10ffff
90+
n += floor(i / out);
91+
if (n > 0x10ffff) error(ERR.OVERFLOW);
92+
i %= out;
93+
94+
output.splice(i++, 0, n);
95+
}
96+
97+
return String.fromCodePoint(...output);
98+
};

0 commit comments

Comments
 (0)