rc-plugin/apps/neteasepro.js
2023-04-10 23:16:14 +08:00

460 lines
16 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import plugin from "../../../lib/plugins/plugin.js";
import axios from "axios";
import fs from "node:fs";
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(
'<?xml version="1.0" encoding="utf-8"?>',
'<?xml version="1.0" encoding="utf-8" ?>',
)
.replace(/\n/g, "")
.replace(/<title color="#777777" size="26">(.+?)<\/title>/g, "___")
.replace(/___+/, `<title color="#777777" size="26">${forwardMsgName}</title>`);
return forwardMsg;
}
// 获取redis的key
async getRedisKey(user_id) {
return `Yz:rconsole:netease:${user_id}`;
}
}