放宽合并工具3D2D的处理,增强API路径前缀智能处理逻辑(适配danmaku-anywhere项目请求),修复链接解析错误占位符字符串 (#254)
* dandan.js:从tags中提取3D/2D追加到类型 * merge-util.js:放宽没有3D2D标识时的处理 * worker.js:增强API路径前缀智能处理逻辑,适配Danmaku Anywhere项目仅/v2/的请求 旧逻辑面对http://127.0.0.1:9321/********/v2/search/anime会错误补全为http://127.0.0.1:9321/********/api/v2/v2/search/anime * dandan-api.js:修复了当请求直链 URL 时,API 错误返回占位符字符串 这个问题导致了danmaku-anywhere项目在解析图片时报错,详情:https://github.com/Mr-Quin/danmaku-anywhere/issues/343
This commit is contained in:
1 parent
e74749b3f9
commit
57987959c2
4 files changed
+70
-45
No files matched your search
@@ -251,13 +251,13 @@ export async function searchAnime(url, preferAnimeId = null, preferSource = null
|
||||
const urlRegex = /^(https?:\/\/)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,6}(:\d+)?(\/[^\s]*)?$/;
|
||||
if (urlRegex.test(queryTitle)) {
|
||||
const tmpAnime = Anime.fromJson({
|
||||
"animeId": 111,
|
||||
"bangumiId": "string",
|
||||
"animeId": 0,
|
||||
"bangumiId": "0",
|
||||
"animeTitle": queryTitle,
|
||||
"type": "type",
|
||||
"typeDescription": "string",
|
||||
"imageUrl": "string",
|
||||
"startDate": "2025-08-08T13:25:11.189Z",
|
||||
"type": "",
|
||||
"typeDescription": "链接解析",
|
||||
"imageUrl": "",
|
||||
"startDate": "",
|
||||
"episodeCount": 1,
|
||||
"rating": 0,
|
||||
"isFavorited": true
|
||||
|
||||
+27
-12
@@ -27,7 +27,7 @@ const DandanUserAgent = `LogVar Danmu API/${globals.version}`
|
||||
// 获取弹弹play弹幕
|
||||
// =====================
|
||||
export default class DandanSource extends BaseSource {
|
||||
|
||||
|
||||
/**
|
||||
* 搜索动画条目
|
||||
* 包含常规搜索、TMDB 日语原名搜索,以及去除季度信息后的降级搜索策略
|
||||
@@ -120,7 +120,7 @@ export default class DandanSource extends BaseSource {
|
||||
}
|
||||
|
||||
const animes = resp.data.animes;
|
||||
|
||||
|
||||
// 标记 TMDB 来源,供后续处理环节识别以跳过常规标题匹配
|
||||
for (const anime of animes) {
|
||||
anime.isTmdbSource = true;
|
||||
@@ -161,7 +161,7 @@ export default class DandanSource extends BaseSource {
|
||||
// 当搜索无结果且包含季度信息时,尝试剥离季度信息后重新搜索
|
||||
if (!isFallback) {
|
||||
const strippedKeyword = keyword.replace(/(?:第\s*[0-9一二三四五六七八九十百千万]+\s*[季期部])|(?:S(?:eason)?\s*\d+)|(?:Part\s*\d+)/gi, '').trim();
|
||||
|
||||
|
||||
if (strippedKeyword && strippedKeyword !== keyword) {
|
||||
log("info", `[Dandan] 尝试去除季度信息进行降级搜索: ${strippedKeyword}`);
|
||||
return await this.search(strippedKeyword, true);
|
||||
@@ -203,10 +203,10 @@ export default class DandanSource extends BaseSource {
|
||||
}
|
||||
|
||||
const bangumiData = resp.data.bangumi;
|
||||
|
||||
|
||||
// 提取剧集列表,确保它是数组
|
||||
const episodes = Array.isArray(bangumiData.episodes) ? bangumiData.episodes : [];
|
||||
|
||||
|
||||
// 提取标题别名列表
|
||||
// 数据源格式: [{"language":"主标题","title":"雨天遇见狸"}, ...]
|
||||
const titles = Array.isArray(bangumiData.titles) ? bangumiData.titles.map(t => t.title) : [];
|
||||
@@ -216,7 +216,22 @@ export default class DandanSource extends BaseSource {
|
||||
|
||||
// 提取番剧类型信息,用于相关作品无法从搜索接口获取该字段时的数据补全
|
||||
const type = bangumiData.type || null;
|
||||
const typeDescription = bangumiData.typeDescription || null;
|
||||
let typeDescription = bangumiData.typeDescription || null;
|
||||
|
||||
// 识别 3D 与 2D 标签并追加至类型描述
|
||||
let is3D = false;
|
||||
let is2D = false;
|
||||
if (bangumiData.tags && Array.isArray(bangumiData.tags)) {
|
||||
bangumiData.tags.forEach(tag => {
|
||||
if (tag.name && tag.name.toUpperCase().includes('3D')) is3D = true;
|
||||
if (tag.name && tag.name.toUpperCase().includes('2D')) is2D = true;
|
||||
});
|
||||
}
|
||||
if (is3D) {
|
||||
typeDescription = "3D" + (typeDescription || "");
|
||||
} else if (is2D) {
|
||||
typeDescription = "2D" + (typeDescription || "");
|
||||
}
|
||||
|
||||
// 提取封面图片 URL,用于 episodes 接口返回结果缺少 imageUrl 时的数据补全
|
||||
const imageUrl = bangumiData.imageUrl || null;
|
||||
@@ -447,12 +462,12 @@ export default class DandanSource extends BaseSource {
|
||||
const url = rel.url;
|
||||
const shift = rel.shift || 0;
|
||||
const sourceInfo = this.parseRelatedUrl(url);
|
||||
|
||||
|
||||
if (!sourceInfo) continue;
|
||||
|
||||
const { sourceName } = sourceInfo;
|
||||
const coreUrl = getCoreIdentifier(url, sourceName);
|
||||
|
||||
|
||||
// 构建唯一键存储偏移量,包含平台与核心标识,防止同平台多链接导致数据覆盖
|
||||
relatedShifts[`${sourceName}:${coreUrl}`] = shift;
|
||||
|
||||
@@ -472,7 +487,7 @@ export default class DandanSource extends BaseSource {
|
||||
if (firstColonIndex === -1) return false;
|
||||
const mSource = part.substring(0, firstColonIndex);
|
||||
const mId = part.substring(firstColonIndex + 1);
|
||||
|
||||
|
||||
// 来源标识必须一致
|
||||
if (mSource !== sourceName) return false;
|
||||
|
||||
@@ -505,7 +520,7 @@ export default class DandanSource extends BaseSource {
|
||||
allDanmus = allDanmus.concat(extra);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 汇总日志:仅在产生实际拉取任务时,输出拉取总数与原生基础数据
|
||||
const totalCount = allDanmus.length;
|
||||
const dandanCount = stats['dandan'] || 0;
|
||||
@@ -562,7 +577,7 @@ export default class DandanSource extends BaseSource {
|
||||
let comments = [];
|
||||
let sourceInstance = null;
|
||||
let platName = sourceName; // 映射标准平台名称用于去重工具
|
||||
|
||||
|
||||
// 匹配对应的源实例,并处理标准平台名
|
||||
if (sourceName === 'tencent') { sourceInstance = tencentSource; platName = 'qq'; }
|
||||
else if (sourceName === 'iqiyi') { sourceInstance = iqiyiSource; platName = 'qiyi'; }
|
||||
@@ -618,7 +633,7 @@ export default class DandanSource extends BaseSource {
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
cid: c.cid,
|
||||
p: `${c.p.replace(/([A-Za-z]+)([0-9a-fA-F]{6})/, (_, platform, hexColor) => {
|
||||
|
||||
@@ -739,6 +739,7 @@ function checkTheatricalExemption(titleA, titleB, typeDescA, typeDescB) {
|
||||
|
||||
/**
|
||||
* 校验媒体类型是否冲突 (真人 vs 动漫, TV vs Movie, 3D vs 2D)
|
||||
* 包含维数通配符逻辑:无明确 3D/2D 标识的条目视为通配符,允许进行任何关联
|
||||
* @returns {boolean} true=冲突, false=兼容
|
||||
*/
|
||||
function checkMediaTypeMismatch(titleA, titleB, typeDescA, typeDescB, countA, countB, sourceA = '', sourceB = '') {
|
||||
@@ -752,23 +753,23 @@ function checkMediaTypeMismatch(titleA, titleB, typeDescA, typeDescB, countA, co
|
||||
let is2DA = (typeDescA || '').includes('2D');
|
||||
let is2DB = (typeDescB || '').includes('2D');
|
||||
|
||||
// 维数动态探测逻辑 (Dynamic Dimension Detection):
|
||||
// 如果有一方在类型中明确是 3D,则被授权从另一方的标题中主动探测 3D 关键词
|
||||
if (is3DA && !is3DB) {
|
||||
// 状态判定:检查是否为无维数标识的通配符状态
|
||||
const isWildcardA = !is3DA && !is2DA;
|
||||
const isWildcardB = !is3DB && !is2DB;
|
||||
|
||||
// 动态探测:仅当一方具备明确维数,另一方为通配符时,尝试从通配符方标题中提取维数
|
||||
if (!isWildcardA && isWildcardB) {
|
||||
if (/3[dD]/.test(titleB)) is3DB = true;
|
||||
} else if (is3DB && !is3DA) {
|
||||
else if (/2[dD]/.test(titleB)) is2DB = true;
|
||||
} else if (!isWildcardB && isWildcardA) {
|
||||
if (/3[dD]/.test(titleA)) is3DA = true;
|
||||
else if (/2[dD]/.test(titleA)) is2DA = true;
|
||||
}
|
||||
|
||||
// 同样适用 2D 的动态探测:明确有 2D 标签时,才去对方标题里找 2D
|
||||
if (is2DA && !is2DB) {
|
||||
if (/2[dD]/.test(titleB)) is2DB = true;
|
||||
} else if (is2DB && !is2DA) {
|
||||
if (/2[dD]/.test(titleA)) is2DA = true;
|
||||
}
|
||||
|
||||
// 检测 3D 与 2D 的维数属性冲突:任意一方带 3D 且另一方不带 3D(即视为 2D)则判定为冲突
|
||||
if (is3DA !== is3DB) return true;
|
||||
// 维数冲突校验:双方最终都具有确切维数时,才执行严格比对
|
||||
const finalHasDimA = is3DA || is2DA;
|
||||
const finalHasDimB = is3DB || is2DB;
|
||||
if (finalHasDimA && finalHasDimB && is3DA !== is3DB) return true;
|
||||
|
||||
const mediaA = getStrictMediaType(titleA, typeDescA);
|
||||
const mediaB = getStrictMediaType(titleB, typeDescB);
|
||||
|
||||
+23
-14
@@ -99,14 +99,14 @@ async function handleRequest(req, env, deployPlatform, clientIp) {
|
||||
'/api/v2/comment',
|
||||
'/api/v2/segmentcomment'
|
||||
];
|
||||
|
||||
|
||||
// 只有当path包含指定接口关键字时才添加到请求记录数组
|
||||
if (targetPaths.some(targetPath => path.includes(targetPath))) {
|
||||
// 更新今日请求计数
|
||||
// 从 reqRecords 最后一个元素获取上一个请求的时间
|
||||
const lastRecord = globals.reqRecords.length > 0 ? globals.reqRecords[globals.reqRecords.length - 1] : null;
|
||||
const currentDate = new Date().toDateString();
|
||||
|
||||
|
||||
if (lastRecord) {
|
||||
const lastDate = new Date(lastRecord.timestamp).toDateString();
|
||||
console.log("currentDate: ", currentDate);
|
||||
@@ -242,39 +242,48 @@ async function handleRequest(req, env, deployPlatform, clientIp) {
|
||||
&& !path.startsWith('/api/ai')) {
|
||||
log("info", `[Path Check] Starting path normalization for: "${path}"`);
|
||||
const pathBeforeCleanup = path; // 保存清理前的路径检查是否修改
|
||||
|
||||
// 1. 清理:应对"用户填写/api/v2"+"客户端添加/api/v2"导致的重复前缀
|
||||
|
||||
// 清理:应对"用户填写/api/v2"+"客户端添加/api/v2"导致的重复前缀
|
||||
path = path.replace(/\/+/g, '/');
|
||||
while (path.startsWith('/api/v2/api/v2/')) {
|
||||
log("info", `[Path Check] Found redundant /api/v2 prefix. Cleaning...`);
|
||||
// 从第二个 /api/v2 的位置开始截取,相当于移除第一个
|
||||
path = path.substring('/api/v2'.length);
|
||||
}
|
||||
|
||||
|
||||
// 打印日志:只有在发生清理时才显示清理后的路径,否则显示"无需清理"
|
||||
if (path !== pathBeforeCleanup) {
|
||||
log("info", `[Path Check] Path after cleanup: "${path}"`);
|
||||
} else {
|
||||
log("info", `[Path Check] Path after cleanup: No cleanup needed.`);
|
||||
}
|
||||
|
||||
// 2. 补全:如果路径缺少前缀(例如请求原始路径为 /search/anime),则补全
|
||||
|
||||
// 补全:如果路径缺少前缀(例如请求原始路径为 /search/anime 或 /v2/search/anime),则智能补全
|
||||
const pathBeforePrefixCheck = path;
|
||||
if (!path.startsWith('/api/v2') && path !== '/' && !path.startsWith('/api/logs')
|
||||
&& !path.startsWith('/api/env') && !path.startsWith('/api/cache')
|
||||
&& !path.startsWith('/api/cookie') && !path.startsWith('/api/config')
|
||||
&& !path.startsWith('/api/ai')) {
|
||||
log("info", `[Path Check] Path is missing /api/v2 prefix. Adding...`);
|
||||
path = '/api/v2' + path;
|
||||
if (path.startsWith('/v2/') || path === '/v2') {
|
||||
log("info", `[Path Check] Path is missing /api prefix. Adding /api...`);
|
||||
path = '/api' + path;
|
||||
} else if (path.startsWith('/api/') || path === '/api') {
|
||||
log("info", `[Path Check] Path is missing /v2 prefix. Adding /v2...`);
|
||||
path = '/api/v2' + path.substring(4);
|
||||
} else {
|
||||
log("info", `[Path Check] Path is missing /api/v2 prefix. Adding /api/v2...`);
|
||||
path = '/api/v2' + (path.startsWith('/') ? path : '/' + path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 打印日志:只有在发生添加前缀时才显示添加后的路径,否则显示"无需补全"
|
||||
if (path === pathBeforePrefixCheck) {
|
||||
log("info", `[Path Check] Prefix Check: No prefix addition needed.`);
|
||||
}
|
||||
|
||||
|
||||
log("info", `[Path Check] Final normalized path: "${path}"`);
|
||||
}
|
||||
|
||||
|
||||
// GET /
|
||||
if (path === "/" && method === "GET") {
|
||||
return handleUI();
|
||||
@@ -424,7 +433,7 @@ async function handleRequest(req, env, deployPlatform, clientIp) {
|
||||
// 从请求体获取segment数据
|
||||
const requestBody = await req.json();
|
||||
let segment;
|
||||
|
||||
|
||||
// 尝试解析JSON
|
||||
try {
|
||||
segment = Segment.fromJson(requestBody);
|
||||
@@ -483,7 +492,7 @@ async function handleRequest(req, env, deployPlatform, clientIp) {
|
||||
}
|
||||
|
||||
// ========== Cookie 管理 API ==========
|
||||
|
||||
|
||||
// GET /api/cookie/status - 获取Cookie状态
|
||||
if (path === "/api/cookie/status" && method === "GET") {
|
||||
return handleCookieStatus();
|
||||
|
||||
Reference in new issue
Block a user