Merge branch 'typescript' into eco-bet

This commit is contained in:
フズキ 2021-04-06 08:23:36 +02:00 committed by GitHub
commit 61cb648c70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 5622 additions and 173 deletions

4614
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,16 +5,23 @@
"main": "dist/index.js",
"private": true,
"dependencies": {
"canvas": "^2.7.0",
"chalk": "^4.1.0",
"discord.js": "^12.5.1",
"discord.js-lavalink-lib": "^0.1.8",
"figlet": "^1.5.0",
"inquirer": "^7.3.3",
"mathjs": "^9.3.0",
"moment": "^2.29.1",
"ms": "^2.1.3",
"os": "^0.1.1"
"relevant-urban": "^2.0.0",
"translate-google": "^1.4.3",
"weather-js": "^2.0.0"
},
"devDependencies": {
"@types/figlet": "^1.5.0",
"@types/inquirer": "^6.5.0",
"@types/mathjs": "^6.0.11",
"@types/mocha": "^8.2.0",
"@types/ms": "^0.7.31",
"@types/node": "^14.14.20",

View File

@ -55,6 +55,115 @@ export default new Command({
$.channel.send(`The custom prefix for this guild is now \`${$.args[0]}\`.`);
}
})
}),
welcome: new Command({
description: "Configure your server's welcome settings for the bot.",
usage: "type/channel <...>",
run: "You need to specify which part to modify, `type`/`channel`.",
subcommands: {
type: new Command({
description:
"Sets how welcome messages are displayed for your server. Removes welcome messages if unspecified.",
usage: "`none`/`text`/`graphical`",
async run($) {
if ($.guild) {
Storage.getGuild($.guild.id).welcomeType = "none";
Storage.save();
$.channel.send("Set this server's welcome type to `none`.");
} else {
$.channel.send("You must use this command in a server.");
}
},
// I should probably make this a bit more dynamic... Oh well.
subcommands: {
text: new Command({
async run($) {
if ($.guild) {
Storage.getGuild($.guild.id).welcomeType = "text";
Storage.save();
$.channel.send("Set this server's welcome type to `text`.");
} else {
$.channel.send("You must use this command in a server.");
}
}
}),
graphical: new Command({
async run($) {
if ($.guild) {
Storage.getGuild($.guild.id).welcomeType = "graphical";
Storage.save();
$.channel.send("Set this server's welcome type to `graphical`.");
} else {
$.channel.send("You must use this command in a server.");
}
}
})
}
}),
channel: new Command({
description: "Sets the welcome channel for your server. Type `#` to reference the channel.",
usage: "(<channel mention>)",
async run($) {
if ($.guild) {
Storage.getGuild($.guild.id).welcomeChannel = $.channel.id;
Storage.save();
$.channel.send(
`Successfully set ${$.channel} as the welcome channel for this server.`
);
} else {
$.channel.send("You must use this command in a server.");
}
},
// If/when channel types come out, this will be the perfect candidate to test it.
any: new Command({
async run($) {
if ($.guild) {
const match = $.args[0].match(/^<#(\d{17,19})>$/);
if (match) {
Storage.getGuild($.guild.id).welcomeChannel = match[1];
Storage.save();
$.channel.send(
`Successfully set this server's welcome channel to ${match[0]}.`
);
} else {
$.channel.send(
"You must provide a reference channel. You can do this by typing `#` then searching for the proper channel."
);
}
} else {
$.channel.send("You must use this command in a server.");
}
}
})
}),
message: new Command({
description:
"Sets a custom welcome message for your server. Use `%user%` as the placeholder for the user.",
usage: "(<message>)",
async run($) {
if ($.guild) {
Storage.getGuild($.guild.id).welcomeMessage = null;
Storage.save();
$.channel.send("Reset your server's welcome message to the default.");
} else {
$.channel.send("You must use this command in a server.");
}
},
any: new Command({
async run($) {
if ($.guild) {
const message = $.args.join(" ");
Storage.getGuild($.guild.id).welcomeMessage = message;
Storage.save();
$.channel.send(`Set your server's welcome message to \`${message}\`.`);
} else {
$.channel.send("You must use this command in a server.");
}
}
})
})
}
})
}
}),
@ -124,14 +233,15 @@ export default new Command({
number: new Command({
description: "Amount of messages to delete.",
async run($: CommonLibrary): Promise<any> {
if ($.channel.type === "dm") {
await $.channel.send("Can't clear messages in the DMs!");
return;
}
$.message.delete();
const fetched = await $.channel.messages.fetch({
limit: $.args[0]
});
$.channel
/// @ts-ignore
.bulkDelete(fetched)
.catch((error: any) => $.channel.send(`Error: ${error}`));
await $.channel.bulkDelete(fetched);
}
})
}),
@ -157,8 +267,7 @@ export default new Command({
permission: Command.PERMISSIONS.BOT_SUPPORT,
async run($: CommonLibrary): Promise<any> {
const nickName = $.args.join(" ");
const trav = $.guild?.members.cache.find((member) => member.id === $.client.user?.id);
await trav?.setNickname(nickName);
await $.guild?.me?.setNickname(nickName);
if (botHasPermission($.guild, Permissions.FLAGS.MANAGE_MESSAGES))
$.message.delete({timeout: 5000}).catch($.handler.bind($));
$.channel.send(`Nickname set to \`${nickName}\``).then((m) => m.delete({timeout: 5000}));
@ -202,6 +311,19 @@ export default new Command({
);
}
})
}),
syslog: new Command({
description: "Sets up the current channel to receive system logs.",
permission: Command.PERMISSIONS.BOT_ADMIN,
async run($) {
if ($.guild) {
Config.systemLogsChannel = $.channel.id;
Config.save();
$.channel.send(`Successfully set ${$.channel} as the system logs channel.`);
} else {
$.channel.send("DM system log channels aren't supported.");
}
}
})
}
});

