diff --git a/danmu_api/sources/iqiyi.js b/danmu_api/sources/iqiyi.js index 25e4d1e..a9040d5 100644 --- a/danmu_api/sources/iqiyi.js +++ b/danmu_api/sources/iqiyi.js @@ -122,28 +122,16 @@ export default class IqiyiSource extends BaseSource { return null; } - // 提取 link_id (从 pageUrl 或 playUrl 中提取) - const url = album.pageUrl || album.playUrl; - if (!url) { - return null; - } - - const linkIdMatch = url.match(/v_(\w+?)\.html/); - if (!linkIdMatch) { - return null; - } - const linkId = linkIdMatch[1]; - // 过滤外站付费播放 if (album.btnText === '外站付费播放') { log("debug", `[iQiyi] 过滤掉外站付费播放内容: ${album.title}`); return null; } - // 提取媒体类型(参考优化后的 youku.js 和 bilibili.js) + // 提取媒体类型 const channel = album.channel || ""; let mediaType = "电视剧"; // 默认类型 - + if (channel.includes("电影")) { mediaType = "电影"; } else if (channel.includes("动漫")) { @@ -159,6 +147,52 @@ export default class IqiyiSource extends BaseSource { return null; } + // 电影类型:使用 qipuId 作为 mediaId + if (mediaType === "电影") { + const qipuId = album.qipuId || album.playQipuId; + if (!qipuId) { + log("debug", `[iQiyi] 电影缺少 qipuId: ${album.title}`); + return null; + } + + // 提取年份 + let year = null; + if (album.year) { + const yearStr = album.year.value || album.year.name; + if (yearStr && typeof yearStr === 'string' && yearStr.length === 4 && /^\d{4}$/.test(yearStr)) { + year = parseInt(yearStr); + } + } + + // 清理标题 + const cleanedTitle = album.title.replace(/<[^>]+>/g, '').replace(/:/g, ':'); + + return { + provider: "iqiyi", + mediaId: `movie_${qipuId}`, // 使用特殊前缀标识电影 + title: cleanedTitle, + type: mediaType, + year: year, + imageUrl: album.img || album.imgH, + episodeCount: 1, // 电影只有1集 + _qipuId: qipuId // 保存原始 qipuId 供后续使用 + }; + } + + // 非电影类型:从 pageUrl 提取 link_id + const url = album.pageUrl; + if (!url) { + log("debug", `[iQiyi] 非电影内容缺少 pageUrl: ${album.title}`); + return null; + } + + const linkIdMatch = url.match(/v_(\w+?)\.html/); + if (!linkIdMatch) { + log("debug", `[iQiyi] 无法从 pageUrl 提取 link_id: ${url}`); + return null; + } + const linkId = linkIdMatch[1]; + // 提取年份 let year = null; if (album.year) { @@ -201,13 +235,34 @@ export default class IqiyiSource extends BaseSource { /** * 获取分集列表 - * @param {string} id - 视频 ID (link_id) + * @param {string} id - 视频 ID (link_id 或 movie_qipuId) * @returns {Promise} 分集列表 */ async getEpisodes(id) { try { log("info", `[iQiyi] 获取分集列表: media_id=${id}`); + // 检查是否是电影类型(以 movie_ 开头) + if (id.startsWith('movie_')) { + const qipuId = id.substring(6); // 移除 "movie_" 前缀 + log("info", `[iQiyi] 电影类型,调用 base_info API 获取视频ID: qipuId=${qipuId}`); + + // 调用 base_info API 获取电影详情 + const videoId = await this._getMovieVideoId(qipuId); + if (!videoId) { + log("error", `[iQiyi] 无法获取电影的视频ID: qipuId=${qipuId}`); + return []; + } + + log("info", `[iQiyi] 电影视频ID: ${videoId}`); + return [{ + id: videoId, + title: "正片", + order: 1, + link: `https://www.iqiyi.com/v_${videoId}.html` + }]; + } + // 第一步:将 video_id 转换为 entity_id const entityId = this._videoIdToEntityId(id); if (!entityId) { @@ -271,68 +326,125 @@ export default class IqiyiSource extends BaseSource { } const blocks = tabs[0].blocks || []; - const episodeGroups = []; + let foundEpisodes = false; for (const block of blocks) { - if (block.bk_type === "album_episodes") { - const blockData = block.data?.data || []; - episodeGroups.push(...blockData); - } - } + // 查找 video_list 类型的块(新版API) + if (block.bk_type === "video_list" && block.data?.data) { + log("debug", `[iQiyi] 找到 video_list 类型的分集数据块, bk_id: ${block.bk_id}`); - if (episodeGroups.length === 0) { - log("info", "[iQiyi] 未找到分集数据块"); - return []; - } - - // 第五步:处理分集数据 - for (const group of episodeGroups) { - let videosData = group.videos; - - // 如果 videos 是 URL,需要额外请求 - if (typeof videosData === 'string') { - log("info", `[iQiyi] 发现分季URL,正在获取: ${videosData}`); - try { - const seasonResponse = await httpGet(videosData); - videosData = typeof seasonResponse.data === "string" ? JSON.parse(seasonResponse.data) : seasonResponse.data; - } catch (error) { - log("error", `[iQiyi] 获取分季数据失败: ${error.message}`); + // 检查是否是分集选择器块 + if (!block.tag || !block.tag.includes("episodes")) { + log("debug", `[iQiyi] 跳过非分集块: ${block.bk_id}`); continue; } - } - // 处理分页数据 - if (videosData && typeof videosData === 'object' && videosData.feature_paged) { - for (const pageKey in videosData.feature_paged) { - const pagedList = videosData.feature_paged[pageKey]; - for (const epData of pagedList) { - if (epData.content_type !== 1) continue; + foundEpisodes = true; - const playUrl = epData.play_url || ""; - const tvidMatch = playUrl.match(/tvid=(\d+)/); - if (!tvidMatch) continue; + const dataGroups = block.data.data; + if (!Array.isArray(dataGroups)) { + log("warn", "[iQiyi] data.data 不是数组,跳过此块"); + continue; + } - const tvid = tvidMatch[1]; - let title = epData.short_display_name || epData.title || "未知分集"; - const subtitle = epData.subtitle; - if (subtitle && !title.includes(subtitle)) { - title = `${title} ${subtitle}`; - } + for (const group of dataGroups) { + if (!group.videos || !Array.isArray(group.videos)) continue; - const order = epData.album_order; - const pageUrl = epData.page_url; + // 遍历每个年份/季度分组 + for (const videoGroup of group.videos) { + if (!videoGroup.data || !Array.isArray(videoGroup.data)) continue; - if (tvid && title && order && pageUrl) { - allEpisodes.push({ - id: tvid, - title: title, - order: order, - link: pageUrl - }); + // 处理每个分集 + for (const epData of videoGroup.data) { + // 只处理正片内容 (content_type === 1) + if (epData.content_type !== 1) continue; + + const playUrl = epData.play_url || ""; + const tvidMatch = playUrl.match(/tvid=(\d+)/); + if (!tvidMatch) continue; + + const tvid = tvidMatch[1]; + let title = epData.short_display_name || epData.title || "未知分集"; + const subtitle = epData.subtitle; + if (subtitle && !title.includes(subtitle)) { + title = `${title} ${subtitle}`; + } + + const order = epData.album_order; + const pageUrl = epData.page_url; + + if (tvid && title && pageUrl) { + allEpisodes.push({ + id: tvid, + title: title, + order: order !== undefined ? order : allEpisodes.length, + link: pageUrl + }); + } } } } } + // 兼容旧版 API 的 album_episodes 类型 + else if (block.bk_type === "album_episodes" && block.data?.data) { + log("debug", "[iQiyi] 找到 album_episodes 类型的分集数据块"); + foundEpisodes = true; + + const episodeGroups = block.data.data; + for (const group of episodeGroups) { + let videosData = group.videos; + + // 如果 videos 是 URL,需要额外请求 + if (typeof videosData === 'string') { + log("info", `[iQiyi] 发现分季URL,正在获取: ${videosData}`); + try { + const seasonResponse = await httpGet(videosData); + videosData = typeof seasonResponse.data === "string" ? JSON.parse(seasonResponse.data) : seasonResponse.data; + } catch (error) { + log("error", `[iQiyi] 获取分季数据失败: ${error.message}`); + continue; + } + } + + // 处理分页数据 + if (videosData && typeof videosData === 'object' && videosData.feature_paged) { + for (const pageKey in videosData.feature_paged) { + const pagedList = videosData.feature_paged[pageKey]; + for (const epData of pagedList) { + if (epData.content_type !== 1) continue; + + const playUrl = epData.play_url || ""; + const tvidMatch = playUrl.match(/tvid=(\d+)/); + if (!tvidMatch) continue; + + const tvid = tvidMatch[1]; + let title = epData.short_display_name || epData.title || "未知分集"; + const subtitle = epData.subtitle; + if (subtitle && !title.includes(subtitle)) { + title = `${title} ${subtitle}`; + } + + const order = epData.album_order; + const pageUrl = epData.page_url; + + if (tvid && title && order && pageUrl) { + allEpisodes.push({ + id: tvid, + title: title, + order: order, + link: pageUrl + }); + } + } + } + } + } + } + } + + if (!foundEpisodes) { + log("info", "[iQiyi] 未找到分集数据块"); + return []; } // 去重并排序 @@ -350,6 +462,94 @@ export default class IqiyiSource extends BaseSource { } } + /** + * 获取电影的视频ID(从 qipuId 获取正确的 video_id) + * @param {string} qipuId - 电影的 qipuId + * @returns {Promise} 视频ID + */ + async _getMovieVideoId(qipuId) { + try { + // 构建 base_info API 请求参数 + const params = { + entity_id: qipuId, + device_id: 'qd5fwuaj4hunxxdgzwkcqmefeb3ww5hx', + auth_cookie: '', + user_id: '0', + vip_type: '-1', + vip_status: '0', + conduit_id: '', + pcv: '13.103.23529', + app_version: '13.103.23529', + ext: '', + app_mode: 'standard', + scale: '125', + timestamp: String(Date.now()), + src: 'pca_tvg', + os: '', + ad_ext: '{"r":"2.5.0-ares6-pure"}' + }; + + // 生成签名(使用与 getEpisodes 相同的方法) + params.sign = this._createSign(params); + + // 构建 URL + const queryString = buildQueryString(params); + const url = `https://mesh.if.iqiyi.com/tvg/v2/lw/base_info?${queryString}`; + + log("debug", `[iQiyi] 请求电影详情: ${url}`); + + const response = await httpGet(url, { + headers: { + 'accept': '*/*', + 'origin': 'https://www.iqiyi.com', + 'referer': 'https://www.iqiyi.com/', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' + } + }); + + if (!response || !response.data) { + log("error", "[iQiyi] base_info API 响应为空"); + return null; + } + + const data = typeof response.data === "string" ? JSON.parse(response.data) : response.data; + + // 从响应中提取视频ID + // 尝试多个可能的路径 + if (data.data && data.data.base_data) { + const baseData = data.data.base_data; + + // 尝试 1: 从 share_url 中提取(最可靠) + if (baseData.share_url) { + const match = baseData.share_url.match(/v_(\w+)\.html/); + if (match) { + const videoId = match[1]; + log("info", `[iQiyi] 从 share_url 提取视频ID: ${videoId}`); + return videoId; + } + } + + // 尝试 2: 从 page_url 中提取 + if (baseData.page_url) { + const match = baseData.page_url.match(/v_(\w+)\.html/); + if (match) { + const videoId = match[1]; + log("info", `[iQiyi] 从 page_url 提取视频ID: ${videoId}`); + return videoId; + } + } + } + + log("error", "[iQiyi] base_info API 响应中未找到视频ID"); + log("debug", `[iQiyi] 响应数据结构: ${JSON.stringify(data).substring(0, 1000)}...`); + return null; + + } catch (error) { + log("error", `[iQiyi] 获取电影视频ID时出错: ${error.message}`); + return null; + } + } + /** * 将 video_id 转换为 entity_id * @param {string} videoId - 视频 ID