From bba3ded08c3a9f5020c5bf22892195b37a0fdc96 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Thu, 23 Jun 2022 18:21:33 -0500 Subject: [PATCH] feat: members --- structures/Member.ts | 111 ++++++++++++++++++++++++++++++++++++++++++ structures/Message.ts | 8 +++ structures/User.ts | 11 +---- tests/mod.ts | 2 +- util/Routes.ts | 37 ++++++++++++-- util/mod.ts | 1 + util/shared/images.ts | 9 ++++ 7 files changed, 164 insertions(+), 15 deletions(-) create mode 100644 structures/Member.ts create mode 100644 util/shared/images.ts diff --git a/structures/Member.ts b/structures/Member.ts new file mode 100644 index 0000000..dcae31f --- /dev/null +++ b/structures/Member.ts @@ -0,0 +1,111 @@ +import type { Model } from "./Base.ts"; +import type { Snowflake } from "../util/Snowflake.ts"; +import type { Session } from "../session/Session.ts"; +import type { DiscordMember, MakeRequired } from "../vendor/external.ts"; +import type { ImageFormat, ImageSize } from "../util/shared/images.ts"; +import { iconBigintToHash, iconHashToBigInt } from "../util/hash.ts"; +import { Routes } from "../util/mod.ts"; +import { User } from "./User.ts"; + +/** + * @link https://discord.com/developers/docs/resources/guild#create-guild-ban + * */ +export interface CreateGuildBan { + /** Number of days to delete messages for (0-7) */ + deleteMessageDays?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7; + /** Reason for the ban */ + reason?: string; +} + +/** + * Represents a guild member + * TODO: add a `guild` property somehow + * @link https://discord.com/developers/docs/resources/guild#guild-member-object + * */ +export class Member implements Model { + constructor(session: Session, data: MakeRequired) { + this.session = session; + this.user = new User(session, data.user); + this.avatarHash = data.avatar ? iconHashToBigInt(data.avatar) : undefined; + this.nickname = data.nick ? data.nick : undefined; + this.joinedTimestamp = Number.parseInt(data.joined_at); + this.roles = data.roles; + this.deaf = !!data.deaf; + this.mute = !!data.mute; + this.pending = !!data.pending; + this.communicationDisabledUntilTimestamp = data.communication_disabled_until ? Number.parseInt(data.communication_disabled_until) : undefined; + } + + readonly session: Session; + + user: User; + avatarHash?: bigint; + nickname?: string; + joinedTimestamp: number; + roles: Snowflake[]; + deaf: boolean; + mute: boolean; + pending: boolean; + communicationDisabledUntilTimestamp?: number; + + /** shorthand to User.id */ + get id(): Snowflake { + return this.user.id; + } + + get nicknameOrUsername() { + return this.nickname ?? this.user.username; + } + + get joinedAt() { + return new Date(this.joinedTimestamp); + } + + /** + * Bans the member + * */ + async ban(guildId: Snowflake, options: CreateGuildBan): Promise { + await this.session.rest.runMethod( + this.session.rest, + "PUT", + Routes.GUILD_BAN(guildId, this.id), + options ? { + delete_message_days: options.deleteMessageDays, + reason: options.reason + } : {} + ); + + return this; + } + + /** + * Kicks the member + * */ + async kick(guildId: Snowflake, { reason }: { reason?: string }): Promise { + await this.session.rest.runMethod( + this.session.rest, + "DELETE", + Routes.GUILD_MEMBER(guildId, this.id), + { reason } + ); + + return this; + } + + /** gets the user's avatar */ + avatarUrl(options: { format?: ImageFormat; size?: ImageSize } = { size: 128 }) { + let url: string; + + if (!this.avatarHash) { + url = Routes.USER_DEFAULT_AVATAR(Number(this.user.discriminator) % 5); + } else { + url = Routes.USER_AVATAR(this.id, iconBigintToHash(this.avatarHash)); + } + + return `${url}.${options.format ?? (url.includes("/a_") ? "gif" : "jpg")}?size=${options.size}`; + } + + toString() { + return `<@!${this.user.id}>`; + } +} diff --git a/structures/Message.ts b/structures/Message.ts index 8000858..9bff529 100644 --- a/structures/Message.ts +++ b/structures/Message.ts @@ -3,6 +3,7 @@ import type { Snowflake } from "../util/Snowflake.ts"; import type { Session } from "../session/mod.ts"; import type { AllowedMentionsTypes, DiscordMessage } from "../vendor/external.ts"; import { User } from "./User.ts"; +import { Member } from "./Member.ts"; import { Attachment } from "./Attachment.ts"; import { MessageFlags, Routes } from "../util/mod.ts"; @@ -52,6 +53,12 @@ export class Message implements Model { this.content = data.content!; this.attachments = data.attachments.map((attachment) => new Attachment(session, attachment)); + + // user is always null on MessageCreate and its replaced with author + this.member = data.member ? new Member(session, { + ...data.member, + user: data.author, + }) : undefined; } readonly session: Session; @@ -66,6 +73,7 @@ export class Message implements Model { content: string; attachments: Attachment[]; + member?: Member; get url() { return `https://discord.com/channels/${this.guildId ?? "@me"}/${this.channelId}/${this.id}`; diff --git a/structures/User.ts b/structures/User.ts index a7fa2ae..42c623c 100644 --- a/structures/User.ts +++ b/structures/User.ts @@ -2,19 +2,10 @@ import type { Model } from "./Base.ts"; import type { Snowflake } from "../util/Snowflake.ts"; import type { Session } from "../session/mod.ts"; import type { DiscordUser } from "../vendor/external.ts"; +import type { ImageFormat, ImageSize } from "../util/shared/images.ts"; import { iconBigintToHash, iconHashToBigInt } from "../util/hash.ts"; import { Routes } from "../util/mod.ts"; -/** - * @link https://discord.com/developers/docs/reference#image-formatting - */ -export type ImageFormat = "jpg" | "jpeg" | "png" | "webp" | "gif" | "json"; - -/** - * @link https://discord.com/developers/docs/reference#image-formatting - */ -export type ImageSize = 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096; - /** * Represents a user * @link https://discord.com/developers/docs/resources/user#user-object diff --git a/tests/mod.ts b/tests/mod.ts index aeb6f6e..402cd5a 100644 --- a/tests/mod.ts +++ b/tests/mod.ts @@ -12,7 +12,7 @@ session.on("ready", (_shardId, payload) => { }); session.on("messageCreate", (message) => { - if (message.content === "!ping") { + if (message.content.startsWith("!ping")) { message.reply({ content: "pong!" }); } }); diff --git a/util/Routes.ts b/util/Routes.ts index e0dc644..a6adec0 100644 --- a/util/Routes.ts +++ b/util/Routes.ts @@ -31,10 +31,10 @@ export function CHANNEL_MESSAGES(channelId: Snowflake, options?: GetMessagesOpti let url = `/channels/${channelId}/messages?`; if (options) { - if ("after" in options && options.after) url += `after=${options.after}`; - if ("before" in options && options.before) url += `&before=${options.before}`; - if ("around" in options && options.around) url += `&around=${options.around}`; - if ("limit" in options && options.limit) url += `&limit=${options.limit}`; + if (options.after) url += `after=${options.after}`; + if (options.before) url += `&before=${options.before}`; + if (options.around) url += `&around=${options.around}`; + if (options.limit) url += `&limit=${options.limit}`; } return url; @@ -44,3 +44,32 @@ export function CHANNEL_MESSAGES(channelId: Snowflake, options?: GetMessagesOpti export function CHANNEL_MESSAGE(channelId: Snowflake, messageId: Snowflake) { return `/channels/${channelId}/messages/${messageId}`; } + +/** used to kick members */ +export function GUILD_MEMBER(guildId: Snowflake, userId: Snowflake) { + return `/guilds/${guildId}/members/${userId}`; +} + +/** used to ban members */ +export function GUILD_BAN(guildId: Snowflake, userId: Snowflake) { + return `/guilds/${guildId}/bans/${userId}`; +} + +export interface GetBans { + limit?: number; + before?: Snowflake; + after?: Snowflake; +} + +/** used to unban members */ +export function GUILD_BANS(guildId: Snowflake, options?: GetBans) { + let url = `/guilds/${guildId}/bans?`; + + if (options) { + if (options.limit) url += `limit=${options.limit}`; + if (options.after) url += `&after=${options.after}`; + if (options.before) url += `&before=${options.before}`; + } + + return url; +} diff --git a/util/mod.ts b/util/mod.ts index 53eb3dc..45354dc 100644 --- a/util/mod.ts +++ b/util/mod.ts @@ -2,4 +2,5 @@ export * from "./EventEmmiter.ts"; export * from "./Snowflake.ts"; export * from "./hash.ts"; export * from "./shared/flags.ts"; +export * from "./shared/images.ts"; export * as Routes from "./Routes.ts"; diff --git a/util/shared/images.ts b/util/shared/images.ts new file mode 100644 index 0000000..9c8f86d --- /dev/null +++ b/util/shared/images.ts @@ -0,0 +1,9 @@ +/** + * @link https://discord.com/developers/docs/reference#image-formatting + */ +export type ImageFormat = "jpg" | "jpeg" | "png" | "webp" | "gif" | "json"; + +/** + * @link https://discord.com/developers/docs/reference#image-formatting + */ +export type ImageSize = 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096;