Replace Lavalink wrapper, migrate to pnpm, add ko-fi sponsor link

This commit is contained in:
Essem 2022-06-14 00:38:01 -05:00
parent cefafba8fb
commit 10becff3a0
No known key found for this signature in database
GPG Key ID: 7D497397CC3A2A8C
19 changed files with 2768 additions and 8025 deletions

1
.github/FUNDING.yml vendored
View File

@ -1 +1,2 @@
patreon: TheEssem patreon: TheEssem
ko_fi: TheEssem

View File

@ -17,10 +17,12 @@ jobs:
uses: actions/checkout@v1 uses: actions/checkout@v1
- name: Setup Node.js environment - name: Setup Node.js environment
uses: actions/setup-node@v2.1.2 uses: actions/setup-node@v2.1.2
- name: Setup pnpm
uses: pnpm/action-setup@v2.2.2
- name: Install dependencies - name: Install dependencies
run: sudo apt install libvips-dev libmagick++-dev run: sudo apt install libvips-dev libmagick++-dev
- name: Build - name: Build
run: npm install --legacy-peer-deps && npm run build run: pnpm install && npm run build
win32: win32:
runs-on: windows-latest runs-on: windows-latest
@ -29,10 +31,12 @@ jobs:
uses: actions/checkout@v1 uses: actions/checkout@v1
- name: Setup Node.js environment - name: Setup Node.js environment
uses: actions/setup-node@v2.1.2 uses: actions/setup-node@v2.1.2
- name: Setup pnpm
uses: pnpm/action-setup@v2.2.2
- name: Install dependencies - name: Install dependencies
run: choco install imagemagick -PackageParameters InstallDevelopmentHeaders=true run: choco install imagemagick -PackageParameters InstallDevelopmentHeaders=true
- name: Build - name: Build
run: npm install --legacy-peer-deps && npm run build run: pnpm install && npm run build
darwin: darwin:
runs-on: macos-latest runs-on: macos-latest
@ -41,7 +45,9 @@ jobs:
uses: actions/checkout@v1 uses: actions/checkout@v1
- name: Setup Node.js environment - name: Setup Node.js environment
uses: actions/setup-node@v2.1.2 uses: actions/setup-node@v2.1.2
- name: Setup pnpm
uses: pnpm/action-setup@v2.2.2
- name: Install dependencies - name: Install dependencies
run: brew install imagemagick vips run: brew install imagemagick vips
- name: Build - name: Build
run: npm install --legacy-peer-deps && npm run build run: pnpm install && npm run build

View File

@ -52,8 +52,6 @@ jobs:
#scope: # optional #scope: # optional
# Used to pull node distributions from node-versions. Since there's a default, this is typically not supplied by the user. # Used to pull node distributions from node-versions. Since there's a default, this is typically not supplied by the user.
#token: # optional, default is ${{ github.token }} #token: # optional, default is ${{ github.token }}
- run: sudo apt install libvips-dev libmagick++-dev
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
@ -65,20 +63,5 @@ jobs:
# Prefix the list here with "+" to use these queries and those in the config file. # Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main # queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
#- name: Autobuild
# uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# npm install --legacy-peer-deps
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2 uses: github/codeql-action/analyze@v2

View File

