diff --git a/publish/changeLog.md b/publish/changeLog.md index 2e5aecd7..b4ce8b20 100644 --- a/publish/changeLog.md +++ b/publish/changeLog.md @@ -3,6 +3,9 @@ - 新增“我的收藏”本地播放列表 - 新增缓存清理功能,可到**设置-其他**查看与清理软件缓存 - 新增QQ音乐源搜索 +- 新增咪咕源搜索 +- 新增咪咕源歌单 +- 新增咪咕源排行榜 #### 优化 diff --git a/src/renderer/utils/music/api-source.js b/src/renderer/utils/music/api-source.js index bcf67b7d..e1163037 100644 --- a/src/renderer/utils/music/api-source.js +++ b/src/renderer/utils/music/api-source.js @@ -4,6 +4,7 @@ import tx_api_test from './tx/api-test' import kg_api_test from './kg/api-test' import wy_api_test from './wy/api-test' import bd_api_test from './bd/api-test' +import mg_api_test from './mg/api-test' // import kw_api_internal from './kw/api-internal' // import tx_api_internal from './tx/api-internal' // import kg_api_internal from './kg/api-internal' @@ -16,6 +17,7 @@ const apis = { kg_api_test, wy_api_test, bd_api_test, + mg_api_test, // kw_api_internal, // tx_api_internal, // kg_api_internal, @@ -46,6 +48,8 @@ export default source => { return getAPI('wy') case 'bd': return getAPI('bd') + case 'mg': + return getAPI('mg') default: return getAPI('kw') } diff --git a/src/renderer/utils/music/index.js b/src/renderer/utils/music/index.js index 57fa475b..108c3abd 100644 --- a/src/renderer/utils/music/index.js +++ b/src/renderer/utils/music/index.js @@ -2,6 +2,7 @@ import kw from './kw' import kg from './kg' import tx from './tx' import wy from './wy' +import mg from './mg' import bd from './bd' export default { sources: [ @@ -21,6 +22,10 @@ export default { name: '网易音乐', id: 'wy', }, + { + name: '咪咕音乐', + id: 'mg', + }, { name: '百度音乐', id: 'bd', @@ -30,5 +35,6 @@ export default { kg, tx, wy, + mg, bd, } diff --git a/src/renderer/utils/music/kg/lyric.js b/src/renderer/utils/music/kg/lyric.js index 3b3bb45e..dab3fc00 100644 --- a/src/renderer/utils/music/kg/lyric.js +++ b/src/renderer/utils/music/kg/lyric.js @@ -11,7 +11,7 @@ export default { } return parseInt(intv) }, - getLyric(songInfo, tryNum) { + getLyric(songInfo, tryNum = 0) { let requestObj = httpFetch(`http://m.kugou.com/app/i/krc.php?cmd=100&keyword=${encodeURIComponent(songInfo.name)}&hash=${songInfo.hash}&timelength=${songInfo._interval || this.getIntv(songInfo.interval)}&d=0.38664927426725626`, { headers: { 'KG-RC': 1, @@ -22,7 +22,7 @@ export default { requestObj.promise = requestObj.promise.then(({ body, statusCode }) => { if (statusCode !== 200) { if (tryNum > 5) return Promise.reject('歌词获取失败') - let tryRequestObj = this.getLyric(songInfo, tryNum) + let tryRequestObj = this.getLyric(songInfo, ++tryNum) requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj) return tryRequestObj.promise } diff --git a/src/renderer/utils/music/kw/songList.js b/src/renderer/utils/music/kw/songList.js index 6bb23cb9..5559d1fe 100644 --- a/src/renderer/utils/music/kw/songList.js +++ b/src/renderer/utils/music/kw/songList.js @@ -211,7 +211,7 @@ export default { }) }, getTags() { - return Promise.all([this.getTag(), this.getHotTag()]).then(([tags, hotTag]) => ({ tags, hotTag })) + return Promise.all([this.getTag(), this.getHotTag()]).then(([tags, hotTag]) => ({ tags, hotTag, source: 'kw' })) }, } diff --git a/src/renderer/utils/music/mg/api-test.js b/src/renderer/utils/music/mg/api-test.js new file mode 100644 index 00000000..02446db5 --- /dev/null +++ b/src/renderer/utils/music/mg/api-test.js @@ -0,0 +1,20 @@ +import { httpFetch } from '../../request' +import { requestMsg } from '../../message' +import { headers, timeout } from '../options' + +const api_test = { + getMusicUrl(songInfo, type) { + const requestObj = httpFetch(`http://ts.tempmusic.tk/url/mg/${songInfo.copyrightId}/${type}`, { + method: 'get', + timeout, + headers, + family: 4, + }) + requestObj.promise = requestObj.promise.then(({ body }) => { + return body.code === 0 ? Promise.resolve({ type, url: encodeURI(body.data) }) : Promise.reject(new Error(requestMsg.fail)) + }) + return requestObj + }, +} + +export default api_test diff --git a/src/renderer/utils/music/mg/index.js b/src/renderer/utils/music/mg/index.js new file mode 100644 index 00000000..dda6d214 --- /dev/null +++ b/src/renderer/utils/music/mg/index.js @@ -0,0 +1,23 @@ +import api_source from '../api-source' +import leaderboard from './leaderboard' +import songList from './songList' +import musicSearch from './musicSearch' +import pic from './pic' +import lyric from './lyric' + +const mg = { + songList, + musicSearch, + leaderboard, + getMusicUrl(songInfo, type) { + return api_source('mg').getMusicUrl(songInfo, type) + }, + getLyric(songInfo) { + return lyric.getLyric(songInfo) + }, + getPic(songInfo) { + return pic.getPic(songInfo) + }, +} + +export default mg diff --git a/src/renderer/utils/music/mg/leaderboard.js b/src/renderer/utils/music/mg/leaderboard.js new file mode 100644 index 00000000..3c45f661 --- /dev/null +++ b/src/renderer/utils/music/mg/leaderboard.js @@ -0,0 +1,130 @@ +import { httpFetch } from '../../request' +// import { formatPlayTime } from '../../index' +// import jshtmlencode from 'js-htmlencode' + +export default { + limit: 200, + list: [ + { + id: 'mgyyb', + name: '音乐榜', + bangid: '23603703', + }, + { + id: 'mgysb', + name: '影视榜', + bangid: '23603721', + }, + { + id: 'mghybnd', + name: '华语内地榜', + bangid: '23603926', + }, + { + id: 'mghyjqbgt', + name: '华语港台榜', + bangid: '23603954', + }, + { + id: 'mgomb', + name: '欧美榜', + bangid: '23603974', + }, + { + id: 'mgrhb', + name: '日韩榜', + bangid: '23603982', + }, + { + id: 'mgwlb', + name: '网络榜', + bangid: '23604058', + }, + { + id: 'mgclb', + name: '彩铃榜', + bangid: '23604023', + }, + { + id: 'mgktvb', + name: 'KTV榜', + bangid: '23604040', + }, + { + id: 'mgrcb', + name: '原创榜', + bangid: '23604032', + }, + ], + getUrl(id, page) { + return `http://m.music.migu.cn/migu/remoting/cms_list_tag?nid=${id}&pageSize=${this.limit}&pageNo=${page - 1}` + }, + requestObj: null, + getData(url) { + if (this.requestObj) this.requestObj.cancelHttp() + this.requestObj = httpFetch(url) + return this.requestObj.promise + }, + filterData(rawList) { + // console.log(rawList) + const list = [] + rawList.forEach(({ songData }) => { + if (!songData) return + + const types = [] + const _types = {} + let size = null + types.push({ type: '128k', size }) + _types['128k'] = { + size, + } + + if (songData.hasHQqq === '1') { + types.push({ type: '320k', size }) + _types['320k'] = { + size, + } + } + if (songData.hasSQqq === '1') { + types.push({ type: 'flac', size }) + _types.flac = { + size, + } + } + // types.reverse() + + list.push({ + singer: songData.singerName.join('、'), + name: songData.songName, + // albumName: songData.album_title, + // albumId: songData.album_id, + source: 'mg', + interval: null, + songmid: songData.songId, + copyrightId: songData.copyrightId, + img: songData.picL || songData.M || songData.picS, + lrc: null, + types, + _types, + typeUrl: {}, + }) + }) + + return list + }, + getList(id, page) { + let type = this.list.find(s => s.id === id) + if (!type) return Promise.reject() + return this.getData(this.getUrl(type.bangid, page)).then(({ statusCode, body }) => { + if (statusCode !== 200) return Promise.reject(new Error('获取列表失败')) + const list = this.filterData(body.result.results) + return { + total: body.result.totalCount, + list, + limit: body.result.pageSize, + page, + source: 'mg', + } + }) + }, +} diff --git a/src/renderer/utils/music/mg/lyric.js b/src/renderer/utils/music/mg/lyric.js new file mode 100644 index 00000000..65191f79 --- /dev/null +++ b/src/renderer/utils/music/mg/lyric.js @@ -0,0 +1,36 @@ +import { httpFetch } from '../../request' + +export default { + getLyric(songInfo, tryNum = 0) { + console.log(songInfo.copyrightId) + if (songInfo.lrcUrl) { + let requestObj = httpFetch(songInfo.lrcUrl) + requestObj.promise = requestObj.promise.then(({ body, statusCode }) => { + if (statusCode !== 200) { + if (tryNum > 5) return Promise.reject('歌词获取失败') + let tryRequestObj = this.getLyric(songInfo, ++tryNum) + requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj) + return tryRequestObj.promise + } + return body + }) + return requestObj + } else { + let requestObj = httpFetch(`http://music.migu.cn/v3/api/music/audioPlayer/getLyric?copyrightId=${songInfo.copyrightId}`, { + headers: { + Referer: 'http://music.migu.cn/v3/music/player/audio?from=migu', + }, + }) + requestObj.promise = requestObj.promise.then(({ body }) => { + if (body.returnCode !== '000000') { + if (tryNum > 5) return Promise.reject('歌词获取失败') + let tryRequestObj = this.getLyric(songInfo, ++tryNum) + requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj) + return tryRequestObj.promise + } + return body.lyric + }) + return requestObj + } + }, +} diff --git a/src/renderer/utils/music/mg/musicSearch.js b/src/renderer/utils/music/mg/musicSearch.js new file mode 100644 index 00000000..81a0b008 --- /dev/null +++ b/src/renderer/utils/music/mg/musicSearch.js @@ -0,0 +1,108 @@ +// import '../../polyfill/array.find' +// import jshtmlencode from 'js-htmlencode' +import { httpFetch } from '../../request' +import { sizeFormate } from '../../index' +// import { debug } from '../../utils/env' +// import { formatSinger } from './util' + +let searchRequest +export default { + limit: 30, + total: 0, + page: 0, + allPage: 1, + musicSearch(str, page) { + if (searchRequest && searchRequest.cancelHttp) searchRequest.cancelHttp() + searchRequest = httpFetch(`https://app.c.nf.migu.cn/MIGUM2.0/v1.0/content/search_all.do?isCopyright=1&isCorrect=1&pageNo=${page}&pageSize=${this.limit}&searchSwitch={%22song%22:1,%22album%22:0,%22singer%22:0,%22tagSong%22:0,%22mvSong%22:0,%22songlist%22:0,%22bestShow%22:0}&sort=0&text=${encodeURIComponent(str)}`) + return searchRequest.promise.then(({ body }) => body) + }, + getSinger(singers) { + let arr = [] + singers.forEach(singer => { + arr.push(singer.name) + }) + return arr.join('、') + }, + handleResult(rawData) { + // console.log(rawData) + let ids = new Set() + const list = [] + rawData.forEach(item => { + if (ids.has(item.id)) return + ids.add(item.id) + const types = [] + const _types = {} + item.rateFormats && item.rateFormats.forEach(type => { + let size + switch (type.formatType) { + case 'PQ': + size = sizeFormate(type.size) + types.push({ type: '128k', size }) + _types['128k'] = { + size, + } + break + case 'HQ': + size = sizeFormate(type.size) + types.push({ type: '320k', size }) + _types['320k'] = { + size, + } + break + case 'SQ': + size = sizeFormate(type.size) + types.push({ type: 'flac', size }) + _types.flac = { + size, + } + break + } + }) + + const albumNInfo = item.albums && item.albums.length ? { + id: item.albums[0].id, + name: item.albums[0].name, + } : {} + + list.push({ + singer: this.getSinger(item.singers), + name: item.name, + albumName: albumNInfo.name, + albumId: albumNInfo.id, + songmid: item.id, + copyrightId: item.copyrightId, + source: 'mg', + interval: null, + img: item.imgItems && item.imgItems.length ? item.imgItems[0].img : null, + lrc: null, + lrcUrl: item.lyricUrl, + types, + _types, + typeUrl: {}, + }) + }) + return list + }, + search(str, page = 1, { limit } = {}) { + if (limit != null) this.limit = limit + // http://newlyric.kuwo.cn/newlyric.lrc?62355680 + return this.musicSearch(str, page).then(result => { + if (!result || result.code !== '000000') return this.search(str, page, { limit }) + let list = this.handleResult(result.songResultData.resultList.flat()) + + if (list == null) return this.search(str, page, { limit }) + + this.total = parseInt(result.songResultData.totalCount) + this.page = page + this.allPage = Math.ceil(this.total / this.limit) + + return Promise.resolve({ + list, + allPage: this.allPage, + limit: this.limit, + total: this.total, + source: 'mg', + }) + }) + }, +} diff --git a/src/renderer/utils/music/mg/pic.js b/src/renderer/utils/music/mg/pic.js new file mode 100644 index 00000000..46a40461 --- /dev/null +++ b/src/renderer/utils/music/mg/pic.js @@ -0,0 +1,21 @@ +import { httpFetch } from '../../request' + +export default { + getPic(songInfo, tryNum = 0) { + let requestObj = httpFetch(`http://music.migu.cn/v3/api/music/audioPlayer/getSongPic?songId=${songInfo.songmid}`, { + headers: { + Referer: 'http://music.migu.cn/v3/music/player/audio?from=migu', + }, + }) + requestObj.promise = requestObj.promise.then(({ body }) => { + if (body.returnCode !== '000000') { + if (tryNum > 5) return Promise.reject('图片获取失败') + let tryRequestObj = this.getPic(songInfo, ++tryNum) + requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj) + return tryRequestObj.promise + } + return body.largePic || body.mediumPic || body.smallPic + }) + return requestObj + }, +} diff --git a/src/renderer/utils/music/mg/songList.js b/src/renderer/utils/music/mg/songList.js new file mode 100644 index 00000000..5419192e --- /dev/null +++ b/src/renderer/utils/music/mg/songList.js @@ -0,0 +1,213 @@ +import { httpFetch } from '../../request' +import { sizeFormate } from '../../index' + +export default { + _requestObj_tags: null, + _requestObj_list: null, + _requestObj_listDetail: null, + limit_list: 10, + limit_song: 100, + successCode: '000000', + sortList: [ + { + name: '推荐', + id: '15127315', + }, + { + name: '最新', + id: '15127272', + }, + ], + regExps: { + list: /
  • .+?<\/li>/g, + listInfo: /.+data-original="(.+?)".*data-id="(\d+)".*
    (.+?)<\/a>.+<\/i>(.+?)<\/div>/, + }, + tagsUrl: 'https://app.c.nf.migu.cn/MIGUM2.0/v1.0/content/indexTagPage.do?needAll=0', + getSongListUrl(sortId, tagId, page) { + // if (tagId == null) { + // return sortId == 'recommend' + // ? `http://music.migu.cn/v3/music/playlist?page=${page}&from=migu` + // : `http://music.migu.cn/v3/music/playlist?sort=${sortId}&page=${page}&from=migu` + // } + // return `http://music.migu.cn/v3/music/playlist?tagId=${tagId}&page=${page}&from=migu` + if (tagId == null) { + return `http://m.music.migu.cn/migu/remoting/playlist_bycolumnid_tag?playListType=2&type=1&columnId=${sortId}&startIndex=${(page - 1) * 10}` + } + return `http://m.music.migu.cn/migu/remoting/playlist_bycolumnid_tag?playListType=2&type=1&tagId=${tagId}&startIndex=${(page - 1) * 10}` + }, + getSongListDetailUrl(id, page) { + return `https://app.c.nf.migu.cn/MIGUM2.0/v1.0/user/queryMusicListSongs.do?musicListId=${id}&pageNo=${page}&pageSize=${this.limit_song}` + }, + defaultHeaders: { + language: 'Chinese', + ua: 'Android_migu', + mode: 'android', + version: '6.8.5', + }, + + /** + * 格式化播放数量 + * @param {*} num + */ + formatPlayCount(num) { + if (num > 100000000) return parseInt(num / 10000000) / 10 + '亿' + if (num > 10000) return parseInt(num / 1000) / 10 + '万' + return num + }, + + getListDetail(id, page) { // 获取歌曲列表内的音乐 + if (this._requestObj_listDetail) this._requestObj_listDetail.cancelHttp() + this._requestObj_listDetail = httpFetch(this.getSongListDetailUrl(id, page), { headers: this.defaultHeaders }) + return this._requestObj_listDetail.promise.then(({ body }) => { + if (body.code !== this.successCode) return this.getListDetail(id, page) + return { + list: this.filterListDetail(body.list), + page, + limit: this.limit_song, + total: body.totalCount, + source: 'mg', + } + }) + }, + filterListDetail(rawList) { + // console.log(rawList) + let ids = new Set() + const list = [] + rawList.forEach(item => { + if (ids.has(item.songId)) return + ids.add(item.songId) + const types = [] + const _types = {} + item.rateFormats && item.rateFormats.forEach(type => { + let size + switch (type.formatType) { + case 'PQ': + size = sizeFormate(type.size) + types.push({ type: '128k', size }) + _types['128k'] = { + size, + } + break + case 'HQ': + size = sizeFormate(type.size) + types.push({ type: '320k', size }) + _types['320k'] = { + size, + } + break + case 'SQ': + size = sizeFormate(type.size) + types.push({ type: 'flac', size }) + _types.flac = { + size, + } + break + } + }) + + + list.push({ + singer: item.singer, + name: item.songName, + albumName: item.album, + albumId: item.albumId, + songmid: item.songId, + copyrightId: item.copyrightId, + source: 'mg', + interval: null, + img: item.albumImgs && item.albumImgs.length ? item.albumImgs[0].img : null, + lrc: null, + lrcUrl: item.lrcUrl, + types, + _types, + typeUrl: {}, + }) + }) + return list + }, + + // 获取列表数据 + getList(sortId, tagId, page) { + if (this._requestObj_list) this._requestObj_list.cancelHttp() + this._requestObj_list = httpFetch(this.getSongListUrl(sortId, tagId, page)) + // return this._requestObj_list.promise.then(({ statusCode, body }) => { + // if (statusCode !== 200) return this.getList(sortId, tagId, page) + // let list = body.replace(/[\r\n]/g, '').match(this.regExps.list) + // if (!list) return Promise.reject('获取列表失败') + // return list.map(item => { + // let info = item.match(this.regExps.listInfo) + // return { + // play_count: info[4], + // id: info[2], + // author: '', + // name: info[3], + // time: '', + // img: info[1], + // grade: 0, + // desc: '', + // source: 'mg', + // } + // }) + // }) + return this._requestObj_list.promise.then(({ body }) => { + if (body.retCode !== '100000' || body.retMsg.code !== this.successCode) return this.getList(sortId, tagId, page) + return { + list: this.filterList(body.retMsg.playlist), + total: parseInt(body.retMsg.countSize), + page, + limit: this.limit_list, + source: 'mg', + } + }) + }, + filterList(rawData) { + return rawData.map(item => ({ + play_count: this.formatPlayCount(item.playCount), + id: item.playListId, + author: item.createName, + name: item.playListName, + time: item.createTime, + img: item.image, + grade: item.grade, + desc: item.summary, + source: 'mg', + })) + }, + + // 获取标签 + getTag() { + if (this._requestObj_tags) this._requestObj_tags.cancelHttp() + this._requestObj_tags = httpFetch(this.tagsUrl, { headers: this.defaultHeaders }) + return this._requestObj_tags.promise.then(({ body }) => { + if (body.code !== this.successCode) return this.getTag() + return this.filterTagInfo(body.columnInfo.contents) + }) + }, + filterTagInfo(rawList) { + return { + hotTag: rawList[0].objectInfo.contents.map(item => ({ + id: item.objectInfo.tagId, + name: item.objectInfo.tagName, + source: 'mg', + })), + tags: rawList.slice(1).map(({ objectInfo }) => ({ + name: objectInfo.columnTitle, + list: objectInfo.contents.map(item => ({ + parent_id: objectInfo.columnId, + parent_name: objectInfo.columnTitle, + id: item.objectInfo.tagId, + name: item.objectInfo.tagName, + source: 'mg', + })), + })), + source: 'mg', + } + }, + getTags() { + return this.getTag() + }, +} + +// getList +// getTags +// getListDetail diff --git a/src/renderer/views/Search.vue b/src/renderer/views/Search.vue index d683a5a6..fb2c0569 100644 --- a/src/renderer/views/Search.vue +++ b/src/renderer/views/Search.vue @@ -33,7 +33,7 @@ :play-btn="item.source == 'kw' || (!isAPITemp && item.source != 'wy')" :download-btn="item.source == 'kw' || (!isAPITemp && item.source != 'wy')" @btn-click="handleListBtnClick") - td(style="width: 10%;") {{item.interval}} + td(style="width: 10%;") {{item.interval || '--/--'}} div(:class="$style.pagination") material-pagination(:count="listInfo.total" :limit="listInfo.limit" :page="page" @btn-click="handleTogglePage") div(v-else :class="$style.noitem") diff --git a/src/renderer/views/SongList.vue b/src/renderer/views/SongList.vue index aad5c2ed..27700b86 100644 --- a/src/renderer/views/SongList.vue +++ b/src/renderer/views/SongList.vue @@ -288,6 +288,7 @@ export default { .songListContent { display: flex; flex-flow: column nowrap; + height: 100%; // position: relative; }