View File

@ -0,0 +1,20 @@
import Command from "../../core/command";
import figlet from "figlet";
export default new Command({
description: "Generates a figlet of your input.",
async run($) {
const input = $.args.join(" ");
if (!$.args[0]) {
$.channel.send("You have to provide input for me to create a figlet!");
return;
}
$.channel.send(
"```" +
figlet.textSync(`${input}`, {
horizontalLayout: "full"
}) +
"```"
);
}
});

View File

@ -0,0 +1,14 @@
import Command from "../../core/command";
export default new Command({
description: "Insult TravBot! >:D",
async run($) {
$.channel.startTyping();
setTimeout(() => {
$.channel.send(
`${$.author.toString()} 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();
}, 60000);
}
});

13
src/commands/fun/love.ts Normal file
View File

@ -0,0 +1,13 @@
import Command from "../../core/command";
export default new Command({
description: "Chooses someone to love.",
async run($) {
if ($.guild) {
const member = $.guild.members.cache.random();
$.channel.send(`I love ${member.user.username}!`);
} else {
$.channel.send("You must use this command in a guild!");
}
}
});

38
src/commands/fun/ravi.ts Normal file
View File

@ -0,0 +1,38 @@
import Command from "../../core/command";
import {Random} from "../../core/lib";
export default new Command({
description: "Ravioli ravioli...",
usage: "[number from 1 to 9]",
async run($) {
$.channel.send({
embed: {
title: "Ravioli ravioli...",
image: {
url: `https://raw.githubusercontent.com/keanuplayz/TravBot/master/assets/ravi${Random.int(
1,
10
)}.png`
}
}
});
},
number: new Command({
async run($) {
const arg: number = $.args[0];
if (arg >= 1 && arg <= 9) {
$.channel.send({
embed: {
title: "Ravioli ravioli...",
image: {
url: `https://raw.githubusercontent.com/keanuplayz/TravBot/master/assets/ravi${arg}.png`
}
}
});
} else {
$.channel.send("Please provide a number between 1 and 9.");
}
}
})
});

View File

@ -62,9 +62,9 @@ export function getSendEmbed(sender: User, receiver: User, amount: number): obje
}
export function isAuthorized(guild: Guild | null, channel: TextChannel | DMChannel | NewsChannel): boolean {
if (guild?.id === "637512823676600330" || process.argv[2] === "dev") return true;
if (guild?.id === "637512823676600330" && channel?.id === "669464416420364288" || process.argv[2] === "dev") return true;
else {
channel.send("Sorry, this command can only be used in Monika's emote server.");
channel.send("Sorry, this command can only be used in Monika's emote server. (#mon-stocks)");
return false;
}
}

41
src/commands/fun/thonk.ts Normal file
View File

@ -0,0 +1,41 @@
import Command from "../../core/command";
const letters: {[letter: string]: string[]} = {
a: "aáàảãạâấầẩẫậăắằẳẵặ".split(""),
e: "eéèẻẽẹêếềểễệ".split(""),
i: "iíìỉĩị".split(""),
o: "oóòỏõọôốồổỗộơớờởỡợ".split(""),
u: "uúùủũụưứừửữự".split(""),
y: "yýỳỷỹỵ".split(""),
d: "dđ".split("")
};
function transform(str: string) {
let out = "";
for (const c of str) {
const token = c.toLowerCase();
const isUpperCase = token !== c;
if (token in letters) {
const set = letters[token];
const add = set[Math.floor(Math.random() * set.length)];
out += isUpperCase ? add.toUpperCase() : add;
} else {
out += c;
}
}
return out;
}
let phrase = "I have no currently set phrase!";
export default new Command({
description: "Transforms your text into .",
usage: "thonk ([text])",
async run($) {
if ($.args.length > 0) phrase = $.args.join(" ");
$.channel.send(transform(phrase));
}
});

27
src/commands/fun/urban.ts Normal file
View File

@ -0,0 +1,27 @@
import Command from "../../core/command";
import {MessageEmbed} from "discord.js";
// Anycasting Alert
const urban = require("relevant-urban");
export default new Command({
description: "Gives you a definition of the inputted word.",
async run($) {
if (!$.args[0]) {
$.channel.send("Please input a word.");
}
const res = await urban($.args.join(" ")).catch((e: Error) => {
return $.channel.send("Sorry, that word was not found.");
});
const embed = new MessageEmbed()
.setColor(0x1d2439)
.setTitle(res.word)
.setURL(res.urbanURL)
.setDescription(`**Definition:**\n*${res.definition}*\n\n**Example:**\n*${res.example}*`)
.addField("Author", res.author, true)
.addField("Rating", `**\`Upvotes: ${res.thumbsUp} | Downvotes: ${res.thumbsDown}\`**`);
if (res.tags.length > 0 && res.tags.join(" ").length < 1024) {
embed.addField("Tags", res.tags.join(", "), true);
}
$.channel.send(embed);
}
});

View File

@ -0,0 +1,39 @@
import Command from "../../core/command";
import {MessageEmbed} from "discord.js";
// Anycasting Alert
const weather = require("weather-js");
export default new Command({
description: "Shows weather info of specified location.",
async run($) {
if ($.args.length == 0) {
$.channel.send("You need to provide a city.");
return;
}
weather.find(
{
search: $.args.join(" "),
degreeType: "C"
},
function (err: any, result: any) {
if (err) $.channel.send(err);
var current = result[0].current;
var location = result[0].location;
const embed = new MessageEmbed()
.setDescription(`**${current.skytext}**`)
.setAuthor(`Weather for ${current.observationpoint}`)
.setThumbnail(current.imageUrl)
.setColor(0x00ae86)
.addField("Timezone", `UTC${location.timezone}`, true)
.addField("Degree Type", "C", true)
.addField("Temperature", `${current.temperature} Degrees`, true)
.addField("Feels like", `${current.feelslike} Degrees`, true)
.addField("Winds", current.winddisplay, true)
.addField("Humidity", `${current.humidity}%`, true);
$.channel.send({
embed
});
}
);
}
});

