index.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. const axios_1 = require("axios");
  4. const CryptoJs = require("crypto-js");
  5. const pageSize = 20;
  6. function formatMusicItem(_) {
  7. var _a, _b, _c;
  8. const albumid = _.albumid || ((_a = _.album) === null || _a === void 0 ? void 0 : _a.id);
  9. const albummid = _.albummid || ((_b = _.album) === null || _b === void 0 ? void 0 : _b.mid);
  10. const albumname = _.albumname || ((_c = _.album) === null || _c === void 0 ? void 0 : _c.title);
  11. return {
  12. id: _.id || _.songid,
  13. songmid: _.mid || _.songmid,
  14. title: _.title || _.songname,
  15. artist: _.singer.map((s) => s.name).join(", "),
  16. artwork: albummid
  17. ? `https://y.gtimg.cn/music/photo_new/T002R300x300M000${albummid}.jpg`
  18. : undefined,
  19. album: albumname,
  20. lrc: _.lyric || undefined,
  21. albumid: albumid,
  22. albummid: albummid,
  23. };
  24. }
  25. function formatAlbumItem(_) {
  26. return {
  27. id: _.albumID || _.albumid,
  28. albumMID: _.albumMID || _.album_mid,
  29. title: _.albumName || _.album_name,
  30. artwork: _.albumPic ||
  31. `https://y.gtimg.cn/music/photo_new/T002R300x300M000${_.albumMID || _.album_mid}.jpg`,
  32. date: _.publicTime || _.pub_time,
  33. singerID: _.singerID || _.singer_id,
  34. artist: _.singerName || _.singer_name,
  35. singerMID: _.singerMID || _.singer_mid,
  36. description: _.desc,
  37. };
  38. }
  39. function formatArtistItem(_) {
  40. return {
  41. name: _.singerName,
  42. id: _.singerID,
  43. singerMID: _.singerMID,
  44. avatar: _.singerPic,
  45. worksNum: _.songNum,
  46. };
  47. }
  48. const searchTypeMap = {
  49. 0: "song",
  50. 2: "album",
  51. 1: "singer",
  52. 3: "songlist",
  53. 7: "song",
  54. 12: "mv",
  55. };
  56. const headers = {
  57. referer: "https://y.qq.com",
  58. "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",
  59. Cookie: "uin=",
  60. };
  61. const validSongFilter = (item) => {
  62. return item.pay.pay_play === 0 || item.pay.payplay === 0;
  63. };
  64. async function searchBase(query, page, type) {
  65. const res = (await (0, axios_1.default)({
  66. url: "https://u.y.qq.com/cgi-bin/musicu.fcg",
  67. method: "POST",
  68. data: {
  69. req_1: {
  70. method: "DoSearchForQQMusicDesktop",
  71. module: "music.search.SearchCgiService",
  72. param: {
  73. num_per_page: pageSize,
  74. page_num: page,
  75. query: query,
  76. search_type: type,
  77. },
  78. },
  79. },
  80. headers: headers,
  81. xsrfCookieName: "XSRF-TOKEN",
  82. withCredentials: true,
  83. })).data;
  84. return {
  85. isEnd: res.req_1.data.meta.sum <= page * pageSize,
  86. data: res.req_1.data.body[searchTypeMap[type]].list,
  87. };
  88. }
  89. async function searchMusic(query, page) {
  90. const songs = await searchBase(query, page, 0);
  91. return {
  92. isEnd: songs.isEnd,
  93. data: songs.data.filter(validSongFilter).map(formatMusicItem),
  94. };
  95. }
  96. async function searchAlbum(query, page) {
  97. const albums = await searchBase(query, page, 2);
  98. return {
  99. isEnd: albums.isEnd,
  100. data: albums.data.map(formatAlbumItem),
  101. };
  102. }
  103. async function searchArtist(query, page) {
  104. const artists = await searchBase(query, page, 1);
  105. return {
  106. isEnd: artists.isEnd,
  107. data: artists.data.map(formatArtistItem),
  108. };
  109. }
  110. async function searchMusicSheet(query, page) {
  111. const musicSheet = await searchBase(query, page, 3);
  112. return {
  113. isEnd: musicSheet.isEnd,
  114. data: musicSheet.data.map((item) => ({
  115. title: item.dissname,
  116. createAt: item.createtime,
  117. description: item.introduction,
  118. playCount: item.listennum,
  119. worksNums: item.song_count,
  120. artwork: item.imgurl,
  121. id: item.dissid,
  122. artist: item.creator.name,
  123. })),
  124. };
  125. }
  126. async function searchLyric(query, page) {
  127. const songs = await searchBase(query, page, 7);
  128. return {
  129. isEnd: songs.isEnd,
  130. data: songs.data.map((it) => (Object.assign(Object.assign({}, formatMusicItem(it)), { rawLrcTxt: it.content }))),
  131. };
  132. }
  133. function getQueryFromUrl(key, search) {
  134. try {
  135. const sArr = search.split("?");
  136. let s = "";
  137. if (sArr.length > 1) {
  138. s = sArr[1];
  139. }
  140. else {
  141. return key ? undefined : {};
  142. }
  143. const querys = s.split("&");
  144. const result = {};
  145. querys.forEach((item) => {
  146. const temp = item.split("=");
  147. result[temp[0]] = decodeURIComponent(temp[1]);
  148. });
  149. return key ? result[key] : result;
  150. }
  151. catch (err) {
  152. return key ? "" : {};
  153. }
  154. }
  155. function changeUrlQuery(obj, baseUrl) {
  156. const query = getQueryFromUrl(null, baseUrl);
  157. let url = baseUrl.split("?")[0];
  158. const newQuery = Object.assign(Object.assign({}, query), obj);
  159. let queryArr = [];
  160. Object.keys(newQuery).forEach((key) => {
  161. if (newQuery[key] !== undefined && newQuery[key] !== "") {
  162. queryArr.push(`${key}=${encodeURIComponent(newQuery[key])}`);
  163. }
  164. });
  165. return `${url}?${queryArr.join("&")}`.replace(/\?$/, "");
  166. }
  167. const typeMap = {
  168. m4a: {
  169. s: "C400",
  170. e: ".m4a",
  171. },
  172. 128: {
  173. s: "M500",
  174. e: ".mp3",
  175. },
  176. 320: {
  177. s: "M800",
  178. e: ".mp3",
  179. },
  180. ape: {
  181. s: "A000",
  182. e: ".ape",
  183. },
  184. flac: {
  185. s: "F000",
  186. e: ".flac",
  187. },
  188. };
  189. async function getSourceUrl(id, type = "128") {
  190. const mediaId = id;
  191. let uin = "";
  192. const guid = (Math.random() * 10000000).toFixed(0);
  193. const typeObj = typeMap[type];
  194. const file = `${typeObj.s}${id}${mediaId}${typeObj.e}`;
  195. const url = changeUrlQuery({
  196. "-": "getplaysongvkey",
  197. g_tk: 5381,
  198. loginUin: uin,
  199. hostUin: 0,
  200. format: "json",
  201. inCharset: "utf8",
  202. outCharset: "utf-8¬ice=0",
  203. platform: "yqq.json",
  204. needNewCode: 0,
  205. data: JSON.stringify({
  206. req_0: {
  207. module: "vkey.GetVkeyServer",
  208. method: "CgiGetVkey",
  209. param: {
  210. filename: [file],
  211. guid: guid,
  212. songmid: [id],
  213. songtype: [0],
  214. uin: uin,
  215. loginflag: 1,
  216. platform: "20",
  217. },
  218. },
  219. comm: {
  220. uin: uin,
  221. format: "json",
  222. ct: 19,
  223. cv: 0,
  224. authst: "",
  225. },
  226. }),
  227. }, "https://u.y.qq.com/cgi-bin/musicu.fcg");
  228. return (await (0, axios_1.default)({
  229. method: "GET",
  230. url: url,
  231. xsrfCookieName: "XSRF-TOKEN",
  232. withCredentials: true,
  233. })).data;
  234. }
  235. async function getAlbumInfo(albumItem) {
  236. const url = changeUrlQuery({
  237. data: JSON.stringify({
  238. comm: {
  239. ct: 24,
  240. cv: 10000,
  241. },
  242. albumSonglist: {
  243. method: "GetAlbumSongList",
  244. param: {
  245. albumMid: albumItem.albumMID,
  246. albumID: 0,
  247. begin: 0,
  248. num: 999,
  249. order: 2,
  250. },
  251. module: "music.musichallAlbum.AlbumSongList",
  252. },
  253. }),
  254. }, "https://u.y.qq.com/cgi-bin/musicu.fcg?g_tk=5381&format=json&inCharset=utf8&outCharset=utf-8");
  255. const res = (await (0, axios_1.default)({
  256. url: url,
  257. headers: headers,
  258. xsrfCookieName: "XSRF-TOKEN",
  259. withCredentials: true,
  260. })).data;
  261. return {
  262. musicList: res.albumSonglist.data.songList
  263. .filter((_) => validSongFilter(_.songInfo))
  264. .map((item) => {
  265. const _ = item.songInfo;
  266. return formatMusicItem(_);
  267. }),
  268. };
  269. }
  270. async function getArtistSongs(artistItem, page) {
  271. const url = changeUrlQuery({
  272. data: JSON.stringify({
  273. comm: {
  274. ct: 24,
  275. cv: 0,
  276. },
  277. singer: {
  278. method: "get_singer_detail_info",
  279. param: {
  280. sort: 5,
  281. singermid: artistItem.singerMID,
  282. sin: (page - 1) * pageSize,
  283. num: pageSize,
  284. },
  285. module: "music.web_singer_info_svr",
  286. },
  287. }),
  288. }, "http://u.y.qq.com/cgi-bin/musicu.fcg");
  289. const res = (await (0, axios_1.default)({
  290. url: url,
  291. method: "get",
  292. headers: headers,
  293. xsrfCookieName: "XSRF-TOKEN",
  294. withCredentials: true,
  295. })).data;
  296. return {
  297. isEnd: res.singer.data.total_song <= page * pageSize,
  298. data: res.singer.data.songlist.filter(validSongFilter).map(formatMusicItem),
  299. };
  300. }
  301. async function getArtistAlbums(artistItem, page) {
  302. const url = changeUrlQuery({
  303. data: JSON.stringify({
  304. comm: {
  305. ct: 24,
  306. cv: 0,
  307. },
  308. singerAlbum: {
  309. method: "get_singer_album",
  310. param: {
  311. singermid: artistItem.singerMID,
  312. order: "time",
  313. begin: (page - 1) * pageSize,
  314. num: pageSize / 1,
  315. exstatus: 1,
  316. },
  317. module: "music.web_singer_info_svr",
  318. },
  319. }),
  320. }, "http://u.y.qq.com/cgi-bin/musicu.fcg");
  321. const res = (await (0, axios_1.default)({
  322. url,
  323. method: "get",
  324. headers: headers,
  325. xsrfCookieName: "XSRF-TOKEN",
  326. withCredentials: true,
  327. })).data;
  328. return {
  329. isEnd: res.singerAlbum.data.total <= page * pageSize,
  330. data: res.singerAlbum.data.list.map(formatAlbumItem),
  331. };
  332. }
  333. async function getArtistWorks(artistItem, page, type) {
  334. if (type === "music") {
  335. return getArtistSongs(artistItem, page);
  336. }
  337. if (type === "album") {
  338. return getArtistAlbums(artistItem, page);
  339. }
  340. }
  341. async function getLyric(musicItem) {
  342. const result = (await (0, axios_1.default)({
  343. 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`,
  344. headers: { Referer: "https://y.qq.com", Cookie: "uin=" },
  345. method: "get",
  346. xsrfCookieName: "XSRF-TOKEN",
  347. withCredentials: true,
  348. })).data;
  349. const res = JSON.parse(result.replace(/callback\(|MusicJsonCallback\(|jsonCallback\(|\)$/g, ""));
  350. return {
  351. rawLrc: CryptoJs.enc.Base64.parse(res.lyric).toString(CryptoJs.enc.Utf8),
  352. };
  353. }
  354. async function importMusicSheet(urlLike) {
  355. let id;
  356. if (!id) {
  357. id = (urlLike.match(/https?:\/\/i\.y\.qq\.com\/n2\/m\/share\/details\/taoge\.html\?.*id=([0-9]+)/) || [])[1];
  358. }
  359. if (!id) {
  360. id = (urlLike.match(/https?:\/\/y\.qq\.com\/n\/ryqq\/playlist\/([0-9]+)/) ||
  361. [])[1];
  362. }
  363. if (!id) {
  364. id = (urlLike.match(/^(\d+)$/) || [])[1];
  365. }
  366. if (!id) {
  367. return;
  368. }
  369. const result = (await (0, axios_1.default)({
  370. url: `http://c.y.qq.com/qzone/fcg-bin/fcg_ucc_getcdinfo_byids_cp.fcg?type=1&utf8=1&disstid=${id}&loginUin=0`,
  371. headers: { Referer: "https://y.qq.com/n/yqq/playlist", Cookie: "uin=" },
  372. method: "get",
  373. xsrfCookieName: "XSRF-TOKEN",
  374. withCredentials: true,
  375. })).data;
  376. const res = JSON.parse(result.replace(/callback\(|MusicJsonCallback\(|jsonCallback\(|\)$/g, ""));
  377. return res.cdlist[0].songlist.filter(validSongFilter).map(formatMusicItem);
  378. }
  379. async function getTopLists() {
  380. const list = await (0, axios_1.default)({
  381. url: "https://u.y.qq.com/cgi-bin/musicu.fcg?_=1577086820633&data=%7B%22comm%22%3A%7B%22g_tk%22%3A5381%2C%22uin%22%3A123456%2C%22format%22%3A%22json%22%2C%22inCharset%22%3A%22utf-8%22%2C%22outCharset%22%3A%22utf-8%22%2C%22notice%22%3A0%2C%22platform%22%3A%22h5%22%2C%22needNewCode%22%3A1%2C%22ct%22%3A23%2C%22cv%22%3A0%7D%2C%22topList%22%3A%7B%22module%22%3A%22musicToplist.ToplistInfoServer%22%2C%22method%22%3A%22GetAll%22%2C%22param%22%3A%7B%7D%7D%7D",
  382. method: "get",
  383. headers: {
  384. Cookie: "uin=",
  385. },
  386. xsrfCookieName: "XSRF-TOKEN",
  387. withCredentials: true,
  388. });
  389. return list.data.topList.data.group.map((e) => ({
  390. title: e.groupName,
  391. data: e.toplist.map((_) => ({
  392. id: _.topId,
  393. description: _.intro,
  394. title: _.title,
  395. period: _.period,
  396. coverImg: _.headPicUrl || _.frontPicUrl,
  397. })),
  398. }));
  399. }
  400. async function getTopListDetail(topListItem) {
  401. var _a;
  402. const res = await (0, axios_1.default)({
  403. url: `https://u.y.qq.com/cgi-bin/musicu.fcg?g_tk=5381&data=%7B%22detail%22%3A%7B%22module%22%3A%22musicToplist.ToplistInfoServer%22%2C%22method%22%3A%22GetDetail%22%2C%22param%22%3A%7B%22topId%22%3A${topListItem.id}%2C%22offset%22%3A0%2C%22num%22%3A100%2C%22period%22%3A%22${(_a = topListItem.period) !== null && _a !== void 0 ? _a : ""}%22%7D%7D%2C%22comm%22%3A%7B%22ct%22%3A24%2C%22cv%22%3A0%7D%7D`,
  404. method: "get",
  405. headers: {
  406. Cookie: "uin=",
  407. },
  408. xsrfCookieName: "XSRF-TOKEN",
  409. withCredentials: true,
  410. });
  411. return Object.assign(Object.assign({}, topListItem), { musicList: res.data.detail.data.songInfoList
  412. .filter(validSongFilter)
  413. .map(formatMusicItem) });
  414. }
  415. async function getRecommendSheetTags() {
  416. const res = (await axios_1.default.get("https://c.y.qq.com/splcloud/fcgi-bin/fcg_get_diss_tag_conf.fcg?format=json&inCharset=utf8&outCharset=utf-8", {
  417. headers: {
  418. referer: "https://y.qq.com/",
  419. },
  420. })).data.data.categories;
  421. const data = res.slice(1).map((_) => ({
  422. title: _.categoryGroupName,
  423. data: _.items.map((tag) => ({
  424. id: tag.categoryId,
  425. title: tag.categoryName,
  426. })),
  427. }));
  428. const pinned = [];
  429. for (let d of data) {
  430. if (d.data.length) {
  431. pinned.push(d.data[0]);
  432. }
  433. }
  434. return {
  435. pinned,
  436. data,
  437. };
  438. }
  439. async function getRecommendSheetsByTag(tag, page) {
  440. const pageSize = 20;
  441. const rawRes = (await axios_1.default.get("https://c.y.qq.com/splcloud/fcgi-bin/fcg_get_diss_by_tag.fcg", {
  442. headers: {
  443. referer: "https://y.qq.com/",
  444. },
  445. params: {
  446. inCharset: "utf8",
  447. outCharset: "utf-8",
  448. sortId: 5,
  449. categoryId: (tag === null || tag === void 0 ? void 0 : tag.id) || "10000000",
  450. sin: pageSize * (page - 1),
  451. ein: page * pageSize - 1,
  452. },
  453. })).data;
  454. const res = JSON.parse(rawRes.replace(/callback\(|MusicJsonCallback\(|jsonCallback\(|\)$/g, "")).data;
  455. const isEnd = res.sum <= page * pageSize;
  456. const data = res.list.map((item) => {
  457. var _a, _b;
  458. return ({
  459. id: item.dissid,
  460. createTime: item.createTime,
  461. title: item.dissname,
  462. artwork: item.imgurl,
  463. description: item.introduction,
  464. playCount: item.listennum,
  465. artist: (_b = (_a = item.creator) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : "",
  466. });
  467. });
  468. return {
  469. isEnd,
  470. data,
  471. };
  472. }
  473. async function getMusicSheetInfo(sheet, page) {
  474. const data = await importMusicSheet(sheet.id);
  475. return {
  476. isEnd: true,
  477. musicList: data,
  478. };
  479. }
  480. module.exports = {
  481. platform: "QQ音乐",
  482. version: "0.2.1",
  483. srcUrl: "https://gitee.com/maotoumao/MusicFreePlugins/raw/v0.1/dist/qq/index.js",
  484. cacheControl: "no-cache",
  485. hints: {
  486. importMusicSheet: [
  487. "QQ音乐APP:自建歌单-分享-分享到微信好友/QQ好友;然后点开并复制链接,直接粘贴即可",
  488. "H5:复制URL并粘贴,或者直接输入纯数字歌单ID即可",
  489. "导入过程中会过滤掉所有VIP/试听/收费音乐,导入时间和歌单大小有关,请耐心等待",
  490. ],
  491. },
  492. primaryKey: ['id', 'songmid'],
  493. supportedSearchType: ["music", "album", "sheet", "artist", "lyric"],
  494. async search(query, page, type) {
  495. if (type === "music") {
  496. return await searchMusic(query, page);
  497. }
  498. if (type === "album") {
  499. return await searchAlbum(query, page);
  500. }
  501. if (type === "artist") {
  502. return await searchArtist(query, page);
  503. }
  504. if (type === "sheet") {
  505. return await searchMusicSheet(query, page);
  506. }
  507. if (type === "lyric") {
  508. return await searchLyric(query, page);
  509. }
  510. },
  511. async getMediaSource(musicItem, quality) {
  512. let purl = "";
  513. let domain = "";
  514. let type = "128";
  515. if (quality === "standard") {
  516. type = "320";
  517. }
  518. else if (quality === "high") {
  519. type = "m4a";
  520. }
  521. else if (quality === "super") {
  522. type = "flac";
  523. }
  524. const result = await getSourceUrl(musicItem.songmid, type);
  525. if (result.req_0 && result.req_0.data && result.req_0.data.midurlinfo) {
  526. purl = result.req_0.data.midurlinfo[0].purl;
  527. }
  528. if (!purl) {
  529. return null;
  530. }
  531. if (domain === "") {
  532. domain =
  533. result.req_0.data.sip.find((i) => !i.startsWith("http://ws")) ||
  534. result.req_0.data.sip[0];
  535. }
  536. return {
  537. url: `${domain}${purl}`,
  538. };
  539. },
  540. getLyric,
  541. getAlbumInfo,
  542. getArtistWorks,
  543. importMusicSheet,
  544. getTopLists,
  545. getTopListDetail,
  546. getRecommendSheetTags,
  547. getRecommendSheetsByTag,
  548. getMusicSheetInfo,
  549. };