feat: 新增群文件上传

This commit is contained in:
秋刀鱼 2024-11-13 15:58:26 +08:00
parent 05931a6586
commit 1671ab4cd7
3 changed files with 164 additions and 60 deletions

View File

@ -6,12 +6,14 @@ import PickSongList from "../model/pick-song.js";
import NeteaseMusicInfo from '../model/neteaseMusicInfo.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, REDIS_YUNZAI_CLOUDSONGLIST } from "../constants/constant.js";
import { downloadAudio } from "../utils/common.js";
import { downloadAudio, retryAxiosReq } from "../utils/common.js";
import { redisExistKey, redisGetKey, redisSetKey } from "../utils/redis-util.js";
import { checkAndRemoveFile } from "../utils/file.js";
import { sendMusicCard } from "../utils/yunzai-util.js";
import { sendMusicCard, getGroupFileUrl } from "../utils/yunzai-util.js";
import config from "../model/config.js";
import FormData from 'form-data';
import NodeID3 from 'node-id3';
import { isError } from "node:util";
let FileSuffix = 'flac'
@ -53,6 +55,11 @@ export class songRequest extends plugin {
reg: '^#?清除云盘缓存$',
fnc: 'cleanCloudData',
permission: 'master'
},
{
reg: '^#?获取群文件$',
fnc: 'getLatestDocument',
permission: 'master'
}
]
});
@ -95,16 +102,16 @@ export class songRequest extends plugin {
const saveId = songInfo.findIndex(item => item.group_id === e.group.group_id)
let musicDate = { 'group_id': group_id, data: [] }
// 获取搜索歌曲列表信息
let detailUrl = autoSelectNeteaseApi + "/song/detail?ids={}" //歌曲详情API
let detailUrl = autoSelectNeteaseApi + "/song/detail?ids={}&time=" + Date.now() //歌曲详情API
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].replace(/[^\w\u4e00-\u9fa5]/g, '')
// 获取云盘歌单列表
const cloudSongList = await this.getCloudSong()
// 搜索云盘歌单并进行搜索
const matchedSongs = await cloudSongList.filter(({ songName, singerName }) =>
songName.includes(songKeyWord) || singerName.includes(songKeyWord)
songName.includes(songKeyWord) || singerName.includes(songKeyWord) || songName == songKeyWord || singerName == songKeyWord
);
// 计算列表数 计算偏移量
// 计算列表数
let songListCount = matchedSongs.length >= this.songRequestMaxList ? this.songRequestMaxList : matchedSongs.length
let searchCount = this.songRequestMaxList - songListCount
for (let i = 0; i < songListCount; i++) {
@ -317,51 +324,47 @@ export class songRequest extends plugin {
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 + '.' + FileSuffix
let tryCount = 0
let path = this.getCurDownloadPath(e) + '/' + desc + '-' + title + '.' + FileSuffix
const tryUpload = async () => {
let formData = new FormData()
await formData.append('songFile', fs.createReadStream(path))
let formData = new FormData();
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, {
const updateUrl = `${autoSelectNeteaseApi}/cloud?time=${Date.now()}`;
try {
const res = await axios({
method: 'post',
url: updateUrl,
headers: headers,
data: formData,
});
if (res.data.code == 200) {
let matchUrl = `${autoSelectNeteaseApi}/cloud/match?uid=${this.uid}&sid=${res.data.privateCloud.songId}&asid=${id}`;
try {
const matchRes = 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)
});
logger.info('歌曲信息匹配成功');
} catch (error) {
logger.error('歌曲信息匹配错误', error);
}
this.songCloudUpdate(e);
return res;
} else {
throw new Error('上传失败,响应不正确');
}
)
} catch (error) {
throw error;
}
};
tryUpload();
await retryAxiosReq(() => tryUpload())
await checkAndRemoveFile(path)
}
// 获取云盘歌单
@ -380,10 +383,10 @@ export class songRequest extends plugin {
"Cookie": this.neteaseCookie
}
});
const songs = res.data.data.map(({ simpleSong }) => ({
'songName': simpleSong.name,
'id': simpleSong.id,
'singerName': simpleSong.ar[0].name || '喵喵~',
const songs = res.data.data.map(({ songId, songName, artist }) => ({
'songName': songName,
'id': songId,
'singerName': artist || '喵喵~',
'duration': '云盘'
}));
songList.push(...songs);
@ -404,6 +407,70 @@ export class songRequest extends plugin {
}
}
// 群文件上传云盘
async getLatestDocument(e) {
const autoSelectNeteaseApi = await this.pickApi()
const cleanPath = await getGroupFileUrl(e)
logger.info(cleanPath)
// 拓展名
const extension = cleanPath.match(/\.\w+$/);
// 获取文件路径
const dirPath = cleanPath.substring(0, cleanPath.lastIndexOf('/'));
// 获取文件名
const fileName = cleanPath.split('/').pop().replace(/\.\w+$/, '');
logger.info(fileName)
const parts = fileName.match(/([\u4e00-\u9fa5a-zA-Z0-9]+)\s*-\s*([\u4e00-\u9fa5a-zA-Z0-9]+)/);
logger.info(parts)
logger.info(parts[1].trim())
const newFileName = dirPath + '/' + parts[2].trim() + extension
// 进行元数据编辑
if (parts) {
const tags = {
title: parts[2].trim(),
artist: parts[1].trim()
};
// 写入元数据
let success = NodeID3.write(tags, cleanPath)
if (fs.existsSync(newFileName)) {
logger.info(`音频已存在`);
fs.unlinkSync(newFileName);
}
// 文件重命名
fs.renameSync(cleanPath, newFileName)
if (success) logger.info('写入元数据成功')
} else {
logger.info('未按照标准命名')
}
// 上传请求
const tryUpload = async () => {
let formData = new FormData()
await formData.append('songFile', fs.createReadStream(newFileName))
const headers = {
...formData.getHeaders(),
'Cookie': this.neteaseCookie,
};
const updateUrl = autoSelectNeteaseApi + `/cloud?time=${Date.now()}`
try {
const res = await axios({
method: 'post',
url: updateUrl,
headers: headers,
data: formData,
});
this.songCloudUpdate(e);
return res;
} catch (error) {
throw error;
}
};
// 重试
await retryAxiosReq(() => tryUpload())
checkAndRemoveFile(newFileName)
}
// 清除缓存
async cleanCloudData(e) {
await redisSetKey(REDIS_YUNZAI_CLOUDSONGLIST, [])
}
@ -494,7 +561,7 @@ export class songRequest extends plugin {
const AudioSize = bytesToMB(resp.data.data?.[0]?.size)
// 获取歌曲标题
let title = songInfo[pickNumber].songName + '-' + songInfo[pickNumber].singerName
let title = songInfo[pickNumber].singerName + '-' + songInfo[pickNumber].songName
let typelist = []
// 歌曲百科API
await axios.get(songWikiUrl, {
@ -503,24 +570,25 @@ export class songRequest extends plugin {
// "Cookie": this.neteaseCookie
},
}).then(res => {
const wikiData = res.data.data.blocks[1].creatives
typelist.push(wikiData[0].resources[0].uiElement.mainTitle.title)
// 防止数据过深出错
const recTags = wikiData[1]
if (recTags.resources[0]) {
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) {
typelist.push(recTags.resources[i].uiElement.mainTitle.title)
const wikiData = res.data.data.blocks[1]?.creatives || []
if (wikiData[0]) {
typelist.push(wikiData[0].resources[0].uiElement.mainTitle.title)
// 防止数据过深出错
const recTags = wikiData[1]
if (recTags.resources[0]) {
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) {
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') {
typelist.push('BPM ' + wikiData[2].uiElement.textLinks[0].text)
} else {
typelist.push(wikiData[2].uiElement.textLinks[0].text)
}
} else {
if (recTags.uiElement.textLinks[0].text) typelist.push(recTags.uiElement.textLinks[0].text)
}
if (wikiData[2].uiElement.mainTitle.title == 'BPM') {
typelist.push('BPM ' + wikiData[2].uiElement.textLinks[0].text)
} else {
typelist.push(wikiData[2].uiElement.textLinks[0].text)
}
typelist.push(AudioLevel)
})

View File

@ -5,6 +5,7 @@
"dependencies": {
"axios": "^1.3.4",
"form-data": "^4.0.1",
"node-id3": "^0.2.6",
"qrcode": "^1.5.3",
"p-queue": "^8.0.1"
}

View File

@ -34,3 +34,38 @@ export async function sendMusicCard(e, platformType, musicId) {
]
});
}
/**
* 获取群文件Url地址
* @param e
* @param count 获取群聊条数
*/
export async function getGroupFileUrl(e, count = 10) {
const latestChat = await e.bot.sendApi("get_group_msg_history", {
"group_id": e.group_id,
"count": count
});
const messages = latestChat.data.messages;
let file_id = "";
for (let i = messages.length - 1; i >= 0; i--) {
const message = messages?.[i]?.message;
if (message?.[0]?.type === "file") {
file_id = message?.[0].data?.file_id;
break;
}
}
if (file_id === "") {
logger.info('未找到群文件')
return "";
}
// 获取文件信息
let latestFileUrl = await e.bot.sendApi("get_group_file_url", {
"group_id": e.group_id,
"file_id": file_id
});
let path = decodeURIComponent(latestFileUrl.data.url)
const cleanPath = path.startsWith('file:///') ? path.replace('file:///', '') : path;
return cleanPath
}