kt-template-online-api/src/modules/qqbot/plugins/bilibili-card/src/domain/bilibili-url-parser.ts

80 lines
2.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { BilibiliVideoReference } from './bilibili-card.types';
const BILIBILI_HOST = 'bilibili.com';
const B23_HOST = 'b23.tv';
const TRAILING_WRAPPERS = /[\s"'<>)\]}]+$/u;
const LEADING_WRAPPERS = /^[\s"'<>([{]+/u;
const BVID_PATTERN = /(?:^|\/)(BV[0-9A-Za-z]{10,})/;
const AID_PATTERN = /(?:^|\/)(?:av|AV)(\d+)(?:$|[/?#])/;
/**
* Removes QQ card wrappers and punctuation that often stick to copied URLs.
* @param candidate - Raw string fragment found in text or card JSON.
* @returns Cleaned URL candidate ready for `URL` parsing.
*/
export function cleanBilibiliUrlCandidate(candidate: string) {
return candidate
.replaceAll('&amp;', '&')
.replaceAll('&quot;', '"')
.replaceAll('&#34;', '"')
.replace(LEADING_WRAPPERS, '')
.replace(TRAILING_WRAPPERS, '')
.trim();
}
/**
* Checks whether a URL belongs to the Bilibili domains this plugin is allowed to parse.
* @param candidate - URL string collected from a QQ message or redirect result.
* @returns `true` when the host is Bilibili-owned or `b23.tv`.
*/
export function isAllowedBilibiliUrl(candidate: string) {
try {
const url = new URL(cleanBilibiliUrlCandidate(candidate));
const hostname = url.hostname.toLowerCase();
return (
(url.protocol === 'http:' || url.protocol === 'https:') &&
(hostname === B23_HOST ||
hostname === BILIBILI_HOST ||
hostname.endsWith(`.${BILIBILI_HOST}`))
);
} catch {
return false;
}
}
/**
* Extracts a Bilibili video identifier from a supported URL.
* @param candidate - Direct Bilibili video URL or short-link URL that already embeds BV/av.
* @returns Parsed video reference, or `null` when the URL is not a supported video URL.
*/
export function parseBilibiliVideoReference(
candidate: string,
): BilibiliVideoReference | null {
const cleaned = cleanBilibiliUrlCandidate(candidate);
if (!isAllowedBilibiliUrl(cleaned)) return null;
const url = new URL(cleaned);
const probe = `${url.pathname}${url.search}${url.hash}`;
const bvid = probe.match(BVID_PATTERN)?.[1];
if (bvid) {
return {
canonicalVideoId: bvid,
kind: 'bvid',
sourceUrl: cleaned,
value: bvid,
};
}
const aid = `${url.pathname}/`.match(AID_PATTERN)?.[1];
if (aid) {
return {
canonicalVideoId: `av${aid}`,
kind: 'aid',
sourceUrl: cleaned,
value: aid,
};
}
return null;
}