Merge pull request #50 from nikoyoke1/master

网易云云盘操作(beta)
This commit is contained in:
Zhiyu 2024-11-12 14:27:13 +08:00 committed by GitHub
commit 9a61d9c96a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 281 additions and 36 deletions

View File

@ -1,15 +1,17 @@
import axios from "axios"; import axios from "axios";
import { formatTime } from '../utils/other.js' import fs from "node:fs";
import { formatTime, toGBorTB } from '../utils/other.js'
import puppeteer from "../../../lib/puppeteer/puppeteer.js"; import puppeteer from "../../../lib/puppeteer/puppeteer.js";
import PickSongList from "../model/pick-song.js"; import PickSongList from "../model/pick-song.js";
import NeteaseMusicInfo from '../model/neteaseMusicInfo.js' import NeteaseMusicInfo from '../model/neteaseMusicInfo.js'
import { NETEASE_API_CN, NETEASE_SONG_DOWNLOAD, NETEASE_TEMP_API } from "../constants/tools.js"; import { NETEASE_API_CN, NETEASE_SONG_DOWNLOAD, NETEASE_TEMP_API } from "../constants/tools.js";
import { COMMON_USER_AGENT, REDIS_YUNZAI_ISOVERSEA, REDIS_YUNZAI_SONGINFO } from "../constants/constant.js"; import { COMMON_USER_AGENT, REDIS_YUNZAI_ISOVERSEA, REDIS_YUNZAI_SONGINFO, REDIS_YUNZAI_CLOUDSONGLIST } from "../constants/constant.js";
import { downloadAudio } from "../utils/common.js"; import { downloadAudio } from "../utils/common.js";
import { redisExistKey, redisGetKey, redisSetKey } from "../utils/redis-util.js"; import { redisExistKey, redisGetKey, redisSetKey } from "../utils/redis-util.js";
import { checkAndRemoveFile } from "../utils/file.js"; import { checkAndRemoveFile } from "../utils/file.js";
import { sendMusicCard } from "../utils/yunzai-util.js"; import { sendMusicCard } from "../utils/yunzai-util.js";
import config from "../model/config.js"; import config from "../model/config.js";
import FormData from 'form-data';
export class songRequest extends plugin { export class songRequest extends plugin {
constructor() { constructor() {
@ -27,9 +29,29 @@ export class songRequest extends plugin {
fnc: "playSong" fnc: "playSong"
}, },
{ {
reg: "^#?上传(.*)", reg: "^#?上传$",
fnc: "upLoad" fnc: "upLoad"
}, },
{
reg: '^#?我的云盘$|#rnc|#RNC',
fnc: 'myCloud',
permission: 'master'
},
{
reg: '^#?云盘更新|#?更新云盘$',
fnc: 'songCloudUpdate',
permission: 'master'
},
{
reg: '^#?上传云盘|#?上传网盘$|#rnu|#RNU',
fnc: 'uploadCloud',
permission: 'master'
},
{
reg: '^#?清除云盘缓存$',
fnc: 'cleanCloudData',
permission: 'master'
}
] ]
}); });
this.toolsConfig = config.getConfig("tools"); this.toolsConfig = config.getConfig("tools");
@ -51,6 +73,8 @@ export class songRequest extends plugin {
this.songRequestMaxList = this.toolsConfig.songRequestMaxList this.songRequestMaxList = this.toolsConfig.songRequestMaxList
// 视频保存路径 // 视频保存路径
this.defaultPath = this.toolsConfig.defaultPath; this.defaultPath = this.toolsConfig.defaultPath;
// uid
this.uid = this.toolsConfig.neteaseUserId
} }
async pickSong(e) { async pickSong(e) {
@ -59,6 +83,7 @@ export class songRequest extends plugin {
logger.info('当前未开启网易云点歌') logger.info('当前未开启网易云点歌')
return false return false
} }
// 获取自定义API
const autoSelectNeteaseApi = await this.pickApi() const autoSelectNeteaseApi = await this.pickApi()
// 只在群里可以使用 // 只在群里可以使用
let group_id = e.group.group_id let group_id = e.group.group_id
@ -68,24 +93,45 @@ export class songRequest extends plugin {
const saveId = songInfo.findIndex(item => item.group_id === e.group.group_id) const saveId = songInfo.findIndex(item => item.group_id === e.group.group_id)
let musicDate = { 'group_id': group_id, data: [] } let musicDate = { 'group_id': group_id, data: [] }
// 获取搜索歌曲列表信息 // 获取搜索歌曲列表信息
let searchUrl = autoSelectNeteaseApi + '/search?keywords={}&limit=' + this.songRequestMaxList //搜索API
let detailUrl = autoSelectNeteaseApi + "/song/detail?ids={}" //歌曲详情API let detailUrl = autoSelectNeteaseApi + "/song/detail?ids={}" //歌曲详情API
if (e.msg.replace(/\s+/g, "").match(/点歌(.+)/)) { if (e.msg.replace(/\s+/g, "").match(/点歌(.+)/)) {
const songKeyWord = e.msg.replace(/\s+/g, "").match(/点歌(.+)/)[1] const songKeyWord = e.msg.replace(/\s+/g, "").match(/点歌(.+)/)[1]
// 获取云盘歌单列表
const cloudSongList = await this.getCloudSong()
// 搜索云盘歌单并进行搜索
const matchedSongs = await cloudSongList.filter(({ songName, singerName }) =>
songName.includes(songKeyWord) || singerName.includes(songKeyWord)
);
// 计算列表数 计算偏移量
let songListCount = matchedSongs.length >= this.songRequestMaxList ? this.songRequestMaxList : matchedSongs.length
let searchCount = this.songRequestMaxList - songListCount
for (let i = 0; i < songListCount; i++) {
musicDate.data.push({
'id': matchedSongs[i].id,
'songName': matchedSongs[i].songName,
'singerName': matchedSongs[i].singerName,
'duration': matchedSongs[i].duration
});
}
let searchUrl = autoSelectNeteaseApi + '/search?keywords={}&limit=' + searchCount + '&offset=' + songListCount//搜索API
searchUrl = searchUrl.replace("{}", songKeyWord) searchUrl = searchUrl.replace("{}", songKeyWord)
await axios.get(searchUrl, { await axios.get(searchUrl, {
headers: { headers: {
"User-Agent": COMMON_USER_AGENT "User-Agent": COMMON_USER_AGENT
}, },
}).then(async res => { }).then(async res => {
if (res.data.result.songs) { if (res.data.result.songs || musicDate.data[0]) {
for (const info of res.data.result.songs) { try {
musicDate.data.push({ for (const info of res.data.result.songs) {
'id': info.id, musicDate.data.push({
'songName': info.name, 'id': info.id,
'singerName': info.artists[0]?.name, 'songName': info.name,
'duration': formatTime(info.duration) 'singerName': info.artists[0]?.name,
}); 'duration': formatTime(info.duration)
});
}
} catch (error) {
logger.info('并未获取云服务歌曲')
} }
const ids = musicDate.data.map(item => item.id).join(','); const ids = musicDate.data.map(item => item.id).join(',');
detailUrl = detailUrl.replace("{}", ids) detailUrl = detailUrl.replace("{}", ids)
@ -185,8 +231,46 @@ export class songRequest extends plugin {
} }
} }
// 获取云盘信息
async myCloud(e) {
const autoSelectNeteaseApi = await this.pickApi()
const cloudUrl = autoSelectNeteaseApi + '/user/cloud'
// 云盘数据API
await axios.get(cloudUrl, {
headers: {
"User-Agent": COMMON_USER_AGENT,
"Cookie": this.neteaseCookie
},
}).then(res => {
const cloudData = {
'songCount': res.data.count,
'useSize': toGBorTB(res.data.size),
'cloudSize': toGBorTB(res.data.maxSize)
}
e.reply(`云盘数据\n歌曲数量:${cloudData.songCount}\n云盘容量:${cloudData.cloudSize}\n已使用容量:${cloudData.useSize}\n数据可能有延迟`)
})
}
// 更新云盘
async songCloudUpdate(e) {
try {
await this.cleanCloudData()
await this.getCloudSong(e, true)
try {
await e?.reply('更新成功')
} catch (error) {
logger.error('trss又拉屎了')
}
await this.myCloud(e)
} catch (error) {
logger.error('更新云盘失败', error)
}
}
// 上传音频文件
async upLoad(e) { async upLoad(e) {
let msg = await e.getReply(); let msg = await e?.getReply();
const musicUrlReg = /(http:|https:)\/\/music.163.com\/song\/media\/outer\/url\?id=(\d+)/; 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 musicUrlReg2 = /(http:|https:)\/\/y.music.163.com\/m\/song\?(.*)&id=(\d+)/;
const musicUrlReg3 = /(http:|https:)\/\/music.163.com\/m\/song\/(\d+)/; const musicUrlReg3 = /(http:|https:)\/\/music.163.com\/m\/song\/(\d+)/;
@ -198,17 +282,122 @@ export class songRequest extends plugin {
const title = msg.message[0].data.match(/"title":"([^"]+)"/)[1] const title = msg.message[0].data.match(/"title":"([^"]+)"/)[1]
const desc = msg.message[0].data.match(/"desc":"([^"]+)"/)[1] const desc = msg.message[0].data.match(/"desc":"([^"]+)"/)[1]
if (id === "") return if (id === "") return
let path = this.getCurDownloadPath(e) + '/' + title + '-' + desc + '.' + 'flac' let path = this.getCurDownloadPath(e) + '/' + title + '-' + desc + '.flac'
try { try {
// 上传群文件 // 上传群文件
await this.uploadGroupFile(e, path); await this.uploadGroupFile(e, path);
// 删除文件 // 删除文件
await checkAndRemoveFile(path); await checkAndRemoveFile(path);
} catch (error) { } catch (error) {
logger.error(error) logger.error(error);
} }
} }
// 上传云盘
async uploadCloud(e) {
let msg = await e?.getReply();
const autoSelectNeteaseApi = await this.pickApi()
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 musicUrlReg3 = /(http:|https:)\/\/music.163.com\/m\/song\/(\d+)/;
const id =
musicUrlReg2.exec(msg.message[0].data)?.[3] ||
musicUrlReg.exec(msg.message[0].data)?.[2] ||
musicUrlReg3.exec(msg.message[0].data)?.[2] ||
/(?<!user)id=(\d+)/.exec(msg.message[0].data)[1] || "";
const title = msg.message[0].data.match(/"title":"([^"]+)"/)[1]
const desc = msg.message[0].data.match(/"desc":"([^"]+)"/)[1]
if (id === "") return
let path = this.getCurDownloadPath(e) + '/' + title + '-' + desc + '.flac'
let tryCount = 0
const tryUpload = async () => {
let formData = new FormData()
await formData.append('songFile', fs.createReadStream(path))
const headers = {
...formData.getHeaders(),
'Cookie': this.neteaseCookie,
};
const updateUrl = autoSelectNeteaseApi + `/cloud?time=${Date.now()}`
axios({
method: 'post',
url: updateUrl,
headers: headers,
data: formData,
})
.then(async res => {
if (res.data.code == 200) {
let matchUrl = autoSelectNeteaseApi + '/cloud/match?uid=' + this.uid + "&sid=" + res.data.privateCloud.songId + '&asid=' + id
await axios.get(matchUrl, {
headers: {
"User-Agent": COMMON_USER_AGENT,
"Cookie": this.neteaseCookie
},
}).then(res => {
logger.info('歌曲信息匹配成功')
})
.catch(error => {
logger.error('歌曲信息匹配错误', error)
})
this.songCloudUpdate(e)
}
})
.catch(error => {
tryCount += 1;
logger.info('失败喽~再试一次')
if (tryCount < 3) {
tryUpload(); // 直接调用
} else {
logger.error('怎么想都传不上去吧', error)
}
}
)
};
tryUpload();
}
// 获取云盘歌单
async getCloudSong(e, cloudUpdate = false) {
let songList = await redisGetKey(REDIS_YUNZAI_CLOUDSONGLIST) || []
if (!songList[0] || cloudUpdate) {
const autoSelectNeteaseApi = await this.pickApi();
const limit = 100;
let offset = 0;
let cloudUrl = autoSelectNeteaseApi + `/user/cloud?limit=${limit}&offset=${offset}&timestamp=${Date.now()}`;
while (true) {
try {
const res = await axios.get(cloudUrl, {
headers: {
"User-Agent": COMMON_USER_AGENT,
"Cookie": this.neteaseCookie
}
});
const songs = res.data.data.map(({ simpleSong }) => ({
'songName': simpleSong.name,
'id': simpleSong.id,
'singerName': simpleSong.ar[0].name || '喵喵~',
'duration': '云盘'
}));
songList.push(...songs);
if (!res.data.hasMore) {
break;
}
offset += limit;
cloudUrl = autoSelectNeteaseApi + `/user/cloud?limit=${limit}&offset=${offset}`;
} catch (error) {
console.error("获取歌单失败", error);
break;
}
}
await redisSetKey(REDIS_YUNZAI_CLOUDSONGLIST, songList)
return songList;
} else {
return songList;
}
}
async cleanCloudData(e) {
await redisSetKey(REDIS_YUNZAI_CLOUDSONGLIST, [])
}
// 判断是否海外服务器 // 判断是否海外服务器
async isOverseasServer() { async isOverseasServer() {
@ -237,7 +426,6 @@ export class songRequest extends plugin {
} }
// 检测cooike活性 // 检测cooike活性
async checkCooike(statusUrl) { async checkCooike(statusUrl) {
let status let status
await axios.get(statusUrl, { await axios.get(statusUrl, {
@ -245,8 +433,9 @@ export class songRequest extends plugin {
"User-Agent": COMMON_USER_AGENT, "User-Agent": COMMON_USER_AGENT,
"Cookie": this.neteaseCookie "Cookie": this.neteaseCookie
}, },
}).then(res => { }).then(async res => {
const userInfo = res.data.data.profile const userInfo = res.data.data.profile
await config.updateField("tools", "neteaseUserId", res.data.data.profile.userId);
if (userInfo) { if (userInfo) {
logger.info('ck活着使用ck进行高音质下载') logger.info('ck活着使用ck进行高音质下载')
status = true status = true
@ -306,26 +495,23 @@ export class songRequest extends plugin {
}, },
}).then(res => { }).then(res => {
const wikiData = res.data.data.blocks[1].creatives const wikiData = res.data.data.blocks[1].creatives
try {
typelist.push(wikiData[0].resources[0]?.uiElement?.mainTitle?.title || "") typelist.push(wikiData[0].resources[0].uiElement.mainTitle.title)
// 防止数据过深出错 // 防止数据过深出错
const recTags = wikiData[1] const recTags = wikiData[1]
if (recTags?.resources[0]) { if (recTags.resources[0]) {
for (let i = 0; i < Math.min(3, recTags.resources.length); i++) { for (let i = 0; i < Math.min(3, recTags.resources.length); i++) {
if (recTags.resources[i] && recTags.resources[i].uiElement && recTags.resources[i].uiElement.mainTitle.title) { if (recTags.resources[i] && recTags.resources[i].uiElement && recTags.resources[i].uiElement.mainTitle.title) {
typelist.push(recTags.resources[i].uiElement.mainTitle.title) typelist.push(recTags.resources[i].uiElement.mainTitle.title)
}
} }
} else {
if (recTags.uiElement.textLinks[0].text) typelist.push(recTags.uiElement.textLinks[0].text)
} }
if (wikiData[2].uiElement.mainTitle.title == 'BPM') { } else {
typelist.push('BPM ' + wikiData[2].uiElement.textLinks[0].text) if (recTags.uiElement.textLinks[0].text) typelist.push(recTags.uiElement.textLinks[0].text)
} else { }
typelist.push(wikiData[2].uiElement.textLinks[0].text || '') if (wikiData[2].uiElement.mainTitle.title == 'BPM') {
} typelist.push('BPM ' + wikiData[2].uiElement.textLinks[0].text)
} catch (error) { } else {
logger.error('获取标签报错:', error) typelist.push(wikiData[2].uiElement.textLinks[0].text)
} }
typelist.push(AudioLevel) typelist.push(AudioLevel)
}) })

View File

@ -29,6 +29,7 @@ songRequestMaxList: 10 # 网易云点歌请求最大列表数
neteaseCookie: '' # 网易云ck neteaseCookie: '' # 网易云ck
neteaseCloudAPIServer: '' # 网易云自建服务器地址 neteaseCloudAPIServer: '' # 网易云自建服务器地址
neteaseCloudAudioQuality: exhigh # 网易云解析最高音质 默认exhigh(极高) 分类standard => 标准,higher => 较高, exhigh=>极高, lossless=>无损, hires=>Hi-Res, jyeffect => 高清环绕声, sky => 沉浸环绕声, dolby => 杜比全景声, jymaster => 超清母带 neteaseCloudAudioQuality: exhigh # 网易云解析最高音质 默认exhigh(极高) 分类standard => 标准,higher => 较高, exhigh=>极高, lossless=>无损, hires=>Hi-Res, jyeffect => 高清环绕声, sky => 沉浸环绕声, dolby => 杜比全景声, jymaster => 超清母带
neteaseUserId: '' # 网易云用户ID 不要手动更改!!!!除非你非常清楚你在做什么
youtubeGraphicsOptions: 720 # YouTobe的下载画质0为原画1080720480自定义画面高度默认为720 youtubeGraphicsOptions: 720 # YouTobe的下载画质0为原画1080720480自定义画面高度默认为720
youtubeClipTime: 0 # YouTobe限制的最大视频时长默认不开启单位秒 最好不要超过5分钟否则截取效率非常低 youtubeClipTime: 0 # YouTobe限制的最大视频时长默认不开启单位秒 最好不要超过5分钟否则截取效率非常低

View File

@ -80,6 +80,11 @@ export const REDIS_YUNZAI_ISOVERSEA = "Yz:rconsole:tools:oversea";
*/ */
export const REDIS_YUNZAI_SONGINFO = "Yz:rconsole:tools:songinfo"; export const REDIS_YUNZAI_SONGINFO = "Yz:rconsole:tools:songinfo";
/**
* 缓存网易云云盘列表
* @type {string}
*/
export const REDIS_YUNZAI_CLOUDSONGLIST = "Yz:rconsole:tools:cloudsonglist";
/** /**
* 某些功能的解析白名单 * 某些功能的解析白名单

View File

@ -4,6 +4,7 @@
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"axios": "^1.3.4", "axios": "^1.3.4",
"form-data": "^4.0.1",
"qrcode": "^1.5.3", "qrcode": "^1.5.3",
"p-queue": "^8.0.1" "p-queue": "^8.0.1"
} }

View File

@ -128,6 +128,10 @@ html {
box-shadow: 0px 0px 3px rgba(255, 255, 255, 0.4); box-shadow: 0px 0px 3px rgba(255, 255, 255, 0.4);
} }
.typeNav:first-child {
margin-left: 0px;
}
.logo{ .logo{
width: 100%; width: 100%;
text-align: center; text-align: center;

View File

@ -62,11 +62,30 @@ html {
.navDuration { .navDuration {
color: #aaa; color: #aaa;
width: 40px; width: 70px;
font-size: 25px; font-size: 25px;
height: 100%; height: 100%;
text-align: center;
}
.cloudBox{
height: 100%;
width: 70px;
justify-content: center;
display: flex;
align-items: center;
}
.cloud{
color: #dd001b;
box-sizing: border-box;
padding: 0px 8px;
font-size: 22px;
height: 40px;
width: 70px;
border-radius: 10px;
display: flex; display: flex;
justify-content: center; justify-content: center;
border: 1px solid;
align-items: center; align-items: center;
} }
@ -74,6 +93,7 @@ html {
width: 90px; width: 90px;
height: 90px; height: 90px;
border-radius: 8px; border-radius: 8px;
flex-shrink: 0;
} }
.bgicon { .bgicon {
@ -103,4 +123,13 @@ html {
margin-right: 25px; margin-right: 25px;
margin-left: -20px; margin-left: -20px;
font-family: 'number' font-family: 'number'
}
.footer{
width: 100%;
text-align: center;
font-size: 20px;
margin-top: 20px;
color: rgba(255, 255, 255, 0.7);
font-family: 'number';
} }

View File

@ -22,12 +22,19 @@
<div class="singerText">{{ info.singerName }}</div> <div class="singerText">{{ info.singerName }}</div>
</div> </div>
</div> </div>
{{ if info.duration == '云盘' }}
<div class="cloudBox">
<div class="cloud">{{ info.duration }}</div>
</div>
{{ else }}
<div class="navDuration">{{ info.duration }}</div> <div class="navDuration">{{ info.duration }}</div>
{{ /if }}
</div> </div>
{{ /each }} {{ /each }}
<div class="bgicon"> <div class="bgicon">
<img src="{{pluResPath}}img/icon/neteaseRank.png" alt=""> <img src="{{pluResPath}}img/icon/neteaseRank.png" alt="">
</div> </div>
<div class="footer">Created By Yunzai-Bot & R-Plugin</div>
</div> </div>
</body> </body>

View File

@ -8,4 +8,16 @@ export function formatTime(timestamp) {
const formattedSeconds = String(seconds).padStart(2, '0'); const formattedSeconds = String(seconds).padStart(2, '0');
return `${formattedMinutes}:${formattedSeconds}`; return `${formattedMinutes}:${formattedSeconds}`;
}
export function toGBorTB(Bytes) {
const GB = 1024 ** 3;
let sizeInGB = Bytes / GB;
let unit = "GB";
if (sizeInGB > 1024) {
sizeInGB /= 1024;
unit = "TB";
}
sizeInGB = sizeInGB % 1 === 0 ? sizeInGB.toString() : sizeInGB.toFixed(2);
return sizeInGB + unit;
} }