From 66b90ad69cbc323a8a3f740bacdf97b81a097150 Mon Sep 17 00:00:00 2001 From: trazyn Date: Sun, 15 Oct 2017 02:02:41 +0800 Subject: [PATCH] fix dead link, #37 #26 #3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QQ music, Kugou music, need moreā€¦ --- .eslintignore | 2 +- package.json | 5 ++- server/router/home.js | 4 +-- server/router/player.js | 11 +++++- server/search/Kugou.js | 46 ++++++++++++++++++++++++ server/search/QQ.js | 80 +++++++++++++++++++++++++++++++++++++++++ server/search/index.js | 26 ++++++++++++++ 7 files changed, 169 insertions(+), 5 deletions(-) create mode 100644 server/search/Kugou.js create mode 100644 server/search/QQ.js create mode 100644 server/search/index.js diff --git a/.eslintignore b/.eslintignore index 8f189b1..466f271 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,6 +2,6 @@ dist/* __tests__/* src/assets/* -server/* +server/api/* NeteaseCloudMusicApi/* diff --git a/package.json b/package.json index b7914fb..ac5f55d 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,9 @@ }, "linux": { "target": [ - "deb", "rpm", "AppImage" + "deb", + "rpm", + "AppImage" ], "icon": "../resource", "category": "Music" @@ -132,6 +134,7 @@ "electron-window-state": "^4.1.1", "han": "0.0.7", "ionicons201": "^1.0.0", + "md5": "^2.2.1", "mobx": "^3.2.2", "mobx-react": "^4.2.2", "moment": "^2.18.1", diff --git a/server/router/home.js b/server/router/home.js index f9906ea..5b42394 100644 --- a/server/router/home.js +++ b/server/router/home.js @@ -62,7 +62,6 @@ async function getPersonalized() { } async function getSongs(id) { - var songs = []; try { @@ -70,7 +69,8 @@ async function getSongs(id) { if (response.data.code === 200) { songs = response.data.playlist.tracks.map(e => { - var { al /* Album */, ar /* Artist */ } = e; + // eslint-disable-next-line + var {al /* Album */, ar /* Artist */} = e; return { id: e.id.toString(), diff --git a/server/router/player.js b/server/router/player.js index caacbc1..9687aff 100644 --- a/server/router/player.js +++ b/server/router/player.js @@ -4,6 +4,7 @@ import express from 'express'; import apicache from 'apicache' import axios from 'axios'; import _debug from 'debug'; +import search from '../search'; /* eslint-enable */ const debug = _debug('dev:api'); @@ -228,7 +229,12 @@ router.get('/song/:id/:name/:artists/:flac?', cache('5 minutes', onlyStatus200), let data = response.data; if (data.code !== 200) { - throw data; + // Search from other source + song = await search(name, artists); + + if (!song.src) { + throw data; + } } song = data.data[0]; @@ -242,6 +248,9 @@ router.get('/song/:id/:name/:artists/:flac?', cache('5 minutes', onlyStatus200), error('Failed to get song URL: %O', ex); } + var other = await search(name, artists); + debug('%O', other); + res.send({ song, }); diff --git a/server/search/Kugou.js b/server/search/Kugou.js new file mode 100644 index 0000000..24919fb --- /dev/null +++ b/server/search/Kugou.js @@ -0,0 +1,46 @@ + +import axios from 'axios'; +import md5 from 'md5'; +import _debug from 'debug'; + +const debug = _debug('dev:plugin:Kugou'); +const error = _debug('dev:plugin:Kugou:error'); + +async function getURL(hash) { + var key = md5(`${hash}kgcloud`); + + var response = await axios.get(`http://trackercdn.kugou.com/i/?acceptMp3=1&cmd=4&pid=6&hash=${hash}&key=${key}`); + var data = response.data; + + if (data.error + || data.status !== 1) { + error('Failed to get song URL: %O', data); + return null; + } else { + return data.url; + } +} + +export default async(keyword, artists) => { + debug(`Search '${keyword} - ${artists}' use Kugou plugin.`); + + var response = await axios.get(`http://mobilecdn.kugou.com/api/v3/search/song?format=json&keyword=${encodeURIComponent(keyword)}&page=1&pagesize=1&showtype=1`); + var data = response.data; + + if (data.status !== 1 + || data.data.info.length === 0) { + return; + } + + for (let e of data.data.info) { + if (artists.split(',').findIndex(artist => e.singername.indexOf(artist)) === -1) { + continue; + } + + debug('Find %O', e); + + return { + src: await getURL(e['320hash'] || e['hash']) + }; + } +}; diff --git a/server/search/QQ.js b/server/search/QQ.js new file mode 100644 index 0000000..c66be9a --- /dev/null +++ b/server/search/QQ.js @@ -0,0 +1,80 @@ + +import axios from 'axios'; +import _debug from 'debug'; + +const debug = _debug('dev:plugin:QQ'); +const error = _debug('dev:plugin:QQ:error'); +const baseUrl = 'dl.stream.qqmusic.qq.com'; +const baseApi = 'http://101.96.10.58/c.y.qq.com'; + +let updateTime = null; +let vkey = null; +let guid = null; + +async function updateVkey() { + var currentMs = (new Date()).getUTCMilliseconds(); + guid = Math.round(2147483647 * Math.random()) * currentMs % 1e10; + + if (!updateTime + || updateTime + 3600 * 1000 < +new Date()) { + try { + var response = await axios.get(`${baseApi}/base/fcgi-bin/fcg_musicexpress.fcg?json=3&guid=${guid}&g_tk=5381&jsonpCallback=jsonCallback&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf8¬ice=0&platform=yqq&needNewCode=0`); + vkey = response.data.key; + updateTime = +new Date(); + } catch (ex) { + error('Failed to update vkey: %O', ex); + } + } + return vkey; +} + +function getURL(data) { + return `http://${baseUrl}/${data.prefix}${data.mid}.${data.type}?vkey=${vkey}&guid=${guid}&uin=0&fromtag=30`; +} + +export default async(keyword, artists) => { + debug(`Search '${keyword} - ${artists}' use QQ plugin.`); + + try { + await updateVkey(); + } catch (ex) { + error('Failed to initialize QQ plugin: %O', ex); + throw ex; + } + + var response = await axios.get(`${baseApi}/soso/fcgi-bin/client_search_cp?ct=24&qqmusic_ver=1298&new_json=1&remoteplace=txt.yqq.song&t=0&aggr=1&cr=1&catZhida=1&lossless=1&flag_qc=0&p=1&n=1&w=${encodeURIComponent(keyword)}&g_tk=5381&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq&needNewCode=0`); + var data = response.data; + + if (data.code !== 0 + || data.data.song.list.length === 0) { + return; + } + + for (let e of data.data.song.list) { + let list = e.file; + let song = {}; + + // Match the artists + if (e.singer.findIndex(e => artists.indexOf(e.name) === -1)) { + continue; + } + + if (list.size_128 > 0) { + song.src = getURL({ + mid: list.media_mid, + prefix: 'M500', + type: 'mp3', + }); + } + + if (list.size_320 > 0) { + song.src = getURL({ + mid: list.media_mid, + prefix: 'M800', + type: 'mp3', + }); + } + + return song; + } +}; diff --git a/server/search/index.js b/server/search/index.js new file mode 100644 index 0000000..e29b715 --- /dev/null +++ b/server/search/index.js @@ -0,0 +1,26 @@ + +import QQ from './QQ'; +import Kugou from './Kugou'; +import _debug from 'debug'; + +const debug = _debug('plugin:'); + +export default (keyword, artists) => { + var plugins = [QQ, Kugou]; + + debug('Plugin has loaded, search: \'%s\', \'%s\'', keyword, artists); + + return Promise.all( + plugins.map(e => { + // If a request failed will keep waiting for other possible successes, if a request successed, + // treat it as a rejection so Promise.all immediate break. + return e(keyword, artists).then( + val => Promise.reject(val), + err => Promise.resolve(err) + ); + }) + ).then( + errs => Promise.reject(errs), + val => Promise.resolve(val), + ); +};