Upgrade to Discord.JS v13.2.0

Co-Authored-By: Dmytro Meleshko <dmytro.meleshko@gmail.com>
This commit is contained in:
Alyxia Sother 2021-10-29 14:52:46 +02:00
parent 36bc488757
commit fbb687d3d6
No known key found for this signature in database
GPG Key ID: 355968D14144B739
41 changed files with 1491 additions and 738 deletions

1075
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,20 +12,19 @@
"dev-instance": "rimraf dist && tsc && node . dev",
"test": "jest",
"format": "prettier --write **/*",
"postinstall": "node patch.js && husky install"
"postinstall": "husky install"
},
"dependencies": {
"canvas": "^2.7.0",
"chalk": "^4.1.0",
"discord.js": "github:discordjs/discord.js",
"discord.js-lavalink-lib": "^0.1.8",
"discord.js": "^13.2.0",
"figlet": "^1.5.0",
"glob": "^7.1.6",
"inquirer": "^7.3.3",
"mathjs": "^9.3.0",
"moment": "^2.29.1",
"ms": "^2.1.3",
"onion-lasers": "^1.2.0-unstable.0",
"onion-lasers": "npm:onion-lasers-v13@^2.0.0",
"pet-pet-gif": "^1.0.8",
"relevant-urban": "^2.0.0",
"translate-google": "^1.4.3",
@ -46,7 +45,7 @@
"rimraf": "^3.0.2",
"ts-jest": "^26.4.4",
"tsc-watch": "^4.2.9",
"typescript": "^4.2.4"
"typescript": "^4.4.4"
},
"optionalDependencies": {
"fsevents": "^2.1.2"

View File

@ -1,23 +0,0 @@
// This is a nightmarishly bad way to handle module patches... but oh well, it's on the unstable branch for a reason.
const fs = require("fs");
const DECLARATION_FILE = "node_modules/discord.js/typings/index.d.ts";
fs.readFile(DECLARATION_FILE, "utf-8", (err, data) => {
if (err) console.error(err);
else {
const declaration = data.split(/\r?\n/);
// "discord-api-types/v8" is apparently not found so just ignore it to get the typings to work.
for (let i = 0; i < declaration.length; i++) {
const line = declaration[i];
if (line.includes("@ts-ignore")) {
break;
} else if (line.includes("discord-api-types/v8")) {
declaration.splice(i, 0, "// @ts-ignore");
fs.writeFile(DECLARATION_FILE, declaration.join("\n"), () => {});
break;
}
}
}
});

View File

@ -1,5 +1,6 @@
import {NamedCommand, RestCommand} from "onion-lasers";
import figlet from "figlet";
import {Util} from "discord.js";
export default new NamedCommand({
description: "Generates a figlet of your input.",
@ -7,12 +8,11 @@ export default new NamedCommand({
any: new RestCommand({
async run({send, combined}) {
return send(
figlet.textSync(combined, {
horizontalLayout: "full"
}),
{
code: true
}
`\`\`\`\n${Util.cleanCodeBlockContent(
figlet.textSync(combined, {
horizontalLayout: "full"
})
)}\n\`\`\``
);
}
})

View File

@ -3,12 +3,12 @@ import {NamedCommand} from "onion-lasers";
export default new NamedCommand({
description: "Insult TravBot! >:D",
async run({send, channel, author}) {
channel.startTyping();
channel.sendTyping();
setTimeout(() => {
send(
`${author} What the fuck did you just fucking say about me, you little bitch? I'll have you know I graduated top of my class in the Navy Seals, and I've been involved in numerous secret raids on Al-Quaeda, and I have over 300 confirmed kills. I am trained in gorilla warfare and I'm the top sniper in the entire US armed forces. You are nothing to me but just another target. I will wipe you the fuck out with precision the likes of which has never been seen before on this Earth, mark my fucking words. You think you can get away with saying that shit to me over the Internet? Think again, fucker. As we speak I am contacting my secret network of spies across the USA and your IP is being traced right now so you better prepare for the storm, maggot. The storm that wipes out the pathetic little thing you call your life. You're fucking dead, kid. I can be anywhere, anytime, and I can kill you in over seven hundred ways, and that's just with my bare hands. Not only am I extensively trained in unarmed combat, but I have access to the entire arsenal of the United States Marine Corps and I will use it to its full extent to wipe your miserable ass off the face of the continent, you little shit. If only you could have known what unholy retribution your little "clever" comment was about to bring down upon you, maybe you would have held your fucking tongue. But you couldn't, you didn't, and now you're paying the price, you goddamn idiot. I will shit fury all over you and you will drown in it. You're fucking dead, kiddo.`
);
channel.stopTyping();
channel.sendTyping();
}, 60000);
}
});

View File

@ -39,9 +39,12 @@ export const BetCommand = new NamedCommand({
// handle invalid amount
if (amount <= 0) return send("You must bet at least one Mon!");
else if (sender.money < amount)
return send("You don't have enough Mons for that.", getMoneyEmbed(author));
return send({content: "You don't have enough Mons for that.", embeds: [getMoneyEmbed(author)]});
else if (receiver.money < amount)
return send("They don't have enough Mons for that.", getMoneyEmbed(target));
return send({
content: "They don't have enough Mons for that.",
embeds: [getMoneyEmbed(target)]
});
return send("How long until the bet ends?");
} else return;
@ -67,9 +70,15 @@ export const BetCommand = new NamedCommand({
// handle invalid amount
if (amount <= 0) return send("You must bet at least one Mon!");
else if (sender.money < amount)
return send("You don't have enough Mons for that.", getMoneyEmbed(author));
return send({
content: "You don't have enough Mons for that.",
embeds: [getMoneyEmbed(author)]
});
else if (receiver.money < amount)
return send("They don't have enough Mons for that.", getMoneyEmbed(target));
return send({
content: "They don't have enough Mons for that.",
embeds: [getMoneyEmbed(target)]
});
// handle invalid duration
if (duration <= 0) return send("Invalid bet duration");
@ -107,7 +116,7 @@ export const BetCommand = new NamedCommand({
);
// Wait for the duration of the bet.
return client.setTimeout(async () => {
return setTimeout(async () => {
// In debug mode, saving the storage will break the references, so you have to redeclare sender and receiver for it to actually save.
const sender = Storage.getUser(author.id);
const receiver = Storage.getUser(target.id);

View File

@ -16,27 +16,26 @@ export const DailyCommand = new NamedCommand({
user.lastReceived = now;
Storage.save();
send({
embed: {
title: "Daily Reward",
description: "You received 1 Mon!",
color: ECO_EMBED_COLOR,
fields: [
{
name: "New balance:",
value: pluralise(user.money, "Mon", "s")
}
]
}
embeds: [
{
title: "Daily Reward",
description: "You received 1 Mon!",
color: ECO_EMBED_COLOR
}
]
});
} else
send({
embed: {
title: "Daily Reward",
description: `It's too soon to pick up your daily Mons. Try again at <t:${Math.floor(
(user.lastReceived + 79200000) / 1000
)}:t>.`,
color: ECO_EMBED_COLOR
}
embeds: [
{
title: "Daily Reward",
description: `It's too soon to pick up your daily Mons. You have about ${(
(user.lastReceived + 79200000 - now) /
3600000
).toFixed(1)} hours to go.`,
color: ECO_EMBED_COLOR
}
]
});
}
}
@ -55,25 +54,27 @@ export const GuildCommand = new NamedCommand({
}
send({
embed: {
title: `The Bank of ${guild!.name}`,
color: ECO_EMBED_COLOR,
fields: [
{
name: "Accounts",
value: Object.keys(users).length,
inline: true
},
{
name: "Total Mons",
value: totalAmount,
inline: true
embeds: [
{
title: `The Bank of ${guild!.name}`,
color: ECO_EMBED_COLOR,
fields: [
{
name: "Accounts",
value: Object.keys(users).length.toString(),
inline: true
},
{
name: "Total Mons",
value: totalAmount.toString(),
inline: true
}
],
thumbnail: {
url: guild?.iconURL() ?? ""
}
],
thumbnail: {
url: guild?.iconURL() ?? ""
}
}
]
});
}
}
@ -100,14 +101,16 @@ export const LeaderboardCommand = new NamedCommand({
}
send({
embed: {
title: "Top 10 Richest Players",
color: ECO_EMBED_COLOR,
fields: fields,
thumbnail: {
url: guild?.iconURL() ?? ""
embeds: [
{
title: "Top 10 Richest Players",
color: ECO_EMBED_COLOR,
fields: fields,
thumbnail: {
url: guild?.iconURL() ?? ""
}
}
}
]
});
}
}
@ -130,7 +133,7 @@ export const PayCommand = new NamedCommand({
if (amount <= 0) return send("You must send at least one Mon!");
else if (sender.money < amount)
return send("You don't have enough Mons for that.", getMoneyEmbed(author));
return send({content: "You don't have enough Mons for that.", embeds: [getMoneyEmbed(author)]});
else if (target.id === author.id) return send("You can't send Mons to yourself!");
else if (target.bot && !IS_DEV_MODE) return send("You can't send Mons to a bot!");
@ -157,7 +160,7 @@ export const PayCommand = new NamedCommand({
if (amount <= 0) return send("You must send at least one Mon!");
else if (sender.money < amount)
return send("You don't have enough Mons to do that!", getMoneyEmbed(author));
return send({content: "You don't have enough Mons to do that!", embeds: [getMoneyEmbed(author)]});
else if (!guild)
return send("You have to use this in a server if you want to send Mons with a username!");
@ -168,17 +171,20 @@ export const PayCommand = new NamedCommand({
else if (user.bot && !IS_DEV_MODE) return send("You can't send Mons to a bot!");
const confirmed = await confirm(
await send(`Are you sure you want to send ${pluralise(amount, "Mon", "s")} to this person?`, {
embed: {
color: ECO_EMBED_COLOR,
author: {
name: user.tag,
icon_url: user.displayAvatarURL({
format: "png",
dynamic: true
})
await send({
content: `Are you sure you want to send ${pluralise(amount, "Mon", "s")} to this person?`,
embeds: [
{
color: ECO_EMBED_COLOR,
author: {
name: user.tag,
icon_url: user.displayAvatarURL({
format: "png",
dynamic: true
})
}
}
}
]
}),
author.id
);

View File

@ -21,7 +21,7 @@ export const MondayCommand = new NamedCommand({
user.money++;
user.lastMonday = now.getTime();
Storage.save();
send("It is **Mon**day, my dudes.", getMoneyEmbed(author));
send({content: "It is **Mon**day, my dudes.", embeds: [getMoneyEmbed(author)]});
} else send("You've already claimed your **Mon**day reward for this week.");
} else {
const weekdayName = WEEKDAY[weekday];
@ -47,7 +47,7 @@ export const AwardCommand = new NamedCommand({
const user = Storage.getUser(target.id);
user.money++;
Storage.save();
send(`1 Mon given to ${target.username}.`, getMoneyEmbed(target));
send({content: `1 Mon given to ${target.username}.`, embeds: [getMoneyEmbed(target)]});
} else {
send("This command is restricted to the bean.");
}
@ -62,7 +62,10 @@ export const AwardCommand = new NamedCommand({
const user = Storage.getUser(target.id);
user.money += amount;
Storage.save();
send(`${pluralise(amount, "Mon", "s")} given to ${target.username}.`, getMoneyEmbed(target));
send({
content: `${pluralise(amount, "Mon", "s")} given to ${target.username}.`,
embeds: [getMoneyEmbed(target)]
});
} else {
send("You need to enter a number greater than 0.");
}

View File

@ -52,7 +52,8 @@ export const ShopItems: ShopItem[] = [
description: "Buys what is technically a laser bridge.",
usage: "laser bridge",
run(message) {
message.channel.send(random(lines), {
message.channel.send({
content: random(lines),
files: [
{
attachment:

View File

@ -3,13 +3,13 @@ import {pluralise, split} from "../../../lib";
import {Storage, getPrefix} from "../../../structures";
import {isAuthorized, ECO_EMBED_COLOR} from "./eco-utils";
import {ShopItems, ShopItem} from "./eco-shop-items";
import {EmbedField} from "discord.js";
import {EmbedField, MessageEmbedOptions} from "discord.js";
export const ShopCommand = new NamedCommand({
description: "Displays the list of items you can buy in the shop.",
async run({send, guild, channel, author}) {
if (isAuthorized(guild, channel)) {
function getShopEmbed(selection: ShopItem[], title: string) {
function getShopEmbed(selection: ShopItem[], title: string): MessageEmbedOptions {
const fields: EmbedField[] = [];
for (const item of selection)
@ -20,13 +20,11 @@ export const ShopCommand = new NamedCommand({
});
return {
embed: {
color: ECO_EMBED_COLOR,
title: title,
fields: fields,
footer: {
text: "Mon Shop | TravBot Services"
}
color: ECO_EMBED_COLOR,
title: title,
fields: fields,
footer: {
text: "Mon Shop | TravBot Services"
}
};
}
@ -35,10 +33,14 @@ export const ShopCommand = new NamedCommand({
const pageAmount = shopPages.length;
paginate(send, author.id, pageAmount, (page, hasMultiplePages) => {
return getShopEmbed(
shopPages[page],
hasMultiplePages ? `Shop (Page ${page + 1} of ${pageAmount})` : "Shop"
);
return {
embeds: [
getShopEmbed(
shopPages[page],
hasMultiplePages ? `Shop (Page ${page + 1} of ${pageAmount})` : "Shop"
)
]
};
});
}
}

View File

@ -1,6 +1,6 @@
import {pluralise} from "../../../lib";
import {Storage} from "../../../structures";
import {User, Guild, TextChannel, DMChannel, NewsChannel} from "discord.js";
import {User, Guild, TextChannel, DMChannel, NewsChannel, Channel, TextBasedChannels} from "discord.js";
export const ECO_EMBED_COLOR = 0xf1c40f;
@ -61,18 +61,10 @@ export function getSendEmbed(sender: User, receiver: User, amount: number): obje
};
}
export function isAuthorized(guild: Guild | null, channel: TextChannel | DMChannel | NewsChannel): boolean {
if (IS_DEV_MODE) {
return true;
}
if (guild?.id !== "637512823676600330") {
channel.send("Sorry, this command can only be used in Monika's emote server.");
export function isAuthorized(guild: Guild | null, channel: TextBasedChannels): boolean {
if ((guild?.id === "637512823676600330" && channel?.id === "669464416420364288") || IS_DEV_MODE) return true;
else {
channel.send("Sorry, this command can only be used in Monika's emote server. (#mon-stocks)");
return false;
} else if (channel?.id !== "669464416420364288") {
channel.send("Sorry, this command can only be used in <#669464416420364288>.");
return false;
} else {
return true;
}
}

View File

@ -10,11 +10,11 @@ export default new NamedCommand({
const attachment = message.attachments.first()!;
const gif = await petPetGif(attachment.url);
const file = new MessageAttachment(gif, "pat.gif");
send(file);
send({attachments: [file]});
} else {
const gif = await petPetGif(author.displayAvatarURL({format: "png"}));
const file = new MessageAttachment(gif, "pat.gif");
send(file);
send({attachments: [file]});
}
},
id: "user",
@ -24,7 +24,7 @@ export default new NamedCommand({
const user: User = args[0];
const gif = await petPetGif(user.displayAvatarURL({format: "png"}));
const file = new MessageAttachment(gif, "pat.gif");
send(file);
send({attachments: [file]});
}
}),
any: new RestCommand({
@ -36,7 +36,7 @@ export default new NamedCommand({
else {
const gif = await petPetGif(user.displayAvatarURL({format: "png"}));
const file = new MessageAttachment(gif, "pat.gif");
send(file);
send({attachments: [file]});
}
}
})

View File

@ -33,26 +33,35 @@ async function execPoll(send: SendFunction, message: Message, user: User, questi
dynamic: true,
size: 2048
}) || user.defaultAvatarURL;
const msg = await send(
new MessageEmbed()
.setAuthor(`Poll created by ${message.author.username}`, icon)
.setColor(0xffffff)
.setFooter("React to vote.")
.setDescription(question)
);
const msg = await send({
embeds: [
new MessageEmbed()
.setAuthor(`Poll created by ${message.author.username}`, icon)
.setColor(0xffffff)
.setFooter("React to vote.")
.setDescription(question)
]
});
const results = await poll(msg, [AGREE, DISAGREE], duration);
send(
new MessageEmbed()
.setAuthor(`The results of ${message.author.username}'s poll:`, icon)
.setTitle(question)
.setDescription(
`${AGREE} ${pluralise(
results[AGREE],
"",
"people who agree",
"person who agrees"
)}\n${DISAGREE} ${pluralise(results[DISAGREE], "", "people who disagree", "person who disagrees")}`
)
);
send({
embeds: [
new MessageEmbed()
.setAuthor(`The results of ${message.author.username}'s poll:`, icon)
.setTitle(question)
.setDescription(
`${AGREE} ${pluralise(
results[AGREE],
"",
"people who agree",
"person who agrees"
)}\n${DISAGREE} ${pluralise(
results[DISAGREE],
"",
"people who disagree",
"person who disagrees"
)}`
)
]
});
msg.delete();
}

View File

@ -6,15 +6,17 @@ export default new NamedCommand({
usage: "[number from 1 to 9]",
async run({send}) {
send({
embed: {
title: "Ravioli ravioli...",
image: {
url: `https://raw.githubusercontent.com/keanuplayz/TravBot/master/assets/ravi${Random.int(
1,
10
)}.png`
embeds: [
{
title: "Ravioli ravioli...",
image: {
url: `https://raw.githubusercontent.com/keanuplayz/TravBot/master/assets/ravi${Random.int(
1,
10
)}.png`
}
}
}
]
});
},
number: new Command({
@ -23,12 +25,14 @@ export default new NamedCommand({
if (arg >= 1 && arg <= 9) {
send({
embed: {
title: "Ravioli ravioli...",
image: {
url: `https://raw.githubusercontent.com/keanuplayz/TravBot/master/assets/ravi${arg}.png`
embeds: [
{
title: "Ravioli ravioli...",
image: {
url: `https://raw.githubusercontent.com/keanuplayz/TravBot/master/assets/ravi${arg}.png`
}
}
}
]
});
} else {
send("Please provide a number between 1 and 9.");

View File

@ -36,25 +36,25 @@ export default new NamedCommand({
usage: "([text])",
async run({send, author}) {
const msg = await send(transform(phrase));
msg.createReactionCollector(
(reaction, user) => {
msg.createReactionCollector({
filter: (reaction, user) => {
if (user.id === author.id && reaction.emoji.name === "❌") msg.delete();
return false;
},
{time: 60000}
);
time: 60000
});
},
any: new RestCommand({
async run({send, author, combined}) {
phrase = combined;
const msg = await send(transform(phrase));
msg.createReactionCollector(
(reaction, user) => {
msg.createReactionCollector({
filter: (reaction, user) => {
if (user.id === author.id && reaction.emoji.name === "❌") msg.delete();
return false;
},
{time: 60000}
);
time: 60000
});
}
})
});

View File

@ -21,7 +21,7 @@ export default new NamedCommand({
if (res.tags && res.tags.length > 0 && res.tags.join(" ").length < 1024)
embed.addField("Tags", res.tags.join(", "), true);
send(embed);
send({embeds: [embed]});
})
.catch(() => {
send("Sorry, that word was not found.");

View File

@ -29,7 +29,7 @@ export default new NamedCommand({
.addField("Winds", current.winddisplay, true)
.addField("Humidity", `${current.humidity}%`, true);
return send({
embed
embeds: [embed]
});
}
);

View File

@ -57,7 +57,7 @@ export default new NamedCommand({
const id = author.id;
if (id in registry) {
send(`${author} ${registry[id]}`, {allowedMentions: {parse: []}});
send({content: `${author} ${registry[id]}`, allowedMentions: {parse: []}});
} else {
send("You haven't been added to the registry yet!");
}
@ -69,9 +69,9 @@ export default new NamedCommand({
const id = user.id;
if (id in registry) {
send(`${user} ${registry[id]}`, {allowedMentions: {parse: []}});
send({content: `${user} ${registry[id]}`, allowedMentions: {parse: []}});
} else {
send(`${user} hasn't been added to the registry yet!`, {allowedMentions: {parse: []}});
send({content: `${user} hasn't been added to the registry yet!`, allowedMentions: {parse: []}});
}
}
}),
@ -81,9 +81,9 @@ export default new NamedCommand({
if (typeof user !== "string") {
if (user.id in registry) {
send(`${user} ${registry[user.id]}`, {allowedMentions: {parse: []}});
send({content: `${user} ${registry[user.id]}`, allowedMentions: {parse: []}});
} else {
send(`${user} hasn't been added to the registry yet!`, {allowedMentions: {parse: []}});
send({content: `${user} hasn't been added to the registry yet!`, allowedMentions: {parse: []}});
}
} else {
send(user);

View File

@ -1,9 +1,7 @@
import {Command, NamedCommand, getPermissionLevel, getPermissionName, CHANNEL_TYPE, RestCommand} from "onion-lasers";
import {clean} from "../../lib";
import {Config, Storage} from "../../structures";
import {Permissions, TextChannel, User, Role, Channel} from "discord.js";
import {Permissions, TextChannel, User, Role, Channel, Util} from "discord.js";
import {logs} from "../../modules/globals";
import {inspect} from "util";
function getLogBuffer(type: string) {
return {
@ -372,28 +370,34 @@ export default new NamedCommand({
}
})
}),
eval: new NamedCommand({
description: "Evaluate code.",
usage: "<code>",
permission: PERMISSIONS.BOT_OWNER,
run: "You have to enter some code to execute first.",
any: new RestCommand({
// You have to bring everything into scope to use them. AFAIK, there isn't a more maintainable way to do this, but at least TS will let you know if anything gets removed.
async run({send, message, channel, guild, author, member, client, args, combined}) {
try {
let evaled: unknown = eval(combined);
// If promises like message.channel.send() are invoked, await them so unnecessary error reports don't leak into the command handler.
// Also, it's more useful to see the value rather than Promise { <pending> }.
if (evaled instanceof Promise) evaled = await evaled;
if (typeof evaled !== "string") evaled = inspect(evaled);
// Also await this send call so that if the message is empty, it doesn't leak into the command handler.
await send(clean(evaled), {code: "js", split: true});
} catch (err) {
send(clean(err), {code: "js", split: true});
}
}
})
}),
// TODO: Reimplement this entire command, for `send` doesn't allow
// types like `unknown` to be sent anymore. Perhaps try to echo
// whatever `evaled` is into an empty buffer and send this.
// (see: `Buffer.alloc(...)`) This is unlikely to work though, since
// `Buffer.alloc(...)` requires a length, which we can't retrieve from
// an `unknown` variable.
// eval: new NamedCommand({
// description: "Evaluate code.",
// usage: "<code>",
// permission: PERMISSIONS.BOT_OWNER,
// run: "You have to enter some code to execute first.",
// any: new RestCommand({
// // You have to bring everything into scope to use them. AFAIK, there isn't a more maintainable way to do this, but at least TS will let you know if anything gets removed.
// async run({send, message, channel, guild, author, member, client, args, combined}) {
// try {
// let evaled: unknown = eval(combined);
// // If promises like message.channel.send() are invoked, await them so unnecessary error reports don't leak into the command handler.
// // Also, it's more useful to see the value rather than Promise { <pending> }.
// if (evaled instanceof Promise) evaled = await evaled;
// if (typeof evaled !== "string") evaled = inspect(evaled);
// // Also await this send call so that if the message is empty, it doesn't leak into the command handler.
// await send(clean(evaled), {code: "js", split: true});
// } catch (err) {
// send(clean(err), {code: "js", split: true});
// }
// }
// })
// }),
nick: new NamedCommand({
description: "Change the bot's nickname.",
permission: PERMISSIONS.BOT_SUPPORT,
@ -410,8 +414,12 @@ export default new NamedCommand({
description: "Shows a list of all guilds the bot is a member of.",
permission: PERMISSIONS.BOT_SUPPORT,
async run({send, client}) {
const guildList = client.guilds.cache.array().map((e) => e.name);
send(guildList, {split: true});
const guildList = Util.splitMessage(
Array.from(client.guilds.cache.map((e) => e.name).values()).join("\n")
);
for (let guildListPart of guildList) {
send(guildListPart);
}
}
}),
activity: new NamedCommand({

View File

@ -22,7 +22,7 @@ export default new NamedCommand({
const helpMenuPages: [string, string][] = []; // An array of (category, description) tuples.
// Prevent the description of one category from overflowing by splitting it into multiple pages if needed.
for (const category of commands.keyArray()) {
for (const category of commands.keys()) {
const commandList = commands.get(category)!;
let output = LEGEND;
@ -45,10 +45,16 @@ export default new NamedCommand({
paginate(send, author.id, helpMenuPages.length, (page, hasMultiplePages) => {
const [category, output] = helpMenuPages[page];
return new MessageEmbed()
.setTitle(hasMultiplePages ? `${category} (Page ${page + 1} of ${helpMenuPages.length})` : category)
.setDescription(output)
.setColor(EMBED_COLOR);
return {
embeds: [
new MessageEmbed()
.setTitle(
hasMultiplePages ? `${category} (Page ${page + 1} of ${helpMenuPages.length})` : category
)
.setDescription(output)
.setColor(EMBED_COLOR)
]
};
});
},
any: new RestCommand({
@ -87,43 +93,45 @@ export default new NamedCommand({
aliases = formattedAliases.join(", ") || "None";
}
return send(
new MessageEmbed()
.setTitle(header)
.setDescription(command.description)
.setColor(EMBED_COLOR)
.addFields(
{
name: "Aliases",
value: aliases,
inline: true
},
{
name: "Category",
value: category,
inline: true
},
{
name: "Permission Required",
value: `\`${getPermissionName(result.permission)}\` (Level ${result.permission})`,
inline: true
},
{
name: "Channel Type",
value: getChannelTypeName(result.channelType),
inline: true
},
{
name: "NSFW Only?",
value: result.nsfw ? "Yes" : "No",
inline: true
},
{
name: "Usages",
value: append
}
)
);
return send({
embeds: [
new MessageEmbed()
.setTitle(header)
.setDescription(command.description)
.setColor(EMBED_COLOR)
.addFields(
{
name: "Aliases",
value: aliases,
inline: true
},
{
name: "Category",
value: category,
inline: true
},
{
name: "Permission Required",
value: `\`${getPermissionName(result.permission)}\` (Level ${result.permission})`,
inline: true
},
{
name: "Channel Type",
value: getChannelTypeName(result.channelType),
inline: true
},
{
name: "NSFW Only?",
value: result.nsfw ? "Yes" : "No",
inline: true
},
{
name: "Usages",
value: append
}
)
]
});
}
})
});

View File

@ -18,7 +18,7 @@ export default new NamedCommand({
.setTitle("Math Calculation")
.addField("Input", `\`\`\`js\n${combined}\`\`\``)
.addField("Output", `\`\`\`js\n${resp}\`\`\``);
return send(embed);
return send({embeds: [embed]});
}
})
});

View File

@ -11,16 +11,16 @@ export default new NamedCommand({
var queryString = args[0];
let url = new URL(`https://djsdocs.sorta.moe/v2/embed?src=master&q=${queryString}`);
const content = await getContent(url.toString());
const msg = await send({embed: content});
const msg = await send({embeds: [content]});
const react = await msg.react("❌");
const collector = msg.createReactionCollector(
(reaction, user) => {
const collector = msg.createReactionCollector({
filter: (reaction, user) => {
if (user.id === author.id && reaction.emoji.name === "❌") msg.delete();
return false;
},
{time: 60000}
);
time: 60000
});
collector.on("end", () => {
if (!msg.deleted) react.users.remove(msg.author);

View File

@ -1,15 +1,15 @@
import {MessageEmbed, version as djsversion, Guild, User, GuildMember} from "discord.js";
import {MessageEmbed, version as djsversion, Guild, User, GuildMember, TextChannel, VoiceChannel} from "discord.js";
import ms from "ms";
import os from "os";
import {Command, NamedCommand, getUserByNickname, CHANNEL_TYPE, getGuildByName, RestCommand} from "onion-lasers";
import {formatBytes, trimArray} from "../../lib";
import {verificationLevels, filterLevels, regions} from "../../defs/info";
import {verificationLevels, filterLevels} from "../../defs/info";
import moment, {utc} from "moment";
export default new NamedCommand({
description: "Command to provide all sorts of info about the current server, a user, etc.",
async run({send, author, member}) {
send(await getUserInfo(author, member));
send({embeds: [await getUserInfo(author, member)]});
},
subcommands: {
avatar: new NamedCommand({
@ -55,39 +55,47 @@ export default new NamedCommand({
const core = os.cpus()[0];
const embed = new MessageEmbed()
.setColor(guild?.me?.displayHexColor || "BLUE")
.addField("General", [
`** Client:** ${client.user?.tag} (${client.user?.id})`,
`** Servers:** ${client.guilds.cache.size.toLocaleString()}`,
`** Users:** ${client.guilds.cache
.reduce((a: any, b: {memberCount: any}) => a + b.memberCount, 0)
.toLocaleString()}`,
`** Channels:** ${client.channels.cache.size.toLocaleString()}`,
`** Creation Date:** ${utc(client.user?.createdTimestamp).format("Do MMMM YYYY HH:mm:ss")}`,
`** Node.JS:** ${process.version}`,
`** Version:** v${BOT_VERSION}`,
`** Discord.JS:** v${djsversion}`,
"\u200b"
])
.addField("System", [
`** Platform:** ${process.platform}`,
`** Uptime:** ${ms(os.uptime() * 1000, {
long: true
})}`,
`** CPU:**`,
`\u3000 • Cores: ${os.cpus().length}`,
`\u3000 • Model: ${core.model}`,
`\u3000 • Speed: ${core.speed}MHz`,
`** Memory:**`,
`\u3000 • Total: ${formatBytes(process.memoryUsage().heapTotal)}`,
`\u3000 • Used: ${formatBytes(process.memoryUsage().heapUsed)}`
])
.addField(
"General",
[
`** Client:** ${client.user?.tag} (${client.user?.id})`,
`** Servers:** ${client.guilds.cache.size.toLocaleString()}`,
`** Users:** ${client.guilds.cache
.reduce((a: any, b: {memberCount: any}) => a + b.memberCount, 0)
.toLocaleString()}`,
`** Channels:** ${client.channels.cache.size.toLocaleString()}`,
`** Creation Date:** ${utc(client.user?.createdTimestamp).format(
"Do MMMM YYYY HH:mm:ss"
)}`,
`** Node.JS:** ${process.version}`,
`** Version:** v${process.env.npm_package_version}`,
`** Discord.JS:** v${djsversion}`,
"\u200b"
].join("\n")
)
.addField(
"System",
[
`** Platform:** ${process.platform}`,
`** Uptime:** ${ms(os.uptime() * 1000, {
long: true
})}`,
`** CPU:**`,
`\u3000 • Cores: ${os.cpus().length}`,
`\u3000 • Model: ${core.model}`,
`\u3000 • Speed: ${core.speed}MHz`,
`** Memory:**`,
`\u3000 • Total: ${formatBytes(process.memoryUsage().heapTotal)}`,
`\u3000 • Used: ${formatBytes(process.memoryUsage().heapUsed)}`
].join("\n")
)
.setTimestamp();
const avatarURL = client.user?.displayAvatarURL({
dynamic: true,
size: 2048
});
if (avatarURL) embed.setThumbnail(avatarURL);
send(embed);
send({embeds: [embed]});
}
}),
guild: new NamedCommand({
@ -95,14 +103,14 @@ export default new NamedCommand({
usage: "(<guild name>/<guild ID>)",
channelType: CHANNEL_TYPE.GUILD,
async run({send, guild}) {
send(await getGuildInfo(guild!, guild));
send({embeds: [await getGuildInfo(guild!, guild)]});
},
id: "guild",
guild: new Command({
description: "Display info about a guild by its ID.",
async run({send, guild, args}) {
const targetGuild = args[0] as Guild;
send(await getGuildInfo(targetGuild, guild));
send({embeds: [await getGuildInfo(targetGuild, guild)]});
}
}),
any: new RestCommand({
@ -111,7 +119,7 @@ export default new NamedCommand({
const targetGuild = getGuildByName(combined);
if (typeof targetGuild !== "string") {
send(await getGuildInfo(targetGuild, guild));
send({embeds: [await getGuildInfo(targetGuild, guild)]});
} else {
send(targetGuild);
}
@ -126,7 +134,7 @@ export default new NamedCommand({
const user = args[0] as User;
// Transforms the User object into a GuildMember object of the current guild.
const member = guild?.members.resolve(user);
send(await getUserInfo(user, member));
send({embeds: [await getUserInfo(user, member)]});
}
}),
any: new RestCommand({
@ -135,7 +143,7 @@ export default new NamedCommand({
const user = await getUserByNickname(combined, guild);
// Transforms the User object into a GuildMember object of the current guild.
const member = guild?.members.resolve(user);
if (typeof user !== "string") send(await getUserInfo(user, member));
if (typeof user !== "string") send({embeds: [await getUserInfo(user, member)]});
else send(user);
}
})
@ -147,20 +155,21 @@ async function getUserInfo(user: User, member: GuildMember | null | undefined):
const embed = new MessageEmbed()
.setThumbnail(user.displayAvatarURL({dynamic: true, size: 512}))
.setColor("BLUE")
.addField("User", [
`** Username:** ${user.username}`,
`** Discriminator:** ${user.discriminator}`,
`** ID:** ${user.id}`,
`** Flags:** ${userFlags.length ? userFlags.join(", ") : "None"}`,
`** Avatar:** [Link to avatar](${user.displayAvatarURL({
dynamic: true
})})`,
`** Time Created:** ${moment(user.createdTimestamp).format("LT")} ${moment(user.createdTimestamp).format(
"LL"
)} ${moment(user.createdTimestamp).fromNow()}`,
`** Status:** ${user.presence.status}`,
`** Game:** ${user.presence.activities || "Not playing a game."}`
]);
.addField(
"User",
[
`** Username:** ${user.username}`,
`** Discriminator:** ${user.discriminator}`,
`** ID:** ${user.id}`,
`** Flags:** ${userFlags.length ? userFlags.join(", ") : "None"}`,
`** Avatar:** [Link to avatar](${user.displayAvatarURL({
dynamic: true
})})`,
`** Time Created:** ${moment(user.createdTimestamp).format("LT")} ${moment(
user.createdTimestamp
).format("LL")} ${moment(user.createdTimestamp).fromNow()}`
].join("\n")
);
if (member) {
const roles = member.roles.cache
@ -170,16 +179,21 @@ async function getUserInfo(user: User, member: GuildMember | null | undefined):
embed
.setColor(member.displayHexColor)
.addField("Member", [
`** Highest Role:** ${
member.roles.highest.id === member.guild.id ? "None" : member.roles.highest.name
}`,
`** Server Join Date:** ${moment(member.joinedAt).format("LL LTS")}`,
`** Hoist Role:** ${member.roles.hoist ? member.roles.hoist.name : "None"}`,
`** Roles:** [${roles.length}]: ${
roles.length == 0 ? "None" : roles.length <= 10 ? roles.join(", ") : trimArray(roles).join(", ")
}`
]);
.addField(
"Member",
[
`** Status:** ${member.presence?.status}`,
`** Game:** ${member.presence?.activities ?? "Not playing a game."}`,
`** Highest Role:** ${
member.roles.highest.id === member.guild.id ? "None" : member.roles.highest.name
}`,
`** Server Join Date:** ${moment(member.joinedAt).format("LL LTS")}`,
`** Hoist Role:** ${member.roles.hoist ? member.roles.hoist.name : "None"}`,
`** Roles:** [${roles.length}]: ${
roles.length == 0 ? "None" : roles.length <= 10 ? roles.join(", ") : trimArray(roles).join(", ")
}`
].join("\n")
);
}
return embed;
@ -199,39 +213,47 @@ async function getGuildInfo(guild: Guild, currentGuild: Guild | null) {
const owner = await guild.fetchOwner();
embed
.addField("General", [
`** Name:** ${guild.name}`,
`** ID:** ${guild.id}`,
`** Owner:** ${owner.user.tag} (${guild.ownerID})`,
`** Region:** ${regions[guild.region]}`,
`** Boost Tier:** ${guild.premiumTier ? `Tier ${guild.premiumTier}` : "None"}`,
`** Explicit Filter:** ${filterLevels[guild.explicitContentFilter]}`,
`** Verification Level:** ${verificationLevels[guild.verificationLevel]}`,
`** Time Created:** ${moment(guild.createdTimestamp).format("LT")} ${moment(guild.createdTimestamp).format(
"LL"
)} ${moment(guild.createdTimestamp).fromNow()}`,
"\u200b"
])
.addField("Statistics", [
`** Role Count:** ${roles.length}`,
`** Emoji Count:** ${emojis.size}`,
`** Regular Emoji Count:** ${emojis.filter((emoji) => !emoji.animated).size}`,
`** Animated Emoji Count:** ${emojis.filter((emoji) => emoji.animated).size}`,
`** Member Count:** ${guild.memberCount}`,
`** Humans:** ${members.filter((member) => !member.user.bot).size}`,
`** Bots:** ${members.filter((member) => member.user.bot).size}`,
`** Text Channels:** ${channels.filter((channel) => channel.type === "text").size}`,
`** Voice Channels:** ${channels.filter((channel) => channel.type === "voice").size}`,
`** Boost Count:** ${guild.premiumSubscriptionCount || "0"}`,
`\u200b`
])
.addField("Presence", [
`** Online:** ${members.filter((member) => member.presence.status === "online").size}`,
`** Idle:** ${members.filter((member) => member.presence.status === "idle").size}`,
`** Do Not Disturb:** ${members.filter((member) => member.presence.status === "dnd").size}`,
`** Offline:** ${members.filter((member) => member.presence.status === "offline").size}`,
displayRoles ? "\u200b" : ""
])
.addField(
"General",
[
`** Name:** ${guild.name}`,
`** ID:** ${guild.id}`,
`** Owner:** ${owner.user.tag} (${guild.ownerId})`,
`** Boost Tier:** ${guild.premiumTier ? `Tier ${guild.premiumTier}` : "None"}`,
`** Explicit Filter:** ${filterLevels[guild.explicitContentFilter]}`,
`** Verification Level:** ${verificationLevels[guild.verificationLevel]}`,
`** Time Created:** ${moment(guild.createdTimestamp).format("LT")} ${moment(
guild.createdTimestamp
).format("LL")} ${moment(guild.createdTimestamp).fromNow()}`,
"\u200b"
].join("\n")
)
.addField(
"Statistics",
[
`** Role Count:** ${roles.length}`,
`** Emoji Count:** ${emojis.size}`,
`** Regular Emoji Count:** ${emojis.filter((emoji) => !emoji.animated).size}`,
`** Animated Emoji Count:** ${emojis.filter((emoji) => !!emoji.animated).size}`,
`** Member Count:** ${guild.memberCount}`,
`** Humans:** ${members.filter((member) => !member.user.bot).size}`,
`** Bots:** ${members.filter((member) => member.user.bot).size}`,
`** Text Channels:** ${channels.filter((channel) => channel instanceof TextChannel).size}`,
`** Voice Channels:** ${channels.filter((channel) => channel instanceof VoiceChannel).size}`,
`** Boost Count:** ${guild.premiumSubscriptionCount || "0"}`,
`\u200b`
].join("\n")
)
.addField(
"Presence",
[
`** Online:** ${members.filter((member) => member.presence?.status === "online").size}`,
`** Idle:** ${members.filter((member) => member.presence?.status === "idle").size}`,
`** Do Not Disturb:** ${members.filter((member) => member.presence?.status === "dnd").size}`,
`** Offline:** ${members.filter((member) => member.presence?.status === "offline").size}`,
displayRoles ? "\u200b" : ""
].join("\n")
)
.setTimestamp();
if (iconURL) embed.setThumbnail(iconURL);

View File

@ -9,7 +9,7 @@ export default new NamedCommand({
description: "Lists all emotes the bot has in it's registry,",
usage: "<regex pattern> (-flags)",
async run({send, author, client}) {
displayEmoteList(client.emojis.cache.array(), send, author);
displayEmoteList(Array.from(client.emojis.cache.values()), send, author);
},
any: new RestCommand({
description:
@ -20,7 +20,7 @@ export default new NamedCommand({
const guildID: string = args[0];
displayEmoteList(
client.emojis.cache.filter((emote) => emote.guild.id === guildID).array(),
Array.from(client.emojis.cache.filter((emote) => emote.guild.id === guildID).values()),
send,
author
);
@ -32,7 +32,7 @@ export default new NamedCommand({
flags = args.pop().substring(1);
}
let emoteCollection = client.emojis.cache.array();
let emoteCollection = Array.from(client.emojis.cache.values());
// Creates a sandbox to stop a regular expression if it takes too much time to search.
// To avoid passing in a giant data structure, I'll just pass in the structure {[id: string]: [name: string]}.
let emotes = new Map<string, string>();
@ -61,12 +61,18 @@ export default new NamedCommand({
emoteCollection = emoteCollection.filter((emote) => emotes.has(emote.id)); // Only allow emotes that haven't been deleted.
displayEmoteList(emoteCollection, send, author);
} catch (error) {
if (error.code === "ERR_SCRIPT_EXECUTION_TIMEOUT") {
// FIXME: `error` is of type `unknown` here.
// Also: <https://stackoverflow.com/questions/40141005/property-code-does-not-exist-on-type-error>
let errorName = "???";
if (error instanceof Error) {
errorName = error.name;
}
if (errorName === "ERR_SCRIPT_EXECUTION_TIMEOUT") {
send(
`The regular expression you entered exceeded the time limit of ${REGEX_TIMEOUT_MS} milliseconds.`
);
} else {
throw new Error(error);
throw new Error(errorName);
}
}
} else {
@ -102,7 +108,7 @@ async function displayEmoteList(emotes: GuildEmoji[], send: SendFunction, author
}
embed.setDescription(desc);
return embed;
return {embeds: [embed]};
});
} else {
send("No valid emotes found by that query.");

View File

@ -1,5 +1,5 @@
import {NamedCommand, RestCommand} from "onion-lasers";
import {Message, Channel, TextChannel} from "discord.js";
import {Message, Channel, TextChannel, TextBasedChannels} from "discord.js";
import {processEmoteQuery} from "./modules/emote-utils";
export default new NamedCommand({
@ -15,7 +15,7 @@ export default new NamedCommand({
if (message.reference) {
// If the command message is a reply to another message, use that as the react target.
target = await channel.messages.fetch(message.reference.messageID!);
target = await channel.messages.fetch(message.reference.messageId!);
}
// handles reacts by message id/distance
else if (args.length >= 2) {
@ -29,7 +29,7 @@ export default new NamedCommand({
const guildID = match[1];
const channelID = match[2];
const messageID = match[3];
let tmpChannel: Channel | undefined = channel;
let tmpChannel: TextBasedChannels | undefined = channel;
if (guild?.id !== guildID) {
try {
@ -39,12 +39,13 @@ export default new NamedCommand({
}
}
if (tmpChannel.id !== channelID) tmpChannel = guild.channels.cache.get(channelID);
if (tmpChannel?.id !== channelID)
tmpChannel = guild.channels.cache.get(channelID) as TextBasedChannels;
if (!tmpChannel) return send(`\`${channelID}\` is an invalid channel ID!`);
if (message.id !== messageID) {
try {
target = await (tmpChannel as TextChannel).messages.fetch(messageID);
target = await tmpChannel.messages.fetch(messageID);
} catch {
return send(`\`${messageID}\` is an invalid message ID!`);
}
@ -57,14 +58,15 @@ export default new NamedCommand({
const match = copyIDPattern.exec(last)!;
const channelID = match[1];
const messageID = match[2];
let tmpChannel: Channel | undefined = channel;
let tmpChannel: TextBasedChannels | undefined = channel;
if (tmpChannel.id !== channelID) tmpChannel = guild?.channels.cache.get(channelID);
if (tmpChannel?.id !== channelID)
tmpChannel = guild?.channels.cache.get(channelID) as TextBasedChannels;
if (!tmpChannel) return send(`\`${channelID}\` is an invalid channel ID!`);
if (message.id !== messageID) {
try {
target = await (tmpChannel as TextChannel).messages.fetch(messageID);
target = await tmpChannel.messages.fetch(messageID);
} catch {
return send(`\`${messageID}\` is an invalid message ID!`);
}

View File

@ -29,7 +29,8 @@ export default new NamedCommand({
const resolvedMessage = resolveMessageWithEmotes(combined);
if (resolvedMessage)
webhook.send(resolvedMessage, {
webhook.send({
content: resolvedMessage,
username: member!.nickname ?? author.username,
// Webhooks cannot have animated avatars, so requesting the animated version is a moot point.
avatarURL:
@ -38,12 +39,13 @@ export default new NamedCommand({
}) || author.defaultAvatarURL,
allowedMentions: {parse: []}, // avoids double pings
// "embeds" will not be included because it messes with the default ones that generate
files: message.attachments.array()
files: Array.from(message.attachments.values())
});
else send("Cannot send an empty message.");
} else {
const resolvedMessage = resolveMessageWithEmotes(combined);
if (resolvedMessage) send(`*${author} says:*\n${resolvedMessage}`, {allowedMentions: {parse: []}});
if (resolvedMessage)
send({content: `*${author} says:*\n${resolvedMessage}`, allowedMentions: {parse: []}});
else send("Cannot send an empty message.");
}

View File

@ -1,7 +1,7 @@
import {NamedCommand, CHANNEL_TYPE} from "onion-lasers";
import {pluralise} from "../../lib";
import moment from "moment";
import {Collection, TextChannel} from "discord.js";
import {Collection, TextChannel, Util} from "discord.js";
const lastUsedTimestamps = new Collection<string, number>();
@ -33,7 +33,7 @@ export default new NamedCommand({
let totalUserEmoteUsage = 0;
// IMPORTANT: You MUST check if the bot actually has access to the channel in the first place. It will get the list of all channels, but that doesn't mean it has access to every channel. Without this, it'll require admin access and throw an annoying unhelpful DiscordAPIError: Missing Access otherwise.
const allTextChannelsInCurrentGuild = guild!.channels.cache.filter(
(channel) => channel.type === "text" && channel.viewable
(channel) => channel instanceof TextChannel && channel.viewable
) as Collection<string, TextChannel>;
let messagesSearched = 0;
let channelsSearched = 0;
@ -41,7 +41,7 @@ export default new NamedCommand({
const totalChannels = allTextChannelsInCurrentGuild.size;
const statusMessage = await send("Gathering emotes...");
let warnings = 0;
channel.startTyping();
channel.sendTyping();
// Initialize the emote stats object with every emote in the current guild.
// The goal here is to cut the need to access guild.emojis.get() which'll make it faster and easier to work with.
@ -63,7 +63,7 @@ export default new NamedCommand({
for (const channel of allTextChannelsInCurrentGuild.values()) {
currentChannelName = channel.name;
let selected = channel.lastMessageID ?? message.id;
let selected = channel.lastMessageId ?? message.id;
let continueLoop = true;
while (continueLoop) {
@ -161,7 +161,6 @@ export default new NamedCommand({
)}.`
);
console.log("[scanemotes]", `Finished operation in ${finishTime - startTime} ms.`);
channel.stopTyping();
// Display stats on emote usage.
// This can work outside the loop now that it's synchronous, and now it's clearer what code is meant to execute at the end.
@ -180,7 +179,10 @@ export default new NamedCommand({
);
}
return await send(lines, {split: true});
let emoteList = Util.splitMessage(lines.join("\n"));
for (let emoteListPart of emoteList) {
return await send(emoteListPart);
}
},
subcommands: {
forcereset: new NamedCommand({

View File

@ -34,11 +34,14 @@ export default new NamedCommand({
const stream = streamList.get(userID)!;
stream.description = combined;
stream.update();
send("Successfully set the stream description to:", {
embed: {
description: stream.description,
color: member!.displayColor
}
send({
content: "Successfully set the stream description to:",
embeds: [
{
description: stream.description,
color: member!.displayColor
}
]
});
} else {
send("You can only use this command when streaming.");
@ -70,12 +73,15 @@ export default new NamedCommand({
const stream = streamList.get(userID)!;
stream.thumbnail = combined;
stream.update();
send(`Successfully set the stream thumbnail to: ${combined}`, {
embed: {
description: stream.description,
thumbnail: {url: combined},
color: member!.displayColor
}
send({
content: `Successfully set the stream thumbnail to: ${combined}`,
embeds: [
{
description: stream.description,
thumbnail: {url: combined},
color: member!.displayColor
}
]
});
} else {
send("You can only use this command when streaming.");

View File

@ -124,42 +124,40 @@ function getTimeEmbed(user: User) {
}
const embed = {
embed: {
color: TIME_EMBED_COLOR,
author: {
name: user.username,
icon_url: user.displayAvatarURL({
format: "png",
dynamic: true
})
color: TIME_EMBED_COLOR,
author: {
name: user.username,
icon_url: user.displayAvatarURL({
format: "png",
dynamic: true
})
},
fields: [
{
name: "Local Date",
value: localDate
},
fields: [
{
name: "Local Date",
value: localDate
},
{
name: "Day of the Week",
value: dayOfWeek
},
{
name: "Local Time",
value: localTime
},
{
name: daylightSavingsRegion !== null ? "Current Timezone Offset" : "Timezone Offset",
value: timezoneOffset
},
{
name: "Observes Daylight Savings?",
value: daylightSavingsRegion ? "Yes" : "No"
}
]
}
{
name: "Day of the Week",
value: dayOfWeek
},
{
name: "Local Time",
value: localTime
},
{
name: daylightSavingsRegion !== null ? "Current Timezone Offset" : "Timezone Offset",
value: timezoneOffset
},
{
name: "Observes Daylight Savings?",
value: daylightSavingsRegion ? "Yes" : "No"
}
]
};
if (daylightSavingsRegion) {
embed.embed.fields.push(
embed.fields.push(
{
name: "Daylight Savings Active?",
value: hasDaylightSavings(daylightSavingsRegion) ? "Yes" : "No"
@ -178,7 +176,7 @@ export default new NamedCommand({
description: "Show others what time it is for you.",
aliases: ["tz"],
async run({send, author}) {
send(getTimeEmbed(author));
send({embeds: [getTimeEmbed(author)]});
},
subcommands: {
// Welcome to callback hell. We hope you enjoy your stay here!
@ -298,10 +296,11 @@ export default new NamedCommand({
const finalize = () => {
Storage.save();
send(
"You've finished setting up your timezone! Just check to see if this looks right, and if it doesn't, run this setup again.",
getTimeEmbed(author)
);
send({
content:
"You've finished setting up your timezone! Just check to see if this looks right, and if it doesn't, run this setup again.",
embeds: [getTimeEmbed(author)]
});
};
if (hasDST) {
@ -358,23 +357,25 @@ export default new NamedCommand({
const time = moment().utc();
send({
embed: {
color: TIME_EMBED_COLOR,
fields: [
{
name: "Local Date",
value: time.format(DATE_FORMAT)
},
{
name: "Day of the Week",
value: time.format(DOW_FORMAT)
},
{
name: "Local Time",
value: time.format(TIME_FORMAT)
}
]
}
embeds: [
{
color: TIME_EMBED_COLOR,
fields: [
{
name: "Local Date",
value: time.format(DATE_FORMAT)
},
{
name: "Day of the Week",
value: time.format(DOW_FORMAT)
},
{
name: "Local Time",
value: time.format(TIME_FORMAT)
}
]
}
]
});
}
}),
@ -387,14 +388,14 @@ export default new NamedCommand({
user: new Command({
description: "See what time it is for someone else.",
async run({send, args}) {
send(getTimeEmbed(args[0]));
send({embeds: [getTimeEmbed(args[0])]});
}
}),
any: new RestCommand({
description: "See what time it is for someone else (by their username).",
async run({send, guild, combined}) {
const user = await getUserByNickname(combined, guild);
if (typeof user !== "string") send(getTimeEmbed(user));
if (typeof user !== "string") send({embeds: [getTimeEmbed(user)]});
else send(user);
}
})

View File

@ -17,7 +17,7 @@ export default new NamedCommand({
);
}
send(embed);
send({embeds: [embed]});
},
subcommands: {
add: new NamedCommand({

View File

@ -16,19 +16,21 @@ export default new NamedCommand({
})
.then((res) => {
send({
embed: {
title: "Translation",
fields: [
{
name: "Input",
value: `\`\`\`${input}\`\`\``
},
{
name: "Output",
value: `\`\`\`${res}\`\`\``
}
]
}
embeds: [
{
title: "Translation",
fields: [
{
name: "Input",
value: `\`\`\`${input}\`\`\``
},
{
name: "Output",
value: `\`\`\`${res}\`\`\``
}
]
}
]
});
})
.catch((error) => {

View File

@ -4,7 +4,18 @@ import path from "path";
// This is here in order to make it much less of a headache to access the client from other files.
// This of course won't actually do anything until the setup process is complete and it logs in.
export const client = new Client({intents: Intents.ALL});
export const client = new Client({
intents: [
Intents.FLAGS.GUILDS,
Intents.FLAGS.GUILD_MEMBERS,
Intents.FLAGS.GUILD_EMOJIS_AND_STICKERS,
Intents.FLAGS.GUILD_VOICE_STATES,
Intents.FLAGS.GUILD_PRESENCES,
Intents.FLAGS.GUILD_MESSAGES,
Intents.FLAGS.GUILD_MESSAGE_REACTIONS,
Intents.FLAGS.DIRECT_MESSAGES
]
});
import {launch} from "onion-lasers";
import setup from "./modules/setup";
@ -44,7 +55,7 @@ launch(client, path.join(__dirname, "commands"), {
{
// OWNER //
name: "Server Owner",
check: (_user, member) => !!member && member.guild.ownerID === member.id
check: (_user, member) => !!member && member.guild.ownerId === member.id
},
{
// BOT_SUPPORT //
@ -67,7 +78,8 @@ launch(client, path.join(__dirname, "commands"), {
// Initialize Modules //
import "./modules/ready";
import "./modules/presence";
import "./modules/lavalink";
// TODO: Reimplement entire music system, contact Sink
// import "./modules/lavalink";
import "./modules/emoteRegistry";
import "./modules/systemInfo";
import "./modules/intercept";

View File

@ -154,7 +154,11 @@ export function getContent(url: string): Promise<{url: string}> {
const parsedData = JSON.parse(rawData);
resolve(parsedData);
} catch (e) {
reject(`Error: ${e.message}`);
let errorMessage = "Something went wrong! We don't know what, though...";
if (e instanceof Error) {
errorMessage = e.message;
}
reject(`Error: ${errorMessage}`);
}
});
}).on("error", (err: {message: any}) => {

View File

@ -11,8 +11,8 @@ function updateGlobalEmoteRegistry(): void {
ref: emote.name,
id: emote.id,
name: emote.name,
requires_colons: emote.requiresColons || false,
animated: emote.animated,
requires_colons: emote.requiresColons ?? false,
animated: emote.animated ?? false,
url: emote.url,
guild_id: emote.guild.name,
guild_name: emote.guild.name

View File

@ -25,7 +25,7 @@ client.on("guildMemberAdd", async (member) => {
if (welcomeChannel) {
const channel = member.guild.channels.cache.get(welcomeChannel);
if (channel && channel.type === "text") {
if (channel && channel instanceof TextChannel) {
if (welcomeType === "graphical") {
const canvas = createCanvas(700, 250);
const ctx = canvas.getContext("2d");
@ -60,9 +60,9 @@ client.on("guildMemberAdd", async (member) => {
ctx.drawImage(avatar, 25, 25, 200, 200);
const attachment = new MessageAttachment(canvas.toBuffer("image/png"), "welcome-image.png");
(channel as TextChannel).send(`Welcome \`${member.user.tag}\`!`, attachment);
channel.send({content: `Welcome \`${member.user.tag}\`!`, attachments: [attachment]});
} else if (welcomeType === "text") {
(channel as TextChannel).send(
channel.send(
parseVars(
welcomeMessage || "Say hello to `%user%`, everyone! We all need a warm welcome sometimes :D",
{

View File

@ -1,51 +1,49 @@
import attachClientToLavalink from "discord.js-lavalink-lib";
import {Config} from "../structures";
import {client} from "../index";
if (Config.lavalink) {
// Although the example showed to do "client.music = LavaLink(...)" and "(client as any).music = Lavalink(...)" was done to match that, nowhere in the library is client.music ever actually used nor does the function return anything. In other words, client.music is undefined and is never used.
attachClientToLavalink(client, {
lavalink: {
restnode: {
host: "localhost",
port: 2333,
password: "youshallnotpass"
},
nodes: [
{
host: "localhost",
port: 2333,
password: "youshallnotpass"
}
]
},
prefix: Config.prefix,
helpCmd: "mhelp",
admins: ["717352467280691331"]
});
// Disable the unhandledRejection listener by Lavalink because it captures every single unhandled
// rejection and adds its message with it. Then replace it with a better, more selective error handler.
for (const listener of process.listeners("unhandledRejection")) {
if (listener.toString().includes("discord.js-lavalink-musicbot")) {
process.off("unhandledRejection", listener);
}
}
process.on("unhandledRejection", (reason: any) => {
if (reason?.code === "ECONNREFUSED") {
// This is console.warn instead of console.error because on development environments, unless Lavalink is being tested, it won't interfere with the bot's functionality.
console.warn(
`[discord.js-lavalink-musicbot] Caught unhandled rejection: ${reason.stack}\nIf this is causing issues, head to the support server at https://discord.gg/dNN4azK`
);
}
});
// It's unsafe to process uncaughtException because after an uncaught exception, the system
// becomes corrupted. So disable Lavalink from adding a hook to it.
for (const listener of process.listeners("uncaughtException")) {
if (listener.toString().includes("discord.js-lavalink-musicbot")) {
process.off("uncaughtException", listener);
}
}
}
// import attachClientToLavalink from "discord.js-lavalink-lib";
// import {Config} from "../structures";
// import {client} from "../index";
//
// // Although the example showed to do "client.music = LavaLink(...)" and "(client as any).music = Lavalink(...)" was done to match that, nowhere in the library is client.music ever actually used nor does the function return anything. In other words, client.music is undefined and is never used.
// attachClientToLavalink(client, {
// lavalink: {
// restnode: {
// host: "localhost",
// port: 2333,
// password: "youshallnotpass"
// },
// nodes: [
// {
// host: "localhost",
// port: 2333,
// password: "youshallnotpass"
// }
// ]
// },
// prefix: Config.prefix,
// helpCmd: "mhelp",
// admins: ["717352467280691331"]
// });
//
// // Disable the unhandledRejection listener by Lavalink because it captures every single unhandled
// // rejection and adds its message with it. Then replace it with a better, more selective error handler.
// for (const listener of process.listeners("unhandledRejection")) {
// if (listener.toString().includes("discord.js-lavalink-musicbot")) {
// process.off("unhandledRejection", listener);
// }
// }
//
// process.on("unhandledRejection", (reason: any) => {
// if (reason?.code === "ECONNREFUSED") {
// // This is console.warn instead of console.error because on development environments, unless Lavalink is being tested, it won't interfere with the bot's functionality.
// console.warn(
// `[discord.js-lavalink-musicbot] Caught unhandled rejection: ${reason.stack}\nIf this is causing issues, head to the support server at https://discord.gg/dNN4azK`
// );
// }
// });
//
// // It's unsafe to process uncaughtException because after an uncaught exception, the system
// // becomes corrupted. So disable Lavalink from adding a hook to it.
// for (const listener of process.listeners("uncaughtException")) {
// if (listener.toString().includes("discord.js-lavalink-musicbot")) {
// process.off("uncaughtException", listener);
// }
// }

View File

@ -2,12 +2,9 @@ import {client} from "../index";
import {MessageEmbed} from "discord.js";
import {getPrefix} from "../structures";
import {getMessageByID} from "onion-lasers";
import {Storage} from "../structures";
client.on("message", async (message) => {
const {messageEmbeds} = Storage.getGuild(message.guild!.id);
if (messageEmbeds) {
client.on("message", (message) => {
(async () => {
// Only execute if the message is from a user and isn't a command.
if (message.content.startsWith(getPrefix(message.guild)) || message.author.bot) return;
const messageLink = extractFirstMessageLink(message.content);
@ -27,11 +24,7 @@ client.on("message", async (message) => {
];
if (!linkMessage.cleanContent && embeds.length === 0) {
return message.channel.send(new MessageEmbed().setDescription("🚫 The message is empty."));
}
if (linkMessage.cleanContent.length > 2048) {
return message.channel.send(new MessageEmbed().setDescription("🚫 This message is too long."));
return message.channel.send({embeds: [new MessageEmbed().setDescription("🚫 The message is empty.")]});
}
const infoEmbed = new MessageEmbed()
@ -49,8 +42,8 @@ client.on("message", async (message) => {
infoEmbed.setImage(image!.url);
}
return await message.channel.send(infoEmbed);
} else return;
return await message.channel.send({embeds: [infoEmbed]});
})();
});
export function extractFirstMessageLink(message: string): [string, string, string] | null {

View File

@ -38,7 +38,7 @@ function getStreamEmbed(
// I decided to not include certain fields:
// .addField("Activity", "CrossCode", true) - Probably too much presence data involved, increasing memory usage.
// .addField("Viewers", 5, true) - There doesn't seem to currently be a way to track how many viewers there are. Presence data for "WATCHING" doesn't seem to affect it, and listening to raw client events doesn't seem to make it appear either.
.addField("Voice Channel", channel, true)
.addField("Voice Channel", channel.toString(), true)
.addField("Category", category, true)
.setColor(streamer.displayColor)
.setFooter(
@ -90,21 +90,23 @@ client.on("voiceStateUpdate", async (before, after) => {
streamer: member,
channel: voiceChannel,
category,
message: await textChannel.send(
streamNotificationPing,
getStreamEmbed(member, voiceChannel, streamStart, category)
),
message: await textChannel.send({
content: streamNotificationPing,
embeds: [getStreamEmbed(member, voiceChannel, streamStart, category)]
}),
update(this: Stream) {
this.message.edit(
getStreamEmbed(
this.streamer,
this.channel,
streamStart,
this.category,
this.description,
this.thumbnail
)
);
this.message.edit({
embeds: [
getStreamEmbed(
this.streamer,
this.channel,
streamStart,
this.category,
this.description,
this.thumbnail
)
]
});
},
streamStart
});
@ -125,7 +127,7 @@ client.on("voiceStateUpdate", async (before, after) => {
});
client.on("channelUpdate", (before, after) => {
if (before.type === "voice" && after.type === "voice") {
if (before instanceof VoiceChannel && after instanceof VoiceChannel) {
for (const stream of streamList.values()) {
if (after.id === stream.channel.id) {
stream.update();

View File

@ -13,8 +13,8 @@ client.on("guildCreate", async (guild) => {
if (Config.systemLogsChannel) {
const channel = client.channels.cache.get(Config.systemLogsChannel);
if (channel && channel.type === "text") {
(channel as TextChannel).send(
if (channel instanceof TextChannel) {
channel.send(
`TravBot joined: \`${guild.name}\`. The owner of this guild is: \`${owner.user.tag}\` (\`${owner.user.id}\`)`
);
} else {
@ -29,8 +29,8 @@ client.on("guildDelete", (guild) => {
if (Config.systemLogsChannel) {
const channel = client.channels.cache.get(Config.systemLogsChannel);
if (channel && channel.type === "text") {
(channel as TextChannel).send(`\`${guild.name}\` (\`${guild.id}\`) removed the bot.`);
if (channel instanceof TextChannel) {
channel.send(`\`${guild.name}\` (\`${guild.id}\`) removed the bot.`);
} else {
console.warn(
`${Config.systemLogsChannel} is not a valid text channel for system logs! Removing it from storage.`

View File

@ -61,7 +61,7 @@ export async function refreshWebhookCache(): Promise<void> {
// If there are stored webhook IDs/tokens that don't work, delete those webhooks from storage.
try {
const webhook = await client.fetchWebhook(id, token);
webhookStorage.set(webhook.channelID, webhook);
webhookStorage.set(webhook.channelId, webhook);
} catch {
delete Config.webhooks[id];
Config.save();

View File

@ -10,7 +10,7 @@
// Type Settings //
"strict": true, // Enables all strict checks possible.
"noImplicitReturns": true, // Makes sure you don't accidentally return something + undefined.
"noImplicitReturns": false, // Makes sure you don't accidentally return something + undefined.
"noFallthroughCasesInSwitch": true, // Prevents accidentally forgetting to break every switch case. Of course, if you know what you're doing, feel free to add a @ts-ignore, which also signals that it's not a mistake.
"forceConsistentCasingInFileNames": true, // Make import paths case-sensitive. "./tEst" is no longer the same as "./test".
"esModuleInterop": true, // Enables compatibility with Node.js' module system since the entire export can be whatever you want. allowSyntheticDefaultImports doesn't address runtime issues and is made redundant by this setting.