import plugin from "../../../lib/plugins/plugin.js"; import axios from "axios"; import fs from "node:fs"; import { segment } from "oicq"; import { checkMusic, getCookies, getDailyRecommend, getKey, getLoginStatus, getQrCode, getSong, getSongDetail, getUserRecord, getCloud, getCloudMusicDetail, } from "../utils/netease.js"; import { ha12store, store2ha1 } from "../utils/encrypt.js"; import { downloadMp3 } from "../utils/common.js"; import _ from "lodash"; export class neteasepro extends plugin { constructor() { super({ /** 功能名称 */ name: "R插件网易云音乐解析", /** 功能描述 */ dsc: "网易云音乐解析Pro", /** https://oicqjs.github.io/oicq/#events */ event: "message", /** 优先级,数字越小等级越高 */ priority: 500, rule: [ { /** 命令正则匹配 */ reg: "^#网易云登录$", /** 执行方法 */ fnc: "neteaseCloudLogin", }, { reg: "^#网易云每日推荐$", fnc: "neteaseDailyRecommend", }, { reg: "^#网易云听歌排行$", fnc: "neteaseListenRank", }, { reg: "^#网易云云盘$", fnc: "neteaseCloud", }, { reg: "^#网易云云盘下载(.*)", fnc: "neteaseCloudDownload", }, { reg: "^#网易云云盘(.*)", fnc: "neteaseCloudApplet", }, { reg: "music.163.com", fnc: "netease", }, ], }); } async neteaseCloudLogin(e) { let neteaseCookie; // 如果不存在cookie if (!(await redis.exists(await this.getRedisKey(e.user_id)))) { // 获取密钥 const key = await getKey(); // console.log(key); // 获取二维码 const qrPic = await getQrCode(key); // 下载qrcode await this.downloadQrCode(qrPic).then(path => { // 发送二维码 e.reply(segment.image(fs.readFileSync(path))); }); // 定时轮询 await this.poll(key).then(async cookie => { // 存放到redis neteaseCookie = cookie; }); } else { const userData = await redis.get(await this.getRedisKey(e.user_id)); // 如果cookie存在但是为空 if (_.isEmpty(userData)) { await redis.del(await this.getRedisKey(e.user_id)); e.reply("发生已知错误:cookie为空,请重试 #网易云登录 即可!"); return; } // 已经登陆过的,直接从redis取出 neteaseCookie = await store2ha1(JSON.parse(userData).cookie); } // 获取用户信息 const userInfo = await getLoginStatus(neteaseCookie); // 提取信息 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)).cookie; if (realCookie === "") { return true; } // 获取每日推荐所有数据 const dailyRecommend = await getDailyRecommend(realCookie); const combineMsg = await dailyRecommend.dailySongs.map(async item => { // 组合数据 return { message: segment.json(await this.musicPack(item)), nickname: e.sender.card || e.user_id, user_id: e.user_id, }; }); let forwardMsg = await Bot.makeForwardMsg(await Promise.all(combineMsg)); await e.reply(await this.musicForwardPack(forwardMsg)); } 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); e.reply(" 😘亲,这是你的听歌排行榜Top10"); // 由于数据过大,取前10 const rank = userRecord.weekData.slice(0, 10).map(async item => { // 组合数据 const song = item.song; return { message: segment.json(await this.musicPack(song)), nickname: e.sender.card || e.user_id, user_id: e.user_id, }; }); let forwardMsg = await Bot.makeForwardMsg(await Promise.all(rank)); await e.reply(await this.musicForwardPack(forwardMsg)); } async neteaseCloud(e) { const userInfo = await this.aopBefore(e); const realCookie = userInfo.cookie; if (realCookie === "") { return true; } const cloudMusics = await ( await getCloud(realCookie) ).map(item => { return { message: `${item.songId}: ${item?.songName??"暂无歌曲信息"}-${item?.artist??"暂无歌手信息"}`, nickname: e.sender.card || e.user_id, user_id: e.user_id, } }); // 获取用户信息 const { profile } = await getLoginStatus(realCookie); e.reply(`<${profile.nickname}> 的网易云云盘`); e.reply(await Bot.makeForwardMsg(cloudMusics)); return true; } async neteaseCloudDownload(e) { const id = e.msg.replace("#网易云云盘下载", "").trim(); const userInfo = await this.aopBefore(e); const realCookie = userInfo.cookie; if (realCookie === "") { return true; } const music = (await getSong(id, realCookie))[0]; const item = (await getCloudMusicDetail(id, realCookie)).data[0]; const simpleSong = item.simpleSong; e.reply([ segment.image(simpleSong?.al?.picUrl), `识别:云盘音乐,${simpleSong?.name}-${simpleSong?.al?.name}`, ]); const downloadPath = `./data/rcmp4/${this.e.group_id || this.e.user_id}`; await downloadMp3(music.url, downloadPath) .then(path => { Bot.acquireGfs(e.group_id).upload( fs.readFileSync(path), "/", `${simpleSong?.name}.mp3`, ); }) .catch(err => { console.error(`下载音乐失败,错误信息为: ${err.message}`); }); return true; } async neteaseCloudApplet(e) { const id = e.msg.replace("#网易云云盘", "").trim(); logger.mark(id); const userInfo = await this.aopBefore(e); const realCookie = userInfo.cookie; if (realCookie === "") { return true; } const music = (await getSong(id, realCookie))[0]; const item = (await getCloudMusicDetail(id, realCookie)).data[0]; const appletMusic = { message: segment.json(await this.cloudMusicPack(item, music.url)), nickname: e.sender.card || e.user_id, user_id: e.user_id, }; let forwardMsg = await Bot.makeForwardMsg(appletMusic); await e.reply(await this.musicForwardPack(forwardMsg)); } 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 downloadPath = `./data/rcmp4/${this.e.group_id || this.e.user_id}`; // 是游客 if (!(await redis.get(await this.getRedisKey(e.user_id)))) { // 是小程序 if (await this.isJSON(message)) { const musicJson = JSON.parse(message); const { preview, title, desc } = musicJson.meta.music || musicJson.meta.news; // console.log(musicUrl, preview, title, desc); // 如果没有登陆,就使用官方接口 e.reply([`识别:网易云音乐,${title}--${desc}`, segment.image(preview)]); } else { // 非小程序 const title = await getSongDetail(id).then(res => { const song = res.songs?.[0]; return song.length > 0 ? `${song?.name}-${song?.ar?.[0].name}`.replace(/[\/\?<>\\:\*\|".… ]/g, "") : "暂无信息"; }); e.reply(`识别:网易云音乐,${title}`); } // 下载游客歌曲 downloadMp3( `https://music.163.com/song/media/outer/url?id=${id}`, downloadPath, "follow", ) .then(path => { Bot.acquireGfs(e.group_id).upload(fs.readFileSync(path), "/", `${id}.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; const title = await getSongDetail(id).then(res => { const song = res.songs[0]; return `${song?.name}-${song?.ar?.[0].name}`.replace(/[\/\?<>\\:\*\|".… ]/g, ""); }); await downloadMp3(userDownloadUrl, downloadPath) .then(path => { Bot.acquireGfs(e.group_id).upload(fs.readFileSync(path), "/", `${title}.mp3`); }) .catch(err => { console.error(`下载音乐失败,错误信息为: ${err.message}`); }); return true; } // 切面方法检测cookie & 获取cookie和uid async aopBefore(e) { // 取出cookie const userDataJson = await redis.get(await this.getRedisKey(e.user_id)); // 如果不存在cookie if (_.isEmpty(userDataJson)) { e.reply("请先#网易云登录"); return ""; } let userData = JSON.parse(userDataJson); const cookie = userData?.cookie; logger.mark(cookie); // 解析cookie userData.cookie = await store2ha1(cookie); // 检查cookie是否可用 const userInfo = await getLoginStatus(userData.cookie); logger.mark(userData); if (_.isNil(userInfo.profile)) { e.reply("cookie已经过期,请重新#网易云登录!"); // 删除过期的cookie await redis.del(await this.getRedisKey(e.user_id)); return ""; } // 没有过期直接返回 return userData; } // 下载二维码 async downloadQrCode(qrPic) { return axios .get(qrPic, { 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", }) .then(resp => { const filepath = "./netease_qr.jpg"; const writer = fs.createWriteStream(filepath); resp.data.pipe(writer); return new Promise((resolve, reject) => { writer.on("finish", () => resolve(filepath)); writer.on("error", reject); }); }); } // 判断是否是json的字符串 async isJSON(str) { if (typeof str !== "string") { return false; } try { JSON.parse(str); return true; } catch (e) { return false; } } // 定时轮询 async poll(key) { let timer; return new Promise((resolve, reject) => { timer = setInterval(async () => { const statusRes = await getCookies(key); // console.log(statusRes); if (statusRes.code === 800) { clearInterval(timer); reject("二维码已过期,请重新获取"); } if (statusRes.code === 803) { // 这一步会返回cookie clearInterval(timer); const cookie = statusRes.cookie; resolve( /__csrf=[0-9a-z]+;/.exec(cookie)[0] + /MUSIC_U=[0-9a-z]+;/.exec(cookie)[0], ); } }, 3000); }); } async cloudMusicPack(item, url) { return { app: "com.tencent.structmsg", desc: "音乐", view: "music", ver: "0.0.0.1", prompt: "[分享]" + item.songName + "-" + item.album, meta: { music: { app_type: 1, appid: 100495085, desc: item.artist, jumpUrl: `https://y.music.163.com/m/song?id=${item.songId}`, musicUrl: url, preview: "https://i.gtimg.cn/open/app_icon/00/49/50/85/100495085_100_m.png", sourceMsgId: "0", source_icon: "https://i.gtimg.cn/open/app_icon/00/49/50/85/100495085_100_m.png", source_url: "", tag: "网易云音乐", title: item.fileName, }, }, config: { type: "normal", forward: true, ctime: Date.now(), }, }; } // 包装分享小程序数据 async musicPack(song) { const title = song.name; const singer = song.ar?.[0]?.name; const jumpUrl = `https://y.music.163.com/m/song?id=${song.id}`; const preview = song.al?.picUrl; const musicUrl = `https://music.163.com/song/media/outer/url?id=${song.id}.mp3`; return { app: "com.tencent.structmsg", desc: "音乐", view: "music", ver: "0.0.0.1", prompt: "[分享]" + title + "-" + singer, meta: { music: { app_type: 1, appid: 100495085, desc: singer, jumpUrl: jumpUrl, musicUrl: musicUrl, preview: preview, sourceMsgId: "0", source_icon: "https://i.gtimg.cn/open/app_icon/00/49/50/85/100495085_100_m.png", source_url: "", tag: "网易云音乐", title: title, }, }, config: { type: "normal", forward: true, ctime: Date.now(), }, }; } async musicForwardPack(forwardMsg, forwardMsgName = "R插件消息") { forwardMsg.data = forwardMsg.data .replace( '', '', ) .replace(/\n/g, "") .replace(/(.+?)<\/title>/g, "___") .replace(/___+/, `<title color="#777777" size="26">${forwardMsgName}`); return forwardMsg; } // 获取redis的key async getRedisKey(user_id) { return `Yz:rconsole:netease:${user_id}`; } }