96 lines
2.4 KiB
TypeScript
96 lines
2.4 KiB
TypeScript
interface FeedTargetInput {
|
|
feed_id?: string;
|
|
xsec_token?: string;
|
|
url?: string;
|
|
}
|
|
|
|
interface UserTargetInput {
|
|
user_id?: string;
|
|
xsec_token?: string;
|
|
url?: string;
|
|
}
|
|
|
|
interface FeedTargetResolved {
|
|
feedId: string;
|
|
xsecToken: string;
|
|
}
|
|
|
|
interface UserTargetResolved {
|
|
userId: string;
|
|
xsecToken: string;
|
|
}
|
|
|
|
function parseXhsUrl(rawUrl: string): URL {
|
|
const trimmed = rawUrl.trim();
|
|
if (!trimmed) {
|
|
throw new Error('url cannot be empty');
|
|
}
|
|
|
|
if (/^https?:\/\//i.test(trimmed)) {
|
|
return new URL(trimmed);
|
|
}
|
|
|
|
if (trimmed.startsWith('/')) {
|
|
return new URL(`https://www.xiaohongshu.com${trimmed}`);
|
|
}
|
|
|
|
return new URL(`https://${trimmed}`);
|
|
}
|
|
|
|
function extractFeedIdFromPath(pathname: string): string | undefined {
|
|
const patterns = [
|
|
/\/explore\/([a-zA-Z0-9_-]+)/,
|
|
/\/discovery\/item\/([a-zA-Z0-9_-]+)/,
|
|
/\/note\/([a-zA-Z0-9_-]+)/,
|
|
];
|
|
|
|
for (const pattern of patterns) {
|
|
const match = pathname.match(pattern);
|
|
if (match?.[1]) return match[1];
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function extractUserIdFromPath(pathname: string): string | undefined {
|
|
const match = pathname.match(/\/user\/profile\/([a-zA-Z0-9_-]+)/);
|
|
return match?.[1];
|
|
}
|
|
|
|
function extractXsecToken(url: URL): string | undefined {
|
|
return url.searchParams.get('xsec_token') ?? url.searchParams.get('xsecToken') ?? undefined;
|
|
}
|
|
|
|
export function resolveFeedTarget(input: FeedTargetInput): FeedTargetResolved {
|
|
let feedId = input.feed_id?.trim();
|
|
let xsecToken = input.xsec_token?.trim();
|
|
|
|
if (input.url) {
|
|
const parsed = parseXhsUrl(input.url);
|
|
feedId = feedId || extractFeedIdFromPath(parsed.pathname);
|
|
xsecToken = xsecToken || extractXsecToken(parsed);
|
|
}
|
|
|
|
if (!feedId || !xsecToken) {
|
|
throw new Error('xhs_get_feed_detail requires either url with feed_id/xsec_token, or both feed_id and xsec_token');
|
|
}
|
|
|
|
return { feedId, xsecToken };
|
|
}
|
|
|
|
export function resolveUserTarget(input: UserTargetInput): UserTargetResolved {
|
|
let userId = input.user_id?.trim();
|
|
let xsecToken = input.xsec_token?.trim();
|
|
|
|
if (input.url) {
|
|
const parsed = parseXhsUrl(input.url);
|
|
userId = userId || extractUserIdFromPath(parsed.pathname);
|
|
xsecToken = xsecToken || extractXsecToken(parsed);
|
|
}
|
|
|
|
if (!userId || !xsecToken) {
|
|
throw new Error('xhs_get_user_profile requires either url with user_id/xsec_token, or both user_id and xsec_token');
|
|
}
|
|
|
|
return { userId, xsecToken };
|
|
}
|