2021-08-19 14:19:14 +00:00
import * as logger from "./logger.js" ;
import fetch from "node-fetch" ;
import fs from "fs" ;
import format from "format-duration" ;
2021-09-15 00:14:44 +00:00
import { Manager , Rest } from "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
2021-08-19 14:19:14 +00:00
export const players = new Map ( ) ;
export const queues = new Map ( ) ;
export const skipVotes = new Map ( ) ;
2020-07-06 20:19:30 +00:00
2021-08-19 14:19:14 +00:00
export let manager ;
export let status = false ;
export let connected = false ;
2020-07-16 14:28:09 +00:00
2021-08-19 14:19:14 +00:00
export async function checkStatus ( ) {
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 {
Class commands, improved sharding, and many other changes (#88)
* Load commands recursively
* Sort commands
* Missed a couple of spots
* missed even more spots apparently
* Ported commands in "fun" category to new class-based format, added babel eslint plugin
* Ported general commands, removed old/unneeded stuff, replaced moment with day, many more fixes I lost track of
* Missed a spot
* Removed unnecessary abort-controller package, add deprecation warning for mongo database
* Added imagereload, clarified premature end message
* Fixed docker-compose path issue, added total bot uptime to stats, more fixes for various parts
* Converted image commands into classes, fixed reload, ignore another WS event, cleaned up command handler and image runner
* Converted music/soundboard commands to class format
* Cleanup unnecessary logs
* awful tag command class port
* I literally somehow just learned that you can leave out the constructor in classes
* Pass client directly to commands/events, cleaned up command handler
* Migrated bot to eris-sharder, fixed some error handling stuff
* Remove unused modules
* Fixed type returning
* Switched back to Eris stable
* Some fixes and cleanup
* might wanna correct this
* Implement image command ratelimiting
* Added Bot token prefix, added imagestats, added running endpoint to API
2021-04-12 16:16:12 +00:00
logger . error ( ` 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 ;
2021-08-19 14:19:14 +00:00
status = newNodes . length === 0 ? true : false ;
return status ;
}
2020-06-27 21:34:31 +00:00
2021-08-19 14:19:14 +00:00
export async function connect ( client ) {
manager = new Manager ( nodes , {
2021-03-05 18:06:22 +00:00
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
} ) ;
2021-08-19 14:19:14 +00:00
const { length } = await manager . connect ( ) ;
2020-06-27 17:18:26 +00:00
logger . log ( ` Successfully connected to ${ length } Lavalink node(s). ` ) ;
2021-08-19 14:19:14 +00:00
connected = true ;
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 ;
2021-08-19 14:19:14 +00:00
}
2020-06-27 17:18:26 +00:00
2021-08-19 14:19:14 +00:00
export async function play ( client , sound , message , music = false ) {
if ( ! manager ) return "The sound commands are still starting up!" ;
2021-05-11 16:12:01 +00:00
if ( ! message . channel . guild ) return "This command only works in servers!" ;
if ( ! message . member . voiceState . channelID ) return "You need to be in a voice channel first!" ;
2021-08-30 21:17:20 +00:00
if ( ! message . channel . guild . permissionsOf ( client . user . id ) . has ( "voiceConnect" ) ) return "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 ) ;
2021-05-11 16:12:01 +00:00
if ( ! voiceChannel . permissionsOf ( client . user . id ) . has ( "voiceConnect" ) ) return "I don't have permission to join this voice channel!" ;
2021-08-19 14:19:14 +00:00
const player = players . get ( message . channel . guild . id ) ;
if ( ! music && manager . voiceStates . has ( message . channel . guild . id ) && ( player && player . type === "music" ) ) return "I can't play a sound effect while playing music!" ;
2022-01-08 21:54:34 +00:00
let node = manager . idealNodes [ 0 ] ;
if ( ! node ) {
const status = await checkStatus ( ) ;
if ( ! status ) {
await connect ( client ) ;
node = 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/" ) ;
}
2022-01-08 21:54:34 +00:00
let tracks , playlistInfo ;
try {
( { tracks , playlistInfo } = await Rest . load ( node , sound ) ) ;
} catch {
return "🔊 Hmmm, seems that all of the audio servers are down. Try again in a bit." ;
}
2021-08-19 14:19:14 +00:00
const oldQueue = queues . get ( voiceChannel . guild . id ) ;
2021-05-11 16:12:01 +00:00
if ( ! tracks || tracks . length === 0 ) return "I couldn't find that song!" ;
2020-09-22 20:33:07 +00:00
if ( music ) {
2021-09-15 03:40:44 +00:00
const sortedTracks = tracks . map ( ( val ) => { return val . track ; } ) ;
2022-01-07 17:44:18 +00:00
const playlistTracks = playlistInfo . selectedTrack ? sortedTracks : [ sortedTracks [ 0 ] ] ;
2021-09-15 00:14:44 +00:00
queues . set ( voiceChannel . guild . id , oldQueue ? [ ... oldQueue , ... playlistTracks ] : playlistTracks ) ;
2020-09-22 20:33:07 +00:00
}
2021-08-31 04:44:22 +00:00
const connection = await manager . join ( {
guild : voiceChannel . guild . id ,
channel : voiceChannel . id ,
node : node . id
} ) ;
2020-07-06 20:19:30 +00:00
2021-12-12 05:44:49 +00:00
if ( oldQueue && oldQueue . length !== 0 && music ) {
2022-01-06 00:48:08 +00:00
return ` Your ${ playlistInfo . name ? "playlist" : "tune" } \` ${ playlistInfo . name ? playlistInfo . name . trim ( ) : ( tracks [ 0 ] . info . title !== "" ? tracks [ 0 ] . info . title . trim ( ) : "(blank)" ) } \` has been added to the queue! ` ;
2019-09-13 20:02:41 +00:00
} else {
2021-09-20 17:26:40 +00:00
nextSong ( client , message , connection , tracks [ 0 ] . track , tracks [ 0 ] . info , music , voiceChannel , player ? player . loop : false , player ? player . shuffle : false ) ;
2020-12-11 19:52:02 +00:00
return ;
2019-09-13 20:02:41 +00:00
}
2021-08-19 14:19:14 +00:00
}
2020-07-06 20:19:30 +00:00
2021-09-20 17:26:40 +00:00
export async function nextSong ( client , message , connection , track , info , music , voiceChannel , loop = false , shuffle = false , lastTrack = null ) {
2021-08-31 04:44:22 +00:00
skipVotes . delete ( voiceChannel . guild . id ) ;
2020-07-06 20:19:30 +00:00
const parts = Math . floor ( ( 0 / info . length ) * 10 ) ;
Class commands, improved sharding, and many other changes (#88)
* Load commands recursively
* Sort commands
* Missed a couple of spots
* missed even more spots apparently
* Ported commands in "fun" category to new class-based format, added babel eslint plugin
* Ported general commands, removed old/unneeded stuff, replaced moment with day, many more fixes I lost track of
* Missed a spot
* Removed unnecessary abort-controller package, add deprecation warning for mongo database
* Added imagereload, clarified premature end message
* Fixed docker-compose path issue, added total bot uptime to stats, more fixes for various parts
* Converted image commands into classes, fixed reload, ignore another WS event, cleaned up command handler and image runner
* Converted music/soundboard commands to class format
* Cleanup unnecessary logs
* awful tag command class port
* I literally somehow just learned that you can leave out the constructor in classes
* Pass client directly to commands/events, cleaned up command handler
* Migrated bot to eris-sharder, fixed some error handling stuff
* Remove unused modules
* Fixed type returning
* Switched back to Eris stable
* Some fixes and cleanup
* might wanna correct this
* Implement image command ratelimiting
* Added Bot token prefix, added imagestats, added running endpoint to API
2021-04-12 16:16:12 +00:00
let playingMessage ;
2021-08-19 14:19:14 +00:00
if ( ! music && players . get ( voiceChannel . guild . id ) ) {
const playMessage = players . get ( voiceChannel . guild . id ) . playMessage ;
2021-08-17 04:04:11 +00:00
try {
2021-08-31 04:44:22 +00:00
players . delete ( voiceChannel . guild . id ) ;
2021-12-13 21:46:08 +00:00
await playMessage . delete ( ) ;
2021-08-17 04:04:11 +00:00
} catch {
// no-op
}
2021-07-06 13:25:12 +00:00
}
2021-08-31 04:44:22 +00:00
if ( music && lastTrack === track && players . get ( voiceChannel . guild . id ) ) {
2021-08-19 14:19:14 +00:00
playingMessage = players . get ( voiceChannel . guild . id ) . playMessage ;
Class commands, improved sharding, and many other changes (#88)
* Load commands recursively
* Sort commands
* Missed a couple of spots
* missed even more spots apparently
* Ported commands in "fun" category to new class-based format, added babel eslint plugin
* Ported general commands, removed old/unneeded stuff, replaced moment with day, many more fixes I lost track of
* Missed a spot
* Removed unnecessary abort-controller package, add deprecation warning for mongo database
* Added imagereload, clarified premature end message
* Fixed docker-compose path issue, added total bot uptime to stats, more fixes for various parts
* Converted image commands into classes, fixed reload, ignore another WS event, cleaned up command handler and image runner
* Converted music/soundboard commands to class format
* Cleanup unnecessary logs
* awful tag command class port
* I literally somehow just learned that you can leave out the constructor in classes
* Pass client directly to commands/events, cleaned up command handler
* Migrated bot to eris-sharder, fixed some error handling stuff
* Remove unused modules
* Fixed type returning
* Switched back to Eris stable
* Some fixes and cleanup
* might wanna correct this
* Implement image command ratelimiting
* Added Bot token prefix, added imagestats, added running endpoint to API
2021-04-12 16:16:12 +00:00
} else {
2022-01-06 00:48:08 +00:00
try {
playingMessage = await client . createMessage ( message . channel . id , ! music ? "🔊 Playing sound..." : {
embeds : [ {
color : 16711680 ,
author : {
name : "Now Playing" ,
icon _url : client . user . avatarURL
} ,
fields : [ {
name : "ℹ ️ Title:" ,
value : info . title && info . title . trim ( ) !== "" ? info . title : "(blank)"
} ,
{
name : "🎤 Artist:" ,
value : info . title && info . author . trim ( ) !== "" ? info . author : "(blank)"
} ,
{
name : "💬 Channel:" ,
value : voiceChannel . name
} ,
{
name : ` ${ "▬" . repeat ( parts ) } 🔘 ${ "▬" . repeat ( 10 - parts ) } ` ,
value : ` 0:00/ ${ info . isStream ? "∞" : format ( info . length ) } `
} ]
Class commands, improved sharding, and many other changes (#88)
* Load commands recursively
* Sort commands
* Missed a couple of spots
* missed even more spots apparently
* Ported commands in "fun" category to new class-based format, added babel eslint plugin
* Ported general commands, removed old/unneeded stuff, replaced moment with day, many more fixes I lost track of
* Missed a spot
* Removed unnecessary abort-controller package, add deprecation warning for mongo database
* Added imagereload, clarified premature end message
* Fixed docker-compose path issue, added total bot uptime to stats, more fixes for various parts
* Converted image commands into classes, fixed reload, ignore another WS event, cleaned up command handler and image runner
* Converted music/soundboard commands to class format
* Cleanup unnecessary logs
* awful tag command class port
* I literally somehow just learned that you can leave out the constructor in classes
* Pass client directly to commands/events, cleaned up command handler
* Migrated bot to eris-sharder, fixed some error handling stuff
* Remove unused modules
* Fixed type returning
* Switched back to Eris stable
* Some fixes and cleanup
* might wanna correct this
* Implement image command ratelimiting
* Added Bot token prefix, added imagestats, added running endpoint to API
2021-04-12 16:16:12 +00:00
} ]
2022-01-06 00:48:08 +00:00
} ) ;
} catch {
// no-op
}
Class commands, improved sharding, and many other changes (#88)
* Load commands recursively
* Sort commands
* Missed a couple of spots
* missed even more spots apparently
* Ported commands in "fun" category to new class-based format, added babel eslint plugin
* Ported general commands, removed old/unneeded stuff, replaced moment with day, many more fixes I lost track of
* Missed a spot
* Removed unnecessary abort-controller package, add deprecation warning for mongo database
* Added imagereload, clarified premature end message
* Fixed docker-compose path issue, added total bot uptime to stats, more fixes for various parts
* Converted image commands into classes, fixed reload, ignore another WS event, cleaned up command handler and image runner
* Converted music/soundboard commands to class format
* Cleanup unnecessary logs
* awful tag command class port
* I literally somehow just learned that you can leave out the constructor in classes
* Pass client directly to commands/events, cleaned up command handler
* Migrated bot to eris-sharder, fixed some error handling stuff
* Remove unused modules
* Fixed type returning
* Switched back to Eris stable
* Some fixes and cleanup
* might wanna correct this
* Implement image command ratelimiting
* Added Bot token prefix, added imagestats, added running endpoint to API
2021-04-12 16:16:12 +00:00
}
2021-09-13 22:24:15 +00:00
connection . removeAllListeners ( "error" ) ;
connection . removeAllListeners ( "end" ) ;
2020-07-06 20:19:30 +00:00
await connection . play ( track ) ;
2021-09-19 22:49:02 +00:00
await connection . volume ( 75 ) ;
2021-09-20 17:26:40 +00:00
players . set ( voiceChannel . guild . id , { player : connection , type : music ? "music" : "sound" , host : message . author . id , voiceChannel : voiceChannel , originalChannel : message . channel , loop : loop , shuffle : shuffle , playMessage : playingMessage } ) ;
2021-12-13 21:46:08 +00:00
connection . once ( "error" , async ( error ) => {
2021-09-20 23:32:59 +00:00
try {
2021-12-13 21:46:08 +00:00
if ( playingMessage . channel . messages . get ( playingMessage . id ) ) await playingMessage . delete ( ) ;
2021-09-20 23:32:59 +00:00
const playMessage = players . get ( voiceChannel . guild . id ) . playMessage ;
2021-12-13 21:46:08 +00:00
if ( playMessage . channel . messages . get ( playMessage . id ) ) await playMessage . delete ( ) ;
2021-09-20 23:32:59 +00:00
} catch {
// no-op
}
2022-01-08 21:54:34 +00:00
try {
await manager . leave ( voiceChannel . guild . id ) ;
await connection . destroy ( ) ;
} catch {
// no-op
}
2021-09-13 22:24:15 +00:00
connection . removeAllListeners ( "end" ) ;
players . delete ( voiceChannel . guild . id ) ;
queues . delete ( voiceChannel . guild . id ) ;
logger . error ( error ) ;
2022-01-08 21:54:34 +00:00
await client . createMessage ( message . channel . id , ` 🔊 Looks like there was an error regarding sound playback: \n \` \` \` ${ error . type } : ${ error . error } \` \` \` ` ) ;
2021-09-13 22:24:15 +00:00
} ) ;
connection . on ( "end" , async ( data ) => {
if ( data . reason === "REPLACED" ) return ;
2021-09-20 17:26:40 +00:00
let queue = queues . get ( voiceChannel . guild . id ) ;
2021-09-13 22:24:15 +00:00
const player = players . get ( voiceChannel . guild . id ) ;
2021-12-12 05:44:49 +00:00
if ( player && process . env . STAYVC === "true" ) {
player . type = "idle" ;
players . set ( voiceChannel . guild . id , player ) ;
}
2021-09-13 22:24:15 +00:00
let newQueue ;
2021-09-20 17:26:40 +00:00
if ( player && player . shuffle ) {
if ( player . loop ) {
queue . push ( queue . shift ( ) ) ;
} else {
queue = queue . slice ( 1 ) ;
}
queue . unshift ( queue . splice ( Math . floor ( Math . random ( ) * queue . length ) , 1 ) [ 0 ] ) ;
newQueue = queue ;
} else if ( player && player . loop ) {
2021-09-13 22:24:15 +00:00
queue . push ( queue . shift ( ) ) ;
newQueue = queue ;
} else {
newQueue = queue ? queue . slice ( 1 ) : [ ] ;
}
queues . set ( voiceChannel . guild . id , newQueue ) ;
2021-12-12 05:44:49 +00:00
if ( newQueue . length !== 0 ) {
const newTrack = await Rest . decode ( connection . node , newQueue [ 0 ] ) ;
nextSong ( client , message , connection , newQueue [ 0 ] , newTrack , music , voiceChannel , player . loop , player . shuffle , track ) ;
try {
if ( newQueue [ 0 ] !== track && playingMessage . channel . messages . get ( playingMessage . id ) ) await playingMessage . delete ( ) ;
if ( newQueue [ 0 ] !== track && player . playMessage . channel . messages . get ( player . playMessage . id ) ) await player . playMessage . delete ( ) ;
} catch {
// no-op
}
} else if ( process . env . STAYVC !== "true" ) {
2021-12-13 22:09:12 +00:00
await manager . leave ( voiceChannel . guild . id ) ;
await connection . destroy ( ) ;
2021-08-19 14:19:14 +00:00
players . delete ( voiceChannel . guild . id ) ;
queues . delete ( voiceChannel . guild . id ) ;
2021-09-19 22:49:02 +00:00
skipVotes . delete ( voiceChannel . guild . id ) ;
2021-09-13 22:24:15 +00:00
if ( music ) await client . createMessage ( message . channel . id , "🔊 The current voice channel session has ended." ) ;
try {
if ( playingMessage . channel . messages . get ( playingMessage . id ) ) await playingMessage . delete ( ) ;
if ( player && player . playMessage . channel . messages . get ( player . playMessage . id ) ) await player . playMessage . delete ( ) ;
} catch {
// no-op
2020-12-11 19:52:02 +00:00
}
2021-09-13 22:24:15 +00:00
} else {
try {
2021-12-12 05:44:49 +00:00
if ( playingMessage . channel . messages . get ( playingMessage . id ) ) await playingMessage . delete ( ) ;
if ( player && player . playMessage . channel . messages . get ( player . playMessage . id ) ) await player . playMessage . delete ( ) ;
2021-09-13 22:24:15 +00:00
} catch {
// no-op
2020-11-05 21:40:18 +00:00
}
2021-09-13 22:24:15 +00:00
}
} ) ;
2021-12-12 05:44:49 +00:00
}