Skip to content
Merged
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
3 changes: 2 additions & 1 deletion src/pages/install/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@
import { useSearchParams } from "react-router-dom";
import { CACHE_KEY_SCRIPT_INFO } from "@App/app/cache_key";
import { cacheInstance } from "@App/app/cache";
import { formatBytes, prettyUrl } from "@App/pkg/utils/utils";
import { formatBytes } from "@App/pkg/utils/utils";
import { ScriptIcons } from "../options/routes/utils";
import { bytesDecode, detectEncoding } from "@App/pkg/utils/encoding";
import { prettyUrl } from "@App/pkg/utils/url-utils";

const backgroundPromptShownKey = "background_prompt_shown";

Expand Down Expand Up @@ -330,7 +331,7 @@

useEffect(() => {
!loaded && initAsync();
}, [searchParams, loaded]);

Check warning on line 334 in src/pages/install/App.tsx

View workflow job for this annotation

GitHub Actions / Run tests

React Hook useEffect has a missing dependency: 'initAsync'. Either include it or remove the dependency array

const [watchFile, setWatchFile] = useState(false);
const metadataLive = useMemo(() => (scriptInfo?.metadata || {}) as SCMetadata, [scriptInfo]);
Expand Down Expand Up @@ -645,7 +646,7 @@
return () => {
unmountFileTrack(handle);
};
}, [memoWatchFile]);

Check warning on line 649 in src/pages/install/App.tsx

View workflow job for this annotation

GitHub Actions / Run tests

React Hook useEffect has missing dependencies: 'localFileHandle', 'scriptInfo?.uuid', 'setupWatchFile', and 'watchFile'. Either include them or remove the dependency array

