utility: add appinfo
This commit is contained in:
parent
cabe10c230
commit
82106f0dd2
2 changed files with 410 additions and 39 deletions
|
@ -401,6 +401,14 @@ function getUploadLimit(guild) {
|
|||
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
|
||||
? TWITTER_EPOCH
|
||||
: DISCORD_EPOCH;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
pastelize,
|
||||
formatUsername,
|
||||
|
@ -416,4 +424,5 @@ module.exports = {
|
|||
lookupUser,
|
||||
parseHtmlEntities,
|
||||
getUploadLimit,
|
||||
snowflakeToTimestamp,
|
||||
};
|
||||
|
|
|
@ -11,6 +11,7 @@ const {
|
|||
lookupUser,
|
||||
formatUsername,
|
||||
safeString,
|
||||
snowflakeToTimestamp,
|
||||
} = require("../lib/utils.js");
|
||||
const {getNamesFromString} = require("../lib/unicode.js");
|
||||
|
||||
|
@ -33,6 +34,8 @@ const SPLASH_BASE = CDN + "splashes/";
|
|||
const BANNER_BASE = CDN + "banners/";
|
||||
const EMOTE_BASE = CDN + "emojis/";
|
||||
const CHANNEL_ICON_BASE = CDN + "channel-icons/";
|
||||
const APP_ICON_BASE = CDN + "app-icons/";
|
||||
const APP_ASSET_BASE = CDN + "app-assets/";
|
||||
|
||||
const DEFAULT_GROUP_DM_AVATARS = [
|
||||
"/assets/ee9275c5a437f7dc7f9430ba95f12ebd.png",
|
||||
|
@ -46,6 +49,8 @@ const DEFAULT_GROUP_DM_AVATARS = [
|
|||
];
|
||||
|
||||
const CUSTOM_EMOTE_REGEX = /<(?:\u200b|&)?(a)?:(\w+):(\d+)>/;
|
||||
const POMELO_REGEX = /^[a-z0-9._]{1,32}$/;
|
||||
const SNOWFLAKE_REGEX = /[0-9]{17,21}/;
|
||||
|
||||
const NOWPLAYING_BAR_LENGTH = 30;
|
||||
|
||||
|
@ -71,6 +76,11 @@ const PRESENCE_ICONS = {
|
|||
dnd: "<:embedded_dnd:1104972134964543518>",
|
||||
},
|
||||
};
|
||||
const OS_ICONS = {
|
||||
darwin: "\u{1f34e}",
|
||||
win32: "\u{1fa9f}",
|
||||
linux: "\u{1f427}",
|
||||
};
|
||||
|
||||
const PRESENCE_TYPES = [
|
||||
"Playing",
|
||||
|
@ -136,6 +146,43 @@ const USER_FLAGS = [
|
|||
"RESTRICTED_COLLABORATOR",
|
||||
];
|
||||
|
||||
const APPLICATION_FLAGS = [
|
||||
"Embedded Release (deprecated)",
|
||||
"Create Managed Emoji",
|
||||
"Embedded In-App Purchaces",
|
||||
"Create Group DMs",
|
||||
"RPC Private Beta",
|
||||
"Uses AutoMod",
|
||||
undefined,
|
||||
"Allow Assets (deprecated)",
|
||||
"Allow Spectate (deprecated)",
|
||||
"Allow Join Requests (deprecated)",
|
||||
"Has Used RPC (deprecated)",
|
||||
"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",
|
||||
];
|
||||
|
||||
const APPLICATION_TYPES = [
|
||||
undefined,
|
||||
"Game",
|
||||
"Music",
|
||||
"Ticketed Event",
|
||||
"Creator Monetization",
|
||||
];
|
||||
|
||||
// https://discord-userdoccers.vercel.app/resources/guild#guild-features
|
||||
const GUILD_FEATURES = {
|
||||
ACTIVITIES_ALPHA: {icon: "\u{1f680}"},
|
||||
|
@ -316,6 +363,74 @@ EMOJI_SETS.fb = EMOJI_SETS.facebook;
|
|||
|
||||
// }}}
|
||||
|
||||
// {{{ helpers
|
||||
|
||||
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
|
||||
? ` (1 << ${flag}, ${1n << BigInt(flag)})`
|
||||
: ""
|
||||
);
|
||||
}
|
||||
|
||||
return out.join("\n");
|
||||
}
|
||||
|
||||
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",
|
||||
`/guilds/${id}/preview`,
|
||||
true
|
||||
);
|
||||
if (preview) return {source: "preview", data: preview};
|
||||
} catch {
|
||||
try {
|
||||
const discovery = await hf.bot.requestHandler.request(
|
||||
"GET",
|
||||
`/discovery/${id}`,
|
||||
false
|
||||
);
|
||||
if (discovery) return {source: "discovery", data: discovery};
|
||||
} catch {
|
||||
try {
|
||||
const widget = await hf.bot.requestHandler.request(
|
||||
"GET",
|
||||
`/guilds/${id}/widget.json`,
|
||||
false
|
||||
);
|
||||
if (widget) return {source: "widget", data: widget};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
// {{{ commands
|
||||
|
||||
const avatar = new Command("avatar");
|
||||
|
@ -796,16 +911,10 @@ snowflake.category = CATEGORY;
|
|||
snowflake.helpText = "Converts a snowflake ID into readable time.";
|
||||
snowflake.usage = "<--twitter> [snowflake]";
|
||||
snowflake.callback = function (msg, line, [snowflake], {twitter}) {
|
||||
const num = parseInt(snowflake);
|
||||
const num = Number(snowflake);
|
||||
if (!isNaN(num)) {
|
||||
let binary = num.toString(2);
|
||||
binary = "0".repeat(64 - binary.length) + binary;
|
||||
const timestamp =
|
||||
parseInt(binary.substr(0, 42), 2) +
|
||||
(twitter ? 1288834974657 : 1420070400000);
|
||||
|
||||
return `The timestamp for \`${snowflake}\` is <t:${Math.floor(
|
||||
timestamp / 1000
|
||||
snowflakeToTimestamp(num, twitter) / 1000
|
||||
)}:F>`;
|
||||
} else {
|
||||
return "Argument provided is not a number.";
|
||||
|
@ -813,31 +922,6 @@ snowflake.callback = function (msg, line, [snowflake], {twitter}) {
|
|||
};
|
||||
hf.registerCommand(snowflake);
|
||||
|
||||
function flagFromInt(int) {
|
||||
const bits = int.toString(2);
|
||||
const splitBits = bits.split("").reverse();
|
||||
|
||||
const reassignedBits = {};
|
||||
|
||||
for (const shift in splitBits) {
|
||||
reassignedBits[shift] = splitBits[shift];
|
||||
}
|
||||
|
||||
const flags = Object.keys(reassignedBits).filter(
|
||||
(bit) => reassignedBits[bit] == 1
|
||||
);
|
||||
|
||||
let out = "";
|
||||
|
||||
for (const flag of flags) {
|
||||
out +=
|
||||
(USER_FLAGS[flag] || "<Undocumented Flag>") +
|
||||
` (1 << ${flag}, ${1n << BigInt(flag)})\n`;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
const flagdump = new Command("flagdump");
|
||||
flagdump.category = CATEGORY;
|
||||
flagdump.helpText = "Dumps Discord user flags.";
|
||||
|
@ -850,7 +934,7 @@ flagdump.callback = async function (msg, line, [numOrMention], {id, list}) {
|
|||
if (USER_FLAGS[index] == undefined) continue;
|
||||
allFlags += 1n << BigInt(index);
|
||||
}
|
||||
return `All flags:\n\`\`\`${flagFromInt(allFlags)}\`\`\``;
|
||||
return `All flags:\n\`\`\`${flagsFromInt(allFlags, USER_FLAGS)}\`\`\``;
|
||||
} else if (/<@!?(\d+)>/.test(numOrMention) || !isNaN(id)) {
|
||||
const targetId = id || numOrMention.match(/<@!?(\d+)>/)?.[1];
|
||||
if (!targetId) return "Got null ID.";
|
||||
|
@ -865,16 +949,20 @@ flagdump.callback = async function (msg, line, [numOrMention], {id, list}) {
|
|||
if (!user) {
|
||||
return "User not cached.";
|
||||
} else {
|
||||
return `\`${formatUsername(user)}\`'s public flags:\n\`\`\`${flagFromInt(
|
||||
user.publicFlags
|
||||
return `\`${formatUsername(user)}\`'s public flags:\n\`\`\`${flagsFromInt(
|
||||
user.publicFlags,
|
||||
USER_FLAGS
|
||||
)}\`\`\``;
|
||||
}
|
||||
} else if (!isNaN(num)) {
|
||||
return `\`\`\`\n${flagFromInt(num)}\`\`\``;
|
||||
return `\`\`\`\n${flagsFromInt(num, USER_FLAGS)}\`\`\``;
|
||||
} else {
|
||||
return `\`${formatUsername(
|
||||
msg.author
|
||||
)}\`'s public flags:\n\`\`\`${flagFromInt(msg.author.publicFlags)}\`\`\``;
|
||||
)}\`'s public flags:\n\`\`\`${flagsFromInt(
|
||||
msg.author.publicFlags,
|
||||
USER_FLAGS
|
||||
)}\`\`\``;
|
||||
}
|
||||
};
|
||||
hf.registerCommand(flagdump);
|
||||
|
@ -1262,7 +1350,6 @@ presence.callback = async function (msg, line) {
|
|||
};
|
||||
hf.registerCommand(presence);
|
||||
|
||||
const POMELO_REGEX = /^[a-z0-9._]{1,32}$/;
|
||||
const pomelo = new Command("pomelo");
|
||||
pomelo.category = CATEGORY;
|
||||
pomelo.helpText = "Check to see if a username is taken or not";
|
||||
|
@ -1310,4 +1397,279 @@ pomelo.callback = async function (msg, line) {
|
|||
};
|
||||
hf.registerCommand(pomelo);
|
||||
|
||||
const appinfo = new Command("appinfo");
|
||||
appinfo.category = CATEGORY;
|
||||
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.";
|
||||
|
||||
try {
|
||||
const _app = await hf.bot.requestHandler.request(
|
||||
"GET",
|
||||
`/applications/${line}/rpc`,
|
||||
false
|
||||
);
|
||||
|
||||
let app = _app;
|
||||
const game = GameData.find((game) => game.id == app.id);
|
||||
if (game) {
|
||||
app = Object.assign(app, game);
|
||||
}
|
||||
|
||||
const assets = await hf.bot.requestHandler.request(
|
||||
"GET",
|
||||
`/oauth2/applications/${app.id}/assets`,
|
||||
false
|
||||
);
|
||||
|
||||
const embed = {
|
||||
title: `${app.name}`,
|
||||
description:
|
||||
app.description.length > 0 ? app.description : "*No description*.",
|
||||
fields: [
|
||||
{
|
||||
name: "Created",
|
||||
value: `<t:${snowflakeToTimestamp(app.id)}:R>`,
|
||||
inline: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (app.icon) {
|
||||
embed.thumbnail = {
|
||||
url: `${APP_ICON_BASE}/app-icons/${app.id}/${app.icon}.png?size=1024`,
|
||||
};
|
||||
}
|
||||
|
||||
if (app.type) {
|
||||
embed.fields.push({
|
||||
name: "Type",
|
||||
value: `${APPLICATION_TYPES[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) =>
|
||||
`${OS_ICONS[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].substring(1).toUpperCase() +
|
||||
x.substring(1).toLowerCase()
|
||||
)
|
||||
.join(" ")
|
||||
.replace(" Net", ".net")}: \`${sku.id}\``
|
||||
)
|
||||
.join("\n"),
|
||||
inline: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
(app.bot_public != null && !app.bot_require_code_grant) ||
|
||||
(app.integration_public != null && !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",
|
||||
`/users/${app.id}`,
|
||||
true
|
||||
);
|
||||
|
||||
embed.fields.push({
|
||||
name: "Bot",
|
||||
value: formatUsername(bot),
|
||||
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, APPLICATION_FLAGS, 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](${APP_ICON_BASE}${app.id}/${app.cover_image}.png?size=2048)`
|
||||
);
|
||||
}
|
||||
if (app.splash) {
|
||||
images.push(
|
||||
`[Splash](${APP_ICON_BASE}${app.id}/${app.splash}.png?size=2048)`
|
||||
);
|
||||
}
|
||||
|
||||
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 (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}](${APP_ASSET_BASE}${app.id}/${asset.id}.png)`
|
||||
);
|
||||
|
||||
embed.fields.push({
|
||||
name: "Assets",
|
||||
value:
|
||||
"- " +
|
||||
mappedAssets
|
||||
.slice(0, Math.ceil(mappedAssets.length / 2))
|
||||
.join("\n- "),
|
||||
inline: true,
|
||||
});
|
||||
if (mappedAssets.length > 1)
|
||||
embed.fields.push({
|
||||
name: "\u200b",
|
||||
value:
|
||||
"- " +
|
||||
mappedAssets
|
||||
.slice(Math.ceil(mappedAssets.length / 2), mappedAssets.length)
|
||||
.join("\n- "),
|
||||
inline: true,
|
||||
});
|
||||
}
|
||||
|
||||
return {embed};
|
||||
} catch (error) {
|
||||
if (error.message === "Unknown Application") {
|
||||
return "ID provided does not point to a valid application.";
|
||||
} else {
|
||||
return `:warning: Got error \`${safeString(error)}\``;
|
||||
}
|
||||
}
|
||||
};
|
||||
hf.registerCommand(appinfo);
|
||||
|
||||
// }}}
|
||||
|
|
Loading…
Reference in a new issue