diff --git a/README.md b/README.md index ccb18bd..ad7a0ef 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ - **部署支持**:支持本地运行、Docker 容器化、Vercel 一键部署、Cloudflare 一键部署和 Docker 一键启动。 ## 前置条件 -- Node.js(v18.0.0 或更高版本) +- Node.js(v18.0.0 或更高版本;理论兼容更低版本,请自行测试) - npm - Docker(可选,用于容器化部署) @@ -163,7 +163,7 @@ Settings > Functions > Advanced Setting > Function Region 切换为 Hong Kong, 支持 forward/senplayer/hills/小幻/yamby/eplayerx/afusekt 等支持弹幕API的播放器。 以`senplayer`为例: -1. 获取到部署之后的API地址,如`http://192.168.1.7:9321/87654321`,其中`87654321`是默认token,前提是没有传TOKEN环境变量 +1. 获取到部署之后的API地址,如 `http://192.168.1.7:9321/87654321` ,其中`87654321`是默认token,如果有自定义环境变量TOKEN,请替换成相应的token 2. 将API地址填入自定义弹幕API,在`设置 - 弹幕设置 - 自定义弹幕API` 3. 播放界面点击`弹幕按钮 - 搜索弹幕`,选择你的弹幕API,会根据标题进行搜索,等待一段时间,选择剧集就行。 @@ -179,9 +179,10 @@ Settings > Functions > Advanced Setting > Function Region 切换为 Hong Kong, | 变量名称 | 描述 | | ----------- | ----------- | | TOKEN | 【可选】自定义用户token,不填默认为`87654321` | -| OTHER_SERVER | 【可选】兜底第三方弹幕服务器,如 https://api.danmu.icu | -| VOD_SERVER | 【可选】vod查询站点,如 https://www.caiji.cyou | +| OTHER_SERVER | 【可选】兜底第三方弹幕服务器,不填默认为`https://api.danmu.icu` | +| VOD_SERVER | 【可选】vod查询站点,不填默认为`https://www.caiji.cyou` | | BILIBILI_COOKIE | 【可选】b站cookie(填入后能抓取完整弹幕),如 `buvid3=E2BCA ... eao6; theme-avatar-tip-show=SHOWED`,请自行通过浏览器或抓包工具抓取 | +| YOUKU_CONCURRENCY | 【可选】youku弹幕请求并发数,用于加快youku弹幕请求速度,不填默认为`8`,最高`16` | ## 项目结构 ``` @@ -215,6 +216,9 @@ danmu_api/ - 如果想更换vod站点,请添加环境变量`VOD_SERVER`,示例`https://www.caiji.cyou`。 - 推荐vercel和claw部署,cloudflare好像不稳定,当然最稳定还是自己本地docker部署最佳。 +### 关联项目 +[danmu_api 自动同步部署方案 - 永远保持最新版本!实时同步原作者更新](https://github.com/xiaoyao20084321/log-var-danmu-deployment-guide) + ### 📈项目 Star 数增长趋势 #### Star History [![Star History Chart](https://api.star-history.com/svg?repos=huangxd-/danmu_api&type=Date)](https://www.star-history.com/#huangxd-/danmu_api&Date) diff --git a/danmu_api/worker.js b/danmu_api/worker.js index 78501de..721d1b9 100644 --- a/danmu_api/worker.js +++ b/danmu_api/worker.js @@ -1,6 +1,6 @@ // 全局状态(Cloudflare 和 Vercel 都可能重用实例) // ⚠️ 不是持久化存储,每次冷启动会丢失 -const VERSION = "1.0.4"; +const VERSION = "1.1.0"; let animes = []; let episodeIds = []; let episodeNum = 10001; // 全局变量,用于自增 ID @@ -56,13 +56,13 @@ let youkuConcurrency = DEFAULT_YOUKU_CONCURRENCY; function resolveYoukuConcurrency(env) { if (env && env.YOUKU_CONCURRENCY) { const n = parseInt(env.YOUKU_CONCURRENCY, 10); - if (!Number.isNaN(n) && n > 0) return n; + if (!Number.isNaN(n) && n > 0) return Math.min(n, 16); } if (typeof process !== "undefined" && process.env?.YOUKU_CONCURRENCY) { const n = parseInt(process.env.YOUKU_CONCURRENCY, 10); - if (!Number.isNaN(n) && n > 0) return n; + if (!Number.isNaN(n) && n > 0) return Math.min(n, 16); } - return DEFAULT_YOUKU_CONCURRENCY; + return Math.min(DEFAULT_YOUKU_CONCURRENCY, 16); } // 添加元素到 episodeIds:检查 url 是否存在,若不存在则以自增 id 添加 @@ -2567,164 +2567,183 @@ function matchSeason(anime, queryTitle, season) { // Extracted function for GET /api/v2/search/anime async function searchAnime(url) { - let curAnimes = []; const queryTitle = url.searchParams.get("keyword"); log("log", `Search anime with keyword: ${queryTitle}`); - // 查询vod - const animesVod = await getVodAnimes(queryTitle); - for (const anime of animesVod) { - let vodPlayFromList = anime.vod_play_from.split("$$$") - vodPlayFromList = vodPlayFromList.map(item => { - if (item === "mgtv") { - return "imgo"; // 将mgtv替换为imgo - } else if (item === "bilibili") { - return "bilibili1"; // 将bilibili替换为bilibili1 - } else { - return item; // 其他情况保持原值 - } - }); + const curAnimes = []; - const vodPlayUrlList = anime.vod_play_url.split("$$$") + try { + // 并行查询不同的来源 + const [animesVod, animes360, animesRenren] = await Promise.all([ + getVodAnimes(queryTitle), + get360Animes(queryTitle), + renrenSearch(queryTitle) + ]); - // 过滤出在allowedPlatforms中的元素,并保持它们在vodPlayFromList中的原始索引 - const validIndices = vodPlayFromList + // 处理Vod来源的结果 + const processVodAnimes = animesVod.map(async (anime) => { + let vodPlayFromList = anime.vod_play_from.split("$$$"); + vodPlayFromList = vodPlayFromList.map(item => { + if (item === "mgtv") return "imgo"; + if (item === "bilibili") return "bilibili1"; + return item; + }); + + const vodPlayUrlList = anime.vod_play_url.split("$$$"); + const validIndices = vodPlayFromList .map((item, index) => allowedPlatforms.includes(item) ? index : -1) .filter(index => index !== -1); - let links = []; - let count = 0; - for (const num of validIndices) { - const platform = vodPlayFromList[num]; - const eps = vodPlayUrlList[num].split("#"); - for (const ep of eps) { - const epInfo = ep.split("$"); - count = count + 1; - links.push({"name": count, "url": epInfo[1], "title": `【${platform}】${anime.vod_name}(${anime.vod_year}) ${epInfo[0]}`}); - } - } - - if (links.length > 0) { - let transformedAnime = { - animeId: Number(anime.vod_id), // Mapping animeId to id - bangumiId: String(anime.vod_id), // Mapping bangumiId to id - animeTitle: `${anime.vod_name}(${anime.vod_year})【${anime.type_name}】from vod`, // Mapping animeTitle to titleTxt - type: anime.type_name, // Mapping type to cat_name - typeDescription: anime.type_name, // Mapping typeDescription to cat_name - imageUrl: anime.vod_pic, // Mapping imageUrl to cover - startDate: `${anime.vod_year}-01-01T00:00:00`, // Start date to the year field in ISO format - episodeCount: links.length, // Mapping episodeCount to length of seriesPlaylinks - rating: 0, // Default rating as 0 - isFavorited: true, // Assuming all anime are favorited by default - }; - - curAnimes.push(transformedAnime); - // Check if the anime already exists in the animes array - const exists = animes.some(existingAnime => existingAnime.animeId === transformedAnime.animeId); - if (!exists) { - const transformedAnimeCopy = { ...transformedAnime, links: links }; - addAnime(transformedAnimeCopy); - } - if (animes.length > MAX_ANIMES) { - removeEarliestAnime(); - } - } - } - - // 查询360 - const animes360 = await get360Animes(queryTitle); - for (const anime of animes360) { - let links = []; - if (anime.cat_name === "电影") { - for (const key in Object.keys(anime.playlinks)) { - if (allowedPlatforms.includes(key)) { - links.push({"name": key, "url": anime.playlinks[key], "title": `【${key}】${anime.titleTxt}(${anime.year})`}); + let links = []; + let count = 0; + for (const num of validIndices) { + const platform = vodPlayFromList[num]; + const eps = vodPlayUrlList[num].split("#"); + for (const ep of eps) { + const epInfo = ep.split("$"); + count++; + links.push({ + "name": count, + "url": epInfo[1], + "title": `【${platform}】${anime.vod_name}(${anime.vod_year}) ${epInfo[0]}` + }); } } - } else if (anime.cat_name === "电视剧" || anime.cat_name === "动漫") { - if (allowedPlatforms.includes(anime.seriesSite)) { - for (let i = 0; i < anime.seriesPlaylinks.length; i++) { - const item = anime.seriesPlaylinks[i]; - links.push({"name": i+1, "url": item.url, "title": `【${anime.seriesSite}】${anime.titleTxt}(${anime.year}) ${i + 1}`}); + + if (links.length > 0) { + let transformedAnime = { + animeId: Number(anime.vod_id), + bangumiId: String(anime.vod_id), + animeTitle: `${anime.vod_name}(${anime.vod_year})【${anime.type_name}】from vod`, + type: anime.type_name, + typeDescription: anime.type_name, + imageUrl: anime.vod_pic, + startDate: `${anime.vod_year}-01-01T00:00:00`, + episodeCount: links.length, + rating: 0, + isFavorited: true, + }; + + curAnimes.push(transformedAnime); + const exists = animes.some(existingAnime => existingAnime.animeId === transformedAnime.animeId); + if (!exists) { + const transformedAnimeCopy = { ...transformedAnime, links: links }; + addAnime(transformedAnimeCopy); } + if (animes.length > MAX_ANIMES) removeEarliestAnime(); } - } else if (anime.cat_name === "综艺") { - for (const site of Object.keys(anime.playlinks_year)) { - if (allowedPlatforms.includes(site)) { - for (const year of anime.playlinks_year[site]) { - const subLinks = await get360Zongyi(anime.id, site, year); - links = links.concat(subLinks); + }); + + // 处理360来源的结果 + const process360Animes = animes360.map(async (anime) => { + let links = []; + if (anime.cat_name === "电影") { + for (const key in Object.keys(anime.playlinks)) { + if (allowedPlatforms.includes(key)) { + links.push({ + "name": key, + "url": anime.playlinks[key], + "title": `【${key}】${anime.titleTxt}(${anime.year})` + }); } } + } else if (anime.cat_name === "电视剧" || anime.cat_name === "动漫") { + if (allowedPlatforms.includes(anime.seriesSite)) { + for (let i = 0; i < anime.seriesPlaylinks.length; i++) { + const item = anime.seriesPlaylinks[i]; + links.push({ + "name": i + 1, + "url": item.url, + "title": `【${anime.seriesSite}】${anime.titleTxt}(${anime.year}) ${i + 1}` + }); + } + } + } else if (anime.cat_name === "综艺") { + const zongyiLinks = await Promise.all( + Object.keys(anime.playlinks_year).map(async (site) => { + if (allowedPlatforms.includes(site)) { + const yearLinks = await Promise.all( + anime.playlinks_year[site].map(async (year) => { + return await get360Zongyi(anime.id, site, year); + }) + ); + return yearLinks.flat(); // 将每个年份的子链接合并到一个数组 + } + return []; + }) + ); + links = zongyiLinks.flat(); // 扁平化所有返回的子链接 } - } - if (links.length > 0) { - let transformedAnime = { - animeId: Number(anime.id), // Mapping animeId to id - bangumiId: String(anime.id), // Mapping bangumiId to id - animeTitle: `${anime.titleTxt}(${anime.year})【${anime.cat_name}】from 360`, // Mapping animeTitle to titleTxt - type: anime.cat_name, // Mapping type to cat_name - typeDescription: anime.cat_name, // Mapping typeDescription to cat_name - imageUrl: anime.cover, // Mapping imageUrl to cover - startDate: `${anime.year}-01-01T00:00:00`, // Start date to the year field in ISO format - episodeCount: links.length, // Mapping episodeCount to length of seriesPlaylinks - rating: 0, // Default rating as 0 - isFavorited: true, // Assuming all anime are favorited by default - }; + if (links.length > 0) { + let transformedAnime = { + animeId: Number(anime.id), + bangumiId: String(anime.id), + animeTitle: `${anime.titleTxt}(${anime.year})【${anime.cat_name}】from 360`, + type: anime.cat_name, + typeDescription: anime.cat_name, + imageUrl: anime.cover, + startDate: `${anime.year}-01-01T00:00:00`, + episodeCount: links.length, + rating: 0, + isFavorited: true, + }; - curAnimes.push(transformedAnime); - // Check if the anime already exists in the animes array - const exists = animes.some(existingAnime => existingAnime.animeId === transformedAnime.animeId); - if (!exists) { - const transformedAnimeCopy = {...transformedAnime, links: links}; - addAnime(transformedAnimeCopy); + curAnimes.push(transformedAnime); + const exists = animes.some(existingAnime => existingAnime.animeId === transformedAnime.animeId); + if (!exists) { + const transformedAnimeCopy = { ...transformedAnime, links: links }; + addAnime(transformedAnimeCopy); + } + if (animes.length > MAX_ANIMES) removeEarliestAnime(); } - if (animes.length > MAX_ANIMES) { - removeEarliestAnime(); + }); + + // 处理Renren来源的结果 + const processRenrenAnimes = animesRenren.filter(s => s.title.includes(queryTitle)).map(async (anime) => { + const eps = await getEpisodes(anime.mediaId); + let links = []; + for (const ep of eps) { + links.push({ + "name": ep.episodeIndex, + "url": ep.episodeId, + "title": `【${ep.provider}】${anime.title}(${anime.year}) ${ep.title}` + }); } - } - } - // 查询renren - const results = await renrenSearch(queryTitle); - log("log", "人人视频搜索结果:", results); + if (links.length > 0) { + let transformedAnime = { + animeId: Number(anime.mediaId), + bangumiId: String(anime.mediaId), + animeTitle: `${anime.title}(${anime.year})【${anime.type}】from renren`, + type: anime.type, + typeDescription: anime.type, + imageUrl: anime.imageUrl, + startDate: `${anime.year}-01-01T00:00:00`, + episodeCount: links.length, + rating: 0, + isFavorited: true, + }; - let animesRenren = results.filter(s => s.title.includes(queryTitle)); - for (const anime of animesRenren) { - const eps = await getEpisodes(anime.mediaId); - printFirst200Chars(eps); - - let links = []; - for (const ep of eps) { - links.push({"name": ep.episodeIndex, "url": ep.episodeId, "title": `【${ep.provider}】${anime.title}(${anime.year}) ${ep.title}`}); - } - - if (links.length > 0) { - let transformedAnime = { - animeId: Number(anime.mediaId), // Mapping animeId to id - bangumiId: String(anime.mediaId), // Mapping bangumiId to id - animeTitle: `${anime.title}(${anime.year})【${anime.type}】from renren`, // Mapping animeTitle to titleTxt - type: anime.type, // Mapping type to cat_name - typeDescription: anime.type, // Mapping typeDescription to cat_name - imageUrl: anime.imageUrl, // Mapping imageUrl to cover - startDate: `${anime.year}-01-01T00:00:00`, // Start date to the year field in ISO format - episodeCount: links.length, // Mapping episodeCount to length of seriesPlaylinks - rating: 0, // Default rating as 0 - isFavorited: true, // Assuming all anime are favorited by default - }; - - curAnimes.push(transformedAnime); - // Check if the anime already exists in the animes array - const exists = animes.some(existingAnime => existingAnime.animeId === transformedAnime.animeId); - if (!exists) { - const transformedAnimeCopy = {...transformedAnime, links: links}; - addAnime(transformedAnimeCopy); + curAnimes.push(transformedAnime); + const exists = animes.some(existingAnime => existingAnime.animeId === transformedAnime.animeId); + if (!exists) { + const transformedAnimeCopy = { ...transformedAnime, links: links }; + addAnime(transformedAnimeCopy); + } + if (animes.length > MAX_ANIMES) removeEarliestAnime(); } - if (animes.length > MAX_ANIMES) { - removeEarliestAnime(); - } - } + }); + + // 等待所有处理完成 + await Promise.all([ + ...processVodAnimes, + ...process360Animes, + ...processRenrenAnimes + ]); + + } catch (error) { + log("error", "发生错误:", error); } return jsonResponse({