From 23a1e993b10a8d8b80b37d60baf8fcc79ca1a70d Mon Sep 17 00:00:00 2001 From: ll <48448919+llsccm@users.noreply.github.com> Date: Wed, 27 May 2026 20:11:31 +0800 Subject: [PATCH] fix(provider): replace joox API - new joox API - use fake cookie --- README.md | 2 +- src/provider/joox.js | 74 ++++++++++++++++++++++++++++---------------- 2 files changed, 49 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 0c08ac17ae..49e5a0cb56 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,7 @@ node app.js -o bilibili ytdlp | 酷我音乐 | `kuwo` | | | | 波点音乐 | `bodian` | ✅ | | | 咪咕音乐 | `migu` | ✅ | 需要准备自己的 `MIGU_COOKIE`(请参阅下方〈环境变量〉处)。 | -| JOOX | `joox` | | 需要准备自己的 `JOOX_COOKIE`(请参阅下方〈环境变量〉处)。似乎有严格地区限制。 | +| JOOX | `joox` | | 需要准备自己的 `JOOX_COOKIE`(请参阅下方〈环境变量〉处)。
仅支持 Hong Kong, Macau, Thailand, Malaysia, Indonesia. | | YouTube(纯 JS 解析方式) | `youtube` | | 需要 Google 认定的**非中国大陆区域** IP 地址。 | | YouTube(通过 `youtube-dl`) | `youtubedl` | | 需要自行安装 `youtube-dl`。 | | YouTube(通过 `yt-dlp`) | `ytdlp` | ✅ | 需要自行安装 `yt-dlp`(`youtube-dl` 仍在活跃维护的 fork)。 | diff --git a/src/provider/joox.js b/src/provider/joox.js index c7f1cd02fe..d4884fc070 100644 --- a/src/provider/joox.js +++ b/src/provider/joox.js @@ -1,6 +1,5 @@ const insure = require('./insure'); const select = require('./select'); -const crypto = require('../crypto'); const request = require('../request'); const { getManagedCacheStorage } = require('../cache'); @@ -10,7 +9,9 @@ const headers = { // Refer to #95, you should register an account // on Joox to use their service. We allow users // to specify it manually. - cookie: process.env.JOOX_COOKIE || null, // 'wmid=; session_key=;' + cookie: + process.env.JOOX_COOKIE || + 'wmid=142420656; user_type=1; country=hk; session_key=2a5d97d05dc8fe238150184eaf3519ad; uid=142420656; backendCountry=hk', }; const fit = (info) => { @@ -21,15 +22,17 @@ const fit = (info) => { }; const format = (song) => { - const { decode } = crypto.base64; return { - id: song.songid, - name: decode(song.info1 || ''), - duration: song.playtime * 1000, - album: { id: song.albummid, name: decode(song.info3 || '') }, - artists: song.singer_list.map(({ id, name }) => ({ + id: song.id, + name: song.name || '', + duration: (parseInt(song.playtime) || 0) * 1000, + album: { + id: song.album_id, + name: song.album_name || '', + }, + artists: (song.artist_list || []).map(({ id, name }) => ({ id, - name: decode(name || ''), + name: name || '', })), }; }; @@ -37,17 +40,20 @@ const format = (song) => { const search = (info) => { const keyword = fit(info); const url = - 'http://api-jooxtt.sanook.com/web-fcgi-bin/web_search?' + - 'country=hk&lang=zh_TW&' + - 'search_input=' + + 'https://cache.api.joox.com/openjoox/v2/search_type?' + + 'country=hk&lang=zh_TW&key=' + encodeURIComponent(keyword) + - '&sin=0&ein=30'; + '&type=0'; return request('GET', url, headers) .then((response) => response.body()) .then((body) => { - const jsonBody = JSON.parse(body.replace(/'/g, '"')); - const list = jsonBody.itemlist.map(format); + const jsonBody = JSON.parse(body); + const tracks = jsonBody.tracks || []; + const list = tracks + .map((track) => (Array.isArray(track) ? track[0] : track)) + .filter(Boolean) + .map(format); const matched = select(list, info); return matched ? matched.id : Promise.reject(); }); @@ -55,22 +61,38 @@ const search = (info) => { const track = (id) => { const url = - 'http://api.joox.com/web-fcgi-bin/web_get_songinfo?' + + 'https://api.joox.com/web-fcgi-bin/web_get_songinfo?' + 'songid=' + id + - '&country=hk&lang=zh_cn&from_type=-1&' + - 'channel_id=-1&_=' + + '&country=hk&lang=zh_TW&from_type=-1&channel_id=-1&_=' + new Date().getTime(); return request('GET', url, headers) - .then((response) => response.jsonp()) - .then((jsonBody) => { - const songUrl = ( - jsonBody.r320Url || - jsonBody.r192Url || - jsonBody.mp3Url || - jsonBody.m4aUrl - ).replace(/M\d00([\w]+).mp3/, 'M800$1.mp3'); + .then((response) => response.body()) + .then((body) => { + const jsonBody = JSON.parse( + body.replace(/^MusicInfoCallback\(/, '').replace(/\);?$/, '') + ); + const candidateFields = [ + 'master_tapeUrl', + 'master_tapeURL', + 'master_tape_url', + 'hiresUrl', + 'hiresURL', + 'hires_url', + 'flacUrl', + 'flacURL', + 'flac_url', + 'r320Url', + 'r192Url', + 'mp3Url', + 'm4aUrl', + ]; + + const songUrl = candidateFields + .map((field) => jsonBody[field]) + .find((url) => url && String(url).startsWith('http')); + if (songUrl) return songUrl; else return Promise.reject(); })