From ac3ea577a770bb6b9e06f8c2b28fa1b1a66f3c8d Mon Sep 17 00:00:00 2001 From: Skillz Date: Mon, 23 Mar 2020 19:26:19 -0400 Subject: [PATCH] client to functional instead of class --- events/channels.ts | 10 +- mod.ts | 2 +- module/client.ts | 808 ++++++++++++++++++------------------- module/sharding_manager.ts | 5 + structures/channel.ts | 32 +- structures/guild.ts | 66 +-- structures/member.ts | 19 +- structures/message.ts | 24 +- 8 files changed, 474 insertions(+), 492 deletions(-) diff --git a/events/channels.ts b/events/channels.ts index 415801860..36a4c89b0 100644 --- a/events/channels.ts +++ b/events/channels.ts @@ -1,17 +1,17 @@ import { cache } from "../utils/cache.ts" import { Channel_Create_Payload, Channel_Types } from "../types/channel.ts" import { create_channel } from "../structures/channel.ts" -import Client, { event_handlers } from "../module/client.ts" +import { event_handlers } from "../module/client.ts" -export const handle_internal_channel_create = (data: Channel_Create_Payload, client: Client) => { - const channel = create_channel(data, client) +export const handle_internal_channel_create = (data: Channel_Create_Payload) => { + const channel = create_channel(data) cache.channels.set(channel.id, channel) event_handlers.channel_create?.(channel) } -export const handle_internal_channel_update = (data: Channel_Create_Payload, client: Client) => { +export const handle_internal_channel_update = (data: Channel_Create_Payload) => { const cached_channel = cache.channels.get(data.id) - const channel = create_channel(data, client) + const channel = create_channel(data) cache.channels.set(channel.id, channel) if (!cached_channel) return diff --git a/mod.ts b/mod.ts index 016a489c2..c394d8c01 100644 --- a/mod.ts +++ b/mod.ts @@ -3,7 +3,7 @@ import { configs } from "./configs.ts" import { Intents } from "./types/options.ts" import { logYellow } from "./utils/logger.ts" -new Client({ +Client({ token: configs.token, bot_id: "675412054529540107", intents: [Intents.GUILDS, Intents.GUILD_MESSAGES], diff --git a/module/client.ts b/module/client.ts index 7d3e83464..2dc7c1535 100644 --- a/module/client.ts +++ b/module/client.ts @@ -10,14 +10,8 @@ import { Voice_State_Update_Payload } from "../types/discord.ts" import { spawnShards } from "./sharding_manager.ts" -import { - connectWebSocket, - isWebSocketCloseEvent, - isWebSocketPingEvent, - isWebSocketPongEvent, - WebSocket -} from "https://deno.land/std/ws/mod.ts" -import { Client_Options, Fulfilled_Client_Options, Event_Handlers } from "../types/options.ts" +import { connectWebSocket, isWebSocketCloseEvent, WebSocket } from "https://deno.land/std/ws/mod.ts" +import { Client_Options, Event_Handlers } from "../types/options.ts" import { CollectedMessageType } from "../types/message-type.ts" import { send_constant_heartbeats, update_previous_sequence_number } from "./gateway.ts" import { create_guild } from "../structures/guild.ts" @@ -70,425 +64,405 @@ const defaultOptions = { } export let authorization = "" +export let bot_id = "" +/** 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. */ +export let token = "" export let event_handlers: Event_Handlers = {} -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 +export const create_client = async (data: Client_Options) => { + // Assign some defaults to the options to make them fulfilled / not annoying to use. + const options = { + ...defaultOptions, + ...data, + intents: data.intents.reduce((bits, next) => (bits |= next), 0) + } + bot_id = data.bot_id + token = data.token + if (data.event_handlers) event_handlers = data.event_handlers + authorization = `Bot ${data.token}` - /** The options (with defaults) passed to the `Client` constructor. */ - options: Fulfilled_Client_Options + // Initial API connection to get info about bots connection + const bot_gateway_data = (await Request_Manager.get(endpoints.GATEWAY_BOT)) as DiscordBotGatewayData + const socket = await connectWebSocket(bot_gateway_data.url) + collect_messages(socket) - constructor(options: Client_Options) { - // Assign some defaults to the options to make them fulfilled / not annoying to use. - this.options = { - ...defaultOptions, - ...options, - intents: options.intents.reduce((bits, next) => (bits |= next), 0) - } - this.bot_id = options.bot_id - this.token = options.token - if (options.event_handlers) event_handlers = options.event_handlers + const payload = { + token: data.token, + // TODO: Let's get compression working, eh? + compress: false, + properties: options.properties, + intents: options.intents, + shards: [0, bot_gateway_data.shards] + } + // Intial identify with the gateway + await socket.send(JSON.stringify({ op: GatewayOpcode.Identify, d: payload })) - authorization = `Bot ${options.token}` - this.bootstrap() + for await (const _message of connect(socket, bot_gateway_data)) { } - async bootstrap() { - const data = (await Request_Manager.get(endpoints.GATEWAY_BOT)) as DiscordBotGatewayData - const socket = await connectWebSocket(data.url) - this.collectMessages(socket) - // Intial identify with the gateway - await socket.send( - JSON.stringify({ - op: GatewayOpcode.Identify, - d: { - token: this.options.token, - // TODO: Let's get compression working, eh? - compress: false, - properties: this.options.properties, - intents: this.options.intents - } - }) - ) + spawnShardss(bot_gateway_data.shards, socket, payload, bot_gateway_data) +} - for await (const _message of this.connect(socket, data)) { - } - } - - async *collectMessages(socket: WebSocket) { - for await (const message of socket.receive()) { - if (typeof message === "string") { - yield { - type: CollectedMessageType.Message, - data: JSON.parse(message) - } - } else if (isWebSocketCloseEvent(message)) { - yield { type: CollectedMessageType.Close, ...message } - return - } else if (isWebSocketPingEvent(message)) { - yield { type: CollectedMessageType.Ping } - } else if (isWebSocketPongEvent(message)) { - yield { type: CollectedMessageType.Pong } +async function* collect_messages(socket: WebSocket) { + for await (const message of socket.receive()) { + if (typeof message === "string") { + yield { + type: CollectedMessageType.Message, + data: JSON.parse(message) } - } - } - - /** Begins initial handshake, creates the websocket with Discord and spawns all necessary shards. */ - async *connect(socket: WebSocket, data: DiscordBotGatewayData) { - for await (const message of this.collectMessages(socket)) { - switch (message.type) { - case CollectedMessageType.Ping: - logRed("Ping!") - yield message - break - case CollectedMessageType.Pong: - logRed("Pong!") - yield message - break - case CollectedMessageType.Close: - logRed(`Close :( ${message}`) - yield message - break - case CollectedMessageType.Message: - this.handleDiscordPayload(message.data, socket) - yield message - break - } - } - - // Begin spawning all necessary shards - spawnShards(data.shards) - } - - handleDiscordPayload(data: DiscordPayload, socket: WebSocket) { - // Update the sequence number if it is present so that heartbeating can be accurate - if (data.s) update_previous_sequence_number(data.s) - - switch (data.op) { - case GatewayOpcode.Hello: - send_constant_heartbeats(socket, (data.d as DiscordHeartbeatPayload).heartbeat_interval) - return - case GatewayOpcode.HeartbeatACK: - // Incase the user wants to listen to heartbeat responses - return event_handlers.heartbeat?.() - case GatewayOpcode.Reconnect: - // TODO: Reconnect to the gateway https://discordapp.com/developers/docs/topics/gateway#reconnect - return - case GatewayOpcode.Dispatch: - if (data.t === "READY") return event_handlers.ready?.() - if (data.t === "CHANNEL_CREATE") return handle_internal_channel_create(data.d as Channel_Create_Payload, this) - if (data.t === "CHANNEL_UPDATE") return handle_internal_channel_update(data.d as Channel_Create_Payload, this) - if (data.t === "CHANNEL_DELETE") return handle_internal_channel_delete(data.d as Channel_Create_Payload) - - if (data.t === "GUILD_CREATE") { - const guild = create_guild(data.d as Create_Guild_Payload, this) - handle_internal_guild_create(guild) - if (cache.unavailableGuilds.get(guild.id())) { - cache.unavailableGuilds.delete(guild.id()) - return - } - return event_handlers.guild_create?.(guild) - } - - if (data.t === "GUILD_UPDATE") { - const options = data.d as Create_Guild_Payload - const cached_guild = cache.guilds.get(options.id) - const guild = create_guild(options, this) - handle_internal_guild_update(guild) - if (!cached_guild) return - - return event_handlers.guild_update?.(guild, cached_guild) - } - - if (data.t === "GUILD_DELETE") { - const options = data.d as Guild_Delete_Payload - const guild = cache.guilds.get(options.id) - if (!guild) return - - guild.channels.forEach((_channel, id) => cache.channels.delete(id)) - if (options.unavailable) return cache.unavailableGuilds.set(options.id, Date.now()) - - handle_internal_guild_delete(guild) - return event_handlers.guild_delete?.(guild) - } - - if (data.t && ["GUILD_BAN_ADD", "GUILD_BAN_REMOVE"].includes(data.t)) { - const options = data.d as Guild_Ban_Payload - const guild = cache.guilds.get(options.guild_id) - if (!guild) return - - const user = create_user(options.user) - return data.t === "GUILD_BAN_ADD" - ? event_handlers.guild_ban_add?.(guild, user) - : event_handlers.guild_ban_remove?.(guild, user) - } - - if (data.t === "GUILD_EMOJIS_UPDATE") { - const options = data.d as Guild_Emojis_Update_Payload - const guild = cache.guilds.get(options.guild_id) - if (!guild) return - - const cached_emojis = guild.emojis() - guild.emojis = () => options.emojis - - return event_handlers.guild_emojis_update?.(guild, options.emojis, cached_emojis) - } - - if (data.t === "GUILD_MEMBER_ADD") { - const options = data.d as Guild_Member_Add_Payload - const guild = cache.guilds.get(options.guild_id) - if (!guild) return - - const member_count = guild.member_count() + 1 - guild.member_count = () => member_count - const member = create_member( - options, - options.guild_id, - [...guild.roles().values()].map(role => role.raw()), - guild.owner_id(), - this - ) - guild.members.set(options.user.id, member) - - return event_handlers.guild_member_add?.(guild, member) - } - - if (data.t === "GUILD_MEMBER_REMOVE") { - const options = data.d as Guild_Ban_Payload - const guild = cache.guilds.get(options.guild_id) - if (!guild) return - - const member_count = guild.member_count() - 1 - guild.member_count = () => member_count - - const member = guild.members.get(options.user.id) - return event_handlers.guild_member_remove?.(guild, member || create_user(options.user)) - } - - if (data.t === "GUILD_MEMBER_UPDATE") { - const options = data.d as Guild_Member_Update_Payload - const guild = cache.guilds.get(options.guild_id) - if (!guild) return - - const cached_member = guild.members.get(options.user.id) - - const new_member_data = { - ...options, - premium_since: options.premium_since || undefined, - joined_at: new Date(cached_member?.joined_at() || Date.now()).toISOString(), - deaf: cached_member?.deaf() || false, - mute: cached_member?.mute() || false - } - const member = create_member( - new_member_data, - options.guild_id, - [...guild.roles().values()].map(r => r.raw()), - guild.owner_id(), - this - ) - guild.members.set(options.user.id, member) - - if (cached_member?.nick() !== options.nick) - event_handlers.nickname_update?.(guild, member, options.nick, cached_member?.nick()) - const role_ids = cached_member?.roles() || [] - - role_ids.forEach(id => { - if (!options.roles.includes(id)) event_handlers.role_lost?.(guild, member, id) - }) - - options.roles.forEach(id => { - if (!role_ids.includes(id)) event_handlers.role_gained?.(guild, member, id) - }) - - return event_handlers.guild_member_update?.(guild, member, cached_member) - } - - if (data.t === "GUILD_MEMBERS_CHUNK") { - const options = data.d as Guild_Member_Chunk_Payload - const guild = cache.guilds.get(options.guild_id) - if (!guild) return - - options.members.forEach(member => - guild.members.set( - member.user.id, - create_member( - member, - options.guild_id, - [...guild.roles().values()].map(r => r.raw()), - guild.owner_id(), - this - ) - ) - ) - } - - if (data.t && ["GUILD_ROLE_CREATE", "GUILD_ROLE_DELETE", "GUILD_ROLE_UPDATE"].includes(data.t)) { - const options = data.d as Guild_Role_Payload - const guild = cache.guilds.get(options.guild_id) - if (!guild) return - - if (data.t === "GUILD_ROLE_CREATE") { - const role = create_role(options.role) - const roles = guild.roles().set(options.role.id, role) - guild.roles = () => roles - return event_handlers.role_create?.(guild, role) - } - - const cached_role = guild.roles().get(options.role.id) - if (!cached_role) return - - if (data.t === "GUILD_ROLE_DELETE") { - const roles = guild.roles() - roles.delete(options.role.id) - guild.roles = () => roles - return event_handlers.role_delete?.(guild, cached_role) - } - - if (data.t === "GUILD_ROLE_UPDATE") { - const role = create_role(options.role) - return event_handlers.role_update?.(guild, role, cached_role) - } - } - - if (data.t === "MESSAGE_CREATE") { - const options = data.d as Message_Create_Options - const message = create_message(options, this) - const channel = message.channel() - if (channel) { - // channel.last_message_id = () => options.id - // if (channel.messages().size > 99) { - // TODO: LIMIT THIS TO 100 messages - // } - } - return event_handlers.message_create?.(message) - } - - if (data.t && ["MESSAGE_DELETE", "MESSAGE_DELETE_BULK"].includes(data.t)) { - const options = data.d as Message_Delete_Payload - const deleted_messages = - data.t === "MESSAGE_DELETE" ? [options.id] : (data.d as Message_Delete_Bulk_Payload).ids - - const channel = cache.channels.get(options.channel_id) - if (!channel) return - - deleted_messages.forEach(id => { - console.log(id) - // const message = channel.messages().get(id) - // if (message) { - // // TODO: update the messages cache - // } - - // return event_handlers.message_delete?.(message || { id, channel }) - }) - } - - if (data.t === "MESSAGE_UPDATE") { - const options = data.d as Message_Update_Payload - const channel = cache.channels.get(options.channel_id) - if (!channel) return - - // const cachedMessage = channel.messages().get(options.id) - // return event_handlers.message_update?.(message, cachedMessage) - } - - if (data.t && ["MESSAGE_REACTION_ADD", "MESSAGE_REACTION_REMOVE"].includes(data.t)) { - const options = data.d as Message_Reaction_Payload - const message = cache.messages.get(options.message_id) - const isAdd = data.t === "MESSAGE_REACTION_ADD" - - if (message) { - const previous_reactions = message.reactions() - const reaction_existed = previous_reactions.find( - reaction => reaction.emoji.id === options.emoji.id && reaction.emoji.name === options.emoji.name - ) - if (reaction_existed) - reaction_existed.count = isAdd ? reaction_existed.count + 1 : reaction_existed.count - 1 - else - message.reactions = () => [ - ...message.reactions(), - { - count: 1, - me: options.user_id === this.bot_id, - emoji: { ...options.emoji, id: options.emoji.id || undefined } - } - ] - - cache.messages.set(options.message_id, message) - } - - return isAdd - ? event_handlers.reaction_add?.(message || options, options.emoji, options.user_id) - : event_handlers.reaction_remove?.(message || options, options.emoji, options.user_id) - } - - if (data.t === "MESSAGE_REACTION_REMOVE_ALL") { - return event_handlers.reaction_remove_all?.(data.d as Base_Message_Reaction_Payload) - } - - if (data.t === "MESSAGE_REACTION_REMOVE_EMOJI") { - return event_handlers.reaction_remove_emoji?.(data.d as Message_Reaction_Remove_Emoji_Payload) - } - - if (data.t === "PRESENCE_UPDATE") { - return event_handlers.presence_update?.(data.d as Presence_Update_Payload) - } - - if (data.t === "TYPING_START") { - return event_handlers.typing_start?.(data.d as Typing_Start_Payload) - } - - if (data.t === "USER_UPDATE") { - const user_data = data.d as User_Payload - const cached_user = cache.users.get(this.bot_id) - const user = create_user(user_data) - cache.users.set(user_data.id, user) - return event_handlers.bot_update?.(user, cached_user) - } - - if (data.t === "VOICE_STATE_UPDATE") { - const payload = data.d as Voice_State_Update_Payload - if (!payload.guild_id) return - - const guild = cache.guilds.get(payload.guild_id) - if (!guild) return - - const member = guild.members.get(payload.user_id) - if (!member) return - - const cached_state = guild.voice_states().find(state => state.user_id === payload.user_id) - // No cached state before so lets make one for em - if (!cached_state) return (guild.voice_states = () => [...guild.voice_states(), payload]) - - if (cached_state.channel_id !== payload.channel_id) { - // Either joined or moved channels - if (payload.channel_id) { - cached_state.channel_id - ? // Was in a channel before - event_handlers.voice_channel_switch?.(member, payload.channel_id, cached_state.channel_id) - : // Was not in a channel before so user just joined - event_handlers.voice_channel_join?.(member, payload.channel_id) - } - // Left the channel - else if (cached_state.channel_id) { - event_handlers.voice_channel_leave?.(member, cached_state.channel_id) - } - } - - return event_handlers.voice_state_update?.(member, payload) - } - - if (data.t === "WEBHOOKS_UPDATE") { - const options = data.d as Webhook_Update_Payload - return event_handlers.webhooks_update?.(options.channel_id, options.guild_id) - } - - return event_handlers.raw?.(data) - default: - return + } else if (isWebSocketCloseEvent(message)) { + yield { type: CollectedMessageType.Close, ...message } + return } } } -export default Client +/** Begins initial handshake, creates the websocket with Discord and spawns all necessary shards. */ +async function* connect(socket: WebSocket, data: DiscordBotGatewayData) { + for await (const message of collect_messages(socket)) { + switch (message.type) { + case CollectedMessageType.Ping: + logRed("Ping!") + yield message + break + case CollectedMessageType.Pong: + logRed("Pong!") + yield message + break + case CollectedMessageType.Close: + logRed(`Close :( ${message}`) + yield message + break + case CollectedMessageType.Message: + handle_discord_payload(message.data, socket) + yield message + break + } + } +} + +function handle_discord_payload(data: DiscordPayload, socket: WebSocket) { + // Update the sequence number if it is present so that heartbeating can be accurate + if (data.s) update_previous_sequence_number(data.s) + + switch (data.op) { + case GatewayOpcode.Hello: + send_constant_heartbeats(socket, (data.d as DiscordHeartbeatPayload).heartbeat_interval) + return + case GatewayOpcode.HeartbeatACK: + // Incase the user wants to listen to heartbeat responses + return event_handlers.heartbeat?.() + case GatewayOpcode.Reconnect: + // TODO: Reconnect to the gateway https://discordapp.com/developers/docs/topics/gateway#reconnect + return + case GatewayOpcode.Dispatch: + if (data.t === "READY") return event_handlers.ready?.() + if (data.t === "CHANNEL_CREATE") return handle_internal_channel_create(data.d as Channel_Create_Payload) + if (data.t === "CHANNEL_UPDATE") return handle_internal_channel_update(data.d as Channel_Create_Payload) + if (data.t === "CHANNEL_DELETE") return handle_internal_channel_delete(data.d as Channel_Create_Payload) + + if (data.t === "GUILD_CREATE") { + const guild = create_guild(data.d as Create_Guild_Payload) + handle_internal_guild_create(guild) + if (cache.unavailableGuilds.get(guild.id())) { + cache.unavailableGuilds.delete(guild.id()) + return + } + return event_handlers.guild_create?.(guild) + } + + if (data.t === "GUILD_UPDATE") { + const options = data.d as Create_Guild_Payload + const cached_guild = cache.guilds.get(options.id) + const guild = create_guild(options) + handle_internal_guild_update(guild) + if (!cached_guild) return + + return event_handlers.guild_update?.(guild, cached_guild) + } + + if (data.t === "GUILD_DELETE") { + const options = data.d as Guild_Delete_Payload + const guild = cache.guilds.get(options.id) + if (!guild) return + + guild.channels.forEach((_channel, id) => cache.channels.delete(id)) + if (options.unavailable) return cache.unavailableGuilds.set(options.id, Date.now()) + + handle_internal_guild_delete(guild) + return event_handlers.guild_delete?.(guild) + } + + if (data.t && ["GUILD_BAN_ADD", "GUILD_BAN_REMOVE"].includes(data.t)) { + const options = data.d as Guild_Ban_Payload + const guild = cache.guilds.get(options.guild_id) + if (!guild) return + + const user = create_user(options.user) + return data.t === "GUILD_BAN_ADD" + ? event_handlers.guild_ban_add?.(guild, user) + : event_handlers.guild_ban_remove?.(guild, user) + } + + if (data.t === "GUILD_EMOJIS_UPDATE") { + const options = data.d as Guild_Emojis_Update_Payload + const guild = cache.guilds.get(options.guild_id) + if (!guild) return + + const cached_emojis = guild.emojis() + guild.emojis = () => options.emojis + + return event_handlers.guild_emojis_update?.(guild, options.emojis, cached_emojis) + } + + if (data.t === "GUILD_MEMBER_ADD") { + const options = data.d as Guild_Member_Add_Payload + const guild = cache.guilds.get(options.guild_id) + if (!guild) return + + const member_count = guild.member_count() + 1 + guild.member_count = () => member_count + const member = create_member( + options, + options.guild_id, + [...guild.roles().values()].map(role => role.raw()), + guild.owner_id() + ) + guild.members.set(options.user.id, member) + + return event_handlers.guild_member_add?.(guild, member) + } + + if (data.t === "GUILD_MEMBER_REMOVE") { + const options = data.d as Guild_Ban_Payload + const guild = cache.guilds.get(options.guild_id) + if (!guild) return + + const member_count = guild.member_count() - 1 + guild.member_count = () => member_count + + const member = guild.members.get(options.user.id) + return event_handlers.guild_member_remove?.(guild, member || create_user(options.user)) + } + + if (data.t === "GUILD_MEMBER_UPDATE") { + const options = data.d as Guild_Member_Update_Payload + const guild = cache.guilds.get(options.guild_id) + if (!guild) return + + const cached_member = guild.members.get(options.user.id) + + const new_member_data = { + ...options, + premium_since: options.premium_since || undefined, + joined_at: new Date(cached_member?.joined_at() || Date.now()).toISOString(), + deaf: cached_member?.deaf() || false, + mute: cached_member?.mute() || false + } + const member = create_member( + new_member_data, + options.guild_id, + [...guild.roles().values()].map(r => r.raw()), + guild.owner_id() + ) + guild.members.set(options.user.id, member) + + if (cached_member?.nick() !== options.nick) + event_handlers.nickname_update?.(guild, member, options.nick, cached_member?.nick()) + const role_ids = cached_member?.roles() || [] + + role_ids.forEach(id => { + if (!options.roles.includes(id)) event_handlers.role_lost?.(guild, member, id) + }) + + options.roles.forEach(id => { + if (!role_ids.includes(id)) event_handlers.role_gained?.(guild, member, id) + }) + + return event_handlers.guild_member_update?.(guild, member, cached_member) + } + + if (data.t === "GUILD_MEMBERS_CHUNK") { + const options = data.d as Guild_Member_Chunk_Payload + const guild = cache.guilds.get(options.guild_id) + if (!guild) return + + options.members.forEach(member => + guild.members.set( + member.user.id, + create_member( + member, + options.guild_id, + [...guild.roles().values()].map(r => r.raw()), + guild.owner_id() + ) + ) + ) + } + + if (data.t && ["GUILD_ROLE_CREATE", "GUILD_ROLE_DELETE", "GUILD_ROLE_UPDATE"].includes(data.t)) { + const options = data.d as Guild_Role_Payload + const guild = cache.guilds.get(options.guild_id) + if (!guild) return + + if (data.t === "GUILD_ROLE_CREATE") { + const role = create_role(options.role) + const roles = guild.roles().set(options.role.id, role) + guild.roles = () => roles + return event_handlers.role_create?.(guild, role) + } + + const cached_role = guild.roles().get(options.role.id) + if (!cached_role) return + + if (data.t === "GUILD_ROLE_DELETE") { + const roles = guild.roles() + roles.delete(options.role.id) + guild.roles = () => roles + return event_handlers.role_delete?.(guild, cached_role) + } + + if (data.t === "GUILD_ROLE_UPDATE") { + const role = create_role(options.role) + return event_handlers.role_update?.(guild, role, cached_role) + } + } + + if (data.t === "MESSAGE_CREATE") { + const options = data.d as Message_Create_Options + const message = create_message(options) + const channel = message.channel() + if (channel) { + // channel.last_message_id = () => options.id + // if (channel.messages().size > 99) { + // TODO: LIMIT THIS TO 100 messages + // } + } + return event_handlers.message_create?.(message) + } + + if (data.t && ["MESSAGE_DELETE", "MESSAGE_DELETE_BULK"].includes(data.t)) { + const options = data.d as Message_Delete_Payload + const deleted_messages = + data.t === "MESSAGE_DELETE" ? [options.id] : (data.d as Message_Delete_Bulk_Payload).ids + + const channel = cache.channels.get(options.channel_id) + if (!channel) return + + deleted_messages.forEach(id => { + console.log(id) + // const message = channel.messages().get(id) + // if (message) { + // // TODO: update the messages cache + // } + + // return event_handlers.message_delete?.(message || { id, channel }) + }) + } + + if (data.t === "MESSAGE_UPDATE") { + const options = data.d as Message_Update_Payload + const channel = cache.channels.get(options.channel_id) + if (!channel) return + + // const cachedMessage = channel.messages().get(options.id) + // return event_handlers.message_update?.(message, cachedMessage) + } + + if (data.t && ["MESSAGE_REACTION_ADD", "MESSAGE_REACTION_REMOVE"].includes(data.t)) { + const options = data.d as Message_Reaction_Payload + const message = cache.messages.get(options.message_id) + const isAdd = data.t === "MESSAGE_REACTION_ADD" + + if (message) { + const previous_reactions = message.reactions() + const reaction_existed = previous_reactions.find( + reaction => reaction.emoji.id === options.emoji.id && reaction.emoji.name === options.emoji.name + ) + if (reaction_existed) reaction_existed.count = isAdd ? reaction_existed.count + 1 : reaction_existed.count - 1 + else + message.reactions = () => [ + ...message.reactions(), + { + count: 1, + me: options.user_id === bot_id, + emoji: { ...options.emoji, id: options.emoji.id || undefined } + } + ] + + cache.messages.set(options.message_id, message) + } + + return isAdd + ? event_handlers.reaction_add?.(message || options, options.emoji, options.user_id) + : event_handlers.reaction_remove?.(message || options, options.emoji, options.user_id) + } + + if (data.t === "MESSAGE_REACTION_REMOVE_ALL") { + return event_handlers.reaction_remove_all?.(data.d as Base_Message_Reaction_Payload) + } + + if (data.t === "MESSAGE_REACTION_REMOVE_EMOJI") { + return event_handlers.reaction_remove_emoji?.(data.d as Message_Reaction_Remove_Emoji_Payload) + } + + if (data.t === "PRESENCE_UPDATE") { + return event_handlers.presence_update?.(data.d as Presence_Update_Payload) + } + + if (data.t === "TYPING_START") { + return event_handlers.typing_start?.(data.d as Typing_Start_Payload) + } + + if (data.t === "USER_UPDATE") { + const user_data = data.d as User_Payload + const cached_user = cache.users.get(bot_id) + const user = create_user(user_data) + cache.users.set(user_data.id, user) + return event_handlers.bot_update?.(user, cached_user) + } + + if (data.t === "VOICE_STATE_UPDATE") { + const payload = data.d as Voice_State_Update_Payload + if (!payload.guild_id) return + + const guild = cache.guilds.get(payload.guild_id) + if (!guild) return + + const member = guild.members.get(payload.user_id) + if (!member) return + + const cached_state = guild.voice_states().find(state => state.user_id === payload.user_id) + // No cached state before so lets make one for em + if (!cached_state) return (guild.voice_states = () => [...guild.voice_states(), payload]) + + if (cached_state.channel_id !== payload.channel_id) { + // Either joined or moved channels + if (payload.channel_id) { + cached_state.channel_id + ? // Was in a channel before + event_handlers.voice_channel_switch?.(member, payload.channel_id, cached_state.channel_id) + : // Was not in a channel before so user just joined + event_handlers.voice_channel_join?.(member, payload.channel_id) + } + // Left the channel + else if (cached_state.channel_id) { + event_handlers.voice_channel_leave?.(member, cached_state.channel_id) + } + } + + return event_handlers.voice_state_update?.(member, payload) + } + + if (data.t === "WEBHOOKS_UPDATE") { + const options = data.d as Webhook_Update_Payload + return event_handlers.webhooks_update?.(options.channel_id, options.guild_id) + } + + return event_handlers.raw?.(data) + default: + return + } +} + +export default create_client diff --git a/module/sharding_manager.ts b/module/sharding_manager.ts index 09237294c..1f971dffd 100644 --- a/module/sharding_manager.ts +++ b/module/sharding_manager.ts @@ -1,4 +1,9 @@ +import { WebSocket } from "https://deno.land/std/ws/mod.ts"import { Client_Options } from "../types/options" export const spawnShards = (total: number, id = 1) => { // this.ShardingManager.spawnShard(id); if (id < total) spawnShards(total, id + 1) } + +export const spawnShardss = (total: number, socket: WebSocket, data: Client_Options, payload: unknown) => { + +} diff --git a/structures/channel.ts b/structures/channel.ts index 412a8b645..d1d590ba1 100644 --- a/structures/channel.ts +++ b/structures/channel.ts @@ -7,7 +7,7 @@ import { MessageContent, Create_Invite_Options } from "../types/channel.ts" -import Client from "../module/client.ts" +import { bot_id } from "../module/client.ts" import { endpoints } from "../constants/discord.ts" import { create_message, Message } from "./message.ts" import { Message_Create_Options } from "../types/message.ts" @@ -17,7 +17,7 @@ import { Errors } from "../types/errors.ts" import { Request_Manager } from "../module/request_manager.ts" import { cache } from "../utils/cache.ts" -export const create_channel = (data: Channel_Create_Payload, client: Client) => { +export const create_channel = (data: Channel_Create_Payload) => { const channel = { /** The raw channel data */ raw: () => data, @@ -45,20 +45,20 @@ export const create_channel = (data: Channel_Create_Payload, client: Client) => /** Fetch a single message from the server. Requires VIEW_CHANNEL and READ_MESSAGE_HISTORY */ get_message: async (id: string) => { if (data.guild_id) { - if (!bot_has_permission(data.guild_id, client.bot_id, [Permissions.VIEW_CHANNEL])) + if (!bot_has_permission(data.guild_id, bot_id, [Permissions.VIEW_CHANNEL])) throw new Error(Errors.MISSING_VIEW_CHANNEL) - if (!bot_has_permission(data.guild_id, client.bot_id, [Permissions.READ_MESSAGE_HISTORY])) + if (!bot_has_permission(data.guild_id, bot_id, [Permissions.READ_MESSAGE_HISTORY])) throw new Error(Errors.MISSING_READ_MESSAGE_HISTORY) } const result = await Request_Manager.get(endpoints.CHANNEL_MESSAGE(data.id, id)) - return create_message(result, client) + return create_message(result) }, /** 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) => { if (data.guild_id) { - if (!bot_has_permission(data.guild_id, client.bot_id, [Permissions.VIEW_CHANNEL])) + if (!bot_has_permission(data.guild_id, bot_id, [Permissions.VIEW_CHANNEL])) throw new Error(Errors.MISSING_VIEW_CHANNEL) - if (!bot_has_permission(data.guild_id, client.bot_id, [Permissions.READ_MESSAGE_HISTORY])) + if (!bot_has_permission(data.guild_id, bot_id, [Permissions.READ_MESSAGE_HISTORY])) throw new Error(Errors.MISSING_READ_MESSAGE_HISTORY) } @@ -68,28 +68,28 @@ export const create_channel = (data: Channel_Create_Payload, client: Client) => endpoints.CHANNEL_MESSAGES(data.id), options )) as Message_Create_Options[] - return result.map(res => create_message(res, client)) + return result.map(res => create_message(res)) }, /** Get pinned messages in this channel. */ get_pins: async () => { const result = (await Request_Manager.get(endpoints.CHANNEL_PINS(data.id))) as Message_Create_Options[] - return result.map(res => create_message(res, client)) + return result.map(res => create_message(res)) }, /** Send a message to the channel. Requires SEND_MESSAGES permission. */ send_message: async (content: string | MessageContent) => { if (typeof content === "string") content = { content } if (data.guild_id) { - if (!bot_has_permission(data.guild_id, client.bot_id, [Permissions.SEND_MESSAGES])) + if (!bot_has_permission(data.guild_id, bot_id, [Permissions.SEND_MESSAGES])) throw new Error(Errors.MISSING_SEND_MESSAGES) - if (content.tts && !bot_has_permission(data.guild_id, client.bot_id, [Permissions.SEND_TTS_MESSAGES])) + if (content.tts && !bot_has_permission(data.guild_id, bot_id, [Permissions.SEND_TTS_MESSAGES])) throw new Error(Errors.MISSING_SEND_TTS_MESSAGE) } if (content.content && content.content.length > 2000) throw new Error(Errors.MESSAGE_MAX_LENGTH) const result = await Request_Manager.post(endpoints.CHANNEL_MESSAGES(data.id), content) - return create_message(result, client) + return create_message(result) }, /** The position of the channel in the server. If this channel does not have a position for example DM channels, it will be -1 */ position: () => { @@ -103,7 +103,7 @@ export const create_channel = (data: Channel_Create_Payload, client: Client) => mention: () => `<#${data.id}>`, /** Delete messages from the channel. 2-100. Requires the MANAGE_MESSAGES permission */ delete_messages: (ids: string[], reason?: string) => { - if (data.guild_id && !bot_has_permission(data.guild_id, client.bot_id, [Permissions.MANAGE_MESSAGES])) + if (data.guild_id && !bot_has_permission(data.guild_id, bot_id, [Permissions.MANAGE_MESSAGES])) throw new Error(Errors.MISSING_MANAGE_MESSAGES) if (ids.length < 2) throw new Error(Errors.DELETE_MESSAGES_MIN) @@ -119,19 +119,19 @@ export const create_channel = (data: Channel_Create_Payload, client: Client) => }, /** Gets the invites for this channel. Requires MANAGE_CHANNEL */ get_invites: () => { - if (data.guild_id && !bot_has_permission(data.guild_id, client.bot_id, [Permissions.MANAGE_CHANNELS])) + if (data.guild_id && !bot_has_permission(data.guild_id, bot_id, [Permissions.MANAGE_CHANNELS])) throw new Error(Errors.MISSING_MANAGE_CHANNELS) return Request_Manager.get(endpoints.CHANNEL_INVITES(data.id)) }, /** Creates a new invite for this channel. Requires CREATE_INSTANT_INVITE */ create_invite: (options: Create_Invite_Options) => { - if (data.guild_id && !bot_has_permission(data.guild_id, client.bot_id, [Permissions.CREATE_INSTANT_INVITE])) + if (data.guild_id && !bot_has_permission(data.guild_id, bot_id, [Permissions.CREATE_INSTANT_INVITE])) throw new Error(Errors.MISSING_CREATE_INSTANT_INVITE) return Request_Manager.post(endpoints.CHANNEL_INVITES(data.id), options) }, /** Gets the webhooks for this channel. Requires MANAGE_WEBHOOKS */ get_webhooks: () => { - if (data.guild_id && !bot_has_permission(data.guild_id, client.bot_id, [Permissions.MANAGE_WEBHOOKS])) + if (data.guild_id && !bot_has_permission(data.guild_id, bot_id, [Permissions.MANAGE_WEBHOOKS])) throw new Error(Errors.MISSING_MANAGE_WEBHOOKS) return Request_Manager.get(endpoints.CHANNEL_WEBHOOKS(data.id)) } diff --git a/structures/guild.ts b/structures/guild.ts index 898007416..f8f99aed9 100644 --- a/structures/guild.ts +++ b/structures/guild.ts @@ -1,4 +1,4 @@ -import Client from "../module/client.ts" +import { bot_id } from "../module/client.ts" import { endpoints } from "../constants/discord.ts" import { format_image_url } from "../utils/cdn.ts" import { @@ -22,8 +22,9 @@ import { Permissions, Permission } from "../types/permission.ts" import { bot_has_permission } from "../utils/permissions.ts" import { Errors } from "../types/errors.ts" import { Request_Manager } from "../module/request_manager.ts" +import { Role_Data } from "../types/role.ts" -export const create_guild = (data: Create_Guild_Payload, client: Client) => { +export const create_guild = (data: Create_Guild_Payload) => { const guild = { /** The raw create guild payload data. */ raw: () => data, @@ -66,9 +67,9 @@ export const create_guild = (data: Create_Guild_Payload, client: Client) => { /** The current open voice states in the guild. */ voice_states: () => data.voice_states, /** The users in this guild. */ - members: new Map(data.members.map(m => [m.user.id, create_member(m, data.id, data.roles, data.owner_id, client)])), + members: new Map(data.members.map(m => [m.user.id, create_member(m, data.id, data.roles, data.owner_id)])), /** The channels in the guild */ - channels: new Map(data.channels.map(c => [c.id, create_channel(c, client)])), + channels: new Map(data.channels.map(c => [c.id, create_channel(c)])), /** 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.) */ @@ -100,7 +101,7 @@ export const create_guild = (data: Create_Guild_Payload, client: Client) => { 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) => { - if (!bot_has_permission(data.id, client.bot_id, [Permissions.MANAGE_CHANNELS])) + if (!bot_has_permission(data.id, bot_id, [Permissions.MANAGE_CHANNELS])) throw new Error(Errors.MISSING_MANAGE_CHANNELS) return Request_Manager.post(endpoints.GUILD_CHANNELS(data.id), { name, @@ -138,7 +139,7 @@ export const create_guild = (data: Create_Guild_Payload, client: Client) => { }, /** 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) => { - if (!bot_has_permission(data.id, client.bot_id, [Permissions.MANAGE_EMOJIS])) + if (!bot_has_permission(data.id, bot_id, [Permissions.MANAGE_EMOJIS])) throw new Error(Errors.MISSING_MANAGE_EMOJIS) return Request_Manager.post(endpoints.GUILD_EMOJIS(data.id), { ...options, @@ -148,7 +149,7 @@ export const create_guild = (data: Create_Guild_Payload, client: Client) => { }, /** Modify the given emoji. Requires the MANAGE_EMOJIS permission. */ edit_emoji: (id: string, options: Edit_Emojis_Options) => { - if (!bot_has_permission(data.id, client.bot_id, [Permissions.MANAGE_EMOJIS])) + if (!bot_has_permission(data.id, bot_id, [Permissions.MANAGE_EMOJIS])) throw new Error(Errors.MISSING_MANAGE_EMOJIS) return Request_Manager.patch(endpoints.GUILD_EMOJI(data.id, id), { name: options.name, @@ -157,30 +158,33 @@ export const create_guild = (data: Create_Guild_Payload, client: Client) => { }, /** Delete the given emoji. Requires the MANAGE_EMOJIS permission. Returns 204 No Content on success. */ delete_emoji: (id: string, reason?: string) => { - if (!bot_has_permission(data.id, client.bot_id, [Permissions.MANAGE_EMOJIS])) + if (!bot_has_permission(data.id, bot_id, [Permissions.MANAGE_EMOJIS])) throw new Error(Errors.MISSING_MANAGE_EMOJIS) return Request_Manager.delete(endpoints.GUILD_EMOJI(data.id, id), { reason }) }, /** Create a new role for the guild. Requires the MANAGE_ROLES permission. */ - create_role: async (options: Create_Role_Options) => { - if (!bot_has_permission(data.id, client.bot_id, [Permissions.MANAGE_ROLES])) + create_role: async (options: Create_Role_Options, reason?: string) => { + if (!bot_has_permission(data.id, bot_id, [Permissions.MANAGE_ROLES])) throw new Error(Errors.MISSING_MANAGE_ROLES) - const role = await Request_Manager.post(endpoints.GUILD_ROLES(data.id), { + const role_data = await Request_Manager.post(endpoints.GUILD_ROLES(data.id), { ...options, - permissions: options.permissions?.map(perm => Permissions[perm]) + permissions: options.permissions?.map(perm => Permissions[perm]), + reason }) + const role = create_role(role_data as Role_Data) + guild.roles().set(role_data.id, role) return role }, /** Edit a guild role. Requires the MANAGE_ROLES permission. */ edit_role: (id: string, options: Create_Role_Options) => { - if (!bot_has_permission(data.id, client.bot_id, [Permissions.MANAGE_ROLES])) + if (!bot_has_permission(data.id, bot_id, [Permissions.MANAGE_ROLES])) throw new Error(Errors.MISSING_MANAGE_ROLES) return Request_Manager.patch(endpoints.GUILD_ROLE(data.id, id), options) }, /** Delete a guild role. Requires the MANAGE_ROLES permission. */ delete_role: (id: string) => { - if (!bot_has_permission(data.id, client.bot_id, [Permissions.MANAGE_ROLES])) + if (!bot_has_permission(data.id, bot_id, [Permissions.MANAGE_ROLES])) throw new Error(Errors.MISSING_MANAGE_ROLES) return Request_Manager.delete(endpoints.GUILD_ROLE(data.id, id)) }, @@ -189,20 +193,20 @@ export const create_guild = (data: Create_Guild_Payload, client: Client) => { * ⚠️ **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: () => { - if (!bot_has_permission(data.id, client.bot_id, [Permissions.MANAGE_ROLES])) + if (!bot_has_permission(data.id, bot_id, [Permissions.MANAGE_ROLES])) throw new Error(Errors.MISSING_MANAGE_ROLES) return Request_Manager.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) => { - if (!bot_has_permission(data.id, client.bot_id, [Permissions.MANAGE_ROLES])) + if (!bot_has_permission(data.id, bot_id, [Permissions.MANAGE_ROLES])) throw new Error(Errors.MISSING_MANAGE_ROLES) return Request_Manager.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 new Error(Errors.PRUNE_MIN_DAYS) - if (!bot_has_permission(data.id, client.bot_id, [Permissions.KICK_MEMBERS])) + if (!bot_has_permission(data.id, bot_id, [Permissions.KICK_MEMBERS])) throw new Error(Errors.MISSING_KICK_MEMBERS) const result = (await Request_Manager.get(endpoints.GUILD_PRUNE(data.id), { days })) as PrunePayload return result.pruned @@ -210,7 +214,7 @@ export const create_guild = (data: Create_Guild_Payload, client: Client) => { /** Begin pruning all members in the given time period */ prune_members: (days: number) => { if (days < 1) throw new Error(Errors.PRUNE_MIN_DAYS) - if (!bot_has_permission(data.id, client.bot_id, [Permissions.KICK_MEMBERS])) + if (!bot_has_permission(data.id, bot_id, [Permissions.KICK_MEMBERS])) throw new Error(Errors.MISSING_KICK_MEMBERS) return Request_Manager.post(endpoints.GUILD_PRUNE(data.id), { days }) }, @@ -219,7 +223,7 @@ export const create_guild = (data: Create_Guild_Payload, client: Client) => { // }, /** Returns the audit logs for the guild. Requires VIEW AUDIT LOGS permission */ get_audit_logs: (options: Get_Audit_Logs_Options) => { - if (!bot_has_permission(data.id, client.bot_id, [Permissions.VIEW_AUDIT_LOG])) + if (!bot_has_permission(data.id, bot_id, [Permissions.VIEW_AUDIT_LOG])) throw new Error(Errors.MISSING_VIEW_AUDIT_LOG) return Request_Manager.get(endpoints.GUILD_AUDIT_LOGS(data.id), { @@ -229,13 +233,13 @@ export const create_guild = (data: Create_Guild_Payload, client: Client) => { }, /** Returns the guild embed object. Requires the MANAGE_GUILD permission. */ get_embed: () => { - if (!bot_has_permission(data.id, client.bot_id, [Permissions.MANAGE_GUILD])) + if (!bot_has_permission(data.id, bot_id, [Permissions.MANAGE_GUILD])) throw new Error(Errors.MISSING_MANAGE_GUILD) return Request_Manager.get(endpoints.GUILD_EMBED(data.id)) }, /** Modify a guild embed object for the guild. Requires the MANAGE_GUILD permission. */ edit_embed: (enabled: boolean, channel_id?: string | null) => { - if (!bot_has_permission(data.id, client.bot_id, [Permissions.MANAGE_GUILD])) + if (!bot_has_permission(data.id, bot_id, [Permissions.MANAGE_GUILD])) throw new Error(Errors.MISSING_MANAGE_GUILD) return Request_Manager.patch(endpoints.GUILD_EMBED(data.id), { enabled, channel_id }) }, @@ -245,43 +249,43 @@ export const create_guild = (data: Create_Guild_Payload, client: Client) => { }, /** Returns a list of integrations for the guild. Requires the MANAGE_GUILD permission. */ get_integrations: () => { - if (!bot_has_permission(data.id, client.bot_id, [Permissions.MANAGE_GUILD])) + if (!bot_has_permission(data.id, bot_id, [Permissions.MANAGE_GUILD])) throw new Error(Errors.MISSING_MANAGE_GUILD) return Request_Manager.get(endpoints.GUILD_INTEGRATIONS(data.id)) }, /** 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) => { - if (!bot_has_permission(data.id, client.bot_id, [Permissions.MANAGE_GUILD])) + if (!bot_has_permission(data.id, bot_id, [Permissions.MANAGE_GUILD])) throw new Error(Errors.MISSING_MANAGE_GUILD) return Request_Manager.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) => { - if (!bot_has_permission(data.id, client.bot_id, [Permissions.MANAGE_GUILD])) + if (!bot_has_permission(data.id, bot_id, [Permissions.MANAGE_GUILD])) throw new Error(Errors.MISSING_MANAGE_GUILD) return Request_Manager.delete(endpoints.GUILD_INTEGRATION(data.id, id)) }, /** Sync an integration. Requires teh MANAGE_GUILD permission. */ sync_integration: (id: string) => { - if (!bot_has_permission(data.id, client.bot_id, [Permissions.MANAGE_GUILD])) + if (!bot_has_permission(data.id, bot_id, [Permissions.MANAGE_GUILD])) throw new Error(Errors.MISSING_MANAGE_GUILD) return Request_Manager.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: () => { - if (!bot_has_permission(data.id, client.bot_id, [Permissions.BAN_MEMBERS])) + if (!bot_has_permission(data.id, bot_id, [Permissions.BAN_MEMBERS])) throw new Error(Errors.MISSING_BAN_MEMBERS) return Request_Manager.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) => { - if (!bot_has_permission(data.id, client.bot_id, [Permissions.BAN_MEMBERS])) + if (!bot_has_permission(data.id, bot_id, [Permissions.BAN_MEMBERS])) throw new Error(Errors.MISSING_BAN_MEMBERS) return Request_Manager.put(endpoints.GUILD_BAN(data.id, id), options) }, /** Remove the ban for a user. REquires BAN_MEMBERS permission */ unban: (id: string) => { - if (!bot_has_permission(data.id, client.bot_id, [Permissions.BAN_MEMBERS])) + if (!bot_has_permission(data.id, bot_id, [Permissions.BAN_MEMBERS])) throw new Error(Errors.MISSING_BAN_MEMBERS) return Request_Manager.delete(endpoints.GUILD_BAN(data.id, id)) }, @@ -318,13 +322,13 @@ export const create_guild = (data: Create_Guild_Payload, client: Client) => { }, /** Modify a guilds settings. Requires the MANAGE_GUILD permission. */ edit: (options: Guild_Edit_Options) => { - if (!bot_has_permission(data.id, client.bot_id, [Permissions.MANAGE_GUILD])) + if (!bot_has_permission(data.id, bot_id, [Permissions.MANAGE_GUILD])) throw new Error(Errors.MISSING_MANAGE_GUILD) return Request_Manager.patch(endpoints.GUILD(data.id), options) }, /** Get all the invites for this guild. Requires MANAGE_GUILD permission */ get_invites: () => { - if (!bot_has_permission(data.id, client.bot_id, [Permissions.MANAGE_GUILD])) + if (!bot_has_permission(data.id, bot_id, [Permissions.MANAGE_GUILD])) throw new Error(Errors.MISSING_MANAGE_GUILD) return Request_Manager.get(endpoints.GUILD_INVITES(data.id)) }, @@ -338,7 +342,7 @@ export const create_guild = (data: Create_Guild_Payload, client: Client) => { }, /** Returns a list of guild webhooks objects. Requires the MANAGE_WEBHOOKs permission. */ get_webhooks: () => { - if (!bot_has_permission(data.id, client.bot_id, [Permissions.MANAGE_WEBHOOKS])) + if (!bot_has_permission(data.id, bot_id, [Permissions.MANAGE_WEBHOOKS])) throw new Error(Errors.MISSING_MANAGE_WEBHOOKS) return Request_Manager.get(endpoints.GUILD_WEBHOOKS(data.id)) diff --git a/structures/member.ts b/structures/member.ts index 401203b14..666253b3d 100644 --- a/structures/member.ts +++ b/structures/member.ts @@ -1,4 +1,4 @@ -import Client from "../module/client.ts" +import { bot_id } from "../module/client.ts" import { endpoints } from "../constants/discord.ts" import { format_image_url } from "../utils/cdn.ts" import { Member_Create_Payload, Edit_Member_Options } from "../types/member.ts" @@ -13,8 +13,7 @@ export const create_member = ( data: Member_Create_Payload, guild_id: string, role_data: Role_Data[], - owner_id: string, - client: Client + owner_id: string ) => ({ /** The complete raw data from the member create payload */ raw: () => data, @@ -50,21 +49,21 @@ export const create_member = ( /** Add a role to the member */ add_role: (role_id: string, reason?: string) => { // TODO: check if the bots highest role is above this one - if (!bot_has_permission(guild_id, client.bot_id, [Permissions.MANAGE_ROLES])) + if (!bot_has_permission(guild_id, bot_id, [Permissions.MANAGE_ROLES])) throw new Error(Errors.MISSING_MANAGE_ROLES) return Request_Manager.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 bots highest role is above this role - if (!bot_has_permission(guild_id, client.bot_id, [Permissions.MANAGE_ROLES])) + if (!bot_has_permission(guild_id, bot_id, [Permissions.MANAGE_ROLES])) throw new Error(Errors.MISSING_MANAGE_ROLES) return Request_Manager.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 the bot is above the user so it is capable of kicking - if (!bot_has_permission(guild_id, client.bot_id, [Permissions.KICK_MEMBERS])) + if (!bot_has_permission(guild_id, bot_id, [Permissions.KICK_MEMBERS])) throw new Error(Errors.MISSING_KICK_MEMBERS) return Request_Manager.delete(endpoints.GUILD_MEMBER(guild_id, data.user.id), { reason }) }, @@ -72,20 +71,20 @@ export const create_member = ( edit: (options: Edit_Member_Options) => { if (options.nick) { if (options.nick.length > 32) throw new Error(Errors.NICKNAMES_MAX_LENGTH) - if (!bot_has_permission(guild_id, client.bot_id, [Permissions.MANAGE_NICKNAMES])) + if (!bot_has_permission(guild_id, bot_id, [Permissions.MANAGE_NICKNAMES])) throw new Error(Errors.MISSING_MANAGE_NICKNAMES) } - if (options.roles && !bot_has_permission(guild_id, client.bot_id, [Permissions.MANAGE_ROLES])) + if (options.roles && !bot_has_permission(guild_id, bot_id, [Permissions.MANAGE_ROLES])) throw new Error(Errors.MISSING_MANAGE_ROLES) if (options.mute) { // TODO: This should check if the member is in a voice channel - if (!bot_has_permission(guild_id, client.bot_id, [Permissions.MUTE_MEMBERS])) + if (!bot_has_permission(guild_id, bot_id, [Permissions.MUTE_MEMBERS])) throw new Error(Errors.MISSING_MUTE_MEMBERS) } - if (options.deaf && !bot_has_permission(guild_id, client.bot_id, [Permissions.DEAFEN_MEMBERS])) + if (options.deaf && !bot_has_permission(guild_id, bot_id, [Permissions.DEAFEN_MEMBERS])) throw new Error(Errors.MISSING_DEAFEN_MEMBERS) // TODO: if channel id is provided check if the bot has CONNECT and MOVE in channel and current channel diff --git a/structures/message.ts b/structures/message.ts index 753946f22..f4170435c 100644 --- a/structures/message.ts +++ b/structures/message.ts @@ -1,4 +1,3 @@ -import Client from "../module/client.ts" import { Message_Create_Options } from "../types/message.ts" import { endpoints } from "../constants/discord.ts" import { MessageContent } from "../types/channel.ts" @@ -10,8 +9,9 @@ import { bot_has_permission } from "../utils/permissions.ts" import { Errors } from "../types/errors.ts" import { Permissions } from "../types/permission.ts" import { Request_Manager } from "../module/request_manager.ts" +import { bot_id } from "../module/client.ts" -export const create_message = (data: Message_Create_Options, client: Client) => ({ +export const create_message = (data: Message_Create_Options) => ({ raw: () => data, author: () => create_user({ ...data.author, avatar: data.author.avatar || "" }), id: () => data.id, @@ -43,21 +43,21 @@ export const create_message = (data: Message_Create_Options, client: Client) => /** Delete a message */ delete: (reason?: string) => { - if (data.guild_id && !bot_has_permission(data.guild_id, client.bot_id, [Permissions.MANAGE_MESSAGES])) + if (data.guild_id && !bot_has_permission(data.guild_id, bot_id, [Permissions.MANAGE_MESSAGES])) throw new Error(Errors.MISSING_MANAGE_MESSAGES) - if (data.author.id !== client.bot_id) { + if (data.author.id !== bot_id) { } Request_Manager.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: () => { - if (data.guild_id && !bot_has_permission(data.guild_id, client.bot_id, [Permissions.MANAGE_MESSAGES])) + if (data.guild_id && !bot_has_permission(data.guild_id, bot_id, [Permissions.MANAGE_MESSAGES])) throw new Error(Errors.MISSING_MANAGE_MESSAGES) Request_Manager.put(endpoints.CHANNEL_MESSAGE(data.channel_id, data.id)) }, unpin: () => { - if (data.guild_id && !bot_has_permission(data.guild_id, client.bot_id, [Permissions.MANAGE_MESSAGES])) + if (data.guild_id && !bot_has_permission(data.guild_id, bot_id, [Permissions.MANAGE_MESSAGES])) throw new Error(Errors.MISSING_MANAGE_MESSAGES) Request_Manager.delete(endpoints.CHANNEL_MESSAGE(data.channel_id, data.id)) }, @@ -71,13 +71,13 @@ export const create_message = (data: Message_Create_Options, client: Client) => }, /** Removes all reactions for all emojis on this message. */ remove_all_reactions: () => { - if (data.guild_id && !bot_has_permission(data.guild_id, client.bot_id, [Permissions.MANAGE_MESSAGES])) + if (data.guild_id && !bot_has_permission(data.guild_id, bot_id, [Permissions.MANAGE_MESSAGES])) throw new Error(Errors.MISSING_MANAGE_MESSAGES) Request_Manager.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) => { - if (data.guild_id && !bot_has_permission(data.guild_id, client.bot_id, [Permissions.MANAGE_MESSAGES])) + if (data.guild_id && !bot_has_permission(data.guild_id, bot_id, [Permissions.MANAGE_MESSAGES])) throw new Error(Errors.MISSING_MANAGE_MESSAGES) Request_Manager.delete(endpoints.CHANNEL_MESSAGE_REACTION(data.channel_id, data.id, reaction)) }, @@ -90,22 +90,22 @@ export const create_message = (data: Message_Create_Options, client: Client) => }, /** 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.author.id !== bot_id) throw "You can only edit a message that was sent by the bot." if (typeof content === "string") content = { content } if (data.guild_id) { - if (!bot_has_permission(data.guild_id, client.bot_id, [Permissions.SEND_MESSAGES])) + if (!bot_has_permission(data.guild_id, bot_id, [Permissions.SEND_MESSAGES])) throw new Error(Errors.MISSING_SEND_MESSAGES) - if (content.tts && !bot_has_permission(data.guild_id, client.bot_id, [Permissions.SEND_TTS_MESSAGES])) + if (content.tts && !bot_has_permission(data.guild_id, bot_id, [Permissions.SEND_TTS_MESSAGES])) throw new Error(Errors.MISSING_SEND_TTS_MESSAGE) } if (content.content && content.content.length > 2000) throw new Error(Errors.MESSAGE_MAX_LENGTH) const result = await Request_Manager.patch(endpoints.CHANNEL_MESSAGE(data.channel_id, data.id), content) - return create_message(result, client) + return create_message(result) } })