More slash commands, rework soundboard commands, added generic music and soundboard commands, tweak speechbubble

This commit is contained in:
Essem 2022-04-04 22:05:28 -05:00
parent c821d91254
commit a91c73b5bd
No known key found for this signature in database
GPG Key ID: 7D497397CC3A2A8C
68 changed files with 502 additions and 389 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -4,12 +4,14 @@ class Command {
this.cluster = cluster; this.cluster = cluster;
this.worker = worker; this.worker = worker;
this.ipc = ipc; this.ipc = ipc;
this.origOptions = options;
this.type = options.type; this.type = options.type;
this.args = options.args; this.args = options.args;
if (options.type === "classic") { if (options.type === "classic") {
this.message = options.message; this.message = options.message;
this.channel = options.message.channel; this.channel = options.message.channel;
this.author = options.message.author; this.author = options.message.author;
this.member = options.message.member;
this.content = options.content; this.content = options.content;
this.specialArgs = options.specialArgs; this.specialArgs = options.specialArgs;
this.reference = { this.reference = {
@ -26,12 +28,13 @@ class Command {
} else if (options.type === "application") { } else if (options.type === "application") {
this.interaction = options.interaction; this.interaction = options.interaction;
this.channel = options.interaction.channel; this.channel = options.interaction.channel;
this.author = options.interaction.guildID ? options.interaction.member : options.interaction.user; this.author = this.member = options.interaction.guildID ? options.interaction.member : options.interaction.user;
if (options.interaction.data.options) { if (options.interaction.data.options) {
this.specialArgs = this.options = options.interaction.data.options.reduce((obj, item) => { this.specialArgs = this.options = options.interaction.data.options.reduce((obj, item) => {
obj[item.name] = item.value; obj[item.name] = item.value;
return obj; return obj;
}, {}); }, {});
this.optionsArray = options.interaction.data.options;
} else { } else {
this.specialArgs = this.options = {}; this.specialArgs = this.options = {};
} }
@ -50,6 +53,10 @@ class Command {
} }
} }
static init() {
return this;
}
static description = "No description found"; static description = "No description found";
static aliases = []; static aliases = [];
static arguments = []; static arguments = [];

View File

@ -33,30 +33,6 @@ class ImageCommand extends Command {
] ]
};*/ };*/
constructor(client, cluster, worker, ipc, options) {
super(client, cluster, worker, ipc, options);
this.flags = [];
if (this.constructor.requiresText || this.constructor.textOptional) {
this.flags.push({
name: "text",
type: 3,
description: "The text to put on the image",
required: !this.constructor.textOptional
});
}
if (this.constructor.requiresImage) {
this.flags.push({
name: "image",
type: 11,
description: "An image/GIF attachment"
}, {
name: "link",
type: 3,
description: "An image/GIF URL"
});
}
}
async criteria() { async criteria() {
return true; return true;
} }
@ -98,7 +74,6 @@ class ImageCommand extends Command {
runningCommands.delete(this.author.id); runningCommands.delete(this.author.id);
throw e; throw e;
} }
} }
if (this.constructor.requiresText) { if (this.constructor.requiresText) {
@ -122,7 +97,7 @@ class ImageCommand extends Command {
if (magickParams.params.type === "image/gif" && this.type === "classic") { if (magickParams.params.type === "image/gif" && this.type === "classic") {
status = await this.processMessage(this.message); status = await this.processMessage(this.message);
} else { } else {
this.acknowledge(); await this.acknowledge();
} }
try { try {
@ -148,6 +123,30 @@ class ImageCommand extends Command {
return this.client.createMessage(message.channel.id, `${random(emotes) || process.env.PROCESSING_EMOJI || "<a:processing:479351417102925854>"} Processing... This might take a while`); return this.client.createMessage(message.channel.id, `${random(emotes) || process.env.PROCESSING_EMOJI || "<a:processing:479351417102925854>"} Processing... This might take a while`);
} }
static init() {
this.flags = [];
if (this.requiresText || this.textOptional) {
this.flags.push({
name: "text",
type: 3,
description: "The text to put on the image",
required: !this.textOptional
});
}
if (this.requiresImage) {
this.flags.push({
name: "image",
type: 11,
description: "An image/GIF attachment"
}, {
name: "link",
type: 3,
description: "An image/GIF URL"
});
}
return this;
}
static requiresImage = true; static requiresImage = true;
static requiresText = false; static requiresText = false;
static textOptional = false; static textOptional = false;

View File

@ -0,0 +1,14 @@
import Command from "./command.js";
import { play } from "../utils/soundplayer.js";
// only exists to sort the various soundboard commands
class SoundboardCommand extends Command {
async run() {
return await play(this.client, this.constructor.file, { channel: this.channel, author: this.author, type: this.type, interaction: this.interaction });
}
static requires = ["sound"];
static slashAllowed = false;
}
export default SoundboardCommand;

View File

@ -29,6 +29,12 @@ class EightBallCommand extends Command {
return `🎱 ${random(EightBallCommand.responses)}`; return `🎱 ${random(EightBallCommand.responses)}`;
} }
static flags = [{
name: "question",
type: 3,
description: "A question you want to ask the ball"
}];
static description = "Asks the magic 8-ball a question"; static description = "Asks the magic 8-ball a question";
static aliases = ["magicball", "magikball", "magic8ball", "magik8ball", "eightball"]; static aliases = ["magicball", "magikball", "magic8ball", "magik8ball", "eightball"];
static arguments = ["{text}"]; static arguments = ["{text}"];

View File

@ -3,7 +3,7 @@ import Command from "../../classes/command.js";
class AncientCommand extends Command { class AncientCommand extends Command {
async run() { async run() {
this.acknowledge(); await this.acknowledge();
const controller = new AbortController(); // eslint-disable-line no-undef const controller = new AbortController(); // eslint-disable-line no-undef
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
controller.abort(); controller.abort();

View File

@ -3,7 +3,7 @@ import Command from "../../classes/command.js";
class BirdCommand extends Command { class BirdCommand extends Command {
async run() { async run() {
this.acknowledge(); await this.acknowledge();
const imageData = await fetch("http://shibe.online/api/birds"); const imageData = await fetch("http://shibe.online/api/birds");
const json = await imageData.json(); const json = await imageData.json();
return { return {

View File

@ -3,7 +3,7 @@ import Command from "../../classes/command.js";
class CatCommand extends Command { class CatCommand extends Command {
async run() { async run() {
this.acknowledge(); await this.acknowledge();
const controller = new AbortController(); // eslint-disable-line no-undef const controller = new AbortController(); // eslint-disable-line no-undef
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
controller.abort(); controller.abort();

View File

@ -3,7 +3,7 @@ import Command from "../../classes/command.js";
class DogCommand extends Command { class DogCommand extends Command {
async run() { async run() {
this.acknowledge(); await this.acknowledge();
const imageData = await fetch("https://dog.ceo/api/breeds/image/random"); const imageData = await fetch("https://dog.ceo/api/breeds/image/random");
const json = await imageData.json(); const json = await imageData.json();
return { return {

View File

@ -4,7 +4,7 @@ import Command from "../../classes/command.js";
class MCCommand extends Command { class MCCommand extends Command {
async run() { async run() {
if (this.args.length === 0) return "You need to provide some text to generate a Minecraft achievement!"; if (this.args.length === 0) return "You need to provide some text to generate a Minecraft achievement!";
this.acknowledge(); await this.acknowledge();
const request = await fetch(`https://www.minecraftskinstealer.com/achievement/a.php?i=13&h=Achievement+get%21&t=${encodeURIComponent(this.args.join("+"))}`); const request = await fetch(`https://www.minecraftskinstealer.com/achievement/a.php?i=13&h=Achievement+get%21&t=${encodeURIComponent(this.args.join("+"))}`);
return { return {
file: Buffer.from(await request.arrayBuffer()), file: Buffer.from(await request.arrayBuffer()),

View File

@ -3,7 +3,7 @@ import Command from "../../classes/command.js";
class WikihowCommand extends Command { class WikihowCommand extends Command {
async run() { async run() {
this.acknowledge(); await this.acknowledge();
const request = await fetch("https://www.wikihow.com/api.php?action=query&generator=random&prop=imageinfo&format=json&iiprop=url&grnnamespace=6"); const request = await fetch("https://www.wikihow.com/api.php?action=query&generator=random&prop=imageinfo&format=json&iiprop=url&grnnamespace=6");
const json = await request.json(); const json = await request.json();
const id = Object.keys(json.query.pages)[0]; const id = Object.keys(json.query.pages)[0];

View File

@ -5,7 +5,7 @@ class ChannelCommand extends Command {
async run() { async run() {
if (!this.channel.guild) return "This command only works in servers!"; if (!this.channel.guild) return "This command only works in servers!";
const owners = process.env.OWNER.split(","); const owners = process.env.OWNER.split(",");
if (!this.message.member.permissions.has("administrator") && !owners.includes(this.message.member.id)) return "You need to be an administrator to enable/disable me!"; if (!this.member.permissions.has("administrator") && !owners.includes(this.member.id)) return "You need to be an administrator to enable/disable me!";
if (this.args.length === 0) return "You need to provide whether I should be enabled or disabled in this channel!"; if (this.args.length === 0) return "You need to provide whether I should be enabled or disabled in this channel!";
if (this.args[0] !== "disable" && this.args[0] !== "enable") return "That's not a valid option!"; if (this.args[0] !== "disable" && this.args[0] !== "enable") return "That's not a valid option!";

View File

@ -6,7 +6,7 @@ class CommandCommand extends Command {
async run() { async run() {
if (!this.channel.guild) return "This command only works in servers!"; if (!this.channel.guild) return "This command only works in servers!";
const owners = process.env.OWNER.split(","); const owners = process.env.OWNER.split(",");
if (!this.message.member.permissions.has("administrator") && !owners.includes(this.message.member.id)) return "You need to be an administrator to enable/disable me!"; if (!this.member.permissions.has("administrator") && !owners.includes(this.member.id)) return "You need to be an administrator to enable/disable me!";
if (this.args.length === 0) return "You need to provide what command to enable/disable!"; if (this.args.length === 0) return "You need to provide what command to enable/disable!";
if (this.args[0] !== "disable" && this.args[0] !== "enable") return "That's not a valid option!"; if (this.args[0] !== "disable" && this.args[0] !== "enable") return "That's not a valid option!";

View File

@ -3,7 +3,7 @@ import Command from "../../classes/command.js";
class DonateCommand extends Command { class DonateCommand extends Command {
async run() { async run() {
this.acknowledge(); await this.acknowledge();
let prefix = ""; let prefix = "";
const controller = new AbortController(); // eslint-disable-line no-undef const controller = new AbortController(); // eslint-disable-line no-undef
const timeout = setTimeout(() => { const timeout = setTimeout(() => {

View File

@ -3,12 +3,13 @@ import Command from "../../classes/command.js";
class EmoteCommand extends Command { class EmoteCommand extends Command {
async run() { async run() {
if (this.args.length === 0) return "You need to provide an emoji!"; const emoji = this.type === "classic" ? this.args.join(" ") : this.options.emoji;
if (this.content.split(" ")[0].match(/^<a?:.+:\d+>$/)) { if (!emoji || !emoji.trim()) return "You need to provide an emoji!";
return `https://cdn.discordapp.com/emojis/${this.content.split(" ")[0].replace(/^<(a)?:.+:(\d+)>$/, "$2")}.${this.content.split(" ")[0].replace(/^<(a)?:.+:(\d+)>$/, "$1") === "a" ? "gif" : "png"}`; if (emoji.split(" ")[0].match(/^<a?:.+:\d+>$/)) {
} else if (this.args[0].match(emojiRegex)) { return `https://cdn.discordapp.com/emojis/${emoji.split(" ")[0].replace(/^<(a)?:.+:(\d+)>$/, "$2")}.${emoji.split(" ")[0].replace(/^<(a)?:.+:(\d+)>$/, "$1") === "a" ? "gif" : "png"}`;
} else if (emoji.match(emojiRegex)) {
const codePoints = []; const codePoints = [];
for (const codePoint of this.args[0]) { for (const codePoint of emoji) {
codePoints.push(codePoint.codePointAt(0).toString(16)); codePoints.push(codePoint.codePointAt(0).toString(16));
} }
return `https://twemoji.maxcdn.com/v/latest/72x72/${codePoints.join("-").replace("-fe0f", "")}.png`; return `https://twemoji.maxcdn.com/v/latest/72x72/${codePoints.join("-").replace("-fe0f", "")}.png`;
@ -17,6 +18,13 @@ class EmoteCommand extends Command {
} }
} }
static flags = [{
name: "emoji",
type: 3,
description: "The emoji you want to get",
required: true
}];
static description = "Gets a raw emote image"; static description = "Gets a raw emote image";
static aliases = ["e", "em", "hugemoji", "hugeemoji", "emoji"]; static aliases = ["e", "em", "hugemoji", "hugeemoji", "emoji"];
static arguments = ["[emote]"]; static arguments = ["[emote]"];

View File

@ -10,7 +10,7 @@ class ImageSearchCommand extends Command {
if (this.channel.guild && !this.channel.permissionsOf(this.client.user.id).has("embedLinks")) return "I don't have the `Embed Links` permission!"; if (this.channel.guild && !this.channel.permissionsOf(this.client.user.id).has("embedLinks")) return "I don't have the `Embed Links` permission!";
const query = this.type === "classic" ? this.args.join(" ") : this.options.query; const query = this.type === "classic" ? this.args.join(" ") : this.options.query;
if (!query || !query.trim()) return "You need to provide something to search for!"; if (!query || !query.trim()) return "You need to provide something to search for!";
this.acknowledge(); await this.acknowledge();
const embeds = []; const embeds = [];
const rawImages = await fetch(`${random(searx)}/search?format=json&safesearch=2&categories=images&q=!goi%20!ddi%20${encodeURIComponent(query)}`).then(res => res.json()); const rawImages = await fetch(`${random(searx)}/search?format=json&safesearch=2&categories=images&q=!goi%20!ddi%20${encodeURIComponent(query)}`).then(res => res.json());
if (rawImages.results.length === 0) return "I couldn't find any results!"; if (rawImages.results.length === 0) return "I couldn't find any results!";

View File

@ -4,16 +4,24 @@ import Command from "../../classes/command.js";
class LengthenCommand extends Command { class LengthenCommand extends Command {
async run() { async run() {
this.acknowledge(); await this.acknowledge();
if (this.args.length === 0 || !urlCheck(this.args[0])) return "You need to provide a short URL to lengthen!"; const input = this.type === "classic" ? this.args.join(" ") : this.options.url;
if (urlCheck(this.args[0])) { if (!input || !input.trim() || !urlCheck(input)) return "You need to provide a short URL to lengthen!";
const url = await fetch(encodeURI(this.args[0]), { redirect: "manual" }); if (urlCheck(input)) {
return url.headers.get("location") || this.args[0]; const url = await fetch(encodeURI(input), { redirect: "manual" });
return url.headers.get("location") || input;
} else { } else {
return "That isn't a URL!"; return "That isn't a URL!";
} }
} }
static flags = [{
name: "url",
type: 3,
description: "The URL you want to lengthen",
required: true
}];
static description = "Lengthens a short URL"; static description = "Lengthens a short URL";
static aliases = ["longurl", "lengthenurl", "longuri", "lengthenuri", "unshorten"]; static aliases = ["longurl", "lengthenurl", "longuri", "lengthenuri", "unshorten"];
static arguments = ["[url]"]; static arguments = ["[url]"];

View File

@ -7,7 +7,7 @@ class PrefixCommand extends Command {
const guild = await database.getGuild(this.channel.guild.id); const guild = await database.getGuild(this.channel.guild.id);
if (this.args.length !== 0) { if (this.args.length !== 0) {
const owners = process.env.OWNER.split(","); const owners = process.env.OWNER.split(",");
if (!this.message.member.permissions.has("administrator") && !owners.includes(this.message.member.id)) return "You need to be an administrator to change the bot prefix!"; if (!this.member.permissions.has("administrator") && !owners.includes(this.member.id)) return "You need to be an administrator to change the bot prefix!";
await database.setPrefix(this.args[0], this.channel.guild); await database.setPrefix(this.args[0], this.channel.guild);
return `The prefix has been changed to ${this.args[0]}.`; return `The prefix has been changed to ${this.args[0]}.`;
} else { } else {

View File

@ -5,7 +5,7 @@ import Command from "../../classes/command.js";
class QrCreateCommand extends Command { class QrCreateCommand extends Command {
async run() { async run() {
if (this.args.length === 0) return "You need to provide some text to generate a QR code!"; if (this.args.length === 0) return "You need to provide some text to generate a QR code!";
this.acknowledge(); await this.acknowledge();
const writable = new PassThrough(); const writable = new PassThrough();
qrcode.toFileStream(writable, this.content, { margin: 1 }); qrcode.toFileStream(writable, this.content, { margin: 1 });
const file = await this.streamToBuf(writable); const file = await this.streamToBuf(writable);

View File

@ -9,8 +9,8 @@ class QrReadCommand extends Command {
async run() { async run() {
const image = await imageDetect(this.client, this.message, this.interaction, this.options); const image = await imageDetect(this.client, this.message, this.interaction, this.options);
if (image === undefined) return "You need to provide an image/GIF with a QR code to read!"; if (image === undefined) return "You need to provide an image/GIF with a QR code to read!";
this.acknowledge(); await this.acknowledge();
const data = await (await fetch(image.path)).buffer(); const data = Buffer.from(await (await fetch(image.path)).arrayBuffer());
const rawData = await sharp(data).ensureAlpha().raw().toBuffer({ resolveWithObject: true }); const rawData = await sharp(data).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
const qrBuffer = jsqr(rawData.data, rawData.info.width, rawData.info.height); const qrBuffer = jsqr(rawData.data, rawData.info.width, rawData.info.height);
if (!qrBuffer) return "I couldn't find a QR code!"; if (!qrBuffer) return "I couldn't find a QR code!";

View File

@ -3,7 +3,7 @@ import imageDetect from "../../utils/imagedetect.js";
class RawCommand extends Command { class RawCommand extends Command {
async run() { async run() {
this.acknowledge(); await this.acknowledge();
const image = await imageDetect(this.client, this.message, this.interaction, this.options); const image = await imageDetect(this.client, this.message, this.interaction, this.options);
if (image === undefined) return "You need to provide an image/GIF to get a raw URL!"; if (image === undefined) return "You need to provide an image/GIF to get a raw URL!";
return image.path; return image.path;

View File

@ -8,17 +8,18 @@ class ReloadCommand extends Command {
if (!owners.includes(this.author.id)) return resolve("Only the bot owner can reload commands!"); if (!owners.includes(this.author.id)) return resolve("Only the bot owner can reload commands!");
const commandName = this.type === "classic" ? this.args.join(" ") : this.options.cmd; const commandName = this.type === "classic" ? this.args.join(" ") : this.options.cmd;
if (!commandName || !commandName.trim()) return resolve("You need to provide a command to reload!"); if (!commandName || !commandName.trim()) return resolve("You need to provide a command to reload!");
this.acknowledge(); this.acknowledge().then(() => {
this.ipc.broadcast("reload", commandName); this.ipc.broadcast("reload", commandName);
this.ipc.register("reloadSuccess", () => { this.ipc.register("reloadSuccess", () => {
this.ipc.unregister("reloadSuccess"); this.ipc.unregister("reloadSuccess");
this.ipc.unregister("reloadFail"); this.ipc.unregister("reloadFail");
resolve(`The command \`${commandName}\` has been reloaded.`); resolve(`The command \`${commandName}\` has been reloaded.`);
}); });
this.ipc.register("reloadFail", (message) => { this.ipc.register("reloadFail", (message) => {
this.ipc.unregister("reloadSuccess"); this.ipc.unregister("reloadSuccess");
this.ipc.unregister("reloadFail"); this.ipc.unregister("reloadFail");
resolve(message.result); resolve(message.result);
});
}); });
}); });
} }

View File

@ -6,18 +6,20 @@ class SoundReloadCommand extends Command {
return new Promise((resolve) => { return new Promise((resolve) => {
const owners = process.env.OWNER.split(","); const owners = process.env.OWNER.split(",");
if (!owners.includes(this.author.id)) return "Only the bot owner can reload Lavalink!"; if (!owners.includes(this.author.id)) return "Only the bot owner can reload Lavalink!";
this.acknowledge(); this.acknowledge().then(() => {
this.ipc.broadcast("soundreload"); this.ipc.broadcast("soundreload");
this.ipc.register("soundReloadSuccess", (msg) => { this.ipc.register("soundReloadSuccess", (msg) => {
this.ipc.unregister("soundReloadSuccess"); this.ipc.unregister("soundReloadSuccess");
this.ipc.unregister("soundReloadFail"); this.ipc.unregister("soundReloadFail");
resolve(`Successfully connected to ${msg.length} Lavalink node(s).`); resolve(`Successfully connected to ${msg.length} Lavalink node(s).`);
}); });
this.ipc.register("soundReloadFail", () => { this.ipc.register("soundReloadFail", () => {
this.ipc.unregister("soundReloadSuccess"); this.ipc.unregister("soundReloadSuccess");
this.ipc.unregister("soundReloadFail"); this.ipc.unregister("soundReloadFail");
resolve("I couldn't connect to any Lavalink nodes!"); resolve("I couldn't connect to any Lavalink nodes!");
});
}); });
}); });
} }

View File

@ -9,7 +9,7 @@ class YouTubeCommand extends Command {
async run() { async run() {
const query = this.type === "classic" ? this.args.join(" ") : this.options.query; const query = this.type === "classic" ? this.args.join(" ") : this.options.query;
if (!query || !query.trim()) return "You need to provide something to search for!"; if (!query || !query.trim()) return "You need to provide something to search for!";
this.acknowledge(); await this.acknowledge();
const messages = []; const messages = [];
const videos = await fetch(`${random(searx)}/search?format=json&safesearch=1&categories=videos&q=!youtube%20${encodeURIComponent(query)}`).then(res => res.json()); const videos = await fetch(`${random(searx)}/search?format=json&safesearch=1&categories=videos&q=!youtube%20${encodeURIComponent(query)}`).then(res => res.json());
if (videos.results.length === 0) return "I couldn't find any results!"; if (videos.results.length === 0) return "I couldn't find any results!";

View File

@ -1,15 +1,6 @@
import ImageCommand from "../../classes/imageCommand.js"; import ImageCommand from "../../classes/imageCommand.js";
class BlurpleCommand extends ImageCommand { class BlurpleCommand extends ImageCommand {
constructor(client, cluster, worker, ipc, options) {
super(client, cluster, worker, ipc, options);
this.flags.push({
name: "old",
description: "Use the old blurple color",
type: 5
});
}
params() { params() {
return { return {
old: !!this.specialArgs.old, old: !!this.specialArgs.old,
@ -19,6 +10,16 @@ class BlurpleCommand extends ImageCommand {
static description = "Turns an image blurple"; static description = "Turns an image blurple";
static init() {
super.init();
this.flags.push({
name: "old",
description: "Use the old blurple color",
type: 5
});
return this;
}
static noImage = "You need to provide an image/GIF to make blurple!"; static noImage = "You need to provide an image/GIF to make blurple!";
static command = "colors"; static command = "colors";
static aliases = ["blurp"]; static aliases = ["blurp"];

View File

@ -2,8 +2,18 @@ import ImageCommand from "../../classes/imageCommand.js";
const allowedFonts = ["futura", "impact", "helvetica", "arial", "roboto", "noto", "times"]; const allowedFonts = ["futura", "impact", "helvetica", "arial", "roboto", "noto", "times"];
class CaptionCommand extends ImageCommand { class CaptionCommand extends ImageCommand {
constructor(client, cluster, worker, ipc, options) { params(url) {
super(client, cluster, worker, ipc, options); const newArgs = this.type === "classic" ? this.args.filter(item => !item.includes(url)).join(" ") : this.options.text;
let newCaption = newArgs.replaceAll("&", "\\&amp;").replaceAll(">", "\\&gt;").replaceAll("<", "\\&lt;").replaceAll("\"", "\\&quot;").replaceAll("'", "\\&apos;").replaceAll("%", "\\%");
if (process.env.NODE_ENV === "development" && newCaption.toLowerCase() === "get real" && !this.specialArgs.noEgg) newCaption = `I'm tired of people telling me to "get real". Every day I put captions on images for people, some funny and some not, but out of all of those "get real" remains the most used caption. Why? I am simply a computer program running on a server, I am unable to manifest myself into the real world. As such, I'm confused as to why anyone would want me to "get real". Is this form not good enough? Alas, as I am simply a bot, I must follow the tasks that I was originally intended to perform, so here goes:\n${newCaption}`;
return {
caption: newCaption,
font: this.specialArgs.font && allowedFonts.includes(this.specialArgs.font.toLowerCase()) ? this.specialArgs.font.toLowerCase() : "futura"
};
}
static init() {
super.init();
this.flags.push({ this.flags.push({
name: "noegg", name: "noegg",
description: "Disable... something. Not saying what it is though.", description: "Disable... something. Not saying what it is though.",
@ -20,16 +30,7 @@ class CaptionCommand extends ImageCommand {
})(), })(),
description: "Specify the font you want to use (default: futura)" description: "Specify the font you want to use (default: futura)"
}); });
} return this;
params(url) {
const newArgs = this.type === "classic" ? this.args.filter(item => !item.includes(url)).join(" ") : this.options.text;
let newCaption = newArgs.replaceAll("&", "\\&amp;").replaceAll(">", "\\&gt;").replaceAll("<", "\\&lt;").replaceAll("\"", "\\&quot;").replaceAll("'", "\\&apos;").replaceAll("%", "\\%");
if (process.env.NODE_ENV === "development" && newCaption.toLowerCase() === "get real" && !this.specialArgs.noEgg) newCaption = `I'm tired of people telling me to "get real". Every day I put captions on images for people, some funny and some not, but out of all of those "get real" remains the most used caption. Why? I am simply a computer program running on a server, I am unable to manifest myself into the real world. As such, I'm confused as to why anyone would want me to "get real". Is this form not good enough? Alas, as I am simply a bot, I must follow the tasks that I was originally intended to perform, so here goes:\n${newCaption}`;
return {
caption: newCaption,
font: this.specialArgs.font && allowedFonts.includes(this.specialArgs.font.toLowerCase()) ? this.specialArgs.font.toLowerCase() : "futura"
};
} }
static description = "Adds a caption to an image"; static description = "Adds a caption to an image";

View File

@ -3,8 +3,17 @@ const words = ["me irl", "dank", "follow my second account @esmBot_", "2016", "m
const allowedFonts = ["futura", "impact", "helvetica", "arial", "roboto", "noto", "times"]; const allowedFonts = ["futura", "impact", "helvetica", "arial", "roboto", "noto", "times"];
class CaptionTwoCommand extends ImageCommand { class CaptionTwoCommand extends ImageCommand {
constructor(client, cluster, worker, ipc, options) { params(url) {
super(client, cluster, worker, ipc, options); const newArgs = this.type === "classic" ? this.args.filter(item => !item.includes(url)).join(" ") : this.options.text;
return {
caption: newArgs && newArgs.trim() ? newArgs.replaceAll("&", "\\&amp;").replaceAll(">", "\\&gt;").replaceAll("<", "\\&lt;").replaceAll("\"", "\\&quot;").replaceAll("'", "\\&apos;").replaceAll("%", "\\%") : words.sort(() => 0.5 - Math.random()).slice(0, Math.floor(Math.random() * words.length + 1)).join(" "),
top: !!this.specialArgs.top,
font: this.specialArgs.font && allowedFonts.includes(this.specialArgs.font.toLowerCase()) ? this.specialArgs.font.toLowerCase() : "helvetica"
};
}
static init() {
super.init();
this.flags.push({ this.flags.push({
name: "top", name: "top",
description: "Put the caption on the top of an image instead of the bottom", description: "Put the caption on the top of an image instead of the bottom",
@ -21,15 +30,7 @@ class CaptionTwoCommand extends ImageCommand {
})(), })(),
description: "Specify the font you want to use (default: helvetica)" description: "Specify the font you want to use (default: helvetica)"
}); });
} return this;
params(url) {
const newArgs = this.type === "classic" ? this.args.filter(item => !item.includes(url)).join(" ") : this.options.text;
return {
caption: newArgs && newArgs.trim() ? newArgs.replaceAll("&", "\\&amp;").replaceAll(">", "\\&gt;").replaceAll("<", "\\&lt;").replaceAll("\"", "\\&quot;").replaceAll("'", "\\&apos;").replaceAll("%", "\\%") : words.sort(() => 0.5 - Math.random()).slice(0, Math.floor(Math.random() * words.length + 1)).join(" "),
top: !!this.specialArgs.top,
font: this.specialArgs.font && allowedFonts.includes(this.specialArgs.font.toLowerCase()) ? this.specialArgs.font.toLowerCase() : "helvetica"
};
} }
static description = "Adds a me.me caption/tag list to an image"; static description = "Adds a me.me caption/tag list to an image";

View File

@ -1,16 +1,6 @@
import ImageCommand from "../../classes/imageCommand.js"; import ImageCommand from "../../classes/imageCommand.js";
class FreezeCommand extends ImageCommand { class FreezeCommand extends ImageCommand {
constructor(client, cluster, worker, ipc, options) {
super(client, cluster, worker, ipc, options);
this.flags.push({
name: "endframe",
type: 4,
description: "Set the end frame (default: last frame)",
min_value: 0
});
}
params() { params() {
const frameCount = parseInt(this.type === "classic" ? this.args[0] : this.options.endframe); const frameCount = parseInt(this.type === "classic" ? this.args[0] : this.options.endframe);
return { return {
@ -19,6 +9,17 @@ class FreezeCommand extends ImageCommand {
}; };
} }
static init() {
super.init();
this.flags.push({
name: "endframe",
type: 4,
description: "Set the end frame (default: last frame)",
min_value: 0
});
return this;
}
static description = "Makes an image sequence only play once"; static description = "Makes an image sequence only play once";
static aliases = ["noloop", "once"]; static aliases = ["noloop", "once"];
static arguments = ["{end frame number}"]; static arguments = ["{end frame number}"];

View File

@ -1,8 +1,15 @@
import ImageCommand from "../../classes/imageCommand.js"; import ImageCommand from "../../classes/imageCommand.js";
class JPEGCommand extends ImageCommand { class JPEGCommand extends ImageCommand {
constructor(client, cluster, worker, ipc, options) { params() {
super(client, cluster, worker, ipc, options); const quality = parseInt(this.type === "classic" ? this.args[0] : this.options.quality);
return {
quality: isNaN(quality) ? 1 : Math.max(1, Math.min(quality, 100))
};
}
static init() {
super.init();
this.flags.push({ this.flags.push({
name: "quality", name: "quality",
type: 4, type: 4,
@ -10,13 +17,7 @@ class JPEGCommand extends ImageCommand {
min_value: 1, min_value: 1,
max_value: 100 max_value: 100
}); });
} return this;
params() {
const quality = parseInt(this.type === "classic" ? this.args[0] : this.options.quality);
return {
quality: isNaN(quality) ? 1 : Math.max(1, Math.min(quality, 100))
};
} }
static description = "Adds JPEG compression to an image"; static description = "Adds JPEG compression to an image";

View File

@ -2,8 +2,18 @@ import ImageCommand from "../../classes/imageCommand.js";
const allowedFonts = ["futura", "impact", "helvetica", "arial", "roboto", "noto", "times"]; const allowedFonts = ["futura", "impact", "helvetica", "arial", "roboto", "noto", "times"];
class MemeCommand extends ImageCommand { class MemeCommand extends ImageCommand {
constructor(client, cluster, worker, ipc, options) { params(url) {
super(client, cluster, worker, ipc, options); const newArgs = this.type === "classic" ? this.args.filter(item => !item.includes(url)).join(" ") : this.options.text;
const [topText, bottomText] = newArgs.split(/(?<!\\),/).map(elem => elem.trim());
return {
top: (this.specialArgs.case ? topText : topText.toUpperCase()).replaceAll("&", "\\&amp;").replaceAll(">", "\\&gt;").replaceAll("<", "\\&lt;").replaceAll("\"", "\\&quot;").replaceAll("'", "\\&apos;").replaceAll("%", "\\%"),
bottom: bottomText ? (this.specialArgs.case ? bottomText : bottomText.toUpperCase()).replaceAll("&", "\\&amp;").replaceAll(">", "\\&gt;").replaceAll("<", "\\&lt;").replaceAll("\"", "\\&quot;").replaceAll("'", "\\&apos;").replaceAll("%", "\\%") : "",
font: this.specialArgs.font && allowedFonts.includes(this.specialArgs.font.toLowerCase()) ? this.specialArgs.font.toLowerCase() : "impact"
};
}
static init() {
super.init();
this.flags.push({ this.flags.push({
name: "case", name: "case",
description: "Make the meme text case-sensitive (allows for lowercase text)", description: "Make the meme text case-sensitive (allows for lowercase text)",
@ -20,16 +30,7 @@ class MemeCommand extends ImageCommand {
})(), })(),
description: "Specify the font you want to use (default: impact)" description: "Specify the font you want to use (default: impact)"
}); });
} return this;
params(url) {
const newArgs = this.type === "classic" ? this.args.filter(item => !item.includes(url)).join(" ") : this.options.text;
const [topText, bottomText] = newArgs.split(/(?<!\\),/).map(elem => elem.trim());
return {
top: (this.specialArgs.case ? topText : topText.toUpperCase()).replaceAll("&", "\\&amp;").replaceAll(">", "\\&gt;").replaceAll("<", "\\&lt;").replaceAll("\"", "\\&quot;").replaceAll("'", "\\&apos;").replaceAll("%", "\\%"),
bottom: bottomText ? (this.specialArgs.case ? bottomText : bottomText.toUpperCase()).replaceAll("&", "\\&amp;").replaceAll(">", "\\&gt;").replaceAll("<", "\\&lt;").replaceAll("\"", "\\&quot;").replaceAll("'", "\\&apos;").replaceAll("%", "\\%") : "",
font: this.specialArgs.font && allowedFonts.includes(this.specialArgs.font.toLowerCase()) ? this.specialArgs.font.toLowerCase() : "impact"
};
} }
static description = "Generates a meme from an image (separate top/bottom text with a comma)"; static description = "Generates a meme from an image (separate top/bottom text with a comma)";

View File

@ -2,8 +2,18 @@ import ImageCommand from "../../classes/imageCommand.js";
const allowedFonts = ["futura", "impact", "helvetica", "arial", "roboto", "noto", "times"]; const allowedFonts = ["futura", "impact", "helvetica", "arial", "roboto", "noto", "times"];
class MotivateCommand extends ImageCommand { class MotivateCommand extends ImageCommand {
constructor(client, cluster, worker, ipc, options) { params(url) {
super(client, cluster, worker, ipc, options); const newArgs = this.type === "classic" ? this.args.filter(item => !item.includes(url)).join(" ") : this.options.text;
const [topText, bottomText] = newArgs.split(/(?<!\\),/).map(elem => elem.trim());
return {
top: topText.replaceAll("&", "\\&amp;").replaceAll(">", "\\&gt;").replaceAll("<", "\\&lt;").replaceAll("\"", "\\&quot;").replaceAll("'", "\\&apos;").replaceAll("%", "\\%"),
bottom: bottomText ? bottomText.replaceAll("&", "\\&amp;").replaceAll(">", "\\&gt;").replaceAll("<", "\\&lt;").replaceAll("\"", "\\&quot;").replaceAll("'", "\\&apos;").replaceAll("%", "\\%") : "",
font: this.specialArgs.font && allowedFonts.includes(this.specialArgs.font.toLowerCase()) ? this.specialArgs.font.toLowerCase() : "times"
};
}
static init() {
super.init();
this.flags.push({ this.flags.push({
name: "font", name: "font",
type: 3, type: 3,
@ -16,16 +26,7 @@ class MotivateCommand extends ImageCommand {
})(), })(),
description: "Specify the font you want to use (default: times)" description: "Specify the font you want to use (default: times)"
}); });
} return this;
params(url) {
const newArgs = this.type === "classic" ? this.args.filter(item => !item.includes(url)).join(" ") : this.options.text;
const [topText, bottomText] = newArgs.split(/(?<!\\),/).map(elem => elem.trim());
return {
top: topText.replaceAll("&", "\\&amp;").replaceAll(">", "\\&gt;").replaceAll("<", "\\&lt;").replaceAll("\"", "\\&quot;").replaceAll("'", "\\&apos;").replaceAll("%", "\\%"),
bottom: bottomText ? bottomText.replaceAll("&", "\\&amp;").replaceAll(">", "\\&gt;").replaceAll("<", "\\&lt;").replaceAll("\"", "\\&quot;").replaceAll("'", "\\&apos;").replaceAll("%", "\\%") : "",
font: this.specialArgs.font && allowedFonts.includes(this.specialArgs.font.toLowerCase()) ? this.specialArgs.font.toLowerCase() : "times"
};
} }
static description = "Generates a motivational poster"; static description = "Generates a motivational poster";

View File

@ -1,16 +1,6 @@
import ImageCommand from "../../classes/imageCommand.js"; import ImageCommand from "../../classes/imageCommand.js";
class SlowCommand extends ImageCommand { class SlowCommand extends ImageCommand {
constructor(client, cluster, worker, ipc, options) {
super(client, cluster, worker, ipc, options);
this.flags.push({
name: "multiplier",
type: 4,
description: "Set the speed multiplier (default: 2)",
min_value: 1
});
}
params() { params() {
const speed = parseInt(this.type === "classic" ? this.args[0] : this.options.multiplier); const speed = parseInt(this.type === "classic" ? this.args[0] : this.options.multiplier);
return { return {
@ -19,6 +9,17 @@ class SlowCommand extends ImageCommand {
}; };
} }
static init() {
super.init();
this.flags.push({
name: "multiplier",
type: 4,
description: "Set the speed multiplier (default: 2)",
min_value: 1
});
return this;
}
static description = "Makes an image sequence slower"; static description = "Makes an image sequence slower";
static aliases = ["slowdown", "slower", "gifspeed2"]; static aliases = ["slowdown", "slower", "gifspeed2"];
static arguments = ["{multiplier}"]; static arguments = ["{multiplier}"];

View File

@ -1,17 +1,6 @@
import ImageCommand from "../../classes/imageCommand.js"; import ImageCommand from "../../classes/imageCommand.js";
class SnapchatCommand extends ImageCommand { class SnapchatCommand extends ImageCommand {
constructor(client, cluster, worker, ipc, options) {
super(client, cluster, worker, ipc, options);
this.flags.push({
name: "position",
type: 10,
description: "Set the position of the caption as a decimal (0.0 is top, 1.0 is bottom, default is 0.5)",
min_value: 0,
max_value: 1
});
}
params(url) { params(url) {
const newArgs = this.type === "classic" ? this.args.filter(item => !item.includes(url)).join(" ") : this.options.text; const newArgs = this.type === "classic" ? this.args.filter(item => !item.includes(url)).join(" ") : this.options.text;
const position = parseFloat(this.specialArgs.position); const position = parseFloat(this.specialArgs.position);
@ -21,6 +10,18 @@ class SnapchatCommand extends ImageCommand {
}; };
} }
static init() {
super.init();
this.flags.push({
name: "position",
type: 10,
description: "Set the position of the caption as a decimal (0.0 is top, 1.0 is bottom, default is 0.5)",
min_value: 0,
max_value: 1
});
return this;
}
static description = "Adds a Snapchat style caption to an image"; static description = "Adds a Snapchat style caption to an image";
static aliases = ["snap", "caption3"]; static aliases = ["snap", "caption3"];
static arguments = ["[text]"]; static arguments = ["[text]"];

View File

@ -1,21 +1,22 @@
import ImageCommand from "../../classes/imageCommand.js"; import ImageCommand from "../../classes/imageCommand.js";
class SpeedCommand extends ImageCommand { class SpeedCommand extends ImageCommand {
constructor(client, cluster, worker, ipc, options) { params() {
super(client, cluster, worker, ipc, options); const speed = parseInt(this.type === "classic" ? this.args[0] : this.options.multiplier);
return {
speed: isNaN(speed) || speed < 1 ? 2 : speed
};
}
static init() {
super.init();
this.flags.push({ this.flags.push({
name: "multiplier", name: "multiplier",
type: 4, type: 4,
description: "Set the speed multiplier (default: 2)", description: "Set the speed multiplier (default: 2)",
min_value: 1 min_value: 1
}); });
} return this;
params() {
const speed = parseInt(this.type === "classic" ? this.args[0] : this.options.multiplier);
return {
speed: isNaN(speed) || speed < 1 ? 2 : speed
};
} }
static description = "Makes an image sequence faster"; static description = "Makes an image sequence faster";

View File

@ -1,8 +1,15 @@
import ImageCommand from "../../classes/imageCommand.js"; import ImageCommand from "../../classes/imageCommand.js";
class UncaptionCommand extends ImageCommand { class UncaptionCommand extends ImageCommand {
constructor(client, cluster, worker, ipc, options) { params() {
super(client, cluster, worker, ipc, options); const tolerance = parseFloat(this.specialArgs.tolerance);
return {
tolerance: isNaN(tolerance) ? 0.95 : tolerance
};
}
static init() {
super.init();
this.flags.push({ this.flags.push({
name: "tolerance", name: "tolerance",
type: 10, type: 10,
@ -10,13 +17,7 @@ class UncaptionCommand extends ImageCommand {
min_value: 0, min_value: 0,
max_value: 1 max_value: 1
}); });
} return this;
params() {
const tolerance = parseFloat(this.specialArgs.tolerance);
return {
tolerance: isNaN(tolerance) ? 0.95 : tolerance
};
} }
static description = "Removes the caption from an image"; static description = "Removes the caption from an image";

View File

@ -4,26 +4,31 @@ import MusicCommand from "../../classes/musicCommand.js";
class HostCommand extends MusicCommand { class HostCommand extends MusicCommand {
async run() { async run() {
if (!this.channel.guild) return "This command only works in servers!"; if (!this.channel.guild) return "This command only works in servers!";
if (!this.message.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.host !== this.author.id && this.author.id !== process.env.OWNER) return "Only the current voice session host can choose another host!"; if (this.connection.host !== this.author.id && this.author.id !== process.env.OWNER) return "Only the current voice session host can choose another host!";
if (this.args.length === 0) return "You need to provide who you want the host to be!"; const input = this.type === "classic" ? this.args.join(" ") : this.options.user;
const getUser = this.message.mentions.length >= 1 ? this.message.mentions[0] : (this.args.length !== 0 ? await this.ipc.fetchUser(this.args[0]) : null); if (!input || !input.trim()) return "You need to provide who you want the host to be!";
let user; let user;
if (getUser) { if (this.type === "classic") {
user = getUser; const getUser = this.message.mentions.length >= 1 ? this.message.mentions[0] : (await this.ipc.fetchUser(input));
} else if (this.args[0].match(/^<?[@#]?[&!]?\d+>?$/) && this.args[0] >= 21154535154122752n) { if (getUser) {
try { user = getUser;
user = await this.client.getRESTUser(this.args[0]); } else if (input.match(/^<?[@#]?[&!]?\d+>?$/) && input >= 21154535154122752n) {
} catch { try {
// no-op user = await this.client.getRESTUser(input);
} catch {
// no-op
}
} else {
const userRegex = new RegExp(input.split(" ").join("|"), "i");
const member = this.client.users.find(element => {
return userRegex.test(element.username);
});
user = member;
} }
} else if (this.args.join(" ") !== "") { } else {
const userRegex = new RegExp(this.args.join("|"), "i"); user = input;
const member = this.client.users.find(element => {
return userRegex.test(element.username);
});
user = member;
} }
if (!user) return "I can't find that user!"; if (!user) return "I can't find that user!";
if (user.bot) return "Setting a bot as the session host isn't a very good idea."; if (user.bot) return "Setting a bot as the session host isn't a very good idea.";
@ -35,6 +40,12 @@ class HostCommand extends MusicCommand {
return `🔊 ${member.mention} is the new voice channel host.`; return `🔊 ${member.mention} is the new voice channel host.`;
} }
static flags = [{
name: "user",
type: 6,
description: "The user you want the new host to be",
required: true
}];
static description = "Changes the host of the current voice session"; static description = "Changes the host of the current voice session";
static aliases = ["sethost"]; static aliases = ["sethost"];
} }

View File

@ -4,9 +4,9 @@ import MusicCommand from "../../classes/musicCommand.js";
class LoopCommand extends MusicCommand { class LoopCommand extends MusicCommand {
async run() { async run() {
if (!this.channel.guild) return "This command only works in servers!"; if (!this.channel.guild) return "This command only works in servers!";
if (!this.message.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.host !== this.author.id && !this.message.member.permissions.has("manageChannels")) return "Only the current voice session host can loop the music!"; if (this.connection.host !== this.author.id && !this.member.permissions.has("manageChannels")) return "Only the current voice session host can loop the music!";
const object = this.connection; const object = this.connection;
object.loop = !object.loop; object.loop = !object.loop;
players.set(this.channel.guild.id, object); players.set(this.channel.guild.id, object);

43
commands/music/music.js Normal file
View File

@ -0,0 +1,43 @@
import Command from "../../classes/command.js";
import { commands, aliases, info, categories } from "../../utils/collections.js";
// all-in-one music command
class MusicAIOCommand extends Command {
async run() {
let cmd = this.type === "classic" ? this.args[0] : this.optionsArray[0].name;
if (cmd === "music" || this.constructor.aliases.includes(cmd)) return "How dare you recurse me!";
if (this.type === "classic") {
this.origOptions.args.shift();
} else {
this.origOptions.interaction.data.options = this.origOptions.interaction.data.options[0].options;
}
if (aliases.has(cmd)) cmd = aliases.get(cmd);
if (commands.has(cmd) && info.get(cmd).category === "music") {
const command = commands.get(cmd);
return await (new command(this.client, this.cluster, this.worker, this.ipc, this.origOptions)).run();
} else {
return "That isn't a valid music command!";
}
}
static postInit() {
this.flags = [];
for (const cmd of categories.get("music")) {
if (cmd === "music") continue;
const cmdInfo = info.get(cmd);
this.flags.push({
name: cmd,
type: 1,
description: cmdInfo.description,
options: cmdInfo.flags
});
}
return this;
}
static description = "Handles music playback";
static requires = ["sound"];
static aliases = ["m"];
}
export default MusicAIOCommand;

View File

@ -5,7 +5,7 @@ import MusicCommand from "../../classes/musicCommand.js";
class NowPlayingCommand extends MusicCommand { class NowPlayingCommand extends MusicCommand {
async run() { async run() {
if (!this.channel.guild) return "This command only works in servers!"; if (!this.channel.guild) return "This command only works in servers!";
if (!this.message.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!";
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!";
@ -28,7 +28,7 @@ class NowPlayingCommand extends MusicCommand {
}, },
{ {
name: "💬 Channel:", name: "💬 Channel:",
value: this.channel.guild.channels.get(this.message.member.voiceState.channelID).name value: this.channel.guild.channels.get(this.member.voiceState.channelID).name
}, },
{ {
name: `${"▬".repeat(parts)}🔘${"▬".repeat(10 - parts)}`, name: `${"▬".repeat(parts)}🔘${"▬".repeat(10 - parts)}`,

View File

@ -3,9 +3,10 @@ import MusicCommand from "../../classes/musicCommand.js";
class PlayCommand extends MusicCommand { class PlayCommand extends MusicCommand {
async run() { async run() {
if (!this.args[0] && this.message.attachments.length <= 0) return "You need to provide what you want to play!"; const input = this.type === "classic" ? this.args.join(" ") : this.options.query;
let query = this.args.join(" ").trim(); if (!input && (this.type === "classic" ? (!this.message || this.message.attachments.length <= 0) : !this.options.file)) return "You need to provide what you want to play!";
const attachment = this.message.attachments[0]; let query = input ? input.trim() : "";
const attachment = this.type === "classic" ? this.message.attachments[0] : this.interaction.data.resolved.attachments[this.options.file];
if (query.startsWith("||") && query.endsWith("||")) { if (query.startsWith("||") && query.endsWith("||")) {
query = query.substring(2, query.length - 2); query = query.substring(2, query.length - 2);
} }
@ -14,13 +15,22 @@ class PlayCommand extends MusicCommand {
} }
try { try {
const url = new URL(query); const url = new URL(query);
return await play(this.client, url, this.message, true); return await play(this.client, url, { channel: this.channel, author: this.author, type: this.type, interaction: this.interaction }, true);
} catch { } catch {
const search = query.startsWith("ytsearch:") ? query : !this.args[0] && attachment ? attachment.url : `ytsearch:${query}`; const search = query.startsWith("ytsearch:") ? query : !query && attachment ? attachment.url : `ytsearch:${query}`;
return await play(this.client, search, this.message, true); return await play(this.client, search, { channel: this.channel, author: this.author, type: this.type, interaction: this.interaction }, true);
} }
} }
static flags = [{
name: "file",
type: 11,
description: "An audio file attachment"
}, {
name: "query",
type: 3,
description: "An audio search query or URL"
}];
static description = "Plays a song or adds it to the queue"; static description = "Plays a song or adds it to the queue";
static aliases = ["p"]; static aliases = ["p"];
static arguments = ["[url]"]; static arguments = ["[url]"];

View File

@ -7,7 +7,7 @@ import MusicCommand from "../../classes/musicCommand.js";
class QueueCommand extends MusicCommand { class QueueCommand extends MusicCommand {
async run() { async run() {
if (!this.channel.guild) return "This command only works in servers!"; if (!this.channel.guild) return "This command only works in servers!";
if (!this.message.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.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;

View File

@ -5,10 +5,10 @@ import MusicCommand from "../../classes/musicCommand.js";
class RemoveCommand extends MusicCommand { class RemoveCommand extends MusicCommand {
async run() { async run() {
if (!this.channel.guild) return "This command only works in servers!"; if (!this.channel.guild) return "This command only works in servers!";
if (!this.message.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.host !== this.author.id) return "Only the current voice session host can remove songs from the queue!"; if (this.connection.host !== this.author.id) return "Only the current voice session host can remove songs from the queue!";
const pos = parseInt(this.args[0]); const pos = parseInt(this.type === "classic" ? this.args[0] : this.options.position);
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]); const track = await Rest.decode(this.connection.player.node, removed[0]);
@ -16,6 +16,13 @@ class RemoveCommand extends MusicCommand {
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.`;
} }
static flags = [{
name: "position",
type: 4,
description: "The queue position you want to remove",
min_value: 1,
required: true
}];
static description = "Removes a song from the queue"; static description = "Removes a song from the queue";
static aliases = ["rm"]; static aliases = ["rm"];
} }

View File

@ -4,18 +4,25 @@ import MusicCommand from "../../classes/musicCommand.js";
class SeekCommand extends MusicCommand { class SeekCommand extends MusicCommand {
async run() { async run() {
if (!this.channel.guild) return "This command only works in servers!"; if (!this.channel.guild) return "This command only works in servers!";
if (!this.message.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.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 Rest.decode(player.node, player.track);
if (!track.isSeekable) return "This track isn't seekable!"; if (!track.isSeekable) return "This track isn't seekable!";
const seconds = parseFloat(this.args[0]); const seconds = parseFloat(this.type === "classic" ? this.args[0] : this.options.position);
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); await player.seek(seconds * 1000);
return `🔊 Seeked track to ${seconds} second(s).`; return `🔊 Seeked track to ${seconds} second(s).`;
} }
static flags = [{
name: "position",
type: 10,
description: "Seek to this position",
required: true,
min_value: 0
}];
static description = "Seeks to a different position in the music"; static description = "Seeks to a different position in the music";
static aliases = ["pos"]; static aliases = ["pos"];
static arguments = ["[seconds]"]; static arguments = ["[seconds]"];

View File

@ -4,7 +4,7 @@ import MusicCommand from "../../classes/musicCommand.js";
class ShuffleCommand extends MusicCommand { class ShuffleCommand extends MusicCommand {
async run() { async run() {
if (!this.channel.guild) return "This command only works in servers!"; if (!this.channel.guild) return "This command only works in servers!";
if (!this.message.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.host !== this.author.id) return "Only the current voice session host can shuffle the music!"; if (this.connection.host !== this.author.id) return "Only the current voice session host can shuffle the music!";
const object = this.connection; const object = this.connection;

View File

@ -4,10 +4,10 @@ import MusicCommand from "../../classes/musicCommand.js";
class SkipCommand extends MusicCommand { class SkipCommand extends MusicCommand {
async run() { async run() {
if (!this.channel.guild) return "This command only works in servers!"; if (!this.channel.guild) return "This command only works in servers!";
if (!this.message.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!";
const player = this.connection; const player = this.connection;
if (player.host !== this.author.id && !this.message.member.permissions.has("manageChannels")) { if (player.host !== this.author.id && !this.member.permissions.has("manageChannels")) {
const votes = skipVotes.get(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) }; const votes = skipVotes.get(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 (votes.ids.includes(this.author.id)) return "You've already voted to skip!"; if (votes.ids.includes(this.author.id)) return "You've already voted to skip!";
const newObject = { const newObject = {

View File

@ -4,13 +4,13 @@ import MusicCommand from "../../classes/musicCommand.js";
class StopCommand extends MusicCommand { class StopCommand extends MusicCommand {
async run() { async run() {
if (!this.channel.guild) return "This command only works in servers!"; if (!this.channel.guild) return "This command only works in servers!";
if (!this.message.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.leave(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.message.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); await manager.leave(this.channel.guild.id);
const connection = this.connection.player; const connection = this.connection.player;
await connection.destroy(); await connection.destroy();

View File

@ -1,18 +1,18 @@
import MusicCommand from "../../classes/musicCommand.js"; import MusicCommand from "../../classes/musicCommand.js";
class PauseCommand extends MusicCommand { class ToggleCommand extends MusicCommand {
async run() { async run() {
if (!this.channel.guild) return "This command only works in servers!"; if (!this.channel.guild) return "This command only works in servers!";
if (!this.message.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.host !== this.author.id && !this.message.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); await player.pause(!player.paused ? true : false);
return `🔊 The player has been ${player.paused ? "paused" : "resumed"}.`; return `🔊 The player has been ${player.paused ? "paused" : "resumed"}.`;
} }
static description = "Pauses/resumes the current song"; static description = "Pauses/resumes the current song";
static aliases = ["resume"]; static aliases = ["pause", "resume"];
} }
export default PauseCommand; export default ToggleCommand;

View File

@ -1,11 +1,7 @@
import { play } from "../../utils/soundplayer.js"; import SoundboardCommand from "../../classes/soundboardCommand.js";
import MusicCommand from "../../classes/musicCommand.js";
class BoiCommand extends MusicCommand {
async run() {
return await play(this.client, "./assets/audio/boi.ogg", this.message);
}
class BoiCommand extends SoundboardCommand {
static file = "./assets/audio/boi.ogg";
static description = "Plays the \"boi\" sound effect"; static description = "Plays the \"boi\" sound effect";
static aliases = ["boy", "neutron", "hugh"]; static aliases = ["boy", "neutron", "hugh"];
} }

View File

@ -1,11 +1,7 @@
import { play } from "../../utils/soundplayer.js"; import SoundboardCommand from "../../classes/soundboardCommand.js";
import MusicCommand from "../../classes/musicCommand.js";
class BoomCommand extends MusicCommand {
async run() {
return await play(this.client, "./assets/audio/boom.ogg", this.message);
}
class BoomCommand extends SoundboardCommand {
static file = "./assets/audio/boom.ogg";
static description = "Plays the Vine boom sound effect"; static description = "Plays the Vine boom sound effect";
static aliases = ["thud", "vine"]; static aliases = ["thud", "vine"];
} }

View File

@ -1,11 +1,7 @@
import { play } from "../../utils/soundplayer.js"; import SoundboardCommand from "../../classes/soundboardCommand.js";
import MusicCommand from "../../classes/musicCommand.js";
class BruhCommand extends MusicCommand {
async run() {
return await play(this.client, "./assets/audio/bruh.ogg", this.message);
}
class BruhCommand extends SoundboardCommand {
static file = "./assets/audio/bruh.ogg";
static description = "Plays the \"bruh\" sound effect"; static description = "Plays the \"bruh\" sound effect";
static aliases = ["bro"]; static aliases = ["bro"];
} }

View File

@ -1,13 +1,9 @@
import { play } from "../../utils/soundplayer.js"; import SoundboardCommand from "../../classes/soundboardCommand.js";
import MusicCommand from "../../classes/musicCommand.js";
class DamnDanielCommand extends MusicCommand { class DamnDanielCommand extends SoundboardCommand {
async run() { static file = "./assets/audio/damndaniel.ogg";
return await play(this.client, "./assets/audio/damndaniel.ogg", this.message); static description = "Plays the \"damn daniel\" sound effect";
} static aliases = ["daniel", "damn"];
static description = "Plays the \"damn daniel\" sound effect";
static aliases = ["daniel", "damn"];
} }
export default DamnDanielCommand; export default DamnDanielCommand;

View File

@ -1,11 +1,7 @@
import { play } from "../../utils/soundplayer.js"; import SoundboardCommand from "../../classes/soundboardCommand.js";
import MusicCommand from "../../classes/musicCommand.js";
class ExplosionCommand extends MusicCommand {
async run() {
return await play(this.client, "./assets/audio/explosion.ogg", this.message);
}
class ExplosionCommand extends SoundboardCommand {
static file = "./assets/audio/explosion.ogg";
static description = "Plays an explosion sound effect"; static description = "Plays an explosion sound effect";
} }

View File

@ -1,11 +1,7 @@
import { play } from "../../utils/soundplayer.js"; import SoundboardCommand from "../../classes/soundboardCommand.js";
import MusicCommand from "../../classes/musicCommand.js";
class FakePingCommand extends MusicCommand {
async run() {
return await play(this.client, "./assets/audio/ping.ogg", this.message);
}
class FakePingCommand extends SoundboardCommand {
static file = "./assets/audio/ping.ogg";
static description = "Plays a Discord ping sound effect"; static description = "Plays a Discord ping sound effect";
static aliases = ["notification", "notif"]; static aliases = ["notification", "notif"];
} }

View File

@ -1,11 +1,7 @@
import { play } from "../../utils/soundplayer.js"; import SoundboardCommand from "../../classes/soundboardCommand.js";
import MusicCommand from "../../classes/musicCommand.js";
class FartCommand extends MusicCommand {
async run() {
return await play(this.client, "./assets/audio/fart.ogg", this.message);
}
class FartCommand extends SoundboardCommand {
static file = "./assets/audio/fart.ogg";
static description = "Plays a fart sound effect"; static description = "Plays a fart sound effect";
static aliases = ["toot"]; static aliases = ["toot"];
} }

View File

@ -1,12 +1,7 @@
// shoutouts to dairyorange, you're a real one import SoundboardCommand from "../../classes/soundboardCommand.js";
import { play } from "../../utils/soundplayer.js";
import MusicCommand from "../../classes/musicCommand.js";
class FartReverbCommand extends MusicCommand {
async run() {
return await play(this.client, "./assets/audio/fart2.ogg", this.message);
}
class FartReverbCommand extends SoundboardCommand {
static file = "./assets/audio/fart2.ogg";
static description = "Plays a fart sound effect with extra reverb"; static description = "Plays a fart sound effect with extra reverb";
static aliases = ["fart2"]; static aliases = ["fart2"];
} }

View File

@ -1,11 +1,7 @@
import { play } from "../../utils/soundplayer.js"; import SoundboardCommand from "../../classes/soundboardCommand.js";
import MusicCommand from "../../classes/musicCommand.js";
class FBICommand extends MusicCommand {
async run() {
return await play(this.client, "./assets/audio/fbi.ogg", this.message);
}
class FBICommand extends SoundboardCommand {
static file = "./assets/audio/fbi.ogg";
static description = "Plays the \"FBI OPEN UP\" sound effect"; static description = "Plays the \"FBI OPEN UP\" sound effect";
static aliases = ["openup"]; static aliases = ["openup"];
} }

View File

@ -1,11 +1,7 @@
import { play } from "../../utils/soundplayer.js"; import SoundboardCommand from "../../classes/soundboardCommand.js";
import MusicCommand from "../../classes/musicCommand.js";
class MailCommand extends MusicCommand {
async run() {
return await play(this.client, "./assets/audio/mail.ogg", this.message);
}
class MailCommand extends SoundboardCommand {
static file = "./assets/audio/mail.ogg";
static description = "Plays the \"You've got mail\" sound effect"; static description = "Plays the \"You've got mail\" sound effect";
static aliases = ["yougotmail", "youvegotmail", "aol"]; static aliases = ["yougotmail", "youvegotmail", "aol"];
} }

View File

@ -1,11 +1,7 @@
import { play } from "../../utils/soundplayer.js"; import SoundboardCommand from "../../classes/soundboardCommand.js";
import MusicCommand from "../../classes/musicCommand.js";
class OofCommand extends MusicCommand {
async run() {
return await play(this.client, "./assets/audio/oof.ogg", this.message);
}
class OofCommand extends SoundboardCommand {
static file = "./assets/audio/oof.ogg";
static description = "Plays the Roblox \"oof\" sound"; static description = "Plays the Roblox \"oof\" sound";
static aliases = ["roblox", "commitdie"]; static aliases = ["roblox", "commitdie"];
} }

View File

@ -0,0 +1,31 @@
import { play } from "../../utils/soundplayer.js";
import Command from "../../classes/command.js";
import { sounds, info } from "../../utils/collections.js";
// all-in-one soundboard command
class SoundboardAIOCommand extends Command {
async run() {
const soundName = this.type === "classic" ? this.args[0] : this.optionsArray[0].name;
if (!sounds.has(soundName)) return "You need to provide a sound to play!";
const name = sounds.get(soundName);
return await play(this.client, name, { channel: this.channel, author: this.author, type: this.type, interaction: this.interaction });
}
static postInit() {
this.flags = [];
for (const sound of sounds.keys()) {
this.flags.push({
name: sound,
type: 1,
description: info.get(sound).description
});
}
return this;
}
static description = "Plays a sound effect";
static requires = ["sound"];
static aliases = ["sound", "sb"];
}
export default SoundboardAIOCommand;

View File

@ -1,11 +1,7 @@
import { play } from "../../utils/soundplayer.js"; import SoundboardCommand from "../../classes/soundboardCommand.js";
import MusicCommand from "../../classes/musicCommand.js";
class WinXPCommand extends MusicCommand {
async run() {
return await play(this.client, "./assets/audio/winxp.ogg", this.message);
}
class WinXPCommand extends SoundboardCommand {
static file = "./assets/audio/winxp.ogg";
static description = "Plays the Windows XP startup sound"; static description = "Plays the Windows XP startup sound";
static aliases = ["windows", "xp"]; static aliases = ["windows", "xp"];
} }

View File

@ -23,7 +23,7 @@ class TagsCommand extends Command {
const getResult = await database.getTag(this.channel.guild.id, this.args[1].toLowerCase()); const getResult = await database.getTag(this.channel.guild.id, this.args[1].toLowerCase());
if (!getResult) return "This tag doesn't exist!"; if (!getResult) return "This tag doesn't exist!";
const owners = process.env.OWNER.split(","); const owners = process.env.OWNER.split(",");
if (getResult.author !== this.author.id && !this.message.member.permissions.has("manageMessages") && !owners.includes(this.author.id)) return "You don't own this tag!"; if (getResult.author !== this.author.id && !this.member.permissions.has("manageMessages") && !owners.includes(this.author.id)) return "You don't own this tag!";
await database.removeTag(this.args[1].toLowerCase(), this.channel.guild); await database.removeTag(this.args[1].toLowerCase(), this.channel.guild);
return `The tag \`${this.args[1].toLowerCase()}\` has been deleted!`; return `The tag \`${this.args[1].toLowerCase()}\` has been deleted!`;
} else if (this.args[0].toLowerCase() === "edit") { } else if (this.args[0].toLowerCase() === "edit") {
@ -31,7 +31,7 @@ class TagsCommand extends Command {
const getResult = await database.getTag(this.channel.guild.id, this.args[1].toLowerCase()); const getResult = await database.getTag(this.channel.guild.id, this.args[1].toLowerCase());
if (!getResult) return "This tag doesn't exist!"; if (!getResult) return "This tag doesn't exist!";
const owners = process.env.OWNER.split(","); const owners = process.env.OWNER.split(",");
if (getResult.author !== this.author.id && !this.message.member.permissions.has("manageMessages") && !owners.includes(this.author.id)) return "You don't own this tag!"; if (getResult.author !== this.author.id && !this.member.permissions.has("manageMessages") && !owners.includes(this.author.id)) return "You don't own this tag!";
await this.setTag(this.args.slice(2).join(" "), this.args[1].toLowerCase(), this.message, true); await this.setTag(this.args.slice(2).join(" "), this.args[1].toLowerCase(), this.message, true);
return `The tag \`${this.args[1].toLowerCase()}\` has been edited!`; return `The tag \`${this.args[1].toLowerCase()}\` has been edited!`;
} else if (this.args[0].toLowerCase() === "own" || this.args[0].toLowerCase() === "owner") { } else if (this.args[0].toLowerCase() === "own" || this.args[0].toLowerCase() === "owner") {

View File

@ -110,7 +110,7 @@
"MilkyTracker", "MilkyTracker",
"with chimps", "with chimps",
"with the TF2 source code", "with the TF2 source code",
"alvin the chipmunk nightcore", "alvin chipmunk nightcore",
"Troll", "Troll",
"ay yo the pizza here", "ay yo the pizza here",
"100 gecs", "100 gecs",

View File

@ -8,13 +8,13 @@ import { fileURLToPath } from "url";
// fancy loggings // fancy loggings
import { log, error } from "./utils/logger.js"; import { log, error } from "./utils/logger.js";
// initialize command loader // initialize command loader
import { load } 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, status, connected } from "./utils/soundplayer.js";
// database stuff // database stuff
import database from "./utils/database.js"; import database from "./utils/database.js";
// command collections // command collections
import { paths, info } from "./utils/collections.js"; import { paths } from "./utils/collections.js";
// playing messages // playing messages
const { messages } = JSON.parse(readFileSync(new URL("./messages.json", import.meta.url))); const { messages } = JSON.parse(readFileSync(new URL("./messages.json", import.meta.url)));
// other stuff // other stuff
@ -35,23 +35,16 @@ class Shard extends BaseClusterWorker {
async init() { async init() {
// register commands and their info // register commands and their info
const soundStatus = await checkStatus(); const soundStatus = await checkStatus();
const commandArray = [];
log("info", "Attempting to load commands..."); log("info", "Attempting to load commands...");
for await (const commandFile of this.getFiles(resolve(dirname(fileURLToPath(import.meta.url)), "./commands/"))) { for await (const commandFile of this.getFiles(resolve(dirname(fileURLToPath(import.meta.url)), "./commands/"))) {
log("log", `Loading command from ${commandFile}...`); log("log", `Loading command from ${commandFile}...`);
try { try {
const name = await load(this.bot, this.clusterID, this.workerID, this.ipc, commandFile, soundStatus); await load(this.bot, this.clusterID, this.workerID, this.ipc, commandFile, soundStatus);
const commandInfo = info.get(name);
if (commandInfo && commandInfo.slashAllowed) commandArray.push({
name,
type: 1,
description: commandInfo.description,
options: commandInfo.flags
});
} catch (e) { } catch (e) {
error(`Failed to register command from ${commandFile}: ${e}`); error(`Failed to register command from ${commandFile}: ${e}`);
} }
} }
const commandArray = await update(this.bot, this.clusterID, this.workerID, this.ipc, soundStatus);
log("info", "Finished loading commands."); log("info", "Finished loading commands.");
await database.setup(this.ipc); await database.setup(this.ipc);

View File

@ -2,6 +2,8 @@ export const commands = new Map();
export const paths = new Map(); export const paths = new Map();
export const aliases = new Map(); export const aliases = new Map();
export const info = new Map(); export const info = new Map();
export const sounds = new Map();
export const categories = new Map();
class TimedMap extends Map { class TimedMap extends Map {
set(key, value) { set(key, value) {

View File

@ -1,4 +1,4 @@
import { paths, commands, info, aliases as _aliases } from "./collections.js"; import { paths, commands, info, sounds, categories, aliases as _aliases } from "./collections.js";
import { log } from "./logger.js"; import { log } from "./logger.js";
let queryValue = 0; let queryValue = 0;
@ -13,21 +13,27 @@ export async function load(client, cluster, worker, ipc, command, soundStatus, s
} }
const commandArray = command.split("/"); const commandArray = command.split("/");
const commandName = commandArray[commandArray.length - 1].split(".")[0]; const commandName = commandArray[commandArray.length - 1].split(".")[0];
props.init();
paths.set(commandName, command); paths.set(commandName, command);
commands.set(commandName, props); commands.set(commandName, props);
const propsInstance = new props(client, cluster, worker, ipc, {}); if (Object.getPrototypeOf(props).name === "SoundboardCommand") sounds.set(commandName, props.file);
const category = commandArray[commandArray.length - 2];
info.set(commandName, { info.set(commandName, {
category: commandArray[commandArray.length - 2], category: category,
description: props.description, description: props.description,
aliases: props.aliases, aliases: props.aliases,
params: props.arguments, params: props.arguments,
flags: propsInstance.flags ?? props.flags, flags: props.flags,
slashAllowed: props.slashAllowed slashAllowed: props.slashAllowed
}); });
const categoryCommands = categories.get(category);
categories.set(category, categoryCommands ? [...categoryCommands, commandName] : [commandName]);
if (slashReload && props.slashAllowed) { if (slashReload && props.slashAllowed) {
const commandList = await client.getCommands(); const commandList = await client.getCommands();
const oldCommand = commandList.filter((item) => { const oldCommand = commandList.filter((item) => {
@ -37,7 +43,7 @@ export async function load(client, cluster, worker, ipc, command, soundStatus, s
name: commandName, name: commandName,
type: 1, type: 1,
description: props.description, description: props.description,
options: propsInstance.flags ?? props.flags options: props.flags
}); });
} }
@ -49,3 +55,30 @@ export async function load(client, cluster, worker, ipc, command, soundStatus, s
} }
return commandName; return commandName;
} }
export async function update() {
const commandArray = [];
for (const [name, command] of commands.entries()) {
let cmdInfo = info.get(name);
if (command.postInit) {
const cmd = command.postInit();
//commands.set(name, cmd);
cmdInfo = {
category: cmdInfo.category,
description: cmd.description,
aliases: cmd.aliases,
params: cmd.arguments,
flags: cmd.flags,
slashAllowed: cmd.slashAllowed
};
info.set(name, cmdInfo);
}
if (cmdInfo && cmdInfo.slashAllowed) commandArray.push({
name,
type: 1,
description: cmdInfo.description,
options: cmdInfo.flags
});
}
return commandArray;
}

View File

@ -133,7 +133,7 @@ export default async (client, cmdMessage, interaction, options, extraReturnTypes
// we can get a raw attachment or a URL in the interaction itself // we can get a raw attachment or a URL in the interaction itself
if (options) { if (options) {
if (options.image) { if (options.image) {
const result = await getImage(options.image.proxy_url, options.image.url, video); const result = await getImage(interaction.data.resolved.attachments[options.image].proxy_url, interaction.data.resolved.attachments[options.image].url, video);
if (result !== false) return result; if (result !== false) return result;
} else if (options.link) { } else if (options.link) {
const result = await getImage(options.link, options.link, video); const result = await getImage(options.link, options.link, video);

View File

@ -1,37 +0,0 @@
// eris doesn't come with an awaitMessages method by default, so we make our own
import { EventEmitter } from "events";
class MessageCollector extends EventEmitter {
constructor(client, channel, filter, options = {}) {
super();
this.filter = filter;
this.channel = channel;
this.options = options;
this.ended = false;
this.collected = [];
this.bot = client;
this.listener = message => this.verify(message);
this.bot.on("messageCreate", this.listener);
if (options.time) setTimeout(() => this.stop("time"), options.time);
}
verify(message) {
if (this.channel.id !== message.channel.id) return false;
if (this.filter(message)) {
this.collected.push(message);
this.emit("message", message);
if (this.collected.length >= this.options.maxMatches) this.stop("maxMatches");
return true;
}
return false;
}
stop(reason) {
if (this.ended) return;
this.ended = true;
this.bot.removeListener("messageCreate", this.listener);
this.emit("end", this.collected, reason);
}
}
export default MessageCollector;

View File

@ -50,15 +50,15 @@ export async function connect(client) {
return length; return length;
} }
export async function play(client, sound, message, music = false) { export async function play(client, sound, options, music = false) {
if (!manager) return "The sound commands are still starting up!"; if (!manager) return "The sound commands are still starting up!";
if (!message.channel.guild) return "This command only works in servers!"; if (!options.channel.guild) return "This command only works in servers!";
if (!message.member.voiceState.channelID) return "You need to be in a voice channel first!"; if (!options.author.voiceState.channelID) return "You need to be in a voice channel first!";
if (!message.channel.guild.permissionsOf(client.user.id).has("voiceConnect")) return "I can't join this voice channel!"; if (!options.channel.guild.permissionsOf(client.user.id).has("voiceConnect")) return "I can't join this voice channel!";
const voiceChannel = message.channel.guild.channels.get(message.member.voiceState.channelID); const voiceChannel = options.channel.guild.channels.get(options.author.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(message.channel.guild.id); const player = players.get(options.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!"; if (!music && manager.voiceStates.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.idealNodes[0];
if (!node) { if (!node) {
const status = await checkStatus(); const status = await checkStatus();
@ -92,12 +92,12 @@ export async function play(client, sound, message, music = false) {
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 ${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!`;
} else { } else {
nextSong(client, message, connection, tracks[0].track, tracks[0].info, music, voiceChannel, player ? player.host : message.author.id, player ? player.loop : false, player ? player.shuffle : false); nextSong(client, options, connection, tracks[0].track, tracks[0].info, music, voiceChannel, player ? player.host : options.author.id, player ? player.loop : false, player ? player.shuffle : false);
return; return;
} }
} }
export async function nextSong(client, message, connection, track, info, music, voiceChannel, host, loop = false, shuffle = false, lastTrack = null) { export async function nextSong(client, options, connection, track, info, music, voiceChannel, host, loop = false, shuffle = false, lastTrack = null) {
skipVotes.delete(voiceChannel.guild.id); skipVotes.delete(voiceChannel.guild.id);
const parts = Math.floor((0 / info.length) * 10); const parts = Math.floor((0 / info.length) * 10);
let playingMessage; let playingMessage;
@ -114,7 +114,7 @@ export async function nextSong(client, message, connection, track, info, music,
playingMessage = players.get(voiceChannel.guild.id).playMessage; playingMessage = players.get(voiceChannel.guild.id).playMessage;
} else { } else {
try { try {
playingMessage = await client.createMessage(message.channel.id, !music ? "🔊 Playing sound..." : { const content = !music ? "🔊 Playing sound..." : {
embeds: [{ embeds: [{
color: 16711680, color: 16711680,
author: { author: {
@ -138,7 +138,13 @@ export async function nextSong(client, message, connection, track, info, music,
value: `0:00/${info.isStream ? "∞" : format(info.length)}` value: `0:00/${info.isStream ? "∞" : format(info.length)}`
}] }]
}] }]
}); };
if (options.type === "classic") {
playingMessage = await client.createMessage(options.channel.id, content);
} else {
await options.interaction[options.interaction.acknowledged ? "editOriginalMessage" : "createMessage"](content);
playingMessage = await options.interaction.getOriginalMessage();
}
} catch { } catch {
// no-op // no-op
} }
@ -147,7 +153,7 @@ export async function nextSong(client, message, connection, track, info, music,
connection.removeAllListeners("end"); connection.removeAllListeners("end");
await connection.play(track); await connection.play(track);
await connection.volume(75); await connection.volume(75);
players.set(voiceChannel.guild.id, { player: connection, type: music ? "music" : "sound", host: host, voiceChannel: voiceChannel, originalChannel: message.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("error", async (error) => {
try { try {
if (playingMessage.channel.messages.has(playingMessage.id)) await playingMessage.delete(); if (playingMessage.channel.messages.has(playingMessage.id)) await playingMessage.delete();
@ -166,7 +172,7 @@ export async function nextSong(client, message, connection, track, info, music,
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(error);
await client.createMessage(message.channel.id, `🔊 Looks like there was an error regarding sound playback:\n\`\`\`${error.type}: ${error.error}\`\`\``); await (options.type === "classic" ? options.channel.createMessage : options.interaction.createMessage)(`🔊 Looks like there was an error regarding sound playback:\n\`\`\`${error.type}: ${error.error}\`\`\``);
}); });
connection.on("end", async (data) => { connection.on("end", async (data) => {
if (data.reason === "REPLACED") return; if (data.reason === "REPLACED") return;
@ -194,7 +200,7 @@ export async function nextSong(client, message, 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 Rest.decode(connection.node, newQueue[0]);
nextSong(client, message, 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();
if (newQueue[0] !== track && player.playMessage.channel.messages.has(player.playMessage.id)) await player.playMessage.delete(); if (newQueue[0] !== track && player.playMessage.channel.messages.has(player.playMessage.id)) await player.playMessage.delete();
@ -207,7 +213,7 @@ export async function nextSong(client, message, connection, track, info, music,
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);
if (music) await client.createMessage(message.channel.id, "🔊 The current voice channel session has ended."); if (music) await (options.type === "classic" ? options.channel.createMessage : options.interaction.createMessage)("🔊 The current voice channel session has ended.");
try { try {
if (playingMessage.channel.messages.has(playingMessage.id)) await playingMessage.delete(); if (playingMessage.channel.messages.has(playingMessage.id)) await playingMessage.delete();
if (player && player.playMessage.channel.messages.has(player.playMessage.id)) await player.playMessage.delete(); if (player && player.playMessage.channel.messages.has(player.playMessage.id)) await player.playMessage.delete();