diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/bin/www b/bin/www index 7ef9c9d..00aef28 100755 --- a/bin/www +++ b/bin/www @@ -12,7 +12,7 @@ var http = require('http') * Get port from environment and store in Express. */ -var port = normalizePort(process.env.PORT || '1234') +var port = normalizePort(process.env.PORT || '4321') // app.set('port', port); /** diff --git a/config/index.js b/config/index.js index 220ea9a..0941114 100644 --- a/config/index.js +++ b/config/index.js @@ -12,13 +12,11 @@ const tempstamp = Date.now() // 小程序基础配置 const projectConfig = { - appid: 'wxd292348da4b04d47', - appSecret: '39f56668e476eea5d514315898927169', - jwtExpiresIn: '1d', - jwtSalt: 'quanquandequan', - userInfoExpiresIn: 10000, qqMusicCommonBaseUrl: 'https://c.y.qq.com', qqMusicUrlBaseUrl: 'https://u.y.qq.com', + qqMusicHtmlUrl: 'https://i.y.qq.com/v8/playsong.html?songmid=004AeIvh4ML0Bz', + albumImgUrl: 'https://y.gtimg.cn/music/photo_new/T002R300x300M000 .jpg', + singerAvatarUrl: 'https://y.gtimg.cn/music/photo_new/T001R150x150M000 .jpg', defaultData: { g_tk: '5381', uin: '0', @@ -32,7 +30,6 @@ const projectConfig = { }, defaultHeader: { authority: 'c.y.qq.com', - method: 'GET', path: 'musichall/fcgi-bin/fcg_yqqhomepagerecommend.fcg?g_tk=5381&uin=0&format=json&inCharset=utf-8&outCharset=utf-8¬ice=0&platform=h5&needNewCode=1&_=' + tempstamp, scheme: 'https', accept: 'application/json', @@ -42,9 +39,7 @@ const projectConfig = { dnt: '1', origin: 'https://m.y.qq.com', pragma: 'no-cache', - referer: 'https://m.y.qq.com/', - 'Content-Type': 'application/json', - 'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1' + 'Content-Type': 'application/json' } } module.exports = Object.assign({ diff --git a/package.json b/package.json index 1d98229..ca5ed42 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "prod": "NODE_ENV=prod pm2 start bin/www" }, "dependencies": { + "@koa/cors": "^2.2.3", "axios": "^0.18.0", "debug": "^2.6.3", "koa": "^2.2.0", diff --git a/readMe.md b/readMe.md index 9edba9c..619c27f 100644 --- a/readMe.md +++ b/readMe.md @@ -1,7 +1,20 @@ -> 吃着火锅, 写着代码, 听着歌...一不小心某音乐就上市了. 趁着兴头扒了一套 api (极简, 可用, 从获取排行榜到歌曲播放 url). 现共享一下接口文档. 大家玩儿的开心. 😄 + +> 吃着火锅, 写着代码, 听着歌...一不小心某音乐就上市了. 趁着兴头扒了一套 api (极简, 可用, 从获取排行榜到歌曲播放 url, 歌词, 封面等信息). 现共享一下接口文档. 大家玩儿的开心. 😄 ![2018-12-24-22-58-59](https://user-gold-cdn.xitu.io/2018/12/24/167e0d20c1822778?w=1120&h=472&f=png&s=827839) +## 诚邀共同维护者 + +由于个人的力量毕竟有限, 加之 🐧 厂的兄弟们一直致力于升级 api 上. 所以维护过程中出现心有余力不足的情况, 这里盛情邀请有精力且愿意折腾一下的小伙伴们和我一起来维护这个项目, 有兴趣的小伙伴请不吝提出 issue 和 PR. + +![2019-01-06-16-05-29](http://img.blog.niubishanshan.top/2019-01-06-16-05-29.png) + ## 本项目项目地址[github](https://github.com/luoquanquan/musicInterFace) ## 接口域名 @@ -10,7 +23,7 @@ ## basePath -> /api/music +> /api/v2/music ## 1. 获取首页推荐信息 @@ -22,7 +35,7 @@ > 请求说明:
> 请求方式 GET
-> 请求URL :[/recommend](https://music.niubishanshan.top/api/music/recommend) +> 请求URL :[/recommend](https://music.niubishanshan.top/api/v2/music/recommend) ### 1.3 请求参数 @@ -34,15 +47,16 @@ ```json { + "announce": "本接口所有数据均来自 QQ 音乐, 仅供学习交流之用,请不要用于商业用途. 如果喜欢请下载 QQ 音乐 APP 畅听.如有侵权请联系微信(QQ): 1363693666, 我会第一时间删除~", "errno": 0, "msg": "success", "data": { "slider": [ - "http://y.gtimg.cn/music/common/upload/MUSIC_FOCUS/1111137.jpg", - "http://y.gtimg.cn/music/common/upload/MUSIC_FOCUS/1109397.jpg", - "http://y.gtimg.cn/music/common/upload/MUSIC_FOCUS/1109234.jpg", - "http://y.gtimg.cn/music/common/upload/MUSIC_FOCUS/1109139.jpg", - "http://y.gtimg.cn/music/common/upload/MUSIC_FOCUS/1110553.jpg" + "http://y.gtimg.cn/music/common/upload/MUSIC_FOCUS/1121987.jpg", + "http://y.gtimg.cn/music/common/upload/MUSIC_FOCUS/1122653.jpg", + "http://y.gtimg.cn/music/common/upload/MUSIC_FOCUS/1121479.jpg", + "http://y.gtimg.cn/music/common/upload/MUSIC_FOCUS/1120772.jpg", + "http://y.gtimg.cn/music/common/upload/MUSIC_FOCUS/1123020.jpg" ], "radioList": [ { @@ -64,6 +78,7 @@ 字段 | 字段类型 | 字段说明 --- | --- | --- +announce | string | 声明文案 errno | int | 0: 表示没有问题, 其他表示有问题. 详情参考 msg msg | string | 接口返回状态描述 data | object | 接口返回数据主体 @@ -87,7 +102,7 @@ data | object | 接口返回数据主体 > 请求说明:
> 请求方式 GET
-> 请求URL :[/toplist](https://music.niubishanshan.top/api/music/toplist) +> 请求URL :[/toplist](https://music.niubishanshan.top/api/v2/music/toplist) ### 2.3 请求参数 @@ -99,33 +114,34 @@ data | object | 接口返回数据主体 ```json { + "announce": "本接口所有数据均来自 QQ 音乐, 仅供学习交流之用,请不要用于商业用途. 如果喜欢请下载 QQ 音乐 APP 畅听.如有侵权请联系微信(QQ): 1363693666, 我会第一时间删除~", "errno": 0, "msg": "success", "data": [ { "id": 4, "title": "巅峰榜·流行指数", - "listenCount": 19500000, - "picUrl": "http://y.gtimg.cn/music/photo_new/T003R300x300M000000VjrhC1PVxWS.jpg", + "listenCount": 19800000, + "picUrl": "http://y.gtimg.cn/music/photo_new/T003R300x300M000004DDmku3TdWR9.jpg", "songList": [ { - "singername": "薛之谦", - "songname": "天份", + "singername": "林俊杰", + "songname": "不为谁而作的歌", "number": 1 }, { - "singername": "陈柯宇", - "songname": "生僻字", + "singername": "林俊杰", + "songname": "圣所", "number": 2 }, { - "singername": "毛不易", - "songname": "别再闹了", + "singername": "GAI", + "songname": "永不独行", "number": 3 } ] - } - ... 各种数据 ... + }, + ...各种数据... ] } ``` @@ -134,13 +150,14 @@ data | object | 接口返回数据主体 字段 | 字段类型 | 字段说明 --- | --- | --- +announce | string | 声明文案 errno | int | 0: 表示没有问题, 其他表示有问题. 详情参考 msg msg | string | 接口返回状态描述 data | array | 接口返回数据主体   id | int | 歌单id   title | string | 歌单标题   listenCount | int | 歌单播放次数 -  picUrl | string | 歌单logo +  picUrl | string | 歌单 logo url   songList | array | 歌单中排行榜前三的曲目     singername | string | 歌手名称     songname | string | 歌曲名称 @@ -160,7 +177,7 @@ data | array | 接口返回数据主体 > 请求说明:
> 请求方式 GET
-> 请求URL :[/songIdlist/{:songListId}](https://music.niubishanshan.top/api/music/songIdlist/4) +> 请求URL :[/songList/{:songListId}](https://music.niubishanshan.top/api/v2/music/songList/4) ### 3.3 请求参数 @@ -172,22 +189,28 @@ songListId | string | 歌单id, 就是排行榜中获取的歌单条目的id字 ```json { + "announce": "本接口所有数据均来自 QQ 音乐, 仅供学习交流之用,请不要用于商业用途. 如果喜欢请下载 QQ 音乐 APP 畅听.如有侵权请联系微信(QQ): 1363693666, 我会第一时间删除~", "errno": 0, "msg": "success", "data": { - "update_time": "2018-12-24", - "total_song_num": 100, - "topinfo": { - "pic_album": "http://imgcache.qq.com/music/photo_new/T002R300x300M000004KfMU92CZeAd.jpg", - "ListName": "巅峰榜·流行指数" + "updateTime": "2019-01-10", + "totalSongNum": 100, + "topInfo": { + "picAlbum": "http://imgcache.qq.com/music/photo_new/T002R300x300M000003nbc0602Tgfx.jpg", + "listName": "巅峰榜·流行指数" }, - "songlist": [ + "songList": [ { - "songmid": "000Qepff3UyUWO", - "singer": "薛之谦", - "songname": "天份" - }, - ...各种数据... + "songMid": "002K4xqW4A7m7q", + "singer": { + "singerName": "林俊杰", + "singerMid": "001BLpXF2DyJe2" + }, + "songName": "不为谁而作的歌", + "songId": 105095766, + "albumMid": "003nbc0602Tgfx" + } + ...各种数据... ] } } @@ -197,18 +220,23 @@ songListId | string | 歌单id, 就是排行榜中获取的歌单条目的id字 字段 | 字段类型 | 字段说明 --- | --- | --- +announce | string | 声明文案 errno | int | 0: 表示没有问题, 其他表示有问题. 详情参考 msg msg | string | 接口返回状态描述 data | object | 接口返回数据主体 -  update_time | string | 更新时间 -  total_song_num | int | 歌单中歌曲数目 -  topinfo | object | 歌单信息 -    pic_album | string | 歌单封面logo -    ListName | string | 歌单名称 -  songlist | array | 歌曲列表 -    songmid | string | 歌曲id +  updateTime | string | 更新时间 +  totalSongNum | int | 歌单中歌曲数目 +  topInfo | object | 歌单信息 +    picAlbum | string | 歌单封面logo +    listName | string | 歌单名称 +  songList | array | 歌曲列表 +    songMid | string | 歌曲id     singer | string | 歌手名称 -    songname | string | 歌曲名称 +      singerName | string | 歌手名称 +      singerMid | string | 歌手媒体 id, 用户获取歌手头像 +    songName | string | 歌曲名称 +    songId | string | 歌曲 id, 用于获取歌词 +    albumMid | string | 专辑媒体 id, 用于获取专辑封面 url ### 3.6 错误状态码 @@ -224,7 +252,7 @@ data | object | 接口返回数据主体 > 请求说明:
> 请求方式 GET
-> 请求URL :[/songUrllist/{:songIdList}](https://music.niubishanshan.top/api/music/songUrllist/000Qepff3UyUWO,001KxFBr3ZrMIk) +> 请求URL :[/songUrllist/{:songIdList}](https://music.niubishanshan.top/api/v2/music/songUrllist/000Qepff3UyUWO,001KxFBr3ZrMIk) ### 4.3 请求参数 @@ -236,12 +264,13 @@ songIdList | stringArray | 歌曲 id 列表, 需要拼接成 `id1,id2,id3,id4` ```json { - "errno": 0, - "msg": "success", - "data": [ - "http://isure.stream.qqmusic.qq.com//C400000Qepff3UyUWO.m4a?guid=5579254314&vkey=70D5522DDF8F35B36B133AA0F85A9C2FA608F2FA85BCBB4EC31CC6A0047CEAB873E9E2B947A6D893C219C65781B9EFE1F00C583518290F4E&uin=0&fromtag=38", - "http://isure.stream.qqmusic.qq.com//C400001KxFBr3ZrMIk.m4a?guid=5579254314&vkey=7DC202D78758D601A1EF4B15F5597A805C740CFCE9210870073D05247716E83D4146EE3907962645D2F7BE99071BFC0B01E73F09AFA5114D&uin=0&fromtag=38" - ] + "announce": "本接口所有数据均来自 QQ 音乐, 仅供学习交流之用,请不要用于商业用途. 如果喜欢请下载 QQ 音乐 APP 畅听.如有侵权请联系微信(QQ): 1363693666, 我会第一时间删除~", + "errno": 0, + "msg": "success", + "data": [ + "http://isure.stream.qqmusic.qq.com//C400000Qepff3UyUWO.m4a?guid=5579254314&vkey=70D5522DDF8F35B36B133AA0F85A9C2FA608F2FA85BCBB4EC31CC6A0047CEAB873E9E2B947A6D893C219C65781B9EFE1F00C583518290F4E&uin=0&fromtag=38", + "http://isure.stream.qqmusic.qq.com//C400001KxFBr3ZrMIk.m4a?guid=5579254314&vkey=7DC202D78758D601A1EF4B15F5597A805C740CFCE9210870073D05247716E83D4146EE3907962645D2F7BE99071BFC0B01E73F09AFA5114D&uin=0&fromtag=38" + ] } ``` @@ -249,12 +278,183 @@ songIdList | stringArray | 歌曲 id 列表, 需要拼接成 `id1,id2,id3,id4` 字段 | 字段类型 | 字段说明 --- | --- | --- +announce | string | 声明文案 errno | int | 0: 表示没有问题, 其他表示有问题. 详情参考 msg msg | string | 接口返回状态描述 -data | array | item 为对应音乐的 +data | array | item 为对应音乐的播放 url, 直接放到 audio 标签就可以播放 ### 4.6 错误状态码 代码健壮的像一头牛, 不会报错~ -#### 本接口仅用作学习交流之用, 请不要用在不正当手段. 测试服务没有做任何处理, 扛不住 ddos 等等各种攻击, 希望大佬手下留情. +## 5. 搜索 + +### 5.1 功能描述 + +音乐搜索功能, 可以根据音乐信息搜索出匹配的歌曲列表 + +### 5.2 请求说明 + +> 请求说明:
+> 请求方式 GET
+> 请求URL :[/music/search/唐人/1/10](https://music.niubishanshan.top/api/v2/music/search/唐人/1/10) + +### 5.3 请求参数 + +字段 | 字段类型 | 字段说明 +--- | --- | --- +key | string | 搜索关键词 +page | int | (非必须, 默认值为 1)当前页码 +page | int | (非必须, 默认值为 20)每页条数 + +### 5.4 返回结果 + +```json +{ + "announce": "本接口所有数据均来自 QQ 音乐, 仅供学习交流之用,请不要用于商业用途. 如果喜欢请下载 QQ 音乐 APP 畅听.如有侵权请联系微信(QQ): 1363693666, 我会第一时间删除~", + "errno": 0, + "msg": "success", + "data": { + "page": { + "currentNumber": 10, + "currentPage": 1, + "totalNumber": 397 + }, + "songList": [ + { + "songMid": "003ALEZa186Qlq", + "singer": { + "singerName": "孙子涵", + "singerMid": "001oXbjs29oPul" + }, + "songName": "唐人", + "songId": 4823575, + "albumMid": "002CWEnV2g4m3p" + } + ...各种数据... + ] + } +} +``` + +### 5.5 返回参数 + +字段 | 字段类型 | 字段说明 +--- | --- | --- +errno | int | 0: 表示没有问题, 其他表示有问题. 详情参考 msg +announce | string | 声明文案 +msg | string | 接口返回状态描述 +data | object | 返回数据 +  page | object | 分页信息 +    currentNumber | int | 当前返回的条目数 +    currentPage | int | 当前页码 +    totalNumber | int | 总条目数 +  songList | array | 歌曲列表 +    songMid | string | 歌曲id +    singer | string | 歌手名称 +      singerName | string | 歌手名称 +      singerMid | string | 歌手媒体 id, 用户获取歌手头像 +    songName | string | 歌曲名称 +    songId | string | 歌曲 id, 用于获取歌词 +    albumMid | string | 专辑媒体 id, 用于获取专辑封面 url + +### 5.6 错误状态码 + +代码健壮的像一头牛, 不会报错~ + +## 6. 获取歌词(基于歌曲 songid) + +### 6.1 功能描述 + +获取歌词, 这个没啥可以描述的啦~ + +### 6.2 请求说明 + +> 请求说明:
+> 请求方式 GET
+> 请求URL :[/music/lrc/:id](https://music.niubishanshan.top/api/v2/music/lrc/4823575) + +### 6.3 请求参数 + +字段 | 字段类型 | 字段说明 +--- | --- | --- +id | string | 歌曲 id + +### 6.4 返回结果 + +```json +{ + "announce": "本接口所有数据均来自 QQ 音乐, 仅供学习交流之用,请不要用于商业用途. 如果喜欢请下载 QQ 音乐 APP 畅听.如有侵权请联系微信(QQ): 1363693666, 我会第一时间删除~", + "errno": 0, + "msg": "success", + "data": { + "lyric": "[ti:唐人][换行][ar:孙子涵][换行][al:唐朝好男人 电视剧原声带][换行][by:][换行][offset:0][换行][00:00.00]唐人 (《唐朝好男人》电视剧主题曲) - 孙子涵 (Niko Sun)[换行][00:09.66]词:孙子涵[换行][00:19.32]曲:孙子涵[换行][00:28.99]一如昨日烛火 伴扁舟相随[换行][00:32.46][换行][00:33.22]哪有唐人不懂得陶醉[换行][00:36.73]我孤舟 你窈窕 岸上有隐晦[换行][00:40.54][换行][00:41.24]一踏万里与谁相随[换行][00:43.91][换行][00:44.62]你穿错了嫁妆怎能有快乐[换行][00:48.50][换行][00:49.19]再上一层胭脂也不美[换行][00:52.77]一声戛然而止庭前的鞭炮[换行][00:57.10]妄想同你华发的心作废[换行][01:00.27][换行][01:00.84]你说不要自作自受自己创造伤悲[换行][01:04.77]谁都可以彻底忘记谁[换行][01:08.02][换行][01:08.71]你说过往不及回首 别后悔了才会[换行][01:12.90]想方设法的把你追回[换行][01:16.13][换行][01:16.69]你说孤独是诗人应该具有的体会[换行][01:20.81]写歌的人就该有伤悲[换行][01:24.17][换行][01:24.74]我点一丝烛火 一时泛滥了思念[换行][01:28.75]写首小调名字叫后悔[换行][01:32.32][换行][01:33.66]一如昨日烛火 伴着扁舟相随[换行][01:36.91][换行][01:37.53]哪有唐人不懂得陶醉[换行][01:40.67][换行][01:41.48]你穿错了嫁妆怎可能有快乐[换行][01:44.99][换行][01:45.54]再上一层胭脂也不美[换行][01:47.94][换行][01:48.91]你穿错了嫁妆怎能有快乐[换行][01:52.65][换行][01:53.16]再上一层胭脂也不美[换行][01:56.71]一声戛然而止庭前的鞭炮[换行][02:00.45][换行][02:01.03]妄想同你华发的心作废[换行][02:04.27][换行][02:04.88]你说不要自作自受自己创造伤悲[换行][02:08.73]谁都可以彻底忘记谁[换行][02:12.07][换行][02:12.69]你说过往不及回首 别后悔了才会[换行][02:16.90]想方设法的把你追回[换行][02:20.11][换行][02:20.68]你说孤独是诗人应该具有的体会[换行][02:24.90]写歌的人就该有伤悲[换行][02:28.75]我点一丝烛火 一时泛滥了思念[换行][02:32.74]写首小调名字叫后悔[换行][02:36.73][换行][02:40.74]你说不要自作自受自己创造伤悲[换行][02:44.85]谁都可以彻底忘记谁[换行][02:48.62]你说过往不及回首 别后悔了才会[换行][02:52.80]想方设法的把你追回[换行][02:56.10][换行][02:56.63]你说孤独是诗人应该具有的体会[换行][03:00.80]写歌的人就该有伤悲[换行][03:04.64]我点一丝烛火 一时泛滥了思念[换行][03:08.77]写首小调名字叫后悔[换行][03:12.45][换行][03:13.38]一如昨日烛火 伴着扁舟相随[换行][03:17.51]哪有唐人不懂得陶醉[换行][03:20.69][换行][03:21.48]你穿错了嫁妆怎可能有快乐[换行][03:25.56]再上一层胭脂也不美[换行][03:28.19][换行][03:29.39]一如昨日烛火 伴着扁舟相随[换行][03:32.86][换行][03:33.47]哪有唐人不懂得陶醉[换行][03:36.65][换行][03:37.48]你穿错了嫁妆怎可能有快乐[换行][03:41.46]再上一层胭脂也不美" + } +} +``` + +### 6.5 返回参数 + +字段 | 字段类型 | 字段说明 +--- | --- | --- +announce | string | 声明文案 +errno | int | 0: 表示没有问题, 其他表示有问题. 详情参考 msg +msg | string | 接口返回状态描述 +data | object | 返回数据 +  lyric | string | 歌词信息, 由于 JSON 不能添加换行, 所以直接在 lrc 文件本该换行的位置插入了换行标记`[换行]` 前端代码中直接 `lyric.replace(/\[换行\]/g, '\n')` 即可还原 lrc 文件. + +### 6.6 错误状态码 + +代码健壮的像一头牛, 不会报错~ + +## 7. 获取歌曲封面图片和歌手头像图片 + +### 7.1 功能描述 + +获取歌曲封面和歌手的头像图片, 就是播放器用来转圈的那个~ + +### 7.2 请求说明 + +> 请求说明:
+> 请求方式 GET
+> 请求URL :[/music/imgs/{:albummid}/{:singerMid}](https://music.niubishanshan.top/api/v2/music/albumImg/002CWEnV2g4m3p/001oXbjs29oPul) + +### 7.3 请求参数 + +字段 | 字段类型 | 字段说明 +--- | --- | --- +albummid | string | 歌曲所属的专辑的媒体 id +singerMid | string | 歌手的媒体 id + +### 7.4 返回结果 + +```json +{ + "announce": "本接口所有数据均来自 QQ 音乐, 仅供学习交流之用,请不要用于商业用途. 如果喜欢请下载 QQ 音乐 APP 畅听.如有侵权请联系微信(QQ): 1363693666, 我会第一时间删除~", + "errno": 0, + "msg": "success", + "data": { + "albumImgUrl": "https://y.gtimg.cn/music/photo_new/T002R300x300M000002CWEnV2g4m3p.jpg", + "singerAvatarUrl": "https://y.gtimg.cn/music/photo_new/T001R150x150M000001oXbjs29oPul.jpg" + } +} +``` + +### 7.5 返回参数 + +字段 | 字段类型 | 字段说明 +--- | --- | --- +errno | int | 0: 表示没有问题, 其他表示有问题. 详情参考 msg +announce | string | 声明文案 +msg | string | 接口返回状态描述 +data | object | 返回数据 +  albumImgUrl | string | 歌曲所属的专辑封面 url +  singerAvatarUrl | string | 歌手头像 url + +### 7.6 错误状态码 + +代码健壮的像一头牛, 不会报错~ + +# 最后我也立个 flag: 如果这篇文章点赞超过 666, 我写文章分享我爬取数据的过程和实现的方式. 感谢大家的支持... + +#### 本接口仅用作学习交流之用, 请不要用在不正当手段. 测试服务没有做任何处理, 扛不住 ddos 等等各种攻击, 希望大佬手下留情 diff --git a/src/api/index.js b/src/api/index.js index 199a7c8..ebc58c0 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -9,7 +9,7 @@ const router = require('koa-router')() const demo = require('./demo') const music = require('./music') -router.prefix('/api') +router.prefix('/api/v2') router.use('/demo', demo.routes(), demo.allowedMethods()) router.use('/music', music.routes(), music.allowedMethods()) diff --git a/src/api/music.js b/src/api/music.js index 6bde819..cd1d97c 100644 --- a/src/api/music.js +++ b/src/api/music.js @@ -1,17 +1,27 @@ /* + * @file: 音乐模块路由 * @Author: luoquanquan - * @Date: 2018-12-20 15:58:27 + * @Date: 2019-01-04 10:20:10 * @LastEditors: luoquanquan - * @LastEditTime: 2018-12-20 16:03:57 + * @LastEditTime: 2019-01-09 10:33:44 */ const router = require('koa-router')() const { Music } = require('../controllers') +// 获取首页的推荐信息 router.get('/recommend', Music.getRecommend) +// 获取排行榜列表 router.get('/toplist', Music.getToplist) -router.get('/songIdlist/:id', Music.getSongIdlist) -router.get('/songUrllist/:ids', Music.getSongUrllist) +// 获取排行榜内的歌曲列表 router.get('/songList/:id', Music.getSongList) +// 通过歌曲 id 获取歌曲的播放 url +router.get('/songUrllist/:ids', Music.getSongUrllist) +// 通过关键词搜索歌曲列表 +router.get('/search/:w/:page?/:perPage?', Music.search) +// 通过音乐 id 获取歌词 +router.get('/lrc/:id', Music.getLrc) +// 通过专辑 id 获取专辑封面 +router.get('/albumImg/:albummid/:singerMid', Music.getAlbumImg) module.exports = router diff --git a/src/app.js b/src/app.js index b3e064a..fbce5b0 100644 --- a/src/app.js +++ b/src/app.js @@ -1,4 +1,12 @@ +/* + * @file: 文件信息 😄 + * @Author: luoquanquan + * @Date: 2019-01-04 10:20:10 + * @LastEditors: luoquanquan + * @LastEditTime: 2019-01-07 00:40:53 + */ const Koa = require('koa') +const cors = require('@koa/cors') const app = new Koa() @@ -26,6 +34,7 @@ app.use(json()) app.use(require('koa-static')(`${__dirname}/public`)) app.use(views(`${__dirname}/views`, { extension: 'pug' })) app.use(responseFormatter) +app.use(cors()) // logger if (config.log && 0) { diff --git a/src/controllers/music.js b/src/controllers/music.js index 489c75a..0de373f 100644 --- a/src/controllers/music.js +++ b/src/controllers/music.js @@ -1,18 +1,71 @@ /* + * @file: 音乐模块控制器 * @Author: luoquanquan * @Date: 2018-12-20 15:58:19 * @LastEditors: luoquanquan - * @LastEditTime: 2018-12-24 23:19:14 + * @LastEditTime: 2019-01-10 19:30:13 */ + const _ = require('lodash') const request = require('../services/request') const { getReqData } = require('../utils') const { qqMusicCommonBaseUrl, qqMusicUrlBaseUrl, + singerAvatarUrl, defaultHeader, - defaultData + defaultData, + albumImgUrl } = require('../../config') + +const n = e => { + if (e > 65535) { + return String.fromCodePoint(e) + } else { + return String.fromCharCode(e) + } +} + +const getSougListInfo = songlist => songlist.map((song) => { + let songMid, singerName, singerMid, songName, songId, albumMid + try { + ({ + data: { + songmid: songMid, + singer: [{ + name: singerName, + mid: singerMid + }], + songname: songName, + songid: songId, + albummid: albumMid + } + } = song) + } catch (e) { + ({ + songmid: songMid, + singer: [{ + name: singerName, + mid: singerMid + }], + songname: songName, + songid: songId, + albummid: albumMid + } = song) + } + + return { + songMid, + singer: { + singerName, + singerMid + }, + songName, + songId, + albumMid + } +}) + module.exports = { /** * 获取首页推荐列表 @@ -53,22 +106,25 @@ module.exports = { /** * 获取歌单内歌曲id列表 */ - async getSongIdlist (ctx, next) { + async getSongList (ctx, next) { const [{ id }] = getReqData(ctx) const { - update_time, total_song_num, topinfo: _topinfo, songlist: _songlist + update_time: updateTime, total_song_num: totalSongNum, topinfo: _topinfo, songlist } = await request({ url: `${qqMusicCommonBaseUrl}/v8/fcg-bin/fcg_v8_toplist_cp.fcg`, headers: defaultHeader, params: Object.assign({}, defaultData, { topid: id }) }) - const topinfo = _.pick(_topinfo, ['pic_album', 'ListName']) - const songlist = _songlist.map((song) => { - // 一个 song 对象的结构 - const { data: { songmid, singer: [{ name: singer }], songname } } = song - return { songmid, singer, songname } - }) - ctx.body = { update_time, total_song_num, topinfo, songlist } + const topInfo = { + picAlbum: _topinfo.pic_album, + listName: _topinfo.ListName + } + ctx.body = { + updateTime, + totalSongNum, + topInfo, + songList: getSougListInfo(songlist) + } }, /** * 获取歌曲id相关的歌曲url列表 @@ -100,50 +156,72 @@ module.exports = { ctx.body = songUrlList }, - /** - * 直接获取用歌单内的歌曲url列表 - */ - async getSongList (ctx, next) { - const [{ id }] = getReqData(ctx) - - // 重复代码 考虑优化 - // 第一步, 通过排行榜 id 获取排行榜信息 - const { - update_time, total_song_num, topinfo: _topinfo, songlist: _songlist - } = await request({ - url: `${qqMusicCommonBaseUrl}/v8/fcg-bin/fcg_v8_toplist_cp.fcg`, - headers: defaultHeader, - params: Object.assign({}, defaultData, { topid: id }) - }) - const topinfo = _.pick(_topinfo, ['pic_album', 'ListName']) - const songlist = _songlist.map((song) => { - // 一个 song 对象的结构 - const { data: { songmid, singer: [{ name: singer }], songname } } = song - return { songmid, singer, songname } - }) - // 第二步, 通过排行榜歌曲中的歌曲 id 获取每首歌的播放 url - const { req_0: { data: { midurlinfo, sip: [, baseUrl] } } } = await request({ - url: `${qqMusicUrlBaseUrl}/cgi-bin/musicu.fcg`, - headers: defaultHeader, - method: 'POST', + async search (ctx) { + const [{ + w, + page: p = 1, + perPage: n = 20 + }] = getReqData(ctx) + let { data: { - req_0: { - module: 'vkey.GetVkeyServer', - method: 'CgiGetVkey', - param: { - guid: '5579254314', songmid: songlist.map(song => song.songmid), songtype: [], uin: '', loginflag: 1, platform: '23', h5to: 'speed' - } - }, - comm: { - g_tk: 1679324996, uin: '', format: 'json', ct: 23, cv: 0 + song: { + curnum: currentNumber, + curpage: currentPage, + totalnum: totalNumber, + list } } + } = await request({ + url: `${qqMusicCommonBaseUrl}/soso/fcgi-bin/search_for_qq_cp`, + headers: defaultHeader, + method: 'GET', + params: Object.assign({}, defaultData, { w, p, n }) }) - // 第三步, 整合数据 - songlist.forEach((song, index) => { - song.src = `${baseUrl}${midurlinfo[index] && midurlinfo[index].purl}` + ctx.body = { + page: { + currentNumber, + currentPage, + totalNumber + }, + songList: getSougListInfo(list) + } + }, + getAlbumImg (ctx) { + const [{ albummid, singerMid }] = getReqData(ctx) + ctx.body = { + albumImgUrl: albumImgUrl.replace(/ /, albummid), + singerAvatarUrl: singerAvatarUrl.replace(/ /, singerMid) + } + }, + async getLrc (ctx) { + const [{ id: musicid }] = getReqData(ctx) + const reg = /jsonp1\((.*)\)/ + const data = await request({ + url: `${qqMusicCommonBaseUrl}/lyric/fcgi-bin/fcg_query_lyric.fcg`, + headers: defaultHeader, + params: Object.assign({}, defaultData, { + musicid, + nobase64: 1, + jsonpCallback: 'jsonp1' + }) }) - ctx.body = { update_time, total_song_num, topinfo, songlist } + const lrcContent = data.match(reg)[1] + + if (lrcContent) { + let { lyric } = JSON.parse(lrcContent) + lyric = lyric.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/ /g, ' ').replace(/&#(\d+);?/g, function (e, t) { + if (+t === 10) return '[换行]' + return n(t) + }).replace(/&#x([0-9a-f]+);?/gi, function (e, t) { + return n(parseInt(t, 16)) + }) + ctx.body = { lyric } + } else { + ctx.body = { + code: 3000, + msg: '歌词检索失败' + } + } } } diff --git a/src/services/pageAnalyse/getSrcFromMid.js b/src/services/pageAnalyse/getSrcFromMid.js new file mode 100644 index 0000000..ec65bea --- /dev/null +++ b/src/services/pageAnalyse/getSrcFromMid.js @@ -0,0 +1,20 @@ +const PageInit = require('./pageInit') +let face +class GetSrcFromMid extends PageInit { + constructor () { + super() + } + + getInterface () { + if (!face) { + face = new GetSrcFromMid() + } + return face + } + async getData (url) { + const $ = await this.download(url) + console.log($('#h5audio_media').attr('src')) + } +} + +module.exports = GetSrcFromMid.prototype.getInterface() diff --git a/src/services/pageAnalyse/pageInit.js b/src/services/pageAnalyse/pageInit.js new file mode 100644 index 0000000..597b077 --- /dev/null +++ b/src/services/pageAnalyse/pageInit.js @@ -0,0 +1,22 @@ +const request = require('../request') +const cheerio = require('cheerio') +class PageInit { + constructor () { + this.request = request + } + + async download (url) { + const htmlStr = await this.request(url) + return Promise.resolve(this.cheer(htmlStr)) + } + + cheer (htmlStr) { + return cheerio.load(htmlStr) + } + + getData () { + throw new Error('请在子类中重写这个方法') + } +} + +module.exports = PageInit diff --git a/src/services/readMe.md b/src/services/readMe.md index b10d90b..ce8ea7e 100644 --- a/src/services/readMe.md +++ b/src/services/readMe.md @@ -1 +1,5 @@ # 系统级服务 + +# request 请求层封装 + +# pageAnalyse 分析 HTML diff --git a/src/services/request.js b/src/services/request.js index ba63733..1950d41 100644 --- a/src/services/request.js +++ b/src/services/request.js @@ -2,11 +2,16 @@ * @Author: luoquanquan * @Date: 2018-12-20 16:36:56 * @LastEditors: luoquanquan - * @LastEditTime: 2018-12-20 16:36:59 + * @LastEditTime: 2019-01-06 13:19:11 */ const axios = require('axios') -const service = axios.create() +const service = axios.create({ + headers: { + 'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', + referer: 'https://m.y.qq.com/' + } +}) // request interceptor service.interceptors.request.use(