2019-09-13 20:02:41 +00:00
const client = require ( "./client.js" ) ;
2019-10-24 18:54:23 +00:00
const logger = require ( "./logger.js" ) ;
2020-07-06 20:19:30 +00:00
const paginator = require ( "./pagination/pagination.js" ) ;
2020-06-27 17:18:26 +00:00
const fetch = require ( "node-fetch" ) ;
2021-03-05 21:13:54 +00:00
const fs = require ( "fs" ) ;
2020-07-06 20:19:30 +00:00
const moment = require ( "moment" ) ;
require ( "moment-duration-format" ) ;
2021-03-05 18:06:22 +00:00
const { Manager } = require ( "lavacord" ) ;
2019-09-13 20:02:41 +00:00
2021-03-05 21:13:54 +00:00
let nodes ;
2020-06-27 17:18:26 +00:00
2020-07-06 20:19:30 +00:00
exports . players = new Map ( ) ;
2020-09-02 02:52:12 +00:00
exports . queues = new Map ( ) ;
2020-07-06 20:19:30 +00:00
const skipVotes = new Map ( ) ;
exports . manager ;
2020-06-27 17:18:26 +00:00
2020-06-27 21:34:31 +00:00
exports . status = false ;
2020-07-16 14:28:09 +00:00
exports . connected = false ;
2020-06-27 21:34:31 +00:00
exports . checkStatus = async ( ) => {
2021-03-05 21:13:54 +00:00
const json = await fs . promises . readFile ( "./servers.json" , { encoding : "utf8" } ) ;
nodes = JSON . parse ( json ) . lava ;
2020-12-11 22:46:05 +00:00
const newNodes = [ ] ;
2020-06-27 21:34:31 +00:00
for ( const node of nodes ) {
try {
const response = await fetch ( ` http:// ${ node . host } : ${ node . port } /version ` , { headers : { Authorization : node . password } } ) . then ( res => res . text ( ) ) ;
2020-12-11 22:46:05 +00:00
if ( response ) newNodes . push ( node ) ;
2020-06-27 21:34:31 +00:00
} catch {
2020-12-11 22:46:05 +00:00
logger . log ( ` Failed to get status of Lavalink node ${ node . host } . ` ) ;
2020-06-27 21:34:31 +00:00
}
}
2020-12-11 22:46:05 +00:00
nodes = newNodes ;
this . status = newNodes . length === 0 ? true : false ;
2020-06-27 21:34:31 +00:00
return this . status ;
} ;
2020-06-27 17:18:26 +00:00
exports . connect = async ( ) => {
2021-03-05 18:06:22 +00:00
this . manager = new Manager ( nodes , {
user : client . user . id ,
shards : client . shards . size || 1 ,
send : ( packet ) => {
const guild = client . guilds . get ( packet . d . guild _id ) ;
if ( ! guild ) return ;
return guild . shard . sendWS ( packet . op , packet . d ) ;
}
2020-06-27 17:18:26 +00:00
} ) ;
2020-07-06 20:19:30 +00:00
const { length } = await this . manager . connect ( ) ;
2020-06-27 17:18:26 +00:00
logger . log ( ` Successfully connected to ${ length } Lavalink node(s). ` ) ;
2020-07-16 14:28:09 +00:00
exports . connected = true ;
2020-07-06 20:19:30 +00:00
this . manager . on ( "error" , ( error , node ) => {
2020-06-27 17:18:26 +00:00
logger . error ( ` An error occurred on Lavalink node ${ node } : ${ error } ` ) ;
} ) ;
2021-01-08 18:08:10 +00:00
return length ;
2020-06-27 17:18:26 +00:00
} ;
2020-07-06 20:19:30 +00:00
exports . play = async ( sound , message , music = false ) => {
2021-03-05 21:32:37 +00:00
if ( ! this . manager ) return ` ${ message . author . mention } , the sound commands are still starting up! ` ;
2020-12-26 18:17:10 +00:00
if ( ! message . channel . guild ) return ` ${ message . author . mention } , this command only works in servers! ` ;
2020-12-11 19:52:02 +00:00
if ( ! message . member . voiceState . channelID ) return ` ${ message . author . mention } , you need to be in a voice channel first! ` ;
2021-02-07 17:08:55 +00:00
if ( ! message . channel . guild . members . get ( client . user . id ) . permissions . has ( "voiceConnect" ) || ! message . channel . permissionsOf ( client . user . id ) . has ( "voiceConnect" ) ) return ` ${ message . author . mention } , I can't join this voice channel! ` ;
2020-07-06 20:19:30 +00:00
const voiceChannel = message . channel . guild . channels . get ( message . member . voiceState . channelID ) ;
2020-12-11 19:52:02 +00:00
if ( ! voiceChannel . permissionsOf ( client . user . id ) . has ( "voiceConnect" ) ) return ` ${ message . author . mention } , I don't have permission to join this voice channel! ` ;
2020-11-05 21:40:18 +00:00
const player = this . players . get ( message . channel . guild . id ) ;
2020-12-11 19:52:02 +00:00
if ( ! music && this . manager . voiceStates . has ( message . channel . guild . id ) && ( player && player . type === "music" ) ) return ` ${ message . author . mention } , I can't play a sound effect while playing music! ` ;
2020-07-06 20:19:30 +00:00
const node = this . manager . idealNodes [ 0 ] ;
2021-01-04 16:29:18 +00:00
if ( ! music && ! nodes . filter ( obj => obj . host === node . host ) [ 0 ] . local ) {
sound = sound . replace ( /\.\// , "https://raw.githubusercontent.com/esmBot/esmBot/master/" ) ;
}
2020-07-06 20:19:30 +00:00
const { tracks } = await fetch ( ` http:// ${ node . host } : ${ node . port } /loadtracks?identifier= ${ sound } ` , { headers : { Authorization : node . password } } ) . then ( res => res . json ( ) ) ;
2020-09-02 02:52:12 +00:00
const oldQueue = this . queues . get ( voiceChannel . guild . id ) ;
2021-03-06 04:13:58 +00:00
if ( ! tracks || tracks . length === 0 ) return ` ${ message . author . mention } , I couldn't find that song! ` ;
2020-09-22 20:33:07 +00:00
if ( music ) {
2020-12-14 16:58:31 +00:00
this . queues . set ( voiceChannel . guild . id , oldQueue ? [ ... oldQueue , tracks [ 0 ] . track ] : [ tracks [ 0 ] . track ] ) ;
2020-09-22 20:33:07 +00:00
}
2020-11-05 21:40:18 +00:00
let connection ;
if ( player ) {
2020-12-11 19:52:02 +00:00
connection = player . player ;
2020-11-05 21:40:18 +00:00
} else {
connection = await this . manager . join ( {
guild : voiceChannel . guild . id ,
channel : voiceChannel . id ,
node : node . id
} ) ;
}
2020-07-06 20:19:30 +00:00
2020-09-22 20:33:07 +00:00
if ( oldQueue && music ) {
2020-12-11 19:52:02 +00:00
return ` ${ message . author . mention } , your tune has been added to the queue! ` ;
2019-09-13 20:02:41 +00:00
} else {
2020-12-11 19:52:02 +00:00
this . nextSong ( message , connection , tracks [ 0 ] . track , tracks [ 0 ] . info , music , voiceChannel , player ? player . loop : false ) ;
return ;
2019-09-13 20:02:41 +00:00
}
} ;
2020-07-06 20:19:30 +00:00
2020-12-11 19:52:02 +00:00
exports . nextSong = async ( message , connection , track , info , music , voiceChannel , loop = false , inQueue = false ) => {
2020-07-06 20:19:30 +00:00
const parts = Math . floor ( ( 0 / info . length ) * 10 ) ;
const playingMessage = await client . createMessage ( message . channel . id , ! music ? "🔊 Playing sound..." : {
"embed" : {
"color" : 16711680 ,
"author" : {
"name" : "Now Playing" ,
"icon_url" : client . user . avatarURL
} ,
"fields" : [ {
"name" : "ℹ ️ Title:" ,
"value" : info . title
} ,
{
"name" : "🎤 Artist:" ,
"value" : info . author
} ,
{
"name" : "💬 Channel:" ,
"value" : voiceChannel . name
} ,
{
"name" : ` ${ "▬" . repeat ( parts ) } 🔘 ${ "▬" . repeat ( 10 - parts ) } ` ,
"value" : ` ${ moment . duration ( 0 ) . format ( "m:ss" , { trim : false } )}/ ${ info . isStream ? "∞" : moment . duration ( info . length ) . format ( "m:ss" , { trim : false } )} `
} ]
}
} ) ;
await connection . play ( track ) ;
2020-12-11 19:52:02 +00:00
this . players . set ( voiceChannel . guild . id , { player : connection , type : music ? "music" : "sound" , host : message . author . id , voiceChannel : voiceChannel , originalChannel : message . channel , loop : loop } ) ;
2020-11-05 21:40:18 +00:00
if ( inQueue && connection . listeners ( "error" ) . length === 0 ) {
2020-08-16 16:48:37 +00:00
connection . on ( "error" , ( error ) => {
2020-09-23 19:12:39 +00:00
if ( playingMessage . channel . messages . get ( playingMessage . id ) ) playingMessage . delete ( ) ;
2020-08-16 16:48:37 +00:00
this . manager . leave ( voiceChannel . guild . id ) ;
connection . destroy ( ) ;
this . players . delete ( voiceChannel . guild . id ) ;
2020-09-02 02:52:12 +00:00
this . queues . delete ( voiceChannel . guild . id ) ;
2020-08-16 16:48:37 +00:00
logger . error ( error ) ;
} ) ;
}
2020-11-05 21:40:18 +00:00
if ( connection . listeners ( "end" ) . length === 0 ) {
connection . on ( "end" , async ( data ) => {
if ( data . reason === "REPLACED" ) return ;
const queue = this . queues . get ( voiceChannel . guild . id ) ;
2020-12-11 19:52:02 +00:00
const isLooping = this . players . get ( voiceChannel . guild . id ) . loop ;
let newQueue ;
if ( isLooping ) {
queue . push ( queue . shift ( ) ) ;
newQueue = queue ;
} else {
newQueue = queue ? queue . slice ( 1 ) : [ ] ;
}
2020-11-05 21:40:18 +00:00
this . queues . set ( voiceChannel . guild . id , newQueue ) ;
if ( newQueue . length === 0 ) {
this . manager . leave ( voiceChannel . guild . id ) ;
connection . destroy ( ) ;
this . players . delete ( voiceChannel . guild . id ) ;
this . queues . delete ( voiceChannel . guild . id ) ;
if ( music ) await client . createMessage ( message . channel . id , "🔊 The current voice channel session has ended." ) ;
if ( playingMessage . channel . messages . get ( playingMessage . id ) ) await playingMessage . delete ( ) ;
} else {
const track = await fetch ( ` http:// ${ connection . node . host } : ${ connection . node . port } /decodetrack?track= ${ encodeURIComponent ( newQueue [ 0 ] ) } ` , { headers : { Authorization : connection . node . password } } ) . then ( res => res . json ( ) ) ;
2020-12-11 19:52:02 +00:00
this . nextSong ( message , connection , newQueue [ 0 ] , track , music , voiceChannel , isLooping , true ) ;
2020-11-05 21:40:18 +00:00
if ( playingMessage . channel . messages . get ( playingMessage . id ) ) await playingMessage . delete ( ) ;
}
} ) ;
}
2020-07-06 20:19:30 +00:00
} ;
exports . stop = async ( message ) => {
2020-12-26 18:17:10 +00:00
if ( ! message . channel . guild ) return ` ${ message . author . mention } , this command only works in servers! ` ;
2020-12-11 19:52:02 +00:00
if ( ! message . member . voiceState . channelID ) return ` ${ message . author . mention } , you need to be in a voice channel first! ` ;
if ( ! message . channel . guild . members . get ( client . user . id ) . voiceState . channelID ) return ` ${ message . author . mention } , I'm not in a voice channel! ` ;
if ( this . players . get ( message . channel . guild . id ) . host !== message . author . id ) return ` ${ message . author . mention } , only the current voice session host can stop the music! ` ;
2020-07-06 20:19:30 +00:00
this . manager . leave ( message . channel . guild . id ) ;
2020-07-07 19:28:16 +00:00
const connection = this . players . get ( message . channel . guild . id ) . player ;
connection . destroy ( ) ;
this . players . delete ( message . channel . guild . id ) ;
2020-09-02 02:52:12 +00:00
this . queues . delete ( message . channel . guild . id ) ;
2020-12-11 19:52:02 +00:00
return "🔊 The current voice channel session has ended." ;
2020-07-06 20:19:30 +00:00
} ;
exports . skip = async ( message ) => {
2020-12-26 18:17:10 +00:00
if ( ! message . channel . guild ) return ` ${ message . author . mention } , this command only works in servers! ` ;
2020-12-11 19:52:02 +00:00
if ( ! message . member . voiceState . channelID ) return ` ${ message . author . mention } , you need to be in a voice channel first! ` ;
if ( ! message . channel . guild . members . get ( client . user . id ) . voiceState . channelID ) return ` ${ message . author . mention } , I'm not in a voice channel! ` ;
2020-07-06 20:19:30 +00:00
const player = this . players . get ( message . channel . guild . id ) ;
if ( player . host !== message . author . id ) {
2020-09-19 00:54:52 +00:00
const votes = skipVotes . has ( message . channel . guild . id ) ? skipVotes . get ( message . channel . guild . id ) : { count : 0 , ids : [ ] } ;
2020-12-11 19:52:02 +00:00
if ( votes . ids . includes ( message . author . id ) ) return ` ${ message . author . mention } , you've already voted to skip! ` ;
2020-09-19 00:54:52 +00:00
const newObject = {
count : votes . count + 1 ,
ids : [ ... votes . ids , message . author . id ] . filter ( item => ! ! item )
} ;
if ( votes . count + 1 === 3 ) {
2020-07-06 20:19:30 +00:00
player . player . stop ( message . channel . guild . id ) ;
2020-09-19 00:54:52 +00:00
skipVotes . set ( message . channel . guild . id , { count : 0 , ids : [ ] } ) ;
2020-07-06 20:19:30 +00:00
} else {
2020-09-19 00:54:52 +00:00
skipVotes . set ( message . channel . guild . id , newObject ) ;
2020-12-11 19:52:02 +00:00
return ` 🔊 Voted to skip song ( ${ votes . count + 1 } /3 people have voted). ` ;
2020-07-06 20:19:30 +00:00
}
} else {
player . player . stop ( message . channel . guild . id ) ;
2020-12-11 19:52:02 +00:00
return ;
2020-07-06 20:19:30 +00:00
}
} ;
exports . pause = async ( message ) => {
2020-12-26 18:17:10 +00:00
if ( ! message . channel . guild ) return ` ${ message . author . mention } , this command only works in servers! ` ;
2020-12-11 19:52:02 +00:00
if ( ! message . member . voiceState . channelID ) return ` ${ message . author . mention } , you need to be in a voice channel first! ` ;
if ( ! message . channel . guild . members . get ( client . user . id ) . voiceState . channelID ) return ` ${ message . author . mention } , I'm not in a voice channel! ` ;
if ( this . players . get ( message . channel . guild . id ) . host !== message . author . id ) return ` ${ message . author . mention } , only the current voice session host can pause/resume the music! ` ;
2020-07-06 20:19:30 +00:00
const player = this . players . get ( message . channel . guild . id ) . player ;
player . pause ( ! player . paused ? true : false ) ;
2020-12-11 19:52:02 +00:00
return ` 🔊 The player has been ${ ! player . paused ? "paused" : "resumed" } . ` ;
2020-07-06 20:19:30 +00:00
} ;
exports . playing = async ( message ) => {
2020-12-26 18:17:10 +00:00
if ( ! message . channel . guild ) return ` ${ message . author . mention } , this command only works in servers! ` ;
2020-12-11 19:52:02 +00:00
if ( ! message . member . voiceState . channelID ) return ` ${ message . author . mention } , you need to be in a voice channel first! ` ;
if ( ! message . channel . guild . members . get ( client . user . id ) . voiceState . channelID ) return ` ${ message . author . mention } , I'm not in a voice channel! ` ;
2020-07-06 20:19:30 +00:00
const player = this . players . get ( message . channel . guild . id ) . player ;
2020-12-11 19:52:02 +00:00
if ( ! player ) return ` ${ message . author . mention } , I'm not playing anything! ` ;
2020-07-06 20:19:30 +00:00
const track = await fetch ( ` http:// ${ player . node . host } : ${ player . node . port } /decodetrack?track= ${ encodeURIComponent ( player . track ) } ` , { headers : { Authorization : player . node . password } } ) . then ( res => res . json ( ) ) ;
const parts = Math . floor ( ( player . state . position / track . length ) * 10 ) ;
2020-12-11 19:52:02 +00:00
return {
2020-07-06 20:19:30 +00:00
"embed" : {
"color" : 16711680 ,
"author" : {
"name" : "Now Playing" ,
"icon_url" : client . user . avatarURL
} ,
"fields" : [ {
"name" : "ℹ ️ Title:" ,
2020-09-22 20:33:07 +00:00
"value" : track . title ? track . title : "Unknown"
2020-07-06 20:19:30 +00:00
} ,
{
"name" : "🎤 Artist:" ,
2020-09-22 20:33:07 +00:00
"value" : track . author ? track . author : "Unknown"
2020-07-06 20:19:30 +00:00
} ,
{
"name" : "💬 Channel:" ,
"value" : message . channel . guild . channels . get ( message . member . voiceState . channelID ) . name
} ,
{
"name" : ` ${ "▬" . repeat ( parts ) } 🔘 ${ "▬" . repeat ( 10 - parts ) } ` ,
"value" : ` ${ moment . duration ( player . state . position ) . format ( "m:ss" , { trim : false } )}/ ${ track . isStream ? "∞" : moment . duration ( track . length ) . format ( "m:ss" , { trim : false } )} `
} ]
}
2020-12-11 19:52:02 +00:00
} ;
2020-07-06 20:19:30 +00:00
} ;
exports . queue = async ( message ) => {
2020-12-26 18:17:10 +00:00
if ( ! message . channel . guild ) return ` ${ message . author . mention } , this command only works in servers! ` ;
2020-12-11 19:52:02 +00:00
if ( ! message . member . voiceState . channelID ) return ` ${ message . author . mention } , you need to be in a voice channel first! ` ;
if ( ! message . channel . guild . members . get ( client . user . id ) . voiceState . channelID ) return ` ${ message . author . mention } , I'm not in a voice channel! ` ;
2021-02-07 17:08:55 +00:00
if ( ! message . channel . guild . members . get ( client . user . id ) . permissions . has ( "addReactions" ) && ! message . channel . permissionsOf ( client . user . id ) . has ( "addReactions" ) ) return ` ${ message . author . mention } , I don't have the \` Add Reactions \` permission! ` ;
if ( ! message . channel . guild . members . get ( client . user . id ) . permissions . has ( "embedLinks" ) && ! message . channel . permissionsOf ( client . user . id ) . has ( "embedLinks" ) ) return ` ${ message . author . mention } , I don't have the \` Embed Links \` permission! ` ;
2020-09-02 02:52:12 +00:00
const queue = this . queues . get ( message . channel . guild . id ) ;
2020-12-11 19:52:02 +00:00
const player = this . players . get ( message . channel . guild . id ) ;
const tracks = await fetch ( ` http:// ${ player . player . node . host } : ${ player . player . node . port } /decodetracks ` , { method : "POST" , body : JSON . stringify ( queue ) , headers : { Authorization : player . player . node . password , "Content-Type" : "application/json" } } ) . then ( res => res . json ( ) ) ;
2020-07-06 20:19:30 +00:00
const trackList = [ ] ;
const firstTrack = tracks . shift ( ) ;
for ( const [ i , track ] of tracks . entries ( ) ) {
trackList . push ( ` ${ i + 1 } . ${ track . info . author } - ** ${ track . info . title } ** ( ${ track . info . isStream ? "∞" : moment . duration ( track . info . length ) . format ( "m:ss" , { trim : false } )}) ` ) ;
}
const pageSize = 5 ;
const embeds = [ ] ;
const groups = trackList . map ( ( item , index ) => {
return index % pageSize === 0 ? trackList . slice ( index , index + pageSize ) : null ;
} ) . filter ( Boolean ) ;
2020-07-06 21:39:56 +00:00
if ( groups . length === 0 ) groups . push ( "del" ) ;
2020-07-06 20:19:30 +00:00
for ( const [ i , value ] of groups . entries ( ) ) {
embeds . push ( {
"embed" : {
"author" : {
"name" : "Queue" ,
"icon_url" : client . user . avatarURL
} ,
"color" : 16711680 ,
"footer" : {
"text" : ` Page ${ i + 1 } of ${ groups . length } `
} ,
"fields" : [ {
"name" : "🎶 Now Playing" ,
"value" : ` ${ firstTrack . info . author } - ** ${ firstTrack . info . title } ** ( ${ firstTrack . info . isStream ? "∞" : moment . duration ( firstTrack . info . length ) . format ( "m:ss" , { trim : false } )}) `
2020-12-11 19:52:02 +00:00
} , {
"name" : "🔁 Looping?" ,
"value" : player . loop ? "Yes" : "No"
2020-07-06 20:19:30 +00:00
} , {
"name" : "🗒️ Queue" ,
2020-07-06 21:39:56 +00:00
"value" : value !== "del" ? value . join ( "\n" ) : "There's nothing in the queue!"
2020-07-06 20:19:30 +00:00
} ]
}
} ) ;
}
if ( embeds . length === 0 ) return ` ${ message . author . mention } , there's nothing in the queue! ` ;
return paginator ( message , embeds ) ;
2020-12-11 19:52:02 +00:00
} ;
exports . loop = async ( message ) => {
2020-12-26 18:17:10 +00:00
if ( ! message . channel . guild ) return ` ${ message . author . mention } , this command only works in servers! ` ;
2020-12-11 19:52:02 +00:00
if ( ! message . member . voiceState . channelID ) return ` ${ message . author . mention } , you need to be in a voice channel first! ` ;
if ( ! message . channel . guild . members . get ( client . user . id ) . voiceState . channelID ) return ` ${ message . author . mention } , I'm not in a voice channel! ` ;
if ( this . players . get ( message . channel . guild . id ) . host !== message . author . id ) return ` ${ message . author . mention } , only the current voice session host can loop the music! ` ;
const object = this . players . get ( message . channel . guild . id ) ;
object . loop = ! object . loop ;
this . players . set ( message . channel . guild . id , object ) ;
return object . loop ? "🔊 The player is now looping." : "🔊 The player is no longer looping." ;
2020-07-06 20:19:30 +00:00
} ;