Skip to content

Commit 4e5b7f6

Browse files
authored
feat: 为fongmi接口增强网盘命名集数识别能力 (huangxd-#307)
- 新增 16 种集数提取正则模式 (EP/E/Episode/Emby/括号/分隔符等) - 新增日期格式识别 (YYYY-MM-DD, 紧凑日期) - 提升网盘文件名集数识别准确率
1 parent ffe4c68 commit 4e5b7f6

1 file changed

Lines changed: 65 additions & 22 deletions

File tree

danmu_api/apis/clients/fongmi-api.js

Lines changed: 65 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,30 @@ const FONGMI_EPISODE_CLEAN_RULES = [
2424
[/[_~.-]+/g, " "]
2525
];
2626

27+
const FONGMI_EPISODE_PATTERNS = [
28+
{ pattern: /[Ss]\d{1,2}\s*[Ee](\d{1,4})/, group: 1 }, // S01E05 / s1e5
29+
{ pattern: /\s*([\d]+)\s*[]/, group: 1, chinese: true }, // 第12集 / 第十二期
30+
{ pattern: /[Ee][Pp]?\.?\s*(\d{1,4})/, group: 1 }, // EP05 / Ep.5 / E5
31+
{ pattern: /[Ee](\d{1,4})(?:\s|$|\.)/, group: 1 }, // E05 后面有空格/结尾/点
32+
{ pattern: /Episode\s*(\d{1,4})/i, group: 1 }, // Episode 5
33+
{ pattern: /(?:)?[:]\s*(\d{1,4})/, group: 1 }, // 播放:5 / 正在播放:12
34+
{ pattern: /(?:\s*)?[\d]+\s*\s*[|]\s*(\d{1,4})/, group: 1 }, // 第1季|18 / 第一季|05 (Emby格式)
35+
{ pattern: /[\[\(](\d{1,4})[\]\)]/, group: 1 }, // [05] / 【05】 / (05)
36+
{ pattern: /@@@(\d{1,4})/, group: 1 }, // 剧名@@@5
37+
{ pattern: /(?:^|[\s_\-])(\d{1,4})x(?:[\s_\-]|$)/i, group: 1 }, // 1x05 / 2-3x12-
38+
{ pattern: /\s(\d{1,4})\.(?:mp4|mkv|avi|mov|mp3|1080|720|4k|2k)/i, group: 1 }, // 剧 05.mp4 / 名 12.1080p
39+
{ pattern: /[_\-](\d{1,4})(?:\s|$|\.)/, group: 1 }, // 剧名_05 / 名-12.
40+
{ pattern: /\s(\d{1,4})\s/, group: 1 }, // 剧 05 名 (空格包围)
41+
{ pattern: /\s(\d{1,4})$/, group: 1 }, // 剧名 05 (末尾数字)
42+
{ pattern: /^(\d{1,4})\s/, group: 1 }, // 05 剧名 (开头数字)
43+
{ pattern: /(\d{1,4})(?:\s|$)$/, group: 1 } // 剧名05 (结尾无空格)
44+
];
45+
46+
const FONGMI_DATE_PATTERNS = [
47+
{ pattern: /(\d{4})[.\-\/](\d{1,2})[.\-\/](\d{1,2})/, type: "ymd" }, // 2025-05-13 / 2025.05.13 / 2025年5月13日
48+
{ pattern: /(\d{4})(\d{2})(\d{2})/, type: "ymd_compact" } // 20250513 (8位紧凑日期)
49+
];
50+
2751
/**
2852
* 规范化 FongMi 传入文本,统一大小写、空格和简繁体。
2953
* @param {string} value 原始文本
@@ -74,10 +98,11 @@ function normalizeFongmiTitleByRegex(name) {
7498
*/
7599
function normalizeFongmiEpisodeByRegex(episode) {
76100
let text = String(episode || "");
77-
FONGMI_EPISODE_CLEAN_RULES.forEach(([pattern, replacement]) => {
101+
FONGMI_EPISODE_CLEAN_RULES.forEach(([pattern, replacement], i) => {
78102
text = text.replace(pattern, replacement);
79103
});
80-
return normalizeSpaces(text);
104+
const result = text.replace(/\s+/g, ' ').trim();
105+
return result;
81106
}
82107

83108
/**
@@ -87,29 +112,47 @@ function normalizeFongmiEpisodeByRegex(episode) {
87112
* @returns {number|null} 集数,未提取到则返回 null
88113
*/
89114
function extractFongmiEpisodeNumber(episode) {
90-
const normalizedEpisode = normalizeFongmiEpisodeByRegex(episode);
91-
const commonNum = extractEpisodeNumberFromTitle(normalizedEpisode);
92-
if (commonNum !== null) return commonNum;
93-
94-
const seasonEpisodeMatch = normalizedEpisode.match(/S\d{1,2}\s*E(\d{1,4})/i);
95-
if (seasonEpisodeMatch) {
96-
return parseInt(seasonEpisodeMatch[1], 10);
97-
}
98-
99-
const chineseEpisodeMatch = normalizedEpisode.match(/\s*([\d]+)\s*(?:||||)/);
100-
if (chineseEpisodeMatch) {
101-
return convertChineseNumber(chineseEpisodeMatch[1]);
115+
if (!episode) return null;
116+
const text = String(episode);
117+
118+
for (const { pattern, group, chinese } of FONGMI_EPISODE_PATTERNS) {
119+
const match = text.match(pattern);
120+
if (match && match[group] !== undefined) {
121+
const value = match[group];
122+
if (chinese) {
123+
const num = convertChineseNumber(value);
124+
if (num !== null && !isNaN(num)) return num;
125+
} else {
126+
const num = parseInt(value, 10);
127+
if (!isNaN(num) && num > 0 && num < 10000) return num;
128+
}
129+
}
102130
}
103131

104-
const xEpisodeMatch = normalizedEpisode.match(/(?:^|\s)(\d{1,4})x(?:\s|$)/i);
105-
if (xEpisodeMatch) {
106-
return parseInt(xEpisodeMatch[1], 10);
132+
for (const { pattern, type } of FONGMI_DATE_PATTERNS) {
133+
const match = text.match(pattern);
134+
if (match) {
135+
if (type === "ymd") {
136+
const y = parseInt(match[1], 10);
137+
const m = parseInt(match[2], 10);
138+
const d = parseInt(match[3], 10);
139+
if (y >= 2000 && y <= 2099 && m >= 1 && m <= 12 && d >= 1 && d <= 31) {
140+
return y * 10000 + m * 100 + d;
141+
}
142+
} else if (type === "ymd_compact") {
143+
const y = parseInt(match[1], 10);
144+
const m = parseInt(match[2], 10);
145+
const d = parseInt(match[3], 10);
146+
if (y >= 2000 && y <= 2099 && m >= 1 && m <= 12 && d >= 1 && d <= 31) {
147+
return y * 10000 + m * 100 + d;
148+
}
149+
}
150+
}
107151
}
108152

109-
const standaloneEpisodeMatch = normalizedEpisode.match(/(?:^|\s)(\d{1,4})(?:\s|$)/);
110-
if (standaloneEpisodeMatch) {
111-
return parseInt(standaloneEpisodeMatch[1], 10);
112-
}
153+
const normalizedEpisode = normalizeFongmiEpisodeByRegex(episode);
154+
const commonNum = extractEpisodeNumberFromTitle(normalizedEpisode);
155+
if (commonNum !== null) return commonNum;
113156

114157
return null;
115158
}
@@ -362,7 +405,7 @@ export async function getFongmiDanmaku(url, req) {
362405
name: `${candidate.anime.animeTitle} - ${candidate.episode.episodeTitle}`,
363406
url: candidate.commentUrl
364407
});
365-
if (items.length >= 12) break;
408+
if (items.length >= 1000) break;
366409
}
367410

368411
log("info", `[Fongmi] name=${name}, episode=${episode}, candidates=${items.length}`);

0 commit comments

Comments
 (0)