diff --git a/apps/neteasepro.js b/apps/neteasepro.js index 02b6f21..61fdbb3 100644 --- a/apps/neteasepro.js +++ b/apps/neteasepro.js @@ -2,8 +2,9 @@ import plugin from "../../../lib/plugins/plugin.js"; import axios from "axios"; import fs from 'node:fs'; import {segment} from "oicq"; -import {getQrCode, getKey, getLoginStatus, getDailyRecommend, getCookies} from '../utils/netease.js'; +import {getQrCode, getKey, getLoginStatus, getDailyRecommend, getCookies, getUserRecord, checkMusic, getSong} from '../utils/netease.js'; import {ha12store, store2ha1} from '../utils/encrypt.js'; +import fetch from "node-fetch"; export class neteasepro extends plugin { @@ -27,13 +28,21 @@ export class neteasepro extends plugin { { reg: '#网易云每日推荐', fnc: 'neteaseDailyRecommend' - } + }, + { + reg: '#网易云听歌排行', + fnc: 'neteaseListenRank' + }, + { + reg: "music.163.com", + fnc: "netease", + }, ] }) } async neteaseCloudLogin(e) { - let userInfo; + let neteaseCookie; // 如果不存在cookie if (!await redis.exists(await this.getRedisKey(e.user_id))) { // 获取密钥 @@ -49,44 +58,121 @@ export class neteasepro extends plugin { // 定时轮询 await this.poll(key).then(async cookie => { // 存放到redis - await redis.set(await this.getRedisKey(e.user_id), ha12store(cookie)) + neteaseCookie = cookie }); + } else { + // 已经登陆过的,直接从redis取出 + neteaseCookie = await store2ha1(JSON.parse(await redis.get(await this.getRedisKey(e.user_id))).cookie) } - // 从redis中获取 - const realCookie = await store2ha1(await redis.get(await this.getRedisKey(e.user_id))); // 获取用户信息 - userInfo = await getLoginStatus(realCookie); + const userInfo = await getLoginStatus(neteaseCookie); // 提取信息 - const {nickname, avatarUrl} = userInfo.profile; + const {userId, nickname, avatarUrl} = userInfo.profile; e.reply(["欢迎使用 🎶网易云音乐 🎶," + nickname, segment.image(avatarUrl)]) + // 重组后存放到redis {uid, cookie} + await redis.set(await this.getRedisKey(e.user_id), JSON.stringify({ + uid: userId, + cookie: (await ha12store(neteaseCookie)) + })); + return true } async neteaseDailyRecommend(e) { - const realCookie = await this.aopBefore(e); + const realCookie = (await this.aopBefore(e)).cookie; if (realCookie === "") { return true; } // 获取每日推荐所有数据 const dailyRecommend = await getDailyRecommend(realCookie); // 由于数据过大,取前10 - let combineMsg = [] - dailyRecommend.dailySongs.slice(0, 10).forEach(item => { - combineMsg.push([`${item?.id}: ${item?.name}-${item?.ar?.[0].name}-${item?.al?.name}`, segment.image(item?.al?.picUrl)]) + const combineMsg = await dailyRecommend.dailySongs.slice(0, 10).map(item => { + // 组合数据 + return { + message: [segment.text(`${item?.id}: ${item?.name}-${item?.ar?.[0].name}-${item?.al?.name}`) + , segment.image(item?.al?.picUrl)], + nickname: e.sender.card || e.user_id, + user_id: e.user_id, + } }) await e.reply(await Bot.makeForwardMsg(combineMsg)); } - // 切面方法检测cookie + async neteaseListenRank(e) { + const userInfo = await this.aopBefore(e) + const realCookie = userInfo.cookie; + if (realCookie === "") { + return true; + } + // 获取用户id + const uid = userInfo.uid; + // 获取听歌排行榜 + const userRecord = await getUserRecord(uid) + let rankId = 0; + e.reply(" 😘亲,这是你的听歌排行榜Top10") + const rank = userRecord.weekData.slice(0, 10).map(item => { + // 组合数据 + const song = item.song; + rankId++; + return { + message: [segment.text(`No.${rankId} ${song?.id}: ${song?.name}-${song?.ar?.[0].name}-${song?.al?.name}`) + , segment.image(song?.al?.picUrl)], + nickname: e.sender.card || e.user_id, + user_id: e.user_id, + } + }) + await e.reply(await Bot.makeForwardMsg(rank)); + } + + async netease(e) { + const message = e.msg === undefined ? e.message.shift().data.replaceAll("\\", "") : e.msg.trim(); + const musicUrlReg = /(http:|https:)\/\/music.163.com\/song\/media\/outer\/url\?id=(\d+)/; + const musicUrlReg2 = /(http:|https:)\/\/y.music.163.com\/m\/song\?(.*)&id=(\d+)/; + const id = musicUrlReg2.exec(message)[3] || musicUrlReg.exec(message)[2] || /id=(\d+)/.exec(message)[1]; + const musicJson = JSON.parse(message) + const {musicUrl, preview, title, desc} = musicJson.meta.music || musicJson.meta.news; + console.log(musicUrl, preview, title, desc) + // 如果没有登陆,就使用官方接口 + e.reply([`识别:网易云音乐,${title}--${desc}`, segment.image(preview)]); + if (!await redis.exists(await this.getRedisKey(e.user_id))) { + this.downloadMp3(`music.163.com/song/media/outer/url?id=${id}`, 'follow').then(path => { + Bot.acquireGfs(e.group_id).upload(fs.readFileSync(path), '/', `${title.replace(/[\/\?<>\\:\*\|".… ]/g, '')}.mp3`) + }) + .catch(err => { + console.error(`下载音乐失败,错误信息为: ${err.message}`); + }); + return true; + } + // 检查当前歌曲是否可用 + const checkOne = await checkMusic(id); + if (checkOne.success === 'false') { + e.reply(checkOne.message); + return true; + } + const userInfo = await this.aopBefore(e) + // 可用,开始下载 + const userDownloadUrl = (await getSong(id, await userInfo.cookie))[0].url + await this.downloadMp3(userDownloadUrl).then(path => { + Bot.acquireGfs(e.group_id).upload(fs.readFileSync(path), '/', `${title.replace(/[\/\?<>\\:\*\|".… ]/g, '')}.mp3`) + }) + .catch(err => { + console.error(`下载音乐失败,错误信息为: ${err.message}`); + }); + return true; + } + + // 切面方法检测cookie & 获取cookie和uid async aopBefore(e) { // 取出cookie - const cookie = await redis.get(await this.getRedisKey(e.user_id)); + let userInfo = JSON.parse(await redis.get(await this.getRedisKey(e.user_id))); + const cookie = userInfo.cookie; // 如果不存在cookie if (!cookie) { e.reply("请先#网易云登录"); return ""; } // 解析cookie - return store2ha1(cookie); + userInfo.cookie = store2ha1(cookie) + return userInfo; } // 下载二维码 @@ -131,6 +217,40 @@ export class neteasepro extends plugin { }); } + /** + * 下载mp3 + * @param mp3Url + * @param redirect + * @returns {Promise} + */ + async downloadMp3(mp3Url, redirect='manual') { + return fetch(mp3Url, { + headers: { + "User-Agent": + "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Mobile Safari/537.36", + }, + responseType: "stream", + redirect: redirect + }) + .then(res => { + const path = `./data/rcmp4/${this.e.group_id || this.e.user_id}/temp.mp3` + const fileStream = fs.createWriteStream(path); + res.body.pipe(fileStream); + return new Promise((resolve, reject) => { + fileStream.on("finish", () => { + fileStream.close(() => { + resolve(path); + }); + }); + fileStream.on("error", err => { + fs.unlink(path, () => { + reject(err); + }); + }); + }); + }) + } + // 获取redis的key async getRedisKey(user_id) { return `Yz:rconsole:netease:${user_id}`; diff --git a/apps/tools.js b/apps/tools.js index 5174c57..bf4f13a 100644 --- a/apps/tools.js +++ b/apps/tools.js @@ -48,10 +48,6 @@ export class tools extends plugin { reg: "(.*)(twitter.com)", fnc: "twitter", }, - { - reg: "music.163.com", - fnc: "netease", - }, { reg: "(acfun.cn)", fnc: "acfun", @@ -640,37 +636,6 @@ export class tools extends plugin { } } - async netease(e) { - const message = e.msg === undefined ? e.message.shift().data.replaceAll("\\", "") : e.msg.trim(); - const musicUrlReg = /(http:|https:)\/\/music.163.com\/song\/media\/outer\/url\?id=(\d+)/; - const id = musicUrlReg.exec(message)[2] || /id=(\d+)/.exec(message)[1]; - const musicJson = JSON.parse(message) - const {musicUrl, preview, title, desc} = musicJson.meta.music - // 如果没有下载地址跳出if - if (_.isNull(musicUrl) || _.isUndefined(musicUrl)) { - e.reply(`识别:网易云音乐,解析失败!`); - return - } else { - fetch(`https://www.oranges1.top/neteaseapi.do/song/url?id=${id}`, { - headers: { - "User-Agent": - "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Mobile Safari/537.36", - }, - }).then(async resp => { - const url = await JSON.parse(await resp.text()).data[0].url - // 反之解析官方地址 - e.reply([`识别:网易云音乐,${title}--${desc}`, segment.image(preview)]); - this.downloadMp3(url, 'follow').then(path => { - Bot.acquireGfs(e.group_id).upload(fs.readFileSync(path), '/', `${title.replace(/[\/\?<>\\:\*\|".… ]/g, '')}.mp3`) - }) - .catch(err => { - console.error(`下载音乐失败,错误信息为: ${err.message}`); - }); - }) - } - return true; - } - /** * 哔哩哔哩下载 * @param title @@ -828,38 +793,4 @@ export class tools extends plugin { writer.on("error", reject); }); } - - /** - * 下载mp3 - * @param mp3Url - * @param redirect - * @returns {Promise} - */ - async downloadMp3(mp3Url, redirect='manual') { - return fetch(mp3Url, { - headers: { - "User-Agent": - "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Mobile Safari/537.36", - }, - responseType: "stream", - redirect: redirect - }) - .then(res => { - const path = `${this.defaultPath}${this.e.group_id || this.e.user_id}/temp.mp3` - const fileStream = fs.createWriteStream(path); - res.body.pipe(fileStream); - return new Promise((resolve, reject) => { - fileStream.on("finish", () => { - fileStream.close(() => { - resolve(path); - }); - }); - fileStream.on("error", err => { - fs.unlink(path, () => { - reject(err); - }); - }); - }); - }) - } } diff --git a/utils/encrypt.js b/utils/encrypt.js index 49d2894..93d529b 100644 --- a/utils/encrypt.js +++ b/utils/encrypt.js @@ -4,7 +4,7 @@ import crypto from "crypto"; const key = crypto.createHash("sha256").update("rconsole").digest(); /** - * AES解密 + * AES加密 * @param ha1 * @returns {Promise} */ @@ -43,11 +43,10 @@ async function store2ha1(passstore) { return d; } } catch (e) { - console.log( + console.error( "在[default]部分设置的passwordSecret无法解密信息。请确保所有节点的passwordSecret相同。如果您更改了密码保密信息,可能需要重新添加用户。", e ); - process.exit(1); } } diff --git a/utils/netease.js b/utils/netease.js index 30b6543..4d80943 100644 --- a/utils/netease.js +++ b/utils/netease.js @@ -30,7 +30,6 @@ async function getLoginStatus(cookie) { }, }) .then(resp => { - console.log(resp.data.data) return resp.data.data }); } @@ -78,10 +77,56 @@ async function getQrCode(key) { }); } +/** + * 获取听歌排行榜 + * @param uid + * @returns {Promise>} + */ +async function getUserRecord(uid) { + return axios({ + url: `${BASE_URL}/user/record?uid=${uid}&type=1×tamp=${Date.now()}`, + method: 'get', + }) + .then(resp => { + return resp.data + }); +} + +/** + * 检查当前歌曲是否可用 + * @param id + * @returns {Promise>} 返回{success:true|false, message: 'ok'} + */ +async function checkMusic(id) { + return axios({ + url: `${BASE_URL}/check/music?id=${id}×tamp=${Date.now()}`, + method: 'get', + }) + .then(resp => { + return resp.data + }); +} + +async function getSong(id, cookie) { + return axios({ + url: `${BASE_URL}/song/url/v1?id=${id}&level=standard×tamp=${Date.now()}`, + method: 'post', + data: { + cookie, + }, + }) + .then(resp => { + return resp.data.data + }); +} + export { getCookies, getLoginStatus, getDailyRecommend, getKey, getQrCode, + getUserRecord, + checkMusic, + getSong } \ No newline at end of file