88
src/commands/fun/whois.ts Normal file
View File

@ -0,0 +1,88 @@
import {User} from "discord.js";
import Command from "../../core/command";
// Quotes must be used here or the numbers will change
const registry: {[id: string]: string} = {
"465662909645848577": "You're an idiot, that's what.",
"306499531665833984":
"Kuma, you eldritch fuck, I demand you to release me from this Discord bot and let me see my Chromebook!",
"137323711844974592": "The purple haired gunner man who makes loud noises.",
"208763015657553921": "Minzy's master.",
"229636002443034624": "The ***God*** of being Smug.",
"280876114153308161": "The best girl.",
"175823837835821067": "The somehow sentient pear.",
"145839753118351360": "The blueberry with horns.",
"173917366504259585": "A talented developer.",
"216112465321263105": "The red strawberry cat.",
"394808963356688394": "The cutest, bestest, most caring girl ever.",
"142200534781132800": "The masters of chaos.",
"186496078273708033": "The cute blue cat.",
"241293368267767808": "The cute catgirl.",
"540419616803913738": "The generically Generic hologram man.",
"157598993298227211": "The somehow sentient bowl of nachos.",
"225214401228177408": "The CMD user.",
"224619540263337984": "The guy that did 50% of the work.",
"374298111255773184": "The cutest fox around.",
"150400803503472640": "The big huggy turtle boye.",
"620777734427115523": "The small huggy turtle boye.",
"310801870048198667": "An extremely talented artist and modder.",
"328223274133880833": "The stealthiest hitman.",
"219661798742163467": "An extremely talented artist and modder.",
"440399719076855818":
"You are, uhh, Stay Put, Soft Puppy, Es-Pee, Swift Pacemaker, Smug Poyo, and many more.\n...Seriously, this woman has too many names.",
"243061915281129472": "Some random conlanger, worldbuilder and programmer doofus. ~~May also secretly be a nyan. :3~~",
"367439475153829892": "A weeb.",
"760375501775700038": "˙qǝǝʍ ∀",
"389178357302034442": "In his dreams, he is the star. its him. <:itsMe:808174425253871657>",
"606395763404046349": "Me."
};
export default new Command({
description: "Tells you who you or the specified user is.",
aliases: ["whoami"],
async run($) {
const id = $.author.id;
if (id in registry) {
$.channel.send(registry[id]);
} else {
$.channel.send("You haven't been added to the registry yet!");
}
},
user: new Command({
async run($) {
const user: User = $.args[0];
const id = user.id;
if (id in registry) {
$.channel.send(`\`${user.username}\` - ${registry[id]}`);
} else {
$.channel.send(`\`${user.username}#${user.discriminator}\` hasn't been added to the registry yet!`);
}
}
}),
any: new Command({
async run($) {
if ($.guild) {
const query: string = $.args.join(" ");
const member = await $.getMemberByUsername($.guild, query);
if (member && member.id in registry) {
const id = member.id;
if (id in registry) {
$.channel.send(`\`${member.user.username}\` - ${registry[member.id]}`);
} else {
$.channel.send(`\`${member.user.username}\` hasn't been added to the registry yet!`);
}
} else {
$.channel.send(`Couldn't find a user by the name of \`${query}\`!`);
}
} else {
$.channel.send(
"You must run this in a guild! (*If you have the user's ID, you don't have to be in a guild.*)"
);
}
}
})
});

View File

