QQ音乐.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. function qq(packages) {
  2. const { axios, CryptoJs } = packages;
  3. const pageSize = 20;
  4. function formatMusicItem(_) {
  5. const albumid = _.albumid || (_.album || {}).id;
  6. const albummid = _.albummid || (_.album || {}).mid;
  7. const albumname = _.albumname || (_.album || {}).title;
  8. return {
  9. id: _.id || _.songid,
  10. songmid: _.mid || _.songmid,
  11. title: _.title || _.songname,
  12. artist: _.singer.map(s => s.name).join(', '),
  13. artwork: albummid ? `https://y.gtimg.cn/music/photo_new/T002R300x300M000${albummid}.jpg` : undefined,
  14. album: albumname,
  15. lrc: _.lyric || undefined,
  16. albumid: albumid,
  17. albummid: albummid,
  18. }
  19. }
  20. function formatAlbumItem(_) {
  21. return {
  22. id: _.albumID || _.albumid,
  23. albumMID: _.albumMID || _.album_mid,
  24. title: _.albumName || _.album_name,
  25. artwork: _.albumPic || `https://y.gtimg.cn/music/photo_new/T002R300x300M000${_.albumMID || _.album_mid}.jpg`,
  26. date: _.publicTime || _.pub_time,
  27. singerID: _.singerID || _.singer_id,
  28. artist: _.singerName || _.singer_name,
  29. singerMID: _.singerMID || _.singer_mid,
  30. description: _.desc
  31. }
  32. }
  33. function formatArtistItem(_) {
  34. return {
  35. name: _.singerName,
  36. id: _.singerID,
  37. singerMID: _.singerMID,
  38. avatar: _.singerPic,
  39. worksNum: _.songNum
  40. }
  41. }
  42. const searchTypeMap = {
  43. 0: 'song',
  44. 2: 'album',
  45. 1: 'singer',
  46. 3: 'songlist',
  47. 7: 'lyric',
  48. 12: 'mv',
  49. };
  50. const headers = {
  51. referer: 'https://y.qq.com',
  52. 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36',
  53. Cookie: 'uin='
  54. }
  55. const validSongFilter = (item) => {
  56. return item.pay.pay_play === 0 || item.pay.payplay === 0
  57. }
  58. async function searchBase(query, page, type) {
  59. const res = (await axios({
  60. "url": "https://u.y.qq.com/cgi-bin/musicu.fcg",
  61. "method": "POST",
  62. "data": {
  63. "req_1": {
  64. "method": "DoSearchForQQMusicDesktop",
  65. "module": "music.search.SearchCgiService",
  66. "param": {
  67. "num_per_page": pageSize,
  68. "page_num": page,
  69. "query": query,
  70. "search_type": type
  71. }
  72. }
  73. },
  74. "headers": headers,
  75. "xsrfCookieName": "XSRF-TOKEN",
  76. "withCredentials": true
  77. }
  78. )).data;
  79. return {
  80. isEnd: res.req_1.data.meta.sum <= page * pageSize,
  81. data: res.req_1.data.body[searchTypeMap[type]].list
  82. }
  83. }
  84. async function searchMusic(query, page) {
  85. const songs = await searchBase(query, page, 0);
  86. return {
  87. isEnd: songs.isEnd,
  88. data: songs.data.filter(validSongFilter).map(formatMusicItem)
  89. }
  90. }
  91. async function searchAlbum(query, page) {
  92. const albums = await searchBase(query, page, 2);
  93. return {
  94. isEnd: albums.isEnd,
  95. data: albums.data.map(formatAlbumItem)
  96. }
  97. }
  98. async function searchArtist(query, page) {
  99. const artists = await searchBase(query, page, 1);
  100. return {
  101. isEnd: artists.isEnd,
  102. data: artists.data.map(formatArtistItem)
  103. }
  104. }
  105. function getQueryFromUrl(key, search) {
  106. try {
  107. const sArr = search.split('?');
  108. let s = '';
  109. if (sArr.length > 1) {
  110. s = sArr[1];
  111. } else {
  112. return key ? undefined : {};
  113. }
  114. const querys = s.split('&');
  115. const result = {};
  116. querys.forEach((item) => {
  117. const temp = item.split('=');
  118. result[temp[0]] = decodeURIComponent(temp[1]);
  119. });
  120. return key ? result[key] : result;
  121. } catch (err) {
  122. // 除去search为空等异常
  123. return key ? '' : {};
  124. }
  125. }
  126. // geturl
  127. function changeUrlQuery(obj, baseUrl) {
  128. const query = getQueryFromUrl(null, baseUrl);
  129. let url = baseUrl.split('?')[0];
  130. const newQuery = { ...query, ...obj };
  131. let queryArr = [];
  132. Object.keys(newQuery).forEach((key) => {
  133. if (newQuery[key] !== undefined && newQuery[key] !== '') {
  134. queryArr.push(`${key}=${encodeURIComponent(newQuery[key])}`);
  135. }
  136. });
  137. return `${url}?${queryArr.join('&')}`.replace(/\?$/, '');
  138. }
  139. const typeMap = {
  140. m4a: {
  141. s: 'C400',
  142. e: '.m4a',
  143. },
  144. 128: {
  145. s: 'M500',
  146. e: '.mp3',
  147. },
  148. 320: {
  149. s: 'M800',
  150. e: '.mp3',
  151. },
  152. ape: {
  153. s: 'A000',
  154. e: '.ape',
  155. },
  156. flac: {
  157. s: 'F000',
  158. e: '.flac',
  159. },
  160. };
  161. async function getSourceUrl(id, type = '128') {
  162. const mediaId = id;
  163. let uin = ''
  164. const guid = (Math.random() * 10000000).toFixed(0);
  165. const typeObj = typeMap[type];
  166. const file = `${typeObj.s}${id}${mediaId}${typeObj.e}`;
  167. const url = changeUrlQuery({
  168. '-': 'getplaysongvkey',
  169. g_tk: 5381,
  170. loginUin: uin,
  171. hostUin: 0,
  172. format: 'json',
  173. inCharset: 'utf8',
  174. outCharset: 'utf-8¬ice=0',
  175. platform: 'yqq.json',
  176. needNewCode: 0,
  177. data: JSON.stringify({
  178. req_0: {
  179. module: 'vkey.GetVkeyServer',
  180. method: 'CgiGetVkey',
  181. param: {
  182. filename: [file],
  183. guid: guid,
  184. songmid: [id],
  185. songtype: [0],
  186. uin: uin,
  187. loginflag: 1,
  188. platform: '20',
  189. },
  190. },
  191. comm: {
  192. uin: uin,
  193. format: 'json',
  194. ct: 19,
  195. cv: 0,
  196. authst: '',
  197. },
  198. }),
  199. }, 'https://u.y.qq.com/cgi-bin/musicu.fcg')
  200. return (await axios({
  201. method: 'GET',
  202. url: url,
  203. xsrfCookieName: 'XSRF-TOKEN',
  204. withCredentials: true
  205. })).data
  206. }
  207. async function getAlbumInfo(albumItem) {
  208. const url = changeUrlQuery({
  209. data: JSON.stringify({
  210. comm: {
  211. ct: 24,
  212. cv: 10000
  213. },
  214. albumSonglist: {
  215. method: "GetAlbumSongList",
  216. param: {
  217. albumMid: albumItem.albumMID,
  218. albumID: 0,
  219. begin: 0,
  220. num: 999,
  221. order: 2
  222. },
  223. module: "music.musichallAlbum.AlbumSongList"
  224. }
  225. })
  226. }, 'https://u.y.qq.com/cgi-bin/musicu.fcg?g_tk=5381&format=json&inCharset=utf8&outCharset=utf-8');
  227. const res = (await axios({
  228. "url": url,
  229. "headers": headers,
  230. "xsrfCookieName": "XSRF-TOKEN",
  231. "withCredentials": true
  232. }
  233. )).data;
  234. return {
  235. ...albumItem,
  236. musicList: res.albumSonglist.data.songList.filter(_ => validSongFilter(_.songInfo)).map((item) => {
  237. const _ = item.songInfo;
  238. return formatMusicItem(_)
  239. })
  240. }
  241. }
  242. async function getArtistSongs(artistItem, page) {
  243. const url = changeUrlQuery({
  244. data: JSON.stringify({
  245. comm: {
  246. ct: 24,
  247. cv: 0
  248. },
  249. singer: {
  250. method: "get_singer_detail_info",
  251. param: {
  252. sort: 5,
  253. singermid: artistItem.singerMID,
  254. sin: (page - 1) * pageSize,
  255. num: pageSize,
  256. },
  257. module: "music.web_singer_info_svr"
  258. }
  259. })
  260. }, 'http://u.y.qq.com/cgi-bin/musicu.fcg')
  261. const res = (await axios({
  262. "url": url,
  263. "method": "get",
  264. "headers": headers,
  265. "xsrfCookieName": "XSRF-TOKEN",
  266. "withCredentials": true
  267. }
  268. )).data;
  269. return {
  270. isEnd: res.singer.data.total_song <= page * pageSize,
  271. data: res.singer.data.songlist.filter(validSongFilter).map(formatMusicItem)
  272. }
  273. }
  274. async function getArtistAlbums(artistItem, page) {
  275. const url = changeUrlQuery({
  276. data: JSON.stringify({
  277. comm: {
  278. ct: 24,
  279. cv: 0
  280. },
  281. singerAlbum: {
  282. method: "get_singer_album",
  283. param: {
  284. singermid: artistItem.singerMID,
  285. order: "time",
  286. begin: (page - 1) * pageSize,
  287. num: pageSize / 1,
  288. exstatus: 1
  289. },
  290. module: "music.web_singer_info_svr"
  291. }
  292. })
  293. }, 'http://u.y.qq.com/cgi-bin/musicu.fcg');
  294. const res = (await axios({
  295. url,
  296. "method": "get",
  297. "headers": headers,
  298. "xsrfCookieName": "XSRF-TOKEN",
  299. "withCredentials": true
  300. }
  301. )).data;
  302. return {
  303. isEnd: res.singerAlbum.data.total <= page * pageSize,
  304. data: res.singerAlbum.data.list.map(formatAlbumItem)
  305. }
  306. }
  307. async function getArtistWorks(artistItem, page, type) {
  308. if (type === 'music') {
  309. return getArtistSongs(artistItem, page);
  310. }
  311. if (type === 'album') {
  312. return getArtistAlbums(artistItem, page);
  313. }
  314. }
  315. async function getLyric(musicItem) {
  316. const result = (await axios({
  317. "url": `http://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg?songmid=${musicItem.songmid}&pcachetime=${new Date().getTime()}&g_tk=5381&loginUin=0&hostUin=0&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq&needNewCode=0`,
  318. "headers": { "Referer": "https://y.qq.com", "Cookie": "uin=" },
  319. "method": "get",
  320. "xsrfCookieName": "XSRF-TOKEN",
  321. "withCredentials": true
  322. }
  323. )).data;
  324. const res = JSON.parse(result.replace(/callback\(|MusicJsonCallback\(|jsonCallback\(|\)$/g, ''))
  325. return {
  326. rawLrc: CryptoJs.enc.Base64.parse(res.lyric).toString(CryptoJs.enc.Utf8)
  327. }
  328. }
  329. async function importMusicSheet(urlLike) {
  330. //
  331. let id;
  332. if (!id) {
  333. id = (urlLike.match(/https?:\/\/i\.y\.qq\.com\/n2\/m\/share\/details\/taoge\.html\?.*id=([0-9]+)/) || [])[1];
  334. }
  335. if (!id) {
  336. id = (urlLike.match(/https?:\/\/y\.qq\.com\/n\/ryqq\/playlist\/([0-9]+)/) || [])[1];
  337. }
  338. if (!id) {
  339. return;
  340. }
  341. const result = (await axios({
  342. "url": `http://c.y.qq.com/qzone/fcg-bin/fcg_ucc_getcdinfo_byids_cp.fcg?type=1&utf8=1&disstid=${id}&loginUin=0`,
  343. "headers": { "Referer": "https://y.qq.com/n/yqq/playlist", "Cookie": "uin=" },
  344. "method": "get",
  345. "xsrfCookieName": "XSRF-TOKEN",
  346. "withCredentials": true
  347. }
  348. )).data;
  349. const res = JSON.parse(result.replace(/callback\(|MusicJsonCallback\(|jsonCallback\(|\)$/g, ''))
  350. console.log(res);
  351. return res.cdlist[0].songlist.filter(validSongFilter).map(formatMusicItem);
  352. }
  353. // 接口参考:https://jsososo.github.io/QQMusicApi/#/
  354. return {
  355. platform: 'QQ音乐',
  356. version: '0.0.2',
  357. srcUrl: 'https://gitee.com/maotoumao/MusicFreePlugins/raw/master/qq.js',
  358. cacheControl: 'no-cache',
  359. async search(query, page, type) {
  360. if (type === 'music') {
  361. return await searchMusic(query, page);
  362. }
  363. if (type === 'album') {
  364. return await searchAlbum(query, page);
  365. }
  366. if (type === 'artist') {
  367. return await searchArtist(query, page);
  368. }
  369. },
  370. async getMediaSource(musicItem) {
  371. let purl = '';
  372. let domain = '';
  373. const result = await getSourceUrl(musicItem.songmid);
  374. if (result.req_0 && result.req_0.data && result.req_0.data.midurlinfo) {
  375. purl = result.req_0.data.midurlinfo[0].purl;
  376. }
  377. if (domain === '') {
  378. domain =
  379. result.req_0.data.sip.find(i => !i.startsWith('http://ws')) ||
  380. result.req_0.data.sip[0];
  381. }
  382. return {
  383. url: `${domain}${purl}`
  384. };
  385. },
  386. getLyric,
  387. getAlbumInfo,
  388. getArtistWorks,
  389. importMusicSheet
  390. }
  391. }