diff --git a/constants/discord.ts b/constants/discord.ts index 7e8d91fa1..62f4c97fb 100644 --- a/constants/discord.ts +++ b/constants/discord.ts @@ -17,6 +17,10 @@ export const endpoints = { 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)}`, diff --git a/module/client.ts b/module/client.ts index 0f2c5268b..4c9a3293e 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.RequestManager = new RequestManager(this, this.authorization) diff --git a/structures/channel.ts b/structures/channel.ts index 91efc333c..7c352a466 100644 --- a/structures/channel.ts +++ b/structures/channel.ts @@ -2,6 +2,7 @@ import { Channel_Create_Options, Channel_Types, Get_Messages_After, Get_Messages import { Guild, Permission, Permissions } from '../types/guild' import Client from '../module/client' import { endpoints } from '../constants/discord' +import { create_message } from './message' export const create_channel = (data: Channel_Create_Options, guild: Guild, client: Client) => { const base_channel = { diff --git a/structures/message.ts b/structures/message.ts index 612f623a4..9bf53234b 100644 --- a/structures/message.ts +++ b/structures/message.ts @@ -1,30 +1,106 @@ 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' 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, + message_id: data.message_reference?.message_id }), flags: () => data.flags || 0, - channel_id: () => data.channel_id + channel_id: () => data.channel_id, + channel: () => client.channels.get(data.channel_id), + + delete: (reason: string) => { + // TODO: Requires MANAGE_MESSAGES + if (data.author.id !== client.bot_id) checkPermission() + + 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: (reaction: string) => { + return client.RequestManager.get(endpoints.CHANNEL_MESSAGE_REACTION(data.channel_id, data.id, reaction)) as User[] + }, + /** 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 + ...base_message, + author: create_user(data.author, client) } } - return { + const guild = cache.guilds.get(data.guild_id) + return { + ...base_message, + guild: () => guild, + member: () => data.member } } diff --git a/utils/cache.ts b/utils/cache.ts new file mode 100644 index 000000000..e0d66532d --- /dev/null +++ b/utils/cache.ts @@ -0,0 +1,7 @@ +import { Guild } from "../types/guild"; + +export const cache = { + guilds: new Map(), + users: new Map(), + channels: new Map(), +}