@ -1,6 +1,4 @@
import {MessageEmbed, version as djsversion} from "discord.js";
/// @ts-ignore
import {version} from "../../package.json";
import ms from "ms";
import os from "os";
import Command from "../core/command";
@ -8,6 +6,9 @@ import {CommonLibrary, formatBytes, trimArray} from "../core/lib";
import {verificationLevels, filterLevels, regions, flags} from "../defs/info";
import moment from "moment";
import utc from "moment";
import {Guild} from "discord.js";
const {version} = require("../../package.json");
export default new Command({
description: "Command to provide all sorts of info about the current server, a user, etc.",
@ -36,13 +37,6 @@ export default new Command({
async run($: CommonLibrary): Promise<any> {
const core = os.cpus()[0];
const embed = new MessageEmbed()
.setThumbnail(
/// @ts-ignore
$.client.user?.displayAvatarURL({
dynamic: true,
size: 2048
})
)
.setColor($.guild?.me?.displayHexColor || "BLUE")
.addField("General", [
`** Client:** ${$.client.user?.tag} (${$.client.user?.id})`,
@ -68,84 +62,59 @@ export default new Command({
`\u3000 • Speed: ${core.speed}MHz`,
`** Memory:**`,
`\u3000 • Total: ${formatBytes(process.memoryUsage().heapTotal)}`,
`\u3000 • Used: ${formatBytes(process.memoryUsage().heapTotal)}`
`\u3000 • Used: ${formatBytes(process.memoryUsage().heapUsed)}`
])
.setTimestamp();
const avatarURL = $.client.user?.displayAvatarURL({
dynamic: true,
size: 2048
});
if (avatarURL) embed.setThumbnail(avatarURL);
$.channel.send(embed);
}
}),
guild: new Command({
description: "Displays info about the current guild.",
description: "Displays info about the current guild or another guild.",
usage: "(<guild name>/<guild ID>)",
async run($: CommonLibrary): Promise<any> {
if ($.guild) {
const roles = $.guild.roles.cache
.sort((a, b) => b.position - a.position)
.map((role) => role.toString());
const members = $.guild.members.cache;
const channels = $.guild.channels.cache;
const emojis = $.guild.emojis.cache;
const iconURL = $.guild.iconURL({dynamic: true});
const embed = new MessageEmbed()
.setDescription(`**Guild information for __${$.guild.name}__**`)
.setColor("BLUE");
if (iconURL)
embed
.setThumbnail(iconURL)
.addField("General", [
`** Name:** ${$.guild.name}`,
`** ID:** ${$.guild.id}`,
`** Owner:** ${$.guild.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
}`,
"\u200b"
])
.addField(
`Roles [${roles.length - 1}]`,
roles.length < 10 ? roles.join(", ") : roles.length > 10 ? trimArray(roles) : "None"
)
.setTimestamp();
$.channel.send(embed);
$.channel.send(await getGuildInfo($.guild, $.guild));
} else {
$.channel.send("Please execute this command in a guild.");
}
}
},
any: new Command({
description: "Display info about a guild by finding its name or ID.",
async run($: CommonLibrary): Promise<any> {
// If a guild ID is provided (avoid the "number" subcommand because of inaccuracies), search for that guild
if ($.args.length === 1 && /^\d{17,19}$/.test($.args[0])) {
const id = $.args[0];
const guild = $.client.guilds.cache.get(id);
if (guild) {
$.channel.send(await getGuildInfo(guild, $.guild));
} else {
$.channel.send(`None of the servers I'm in matches the guild ID \`${id}\`!`);
}
} else {
const query: string = $.args.join(" ").toLowerCase();
const guild = $.client.guilds.cache.find((guild) => guild.name.toLowerCase().includes(query));
if (guild) {
$.channel.send(await getGuildInfo(guild, $.guild));
} else {
$.channel.send(`None of the servers I'm in matches the query \`${query}\`!`);
}
}
}
})
})
},
user: new Command({
description: "Displays info about mentioned user.",
async run($: CommonLibrary): Promise<any> {
// Transforms the User object into a GuildMember object of the current guild.
const member = $.guild?.members.resolve($.args[0]) ?? (await $.guild?.members.fetch($.args[0]));
const member = await $.guild?.members.fetch($.args[0]);
if (!member)
return $.channel.send(
@ -156,8 +125,7 @@ export default new Command({
.sort((a: {position: number}, b: {position: number}) => b.position - a.position)
.map((role: {toString: () => any}) => role.toString())
.slice(0, -1);
// @ts-ignore - Discord.js' typings seem to be outdated here. According to their v12 docs, it's User.fetchFlags() instead of User.flags.
const userFlags = ((await member.user.fetchFlags()) as UserFlags).toArray();
const userFlags = (await member.user.fetchFlags()).toArray();
const embed = new MessageEmbed()
.setThumbnail(member.user.displayAvatarURL({dynamic: true, size: 512}))
@ -183,14 +151,71 @@ export default new Command({
`** Server Join Date:** ${moment(member.joinedAt).format("LL LTS")}`,
`** Hoist Role:** ${member.roles.hoist ? member.roles.hoist.name : "None"}`,
`** Roles:** [${roles.length}]: ${
roles.length < 10
? roles.join(", ")
: roles.length > 10
? this.client.utils.trimArray(roles)
: "None"
roles.length == 0 ? "None" : roles.length <= 10 ? roles.join(", ") : trimArray(roles).join(", ")
}`
]);
$.channel.send(embed);
}
})
});
async function getGuildInfo(guild: Guild, currentGuild: Guild | null) {
const members = await guild.members.fetch({
withPresences: true,
force: true
});
const roles = guild.roles.cache.sort((a, b) => b.position - a.position).map((role) => role.toString());
const channels = guild.channels.cache;
const emojis = guild.emojis.cache;
const iconURL = guild.iconURL({dynamic: true});
const embed = new MessageEmbed().setDescription(`**Guild information for __${guild.name}__**`).setColor("BLUE");
const displayRoles = !!(currentGuild && guild.id === currentGuild.id);
if (iconURL) {
embed
.setThumbnail(iconURL)
.addField("General", [
`** Name:** ${guild.name}`,
`** ID:** ${guild.id}`,
`** Owner:** ${guild.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" : ""
])
.setTimestamp();
// Only add the roles if the guild the bot is sending the message to is the same one that's being requested.
if (displayRoles) {
embed.addField(
`Roles [${roles.length - 1}]`,
roles.length < 10 ? roles.join(", ") : roles.length > 10 ? trimArray(roles) : "None"
);
}
}
return embed;
}

View File

@ -0,0 +1,26 @@
import Command from "../../core/command";
import * as math from "mathjs";
import {MessageEmbed} from "discord.js";
export default new Command({
description: "Calculates a specified math expression.",
async run($) {
if (!$.args[0]) {
$.channel.send("Please provide a calculation.");
return;
}
let resp;
try {
resp = math.evaluate($.args.join(" "));
} catch (e) {
$.channel.send("Please provide a *valid* calculation.");
return;
}
const embed = new MessageEmbed()
.setColor(0xffffff)
.setTitle("Math Calculation")
.addField("Input", `\`\`\`js\n${$.args.join("")}\`\`\``)
.addField("Output", `\`\`\`js\n${resp}\`\`\``);
$.channel.send(embed);
}
});

View File

@ -0,0 +1,6 @@
import Command from "../../core/command";
export default new Command({
description: "Gives you the Github link.",
run: "https://github.com/keanuplayz/TravBot-v3"
});

View File

@ -9,15 +9,14 @@ export default new Command({
if (!voiceChannel) return $.channel.send("You are not in a voice channel.");
if (!$.guild?.me?.hasPermission("MANAGE_CHANNELS"))
if (!voiceChannel.guild.me?.hasPermission("MANAGE_CHANNELS"))
return $.channel.send("I am lacking the required permissions to perform this action.");
if ($.args.length === 0) return $.channel.send("Please provide a new voice channel name.");
const changeVC = $.guild.channels.resolve(voiceChannel.id);
$.channel
.send(`Changed channel name from "${voiceChannel}" to "${$.args.join(" ")}".`)
/// @ts-ignore
.then(changeVC?.setName($.args.join(" ")));
const prevName = voiceChannel.name;
const newName = $.args.join(" ");
await voiceChannel.setName(newName);
await $.channel.send(`Changed channel name from "${prevName}" to "${newName}".`);
}
});

View File

@ -12,7 +12,6 @@ export default new Command({
async run({guild, channel, message, args}) {
let output = "";
for (const query of args) output += queryClosestEmoteByName(query).toString();
if (botHasPermission(guild, Permissions.FLAGS.MANAGE_MESSAGES)) message.delete();
channel.send(output);
}
})

View File

@ -0,0 +1,12 @@
import Command from "../../core/command";
export default new Command({
description: "Gives you the invite link.",
async run($) {
$.channel.send(
`https://discordapp.com/api/oauth2/authorize?client_id=${$.client.user!.id}&permissions=${
$.args[0] || 8
}&scope=bot`
);
}
});

View File

@ -1,32 +1,117 @@
import {GuildEmoji} from "discord.js";
import {MessageEmbed} from "discord.js";
import Command from "../../core/command";
import {CommonLibrary} from "../../core/lib";
import vm from "vm";
const REGEX_TIMEOUT_MS = 1000;
export default new Command({
description: "Lists all emotes the bot has in it's registry,",
endpoint: true,
usage: "<regex pattern> (-flags)",
async run($: CommonLibrary): Promise<any> {
const nsfw: string | string[] = [];
const pages = $.client.emojis.cache.filter((x) => !nsfw.includes(x.guild.id), this).array();
const pagesSplit = $(pages).split(20);
$.log(pagesSplit);
var embed = new MessageEmbed().setTitle("**Emoji list!**").setColor("AQUA");
let desc = "";
displayEmoteList($, $.client.emojis.cache.array());
},
any: new Command({
description:
"Filters emotes by via a regular expression. Flags can be added by adding a dash at the end. For example, to do a case-insensitive search, do %prefix%lsemotes somepattern -i",
async run($: CommonLibrary): Promise<any> {
// If a guild ID is provided, filter all emotes by that guild (but only if there aren't any arguments afterward)
if ($.args.length === 1 && /^\d{17,19}$/.test($.args[0])) {
const guildID: string = $.args[0];
for (const emote of pagesSplit[0]) {
desc += `${emote} | ${emote.name}\n`;
displayEmoteList($, $.client.emojis.cache.filter((emote) => emote.guild.id === guildID).array());
} else {
// Otherwise, search via a regex pattern
let flags: string | undefined = undefined;
if (/^-[dgimsuy]{1,7}$/.test($.args[$.args.length - 1])) {
flags = $.args.pop().substring(1);
}
let emoteCollection = $.client.emojis.cache.array();
// 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: {[id: string]: string} = {};
let emotes = new Map<string, string>();
for (const emote of emoteCollection) {
emotes.set(emote.id, emote.name);
}
// The result will be sandbox.emotes because it'll be modified in-place.
const sandbox = {
regex: new RegExp($.args.join(" "), flags),
emotes
};
const context = vm.createContext(sandbox);
if (vm.isContext(sandbox)) {
// Restrict an entire query to the timeout specified.
try {
const script = new vm.Script(
"for(const [id, name] of emotes.entries()) if(!regex.test(name)) emotes.delete(id);"
);
script.runInContext(context, {timeout: REGEX_TIMEOUT_MS});
emotes = sandbox.emotes;
emoteCollection = emoteCollection.filter((emote) => emotes.has(emote.id)); // Only allow emotes that haven't been deleted.
displayEmoteList($, emoteCollection);
} catch (error) {
if (error.code === "ERR_SCRIPT_EXECUTION_TIMEOUT") {
$.channel.send(
`The regular expression you entered exceeded the time limit of ${REGEX_TIMEOUT_MS} milliseconds.`
);
} else {
throw new Error(error);
}
}
} else {
$.channel.send("Failed to initialize sandbox.");
}
}
}
})
});
async function displayEmoteList($: CommonLibrary, emotes: GuildEmoji[]) {
emotes.sort((a, b) => {
const first = a.name.toLowerCase();
const second = b.name.toLowerCase();
if (first > second) return 1;
else if (first < second) return -1;
else return 0;
});
const sections = $(emotes).split(20);
const pages = sections.length;
const embed = new MessageEmbed().setTitle("**Emotes**").setColor("AQUA");
let desc = "";
// Gather the first page (if it even exists, which it might not if there no valid emotes appear)
if (pages > 0) {
for (const emote of sections[0]) {
desc += `${emote} ${emote.name} (**${emote.guild.name}**)\n`;
}
embed.setDescription(desc);
const msg = await $.channel.send({embed});
$.paginate(msg, $.author.id, pages.length, (page) => {
let desc = "";
for (const emote of pagesSplit[page]) {
desc += `${emote} | ${emote.name}\n`;
}
embed.setDescription(desc);
msg.edit(embed);
});
if (pages > 1) {
embed.setTitle(`**Emotes** (Page 1 of ${pages})`);
const msg = await $.channel.send({embed});
$.paginate(msg, $.author.id, pages, (page) => {
let desc = "";
for (const emote of sections[page]) {
desc += `${emote} ${emote.name} (**${emote.guild.name}**)\n`;
}
embed.setTitle(`**Emotes** (Page ${page + 1} of ${pages})`);
embed.setDescription(desc);
msg.edit(embed);
});
} else {
await $.channel.send({embed});
}
} else {
$.channel.send("No valid emotes found by that query.");
}
});
}

View File

@ -0,0 +1,62 @@
import Command from "../../core/command";
import moment from "moment";
import {Storage} from "../../core/structures";
import {MessageEmbed} from "discord.js";
export default new Command({
description: "Keep and edit your personal todo list.",
async run($) {
const user = Storage.getUser($.author.id);
const embed = new MessageEmbed().setTitle(`Todo list for ${$.author.tag}`).setColor("BLUE");
for (const timestamp in user.todoList) {
const date = new Date(Number(timestamp));
embed.addField(
`${moment(date).format("LT")} ${moment(date).format("LL")} (${moment(date).fromNow()})`,
user.todoList[timestamp]
);
}
$.channel.send(embed);
},
subcommands: {
add: new Command({
async run($) {
const user = Storage.getUser($.author.id);
const note = $.args.join(" ");
user.todoList[Date.now().toString()] = note;
console.debug(user.todoList);
Storage.save();
$.channel.send(`Successfully added \`${note}\` to your todo list.`);
}
}),
remove: new Command({
async run($) {
const user = Storage.getUser($.author.id);
const note = $.args.join(" ");
let isFound = false;
for (const timestamp in user.todoList) {
const selectedNote = user.todoList[timestamp];
if (selectedNote === note) {
delete user.todoList[timestamp];
Storage.save();
isFound = true;
$.channel.send(`Removed \`${note}\` from your todo list.`);
}
}
if (!isFound) $.channel.send("That item couldn't be found.");
}
}),
clear: new Command({
async run($) {
const user = Storage.getUser($.author.id);
user.todoList = {};
Storage.save();
$.channel.send("Cleared todo list.");
}
})
}
});

View File

@ -0,0 +1,38 @@
import Command from "../../core/command";
// Anycasting Alert
const translate = require("translate-google");
export default new Command({
description: "Translates your input.",
usage: "<lang ID> <input>",
async run($) {
const lang = $.args[0];
const input = $.args.slice(1).join(" ");
translate(input, {
to: lang
})
.then((res: any) => {
$.channel.send({
embed: {
title: "Translation",
fields: [
{
name: "Input",
value: `\`\`\`${input}\`\`\``
},
{
name: "Output",
value: `\`\`\`${res}\`\`\``
}
]
}
});
})
.catch((err: any) => {
console.error(err);
$.channel.send(
`${err}\nPlease use the following list: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes`
);
});
}
});

View File

@ -182,12 +182,16 @@ export async function loadCommands(): Promise<Collection<string, Command>> {
if (cmd.isDirectory()) {
if (cmd.name === "subcommands") continue;
else $.warn(`You can't have multiple levels of directories! From: "dist/commands/${cmd.name}"`);
} else loadCommand(cmd.name, list, selected.name);
} else if (cmd.name.endsWith(".js")) {
loadCommand(cmd.name, list, selected.name);
}
}
subdir.close();
categories.set(category, list);
} else loadCommand(selected.name, listMisc);
} else if (selected.name.endsWith(".js")) {
loadCommand(selected.name, listMisc);
}
}
dir.close();
@ -236,7 +240,7 @@ export default new Command({
permission: null,
aliases: [],
async run($: CommonLibrary): Promise<any> {
},
subcommands: {
layer: new Command({
@ -246,7 +250,7 @@ export default new Command({
permission: null,
aliases: [],
async run($: CommonLibrary): Promise<any> {
}
})
},
@ -256,7 +260,7 @@ export default new Command({
usage: '',
permission: null,
async run($: CommonLibrary): Promise<any> {
}
}),
number: new Command({
@ -265,7 +269,7 @@ export default new Command({
usage: '',
permission: null,
async run($: CommonLibrary): Promise<any> {
}
}),
any: new Command({
@ -274,7 +278,7 @@ export default new Command({
usage: '',
permission: null,
async run($: CommonLibrary): Promise<any> {
}
})
});`;

View File

@ -172,7 +172,7 @@ export function formatUTCTimestamp(now = new Date()) {
}
export function botHasPermission(guild: Guild | null, permission: number): boolean {
return !!(client.user && guild?.members.resolve(client.user)?.hasPermission(permission));
return !!guild?.me?.hasPermission(permission);
}
export function updateGlobalEmoteRegistry(): void {
@ -216,20 +216,22 @@ $.paginate = async (
callback(page);
};
const BACKWARDS_EMOJI = "⬅️";
const FORWARDS_EMOJI = "➡️";
const handle = (emote: string, reacterID: string) => {
switch (emote) {
case "⬅️":
case BACKWARDS_EMOJI:
turn(-1);
break;
case "➡️":
case FORWARDS_EMOJI:
turn(1);
break;
}
};
// Listen for reactions and call the handler.
await message.react("⬅️");
await message.react("➡️");
let backwardsReaction = await message.react(BACKWARDS_EMOJI);
let forwardsReaction = await message.react(FORWARDS_EMOJI);
eventListeners.set(message.id, handle);
await message.awaitReactions(
(reaction, user) => {
@ -248,8 +250,8 @@ $.paginate = async (
);
// When time's up, remove the bot's own reactions.
eventListeners.delete(message.id);
message.reactions.cache.get("⬅️")?.users.remove(message.author);
message.reactions.cache.get("➡️")?.users.remove(message.author);
backwardsReaction.users.remove(message.author);
forwardsReaction.users.remove(message.author);
};
// Waits for the sender to either confirm an action or let it pass (and delete the message).

View File

@ -3,12 +3,15 @@ import $, {select, GenericJSON, GenericStructure} from "./lib";
import {watch} from "fs";
import {Guild as DiscordGuild, Snowflake} from "discord.js";
// Maybe use getters and setters to auto-save on set?
class ConfigStructure extends GenericStructure {
public token: string;
public prefix: string;
public owner: string;
public admins: string[];
public support: string[];
public systemLogsChannel: string | null;
constructor(data: GenericJSON) {
super("config");
@ -17,6 +20,7 @@ class ConfigStructure extends GenericStructure {
this.owner = select(data.owner, "", String);
this.admins = select(data.admins, [], String, true);
this.support = select(data.support, [], String, true);
this.systemLogsChannel = select(data.systemLogsChannel, null, String);
}
}
@ -26,6 +30,7 @@ class User {
public lastMonday: number;
public timezone: number | null; // This is for the standard timezone only, not the daylight savings timezone
public daylightSavingsRegion: "na" | "eu" | "sh" | null;
public todoList: {[timestamp: string]: string};
public ecoBetInsurance: number;
constructor(data?: GenericJSON) {
@ -36,15 +41,42 @@ class User {
this.daylightSavingsRegion = /^((na)|(eu)|(sh))$/.test(data?.daylightSavingsRegion)
? data?.daylightSavingsRegion
: null;
this.todoList = {};
if (data) {
for (const timestamp in data.todoList) {
const note = data.todoList[timestamp];
if (typeof note === "string") {
this.todoList[timestamp] = note;
}
}
}
this.ecoBetInsurance = select(data?.ecoBetInsurance, 0, Number);
}
}
class Guild {
public prefix: string | null;
public welcomeType: "none" | "text" | "graphical";
public welcomeChannel: string | null;
public welcomeMessage: string | null;
constructor(data?: GenericJSON) {
this.prefix = select(data?.prefix, null, String);
this.welcomeChannel = select(data?.welcomeChannel, null, String);
this.welcomeMessage = select(data?.welcomeMessage, null, String);
switch (data?.welcomeType) {
case "text":
this.welcomeType = "text";
break;
case "graphical":
this.welcomeType = "graphical";
break;
default:
this.welcomeType = "none";
break;
}
}
}

View File

@ -1,10 +1,32 @@
import Event from "../core/event";
import $ from "../core/lib";
import {updateGlobalEmoteRegistry} from "../core/lib";
import {client} from "../index";
import {Config} from "../core/structures";
import {TextChannel} from "discord.js";
export default new Event<"guildCreate">({
on() {
$.log("Updated emote registry.");
on(guild) {
$.log(
`[GUILD JOIN] ${guild.name} (${guild.id}) added the bot. Owner: ${guild.owner!.user.tag} (${
guild.owner!.user.id
}). Updated emote registry.`
);
if (Config.systemLogsChannel) {
const channel = client.channels.cache.get(Config.systemLogsChannel);
if (channel && channel.type === "text") {
(channel as TextChannel).send(
`TravBot joined: \`${guild.name}\`. The owner of this guild is: \`${guild.owner!.user.tag}\` (\`${
guild.owner!.user.id
}\`)`
);
} else {
console.warn(`${Config.systemLogsChannel} is not a valid text channel for system logs!`);
}
}
updateGlobalEmoteRegistry();
}
});

