big refactor to keep motivation

This commit is contained in:
Cynthia Foxwell 2024-07-23 16:10:17 -06:00
parent 8f89815106
commit 8bb2ad3012
46 changed files with 3949 additions and 4766 deletions

View file

@ -1,5 +1,6 @@
{
"semi": true,
"bracketSpacing": false,
"endOfLine": "lf"
"semi": true,
"bracketSpacing": false,
"endOfLine": "lf",
"printWidth": 120
}

View file

@ -1,5 +1,4 @@
const Dysnomia = require("@projectdysnomia/dysnomia");
const logger = require("./lib/logger.js");
const {Client, Collection, Channel, Permission} = require("@projectdysnomia/dysnomia");
const fs = require("node:fs");
const {resolve} = require("node:path");
const sqlite3 = require("sqlite3");
@ -8,22 +7,25 @@ const {instead, before} = require("spitroast");
const config = require("../config.json");
const apikeys = require("../apikeys.json");
const logger = require("./lib/logger.js");
const events = require("./lib/events.js");
const timer = require("./lib/timer.js");
const Command = require("./lib/command.js");
const InteractionCommand = require("./lib/interactionCommand.js");
const bot = new Dysnomia.Client(config.token, {
const {APIEndpoints, Intents, ApplicationCommandTypes, GatewayOPCodes} = require("./util/dconstants.js");
const bot = new Client(config.token, {
defaultImageFormat: "png",
defaultImageSize: 1024,
gateway: {
intents: Object.values(Dysnomia.Constants.Intents),
intents: Object.values(Intents),
},
restMode: true,
});
const commands = new Dysnomia.Collection();
const interactionCommands = new Dysnomia.Collection();
const commands = new Collection();
const interactionCommands = new Collection();
const database = new sqlite3.Database(resolve(__dirname, "..", "database.db"));
@ -33,9 +35,7 @@ function registerCommand(cmdObj) {
const aliases = cmdObj.getAliases();
logger.info(
"hf:cmd",
`Registered command '${cmdObj.name}'${
aliases.length > 0 ? ` (aliases: ${aliases.join(", ")})` : ""
}`
`Registered command '${cmdObj.name}'${aliases.length > 0 ? ` (aliases: ${aliases.join(", ")})` : ""}`
);
} else if (cmdObj instanceof InteractionCommand) {
interactionCommands.set(cmdObj.name, cmdObj);
@ -55,57 +55,51 @@ global.hf = {
database,
};
const {formatUsername} = require("./lib/utils.js");
const {formatUsername} = require("./util/misc.js");
const CommandDispatcher = require("./lib/commandDispatcher.js");
const {InteractionDispatcher} = require("./lib/interactionDispatcher.js");
const {hasFlag} = require("./lib/guildData.js");
for (const file of fs.readdirSync(resolve(__dirname, "modules"))) {
require(resolve(__dirname, "modules", file));
logger.info("hf:modules", `Loaded module: '${file}'`);
try {
require(resolve(__dirname, "modules", file));
logger.info("hf:modules", `Loaded module: "${file}"`);
} catch (err) {
logger.error("hf:modules", `Failed to load "${file}": ${err}`);
}
}
bot.on("messageCreate", async (msg) => {
try {
// fix DMs cause of gateway v8 changes
if (
!(msg.channel instanceof Dysnomia.Channel) &&
msg.author.id != bot.user.id &&
!msg.guildID
) {
if (!(msg.channel instanceof Channel) && msg.author.id != bot.user.id && !msg.guildID) {
const newChannel = await bot.getDMChannel(msg.author.id);
if (msg.channel.id == newChannel.id) msg.channel = newChannel;
}
if (!(msg.channel instanceof Dysnomia.Channel)) return;
if (!(msg.channel instanceof Channel)) return;
if (msg.author.bot && msg.guildID) {
const canBot = await hasFlag(msg.guildID, "replyToBots");
if (!canBot) return;
}
await CommandDispatcher(msg);
} catch (err) {
const stack = (err?.stack ?? err.message).split("\n");
const error = stack.shift();
logger.error(
"hf:main",
`Failed to dispatch command: ${error}\n\t${stack.join("\n\t")}`
);
logger.error("hf:main", `Failed to dispatch command: ${error}\n\t${stack.join("\n\t")}`);
}
});
bot.on("messageUpdate", async (msg, oldMsg) => {
try {
const oneDay = Date.now() - 86400000;
if (
msg.timestamp > oneDay &&
!msg.hasRan &&
oldMsg &&
oldMsg.content !== msg.content
) {
if (msg.timestamp > oneDay && !msg.hasRan && oldMsg && oldMsg.content !== msg.content) {
await CommandDispatcher(msg);
}
} catch (err) {
const stack = (err?.stack ?? err.message).split("\n");
const error = stack.shift();
logger.error(
"hf:main",
`Failed to dispatch command update: ${error}\n\t${stack.join("\n\t")}`
);
logger.error("hf:main", `Failed to dispatch command update: ${error}\n\t${stack.join("\n\t")}`);
}
});
bot.on("messageReactionAdd", async (msg, reaction, reactor) => {
@ -114,7 +108,7 @@ bot.on("messageReactionAdd", async (msg, reaction, reactor) => {
try {
let channel = msg.channel;
if (!(channel instanceof Dysnomia.Channel)) {
if (!(channel instanceof Channel)) {
const newChannel = hf.bot.getChannel(channel.id);
if (newChannel) {
channel = newChannel;
@ -137,10 +131,7 @@ bot.on("messageReactionAdd", async (msg, reaction, reactor) => {
} catch (err) {
const stack = (err?.stack ?? err.message).split("\n");
const error = stack.shift();
logger.error(
"hf:main",
`Failed to self-delete message: ${error}\n\t${stack.join("\n\t")}`
);
logger.error("hf:main", `Failed to self-delete message: ${error}\n\t${stack.join("\n\t")}`);
}
});
bot.on("interactionCreate", async (interaction) => {
@ -149,21 +140,13 @@ bot.on("interactionCreate", async (interaction) => {
} catch (err) {
const stack = (err?.stack ?? err.message).split("\n");
const error = stack.shift();
logger.error(
"hf:main",
`Failed to dispatch interaction command: ${error}\n\t${stack.join(
"\n\t"
)}`
);
logger.error("hf:main", `Failed to dispatch interaction command: ${error}\n\t${stack.join("\n\t")}`);
}
});
bot.once("ready", async () => {
logger.info("hf:main", "Connected to Discord.");
logger.info(
"hf:main",
`Logged in as: ${formatUsername(bot.user)} (${bot.user.id})`
);
logger.info("hf:main", `Logged in as: ${formatUsername(bot.user)} (${bot.user.id})`);
const channel = await bot.getDMChannel(config.owner_id);
if (channel) {
@ -202,25 +185,18 @@ bot.once("ready", async () => {
formattedCommand.contexts.push(1, 2);
}
if (command.type === Dysnomia.Constants.ApplicationCommandTypes.CHAT_INPUT)
if (command.type === ApplicationCommandTypes.CHAT_INPUT)
formattedCommand.name = formattedCommand.name.toLowerCase();
if (command.permissions !== undefined) {
formattedCommand.default_member_permissions =
command.permissions instanceof Dysnomia.Permission
? String(command.permissions.allow)
: String(command.permissions);
command.permissions instanceof Permission ? String(command.permissions.allow) : String(command.permissions);
}
commands.push(formattedCommand);
}
await bot.requestHandler.request(
"PUT",
`/applications/${bot.application.id}/commands`,
true,
commands
);
await bot.requestHandler.request("PUT", APIEndpoints.COMMANDS(bot.application.id), true, commands);
});
bot.on("error", (err) => {
@ -243,10 +219,7 @@ bot.on("shardReady", (id) => {
logger.verbose("hf:shard", `Shard ${id} ready`);
});
bot.on("unknown", (packet, id) => {
logger.verbose(
"hf:main",
`Shard ${id} caught unknown packet: ${JSON.stringify(packet)}`
);
logger.verbose("hf:main", `Shard ${id} caught unknown packet: ${JSON.stringify(packet)}`);
});
instead("spawn", bot.shards, function (args, orig) {
@ -254,7 +227,7 @@ instead("spawn", bot.shards, function (args, orig) {
const shard = this.get(args[0]);
if (shard) {
before("sendWS", shard.__proto__, function ([op, _data]) {
if (op === Dysnomia.Constants.GatewayOPCodes.IDENTIFY) {
if (op === GatewayOPCodes.IDENTIFY) {
_data.properties.browser = "Discord Embedded";
delete _data.properties.device;
}

View file

@ -1,5 +1,5 @@
const logger = require("./logger.js");
const {pastelize, getTopColor} = require("./utils.js");
const {pastelize, getTopColor} = require("../util/misc.js");
function convertIfApplicable(val) {
if (isNaN(val)) {
@ -27,10 +27,7 @@ function parseAsArgv(args) {
for (let i = 0; i < args.length; i++) {
const arg = args.shift();
const equalsIndex = arg.charAt(0) === "-" ? arg.indexOf("=") : -1;
const argName =
equalsIndex === -1
? removeStartHyphens(arg)
: removeStartHyphens(arg.slice(0, equalsIndex));
const argName = equalsIndex === -1 ? removeStartHyphens(arg) : removeStartHyphens(arg.slice(0, equalsIndex));
if (equalsIndex !== -1) {
optional[argName] = convertIfApplicable(arg.slice(equalsIndex + 1));
@ -176,9 +173,7 @@ async function CommandDispatcher(msg) {
}
if (response.embeds) {
for (const embed of response.embeds) {
embed.color =
embed.color ||
getTopColor(msg, hf.bot.user.id, pastelize(hf.bot.user.id));
embed.color = embed.color || getTopColor(msg, hf.bot.user.id, pastelize(hf.bot.user.id));
}
}
if (response.reaction) {
@ -186,17 +181,14 @@ async function CommandDispatcher(msg) {
} else {
try {
const outMessage = await msg.channel.createMessage(
Object.assign(
typeof response === "string" ? {content: response} : response,
{
allowedMentions: {
repliedUser: false,
},
messageReference: {
messageID: msg.id,
},
}
)
Object.assign(typeof response === "string" ? {content: response} : response, {
allowedMentions: {
repliedUser: false,
},
messageReference: {
messageID: msg.id,
},
})
);
if (response.addReactions) {
for (const index in response.addReactions) {

View file

@ -17,10 +17,7 @@ function add(event, identifier, callback) {
try {
callback.apply(this, arguments);
} catch (error) {
logger.error(
"hf:event",
`Event "${event}:${identifier}" failed: ${error}`
);
logger.error("hf:event", `Event "${event}:${identifier}" failed: ${error}`);
}
}

View file

@ -1,6 +1,4 @@
hf.database.run(
"CREATE TABLE IF NOT EXISTS guild_data (key TEXT PRIMARY KEY, value TEXT NOT NULL) WITHOUT ROWID"
);
hf.database.run("CREATE TABLE IF NOT EXISTS guild_data (key TEXT PRIMARY KEY, value TEXT NOT NULL) WITHOUT ROWID");
function setGuildData(id, key, value) {
return new Promise((resolve, reject) => {

View file

@ -2,7 +2,7 @@ const {getGuildData, setGuildData} = require("./guildData.js");
const flags = Object.freeze({
codePreviews: 1 << 0,
tweetUnrolling: 1 << 1,
replyToBots: 1 << 1,
fedimbed: 1 << 2,
});
@ -18,9 +18,7 @@ async function getFlags(guildId) {
}
async function hasFlag(guildId, key) {
return (
((await getGuildData(guildId, "settings_flags", 0)) & flags[key]) !== 0
);
return ((await getGuildData(guildId, "settings_flags", 0)) & flags[key]) !== 0;
}
async function enableFlag(guildId, key) {

View file

@ -1,5 +1,4 @@
const {ApplicationCommandTypes, ApplicationCommandOptionTypes} =
require("@projectdysnomia/dysnomia").Constants;
const {ApplicationCommandTypes, ApplicationCommandOptionTypes} = require("../util/dconstants.js");
class InteractionCommand {
constructor(name) {
@ -18,6 +17,10 @@ class InteractionCommand {
};
}
getOption(interaction, key) {
return interaction.data.options?.find((o) => o.name === key)?.value ?? this.options[key].default;
}
callback() {
return "Callback not overwritten.";
}

View file

@ -1,13 +1,6 @@
const logger = require("./logger.js");
const {MessageFlags} = require("@projectdysnomia/dysnomia").Constants;
const {pastelize, getTopColor} = require("./utils.js");
function getOption(interaction, command, key) {
return (
interaction.data.options?.find((o) => o.name === key)?.value ??
command.options[key].default
);
}
const {MessageFlags} = require("../util/dconstants.js");
const {pastelize, getTopColor} = require("../util/misc.js");
async function runCommand(interaction, command) {
if (command.ownerOnly && interaction.user.id != hf.config.owner_id) {
@ -17,10 +10,7 @@ async function runCommand(interaction, command) {
};
}
if (
command.elevatedOnly &&
!hf.config.elevated.includes(interaction.user.id)
) {
if (command.elevatedOnly && !hf.config.elevated.includes(interaction.user.id)) {
return {
content: "No\n\nSent from my iPhone",
flags: MessageFlags.EPHEMERAL,
@ -45,7 +35,7 @@ async function runCommand(interaction, command) {
async function InteractionDispatcher(interaction) {
const command = hf.interactionCommands.get(interaction.data.name);
const shouldSend = getOption(interaction, command, "send");
const shouldSend = command.getOption(interaction, "send");
const ack = interaction.acknowledge(shouldSend ? 0 : MessageFlags.EPHEMERAL);
try {
@ -86,24 +76,19 @@ async function InteractionDispatcher(interaction) {
}
if (response.embeds) {
for (const embed of response.embeds) {
embed.color =
embed.color ??
getTopColor(interaction, hf.bot.user.id, pastelize(hf.bot.user.id));
embed.color = embed.color ?? getTopColor(interaction, hf.bot.user.id, pastelize(hf.bot.user.id));
}
}
try {
await ack;
await interaction.createMessage(
Object.assign(
typeof response === "string" ? {content: response} : response,
{
flags: shouldSend ? response.flags : MessageFlags.EPHEMERAL,
allowedMentions: {
repliedUser: false,
},
}
)
Object.assign(typeof response === "string" ? {content: response} : response, {
flags: shouldSend ? response.flags : MessageFlags.EPHEMERAL,
allowedMentions: {
repliedUser: false,
},
})
);
} catch (err) {
await ack;
@ -128,4 +113,4 @@ async function InteractionDispatcher(interaction) {
}
}
module.exports = {InteractionDispatcher, getOption};
module.exports = {InteractionDispatcher};

View file

@ -5,16 +5,7 @@ const BRIGHT = "[9";
const NORMAL_BG = "[4";
const BRIGHT_BG = "[10";
const NAMES = [
"Black",
"Red",
"Green",
"Yellow",
"Blue",
"Magenta",
"Cyan",
"White",
];
const NAMES = ["Black", "Red", "Green", "Yellow", "Blue", "Magenta", "Cyan", "White"];
const COLORS = {};
for (const index in NAMES) {
@ -73,6 +64,5 @@ function baseLogger(prefix, level, ...message) {
module.exports = {};
for (const level in LEVEL_COLORS) {
module.exports[level] = (prefix, ...message) =>
baseLogger.apply(null, [prefix, level, ...message]);
module.exports[level] = (prefix, ...message) => baseLogger.apply(null, [prefix, level, ...message]);
}

View file

@ -1,6 +1,4 @@
hf.database.run(
"CREATE TABLE IF NOT EXISTS user_data (key TEXT PRIMARY KEY, value TEXT NOT NULL) WITHOUT ROWID"
);
hf.database.run("CREATE TABLE IF NOT EXISTS user_data (key TEXT PRIMARY KEY, value TEXT NOT NULL) WITHOUT ROWID");
function setUserData(id, key, value) {
return new Promise((resolve, reject) => {

View file

@ -1,440 +0,0 @@
const {Collection} = require("@projectdysnomia/dysnomia");
const murmurhash = require("murmurhash").v3;
const {tinycolor} = require("@ctrl/tinycolor");
const logger = require("../lib/logger.js");
function pastelize(id) {
const hue = murmurhash(id) % 360;
const hex = tinycolor(`hsl(${hue},75%,60%)`).toHex();
return parseInt(hex, 16);
}
function formatUsername(user) {
return user.discriminator && user.discriminator != "0"
? `${user.username}#${user.discriminator}`
: `@${user.username}`;
}
function getTopColor(msg, id, fallback = 0x7289da) {
if (!msg.guildID) return fallback;
const guild = msg.channel?.guild ?? hf.bot.guilds.get(msg.guildID);
if (!guild) return fallback;
const roles = guild.members
.get(id)
.roles.map((role) => guild.roles.get(role))
.filter((role) => role.color);
roles.sort((a, b) => b.position - a.position);
return roles[0]?.color || fallback;
}
function safeString(string, newLines = true) {
string = string ? string.toString() : "";
string = string.replace(/`/g, "'");
string = string.replace(/<@/g, "<@\u200b");
string = string.replace(/<#/g, "<#\u200b");
string = string.replace(/<&/g, "<&\u200b");
if (newLines) string = string.replace(/\n/g, " ");
return string;
}
function formatTime(number) {
let seconds = parseInt(number) / 1000;
const days = Math.floor(seconds / 86400);
seconds = seconds % 86400;
const hours = Math.floor(seconds / 3600);
seconds = seconds % 3600;
const minutes = Math.floor(seconds / 60);
seconds = Math.floor(seconds % 60);
return (
(days !== 0 ? `${days.toString().padStart(2, "0")}:` : "") +
(hours !== 0 ? `${hours.toString().padStart(2, "0")}:` : "") +
`${minutes.toString().padStart(2, "0")}:${seconds
.toString()
.padStart(2, "0")}`
);
}
function readableTime(number) {
const seconds = number / 1000;
const days = seconds / 60 / 60 / 24;
const years = days / 365.25;
if (years >= 1) {
return `${years.toFixed(2)} years`;
} else {
return `${days.toFixed(2)} days`;
}
}
async function isGif(url) {
const type = await fetch(url, {method: "HEAD"}).then((res) =>
res.headers.get("Content-Type")
);
return type == "image/gif";
}
async function findLastImage(msg, gif = false) {
const messages = await msg.channel.getMessages(20);
let img;
for (const message of messages) {
if (message.attachments.size > 0) {
img = [...msg.attachments.values()][0].url;
if (gif && (await isGif(img))) {
break;
} else {
break;
}
} else if (message.embeds.length > 0) {
img = message.embeds[0]?.thumbnail?.url || message.embeds[0]?.image?.url;
if (img) {
if (gif && (await isGif(img))) {
break;
} else {
break;
}
}
}
}
return await new Promise((resolve, reject) => {
if (!img) {
reject("Image not found in last 20 messages.");
} else {
resolve(img);
}
});
}
const urlRegex = /((https?):\/)?\/?([^:/\s]+)((\/\w+)*\/)([\w\-.]+)/;
async function getImage(msg, str) {
const refMsg = msg.referencedMessage;
if (refMsg) {
const attachments = [...refMsg.attachments.values()];
if (attachments[0]?.url) {
return attachments[0].url;
} else if (/<a?:[a-zA-Z0-9_]+:([0-9]+)>/.test(refMsg.content)) {
const id = refMsg.content.match(/<a?:[a-zA-Z0-9_]+:([0-9]+)>/)[1];
return `https://cdn.discordapp.com/emojis/${id}.png?v=1`;
} else if (/<@!?([0-9]+)>/.test(refMsg.content)) {
const id = refMsg.content.match(/<@!?([0-9]+)>/)[1];
const user = await hf.bot.requestHandler.request(
"GET",
"/users/" + id,
true
);
if (user)
return `https://cdn.discordapp.com/avatars/${id}/${user.avatar}.png?size=1024`;
}
}
const img = await findLastImage(msg, false);
if (!str) {
if (img) return img;
}
const attachments = [...msg.attachments.values()];
if (attachments[0]?.url) {
return attachments[0]?.url;
} else if (urlRegex.test(str)) {
return str;
} else if (/<a?:[a-zA-Z0-9_]+:([0-9]+)>/.test(str)) {
const id = str.match(/<a?:[a-zA-Z0-9_]+:([0-9]+)>/)[1];
return `https://cdn.discordapp.com/emojis/${id}.png?v=1`;
} else if (/<@!?([0-9]+)>/.test(str)) {
const id = str.match(/<@!?([0-9]+)>/)[1];
const user = await hf.bot.requestHandler.request(
"GET",
"/users/" + id,
true
);
if (user)
return `https://cdn.discordapp.com/avatars/${id}/${user.avatar}.png?size=1024`;
} else if (img) {
return img;
}
return null;
}
async function hastebin(body) {
const res = await fetch(`${hf.config.haste_provider}/documents`, {
method: "POST",
body,
}).then((r) => r.json());
return `<${hf.config.haste_provider}/${res.key}>`;
}
hf.selectionMessages = hf.selectionMessages ?? new Collection();
async function selectionMessage(
msg,
heading,
options,
timeout = 30000,
maxItems = 1
) {
const data = {
content: heading,
allowedMentions: {
repliedUser: false,
},
messageReference: {
messageID: msg.id,
},
components: [
{
type: 1,
components: [
{
type: 3,
custom_id: msg.id,
options: [],
max_values: maxItems,
},
],
},
{
type: 1,
components: [
{
type: 2,
style: 4,
label: "Cancel",
custom_id: "cancel",
},
],
},
],
};
options.slice(0, 25).forEach((value) => {
data.components[0].components[0].options.push({
label: value.display,
value: value.key,
description: value.description,
});
});
if (options.length > 25) {
data.content += `\n\nDisplaying 25/${options.length} results`;
}
const displayMessage = await msg.channel
.createMessage(data)
.catch((err) =>
logger.error(
"selectionMessage",
"Failed to create selection message: " + err
)
);
return await new Promise((resolve, reject) => {
function listener(interaction) {
const user = interaction.member.user || interaction.user;
if (
user.id == msg.author.id &&
interaction.channel.id == msg.channel.id &&
interaction.message.components[0].components[0].custom_id == msg.id
) {
if (interaction.data.custom_id == "cancel") {
hf.events.remove("interactionCreate", `selection.${msg.id}`);
clearTimeout(hf.selectionMessages.get(msg.id));
hf.selectionMessages.delete(msg.id);
interaction.deferUpdate();
displayMessage.delete();
reject("Canceled");
} else {
hf.events.remove("interactionCreate", `selection.${msg.id}`);
clearTimeout(hf.selectionMessages.get(msg.id));
hf.selectionMessages.delete(msg.id);
interaction.deferUpdate();
displayMessage.delete();
let result;
if (maxItems > 1) {
result = options
.filter((opt) => interaction.data.values.includes(opt.key))
.map((opt) => opt.key);
} else {
result = options.filter(
(opt) => opt.key == interaction.data.values[0]
)[0].value;
}
resolve(result);
}
}
}
hf.events.add("interactionCreate", `selection.${msg.id}`, listener);
hf.selectionMessages.set(
msg.id,
setTimeout(() => {
hf.events.remove("interactionCreate", `selection.${msg.id}`);
hf.selectionMessages.delete(msg.id);
reject("Request timed out");
}, timeout)
);
});
}
async function lookupUser(msg, str, filter) {
if (/[0-9]{17,21}/.test(str)) {
return await hf.bot.requestHandler.request(
"GET",
"/users/" + str.match(/([0-9]{17,21})/)[1],
true
);
}
let users;
if (filter) {
users = hf.bot.users.filter(filter).values();
} else if (msg.guildID) {
const guild = msg.channel.guild || hf.bot.guilds.get(msg.guildID);
users = guild.members.values();
} else {
users = hf.bot.users.values();
}
if (/(.+?)#([0-9]{4})/.test(str)) {
const [_, name, discrim] = str.match(/(.+?)#([0-9]{4})/);
for (const user of users) {
if (
user.username.toLowerCase() == name.toLowerCase() &&
user.discriminator == discrim
) {
return user;
}
}
}
const selection = [];
for (const user of users) {
if (
user.username.toLowerCase() == str.toLowerCase() ||
(user.nickname && user.nickname == str.toLowerCase())
) {
return user;
} else if (
user.username.toLowerCase().indexOf(str.toLowerCase()) > -1 ||
(user.nick && user.nick.toLowerCase().indexOf(str.toLowerCase()) > -1) ||
(user.globalName &&
user.globalName.toLowerCase().indexOf(str.toLowerCase()) > -1)
) {
selection.push({
value: user,
key: user.id,
display: `${formatUsername(user)}${
user.nick
? ` (${user.nick})`
: user.globalName
? ` (${user.globalName})`
: ""
}`,
});
}
}
selection.sort((a, b) => a.display - b.display);
if (selection.length == 0) {
return "No results";
} else if (selection.length == 1) {
return selection[0].value;
} else {
selection.splice(20);
try {
return await selectionMessage(
msg,
"Multiple users found, please pick from this list:",
selection
);
} catch (out) {
return out;
}
}
}
// https://stackoverflow.com/a/39243641
const htmlEntities = {
nbsp: " ",
cent: "¢",
pound: "£",
yen: "¥",
euro: "€",
copy: "©",
reg: "®",
lt: "<",
gt: ">",
quot: '"',
amp: "&",
apos: "'",
};
function parseHtmlEntities(str) {
return str.replace(/&([^;]+);/g, function (entity, entityCode) {
let match;
if (entityCode in htmlEntities) {
return htmlEntities[entityCode];
} else if ((match = entityCode.match(/^#x([\da-fA-F]+)$/))) {
return String.fromCharCode(parseInt(match[1], 16));
} else if ((match = entityCode.match(/^#(\d+)$/))) {
return String.fromCharCode(~~match[1]);
} else {
return entity;
}
});
}
const UPLOAD_LIMIT = 26214400;
const UPLOAD_LIMIT_TIER_2 = 52428800;
const UPLOAD_LIMIT_TIER_3 = 104857600;
function getUploadLimit(guild) {
if (!guild) return UPLOAD_LIMIT;
if (guild.premiumTier == 2) return UPLOAD_LIMIT_TIER_2;
if (guild.premiumTier == 3) return UPLOAD_LIMIT_TIER_3;
return UPLOAD_LIMIT;
}
const TWITTER_EPOCH = 1288834974657;
const DISCORD_EPOCH = 1420070400000;
function snowflakeToTimestamp(snowflake, twitter = false) {
return (
Math.floor(Number(snowflake) / Math.pow(2, 22)) +
(twitter == true ? TWITTER_EPOCH : DISCORD_EPOCH)
);
}
module.exports = {
pastelize,
formatUsername,
getTopColor,
safeString,
formatTime,
readableTime,
isGif,
findLastImage,
getImage,
hastebin,
selectionMessage,
lookupUser,
parseHtmlEntities,
getUploadLimit,
snowflakeToTimestamp,
};

View file

@ -9,28 +9,20 @@ if (hf.__anonradio_timeout) {
async function updateNowPlaying() {
let playing;
try {
playing = await fetch("https://anonradio.net/playing").then((res) =>
res.text()
);
playing = await fetch("https://anonradio.net/playing").then((res) => res.text());
} catch (err) {
try {
playing = await fetch("http://anonradio.net/playing").then((res) =>
res.text()
);
playing = await fetch("http://anonradio.net/playing").then((res) => res.text());
} catch (err) {
//
}
}
let schedule;
try {
schedule = await fetch("https://anonradio.net/schedule/").then((res) =>
res.text()
);
schedule = await fetch("https://anonradio.net/schedule/").then((res) => res.text());
} catch (err) {
try {
schedule = await fetch("http://anonradio.net/schedule/").then((res) =>
res.text()
);
schedule = await fetch("http://anonradio.net/schedule/").then((res) => res.text());
} catch (err) {
//
}
@ -68,19 +60,14 @@ async function updateNowPlaying() {
} else if (playing.startsWith("Coming up")) {
title = playing;
} else {
const metadataLine = playing.match(
/\[(\d+)\/(\d+)\/(\d+)\] \((.+?)\): (.+)/
);
const metadataLine = playing.match(/\[(\d+)\/(\d+)\/(\d+)\] \((.+?)\): (.+)/);
const current = metadataLine?.[1] ?? "??";
const peakDay = metadataLine?.[2] ?? "??";
const peakMonth = metadataLine?.[3] ?? "??";
const dj = metadataLine?.[4] ?? "unknown";
const metadata = metadataLine?.[5] ?? "unknown";
if (
metadata == "https://archives.anonradio.net" ||
liveNow.name == "Synth Battle Royale"
) {
if (metadata == "https://archives.anonradio.net" || liveNow.name == "Synth Battle Royale") {
title = `${liveNow.name} (\`${dj}\`)`;
} else {
title = `${metadata} (\`${dj}\`)`;
@ -93,24 +80,16 @@ async function updateNowPlaying() {
try {
const icecast = await fetch("http://anonradio.net:8010/status-json.xsl")
.then((res) => res.text())
.then((data) =>
JSON.parse(data.replace(/"title": - ,/g, '"title":" - ",'))
);
const streamData = icecast.icestats.source.find(
(src) => src.listenurl == "http://anonradio.net:8010/openmic"
);
.then((data) => JSON.parse(data.replace(/"title": - ,/g, '"title":" - ",')));
const streamData = icecast.icestats.source.find((src) => src.listenurl == "http://anonradio.net:8010/openmic");
if (streamData && streamData.stream_start_iso8601) {
const startTime = new Date(streamData.stream_start_iso8601).getTime();
title = `${streamData.title} (\`${
streamData.server_name
? streamData.server_name + " | " + liveNow.id
: liveNow.id
streamData.server_name ? streamData.server_name + " | " + liveNow.id : liveNow.id
}\`)`;
openmicTime = `-\\*- OpenMIC DJ has been streaming since <t:${Math.round(
startTime / 1000
)}:R> -\\*-\n`;
openmicTime = `-\\*- OpenMIC DJ has been streaming since <t:${Math.round(startTime / 1000)}:R> -\\*-\n`;
}
} catch (err) {
} catch {
//
}
}
@ -129,7 +108,7 @@ async function updateNowPlaying() {
content: timestamp + "\n" + content,
});
}
} catch (err) {
} catch {
//
}
}

View file

@ -9,11 +9,10 @@ const {resolve} = require("path");
const guildSettings = require("../lib/guildSettings.js");
function spawn(args) {
const shell =
process.env.SHELL || (process.platform == "win32" ? "powershell" : "sh");
const shell = process.env.SHELL || (process.platform == "win32" ? "pwsh" : "sh");
const newArgs = [];
if (shell.match(/powershell/i) && process.platform == "win32") {
if (shell.match(/pwsh/i) && process.platform == "win32") {
newArgs.push("-NoLogo", "-Command");
} else {
newArgs.push("-c");
@ -52,16 +51,18 @@ reload.callback = function (msg, line) {
if (err.code == "MODULE_NOT_FOUND") {
return "Module not found.";
} else {
logger.info("hf:modules", `Failed to resolve "${line}": ${err}`);
return `:warning: An error occurred: \`\`\`\n${err}\`\`\``;
}
}
try {
logger.info("hf:modules", `Reloading module: '${line}'`);
logger.info("hf:modules", `Reloading module: "${line}"`);
delete require.cache[require.resolve(`./${line}.js`)];
require(`./${line}.js`);
return {reaction: "\uD83D\uDC4C"};
} catch (err) {
logger.info("hf:modules", `Failed to reload "${line}": ${err}`);
return `:warning: An error occurred: \`\`\`\n${err}\`\`\``;
}
};
@ -96,10 +97,7 @@ _eval.callback = async function (msg, line) {
out = errored ? out : inspect(out, {depth: 0});
const token = hf.config.token;
out = out.replace(
new RegExp(token.replace(/\./g, "\\."), "g"),
"lol no key 4 u"
);
out = out.replace(new RegExp(token.replace(/\./g, "\\."), "g"), "lol no key 4 u");
if (errored) {
return ":warning: Output (errored):\n```js\n" + out + "\n```";
@ -136,9 +134,7 @@ exec.callback = async function (msg, line) {
});
proc.on("close", async (code) => {
out += `\n\x1b[0m\x1b[1m====\x1b[0m\n\x1b[1m${
code != 0 ? "\x1b[31m" : ""
}Exited with ${code}\x1b[0m`;
out += `\n\x1b[0m\x1b[1m====\x1b[0m\n\x1b[1m${code != 0 ? "\x1b[31m" : ""}Exited with ${code}\x1b[0m`;
if (out.length > 1980) {
msg.channel.createMessage({
content: `Output too long to send in a message:`,
@ -188,10 +184,7 @@ settings.callback = async function (msg, line, [cmd, key, value]) {
.join("\n")}\n\`\`\``;
}
case "enable": {
if (
!msg.channel.permissionsOf(msg.author.id).has("manageGuild") &&
!hf.config.elevated.includes(msg.author.id)
)
if (!msg.channel.permissionsOf(msg.author.id).has("manageGuild") && !hf.config.elevated.includes(msg.author.id))
return "You do not have `Manage Server` permissions";
if (msg.author.bot) return "Zero-width space your say command.";
@ -204,10 +197,7 @@ settings.callback = async function (msg, line, [cmd, key, value]) {
return {reaction: "\uD83D\uDC4C"};
}
case "disable": {
if (
!msg.channel.permissionsOf(msg.author.id).has("manageGuild") &&
!hf.config.elevated.includes(msg.author.id)
)
if (!msg.channel.permissionsOf(msg.author.id).has("manageGuild") && !hf.config.elevated.includes(msg.author.id))
return "You do not have `Manage Server` permissions";
if (msg.author.bot) return "Zero-width space your say command.";

View file

@ -1,7 +1,5 @@
const {ApplicationCommandOptionTypes, MessageFlags} =
require("@projectdysnomia/dysnomia").Constants;
const {ApplicationCommandOptionTypes, MessageFlags} = require("../util/dconstants.js");
const InteractionCommand = require("../lib/interactionCommand.js");
const {getOption} = require("../lib/interactionDispatcher.js");
const events = require("../lib/events.js");
const {hasFlag} = require("../lib/guildSettings.js");
@ -15,10 +13,7 @@ const REGEX_SPOILER = /(?:\s|^)\|\|([\s\S]+?)\|\|/;
function unindent(str) {
str = str.replace(/\t/g, " ");
const minIndent =
str
.match(/^ *(?=\S)/gm)
?.reduce((prev, curr) => Math.min(prev, curr.length), Infinity) ?? 0;
const minIndent = str.match(/^ *(?=\S)/gm)?.reduce((prev, curr) => Math.min(prev, curr.length), Infinity) ?? 0;
if (!minIndent) return str;
return str.replace(new RegExp(`^ {${minIndent}}`, "gm"), "");
}
@ -44,12 +39,7 @@ const fileTypeAliases = {
"code-snippets": "json",
};
async function processFile(
link,
originalLink,
spoiler = false,
linkFile = false
) {
async function processFile(link, originalLink, spoiler = false, linkFile = false) {
link = link.replaceAll("||", "").trim();
const res = await fetch(link);
if (!res.ok) return "";
@ -63,10 +53,7 @@ async function processFile(
urlObj.pathname.lastIndexOf("/") + 1,
urlObj.pathname.length
);
const fileType =
fileName.lastIndexOf(".") == -1
? ""
: fileName.substring(fileName.lastIndexOf(".") + 1);
const fileType = fileName.lastIndexOf(".") == -1 ? "" : fileName.substring(fileName.lastIndexOf(".") + 1);
if (linkFile) {
fileName = `[${fileName}](<${originalLink}>)`;
@ -101,9 +88,7 @@ async function processFile(
if (lines.length > 20) return "";
}
let targetLines = (
entireFile ? lines : lines.slice(startLine - 1, endLine)
).join("\n");
let targetLines = (entireFile ? lines : lines.slice(startLine - 1, endLine)).join("\n");
let warning = "";
if (spoiler && targetLines.includes("||")) {
@ -115,11 +100,9 @@ async function processFile(
warning = " - :warning: Zero width spaces present";
}
return `**${fileName}:** ${whichLines}${warning}\n${
spoiler ? "||" : ""
}\`\`\`${fileTypeAliases[fileType] ?? fileType}\n${unindent(
targetLines
)}\n\`\`\`${spoiler ? "||" : ""}`;
return `**${fileName}:** ${whichLines}${warning}\n${spoiler ? "||" : ""}\`\`\`${
fileTypeAliases[fileType] ?? fileType
}\n${unindent(targetLines)}\n\`\`\`${spoiler ? "||" : ""}`;
}
events.add("messageCreate", "codePreviews", async function (msg) {
@ -136,27 +119,21 @@ events.add("messageCreate", "codePreviews", async function (msg) {
if (githubLinks?.length) {
for (const link of githubLinks) {
const spoiler = REGEX_SPOILER.test(link);
files.push(
await processFile(link.replace("/blob/", "/raw/"), link, spoiler)
);
files.push(await processFile(link.replace("/blob/", "/raw/"), link, spoiler));
}
}
if (gitlabLinks?.length) {
for (const link of gitlabLinks) {
const spoiler = REGEX_SPOILER.test(link);
files.push(
await processFile(link.replace("/blob/", "/raw/"), link, spoiler)
);
files.push(await processFile(link.replace("/blob/", "/raw/"), link, spoiler));
}
}
if (giteaLinks?.length) {
for (const link of giteaLinks) {
const spoiler = REGEX_SPOILER.test(link);
files.push(
await processFile(link.replace("/src/", "/raw/"), link, spoiler)
);
files.push(await processFile(link.replace("/src/", "/raw/"), link, spoiler));
}
}
@ -201,8 +178,7 @@ events.add("messageCreate", "codePreviews", async function (msg) {
});
const codepreviewsCommand = new InteractionCommand("codepreview");
codepreviewsCommand.helpText =
"Post snippets of codes from files on GitHub, Gitlab and Gitea instances.";
codepreviewsCommand.helpText = "Post snippets of codes from files on GitHub, Gitlab and Gitea instances.";
codepreviewsCommand.options.url = {
name: "url",
type: ApplicationCommandOptionTypes.STRING,
@ -218,8 +194,8 @@ codepreviewsCommand.options.spoiler = {
default: false,
};
codepreviewsCommand.callback = async function (interaction) {
const url = getOption(interaction, codepreviewsCommand, "url");
const spoiler = getOption(interaction, codepreviewsCommand, "spoiler");
const url = this.getOption(interaction, "url");
const spoiler = this.getOption(interaction, "spoiler");
const githubOrGitlab = url.match(REGEX_GITHUB) ?? url.match(REGEX_GITLAB);
const gitea = url.match(REGEX_GITEA);
@ -238,8 +214,7 @@ codepreviewsCommand.callback = async function (interaction) {
if (out == "") {
return {
content:
"No content was returned. Provided file is either too long, a markdown file, or not plaintext.",
content: "No content was returned. Provided file is either too long, a markdown file, or not plaintext.",
flags: MessageFlags.EPHEMERAL,
};
}

View file

@ -1,6 +1,5 @@
const Dysnomia = require("@projectdysnomia/dysnomia");
const {MessageFlags, ApplicationCommandOptionTypes, Permissions} =
Dysnomia.Constants;
const {Message} = require("@projectdysnomia/dysnomia");
const {MessageFlags, ApplicationCommandOptionTypes, Permissions} = require("../util/dconstants.js");
const fs = require("node:fs");
const path = require("node:path");
const httpSignature = require("@peertube/http-signature");
@ -8,22 +7,20 @@ const httpSignature = require("@peertube/http-signature");
const events = require("../lib/events.js");
const logger = require("../lib/logger.js");
const {hasFlag} = require("../lib/guildSettings.js");
const {parseHtmlEntities, getUploadLimit} = require("../lib/utils.js");
const InteractionCommand = require("../lib/interactionCommand.js");
const {getOption} = require("../lib/interactionDispatcher.js");
const FRIENDLY_USERAGENT =
"HiddenPhox/fedimbed (https://gitdab.com/Cynosphere/HiddenPhox)";
const {getUploadLimit} = require("../util/misc.js");
const {htmlToMarkdown} = require("../util/html.js");
const URLS_REGEX =
/(?:\s|^|\]\()(\|\|\s*)?(https?:\/\/[^\s<]+[^<.,:;"'\]|)\s])(\s*\)?\|\||\s*[\S]*?\))?/g;
const FRIENDLY_USERAGENT = "HiddenPhox/fedimbed (https://gitdab.com/Cynosphere/HiddenPhox)";
const URLS_REGEX = /(?:\s|^|\]\()(\|\|\s*)?(https?:\/\/[^\s<]+[^<.,:;"'\]|)\s])(\s*\)?\|\||\s*[\S]*?\))?/g;
const SPOILER_REGEX = /(?:\s|^)\|\|([\s\S]+?)\|\|/;
const PATH_REGEX = {
mastodon: /^\/@(.+?)\/(\d+)\/?/,
mastodon2: /^\/(.+?)\/statuses\/\d+\/?/,
pleroma:
/^\/objects\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/?/,
pleroma: /^\/objects\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/?/,
pleroma2: /^\/notice\/[A-Za-z0-9]+\/?/,
misskey: /^\/notes\/[a-z0-9]+\/?/,
gotosocial: /^\/@(.+?)\/statuses\/[0-9A-Z]+\/?/,
@ -76,10 +73,7 @@ async function resolvePlatform(url) {
}).then((res) => res.json());
if (!nodeinfo?.software?.name) {
logger.error(
"fedimbed",
`Got nodeinfo for "${urlObj.hostname}", but missing software name.`
);
logger.error("fedimbed", `Got nodeinfo for "${urlObj.hostname}", but missing software name.`);
domainCache.set(urlObj.hostname, null);
return null;
}
@ -89,9 +83,7 @@ async function resolvePlatform(url) {
}
const keyId = "https://hf.c7.pm/actor#main-key";
const privKey = fs.readFileSync(
path.resolve(__dirname, "../../priv/private.pem")
);
const privKey = fs.readFileSync(path.resolve(__dirname, "../../priv/private.pem"));
async function signedFetch(url, options) {
const urlObj = new URL(url);
@ -122,36 +114,6 @@ async function signedFetch(url, options) {
return await fetch(url, options);
}
function htmlToMarkdown(str) {
// FIXME: stop being lazy and use an html parser
str = str.replace(/<a .*?href="([^"]+?)".*?>(.+?)<\/a>/gi, (_, url, text) =>
url == text ? url : `[${text}](${url})`
);
str = str.replace(
/<img .*?src="([^"]+?)".*?(alt|title)="([^"]+?)".*?\/>/gi,
"[$3]($1)"
);
str = str.replace(/<\/?\s*br\s*\/?>/gi, "\n");
str = str.replace(
/<blockquote.*?>((.|\n)*?)<\/blockquote>/gi,
(_, quote) => "> " + quote.split("\n").join("\n> ")
);
str = str.replace(/<\/p><p>/gi, "\n\n");
str = str.replace(/<ol>/gi, "\n");
str = str.replace(/<li>/gi, "- ");
str = str.replace(/<\/li>/gi, "\n");
str = str.replace(/<\/?code>/gi, "`");
str = str.replace(/<\/?em>/gi, "*");
str = str.replace(/<\/?u>/gi, "__");
str = str.replace(/<\/?s>/gi, "~~");
str = str.replace(/(<([^>]+)>)/gi, "");
str = parseHtmlEntities(str);
// whyyyyyyyyyyyy
str = str.replace(/\[https?:\/\/.+?\]\((https?:\/\/.+?)\)/gi, "$1");
return str;
}
async function processUrl(msg, url, spoiler = false) {
let invalidUrl = false;
let urlObj;
@ -200,10 +162,7 @@ async function processUrl(msg, url, spoiler = false) {
},
}).then((res) => res.text());
} catch (err) {
logger.error(
"fedimbed",
`Failed to signed fetch "${url}", retrying unsigned: ${err}`
);
logger.error("fedimbed", `Failed to signed fetch "${url}", retrying unsigned: ${err}`);
}
if (!rawPostData) {
try {
@ -223,10 +182,7 @@ async function processUrl(msg, url, spoiler = false) {
try {
postData = JSON.parse(rawPostData);
} catch (err) {
logger.error(
"fedimbed",
`Failed to decode JSON for "${url}": ${err}\n "${rawPostData}"`
);
logger.error("fedimbed", `Failed to decode JSON for "${url}": ${err}\n "${rawPostData}"`);
}
} else {
logger.warn("fedimbed", `Got non-JSON for "${url}": ${rawPostData}`);
@ -280,18 +236,13 @@ async function processUrl(msg, url, spoiler = false) {
options.body = JSON.stringify({noteId});
headers["Content-Type"] = "application/json";
} else {
logger.error(
"fedimbed",
`Missing MastoAPI replacement for "${platform}"`
);
logger.error("fedimbed", `Missing MastoAPI replacement for "${platform}"`);
}
if (redirUrl) {
logger.verbose(
"fedimbed",
`Redirecting "${url}" to "${redirUrl}": ${JSON.stringify(
options
)}, ${JSON.stringify(headers)}`
`Redirecting "${url}" to "${redirUrl}": ${JSON.stringify(options)}, ${JSON.stringify(headers)}`
);
let rawPostData2;
try {
@ -304,10 +255,7 @@ async function processUrl(msg, url, spoiler = false) {
})
).then((res) => res.text());
} catch (err) {
logger.error(
"fedimbed",
`Failed to signed fetch "${url}" via MastoAPI, retrying unsigned: ${err}`
);
logger.error("fedimbed", `Failed to signed fetch "${url}" via MastoAPI, retrying unsigned: ${err}`);
}
if (!rawPostData2) {
try {
@ -320,10 +268,7 @@ async function processUrl(msg, url, spoiler = false) {
})
).then((res) => res.text());
} catch (err) {
logger.error(
"fedimbed",
`Failed to fetch "${url}" via MastoAPI: ${err}`
);
logger.error("fedimbed", `Failed to fetch "${url}" via MastoAPI: ${err}`);
}
}
@ -331,27 +276,18 @@ async function processUrl(msg, url, spoiler = false) {
if (rawPostData2?.startsWith("{")) {
postData2 = JSON.parse(rawPostData2);
} else {
logger.warn(
"fedimbed",
`Got non-JSON for "${url}" via MastoAPI: ${rawPostData2}`
);
logger.warn("fedimbed", `Got non-JSON for "${url}" via MastoAPI: ${rawPostData2}`);
}
if (!postData2) {
logger.warn(
"fedimbed",
`Bailing trying to re-embed "${url}": Failed to get post from normal and MastoAPI.`
);
logger.warn("fedimbed", `Bailing trying to re-embed "${url}": Failed to get post from normal and MastoAPI.`);
} else if (postData2.error) {
logger.error(
"fedimbed",
`Bailing trying to re-embed "${url}", MastoAPI gave us error: ${JSON.stringify(
postData2.error
)}`
`Bailing trying to re-embed "${url}", MastoAPI gave us error: ${JSON.stringify(postData2.error)}`
);
} else {
cw =
postData2.spoiler_warning ?? postData2.spoiler_text ?? postData2.cw;
cw = postData2.spoiler_warning ?? postData2.spoiler_text ?? postData2.cw;
content =
postData2.akkoma?.source?.content ??
postData2.pleroma?.content?.["text/plain"] ??
@ -364,21 +300,12 @@ async function processUrl(msg, url, spoiler = false) {
postData2.user?.name ??
postData2.user?.username,
handle:
postData2.account?.fqn ??
`${postData2.account?.username ?? postData2.user?.username}@${
urlObj.hostname
}`,
url:
postData2.account?.url ??
`${urlObj.origin}/@${
postData2.account?.username ?? postData2.user?.username
}`,
postData2.account?.fqn ?? `${postData2.account?.username ?? postData2.user?.username}@${urlObj.hostname}`,
url: postData2.account?.url ?? `${urlObj.origin}/@${postData2.account?.username ?? postData2.user?.username}`,
avatar: postData2.account?.avatar ?? postData2.user?.avatarUrl,
};
timestamp = postData2.created_at ?? postData2.createdAt;
emotes = postData2.emojis
.filter((x) => !x.name.endsWith("@."))
.map((x) => ({name: `:${x.name}:`, url: x.url}));
emotes = postData2.emojis.filter((x) => !x.name.endsWith("@.")).map((x) => ({name: `:${x.name}:`, url: x.url}));
sensitive = postData2.sensitive;
const attachments = postData2.media_attachments ?? postData2.files;
@ -465,15 +392,12 @@ async function processUrl(msg, url, spoiler = false) {
if (realUrlObj.origin != urlObj.origin) {
platform = await resolvePlatform(postData.id);
color = PLATFORM_COLORS[platform];
platformName = platform
.replace("gotosocial", "GoToSocial")
.replace(/^(.)/, (_, c) => c.toUpperCase());
platformName = platform.replace("gotosocial", "GoToSocial").replace(/^(.)/, (_, c) => c.toUpperCase());
url = postData.id;
}
}
content =
postData._misskey_content ?? postData.source?.content ?? postData.content;
content = postData._misskey_content ?? postData.source?.content ?? postData.content;
cw = postData.summary;
timestamp = postData.published;
sensitive = postData.sensitive;
@ -482,36 +406,29 @@ async function processUrl(msg, url, spoiler = false) {
let tag = postData.tag;
// gts moment
if (!Array.isArray(tag)) tag = [tag];
emotes = tag
.filter((x) => !!x.icon)
.map((x) => ({name: x.name, url: x.icon.url}));
emotes = tag.filter((x) => !!x.icon).map((x) => ({name: x.name, url: x.icon.url}));
}
// NB: gts doesnt send singular attachments as array
const attachments = Array.isArray(postData.attachment)
? postData.attachment
: [postData.attachment];
const attachments = Array.isArray(postData.attachment) ? postData.attachment : [postData.attachment];
for (const attachment of attachments) {
if (attachment.mediaType) {
if (attachment.mediaType.startsWith("video/")) {
videos.push({
url: attachment.url,
desc:
attachment.name ?? attachment.description ?? attachment.comment,
desc: attachment.name ?? attachment.description ?? attachment.comment,
type: attachment.mediaType,
});
} else if (attachment.mediaType.startsWith("image/")) {
images.push({
url: attachment.url,
desc:
attachment.name ?? attachment.description ?? attachment.comment,
desc: attachment.name ?? attachment.description ?? attachment.comment,
type: attachment.mediaType,
});
} else if (attachment.mediaType.startsWith("audio/")) {
audios.push({
url: attachment.url,
desc:
attachment.name ?? attachment.description ?? attachment.comment,
desc: attachment.name ?? attachment.description ?? attachment.comment,
type: attachment.mediaType,
});
}
@ -524,22 +441,19 @@ async function processUrl(msg, url, spoiler = false) {
if (contentType.startsWith("image/")) {
images.push({
url: attachment.url,
desc:
attachment.name ?? attachment.description ?? attachment.comment,
desc: attachment.name ?? attachment.description ?? attachment.comment,
type: contentType,
});
} else if (contentType.startsWith("video/")) {
videos.push({
url: attachment.url,
desc:
attachment.name ?? attachment.description ?? attachment.comment,
desc: attachment.name ?? attachment.description ?? attachment.comment,
type: contentType,
});
} else if (contentType.startsWith("audio/")) {
audios.push({
url: attachment.url,
desc:
attachment.name ?? attachment.description ?? attachment.comment,
desc: attachment.name ?? attachment.description ?? attachment.comment,
type: contentType,
});
}
@ -551,39 +465,29 @@ async function processUrl(msg, url, spoiler = false) {
? type
: type +
"/" +
(url.match(/\.([a-z0-9]{3,4})$/)?.[0] ?? type == "image"
? "png"
: type == "video"
? "mp4"
: "mpeg");
(url.match(/\.([a-z0-9]{3,4})$/)?.[0] ?? type == "image" ? "png" : type == "video" ? "mp4" : "mpeg");
if (type.startsWith("image")) {
images.push({
url: attachment.url,
desc:
attachment.name ?? attachment.description ?? attachment.comment,
desc: attachment.name ?? attachment.description ?? attachment.comment,
type: fileType,
});
} else if (type.startsWith("video")) {
videos.push({
url: attachment.url,
desc:
attachment.name ?? attachment.description ?? attachment.comment,
desc: attachment.name ?? attachment.description ?? attachment.comment,
type: fileType,
});
} else if (type.startsWith("audio")) {
audios.push({
url: attachment.url,
desc:
attachment.name ?? attachment.description ?? attachment.comment,
desc: attachment.name ?? attachment.description ?? attachment.comment,
type: fileType,
});
}
}
} else {
logger.warn(
"fedimbed",
`Unhandled attachment structure! ${JSON.stringify(attachment)}`
);
logger.warn("fedimbed", `Unhandled attachment structure! ${JSON.stringify(attachment)}`);
}
}
@ -599,30 +503,23 @@ async function processUrl(msg, url, spoiler = false) {
images.push({
url: postData.image.url,
desc: "",
type:
contentType ??
"image/" +
imageUrl.pathname.substring(imageUrl.pathname.lastIndexOf(".") + 1),
type: contentType ?? "image/" + imageUrl.pathname.substring(imageUrl.pathname.lastIndexOf(".") + 1),
});
}
if (postData.name) title = postData.name;
// Author data is not sent with the post with AS2
const authorData = await signedFetch(
postData.actor ?? postData.attributedTo,
{
headers: {
"User-Agent": FRIENDLY_USERAGENT,
Accept: "application/activity+json",
},
}
)
const authorData = await signedFetch(postData.actor ?? postData.attributedTo, {
headers: {
"User-Agent": FRIENDLY_USERAGENT,
Accept: "application/activity+json",
},
})
.then((res) => res.json())
.catch((err) => {
// only posts can be activity+json right now, reduce log spam
if (platform !== "cohost")
logger.error("fedimbed", `Failed to get author for "${url}": ${err}`);
if (platform !== "cohost") logger.error("fedimbed", `Failed to get author for "${url}": ${err}`);
});
if (authorData) {
@ -637,9 +534,7 @@ async function processUrl(msg, url, spoiler = false) {
// bootleg author, mainly for cohost
const authorUrl = postData.actor ?? postData.attributedTo;
const authorUrlObj = new URL(authorUrl);
const name = authorUrlObj.pathname.substring(
authorUrlObj.pathname.lastIndexOf("/") + 1
);
const name = authorUrlObj.pathname.substring(authorUrlObj.pathname.lastIndexOf("/") + 1);
author = {
name,
handle: `${name}@${authorUrlObj.hostname}`,
@ -661,10 +556,7 @@ async function processUrl(msg, url, spoiler = false) {
// We could just continue without author but it'd look ugly and be confusing.
if (!author) {
logger.warn(
"fedimbed",
`Bailing trying to re-embed "${url}": Failed to get author.`
);
logger.warn("fedimbed", `Bailing trying to re-embed "${url}": Failed to get author.`);
return {};
}
@ -682,12 +574,7 @@ async function processUrl(msg, url, spoiler = false) {
let desc = "";
let MAX_LENGTH = 3999;
if (
(cw != "" || sensitive) &&
images.length == 0 &&
videos.length == 0 &&
audios.length == 0
) {
if ((cw != "" || sensitive) && images.length == 0 && videos.length == 0 && audios.length == 0) {
desc += "||" + content + "||";
MAX_LENGTH -= 4;
@ -708,9 +595,7 @@ async function processUrl(msg, url, spoiler = false) {
}
}
const user = author.name
? `${author.name} (${author.handle})`
: author.handle;
const user = author.name ? `${author.name} (${author.handle})` : author.handle;
const baseEmbed = {
color,
@ -734,9 +619,7 @@ async function processUrl(msg, url, spoiler = false) {
};
if (images.length > 0) {
if (images.length > 1) {
const links = images
.map((attachment, index) => `[Image ${index + 1}](${attachment.url})`)
.join(" | ");
const links = images.map((attachment, index) => `[Image ${index + 1}](${attachment.url})`).join(" | ");
if (links.length <= 1024)
baseEmbed.fields.push({
@ -756,9 +639,7 @@ async function processUrl(msg, url, spoiler = false) {
if (videos.length > 1) {
baseEmbed.fields.push({
name: "Videos",
value: videos
.map((attachment, index) => `[Video ${index + 1}](${attachment.url})`)
.join(" | "),
value: videos.map((attachment, index) => `[Video ${index + 1}](${attachment.url})`).join(" | "),
inline: true,
});
} else {
@ -773,9 +654,7 @@ async function processUrl(msg, url, spoiler = false) {
if (audios.length > 1) {
baseEmbed.fields.push({
name: "Audios",
value: audios
.map((attachment, index) => `[Audio ${index + 1}](${attachment.url})`)
.join(" | "),
value: audios.map((attachment, index) => `[Audio ${index + 1}](${attachment.url})`).join(" | "),
inline: true,
});
} else {
@ -796,29 +675,24 @@ async function processUrl(msg, url, spoiler = false) {
const percent = o.count / poll.total;
const bar = Math.round(percent * 30);
return `**${o.name}** (${o.count}, ${Math.round(
percent * 100
)}%)\n\`[${"=".repeat(bar)}${" ".repeat(30 - bar)}]\``;
return `**${o.name}** (${o.count}, ${Math.round(percent * 100)}%)\n\`[${"=".repeat(bar)}${" ".repeat(
30 - bar
)}]\``;
})
.join("\n\n") +
`\n\n${poll.total} votes \u2022 Ends <t:${Math.floor(
poll.end.getTime() / 1000
)}:R>`,
.join("\n\n") + `\n\n${poll.total} votes \u2022 Ends <t:${Math.floor(poll.end.getTime() / 1000)}:R>`,
});
}
let sendWait = false;
if (videos.length > 0 || audios.length > 0 || images.length > 4) {
sendWait = true;
if (msg instanceof Dysnomia.Message) await msg.addReaction("\uD83D\uDCE4");
if (msg instanceof Message) await msg.addReaction("\uD83D\uDCE4");
}
const embeds = [];
const files = [];
const guild =
msg.channel?.guild ??
(msg.guildID ? hf.bot.guilds.get(msg.guildID) : false);
const guild = msg.channel?.guild ?? (msg.guildID ? hf.bot.guilds.get(msg.guildID) : false);
if (images.length > 0) {
if (images.length <= 4) {
@ -852,9 +726,7 @@ async function processUrl(msg, url, spoiler = false) {
(cw != "" || spoiler ? "SPOILER_" : "") +
(attachment.type.indexOf("/") > -1
? attachment.type.replace("/", ".")
: attachment.type +
"." +
(url.match(/\.([a-z0-9]{3,4})$/)?.[0] ?? "png")),
: attachment.type + "." + (url.match(/\.([a-z0-9]{3,4})$/)?.[0] ?? "png")),
file,
description: attachment.desc,
});
@ -886,9 +758,7 @@ async function processUrl(msg, url, spoiler = false) {
(cw != "" || spoiler ? "SPOILER_" : "") +
(attachment.type.indexOf("/") > -1
? attachment.type.replace("/", ".")
: attachment.type +
"." +
(url.match(/\.([a-z0-9]{3,4})$/)?.[0] ?? "png")),
: attachment.type + "." + (url.match(/\.([a-z0-9]{3,4})$/)?.[0] ?? "png")),
file,
description: attachment.desc,
});
@ -959,9 +829,7 @@ async function processUrl(msg, url, spoiler = false) {
(cw != "" || spoiler ? "SPOILER_" : "") +
(attachment.type.indexOf("/") > -1
? attachment.type.replace("/", ".").replace("quicktime", "mov")
: attachment.type +
"." +
(url.match(/\.([a-z0-9]{3,4})$/)?.[0] ?? "mp4")),
: attachment.type + "." + (url.match(/\.([a-z0-9]{3,4})$/)?.[0] ?? "mp4")),
file,
});
}
@ -989,14 +857,8 @@ async function processUrl(msg, url, spoiler = false) {
filename:
(cw != "" || spoiler ? "SPOILER_" : "") +
(attachment.type.indexOf("/") > -1
? attachment.type
.replace("/", ".")
.replace("mpeg", "mp3")
.replace("vnd.wave", "wav")
.replace("x-", "")
: attachment.type +
"." +
(url.match(/\.([a-z0-9]{3,4})$/)?.[0] ?? "mp3")),
? attachment.type.replace("/", ".").replace("mpeg", "mp3").replace("vnd.wave", "wav").replace("x-", "")
: attachment.type + "." + (url.match(/\.([a-z0-9]{3,4})$/)?.[0] ?? "mp3")),
file,
});
}
@ -1006,8 +868,7 @@ async function processUrl(msg, url, spoiler = false) {
return {
response: {
content:
cw != "" &&
(images.length > 0 || videos.length > 0 || audios.length > 0)
cw != "" && (images.length > 0 || videos.length > 0 || audios.length > 0)
? `:warning: ${cw} || ${url} ||`
: spoiler
? `|| ${url} ||`
@ -1051,10 +912,7 @@ events.add("messageCreate", "fedimbed", async function (msg) {
for (const service of Object.keys(PATH_REGEX)) {
const regex = PATH_REGEX[service];
if (urlObj && regex.test(urlObj.pathname)) {
logger.verbose(
"fedimbed",
`Hit "${service}" for "${url}", processing now.`
);
logger.verbose("fedimbed", `Hit "${service}" for "${url}", processing now.`);
try {
const {response, sendWait} = await processUrl(msg, url, hasSpoiler);
await msg.channel.createMessage(response).then(() => {
@ -1067,10 +925,7 @@ events.add("messageCreate", "fedimbed", async function (msg) {
}
});
} catch (err) {
logger.error(
"fedimbed",
`Error processing "${url}":\n` + err.stack
);
logger.error("fedimbed", `Error processing "${url}":\n` + err.stack);
}
break;
}
@ -1080,8 +935,7 @@ events.add("messageCreate", "fedimbed", async function (msg) {
});
const fedimbedCommand = new InteractionCommand("fedimbed");
fedimbedCommand.helpText =
"Better embeds for fediverse (Mastodon, Pleroma, etc) posts";
fedimbedCommand.helpText = "Better embeds for fediverse (Mastodon, Pleroma, etc) posts";
fedimbedCommand.options.url = {
name: "url",
type: ApplicationCommandOptionTypes.STRING,
@ -1098,8 +952,8 @@ fedimbedCommand.options.spoiler = {
};
fedimbedCommand.permissions = Permissions.embedLinks | Permissions.attachFiles;
fedimbedCommand.callback = async function (interaction) {
let url = getOption(interaction, fedimbedCommand, "url");
const spoiler = getOption(interaction, fedimbedCommand, "spoiler");
let url = this.getOption(interaction, "url");
const spoiler = this.getOption(interaction, "spoiler");
url = url
.replace(/\|/g, "")

View file

@ -5,16 +5,15 @@ const CATEGORY = "misc";
const FOXWELLS_GUILD_ID = "300436792916836352";
const {tinycolor} = require("@ctrl/tinycolor");
const {pastelize} = require("../lib/utils.js");
const {pastelize} = require("../util/misc.js");
const {createBoardMessage} = require("../util/starboard.js");
const logger = require("../lib/logger.js");
// taken from rot13.com
function rot(s, i) {
return s.replace(/[a-zA-Z]/g, function (c) {
return String.fromCharCode(
(c <= "Z" ? 90 : 122) >= (c = c.charCodeAt(0) + i) ? c : c - 26
);
return String.fromCharCode((c <= "Z" ? 90 : 122) >= (c = c.charCodeAt(0) + i) ? c : c - 26);
});
}
@ -42,9 +41,7 @@ utsuholights.callback = async function (msg, line, [hex, bri]) {
cachedLightsURL = Buffer.from(rot(LIGHTS_URL, 13), "base64").toString(); // Wow! It's That Easy!
}
const response = await fetch(
`${cachedLightsURL}?r=${r}&g=${g}&b=${b}${bri ? `&bri=${bri}` : ""}`
);
const response = await fetch(`${cachedLightsURL}?r=${r}&g=${g}&b=${b}${bri ? `&bri=${bri}` : ""}`);
if (response.status == 200) {
return {reaction: "\uD83D\uDC4C"};
@ -54,17 +51,11 @@ utsuholights.callback = async function (msg, line, [hex, bri]) {
};
//hf.registerCommand(utsuholights);
const JPEG_HEADER = Buffer.from([
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46,
]);
const JPEG_HEADER = Buffer.from([0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46]);
async function fetchPlant() {
const res = await fetch("https://nuclear.utsuho.rocks/plant/");
const boundary = res.headers
.get("Content-Type")
.split(";")[1]
.trim()
.replace("boundary=", "--");
const boundary = res.headers.get("Content-Type").split(";")[1].trim().replace("boundary=", "--");
let buffer = Buffer.alloc(0);
let searchForNextBoundary = false;
@ -78,9 +69,7 @@ async function fetchPlant() {
} else if (searchForNextBoundary) {
if (data.toString().startsWith(boundary)) {
res.body.end();
const length = Number(
buffer.toString().match(/Content-Length:.*?(\d+)/)[1]
);
const length = Number(buffer.toString().match(/Content-Length:.*?(\d+)/)[1]);
const headerOffset = buffer.indexOf(JPEG_HEADER);
const data = buffer.slice(headerOffset, headerOffset + length);
@ -169,127 +158,6 @@ function deleteBoardEntry(id) {
});
}
async function findSuitableImage(msg) {
const out = {};
const attachments = [...msg.attachments.values()];
const attachment = attachments[0];
if (attachment) {
const url = attachment.url;
const parsed = new URL(url);
if (/(jpe?g|png|gif|webp)$/.test(parsed.pathname)) {
out.url = url;
} else if (/(mp4|webm|mov)$/.test(parsed.pathname)) {
out.video = true;
out.attachment = true;
out.url = "attachment://thumb.jpg";
let thumbUrl = attachment.proxyURL;
if (thumbUrl.includes("media.discordapp.net") && thumbUrl.includes("?")) {
if (thumbUrl.endsWith("&")) {
thumbUrl = thumbUrl += "format=jpeg";
} else {
thumbUrl = thumbUrl += "&format=jpeg";
}
} else {
thumbUrl = thumbUrl += "?format=jpeg";
}
out.file = await fetch(thumbUrl)
.then((res) => res.arrayBuffer())
.then((buf) => Buffer.from(buf));
}
} else {
for (const embed of msg.embeds) {
if (!embed.url) continue;
const parsed = new URL(embed.url);
if (embed.url && /(jpe?g|png|gif|webp)$/.test(parsed.pathname)) {
out.url = embed.url;
} else if (embed.image) {
out.url = embed.image.url;
break;
} else if (embed.video) {
out.video = true;
out.url = "attachment://thumb.jpg";
let thumbUrl =
embed.video.proxyURL ??
embed.video.url.replace("cdn.discordapp.com", "media.discordapp.net");
if (
thumbUrl.includes("media.discordapp.net") &&
thumbUrl.includes("?")
) {
if (thumbUrl.endsWith("&")) {
thumbUrl = thumbUrl += "format=jpeg";
} else {
thumbUrl = thumbUrl += "&format=jpeg";
}
} else {
thumbUrl = thumbUrl += "?format=jpeg";
}
if (embed.thumbnail?.url) thumbUrl = embed.thumbnail.url;
let valid = await fetch(thumbUrl, {method: "HEAD"}).then(
(res) => res.ok
);
if (!valid && thumbUrl.includes("media.discordapp.net")) {
thumbUrl = await hf.bot.requestHandler
.request("POST", "/attachments/refresh-urls", true, {
attachment_urls: [thumbUrl],
})
.then((res) => res.refreshed_urls[0].refreshed);
valid = true;
}
if (valid)
out.file = await fetch(thumbUrl)
.then((res) => res.arrayBuffer())
.then((buf) => Buffer.from(buf));
break;
}
}
}
return out;
}
async function createBoardMessage(msg, count, fetchAttachment = true) {
const name = msg.member?.nick ?? msg.author.globalName ?? msg.author.username;
const embed = {
title: `${count} \u2b50 - ${msg.jumpLink}`,
color: pastelize(name),
description: msg.content,
timestamp: new Date(msg.timestamp).toISOString(),
};
let image;
if (fetchAttachment) {
image = await findSuitableImage(msg);
if (image.url) {
if (image.video) {
embed.description += `\n(contains video ${
image.attachment ? "attachment" : "embed"
})`;
}
embed.image = {
url: image.url,
};
}
}
return {
avatarURL: msg.member?.avatarURL ?? msg.author.avatarURL,
username: name,
threadID: VINBOARD_THREAD_ID,
embeds: [embed],
attachments: image?.file
? [{file: image.file, filename: "thumb.jpg"}]
: null,
wait: true,
};
}
let vinboard_webhook;
let vin_channel;
let board_channel;
@ -300,9 +168,7 @@ async function processReaction(_msg, reaction, user) {
if (reaction.name != "\u2b50") return;
if (!vin_channel) {
vin_channel = hf.bot.guilds
.get(FOXWELLS_GUILD_ID)
.channels.get(VINBOARD_CHANNEL_ID);
vin_channel = hf.bot.guilds.get(FOXWELLS_GUILD_ID).channels.get(VINBOARD_CHANNEL_ID);
}
if (!vin_channel) {
@ -311,9 +177,7 @@ async function processReaction(_msg, reaction, user) {
}
if (!board_channel) {
board_channel = hf.bot.guilds
.get(FOXWELLS_GUILD_ID)
.threads.get(VINBOARD_THREAD_ID);
board_channel = hf.bot.guilds.get(FOXWELLS_GUILD_ID).threads.get(VINBOARD_THREAD_ID);
}
if (!board_channel) {
@ -321,15 +185,11 @@ async function processReaction(_msg, reaction, user) {
return;
}
const msg =
vin_channel.messages.get(_msg.id) ??
(await vin_channel.getMessage(_msg.id));
const msg = vin_channel.messages.get(_msg.id) ?? (await vin_channel.getMessage(_msg.id));
if (!vinboard_webhook) {
const webhooks = await vin_channel.getWebhooks();
vinboard_webhook = webhooks.find(
(webhook) => webhook.id == VINBOARD_WEBHOOK_ID
);
vinboard_webhook = webhooks.find((webhook) => webhook.id == VINBOARD_WEBHOOK_ID);
}
if (!vinboard_webhook) {
@ -338,9 +198,7 @@ async function processReaction(_msg, reaction, user) {
}
const reacts = await msg.getReaction("\u2b50");
const trueCount = reacts.filter(
(reactor) => reactor.id != msg.author.id
).length;
const trueCount = reacts.filter((reactor) => reactor.id != msg.author.id).length;
const dbEntry = await getBoardEntry(msg.id);
if (dbEntry) {
@ -360,12 +218,7 @@ async function processReaction(_msg, reaction, user) {
board_channel.messages.get(dbEntry.board_id) ??
(await board_channel.getMessage(dbEntry.board_id).catch(() => {}));
if (_boardMessage) {
logger.verbose(
"vinboard",
`Updating count for "${msg.id}" (${
dbEntry.count ?? 0
} -> ${trueCount})`
);
logger.verbose("vinboard", `Updating count for "${msg.id}" (${dbEntry.count ?? 0} -> ${trueCount})`);
const props = {
avatarURL: _boardMessage.author.avatarURL,
@ -374,24 +227,17 @@ async function processReaction(_msg, reaction, user) {
embeds: _boardMessage.embeds,
wait: true,
};
props.attachments = [..._boardMessage.attachments.values()].map(
(attach) => ({id: attach.id})
);
props.attachments = [..._boardMessage.attachments.values()].map((attach) => ({id: attach.id}));
props.embeds[0].title = `${trueCount} \u2b50`;
props.embeds[0].color = pastelize(msg.author.username);
await hf.bot.editWebhookMessage(
vinboard_webhook.id,
vinboard_webhook.token,
_boardMessage.id,
props
);
await hf.bot.editWebhookMessage(vinboard_webhook.id, vinboard_webhook.token, _boardMessage.id, props);
await setBoardEntry(msg.id, trueCount, _boardMessage.id);
} else {
logger.verbose("vinboard", `Creating entry for "${msg.id}"`);
const boardMessage = await hf.bot.executeWebhook(
vinboard_webhook.id,
vinboard_webhook.token,
await createBoardMessage(msg, trueCount)
await createBoardMessage(msg, trueCount, VINBOARD_THREAD_ID)
);
await setBoardEntry(msg.id, trueCount, boardMessage.id);
}
@ -400,7 +246,7 @@ async function processReaction(_msg, reaction, user) {
const boardMessage = await hf.bot.executeWebhook(
vinboard_webhook.id,
vinboard_webhook.token,
await createBoardMessage(msg, trueCount)
await createBoardMessage(msg, trueCount, VINBOARD_THREAD_ID)
);
await setBoardEntry(msg.id, trueCount, boardMessage.id);
}
@ -409,7 +255,7 @@ async function processReaction(_msg, reaction, user) {
const boardMessage = await hf.bot.executeWebhook(
vinboard_webhook.id,
vinboard_webhook.token,
await createBoardMessage(msg, trueCount)
await createBoardMessage(msg, trueCount, VINBOARD_THREAD_ID)
);
await setBoardEntry(msg.id, trueCount, boardMessage.id);
}

View file

@ -1,7 +1,7 @@
const Command = require("../lib/command.js");
const CATEGORY = "general";
const {snowflakeToTimestamp} = require("../lib/utils.js");
const {snowflakeToTimestamp} = require("../util/time.js");
const help = new Command("help");
help.category = CATEGORY;
@ -27,9 +27,7 @@ help.callback = function (msg, line) {
for (const cat in sorted) {
embed.fields.push({
name: cat.toUpperCase().charAt(0) + cat.toLowerCase().substring(1),
value: `${sorted[cat].length} Commands\n\`${
hf.config.prefix
}help --${cat.toLowerCase()}\``,
value: `${sorted[cat].length} Commands\n\`${hf.config.prefix}help --${cat.toLowerCase()}\``,
inline: true,
});
}
@ -40,9 +38,7 @@ help.callback = function (msg, line) {
if (sorted[cat]) {
const embed = {
title: `HiddenPhox Help: Category > ${
cat.toUpperCase().charAt(0) + cat.toLowerCase().substring(1)
}`,
title: `HiddenPhox Help: Category > ${cat.toUpperCase().charAt(0) + cat.toLowerCase().substring(1)}`,
fields: [],
};
@ -78,9 +74,7 @@ help.callback = function (msg, line) {
fields: [
{
name: "Category",
value:
cmd.category.toUpperCase().charAt(0) +
cmd.category.toLowerCase().substring(1),
value: cmd.category.toUpperCase().charAt(0) + cmd.category.toLowerCase().substring(1),
inline: true,
},
],
@ -125,9 +119,7 @@ ping.callback = async function (msg) {
const msgTimestamp = snowflakeToTimestamp(msg.id);
const noncePing = Math.floor(msgTimestamp - nonceTimestamp);
const gateway = hf.bot.shards.get(
hf.bot.guildShardMap[hf.bot.channelGuildMap[msg.channel.id]] || 0
).latency;
const gateway = hf.bot.shards.get(hf.bot.guildShardMap[hf.bot.channelGuildMap[msg.channel.id]] || 0).latency;
const newMsg = await msg.channel.createMessage({
content: `Pong.\n**RTT:** \`...\`\n**Gateway:** \`${gateway}ms\`${

View file

@ -1,9 +1,9 @@
const Command = require("../lib/command.js");
const CATEGORY = "image";
const {getImage} = require("../lib/utils.js");
const {getImage} = require("../util/image.js");
const dumpyConvert = require("dumpy").convert;
//const dumpyConvert = require("dumpy").convert;
const Jimp = require("jimp");
async function createImageCallback(msg, url, callback, filename) {
@ -29,7 +29,7 @@ async function createImageCallback(msg, url, callback, filename) {
}
}
const dumpy = new Command("dumpy");
/*const dumpy = new Command("dumpy");
dumpy.category = CATEGORY;
dumpy.helpText = "Among Us Dumpy GIF Creator";
dumpy.usage = "<width> [url]";
@ -37,14 +37,9 @@ dumpy.callback = async function (msg, line, [width, url]) {
if (isNaN(parseInt(width))) url = width;
width = Math.min(Math.max(isNaN(parseInt(width)) ? 10 : width, 2), 48);
return await createImageCallback(
msg,
url,
async (img) => await dumpyConvert(img, width),
"dumpy.gif"
);
return await createImageCallback(msg, url, async (img) => await dumpyConvert(img, width), "dumpy.gif");
};
hf.registerCommand(dumpy);
hf.registerCommand(dumpy);*/
const hooh = new Command("hooh");
hooh.category = CATEGORY;
@ -56,14 +51,7 @@ hooh.callback = async function (msg, line) {
line,
async (url) => {
const img = await Jimp.read(url);
const half1 = img
.clone()
.crop(
0,
img.bitmap.height / 2,
img.bitmap.width,
img.bitmap.height / 2
);
const half1 = img.clone().crop(0, img.bitmap.height / 2, img.bitmap.width, img.bitmap.height / 2);
const half2 = half1.clone().mirror(false, true);
return await img
@ -86,9 +74,7 @@ woow.callback = async function (msg, line) {
line,
async (url) => {
const img = await Jimp.read(url);
const half1 = img
.clone()
.crop(0, 0, img.bitmap.width, img.bitmap.height / 2);
const half1 = img.clone().crop(0, 0, img.bitmap.width, img.bitmap.height / 2);
const half2 = half1.clone().mirror(false, true);
return await img
@ -111,9 +97,7 @@ haah.callback = async function (msg, line) {
line,
async (url) => {
const img = await Jimp.read(url);
const half1 = img
.clone()
.crop(0, 0, img.bitmap.width / 2, img.bitmap.height);
const half1 = img.clone().crop(0, 0, img.bitmap.width / 2, img.bitmap.height);
const half2 = half1.clone().mirror(true, false);
return await img
@ -136,9 +120,7 @@ waaw.callback = async function (msg, line) {
line,
async (url) => {
const img = await Jimp.read(url);
const half1 = img
.clone()
.crop(img.bitmap.width / 2, 0, img.bitmap.width / 2, img.bitmap.height);
const half1 = img.clone().crop(img.bitmap.width / 2, 0, img.bitmap.width / 2, img.bitmap.height);
const half2 = half1.clone().mirror(true, false);
return await img
@ -160,9 +142,7 @@ invert.callback = async function (msg, line) {
msg,
line,
async (img) => {
return await Jimp.read(img).then((x) =>
x.invert().getBufferAsync(Jimp.MIME_PNG)
);
return await Jimp.read(img).then((x) => x.invert().getBufferAsync(Jimp.MIME_PNG));
},
"invert.png"
);
@ -178,9 +158,7 @@ flip.callback = async function (msg, line) {
msg,
line,
async (img) => {
return await Jimp.read(img).then((x) =>
x.mirror(true, false).getBufferAsync(Jimp.MIME_PNG)
);
return await Jimp.read(img).then((x) => x.mirror(true, false).getBufferAsync(Jimp.MIME_PNG));
},
"flip.png"
);
@ -196,9 +174,7 @@ flop.callback = async function (msg, line) {
msg,
line,
async (img) => {
return await Jimp.read(img).then((x) =>
x.mirror(false, true).getBufferAsync(Jimp.MIME_PNG)
);
return await Jimp.read(img).then((x) => x.mirror(false, true).getBufferAsync(Jimp.MIME_PNG));
},
"flop.png"
);
@ -215,9 +191,7 @@ jpeg.callback = async function (msg, line) {
msg,
line,
async (img) => {
return await Jimp.read(img).then((x) =>
x.quality(1).getBufferAsync(Jimp.MIME_JPEG)
);
return await Jimp.read(img).then((x) => x.quality(1).getBufferAsync(Jimp.MIME_JPEG));
},
"jpeg.jpg"
);

View file

@ -3,16 +3,12 @@ const InteractionCommand = require("../lib/interactionCommand.js");
const logger = require("../lib/logger.js");
const CATEGORY = "misc";
const {ApplicationCommandOptionTypes} =
require("@projectdysnomia/dysnomia").Constants;
const {librex} = require("../../config.json");
const {getOption} = require("../lib/interactionDispatcher.js");
const {
formatTime,
parseHtmlEntities,
formatUsername,
safeString,
} = require("../lib/utils.js");
const {ApplicationCommandOptionTypes} = require("../util/dconstants.js");
const {formatUsername, safeString} = require("../util/misc.js");
const {formatTime} = require("../util/time.js");
const {parseHtmlEntities} = require("../util/html.js");
const GoogleImages = require("google-images");
const {tinycolor, random: randomColor} = require("@ctrl/tinycolor");
const sharp = require("sharp");
@ -28,15 +24,13 @@ yt.usage = "[search term]";
yt.callback = async function (msg, line) {
if (!line) return "Arguments are required.";
const req = await fetch(
`${hf.config.piped_api}/search?q=${encodeURIComponent(line)}&filter=videos`
).then((x) => x.json());
const req = await fetch(`${hf.config.piped_api}/search?q=${encodeURIComponent(line)}&filter=videos`).then((x) =>
x.json()
);
const topVid = req.items[0];
let out = `**${safeString(
parseHtmlEntities(topVid.title)
)}** | \`${safeString(
let out = `**${safeString(parseHtmlEntities(topVid.title))}** | \`${safeString(
parseHtmlEntities(topVid.uploaderName)
)}\`\nhttps://youtube.com${topVid.url}\n\n**__See Also:__**\n`;
@ -44,9 +38,7 @@ yt.callback = async function (msg, line) {
const vid = req.items[i];
if (!vid) continue;
out += `- **${safeString(
parseHtmlEntities(vid.title)
)}** | By: \`${safeString(
out += `- **${safeString(parseHtmlEntities(vid.title))}** | By: \`${safeString(
parseHtmlEntities(vid.uploaderName)
)}\` | <https://youtube.com${vid.url}>\n`;
}
@ -65,7 +57,7 @@ ytInteraction.options.search = {
default: "",
};
ytInteraction.callback = async function (interaction) {
const search = getOption(interaction, ytInteraction, "search");
const search = this.getOption(interaction, "search");
return yt.callback(interaction, search);
};
@ -78,9 +70,9 @@ fyt.usage = "[search term]";
fyt.callback = async function (msg, line) {
if (!line) return "Arguments are required.";
const req = await fetch(
`${hf.config.piped_api}/search?q=${encodeURIComponent(line)}&filter=videos`
).then((x) => x.json());
const req = await fetch(`${hf.config.piped_api}/search?q=${encodeURIComponent(line)}&filter=videos`).then((x) =>
x.json()
);
const vid = req.items[0];
@ -100,7 +92,7 @@ fytInteraction.options.search = {
default: "",
};
fytInteraction.callback = async function (interaction) {
const search = getOption(interaction, fytInteraction, "search");
const search = this.getOption(interaction, "search");
return fyt.callback(interaction, search);
};
@ -119,9 +111,7 @@ wolfram.callback = async function (msg, line, args, {verbose, v}) {
const query = args.join(" ");
const req = await fetch(
`http://api.wolframalpha.com/v2/query?input=${encodeURIComponent(
query
)}&appid=LH2K8H-T3QKETAGT3&output=json`
`http://api.wolframalpha.com/v2/query?input=${encodeURIComponent(query)}&appid=LH2K8H-T3QKETAGT3&output=json`
).then((x) => x.json());
const data = req.queryresult.pods;
@ -148,11 +138,9 @@ wolfram.callback = async function (msg, line, args, {verbose, v}) {
for (const x in extra) {
embed.fields.push({
name: extra[x].title,
value: `[${
extra[x].subpods[0].plaintext.length > 0
? extra[x].subpods[0].plaintext
: "<click for image>"
}](${extra[x].subpods[0].img.src})`,
value: `[${extra[x].subpods[0].plaintext.length > 0 ? extra[x].subpods[0].plaintext : "<click for image>"}](${
extra[x].subpods[0].img.src
})`,
inline: true,
});
}
@ -173,8 +161,7 @@ wolfram.callback = async function (msg, line, args, {verbose, v}) {
}
let string = "";
if (data[1].subpods[0].plaintext.length > 0)
string = safeString(data[1].subpods[0].plaintext);
if (data[1].subpods[0].plaintext.length > 0) string = safeString(data[1].subpods[0].plaintext);
let text;
if (string.length > 2000 - (6 + safeString(query).length)) {
@ -216,8 +203,8 @@ wolframInteraction.options.verbose = {
default: false,
};
wolframInteraction.callback = async function (interaction) {
const query = getOption(interaction, wolframInteraction, "query");
const verbose = getOption(interaction, wolframInteraction, "verbose");
const query = this.getOption(interaction, "query");
const verbose = this.getOption(interaction, "verbose");
return wolfram.callback(interaction, query, [query], {verbose});
};
@ -232,10 +219,7 @@ gimg.callback = async function (msg, line) {
if (!line) return "No arguments given.";
const images = await imagesClient.search(line, {
safe:
msg.channel.nsfw && !msg.channel?.topic.includes("[no_nsfw]")
? "off"
: "high",
safe: msg.channel.nsfw && !msg.channel?.topic.includes("[no_nsfw]") ? "off" : "high",
});
const index = Math.floor(Math.random() * images.length);
@ -250,9 +234,7 @@ gimg.callback = async function (msg, line) {
url: image.url,
},
footer: {
text: `Image ${Number(index) + 1}/${
images.length
}. Rerun to get a different image.`,
text: `Image ${Number(index) + 1}/${images.length}. Rerun to get a different image.`,
},
},
],
@ -268,10 +250,7 @@ fimg.callback = async function (msg, line) {
if (!line) return "No arguments given.";
const images = await imagesClient.search(line, {
safe:
msg.channel.nsfw && !msg.channel?.topic.includes("[no_nsfw]")
? "off"
: "high",
safe: msg.channel.nsfw && !msg.channel?.topic.includes("[no_nsfw]") ? "off" : "high",
});
const image = images[0];
@ -295,22 +274,15 @@ poll.category = CATEGORY;
poll.helpText = "Start a poll";
poll.usage = "[topic] [option 1] [option 2] [...option 3-10]";
poll.callback = async function (msg, line, [topic, ...options]) {
if (!line || !topic)
return 'Usage: hf!poll "topic" "option 1" "option 2" "...options 3-10"';
if (!line || !topic) return 'Usage: hf!poll "topic" "option 1" "option 2" "...options 3-10"';
const arrOptions = [...options].slice(0, 10);
if (arrOptions.length < 2) return "A minimum of two options are required.";
const reactions = [];
let displayString = `**${formatUsername(
msg.author
)}** has started a poll:\n## __${topic}__\n`;
let displayString = `**${formatUsername(msg.author)}** has started a poll:\n## __${topic}__\n`;
for (let i = 0; i < arrOptions.length; i++) {
displayString +=
(i === 9 ? "\ud83d\udd1f" : `${i + 1}\u20e3`) +
": " +
arrOptions[i] +
"\n";
displayString += (i === 9 ? "\ud83d\udd1f" : `${i + 1}\u20e3`) + ": " + arrOptions[i] + "\n";
reactions[i] = i === 9 ? "\ud83d\udd1f" : `${i + 1}\u20e3`;
}
@ -353,28 +325,20 @@ anonradio.callback = async function () {
let playing;
try {
playing = await fetch("https://anonradio.net/playing").then((res) =>
res.text()
);
playing = await fetch("https://anonradio.net/playing").then((res) => res.text());
} catch (err) {
try {
playing = await fetch("http://anonradio.net/playing").then((res) =>
res.text()
);
playing = await fetch("http://anonradio.net/playing").then((res) => res.text());
} catch (err) {
//
}
}
let schedule;
try {
schedule = await fetch("https://anonradio.net/schedule/").then((res) =>
res.text()
);
schedule = await fetch("https://anonradio.net/schedule/").then((res) => res.text());
} catch (err) {
try {
schedule = await fetch("http://anonradio.net/schedule/").then((res) =>
res.text()
);
schedule = await fetch("http://anonradio.net/schedule/").then((res) => res.text());
} catch (err) {
//
}
@ -384,9 +348,7 @@ anonradio.callback = async function () {
const icecast = await fetch("http://anonradio.net:8010/status-json.xsl")
.then((res) => res.text())
.then((data) =>
JSON.parse(data.replace(/"title": - ,/g, '"title":" - ",'))
);
.then((data) => JSON.parse(data.replace(/"title": - ,/g, '"title":" - ",')));
let lines = schedule.split("\n");
lines = lines.slice(4, lines.length - 2);
@ -418,17 +380,11 @@ anonradio.callback = async function () {
hour = hour.slice(0, 2) + ":" + hour.slice(-2);
const timestamp =
Date.parse(
`${DAYS[targetDay]}, ${currentYear}-${targetMonth}-${targetDateDay} ${hour} UTC`
) / 1000;
Date.parse(`${DAYS[targetDay]}, ${currentYear}-${targetMonth}-${targetDateDay} ${hour} UTC`) / 1000;
let nameOut = name;
if (time == "Sat 0300")
nameOut = name.replace(
"Open Mic - Anyone can stream",
"Synth Battle Royale"
);
if (time == "Sat 0300") nameOut = name.replace("Open Mic - Anyone can stream", "Synth Battle Royale");
parsedLines.push({
timestamp,
@ -451,14 +407,9 @@ anonradio.callback = async function () {
title = `${liveNow.name} (\`${liveNow.id}\`)`;
subtitle = playing;
} else {
const [_, current, peakDay, peakMonth, dj, metadata] = playing.match(
/\[(\d+)\/(\d+)\/(\d+)\] \((.+?)\): (.+)/
);
const [_, current, peakDay, peakMonth, dj, metadata] = playing.match(/\[(\d+)\/(\d+)\/(\d+)\] \((.+?)\): (.+)/);
if (
metadata == "https://archives.anonradio.net" ||
liveNow.name == "Synth Battle Royale"
) {
if (metadata == "https://archives.anonradio.net" || liveNow.name == "Synth Battle Royale") {
title = `${liveNow.name} (\`${dj}\`)`;
} else {
title = `${metadata} (\`${dj}\`)`;
@ -468,14 +419,10 @@ anonradio.callback = async function () {
let openmicTime = "";
if (liveNow.id == "openmic") {
const streamData = icecast.icestats.source.find(
(src) => src.listenurl == "http://anonradio.net:8010/openmic"
);
const streamData = icecast.icestats.source.find((src) => src.listenurl == "http://anonradio.net:8010/openmic");
if (streamData && streamData.stream_start_iso8601) {
const startTime = new Date(streamData.stream_start_iso8601).getTime();
openmicTime = `-\\*- OpenMIC DJ has been streaming for ${formatTime(
Date.now() - startTime
)} -\\*-\n`;
openmicTime = `-\\*- OpenMIC DJ has been streaming for ${formatTime(Date.now() - startTime)} -\\*-\n`;
}
}
@ -507,9 +454,7 @@ shodan.callback = async function (msg, line) {
if (!line || line == "") return "Arguments required.";
if (!REGEX_IPV4.test(line)) return "Invalid IP address.";
const data = await fetch("https://internetdb.shodan.io/" + line).then((res) =>
res.json()
);
const data = await fetch("https://internetdb.shodan.io/" + line).then((res) => res.json());
if (data.detail) return data.detail;
@ -519,10 +464,7 @@ shodan.callback = async function (msg, line) {
fields: [
{
name: "Hostnames",
value:
data.hostnames.length > 0
? data.hostnames.map((str) => `\`${str}\``).join(" ")
: "None",
value: data.hostnames.length > 0 ? data.hostnames.map((str) => `\`${str}\``).join(" ") : "None",
inline: true,
},
{
@ -532,26 +474,17 @@ shodan.callback = async function (msg, line) {
},
{
name: "Tags",
value:
data.tags.length > 0
? data.tags.map((str) => `\`${str}\``).join(", ")
: "None",
value: data.tags.length > 0 ? data.tags.map((str) => `\`${str}\``).join(", ") : "None",
inline: true,
},
{
name: "CPEs",
value:
data.cpes.length > 0
? data.cpes.map((str) => `\`${str}\``).join(" ")
: "None",
value: data.cpes.length > 0 ? data.cpes.map((str) => `\`${str}\``).join(" ") : "None",
inline: true,
},
{
name: "Vulnerabilities",
value:
data.vulns.length > 0
? data.vulns.map((str) => `\`${str}\``).join(" ")
: "None",
value: data.vulns.length > 0 ? data.vulns.map((str) => `\`${str}\``).join(" ") : "None",
inline: true,
},
],
@ -596,9 +529,7 @@ generate.callback = async function (msg, line) {
const title = `Responses for "${safeString(line)}"`;
const out = {
content: `Generated in ${formatTime(Date.now() - start)}${
retries > 0 ? " with " + retries + " retries" : ""
}`,
content: `Generated in ${formatTime(Date.now() - start)}${retries > 0 ? " with " + retries + " retries" : ""}`,
embeds: [],
files: images,
};
@ -627,7 +558,7 @@ search.addAlias("google");
search.addAlias("ddg");
search.callback = async function (msg, line, args, {results = 2}) {
const query = args.join(" ");
if (!librex) return "LibreX instance not defined.";
if (!hf.config.librex) return "LibreX instance not defined.";
if (!query || query == "") return "Search query required.";
const encodedQuery = encodeURIComponent(query);
@ -638,9 +569,7 @@ search.callback = async function (msg, line, args, {results = 2}) {
if (res.url != url) return res.url;
}
const res = await fetch(`${librex}/api.php?q=${encodedQuery}&p=0&t=0`).then(
(res) => res.json()
);
const res = await fetch(`${hf.config.librex}/api.php?q=${encodedQuery}&p=0&t=0`).then((res) => res.json());
if (res.error?.message) {
if (res.error.message.indexOf("No results found.") > -1) {
return "Search returned no results.";
@ -649,27 +578,17 @@ search.callback = async function (msg, line, args, {results = 2}) {
}
} else {
const searchResults = Object.values(res)
.filter(
(result) => result.did_you_mean == null && typeof result !== "string"
)
.filter((result) => result.did_you_mean == null && typeof result !== "string")
.splice(0, Number(results));
if (searchResults.length > 0) {
let out = `__**Results for \`${safeString(query)}\`**__\n`;
for (const result of searchResults) {
if (result.special_response) {
out +=
"> " +
safeString(
parseHtmlEntities(
result.special_response.response.split("\n").join("\n> ")
)
);
out += "> " + safeString(parseHtmlEntities(result.special_response.response.split("\n").join("\n> ")));
out += `\n<${encodeURI(result.special_response.source)}>`;
} else {
out += `**${safeString(
parseHtmlEntities(result.title)
).trim()}** - <${encodeURI(result.url)}>`;
out += `**${safeString(parseHtmlEntities(result.title)).trim()}** - <${encodeURI(result.url)}>`;
out += `\n> ${safeString(parseHtmlEntities(result.description))}`;
}
out += "\n\n";
@ -702,8 +621,8 @@ searchInteraction.options.results = {
default: 2,
};
searchInteraction.callback = async function (interaction) {
const query = getOption(interaction, searchInteraction, "query");
const results = getOption(interaction, searchInteraction, "results");
const query = this.getOption(interaction, "query");
const results = this.getOption(interaction, "results");
return search.callback(interaction, query, [query], {results});
};
@ -717,9 +636,7 @@ color.callback = async function (msg, line, args, {truerandom}) {
random = false;
if (!line || line == "" || args.length == 0) {
color = truerandom
? tinycolor(Math.floor(Math.random() * 0xffffff))
: randomColor();
color = truerandom ? tinycolor(Math.floor(Math.random() * 0xffffff)) : randomColor();
random = true;
}
@ -806,14 +723,13 @@ colorInteraction.options.input = {
colorInteraction.options.truerandom = {
name: "truerandom",
type: ApplicationCommandOptionTypes.BOOLEAN,
description:
"Should the random color give a 'true random' color instead of an adjust color",
description: "Should the random color give a 'true random' color instead of an adjust color",
required: false,
default: false,
};
colorInteraction.callback = async function (interaction) {
const input = getOption(interaction, colorInteraction, "input");
const truerandom = getOption(interaction, colorInteraction, "truerandom");
const input = this.getOption(interaction, "input");
const truerandom = this.getOption(interaction, "truerandom");
return color.callback(interaction, input, [input], {truerandom});
};
@ -849,10 +765,7 @@ const handshake = Buffer.concat([
identPort,
writeVarInt(1),
]);
const handshakeWithLength = Buffer.concat([
writeVarInt(handshake.length),
handshake,
]);
const handshakeWithLength = Buffer.concat([writeVarInt(handshake.length), handshake]);
const status = Buffer.concat([writeVarInt(1), writeVarInt(0x0)]);
@ -927,12 +840,7 @@ mcserver.callback = async function (msg, line) {
}
const dataAsString = totalData.toString().trim();
console.log(dataAsString);
const json = JSON.parse(
dataAsString.slice(
dataAsString.indexOf("{"),
dataAsString.lastIndexOf("}") + 1
)
);
const json = JSON.parse(dataAsString.slice(dataAsString.indexOf("{"), dataAsString.lastIndexOf("}") + 1));
logger.verbose("mcserver", "close", json);
clearTimeout(timeout);
return resolve(json);
@ -946,22 +854,16 @@ mcserver.callback = async function (msg, line) {
} else {
await msg.removeReaction("\uD83C\uDFD3");
const motd = data.description.text.replace(
/\u00a7([a-f0-9k-or])/gi,
(formatting) => {
const ansi = formattingToAnsi[formatting];
return ansi ? `\x1b[${ansi}m` : "";
}
);
const motd = data.description.text.replace(/\u00a7([a-f0-9k-or])/gi, (formatting) => {
const ansi = formattingToAnsi[formatting];
return ansi ? `\x1b[${ansi}m` : "";
});
const players = data.players?.sample?.map((player) => player.name) ?? [];
const totalPlayers = `(${data.players.online}/${data.players.max})`;
let image;
if (data.favicon) {
image = Buffer.from(
data.favicon.slice(data.favicon.indexOf(",")),
"base64"
);
image = Buffer.from(data.favicon.slice(data.favicon.indexOf(",")), "base64");
}
return {

View file

@ -1,7 +1,8 @@
const Command = require("../lib/command.js");
const CATEGORY = "moderation";
const {lookupUser, formatUsername} = require("../lib/utils.js");
const {formatUsername} = require("../util/misc.js");
const {lookupUser} = require("../util/selection.js");
const tidy = new Command("tidy");
tidy.addAlias("prune");
@ -14,16 +15,10 @@ tidy.callback = async function (msg, line, [subcommand, count, extra]) {
if (!showHelp && !msg.guildID) {
return "Can only be used in guilds.";
}
if (
!showHelp &&
!msg.channel.permissionsOf(msg.author.id).has("manageMessages")
) {
if (!showHelp && !msg.channel.permissionsOf(msg.author.id).has("manageMessages")) {
return "You do not have `Manage Messages` permission.";
}
if (
!showHelp &&
!msg.channel.permissionsOf(hf.bot.user.id).has("manageMessages")
) {
if (!showHelp && !msg.channel.permissionsOf(hf.bot.user.id).has("manageMessages")) {
return "I do not have `Manage Messages` permission.";
}
if (msg.author.bot) return "Zero-width space your say command.";
@ -36,10 +31,7 @@ tidy.callback = async function (msg, line, [subcommand, count, extra]) {
limit: count && parseInt(count) > 0 ? parseInt(count) : 10,
})
.then((msgs) => msgs.map((m) => m.id));
await msg.channel.deleteMessages(
messages,
`Message purge by ${formatUsername(msg.author)}`
);
await msg.channel.deleteMessages(messages, `Message purge by ${formatUsername(msg.author)}`);
return `Deleted ${messages.length} message(s).`;
}
@ -53,14 +45,10 @@ tidy.callback = async function (msg, line, [subcommand, count, extra]) {
before: msg.id,
limit: extra && parseInt(extra) > 0 ? parseInt(extra) : 10,
})
.then((msgs) =>
msgs.filter((m) => m.author.id == user.id).map((m) => m.id)
);
.then((msgs) => msgs.filter((m) => m.author.id == user.id).map((m) => m.id));
await msg.channel.deleteMessages(
messages,
`Message purge by ${formatUsername(
msg.author
)} targeting ${formatUsername(user)}`
`Message purge by ${formatUsername(msg.author)} targeting ${formatUsername(user)}`
);
return `Deleted ${messages.length} message(s).`;
@ -73,29 +61,20 @@ tidy.callback = async function (msg, line, [subcommand, count, extra]) {
limit: count && parseInt(count) > 0 ? parseInt(count) : 50,
})
.then((msgs) => msgs.filter((m) => msg.author.bot).map((m) => m.id));
await msg.channel.deleteMessages(
messages,
`Message purge by ${formatUsername(msg.author)} targeting bots`
);
await msg.channel.deleteMessages(messages, `Message purge by ${formatUsername(msg.author)} targeting bots`);
return `Deleted ${messages.length} message(s).`;
}
case "filter": {
if (count.length === 0)
return "Filter must be at least 1 character long.";
if (count.length === 0) return "Filter must be at least 1 character long.";
const messages = await msg.channel
.getMessages({
before: msg.id,
limit: extra && parseInt(extra) > 0 ? parseInt(extra) : 10,
})
.then((msgs) =>
msgs.filter((m) => m.content.indexOf(count) > -1).map((m) => m.id)
);
await msg.channel.deleteMessages(
messages,
`Message purge by ${formatUsername(msg.author)} targeting "${count}"`
);
.then((msgs) => msgs.filter((m) => m.content.indexOf(count) > -1).map((m) => m.id));
await msg.channel.deleteMessages(messages, `Message purge by ${formatUsername(msg.author)} targeting "${count}"`);
return `Deleted ${messages.length} message(s).`;
}

View file

@ -4,23 +4,18 @@ const {Readable} = require("node:stream");
const ffprobe = require("node-ffprobe");
const Command = require("../lib/command.js");
const {
formatTime,
parseHtmlEntities,
formatUsername,
selectionMessage,
} = require("../lib/utils.js");
const {formatUsername} = require("../util/misc.js");
const {formatTime} = require("../util/time.js");
const {parseHtmlEntities} = require("../util/html.js");
const {selectionMessage} = require("../util/selection.js");
const REGEX_YOUTUBE = /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.?be)\/.+$/;
const REGEX_YOUTUBE_PLAYLIST =
/^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.?be)\/playlist\?list=(.+)$/;
const REGEX_YOUTUBE_PLAYLIST = /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.?be)\/playlist\?list=(.+)$/;
const REGEX_YOUTUBE_PLAYLIST_SHORT = /^PL[a-zA-Z0-9-_]{1,32}$/;
const REGEX_SOUNDCLOUD =
/^((https?:\/\/)?(www\.|m\.)?soundcloud\.com\/|sc:).+\/.+$/;
const REGEX_SOUNDCLOUD_PLAYLIST =
/^((https?:\/\/)?(www\.|m\.)?soundcloud\.com\/|sc:).+\/(sets\/.+|likes|tracks)$/;
const REGEX_FILE =
/^(https?:\/\/)?.*\..*\/.+\.(mp3|ogg|flac|wav|webm|mp4|mov|mkv|mod|s3m|it|xm)$/;
const REGEX_SOUNDCLOUD = /^((https?:\/\/)?(www\.|m\.)?soundcloud\.com\/|sc:).+\/.+$/;
const REGEX_SOUNDCLOUD_PLAYLIST = /^((https?:\/\/)?(www\.|m\.)?soundcloud\.com\/|sc:).+\/(sets\/.+|likes|tracks)$/;
const REGEX_FILE = /^(https?:\/\/)?.*\..*\/.+\.(mp3|ogg|flac|wav|webm|mp4|mov|mkv|mod|s3m|it|xm)$/;
let SOUNDCLOUD_CLIENTID;
@ -40,9 +35,7 @@ async function getSoundcloudClientID() {
return SOUNDCLOUD_CLIENTID;
}
const page = await fetch("https://soundcloud.com").then((res) => res.text());
const scripts = page
.match(/<script crossorigin src="(.+?)"><\/script>/g)
.reverse();
const scripts = page.match(/<script crossorigin src="(.+?)"><\/script>/g).reverse();
for (const script of scripts) {
const url = script.match(/src="(.+?)"/)[1];
const contents = await fetch(url).then((res) => res.text());
@ -55,37 +48,23 @@ async function getSoundcloudClientID() {
return null;
}
async function processPlaylist(
url,
type,
shuffle = false,
limit = -1,
offset = 0
) {
async function processPlaylist(url, type, shuffle = false, limit = -1, offset = 0) {
let playlist;
if (type === "yt") {
const playlistId =
url.match(REGEX_YOUTUBE_PLAYLIST)?.[4] ??
url.match(REGEX_YOUTUBE_PLAYLIST_SHORT)?.[0];
const playlistId = url.match(REGEX_YOUTUBE_PLAYLIST)?.[4] ?? url.match(REGEX_YOUTUBE_PLAYLIST_SHORT)?.[0];
if (!playlistId) return null;
const baseUrl = "/playlists/" + playlistId;
const data = await fetch(hf.config.piped_api + baseUrl).then((res) =>
res.json()
);
const data = await fetch(hf.config.piped_api + baseUrl).then((res) => res.json());
playlist = data.relatedStreams;
let pageToken = data.nextpage;
while (pageToken?.startsWith("{")) {
const pageData = await fetch(
hf.config.piped_api +
"/nextpage" +
baseUrl +
"&nextpage=" +
encodeURIComponent(pageToken)
hf.config.piped_api + "/nextpage" + baseUrl + "&nextpage=" + encodeURIComponent(pageToken)
).then((res) => res.json());
if (pageData.nextpage) pageToken = pageData.nextpage;
@ -100,23 +79,18 @@ async function processPlaylist(
).then((res) => res.json());
while (!userInfo.uri) {
userInfo = await fetch(
`https://api-v2.soundcloud.com/resolve?url=${url}&client_id=${clientId}&limit=500`
).then((res) => res.json());
userInfo = await fetch(`https://api-v2.soundcloud.com/resolve?url=${url}&client_id=${clientId}&limit=500`).then(
(res) => res.json()
);
}
const likesUrl =
userInfo.uri.replace("api.", "api-v2.") +
"/likes?limit=500&client_id=" +
clientId;
const likesUrl = userInfo.uri.replace("api.", "api-v2.") + "/likes?limit=500&client_id=" + clientId;
let currentLikes = await fetch(likesUrl).then((res) => res.json());
playlist = currentLikes.collection;
while (currentLikes.next_href != null) {
currentLikes = await fetch(
currentLikes.next_href + "&client_id=" + clientId
).then((res) => res.json());
currentLikes = await fetch(currentLikes.next_href + "&client_id=" + clientId).then((res) => res.json());
playlist = [...playlist, ...currentLikes.collection];
}
} else if (url.indexOf("/tracks")) {
@ -125,29 +99,22 @@ async function processPlaylist(
).then((res) => res.json());
while (!userInfo.uri) {
userInfo = await fetch(
`https://api-v2.soundcloud.com/resolve?url=${url}&client_id=${clientId}&limit=500`
).then((res) => res.json());
userInfo = await fetch(`https://api-v2.soundcloud.com/resolve?url=${url}&client_id=${clientId}&limit=500`).then(
(res) => res.json()
);
}
const tracksUrl =
userInfo.uri.replace("api.", "api-v2.") +
"/tracks?limit=500&client_id=" +
clientId;
const tracksUrl = userInfo.uri.replace("api.", "api-v2.") + "/tracks?limit=500&client_id=" + clientId;
let currentTracks = await fetch(tracksUrl).then((res) => res.json());
playlist = currentTracks.collection;
while (currentTracks.next_href != null) {
currentTracks = await fetch(
currentTracks.next_href + "&client_id=" + clientId
).then((res) => res.json());
currentTracks = await fetch(currentTracks.next_href + "&client_id=" + clientId).then((res) => res.json());
playlist = [...playlist, ...currentTracks.collection];
}
} else {
playlist = await fetch(
`https://api-v2.soundcloud.com/resolve?url=${url}&client_id=${clientId}&limit=500`
)
playlist = await fetch(`https://api-v2.soundcloud.com/resolve?url=${url}&client_id=${clientId}&limit=500`)
.then((res) => res.json())
.then((obj) => obj.tracks);
}
@ -210,21 +177,10 @@ async function createVoiceConnection(guild_id, voice_id, text_id) {
}
const REGEX_HLS_AUDIO_TRACK = /#EXT-X-MEDIA:URI="(.+?)",TYPE=AUDIO,/;
async function enqueue({
guild_id,
voice_id,
text_id,
url,
type,
addedBy,
suppress = false,
queueNext = false,
}) {
async function enqueue({guild_id, voice_id, text_id, url, type, addedBy, suppress = false, queueNext = false}) {
if (!url) return;
const connection =
voiceStorage.get(guild_id) ??
(await createVoiceConnection(guild_id, voice_id, text_id));
const connection = voiceStorage.get(guild_id) ?? (await createVoiceConnection(guild_id, voice_id, text_id));
const textChannel = hf.bot.guilds.get(guild_id).channels.get(text_id);
let title,
@ -245,9 +201,7 @@ async function enqueue({
id = uri.searchParams.get("v");
}
}
info = await fetch(`${hf.config.piped_api}/streams/${id}`).then((res) =>
res.json()
);
info = await fetch(`${hf.config.piped_api}/streams/${id}`).then((res) => res.json());
} catch (err) {
await textChannel.createMessage({
content: `:warning: Failed to get metadata: \`\`\`\n${err}\n\`\`\``,
@ -261,35 +215,23 @@ async function enqueue({
const hlsUrl = new URL(info.hls);
const hlsBase = await fetch(info.hls)
.then((res) => res.text())
.then((data) =>
data.replaceAll(
"/api/manifest/",
`https://${hlsUrl.hostname}/api/manifest/`
)
);
.then((data) => data.replaceAll("/api/manifest/", `https://${hlsUrl.hostname}/api/manifest/`));
media = Readable.from(
await fetch(hlsBase.match(REGEX_HLS_AUDIO_TRACK)[1])
.then((res) => res.text())
.then((data) =>
data.replaceAll(
"/videoplayback/",
`https://${hlsUrl.hostname}/videoplayback/`
)
)
.then((data) => data.replaceAll("/videoplayback/", `https://${hlsUrl.hostname}/videoplayback/`))
);
} else if (type == "sc") {
if (url?.startsWith("sc:"))
url = url.replace(/^sc:/, "https://soundcloud.com/");
if (url?.startsWith("sc:")) url = url.replace(/^sc:/, "https://soundcloud.com/");
const client_id = await getSoundcloudClientID();
const info = await fetch(
`https://api-v2.soundcloud.com/resolve?url=${url}&client_id=${client_id}`
).then((res) => res.json());
const info = await fetch(`https://api-v2.soundcloud.com/resolve?url=${url}&client_id=${client_id}`).then((res) =>
res.json()
);
const formatUrl = info.media.transcodings.filter(
(obj) => !obj.snipped && obj.format.protocol == "progressive"
)[0].url;
const formatUrl = info.media.transcodings.filter((obj) => !obj.snipped && obj.format.protocol == "progressive")[0]
.url;
const streamUrl = await fetch(`${formatUrl}?client_id=${client_id}`)
.then((res) => res.json())
.then((obj) => obj.url);
@ -314,11 +256,7 @@ async function enqueue({
if (info.tags) {
title = `${
info.tags.artist ??
info.tags.ARTIST ??
info.tags.album_artist ??
info.tags.ALBUM_ARTIST ??
"<unknown artist>"
info.tags.artist ?? info.tags.ARTIST ?? info.tags.album_artist ?? info.tags.ALBUM_ARTIST ?? "<unknown artist>"
} - ${info.tags.title ?? info.tags.TITLE ?? "<no title>"}`;
}
@ -347,26 +285,17 @@ async function enqueue({
textChannel.createMessage({
embeds: [
{
title: `<:ms_tick:503341995348066313> Added to queue ${
queueNext === true ? "(next up)" : ""
}`,
title: `<:ms_tick:503341995348066313> Added to queue ${queueNext === true ? "(next up)" : ""}`,
color: 0x00cc00,
fields: [
{
name: "Title",
value: (title !== url ? `[${title}](${url})` : url).substring(
0,
1024
),
value: (title !== url ? `[${title}](${url})` : url).substring(0, 1024),
inline: true,
},
{
name: "Length",
value: stream
? "<continuous>"
: length
? formatTime(length)
: "<unknown>",
value: stream ? "<continuous>" : length ? formatTime(length) : "<unknown>",
inline: true,
},
{
@ -393,10 +322,7 @@ async function enqueue({
await connection.connection.play(media, {
inlineVolume: true,
voiceDataTimeout: -1,
inputArgs: [
"-protocol_whitelist",
"file,http,https,tcp,tls,pipe,data,crypto",
],
inputArgs: ["-protocol_whitelist", "file,http,https,tcp,tls,pipe,data,crypto"],
});
textChannel.createMessage({
@ -407,19 +333,12 @@ async function enqueue({
fields: [
{
name: "Title",
value: (title && title != url
? `[${title}](${url})`
: url
).substring(0, 1024),
value: (title && title != url ? `[${title}](${url})` : url).substring(0, 1024),
inline: true,
},
{
name: "Length",
value: stream
? "<continuous>"
: length
? formatTime(length)
: "<unknown>",
value: stream ? "<continuous>" : length ? formatTime(length) : "<unknown>",
inline: true,
},
{
@ -448,20 +367,17 @@ async function enqueue({
}
async function youtubeSearch(msg, str) {
const {items} = await fetch(
`${hf.config.piped_api}/search?q=${encodeURIComponent(str)}&filter=videos`
).then((x) => x.json());
const {items} = await fetch(`${hf.config.piped_api}/search?q=${encodeURIComponent(str)}&filter=videos`).then((x) =>
x.json()
);
const selection = items.map((item) => ({
value: "https://youtube.com" + item.url,
key: item.url.replace("/watch?v=", ""),
display: `${parseHtmlEntities(item.title).substring(0, 99)}${
parseHtmlEntities(item.title).length > 99 ? "…" : ""
display: `${parseHtmlEntities(item.title).substring(0, 99)}${parseHtmlEntities(item.title).length > 99 ? "…" : ""}`,
description: `from ${parseHtmlEntities(item.uploaderName).substring(0, 95)}${
parseHtmlEntities(item.uploaderName).length > 95 ? "…" : ""
}`,
description: `from ${parseHtmlEntities(item.uploaderName).substring(
0,
95
)}${parseHtmlEntities(item.uploaderName).length > 95 ? "…" : ""}`,
}));
try {
@ -478,12 +394,7 @@ command.addAlias("m");
command.category = "misc";
command.helpText = "Music";
command.usage = "help";
command.callback = async function (
msg,
line,
args,
{shuffle = false, limit = -1, offset = 0, next = false}
) {
command.callback = async function (msg, line, args, {shuffle = false, limit = -1, offset = 0, next = false}) {
if (!msg.guildID) return "This command can only be used in guilds.";
const subcommand = args.shift();
@ -503,10 +414,7 @@ command.callback = async function (
let type;
let playlist = false;
if (
REGEX_YOUTUBE_PLAYLIST.test(argStr) ||
REGEX_YOUTUBE_PLAYLIST_SHORT.test(argStr)
) {
if (REGEX_YOUTUBE_PLAYLIST.test(argStr) || REGEX_YOUTUBE_PLAYLIST_SHORT.test(argStr)) {
type = "yt";
playlist = true;
} else if (REGEX_SOUNDCLOUD_PLAYLIST.test(argStr)) {
@ -533,25 +441,17 @@ command.callback = async function (
const statusMessage = await msg.channel.createMessage({
embeds: [
{
title:
"<a:loading:493087964918972426> Processing playlist...",
title: "<a:loading:493087964918972426> Processing playlist...",
description: `Fetching tracks...`,
color: 0xcc0088,
},
],
});
const playlist = await processPlaylist(
argStr,
type,
shuffle,
limit,
offset
);
const playlist = await processPlaylist(argStr, type, shuffle, limit, offset);
await statusMessage.edit({
embeds: [
{
title:
"<a:loading:493087964918972426> Processing playlist...",
title: "<a:loading:493087964918972426> Processing playlist...",
description: `${playlist.length} tracks`,
color: 0xcc0088,
},
@ -562,9 +462,7 @@ command.callback = async function (
if (type == "yt") {
url = "https://youtube.com" + track.url;
} else if (type == "sc") {
url = track.track
? track.track.permalink_url
: track.permalink_url;
url = track.track ? track.track.permalink_url : track.permalink_url;
}
await enqueue({
@ -599,20 +497,13 @@ command.callback = async function (
}
} else {
if (argStr.match(/^https?:\/\//)) {
let contentType = await fetch(argStr, {method: "HEAD"}).then(
(res) => res.headers.get("Content-Type")
);
let contentType = await fetch(argStr, {method: "HEAD"}).then((res) => res.headers.get("Content-Type"));
if (!contentType) {
contentType = await fetch(argStr, {method: "GET"}).then((res) =>
res.headers.get("Content-Type")
);
contentType = await fetch(argStr, {method: "GET"}).then((res) => res.headers.get("Content-Type"));
}
if (
contentType.startsWith("audio/") ||
contentType.startsWith("video/")
) {
if (contentType.startsWith("audio/") || contentType.startsWith("video/")) {
await enqueue({
guild_id: msg.guildID,
voice_id: msg.member.voiceState.channelID,
@ -684,30 +575,22 @@ command.callback = async function (
return "You are not in a voice channel.";
}
case "np": {
if (!voiceStorage.has(msg.guildID))
return "The bot is not in a voice channel.";
if (!voiceStorage.has(msg.guildID)) return "The bot is not in a voice channel.";
const connection = voiceStorage.get(msg.guildID);
const nowPlaying = connection.nowplaying;
if (!nowPlaying || !connection.connection.playing)
return "Nothing is currently playing.";
if (!nowPlaying || !connection.connection.playing) return "Nothing is currently playing.";
const position = Date.now() - nowPlaying.start;
const timeEnd =
nowPlaying.length == 0 ? "\u221e" : formatTime(nowPlaying.length);
const timeEnd = nowPlaying.length == 0 ? "\u221e" : formatTime(nowPlaying.length);
const timePos = formatTime(position);
const progress =
nowPlaying.length == 0 ? 1 : position / nowPlaying.length;
const progress = nowPlaying.length == 0 ? 1 : position / nowPlaying.length;
const barLength = Math.round(progress * NOWPLAYING_BAR_LENGTH);
const bar = `\`[${"=".repeat(barLength)}${" ".repeat(
NOWPLAYING_BAR_LENGTH - barLength
)}]\``;
const time = `\`${timePos}${" ".repeat(
NOWPLAYING_BAR_LENGTH + 2 - timePos.length - timeEnd.length
)}${timeEnd}\``;
const bar = `\`[${"=".repeat(barLength)}${" ".repeat(NOWPLAYING_BAR_LENGTH - barLength)}]\``;
const time = `\`${timePos}${" ".repeat(NOWPLAYING_BAR_LENGTH + 2 - timePos.length - timeEnd.length)}${timeEnd}\``;
return {
embed: {
@ -738,8 +621,7 @@ command.callback = async function (
}
case "queue":
case "q": {
if (!voiceStorage.has(msg.guildID))
return "The bot is not in a voice channel";
if (!voiceStorage.has(msg.guildID)) return "The bot is not in a voice channel";
const connection = voiceStorage.get(msg.guildID);
const queue = connection.queue;
@ -754,11 +636,9 @@ command.callback = async function (
const item = queue[index];
fields.push({
name: item.title ?? item.url,
value: `${item.title ? `[Link](${item.url}) - ` : ""}${formatTime(
item.length
)}\nAdded by: <@${item.addedBy}>\n<t:${Math.floor(
nextTrack / 1000
)}:R>`,
value: `${item.title ? `[Link](${item.url}) - ` : ""}${formatTime(item.length)}\nAdded by: <@${
item.addedBy
}>\n<t:${Math.floor(nextTrack / 1000)}:R>`,
inline: true,
});
nextTrack += item.length;
@ -796,8 +676,7 @@ command.callback = async function (
if (queue.length === 0) return "Nothing else is currently queued";
const hasManageMessages = msg.member.permissions.has("manageMessages");
if (!hasManageMessages)
queue = queue.filter((item) => item.addedBy == msg.member.id);
if (!hasManageMessages) queue = queue.filter((item) => item.addedBy == msg.member.id);
if (queue.length === 0) return "You currently have nothing queued";
@ -809,9 +688,7 @@ command.callback = async function (
return {
key: item.id,
display: (item.title ?? item.url).substr(0, 100),
description: hasManageMessages
? `Added by: ${formatUsername(user)}`
: "",
description: hasManageMessages ? `Added by: ${formatUsername(user)}` : "",
};
}),
30000,
@ -819,9 +696,7 @@ command.callback = async function (
);
if (Array.isArray(toRemove)) {
connection.queue = connection.queue.filter(
(item) => !toRemove.includes(item.id)
);
connection.queue = connection.queue.filter((item) => !toRemove.includes(item.id));
return `Removed ${toRemove.length} item(s).`;
} else {
return toRemove;

View file

@ -16,18 +16,12 @@ const {resolve} = require("path");
const timer = require("../lib/timer");
const logger = require("../lib/logger");
if (!fs.existsSync(resolve(__dirname, "..", "..", "private_reminders.json")))
return;
if (!fs.existsSync(resolve(__dirname, "..", "..", "private_reminders.json"))) return;
const tzFormatterCache = {};
const dmCache = {};
const reminderData = require(resolve(
__dirname,
"..",
"..",
"private_reminders.json"
));
const reminderData = require(resolve(__dirname, "..", "..", "private_reminders.json"));
hf.database.run(
"CREATE TABLE IF NOT EXISTS private_reminders (user TEXT NOT NULL PRIMARY KEY, last_run TEXT NOT NULL) WITHOUT ROWID"
@ -97,10 +91,7 @@ hf.bot.once("ready", () => {
const lastRan = await getLastRun(data.user);
if (date != lastRan && hour == data.hour && minutes == 0) {
logger.verbose(
"privateReminders",
`attempting to send reminder to ${data.user}`
);
logger.verbose("privateReminders", `attempting to send reminder to ${data.user}`);
if (channel != null) {
await channel.createMessage({
content: ":alarm_clock: " + data.message,

View file

@ -1,6 +1,6 @@
const Command = require("../lib/command.js");
const {selectionMessage} = require("../lib/utils.js");
const {selectionMessage} = require("../util/selection.js");
hf.database.run(
"CREATE TABLE IF NOT EXISTS roleme (guild TEXT NOT NULL PRIMARY KEY, roles TEXT NOT NULL) WITHOUT ROWID"
@ -74,11 +74,7 @@ async function lookupRole(msg, str, filter) {
selection.splice(20);
try {
return await selectionMessage(
msg,
"Multiple roles found, please pick from this list:",
selection
);
return await selectionMessage(msg, "Multiple roles found, please pick from this list:", selection);
} catch (out) {
return out;
}
@ -129,9 +125,7 @@ roleme.callback = async function (msg, line, args) {
const roles = await getRoles(msg.guildID);
const role = await lookupRole(msg, argStr, (role) =>
roles.includes(role.id)
);
const role = await lookupRole(msg, argStr, (role) => roles.includes(role.id));
if (role === "No results") return role;
if (!roles.includes(role.id)) {
@ -169,9 +163,7 @@ roleme.callback = async function (msg, line, args) {
const roles = await getRoles(msg.guildID);
const role = await lookupRole(msg, argStr, (role) =>
roles.includes(role.id)
);
const role = await lookupRole(msg, argStr, (role) => roles.includes(role.id));
if (role === "No results") return role;
if (!roles.includes(role.id)) {
@ -225,11 +217,7 @@ roleme.callback = async function (msg, line, args) {
const roles = await getRoles(msg.guildID);
const role = await lookupRole(
msg,
(subcommand + " " + argStr).trim(),
(role) => roles.includes(role.id)
);
const role = await lookupRole(msg, (subcommand + " " + argStr).trim(), (role) => roles.includes(role.id));
if (role === "No results") return role;
if (!roles.includes(role.id)) return "Role not in assignable roles.";

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,313 @@
const Command = require("../../lib/command.js");
const InteractionCommand = require("../../lib/interactionCommand.js");
const {
APIEndpoints,
API_URL,
ApplicationCommandOptionTypes,
ApplicationTypes,
CDNEndpoints,
Games,
} = require("../../util/dconstants.js");
const {
ApplicationFlagNames,
Icons,
RegExp: {Snowflake: SNOWFLAKE_REGEX},
} = require("../../util/constants.js");
const {snowflakeToTimestamp} = require("../../util/time.js");
const {getGuild, safeString, formatUsername, flagsFromInt} = require("../../util/misc.js");
const appinfo = new Command("appinfo");
appinfo.category = "utility";
appinfo.helpText = "Get information on an application";
appinfo.usage = "[application id]";
appinfo.addAlias("ainfo");
appinfo.addAlias("ai");
appinfo.callback = async function (msg, line) {
if (!line || line === "") return "Arguments required.";
if (!SNOWFLAKE_REGEX.test(line)) return "Not a snowflake.";
const snowflake = line.match(SNOWFLAKE_REGEX)[1];
try {
const _app = await hf.bot.requestHandler.request("GET", APIEndpoints.APPLICATION_RPC(snowflake), false);
let app = _app;
const game = Games.find((game) => game.id == app.id);
if (game) {
app = Object.assign(app, game);
}
const assets = await hf.bot.requestHandler.request("GET", CDNEndpoints.APPLICATION_ASSETS(app.id), false);
const embed = {
title: `${app.name}`,
description: app.description.length > 0 ? app.description : "*No description*.",
fields: [
{
name: "Created",
value: `<t:${Math.floor(snowflakeToTimestamp(app.id) / 1000)}:R>`,
inline: true,
},
],
};
if (app.icon) {
embed.thumbnail = {
url: CDNEndpoints.APP_ICON(app.id, app.icon),
};
}
if (app.type) {
embed.fields.push({
name: "Type",
value: `${ApplicationTypes[app.type] ?? "<unknown type>"} (\`${app.type}\`)`,
inline: true,
});
}
if (app.guild_id) {
const guild = await getGuild(app.guild_id);
if (guild) {
embed.fields.push({
name: "Guild",
value: `${guild.data.name} (\`${app.guild_id}\`)`,
inline: true,
});
} else {
embed.fields.push({
name: "Guild ID",
value: `\`${app.guild_id}\``,
inline: true,
});
}
}
if (app.tags) {
embed.fields.push({
name: "Tags",
value: app.tags.join(", "),
inline: true,
});
}
if (app.publishers || app.developers) {
embed.fields.push({
name: "Game Companies",
value: `**Developers:** ${
app.developers?.length > 0 ? app.developers.map((x) => x.name).join(", ") : "<unknown>"
}\n**Publishers:** ${app.publishers?.length > 0 ? app.publishers.map((x) => x.name).join(", ") : "<unknown>"}`,
inline: true,
});
}
if (app.executables) {
embed.fields.push({
name: "Game Executables",
value: app.executables
.map((exe) => `${Icons.os[exe.os] ?? "\u2753"} \`${exe.name}\`${exe.is_launcher ? " (launcher)" : ""}`)
.join("\n"),
inline: true,
});
}
if (app.third_party_skus) {
embed.fields.push({
name: "Game Distributors",
value: app.third_party_skus
.map((sku) =>
sku.distributor == "steam"
? `[Steam](https://steamdb.info/app/${sku.id})`
: sku.distributor == "discord"
? `[Discord](https://discord.com/store/skus/${sku.id})`
: `${sku.distributor
.split("_")
.map((x) => x[0].toUpperCase() + x.substring(1).toLowerCase())
.join(" ")
.replace(" Net", ".net")}: \`${sku.id}\``
)
.join("\n"),
inline: true,
});
}
if (app.bot_public != null || app.integration_public != null) {
if (
(app.bot_public && !app.bot_require_code_grant) ||
(app.integration_public && !app.integration_require_code_grant)
) {
let scope = "bot";
let permissions = "";
if (app.install_params) {
if (app.install_params.scopes) {
scope = app.install_params.scopes.join("+");
}
if (app.install_params.permissions) {
permissions = "&permissions=" + app.install_params.permissions;
}
}
embed.url = `https://discord.com/oauth2/authorize?client_id=${app.id}&scope=${scope}${permissions}`;
}
try {
const bot = await hf.bot.requestHandler.request("GET", APIEndpoints.USER(app.id), true);
embed.fields.push({
name: "Bot",
value: formatUsername(bot) + ((bot.flags & 65536) != 0 ? " \u2713" : ""),
inline: false,
});
} catch {
embed.fields.push({
name: "Bot",
value: "<app id and bot id mismatch or other error>",
inline: false,
});
}
}
if (app.custom_install_url) {
embed.url = app.custom_install_url;
}
if (app.flags > 0) {
const flags = flagsFromInt(app.flags, ApplicationFlagNames, false).split("\n");
embed.fields.push({
name: "Flags",
value: "- " + flags.slice(0, Math.ceil(flags.length / 2)).join("\n- "),
inline: true,
});
if (flags.length > 1)
embed.fields.push({
name: "\u200b",
value: "- " + flags.slice(Math.ceil(flags.length / 2), flags.length).join("\n- "),
inline: true,
});
}
const images = [];
if (app.icon) {
images.push(`[Icon](${embed.thumbnail.url})`);
}
if (app.cover_image) {
images.push(`[Cover](${CDNEndpoints.APP_ICON(app.id, app.cover_image)}?size=4096)`);
}
if (app.splash) {
images.push(`[Splash](${CDNEndpoints.APP_ICON(app.id, app.splash)}?size=4096)`);
}
const links = [];
if (app.terms_of_service_url) {
links.push(`[Terms of Service](${app.terms_of_service_url})`);
}
if (app.privacy_policy_url) {
links.push(`[Privacy Policy](${app.privacy_policy_url})`);
}
if (assets.length > 0) {
links.push(`[Assets](${API_URL + CDNEndpoints.APPLICATION_ASSETS(app.id)})`);
}
if (images.length > 0 || links.length > 0) {
embed.fields.push({
name: "\u200b",
value: (images.join(" | ") + "\n" + links.join(" | ")).trim(),
inline: false,
});
}
if (assets.length > 0) {
if (images.length == 0 && links.length == 0) {
embed.fields.push({
name: "\u200b",
value: "\u200b",
inline: false,
});
}
const mappedAssets = assets.map(
(asset) =>
`[${asset.name.length > 32 ? asset.name.substring(0, 32) + "\u2026" : asset.name}](${CDNEndpoints.APP_ASSET(
app.id,
asset.id
)})`
);
let left = "- " + mappedAssets.slice(0, Math.ceil(mappedAssets.length / 2)).join("\n- ");
let right = "- " + mappedAssets.slice(Math.ceil(mappedAssets.length / 2), mappedAssets.length).join("\n- ");
if (left.length > 1024 || right.length > 1024) {
const linklessAssets = assets.map((asset) =>
asset.name.length > 32 ? asset.name.substring(0, 32) + "\u2026" : asset.name
);
left = "- " + linklessAssets.slice(0, Math.ceil(linklessAssets.length / 2)).join("\n- ");
right = "- " + linklessAssets.slice(Math.ceil(linklessAssets.length / 2), linklessAssets.length).join("\n- ");
}
if (left.length <= 1024 && right.length <= 1024) {
embed.fields.push({
name: `Assets (${assets.length})`,
value: left,
inline: true,
});
if (mappedAssets.length > 1)
embed.fields.push({
name: "\u200b",
value: right,
inline: true,
});
} else {
const assetList = assets
.map((asset) => (asset.name.length > 32 ? asset.name.substring(0, 31) + "\u2026" : asset.name))
.join(", ");
if (assetList.length <= 1024) {
embed.fields.push({
name: `Assets (${assets.length})`,
value: assetList,
inline: false,
});
} else {
embed.fields.push({
name: `Assets (${assets.length})`,
value: "*Exceeds 1024 characters.*",
inline: false,
});
}
}
}
return {embed};
} catch (error) {
if (error.message === "Unknown Application") {
try {
const bot = await hf.bot.requestHandler.request("GET", APIEndpoints.USER(snowflake), true);
if (bot) {
return "Application has been deleted.";
} else {
return "ID provided does not point to a valid application.";
}
} catch {
return "ID provided does not point to a valid application.";
}
} else {
return `:warning: Got error \`${safeString(error)}\``;
}
}
};
hf.registerCommand(appinfo);
const appinfoInteraction = new InteractionCommand("appinfo");
appinfoInteraction.helpText = "Get information on an application";
appinfoInteraction.options.id = {
name: "id",
type: ApplicationCommandOptionTypes.STRING,
description: "Application ID to get info for",
required: true,
default: "",
};
appinfoInteraction.callback = async function (interaction) {
const id = this.getOption(interaction, "id");
return appinfo.callback(interaction, id);
};
hf.registerCommand(appinfoInteraction);

View file

@ -0,0 +1,98 @@
const Command = require("../../lib/command.js");
const {CDNEndpoints} = require("../../util/dconstants.js");
const {formatUsername, getDefaultAvatar} = require("../../util/misc.js");
const {lookupUser} = require("../../util/selection.js");
const avatar = new Command("avatar");
avatar.category = "utility";
avatar.helpText = "Get avatar of a user";
avatar.usage = "<user>";
avatar.callback = async function (msg, line, [user], {server, guild}) {
if (server || 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 = CDNEndpoints.GUILD_ICON(guild.id, guild.icon);
return {
embeds: [
{
title: "Server Icon",
url,
image: {
url,
},
},
],
};
}
} else if (user) {
const lookup = await lookupUser(msg, user);
if (lookup == "No results" || lookup == "Canceled" || lookup == "Request timed out") {
return lookup;
} else {
let member = lookup;
const guild = msg.channel.guild || hf.bot.guilds.get(msg.guildID);
if (guild) {
if (guild.members.has(lookup.id)) {
member = guild.members.get(lookup.id);
} else {
const fetched = await guild.fetchMembers({
userIDs: [lookup.id],
});
member = fetched[0];
}
}
const baseEmbed = {
title: `Avatar for \`${formatUsername(member)}\``,
};
const defaultAvatar = getDefaultAvatar(member.id, member.user?.discriminator ?? member.discriminator ?? 0);
const normalAvatar = member.user ? member.user.avatar ?? defaultAvatar : member.avatar ?? defaultAvatar;
const guildAvatar = guild ? member.avatar : null;
const normalUrl = CDNEndpoints.USER_AVATAR(member.id, normalAvatar);
const guildUrl = CDNEndpoints.GUILD_MEMBER_AVATAR(guild.id, member.id, guildAvatar);
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 \`${formatUsername(msg.author)}\``,
};
const normalAvatar = msg.author?.avatar ?? getDefaultAvatar(msg.author.id, msg.author.discriminator);
const guildAvatar = msg.member?.avatar;
const normalUrl = CDNEndpoints.USER_AVATAR(msg.author.id, normalAvatar);
const guildUrl = CDNEndpoints.GUILD_MEMBER_AVATAR(guild.id, msg.author.id, guildAvatar);
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);

View file

@ -0,0 +1,76 @@
const Command = require("../../lib/command.js");
const {APIEndpoints, CDNEndpoints} = require("../../util/dconstants.js");
const {formatUsername} = require("../../util/misc.js");
const {lookupUser} = require("../../util/selection.js");
const banner = new Command("banner");
banner.category = "utility";
banner.helpText = "Get banner of a user";
banner.usage = "<user>";
banner.callback = async function (msg, line, [user], {server, guild}) {
let id = msg.author.id;
if (server || 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 = CDNEndpoints.BANNER(guild.id, guild.banner);
return {
embeds: [
{
title: "Server Banner",
url,
image: {
url,
},
},
],
};
}
} else if (user) {
const lookup = await lookupUser(msg, user);
if (lookup == "No results" || lookup == "Canceled" || lookup == "Request timed out") {
return lookup;
} else {
id = lookup.id;
}
}
const userObj = await hf.bot.requestHandler.request("GET", APIEndpoints.USER(id), true);
let memberObj;
if (msg.guildID) {
memberObj = await hf.bot.requestHandler.request("GET", APIEndpoints.GUILD_MEMBER(msg.guildID, id), true);
}
if (!userObj.banner && !memberObj?.banner) return "This user does not have a banner.";
const url = userObj.banner && CDNEndpoints.BANNER(userObj.id, userObj.banner);
const guildUrl = memberObj?.banner && CDNEndpoints.GUILD_MEMBER_BANNER(msg.guildID, userObj.id, memberObj.banner);
return {
embeds: [
url && {
title: `Banner for \`${formatUsername(userObj)}\``,
url,
image: {
url,
},
},
guildUrl && {
title: `Server banner for \`${formatUsername(userObj)}\``,
url: guildUrl,
image: {
url: guildUrl,
},
},
].filter((x) => !!x),
};
};
hf.registerCommand(banner);

View file

@ -0,0 +1,71 @@
const Command = require("../../lib/command.js");
const InteractionCommand = require("../../lib/interactionCommand.js");
const {ApplicationCommandOptionTypes} = require("../../util/dconstants.js");
const {getNamesFromString} = require("../../util/unicode.js");
const charinfo = new Command("charinfo");
charinfo.category = "utility";
charinfo.helpText = "Get information about a set of characters.";
charinfo.usage = "[characters]";
charinfo.addAlias("char");
charinfo.callback = async function (msg, line) {
if (!line || line == "") {
if (msg.messageReference?.messageID) {
const reply = await msg.channel.getMessage(msg.messageReference.messageID);
line = reply.content;
} else {
return "Arguments or reply required.";
}
}
const names = await getNamesFromString(line);
const chars = [...line];
const lines = names
.map(
([code, name], index) =>
`[\`\\u${code}${
chars[index].length > 1
? ` (${chars[index]
.split("")
.map((c) => `\\u${c.codePointAt().toString(16)}`)
.join("")})`
: ""
}\`](<http://www.fileformat.info/info/unicode/char/${code}>): ${name} - ${chars[index]
.replace("_", "\\_")
.replace("`", "\\`")
.replace("*", "\\*")}`
)
.join("\n");
if (lines.length > 2000) {
return {
content: "Output too long:",
attachments: [
{
file: lines,
filename: "message.txt",
},
],
};
} else {
return lines;
}
};
hf.registerCommand(charinfo);
const charinfoInteraction = new InteractionCommand("charinfo");
charinfoInteraction.helpText = "Get information about a set of characters.";
charinfoInteraction.options.content = {
name: "content",
type: ApplicationCommandOptionTypes.STRING,
description: "Characters to get info for",
required: true,
default: "",
};
charinfoInteraction.callback = async function (interaction) {
const content = this.getOption(interaction, "content");
return charinfo.callback(interaction, content);
};
hf.registerCommand(charinfoInteraction);

View file

@ -0,0 +1,98 @@
const Command = require("../../lib/command.js");
const {APIEndpoints, CDNEndpoints} = require("../../util/dconstants.js");
const {formatUsername} = require("../../util/misc.js");
const {lookupUser} = require("../../util/selection.js");
const decoration = new Command("decoration");
decoration.category = "utility";
decoration.helpText = "Get decoration of a user";
decoration.usage = "<user>";
decoration.addAlias("decor");
decoration.callback = async function (msg, line, [user]) {
let id = msg.author.id;
if (user) {
const lookup = await lookupUser(msg, user);
if (lookup == "No results" || lookup == "Canceled" || lookup == "Request timed out") {
return lookup;
} else {
id = lookup.id;
}
}
const userObj = await hf.bot.requestHandler.request("GET", APIEndpoints.USER(id), true);
let decor, decorRes;
try {
decorRes = await fetch(`https://decor.fieryflames.dev/api/users/${id}/decoration`, {
headers: {
"User-Agent": "HiddenPhox/decoration (https://gitdab.com/Cynosphere/HiddenPhox)",
},
}).then((res) => res.json());
if (decorRes.presetId) {
// horror
const presets = await fetch(`https://decor.fieryflames.dev/api/decorations/presets`, {
headers: {
"User-Agent": "HiddenPhox/decoration (https://gitdab.com/Cynosphere/HiddenPhox)",
},
}).then((res) => res.json());
decorRes.preset = presets.find((p) => p.id === decorRes.presetId);
}
decor = `https://decorcdn.fieryflames.dev/${decorRes.animated ? "a_" : ""}${decorRes.hash}.png`;
} catch {
// noop
}
if (!userObj.avatar_decoration_data && !decor) return "This user does not have a decoration.";
const normalUrl =
userObj.avatar_decoration_data && CDNEndpoints.AVATAR_DECORATION(userObj.avatar_decoration_data.asset);
const baseEmbed = {
title: `Decoration for \`${formatUsername(userObj)}\``,
};
baseEmbed.fields = [];
if (normalUrl) {
const listing = await hf.bot.requestHandler
.request("GET", APIEndpoints.STORE_PUBLISHED_LISTING(userObj.avatar_decoration_data.sku_id), true)
.catch(() => {});
baseEmbed.fields.push({
name: `Discord ${userObj.avatar_decoration_data.asset.startsWith("a_") ? "(Animated)" : ""}`,
value: `**${
listing?.sku
? `[${listing?.sku?.name}](https://discord.com/shop#itemSkuId=${userObj.avatar_decoration_data.sku_id})`
: "Unknown"
}** (\`${userObj.avatar_decoration_data.sku_id}\`)\n[Image](${normalUrl})`,
inline: true,
});
}
if (decor) {
baseEmbed.fields.push({
name: `Decor ${decor.indexOf("/a_") > -1 ? "(Animated)" : ""}`,
value: `**${decorRes.alt}**\n${
decorRes.preset ? `\u3000from preset **${decorRes.preset.name}**\n` : ""
}[Image](${decor})`,
inline: true,
});
}
baseEmbed.url = normalUrl || decor;
const decorEmbed = {...baseEmbed};
baseEmbed.image = {url: normalUrl || decor};
decorEmbed.image = {url: decor};
return {
embeds: [normalUrl && baseEmbed, decor && decorEmbed].filter((x) => x != null),
};
};
hf.registerCommand(decoration);

View file

@ -0,0 +1,53 @@
const Command = require("../../lib/command.js");
const {UserFlags} = require("../../util/dconstants.js");
const {flagsFromInt, formatUsername} = require("../../util/misc.js");
const _UserFlags = Object.entries(UserFlags).filter(([name]) => name != "NONE");
for (const set of _UserFlags) {
const bits = Object.entries(set[1].toString("2").split("").reverse().map(Number));
const index = bits.find(([index, bit]) => bit == 1)[0];
set[1] = Number(index);
}
const UserFlagsMapped = Object.fromEntries(_UserFlags.map((x) => x.reverse()));
const flagdump = new Command("flagdump");
flagdump.category = "utility";
flagdump.helpText = "Dumps Discord user flags.";
flagdump.usage = "[flags or user mention]";
flagdump.callback = async function (msg, line, [numOrMention], {id, list}) {
if (!line || line == "") numOrMention = `<@${msg.author.id}>`;
const num = Number(numOrMention);
if (list) {
let allFlags = 0n;
for (const index in UserFlagsMapped) {
if (UserFlagsMapped[index] == undefined) continue;
allFlags += 1n << BigInt(index);
}
return `All flags:\n\`\`\`${flagsFromInt(allFlags, UserFlagsMapped)}\`\`\``;
} else if (/<@!?(\d+)>/.test(numOrMention) || !isNaN(id)) {
const targetId = id ?? numOrMention.match(/<@!?(\d+)>/)?.[1];
if (!targetId) return "Got null ID.";
let user = hf.bot.users.get(id);
if (!user) user = await hf.bot.requestHandler.request("GET", `/users/${id}`, true).catch(() => {});
if (!user) {
return "Failed to get user.";
} else {
return `\`${formatUsername(user)}\`'s public flags:\n\`\`\`${flagsFromInt(
user.public_flags ?? user.publicFlags,
UserFlagsMapped
)}\`\`\``;
}
} else if (!isNaN(num)) {
return `\`\`\`\n${flagsFromInt(num, UserFlagsMapped)}\`\`\``;
} else {
return `\`${formatUsername(msg.author)}\`'s public flags:\n\`\`\`${flagsFromInt(
msg.author.publicFlags,
UserFlagsMapped
)}\`\`\``;
}
};
hf.registerCommand(flagdump);

View file

@ -0,0 +1,636 @@
const Command = require("../../lib/command.js");
const InteractionCommand = require("../../lib/interactionCommand.js");
const {VoiceChannel} = require("@projectdysnomia/dysnomia");
const {
APIEndpoints,
ApplicationCommandOptionTypes,
CDNEndpoints,
ClanPlaystyle,
ExplicitContentFilterStrings,
Permissions,
VerificationLevelStrings,
} = require("../../util/dconstants.js");
const {
RegExp: {Snowflake: SNOWFLAKE_REGEX},
Icons,
ChannelTypeNames,
} = require("../../util/constants.js");
const {snowflakeToTimestamp} = require("../../util/time.js");
const {getGuild, formatGuildFeatures} = require("../../util/misc.js");
const guildinfo = new Command("guildinfo");
guildinfo.category = "utility";
guildinfo.helpText = "Get information on a guild";
guildinfo.usage = "<guild id>";
guildinfo.addAlias("guild");
guildinfo.addAlias("ginfo");
guildinfo.addAlias("gi");
guildinfo.addAlias("serverinfo");
guildinfo.addAlias("server");
guildinfo.addAlias("sinfo");
guildinfo.addAlias("si");
guildinfo.callback = async function (msg, line) {
let _guild, clanEmbed, id;
if (!line || line == "") {
if (!msg.guildID) return "Not in a guild.";
const __guild = msg.channel.guild ?? hf.bot.guilds.get(msg.guildID);
if (__guild) {
_guild = {source: "local", data: __guild};
} else {
_guild = await getGuild(msg.guildID);
}
id = msg.guildID;
} else {
if (!SNOWFLAKE_REGEX.test(line)) return "Not a snowflake.";
const snowflake = line.match(SNOWFLAKE_REGEX)[1];
_guild = await getGuild(snowflake);
id = snowflake;
try {
const clan = await hf.bot.requestHandler.request("GET", APIEndpoints.CLAN(snowflake), true);
if (clan) {
const images = [];
clanEmbed = {
title: _guild != null ? "Clan data" : clan.name,
description: clan.description ?? "*No description*",
fields: [
!_guild && {
name: "Member Count",
value: clan.member_count,
inline: true,
},
{
name: "Tag",
value: clan.tag,
inline: true,
},
{
name: "Playstyle",
value: ClanPlaystyle[clan.playstyle] ?? `<unknown value: ${clan.playstyle}>`,
inline: true,
},
{
name: "Descriptors",
value: clan.wildcard_descriptors.join(", "),
inline: true,
},
{
name: "Interests/Topics/Traits",
value: `\`${clan.search_terms.map((x) => `"${x}"`).join(", ")}\``,
inline: false,
},
{
name: "Associated Game IDs",
value: `\`${clan.game_ids.map((x) => `"${x}"`).join(", ")}\``,
inline: false,
},
{
name: "Badge Colors",
value: `${clan.badge_color_primary}, ${clan.badge_color_secondary}`,
inline: true,
},
{
name: "Banner/Brand Colors",
value: `${clan.brand_color_primary}, ${clan.brand_color_secondary}`,
inline: true,
},
].filter((x) => !!x),
footer: !_guild ? {text: "Fetched from clan"} : null,
};
if (clan.badge_hash) {
const url = CDNEndpoints.CLAN_BADGE(clan.id, clan.badge_hash);
images.push(`[Badge](${url})`);
clanEmbed.thumbnail = {url};
}
if (clan.banner_hash) {
const url = CDNEndpoints.CLAN_BANNER(clan.id, clan.banner_hash);
images.push(`[Banner](${url})`);
clanEmbed.image = {url};
}
if (images.length > 0) {
clanEmbed.fields.push({
name: "\u200b",
value: images.join("\u3000\u3000"),
inline: false,
});
}
}
} catch {
// noop
}
}
if (!_guild) {
if (clanEmbed) {
return {embeds: [clanEmbed]};
} else {
try {
await hf.bot.requestHandler.request("GET", APIEndpoints.GUILD_WIDGET(id), false);
} catch (err) {
if (err.code == 50004) {
return `Guild \`${id}\` is private.`;
} else {
return "Guild not found.";
}
}
}
}
const guild = _guild.data;
switch (_guild.source) {
case "local": {
const roles = Array.from(guild.roles.values());
const channelTypeCounts = {};
let nsfwChannels = 0;
let hiddenChannels = 0;
for (const channel of guild.channels.values()) {
if (!channelTypeCounts[channel.type]) channelTypeCounts[channel.type] = 0;
channelTypeCounts[channel.type]++;
if (channel.nsfw) nsfwChannels++;
let defaultPermissions = channel.permissionOverwrites.get(guild.id);
if (!defaultPermissions && channel.parentID) {
defaultPermissions = guild.channels.get(channel.parentID).permissionOverwrites.get(guild.id);
}
if (
defaultPermissions &&
(channel instanceof VoiceChannel
? (defaultPermissions.deny & Permissions.voiceConnect) === Permissions.voiceConnect
: (defaultPermissions.deny & Permissions.viewChannel) === Permissions.viewChannel)
) {
hiddenChannels++;
}
}
const embed = {
title: guild.name,
description: guild.description ?? "*No description.*",
fields: [
guild.ownerID && {
name: "Owner",
value: `<@${guild.ownerID}>`,
inline: true,
},
guild.vanityURL && {
name: "Vanity URL",
value: `https://discord.gg/${guild.vanityURL}`,
inline: true,
},
{
name: "Created",
value: `<t:${Math.floor(snowflakeToTimestamp(guild.id) / 1000)}:R>`,
inline: true,
},
{
name: "Max Members",
value: guild.maxMembers,
inline: true,
},
{
name: "Max Video Channel Users",
value: `Normal: ${guild.maxVideoChannelUsers}\nStage: ${guild.maxStageVideoChannelUsers}`,
inline: true,
},
{
name: "Verification Level",
value: VerificationLevelStrings[guild.verificationLevel],
inline: true,
},
{
name: "Content Filter",
value: ExplicitContentFilterStrings[guild.explicitContentFilter],
inline: true,
},
{
name: "Moderation 2FA",
value: guild.mfaLevel == 0 ? "Off" : "On",
inline: true,
},
{
name: "Boost Status",
value: `**Level ${guild.premiumTier}**, ${guild.premiumSubscriptionCount} Boosts`,
inline: true,
},
{
name: "Locale",
value: guild.preferredLocale,
inline: true,
},
{
name: "Default Notifications",
value: guild.defaultNotifications == 0 ? "All Messages" : "Only Mentions",
inline: true,
},
guild.rulesChannelID && {
name: "Rules",
value: `<#${guild.rulesChannelID}>`,
inline: true,
},
guild.systemChannelID && {
name: "System Messages",
value: `<#${guild.systemChannelID}>`,
inline: true,
},
guild.publicUpdatesChannelID && {
name: "Community Updates",
value: `<#${guild.publicUpdatesChannelID}>`,
inline: true,
},
guild.safetyAlertsChannelID && {
name: "Safety Alerts",
value: `<#${guild.safetyAlertsChannelID}>`,
inline: true,
},
{
name: `Channels (${guild.channels.size})`,
value:
Object.entries(channelTypeCounts)
.map(([type, count]) => `${count} ${ChannelTypeNames[type]}`)
.join(", ") + `\n${nsfwChannels} age restricted, ${hiddenChannels} hidden`,
inline: false,
},
{
name: `Roles (${guild.roles.size})`,
value: `${roles.filter((role) => role.managed).length} managed, ${
roles.filter((role) => role.tags?.guild_connections).length
} linked, ${roles.filter((role) => role.tags?.integration_id != null).length} integration`,
inline: true,
},
guild.emojis.length > 0 && {
name: `Emotes (${guild.emojis.length})`,
value: `${guild.emojis.filter((e) => e.animated).length} animated, ${
guild.emojis.filter((e) => e.managed).length
} managed\n${guild.emojis.filter((e) => !e.available).length} unavailable`,
inline: true,
},
guild.stickers.length > 0 && {
name: `Stickers (${guild.stickers.length})`,
value: `${guild.stickers.filter((s) => s.format_type == 1).length} PNG, ${
guild.stickers.filter((s) => s.format_type == 2).length
} APNG, ${guild.stickers.filter((s) => s.format_type == 4).length} GIF, ${
guild.stickers.filter((s) => s.format_type == 3).length
} Lottie\n${guild.stickers.filter((s) => !s.available).length} unavailable`,
inline: true,
},
].filter((x) => !!x),
};
if (guild.icon) {
embed.thumbnail = {
url: CDNEndpoints.GUILD_ICON(guild.id, guild.icon),
};
}
const members = Array.from(guild.members.values());
const online = members.filter((member) => member.status != null && member.status != "offline").length;
const bots = members.filter((member) => member.bot).length;
const verfifiedBots = members.filter((member) => member.bot && (member.user.publicFlags & 65536) != 0).length;
embed.fields.push({
name: "Member Count",
value: `${Icons.online}${online} online${Icons.blank}${Icons.blank}${Icons.offline}${guild.memberCount} members\n${Icons.silk.cog} ${bots}${Icons.blank}${Icons.silk.tick} ${verfifiedBots}`,
inline: false,
});
const features = formatGuildFeatures(guild.features);
embed.fields.push({
name: `Features (${features.length})`,
value: features.length > 0 ? features.slice(0, Math.ceil(features.length / 2)).join("\n") : "None",
inline: true,
});
if (features.length > 1)
embed.fields.push({
name: "\u200b",
value: features.slice(Math.ceil(features.length / 2), features.length).join("\n"),
inline: true,
});
const images = [];
if (guild.icon) {
images.push(`[Icon](${embed.thumbnail.url})`);
}
if (guild.banner) {
images.push(`[Banner](${CDNEndpoints.BANNER(guild.id, guild.banner)})`);
}
if (guild.splash) {
images.push(`[Invite Splash](${CDNEndpoints.GUILD_SPLASH(guild.id, guild.splash)})`);
}
if (guild.discoverySplash) {
images.push(`[Discovery Splash](${CDNEndpoints.DISCOVERY_SPLASH(guild.id, guild.discoverySplash)})`);
}
if (images.length > 0) {
embed.fields.push({
name: "\u200b",
value: images.join("\u3000\u3000"),
inline: false,
});
}
if (clanEmbed) {
return {embeds: [embed, clanEmbed]};
} else {
return {embed};
}
}
case "preview":
case "verification": {
const embed = {
title: guild.name,
description: guild.description ?? "*No description.*",
fields: [
{
name: "Created",
value: `<t:${Math.floor(snowflakeToTimestamp(guild.id) / 1000)}:R>`,
inline: true,
},
guild.verification_level && {
name: "Verification Level",
value: VerificationLevelStrings[guild.verification_level],
inline: true,
},
(guild.emojis?.length ?? 0) > 0 && {
name: `Emotes (${guild.emojis.length})`,
value: `${guild.emojis.filter((e) => e.animated).length} animated, ${
guild.emojis.filter((e) => e.managed).length
} managed\n${guild.emojis.filter((e) => !e.available).length} unavailable`,
inline: true,
},
(guild.stickers?.length ?? 0) > 0 && {
name: `Stickers (${guild.stickers.length})`,
value: `${guild.stickers.filter((s) => s.format_type == 1).length} PNG, ${
guild.stickers.filter((s) => s.format_type == 2).length
} APNG, ${guild.stickers.filter((s) => s.format_type == 4).length} GIF, ${
guild.stickers.filter((s) => s.format_type == 3).length
} Lottie\n${guild.stickers.filter((s) => !s.available).length} unavailable`,
inline: true,
},
].filter((x) => !!x),
footer: {
text: `Fetched from ${_guild.source === "verification" ? "membership screening" : "guild preview"}`,
},
};
if (guild.icon) {
embed.thumbnail = {
url: CDNEndpoints.GUILD_ICON(guild.id, guild.icon),
};
}
embed.fields.push({
name: "Member Count",
value: `${Icons.online}${guild.approximate_presence_count} online${Icons.blank}${Icons.blank}${Icons.offline}${guild.approximate_member_count} members`,
inline: false,
});
const features = formatGuildFeatures(guild.features);
embed.fields.push({
name: `Features (${features.length})`,
value: features.length > 0 ? features.slice(0, Math.ceil(features.length / 2)).join("\n") : "None",
inline: true,
});
if (features.length > 1)
embed.fields.push({
name: "\u200b",
value: features.slice(Math.ceil(features.length / 2), features.length).join("\n"),
inline: true,
});
const images = [];
if (guild.icon) {
images.push(`[Icon](${embed.thumbnail.url})`);
}
if (guild.splash) {
images.push(`[Invite Splash](${CDNEndpoints.GUILD_SPLASH(guild.id, guild.splash)})`);
}
if (guild.discovery_splash) {
images.push(`[Discovery Splash](${CDNEndpoints.DISCOVERY_SPLASH(guild.id, guild.discoverySplash)})`);
}
if (images.length > 0) {
embed.fields.push({
name: "\u200b",
value: images.join("\u3000\u3000"),
inline: false,
});
}
if (clanEmbed) {
return {embeds: [embed, clanEmbed]};
} else {
return {embed};
}
}
case "discovery": {
if (!guild.store_page) {
return "Got discovery data but no store page.";
}
const guildObj = guild.store_page.guild;
let invite;
if (guildObj.invite?.code || guild.store_page.role_subscription.purchase_page_invite?.code) {
const code = guildObj.invite?.code ?? guild.store_page.role_subscription.purchase_page_invite?.code;
invite = await hf.bot.requestHandler.request(
"GET",
`${APIEndpoints.INVITE(code)}?with_counts=true&with_expiration=true`
);
}
const embed = {
title: guildObj.name,
description: invite?.guild?.description ?? "*No description.*",
fields: [
{
name: "Created",
value: `<t:${Math.floor(snowflakeToTimestamp(guildObj.id) / 1000)}:R>`,
inline: true,
},
],
footer: {
text: "Fetched from discovery" + (invite ? " + invite" : ""),
},
};
if (guildObj.icon_hash) {
embed.thumbnail = {
url: CDNEndpoints.GUILD_ICON(guildObj.id, guildObj.icon_hash),
};
}
const invites = [];
if (guildObj.invite?.code) invites.push(guildObj.invite.code);
if (guild.store_page.role_subscription.purchase_page_invite?.code)
invites.push(guild.store_page.role_subscription.purchase_page_invite.code);
if (invites.length > 0) {
embed.fields.push({
name: "Invites",
value: invites.map((code) => "https://discord.gg/" + code).join("\n"),
inline: true,
});
}
embed.fields.push({
name: "Member Count",
value: `${Icons.online}${guildObj.approximate_presence_count} online\t\t<:offline:1152111682886316042>${guildObj.approximate_member_count} members`,
inline: false,
});
if (invite?.guild?.features) {
const features = formatGuildFeatures(invite.guild.features);
embed.fields.push({
name: `Features (${features.length})`,
value: features.length > 0 ? features.slice(0, Math.ceil(features.length / 2)).join("\n") : "None",
inline: true,
});
if (features.length > 1)
embed.fields.push({
name: "\u200b",
value: features.slice(Math.ceil(features.length / 2), features.length).join("\n"),
inline: true,
});
}
const images = [];
if (guildObj.icon_hash) {
images.push(`[Icon](${embed.thumbnail.url})`);
}
if (invite?.guild?.splash) {
images.push(`[Invite Splash](${CDNEndpoints.GUILD_SPLASH(guild.id, guild.splash)})`);
}
if (invite?.guild?.banner) {
images.push(`[Banner](${CDNEndpoints.BANNER(guild.id, guild.banner)})`);
}
if (images.length > 0) {
embed.fields.push({
name: "\u200b",
value: images.join("\u3000\u3000"),
inline: false,
});
}
if (clanEmbed) {
return {embeds: [embed, clanEmbed]};
} else {
return {embed};
}
}
case "widget": {
let invite;
if (guild.instant_invite) {
invite = await hf.bot.requestHandler.request(
"GET",
`/invites/${guild.instant_invite.replace(
/(https?:\/\/)?discord(\.gg|(app)?.com\/invite)\//,
""
)}?with_counts=true&with_expiration=true`
);
}
const embed = {
title: guild.name,
description: invite?.guild?.description ?? "*No description.*",
fields: [
{
name: "Created",
value: `<t:${Math.floor(snowflakeToTimestamp(guild.id) / 1000)}:R>`,
inline: true,
},
],
footer: {
text: "Fetched from widget" + (invite ? " + invite" : ""),
},
};
if (invite) {
embed.fields.push({
name: "Invite",
value: guild.instant_invite,
inline: true,
});
embed.fields.push({
name: "Member Count",
value: `<:online:1152111668856361010>${invite.approximate_presence_count} online\t\t<:offline:1152111682886316042>${invite.approximate_member_count} members`,
inline: false,
});
const features = formatGuildFeatures(invite.guild.features);
embed.fields.push({
name: `Features (${features.length})`,
value: features.length > 0 ? features.slice(0, Math.ceil(features.length / 2)).join("\n") : "None",
inline: true,
});
if (features.length > 1)
embed.fields.push({
name: "\u200b",
value: features.slice(Math.ceil(features.length / 2), features.length).join("\n"),
inline: true,
});
const images = [];
if (invite.guild.icon) {
images.push(`[Icon](${CDNEndpoints.GUILD_ICON(invite.guild.id, invite.guild.icon)})`);
}
if (invite.guild.splash) {
images.push(`[Invite Splash](${CDNEndpoints.GUILD_SPLASH(invite.guild.id, invite.guild.splash)})`);
}
if (invite.guild.banner) {
images.push(`[Banner](${CDNEndpoints.BANNER(invite.guild.id, invite.guild.banner)})`);
}
if (images.length > 0) {
embed.fields.push({
name: "\u200b",
value: images.join("\u3000\u3000"),
inline: false,
});
}
} else {
embed.fields.push({
name: "Member Count",
value: `${Icons.online}${guild.presence_count} online`,
inline: false,
});
}
if (clanEmbed) {
return {embeds: [embed, clanEmbed]};
} else {
return {embed};
}
}
default:
return "Guild not found.";
}
};
hf.registerCommand(guildinfo);
const guildinfoInteraction = new InteractionCommand("guildinfo");
guildinfoInteraction.helpText = "Get information on an guild";
guildinfoInteraction.options.id = {
name: "id",
type: ApplicationCommandOptionTypes.STRING,
description: "Guild ID to get info for",
required: false,
default: "",
};
guildinfoInteraction.callback = async function (interaction) {
const id = this.getOption(interaction, "id");
return guildinfo.callback(interaction, id);
};
hf.registerCommand(guildinfoInteraction);

View file

@ -0,0 +1,145 @@
const Command = require("../../lib/command.js");
const InteractionCommand = require("../../lib/interactionCommand.js");
const sharp = require("sharp");
const {
RegExp: {Emote: CUSTOM_EMOTE_REGEX},
EmojiSets,
EmojiNames,
} = require("../../util/constants.js");
const {ApplicationCommandOptionTypes, CDNEndpoints} = require("../../util/dconstants.js");
const {getNamesFromString} = require("../../util/unicode.js");
const jumbo = new Command("jumbo");
jumbo.category = "utility";
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 (!line || line === "") return "Arguments required.";
if (CUSTOM_EMOTE_REGEX.test(line)) {
const [, animatedFlag, name, id] = line.match(CUSTOM_EMOTE_REGEX);
const animated = animatedFlag === "a";
const url = CDNEndpoints.EMOJI(id, animated);
return {
embeds: [
{
title: `:${name}: - \`${id}\``,
url,
image: {
url,
},
},
],
};
} else {
let setName = "twemoji";
for (const set in EmojiSets) {
if (line.startsWith(`--${set} `)) {
setName = set;
line = line.replace(`--${set} `, "");
}
}
const set = EmojiSets[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).then((name) => name.map((x) => x[1]).join(", "));
const emojiFound = await fetch(url, {method: "HEAD"}).then((res) => res.ok);
if (!emojiFound) {
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.arrayBuffer())
.then((b) => Buffer.from(b));
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 jumboInteraction = new InteractionCommand("jumbo");
jumboInteraction.helpText = "Gets the raw image of an emoji.";
jumboInteraction.options.content = {
name: "content",
type: ApplicationCommandOptionTypes.STRING,
description: "Emoji to get image of",
required: true,
default: "",
};
jumboInteraction.options.set = {
name: "set",
type: ApplicationCommandOptionTypes.STRING,
description: "Emoji set for non-custom emoji",
required: false,
choices: [
{
name: "Twemoji (Twitter/Discord)",
value: "twemoji",
},
{
name: "Noto (Google)",
value: "noto",
},
{
name: "Noto Old (Blobs)",
value: "blobs",
},
{
name: "Mutant Standard",
value: "mustd",
},
],
default: "twemoji",
};
jumboInteraction.callback = async function (interaction) {
const content = this.getOption(interaction, "content");
const set = this.getOption(interaction, "set");
return jumbo.callback(interaction, `--${set} ${content}`);
};
hf.registerCommand(jumboInteraction);

View file

@ -0,0 +1,245 @@
const Command = require("../../lib/command.js");
const InteractionCommand = require("../../lib/interactionCommand.js");
const {
APIEndpoints,
ApplicationCommandOptionTypes,
CDNEndpoints,
DEFAULT_GROUP_DM_AVATARS,
} = require("../../util/dconstants.js");
const {Icons} = require("../../util/constants.js");
const {formatUsername} = require("../../util/misc.js");
const {safeString, formatGuildFeatures} = require("../../util/selection.js");
const {snowflakeToTimestamp} = require("../../util/time.js");
const lookupinvite = new Command("lookupinvite");
lookupinvite.category = "utility";
lookupinvite.helpText = "Lookup an invite";
lookupinvite.usage = "<invite code>";
lookupinvite.addAlias("linvite");
lookupinvite.addAlias("inviteinfo");
lookupinvite.addAlias("iinfo");
lookupinvite.addAlias("ii");
lookupinvite.callback = async function (msg, line) {
if (!line || line == "") return "Arguments required.";
line = line.replace(/(https?:\/\/)?discord(\.gg|(app)?.com\/invite)\//, "");
if (decodeURI(line).indexOf("../") > -1) return "nuh uh";
let bail = false;
let error;
let invite;
try {
invite = await hf.bot.requestHandler.request(
"GET",
`${APIEndpoints.INVITE(line)}?with_counts=true&with_expiration=true`
);
} catch (err) {
bail = true;
error = err;
}
if (bail && error) {
if (error.message.includes("Unknown Invite")) {
return "Invite provided is not valid.";
} else {
return `:warning: Got error \`${safeString(error)}\``;
}
}
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: [],
};
const expires = {
name: "Expires",
value: invite.expires_at == null ? "Never" : `<t:${Math.floor(new Date(invite.expires_at).getTime() / 1000)}>`,
inline: true,
};
const inviter = invite.inviter
? {
name: "Inviter",
value: `**${formatUsername(invite.inviter)}** (${invite.inviter.id})`,
inline: false,
}
: null;
const features = formatGuildFeatures(invite.guild.features);
switch (invite.type) {
case 0: {
embed.fields.push(
...[
{
name: "Guild",
value: `**${invite.guild.name}** (${invite.guild.id})`,
inline: true,
},
{
name: "Channel",
value: `**${invite.channel.name}** (${invite.channel.id})`,
inline: true,
},
{
name: "Boosts",
value: invite.guild.premium_subscription_count ?? 0,
inline: true,
},
expires,
{
name: "Member Count",
value: `${Icons.online}${invite.approximate_presence_count} online${Icons.blank}${Icons.blank}${Icons.offline}${invite.approximate_member_count} members`,
inline: false,
},
inviter,
invite.guild.welcome_screen && {
name: "Welcome Screen",
value: `"${invite.guild.welcome_screen.description}"\n${invite.guild.welcome_screen.welcome_channels
.map(
(c) =>
`${
c.emoji_id ? `[:${c.emoji_name}:](${CDNEndpoints.EMOJI(c.emoji_id, false, true)})` : c.emoji_name
} ${c.description} \`(${c.channel_id})\``
)
.join("\n")}`,
inline: false,
},
{
name: `Features (${features.length})`,
value: features.length > 0 ? features.slice(0, Math.ceil(features.length / 2)).join("\n") : "None",
inline: true,
},
features.length > 1
? {
name: "\u200b",
value: features.slice(Math.ceil(features.length / 2), features.length).join("\n"),
inline: true,
}
: null,
].filter((x) => !!x)
);
const guildIcon = invite.guild?.icon && CDNEndpoints.GUILD_ICON(invite.guild.id, invite.guild.icon);
const guildSplash = invite.guild?.splash && CDNEndpoints.GUILD_SPLASH(invite.guild.id, invite.guild.splash);
if (guildIcon)
embed.thumbnail = {
url: guildIcon,
};
if (invite.guild && (invite.guild.icon || invite.guild.splash || invite.guild.banner)) {
const imageLinks = [];
if (guildIcon) imageLinks.push(`[Icon](${guildIcon})`);
if (invite.guild.splash) imageLinks.push(`[Splash](${guildSplash})`);
if (invite.guild.banner)
imageLinks.push(`[Splash](${CDNEndpoints.BANNER(invite.guild.id, invite.guild.banner)})`);
embed.fields.push({
name: "\u200b",
value: imageLinks.join("\u3000\u3000"),
inline: false,
});
}
if (guildSplash)
embed.image = {
url: guildSplash,
};
break;
}
case 1: {
embed.title += " (Group DM)";
embed.fields.push(
...[
{
name: "Channel",
value: `**${
invite.channel.name ?? invite.channel.recipients.map((member) => member.username).join(", ")
}** (${invite.channel.id})`,
inline: false,
},
{
name: "Member Count",
value: `<:offline:1152111682886316042>${invite.approximate_member_count} members`,
inline: true,
},
expires,
invite.channel.name != null && {
name: "Recipients",
value: invite.channel.recipients.map((member) => member.username).join(", "),
inline: false,
},
inviter,
].filter((x) => !!x)
);
const groupIcon = invite.channel.icon
? CDNEndpoints.GDM_ICON(invite.channel.id, invite.channel.icon)
: DEFAULT_GROUP_DM_AVATARS[snowflakeToTimestamp(invite.channel.id) % DEFAULT_GROUP_DM_AVATARS.length];
embed.thumbnail = {
url: groupIcon,
};
embed.fields.push({
name: "\u200b",
value: `[Icon](${groupIcon})`,
inline: false,
});
break;
}
case 2: {
embed.title += " (Friend)";
embed.fields.push(expires, inviter);
const avatarURL = invite.inviter?.avatar && CDNEndpoints.USER_AVATAR(invite.inviter.id, invite.inviter.avatar);
if (avatarURL) {
embed.thumbnail = {
url: avatarURL,
};
embed.fields.push({
name: "\u200b",
value: `[Avatar](${avatarURL})`,
inline: false,
});
}
break;
}
default:
return `Unhandled invite type: \`${invite.type}\``;
}
return {embed};
}
};
hf.registerCommand(lookupinvite);
const inviteinfoInteraction = new InteractionCommand("inviteinfo");
inviteinfoInteraction.helpText = "Get information on an invite code";
inviteinfoInteraction.options.invite = {
name: "invite",
type: ApplicationCommandOptionTypes.STRING,
description: "Invite code to get info for",
required: true,
default: "",
};
inviteinfoInteraction.callback = async function (interaction) {
const invite = this.getOption(interaction, "invite");
return lookupinvite.callback(interaction, invite);
};
hf.registerCommand(inviteinfoInteraction);

View file

@ -0,0 +1,46 @@
const Command = require("../../lib/command.js");
const {APIEndpoints} = require("../../util/dconstants.js");
const {
RegExp: {Pomelo: POMELO_REGEX},
} = require("../../util/constants.js");
const pomelo = new Command("pomelo");
pomelo.category = "utility";
pomelo.helpText = "Check to see if a username is taken or not";
pomelo.usage = "[username] <...username>";
pomelo.callback = async function (msg, line) {
if (!line || line === "") return "Arguments required.";
const usernames = line.toLowerCase().split(" ");
if (usernames.length == 1) {
const name = usernames[0];
if (name.length > 32 || !POMELO_REGEX.test(name)) return {reaction: "\u{1f6ab}"};
const res = await hf.bot.requestHandler.request("POST", APIEndpoints.POMELO_UNAUTHED, false, {
username: name,
});
return {reaction: res.taken ? "\u274c" : "\u2705"};
} else {
const lines = [];
for (const name of usernames) {
if (name.length > 32 || !POMELO_REGEX.test(name)) {
lines.push(`\u{1f6ab} \`${name}\``);
} else {
try {
const res = await hf.bot.requestHandler.request("POST", APIEndpoints.POMELO_UNAUTHED, false, {
username: name,
});
lines.push(`${res.taken ? "\u274c" : "\u2705"} \`${name}\``);
} catch {
lines.push(`\u26a0\ufe0f \`${name}\``);
}
}
}
return lines.join("\n");
}
};
hf.registerCommand(pomelo);

View file

@ -0,0 +1,340 @@
const Command = require("../../lib/command.js");
const sharp = require("sharp");
const {
ActivityTypeNames,
CDNEndpoints,
Games,
HangStatusStrings,
HANG_STATUS_ICONS,
} = require("../../util/dconstants.js");
const {Icons} = require("../../util/constants.js");
const {formatUsername} = require("../../util/misc.js");
const {lookupUser} = require("../../util/selection.js");
const {formatTime} = require("../../util/time.js");
const HangStatusImages = {};
(async () => {
for (const key of Object.keys(HANG_STATUS_ICONS)) {
const svg = await fetch(HANG_STATUS_ICONS[key])
.then((res) => res.arrayBuffer())
.then((b) => Buffer.from(b));
HangStatusImages[key] = await sharp(svg, {density: 2400}).resize(128).toBuffer();
}
})();
const NOWPLAYING_BAR_LENGTH = 30;
function fixMediaProxyURL(url) {
if (url.includes("media.discordapp.net")) {
if (url.includes("/external/")) {
const split = url.replace("https://media.discordapp.net/external/", "").split("/");
split.shift();
let query = "";
if (split[0].startsWith("%")) {
query = decodeURIComponent(split.shift());
}
split[0] = split[0] + ":/";
url = split.join("/") + query;
} else {
url = url.replace("media.discordapp.net", "cdn.discordapp.com");
}
}
return url;
}
const presence = new Command("presence");
presence.category = "utility";
presence.helpText = "Get presences of a user.";
presence.usage = "<user>";
presence.addAlias("status");
presence.callback = async function (msg, line) {
if (!msg.guildID) return "Can only be used in guilds.";
let target;
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];
}
target = member;
}
}
} else {
target = msg.member;
}
if (target) {
if (!target.clientStatus) return `**${formatUsername(target)}** is offline.`;
const icons = [];
for (const platform of Object.keys(target.clientStatus)) {
const status = target.clientStatus[platform];
if (status == "offline") continue;
icons.push(Icons.presence[platform][status]);
}
const embeds = [];
const files = [];
for (const index in target.activities) {
const activity = target.activities[index];
if (activity.type == 4) {
const embed = {};
if (activity.emoji) {
if (activity.emoji.id) {
const url = CDNEndpoints.EMOJI(activity.emoji.id, activity.emoji.animated);
embed.author = {
url,
icon_url: url,
name: activity.state ?? "\u200b",
};
} else {
embed.title = `${activity.emoji.name} ${activity.state ?? ""}`;
}
} else {
embed.title = activity.state ?? "";
}
embeds.push(embed);
} else if (activity.type == 6) {
const embed = {};
embed.title = "Hang Status";
embed.description = `Right now I'm \u2013\n**${
activity.state == "custom" ? activity.details : HangStatusStrings[activity.state]
}**`;
if (activity.emoji) {
embed.thumbnail = {
url: CDNEndpoints.EMOJI(activity.emoji.id, activity.emoji.animated),
};
} else {
files.push({
contents: HANG_STATUS_ICONS[activity.state],
name: `${activity.state}.png`,
});
embed.thumbnail = {
url: `attachment://${activity.state}.png`,
};
}
embeds.push(embed);
} else {
const embed = {
title: `${ActivityTypeNames[activity.type]} **${activity.name}**`,
fields: [],
};
const descLines = [];
if (activity.type == 2) {
if (activity.details) {
let details = activity.details;
if (activity.name == "Spotify" && activity.sync_id) {
details = `[${details}](https://open.spotify.com/track/${activity.sync_id})`;
}
descLines.push(`**${details}**`);
}
if (activity.state) {
let stateLine = activity.state;
if (activity.name == "Spotify") stateLine = "by " + stateLine.split("; ").join(", ");
descLines.push(stateLine);
}
if (activity.assets?.large_text) {
let albumLine = activity.assets.large_text;
if (activity.name == "Spotify") albumLine = "on " + albumLine;
if (activity.party?.size) {
albumLine += ` (${activity.party.size[0]} of ${activity.party.size[1]})`;
}
descLines.push(albumLine);
}
} else {
if (activity.details) descLines.push(activity.details);
if (activity.state) {
let stateLine = activity.state;
if (activity.party?.size) {
stateLine += ` (${activity.party.size[0]} of ${activity.party.size[1]})`;
}
descLines.push(stateLine);
}
}
if (activity.timestamps) {
if (activity.timestamps.start && !activity.timestamps.end) {
descLines.push(formatTime(Date.now() - activity.timestamps.start) + " elapsed");
} else if (!activity.timestamps.start && activity.timestamps.end) {
descLines.push(formatTime(activity.timestamps.end - Date.now()) + " remaining");
} else if (activity.timestamps.start != null && activity.timestamps.end != null) {
const position = Date.now() - activity.timestamps.start;
const length = activity.timestamps.end - activity.timestamps.start;
const timeEnd = formatTime(length);
const timePos = formatTime(position);
const progress = position >= length ? 1 : position / length;
const barLength = Math.round(progress * NOWPLAYING_BAR_LENGTH);
const bar = `\`[${"=".repeat(barLength)}${" ".repeat(NOWPLAYING_BAR_LENGTH - barLength)}]\``;
const time = `\`${timePos}${" ".repeat(
NOWPLAYING_BAR_LENGTH + 2 - timePos.length - timeEnd.length
)}${timeEnd}\``;
descLines.push(bar);
descLines.push(time);
}
}
if (activity.assets?.large_text && activity.type != 2) {
embed.fields.push({
name: "Large Text",
value: activity.assets.large_text,
});
}
if (activity.assets?.small_text) {
embed.fields.push({
name: "Small Text",
value: activity.assets.small_text,
});
}
if (activity.application_id) {
embed.fields.push({
name: "Application ID",
value: `\`${activity.application_id}\``,
});
}
embed.description = descLines.join("\n");
if (activity.assets) {
if (activity.assets.large_image != null) {
const image_links = [];
let largeUrl;
if (activity.assets.large_image.startsWith("mp:")) {
largeUrl = activity.assets.large_image.replace("mp:", "https://media.discordapp.net/");
} else if (activity.assets.large_image.startsWith("spotify:")) {
largeUrl = activity.assets.large_image.replace("spotify:", "https://i.scdn.co/image/");
} else {
largeUrl = CDNEndpoints.APP_ASSET(activity.application_id, activity.assets.large_image);
}
image_links.push(`[Large Image](${fixMediaProxyURL(largeUrl)})`);
let smallUrl;
if (activity.assets.small_image != null) {
if (activity.assets.small_image.startsWith("mp:")) {
smallUrl = activity.assets.small_image.replace("mp:", "https://media.discordapp.net/");
} else if (activity.assets.small_image.startsWith("spotify:")) {
smallUrl = activity.assets.small_image.replace("spotify:", "https://i.scdn.co/image/");
} else {
smallUrl = CDNEndpoints.APP_ASSET(activity.application_id, activity.assets.small_image);
}
image_links.push(`[Small Image](${fixMediaProxyURL(smallUrl)})`);
}
const largeImage = await fetch(largeUrl)
.then((res) => res.arrayBuffer())
.then((b) => Buffer.from(b));
const presenceImage = sharp(largeImage).resize(60, 60);
if (smallUrl) {
const smallImage = await fetch(smallUrl)
.then((res) => res.arrayBuffer())
.then((b) => Buffer.from(b));
const smallImageBuffer = await sharp(smallImage).resize(20, 20).toBuffer();
presenceImage.composite([
{
input: smallImageBuffer,
gravity: "southeast",
},
]);
}
files.push({
contents: await presenceImage.toBuffer(),
name: `${index}.png`,
});
embed.thumbnail = {
url: `attachment://${index}.png`,
};
embed.fields.push({
name: "\u200b",
value: image_links.join("\u3000\u3000"),
});
} else if (!activity.assets.large_image && activity.assets.small_image != null) {
let smallUrl;
if (activity.assets.small_image.startsWith("mp:")) {
smallUrl = activity.assets.small_image.replace("mp:", "https://media.discordapp.net/");
} else if (activity.assets.small_image.startsWith("spotify:")) {
smallUrl = activity.assets.small_image.replace("spotify:", "https://i.scdn.co/image/");
} else {
smallUrl = CDNEndpoints.APP_ASSET(activity.application_id, activity.assets.small_image);
}
const smallImage = await fetch(smallUrl)
.then((res) => res.arrayBuffer())
.then((b) => Buffer.from(b));
const presenceImage = await sharp(smallImage).resize(40, 40).toBuffer();
files.push({
contents: presenceImage,
name: `${index}.png`,
});
embed.thumbnail = {
url: `attachment://${index}.png`,
};
embed.fields.push({
name: "\u200b",
value: `[Small Image](${fixMediaProxyURL(smallUrl)})`,
});
}
}
if (activity.application_id && !activity.assets?.large_image && !activity.assets?.small_image) {
const game = Games.find((game) => game.id == activity.application_id);
if (game?.icon) {
embed.thumbnail = {
url: `${CDNEndpoints.APP_ICON(game.id, game.icon)}?size=40&keep_aspect_ratio=false`,
};
embed.fields.push({
name: "\u200b",
value: `[App Icon](${embed.thumbnail.url.replace("size=40&", "")})`,
});
}
}
embeds.push(embed);
}
}
return {
content: `Presence for **${formatUsername(target)}**: ${icons.join(" ")}`,
embeds,
files,
};
} else {
return ":warning: Could not get user???";
}
};
hf.registerCommand(presence);

View file

@ -0,0 +1,18 @@
const Command = require("../../lib/command.js");
const {snowflakeToTimestamp} = require("../../util/time.js");
const snowflake = new Command("snowflake");
snowflake.category = "utility";
snowflake.helpText = "Converts a snowflake ID into readable time.";
snowflake.usage = "<--twitter> [snowflake]";
snowflake.callback = function (msg, line, [snowflake], {twitter}) {
const num = Number(snowflake);
if (!isNaN(num)) {
const timestamp = Math.floor(snowflakeToTimestamp(num, twitter) / 1000);
return `The timestamp for \`${snowflake}\` is <t:${timestamp}:F> (<t:${timestamp}:R>)`;
} else {
return "Argument provided is not a number.";
}
};
hf.registerCommand(snowflake);

550
src/util/constants.js Normal file
View file

@ -0,0 +1,550 @@
module.exports = {};
const SILK_ICONS = Object.fromEntries(
Object.entries({
joystick: "1250544500246577172",
film: "1250544523730223277",
shield: "1250543253472673875",
vcard: "1250544496765308948",
photo: "1250543277267222639",
world_add: "1250544495435452547",
ruby: "1250544494739325001",
emoticon_smile: "1250543500471046288",
bug: "1250544574104080507",
comments_star: "1250544493841616988",
comments_delete: "1250544492709412946",
flag_blue: "1250544491471831131",
flag_red: "1250544490507141312",
controller: "1250544489156710541",
cog_delete: "1250544595666997288",
cog: "1250543252747321468",
basket: "1250544573088923722",
group: "1250543273726967900",
check_checked_green: "1250544572203929815",
world: "1250543499489706195",
world_delete: "1250544571180515408",
transmit_blue: "1250544569448136857",
comments: "1250543256052301924",
status_away: "1250544568772853801",
page_link: "1250544567665561631",
book_open_mark: "1250544632064901240",
house: "1250543270342426644",
lock: "1250543498470359121",
sitemap: "1250544630907535361",
eye: "1250544630043381872",
link: "1250543266806628412",
time: "1250544629053522030",
delete: "1250543248909406248",
money: "1250544627665211404",
money_delete: "1250544627178803313",
emoticon_smile_add: "1250544626054594560",
image_add: "1250544625068806215",
newspaper: "1250543497010872485",
bell: "1250544670178803722",
application_view_tile: "1250544666080837703",
star: "1250543254869381170",
compress: "1250544665091113080",
bell_delete: "1250544686121095289",
stop: "1250544664000467056",
creditcards: "1250544663014936798",
tag_blue: "1250543263656574996",
calendar: "1250543496255897660",
book_go: "1250544662037528717",
palette: "1250544661035225189",
sound: "1250543260196147323",
key: "1250543495286882375",
book_tabs: "1250544659940380672",
book_tabs_delete: "1250555962364919808",
wand: "1250544659038601266",
tick: "1250543251375653007",
microphone: "1250543257398542436",
user_add: "1250544566503739412",
controller_delete: "1262137043417563256",
}).map(([key, val]) => [key, `<:i:${val}>`])
);
// https://docs.discord.sex/resources/guild#guild-features
module.exports.GuildFeaturesFormatted = {
ACTIVITIES_ALPHA: {icon: SILK_ICONS.joystick, name: "Activities (Alpha)"},
ACTIVITIES_EMPLOYEE: {icon: SILK_ICONS.joystick, name: "Activities (Staff)"},
ACTIVITIES_INTERNAL_DEV: {
icon: SILK_ICONS.joystick,
name: "Activities (Internal Dev)",
},
ACTIVITY_FEED_ENABLED_BY_USER: {icon: SILK_ICONS.controller},
ACTIVITY_FEED_DISABLED_BY_USER: {icon: SILK_ICONS.controller_delete},
ANIMATED_BANNER: {icon: SILK_ICONS.film},
ANIMATED_ICON: {icon: SILK_ICONS.film},
APPLICATION_COMMAND_PERMISSIONS_V2: {
icon: SILK_ICONS.cog,
name: "Command Permissions V2",
deprecated: true,
},
AUTO_MODERATION: {icon: SILK_ICONS.shield, name: "AutoMod"},
AUTOMOD_TRIGGER_KEYWORD_FILTER: {
icon: SILK_ICONS.shield,
name: "AutoMod: Keywords",
deprecated: true,
},
AUTOMOD_TRIGGER_ML_SPAM_FILTER: {
icon: SILK_ICONS.shield,
name: "AutoMod: Spam",
deprecated: true,
},
AUTOMOD_TRIGGER_SPAM_LINK_FILTER: {
icon: SILK_ICONS.shield,
name: "AutoMod: Spam Links",
deprecated: true,
},
AUTOMOD_TRIGGER_USER_PROFILE: {
icon: SILK_ICONS.vcard,
name: "AutoMod: User Profiles",
deprecated: true,
},
BANNER: {icon: SILK_ICONS.photo},
BFG: {icon: SILK_ICONS.world_add, name: "BFG"},
BOOSTING_TIERS_EXPERIMENT_MEDIUM_GUILD: {
icon: SILK_ICONS.ruby,
name: "Boosting: Medium Guild",
deprecated: true,
},
BOOSTING_TIERS_EXPERIMENT_SMALL_GUILD: {
icon: SILK_ICONS.ruby,
name: "Boosting: Small Guild",
deprecated: true,
},
BOT_DEVELOPER_EARLY_ACCESS: {icon: SILK_ICONS.cog},
BURST_REACTIONS: {icon: SILK_ICONS.wand, name: "Super Reactions"},
CHANNEL_BANNER: {icon: SILK_ICONS.photo, deprecated: true},
CHANNEL_ICON_EMOJIS_GENERATED: {
icon: SILK_ICONS.emoticon_smile,
name: "Channel Icon Emojis",
},
CHANNEL_HIGHLIGHTS: {icon: SILK_ICONS.comments_star},
CHANNEL_HIGHLIGHTS_DISABLED: {icon: SILK_ICONS.comments_delete},
CLAN: {icon: SILK_ICONS.flag_blue},
CLAN_DISCOVERY_DISABLED: {icon: SILK_ICONS.flag_red},
CLAN_PILOT_GENSHIN: {
icon: SILK_ICONS.flag_blue,
name: "Clan Pilot: Genshin Impact",
},
CLAN_PILOT_VALORANT: {
icon: SILK_ICONS.flag_blue,
name: "Clan Pilot: Valorant",
},
CLAN_PREPILOT_GENSHIN: {
icon: SILK_ICONS.flag_blue,
name: "Clan Pre-Pilot: Genshin Impact",
},
CLAN_PREPILOT_VALORANT: {
icon: SILK_ICONS.flag_blue,
name: "Clan Pre-Pilot: Valorant",
},
CLYDE_DISABLED: {icon: SILK_ICONS.cog_delete, deprecated: true},
CLYDE_ENABLED: {icon: SILK_ICONS.cog, deprecated: true},
CLYDE_EXPERIMENT_ENABLED: {icon: SILK_ICONS.bug, deprecated: true},
COMMERCE: {icon: SILK_ICONS.basket},
COMMUNITY: {icon: SILK_ICONS.group},
COMMUNITY_CANARY: {icon: SILK_ICONS.bug, name: "Community (Canary)"},
COMMUNITY_EXP_LARGE_GATED: {
icon: SILK_ICONS.group,
name: "Community: Large - Gated",
},
COMMUNITY_EXP_LARGE_UNGATED: {
icon: SILK_ICONS.group,
name: "Community: Large - Ungated",
},
COMMUNITY_EXP_MEDIUM: {icon: SILK_ICONS.group, name: "Community: Medium"},
CREATOR_ACCEPTED_NEW_TERMS: {icon: SILK_ICONS.check_checked_green},
CREATOR_MONETIZABLE: {icon: SILK_ICONS.money, name: "Monetization"},
CREATOR_MONETIZABLE_DISABLED: {
icon: SILK_ICONS.money_delete,
name: "Monetization Disabled",
},
CREATOR_MONETIZABLE_PENDING_NEW_OWNER_ONBOARDING: {
icon: SILK_ICONS.money,
name: "Monetization: Pending Onboarding",
},
CREATOR_MONETIZABLE_PROVISIONAL: {
icon: SILK_ICONS.money,
name: "Monetization (Provisional)",
},
CREATOR_MONETIZABLE_RESTRICTED: {
icon: SILK_ICONS.money_delete,
name: "Monetization Restricted",
},
CREATOR_MONETIZABLE_WHITEGLOVE: {
icon: SILK_ICONS.bug,
name: "Monetization White Glove",
},
CREATOR_MONETIZATION_APPLICATION_ALLOWLIST: {
icon: SILK_ICONS.money,
name: "Monetization Allow List",
},
CREATOR_STORE_PAGE: {icon: SILK_ICONS.basket},
DEVELOPER_SUPPORT_SERVER: {icon: SILK_ICONS.cog},
DISCOVERABLE: {icon: SILK_ICONS.world},
DISCOVERABLE_DISABLED: {icon: SILK_ICONS.world_delete},
ENABLED_DISCOVERABLE_BEFORE: {icon: SILK_ICONS.world},
EXPOSED_TO_ACTIVITIES_WTP_EXPERIMENT: {
icon: SILK_ICONS.bug,
name: "Activities: WTP Experiment",
},
EXPOSED_TO_BOOSTING_TIERS_EXPERIMENT: {
icon: SILK_ICONS.bug,
name: "Boosting: Tiers Experiment",
deprecated: true,
},
FEATURABLE: {icon: SILK_ICONS.world, deprecated: true},
FORCE_RELAY: {icon: SILK_ICONS.transmit_blue, deprecated: true},
GENSHIN_L30: {icon: SILK_ICONS.controller, name: "Clans: Genshin Impact L30"},
GUESTS_ENABLED: {icon: SILK_ICONS.status_away},
GUILD_AUTOMOD_DEFAULT_LIST: {
icon: SILK_ICONS.shield,
name: "AutoMod: Default List",
deprecated: true,
},
GUILD_COMMUNICATION_DISABLED_GUILDS: {
icon: SILK_ICONS.time,
name: "Member Timeouts",
deprecated: true,
},
GUILD_HOME_DEPRECATION_OVERRIDE: {
icon: SILK_ICONS.house,
name: "Home Tab Deprecation Hidden",
},
GUILD_HOME_OVERRIDE: {icon: SILK_ICONS.house, name: "Home Tab (Override)"},
GUILD_HOME_TEST: {icon: SILK_ICONS.bug, name: "Home Tab (Testing)"},
GUILD_MEMBER_VERIFICATION_EXPERIMENT: {icon: SILK_ICONS.bug},
GUILD_ONBOARDING: {
icon: SILK_ICONS.application_view_tile,
name: "Onboarding",
},
GUILD_ONBOARDING_ADMIN_ONLY: {
icon: SILK_ICONS.application_view_tile,
name: "Onboarding: Admin Only",
deprecated: true,
},
GUILD_ONBOARDING_EVER_ENABLED: {
icon: SILK_ICONS.application_view_tile,
name: "Onboarding: Ever Enabled",
},
GUILD_ONBOARDING_HAS_PROMPTS: {
icon: SILK_ICONS.application_view_tile,
name: "Onboarding: Has Prompts",
},
GUILD_PRODUCTS: {icon: SILK_ICONS.basket, name: "Products"},
GUILD_PRODUCTS_ALLOW_ARCHIVED_FILE: {
icon: SILK_ICONS.compress,
name: "Products: Allow Archives",
},
GUILD_ROLE_SUBSCRIPTIONS: {
icon: SILK_ICONS.creditcards,
name: "Role Subscriptions",
deprecated: true,
},
GUILD_ROLE_SUBSCRIPTION_PURCHASE_FEEDBACK_LOOP: {
icon: SILK_ICONS.creditcards,
name: "Role Subscriptions: Feedback Loop",
deprecated: true,
},
GUILD_ROLE_SUBSCRIPTION_TIER_TEMPLATE: {
icon: SILK_ICONS.creditcards,
name: "Role Subscriptions: Tier Template",
deprecated: true,
},
GUILD_ROLE_SUBSCRIPTION_TRIALS: {
icon: SILK_ICONS.creditcards,
name: "Role Subscriptions: Trials",
deprecated: true,
},
GUILD_SERVER_GUIDE: {icon: SILK_ICONS.book_go, name: "Server Guide"},
GUILD_WEB_PAGE_VANITY_URL: {
icon: SILK_ICONS.page_link,
name: "Guild Web Page Vanity URL",
},
HAD_EARLY_ACTIVITIES_ACCESS: {
icon: SILK_ICONS.joystick,
name: "Activities: Had Early Access",
},
HAS_DIRECTORY_ENTRY: {icon: SILK_ICONS.book_open_mark},
HIDE_FROM_EXPERIMENT_UI: {
icon: SILK_ICONS.bug,
name: "Hidden from Experiment UI",
},
HUB: {icon: SILK_ICONS.sitemap, name: "Student Hub"},
INCREASED_THREAD_LIMIT: {
icon: SILK_ICONS.comments,
name: "Threads: Increased Limit",
},
INTERNAL_EMPLOYEE_ONLY: {
icon: SILK_ICONS.key,
name: "Staff: Internal Employee Only",
},
INVITE_SPLASH: {icon: SILK_ICONS.photo},
INVITES_DISABLED: {icon: SILK_ICONS.lock},
LINKED_TO_HUB: {icon: SILK_ICONS.sitemap, name: "Student Hub: Linked to Hub"},
LURKABLE: {icon: SILK_ICONS.eye, deprecated: true},
MARKETPLACES_CONNECTION_ROLES: {icon: SILK_ICONS.link, deprecated: true},
MEDIA_CHANNEL_ALPHA: {icon: SILK_ICONS.photo, deprecated: true},
MEMBER_LIST_DISABLED: {icon: SILK_ICONS.delete, deprecated: true},
MEMBER_PROFILES: {icon: SILK_ICONS.vcard, deprecated: true},
MEMBER_SAFETY_PAGE_ROLLOUT: {icon: SILK_ICONS.shield},
MEMBER_VERIFICATION_GATE_ENABLED: {
icon: SILK_ICONS.shield,
name: "Member Verification Gate",
},
MEMBER_VERIFICATION_MANUAL_APPROVAL: {
icon: SILK_ICONS.user_add,
name: "Member Join Requests",
},
MOBILE_WEB_ROLE_SUBSCRIPTION_PURCHASE_PAGE: {
icon: SILK_ICONS.creditcards,
name: "Role Subscriptions: Mobile Page",
deprecated: true,
},
MONETIZATION_ENABLED: {icon: SILK_ICONS.money, deprecated: true},
MORE_EMOJI: {icon: SILK_ICONS.emoticon_smile_add},
MORE_STICKERS: {icon: SILK_ICONS.photo},
NEWS: {icon: SILK_ICONS.newspaper, name: "Announcement Channels"},
NEW_THREAD_PERMISSIONS: {
icon: SILK_ICONS.comments,
name: "Threads: New Permissions",
deprecated: true,
},
NON_COMMUNITY_RAID_ALERTS: {icon: SILK_ICONS.bell},
PARTNERED: {icon: SILK_ICONS.star},
PREMIUM_TIER_3_OVERRIDE: {icon: SILK_ICONS.ruby},
PREVIEW_ENABLED: {icon: SILK_ICONS.eye},
PRIVATE_THREADS: {
icon: SILK_ICONS.comments,
name: "Threads: Private Threads",
deprecated: true,
},
PRODUCTS_AVAILABLE_FOR_PURCHASE: {
icon: SILK_ICONS.basket,
name: "Products: Has Purchasable",
},
PUBLIC: {icon: SILK_ICONS.world, deprecated: true},
PUBLIC_DISABLED: {icon: SILK_ICONS.world_delete, deprecated: true},
RAID_ALERTS_DISABLED: {icon: SILK_ICONS.bell_delete},
RAID_ALERTS_ENABLED: {icon: SILK_ICONS.bell, deprecated: true},
RELAY_ENABLED: {icon: SILK_ICONS.transmit_blue, name: "Sharded"},
RESTRICT_SPAM_RISK_GUILDS: {icon: SILK_ICONS.stop, deprecated: true},
ROLE_ICONS: {icon: SILK_ICONS.tag_blue},
ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE: {
icon: SILK_ICONS.creditcards,
name: "Role Subscriptions: Has Purchasable",
},
ROLE_SUBSCRIPTIONS_ENABLED: {icon: SILK_ICONS.creditcards},
ROLE_SUBSCRIPTIONS_ENABLED_FOR_PURCHASE: {
icon: SILK_ICONS.creditcards,
name: "Role Subscriptions: Has Purchasable",
deprecated: true,
},
SEVEN_DAY_THREAD_ARCHIVE: {
icon: SILK_ICONS.comments,
name: "Threads: Seven Day Archive",
deprecated: true,
},
SHARD: {icon: SILK_ICONS.sitemap, name: "Student Hub: Shard"},
SHARED_CANVAS_FRIENDS_AND_FAMILY_TEST: {
icon: SILK_ICONS.palette,
name: "Shared Canvas (Testing)",
},
SOUNDBOARD: {icon: SILK_ICONS.sound},
SUMMARIES_ENABLED: {icon: SILK_ICONS.book_tabs, deprecated: true},
SUMMARIES_ENABLED_GA: {
icon: SILK_ICONS.book_tabs,
name: "Summaries (General Access)",
},
SUMMARIES_DISABLED_BY_USER: {icon: SILK_ICONS.book_tabs_delete},
SUMMARIES_ENABLED_BY_USER: {icon: SILK_ICONS.book_tabs},
SUMMARIES_LONG_LOOKBACK: {
icon: SILK_ICONS.book_tabs,
name: "Summaries: Long Lookback",
},
STAFF_LEVEL_COLLABORATOR_REQUIRED: {
icon: SILK_ICONS.key,
name: "Staff: Collaborators Only",
},
STAFF_LEVEL_RESTRICTED_COLLABORATOR_REQUIRED: {
icon: SILK_ICONS.key,
name: "Staff: Restricted Collaborators Only",
},
TEXT_IN_STAGE_ENABLED: {icon: SILK_ICONS.comments, deprecated: true},
TEXT_IN_VOICE_ENABLED: {icon: SILK_ICONS.comments, deprecated: true},
THREADS_ENABLED: {icon: SILK_ICONS.comments, deprecated: true},
THREADS_ENABLED_TESTING: {
icon: SILK_ICONS.comments,
name: "Threads Enabled (Testing)",
deprecated: true,
},
THREAD_DEFAULT_AUTO_ARCHIVE_DURATION: {
icon: SILK_ICONS.comments,
name: "Threads: Default Auto Archive",
},
THREADS_ONLY_CHANNEL: {
icon: SILK_ICONS.comments,
name: "Forum Channels",
deprecated: true,
},
THREE_DAY_THREAD_ARCHIVE: {
icon: SILK_ICONS.comments,
name: "Threads: Three Day Archive",
deprecated: true,
},
TICKETED_EVENTS_ENABLED: {
icon: SILK_ICONS.calendar,
name: "Scheduled Events",
deprecated: true,
},
TICKETING_ENABLED: {
icon: SILK_ICONS.calendar,
name: "Scheduled Events",
deprecated: true,
},
VALORANT_L30: {icon: SILK_ICONS.controller, name: "Clans: Valorant L30"},
VANITY_URL: {icon: SILK_ICONS.link, name: "Vanity URL"},
VERIFIED: {icon: SILK_ICONS.tick},
VIP_REGIONS: {icon: SILK_ICONS.microphone, name: "VIP Voice Regions"},
VOICE_CHANNEL_EFFECTS: {icon: SILK_ICONS.wand, deprecated: true},
VOICE_IN_THREADS: {icon: SILK_ICONS.microphone},
WELCOME_SCREEN_ENABLED: {
icon: SILK_ICONS.application_view_tile,
name: "Onboarding: Welcome Screen",
},
};
const PRESENCE_ICONS = {
desktop: {
online: "<:i:1028887024670871552>",
idle: "<:i:1028887022938624033>",
dnd: "<:i:1028887021848121364>",
},
mobile: {
online: "<:i:1028887017637036043>",
idle: "<:i:1028887019226669116>",
dnd: "<:i:1028887020560449637>",
},
web: {
online: "<:i:1104972136730345552>",
idle: "<:i:1104972138735218729>",
dnd: "<:i:1104972140685570150>",
},
embedded: {
online: "<:i:1104972131265167411>",
idle: "<:i:1104972132687024189>",
dnd: "<:i:1104972134964543518>",
},
};
const OS_ICONS = {
darwin: "\u{1f34e}",
win32: "\u{1fa9f}",
linux: "\u{1f427}",
};
module.exports.Icons = {
silk: SILK_ICONS,
presence: PRESENCE_ICONS,
os: OS_ICONS,
online: "<:i:1152111668856361010>",
offline: "<:i:1152111682886316042>",
blank: "<:i:1250561747476152460>",
};
module.exports.ChannelTypeNames = {
0: "text",
2: "voice",
4: "category",
5: "announcement",
13: "stage",
14: "hub directory",
15: "forum",
16: "media",
};
const EMOJI_SETS = {
blobs: {
prefix: "https://cdn.jsdelivr.net/gh/googlefonts/noto-emoji@e456654119cc3a5f9bebb7bbd00512456f983d2d/svg/emoji_u",
sep: "_",
suffix: ".svg",
},
noto: {
prefix: "https://cdn.jsdelivr.net/gh/googlefonts/noto-emoji@master/svg/emoji_u",
sep: "_",
suffix: ".svg",
},
twemoji: {
prefix: "https://jdecked.github.io/twemoji/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;
module.exports.EmojiSets = EMOJI_SETS;
const EmojiData = require("../../data/emoji.json");
const EMOJI_NAMES = [];
for (const emoji of EmojiData) {
EMOJI_NAMES[emoji.char] = emoji.name.replace(/ /g, "_");
}
module.exports.EmojiNames = EMOJI_NAMES;
module.exports.RegExp = {
Emote: /<(?:\u200b|&)?(a)?:(\w+):(\d+)>/,
Pomelo: /^[a-z0-9._]{1,32}$/,
Snowflake: /([0-9]{17,21})/,
};
module.exports.ApplicationFlagNames = [
undefined,
"~~Embedded Released~~",
"Create Managed Emoji",
"Embedded In-App Purchases",
"Create Group DMs",
"~~RPC Private Beta~~",
"Uses AutoMod",
undefined,
"~~Allow Assets~~",
"~~Allow Spectate~~",
"~~Allow Join Requests~~",
"~~Has Used RPC~~",
"Presence Intent",
"Presence Intent (Unverified)",
"Guild Members Intent",
"Guild Members Intent (Unverified)",
"Suspicious Growth",
"Embedded",
"Message Content Intent",
"Message Content Intent (Unverified)",
"Embedded (First Party)",
undefined,
undefined,
"Supports Commands",
"Active",
undefined,
"iframe Modals",
"Social Layer Integration",
];

163
src/util/dconstants.js Normal file
View file

@ -0,0 +1,163 @@
const {Constants} = require("@projectdysnomia/dysnomia");
const Endpoints = require("@projectdysnomia/dysnomia/lib/rest/Endpoints.js");
module.exports = {...Constants};
module.exports.ActivityTypeNames = [
"Playing",
"Streaming",
"Listening to",
"Watching",
"Custom Status",
"Competing in",
"Hang Status",
];
const APIEndpoints = {...Endpoints};
delete APIEndpoints.BASE_URL;
delete APIEndpoints.CDN_URL;
delete APIEndpoints.CLIENT_URL;
delete APIEndpoints.APPLICATION_ASSET;
delete APIEndpoints.APPLICATION_ICON;
delete APIEndpoints.BANNER;
delete APIEndpoints.CHANNEL_ICON;
delete APIEndpoints.CUSTOM_EMOJI;
delete APIEndpoints.DEFAULT_USER_AVATAR;
delete APIEndpoints.GUILD_AVATAR;
delete APIEndpoints.GUILD_DISCOVERY_SPLASH;
delete APIEndpoints.GUILD_ICON;
delete APIEndpoints.GUILD_SCHEDULED_EVENT_COVER;
delete APIEndpoints.GUILD_SPLASH;
delete APIEndpoints.ROLE_ICON;
delete APIEndpoints.TEAM_ICON;
delete APIEndpoints.USER_AVATAR;
delete APIEndpoints.USER_AVATAR_DECORATION_PRESET;
delete APIEndpoints.MESSAGE_LINK;
APIEndpoints.APPLICATION_ASSETS = (applicationID) => `/oauth2/applications/${applicationID}/assets`; // prettier-ignore
APIEndpoints.APPLICATION_RPC = (applicationID) => `/applications/${applicationID}/rpc`; // prettier-ignore
APIEndpoints.ATTACHMENT_REFRESH = "/attachments/refresh-urls"; // prettier-ignore
APIEndpoints.CLAN = (guildID) => `/discovery/${guildID}/clan`; // prettier-ignore
APIEndpoints.DISCOVERY_SLUG = (guildID) => `/discovery/${guildID}`; // prettier-ignore
APIEndpoints.GUILD_MEMBER_VERIFICATION = (guildID) => `/guilds/${guildID}/member-verification`; // prettier-ignore
APIEndpoints.POMELO_UNAUTHED = "/unique-username/username-attempt-unauthed"; // prettier-ignore
APIEndpoints.STORE_PUBLISHED_LISTING = (skuID) => `/store/published-listings/skus/${skuID}`; // prettier-ignore
module.exports.APIEndpoints = APIEndpoints;
module.exports.API_URL = Endpoints.CLIENT_URL + Endpoints.BASE_URL;
module.exports.ApplicationFlags.EMBEDDED_RELEASED = 1 << 1; // prettier-ignore
module.exports.ApplicationFlags.MANAGED_EMOJI = 1 << 2; // prettier-ignore
module.exports.ApplicationFlags.EMBEDDED_IAP = 1 << 3; // prettier-ignore
module.exports.ApplicationFlags.GROUP_DM_CREATE = 1 << 4; // prettier-ignore
module.exports.ApplicationFlags.RPC_PRIVATE_BETA = 1 << 5; // prettier-ignore
module.exports.ApplicationFlags.ALLOW_ASSETS = 1 << 8; // prettier-ignore
module.exports.ApplicationFlags.ALLOW_ACTIVITY_ACTION_SPECTATE = 1 << 9; // prettier-ignore
module.exports.ApplicationFlags.ALLOW_ACTIVITY_ACTION_JOIN_REQUEST = 1 << 10; // prettier-ignore
module.exports.ApplicationFlags.RPC_HAS_CONNECTED = 1 << 11; // prettier-ignore
module.exports.ApplicationFlags.EMBEDDED_FIRST_PARTY = 1 << 20; // prettier-ignore
module.exports.ApplicationFlags.ACTIVE = 1 << 24; // prettier-ignore
module.exports.ApplicationFlags.IFRAME_MODAL = 1 << 26; // prettier-ignore
module.exports.ApplicationFlags.SOCIAL_LAYER_INTEGRATION = 1 << 27; // prettier-ignore
module.exports.ApplicationTypes = ["Normal", "Game", "Music", "Ticketed Event", "Creator Monetization"];
module.exports.BASE_URL = Endpoints.BASE_URL;
const CDNEndpoints = {};
CDNEndpoints.APP_ASSET = (applicationID, asset) => `${Endpoints.CDN_URL}/app-assets/${applicationID}/${asset}.png`; // prettier-ignore
CDNEndpoints.APP_ICON = (applicationID, icon, size = 4096) => `${Endpoints.CDN_URL}/app-icons/${applicationID}/${icon}.png?size=${size}`; // prettier-ignore
CDNEndpoints.AVATAR_DECORATION = (decoration, size = 4096) => `${Endpoints.CDN_URL}/avatar-decoration-presets/${decoration}.png?size=${size}&passthrough=true`; // prettier-ignore
CDNEndpoints.BANNER = (id, banner, size = 4096) => `${Endpoints.CDN_URL}/banners/${id}/${banner}.${banner.startsWith("a_") ? `gif?size=${size}&_=.gif` : `png?size=${size}`}`; // prettier-ignore
CDNEndpoints.CLAN_BADGE = (guildID, badge, size = 4096) => `${Endpoints.CDN_URL}/clan-badges/${guildID}/${badge}.png?size=${size}`; // prettier-ignore
CDNEndpoints.CLAN_BANNER = (guildID, banner, size = 4096) => `${Endpoints.CDN_URL}/clan-banners/${guildID}/${banner}.png?size=${size}`; // prettier-ignore
CDNEndpoints.DEFAULT_AVATAR = (index, size = 4096) => `${Endpoints.CDN_URL}/embed/avatars/${index}.png?size=${size}`; // prettier-ignore
CDNEndpoints.DISCOVERY_SPLASH = (guildID, splash, size = 4096) => `${Endpoints.CDN_URL}/discovery-splashes/${guildID}/${splash}.png?size=${size}`; // prettier-ignore
CDNEndpoints.EMOJI = (emojiID, animated = false, webp = false) => `${Endpoints.CDN_URL}/emojis/${emojiID}.${webp ? "webp" : animated ? "gif?_=.gif" : "png"}`; // prettier-ignore
CDNEndpoints.GDM_ICON = (channelID, icon, size = 4096) => `${Endpoints.CDN_URL}/channel-icons/${channelID}/${icon}.${icon.startsWith("a_") ? `gif?size=${size}&_=.gif` : `png?size=${size}`}`; // prettier-ignore
CDNEndpoints.GUILD_ICON = (guildID, icon, size = 4096) => `${Endpoints.CDN_URL}/icons/${guildID}/${icon}.${icon.startsWith("a_") ? `gif?size=${size}&_=.gif` : `png?size=${size}`}`; // prettier-ignore
CDNEndpoints.GUILD_MEMBER_AVATAR = (guildID, userID, avatar, size = 4096) => `${Endpoints.CDN_URL}/guilds/${guildID}/users/${userID}/avatars/${avatar}.${avatar.startsWith("a_") ? `gif?size=${size}&_=.gif` : `png?size=${size}`}`; // prettier-ignore
CDNEndpoints.GUILD_MEMBER_BANNER = (guildID, userID, banner, size = 4096) => `${Endpoints.CDN_URL}/guilds/${guildID}/users/${userID}/banners/${banner}.${banner.startsWith("a_") ? `gif?size=${size}&_=.gif` : `png?size=${size}`}`; // prettier-ignore
CDNEndpoints.GUILD_SPLASH = (guildID, splash, size = 4096) => `${Endpoints.CDN_URL}/splashes/${guildID}/${splash}.png?size=${size}`; // prettier-ignore
CDNEndpoints.USER_AVATAR = (userID, avatar, size = 4096) => `${Endpoints.CDN_URL}/avatars/${userID}/${avatar}.${avatar.startsWith("a_") ? `gif?size=${size}&_=.gif` : `png?size=${size}`}`; // prettier-ignore
module.exports.CDNEndpoints = CDNEndpoints;
module.exports.CDN_URL = Endpoints.CDN_URL;
module.exports.CLIENT_URL = Endpoints.CLIENT_URL;
module.exports.ClanPlaystyle = ["None", "Very Casual", "Casual", "Competitive", "Creative", "Very Competitive"];
module.exports.DEFAULT_GROUP_DM_AVATARS = [
"/assets/ee9275c5a437f7dc7f9430ba95f12ebd.png",
"/assets/9baf45aac2a0ec2e2dab288333acb9d9.png",
"/assets/7ba11ffb1900fa2b088cb31324242047.png",
"/assets/f90fca70610c4898bc57b58bce92f587.png",
"/assets/e2779af34b8d9126b77420e5f09213ce.png",
"/assets/c6851bd0b03f1cca5a8c1e720ea6ea17.png",
"/assets/f7e38ac976a2a696161c923502a8345b.png",
"/assets/3cb840d03313467838d658bbec801fcd.png",
].map((i) => Endpoints.CLIENT_URL + i);
module.exports.ExplicitContentFilterStrings = ["Disabled", "Members without roles", "All members"];
module.exports.Games = require("../../data/games.json");
module.exports.HANG_STATUS_ICONS = Object.fromEntries(
Object.entries({
chilling: "/assets/d5df7edf0b2f38954140.svg",
gaming: "/assets/0c7aa94c8471f12c1583.svg",
focusing: "/assets/5ba60eea3fdfa7b63f2e.svg",
brb: "/assets/194dda5f6443bc84d703.svg",
eating: "/assets/5f41131fa82e3a74fbf7.svg",
"in-transit": "/assets/0cb37cec5b33d664373a.svg",
watching: "/assets/b7832fa2d0b75e26e279.svg",
}).map((x) => ((x[1] = Endpoints.CLIENT_URL + x[1]), x))
);
module.exports.HangStatusStrings = {
chilling: "Chilling",
gaming: "GAMING",
focusing: "In the zone",
brb: "Gonna BRB",
eating: "Grubbin",
"in-transit": "Wandering IRL",
watching: "Watchin' stuff",
};
module.exports.UPLOAD_LIMIT = 26214400;
module.exports.UPLOAD_LIMIT_TIER_2 = 52428800;
module.exports.UPLOAD_LIMIT_TIER_3 = 104857600;
module.exports.UserFlags.MFA_SMS = 1 << 4; // prettier-ignore
module.exports.UserFlags.PREMIUM_PROMO_DISMISSED = 1 << 5; // prettier-ignore
module.exports.UserFlags.INTERNAL_APPLICATION = 1 << 11; // prettier-ignore
module.exports.UserFlags.HAS_UNREAD_URGENT_MESSAGES = 1 << 13; // prettier-ignore
module.exports.UserFlags.UNDERAGE_DELETED = 1 << 15; // prettier-ignore
module.exports.UserFlags.DISABLE_PREMIUM = 1 << 21; // prettier-ignore
module.exports.UserFlags.PROVISIONAL_ACCOUNT = 1 << 23; // prettier-ignore
module.exports.UserFlags.QUARANTINED = Number(1n << 44n); // prettier-ignore
module.exports.UserFlags.COLLABORATOR = Number(1n << 50n); // prettier-ignore
module.exports.UserFlags.RESTRICTED_COLLABORATOR = Number(1n << 51n); // prettier-ignore
// admin panel leak aug 2022
module.exports.UserFlags.HIGH_GLOBAL_RATE_LIMIT = Number(1n << 33n); // prettier-ignore
module.exports.UserFlags.DELETED = Number(1n << 34n); // prettier-ignore
module.exports.UserFlags.DISABLED_SUSPICIOUS_ACTIVITY = Number(1n << 35n); // prettier-ignore
module.exports.UserFlags.SELF_DELETED = Number(1n << 36n); // prettier-ignore
module.exports.UserFlags.PREMIUM_DISCRIMINATOR = Number(1n << 37n); // prettier-ignore
module.exports.UserFlags.USED_DESKTOP_CLIENT = Number(1n << 38n); // prettier-ignore
module.exports.UserFlags.USED_WEB_CLIENT = Number(1n << 39n); // prettier-ignore
module.exports.UserFlags.USED_MOBILE_CLIENT = Number(1n << 40n); // prettier-ignore
module.exports.UserFlags.VERIFIED_EMAIL = Number(1n << 43n); // prettier-ignore
module.exports.VerificationLevelStrings = [
"None",
"Low",
"Medium",
"(╯°□°)╯︵ ┻━┻ (High)",
"┻━┻ ミヽ(ಠ益ಠ)ノ彡┻━┻ (Very High/Phone)",
];

103
src/util/html.js Normal file
View file

@ -0,0 +1,103 @@
// https://stackoverflow.com/a/39243641
const htmlEntities = {
nbsp: " ",
cent: "¢",
pound: "£",
yen: "¥",
euro: "€",
copy: "©",
reg: "®",
lt: "<",
gt: ">",
quot: '"',
amp: "&",
apos: "'",
};
function parseHtmlEntities(str) {
return str.replace(/&([^;]+);/g, function (entity, entityCode) {
let match;
if (entityCode in htmlEntities) {
return htmlEntities[entityCode];
} else if ((match = entityCode.match(/^#x([\da-fA-F]+)$/))) {
return String.fromCharCode(parseInt(match[1], 16));
} else if ((match = entityCode.match(/^#(\d+)$/))) {
return String.fromCharCode(~~match[1]);
} else {
return entity;
}
});
}
function htmlToMarkdown(str, images = true, embed = true) {
str = str.replaceAll("\\", "\\\\");
str = str.replaceAll("#", "\\#");
str = str.replace(/<style(\s+[^>]+)?>(.|\n)*?<\/style>/gi, "");
str = str.replace(
/<a (\s+[^>]+)?href="([^"]+?)"(\s+[^>]+)?>(.+?)<\/a>/gi,
(_, __, url, ___, text) => {
url = url.replace(/^\/\//, "https://").replace("\\#", "#");
return url == text
? url
: `[${text}](${embed ? "" : "&lt;"}${url}${embed ? "" : "&gt;"})`;
}
);
if (images)
str = str.replace(
/<img (\s+[^>]+)?src="([^"]+?)"(\s+[^>]+)?(alt|title)="([^"]+?)"(\s+[^>]+)?\/>/gi,
`[$5](${embed ? "" : "&lt;"}$2${embed ? "" : "&gt;"})`
);
str = str.replace(/<\/?\s*br\s*\/?>/gi, "\n");
str = str.replace(
/<blockquote[^>]*?>((.|\n)*?)<\/blockquote>/gi,
(_, quote) => "&gt; " + quote.split("\n").join("\n&gt; ")
);
str = str.replace(/<\/?p>/gi, "\n");
str = str.replace(
/<dd>((.|\n)*?)<\/dd>/gi,
(_, inner) => "\u3000\u3000" + inner.split("\n").join("\n\u3000\u3000")
);
str = str.replace(/<ol(\s+[^>]+)?>((.|\n)*?)<\/ol>/gi, (_, __, inner) => {
let index = 0;
return inner
.replace(/<li>/gi, () => {
index++;
return `${index}. `;
})
.replace(/<\/li>/gi, "\n")
.replaceAll("\n\n", "\n");
});
str = str.replace(/<ul(\s+[^>]+)?>((.|\n)*?)<\/ul>/gi, (_, __, inner) => {
let index = 0;
return inner
.replace(/<li>/gi, () => {
index++;
return `${index}. `;
})
.replace(/<\/li>/gi, "\n")
.replaceAll("\n\n", "\n");
});
str = str.replace(/<\/?code(\s+[^>]+)?>/gi, "`");
str = str.replace(/<\/?em(\s+[^>]+)?>/gi, "_");
str = str.replace(/<\/?i(\s+[^>]+)?>/gi, "_");
str = str.replace(/<\/?b(\s+[^>]+)?>/gi, "**");
str = str.replace(/<\/?u(\s+[^>]+)?>/gi, "__");
str = str.replace(/<\/?s(\s+[^>]+)?>/gi, "~~");
str = str.replace(/<h1(\s+[^>]+)?>/gi, "# ");
str = str.replace(/<h2(\s+[^>]+)?>/gi, "## ");
str = str.replace(/<h3(\s+[^>]+)?>/gi, "### ");
str = str.replace(/<\/?h4(\s+[^>]+)?>/gi, "**");
str = str.replace(
/<(math|noscript)(\s+[^>]+)?>((.|\n)*?)<\/(math|noscript)>/gi,
""
);
str = str.replace(/<[^>]+?>/gi, "");
str = parseHtmlEntities(str);
// whyyyyyyyyyyyy
str = str.replace(/\[https?:\/\/.+?\]\(<?(https?:\/\/.+?)>?\)/gi, "$1");
return str;
}
module.exports = {parseHtmlEntities, htmlToMarkdown};

178
src/util/image.js Normal file
View file

@ -0,0 +1,178 @@
async function findSuitableImage(msg) {
const out = {};
const attachments = [...msg.attachments.values()];
const attachment = attachments[0];
if (attachment) {
const url = attachment.url;
const parsed = new URL(url);
if (/(jpe?g|png|gif|webp)$/.test(parsed.pathname)) {
out.url = url;
} else if (/(mp4|webm|mov)$/.test(parsed.pathname)) {
out.video = true;
out.attachment = true;
out.url = "attachment://thumb.jpg";
let thumbUrl = attachment.proxyURL;
if (thumbUrl.includes("media.discordapp.net") && thumbUrl.includes("?")) {
if (thumbUrl.endsWith("&")) {
thumbUrl = thumbUrl += "format=jpeg";
} else {
thumbUrl = thumbUrl += "&format=jpeg";
}
} else {
thumbUrl = thumbUrl += "?format=jpeg";
}
out.file = await fetch(thumbUrl)
.then((res) => res.arrayBuffer())
.then((buf) => Buffer.from(buf));
}
} else {
for (const embed of msg.embeds) {
if (!embed.url) continue;
const parsed = new URL(embed.url);
if (embed.url && /(jpe?g|png|gif|webp)$/.test(parsed.pathname)) {
out.url = embed.url;
} else if (embed.image) {
out.url = embed.image.url;
break;
} else if (embed.video) {
out.video = true;
out.url = "attachment://thumb.jpg";
let thumbUrl =
embed.video.proxyURL ??
embed.video.url.replace("cdn.discordapp.com", "media.discordapp.net");
if (
thumbUrl.includes("media.discordapp.net") &&
thumbUrl.includes("?")
) {
if (thumbUrl.endsWith("&")) {
thumbUrl = thumbUrl += "format=jpeg";
} else {
thumbUrl = thumbUrl += "&format=jpeg";
}
} else {
thumbUrl = thumbUrl += "?format=jpeg";
}
if (embed.thumbnail?.url) thumbUrl = embed.thumbnail.url;
let valid = await fetch(thumbUrl, {method: "HEAD"}).then(
(res) => res.ok
);
if (!valid && thumbUrl.includes("media.discordapp.net")) {
thumbUrl = await hf.bot.requestHandler
.request("POST", "/attachments/refresh-urls", true, {
attachment_urls: [thumbUrl],
})
.then((res) => res.refreshed_urls[0].refreshed);
valid = true;
}
if (valid)
out.file = await fetch(thumbUrl)
.then((res) => res.arrayBuffer())
.then((buf) => Buffer.from(buf));
break;
}
}
}
return out;
}
async function isGif(url) {
const type = await fetch(url, {method: "HEAD"}).then((res) =>
res.headers.get("Content-Type")
);
return type == "image/gif";
}
async function findLastImage(msg, gif = false) {
const messages = await msg.channel.getMessages(20);
let img;
for (const message of messages) {
if (message.attachments.size > 0) {
img = [...msg.attachments.values()][0].url;
if (gif && (await isGif(img))) {
break;
} else {
break;
}
} else if (message.embeds.length > 0) {
img = message.embeds[0]?.thumbnail?.url || message.embeds[0]?.image?.url;
if (img) {
if (gif && (await isGif(img))) {
break;
} else {
break;
}
}
}
}
return await new Promise((resolve, reject) => {
if (!img) {
reject("Image not found in last 20 messages.");
} else {
resolve(img);
}
});
}
const urlRegex = /((https?):\/)?\/?([^:/\s]+)((\/\w+)*\/)([\w\-.]+)/;
async function getImage(msg, str) {
const refMsg = msg.referencedMessage;
if (refMsg) {
const attachments = [...refMsg.attachments.values()];
if (attachments[0]?.url) {
return attachments[0].url;
} else if (/<a?:[a-zA-Z0-9_]+:([0-9]+)>/.test(refMsg.content)) {
const id = refMsg.content.match(/<a?:[a-zA-Z0-9_]+:([0-9]+)>/)[1];
return `https://cdn.discordapp.com/emojis/${id}.png?v=1`;
} else if (/<@!?([0-9]+)>/.test(refMsg.content)) {
const id = refMsg.content.match(/<@!?([0-9]+)>/)[1];
const user = await hf.bot.requestHandler.request(
"GET",
"/users/" + id,
true
);
if (user)
return `https://cdn.discordapp.com/avatars/${id}/${user.avatar}.png?size=1024`;
}
}
const img = await findLastImage(msg, false);
if (!str) {
if (img) return img;
}
const attachments = [...msg.attachments.values()];
if (attachments[0]?.url) {
return attachments[0]?.url;
} else if (urlRegex.test(str)) {
return str;
} else if (/<a?:[a-zA-Z0-9_]+:([0-9]+)>/.test(str)) {
const id = str.match(/<a?:[a-zA-Z0-9_]+:([0-9]+)>/)[1];
return `https://cdn.discordapp.com/emojis/${id}.png?v=1`;
} else if (/<@!?([0-9]+)>/.test(str)) {
const id = str.match(/<@!?([0-9]+)>/)[1];
const user = await hf.bot.requestHandler.request(
"GET",
"/users/" + id,
true
);
if (user)
return `https://cdn.discordapp.com/avatars/${id}/${user.avatar}.png?size=1024`;
} else if (img) {
return img;
}
return null;
}
module.exports = {findSuitableImage, isGif, findLastImage, getImage};

172
src/util/misc.js Normal file
View file

@ -0,0 +1,172 @@
const murmurhash = require("murmurhash").v3;
const {tinycolor} = require("@ctrl/tinycolor");
const {APIEndpoints, CDNEndpoints, UPLOAD_LIMIT, UPLOAD_LIMIT_TIER_2, UPLOAD_LIMIT_TIER_3} = require("./dconstants.js");
const {GuildFeaturesFormatted, Icons} = require("./constants.js");
function pastelize(id) {
const hue = murmurhash(id) % 360;
const hex = tinycolor(`hsl(${hue},75%,60%)`).toHex();
return parseInt(hex, 16);
}
function formatUsername(user) {
return user.discriminator && user.discriminator != "0"
? `${user.username}#${user.discriminator}`
: `@${user.username}`;
}
function getTopColor(msg, id, fallback = 0x7289da) {
if (!msg.guildID) return fallback;
const guild = msg.channel?.guild ?? hf.bot.guilds.get(msg.guildID);
if (!guild) return fallback;
const roles = guild.members
.get(id)
.roles.map((role) => guild.roles.get(role))
.filter((role) => role.color);
roles.sort((a, b) => b.position - a.position);
return roles[0]?.color || fallback;
}
function safeString(string, newLines = true) {
string = string ? string.toString() : "";
string = string.replace(/`/g, "'");
string = string.replace(/<@/g, "<@\u200b");
string = string.replace(/<#/g, "<#\u200b");
string = string.replace(/<&/g, "<&\u200b");
if (newLines) string = string.replace(/\n/g, " ");
return string;
}
async function hastebin(body) {
const res = await fetch(`${hf.config.haste_provider}/documents`, {
method: "POST",
body,
}).then((r) => r.json());
return `<${hf.config.haste_provider}/${res.key}>`;
}
function getUploadLimit(guild) {
if (!guild) return UPLOAD_LIMIT;
if (guild.premiumTier == 2) return UPLOAD_LIMIT_TIER_2;
if (guild.premiumTier == 3) return UPLOAD_LIMIT_TIER_3;
return UPLOAD_LIMIT;
}
async function getGuild(id) {
if (hf.bot.guilds.has(id)) {
return {source: "local", data: hf.bot.guilds.get(id)};
}
try {
const preview = await hf.bot.requestHandler.request("GET", APIEndpoints.GUILD_PREVIEW(id), true);
if (preview) return {source: "preview", data: preview};
} catch {
try {
const widget = await hf.bot.requestHandler.request("GET", APIEndpoints.GUILD_WIDGET(id), false);
if (widget) return {source: "widget", data: widget};
} catch {
try {
const discovery = await hf.bot.requestHandler.request("GET", APIEndpoints.DISCOVERY_SLUG(id), false);
if (discovery) return {source: "discovery", data: discovery};
} catch {
try {
const verification = await hf.bot.requestHandler.request(
"GET",
`${APIEndpoints.GUILD_MEMBER_VERIFICATION(id)}?with_guild=true`,
true
);
if (verification?.guild) return {source: "verification", data: verification.guild};
} catch {
return null;
}
}
}
}
return null;
}
function enumKeyToName(key) {
return key
.split("_")
.map((x) => x[0] + x.substring(1).toLowerCase())
.join(" ");
}
function formatGuildFeatures(features) {
return features
.sort((a, b) => {
const feature_a = GuildFeaturesFormatted[a];
const feature_b = GuildFeaturesFormatted[b];
return (feature_a?.name ?? enumKeyToName(a)).localeCompare(feature_b?.name ?? enumKeyToName(b));
})
.map(
(feature) =>
`${GuildFeaturesFormatted[feature]?.icon ?? Icons.blank} ${
GuildFeaturesFormatted[feature]?.deprecated ? "~~" : ""
}${
GuildFeaturesFormatted[feature]?.name ??
feature
.split("_")
.map((x) => x[0] + x.substring(1).toLowerCase())
.join(" ")
}${GuildFeaturesFormatted[feature]?.deprecated ? "~~" : ""}`
);
}
function getDefaultAvatar(id, discriminator = null, size = 4096) {
let index = 0;
if (discriminator && discriminator > 0) {
index = discriminator % 5;
} else if (id != null) {
index = (Number(id) << 22) % 6;
}
return CDNEndpoints.DEFAULT_AVATAR(index, size);
}
function flagsFromInt(int, flags, withRaw = true) {
const bits = int.toString(2);
const splitBits = bits.split("").reverse();
const reassignedBits = {};
for (const shift in splitBits) {
reassignedBits[shift] = splitBits[shift];
}
const assignedFlags = Object.keys(reassignedBits).filter((bit) => reassignedBits[bit] == 1);
const out = [];
for (const flag of assignedFlags) {
out.push(
`${flags[flag] ?? "<Undocumented Flag>"}${
withRaw == true || flags[flag] == null ? ` (1 << ${flag}, ${1n << BigInt(flag)})` : ""
}`
);
}
return out.join("\n");
}
module.exports = {
pastelize,
formatUsername,
getTopColor,
safeString,
hastebin,
getUploadLimit,
getGuild,
enumKeyToName,
formatGuildFeatures,
getDefaultAvatar,
flagsFromInt,
};

173
src/util/selection.js Normal file
View file

@ -0,0 +1,173 @@
const {Collection} = require("@projectdysnomia/dysnomia");
const logger = require("../lib/logger.js");
const {formatUsername} = require("./misc.js");
const {APIEndpoints} = require("./dconstants.js");
const {
RegExp: {Snowflake: REGEX_SNOWFLAKE},
} = require("./constants.js");
if (!hf.selectionMessages) hf.selectionMessages = new Collection();
async function selectionMessage(msg, heading, options, timeout = 30000, maxItems = 1) {
const data = {
content: heading,
allowedMentions: {
repliedUser: false,
},
messageReference: {
messageID: msg.id,
},
components: [
{
type: 1,
components: [
{
type: 3,
custom_id: msg.id,
options: [],
max_values: maxItems,
},
],
},
{
type: 1,
components: [
{
type: 2,
style: 4,
label: "Cancel",
custom_id: "cancel",
},
],
},
],
};
options.slice(0, 25).forEach((value) => {
data.components[0].components[0].options.push({
label: value.display,
value: value.key,
description: value.description,
});
});
if (options.length > 25) {
data.content += `\n\nDisplaying 25/${options.length} results`;
}
const displayMessage = await msg.channel
.createMessage(data)
.catch((err) => logger.error("selectionMessage", "Failed to create selection message: " + err));
return await new Promise((resolve, reject) => {
function listener(interaction) {
const user = interaction.member.user || interaction.user;
if (
user.id == msg.author.id &&
interaction.channel.id == msg.channel.id &&
interaction.message.components[0].components[0].custom_id == msg.id
) {
if (interaction.data.custom_id == "cancel") {
hf.events.remove("interactionCreate", `selection.${msg.id}`);
clearTimeout(hf.selectionMessages.get(msg.id));
hf.selectionMessages.delete(msg.id);
interaction.deferUpdate();
displayMessage.delete();
reject("Canceled");
} else {
hf.events.remove("interactionCreate", `selection.${msg.id}`);
clearTimeout(hf.selectionMessages.get(msg.id));
hf.selectionMessages.delete(msg.id);
interaction.deferUpdate();
displayMessage.delete();
let result;
if (maxItems > 1) {
result = options.filter((opt) => interaction.data.values.includes(opt.key)).map((opt) => opt.key);
} else {
result = options.filter((opt) => opt.key == interaction.data.values[0])[0].value;
}
resolve(result);
}
}
}
hf.events.add("interactionCreate", `selection.${msg.id}`, listener);
hf.selectionMessages.set(
msg.id,
setTimeout(() => {
hf.events.remove("interactionCreate", `selection.${msg.id}`);
hf.selectionMessages.delete(msg.id);
reject("Request timed out");
}, timeout)
);
});
}
async function lookupUser(msg, str, filter) {
if (REGEX_SNOWFLAKE.test(str)) {
return await hf.bot.requestHandler.request("GET", APIEndpoints.USER(str.match(REGEX_SNOWFLAKE)[1]), true);
}
let users;
if (filter) {
users = hf.bot.users.filter(filter).values();
} else if (msg.guildID) {
const guild = msg.channel.guild || hf.bot.guilds.get(msg.guildID);
users = guild.members.values();
} else {
users = hf.bot.users.values();
}
if (/(.+?)#([0-9]{4})/.test(str)) {
const [_, name, discrim] = str.match(/(.+?)#([0-9]{4})/);
for (const user of users) {
if (user.username.toLowerCase() == name.toLowerCase() && user.discriminator == discrim) {
return user;
}
}
}
const selection = [];
for (const user of users) {
if (user.username.toLowerCase() == str.toLowerCase() || (user.nickname && user.nickname == str.toLowerCase())) {
return user;
} else if (
user.username.toLowerCase().indexOf(str.toLowerCase()) > -1 ||
(user.nick && user.nick.toLowerCase().indexOf(str.toLowerCase()) > -1) ||
(user.globalName && user.globalName.toLowerCase().indexOf(str.toLowerCase()) > -1)
) {
selection.push({
value: user,
key: user.id,
display: `${formatUsername(user)}${
user.nick ? ` (${user.nick})` : user.globalName ? ` (${user.globalName})` : ""
}`,
});
}
}
selection.sort((a, b) => a.display - b.display);
if (selection.length == 0) {
return "No results";
} else if (selection.length == 1) {
return selection[0].value;
} else {
selection.splice(20);
try {
return await selectionMessage(msg, "Multiple users found, please pick from this list:", selection);
} catch (out) {
return out;
}
}
}
module.exports = {selectionMessage, lookupUser};

45
src/util/starboard.js Normal file
View file

@ -0,0 +1,45 @@
const {pastelize} = require("./misc.js");
const {findSuitableImage} = require("./image.js");
async function createBoardMessage(
msg,
count,
threadId = null,
fetchAttachment = true
) {
const name = msg.member?.nick ?? msg.author.globalName ?? msg.author.username;
const embed = {
title: `${count} \u2b50 - ${msg.jumpLink}`,
color: pastelize(name),
description: msg.content,
timestamp: new Date(msg.timestamp).toISOString(),
};
let image;
if (fetchAttachment) {
image = await findSuitableImage(msg);
if (image.url) {
if (image.video) {
embed.description += `\n(contains video ${
image.attachment ? "attachment" : "embed"
})`;
}
embed.image = {
url: image.url,
};
}
}
return {
avatarURL: msg.member?.avatarURL ?? msg.author.avatarURL,
username: name,
threadID: threadId,
embeds: [embed],
attachments: image?.file
? [{file: image.file, filename: "thumb.jpg"}]
: null,
wait: true,
};
}
module.exports = {createBoardMessage};

View file

@ -1,4 +1,4 @@
module.exports = class {
module.exports = class Table {
constructor(titles) {
this._rows = [titles];
this._widths = [];

44
src/util/time.js Normal file
View file

@ -0,0 +1,44 @@
function formatTime(number) {
let seconds = parseInt(number) / 1000;
const days = Math.floor(seconds / 86400);
seconds = seconds % 86400;
const hours = Math.floor(seconds / 3600);
seconds = seconds % 3600;
const minutes = Math.floor(seconds / 60);
seconds = Math.floor(seconds % 60);
return (
(days !== 0 ? `${days.toString().padStart(2, "0")}:` : "") +
(hours !== 0 ? `${hours.toString().padStart(2, "0")}:` : "") +
`${minutes.toString().padStart(2, "0")}:${seconds
.toString()
.padStart(2, "0")}`
);
}
function readableTime(number) {
const seconds = number / 1000;
const days = seconds / 60 / 60 / 24;
const years = days / 365.25;
if (years >= 1) {
return `${years.toFixed(2)} years`;
} else {
return `${days.toFixed(2)} days`;
}
}
const TWITTER_EPOCH = 1288834974657;
const DISCORD_EPOCH = 1420070400000;
function snowflakeToTimestamp(snowflake, twitter = false) {
return (
Math.floor(Number(snowflake) / Math.pow(2, 22)) +
(twitter == true ? TWITTER_EPOCH : DISCORD_EPOCH)
);
}
module.exports = {
formatTime,
readableTime,
snowflakeToTimestamp,
};

View file

@ -20,10 +20,10 @@ async function getNamesFromString(string) {
const codes = [...string].map((char) => char.codePointAt().toString(16));
return codes.map((code) => [
code.padStart(4, "0"),
global.____unicode_data[code.padStart(4, "0")],
]);
return codes.map((code) => {
const padded = code.padStart(4, "0");
return [padded, global.____unicode_data[padded]];
});
}
module.exports = {