// 检查是否有 uuid 或 file
const hasUUIDorFile = useMemo(() => {
Expand Down Expand Up @@ -712,7 +713,7 @@
useEffect(() => {
if (!urlHref) return;
loadURLAsync(urlHref);
}, [urlHref]);

Check warning on line 716 in src/pages/install/App.tsx

View workflow job for this annotation

GitHub Actions / Run tests

React Hook useEffect has a missing dependency: 'loadURLAsync'. Either include it or remove the dependency array

if (!hasUUIDorFile) {
return urlHref ? (
Expand Down
61 changes: 61 additions & 0 deletions src/pkg/utils/punycode.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { describe, it, expect } from "vitest";
import { decodePunycode } from "./punycode";

const getPunycode = (x: string) => {
return new URL(`http://${x}.io`).hostname.slice(0, -3);
};

describe.concurrent("punycode - decode only", () => {
it.concurrent("basic", () => {
expect(decodePunycode("xn--viertelvergngen-bwb")).toBe("viertelvergnügen");
expect(decodePunycode("xn--maana-pta")).toBe("mañana");
expect(decodePunycode("xn--bcher-kva")).toBe("b\xFCcher");
expect(decodePunycode("xn--caf-dma")).toBe("caf\xE9");
expect(decodePunycode("xn----dqo34k")).toBe("\u2603-\u2318");
expect(decodePunycode("xn----dqo34kn65z")).toBe("\uD400\u2603-\u2318");
expect(decodePunycode("xn--ls8h")).toBe("\uD83D\uDCA9");
expect(decodePunycode("xn--p-8sbkgc5ag7bhce")).toBe("джpумлатест");
expect(decodePunycode("xn--ba-lmcq")).toBe("bрфa");

const codes = {
"为什么选择scriptcat-脚本猫": "xn--scriptcat--xx2pif85dpx1n4mn5i9brrzc1f2c",
"scriptcat脚本猫完全兼容油猴脚本-同时提供后台脚本运行框架-丰富的api扩展-让你的浏览体验更出色":
"xn--scriptcat--api--803xq5lxg84bn7x9law6gwxrftatbw476a18aopi2ky56dycj3jr6wh00beah022clsg5u6cb6en53e7ka292jha3303jiai546nxt1eksp1sw6k3c251h",
"为什么选择scriptcat-基于油猴的设计理念-完全兼容油猴脚本-提供更多丰富的api让脚本能够完成更多强大的功能":
"xn--scriptcat---api-903xremky1ci2a25kznmfnap9t8q1beegea3l0js49dka54lbs864gsvf33vulhm9i656aja69tka9912dga2675eha679g784aral7387kja72mma6139lpna01bz17n",
"为什么选择scriptcat-基于油猴的设计理念-完全兼容油猴脚本-提供更多丰富的api-脚本猫不仅兼容油猴脚本-还支持后台脚本运行-功能更强大-覆盖范围广-安装脚本管理器-寻找适合的脚本一键安装即可":
"xn--scriptcat---api-------ql07anix3aghnnp9dxybze30yj7pksaja010heqhtxe3a13grg250g79e2zm98lzqas99fea7iu6hla65gc3exv9ccdi39j2m0b43e9pbw40acmuh6ysa365alaefkf5967ggar9528i6rahat638mlb3788ara0bz70d1o7id3te56bnaignj0264b5n7g88ioa140pgu0cyicj71n8vat3kmrap232b",
"asmdksmklcmdsk-寻找-lmklamdkjqdenakjc-njkqelnuiconwerj-ksfnvcslkjdmc-jweasjkndjk-sandkjasnjxksakjkxnjaksn适合的-xj-kqwnjkxnqjas-nxsjkanxjksnjxansjk-cnajskn-cjkaxjksn-kxjasnjkxansjk-xnasjkxnksaj-cnjkdcnjksdncjsdnjcsdjkc-nmckj脚本":
"xn--asmdksmklcmdsk--lmklamdkjqdenakjc-njkqelnuiconwerj-ksfnvcslkjdmc-jweasjkndjk-sandkjasnjxksakjkxnjaksn-xj-kqwnjkxnqjas-nxsjkanxjksnjxansjk-cnajskn-cjkaxjksn-kxjasnjkxansjk-xnasjkxnksaj-cnjkdcnjksdncjsdnjcsdjkc-nmckj-0g768an264bok8jtmrh2e00as1gp9lgw",
};

let testRaw: keyof typeof codes;

testRaw = "为什么选择scriptcat-脚本猫";

expect(codes[testRaw]).toBe(getPunycode(testRaw));
expect(decodePunycode(codes[testRaw])).toBe(testRaw);

testRaw = "scriptcat脚本猫完全兼容油猴脚本-同时提供后台脚本运行框架-丰富的api扩展-让你的浏览体验更出色";

expect(codes[testRaw]).toBe(getPunycode(testRaw));
expect(decodePunycode(codes[testRaw])).toBe(testRaw);

testRaw = "为什么选择scriptcat-基于油猴的设计理念-完全兼容油猴脚本-提供更多丰富的api让脚本能够完成更多强大的功能";

expect(codes[testRaw]).toBe(getPunycode(testRaw));
expect(decodePunycode(codes[testRaw])).toBe(testRaw);

testRaw =
"为什么选择scriptcat-基于油猴的设计理念-完全兼容油猴脚本-提供更多丰富的api-脚本猫不仅兼容油猴脚本-还支持后台脚本运行-功能更强大-覆盖范围广-安装脚本管理器-寻找适合的脚本一键安装即可";

expect(codes[testRaw]).toBe(getPunycode(testRaw));
expect(decodePunycode(codes[testRaw])).toBe(testRaw);

testRaw =
"asmdksmklcmdsk-寻找-lmklamdkjqdenakjc-njkqelnuiconwerj-ksfnvcslkjdmc-jweasjkndjk-sandkjasnjxksakjkxnjaksn适合的-xj-kqwnjkxnqjas-nxsjkanxjksnjxansjk-cnajskn-cjkaxjksn-kxjasnjkxansjk-xnasjkxnksaj-cnjkdcnjksdncjsdnjcsdjkc-nmckj脚本";

expect(codes[testRaw]).toBe(getPunycode(testRaw));
expect(decodePunycode(codes[testRaw])).toBe(testRaw);
});
});
98 changes: 98 additions & 0 deletions src/pkg/utils/punycode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
const maxInt = 2147483647; // 2^31-1
const base = 36;
const tMin = 1;
const tMax = 26;
const skew = 38;
const damp = 700;
const initialBias = 72;
const initialN = 128;
const delimiter = "-";
const adaptD = base - tMin; // 35
const adaptL = ((adaptD + 1) * tMax) >>> 1; // 468

const ERR = {
OVERFLOW: "Overflow: input needs wider integers",
INVALID: "Invalid Punycode input",
} as const;

const error = (t: string) => {
throw new RangeError(t);
};

const floor = Math.floor;

const adapt = (delta: number, numPoints: number, firstTime: boolean) => {
delta = firstTime ? floor(delta / damp) : delta >>> 1;
delta += floor(delta / numPoints);
let k = 0;
for (; delta > adaptL; k += base) delta = floor(delta / adaptD);
return k + floor(((adaptD + 1) * delta) / (delta + skew));
};

/**
* Decodes Punycode (RFC 3492)
* npm package "punycode" is too large. We just need a simple and robust one.
* Punycode is case-insensitive; decodePunycode handle labels individually without dot split
*/
export const decodePunycode = (input: string) => {
input = input.toLowerCase();
input = input.startsWith("xn--") ? input.slice(4) : input;
if (!input || input.length > 251) error(ERR.INVALID);
const output: number[] = [];
const len = input.length;
let i = 0;
let n = initialN;
let bias = initialBias;

const k = input.lastIndexOf(delimiter);

let j = 0;
for (; j < k; ++j) {
const cp = input.codePointAt(j)!;
if (cp >= 0x80) error(ERR.INVALID);
output.push(cp);
}

if (j > 0) j++;

if (j >= len) error(ERR.INVALID);

while (j < len) {
const oldi = i;
let w = 1;

for (let k = base; ; k += base) {
if (j >= len) error(ERR.INVALID);
const cp = input.codePointAt(j++)!;

let digit = -1;
// 0-9 / A-Z / a-z
if (cp >= 0x30 && cp < 0x3a) digit = 26 + (cp - 0x30);
else if (cp >= 0x41 && cp < 0x5b) digit = cp - 0x41;
else if (cp >= 0x61 && cp < 0x7b) digit = cp - 0x61;
else error(ERR.INVALID);

i += digit * w;
if (i >= maxInt) error(ERR.OVERFLOW);

const t = k <= bias ? tMin : k >= bias + tMax ? tMax : k - bias;

if (digit < t) break;

const baseMinusT = base - t;
w *= baseMinusT;
if (w >= maxInt) error(ERR.OVERFLOW);
}

const out = output.length + 1;
bias = adapt(i - oldi, out, oldi === 0);
if (bias > 198) error(ERR.OVERFLOW); // 198 is the theoretical max for 251 bytes decoding to ~0x10ffff
n += floor(i / out);
if (n > 0x10ffff) error(ERR.OVERFLOW);
i %= out;

output.splice(i++, 0, n);
}

return String.fromCodePoint(...output);
};
Loading
Loading