fix: fongmi 接口自动匹配增加季数比对,修复跨季同号集错配 (#471)
* fix: fongmi 接口自动匹配增加季数比对,修复跨季同号集错配 episode 文本中的季号此前被丢弃(S02E05 只取 E 后集数),第一季/第二季同号集得分完全相同,自动首条由源返回顺序决定:线上实测 庆余年 S01E01 与 S02E01 返回完全一致、S01E05 与 S02E05 亦逐字节相同。现从分集文本提取显式季号,与候选剧名的季标注比对:同季 +5000、跨季 -12000;任一侧无季标注不调整,电影/综艺日期场景保持原行为。 * Update worker.test.js --------- Co-authored-by: xiangyang <xiangyang2001@outlook.com> Co-authored-by: huangxd- <649924757@qq.com>
This commit is contained in:
2 files changed
+72
-2
No files matched your search
@@ -2,7 +2,7 @@ import { globals } from "../../configs/globals.js";
|
||||
import { jsonResponse } from "../../utils/http-util.js";
|
||||
import { log } from "../../utils/log-util.js";
|
||||
import { simplized } from "../../utils/zh-util.js";
|
||||
import { convertChineseNumber, extractEpisodeTitle, extractEpisodeNumberFromTitle, normalizeSpaces } from "../../utils/common-util.js";
|
||||
import { convertChineseNumber, extractEpisodeTitle, extractEpisodeNumberFromTitle, extractSeasonNumberFromAnimeTitle, getExplicitSeasonNumber, normalizeSpaces } from "../../utils/common-util.js";
|
||||
import { filterSameEpisodeTitle, getBangumiDataForMatch, searchAnime } from "../dandan-api.js";
|
||||
|
||||
// =====================
|
||||
@@ -157,6 +157,26 @@ function extractFongmiEpisodeNumber(episode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 FongMi 集数文本中提取显式季号。
|
||||
* 支持 S02E05 / 第2季第3集 / Season 2 / Part 2 / 2x05 等写法。
|
||||
* 仅接受 1~50 的合理季号,避免把综艺日期"第20180512期"等误判为季。
|
||||
* @param {string} episode 原始集数文本
|
||||
* @returns {number|null} 季号,无法识别时返回 null
|
||||
*/
|
||||
function extractFongmiSeasonNumber(episode) {
|
||||
if (!episode) return null;
|
||||
const text = String(episode);
|
||||
const season = getExplicitSeasonNumber(text);
|
||||
if (season !== null && Number.isFinite(season) && season >= 1 && season <= 50) return season;
|
||||
const match = text.match(/(?:^|[\s_\-])(\d{1,2})x(\d{1,4})(?:$|[\s_\-.])/i);
|
||||
if (match) {
|
||||
const seasonNum = parseInt(match[1], 10);
|
||||
if (seasonNum >= 1 && seasonNum <= 50) return seasonNum;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 FongMi 标题生成搜索关键词列表。
|
||||
* 先保留原始标题,再追加正则清洗后的标题作为回退搜索词。
|
||||
@@ -276,7 +296,7 @@ async function parseFongmiRequestParams(url, req) {
|
||||
|
||||
/**
|
||||
* 计算单个候选分集与目标分集的匹配得分。
|
||||
* 当前综合标题、集数、日期做排序,后续可以继续扩展更多维度。
|
||||
* 当前综合标题、季数、集数、日期做排序,后续可以继续扩展更多维度。
|
||||
* @param {Object} anime 候选剧集对象
|
||||
* @param {Object} episode 候选分集对象
|
||||
* @param {string} targetEpisode FongMi 传入的分集文本
|
||||
@@ -305,6 +325,15 @@ function scoreFongmiEpisodeMatch(anime, episode, targetEpisode, index) {
|
||||
if (targetNum !== null && episodeNum !== null && targetNum === episodeNum) score += 7000;
|
||||
if (targetNum !== null && Number.isFinite(episodeIndexNum) && targetNum === episodeIndexNum) score += 4000;
|
||||
|
||||
// 季数比对:目标与候选都带显式季号时严格区分(如"剧名 S02E05"应优先命中"第二季"条目)。
|
||||
// 集数加分对跨季同号集完全同分,胜负此前只由源返回顺序决定;这里同季加分、跨季重罚,
|
||||
// 保证跨季候选排到任何同季候选之后。任一侧无季标注则不调整,保持原有行为。
|
||||
const targetSeason = extractFongmiSeasonNumber(targetEpisode);
|
||||
const animeSeason = extractSeasonNumberFromAnimeTitle(anime?.animeTitle || "").season;
|
||||
if (targetSeason !== null && animeSeason !== null) {
|
||||
score += targetSeason === animeSeason ? 5000 : -12000;
|
||||
}
|
||||
|
||||
const targetDate = extractDateDigits(normalizedTargetEpisode);
|
||||
const episodeDate = extractDateDigits(episodeTitle);
|
||||
if (targetDate && episodeDate && targetDate === episodeDate) score += 9000;
|
||||
@@ -418,3 +447,5 @@ export async function getFongmiDanmaku(url, req) {
|
||||
log("info", `[system] [fongmi] name=${name}, episode=${episode}, candidates=${items.length}`);
|
||||
return jsonResponse(items, 200);
|
||||
}
|
||||
|
||||
export { extractFongmiEpisodeNumber, extractFongmiSeasonNumber, scoreFongmiEpisodeMatch };
|
||||
@@ -45,6 +45,7 @@ import { convertToDanmakuJson, handleDanmusLike, splitBlockedWords, parseBlocked
|
||||
import { Segment, SegmentListResponse } from "./models/dandan-model.js"
|
||||
import { initBangumiData, searchBangumiData, clearBangumiDataCache, dedupeBangumiSearchResults } from "./utils/bangumi-data-util.js";
|
||||
import { generateNipaplaySignature, parseNipaplayRelatedLinks, resolveNipaplayLink, applyShiftToDanmu } from "./utils/nipaplay-util.js";
|
||||
import { extractFongmiSeasonNumber, scoreFongmiEpisodeMatch } from "./apis/clients/fongmi-api.js";
|
||||
import { localDanmuJsContent } from './ui/js/localdanmu.js';
|
||||
import { buildLocalDanmuResourceKey, groupLocalDanmuResources, parseLocalDanmu, normalizeLocalSeason } from './utils/local-danmu-parser.js';
|
||||
import { handleLocalDanmuUpload, handleLocalDanmuList, handleLocalDanmuDelete, handleLocalDanmuGet } from './apis/local-danmu-api.js';
|
||||
@@ -3124,9 +3125,47 @@ test('worker.js API endpoints', async (t) => {
|
||||
// assert.strictEqual(applyShiftToDanmu(null, 5), null, '空对象直接返回');
|
||||
// });
|
||||
|
||||
test('fongmi-api season aware scoring', () => {
|
||||
// 季号提取: SxxExx / 第x季 / Season N / 2x05; 综艺日期与纯集数不误判
|
||||
assert.equal(extractFongmiSeasonNumber('人生切割术 S02E05'), 2);
|
||||
assert.equal(extractFongmiSeasonNumber('Show.S02.E05.2160p.WEB-DL.mkv'), 2);
|
||||
assert.equal(extractFongmiSeasonNumber('庆余年 第2季第03集'), 2);
|
||||
assert.equal(extractFongmiSeasonNumber('Show Season 3 EP01'), 3);
|
||||
assert.equal(extractFongmiSeasonNumber('剧名 2x05'), 2);
|
||||
assert.equal(extractFongmiSeasonNumber('1920x1080'), null);
|
||||
assert.equal(extractFongmiSeasonNumber('凡人修仙传 第01集'), null);
|
||||
assert.equal(extractFongmiSeasonNumber('奔跑吧 第20180512期'), null);
|
||||
assert.equal(extractFongmiSeasonNumber(''), null);
|
||||
|
||||
const mk = (animeTitle, episodeTitle, index) => ({ anime: { animeTitle }, episode: { episodeTitle }, index });
|
||||
const scoreOf = (c, target) => scoreFongmiEpisodeMatch(c.anime, c.episode, target, c.index);
|
||||
|
||||
// 跨季同号集: 集数加分对第一/二季完全同分(11196), 修复后第二季必须稳定胜出, 不再由源返回顺序决定
|
||||
const targetS2 = '人生切割术 S02E05';
|
||||
const s2e5 = mk('人生切割术 第二季(2025)【电视剧】from renren', '【renren】 第05集', 4);
|
||||
const s1e5 = mk('人生切割术 第一季(2022)【电视剧】from renren', '【renren】 第05集', 4);
|
||||
assert.equal(scoreOf(s2e5, targetS2), 196 + 7000 + 4000 + 5000);
|
||||
assert.equal(scoreOf(s1e5, targetS2), 196 + 7000 + 4000 - 12000);
|
||||
// 候选枚举顺序翻转也不影响自动首条
|
||||
for (const ordered of [[s1e5, s2e5], [s2e5, s1e5]]) {
|
||||
const best = ordered.map(c => ({ ...c, score: scoreOf(c, targetS2) }))
|
||||
.sort((a, b) => b.score - a.score)[0];
|
||||
assert.ok(best.anime.animeTitle.includes('第二季'), 'S02E05 自动首条必须是第二季');
|
||||
}
|
||||
|
||||
// 目标 S01 时同样必须回到第一季
|
||||
const best = [s1e5, s2e5].map(c => ({ ...c, score: scoreOf(c, '人生切割术 S01E05') }))
|
||||
.sort((a, b) => b.score - a.score)[0];
|
||||
assert.ok(best.anime.animeTitle.includes('第一季'), 'S01E05 自动首条必须是第一季');
|
||||
|
||||
// 目标带季但候选剧名无季标注: 不调整, 保持原有行为
|
||||
const anon = mk('人生切割术(2022)【韩剧】from hanjutv', '【hanjutv】 第5集', 4);
|
||||
assert.equal(scoreOf(anon, targetS2), 196 + 7000 + 4000);
|
||||
|
||||
// 目标无季标注: 完全不受影响(向后兼容); 文本包含加分(+4500)为原有行为
|
||||
const plain = mk('凡人修仙传', '第05集', 4);
|
||||
assert.equal(scoreOf(plain, '凡人修仙传 第05集'), 196 + 7000 + 4000 + 4500);
|
||||
});
|
||||
|
||||
const comment = '标题警告‼️ 中文弹幕 😀 \uFFFD';
|
||||
const json = JSON.stringify({ count: 1, comments: [{ p: '1.00,1,16777215,[qiyi]', m: comment }] }, null, 2);
|
||||
|
||||
Reference in new issue
Block a user