From d4d4c9fffaf2c49984126b9aa74674f9e2758ff0 Mon Sep 17 00:00:00 2001 From: Elegy233 <82034747+wan0ge@users.noreply.github.com> Date: Sun, 26 Jul 2026 21:04:12 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=AE=8C=E5=96=84=E8=B7=A8=E5=AD=A3?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E4=B8=8E=E5=B9=B3=E5=8F=B0=E8=BF=87=E6=BB=A4?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=8C=E4=BF=AE=E5=A4=8D=E5=A4=87=E7=94=A8?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E7=8A=B6=E6=80=81=E5=8F=8A=E9=9B=86=E6=A0=87?= =?UTF-8?q?=E9=A2=98=E8=A7=A3=E6=9E=90=20(#392)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 跨季扩展阻塞与备用匹配 isMatched 状态修复 checkEpisodeSatisfied 指定平台无匹配数据时返回 false: 指定目标平台(如 qiyi)但搜索结果均不匹配该平台时,不再误判为满足。 新增 anyPlatformHadData 标记,无匹配平台时返回 false 以触发跨季扩展。 备用匹配成功后设置 isMatched: fallbackMatchAniAndEp 成功匹配剧集后 isMatched 未正确置为 true, 导致客户端收到匹配结果但标记为未匹配。 * fix: checkEpisodeSatisfied 优选平台无匹配时回退到不区分平台重查而非直接返回false 当 PLATFORM_ORDER 指定的平台与搜索结果源不匹配时(例如 PLATFORM_ORDER=qiyi 但搜索结果来自 renren), anyPlatformHadData 为 false,原先直接返回 false 会错误触发跨季扩展(S5~S10)和 spillover 匹配, 造成大量无谓 API 请求。 修复:anyPlatformHadData 为 false 且有 targetPlatform 时,以 targetPlatform=null 递归调用自身, 回退到 _any_ 模式重新查验全部可用数据,再依据集数是否满足决定是否触发扩展。 这同时正确覆盖两种场景: - 当前季集数不足(如 S1E31 仅 11 集)→ 正确触发扩展 - 当前季集数充足但平台不匹配(如 S4E3 在 renren 有 24 集且含 E3)→ 正确抑制扩展 * fix: matchAniAndEp与findCrossSeasonEpisodeMap中平台不匹配源时回退到无平台过滤 当PLATFORM_ORDER与搜索结果源不匹配时(例如PLATFORM_ORDER=renren但搜到iqiyi结果): 1. matchAniAndEp: findEpisodeByNumber因平台过滤丢弃所有集数返回null后, 新增回退:检查候选源是否匹配指定平台,不匹配则以platform=null重试。 这避免了bestRes为空而错误触发findCrossSeasonEpisodeMap spillover。 2. findCrossSeasonEpisodeMap: 平台不匹配时以currentSeason++ + continue 替代break,允许跨季映射在后续匹配季中继续查找而非直接退出。 保护措施:isPreferredAnime的原有回退逻辑保留作为最后一层兜底。 * fix: extractEpisodeNumberFromTitle 支持第X话和第X回格式 此前仅识别 /第(\d+)集/,导致 dandan 等源使用第31话格式的集标题 无法被提取集号,进而 findEpisodeByNumber 对所有分季 dandan 条目返回 null。 跨季扩展虽然正确拉取了 S3 dandan 条目的 E31(第31话 决裂),但 extractEpisodeNumberFromTitle 无法提取数字 31,导致匹配被跳过, 最终落在能识别第31集的 bahamut 不分季条目上。 修复将正则扩展为 /第(\d+)[集话回]/,同时覆盖: - 集:标准中文格式(如第31集) - 话:日式动画格式(如第31话) - 回:旧式分回格式(如第12回) 不匹配 期/章/卷,避免错配综艺/漫画/OVA分卷格式。 * fix: titleMatches 对无季号标题放宽季数守卫,避免不分季长剧被全部拦截 titleMatches 在 querySeason>1 时原先对无季号标题作 (null||1)!=querySeason 拦截,导致 iqiyi 搜索航海王 S2E62 时全部 16 个结果因标题中无季号被过滤, searchData 为空,匹配链路崩溃。 修复:仅当源标题明确含季号且与目标季不一致时才拦截,无季号的不分季长剧 交由上游源处理器(如 iqiyi 季数预过滤)决定是否保留。 影响分析: - 不分季长剧(querySeason>1):修复前被拦截 → 修复后通过 → 正确 - 分季剧S1标题(querySeason=2):修复前拦截 → 修复后仍拦截 → 不变 - 分季剧S2标题(querySeason=2):修复前通过 → 修复后仍通过 → 不变 - 分季剧S3标题(querySeason=2):修复前拦截 → 修复后仍拦截 → 不变 - querySeason=1或null:不受此分支影响,原有行为完全保持 --- danmu_api/apis/dandan-api.js | 26 +++++++++++++++++++++++--- danmu_api/utils/common-util.js | 9 +++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/danmu_api/apis/dandan-api.js b/danmu_api/apis/dandan-api.js index b68dc9c..dbfebdb 100644 --- a/danmu_api/apis/dandan-api.js +++ b/danmu_api/apis/dandan-api.js @@ -252,6 +252,7 @@ function checkEpisodeSatisfied(animesList, querySeason, queryEpisode, requestAni } let allSatisfied = true; + let anyPlatformHadData = false; for (const tPlat of targetPlatforms) { let isEpisodeSatisfied = false; @@ -296,8 +297,9 @@ function checkEpisodeSatisfied(animesList, querySeason, queryEpisode, requestAni } if (!platformHasData) { - continue; + continue; } + anyPlatformHadData = true; if (!isEpisodeSatisfied) { let totalValidEpisodes = 0; @@ -311,6 +313,11 @@ function checkEpisodeSatisfied(animesList, querySeason, queryEpisode, requestAni } } + // 优先平台无匹配数据时回退到不区分平台重新检查全部可用数据 + if (!anyPlatformHadData && targetPlatform) { + return checkEpisodeSatisfied(animesList, querySeason, queryEpisode, requestAnimeDetailsMap, null); + } + return allSatisfied; } @@ -1220,7 +1227,8 @@ function findCrossSeasonEpisodeMap(searchData, title, year, season, episode, pla if (absoluteMatch) { if (platform && getPlatformMatchScore(extractEpisodeTitle(absoluteMatch.episodeTitle), platform) === 0) { - break; + currentSeason++; + continue; } log("info", `[system] [spillover] 跨季溢出查找命中 (按绝对标题数字) -> 所在季:S${currentSeason} 集标题:${absoluteMatch.episodeTitle}`); bestRes = { @@ -1234,7 +1242,8 @@ function findCrossSeasonEpisodeMap(searchData, title, year, season, episode, pla if (currentTargetEpisode <= allEps.length) { const targetEp = allEps[currentTargetEpisode - 1]; if (platform && getPlatformMatchScore(extractEpisodeTitle(targetEp.episodeTitle), platform) === 0) { - break; + currentSeason++; + continue; } log("info", `[system] [spillover] 跨季溢出查找命中 (按相对排位计算) -> 所在季:S${currentSeason} 集标题:${targetEp.episodeTitle}`); bestRes = { @@ -1359,6 +1368,14 @@ async function matchAniAndEp(season, episode, year, searchData, title, req, plat // 匹配集数 matchedEpisode = findEpisodeByNumber(filteredEpisodes, episode, targetEpisode, platform); + // 当指定平台与候选动画源不匹配导致过滤后无匹配时,回退到不区分平台提取集数 + if (!matchedEpisode && platform) { + const actualAnimePlatform = extractPlatformFromTitle(anime.animeTitle) || anime.source; + if (getPlatformMatchScore(actualAnimePlatform, platform) === 0) { + matchedEpisode = findEpisodeByNumber(filteredEpisodes, episode, targetEpisode, null); + } + } + // 如果当前是用户的优选偏好,但由于平台配置限制导致未命中目标平台,则放宽条件无视平台限制提取集数 if (!matchedEpisode && isPreferredAnime) { log("info", `[system] [match] 优选剧集未命中目标平台 ${platform},放宽条件提取集数`); @@ -1745,6 +1762,9 @@ export async function matchAnime(url, req, clientIp) { resAnime = __ret.resAnime; // 跨季集数顺延映射的结果不更新 lastSearch,防止无关番剧污染 lastSelectMap 偏好记录 spilloverMatched = __ret.isSpillover; + if (resAnime) { + resData["isMatched"] = true; + } } } diff --git a/danmu_api/utils/common-util.js b/danmu_api/utils/common-util.js index 092c1d4..4798f4b 100644 --- a/danmu_api/utils/common-util.js +++ b/danmu_api/utils/common-util.js @@ -320,8 +320,9 @@ export function titleMatches(title, query, parsedSeason = null) { const titleSeason = getExplicitSeasonNumber(titleText); if (querySeason > 1) { - // 搜索指定续作(>1)时,标题必须明确包含该季度标识 - if ((titleSeason || 1) !== querySeason) return false; + // 搜索指定续作(>1)时,仅当源标题明确包含季号时才校验季号一致性 + // 源标题无季号的不分季长剧不应被此规则拦截,交由上游源处理器决定 + if (titleSeason !== null && titleSeason !== querySeason) return false; } else if (querySeason === 1) { // 搜索第1季时,拦截明确标明为其他季度(如第2季、第3季)的结果 if (titleSeason !== null && titleSeason !== 1) return false; @@ -452,8 +453,8 @@ export function extractSeasonNumberFromAnimeTitle(animeTitle) { export function extractEpisodeNumberFromTitle(episodeTitle) { if (!episodeTitle) return null; - // 匹配格式:第1集、第01集、第10集等 - const chineseMatch = episodeTitle.match(/第(\d+)集/); + // 匹配格式:第1集、第01集、第1话、第1回等 + const chineseMatch = episodeTitle.match(/第(\d+)[集话回]/); if (chineseMatch) { return parseInt(chineseMatch[1], 10); }