View File

@ -1,10 +1,24 @@
import Event from "../core/event";
import $ from "../core/lib";
import {updateGlobalEmoteRegistry} from "../core/lib";
import {client} from "../index";
import {Config} from "../core/structures";
import {TextChannel} from "discord.js";
export default new Event<"guildDelete">({
on() {
$.log("Updated emote registry.");
on(guild) {
$.log(`[GUILD LEAVE] ${guild.name} (${guild.id}) removed the bot. Updated emote registry.`);
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.`);
} else {
console.warn(`${Config.systemLogsChannel} is not a valid text channel for system logs!`);
}
}
updateGlobalEmoteRegistry();
}
});

View File

@ -0,0 +1,77 @@
import Event from "../core/event";
import $, {parseVars} from "../core/lib";
import {createCanvas, loadImage, Canvas} from "canvas";
import {Storage} from "../core/structures";
import {TextChannel, MessageAttachment} from "discord.js";
function applyText(canvas: Canvas, text: string) {
const ctx = canvas.getContext("2d");
let fontSize = 70;
do {
ctx.font = `${(fontSize -= 10)}px sans-serif`;
} while (ctx.measureText(text).width > canvas.width - 300);
return ctx.font;
}
export default new Event<"guildMemberAdd">({
async on(member) {
const {welcomeType, welcomeChannel, welcomeMessage} = Storage.getGuild(member.guild.id);
if (welcomeChannel) {
const channel = member.guild.channels.cache.get(welcomeChannel);
if (channel && channel.type === "text") {
if (welcomeType === "graphical") {
const canvas = createCanvas(700, 250);
const ctx = canvas.getContext("2d");
const background = await loadImage(
"https://raw.githubusercontent.com/keanuplayz/TravBot/dev/assets/wallpaper.png"
);
ctx.drawImage(background, 0, 0, canvas.width, canvas.height);
ctx.strokeStyle = "#74037b";
ctx.strokeRect(0, 0, canvas.width, canvas.height);
ctx.font = "28px sans-serif";
ctx.fillStyle = "#ffffff";
ctx.fillText("Welcome to the server,", canvas.width / 2.5, canvas.height / 3.5);
ctx.font = applyText(canvas, member.displayName);
ctx.fillStyle = "#ffffff";
ctx.fillText(`${member.displayName}!`, canvas.width / 2.5, canvas.height / 1.5);
ctx.beginPath();
ctx.arc(125, 125, 100, 0, Math.PI * 2, true);
ctx.closePath();
ctx.clip();
const avatarURL =
member.user.avatarURL({
dynamic: true,
size: 2048,
format: "png"
}) ?? member.user.defaultAvatarURL;
const avatar = await loadImage(avatarURL);
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);
} else if (welcomeType === "text") {
(channel as TextChannel).send(
parseVars(
welcomeMessage ||
"Say hello to `%user%`, everyone! We all need a warm welcome sometimes :D",
{
user: member.user.tag
}
)
);
}
} else {
$.error(`"${welcomeChannel}" is not a valid text channel ID!`);
}
}
}
});

View File

@ -4,12 +4,16 @@ import {hasPermission, getPermissionLevel, PermissionNames} from "../core/permis
import {Permissions, Collection} from "discord.js";
import {getPrefix} from "../core/structures";
import $, {replyEventListeners} from "../core/lib";
import quote from "../modules/message_embed";
// It's a rather hacky solution, but since there's no top-level await, I just have to make the loading conditional.
let commands: Collection<string, Command> | null = null;
export default new Event<"message">({
async on(message) {
if (message.content.toLowerCase().includes("remember to drink water")) {
message.react("🚱");
}
// Load commands if it hasn't already done so. Luckily, it's called once at most.
if (!commands) commands = await loadCommands();
@ -28,6 +32,10 @@ export default new Event<"message">({
const clientUser = message.client.user;
let usesBotSpecificPrefix = false;
if (!message.content.startsWith(prefix)) {
return quote(message);
}
// If the client user exists, check if it starts with the bot-specific prefix.
if (clientUser) {
// If the prefix starts with the bot-specific prefix, go off that instead (these two options must mutually exclude each other).

View File

@ -7,7 +7,9 @@ import {updateGlobalEmoteRegistry} from "../core/lib";
export default new Event<"ready">({
once() {
if (client.user) {
$.ready(`Logged in as ${client.user.username}#${client.user.discriminator}.`);
$.ready(
`Logged in as ${client.user.tag}, ready to serve ${client.users.cache.size} users in ${client.guilds.cache.size} servers..`
);
client.user.setActivity({
type: "LISTENING",
name: `${Config.prefix}help`

View File

@ -1,4 +1,4 @@
import {Client} from "discord.js";
import * as discord from "discord.js";
import setup from "./setup";
import {Config} from "./core/structures";
import {loadCommands} from "./core/command";
@ -6,9 +6,37 @@ import {loadEvents} from "./core/event";
import "discord.js-lavalink-lib";
import LavalinkMusic from "discord.js-lavalink-lib";
declare module "discord.js" {
interface Presence {
patch(data: any): void;
}
}
// The terrible hacks were written by none other than The Noble Programmer On The White PC.
// NOTE: Terrible hack ahead!!! In order to reduce the memory usage of the bot
// we only store the information from presences that we actually end up using,
// which currently is only the (online/idle/dnd/offline/...) status (see
// `src/commands/info.ts`). What data is retrieved from the `data` object
// (which contains the data received from the Gateway) and how can be seen
// here:
// <https://github.com/discordjs/discord.js/blob/cee6cf70ce76e9b06dc7f25bfd77498e18d7c8d4/src/structures/Presence.js#L81-L110>.
const oldPresencePatch = discord.Presence.prototype.patch;
discord.Presence.prototype.patch = function patch(data: any) {
oldPresencePatch.call(this, {status: data.status});
};
// 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();
export const client = new discord.Client();
// NOTE: Terrible hack continued!!! Unfortunately we can't receive the presence
// data at all when the GUILD_PRESENCES intent is disabled, so while we do
// waste network bandwidth and the CPU time for decoding the incoming packets,
// the function which handles those packets is NOP-ed out, which, among other
// things, skips the code which caches the referenced users in the packet. See
// <https://github.com/discordjs/discord.js/blob/cee6cf70ce76e9b06dc7f25bfd77498e18d7c8d4/src/client/actions/PresenceUpdate.js#L7-L41>.
(client["actions"] as any)["PresenceUpdate"].handle = () => {};
(client as any).music = LavalinkMusic(client, {
lavalink: {

View File

@ -0,0 +1,60 @@
import { client } from '..'
import { Message, TextChannel, APIMessage, MessageEmbed } from 'discord.js'
import { getPrefix } from '../core/structures'
import { DiscordAPIError } from 'discord.js'
export default async function quote(message: Message) {
if (message.author.bot) return
// const message_link_regex = message.content.match(/(!)?https?:\/\/\w+\.com\/channels\/(\d+)\/(\d+)\/(\d+)/)
const message_link_regex = message.content.match(/([<!]?)https?:\/\/(?:ptb\.|canary\.|)discord(?:app)?\.com\/channels\/(\d+)\/(\d+)\/(\d+)(>?)/)
if (message_link_regex == null) return
const [, char, guildID, channelID, messageID] = message_link_regex
if (char || message.content.startsWith(getPrefix(message.guild))) return
try {
const channel = client.guilds.cache.get(guildID)?.channels.cache.get(channelID) as TextChannel
const link_message = await channel.messages.fetch(messageID)
let rtmsg: string | APIMessage = ''
if (link_message.cleanContent) {
rtmsg = new APIMessage(message.channel as TextChannel, {
content: link_message.cleanContent,
disableMentions: 'all',
files: link_message.attachments.array()
})
}
const embeds = [
...link_message.embeds.filter(v => v.type == 'rich'),
...link_message.attachments.values()
]
/// @ts-ignore
if (!link_message.cleanContent && embeds.empty) {
const Embed = new MessageEmbed()
.setDescription('🚫 The message is empty.')
return message.channel.send(Embed)
}
const infoEmbed = new MessageEmbed()
.setAuthor(
link_message.author.username,
link_message.author.displayAvatarURL({format: 'png', dynamic: true, size: 4096}))
.setTimestamp(link_message.createdTimestamp)
.setDescription(`${link_message.cleanContent}\n\nSent in **${link_message.guild?.name}** | <#${link_message.channel.id}> ([link](https://discord.com/channels/${guildID}/${channelID}/${messageID}))`);
if (link_message.attachments.size !== 0) {
const image = link_message.attachments.first();
/// @ts-ignore
infoEmbed.setImage(image.url);
}
await message.channel.send(infoEmbed)
} catch (error) {
if (error instanceof DiscordAPIError) {
message.channel.send("I don't have access to this channel, or something else went wrong.")
}
return console.error(error)
}
}

View File

@ -2,7 +2,7 @@
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"target": "ES6",
"target": "es2019",
"module": "CommonJS",
"moduleResolution": "node",
"esModuleInterop": true,
@ -11,7 +11,8 @@
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"removeComments": true
"removeComments": true,
"sourceMap": true
},
"exclude": ["test"]
}