638 lines
17 KiB
JavaScript
638 lines
17 KiB
JavaScript
const Command = require("../lib/command.js");
|
|
const CATEGORY = "utility";
|
|
|
|
const ICON_BASE = "https://cdn.discordapp.com/icons/";
|
|
const AVATAR_BASE = "https://cdn.discordapp.com/avatars/";
|
|
const SPLASH_BASE = "https://cdn.discordapp.com/splashes/";
|
|
const BANNER_BASE = "https://cdn.discordapp.com/banners/";
|
|
const EMOTE_BASE = "https://cdn.discordapp.com/emojis/";
|
|
|
|
const STATUS_ICONS = {
|
|
online: "<:online:493173082421461002>",
|
|
idle: "<:idle:493173082006093836>",
|
|
dnd: "<:dnd:493173082261815307>",
|
|
offline: "<:offline:493173082253426688>",
|
|
};
|
|
|
|
const EMOJI_SETS = {
|
|
blobs: {
|
|
prefix:
|
|
"https://cdn.jsdelivr.net/gh/googlefonts/noto-emoji@e456654119cc3a5f9bebb7bbd00512456f983d2d/svg/emoji_u",
|
|
sep: "_",
|
|
suffix: ".svg",
|
|
},
|
|
noto: {
|
|
prefix: "gitcdn.xyz/repo/googlefonts/noto-emoji/master/svg/emoji_u",
|
|
sep: "_",
|
|
suffix: ".svg",
|
|
},
|
|
twemoji: {
|
|
prefix: "https://twemoji.maxcdn.com/v/latest/svg/",
|
|
sep: "-",
|
|
suffix: ".svg",
|
|
},
|
|
mustd: {
|
|
prefix:
|
|
"https://cdn.jsdelivr.net/gh/Mstrodl/mutant-standard-mirror@0435227d9d8c0d6a346c8ae4c12b08a5cdc37041/emoji/",
|
|
sep: "-",
|
|
suffix: ".svg",
|
|
},
|
|
apple: {
|
|
prefix: "https://intrnl.github.io/assetsEmoji/AppleColor/emoji_u",
|
|
sep: "_",
|
|
suffix: ".png",
|
|
},
|
|
facebook: {
|
|
prefix: "https://intrnl.github.io/assetsEmoji/facebook/emoji_u",
|
|
sep: "_",
|
|
suffix: ".png",
|
|
},
|
|
};
|
|
EMOJI_SETS["noto-old"] = EMOJI_SETS.blobs;
|
|
EMOJI_SETS.mutant = EMOJI_SETS.mustd;
|
|
EMOJI_SETS.mutstd = EMOJI_SETS.mustd;
|
|
EMOJI_SETS.ms = EMOJI_SETS.mustd;
|
|
EMOJI_SETS.twitter = EMOJI_SETS.twemoji;
|
|
EMOJI_SETS.fb = EMOJI_SETS.facebook;
|
|
|
|
const CUSTOM_EMOTE_REGEX = /<(?:\u200b|&)?(a)?:(\w+):(\d+)>/;
|
|
|
|
const fetch = require("node-fetch");
|
|
const sharp = require("sharp");
|
|
|
|
const {hastebin, lookupUser} = require("../lib/utils.js");
|
|
const {getNamesFromString} = require("../lib/unicode.js");
|
|
|
|
const avatar = new Command("avatar");
|
|
avatar.category = CATEGORY;
|
|
avatar.helpText = "Get avatar of a user";
|
|
avatar.usage = "<user>";
|
|
avatar.callback = async function (msg, line) {
|
|
if (line == "--server" || line == "--guild") {
|
|
if (!msg.guildID) {
|
|
return "`--server/--guild` can only be used within guilds.";
|
|
} else {
|
|
const guild = msg.channel.guild || hf.bot.guilds.get(msg.guildID);
|
|
const url = `${ICON_BASE}${guild.id}/${guild.icon}.${
|
|
guild.icon.startsWith("a_") ? "gif?size=1024&_=.gif" : "png?size=1024"
|
|
}`;
|
|
return {
|
|
embeds: [
|
|
{
|
|
title: "Server Icon",
|
|
url,
|
|
image: {
|
|
url,
|
|
},
|
|
},
|
|
],
|
|
};
|
|
}
|
|
} else if (line) {
|
|
const user = await lookupUser(msg, line);
|
|
if (
|
|
user == "No results" ||
|
|
user == "Canceled" ||
|
|
user == "Request timed out"
|
|
) {
|
|
return user;
|
|
} else {
|
|
let member = user;
|
|
const guild = msg.channel.guild || hf.bot.guilds.get(msg.guildID);
|
|
if (guild) {
|
|
if (guild.members.has(user.id)) {
|
|
member = guild.members.get(user.id);
|
|
} else {
|
|
const fetched = await guild.fetchMembers({
|
|
userIDs: [user.id],
|
|
});
|
|
member = fetched[0];
|
|
}
|
|
}
|
|
|
|
const baseEmbed = {
|
|
title: `Avatar for \`${member.username}#${member.discriminator}\``,
|
|
};
|
|
|
|
const normalAvatar = member.user.avatar;
|
|
const guildAvatar = member.avatar;
|
|
|
|
const normalUrl =
|
|
AVATAR_BASE +
|
|
member.id +
|
|
"/" +
|
|
normalAvatar +
|
|
"." +
|
|
(normalAvatar.startsWith("a_")
|
|
? "gif?size=1024&_=.gif"
|
|
: "png?size=1024");
|
|
const guildUrl =
|
|
guildAvatar &&
|
|
"https://cdn.discordapp.com/guilds/" +
|
|
guild.id +
|
|
"/users/" +
|
|
member.id +
|
|
"/avatars/" +
|
|
guildAvatar +
|
|
"." +
|
|
(guildAvatar.startsWith("a_")
|
|
? "gif?size=1024&_=.gif"
|
|
: "png?size=1024");
|
|
|
|
baseEmbed.description =
|
|
`[Normal avatar](${normalUrl})` +
|
|
(guild && guildAvatar ? `\n[Guild avatar](${guildUrl})` : "");
|
|
baseEmbed.url = normalUrl;
|
|
|
|
const guildEmbed = {...baseEmbed};
|
|
baseEmbed.image = {url: normalUrl};
|
|
guildEmbed.image = {url: guildUrl};
|
|
|
|
return {
|
|
embeds: [baseEmbed, guildAvatar && guildEmbed].filter((x) => x != null),
|
|
};
|
|
}
|
|
} else {
|
|
const guild = msg.channel.guild || hf.bot.guilds.get(msg.guildID);
|
|
|
|
const baseEmbed = {
|
|
title: `Avatar for \`${msg.author.username}#${msg.author.discriminator}\``,
|
|
};
|
|
|
|
const normalAvatar = msg.author.avatar;
|
|
const guildAvatar = msg.member?.avatar;
|
|
|
|
const normalUrl =
|
|
AVATAR_BASE +
|
|
msg.author.id +
|
|
"/" +
|
|
normalAvatar +
|
|
"." +
|
|
(normalAvatar.startsWith("a_")
|
|
? "gif?size=1024&_=.gif"
|
|
: "png?size=1024");
|
|
const guildUrl =
|
|
guildAvatar &&
|
|
"https://cdn.discordapp.com/guilds/" +
|
|
guild.id +
|
|
"/users/" +
|
|
msg.author.id +
|
|
"/avatars/" +
|
|
guildAvatar +
|
|
"." +
|
|
(guildAvatar.startsWith("a_")
|
|
? "gif?size=1024&_=.gif"
|
|
: "png?size=1024");
|
|
|
|
baseEmbed.description =
|
|
`[Normal avatar](${normalUrl})` +
|
|
(guild && guildAvatar ? `\n[Guild avatar](${guildUrl})` : "");
|
|
baseEmbed.url = normalUrl;
|
|
|
|
const guildEmbed = {...baseEmbed};
|
|
baseEmbed.image = {url: normalUrl};
|
|
guildEmbed.image = {url: guildUrl};
|
|
|
|
return {
|
|
embeds: [baseEmbed, guildAvatar && guildEmbed].filter((x) => x != null),
|
|
};
|
|
}
|
|
};
|
|
hf.registerCommand(avatar);
|
|
|
|
const banner = new Command("banner");
|
|
banner.category = CATEGORY;
|
|
banner.helpText = "Get banner of a user";
|
|
banner.usage = "<user>";
|
|
banner.callback = async function (msg, line) {
|
|
let id = msg.author.id;
|
|
|
|
if (line == "--server" || line == "--guild") {
|
|
if (!msg.guildID) {
|
|
return "`--server/--guild` can only be used within guilds.";
|
|
} else {
|
|
const guild = msg.channel.guild || hf.bot.guilds.get(msg.guildID);
|
|
|
|
if (!guild.banner) return "This guild does not have a banner.";
|
|
|
|
const url = `${BANNER_BASE}${guild.id}/${guild.banner}.${
|
|
guild.banner.startsWith("a_") ? "gif?size=1024&_=.gif" : "png?size=1024"
|
|
}`;
|
|
return {
|
|
embeds: [
|
|
{
|
|
title: "Server Banner",
|
|
url,
|
|
image: {
|
|
url,
|
|
},
|
|
},
|
|
],
|
|
};
|
|
}
|
|
} else if (line) {
|
|
const lookup = await lookupUser(msg, line);
|
|
if (
|
|
lookup == "No results" ||
|
|
lookup == "Canceled" ||
|
|
lookup == "Request timed out"
|
|
) {
|
|
return lookup;
|
|
} else {
|
|
id = lookup.id;
|
|
}
|
|
}
|
|
|
|
const user = await hf.bot.requestHandler.request("GET", "/users/" + id, true);
|
|
|
|
if (!user.banner) return "This user does not have a banner.";
|
|
|
|
const url = `${BANNER_BASE}${user.id}/${user.banner}.${
|
|
user.banner.startsWith("a_") ? "gif?size=1024&_=.gif" : "png?size=1024"
|
|
}`;
|
|
return {
|
|
embeds: [
|
|
{
|
|
title: `Banner for \`${user.username}#${user.discriminator}\``,
|
|
url,
|
|
image: {
|
|
url,
|
|
},
|
|
},
|
|
],
|
|
};
|
|
};
|
|
hf.registerCommand(banner);
|
|
|
|
const lookupinvite = new Command("lookupinvite");
|
|
lookupinvite.category = CATEGORY;
|
|
lookupinvite.helpText = "Lookup an invite";
|
|
lookupinvite.usage = "<invite code>";
|
|
lookupinvite.addAlias("linvite");
|
|
lookupinvite.callback = async function (msg, line) {
|
|
if (!line) {
|
|
return "No arguments passed.";
|
|
}
|
|
|
|
line = line.replace(/(https?:\/\/)?discord\.gg\//, "");
|
|
|
|
const invite = await hf.bot.requestHandler.request(
|
|
"GET",
|
|
"/invites/" + line + "?with_counts=1"
|
|
);
|
|
if (!invite) return ":warning: No data returned.";
|
|
|
|
if (invite.message) {
|
|
if (invite.message == "Unknown Invite") {
|
|
return "Invite provided is not valid.";
|
|
} else {
|
|
return `:warning: Got error \`${invite.code}: "${invite.message}"\``;
|
|
}
|
|
} else {
|
|
const embed = {
|
|
title: `Invite Info: \`${invite.code}\``,
|
|
description: invite.description,
|
|
fields: [
|
|
{
|
|
name: "Guild",
|
|
value: `**${invite.guild.name}** (${invite.guild.id})`,
|
|
inline: true,
|
|
},
|
|
{
|
|
name: "Channel",
|
|
value: `**${invite.channel.name}** (${invite.channel.id})`,
|
|
inline: true,
|
|
},
|
|
{
|
|
name: "Member Count",
|
|
value: `${STATUS_ICONS.online}${invite.approximate_presence_count} online\t\t${STATUS_ICONS.offline}${invite.approximate_member_count} members`,
|
|
inline: false,
|
|
},
|
|
{
|
|
name: "Features",
|
|
value:
|
|
invite.guild.features.length > 0
|
|
? invite.guild.features
|
|
.map((feature) =>
|
|
feature
|
|
.split("_")
|
|
.map((x) => x[0] + x.substring(1).toLowerCase())
|
|
.join(" ")
|
|
)
|
|
.join(", ")
|
|
: "None",
|
|
inline: false,
|
|
},
|
|
],
|
|
thumbnail: {
|
|
url:
|
|
invite.guild.icon &&
|
|
`${ICON_BASE}${invite.guild.id}/${invite.guild.icon}.${
|
|
invite.guild.icon.startsWith("a_")
|
|
? "gif?size=1024&_=.gif"
|
|
: "png?size=1024"
|
|
}`,
|
|
},
|
|
};
|
|
|
|
if (invite.inviter) {
|
|
embed.fields.push({
|
|
name: "Inviter",
|
|
value: `**${invite.inviter.username}#${invite.inviter.discriminator}** (${invite.inviter.id})`,
|
|
inline: true,
|
|
});
|
|
}
|
|
|
|
if (invite.guild.icon || invite.guild.splash || invite.guild.banner) {
|
|
embed.fields.push({
|
|
name: "\u200b",
|
|
value: `${
|
|
invite.guild.icon
|
|
? `[Icon](${ICON_BASE}${invite.guild.id}/${invite.guild.icon}.${
|
|
invite.guild.icon.startsWith("a_")
|
|
? "gif?size=1024"
|
|
: "png?size=1024"
|
|
})`
|
|
: ""
|
|
}${
|
|
invite.guild.splash
|
|
? `${invite.guild.icon ? " | " : ""}[Splash](${SPLASH_BASE}${
|
|
invite.guild.id
|
|
}/${invite.guild.splash}.png?size=2048)`
|
|
: ""
|
|
}${
|
|
invite.guild.banner
|
|
? `${
|
|
invite.guild.icon || invite.guild.splash ? " | " : ""
|
|
}[Banner](${BANNER_BASE}${invite.guild.id}/${
|
|
invite.guild.banner
|
|
}.png?size=2048)`
|
|
: ""
|
|
}`,
|
|
inline: false,
|
|
});
|
|
}
|
|
|
|
if (invite.guild.splash) {
|
|
embed.image = {
|
|
url: `${SPLASH_BASE}${invite.guild.id}/${invite.guild.splash}.png?size=256`,
|
|
};
|
|
}
|
|
|
|
return {embed};
|
|
}
|
|
};
|
|
hf.registerCommand(lookupinvite);
|
|
|
|
const snowflake = new Command("snowflake");
|
|
snowflake.category = CATEGORY;
|
|
snowflake.helpText = "Converts a snowflake ID into readable time.";
|
|
snowflake.usage = "<--twitter> [snowflake]";
|
|
snowflake.callback = function (msg, line) {
|
|
let twitter = false;
|
|
if (line.startsWith("--twitter")) {
|
|
twitter = true;
|
|
line.replace("--twitter ", "");
|
|
}
|
|
const num = parseInt(line);
|
|
if (!isNaN(num)) {
|
|
let binary = num.toString(2);
|
|
binary = "0".repeat(64 - binary.length) + binary;
|
|
const timestamp =
|
|
parseInt(binary.substr(0, 42), 2) +
|
|
(twitter ? 1288834974657 : 1420070400000);
|
|
|
|
return `The timestamp for \`${line}\` is ${new Date(
|
|
timestamp
|
|
).toUTCString()}`;
|
|
} else {
|
|
return "Argument provided is not a number.";
|
|
}
|
|
};
|
|
hf.registerCommand(snowflake);
|
|
|
|
const USER_FLAGS = [
|
|
"STAFF",
|
|
"PARTNER",
|
|
"HYPESQUAD",
|
|
"BUG_HUNTER_LEVEL_1",
|
|
"MFA_SMS",
|
|
"PREMIUM_PROMO_DISMISSED",
|
|
"HYPESQUAD_ONLINE_HOUSE_1",
|
|
"HYPESQUAD_ONLINE_HOUSE_2",
|
|
"HYPESQUAD_ONLINE_HOUSE_3",
|
|
"PREMIUM_EARLY_SUPPORTER",
|
|
"TEAM_PSEUDO_USER",
|
|
"<Internal Application (Partner, etc) Flag>",
|
|
"SYSTEM",
|
|
"HAS_UNREAD_URGENT_MESSAGES",
|
|
"BUG_HUNTER_LEVEL_2",
|
|
"UNDERAGE_DELETED",
|
|
"VERIFIED_BOT",
|
|
"VERIFIED_DEVELOPER",
|
|
"CERTIFIED_MODERATOR",
|
|
"BOT_HTTP_INTERACTIONS",
|
|
"SPAMMER",
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
"<Verified Phone Number (speculative)>",
|
|
];
|
|
|
|
function flagFromInt(int) {
|
|
const bits = int.toString(2);
|
|
const splitBits = bits.split("").reverse();
|
|
|
|
const reassignedBits = {};
|
|
|
|
for (const shift in splitBits) {
|
|
reassignedBits[shift] = splitBits[shift];
|
|
}
|
|
|
|
const flags = Object.keys(reassignedBits).filter(
|
|
(bit) => reassignedBits[bit] == 1
|
|
);
|
|
|
|
let out = "";
|
|
|
|
for (const flag of flags) {
|
|
out +=
|
|
(USER_FLAGS[flag] || "<Undocumented Flag>") +
|
|
` (1 << ${flag}, ${1n << BigInt(flag)})\n`;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
const flagdump = new Command("flagdump");
|
|
flagdump.category = CATEGORY;
|
|
flagdump.helpText = "Dumps Discord user flags.";
|
|
flagdump.usage = "[flags or user mention]";
|
|
flagdump.callback = async function (msg, line) {
|
|
const num = parseInt(line);
|
|
if (/<@!?([0-9]*)>/.test(line)) {
|
|
const id = line.match(/<@?!([0-9]*)>/)[1];
|
|
const guild = msg.channel.guild || hf.bot.guilds.get(msg.guildID);
|
|
let user = guild && (await guild.fetchMembers({userIDs: [id]}));
|
|
if (!user || !user[0]) {
|
|
user = hf.bot.users.get(id);
|
|
} else {
|
|
user = user[0].user;
|
|
}
|
|
|
|
if (!user) {
|
|
return "User not cached.";
|
|
} else {
|
|
return `\`${user.username}#${
|
|
user.discriminator
|
|
}\`'s public flags:\n\`\`\`${flagFromInt(user.publicFlags)}\`\`\``;
|
|
}
|
|
} else if (!isNaN(num)) {
|
|
return `\`\`\`\n${flagFromInt(num)}\`\`\``;
|
|
} else {
|
|
return `\`${msg.author.username}#${
|
|
msg.author.discriminator
|
|
}\`'s public flags:\n\`\`\`${flagFromInt(msg.author.publicFlags)}\`\`\``;
|
|
}
|
|
};
|
|
hf.registerCommand(flagdump);
|
|
|
|
const emojiNames = [];
|
|
fetch("https://unpkg.com/emoji.json/emoji.json")
|
|
.then((res) => res.json())
|
|
.then((body) =>
|
|
body.map(
|
|
(emoji) => (emojiNames[emoji.char] = emoji.name.replace(/ /g, "_"))
|
|
)
|
|
);
|
|
|
|
const jumbo = new Command("jumbo");
|
|
jumbo.category = CATEGORY;
|
|
jumbo.helpText = "Gets the raw image of an emoji.";
|
|
jumbo.usage = "<emoji>";
|
|
jumbo.addAlias("e");
|
|
jumbo.addAlias("emote");
|
|
jumbo.addAlias("emoji");
|
|
jumbo.callback = async function (msg, line) {
|
|
if (CUSTOM_EMOTE_REGEX.test(line)) {
|
|
const [_, animatedFlag, name, id] = line.match(CUSTOM_EMOTE_REGEX);
|
|
const animated = animatedFlag === "a";
|
|
|
|
return {
|
|
embeds: [
|
|
{
|
|
title: `:${name}: - \`${id}\``,
|
|
url: `${EMOTE_BASE}${id}.${animated ? "gif" : "png"}?v=1`,
|
|
image: {
|
|
url: `${EMOTE_BASE}${id}.${animated ? "gif" : "png"}?v=1`,
|
|
},
|
|
},
|
|
],
|
|
};
|
|
} else {
|
|
let setName = "twemoji";
|
|
for (const set in EMOJI_SETS) {
|
|
if (line.startsWith(`--${set} `)) {
|
|
setName = set;
|
|
line = line.replace(`--${set} `, "");
|
|
}
|
|
}
|
|
|
|
const set = EMOJI_SETS[setName];
|
|
|
|
const emoji = Array.from(line)
|
|
.map((char) => char.codePointAt().toString(16))
|
|
.join(set.sep);
|
|
const url = set.prefix + emoji + set.suffix;
|
|
|
|
const name = emojiNames[line]
|
|
? `\\:${emojiNames[line]}\\:`
|
|
: await getNamesFromString(line)[0][1];
|
|
|
|
const statusCode = await fetch(url, {method: "HEAD"}).then(
|
|
(res) => res.status
|
|
);
|
|
|
|
if (statusCode !== 200) {
|
|
return "Emoji not found. The emoji set chosen might not have this emote as an image.";
|
|
}
|
|
|
|
if (set.suffix == ".svg") {
|
|
const svg = await fetch(url).then((res) => res.buffer());
|
|
const converted = await sharp(svg, {density: 2400})
|
|
.resize(1024)
|
|
.toBuffer();
|
|
|
|
return {
|
|
embeds: [
|
|
{
|
|
title: `${name} (${emoji.toUpperCase().replace(/[-_]/g, ", ")})`,
|
|
url,
|
|
image: {
|
|
url: "attachment://emoji.png",
|
|
},
|
|
},
|
|
],
|
|
file: {
|
|
file: converted,
|
|
name: "emoji.png",
|
|
},
|
|
};
|
|
} else {
|
|
return {
|
|
embeds: [
|
|
{
|
|
title: `${name} (${emoji.toUpperCase().replace(/[-_]/g, ", ")})`,
|
|
url,
|
|
image: {
|
|
url,
|
|
},
|
|
},
|
|
],
|
|
};
|
|
}
|
|
}
|
|
};
|
|
hf.registerCommand(jumbo);
|
|
|
|
const charinfo = new Command("charinfo");
|
|
charinfo.category = CATEGORY;
|
|
charinfo.description = "Get information about a set of characters.";
|
|
charinfo.usage = "[characters]";
|
|
charinfo.addAlias("char");
|
|
charinfo.callback = async function (msg, line) {
|
|
const names = await getNamesFromString(line);
|
|
const chars = [...line];
|
|
const lines = names
|
|
.map(
|
|
([code, name], index) =>
|
|
`\`\\u${code}\`: ${name} - ${chars[index]} - <http://www.fileformat.info/info/unicode/char/${code}>`
|
|
)
|
|
.join("\n");
|
|
|
|
if (lines.length > 2000) {
|
|
return "Output too long: " + (await hastebin(lines));
|
|
} else {
|
|
return lines;
|
|
}
|
|
};
|
|
hf.registerCommand(charinfo);
|