Add !invite utility command
This commit is contained in:
parent
ebdeabdc32
commit
024078ccdc
7 changed files with 187 additions and 24 deletions
|
@ -1,9 +1,16 @@
|
||||||
import { PluginData } from "knub";
|
import { PluginData } from "knub";
|
||||||
import { CensorPluginType } from "../types";
|
import { CensorPluginType } from "../types";
|
||||||
import { SavedMessage } from "src/data/entities/SavedMessage";
|
import { SavedMessage } from "src/data/entities/SavedMessage";
|
||||||
import { Embed, GuildInvite } from "eris";
|
import { AnyInvite, Embed, GuildInvite } from "eris";
|
||||||
import { ZalgoRegex } from "src/data/Zalgo";
|
import { ZalgoRegex } from "src/data/Zalgo";
|
||||||
import { getInviteCodesInString, getUrlsInString, resolveMember, resolveInvite } from "src/utils";
|
import {
|
||||||
|
getInviteCodesInString,
|
||||||
|
getUrlsInString,
|
||||||
|
resolveMember,
|
||||||
|
resolveInvite,
|
||||||
|
isGuildInvite,
|
||||||
|
isRESTGuildInvite,
|
||||||
|
} from "src/utils";
|
||||||
import cloneDeep from "lodash.clonedeep";
|
import cloneDeep from "lodash.clonedeep";
|
||||||
import { censorMessage } from "./censorMessage";
|
import { censorMessage } from "./censorMessage";
|
||||||
import escapeStringRegexp from "escape-string-regexp";
|
import escapeStringRegexp from "escape-string-regexp";
|
||||||
|
@ -52,7 +59,7 @@ export async function applyFiltersToMsg(
|
||||||
|
|
||||||
const inviteCodes = getInviteCodesInString(messageContent);
|
const inviteCodes = getInviteCodesInString(messageContent);
|
||||||
|
|
||||||
const invites: Array<GuildInvite | null> = await Promise.all(
|
const invites: Array<AnyInvite | null> = await Promise.all(
|
||||||
inviteCodes.map(code => resolveInvite(pluginData.client, code)),
|
inviteCodes.map(code => resolveInvite(pluginData.client, code)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -63,27 +70,29 @@ export async function applyFiltersToMsg(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!invite.guild && !allowGroupDMInvites) {
|
if (!isGuildInvite(invite) && !allowGroupDMInvites) {
|
||||||
censorMessage(pluginData, savedMessage, `group dm invites are not allowed`);
|
censorMessage(pluginData, savedMessage, `group dm invites are not allowed`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inviteGuildWhitelist && !inviteGuildWhitelist.includes(invite.guild.id)) {
|
if (isRESTGuildInvite(invite)) {
|
||||||
censorMessage(
|
if (inviteGuildWhitelist && !inviteGuildWhitelist.includes(invite.guild.id)) {
|
||||||
pluginData,
|
censorMessage(
|
||||||
savedMessage,
|
pluginData,
|
||||||
`invite guild (**${invite.guild.name}** \`${invite.guild.id}\`) not found in whitelist`,
|
savedMessage,
|
||||||
);
|
`invite guild (**${invite.guild.name}** \`${invite.guild.id}\`) not found in whitelist`,
|
||||||
return true;
|
);
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (inviteGuildBlacklist && inviteGuildBlacklist.includes(invite.guild.id)) {
|
if (inviteGuildBlacklist && inviteGuildBlacklist.includes(invite.guild.id)) {
|
||||||
censorMessage(
|
censorMessage(
|
||||||
pluginData,
|
pluginData,
|
||||||
savedMessage,
|
savedMessage,
|
||||||
`invite guild (**${invite.guild.name}** \`${invite.guild.id}\`) found in blacklist`,
|
`invite guild (**${invite.guild.name}** \`${invite.guild.id}\`) found in blacklist`,
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inviteCodeWhitelist && !inviteCodeWhitelist.includes(invite.code)) {
|
if (inviteCodeWhitelist && !inviteCodeWhitelist.includes(invite.code)) {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import { JumboCmd } from "./commands/JumboCmd";
|
||||||
import { AvatarCmd } from "./commands/AvatarCmd";
|
import { AvatarCmd } from "./commands/AvatarCmd";
|
||||||
import { CleanCmd } from "./commands/CleanCmd";
|
import { CleanCmd } from "./commands/CleanCmd";
|
||||||
import { Message } from "eris";
|
import { Message } from "eris";
|
||||||
|
import { InviteInfoCmd } from "./commands/InviteInfoCmd";
|
||||||
|
|
||||||
const defaultOptions: PluginOptions<UtilityPluginType> = {
|
const defaultOptions: PluginOptions<UtilityPluginType> = {
|
||||||
config: {
|
config: {
|
||||||
|
@ -36,6 +37,7 @@ const defaultOptions: PluginOptions<UtilityPluginType> = {
|
||||||
can_clean: false,
|
can_clean: false,
|
||||||
can_info: false,
|
can_info: false,
|
||||||
can_server: false,
|
can_server: false,
|
||||||
|
can_invite: false,
|
||||||
can_reload_guild: false,
|
can_reload_guild: false,
|
||||||
can_nickname: false,
|
can_nickname: false,
|
||||||
can_ping: false,
|
can_ping: false,
|
||||||
|
@ -59,6 +61,7 @@ const defaultOptions: PluginOptions<UtilityPluginType> = {
|
||||||
can_clean: true,
|
can_clean: true,
|
||||||
can_info: true,
|
can_info: true,
|
||||||
can_server: true,
|
can_server: true,
|
||||||
|
can_invite: true,
|
||||||
can_nickname: true,
|
can_nickname: true,
|
||||||
can_vcmove: true,
|
can_vcmove: true,
|
||||||
can_help: true,
|
can_help: true,
|
||||||
|
@ -108,6 +111,7 @@ export const UtilityPlugin = zeppelinPlugin<UtilityPluginType>()("utility", {
|
||||||
JumboCmd,
|
JumboCmd,
|
||||||
AvatarCmd,
|
AvatarCmd,
|
||||||
CleanCmd,
|
CleanCmd,
|
||||||
|
InviteInfoCmd,
|
||||||
],
|
],
|
||||||
|
|
||||||
onLoad(pluginData) {
|
onLoad(pluginData) {
|
||||||
|
|
27
backend/src/plugins/Utility/commands/InviteInfoCmd.ts
Normal file
27
backend/src/plugins/Utility/commands/InviteInfoCmd.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { utilityCmd } from "../types";
|
||||||
|
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||||
|
import { sendErrorMessage } from "../../../pluginUtils";
|
||||||
|
import { getInviteInfoEmbed } from "../functions/getInviteInfoEmbed";
|
||||||
|
import { parseInviteCodeInput } from "../../../utils";
|
||||||
|
|
||||||
|
export const InviteInfoCmd = utilityCmd({
|
||||||
|
trigger: ["invite", "inviteinfo"],
|
||||||
|
description: "Show information about an invite",
|
||||||
|
usage: "!invite overwatch",
|
||||||
|
permission: "can_invite",
|
||||||
|
|
||||||
|
signature: {
|
||||||
|
inviteCode: ct.string({ required: false }),
|
||||||
|
},
|
||||||
|
|
||||||
|
async run({ message, args, pluginData }) {
|
||||||
|
const inviteCode = parseInviteCodeInput(args.inviteCode);
|
||||||
|
const embed = await getInviteInfoEmbed(pluginData, inviteCode);
|
||||||
|
if (!embed) {
|
||||||
|
sendErrorMessage(pluginData, message.channel, "Unknown invite");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
message.channel.createMessage({ embed });
|
||||||
|
},
|
||||||
|
});
|
94
backend/src/plugins/Utility/functions/getInviteInfoEmbed.ts
Normal file
94
backend/src/plugins/Utility/functions/getInviteInfoEmbed.ts
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
import { PluginData } from "knub";
|
||||||
|
import { UtilityPluginType } from "../types";
|
||||||
|
import { BaseInvite, Constants, EmbedOptions, RESTChannelInvite, RESTPrivateInvite } from "eris";
|
||||||
|
import { snowflakeToTimestamp } from "../../../utils/snowflakeToTimestamp";
|
||||||
|
import moment from "moment-timezone";
|
||||||
|
import humanizeDuration from "humanize-duration";
|
||||||
|
import { emptyEmbedValue, formatNumber, isRESTGroupDMInvite, isRESTGuildInvite, resolveInvite } from "../../../utils";
|
||||||
|
|
||||||
|
export async function getInviteInfoEmbed(
|
||||||
|
pluginData: PluginData<UtilityPluginType>,
|
||||||
|
inviteCode: string,
|
||||||
|
): Promise<EmbedOptions | null> {
|
||||||
|
const invite = await resolveInvite(pluginData.client, inviteCode, true);
|
||||||
|
if (!invite) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRESTGuildInvite(invite)) {
|
||||||
|
const embed: EmbedOptions = {
|
||||||
|
fields: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (invite.guild.icon) {
|
||||||
|
embed.thumbnail = {
|
||||||
|
url: `https://cdn.discordapp.com/icons/${invite.guild.id}/${invite.guild.icon}.png?size=256`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
embed.title = `Server Invite - ${invite.guild.name}`;
|
||||||
|
embed.url = `https://discord.gg/${invite.code}`;
|
||||||
|
|
||||||
|
embed.fields.push({
|
||||||
|
name: "Server ID",
|
||||||
|
value: `\`${invite.guild.id}\``,
|
||||||
|
inline: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
embed.fields.push({
|
||||||
|
name: "Channel",
|
||||||
|
value:
|
||||||
|
invite.channel.type === Constants.ChannelTypes.GUILD_VOICE
|
||||||
|
? `🔉 ${invite.channel.name}\n\`${invite.channel.id}\``
|
||||||
|
: `#${invite.channel.name}\n\`${invite.channel.id}\``,
|
||||||
|
});
|
||||||
|
|
||||||
|
const createdAtTimestamp = snowflakeToTimestamp(invite.guild.id);
|
||||||
|
const createdAt = moment(createdAtTimestamp, "x");
|
||||||
|
const serverAge = humanizeDuration(Date.now() - createdAtTimestamp, {
|
||||||
|
largest: 2,
|
||||||
|
round: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
embed.fields.push({
|
||||||
|
name: "Server age",
|
||||||
|
value: serverAge,
|
||||||
|
});
|
||||||
|
|
||||||
|
embed.fields.push({
|
||||||
|
name: "Members",
|
||||||
|
value: `**${formatNumber(invite.memberCount)}** (${formatNumber(invite.presenceCount)} online)`,
|
||||||
|
inline: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return embed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRESTGroupDMInvite(invite)) {
|
||||||
|
const embed: EmbedOptions = {
|
||||||
|
fields: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (invite.channel.icon) {
|
||||||
|
embed.thumbnail = {
|
||||||
|
url: `https://cdn.discordapp.com/channel-icons/${invite.channel.id}/${invite.channel.icon}.png?size=256`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
embed.title = invite.channel.name ? `Group DM Invite - ${invite.channel.name}` : `Group DM Invite`;
|
||||||
|
|
||||||
|
embed.fields.push({
|
||||||
|
name: "Channel ID",
|
||||||
|
value: `\`${invite.channel.id}\``,
|
||||||
|
});
|
||||||
|
|
||||||
|
embed.fields.push({
|
||||||
|
name: "Members",
|
||||||
|
value: formatNumber((invite as any).memberCount),
|
||||||
|
});
|
||||||
|
|
||||||
|
return embed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ export const ConfigSchema = t.type({
|
||||||
can_clean: t.boolean,
|
can_clean: t.boolean,
|
||||||
can_info: t.boolean,
|
can_info: t.boolean,
|
||||||
can_server: t.boolean,
|
can_server: t.boolean,
|
||||||
|
can_invite: t.boolean,
|
||||||
can_reload_guild: t.boolean,
|
can_reload_guild: t.boolean,
|
||||||
can_nickname: t.boolean,
|
can_nickname: t.boolean,
|
||||||
can_ping: t.boolean,
|
can_ping: t.boolean,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
AnyInvite,
|
AnyInvite,
|
||||||
Attachment,
|
Attachment,
|
||||||
|
BaseInvite,
|
||||||
ChannelInvite,
|
ChannelInvite,
|
||||||
Client,
|
Client,
|
||||||
Embed,
|
Embed,
|
||||||
|
@ -15,6 +16,8 @@ import {
|
||||||
Message,
|
Message,
|
||||||
MessageContent,
|
MessageContent,
|
||||||
PossiblyUncachedMessage,
|
PossiblyUncachedMessage,
|
||||||
|
RESTChannelInvite,
|
||||||
|
RESTPrivateInvite,
|
||||||
TextableChannel,
|
TextableChannel,
|
||||||
TextChannel,
|
TextChannel,
|
||||||
User,
|
User,
|
||||||
|
@ -450,6 +453,14 @@ export function getUrlsInString(str: string, onlyUnique = false): MatchedURL[] {
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseInviteCodeInput(str: string): string {
|
||||||
|
if (str.match(/^[a-z0-9]{6,}$/i)) {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getInviteCodesInString(str)[0];
|
||||||
|
}
|
||||||
|
|
||||||
export function getInviteCodesInString(str: string): string[] {
|
export function getInviteCodesInString(str: string): string[] {
|
||||||
const inviteCodeRegex = /(?:discord.gg|discordapp.com\/invite|discord.com\/invite)\/([a-z0-9]+)/gi;
|
const inviteCodeRegex = /(?:discord.gg|discordapp.com\/invite|discord.com\/invite)\/([a-z0-9]+)/gi;
|
||||||
return Array.from(str.matchAll(inviteCodeRegex)).map(m => m[1]);
|
return Array.from(str.matchAll(inviteCodeRegex)).map(m => m[1]);
|
||||||
|
@ -1064,13 +1075,15 @@ export async function resolveRoleId(bot: Client, guildId: string, value: string)
|
||||||
|
|
||||||
const inviteCache = new SimpleCache<Promise<ChannelInvite>>(10 * MINUTES, 200);
|
const inviteCache = new SimpleCache<Promise<ChannelInvite>>(10 * MINUTES, 200);
|
||||||
|
|
||||||
export async function resolveInvite(client: Client, code: string): Promise<ChannelInvite | null> {
|
export async function resolveInvite(client: Client, code: string, withCounts?: boolean): Promise<AnyInvite | null> {
|
||||||
if (inviteCache.has(code)) {
|
const key = `${code}:${withCounts ? 1 : 0}`;
|
||||||
return inviteCache.get(code);
|
|
||||||
|
if (inviteCache.has(key)) {
|
||||||
|
return inviteCache.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
const promise = client.getInvite(code).catch(() => null);
|
const promise = client.getInvite(code, withCounts).catch(() => null);
|
||||||
inviteCache.set(code, promise);
|
inviteCache.set(key, promise);
|
||||||
|
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
@ -1229,6 +1242,14 @@ export function isGuildInvite(invite: AnyInvite): invite is GuildInvite {
|
||||||
return (invite as GuildInvite).guild != null;
|
return (invite as GuildInvite).guild != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isRESTGuildInvite(invite: BaseInvite): invite is RESTChannelInvite {
|
||||||
|
return (invite as any).guild != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isRESTGroupDMInvite(invite: BaseInvite): invite is RESTPrivateInvite {
|
||||||
|
return (invite as any).guild == null && (invite as any).channel != null;
|
||||||
|
}
|
||||||
|
|
||||||
export function asyncMap<T, R>(arr: T[], fn: (item: T) => Promise<R>): Promise<R[]> {
|
export function asyncMap<T, R>(arr: T[], fn: (item: T) => Promise<R>): Promise<R[]> {
|
||||||
return Promise.all(arr.map((item, index) => fn(item)));
|
return Promise.all(arr.map((item, index) => fn(item)));
|
||||||
}
|
}
|
||||||
|
|
7
backend/src/utils/snowflakeToTimestamp.ts
Normal file
7
backend/src/utils/snowflakeToTimestamp.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
/**
|
||||||
|
* @return Unix timestamp in milliseconds
|
||||||
|
*/
|
||||||
|
export function snowflakeToTimestamp(snowflake: string) {
|
||||||
|
// https://discord.com/developers/docs/reference#snowflakes-snowflake-id-format-structure-left-to-right
|
||||||
|
return Number(BigInt(snowflake) >> 22n) + 1420070400000;
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue