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.worker = worker;
this.ipc = ipc;
this.origOptions = options;
this.type = options.type;
this.args = options.args;
if (options.type === "classic") {
this.message = options.message;
this.channel = options.message.channel;
this.author = options.message.author;
this.member = options.message.member;
this.content = options.content;
this.specialArgs = options.specialArgs;
this.reference = {
@ -26,12 +28,13 @@ class Command {
} else if (options.type === "application") {
this.interaction = options.interaction;
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) {
this.specialArgs = this.options = options.interaction.data.options.reduce((obj, item) => {
obj[item.name] = item.value;
return obj;
}, {});
this.optionsArray = options.interaction.data.options;
} else {
this.specialArgs = this.options = {};
}
@ -50,6 +53,10 @@ class Command {
}
}
static init() {
return this;
}
static description = "No description found";
static aliases = [];
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() {
return true;
}
@ -98,7 +74,6 @@ class ImageCommand extends Command {
runningCommands.delete(this.author.id);
throw e;
}
}
if (this.constructor.requiresText) {
@ -122,7 +97,7 @@ class ImageCommand extends Command {
if (magickParams.params.type === "image/gif" && this.type === "classic") {
status = await this.processMessage(this.message);
} else {
this.acknowledge();
await this.acknowledge();
}
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`);
}
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 requiresText = 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)}`;
}
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 aliases = ["magicball", "magikball", "magic8ball", "magik8ball", "eightball"];
static arguments = ["{text}"];

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ import Command from "../../classes/command.js";
class MCCommand extends Command {
async run() {
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("+"))}`);
return {
file: Buffer.from(await request.arrayBuffer()),

View File

@ -3,7 +3,7 @@ import Command from "../../classes/command.js";
class WikihowCommand extends Command {
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 json = await request.json();
const id = Object.keys(json.query.pages)[0];

View File

@ -5,7 +5,7 @@ class ChannelCommand extends Command {
async run() {
if (!this.channel.guild) return "This command only works in servers!";
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[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() {
if (!this.channel.guild) return "This command only works in servers!";
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[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 {
async run() {
this.acknowledge();
await this.acknowledge();
let prefix = "";
const controller = new AbortController(); // eslint-disable-line no-undef
const timeout = setTimeout(() => {

View File

@ -3,12 +3,13 @@ import Command from "../../classes/command.js";
class EmoteCommand extends Command {
async run() {
if (this.args.length === 0) return "You need to provide an emoji!";
if (this.content.split(" ")[0].match(/^<a?:.+:\d+>$/)) {
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"}`;
} else if (this.args[0].match(emojiRegex)) {
const emoji = this.type === "classic" ? this.args.join(" ") : this.options.emoji;
if (!emoji || !emoji.trim()) return "You need to provide an emoji!";
if (emoji.split(" ")[0].match(/^<a?:.+:\d+>$/)) {
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 = [];
for (const codePoint of this.args[0]) {
for (const codePoint of emoji) {
codePoints.push(codePoint.codePointAt(0).toString(16));
}
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 aliases = ["e", "em", "hugemoji", "hugeemoji", "emoji"];
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!";
const query = this.type === "classic" ? this.args.join(" ") : this.options.query;
if (!query || !query.trim()) return "You need to provide something to search for!";
this.acknowledge();
await this.acknowledge();
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());
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 {
async run() {
this.acknowledge();
if (this.args.length === 0 || !urlCheck(this.args[0])) return "You need to provide a short URL to lengthen!";
if (urlCheck(this.args[0])) {
const url = await fetch(encodeURI(this.args[0]), { redirect: "manual" });
return url.headers.get("location") || this.args[0];
await this.acknowledge();
const input = this.type === "classic" ? this.args.join(" ") : this.options.url;
if (!input || !input.trim() || !urlCheck(input)) return "You need to provide a short URL to lengthen!";
if (urlCheck(input)) {
const url = await fetch(encodeURI(input), { redirect: "manual" });
return url.headers.get("location") || input;
} else {
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 aliases = ["longurl", "lengthenurl", "longuri", "lengthenuri", "unshorten"];
static arguments = ["[url]"];

View File

@ -7,7 +7,7 @@ class PrefixCommand extends Command {
const guild = await database.getGuild(this.channel.guild.id);
if (this.args.length !== 0) {
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);
return `The prefix has been changed to ${this.args[0]}.`;
} else {

View File

@ -5,7 +5,7 @@ import Command from "../../classes/command.js";
class QrCreateCommand extends Command {
async run() {
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();
qrcode.toFileStream(writable, this.content, { margin: 1 });
const file = await this.streamToBuf(writable);

View File

@ -9,8 +9,8 @@ class QrReadCommand extends Command {
async run() {
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!";
this.acknowledge();
const data = await (await fetch(image.path)).buffer();
await this.acknowledge();
const data = Buffer.from(await (await fetch(image.path)).arrayBuffer());
const rawData = await sharp(data).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
const qrBuffer = jsqr(rawData.data, rawData.info.width, rawData.info.height);
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 {
async run() {
this.acknowledge();
await this.acknowledge();
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!";
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!");
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!");
this.acknowledge();
this.ipc.broadcast("reload", commandName);
this.ipc.register("reloadSuccess", () => {
this.ipc.unregister("reloadSuccess");
this.ipc.unregister("reloadFail");
resolve(`The command \`${commandName}\` has been reloaded.`);
});
this.ipc.register("reloadFail", (message) => {
this.ipc.unregister("reloadSuccess");
this.ipc.unregister("reloadFail");
resolve(message.result);
this.acknowledge().then(() => {
this.ipc.broadcast("reload", commandName);
this.ipc.register("reloadSuccess", () => {
this.ipc.unregister("reloadSuccess");
this.ipc.unregister("reloadFail");
resolve(`The command \`${commandName}\` has been reloaded.`);
});
this.ipc.register("reloadFail", (message) => {
this.ipc.unregister("reloadSuccess");
this.ipc.unregister("reloadFail");
resolve(message.result);
});
});
});
}

View File

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

View File

@ -9,7 +9,7 @@ class YouTubeCommand extends Command {
async run() {
const query = this.type === "classic" ? this.args.join(" ") : this.options.query;
if (!query || !query.trim()) return "You need to provide something to search for!";
this.acknowledge();
await this.acknowledge();
const messages = [];
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!";

View File

@ -1,15 +1,6 @@
import ImageCommand from "../../classes/imageCommand.js";
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() {
return {
old: !!this.specialArgs.old,
@ -19,6 +10,16 @@ class BlurpleCommand extends ImageCommand {
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 command = "colors";
static aliases = ["blurp"];

View File

@ -2,8 +2,18 @@ import ImageCommand from "../../classes/imageCommand.js";
const allowedFonts = ["futura", "impact", "helvetica", "arial", "roboto", "noto", "times"];
class CaptionCommand extends ImageCommand {
constructor(client, cluster, worker, ipc, options) {
super(client, cluster, worker, ipc, options);
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 init() {
super.init();
this.flags.push({
name: "noegg",
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)"
});
}
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"
};
return this;
}
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"];
class CaptionTwoCommand extends ImageCommand {
constructor(client, cluster, worker, ipc, options) {
super(client, cluster, worker, ipc, options);
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 init() {
super.init();
this.flags.push({
name: "top",
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)"
});
}
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"
};
return this;
}
static description = "Adds a me.me caption/tag list to an image";

View File

@ -1,16 +1,6 @@
import ImageCommand from "../../classes/imageCommand.js";
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() {
const frameCount = parseInt(this.type === "classic" ? this.args[0] : this.options.endframe);
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 aliases = ["noloop", "once"];
static arguments = ["{end frame number}"];

View File

@ -1,8 +1,15 @@
import ImageCommand from "../../classes/imageCommand.js";
class JPEGCommand extends ImageCommand {
constructor(client, cluster, worker, ipc, options) {
super(client, cluster, worker, ipc, options);
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 init() {
super.init();
this.flags.push({
name: "quality",
type: 4,
@ -10,13 +17,7 @@ class JPEGCommand extends ImageCommand {
min_value: 1,
max_value: 100
});
}
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))
};
return this;
}
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"];
class MemeCommand extends ImageCommand {
constructor(client, cluster, worker, ipc, options) {
super(client, cluster, worker, ipc, options);
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 init() {
super.init();
this.flags.push({
name: "case",
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)"
});
}
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"
};
return this;
}
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"];
class MotivateCommand extends ImageCommand {
constructor(client, cluster, worker, ipc, options) {
super(client, cluster, worker, ipc, options);
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 init() {
super.init();
this.flags.push({
name: "font",
type: 3,
@ -16,16 +26,7 @@ class MotivateCommand extends ImageCommand {
})(),
description: "Specify the font you want to use (default: times)"
});
}
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"
};
return this;
}
static description = "Generates a motivational poster";

View File

@ -1,16 +1,6 @@
import ImageCommand from "../../classes/imageCommand.js";
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() {
const speed = parseInt(this.type === "classic" ? this.args[0] : this.options.multiplier);
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 aliases = ["slowdown", "slower", "gifspeed2"];
static arguments = ["{multiplier}"];

View File

@ -1,17 +1,6 @@
import ImageCommand from "../../classes/imageCommand.js";
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) {
const newArgs = this.type === "classic" ? this.args.filter(item => !item.includes(url)).join(" ") : this.options.text;
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 aliases = ["snap", "caption3"];
static arguments = ["[text]"];

View File

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

View File

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

View File

@ -4,26 +4,31 @@ import MusicCommand from "../../classes/musicCommand.js";
class HostCommand extends MusicCommand {
async run() {
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.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 getUser = this.message.mentions.length >= 1 ? this.message.mentions[0] : (this.args.length !== 0 ? await this.ipc.fetchUser(this.args[0]) : null);
const input = this.type === "classic" ? this.args.join(" ") : this.options.user;
if (!input || !input.trim()) return "You need to provide who you want the host to be!";
let user;
if (getUser) {
user = getUser;
} else if (this.args[0].match(/^<?[@#]?[&!]?\d+>?$/) && this.args[0] >= 21154535154122752n) {
try {
user = await this.client.getRESTUser(this.args[0]);
} catch {
// no-op
if (this.type === "classic") {
const getUser = this.message.mentions.length >= 1 ? this.message.mentions[0] : (await this.ipc.fetchUser(input));
if (getUser) {
user = getUser;
} else if (input.match(/^<?[@#]?[&!]?\d+>?$/) && input >= 21154535154122752n) {
try {
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(" ") !== "") {
const userRegex = new RegExp(this.args.join("|"), "i");
const member = this.client.users.find(element => {
return userRegex.test(element.username);
});
user = member;
} else {
user = input;
}
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.";
@ -35,6 +40,12 @@ class HostCommand extends MusicCommand {
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 aliases = ["sethost"];
}

View File

@ -4,9 +4,9 @@ import MusicCommand from "../../classes/musicCommand.js";
class LoopCommand extends MusicCommand {
async run() {
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.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;
object.loop = !object.loop;
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 {
async run() {
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!";
const player = this.connection.player;
if (!player) return "I'm not playing anything!";
@ -28,7 +28,7 @@ class NowPlayingCommand extends MusicCommand {
},
{
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)}`,

View File

@ -3,9 +3,10 @@ import MusicCommand from "../../classes/musicCommand.js";
class PlayCommand extends MusicCommand {
async run() {
if (!this.args[0] && this.message.attachments.length <= 0) return "You need to provide what you want to play!";
let query = this.args.join(" ").trim();
const attachment = this.message.attachments[0];
const input = this.type === "classic" ? this.args.join(" ") : this.options.query;
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!";
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("||")) {
query = query.substring(2, query.length - 2);
}
@ -14,13 +15,22 @@ class PlayCommand extends MusicCommand {
}
try {
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 {
const search = query.startsWith("ytsearch:") ? query : !this.args[0] && attachment ? attachment.url : `ytsearch:${query}`;
return await play(this.client, search, this.message, true);
const search = query.startsWith("ytsearch:") ? query : !query && attachment ? attachment.url : `ytsearch:${query}`;
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 aliases = ["p"];
static arguments = ["[url]"];

View File

@ -7,7 +7,7 @@ import MusicCommand from "../../classes/musicCommand.js";
class QueueCommand extends MusicCommand {
async run() {
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.permissionsOf(this.client.user.id).has("embedLinks")) return "I don't have the `Embed Links` permission!";
const player = this.connection;

View File

@ -5,10 +5,10 @@ import MusicCommand from "../../classes/musicCommand.js";
class RemoveCommand extends MusicCommand {
async run() {
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.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!";
const removed = this.queue.splice(pos, 1);
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.`;
}
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 aliases = ["rm"];
}

View File

@ -4,18 +4,25 @@ import MusicCommand from "../../classes/musicCommand.js";
class SeekCommand extends MusicCommand {
async run() {
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.connection.host !== this.author.id) return "Only the current voice session host can seek the music!";
const player = this.connection.player;
const track = await Rest.decode(player.node, player.track);
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!";
await player.seek(seconds * 1000);
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 aliases = ["pos"];
static arguments = ["[seconds]"];

View File

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

View File

@ -4,10 +4,10 @@ import MusicCommand from "../../classes/musicCommand.js";
class SkipCommand extends MusicCommand {
async run() {
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!";
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) };
if (votes.ids.includes(this.author.id)) return "You've already voted to skip!";
const newObject = {

View File

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

View File

@ -1,18 +1,18 @@
import MusicCommand from "../../classes/musicCommand.js";
class PauseCommand extends MusicCommand {
class ToggleCommand extends MusicCommand {
async run() {
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.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;
await player.pause(!player.paused ? true : false);
return `🔊 The player has been ${player.paused ? "paused" : "resumed"}.`;
}
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 MusicCommand from "../../classes/musicCommand.js";
class BoiCommand extends MusicCommand {
async run() {
return await play(this.client, "./assets/audio/boi.ogg", this.message);
}
import SoundboardCommand from "../../classes/soundboardCommand.js";
class BoiCommand extends SoundboardCommand {
static file = "./assets/audio/boi.ogg";
static description = "Plays the \"boi\" sound effect";
static aliases = ["boy", "neutron", "hugh"];
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,7 @@
// shoutouts to dairyorange, you're a real one
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);
}
import SoundboardCommand from "../../classes/soundboardCommand.js";
class FartReverbCommand extends SoundboardCommand {
static file = "./assets/audio/fart2.ogg";
static description = "Plays a fart sound effect with extra reverb";
static aliases = ["fart2"];
}

View File

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

View File

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

View File

@ -1,11 +1,7 @@
import { play } from "../../utils/soundplayer.js";
import MusicCommand from "../../classes/musicCommand.js";
class OofCommand extends MusicCommand {
async run() {
return await play(this.client, "./assets/audio/oof.ogg", this.message);
}
import SoundboardCommand from "../../classes/soundboardCommand.js";
class OofCommand extends SoundboardCommand {
static file = "./assets/audio/oof.ogg";
static description = "Plays the Roblox \"oof\" sound";
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 MusicCommand from "../../classes/musicCommand.js";
class WinXPCommand extends MusicCommand {
async run() {
return await play(this.client, "./assets/audio/winxp.ogg", this.message);
}
import SoundboardCommand from "../../classes/soundboardCommand.js";
class WinXPCommand extends SoundboardCommand {
static file = "./assets/audio/winxp.ogg";
static description = "Plays the Windows XP startup sound";
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());
if (!getResult) return "This tag doesn't exist!";
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);
return `The tag \`${this.args[1].toLowerCase()}\` has been deleted!`;
} 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());
if (!getResult) return "This tag doesn't exist!";
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);
return `The tag \`${this.args[1].toLowerCase()}\` has been edited!`;
} else if (this.args[0].toLowerCase() === "own" || this.args[0].toLowerCase() === "owner") {

View File

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

View File

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

View File

@ -2,6 +2,8 @@ export const commands = new Map();
export const paths = new Map();
export const aliases = new Map();
export const info = new Map();
export const sounds = new Map();
export const categories = new Map();
class TimedMap extends Map {
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";
let queryValue = 0;
@ -13,21 +13,27 @@ export async function load(client, cluster, worker, ipc, command, soundStatus, s
}
const commandArray = command.split("/");
const commandName = commandArray[commandArray.length - 1].split(".")[0];
props.init();
paths.set(commandName, command);
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, {
category: commandArray[commandArray.length - 2],
category: category,
description: props.description,
aliases: props.aliases,
params: props.arguments,
flags: propsInstance.flags ?? props.flags,
flags: props.flags,
slashAllowed: props.slashAllowed
});
const categoryCommands = categories.get(category);
categories.set(category, categoryCommands ? [...categoryCommands, commandName] : [commandName]);
if (slashReload && props.slashAllowed) {
const commandList = await client.getCommands();
const oldCommand = commandList.filter((item) => {
@ -37,7 +43,7 @@ export async function load(client, cluster, worker, ipc, command, soundStatus, s
name: commandName,
type: 1,
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;
}
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
if (options) {
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;
} else if (options.link) {
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;
}
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 (!message.channel.guild) return "This command only works in servers!";
if (!message.member.voiceState.channelID) return "You need to be in a voice channel first!";
if (!message.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);
if (!options.channel.guild) return "This command only works in servers!";
if (!options.author.voiceState.channelID) return "You need to be in a voice channel first!";
if (!options.channel.guild.permissionsOf(client.user.id).has("voiceConnect")) return "I can't join this voice channel!";
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!";
const player = players.get(message.channel.guild.id);
if (!music && manager.voiceStates.has(message.channel.guild.id) && (player && player.type === "music")) return "I can't play a sound effect while playing music!";
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!";
let node = manager.idealNodes[0];
if (!node) {
const status = await checkStatus();
@ -92,12 +92,12 @@ export async function play(client, sound, message, music = false) {
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!`;
} 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;
}
}
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);
const parts = Math.floor((0 / info.length) * 10);
let playingMessage;
@ -114,7 +114,7 @@ export async function nextSong(client, message, connection, track, info, music,
playingMessage = players.get(voiceChannel.guild.id).playMessage;
} else {
try {
playingMessage = await client.createMessage(message.channel.id, !music ? "🔊 Playing sound..." : {
const content = !music ? "🔊 Playing sound..." : {
embeds: [{
color: 16711680,
author: {
@ -138,7 +138,13 @@ export async function nextSong(client, message, connection, track, info, music,
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 {
// no-op
}
@ -147,7 +153,7 @@ export async function nextSong(client, message, connection, track, info, music,
connection.removeAllListeners("end");
await connection.play(track);
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) => {
try {
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);
queues.delete(voiceChannel.guild.id);
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) => {
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);
if (newQueue.length !== 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 {
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();
@ -207,7 +213,7 @@ export async function nextSong(client, message, connection, track, info, music,
players.delete(voiceChannel.guild.id);
queues.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 {
if (playingMessage.channel.messages.has(playingMessage.id)) await playingMessage.delete();
if (player && player.playMessage.channel.messages.has(player.playMessage.id)) await player.playMessage.delete();