feat: 添加Bilibili链接解析域逻辑
This commit is contained in:
parent
f8cd07e018
commit
7522b4c2b2
@ -0,0 +1,13 @@
|
||||
export type BilibiliVideoReference =
|
||||
| {
|
||||
canonicalVideoId: string;
|
||||
kind: 'bvid';
|
||||
sourceUrl: string;
|
||||
value: string;
|
||||
}
|
||||
| {
|
||||
canonicalVideoId: string;
|
||||
kind: 'aid';
|
||||
sourceUrl: string;
|
||||
value: string;
|
||||
};
|
||||
@ -0,0 +1,79 @@
|
||||
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('&', '&')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll('"', '"')
|
||||
.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;
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
import {
|
||||
cleanBilibiliUrlCandidate,
|
||||
isAllowedBilibiliUrl,
|
||||
parseBilibiliVideoReference,
|
||||
} from '../../../../../src/modules/qqbot/plugins/bilibili-card/src/domain/bilibili-url-parser';
|
||||
|
||||
describe('Bilibili URL parser', () => {
|
||||
it('parses BV video URLs while ignoring query, hash, and trailing punctuation', () => {
|
||||
const reference = parseBilibiliVideoReference(
|
||||
'https://www.bilibili.com/video/BV1xx411c7mD/?share_source=qq#reply。',
|
||||
);
|
||||
|
||||
expect(reference).toEqual({
|
||||
canonicalVideoId: 'BV1xx411c7mD',
|
||||
kind: 'bvid',
|
||||
sourceUrl:
|
||||
'https://www.bilibili.com/video/BV1xx411c7mD/?share_source=qq#reply',
|
||||
value: 'BV1xx411c7mD',
|
||||
});
|
||||
});
|
||||
|
||||
it('parses av video URLs from mobile Bilibili links', () => {
|
||||
expect(
|
||||
parseBilibiliVideoReference('https://m.bilibili.com/video/av170001'),
|
||||
).toMatchObject({
|
||||
canonicalVideoId: 'av170001',
|
||||
kind: 'aid',
|
||||
value: '170001',
|
||||
});
|
||||
});
|
||||
|
||||
it('allows only Bilibili and b23.tv hosts', () => {
|
||||
expect(isAllowedBilibiliUrl('https://b23.tv/abc123')).toBe(true);
|
||||
expect(isAllowedBilibiliUrl('https://space.bilibili.com/1')).toBe(true);
|
||||
expect(isAllowedBilibiliUrl('https://example.com/video/BV1xx411c7mD')).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('cleans card wrappers, html entities, and trailing brackets', () => {
|
||||
expect(
|
||||
cleanBilibiliUrlCandidate(
|
||||
'"https://www.bilibili.com/video/BV1xx411c7mD?p=1")',
|
||||
),
|
||||
).toBe('https://www.bilibili.com/video/BV1xx411c7mD?p=1');
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user