@ -20,10 +20,8 @@ class AvatarCommand extends Command {
return self.dynamicAvatarURL(null, 512); return self.dynamicAvatarURL(null, 512);
} }
} else if (this.args.join(" ") !== "" && this.channel.guild) { } else if (this.args.join(" ") !== "" && this.channel.guild) {
console.log(member);
const searched = await this.channel.guild.searchMembers(this.args.join(" ")); const searched = await this.channel.guild.searchMembers(this.args.join(" "));
if (searched.length === 0) return self.dynamicAvatarURL(null, 512); if (searched.length === 0) return self.dynamicAvatarURL(null, 512);
console.log(searched);
const user = await this.client.getRESTUser(searched[0].user.id); const user = await this.client.getRESTUser(searched[0].user.id);
return user ? user.dynamicAvatarURL(null, 512) : self.dynamicAvatarURL(null, 512); return user ? user.dynamicAvatarURL(null, 512) : self.dynamicAvatarURL(null, 512);
} else { } else {

View File

@ -6,7 +6,7 @@ class CaptionTwoCommand extends ImageCommand {
params(url) { params(url) {
const newArgs = this.options.text ?? this.args.filter(item => !item.includes(url)).join(" "); const newArgs = this.options.text ?? this.args.filter(item => !item.includes(url)).join(" ");
return { return {
caption: newArgs && newArgs.trim() ? newArgs.replaceAll("&", "&amp;").replaceAll(">", "&gt;").replaceAll("<", "&lt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;").replaceAll("%", "%").replaceAll("\\n", "\n") : words.sort(() => 0.5 - Math.random()).slice(0, Math.floor(Math.random() * words.length + 1)).join(" "), caption: newArgs && newArgs.trim() ? newArgs.replaceAll("&", "&amp;").replaceAll(">", "&gt;").replaceAll("<", "&lt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;").replaceAll("\\n", "\n") : words.sort(() => 0.5 - Math.random()).slice(0, Math.floor(Math.random() * words.length + 1)).join(" "),
top: !!this.specialArgs.top, top: !!this.specialArgs.top,
font: this.specialArgs.font && allowedFonts.includes(this.specialArgs.font.toLowerCase()) ? this.specialArgs.font.toLowerCase() : "helvetica" font: this.specialArgs.font && allowedFonts.includes(this.specialArgs.font.toLowerCase()) ? this.specialArgs.font.toLowerCase() : "helvetica"
}; };

View File

@ -1,4 +1,3 @@
import { Rest } from "lavacord";
import format from "format-duration"; import format from "format-duration";
import MusicCommand from "../../classes/musicCommand.js"; import MusicCommand from "../../classes/musicCommand.js";
@ -9,8 +8,8 @@ class NowPlayingCommand extends MusicCommand {
if (!this.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!"; if (!this.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
const player = this.connection.player; const player = this.connection.player;
if (!player) return "I'm not playing anything!"; if (!player) return "I'm not playing anything!";
const track = await Rest.decode(player.node, player.track); const track = await player.node.rest.decode(player.track);
const parts = Math.floor((player.state.position / track.length) * 10); const parts = Math.floor((player.position / track.length) * 10);
return { return {
embeds: [{ embeds: [{
color: 16711680, color: 16711680,
@ -32,7 +31,7 @@ class NowPlayingCommand extends MusicCommand {
}, },
{ {
name: `${"▬".repeat(parts)}🔘${"▬".repeat(10 - parts)}`, name: `${"▬".repeat(parts)}🔘${"▬".repeat(10 - parts)}`,
value: `${format(player.state.position)}/${track.isStream ? "∞" : format(track.length)}` value: `${format(player.position)}/${track.isStream ? "∞" : format(track.length)}`
}] }]
}] }]
}; };

View File

@ -1,6 +1,6 @@
//import { Rest } from "lavacord";
import fetch from "node-fetch"; import fetch from "node-fetch";
import format from "format-duration"; import format from "format-duration";
import { nodes } from "../../utils/soundplayer.js";
import paginator from "../../utils/pagination/pagination.js"; import paginator from "../../utils/pagination/pagination.js";
import MusicCommand from "../../classes/musicCommand.js"; import MusicCommand from "../../classes/musicCommand.js";
@ -11,8 +11,8 @@ class QueueCommand extends MusicCommand {
if (!this.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!"; if (!this.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
if (!this.channel.permissionsOf(this.client.user.id).has("embedLinks")) return "I don't have the `Embed Links` permission!"; if (!this.channel.permissionsOf(this.client.user.id).has("embedLinks")) return "I don't have the `Embed Links` permission!";
const player = this.connection; const player = this.connection;
//const tracks = await Rest.decode(player.player.node, queue); const node = nodes.filter((val) => { return val.name === player.player.node.name })[0];
const tracks = await fetch(`http://${player.player.node.host}:${player.player.node.port}/decodetracks`, { method: "POST", body: JSON.stringify(this.queue), headers: { Authorization: player.player.node.password, "Content-Type": "application/json" } }).then(res => res.json()); const tracks = await fetch(`http://${node.url}/decodetracks`, { method: "POST", body: JSON.stringify(this.queue), headers: { Authorization: node.auth, "Content-Type": "application/json" } }).then(res => res.json());
const trackList = []; const trackList = [];
const firstTrack = tracks.shift(); const firstTrack = tracks.shift();
for (const [i, track] of tracks.entries()) { for (const [i, track] of tracks.entries()) {

View File

@ -1,4 +1,3 @@
import { Rest } from "lavacord";
import { queues } from "../../utils/soundplayer.js"; import { queues } from "../../utils/soundplayer.js";
import MusicCommand from "../../classes/musicCommand.js"; import MusicCommand from "../../classes/musicCommand.js";
@ -11,7 +10,8 @@ class RemoveCommand extends MusicCommand {
const pos = parseInt(this.options.position ?? this.args[0]); const pos = parseInt(this.options.position ?? this.args[0]);
if (isNaN(pos) || pos > this.queue.length || pos < 1) return "That's not a valid position!"; if (isNaN(pos) || pos > this.queue.length || pos < 1) return "That's not a valid position!";
const removed = this.queue.splice(pos, 1); const removed = this.queue.splice(pos, 1);
const track = await Rest.decode(this.connection.player.node, removed[0]); if (removed.length === 0) return "That's not a valid position!";
const track = await this.connection.player.node.rest.decode(removed[0]);
queues.set(this.channel.guild.id, this.queue); queues.set(this.channel.guild.id, this.queue);
return `🔊 The song \`${track.title ? track.title : "(blank)"}\` has been removed from the queue.`; return `🔊 The song \`${track.title ? track.title : "(blank)"}\` has been removed from the queue.`;
} }

View File

@ -1,4 +1,3 @@
import { Rest } from "lavacord";
import MusicCommand from "../../classes/musicCommand.js"; import MusicCommand from "../../classes/musicCommand.js";
class SeekCommand extends MusicCommand { class SeekCommand extends MusicCommand {
@ -8,11 +7,11 @@ class SeekCommand extends MusicCommand {
if (!this.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!"; if (!this.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
if (this.connection.host !== this.author.id) return "Only the current voice session host can seek the music!"; if (this.connection.host !== this.author.id) return "Only the current voice session host can seek the music!";
const player = this.connection.player; const player = this.connection.player;
const track = await Rest.decode(player.node, player.track); const track = await player.node.rest.decode(player.track);
if (!track.isSeekable) return "This track isn't seekable!"; if (!track.isSeekable) return "This track isn't seekable!";
const seconds = parseFloat(this.options.position ?? this.args[0]); const seconds = parseFloat(this.options.position ?? this.args[0]);
if (isNaN(seconds) || (seconds * 1000) > track.length || (seconds * 1000) < 0) return "That's not a valid position!"; if (isNaN(seconds) || (seconds * 1000) > track.length || (seconds * 1000) < 0) return "That's not a valid position!";
await player.seek(seconds * 1000); player.seekTo(seconds * 1000);
return `🔊 Seeked track to ${seconds} second(s).`; return `🔊 Seeked track to ${seconds} second(s).`;
} }

View File

@ -16,7 +16,7 @@ class SkipCommand extends MusicCommand {
max: votes.max max: votes.max
}; };
if (votes.count + 1 === votes.max) { if (votes.count + 1 === votes.max) {
await player.player.stop(this.channel.guild.id); await player.player.stopTrack(this.channel.guild.id);
skipVotes.set(this.channel.guild.id, { count: 0, ids: [], max: Math.min(3, player.voiceChannel.voiceMembers.filter((i) => i.id !== this.client.user.id && !i.bot).length) }); skipVotes.set(this.channel.guild.id, { count: 0, ids: [], max: Math.min(3, player.voiceChannel.voiceMembers.filter((i) => i.id !== this.client.user.id && !i.bot).length) });
if (this.type === "application") return "🔊 The current song has been skipped."; if (this.type === "application") return "🔊 The current song has been skipped.";
} else { } else {
@ -24,7 +24,7 @@ class SkipCommand extends MusicCommand {
return `🔊 Voted to skip song (${votes.count + 1}/${votes.max} people have voted).`; return `🔊 Voted to skip song (${votes.count + 1}/${votes.max} people have voted).`;
} }
} else { } else {
await player.player.stop(this.channel.guild.id); await player.player.stopTrack();
if (this.type === "application") return "🔊 The current song has been skipped."; if (this.type === "application") return "🔊 The current song has been skipped.";
} }
} }

View File

@ -7,13 +7,12 @@ class StopCommand extends MusicCommand {
if (!this.member.voiceState.channelID) return "You need to be in a voice channel first!"; if (!this.member.voiceState.channelID) return "You need to be in a voice channel first!";
if (!this.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!"; if (!this.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
if (!this.connection) { if (!this.connection) {
await manager.leave(this.channel.guild.id); await manager.getNode().leaveChannel(this.channel.guild.id);
return "🔊 The current voice channel session has ended."; return "🔊 The current voice channel session has ended.";
} }
if (this.connection.host !== this.author.id && !this.member.permissions.has("manageChannels")) return "Only the current voice session host can stop the music!"; if (this.connection.host !== this.author.id && !this.member.permissions.has("manageChannels")) return "Only the current voice session host can stop the music!";
await manager.leave(this.channel.guild.id);
const connection = this.connection.player; const connection = this.connection.player;
await connection.destroy(); connection.node.leaveChannel(this.channel.guild.id);
players.delete(this.channel.guild.id); players.delete(this.channel.guild.id);
queues.delete(this.channel.guild.id); queues.delete(this.channel.guild.id);
return "🔊 The current voice channel session has ended."; return "🔊 The current voice channel session has ended.";

View File

@ -7,7 +7,7 @@ class ToggleCommand extends MusicCommand {
if (!this.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!"; if (!this.channel.guild.members.get(this.client.user.id).voiceState.channelID) return "I'm not in a voice channel!";
if (this.connection.host !== this.author.id && !this.member.permissions.has("manageChannels")) return "Only the current voice session host can pause/resume the music!"; if (this.connection.host !== this.author.id && !this.member.permissions.has("manageChannels")) return "Only the current voice session host can pause/resume the music!";
const player = this.connection.player; const player = this.connection.player;
await player.pause(!player.paused ? true : false); player.setPaused(!player.paused ? true : false);
return `🔊 The player has been ${player.paused ? "paused" : "resumed"}.`; return `🔊 The player has been ${player.paused ? "paused" : "resumed"}.`;
} }

View File

@ -1,17 +0,0 @@
import { manager } from "../utils/soundplayer.js";
// run when a raw packet is sent, used for sending data to lavalink
export default async (client, cluster, worker, ipc, packet) => {
if (!manager) return;
switch (packet.t) {
case "VOICE_SERVER_UPDATE":
await manager.voiceServerUpdate(packet.d);
break;
case "VOICE_STATE_UPDATE":
await manager.voiceStateUpdate(packet.d);
break;
case "GUILD_CREATE":
for (const state of packet.d.voice_states) await manager.voiceStateUpdate({ ...state, guild_id: packet.d.id });
break;
}
};

7920
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -33,15 +33,16 @@
"file-type": "^17.1.1", "file-type": "^17.1.1",
"format-duration": "^2.0.0", "format-duration": "^2.0.0",
"jsqr": "^1.3.1", "jsqr": "^1.3.1",
"lavacord": "^1.1.9",
"node-addon-api": "^5.0.0", "node-addon-api": "^5.0.0",
"node-emoji": "^1.10.0", "node-emoji": "^1.10.0",
"node-fetch": "^3.2.0", "node-fetch": "^3.2.0",
"qrcode": "^1.4.4", "qrcode": "^1.4.4",
"sharp": "^0.30.6", "sharp": "^0.30.6",
"shoukaku": "^3.1.0",
"winston": "^3.3.3" "winston": "^3.3.3"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.18.5",
"@babel/eslint-parser": "^7.13.8", "@babel/eslint-parser": "^7.13.8",
"@babel/eslint-plugin": "^7.13.0", "@babel/eslint-plugin": "^7.13.0",
"@babel/plugin-proposal-class-properties": "^7.13.0", "@babel/plugin-proposal-class-properties": "^7.13.0",

2685
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"lava": [ "lava": [
{ "id": "1", "host": "localhost", "port": 2333, "password": "youshallnotpass", "local": true } { "name": "localhost", "url": "localhost:2333", "auth": "youshallnotpass", "local": true }
], ],
"image": [ "image": [
{ "server": "localhost", "auth": "verycoolpass100", "tls": false } { "server": "localhost", "auth": "verycoolpass100", "tls": false }

View File

@ -10,7 +10,7 @@ import { log, error } from "./utils/logger.js";
// initialize command loader // initialize command loader
import { load, update } from "./utils/handler.js"; import { load, update } from "./utils/handler.js";
// lavalink stuff // lavalink stuff
import { checkStatus, connect, status, connected } from "./utils/soundplayer.js"; import { checkStatus, connect, reload, status, connected, manager } from "./utils/soundplayer.js";
// database stuff // database stuff
import database from "./utils/database.js"; import database from "./utils/database.js";
// command collections // command collections
@ -83,7 +83,7 @@ class Shard extends BaseClusterWorker {
this.ipc.register("soundreload", async () => { this.ipc.register("soundreload", async () => {
const soundStatus = await checkStatus(); const soundStatus = await checkStatus();
if (!soundStatus) { if (!soundStatus) {
const length = await connect(this.bot); const length = reload();
return this.ipc.broadcast("soundReloadSuccess", { length }); return this.ipc.broadcast("soundReloadSuccess", { length });
} else { } else {
return this.ipc.broadcast("soundReloadFail"); return this.ipc.broadcast("soundReloadFail");

View File

@ -2,15 +2,14 @@ import * as logger from "./logger.js";
import fetch from "node-fetch"; import fetch from "node-fetch";
import fs from "fs"; import fs from "fs";
import format from "format-duration"; import format from "format-duration";
import { Manager, Rest } from "lavacord"; import { Shoukaku, Connectors } from "shoukaku";
let nodes;
export const players = new Map(); export const players = new Map();
export const queues = new Map(); export const queues = new Map();
export const skipVotes = new Map(); export const skipVotes = new Map();
export let manager; export let manager;
export let nodes;
export let status = false; export let status = false;
export let connected = false; export let connected = false;
@ -20,7 +19,7 @@ export async function checkStatus() {
const newNodes = []; const newNodes = [];
for (const node of nodes) { for (const node of nodes) {
try { try {
const response = await fetch(`http://${node.host}:${node.port}/version`, { headers: { Authorization: node.password } }).then(res => res.text()); const response = await fetch(`http://${node.url}/version`, { headers: { Authorization: node.auth } }).then(res => res.text());
if (response) newNodes.push(node); if (response) newNodes.push(node);
} catch { } catch {
logger.error(`Failed to get status of Lavalink node ${node.host}.`); logger.error(`Failed to get status of Lavalink node ${node.host}.`);
@ -32,22 +31,31 @@ export async function checkStatus() {
} }
export async function connect(client) { export async function connect(client) {
manager = new Manager(nodes, { manager = new Shoukaku(new Connectors.Eris(client), nodes);
user: client.user.id, client.emit("ready"); // workaround
shards: client.shards.size || 1, manager.on("error", (node, error) => {
send: (packet) => {
const guild = client.guilds.get(packet.d.guild_id);
if (!guild) return;
return guild.shard.sendWS(packet.op, packet.d);
}
});
const { length } = await manager.connect();
logger.log(`Successfully connected to ${length} Lavalink node(s).`);
connected = true;
manager.on("error", (error, node) => {
logger.error(`An error occurred on Lavalink node ${node}: ${error}`); logger.error(`An error occurred on Lavalink node ${node}: ${error}`);
}); });
return length; manager.once("ready", (name) => {
logger.log(`Successfully connected to ${manager.nodes.size} Lavalink node(s).`);
connected = true;
});
}
export function reload() {
const activeNodes = manager.nodes;
const names = nodes.map((a) => a.name);
for (const name in activeNodes) {
if (!names.includes(name)) {
manager.removeNode(name);
}
}
for (const node of nodes) {
if (!activeNodes.has(node.name)) {
manager.addNode(node);
}
}
return manager.nodes.size;
} }
export async function play(client, sound, options, music = false) { export async function play(client, sound, options, music = false) {
@ -58,41 +66,44 @@ export async function play(client, sound, options, music = false) {
const voiceChannel = options.channel.guild.channels.get(options.member.voiceState.channelID); const voiceChannel = options.channel.guild.channels.get(options.member.voiceState.channelID);
if (!voiceChannel.permissionsOf(client.user.id).has("voiceConnect")) return "I don't have permission to join this voice channel!"; if (!voiceChannel.permissionsOf(client.user.id).has("voiceConnect")) return "I don't have permission to join this voice channel!";
const player = players.get(options.channel.guild.id); const player = players.get(options.channel.guild.id);
if (!music && manager.voiceStates.has(options.channel.guild.id) && (player && player.type === "music")) return "I can't play a sound effect while playing music!"; if (!music && manager.players().has(options.channel.guild.id) && (player && player.type === "music")) return "I can't play a sound effect while playing music!";
let node = manager.idealNodes[0]; let node = manager.getNode();
if (!node) { if (!node) {
const status = await checkStatus(); const status = await checkStatus();
if (!status) { if (!status) {
await connect(client); await connect(client);
node = manager.idealNodes[0]; node = manager.getNode();
} }
} }
if (!music && !nodes.filter(obj => obj.host === node.host)[0].local) { if (!music && !nodes.filter(obj => obj.name === node.name)[0].local) {
sound = sound.replace(/\.\//, "https://raw.githubusercontent.com/esmBot/esmBot/master/"); sound = sound.replace(/\.\//, "https://raw.githubusercontent.com/esmBot/esmBot/master/");
} }
let tracks, playlistInfo; let response;
try { try {
({ tracks, playlistInfo } = await Rest.load(node, sound)); response = await node.rest.resolve(sound);
if (!response) return "🔊 I couldn't get a response from the audio server.";
if (response.loadType === "NO_MATCHES" || response.loadType === "LOAD_FAILED") return "I couldn't find that song!";
} catch { } catch {
return "🔊 Hmmm, seems that all of the audio servers are down. Try again in a bit."; return "🔊 Hmmm, seems that all of the audio servers are down. Try again in a bit.";
} }
const oldQueue = queues.get(voiceChannel.guild.id); const oldQueue = queues.get(voiceChannel.guild.id);
if (!tracks || tracks.length === 0) return "I couldn't find that song!"; if (!response.tracks || response.tracks.length === 0) return "I couldn't find that song!";
if (music) { if (music) {
const sortedTracks = tracks.map((val) => { return val.track; }); const sortedTracks = response.tracks.map((val) => { return val.track; });
const playlistTracks = playlistInfo.selectedTrack ? sortedTracks : [sortedTracks[0]]; const playlistTracks = response.playlistInfo.selectedTrack ? sortedTracks : [sortedTracks[0]];
queues.set(voiceChannel.guild.id, oldQueue ? [...oldQueue, ...playlistTracks] : playlistTracks); queues.set(voiceChannel.guild.id, oldQueue ? [...oldQueue, ...playlistTracks] : playlistTracks);
} }
const connection = await manager.join({ const connection = player && player.player ? player.player : await node.joinChannel({
guild: voiceChannel.guild.id, guildId: voiceChannel.guild.id,
channel: voiceChannel.id, channelId: voiceChannel.id,
node: node.id shardId: voiceChannel.guild.shard.id,
}, { selfdeaf: true }); deaf: true
});
if (oldQueue && oldQueue.length !== 0 && music) { if (oldQueue && oldQueue.length !== 0 && music) {
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!`; return `Your ${response.playlistInfo.name ? "playlist" : "tune"} \`${response.playlistInfo.name ? response.playlistInfo.name.trim() : (response.tracks[0].info.title !== "" ? response.tracks[0].info.title.trim() : "(blank)")}\` has been added to the queue!`;
} else { } else {
nextSong(client, options, connection, tracks[0].track, tracks[0].info, music, voiceChannel, player ? player.host : options.member.id, player ? player.loop : false, player ? player.shuffle : false); nextSong(client, options, connection, response.tracks[0].track, response.tracks[0].info, music, voiceChannel, player ? player.host : options.member.id, player ? player.loop : false, player ? player.shuffle : false);
return; return;
} }
} }
@ -151,9 +162,9 @@ export async function nextSong(client, options, connection, track, info, music,
} }
connection.removeAllListeners("error"); connection.removeAllListeners("error");
connection.removeAllListeners("end"); connection.removeAllListeners("end");
await connection.play(track); connection.playTrack({ track });
players.set(voiceChannel.guild.id, { player: connection, type: music ? "music" : "sound", host: host, voiceChannel: voiceChannel, originalChannel: options.channel, loop: loop, shuffle: shuffle, playMessage: playingMessage }); players.set(voiceChannel.guild.id, { player: connection, type: music ? "music" : "sound", host: host, voiceChannel: voiceChannel, originalChannel: options.channel, loop: loop, shuffle: shuffle, playMessage: playingMessage });
connection.once("error", async (error) => { connection.once("exception", async (exception) => {
try { try {
if (playingMessage.channel.messages.has(playingMessage.id)) await playingMessage.delete(); if (playingMessage.channel.messages.has(playingMessage.id)) await playingMessage.delete();
const playMessage = players.get(voiceChannel.guild.id).playMessage; const playMessage = players.get(voiceChannel.guild.id).playMessage;
@ -162,16 +173,15 @@ export async function nextSong(client, options, connection, track, info, music,
// no-op // no-op
} }
try { try {
await manager.leave(voiceChannel.guild.id); connection.node.leaveChannel(voiceChannel.guild.id);
await connection.destroy();
} catch { } catch {
// no-op // no-op
} }
connection.removeAllListeners("end"); connection.removeAllListeners("end");
players.delete(voiceChannel.guild.id); players.delete(voiceChannel.guild.id);
queues.delete(voiceChannel.guild.id); queues.delete(voiceChannel.guild.id);
logger.error(error); logger.error(exception.error);
const content = `🔊 Looks like there was an error regarding sound playback:\n\`\`\`${error.type}: ${error.error}\`\`\``; const content = `🔊 Looks like there was an error regarding sound playback:\n\`\`\`${exception.type}: ${exception.error}\`\`\``;
if (options.type === "classic") { if (options.type === "classic") {
await client.createMessage(options.channel.id, content); await client.createMessage(options.channel.id, content);
} else { } else {
@ -203,7 +213,7 @@ export async function nextSong(client, options, connection, track, info, music,
} }
queues.set(voiceChannel.guild.id, newQueue); queues.set(voiceChannel.guild.id, newQueue);
if (newQueue.length !== 0) { if (newQueue.length !== 0) {
const newTrack = await Rest.decode(connection.node, newQueue[0]); const newTrack = await connection.node.rest.decode(newQueue[0]);
nextSong(client, options, connection, newQueue[0], newTrack, music, voiceChannel, host, player.loop, player.shuffle, track); nextSong(client, options, connection, newQueue[0], newTrack, music, voiceChannel, host, player.loop, player.shuffle, track);
try { try {
if (newQueue[0] !== track && playingMessage.channel.messages.has(playingMessage.id)) await playingMessage.delete(); if (newQueue[0] !== track && playingMessage.channel.messages.has(playingMessage.id)) await playingMessage.delete();
@ -212,8 +222,7 @@ export async function nextSong(client, options, connection, track, info, music,
// no-op // no-op
} }
} else if (process.env.STAYVC !== "true") { } else if (process.env.STAYVC !== "true") {
await manager.leave(voiceChannel.guild.id); connection.node.leaveChannel(voiceChannel.guild.id);
await connection.destroy();
players.delete(voiceChannel.guild.id); players.delete(voiceChannel.guild.id);
queues.delete(voiceChannel.guild.id); queues.delete(voiceChannel.guild.id);
skipVotes.delete(voiceChannel.guild.id); skipVotes.delete(voiceChannel.guild.id);