Skip to content

Commit 02e5322

Browse files
authored
Merge pull request #9 from jkfujr/master
优化视频av/bv号去重功能, 将一个消息内同时存在的相同视频的bv/av号去重
2 parents 6ffdcd0 + 9918808 commit 02e5322

File tree

3 files changed

+151
-9
lines changed

3 files changed

+151
-9
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,6 @@ tsconfig.tsbuildinfo
1616
*.ntvs*
1717
*.njsproj
1818
*.sln
19+
20+
# syncthing临时文件
21+
@eaDir

src/bv_av_converter.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// BV/AV互转算法常量
2+
// 参考 https://github.com/TGSAN/bv2av.js/blob/master/bv2av.js
3+
const TABLE = 'FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf';
4+
const MAX_AVID = 1n << 51n;
5+
const BASE = 58n;
6+
const BVID_LEN = 12n;
7+
const XOR = 23442827791579n;
8+
const MASK = 2251799813685247n;
9+
10+
// 构建字符索引
11+
const charIndex: Record<string, number> = {};
12+
for (let i = 0; i < Number(BASE); i++) {
13+
charIndex[TABLE[i]] = i;
14+
}
15+
16+
/**
17+
* BV号转AV号
18+
* @param bvid BV号 (如: BVfx411c7Z8)
19+
* @returns AV号 (如: av114514)
20+
*/
21+
export function bvToAv(bvid: string): string {
22+
if (!bvid || typeof bvid !== 'string') {
23+
throw new Error('Invalid BV ID');
24+
}
25+
26+
// 移除可能的前缀并验证格式
27+
const cleanBvid = bvid.replace(/^(bv|BV)/i, '');
28+
if (cleanBvid.length !== 10) {
29+
throw new Error('Invalid BV ID format');
30+
}
31+
32+
const fullBvid = 'BV' + cleanBvid;
33+
const chars = fullBvid.split('');
34+
35+
// 交换字符位置
36+
[chars[3], chars[9]] = [chars[9], chars[3]];
37+
[chars[4], chars[7]] = [chars[7], chars[4]];
38+
39+
// 计算av号
40+
let temp = 0n;
41+
for (const char of chars.slice(3)) {
42+
const idx = charIndex[char];
43+
if (idx === undefined) {
44+
throw new Error('Invalid character in BV ID');
45+
}
46+
temp = temp * BASE + BigInt(idx);
47+
}
48+
49+
const avid = (temp & MASK) ^ XOR;
50+
return `av${avid}`;
51+
}
52+
53+
/**
54+
* AV号转BV号
55+
* @param avid AV号 (如: av114514 或 114514)
56+
* @returns BV号 (如: BVfx411c7Z8)
57+
*/
58+
export function avToBv(avid: string | number): string {
59+
let cleanAvid: string;
60+
61+
if (typeof avid === 'string') {
62+
cleanAvid = avid.replace(/^av/i, '');
63+
} else if (typeof avid === 'number') {
64+
cleanAvid = avid.toString();
65+
} else {
66+
throw new Error('Invalid AV ID');
67+
}
68+
69+
const avidBigInt = BigInt(cleanAvid);
70+
if (avidBigInt <= 0n || avidBigInt >= MAX_AVID) {
71+
throw new Error('AV ID out of range');
72+
}
73+
74+
const result = ['B', 'V', '1', '', '', '', '', '', '', '', '', ''];
75+
let idx = BVID_LEN - 1n;
76+
let temp = (MAX_AVID | avidBigInt) ^ XOR;
77+
78+
while (temp !== 0n) {
79+
result[Number(idx)] = TABLE[Number(temp % BASE)];
80+
temp /= BASE;
81+
idx -= 1n;
82+
}
83+
84+
// 交换字符位置
85+
[result[3], result[9]] = [result[9], result[3]];
86+
[result[4], result[7]] = [result[7], result[4]];
87+
88+
return result.join('');
89+
}
90+
91+
/**
92+
* 标准化视频ID为AV号格式,用于去重比较
93+
* @param videoId 视频ID (BV号或AV号)
94+
* @returns 标准化的AV号
95+
*/
96+
export function normalizeVideoId(videoId: string): string {
97+
if (!videoId || typeof videoId !== 'string') {
98+
return videoId;
99+
}
100+
101+
try {
102+
// 如果是BV号,转换为AV号
103+
if (/^bv/i.test(videoId)) {
104+
return bvToAv(videoId);
105+
}
106+
107+
// 如果是AV号,确保格式统一
108+
if (/^av/i.test(videoId)) {
109+
const cleanAv = videoId.replace(/^av/i, '');
110+
return `av${cleanAv}`;
111+
}
112+
113+
// 如果是纯数字,当作AV号处理
114+
if (/^\d+$/.test(videoId)) {
115+
return `av${videoId}`;
116+
}
117+
118+
return videoId;
119+
} catch (error) {
120+
// 转换失败时返回原值
121+
return videoId;
122+
}
123+
}

src/link_parser.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
import { Links, logger, runtime } from ".";
22
import { get_redir_url, puppeteer_get_redir_url } from "./api/bili/short";
3+
import { normalizeVideoId } from "./bv_av_converter";
4+
5+
/**
6+
* 去重函数 (BV/AV号互转去重)
7+
* @param links 链接数组
8+
* @returns 去重后的链接数组
9+
*/
10+
function deduplicateLinks(links: Links[]): Links[] {
11+
return links.filter((item, index, self) => {
12+
// 对于视频类型,使用标准化id进行去重
13+
if (item.type === "Video") {
14+
const normalizedId = normalizeVideoId(item.id);
15+
return index === self.findIndex((t) =>
16+
t.type === item.type && normalizeVideoId(t.id) === normalizedId
17+
);
18+
}
19+
// 其他类型使用原有去重逻辑
20+
return index === self.findIndex((t) =>
21+
t.type === item.type && t.id === item.id
22+
);
23+
});
24+
}
325

426
/**
527
* 类型翻译器
@@ -198,11 +220,8 @@ export function link_parser(content: string): Links[] {
198220
}
199221
});
200222

201-
// 去重链接数组
202-
const unique = results.filter(
203-
(item, index, self) =>
204-
index === self.findIndex((t) => t.type === item.type && t.id === item.id),
205-
);
223+
// 去重
224+
const unique = deduplicateLinks(results);
206225

207226
logger.debug("Links: ", unique);
208227
return unique;
@@ -231,10 +250,7 @@ export async function short_link_parser(links: Links[]): Promise<Links[]> {
231250
}
232251

233252
// 去重
234-
const unique = result.filter(
235-
(item, index, self) =>
236-
index === self.findIndex((t) => t.type === item.type && t.id === item.id),
237-
);
253+
const unique = deduplicateLinks(result);
238254

239255
return unique;
240256
}

0 commit comments

Comments
 (0)