289 lines
8.2 KiB
JavaScript
289 lines
8.2 KiB
JavaScript
// Require the necessary discord.js classes
|
|
const Discord = require('discord.js');
|
|
const Voice = require('@discordjs/voice');
|
|
const { token } = require('../config.json');
|
|
const { exec } = require('child_process');
|
|
const { promisify } = require('util');
|
|
const got = require('got');
|
|
const youtubedl = require('youtube-dl')
|
|
|
|
const bot = new Discord.Client({
|
|
intents: [
|
|
Discord.Intents.FLAGS.GUILDS,
|
|
Discord.Intents.FLAGS.GUILD_MESSAGES,
|
|
Discord.Intents.FLAGS.GUILD_VOICE_STATES,
|
|
Discord.Intents.FLAGS.GUILD_MESSAGE_REACTIONS
|
|
],
|
|
});
|
|
|
|
bot.once('ready', () => {
|
|
console.log('Ready!');
|
|
});
|
|
|
|
const prefix = ';';
|
|
const embedColor = 0xa0ffa5;
|
|
const foggyImg = 'https://cdn.discordapp.com/attachments/789023763396165633/898908687299657728/552bb191-93e3-4eaa-b935-d5031f3845e7.gif';
|
|
const ffprobeRegex = /Audio: ([^,\n]+), (\d+) ?Hz, ?([^,\n]+), ?([^,\n]+)(, ?([^,\n]+))?/;
|
|
|
|
async function checkVoiceChannel(msg) {
|
|
if (!msg.guild) return;
|
|
|
|
const clientVoiceState = msg.guild.me.voice;
|
|
const memberVoiceState = msg.member.voice;
|
|
const voiceConnection = await Voice.getVoiceConnection(msg.guild.id);
|
|
|
|
if (clientVoiceState.channelId === memberVoiceState.channelId && voiceConnection) {
|
|
console.log('reusing connection');
|
|
return voiceConnection;
|
|
}
|
|
if (!memberVoiceState.channelId) {
|
|
msg.channel.send('you aren\'t in a voice channel!');
|
|
return;
|
|
}
|
|
if (clientVoiceState.channelId && clientVoiceState.channelId !== memberVoiceState.channelId) {
|
|
msg.channel.send('you are in a different voice channel!');
|
|
return;
|
|
}
|
|
|
|
console.log('creating new connection');
|
|
return Voice.joinVoiceChannel({
|
|
channelId: msg.member.voice.channelId,
|
|
guildId: msg.guild.id,
|
|
adapterCreator: msg.guild.voiceAdapterCreator,
|
|
});
|
|
}
|
|
|
|
/*
|
|
{
|
|
url: string,
|
|
}
|
|
*/
|
|
let queue = {};
|
|
let players = {};
|
|
let playerStartTime = {};
|
|
|
|
function advanceQueue(id, channel, subscription, connection) {
|
|
if (queue[id][1]) {
|
|
const song = queue[id][1];
|
|
const embed = new Discord.MessageEmbed()
|
|
.setDescription(`now playing: **${song.titleFormat}**`)
|
|
.setThumbnail(song.thumbnail)
|
|
.setColor(embedColor)
|
|
.setAuthor('foggy ♫', foggyImg);
|
|
channel.send({embeds: [embed]});
|
|
play(queue[id][1], id, connection);
|
|
queue[id] = queue[id].slice(1);
|
|
} else {
|
|
channel.send('no songs left, leaving');
|
|
console.log('destroyig');
|
|
subscription.unsubscribe();
|
|
delete players[id];
|
|
connection.destroy();
|
|
queue[id] = [];
|
|
}
|
|
}
|
|
|
|
async function play(song, id, connection, channel) {
|
|
// const stream = ytdl(url, { filter: 'audioonly' });
|
|
|
|
let player;
|
|
let subscription;
|
|
|
|
let url = song.directUrl;
|
|
|
|
if (song.redownload) {
|
|
let err, out
|
|
|
|
try {
|
|
err, out = await promisify(exec)('youtube-dl -f bestaudio --youtube-skip-dash-manifest --force-ipv4 -g "' + song.originalUrl + '"');
|
|
} catch(err_) {
|
|
err = err_;
|
|
}
|
|
|
|
if (err) {
|
|
console.log(err);
|
|
channel.send(`failed to retrieve youtube-dl info!: \`\`\`${err}\`\`\``);
|
|
advanceQueue(id, channel, subscription, connection);
|
|
return;
|
|
}
|
|
|
|
url = out.stdout;
|
|
}
|
|
|
|
const stream = got(url, {isStream: true});
|
|
const resource = Voice.createAudioResource(stream, { inputType: Voice.StreamType.Arbitrary });
|
|
|
|
if (players[id]) {
|
|
console.log('reusing player');
|
|
player = players[id];
|
|
} else {
|
|
console.log('creating new player');
|
|
player = Voice.createAudioPlayer();
|
|
players[id] = player;
|
|
subscription = connection.subscribe(player);
|
|
|
|
player.on(Voice.AudioPlayerStatus.Idle, () => {
|
|
advanceQueue(id, channel, subscription, connection);
|
|
});
|
|
player.on(Voice.AudioPlayerStatus.Playing, () => {
|
|
playerStartTime[id] = Date.now();
|
|
})
|
|
}
|
|
|
|
player.play(resource);
|
|
|
|
return player;
|
|
}
|
|
|
|
function rawYTDLQueue(info) {
|
|
let song = {
|
|
originalUrl: info.webpage_url || url,
|
|
directUrl: info.url,
|
|
redownload: true,
|
|
title: info.title,
|
|
thumbnail: info.thumbnail,
|
|
titleFormat: info.title,
|
|
duration: info._duration_raw,
|
|
fileType: info.acodec,
|
|
bitrate: info.abr,
|
|
};
|
|
|
|
if (song.track && song.artist && song.album) {
|
|
song.titleFormat = `${song.artist} - [${song.track}](${info.webpage_url}) from ${song.album}`;
|
|
} else {
|
|
if (song.originalUrl.startsWith('http')) song.titleFormat = `[${song.title}](${song.originalUrl})`;
|
|
}
|
|
|
|
return song;
|
|
}
|
|
|
|
async function queueUp(url, id) {
|
|
if (!queue[id]) queue[id] = [];
|
|
|
|
let song = {};
|
|
|
|
let err, info;
|
|
try {
|
|
err, info = await promisify(youtubedl.getInfo)(url, ['--force-ipv4']);
|
|
} catch(err_) {
|
|
err = err_;
|
|
}
|
|
|
|
if (err) {
|
|
return [null, `failed to retrieve youtube-dl info!: \`\`\`${err}\`\`\``];
|
|
} else {
|
|
if (info.length) {
|
|
info.forEach(i => {
|
|
let song = rawYTDLQueue(i);
|
|
queue[id].push(song);
|
|
});
|
|
return [info.length, null];
|
|
} else {
|
|
song = rawYTDLQueue(info);
|
|
}
|
|
}
|
|
|
|
queue[id].push(song)
|
|
return [1, null];
|
|
}
|
|
|
|
bot.on('messageCreate', async (msg) => {
|
|
const content = msg.content;
|
|
const params = content.replace(prefix, '').split(' ');
|
|
const cmd = params[0];
|
|
|
|
if (!msg.guild) return;
|
|
|
|
if ((cmd === 'play' || cmd === 'p') && params[1]) {
|
|
const connection = await checkVoiceChannel(msg);
|
|
if (!connection) return;
|
|
const url = params.slice(1).join(" ");
|
|
|
|
console.log('queueing');
|
|
msg.channel.send('queueing...');
|
|
|
|
let [q, e] = await queueUp(url, msg.guild.id);
|
|
if (!q && e) [q, e] = await queueUp('ytsearch:' + url, msg.guild.id);
|
|
if (!q && e) return msg.channel.send(e);
|
|
const song = queue[msg.guild.id][queue[msg.guild.id].length - 1];
|
|
|
|
if (queue[msg.guild.id].length === 1) {
|
|
const embed = new Discord.MessageEmbed()
|
|
.setDescription(`now playing: **${song.titleFormat}**`)
|
|
.setThumbnail(song.thumbnail)
|
|
.setColor(embedColor)
|
|
.setAuthor('foggy ♫', foggyImg);
|
|
|
|
msg.channel.send({embeds: [embed]});
|
|
play(queue[msg.guild.id][0], msg.guild.id, connection, msg.channel);
|
|
} else {
|
|
let queueString = `queued: **${song.titleFormat}** _at position ${queue[msg.guild.id].length - 1}_`;
|
|
if (q > 1) queueString = `queued **${q} songs** _at positions ${queue[msg.guild.id].length - q} - ${queue[msg.guild.id].length - 1}_`;
|
|
|
|
const embed = new Discord.MessageEmbed()
|
|
.setDescription(queueString)
|
|
.setColor(embedColor)
|
|
.setAuthor('foggy ♫', foggyImg);
|
|
msg.channel.send({embeds: [embed]});
|
|
|
|
if (!players[msg.guild.id] || players[msg.guild.id].state === Voice.AudioPlayerStatus.Idle || players[msg.guild.id].state === Voice.AudioPlayerStatus.Paused) {
|
|
play(queue[msg.guild.id][0], msg.guild.id, connection, msg.channel);
|
|
}
|
|
}
|
|
} else if (cmd === 'skip' || cmd === 's') {
|
|
const player = players[msg.guild.id];
|
|
if (!player) return msg.channel.send('the bot isn\'t playing any music!');
|
|
|
|
const song = queue[msg.guild.id][0];
|
|
const embed = new Discord.MessageEmbed()
|
|
.setDescription(`skipped **${song.titleFormat}**`)
|
|
.setColor(embedColor)
|
|
.setAuthor('foggy ♫', foggyImg);
|
|
msg.channel.send({embeds: [embed]});
|
|
player.stop();
|
|
} else if (cmd === 'queue' || cmd === 'q') {
|
|
const q = queue[msg.guild.id] || [];
|
|
if (q.length === 0) {
|
|
msg.channel.send('no songs queued!');
|
|
} else {
|
|
msg.channel.send('```' + q.map((m, i) => `${i === 0 ? 'now playing:' : i + '.'} ${m.title}`).join('\n') + '```');
|
|
}
|
|
} else if (cmd === 'np' || cmd === 'nowplaying') {
|
|
const song = (queue[msg.guild.id] || [])[0];
|
|
if (!song) return msg.channel.send('no song playing!');
|
|
|
|
let progress = (Date.now() - (playerStartTime[msg.guild.id] || 0)) / 1000 / song.duration;
|
|
let progressLength = 20;
|
|
let progressStr = '';
|
|
if (song.duration === -1) {
|
|
progressStr = '🔘 `where?..`';
|
|
} else if (progress < 0 || progress > 1) {
|
|
progressStr = '🔘 `buffering,,`';
|
|
} else {
|
|
progressStr = `${'▬'.repeat(Math.floor(Math.abs(progress) * progressLength))}🔘${'▬'.repeat(Math.floor((1 - Math.abs(progress)) * progressLength))}`;
|
|
}
|
|
|
|
let embed = new Discord.MessageEmbed()
|
|
.setDescription(`now playing: **${song.titleFormat}**\n${progressStr}`)
|
|
.setFooter(`${song.fileType} • ${song.bitrate}KB/s`)
|
|
.setThumbnail(song.thumbnail)
|
|
.setColor(embedColor)
|
|
.setAuthor('foggy ♫', foggyImg);
|
|
|
|
if (queue[msg.guild.id].length !== 1) {
|
|
embed = embed.addField('up next', '```' + queue[msg.guild.id].slice(1, 5).map((m, i) => {
|
|
if (i === 3) {
|
|
return '...';
|
|
} else {
|
|
return `${i + 1}. ${m.title}`;
|
|
}
|
|
}).join('\n') + '```')
|
|
}
|
|
|
|
msg.channel.send({embeds: [embed]});
|
|
}
|
|
});
|
|
|
|
// Login to Discord with your bot's token
|
|
bot.login(token);
|