feat(pugongying): 支持小红书短链接输入

通过 GM_xmlhttpRequest HEAD 请求跟踪短链接重定向,
从 finalUrl 中提取达人 ID。支持 xhslink.com 域名。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
wxs 2026-03-13 23:46:06 +08:00
parent 465e1f7746
commit 99d16f1f70

View File

@ -6,6 +6,7 @@
// @match https://pgy.xiaohongshu.com/* // @match https://pgy.xiaohongshu.com/*
// @grant GM_xmlhttpRequest // @grant GM_xmlhttpRequest
// @connect api.internal.intelligrow.cn // @connect api.internal.intelligrow.cn
// @connect xhslink.com
// @require https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js // @require https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js
// ==/UserScript== // ==/UserScript==
@ -235,27 +236,28 @@
return baseTarget; return baseTarget;
} }
function extractBloggerId(value) { function resolveShortUrl(url) {
const raw = normalizeScalar(value); return new Promise((resolve) => {
if (!raw) { if (typeof GM_xmlhttpRequest !== "function") {
return ""; resolve(url);
return;
}
GM_xmlhttpRequest({
method: "HEAD",
url,
onload(res) {
resolve(res.finalUrl || url);
},
onerror() {
resolve(url);
},
});
});
} }
if (/^[0-9a-f]{24}$/i.test(raw)) { const SHORT_LINK_HOSTS = ["xhslink.com"];
return raw;
}
if (!/^https?:\/\//i.test(raw)) {
return "";
}
let parsedUrl;
try {
parsedUrl = new URL(raw);
} catch (error) {
return "";
}
function extractIdFromUrl(parsedUrl) {
const queryCandidates = ["id", "user_id", "userId", "bloggerId", "creatorId"]; const queryCandidates = ["id", "user_id", "userId", "bloggerId", "creatorId"];
for (const key of queryCandidates) { for (const key of queryCandidates) {
const queryValue = parsedUrl.searchParams.get(key); const queryValue = parsedUrl.searchParams.get(key);
@ -279,7 +281,45 @@
return ""; return "";
} }
function parseCreatorInputs(rawInput) { async function extractBloggerId(value) {
const raw = normalizeScalar(value);
if (!raw) {
return "";
}
if (/^[0-9a-f]{24}$/i.test(raw)) {
return raw;
}
if (!/^https?:\/\//i.test(raw)) {
return "";
}
let parsedUrl;
try {
parsedUrl = new URL(raw);
} catch (error) {
return "";
}
const directId = extractIdFromUrl(parsedUrl);
if (directId) {
return directId;
}
if (SHORT_LINK_HOSTS.some((h) => parsedUrl.hostname.endsWith(h))) {
const realUrl = await resolveShortUrl(raw);
try {
return extractIdFromUrl(new URL(realUrl));
} catch (error) {
return "";
}
}
return "";
}
async function parseCreatorInputs(rawInput) {
const values = normalizeScalar(rawInput) const values = normalizeScalar(rawInput)
.split(/[\n,\s]+/) .split(/[\n,\s]+/)
.map((item) => item.trim()) .map((item) => item.trim())
@ -288,8 +328,8 @@
const ids = []; const ids = [];
const seen = new Set(); const seen = new Set();
for (const value of values) { const resolved = await Promise.all(values.map((v) => extractBloggerId(v)));
const id = extractBloggerId(value); for (const id of resolved) {
if (!id || seen.has(id)) { if (!id || seen.has(id)) {
continue; continue;
} }
@ -508,7 +548,7 @@
return { return {
async preview(rawInput, onProgress) { async preview(rawInput, onProgress) {
const ids = parseCreatorInputs(rawInput); const ids = await parseCreatorInputs(rawInput);
if (!ids.length) { if (!ids.length) {
throw new Error("请输入至少一个有效的达人主页链接或达人 ID。"); throw new Error("请输入至少一个有效的达人主页链接或达人 ID。");
} }