init commit (not really)
parent
b7697967fe
commit
1a61f89856
@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
config.json
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "foggy",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@discordjs/opus": "^0.5.3",
|
||||
"@discordjs/voice": "^0.6.0",
|
||||
"discord.js": "^13.2.0",
|
||||
"ffmpeg-static": "^4.4.0",
|
||||
"got": "^11.8.2",
|
||||
"sodium": "^3.0.2",
|
||||
"youtube-dl": "^3.5.0",
|
||||
"youtube-dl-wrap": "^2.1.1"
|
||||
}
|
||||
}
|
@ -0,0 +1,288 @@
|
||||
// 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);
|
Reference in New Issue