fix: 保留Bilibili链接HTML实体边界

This commit is contained in:
sunlei 2026-06-19 17:52:28 +08:00
parent 0782b1d639
commit ecea754cac
2 changed files with 51 additions and 2 deletions

View File

@ -4,7 +4,8 @@ import {
isAllowedBilibiliUrl,
} from './bilibili-url-parser';
const URL_PATTERN = /https?:\/\/[^\s<>"',;:!\]\)}]+/giu;
const URL_PATTERN = /https?:\/\/[^\s<>"',:!\]\)}]+/giu;
const HTML_ENTITY_BEFORE_SEMICOLON = /&(?:amp|quot|lt|gt|#34|#60|#62)$/iu;
const MAX_DEPTH = 7;
const MAX_NODES = 200;
const MAX_STRINGS = 200;
@ -31,7 +32,9 @@ export function extractBilibiliUrls(input: BilibiliUrlExtractionInput) {
for (const text of candidates) {
for (const rawUrl of text.match(URL_PATTERN) || []) {
const cleaned = cleanBilibiliUrlCandidate(rawUrl);
const cleaned = cleanBilibiliUrlCandidate(
trimNonEntitySemicolonTail(rawUrl),
);
if (!isAllowedBilibiliUrl(cleaned) || seen.has(cleaned)) continue;
seen.add(cleaned);
output.push(cleaned);
@ -42,6 +45,20 @@ export function extractBilibiliUrls(input: BilibiliUrlExtractionInput) {
return output;
}
/**
* Removes semicolon-delimited CQ tail text while keeping common HTML entities intact.
* @param rawUrl - Raw URL token collected by the extractor regex.
* @returns URL token that can still be decoded by `cleanBilibiliUrlCandidate`.
*/
function trimNonEntitySemicolonTail(rawUrl: string) {
for (let index = 0; index < rawUrl.length; index += 1) {
if (rawUrl[index] !== ';') continue;
if (HTML_ENTITY_BEFORE_SEMICOLON.test(rawUrl.slice(0, index))) continue;
return rawUrl.slice(0, index);
}
return rawUrl;
}
/**
* Collects string values that may contain links from text fields and nested QQ card objects.
* @param input - Extraction input built from normalized message state.

View File

@ -135,4 +135,36 @@ describe('Bilibili URL extractor', () => {
}),
).toEqual(['https://b23.tv/abc123']);
});
it('keeps html entity wrappers intact until URL cleanup runs', () => {
expect(
extractBilibiliUrls({
messageText: '',
rawEvent: {
message: [
{
data: {
data: '&lt;【https://www.bilibili.com/video/BV1xx411c7mD】&gt;',
},
type: 'xml',
},
],
},
rawMessage: '',
}),
).toEqual(['https://www.bilibili.com/video/BV1xx411c7mD']);
});
it('preserves ampersand query entities while removing CQ separators', () => {
expect(
extractBilibiliUrls({
messageText:
'[CQ:share,url=https://www.bilibili.com/video/BV1xx411c7mD?foo=1&amp;bar=2,title=视频]',
rawEvent: {},
rawMessage: '',
}),
).toEqual([
'https://www.bilibili.com/video/BV1xx411c7mD?foo=1&bar=2',
]);
});
});