2024-08-17 19:37:33 -04:00
|
|
|
const { createAudioPlayer, createAudioResource, AudioPlayerStatus, joinVoiceChannel } = require('@discordjs/voice');
|
2024-08-17 11:11:10 -04:00
|
|
|
const fs = require('fs');
|
2024-08-18 04:49:20 -04:00
|
|
|
const { spawn } = require('child_process')
|
2024-08-17 11:11:10 -04:00
|
|
|
|
|
|
|
const queueMap = new Map();
|
2024-08-17 11:46:19 -04:00
|
|
|
const playerMap = new Map();
|
2024-08-17 11:23:30 -04:00
|
|
|
const currentTrackMap = new Map();
|
2024-08-17 14:58:17 -04:00
|
|
|
const repeatMap = new Map();
|
2024-08-17 16:53:07 -04:00
|
|
|
const voiceChannelMap = new Map();
|
2024-08-17 11:11:10 -04:00
|
|
|
|
2024-08-17 19:37:33 -04:00
|
|
|
function addToQueue(guildId, filePath, title, voiceChannel, url, requester, avatarURL, thumbnailUrl) {
|
2024-08-17 11:11:10 -04:00
|
|
|
if (!queueMap.has(guildId)) {
|
|
|
|
queueMap.set(guildId, []);
|
|
|
|
}
|
2024-08-17 19:37:33 -04:00
|
|
|
queueMap.get(guildId).push({ filePath, title, url, requester, avatarURL, thumbnailUrl });
|
2024-08-17 16:53:07 -04:00
|
|
|
|
|
|
|
if (voiceChannel) {
|
|
|
|
voiceChannelMap.set(guildId, voiceChannel);
|
|
|
|
}
|
|
|
|
|
|
|
|
const audioPlayer = playerMap.get(guildId);
|
|
|
|
|
|
|
|
if (!audioPlayer || audioPlayer.state.status !== AudioPlayerStatus.Playing) {
|
|
|
|
playNextInQueue(guildId);
|
|
|
|
}
|
2024-08-17 11:11:10 -04:00
|
|
|
}
|
|
|
|
|
2024-08-17 18:05:36 -04:00
|
|
|
function removeFromQueue(guildId, index) {
|
|
|
|
const queue = getQueue(guildId);
|
|
|
|
|
|
|
|
if (index >= 0 && index < queue.length) {
|
|
|
|
return queue.splice(index, 1)[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2024-08-17 11:11:10 -04:00
|
|
|
function getQueue(guildId) {
|
|
|
|
return queueMap.get(guildId) || [];
|
|
|
|
}
|
|
|
|
|
2024-08-17 18:08:42 -04:00
|
|
|
function getPlayer(guildId) {
|
|
|
|
return playerMap.get(guildId);
|
|
|
|
}
|
|
|
|
|
2024-08-17 11:46:19 -04:00
|
|
|
function getCurrentTrack(guildId) {
|
|
|
|
return currentTrackMap.get(guildId) || null;
|
|
|
|
}
|
|
|
|
|
2024-08-17 14:58:17 -04:00
|
|
|
function toggleRepeat(guildId) {
|
|
|
|
const currentRepeat = repeatMap.get(guildId) || false;
|
|
|
|
repeatMap.set(guildId, !currentRepeat);
|
|
|
|
return !currentRepeat;
|
|
|
|
}
|
|
|
|
|
2024-08-17 16:53:07 -04:00
|
|
|
function playNextInQueue(guildId) {
|
2024-08-17 11:11:10 -04:00
|
|
|
const queue = getQueue(guildId);
|
2024-08-17 15:16:17 -04:00
|
|
|
const currentTrack = getCurrentTrack(guildId);
|
2024-08-17 15:09:26 -04:00
|
|
|
const repeat = repeatMap.get(guildId);
|
2024-08-17 16:53:07 -04:00
|
|
|
const voiceChannel = voiceChannelMap.get(guildId);
|
|
|
|
|
|
|
|
if (!voiceChannel) {
|
|
|
|
console.error("Voice channel is undefined. Cannot play track.");
|
|
|
|
return false;
|
|
|
|
}
|
2024-08-17 15:09:26 -04:00
|
|
|
|
2024-08-17 15:16:17 -04:00
|
|
|
if (currentTrack && !repeat) {
|
|
|
|
return false;
|
2024-08-17 15:09:26 -04:00
|
|
|
}
|
2024-08-17 11:46:19 -04:00
|
|
|
|
2024-08-17 15:16:17 -04:00
|
|
|
let trackToPlay = currentTrack;
|
|
|
|
|
|
|
|
if (!trackToPlay) {
|
|
|
|
if (queue.length > 0) {
|
|
|
|
trackToPlay = queue.shift();
|
|
|
|
currentTrackMap.set(guildId, trackToPlay);
|
|
|
|
} else if (repeat && currentTrack) {
|
|
|
|
trackToPlay = currentTrack;
|
|
|
|
} else {
|
|
|
|
const connection = playerMap.get(guildId)?._state?.subscription?.connection;
|
|
|
|
if (connection && connection.state.status !== 'destroyed') {
|
|
|
|
connection.destroy();
|
|
|
|
}
|
|
|
|
return false;
|
2024-08-17 15:09:26 -04:00
|
|
|
}
|
2024-08-17 14:58:17 -04:00
|
|
|
}
|
2024-08-17 11:11:10 -04:00
|
|
|
|
2024-08-17 15:16:17 -04:00
|
|
|
playTrack(guildId, voiceChannel, trackToPlay);
|
2024-08-17 15:09:26 -04:00
|
|
|
}
|
2024-08-17 11:46:19 -04:00
|
|
|
|
2024-08-17 15:09:26 -04:00
|
|
|
function playTrack(guildId, voiceChannel, track) {
|
2024-08-17 19:37:33 -04:00
|
|
|
const connection = joinVoiceChannel({
|
2024-08-17 11:11:10 -04:00
|
|
|
channelId: voiceChannel.id,
|
|
|
|
guildId: guildId,
|
|
|
|
adapterCreator: voiceChannel.guild.voiceAdapterCreator,
|
|
|
|
});
|
|
|
|
|
2024-08-18 04:49:20 -04:00
|
|
|
let audioPlayer = playerMap.get(guildId);
|
2024-08-17 11:11:10 -04:00
|
|
|
|
2024-08-18 04:49:20 -04:00
|
|
|
if (!audioPlayer) {
|
|
|
|
audioPlayer = createAudioPlayer();
|
|
|
|
playerMap.set(guildId, audioPlayer);
|
2024-08-17 11:11:10 -04:00
|
|
|
|
2024-08-18 04:49:20 -04:00
|
|
|
audioPlayer.on(AudioPlayerStatus.Idle, () => {
|
|
|
|
if (!repeatMap.get(guildId)) {
|
|
|
|
currentTrackMap.delete(guildId);
|
|
|
|
fs.unlink(track.filePath, (err) => {
|
|
|
|
if (err) console.error('Error deleting file:', track.filePath, err);
|
|
|
|
});
|
|
|
|
}
|
2024-08-17 11:11:10 -04:00
|
|
|
|
2024-08-18 04:49:20 -04:00
|
|
|
const queue = getQueue(guildId);
|
|
|
|
if (queue.length > 0 || repeatMap.get(guildId)) {
|
|
|
|
playNextInQueue(guildId);
|
|
|
|
} else {
|
|
|
|
if (connection && connection.state.status !== 'destroyed') {
|
|
|
|
connection.destroy();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2024-08-17 18:14:47 -04:00
|
|
|
|
2024-08-18 04:49:20 -04:00
|
|
|
audioPlayer.on('error', (err) => {
|
|
|
|
console.error('AudioPlayer error:', err);
|
2024-08-17 15:09:26 -04:00
|
|
|
currentTrackMap.delete(guildId);
|
|
|
|
if (connection && connection.state.status !== 'destroyed') {
|
|
|
|
connection.destroy();
|
|
|
|
}
|
2024-08-18 04:49:20 -04:00
|
|
|
});
|
|
|
|
}
|
2024-08-17 11:11:10 -04:00
|
|
|
|
2024-08-18 04:49:20 -04:00
|
|
|
if (track.ffmpegProcess) {
|
|
|
|
try {
|
|
|
|
track.ffmpegProcess.kill();
|
|
|
|
console.log('Killed existing ffmpeg process.');
|
|
|
|
} catch (err) {
|
|
|
|
console.error('Error killing existing ffmpeg process:', err);
|
2024-08-17 15:09:26 -04:00
|
|
|
}
|
2024-08-18 04:49:20 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!fs.existsSync(track.filePath)) {
|
|
|
|
console.error('Audio file not found:', track.filePath);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
track.ffmpegProcess = spawn('ffmpeg', [
|
|
|
|
'-i', track.filePath,
|
|
|
|
'-analyzeduration', '0',
|
|
|
|
'-loglevel', '0',
|
|
|
|
'-acodec', 'libopus',
|
|
|
|
'-f', 'opus',
|
|
|
|
'-ar', '48000',
|
|
|
|
'-ac', '2',
|
|
|
|
'pipe:1'
|
|
|
|
]);
|
|
|
|
|
|
|
|
const resource = createAudioResource(track.ffmpegProcess.stdout);
|
|
|
|
|
|
|
|
audioPlayer.play(resource);
|
|
|
|
connection.subscribe(audioPlayer);
|
|
|
|
|
|
|
|
currentTrackMap.set(guildId, { ...track, resource });
|
2024-08-17 11:11:10 -04:00
|
|
|
}
|
|
|
|
|
2024-08-17 16:53:07 -04:00
|
|
|
function skipTrack(guildId) {
|
2024-08-17 11:46:19 -04:00
|
|
|
const player = playerMap.get(guildId);
|
2024-08-17 11:11:10 -04:00
|
|
|
const queue = getQueue(guildId);
|
2024-08-17 11:46:19 -04:00
|
|
|
|
2024-08-17 14:58:17 -04:00
|
|
|
if (!player) {
|
|
|
|
console.error('No player found for this guild.');
|
|
|
|
return;
|
|
|
|
}
|
2024-08-17 11:46:19 -04:00
|
|
|
|
2024-08-17 14:58:17 -04:00
|
|
|
currentTrackMap.delete(guildId);
|
|
|
|
player.stop();
|
|
|
|
|
|
|
|
if (queue.length > 0) {
|
2024-08-17 16:53:07 -04:00
|
|
|
playNextInQueue(guildId);
|
2024-08-17 11:46:19 -04:00
|
|
|
} else {
|
2024-08-17 15:09:26 -04:00
|
|
|
const connection = playerMap.get(guildId)?._state?.subscription?.connection;
|
|
|
|
if (connection && connection.state.status !== 'destroyed') {
|
|
|
|
connection.destroy();
|
|
|
|
}
|
2024-08-17 11:11:10 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-17 15:16:17 -04:00
|
|
|
function clearQueue(guildId) {
|
|
|
|
queueMap.set(guildId, []);
|
|
|
|
}
|
|
|
|
|
2024-08-17 18:08:42 -04:00
|
|
|
module.exports = { addToQueue, getQueue, getCurrentTrack, playNextInQueue, skipTrack, toggleRepeat, clearQueue, removeFromQueue, getPlayer };
|