diff --git a/README.md b/README.md index 1f668b577..c133a7bca 100644 --- a/README.md +++ b/README.md @@ -4,4 +4,29 @@ Discord API library wrapper in Deno ## TODO - [Review compression of payloads with GZIP](https://discordapp.com/developers/docs/topics/gateway#sending-payloads-example-gateway-dispatch) -- +- + +## Motivations/Features + +This project began out of the desire to want to learn and enhance my developer skills. As I was building it, I encountered so many issues that other libraries have that I wanted to change in my library. + +- **TYPESCRIPT:** + - First class support for Typescript! +- **SECURITY:** + - Check all permissions necessary before sending a request to the API. + - Prevent supporting self-bots and abusive behavior. +- **Functional API:** + - This will overall make a cleaner and more performant API, while removing the headaches of extending built-in classes, and inheritance. + - Avoid as many headaches and issues related to `class` and `this` + - Avoid EventEmitter to not have potential of memory leaks or bot crashes because of too many listeners or other silly issues. + - Avoid for loops, while loops etc... +- **MINIMALISTIC:** + - Prevent as many "options" for the sake of customizability. Prefer defaults that Discord recommends. +- **DOCUMENTATION:** + - All of Discord API Documentation available inside your VSC while you code. + - The entire libraries documentation is automatically available to you throw intellisense. +- **LATEST AND GREATEST JAVASCRIPT:** + - Backwards compatibility is the death of code. It causes clutter and uglyness to pile up and makes developers lazier. + - There will be no such thing as backwards compatibility reasons in Discordeno. + - We will always support the latest and greatest of JS. The end! + - That said, we don't expect many things to be changing drastically after v1. As you can imagine Typescript allows the latest and greatest of JS so we will be ahead of the curve for years to come. diff --git a/constants/discord.ts b/constants/discord.ts index 0e8b84e8b..62f4c97fb 100644 --- a/constants/discord.ts +++ b/constants/discord.ts @@ -1,22 +1,55 @@ export const baseEndpoints = { - /** Although, the version can be defaulted, keep the v6 as it can be changed to test newer versions when necessary. */ - BASE_URL: "https://discordapp.com/api/v6", - CDN_URL: "https://cdn.discordapp.com", + /** Although, the version can be defaulted, keep the v6 as it can be changed to test newer versions when necessary. */ + BASE_URL: 'https://discordapp.com/api/v6', + CDN_URL: 'https://cdn.discordapp.com' } +const GUILDS_BASE = (id: string) => `${baseEndpoints.BASE_URL}/guilds/${id}` + export const endpoints = { - GATEWAY_BOT: `${baseEndpoints.BASE_URL}/gateway/bot`, - GUILD_AUDIT_LOGS: (id: string) => `${baseEndpoints.BASE_URL}/guilds/${id}/audit-logs`, - GUILD_BANNER: (id: string, icon: string) => `${baseEndpoints.CDN_URL}/banners/${id}/${icon}`, - GUILD_CHANNELS: (id: string) => `${baseEndpoints.BASE_URL}/guilds/${id}/channels`, - GUILD_EMBED: (id: string) => `${baseEndpoints.BASE_URL}/guilds/${id}/embed`, - GUILD_EMOJI: (id: string, emojiID: string) => `${baseEndpoints.BASE_URL}/guilds/${id}/emojis/${emojiID}`, - GUILD_EMOJIS: (id: string) => `${baseEndpoints.BASE_URL}/guilds/${id}/emojis`, - GUILD_ICON: (id: string, icon: string) => `${baseEndpoints.CDN_URL}/icons/${id}/${icon}`, - GUILD_INTEGRATIONS: (id: string) => `${baseEndpoints.BASE_URL}/guilds/${id}/integrations`, - GUILD_PRUNE: (id: string) => `${baseEndpoints.BASE_URL}/guilds/${id}/prune`, - GUILD_ROLE: (id: string, roleID: string) => `${baseEndpoints.BASE_URL}/guilds/${id}/roles/${roleID}`, - GUILD_ROLES: (id: string) => `${baseEndpoints.BASE_URL}/guilds/${id}/roles`, - GUILD_SPLASH: (id: string, icon: string) => `${baseEndpoints.CDN_URL}/splashes/${id}/${icon}`, - GUILD_VANITY_URL: (id: string) => `${baseEndpoints.BASE_URL}/guilds/${id}/vanity-url` + GATEWAY_BOT: `${baseEndpoints.BASE_URL}/gateway/bot`, + + // Channel Endpoints + CHANNEL_MESSAGE: (id: string, message_id: string) => + `${baseEndpoints.BASE_URL}/channels/${id}/messages/${message_id}`, + CHANNEL_MESSAGES: (id: string) => `${baseEndpoints.BASE_URL}/channels/${id}`, + CHANNEL_PINS: (id: string) => `${baseEndpoints.BASE_URL}/channels/${id}/pins`, + CHANNEL_BULK_DELETE: (id: string) => `${baseEndpoints.BASE_URL}/channels/${id}/messages/bulk-delete`, + CHANNEL_INVITES: (id: string) => `${baseEndpoints.BASE_URL}/channels/${id}/invites`, + CHANNEL_WEBHOOKS: (id: string) => `${baseEndpoints.BASE_URL}/channels/${id}/webhooks`, + CHANNEL_MESSAGE_REACTION_ME: (id: string, message_id: string, emoji: string) => `${baseEndpoints.BASE_URL}/channels/${id}/messages/${message_id}/reactions/${emoji}/@me`, + CHANNEL_MESSAGE_REACTIONS: (id: string, message_id: string) => `${baseEndpoints.BASE_URL}/channels/${id}/messages/${message_id}/reactions`, + CHANNEL_MESSAGE_REACTION: (id: string, message_id: string, emoji: string) => `${baseEndpoints.BASE_URL}/channels/${id}/messages/${message_id}/reactions/${emoji}`, + + + // Guild Endpoints + GUILD: (id: string) => `${GUILDS_BASE(id)}`, + GUILD_AUDIT_LOGS: (id: string) => `${GUILDS_BASE(id)}/audit-logs`, + GUILD_BAN: (id: string, user_id: string) => `${GUILDS_BASE(id)}/bans/${user_id}`, + GUILD_BANS: (id: string) => `${GUILDS_BASE(id)}/bans`, + GUILD_BANNER: (id: string, icon: string) => `${baseEndpoints.CDN_URL}/banners/${id}/${icon}`, + GUILD_CHANNELS: (id: string) => `${GUILDS_BASE(id)}/channels`, + GUILD_EMBED: (id: string) => `${GUILDS_BASE(id)}/embed`, + GUILD_EMOJI: (id: string, emoji_id: string) => `${GUILDS_BASE(id)}/emojis/${emoji_id}`, + GUILD_EMOJIS: (id: string) => `${GUILDS_BASE(id)}/emojis`, + GUILD_ICON: (id: string, icon: string) => `${baseEndpoints.CDN_URL}/icons/${id}/${icon}`, + GUILD_INTEGRATION: (id: string, integration_id: string) => `${GUILDS_BASE(id)}/integrations/${integration_id}`, + GUILD_INTEGRATION_SYNC: (id: string, integration_id: string) => `${GUILDS_BASE(id)}/integrations/${integration_id}/sync`, + GUILD_INTEGRATIONS: (id: string) => `${GUILDS_BASE(id)}/integrations`, + GUILD_INVITES: (id: string) => `${GUILDS_BASE(id)}/invites`, + GUILD_LEAVE: (id: string) => `${baseEndpoints.BASE_URL}/users/@me/guilds/${id}`, + GUILD_MEMBER: (id: string, member_id: string) => `${GUILDS_BASE(id)}/members/${member_id}`, + GUILD_MEMBER_ROLE: (id: string, member_id: string, role_id: string) => + `${GUILDS_BASE(id)}/members/${member_id}/roles/${role_id}`, + GUILD_PRUNE: (id: string) => `${GUILDS_BASE(id)}/prune`, + GUILD_REGIONS: (id: string) => `${GUILDS_BASE(id)}/regions`, + GUILD_ROLE: (id: string, role_id: string) => `${GUILDS_BASE(id)}/roles/${role_id}`, + GUILD_ROLES: (id: string) => `${GUILDS_BASE(id)}/roles`, + GUILD_SPLASH: (id: string, icon: string) => `${baseEndpoints.CDN_URL}/splashes/${id}/${icon}`, + GUILD_VANITY_URL: (id: string) => `${GUILDS_BASE(id)}/vanity-url`, + GUILD_WEBHOOKS: (id: string) => `${GUILDS_BASE(id)}/webhooks`, + + // User endpoints + USER_AVATAR: (id: string, icon: string) => `${baseEndpoints.CDN_URL}/avatars/${id}/${icon}`, + USER_DEFAULT_AVATAR: (icon: number) => `${baseEndpoints.CDN_URL}/embed/avatars${icon}.png` } diff --git a/module/client.ts b/module/client.ts index 1d781af15..ba4941926 100644 --- a/module/client.ts +++ b/module/client.ts @@ -6,14 +6,14 @@ import { connectWebSocket, isWebSocketCloseEvent, isWebSocketPingEvent, - isWebSocketPongEvent, - WebSocket + isWebSocketPongEvent } from 'https://deno.land/std/ws/mod.ts' import Gateway from './gateway.ts' import { ClientOptions, FulfilledClientOptions } from '../types/options.ts' import { CollectedMessageType } from '../types/message-type.ts' class Client { + bot_id: string /** The bot's token. This should never be used by end users. It is meant to be used internally to make requests to the Discord API. */ token: string /** The Rate limit manager to handle all outgoing requests to discord. Not meant to be used by users. */ @@ -39,6 +39,7 @@ class Client { }, options ) + this.bot_id = options.bot_id this.token = options.token this.authorization = `Bot ${this.options.token}` this.discordRequestManager = new DiscordRequestManager(this, this.authorization) diff --git a/structures/channel.ts b/structures/channel.ts index 696770aba..2420ae6a3 100644 --- a/structures/channel.ts +++ b/structures/channel.ts @@ -1,3 +1,180 @@ -export const createChannel = (data: unknown) => { - console.log(data) +import { + Channel_Create_Options, + Channel_Types, + Get_Messages_After, + Get_Messages_Around, + Get_Messages, + Get_Messages_Before, + MessageContent, + Create_Invite_Options +} from '../types/channel' +import Client from '../module/client' +import { endpoints } from '../constants/discord' +import { create_message, Message } from './message' +import { Message_Create_Options } from '../types/message' +import { Permission, Permissions } from '../types/permission' +import { Guild } from '../types/guild' + + +export const create_channel = (data: Channel_Create_Options, guild: Guild, client: Client) => { + const base_channel = { + /** The unique id of the channel */ + id: data.id, + /** The type of the channel. */ + type: () => data.type, + /** The id of the guild where this channel exists */ + guild_id: () => data.guild_id + } + + const base_text_channel = { + /** A short collection of recently sent messages since bot started. */ + messages: new Map(), + /** The last message id in this channel */ + last_message_id: () => data.last_message_id, + /** Fetch a single message from the server. Requires VIEW_CHANNEL and READ_MESSAGE_HISTORY */ + get_message: async (id: string) => { + // TODO: check if the user has VIEW_CHANNEL and READ_MESSAGE_HISTORY + const result = await client.RequestManager.get(endpoints.CHANNEL_MESSAGE(data.id, id)) + return create_message(result, client) + }, + /** Fetches between 2-100 messages. Requires VIEW_CHANNEL and READ_MESSAGE_HISTORY */ + get_messages: async (options?: Get_Messages_After | Get_Messages_Before | Get_Messages_Around | Get_Messages) => { + // TODO: check if the user has VIEW_CHANNEL and READ_MESSAGE_HISTORY + if (options?.limit && options.limit > 100) return + + const result = (await client.RequestManager.get( + endpoints.CHANNEL_MESSAGES(data.id), + options + )) as Message_Create_Options[] + return result.map(res => create_message(res, client)) + }, + /** Get pinned messages in this channel. */ + get_pins: async () => { + const result = (await client.RequestManager.get(endpoints.CHANNEL_PINS(data.id))) as Message_Create_Options[] + return result.map(res => create_message(res, client)) + }, + /** Send a message to the channel. Requires SEND_MESSAGES permission. */ + send_message: async (content: string | MessageContent) => { + if (data.type !== Channel_Types.DM) { + // TODO: check if the bot has SEND_MESSAGES permission + } + + if (typeof content === 'string') content = { content } + if (content.tts) { + // TODO: check if the bot has SEND_TTS_MESSAGE + } + + // TODO: Check content length + + const result = await client.RequestManager.post(endpoints.CHANNEL_MESSAGES(data.id), content) + return create_message(result, client) + } + } + + // If it is a dm channel + if (data.type === Channel_Types.DM) + return { + ...base_channel, + ...base_text_channel + } + + // GUILD CHANNEL ONLY + const base_guild_channel = { + ...base_channel, + /** Whether this channel NSFW enabled. */ + nsfw: () => data.nsfw!, + /** The position of the channel in the server. */ + position: () => data.position!, + /** The category id for this channel. */ + parent_id: () => data.parent_id, + // TODO: fix this from being number on allow and deny to being array of strings + /** Fetch the permission overwrites */ + permission_overwrites: () => data.permission_overwrites, + /** Check whether a member has certain permissions in this channel. */ + has_permissions: (id: string, permissions: Permission[]) => { + if (id === guild.owner_id()) return true + + const member = guild.members.get(id) + if (!member) + throw 'Invalid member id provided. This member was not found in the cache. Please fetch them with getMember on guild.' + + let permissionBits = member.roles().reduce((bits, role_id) => { + const role = guild.roles.get(role_id) + if (!role) return bits + + bits |= role.permissions() + + return bits + }, 0) + + data.permission_overwrites?.forEach(overwrite => { + permissionBits = (permissionBits & ~overwrite.deny) | overwrite.allow + }) + + return permissions.every(permission => permissionBits & Permissions[permission]) + } + } + + // Guild Text Channel + if ([Channel_Types.GUILD_TEXT, Channel_Types.GUILD_NEWS].includes(data.type)) + return { + ...base_guild_channel, + ...base_text_channel, + /** The topic of the channel */ + topic: () => data.topic, + /** The mention of the channel */ + mention: () => `<#${data.id}>`, + /** Delete messages from the channel. 2-100. Requires the MANAGE_MESSAGES permission */ + delete_messages: (ids: string[], reason?: string) => { + // TODO: Requires the MANAGE_MESSAGES permission. + if (ids.length < 2) throw 'This endpoint will only accept 2-100 message ids.' + if (ids.length > 100) + console.warn( + `This endpoint only accepts a maximum of 100 messages. Deleting the first 100 message ids provided.` + ) + return client.RequestManager.POST(endpoints.CHANNEL_BULK_DELETE(data.id), { + messages: ids.splice(0, 100), + reason + }) + }, + /** Gets the invites for this channel. Requires MANAGE_CHANNEL */ + get_invites: () => { + // TODO: Requires the MANAGE_CHANNELS permission + return client.RequestManager.get(endpoints.CHANNEL_INVITES(data.id)) + }, + /** Creates a new invite for this channel. Requires CREATE_INSTANT_INVITE */ + create_invite: (options: Create_Invite_Options) => { + // TODO: Requires CREATE_INSTANT_INVITE permissin. + return client.RequestManager.post(endpoints.CHANNEL_INVITES(data.id), options) + }, + /** Gets the webhooks for this channel. Requires MANAGE_WEBHOOKS */ + get_webhooks: () => { + // TODO: Requires MANAGE_WEBHOOKS + return client.RequestManager.get(endpoints.CHANNEL_WEBHOOKS(data.id)) + } + } + + if (data.type === Channel_Types.GUILD_CATEGORY) + return { + ...base_guild_channel, + /** Gets an array of all the channels ids that are the children of this category. */ + children_ids: () => + Object.keys(guild.channels).filter(channel => guild.channels.get(channel).parent_id === data.id) + } + + if (data.type === Channel_Types.GUILD_VOICE) + return { + ...base_guild_channel, + // TODO: after learning opus and stuff + /** Join a voice channel. */ + join: () => {}, + /** Leave a voice channel */ + leave: () => {} + } + + return { + ...data, + /** The channel mention */ + mention: () => `<#${data.id}>` + } } diff --git a/structures/guild.ts b/structures/guild.ts index 5281e7219..c7437cde4 100644 --- a/structures/guild.ts +++ b/structures/guild.ts @@ -1,497 +1,102 @@ -import Client from '../module/client' -import { endpoints } from '../constants/discord' -import { formatImageURL } from '../utils/cdn' -import { create_role } from './role' -import { create_emoji } from './emoji' -import { createVoiceState } from './voiceState' -import { createMember } from './member' +import Client from '../module/client.ts' +import { endpoints } from '../constants/discord.ts' +import { format_image_url } from '../utils/cdn.ts' +import { + Create_Guild_Payload, + ChannelTypes, + PrunePayload, + Position_Swap, + Get_Audit_Logs_Options, + Edit_Integration_Options, + BanOptions, + Guild_Edit_Options, + Create_Emojis_Options, + Edit_Emojis_Options, + Create_Role_Options +} from '../types/guild' +import { create_role, Role } from './role.ts' +import { create_member } from './member' import { create_channel } from './channel' -import { createPresence } from './presence' +import { Channel_Create_Options, Channel } from '../types/channel' +import { Image_Size, Image_Formats } from '../types/cdn' +import { Permissions } from '../types/permission' +import { Member } from '../types/member' -interface CreateGuildPayload { - /** The guild id */ - id: string - /** The guild name 2-100 characters */ - name: string - /** The guild icon image hash */ - icon: string | null - /** The guild splash image hash */ - splash: string | null - /** The id of the owner */ - owner_id: string - /** The voice region id for the guild */ - region: string - /** The afk channel id */ - afk_channel_id: string | null - /** AFK Timeout in seconds. */ - afk_timeout: number - /** Whether this guild is embeddable (widget) */ - embed_enabled?: boolean - /** If not null, the channel id that the widge will generate an invite to. */ - embed_channel_id?: string | null - /** The verification level required for the guild */ - verification_level: number - /** The roles in the guild */ - roles: Role[] - /** The custom guild emojis */ - emojis: Emoji[] - /** Enabled guild features */ - features: GuildFeatures[] - /** Required MFA level for the guild */ - mfa_level: number - /** The id of the channel to which system mesages are sent */ - system_channel_id: string | null - /** When this guild was joined at */ - joined_at: string - /** Whether this is considered a large guild */ - large: boolean - /** Whether this guild is unavailable */ - unavailable: boolean - /** Total number of members in this guild */ - member_count: number - voice_states: VoiceState[] - /** Users in the guild */ - members: Member[] - /** Channels in the guild */ - channels: Channel[] - presences: Presence[] - /** The maximum amount of presences for the guild(the default value, currently 5000 is in effect when null is returned.) */ - max_presences?: number | null - /** The maximum amount of members for the guild */ - max_members?: number - /** The vanity url code for the guild */ - vanity_url_code: string | null - /** The description for the guild */ - description: string | null - /** The banner hash */ - banner: string | null - /** The premium tier */ - premium_tier: number - /** The total number of users currently boosting this server. */ - premium_subscription_count: number - /** The preferred local of this guild only set if guild has the DISCOVERABLE feature, defaults to en-US */ - preferred_locale: string -} - -interface Guild { - /** The guild id */ - id: string - /** The guild name 2-100 characters */ - name: string - /** The guild icon image hash */ - icon: string | null - /** The guild splash image hash */ - splash: string | null - /** The id of the owner */ - owner_id: string - /** The voice region id for the guild */ - region: string - /** The afk channel id */ - afk_channel_id: string | null - /** AFK Timeout in seconds. */ - afk_timeout: number - /** The verification level required for the guild */ - verification_level: number - /** The roles in the guild */ - roles: Role[] - /** The custom guild emojis */ - emojis: Emoji[] - /** Enabled guild features */ - features: GuildFeatures[] - /** Required MFA level for the guild */ - mfa_level: number - /** The id of the channel to which system mesages are sent */ - system_channel_id: string | null - /** When this guild was joined at */ - joined_at: number - /** Whether this is considered a large guild */ - large: boolean - /** Whether this guild is unavailable */ - unavailable: boolean - /** Total number of members in this guild */ - member_count: number - voice_states: VoiceState[] - /** Users in the guild */ - members: Member[] - /** Channels in the guild */ - channels: Channel[] - presences: Presence[] - /** The maximum amount of presences for the guild(the default value, currently 5000 is in effect when null is returned.) */ - max_presences?: number | null - /** The maximum amount of members for the guild */ - max_members?: number - /** The vanity url code for the guild */ - vanity_url_code: string | null - /** The description for the guild */ - description: string | null - /** The banner hash */ - banner: string | null - /** The premium tier */ - premium_tier: number - /** The total number of users currently boosting this server. */ - premium_subscription_count: number - /** The preferred local of this guild only set if guild has the DISCOVERABLE feature, defaults to en-US */ - preferred_locale: string - /** The full URL of the icon from Discords CDN. Undefined when no icon is set. */ - icon_url(size?: Image_Size, format?: Image_Formats): string | undefined - /** The full URL of the splash from Discords CDN. Undefined if no splash is set. */ - splash_url(size?: Image_Size, format?: Image_Formats): string | undefined - /** The full URL of the banner from Discords CDN. Undefined if no banner is set. */ - banner_url(size?: Image_Size, format?: Image_Formats): string | undefined - /** Create a channel in your server. Bot needs MANAGE_CHANNEL permissions in the server. */ - create_channel(name: string, options: ChannelCreate_Options): Promise - /** Create an emoji in the server. Emojis and animated emojis have a maximum file size of 256kb. Attempting to upload an emoji larger than this limit will fail and return 400 Bad Request and an error message, but not a JSON status code. */ - create_emoji(name: string, image: string, options: Create_Emojis_Options): Promise - /** Modify the given emoji. Requires the MANAGE_EMOJIS permission. */ - edit_emoji(id: string, options: Edit_Emojis_Options): Promise - /** Delete the given emoji. Requires the MANAGE_EMOJIS permission. Returns 204 No Content on success. */ - delete_emoji(id: string, reason?: string): Promise - /** Create a new role for the guild. Requires the MANAGE_ROLES permission. */ - create_role(options: Create_Role_Options): Promise - /** Edi a guild role. Requires the MANAGE_ROLES permission. */ - edit_role(id: string, options: Create_Role_Options): Promise - /** Delete a guild role. Requires the MANAGE_ROLES permission. */ - delete_role(id: string): Promise - /** Check how many members would be removed from the server in a prune operation. Requires the KICK_MEMBERS permission */ - get_prune_count(days: number): Promise - /** Begin pruning all members in the given time period */ - prune_members(days: number): Promise - /** Returns the audit logs for the guild. Requires VIEW AUDIT LOGS permission */ - get_audit_logs(options: Get_audit_logsOptions): Promise - /** Returns the guild embed object. Requires the MANAGE_GUILD permission. */ - get_embed(): Promise - /** Modify a guild embed object for the guild. Requires the MANAGE_GUILD permission. */ - edit_embed(enabled: boolean, channel_id?: string | null): Promise - /** Returns the code and uses of the vanity url for this server if it is enabled. Requires the MANAGE_GUILD permission. */ - get_vanity_url(): Promise - /** Returns a list of integrations for the guild. Requires the MANAGE_GUILD permission. */ - get_integrations(): Promise - - leave_voice_channel(): Promise -} - -export interface Guild_Integration { - /** The integrations unique id */ - id: string - /** the integrations name */ - name: string - /** The integration type like twitch, youtube etc */ - type: string - /** Is this integration enabled */ - enabled: boolean - /** is this integration syncing */ - syncing: boolean - /** id that this integration uses for "subscribers" */ - role_id: string - /** The behavior of expiring subscribers */ - expire_behavior: number - /** The grace period before expiring subscribers */ - expire_grace_period: number - /** The user for this integration */ - user: User_Data - /** The integration account information */ - account: Account - /** When this integration was last synced */ - synced_at: string -} - -export interface User_Data { - /** The user's id */ - id: string - /** the user's username, not unique across the platform */ - username: string - /** The user's 4 digit discord tag */ - discriminator: string - /** The user's avatar hash */ - avatar: string | null - /** Whether the user is a bot */ - bot?: boolean - /** Whether the user is an official discord system user (part of the urgent message system.) */ - system?: boolean - /** Whether the user has two factor enabled on their account */ - mfa_enabled?: boolean - /** the user's chosen language option */ - locale?: string - /** Whether the email on this account has been verified */ - verified?: boolean - /** The user's email */ - email?: string - /** The flags on a user's account. */ - flags?: number - /** The type of Nitro subscription on a user's account. */ - premium_type?: number -} - -export enum User_Flags { - NONE, - DISCORD_EMPLOYEE, - DISCORD_PARTNER, - HYPE_SQUAD_EVENTS = 1 << 2, - BUG_HUNTER = 1 << 3, - HOUSE_BRAVERY = 1 << 6, - HOUSE_BRILLIANCE = 1 << 7, - HOUSE_BALANCE = 1 << 8, - EARLY_SUPPORTER = 1 << 9, - TEAM_USER = 1 << 10, - SYSTEM = 1 << 12 -} - -export enum Nitro_Types { - NITRO_CLASSIC = 1, - NITRO -} - -export interface Vanity_Invite { - code: string | null - uses: number -} - -export interface Guild_Embed { - /** Whether the embed is enbaled. */ - enabled: boolean -} - -export interface Get_audit_logsOptions { - /** Filter the logs for actions made by this user. */ - user_id?: string - /** The type of audit log. */ - action_type?: AuditLogType - /** Filter the logs before a certain log entry. */ - before?: string - /** How many entries are returned. Between 1-100. Default 50. */ - limit?: number -} - -export type AuditLogType = - | `GUILD_UPDATE` - | `CHANNEL_CREATE` - | `CHANNEL_UPDATE` - | `CHANNEL_DELETE` - | `CHANNEL_OVERWRITE_CREATE` - | `CHANNEL_OVERWRITE_UPDATE` - | `CHANNEL_OVERWRITE_DELETE` - | `MEMBER_KICK` - | `MEMBER_PRUNE` - | `MEMBER_BAN_ADD` - | `MEMBER_BAN_REMOVE` - | `MEMBER_UPDATE` - | `MEMBER_ROLE_UPDATE` - | `MEMBER_MOVE` - | `MEMBER_DISCONNECT` - | `BOT_ADD` - | `ROLE_CREATE` - | `ROLE_UPDATE` - | `ROLE_DELETE` - | `INVITE_CREATE` - | `INVITE_UPDATE` - | `INVITE_DELETE` - | `WEBHOOK_CREATE` - | `WEBHOOK_UPDATE` - | `WEBHOOK_DELETE` - | `EMOJI_CREATE` - | `EMOJI_UPDATE` - | `EMOJI_DELETE` - | `MESSAGE_DELETE` - | `MESSAGE_BULK_DELETE` - | `MESSAGE_PIN` - | `MESSAGE_UNPIN` - | `INTEGRATION_CREATE` - | `INTEGRATION_UPDATE` - | `INTEGRATION_DELETE` - -export enum AuditLogs { - GUILD_UPDATE = 1, - CHANNEL_CREATE = 10, - CHANNEL_UPDATE, - CHANNEL_DELETE, - CHANNEL_OVERWRITE_CREATE, - CHANNEL_OVERWRITE_UPDATE, - CHANNEL_OVERWRITE_DELETE, - MEMBER_KICK = 20, - MEMBER_PRUNE, - MEMBER_BAN_ADD, - MEMBER_BAN_REMOVE, - MEMBER_UPDATE, - MEMBER_ROLE_UPDATE, - MEMBER_MOVE, - MEMBER_DISCONNECT, - BOT_ADD, - ROLE_CREATE = 30, - ROLE_UPDATE, - ROLE_DELETE, - INVITE_CREATE = 40, - INVITE_UPDATE, - INVITE_DELETE, - WEBHOOK_CREATE = 50, - WEBHOOK_UPDATE, - WEBHOOK_DELETE, - EMOJI_CREATE = 60, - EMOJI_UPDATE, - EMOJI_DELETE, - MESSAGE_DELETE = 72, - MESSAGE_BULK_DELETE, - MESSAGE_PIN, - MESSAGE_UNPIN, - INTEGRATION_CREATE = 80, - INTEGRATION_UPDATE, - INTEGRATION_DELETE -} - -export type Image_Size = 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 -export type Image_Formats = 'jpg' | 'jpeg' | 'png' | 'webp' | 'gif' -export type ChannelType = 'text' | 'dm' | 'news' | 'voice' | 'category' | 'store' - -export type Permission = - | `CREATE_INSTANT_INVITE` - | `KICK_MEMBERS` - | `BAN_MEMBERS` - | `ADMINISTRATOR` - | `MANAGE_CHANNELS` - | `MANAGE_GUILD` - | `ADD_REACTIONS` - | `VIEW_AUDIT_LOG` - | `VIEW_CHANNEL` - | `SEND_MESSAGES` - | `SEND_TTS_MESSAGES` - | `MANAGE_MESSAGES` - | `EMBED_LINKS` - | `ATTACH_FILES` - | `READ_MESSAGE_HISTORY` - | `MENTION_EVERYONE` - | `USE_EXTERNAL_EMOJIS` - | `CONNECT` - | `SPEAK` - | `MUTE_MEMBERS` - | `DEAFEN_MEMBERS` - | `MOVE_MEMBERS` - | `USE_VAD` - | `PRIORITY_SPEAKER` - | `STREAM` - | `CHANGE_NICKNAME` - | `MANAGE_NICKNAMES` - | `MANAGE_ROLES` - | `MANAGE_WEBHOOKS` - | `MANAGE_EMOJIS` - -export interface Overwrite { - /** The role or user id */ - id: string - /** Whether this is a role or a member */ - type: 'role' | 'member' - /** The permissions that this id is allowed to do. (This will mark it as a green check.) */ - allow: Permission[] - /** The permissions that this id is NOT allowed to do. (This will mark it as a red x.) */ - deny: Permission[] -} - -export enum ChannelTypes { - text, - dm, - voice, - category = 4, - news, - store -} - -export enum Permissions { - CREATE_INSTANT_INVITE = 0x00000001, - KICK_MEMBERS = 0x00000002, - BAN_MEMBERS = 0x00000004, - ADMINISTRATOR = 0x00000008, - MANAGE_CHANNELS = 0x00000010, - MANAGE_GUILD = 0x00000020, - ADD_REACTIONS = 0x00000040, - VIEW_AUDIT_LOG = 0x00000080, - VIEW_CHANNEL = 0x00000400, - SEND_MESSAGES = 0x00000800, - SEND_TTS_MESSAGES = 0x00001000, - MANAGE_MESSAGES = 0x00002000, - EMBED_LINKS = 0x00004000, - ATTACH_FILES = 0x00008000, - READ_MESSAGE_HISTORY = 0x00010000, - MENTION_EVERYONE = 0x00020000, - USE_EXTERNAL_EMOJIS = 0x00040000, - CONNECT = 0x00100000, - SPEAK = 0x00200000, - MUTE_MEMBERS = 0x00400000, - DEAFEN_MEMBERS = 0x00800000, - MOVE_MEMBERS = 0x01000000, - USE_VAD = 0x02000000, - PRIORITY_SPEAKER = 0x00000100, - STREAM = 0x00000200, - CHANGE_NICKNAME = 0x04000000, - MANAGE_NICKNAMES = 0x08000000, - MANAGE_ROLES = 0x10000000, - MANAGE_WEBHOOKS = 0x20000000, - MANAGE_EMOJIS = 0x40000000 -} - -export interface ChannelCreate_Options { - /** The type of the channel */ - type?: ChannelType - /** The channel topic. (0-1024 characters) */ - topic?: string - /** The bitrate(in bits) of the voice channel. */ - bitrate?: number - /** The user limit of the voice channel. */ - user_limit?: number - /** The amount of seconds a user has to wait before sending another message. (0-21600 seconds). Bots, as well as users with the permission `manage_messages or manage_channel` are unaffected. */ - rate_limit_per_user?: number - /** The sorting position of the channel */ - position?: number - /** The channel's permission overwrites */ - permission_overwrites?: Overwrite[] - /** The id of the parent category for the channel */ - parent_id?: string - /** Whether the channel is nsfw */ - nsfw?: boolean - /** The reason to add in the Audit Logs. */ - reason?: string -} - -export interface Create_Emojis_Options { - /** The roles for which this emoji will be whitelisted. Only the users with one of these roles can use this emoji. */ - roles: string[] - /** The reason to have in the Audit Logs. */ - reason: string -} - -export interface Edit_Emojis_Options { - /** The name of the emoji */ - name: string - /** The roles for which this emoji will be whitelisted. Only the users with one of these roles can use this emoji. */ - roles: string[] -} - -export interface Create_Role_Options { - name?: string - permissions?: Permission[] - color?: number - hoist?: boolean - mentionable?: boolean -} - -export interface PrunePayload { - pruned: number -} - -export const createGuild = (data: CreateGuildPayload, client: Client) => { - const guild: Guild = { - ...data, - roles: data.roles.map(role => create_role(role)), - emojis: data.emojis.map(emoji => create_emoji(emoji)), - joinedAt: Date.parse(data.joined_at), - voiceStates: data.voice_states.map(voiceState => createVoiceState(voiceState)), - members: data.members.map(member => createMember(member)), - channels: data.channels.map(channel => create_channel(channel)), - presences: data.presences.map(presence => createPresence(presence)), - icon_url: (size, format) => - data.icon ? formatImageURL(endpoints.GUILD_ICON(data.id, data.icon), size, format) : undefined, - splash_url: (size, format) => - data.splash ? formatImageURL(endpoints.GUILD_SPLASH(data.id, data.splash), size, format) : undefined, - banner_url: (size, format) => - data.banner ? formatImageURL(endpoints.GUILD_BANNER(data.id, data.banner), size, format) : undefined, - create_channel: (name, options) => { +export const create_guild = (data: Create_Guild_Payload, client: Client) => { + const guild = { + /** The raw create guild payload data. */ + raw: () => data, + /** The guild id */ + id: () => data.id, + /** The guild name. 2-100 characters */ + name: () => data.name, + /** The guild icon image hash */ + icon: () => data.icon, + /** The guild splash image hash */ + splash: () => data.splash, + /** The id of the guild owner */ + owner_id: () => data.owner_id, + /** The voice region id for the guild */ + region: () => data.region, + /** The afk channel id */ + afk_channel_id: () => data.afk_channel_id, + /** The AFK timeout in seconds */ + afk_timeout: () => data.afk_timeout, + /** The verification level required for the guild */ + verification_level: () => data.verification_level, + /** The roles in the guild */ + roles: new Map(), + /** The custom guild emojis */ + emojis: () => data.emojis, + /** The enabled guild features. */ + features: () => data.features, + /** The required MFA level for the guild. */ + mfa_level: () => data.mfa_level, + /** The id of the channel to which system messages are sent. */ + system_channel_id: () => data.system_channel_id, + /** When this guild was joined at. */ + joined_at: Date.parse(data.joined_at), + /** Whether this is considered a large guild. */ + large: () => data.large, + /** Whether this guild is unavailable */ + unavailable: () => data.unavailable, + /** The total number of members in this guild. */ + member_count: () => data.member_count, + /** The current open voice states in the guild. */ + voice_states: () => data.voice_states, + /** The users in this guild. */ + members: new Map(), + /** The channels in the guild */ + channels: new Map(), + /** The presences of all the users in the guild. */ + presences: new Map(data.presences.map(p => [p.user.id, p])), + /** The maximum amount of presences for the guild(the default value, currently 5000 is in effect when null is returned.) */ + max_presences: () => data.max_presences, + /** The maximum amount of members for the guild */ + max_members: () => data.max_members, + /** The vanity url code for the guild */ + vanity_url_code: () => data.vanity_url_code, + /** The description for the guild */ + description: () => data.description, + /** The banner hash */ + banner: () => data.banner, + /** The current premium tier of the guild */ + premium_tier: () => data.premium_tier, + /** The total number of users currently boosting this server. */ + premium_subscription_count: () => data.premium_subscription_count, + /** The preferred locale of this guild only set if the guild has the DISCOVERABLE feature, defaults to en-US */ + preferred_locale: () => data.preferred_locale, + /** The full URL of the icon from Discords CDN. Undefined when no icon is set. */ + icon_url: (size: Image_Size = 128, format?: Image_Formats) => + data.icon ? format_image_url(endpoints.GUILD_ICON(data.id, data.icon), size, format) : undefined, + /** The full URL of the splash from Discords CDN. Undefined if no splash is set. */ + splash_url: (size: Image_Size = 128, format?: Image_Formats) => + data.splash ? format_image_url(endpoints.GUILD_SPLASH(data.id, data.splash), size, format) : undefined, + /** The full URL of the banner from Discords CDN. Undefined if no banner is set. */ + banner_url: (size: Image_Size = 128, format?: Image_Formats) => + data.banner ? format_image_url(endpoints.GUILD_BANNER(data.id, data.banner), size, format) : undefined, + /** Create a channel in your server. Bot needs MANAGE_CHANNEL permissions in the server. */ + create_channel: (name: string, options: Channel_Create_Options) => { // TODO: Check if the bot has `MANAGE_CHANNELS` permission before making a channel return client.RequestManager.post(endpoints.GUILD_CHANNELS(data.id), { name, @@ -506,7 +111,27 @@ export const createGuild = (data: CreateGuildPayload, client: Client) => { ...options }) }, - create_emoji: (name, image, options) => { + /** Returns a list of guild channel objects. + * + * ⚠️ **If you need this, you are probably doing something wrong. This is not intended for use. Your channels will be cached in your guild.** + */ + get_channels: () => { + return client.RequestManager.get(endpoints.GUILD_CHANNELS(data.id)) + }, + /** Modify the positions of channels on the guild. Requires MANAGE_CHANNELS permisison. */ + swap_channels: (channel_positions: Position_Swap[]) => { + if (channel_positions.length < 2) throw 'You must provide atleast two channels to be swapped.' + return client.RequestManager.patch(endpoints.GUILD_CHANNELS(data.id), channel_positions) + }, + /** Returns a guild member object for the specified user. + * + * ⚠️ **If you need this, you are probably doing something wrong. This is not intended for use. Your members will be cached in your guild.** + */ + get_member: (id: string) => { + return client.RequestManager.get(endpoints.GUILD_MEMBER(data.id, id)) + }, + /** Create an emoji in the server. Emojis and animated emojis have a maximum file size of 256kb. Attempting to upload an emoji larger than this limit will fail and return 400 Bad Request and an error message, but not a JSON status code. */ + create_emoji: (name: string, image: string, options: Create_Emojis_Options) => { // TODO: Check if the bot has `MANAGE_EMOJIS` permission return client.RequestManager.post(endpoints.GUILD_EMOJIS(data.id), { ...options, @@ -514,18 +139,21 @@ export const createGuild = (data: CreateGuildPayload, client: Client) => { image }) }, - edit_emoji: (id, options) => { + /** Modify the given emoji. Requires the MANAGE_EMOJIS permission. */ + edit_emoji: (id: string, options: Edit_Emojis_Options) => { // TODO: check if the bot has `MANAGE_EMOJIS` permission return client.RequestManager.patch(endpoints.GUILD_EMOJI(data.id, id), { name: options.name, roles: options.roles }) }, - delete_emoji: (id, reason) => { + /** Delete the given emoji. Requires the MANAGE_EMOJIS permission. Returns 204 No Content on success. */ + delete_emoji: (id: string, reason?: string) => { // TODO: check if the bot has `MANAGE_EMOJIS` permission return client.RequestManager.delete(endpoints.GUILD_EMOJI(data.id, id), { reason }) }, - create_role: async options => { + /** Create a new role for the guild. Requires the MANAGE_ROLES permission. */ + create_role: async (options: Create_Role_Options) => { // TODO: check if the bot has the `MANAGE_ROLES` permission. const role = await client.RequestManager.post(endpoints.GUILD_ROLES(data.id), { ...options, @@ -535,50 +163,129 @@ export const createGuild = (data: CreateGuildPayload, client: Client) => { return role }, - edit_role: (id, options) => { + /** Edit a guild role. Requires the MANAGE_ROLES permission. */ + edit_role: (id: string, options: Create_Role_Options) => { + // TODO: check if the bot has the `MANAGE_ROLES` permission. return client.RequestManager.patch(endpoints.GUILD_ROLE(data.id, id), options) }, - delete_role: id => { + /** Delete a guild role. Requires the MANAGE_ROLES permission. */ + delete_role: (id: string) => { + // TODO: check if the bot has the `MANAGE_ROLES` permission. return client.RequestManager.delete(endpoints.GUILD_ROLE(data.id, id)) }, - get_prune_count: async days => { + /** Returns a list of role objects for the guild. + * + * ⚠️ **If you need this, you are probably doing something wrong. This is not intended for use. Your roles will be cached in your guild.** + */ + get_roles: () => { + // TODO: check if the bot has the `MANAGE_ROLES` permission. + return client.RequestManager.get(endpoints.GUILD_ROLES(data.id)) + }, + /** Modify the positions of a set of role objects for the guild. Requires the MANAGE_ROLES permission. */ + swap_roles: (rolePositons: Position_Swap) => { + // TODO: check if the bot has the `MANAGE_ROLES` permission. + return client.RequestManager.patch(endpoints.GUILD_ROLES(data.id), rolePositons) + }, + /** Check how many members would be removed from the server in a prune operation. Requires the KICK_MEMBERS permission */ + get_prune_count: async (days: number) => { if (days < 1) throw `The number of days to count prune for must be 1 or more.` // TODO: check if the bot has `KICK_MEMBERS` permission const result = (await client.RequestManager.get(endpoints.GUILD_PRUNE(data.id), { days })) as PrunePayload return result.pruned }, - prune_members: days => { + /** Begin pruning all members in the given time period */ + prune_members: (days: number) => { if (days < 1) throw `The number of days must be 1 or more.` // TODO: check if the bot has `KICK_MEMBERS` permission. return client.RequestManager.post(endpoints.GUILD_PRUNE(data.id), { days }) }, - fetchAllMembers: () => { - // TODO: REQUEST THIS OVER WEBSOCKET WITH GET_GUILD_MEMBERS ENDPOINT - }, - get_audit_logs: options => { + // TODO: REQUEST THIS OVER WEBSOCKET WITH GET_GUILD_MEMBERS ENDPOINT + // fetch_all_members: () => { + // }, + /** Returns the audit logs for the guild. Requires VIEW AUDIT LOGS permission */ + get_audit_logs: (options: Get_Audit_Logs_Options) => { // TODO: check if the bot has VIEW_AUDIT_LOGS permission return client.RequestManager.get(endpoints.GUILD_AUDIT_LOGS(data.id), { ...options, limit: options.limit && options.limit >= 1 && options.limit <= 100 ? options.limit : 50 }) }, + /** Returns the guild embed object. Requires the MANAGE_GUILD permission. */ get_embed: () => { // TODO: check if the bot has the MANAGE_GUILD permission return client.RequestManager.get(endpoints.GUILD_EMBED(data.id)) }, - edit_embed: (enabled, channel_id) => { + /** Modify a guild embed object for the guild. Requires the MANAGE_GUILD permission. */ + edit_embed: (enabled: boolean, channel_id?: string | null) => { // TODO: Requires the MANAGE_GUILD permission. return client.RequestManager.patch(endpoints.GUILD_EMBED(data.id), { enabled, channel_id }) }, + /** Returns the code and uses of the vanity url for this server if it is enabled. Requires the MANAGE_GUILD permission. */ get_vanity_url: () => { return client.RequestManager.get(endpoints.GUILD_VANITY_URL(data.id)) }, + /** Returns a list of integrations for the guild. Requires the MANAGE_GUILD permission. */ get_integrations: () => { // TODO: requires the MANAGE_GUILD permission return client.RequestManager.get(endpoints.GUILD_INTEGRATIONS(data.id)) }, - leave_voice_channel: () => {} + /** Modify the behavior and settings of an integration object for the guild. Requires the MANAGE_GUILD permission. */ + edit_integration: (id: string, options: Edit_Integration_Options) => { + // TODO: requires the MANAGE_GUILD permission + return client.RequestManager.patch(endpoints.GUILD_INTEGRATION(data.id, id), options) + }, + /** Delete the attached integration object for the guild with this id. Requires MANAGE_GUILD permission. */ + delete_integration: (id: string) => { + // TODO: requires the MANAGE_GUILD permission + return client.RequestManager.delete(endpoints.GUILD_INTEGRATION(data.id, id)) + }, + /** Sync an integration. Requires teh MANAGE_GUILD permission. */ + sync_integration: (id: string) => { + // TODO: requires MANAGE_GUILD + return client.RequestManager.post(endpoints.GUILD_INTEGRATION_SYNC(data.id, id)) + }, + /** Returns a list of ban objects for the users banned from this guild. Requires the BAN_MEMBERS permission. */ + get_bans: () => { + // TODO: requires the BAN_MEMBERS permission + return client.RequestManager.get(endpoints.GUILD_BANS(data.id)) + }, + /** Ban a user from the guild and optionally delete previous messages sent by the user. Requires teh BAN_MEMBERS permission. */ + ban: (id: string, options: BanOptions) => { + // TODO: requires the BAN_MEMBERS permission + return client.RequestManager.put(endpoints.GUILD_BAN(data.id, id), options) + }, + /** Remove the ban for a user. REquires BAN_MEMBERS permission */ + unban: (id: string) => { + // TODO: requires the BAN_MEMBERS permission + return client.RequestManager.delete(endpoints.GUILD_BAN(data.id, id)) + }, + /** Modify a guilds settings. Requires the MANAGE_GUILD permission. */ + edit: (options: Guild_Edit_Options) => { + // TODO: requires the MANAGE_GUILD permission + return client.RequestManager.patch(endpoints.GUILD(data.id), options) + }, + /** Get all the invites for this guild. Requires MANAGE_GUILD permission */ + et_invites: () => { + // TODO: requires MANAGE_GUILD permission + return client.RequestManager.get(endpoints.GUILD_INVITES(data.id)) + }, + /** Leave a guild */ + leave: () => { + return client.RequestManager.delete(endpoints.GUILD_LEAVE(data.id)) + }, + /** Returns a list of voice region objects for the guild. Unlike the similar /voice route, this returns VIP servers when the guild is VIP-enabled. */ + get_voice_regions: () => { + return client.RequestManager.get(endpoints.GUILD_REGIONS(data.id)) + }, + /** Returns a list of guild webhooks objects. Requires the MANAGE_WEBHOOKs permission. */ + get_webhooks: () => { + // TODO: requires MANAGE_WEBHOOKS + return client.RequestManager.get(endpoints.GUILD_WEBHOOKS(data.id)) + } } + data.roles.forEach(r => guild.roles.set(r.id, create_role(r))) + data.members.forEach(m => guild.members.set(m.user.id, create_member(m, data.id, client))) + data.channels.forEach(c => guild.channels.set(c.id, create_channel(c, guild, client))) return guild } diff --git a/structures/member.ts b/structures/member.ts index 49aee74dd..c8c9dfc10 100644 --- a/structures/member.ts +++ b/structures/member.ts @@ -1,3 +1,100 @@ -export const createMember = (data: unknown) => { - console.log(data) +import Client from '../module/client' +import { endpoints } from '../constants/discord' +import { format_image_url } from '../utils/cdn' +import { Member_Create_Payload, Edit_Member_Options } from '../types/member' +import { Image_Size, Image_Formats } from '../types/cdn' +import { Permission, Permissions } from '../types/permission' +import { cache } from '../utils/cache' + +export const create_member = (data: Member_Create_Payload, guild_id: string, client: Client) => { + const guild = cache.guilds.get(guild_id) + + return { + /** The complete raw data from the member create payload */ + raw: () => data, + /** The unique user id */ + id: () => data.user.id, + /** The user's guild nickname if one is set. */ + roles: () => data.roles, + /** Array of role ids that the member has */ + nick: () => data.nick, + /** When the user joined the guild. */ + joined_at: () => Date.parse(data.joined_at), + /** When the user used their nitro boost on the server. */ + premium_since: () => (data.premium_since ? Date.parse(data.premium_since) : undefined), + /** Whether the user is deafened in voice channels */ + deaf: () => data.deaf, + /** Whether the user is muted in voice channels */ + mute: () => data.mute, + /** The username of the this member. */ + username: () => data.user.username, + /** The 4 digit unique identifier */ + discriminator: () => data.user.discriminator, + /** The full username#discriminator */ + tag: () => `${data.user.username}#${data.user.discriminator}`, + /** The users custom avatar or the default avatar */ + avatar_url: (size: Image_Size = 128, format?: Image_Formats) => + data.user.avatar + ? format_image_url(endpoints.USER_AVATAR(data.user.id, data.user.avatar), size, format) + : endpoints.USER_DEFAULT_AVATAR(Number(data.user.discriminator) % 5), + /** The user mention with nickname if possible */ + mention: () => `<@!${data.user.id}>`, + /** Whether the member is a bot */ + bot: () => data.user.bot, + /** Add a role to the member */ + add_role: (role_id: string, reason?: string) => { + // TODO: check if the bot has MANAGE_ROLE and its highest role is above this one + return client.RequestManager.put(endpoints.GUILD_MEMBER_ROLE(guild_id, data.user.id, role_id), { reason }) + }, + /** Remove a role from the member */ + remove_role: (role_id: string, reason?: string) => { + // TODO: check if the bot has MANAGE_ROLE permissions and its highest role is above this role + return client.RequestManager.delete(endpoints.GUILD_MEMBER_ROLE(guild_id, data.user.id, role_id), { reason }) + }, + /** Kick a member from the server */ + kick: (reason?: string) => { + // TODO: check if bot has KICK_MEMBER permissions + return client.RequestManager.delete(endpoints.GUILD_MEMBER(guild_id, data.user.id), { reason }) + }, + /** Ban a member from the server */ + ban: (delete_message_days = 0, reason?: string) => { + return guild.ban(data.user.id, { delete_message_days, reason }) + }, + /** Checks if the member has this permission. If the member is an owner or has admin perms it will always be true. */ + has_permissions: (permissions: Permission[]) => { + if (data.user.id === guild.owner_id) return true + + const permissionBits = data.roles.reduce((bits, role_id) => { + const role = guild.roles.get(role_id) + if (!role) return bits + + bits |= role.permissions + + return bits + }, 0) + + return permissions.every(permission => permissionBits & Permissions[permission]) + }, + /** Edit the member */ + edit: (options: Edit_Member_Options) => { + // TODO: check if has MANAGE_NICKNAME Permission + // TODO: check if it is a valid nickname like 32 characters + options.nick = undefined + + // TODO: check if has MANAGE_ROLES permission + options.roles = undefined + + // TODO: This should check if the member is in a voice channel + // TODO: CHeck if has MUTE_MEMBERS permission + options.mute = undefined + // TODO: check if has DEAFEN_MEMBERS permission + options.deaf = undefined + // TODO: if channel id is provided check if the bot has CONNECT and MOVE in channel and current channel + options.channel_id = undefined + + return client.RequestManager.patch(endpoints.GUILD_MEMBER(guild_id, data.user.id), options) + } + } } + + diff --git a/structures/message.ts b/structures/message.ts new file mode 100644 index 000000000..19bc03976 --- /dev/null +++ b/structures/message.ts @@ -0,0 +1,110 @@ +import Client from '../module/client' +import { Message_Create_Options } from '../types/message' +import { endpoints } from '../constants/discord' +import { Channel_Types, MessageContent } from '../types/channel' +import { cache } from '../utils/cache' +import { create_user, User_Payload } from './user' + +export const create_message = (data: Message_Create_Options, client: Client) => { + const base_message = { + raw: () => data, + type: () => data.type, + timestamp: () => Date.parse(data.timestamp), + content: () => data.content, + reactions: () => data.reactions || [], + guild_id: () => data.guild_id, + webhook_id: () => data.webhook_id, + mentions_everyone: () => data.mentions_everyone, + mentions: () => data.mentions.map(m => m.member.id), + mention_roles: () => data.mention_roles, + mention_channels: () => data.mention_channels?.map(c => c.id) || [], + pinned: () => data.pinned, + edited_timestamp: () => (data.edited_timestamp ? Date.parse(data.edited_timestamp) : undefined), + tts: () => data.tts, + attachments: () => data.attachments, + embeds: () => data.embeds, + activity: () => data.activity, + applications: () => data.applications, + message_reference: () => ({ + channel_id: data.message_reference?.channel_id, + guild_id: data.message_reference?.guild_id, + message_id: data.message_reference?.message_id + }), + flags: () => data.flags || 0, + channel_id: () => data.channel_id, + channel: () => cache.channels.get(data.channel_id), + + delete: (reason: string) => { + // TODO: Requires MANAGE_MESSAGES + if (data.author.id !== client.bot_id) {} + + client.RequestManager.delete(endpoints.CHANNEL_MESSAGE(data.channel_id, data.id), { reason }) + }, + /** Pin a message in a channel. Requires MANAGE_MESSAGES. Max pins allowed in a channel = 50. */ + pin: () => { + // TODO: Requires MANAGE_MESSAGES + client.RequestManager.put(endpoints.CHANNEL_MESSAGE(data.channel_id, data.id)) + }, + unpin: () => { + // TODO: Requires MANAGE_MESSAGES + client.RequestManager.delete(endpoints.CHANNEL_MESSAGE(data.channel_id, data.id)) + }, + /** Create a reaction for the message. Reaction takes the form of **name:id** for custom guild emoji, or Unicode characters. Requires READ_MESSAGE_HISTORY and ADD_REACTIONS */ + add_reaction: (reaction: string) => { + client.RequestManager.put(endpoints.CHANNEL_MESSAGE_REACTION_ME(data.channel_id, data.id, reaction)) + }, + /** Removes a reaction from the bot on this message. Reaction takes the form of **name:id** for custom guild emoji, or Unicode characters. */ + remove_reaction: (reaction: string) => { + client.RequestManager.delete(endpoints.CHANNEL_MESSAGE_REACTION_ME(data.channel_id, data.id, reaction)) + }, + /** Removes all reactions for all emojis on this message. */ + remove_all_reactions: () => { + // TODO: Requires MANAGE_MESSAGES + client.RequestManager.delete(endpoints.CHANNEL_MESSAGE_REACTIONS(data.channel_id, data.id)) + }, + /** Removes all reactions for a single emoji on this message. Reaction takes the form of **name:id** for custom guild emoji, or Unicode characters. */ + remove_reaction_emoji: (reaction: string) => { + // TODO: Requires MANAGE_MESSAGES + client.RequestManager.delete(endpoints.CHANNEL_MESSAGE_REACTION(data.channel_id, data.id, reaction)) + }, + /** Get a list of users that reacted with this emoji. */ + get_reactions: async (reaction: string) => { + const result = await client.RequestManager.get(endpoints.CHANNEL_MESSAGE_REACTION(data.channel_id, data.id, reaction)) as User_Payload[] + return result.map(res => create_user(res)) + }, + /** Edit the message. */ + edit: async (content: string | MessageContent) => { + if (data.author.id !== client.bot_id) throw 'You can only edit a message that was sent by the bot.' + if (data.type !== Channel_Types.DM) { + // TODO: check if the bot has SEND_MESSAGES permission + } + + if (typeof content === 'string') content = { content } + if (content.tts) { + // TODO: check if the bot has SEND_TTS_MESSAGE + } + + // TODO: Check content length + + const result = await client.RequestManager.patch(endpoints.CHANNEL_MESSAGES(data.id), content) + return create_message(result, client) + } + } + + if (!data.guild_id) { + return { + ...base_message, + author: create_user({ ...data.author, avatar: data.author.avatar || '' }) + } + } + + const guild = cache.guilds.get(data.guild_id) + + return { + ...base_message, + guild: () => guild, + member: () => data.member + } +} + +export type Message = ReturnType diff --git a/structures/role.ts b/structures/role.ts index e10ad2b2e..d2e217b6a 100644 --- a/structures/role.ts +++ b/structures/role.ts @@ -1,3 +1,26 @@ -export const createRole = (data: unknown) => { - console.log(data) -} +import { Role_Data } from '../types/role' + +export const create_role = (data: Role_Data) => ({ + /** The entire raw Role data */ + raw: () => data, + /** role id */ + id: () => data.id, + /** role name */ + name: () => data.name, + /** integer representation of hexadecimal color code */ + color: () => data.color, + /** if this role is pinned in the user listing */ + hoist: () => data.hoist, + /** position of this role */ + position: () => data.position, + /** permission bit set */ + permissions: () => data.permissions, + /** whether this role is managed by an integration */ + managed: () => data.managed, + /** whether this role is mentionable */ + mentionable: () => data.mentionable, + /** The @ mention of the role in a string. */ + mention: () => `<@&${data.id}>` +}) + +export type Role = ReturnType diff --git a/structures/user.ts b/structures/user.ts index 3231c3333..4c9e9c01d 100644 --- a/structures/user.ts +++ b/structures/user.ts @@ -1,34 +1,58 @@ -export interface UserPayload { - /** The user's id */ - id: string; +import { format_image_url } from '../utils/cdn' +import { endpoints } from '../constants/discord' +import { Image_Size, Image_Formats } from '../types/cdn' - /** The user's username, not unique across the platform */ - username: string; +export interface User_Payload { + /** The user's id */ + id: string - /** The user's 4-digit discord-tag */ - discriminator: string; + /** The user's username, not unique across the platform */ + username: string - /** The user's avatar hash */ - avatar: string; + /** The user's 4-digit discord-tag */ + discriminator: string - /** Whether the user belongs to an OAuth2 application */ - bot?: boolean; + /** The user's avatar hash */ + avatar: string - /** Whether the user is an Official Discord System user (part of the urgent message system) */ - system?: boolean; + /** Whether the user belongs to an OAuth2 application */ + bot?: boolean - /** Whether the user has two factor enabled on their account */ - mfa_enabled?: boolean; + /** Whether the user is an Official Discord System user (part of the urgent message system) */ + system?: boolean - // Types with "email" scope intentionally left out. - /** The flags on a user's account */ - flags?: number; + /** Whether the user has two factor enabled on their account */ + mfa_enabled?: boolean - /** The type of Nitro subscription on a user's account */ - premium_type?: PremiumType; + // Types with "email" scope intentionally left out. + /** The flags on a user's account */ + flags?: number + + /** The type of Nitro subscription on a user's account */ + premium_type?: PremiumType } export const enum PremiumType { - NitroClassic = 1, - Nitro -} \ No newline at end of file + NitroClassic = 1, + Nitro +} + +export const create_user = (data: User_Payload) => ({ + id: () => data.id, + mention: () => `<@!${data.id}>`, + username: () => data.username, + discriminator: () => data.discriminator, + tag: () => `${data.username}#${data.discriminator}`, + avatar: () => data.avatar, + avatar_url: (size: Image_Size = 128, format?: Image_Formats) => + data.avatar + ? format_image_url(endpoints.USER_AVATAR(data.id, data.avatar), size, format) + : endpoints.USER_DEFAULT_AVATAR(Number(data.discriminator) % 5), + bot: () => data.bot, + system: () => data.system, + mfa_enabled: () => data.mfa_enabled, + flags: () => data.flags, + premium_type: () => data.premium_type +}) + +export type User = ReturnType diff --git a/structures/voiceState.ts b/structures/voiceState.ts deleted file mode 100644 index ef78632d1..000000000 --- a/structures/voiceState.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const createVoiceState = (data: unknown) => { - console.log(data) -} diff --git a/tsconfig.json b/tsconfig.json index 54cdc8971..ee0129cf1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,66 +1,13 @@ { "compilerOptions": { - /* Basic Options */ - // "incremental": true, /* Enable incremental compilation */ - "target": "ESNext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ - "module": "ESNext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - "lib": ["ES7", "DOM"], /* Specify library files to be included in the compilation. */ - // "allowJs": true, /* Allow javascript files to be compiled. */ - // "checkJs": true, /* Report errors in .js files. */ - // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ - // "declaration": true, /* Generates corresponding '.d.ts' file. */ - // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - // "sourceMap": true, /* Generates corresponding '.map' file. */ - // "outFile": "./", /* Concatenate and emit output to single file. */ - // "outDir": "./", /* Redirect output structure to the directory. */ - // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ - // "composite": true, /* Enable project compilation */ - // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ - // "removeComments": true, /* Do not emit comments to output. */ - // "noEmit": true, /* Do not emit outputs. */ - // "importHelpers": true, /* Import emit helpers from 'tslib'. */ - "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ - // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ - - /* Strict Type-Checking Options */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* Enable strict null checks. */ - // "strictFunctionTypes": true, /* Enable strict checking of function types. */ - // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ - // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ - // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ - // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ - - /* Additional Checks */ - "noUnusedLocals": true, /* Report errors on unused locals. */ - "noUnusedParameters": true, /* Report errors on unused parameters. */ - "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - - /* Module Resolution Options */ - // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - // Hack to stop VSCode from suggesting imports without ./ or ../ as a prefix. - "baseUrl": "../../", /* Base directory to resolve non-absolute module names. */ - // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - // "typeRoots": [], /* List of folders to include type definitions from. */ - // "types": [], /* Type declaration files to be included in compilation. */ - // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ - // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - - /* Source Map Options */ - // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ - // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ - - /* Experimental Options */ - "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ - "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ - - /* Advanced Options */ - "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + "target": "ESNext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */, + "module": "ESNext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, + "strict": true /* Enable all strict type-checking options. */, + "noUnusedLocals": true /* Report errors on unused locals. */, + "noUnusedParameters": true /* Report errors on unused parameters. */, + "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, + "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */, + "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, + "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */ } } diff --git a/types/cdn.ts b/types/cdn.ts new file mode 100644 index 000000000..53f878e8e --- /dev/null +++ b/types/cdn.ts @@ -0,0 +1,2 @@ +export type Image_Size = 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 +export type Image_Formats = 'jpg' | 'jpeg' | 'png' | 'webp' | 'gif' diff --git a/types/channel.ts b/types/channel.ts new file mode 100644 index 000000000..5b58c45df --- /dev/null +++ b/types/channel.ts @@ -0,0 +1,108 @@ +import { Raw_Overwrite, Overwrite } from './guild' +import { create_channel } from '../structures/channel' + +export interface Base_Channel_Create { + /** The id of this channel */ + id: string + /** The type of the channel */ + type: Channel_Type + /** The id of the guild */ + guild_id?: string + /** Sorting position of the channel */ + position?: number + /** The name of the channel (2-100 characters) */ + name?: string + /** The channel topic (0-1024 characters) */ + topic?: string + /** Whether the channel is nsfw */ + nsfw?: boolean + /** The id of the last message sent in this channel (may not point to an existing or valid message) */ + last_message_id?: string | null + /** The bitrate (in bits) of the voice channel */ + bitrate?: number + /** The user limit of the voice channel */ + user_limit?: number + /** Amount of seconds a user has to wait before sending another message (0-21600) Bots and users with the permission MANAGE_MESSAGES or MANAGE_CHANNEL are unaffected. */ + rate_limit_per_user?: number + /** The parent category id */ + parent_id?: string | null + /** When the last pinned message was pinned */ + last_pin_timestamp?: string +} + +export interface Channel_Create_Payload extends Base_Channel_Create { + /** Explicit permission overwrites for members and roles */ + permission_overwrites?: Raw_Overwrite[] +} + +export interface Channel_Create_Options extends Base_Channel_Create { + /** Explicit permission overwrites for members and roles */ + permission_overwrites?: Overwrite[] +} + +export type Channel_Type = 0 | 1 | 2 | 4 | 5 | 6 + +export enum Channel_Types { + /** A text channel within a server */ + GUILD_TEXT, + /** A direct message between users */ + DM, + /** A voice channel within a server */ + GUILD_VOICE, + /** A direct message between multiple users. */ + GROUP_DM, + /** An organizational category that contains channels */ + GUILD_CATEGORY, + /** A channel that users can follow and crosspost into their own server. */ + GUILD_NEWS, + /** A channel in which game developers can sell their game on Discord. */ + GUILD_STORE +} + +export interface MessageContent { + /** The message contents, up to 2000 characters */ + content?: string + /** A nonce that can be used for optimistic message sending. */ + nonce?: number | string + /** Whether this is a TextToSpeech message */ + tts?: boolean + /** The contents of the file being sent */ + file?: File_Content + /** Embed object */ + embed?: Embed_Object + /** JSON encoded body of any additional request fields. */ + payload_json?: string +} + +export interface Get_Messages { + /** Max number of messages to return(1-100). Defaults to 50. */ + limit?: number +} + +export interface Get_Messages_After extends Get_Messages { + /** Get messages after this message id */ + after: string +} + +export interface Get_Messages_Before extends Get_Messages { + /** Get messages before this message id */ + before: string +} + +export interface Get_Messages_Around extends Get_Messages { + /** Get messages around this message id. */ + around: string +} + +export interface Create_Invite_Options { + /** Duration of invite in seconds before expiry, or 0 for never. Defaults to 86400 (24 hours) */ + max_age: number + /** Max number of uses or 0 for unlimited. Default 0 */ + max_uses: number + /** Whether this invite only grants temporary membership. */ + temporary: boolean + /** If true, don't try to reuse a similar invite (useful for creating many unique one time use invites.) */ + unique: boolean +} + +export type Channel = ReturnType diff --git a/types/discord.ts b/types/discord.ts index 68750defa..77cd26f71 100644 --- a/types/discord.ts +++ b/types/discord.ts @@ -180,7 +180,9 @@ export enum StatusType { Offline = 'offline' } +export type Status_Type = 'online' | 'dnd' | 'idle' | 'invisible' | 'offline' + export interface Status { afk: boolean; status: StatusType; -} \ No newline at end of file +} diff --git a/types/emoji.ts b/types/emoji.ts new file mode 100644 index 000000000..b717060c9 --- /dev/null +++ b/types/emoji.ts @@ -0,0 +1,18 @@ +import { User } from "../structures/user"; + +export interface Emoji { + /** emoji id. It will be null for default discord emojis. */ + id: string | null + /** The name of the emoji. (can be null only in reaction emoji objects when the custom emoji doesnt exist anymore) */ + name: string | null + /** array of role ids roles this emoji is whitelisted to */ + roles?: string[] + /** User that created this emoji */ + user?: User + /** whether this emoji must be wrapped in colons */ + require_colons?: boolean + /** whether this emoji is managed */ + managed?: boolean + /** whether this emoji is animated */ + animated?: boolean +} diff --git a/types/guild.ts b/types/guild.ts new file mode 100644 index 000000000..b6a2ed87e --- /dev/null +++ b/types/guild.ts @@ -0,0 +1,453 @@ +import { Emoji, StatusType } from './discord' +import { User } from '../structures/user' +import { Permission } from './permission' +import { create_guild } from '../structures/guild' +import { Role_Data } from './role' +import { Member_Create_Payload } from './member' +import { Activity } from './message' +import { ClientStatusPayload } from '../structures/presence' +import { Channel_Create_Options } from './channel' + +export interface Create_Guild_Payload { + /** The guild id */ + id: string + /** The guild name 2-100 characters */ + name: string + /** The guild icon image hash */ + icon: string | null + /** The guild splash image hash */ + splash: string | null + /** The id of the owner */ + owner_id: string + /** The voice region id for the guild */ + region: string + /** The afk channel id */ + afk_channel_id: string | null + /** AFK Timeout in seconds. */ + afk_timeout: number + /** Whether this guild is embeddable (widget) */ + embed_enabled?: boolean + /** If not null, the channel id that the widge will generate an invite to. */ + embed_channel_id?: string | null + /** The verification level required for the guild */ + verification_level: number + /** The roles in the guild */ + roles: Role_Data[] + /** The custom guild emojis */ + emojis: Emoji[] + /** Enabled guild features */ + features: Guild_Features[] + /** Required MFA level for the guild */ + mfa_level: number + /** The id of the channel to which system mesages are sent */ + system_channel_id: string | null + /** When this guild was joined at */ + joined_at: string + /** Whether this is considered a large guild */ + large: boolean + /** Whether this guild is unavailable */ + unavailable: boolean + /** Total number of members in this guild */ + member_count: number + voice_states: Voice_State[] + /** Users in the guild */ + members: Member_Create_Payload[] + /** Channels in the guild */ + channels: Channel_Create_Options[] + presences: Presence[] + /** The maximum amount of presences for the guild(the default value, currently 5000 is in effect when null is returned.) */ + max_presences?: number | null + /** The maximum amount of members for the guild */ + max_members?: number + /** The vanity url code for the guild */ + vanity_url_code: string | null + /** The description for the guild */ + description: string | null + /** The banner hash */ + banner: string | null + /** The premium tier */ + premium_tier: number + /** The total number of users currently boosting this server. */ + premium_subscription_count: number + /** The preferred local of this guild only set if guild has the DISCOVERABLE feature, defaults to en-US */ + preferred_locale: string +} + +export type Guild_Features = + | `INVITE_SPLASH` + | `VIP_REGIONS` + | `VANITY_URL` + | `VERIFIED` + | `PARTNERED` + | `PUBLIC` + | `COMMERCE` + | `NEWS` + | `DISCOVERABLE` + | `FEATURABLE` + | `ANIMATED_ICON` + | `BANNER` + +export interface Voice_Region { + /** unique ID for the region */ + id: string + /** name of the region */ + name: string + /** true if this is a vip-only server */ + vip: boolean + /** true for a single server that is closest to the current user's client */ + optimal: boolean + /** whether this is a deprecated voice region (avoid switching to these) */ + deprecated: boolean + /** whether this is a custom voice region (used for events/etc) */ + custom: boolean +} + +export interface BanOptions { + /** number of days to delete messages for (0-7) */ + delete_message_days?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 + /** The reason for the ban. */ + reason?: string +} + +export interface BannedUser { + /** The reason for the ban */ + reason?: string + /** The banned user object */ + user: User +} + +export interface Position_Swap { + /** The unique id */ + id: string + /** The sorting position number. */ + position: number +} + +export interface Guild_Edit_Options { + /** The guild name */ + name?: string + /** The guild voice region id */ + region?: string + /** The verification level. 0 is UNRESTRICTED. 1 is Verified email. 2 is 5 minutes user. 3 is 10 minutes member in guild. 4 is verified phone number */ + verification_level?: 0 | 1 | 2 | 3 + /** The default message notification level. 0 is ALL_MESSAGES and 1 is ONLY_MENTINS */ + default_message_notifications?: 0 | 1 + /** Explicit content filter level. 0 is DISABLED 1 is members without roles. 2 is all members */ + explicit_content_filter?: 0 | 1 | 2 + /** The id for the afk channel. */ + afk_channel_id?: string + /** The afk timeout in seconds. */ + afk_timeout?: number + /** base64 1024x1024 png/jpeg/gif image for the guild icon (can be animated gif when the server has ANIMATED_ICON feature) */ + icon?: string + /** user id to transfer guild ownership to (must be owner) */ + owner_id?: string + /** base64 16:9 png/jpeg image for the guild splash (when the server has INVITE_SPLASH feature) */ + splash?: string + /** base64 16:9 png/jpeg image for the guild banner (when the server has BANNER feature) */ + banner?: string + /** the id of the channel to which system messages are sent */ + system_channel_id?: string +} + +export interface Edit_Integration_Options { + /** The behavior when an integration subscription lapses. */ + expire_behavior: number + /** The period in seconds where the integration will ignore lapsed subscriptions */ + expire_grace_period: number + /** Whether emoticons should be synced for this integrations (twitch only currently) */ + enable_emoticons: boolean +} + +export interface Guild_Integration { + /** The integrations unique id */ + id: string + /** the integrations name */ + name: string + /** The integration type like twitch, youtube etc */ + type: string + /** Is this integration enabled */ + enabled: boolean + /** is this integration syncing */ + syncing: boolean + /** id that this integration uses for "subscribers" */ + role_id: string + /** The behavior of expiring subscribers */ + expire_behavior: number + /** The grace period before expiring subscribers */ + expire_grace_period: number + /** The user for this integration */ + user: User_Data + /** The integration account information */ + account: Account + /** When this integration was last synced */ + synced_at: string +} + +export interface User_Data { + /** The user's id */ + id: string + /** the user's username, not unique across the platform */ + username: string + /** The user's 4 digit discord tag */ + discriminator: string + /** The user's avatar hash */ + avatar: string | null + /** Whether the user is a bot */ + bot?: boolean + /** Whether the user is an official discord system user (part of the urgent message system.) */ + system?: boolean + /** Whether the user has two factor enabled on their account */ + mfa_enabled?: boolean + /** the user's chosen language option */ + locale?: string + /** Whether the email on this account has been verified */ + verified?: boolean + /** The user's email */ + email?: string + /** The flags on a user's account. */ + flags?: number + /** The type of Nitro subscription on a user's account. */ + premium_type?: number +} + +export enum User_Flags { + NONE, + DISCORD_EMPLOYEE, + DISCORD_PARTNER, + HYPE_SQUAD_EVENTS = 1 << 2, + BUG_HUNTER = 1 << 3, + HOUSE_BRAVERY = 1 << 6, + HOUSE_BRILLIANCE = 1 << 7, + HOUSE_BALANCE = 1 << 8, + EARLY_SUPPORTER = 1 << 9, + TEAM_USER = 1 << 10, + SYSTEM = 1 << 12 +} + +export enum Nitro_Types { + NITRO_CLASSIC = 1, + NITRO +} + +export interface Vanity_Invite { + code: string | null + uses: number +} + +export interface Guild_Embed { + /** Whether the embed is enbaled. */ + enabled: boolean +} + +export interface Get_Audit_Logs_Options { + /** Filter the logs for actions made by this user. */ + user_id?: string + /** The type of audit log. */ + action_type?: AuditLogType + /** Filter the logs before a certain log entry. */ + before?: string + /** How many entries are returned. Between 1-100. Default 50. */ + limit?: number +} + +export type AuditLogType = + | `GUILD_UPDATE` + | `CHANNEL_CREATE` + | `CHANNEL_UPDATE` + | `CHANNEL_DELETE` + | `CHANNEL_OVERWRITE_CREATE` + | `CHANNEL_OVERWRITE_UPDATE` + | `CHANNEL_OVERWRITE_DELETE` + | `MEMBER_KICK` + | `MEMBER_PRUNE` + | `MEMBER_BAN_ADD` + | `MEMBER_BAN_REMOVE` + | `MEMBER_UPDATE` + | `MEMBER_ROLE_UPDATE` + | `MEMBER_MOVE` + | `MEMBER_DISCONNECT` + | `BOT_ADD` + | `ROLE_CREATE` + | `ROLE_UPDATE` + | `ROLE_DELETE` + | `INVITE_CREATE` + | `INVITE_UPDATE` + | `INVITE_DELETE` + | `WEBHOOK_CREATE` + | `WEBHOOK_UPDATE` + | `WEBHOOK_DELETE` + | `EMOJI_CREATE` + | `EMOJI_UPDATE` + | `EMOJI_DELETE` + | `MESSAGE_DELETE` + | `MESSAGE_BULK_DELETE` + | `MESSAGE_PIN` + | `MESSAGE_UNPIN` + | `INTEGRATION_CREATE` + | `INTEGRATION_UPDATE` + | `INTEGRATION_DELETE` + +export enum AuditLogs { + GUILD_UPDATE = 1, + CHANNEL_CREATE = 10, + CHANNEL_UPDATE, + CHANNEL_DELETE, + CHANNEL_OVERWRITE_CREATE, + CHANNEL_OVERWRITE_UPDATE, + CHANNEL_OVERWRITE_DELETE, + MEMBER_KICK = 20, + MEMBER_PRUNE, + MEMBER_BAN_ADD, + MEMBER_BAN_REMOVE, + MEMBER_UPDATE, + MEMBER_ROLE_UPDATE, + MEMBER_MOVE, + MEMBER_DISCONNECT, + BOT_ADD, + ROLE_CREATE = 30, + ROLE_UPDATE, + ROLE_DELETE, + INVITE_CREATE = 40, + INVITE_UPDATE, + INVITE_DELETE, + WEBHOOK_CREATE = 50, + WEBHOOK_UPDATE, + WEBHOOK_DELETE, + EMOJI_CREATE = 60, + EMOJI_UPDATE, + EMOJI_DELETE, + MESSAGE_DELETE = 72, + MESSAGE_BULK_DELETE, + MESSAGE_PIN, + MESSAGE_UNPIN, + INTEGRATION_CREATE = 80, + INTEGRATION_UPDATE, + INTEGRATION_DELETE +} + +export type ChannelType = 'text' | 'dm' | 'news' | 'voice' | 'category' | 'store' + +export interface Overwrite { + /** The role or user id */ + id: string + /** Whether this is a role or a member */ + type: 'role' | 'member' + /** The permissions that this id is allowed to do. (This will mark it as a green check.) */ + allow: Permission[] + /** The permissions that this id is NOT allowed to do. (This will mark it as a red x.) */ + deny: Permission[] +} + +export interface Raw_Overwrite { + /** The role or user id */ + id: string + /** Whether this is a role or a member */ + type: 'role' | 'member' + /** The permissions that this id is allowed to do. (This will mark it as a green check.) */ + allow: number + /** The permissions that this id is NOT allowed to do. (This will mark it as a red x.) */ + deny: number +} + +export enum ChannelTypes { + text, + dm, + voice, + category = 4, + news, + store +} + +export interface ChannelCreate_Options { + /** The type of the channel */ + type?: ChannelType + /** The channel topic. (0-1024 characters) */ + topic?: string + /** The bitrate(in bits) of the voice channel. */ + bitrate?: number + /** The user limit of the voice channel. */ + user_limit?: number + /** The amount of seconds a user has to wait before sending another message. (0-21600 seconds). Bots, as well as users with the permission `manage_messages or manage_channel` are unaffected. */ + rate_limit_per_user?: number + /** The sorting position of the channel */ + position?: number + /** The channel's permission overwrites */ + permission_overwrites?: Overwrite[] + /** The id of the parent category for the channel */ + parent_id?: string + /** Whether the channel is nsfw */ + nsfw?: boolean + /** The reason to add in the Audit Logs. */ + reason?: string +} + +export interface Create_Emojis_Options { + /** The roles for which this emoji will be whitelisted. Only the users with one of these roles can use this emoji. */ + roles: string[] + /** The reason to have in the Audit Logs. */ + reason: string +} + +export interface Edit_Emojis_Options { + /** The name of the emoji */ + name: string + /** The roles for which this emoji will be whitelisted. Only the users with one of these roles can use this emoji. */ + roles: string[] +} + +export interface Create_Role_Options { + name?: string + permissions?: Permission[] + color?: number + hoist?: boolean + mentionable?: boolean +} + +export interface PrunePayload { + pruned: number +} + +export interface Voice_State { + /** the guild id this voice state is for */ + guild_id?: string + /** the channel id this user is connected to */ + channel_id: string | null + /** the user id this voice state is for */ + user_id: string + /** the guild member this voice state is for */ + member?: Member_Create_Payload + /** the session id for this voice state */ + session_id: string + /** whether this user is deafened by the server */ + deaf: boolean + /** whether this user is muted by the server */ + mute: boolean + /** whether this user is locally deafened */ + self_deaf: boolean + /** whether this user is locally muted */ + self_mute: boolean + /** whether this user is streaming using "Go Live" */ + self_stream?: boolean + /** whether this user is muted by the current user */ + suppress: boolean +} + +export interface Presence { + /** The user presence is being updated for */ + user: User_Data + /** The roles this user is in */ + roles: string[] + /** null, or the user's current activity */ + game: Activity | null + /** The id of the guild */ + guild_id: string + /** Either idle */ + status: StatusType + activities: Activity[] + client_status: ClientStatusPayload + premium_since?: string | null + nick?: string | null +} + +export type Guild = ReturnType diff --git a/types/member.ts b/types/member.ts new file mode 100644 index 000000000..73b8ffd44 --- /dev/null +++ b/types/member.ts @@ -0,0 +1,34 @@ +import { User_Payload } from "../structures/user"; +import { create_member } from "../structures/member"; + +export interface Edit_Member_Options { + /** Value to set users nickname to. Requires MANAGE_NICKNAMES permission. */ + nick?: string + /** Array of role ids the member is assigned. Requires MANAGE_ROLES permission. */ + roles?: string[] + /** Whether the user is muted in voice channels. Requires MUTE_MEMBERS permission. */ + mute?: boolean + /** Whether the user is deafened in voice channels. Requires DEAFEN_MEMBERS permission. */ + deaf?: boolean + /** The id of the channel to move user to if they are connected to voice. To kick the user from their current channel, set to null. Requires MOVE_MEMBERS permission. When moving members to channels, must have permissions to both CONNECT to the channel and have the MOVE_MEMBER permission. */ + channel_id?: string | null +} + +export interface Member_Create_Payload { + /** The user this guild member represents */ + user: User_Payload + /** The user's guild nickname if one is set. */ + nick?: string + /** Array of role ids that the member has */ + roles: string[] + /** When the user joined the guild. */ + joined_at: string + /** When the user used their nitro boost on the server. */ + premium_since?: string + /** Whether the user is deafened in voice channels */ + deaf: boolean + /** Whether the user is muted in voice channels */ + mute: boolean +} + +export type Member = ReturnType diff --git a/types/message.ts b/types/message.ts new file mode 100644 index 000000000..f1b1ef4b6 --- /dev/null +++ b/types/message.ts @@ -0,0 +1,268 @@ +import { Member } from '../structures/member' +import { ChannelType, User_Data } from './guild' +import { User } from '../structures/user' + +export interface MentionedUser extends User { + member: Member +} + +export interface Mentioned_Channel { + /** The id of the channel */ + id: string + /** The id of the guild containing the channel */ + guild_id: string + /** The type of the channel. */ + type: ChannelType + /** The name of the channel. */ + name: string +} + +export interface Attachment { + /** Attachment id */ + id: string + /** The name of the file attached */ + filename: string + /** The size of file in bytes */ + size: number + /** Source url of file */ + url: string + /** A proxied url of file */ + proxy_url: string + /** The height of file if an image */ + height: number | null + /** The width of the file if an image */ + width: number | null +} + +export interface Embed { + /** The title of the embed */ + title?: string + /** The type of embed (always rich for webhook embeds) */ + type?: string + /** The description of embeds */ + description?: string + /** The url of embed */ + url?: string + /** The timestap of the embed content */ + timestamp?: string + /** The color code of the embed */ + color?: number + /** The footer information */ + footer?: Embed_Footer + /** The image information */ + image?: Embed_Image + /** The thumbnail information */ + thumbnail?: Embed_Thumbnail + /** The video information */ + video?: Embed_Video + /** Provider information */ + provider?: Embed_Provider + /** Author information */ + author?: Embed_Author + /** Fields information */ + fields?: Embed_Field[] +} + +export interface Embed_Footer { + /** The text of the footer */ + text: string + /** The url of the footer icon. Only supports http(s) and attachments */ + icon_url?: string + /** A proxied url of footer icon */ + proxy_icon_url?: string +} + +export interface Embed_Image { + /** The source url of image (only supports http(s) and attachments) */ + url?: string + /** A proxied url of the image */ + proxy_url?: string + /** The height of image */ + height?: number + /** The width of the image */ + width?: number +} + +export interface Embed_Thumbnail { + /** The source url of image (only supports http(s) and attachments) */ + url?: string + /** A proxied url of the thumbnail */ + proxy_url?: string + /** The height of the thumbnail */ + height?: number + /** The width of the thumbnail */ + width?: number +} + +export interface Embed_Video { + /** The source url of video */ + url?: string + /** The height of the video */ + height?: number + /** The width of the video */ + width?: number +} + +export interface Embed_Provider { + /** The name of the provider */ + name?: string + /** The url of the provider */ + url?: string +} + +export interface Embed_Author { + /** The name of the author */ + name?: string + /** The url of the author */ + url?: string + /** The url of the author icon (supports http(s) and attachments) */ + icon_url?: string + /** A proxied url of author icon */ + proxy_icon_url?: string +} + +export interface Embed_Field { + /** The name of the field */ + name: string + /** The value of the field */ + value: string + /** Whether or not this field should display inline */ + inline?: boolean +} + +export interface Reaction { + /** The times this emoji has been used to react */ + count: number + /** Whether the current user reacted using this emoji */ + me: boolean + /** The emoji information. Can be partial. */ + emoji: Emoji +} + +export enum Message_Types { + DEFAULT, + RECIPIENT_ADD, + RECIPIENT_REMOVE, + CALL, + CHANNEL_NAME_CHANGE, + CHANNEL_ICON_CHANGE, + CHANNEL_PINNED_MESSAGE, + GUILD_MEMBER_JOIN, + USER_PREMIUM_GUILD_SUBSCRIPTION, + USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1, + USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2, + USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3, + CHANNEL_FOLLOW_ADD, +} + +export enum Activity_Types { + JOIN = 1, + SPECTATE, + LISTEN, + JOIN_REQUEST = 5 +} + +export interface Activity { + /** The type of message activity */ + type: 1 | 2 | 3 | 5 + /** The party id from a rich presence event */ + party_id?: string +} + +export interface Application { + /** The id of the application */ + id: string + /** The id of the embed's image asset */ + cover_image?: string + /** The application's description */ + description: string + /** The id of the application's icon */ + icon: string | null + /** The name of the application */ + name: string +} + +export interface Reference { + /** The id of the originating message */ + message_id?: string + /** The id of the originating message's channel */ + channel_id: string + /** The id of the originating message's guild */ + guild_id?: string +} + +export enum Message_Flags { + CROSSPOSTED = 1 << 0, + IS_CROSSPOST = 1 << 1, + SUPPRESS_EMBEDS = 1 << 2, + SOURCE_MESSAGE_DELETED = 1 << 3, + URGENT = 1 << 4 +} + +export interface Emoji { + /** The emoji id. */ + id?: string + /** The emoji name. Null in reaction emoji object if emoji is no longer on the server */ + name: string | null + /** The roles this emoji is whitelisted to */ + roles?: string[] + /** The user that created this emoji */ + user?: User + /** Whether this emoji must be wrapped in colons */ + require_colons?: boolean + /** Whether this emoji is managed */ + managed?: boolean + /** Whether this emoji is animated */ + animated?: boolean +} + +export interface Message_Create_Options { + /** The id of the message */ + id: string + /** The id of the channel the message was sent in */ + channel_id: string + /** The id of the guild the message was sent in */ + guild_id?: string + /** The author of this message (not guaranteed to be a valid user such as a webhook.) */ + author: User_Data + /** The member properties for this message's author. Can be partial. */ + member?: Member + /** The contents of the message */ + content: string + /** When this message was sent */ + timestamp: string + /** When this message was edited (if it was not edited, null) */ + edited_timestamp: string | null + /** Whether this was a TextToSpeech message. */ + tts: boolean + /** Whether this message mentions everyone */ + mentions_everyone: boolean + /** Users specifically mentioned in the message. */ + mentions: MentionedUser[] + /** Roles specifically mentioned in this message */ + mention_roles: string[] + /** Channels specifically mentioned in this message */ + mention_channels?: Mentioned_Channel[] + /** Any attached files */ + attachments: Attachment[] + /** Any embedded content */ + embeds: Embed[] + /** Reactions to the message */ + reactions?: Reaction[] + /** Used for validating a message was sent */ + nonce?: number | string + /** Whether this message is pinned */ + pinned: boolean + /** If the message is generated by a webhook, this is the webhooks id */ + webhook_id?: string + /** The type of message */ + type: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 + /** The activities sent with Rich Presence-related chat embeds. */ + activity?: Activity + /** Applications that sent with Rich Presence related chat embeds. */ + applications?: Application + /** The reference data sent with crossposted messages */ + message_reference?: Reference + /** The message flags combined like permission bits describe extra features of the message */ + flags?: 1 | 2 | 4 | 8 | 16 +} diff --git a/types/permission.ts b/types/permission.ts new file mode 100644 index 000000000..559e880b6 --- /dev/null +++ b/types/permission.ts @@ -0,0 +1,64 @@ +export type Permission = + | `CREATE_INSTANT_INVITE` + | `KICK_MEMBERS` + | `BAN_MEMBERS` + | `ADMINISTRATOR` + | `MANAGE_CHANNELS` + | `MANAGE_GUILD` + | `ADD_REACTIONS` + | `VIEW_AUDIT_LOG` + | `VIEW_CHANNEL` + | `SEND_MESSAGES` + | `SEND_TTS_MESSAGES` + | `MANAGE_MESSAGES` + | `EMBED_LINKS` + | `ATTACH_FILES` + | `READ_MESSAGE_HISTORY` + | `MENTION_EVERYONE` + | `USE_EXTERNAL_EMOJIS` + | `CONNECT` + | `SPEAK` + | `MUTE_MEMBERS` + | `DEAFEN_MEMBERS` + | `MOVE_MEMBERS` + | `USE_VAD` + | `PRIORITY_SPEAKER` + | `STREAM` + | `CHANGE_NICKNAME` + | `MANAGE_NICKNAMES` + | `MANAGE_ROLES` + | `MANAGE_WEBHOOKS` + | `MANAGE_EMOJIS` + +export enum Permissions { + CREATE_INSTANT_INVITE = 0x00000001, + KICK_MEMBERS = 0x00000002, + BAN_MEMBERS = 0x00000004, + ADMINISTRATOR = 0x00000008, + MANAGE_CHANNELS = 0x00000010, + MANAGE_GUILD = 0x00000020, + ADD_REACTIONS = 0x00000040, + VIEW_AUDIT_LOG = 0x00000080, + VIEW_CHANNEL = 0x00000400, + SEND_MESSAGES = 0x00000800, + SEND_TTS_MESSAGES = 0x00001000, + MANAGE_MESSAGES = 0x00002000, + EMBED_LINKS = 0x00004000, + ATTACH_FILES = 0x00008000, + READ_MESSAGE_HISTORY = 0x00010000, + MENTION_EVERYONE = 0x00020000, + USE_EXTERNAL_EMOJIS = 0x00040000, + CONNECT = 0x00100000, + SPEAK = 0x00200000, + MUTE_MEMBERS = 0x00400000, + DEAFEN_MEMBERS = 0x00800000, + MOVE_MEMBERS = 0x01000000, + USE_VAD = 0x02000000, + PRIORITY_SPEAKER = 0x00000100, + STREAM = 0x00000200, + CHANGE_NICKNAME = 0x04000000, + MANAGE_NICKNAMES = 0x08000000, + MANAGE_ROLES = 0x10000000, + MANAGE_WEBHOOKS = 0x20000000, + MANAGE_EMOJIS = 0x40000000 +} diff --git a/types/role.ts b/types/role.ts new file mode 100644 index 000000000..c442169c8 --- /dev/null +++ b/types/role.ts @@ -0,0 +1,18 @@ +export interface Role_Data { + /** role id */ + id: string + /** role name */ + name: string + /** integer representation of hexadecimal color code */ + color: number + /** if this role is pinned in the user listing */ + hoist: boolean + /** position of this role */ + position: number + /** permission bit set */ + permissions: number + /** whether this role is managed by an integration */ + managed: boolean + /** whether this role is mentionable */ + mentionable: boolean +} diff --git a/utils/cache.ts b/utils/cache.ts new file mode 100644 index 000000000..1098d2785 --- /dev/null +++ b/utils/cache.ts @@ -0,0 +1,9 @@ +import { User } from "../structures/user"; +import { Guild } from "../types/guild"; +import { Channel } from "../types/channel"; + +export const cache = { + guilds: new Map(), + users: new Map(), + channels: new Map(), +} diff --git a/utils/cdn.ts b/utils/cdn.ts index ae221007f..0e8ab4425 100644 --- a/utils/cdn.ts +++ b/utils/cdn.ts @@ -1,5 +1,5 @@ import { ImageSize, ImageFormats } from '../structures/guild' -export const formatImageURL = (url: string, size: ImageSize = 128, format?: ImageFormats) => { +export const format_image_url = (url: string, size: ImageSize = 128, format?: ImageFormats) => { return `${url}.${format || url.includes('/a_') ? 'gif' : 'jpg'}/?size=${size}` }