add webhook functionality

This commit is contained in:
Skillz
2020-08-15 13:49:39 -04:00
parent 05ffc015f7
commit 1737e7ffa7
4 changed files with 167 additions and 0 deletions

View File

@@ -77,6 +77,8 @@ export const endpoints = {
GUILD_VANITY_URL: (id: string) => `${GUILDS_BASE(id)}/vanity-url`,
GUILD_WEBHOOKS: (id: string) => `${GUILDS_BASE(id)}/webhooks`,
WEBHOOK: (id: string, token: string) => `${baseEndpoints.BASE_URL}/webhooks/${id}/${token}`,
// User endpoints
USER_AVATAR: (id: string, icon: string) =>
`${baseEndpoints.CDN_URL}/avatars/${id}/${icon}`,

102
src/handlers/webhook.ts Normal file
View File

@@ -0,0 +1,102 @@
import {
WebhookCreateOptions,
WebhookPayload,
ExecuteWebhookOptions,
} from "../types/webhook.ts";
import { botHasChannelPermissions } from "../utils/permissions.ts";
import { Permissions } from "../types/permission.ts";
import { Errors } from "../types/errors.ts";
import { RequestManager } from "../module/requestManager.ts";
import { endpoints } from "../constants/discord.ts";
import { createMessage } from "../structures/message.ts";
import { MessageCreateOptions } from "../types/message.ts";
import { urlToBase64 } from "../utils/utils.ts";
/** Create a new webhook. Requires the MANAGE_WEBHOOKS permission. Returns a webhook object on success. Webhook names follow our naming restrictions that can be found in our Usernames and Nicknames documentation, with the following additional stipulations:
*
* Webhook names cannot be: 'clyde'
*/
export async function createWebhook(
channelID: string,
options: WebhookCreateOptions,
) {
if (
!botHasChannelPermissions(
channelID,
[Permissions.MANAGE_WEBHOOKS],
)
) {
throw new Error(Errors.MISSING_MANAGE_WEBHOOKS);
}
if (
// Specific usernames that discord does not allow
options.name === "clyde" ||
// Character limit checks. [...] checks are because of js unicode length handling
[...options.name].length < 2 || [...options.name].length > 32
) {
throw new Error(Errors.INVALID_WEBHOOK_NAME);
}
return RequestManager.post(
endpoints.CHANNEL_WEBHOOKS(channelID),
{
...options,
avatar: options.avatar ? await urlToBase64(options.avatar) : undefined,
},
) as Promise<WebhookPayload>;
}
export async function executeWebhook(
webhookID: string,
webhookToken: string,
options: ExecuteWebhookOptions,
) {
if (!options.content && !options.file && !options.embeds) {
throw new Error(Errors.INVALID_WEBHOOK_OPTIONS);
}
if (options.embeds && options.embeds.length > 10) {
options.embeds.splice(10);
}
if (options.mentions) {
if (options.mentions.users?.length) {
if (options.mentions.parse.includes("users")) {
options.mentions.parse = options.mentions.parse.filter((p) =>
p !== "users"
);
}
if (options.mentions.users.length > 100) {
options.mentions.users = options.mentions.users.slice(0, 100);
}
}
if (options.mentions.roles?.length) {
if (options.mentions.parse.includes("roles")) {
options.mentions.parse = options.mentions.parse.filter((p) =>
p !== "roles"
);
}
if (options.mentions.roles.length > 100) {
options.mentions.roles = options.mentions.roles.slice(0, 100);
}
}
}
const result = await RequestManager.post(
`${endpoints.WEBHOOK(webhookID, webhookToken)}${
options.wait ? "?wait=true" : ""
}`,
{
...options,
allowed_mentions: options.mentions,
avatar_url: options.avatar_url,
},
);
if (!options.wait) return;
return createMessage(result as MessageCreateOptions);
}

View File

@@ -28,4 +28,6 @@ export enum Errors {
REQUEST_UNKNOWN_ERROR = "REQUEST_UNKNOWN_ERROR",
BOTS_HIGHEST_ROLE_TOO_LOW = "BOTS_HIGHEST_ROLE_TOO_LOW",
CHANNEL_NOT_IN_GUILD = "CHANNEL_NOT_IN_GUILD",
INVALID_WEBHOOK_NAME = "INVALID_WEBHOOK_NAME",
INVALID_WEBHOOK_OPTIONS = "INVALID_WEBHOOK_OPTIONS",
}

61
src/types/webhook.ts Normal file
View File

@@ -0,0 +1,61 @@
import { UserPayload } from "./guild.ts";
import { Embed } from "./message.ts";
export interface WebhookPayload {
/** The id of the webhook */
id: string;
/** The type of the webhook */
type: WebhookType;
/** The guild id this webhook is for */
guild_id?: string;
/** The channel id this webhook is for */
channel_id: string;
/** The user this webhook was created by(not returned when getting a webhook with its token) */
user?: UserPayload;
/** The default name of the webhook */
name?: string;
/** The default avatar of the webhook */
avatar?: string;
/** The secure token of the webhook(returned for Incoming Webhooks) */
token?: string;
}
export enum WebhookType {
/** Incoming Webhooks can post messages to channels with a generated token */
INCOMING = 1,
/** Channel Follower Webhooks are internal webhooks used with Channel Following to post new messages into channels */
CHANNEL_FOLLOWER = 2,
}
export interface WebhookCreateOptions {
/** Name of the webhook (1-80 characters) */
name: string;
/** Image url for avatar image for the default webhook avatar */
avatar?: string;
}
export interface ExecuteWebhookOptions {
/** waits for server confirmation of message send before response, and returns the created message body (defaults to false; when false a message that is not saved does not return an error) */
wait?: boolean;
/** the message contents (up to 2000 characters) */
content?: string;
/** override the default username of the webhook */
username?: string;
/** override the default avatar of the webhook*/
avatar_url?: string;
/** true if this is a TTS message */
tts?: boolean;
/** file contents the contents of the file being sent one of content, file, embeds */
file?: { blob: unknown; name: string };
/** array of up to 10 embed objects embedded rich content. */
embeds?: Embed[];
/** allowed mentions for the message */
mentions?: {
/** An array of allowed mention types to parse from the content. */
parse: ("roles" | "users" | "everyone")[];
/** Array of role_ids to mention (Max size of 100) */
roles?: string[];
/** Array of user_ids to mention (Max size of 100) */
users?: string[];
};
}