From c5e87790cc250402a0f5bd4cd05ac1b5f8b45af7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Serna?= Date: Wed, 13 Jul 2022 14:53:52 -0300 Subject: [PATCH] Update guilds.ts --- packages/biscuit/structures/guilds.ts | 581 +++++++++++++++++++++++++- 1 file changed, 577 insertions(+), 4 deletions(-) diff --git a/packages/biscuit/structures/guilds.ts b/packages/biscuit/structures/guilds.ts index 62e5c44..dd15af4 100644 --- a/packages/biscuit/structures/guilds.ts +++ b/packages/biscuit/structures/guilds.ts @@ -1,12 +1,19 @@ import type { Model } from "./Base.ts"; import type { Session } from "../Session.ts"; -import type { DiscordGuild, GuildNsfwLevel, VerificationLevels } from "../../discordeno/mod.ts"; +import type { ChannelTypes, DefaultMessageNotificationLevels, DiscordEmoji, DiscordGuild, DiscordInviteMetadata, DiscordListActiveThreads, DiscordMemberWithUser, DiscordOverwrite, DiscordRole, ExplicitContentFilterLevels, GuildNsfwLevel, MakeRequired, SystemChannelFlags, VerificationLevels, VideoQualityModes } from "../../discordeno/mod.ts"; import type { ImageFormat, ImageSize } from "../Util.ts"; import { GuildFeatures } from "../../discordeno/mod.ts"; import { Snowflake } from "../Snowflake.ts"; import Util from "../Util.ts"; import * as Routes from "../Routes.ts"; import WelcomeScreen from "./WelcomeScreen.ts"; +import { GuildChannel, ThreadChannel } from "./channels.ts"; +import ThreadMember from "./ThreadMember.ts"; +import Member from "./Member.ts"; +import Role from "./Role.ts"; +import GuildEmoji from "./GuildEmoji.ts"; +import { urlToBase64 } from "../util/urlToBase64.ts"; +import Invite from "./Invite.ts"; /** BaseGuild */ /** @@ -61,8 +68,6 @@ export abstract class BaseGuild implements Model { } } -export default BaseGuild; - /** AnonymousGuild */ export class AnonymousGuild extends BaseGuild implements Model { constructor(session: Session, data: Partial); // TODO: Improve this type (name and id are required) @@ -120,4 +125,572 @@ export class InviteGuild extends AnonymousGuild implements Model { } welcomeScreen?: WelcomeScreen; -} \ No newline at end of file +} + +/** Guild */ +export interface CreateRole { + name?: string; + color?: number; + iconHash?: string | bigint; + unicodeEmoji?: string; + hoist?: boolean; + mentionable?: boolean; +} + +export interface ModifyGuildRole { + name?: string; + color?: number; + hoist?: boolean; + mentionable?: boolean; + unicodeEmoji?: string; +} + +export interface CreateGuildEmoji { + name: string; + image: string; + roles?: Snowflake[]; + reason?: string; +} + +export interface ModifyGuildEmoji { + name?: string; + roles?: Snowflake[]; +} + +/** + * @link https://discord.com/developers/docs/resources/guild#create-guild-ban + */ +export interface CreateGuildBan { + deleteMessageDays?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7; + reason?: string; +} + +/** + * @link https://discord.com/developers/docs/resources/guild#modify-guild-member + */ +export interface ModifyGuildMember { + nick?: string; + roles?: Snowflake[]; + mute?: boolean; + deaf?: boolean; + channelId?: Snowflake; + communicationDisabledUntil?: number; +} + +/** + * @link https://discord.com/developers/docs/resources/guild#begin-guild-prune + */ +export interface BeginGuildPrune { + days?: number; + computePruneCount?: boolean; + includeRoles?: Snowflake[]; +} + +export interface ModifyRolePositions { + id: Snowflake; + position?: number | null; +} + +export interface GuildCreateOptionsRole { + id: Snowflake; + name?: string; + color?: number; + hoist?: boolean; + position?: number; + permissions?: bigint; + mentionable?: boolean; + iconURL?: string; + unicodeEmoji?: string | null; +} + +export interface GuildCreateOptionsRole { + id: Snowflake; + name?: string; + color?: number; + hoist?: boolean; + position?: number; + permissions?: bigint; + mentionable?: boolean; + iconHash?: bigint; + unicodeEmoji?: string | null; +} + +export interface GuildCreateOptionsChannel { + id?: Snowflake; + parentId?: Snowflake; + type?: ChannelTypes.GuildText | ChannelTypes.GuildVoice | ChannelTypes.GuildCategory; + name: string; + topic?: string | null; + nsfw?: boolean; + bitrate?: number; + userLimit?: number; + rtcRegion?: string | null; + videoQualityMode?: VideoQualityModes; + permissionOverwrites?: MakeRequired, "id">[]; + rateLimitPerUser?: number; +} + +/** + * @link https://discord.com/developers/docs/resources/guild#create-guild + */ +export interface GuildCreateOptions { + name: string; + afkChannelId?: Snowflake; + afkTimeout?: number; + channels?: GuildCreateOptionsChannel[]; + defaultMessageNotifications?: DefaultMessageNotificationLevels; + explicitContentFilter?: ExplicitContentFilterLevels; + iconURL?: string; + roles?: GuildCreateOptionsRole[]; + systemChannelFlags?: SystemChannelFlags; + systemChannelId?: Snowflake; + verificationLevel?: VerificationLevels; +} + +export interface GuildCreateOptions { + name: string; + afkChannelId?: Snowflake; + afkTimeout?: number; + channels?: GuildCreateOptionsChannel[]; + defaultMessageNotifications?: DefaultMessageNotificationLevels; + explicitContentFilter?: ExplicitContentFilterLevels; + iconHash?: bigint; + roles?: GuildCreateOptionsRole[]; + systemChannelFlags?: SystemChannelFlags; + systemChannelId?: Snowflake; + verificationLevel?: VerificationLevels; +} + +/** + * @link https://discord.com/developers/docs/resources/guild#modify-guild-json-params + */ +export interface GuildEditOptions extends Omit { + ownerId?: Snowflake; + splashURL?: string; + bannerURL?: string; + discoverySplashURL?: string; + features?: GuildFeatures[]; + rulesChannelId?: Snowflake; + description?: string; + premiumProgressBarEnabled?: boolean; +} + +export interface GuildEditOptions extends Omit { + ownerId?: Snowflake; + splashHash?: bigint; + bannerHash?: bigint; + discoverySplashHash?: bigint; + features?: GuildFeatures[]; + rulesChannelId?: Snowflake; + publicUpdatesChannelId?: Snowflake; + preferredLocale?: string | null; + description?: string; + premiumProgressBarEnabled?: boolean; +} + +/** + * Represents a guild + * @link https://discord.com/developers/docs/resources/guild#guild-object + */ +export class Guild extends BaseGuild implements Model { + constructor(session: Session, data: DiscordGuild) { + super(session, data); + + this.splashHash = data.splash ? Util.iconHashToBigInt(data.splash) : undefined; + this.discoverySplashHash = data.discovery_splash ? Util.iconHashToBigInt(data.discovery_splash) : undefined; + this.ownerId = data.owner_id; + this.widgetEnabled = !!data.widget_enabled; + this.widgetChannelId = data.widget_channel_id ? data.widget_channel_id : undefined; + this.vefificationLevel = data.verification_level; + this.defaultMessageNotificationLevel = data.default_message_notifications; + this.explicitContentFilterLevel = data.explicit_content_filter; + + this.members = new Map( + data.members?.map((member) => [data.id, new Member(session, { ...member, user: member.user! }, data.id)]), + ); + + this.roles = new Map( + data.roles.map((role) => [data.id, new Role(session, role, data.id)]), + ); + + this.emojis = new Map( + data.emojis.map((guildEmoji) => [guildEmoji.id!, new GuildEmoji(session, guildEmoji, data.id)]), + ); + + this.channels = new Map( + data.channels?.map((guildChannel) => [guildChannel.id, new GuildChannel(session, guildChannel, data.id)]), + ); + } + + splashHash?: bigint; + discoverySplashHash?: bigint; + ownerId: Snowflake; + widgetEnabled: boolean; + widgetChannelId?: Snowflake; + vefificationLevel: VerificationLevels; + defaultMessageNotificationLevel: DefaultMessageNotificationLevels; + explicitContentFilterLevel: ExplicitContentFilterLevels; + members: Map; + roles: Map; + emojis: Map; + channels: Map; + + /** + * 'null' would reset the nickname + */ + async editBotNickname(options: { nick: string | null; reason?: string }) { + const result = await this.session.rest.runMethod<{ nick?: string } | undefined>( + this.session.rest, + "PATCH", + Routes.USER_NICK(this.id), + options, + ); + + return result?.nick; + } + + async createEmoji(options: CreateGuildEmoji): Promise { + 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 GuildEmoji(this.session, emoji, this.id); + } + + async deleteEmoji(id: Snowflake, { reason }: { reason?: string } = {}): Promise { + await this.session.rest.runMethod( + this.session.rest, + "DELETE", + Routes.GUILD_EMOJI(this.id, id), + { reason }, + ); + } + + async editEmoji(id: Snowflake, options: ModifyGuildEmoji): Promise { + 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): Promise { + let icon: string | undefined; + + if (options.iconHash) { + if (typeof options.iconHash === "string") { + icon = options.iconHash; + } else { + icon = Util.iconBigintToHash(options.iconHash); + } + } + + const role = await this.session.rest.runMethod( + this.session.rest, + "PUT", + Routes.GUILD_ROLES(this.id), + { + name: options.name, + color: options.color, + icon, + unicode_emoji: options.unicodeEmoji, + hoist: options.hoist, + mentionable: options.mentionable, + }, + ); + + return new Role(this.session, role, this.id); + } + + async deleteRole(roleId: Snowflake): Promise { + await this.session.rest.runMethod(this.session.rest, "DELETE", Routes.GUILD_ROLE(this.id, roleId)); + } + + async editRole(roleId: Snowflake, options: ModifyGuildRole): Promise { + const role = await this.session.rest.runMethod( + this.session.rest, + "PATCH", + Routes.GUILD_ROLE(this.id, roleId), + { + name: options.name, + color: options.color, + hoist: options.hoist, + mentionable: options.mentionable, + }, + ); + + return new Role(this.session, role, this.id); + } + + async addRole(memberId: Snowflake, roleId: Snowflake, { reason }: { reason?: string } = {}) { + await this.session.rest.runMethod( + this.session.rest, + "PUT", + Routes.GUILD_MEMBER_ROLE(this.id, memberId, roleId), + { reason }, + ); + } + + async removeRole(memberId: Snowflake, roleId: Snowflake, { reason }: { reason?: string } = {}) { + await this.session.rest.runMethod( + this.session.rest, + "DELETE", + Routes.GUILD_MEMBER_ROLE(this.id, memberId, roleId), + { reason }, + ); + } + + /** + * Returns the roles moved + */ + async moveRoles(options: ModifyRolePositions[]) { + const roles = await this.session.rest.runMethod( + this.session.rest, + "PATCH", + Routes.GUILD_ROLES(this.id), + options, + ); + + return roles.map((role) => new Role(this.session, role, this.id)); + } + + async deleteInvite(inviteCode: string): Promise { + await this.session.rest.runMethod( + this.session.rest, + "DELETE", + Routes.INVITE(inviteCode), + {}, + ); + } + + async fetchInvite(inviteCode: string, options: Routes.GetInvite): Promise { + const inviteMetadata = await this.session.rest.runMethod( + this.session.rest, + "GET", + Routes.INVITE(inviteCode, options), + ); + + return new Invite(this.session, inviteMetadata); + } + + async fetchInvites(): Promise { + const invites = await this.session.rest.runMethod( + this.session.rest, + "GET", + Routes.GUILD_INVITES(this.id), + ); + + return invites.map((invite) => new Invite(this.session, invite)); + } + + /** + * Bans the member + */ + async banMember(memberId: Snowflake, options: CreateGuildBan) { + await this.session.rest.runMethod( + this.session.rest, + "PUT", + Routes.GUILD_BAN(this.id, memberId), + options + ? { + delete_message_days: options.deleteMessageDays, + reason: options.reason, + } + : {}, + ); + } + + /** + * Kicks the member + */ + async kickMember(memberId: Snowflake, { reason }: { reason?: string }) { + await this.session.rest.runMethod( + this.session.rest, + "DELETE", + Routes.GUILD_MEMBER(this.id, memberId), + { reason }, + ); + } + + /* + * Unbans the member + * */ + async unbanMember(memberId: Snowflake) { + await this.session.rest.runMethod( + this.session.rest, + "DELETE", + Routes.GUILD_BAN(this.id, memberId), + ); + } + + async editMember(memberId: Snowflake, options: ModifyGuildMember) { + const member = await this.session.rest.runMethod( + this.session.rest, + "PATCH", + Routes.GUILD_MEMBER(this.id, memberId), + { + nick: options.nick, + roles: options.roles, + mute: options.mute, + deaf: options.deaf, + channel_id: options.channelId, + communication_disabled_until: options.communicationDisabledUntil + ? new Date(options.communicationDisabledUntil).toISOString() + : undefined, + }, + ); + + return new Member(this.session, member, this.id); + } + + async pruneMembers(options: BeginGuildPrune): Promise { + const result = await this.session.rest.runMethod<{ pruned: number }>( + this.session.rest, + "POST", + Routes.GUILD_PRUNE(this.id), + { + days: options.days, + compute_prune_count: options.computePruneCount, + include_roles: options.includeRoles, + }, + ); + + return result.pruned; + } + + async getPruneCount(): Promise { + const result = await this.session.rest.runMethod<{ pruned: number }>( + this.session.rest, + "GET", + Routes.GUILD_PRUNE(this.id), + ); + + return result.pruned; + } + + async getActiveThreads() { + const { threads, members } = await this.session.rest.runMethod( + this.session.rest, + "GET", + Routes.THREAD_ACTIVE(this.id), + ); + + return { + threads: Object.fromEntries( + threads.map((thread) => [thread.id, new ThreadChannel(this.session, thread, this.id)]), + ) as Record, + members: Object.fromEntries( + members.map((threadMember) => [threadMember.id, new ThreadMember(this.session, threadMember)]), + ) as Record, + }; + } + + /** * + * Makes the bot leave the guild + */ + async leave() { + } + + /** * + * Deletes a guild + */ + async delete() { + await this.session.rest.runMethod( + this.session.rest, + "DELETE", + Routes.GUILDS(), + ); + } + + /** + * Creates a guild and returns its data, the bot joins the guild + * This was modified from discord.js to make it compatible + * precondition: Bot should be in less than 10 servers + */ + static async create(session: Session, options: GuildCreateOptions) { + const guild = await session.rest.runMethod(session.rest, "POST", Routes.GUILDS(), { + name: options.name, + afk_channel_id: options.afkChannelId, + afk_timeout: options.afkTimeout, + default_message_notifications: options.defaultMessageNotifications, + explicit_content_filter: options.explicitContentFilter, + system_channel_flags: options.systemChannelFlags, + verification_level: options.verificationLevel, + icon: "iconURL" in options + ? options.iconURL || urlToBase64(options.iconURL!) + : options.iconHash || Util.iconBigintToHash(options.iconHash!), + channels: options.channels?.map((channel) => ({ + name: channel.name, + nsfw: channel.nsfw, + id: channel.id, + bitrate: channel.bitrate, + parent_id: channel.parentId, + permission_overwrites: channel.permissionOverwrites, + rtc_region: channel.rtcRegion, + user_limit: channel.userLimit, + video_quality_mode: channel.videoQualityMode, + rate_limit_per_user: channel.rateLimitPerUser, + })), + roles: options.roles?.map((role) => ({ + name: role.name, + id: role.id, + color: role.color, + mentionable: role.mentionable, + hoist: role.hoist, + position: role.position, + unicode_emoji: role.unicodeEmoji, + icon: options.iconURL || urlToBase64(options.iconURL!), + })), + }); + + return new Guild(session, guild); + } + + /** + * Edits a guild and returns its data + */ + async edit(session: Session, options: GuildEditOptions) { + const guild = await session.rest.runMethod(session.rest, "PATCH", Routes.GUILDS(), { + name: options.name, + afk_channel_id: options.afkChannelId, + afk_timeout: options.afkTimeout, + default_message_notifications: options.defaultMessageNotifications, + explicit_content_filter: options.explicitContentFilter, + system_channel_flags: options.systemChannelFlags, + verification_level: options.verificationLevel, + icon: "iconURL" in options + ? options.iconURL || urlToBase64(options.iconURL!) + : options.iconHash || Util.iconBigintToHash(options.iconHash!), + // extra props + splash: "splashURL" in options + ? options.splashURL || urlToBase64(options.splashURL!) + : options.splashHash || Util.iconBigintToHash(options.iconHash!), + banner: "bannerURL" in options ? options.bannerURL || urlToBase64(options.bannerURL!) + : options.bannerHash || Util.iconBigintToHash(options.bannerHash!), + discovery_splash: "discoverySplashURL" in options + ? options.discoverySplashURL || urlToBase64(options.discoverySplashURL!) + : options.discoverySplashHash || Util.iconBigintToHash(options.discoverySplashHash!), + owner_id: options.ownerId, + rules_channel_id: options.rulesChannelId, + public_updates_channel_id: options.publicUpdatesChannelId, + preferred_locale: options.preferredLocale, + features: options.features, + description: options.description, + premiumProgressBarEnabled: options.premiumProgressBarEnabled, + }); + + return new Guild(session, guild); + } +} + +export default Guild \ No newline at end of file