diff --git a/mod.ts b/mod.ts index 7304f31..b6d350f 100644 --- a/mod.ts +++ b/mod.ts @@ -16,3 +16,10 @@ export * from "./structures/VoiceChannel.ts"; export * from "./structures/ThreadChannel.ts"; export * from "./structures/NewsChannel.ts"; export * from "./structures/Emoji.ts"; +export * from "./structures/VoiceChannel.ts"; +export * from "./structures/Emoji.ts"; +export * from "./structures/GuildEmoji.ts"; +export * from "./structures/WelcomeChannel.ts"; +export * from "./structures/WelcomeScreen.ts"; +export * from "./structures/Invite.ts"; +export * from "./structures/InviteGuild.ts"; diff --git a/structures/Guild.ts b/structures/Guild.ts index 70bf41f..33c4a2e 100644 --- a/structures/Guild.ts +++ b/structures/Guild.ts @@ -1,15 +1,18 @@ import type { Snowflake } from "../util/Snowflake.ts"; import type { Session } from "../session/Session.ts"; -import type { DiscordGuild, DiscordRole } from "../vendor/external.ts"; +import type { DiscordEmoji, DiscordGuild, DiscordRole } from "../vendor/external.ts"; import { DefaultMessageNotificationLevels, ExplicitContentFilterLevels, VerificationLevels, } from "../vendor/external.ts"; import { iconBigintToHash, iconHashToBigInt } from "../util/hash.ts"; +import { urlToBase64 } from "../util/urlToBase64.ts"; import { Member } from "./Member.ts"; import { BaseGuild } from "./BaseGuild.ts"; import { Role } from "./Role.ts"; +import { Emoji } from "./Emoji.ts"; +import { GuildEmoji } from "./GuildEmoji.ts"; import { Routes } from "../util/mod.ts"; export interface CreateRole { @@ -21,6 +24,18 @@ export interface CreateRole { mentionable?: boolean; } +export interface CreateGuildEmoji { + name: string; + image: string; + roles?: Snowflake[]; + reason?: string; +} + +export interface ModifyGuildEmoji { + name?: string; + roles?: Snowflake[]; +} + /** * Represents a guild * @link https://discord.com/developers/docs/resources/guild#guild-object @@ -39,6 +54,7 @@ export class Guild extends BaseGuild { this.explicitContentFilterLevel = data.explicit_content_filter; this.members = data.members?.map((member) => new Member(session, { ...member, user: member.user! })) ?? []; this.roles = data.roles.map((role) => new Role(session, role, data.id)); + this.emojis = data.emojis.map((guildEmoji) => new GuildEmoji(session, guildEmoji, data.id)); } splashHash?: bigint; @@ -51,6 +67,42 @@ export class Guild extends BaseGuild { explicitContentFilterLevel: ExplicitContentFilterLevels; members: Member[]; roles: Role[]; + emojis: GuildEmoji[]; + + async createEmoji(options: CreateGuildEmoji) { + if (options.image && !options.image.startsWith("data:image/")) { + options.image = await urlToBase64(options.image); + } + + const emoji = await this.session.rest.runMethod( + this.session.rest, + "POST", + Routes.GUILD_EMOJIS(this.id), + options, + ); + + return new Emoji(this.session, emoji); + } + + async deleteEmoji(id: Snowflake, { reason }: { reason?: string } = {}) { + await this.session.rest.runMethod( + this.session.rest, + "DELETE", + Routes.GUILD_EMOJI(this.id, id), + { reason }, + ); + } + + async editEmoji(id: Snowflake, options: ModifyGuildEmoji) { + const emoji = await this.session.rest.runMethod( + this.session.rest, + "PATCH", + Routes.GUILD_EMOJI(this.id, id), + options, + ); + + return new GuildEmoji(this.session, emoji, this.id); + } async createRole(options: CreateRole) { let icon: string | undefined; diff --git a/structures/GuildEmoji.ts b/structures/GuildEmoji.ts index af511c4..6120329 100644 --- a/structures/GuildEmoji.ts +++ b/structures/GuildEmoji.ts @@ -1,6 +1,8 @@ import type { Snowflake } from "../util/Snowflake.ts"; import type { Session } from "../session/Session.ts"; import type { DiscordEmoji } from "../vendor/external.ts"; +import type { ModifyGuildEmoji } from "./Guild.ts"; +import { Guild } from "./Guild.ts"; import { Emoji } from "./Emoji.ts"; import { User } from "./User.ts"; @@ -11,11 +13,30 @@ export class GuildEmoji extends Emoji { this.roles = data.roles; this.user = data.user ? new User(this.session, data.user) : undefined; this.managed = !!data.managed; + this.id = super.id!; } guildId: Snowflake; roles?: Snowflake[]; user?: User; managed?: boolean; + // id cannot be null in a GuildEmoji + override id: Snowflake; + + async edit(options: ModifyGuildEmoji): Promise { + const emoji = await Guild.prototype.editEmoji.call( + { id: this.guildId, session: this.session }, + this.id, + options, + ); + + return emoji; + } + + async delete({ reason }: { reason?: string } = {}): Promise { + await Guild.prototype.deleteEmoji.call({ id: this.guildId, session: this.session }, this.id, { reason }); + + return this; + } } export default GuildEmoji; diff --git a/util/Routes.ts b/util/Routes.ts index 7a06e7e..1d2efea 100644 --- a/util/Routes.ts +++ b/util/Routes.ts @@ -109,3 +109,11 @@ export function GUILD_ROLES(guildId: Snowflake) { export function USER_DM() { return `/users/@me/channels`; } + +export function GUILD_EMOJIS(guildId: Snowflake) { + return `/guilds/${guildId}/emojis`; +} + +export function GUILD_EMOJI(guildId: Snowflake, emojiId: Snowflake) { + return `/guilds/${guildId}/emojis/${emojiId}`; +} diff --git a/util/urlToBase64.ts b/util/urlToBase64.ts new file mode 100644 index 0000000..94d4733 --- /dev/null +++ b/util/urlToBase64.ts @@ -0,0 +1,111 @@ +/** Converts a url to base 64. Useful for example, uploading/creating server emojis. */ +export async function urlToBase64(url: string) { + const buffer = await fetch(url).then((res) => res.arrayBuffer()); + const imageStr = encode(buffer); + const type = url.substring(url.lastIndexOf(".") + 1); + return `data:image/${type};base64,${imageStr}`; +} + +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +const base64abc = [ + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "+", + "/", +]; + +/** + * CREDIT: https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727 + * Encodes a given Uint8Array, ArrayBuffer or string into RFC4648 base64 representation + * @param data + */ +export function encode(data: ArrayBuffer | string): string { + const uint8 = typeof data === "string" + ? new TextEncoder().encode(data) + : data instanceof Uint8Array + ? data + : new Uint8Array(data); + let result = "", + i; + const l = uint8.length; + for (i = 2; i < l; i += 3) { + result += base64abc[uint8[i - 2] >> 2]; + result += base64abc[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)]; + result += base64abc[((uint8[i - 1] & 0x0f) << 2) | (uint8[i] >> 6)]; + result += base64abc[uint8[i] & 0x3f]; + } + if (i === l + 1) { + // 1 octet yet to write + result += base64abc[uint8[i - 2] >> 2]; + result += base64abc[(uint8[i - 2] & 0x03) << 4]; + result += "=="; + } + if (i === l) { + // 2 octets yet to write + result += base64abc[uint8[i - 2] >> 2]; + result += base64abc[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)]; + result += base64abc[(uint8[i - 1] & 0x0f) << 2]; + result += "="; + } + return result; +}