Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ node app.js -o bilibili ytdlp
| 酷我音乐 | `kuwo` | | |
| 波点音乐 | `bodian` | ✅ | |
| 咪咕音乐 | `migu` | ✅ | 需要准备自己的 `MIGU_COOKIE`(请参阅下方〈环境变量〉处)。 |
| JOOX | `joox` | | 需要准备自己的 `JOOX_COOKIE`(请参阅下方〈环境变量〉处)。似乎有严格地区限制。 |
| JOOX | `joox` | | 需要准备自己的 `JOOX_COOKIE`(请参阅下方〈环境变量〉处)。<br>仅支持 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)。 |
Expand Down
74 changes: 48 additions & 26 deletions src/provider/joox.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const insure = require('./insure');
const select = require('./select');
const crypto = require('../crypto');
const request = require('../request');
const { getManagedCacheStorage } = require('../cache');

Expand All @@ -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=<your_wmid>; session_key=<your_session_key>;'
cookie:
process.env.JOOX_COOKIE ||
'wmid=142420656; user_type=1; country=hk; session_key=2a5d97d05dc8fe238150184eaf3519ad; uid=142420656; backendCountry=hk',
};

const fit = (info) => {
Expand All @@ -21,56 +22,77 @@ 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 || '',
})),
};
};

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();
});
};

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();
})
Expand Down