From c7054516b56f1c50f767557c50db1474a4fa4175 Mon Sep 17 00:00:00 2001 From: Skillz Date: Mon, 2 Mar 2020 16:16:43 -0500 Subject: [PATCH] even more events done only few left --- events/guilds.ts | 4 + module/client.ts | 203 ++++++++++++++++++++++++++++++++++++++++- structures/message.ts | 4 +- structures/presence.ts | 58 ++++++------ structures/user.ts | 31 +------ types/guild.ts | 55 ++++++++++- types/member.ts | 2 +- types/message.ts | 27 +++++- types/options.ts | 21 ++++- utils/cache.ts | 1 + 10 files changed, 334 insertions(+), 72 deletions(-) diff --git a/events/guilds.ts b/events/guilds.ts index f84f10345..da114beda 100644 --- a/events/guilds.ts +++ b/events/guilds.ts @@ -8,3 +8,7 @@ export const handle_internal_guild_create = (guild: Guild) => { export const handle_internal_guild_update = (guild: Guild) => { cache.guilds.set(guild.id(), guild) } + +export const handle_internal_guild_delete = (guild: Guild) => { + cache.guilds.delete(guild.id()) +} diff --git a/module/client.ts b/module/client.ts index 94684a75f..de8d1e084 100644 --- a/module/client.ts +++ b/module/client.ts @@ -13,8 +13,21 @@ import { ClientOptions, FulfilledClientOptions, Event_Handlers } from "../types/ 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" -import { handle_internal_guild_create, handle_internal_guild_update } from "../events/guilds.ts" -import { Create_Guild_Payload, Guild_Delete_Payload } from "../types/guild.ts" +import { + handle_internal_guild_create, + handle_internal_guild_update, + handle_internal_guild_delete +} from "../events/guilds.ts" +import { + Create_Guild_Payload, + Guild_Delete_Payload, + Guild_Ban_Payload, + Guild_Emojis_Update_Payload, + Guild_Member_Add_Payload, + Guild_Member_Update_Payload, + Guild_Member_Chunk_Payload, + Guild_Role_Payload +} from "../types/guild.ts" import { create_channel } from "../structures/channel.ts" import { Channel_Create_Payload, Channel_Types } from "../types/channel.ts" import { @@ -23,6 +36,11 @@ import { handle_internal_channel_delete } from "../events/channels.ts" import { cache } from "../utils/cache.ts" +import { create_user } from "../structures/user.ts" +import { create_member } from "../structures/member.ts" +import { create_role } from "../structures/role.ts" +import { create_message } from "../structures/message.ts" +import { Message_Create_Options, Message_Delete_Payload, Message_Delete_Bulk_Payload } from "../types/message.ts" const defaultOptions = { properties: { @@ -191,6 +209,10 @@ class Client { 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 this.event_handlers.guild_create?.(guild) } @@ -204,16 +226,187 @@ class Client { return this.event_handlers.guild_update?.(guild, cached_guild) } - if (data.t === 'GUILD_DELETE') { + 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)) - return options.unavailable ? undefined : this.event_handlers.guild_delete?.(guild) + if (options.unavailable) return cache.unavailableGuilds.set(options.id, Date.now()) + + handle_internal_guild_delete(guild) + return this.event_handlers.guild_delete?.(guild) } - return console.log("UNKNOWN EVENT:", data) + 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" + ? this.event_handlers.guild_ban_add?.(guild, user) + : this.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 this.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 this.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 this.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) + this.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)) this.event_handlers.role_lost?.(guild, member, id) + }) + + options.roles.forEach(id => { + if (!role_ids.includes(id)) this.event_handlers.role_gained?.(guild, member, id) + }) + + return this.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 this.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 this.event_handlers.role_delete?.(guild, cached_role) + } + + if (data.t === 'GUILD_ROLE_UPDATE') { + const role = create_role(options.role) + return this.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 this.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 => { + const message = channel.messages().get(id) + if (message) { + // TODO: update the messages cache + } + + return this.event_handlers.message_delete?.(message || { id, channel }) + }) + } + + return this.event_handlers.raw?.(data) default: return } diff --git a/structures/message.ts b/structures/message.ts index 9f613108f..211c59c72 100644 --- a/structures/message.ts +++ b/structures/message.ts @@ -3,11 +3,13 @@ import { Message_Create_Options } from '../types/message.ts' import { endpoints } from '../constants/discord.ts' import { Channel_Types, MessageContent } from '../types/channel.ts' import { cache } from '../utils/cache.ts' -import { create_user, User_Payload } from './user.ts' +import { create_user } from './user.ts' +import { User_Payload } from '../types/guild.ts' export const create_message = (data: Message_Create_Options, client: Client) => { const base_message = { raw: () => data, + id: () => data.id, type: () => data.type, timestamp: () => Date.parse(data.timestamp), content: () => data.content, diff --git a/structures/presence.ts b/structures/presence.ts index deca21165..efd3425fc 100644 --- a/structures/presence.ts +++ b/structures/presence.ts @@ -1,46 +1,46 @@ -import { User_Payload } from "./user.ts"; -import { ActivityPayload } from "./activity.ts"; -import { StatusType } from "../types/discord.ts"; +import { ActivityPayload } from "./activity.ts" +import { StatusType } from "../types/discord.ts" +import { User_Payload } from "../types/guild.ts" export type PresencePayload = Partial<{ - /** The user presence is being updated for */ - user: User_Payload; + /** The user presence is being updated for */ + user: User_Payload - /** Roles this user is in */ - roles: string[]; + /** Roles this user is in */ + roles: string[] - /** Null, or the user's current activity */ - game: ActivityPayload; + /** Null, or the user's current activity */ + game: ActivityPayload - /** Id of the guild */ - guild_id: string; + /** Id of the guild */ + guild_id: string - // This is a deviation from the docs, as it pretty much says `: StatusType`. - /** The updated status */ - status: StatusType; + // This is a deviation from the docs, as it pretty much says `: StatusType`. + /** The updated status */ + status: StatusType - /** User's current activities */ - activities: ActivityPayload[]; + /** User's current activities */ + activities: ActivityPayload[] - /** User's platform-dependent status */ - client_status: ClientStatusPayload; + /** User's platform-dependent status */ + client_status: ClientStatusPayload - /** When the user used their Nitro boost on the server */ - premium_since: string; + /** When the user used their Nitro boost on the server */ + premium_since: string - /** This users guild nickname (if one is set) */ - nick: string; -}> & { id: string }; + /** This users guild nickname (if one is set) */ + nick: string +}> & { id: string } export interface ClientStatusPayload { - /** The user's status set for an active desktop (Windows, Linux, Mac) application session */ - desktop?: StatusType; + /** The user's status set for an active desktop (Windows, Linux, Mac) application session */ + desktop?: StatusType - /** The user's status set for an active mobile (iOS, Android) application session */ - mobile?: StatusType; + /** The user's status set for an active mobile (iOS, Android) application session */ + mobile?: StatusType - /** The user's status set for an active web (browser, bot account) application session */ - web?: StatusType; + /** The user's status set for an active web (browser, bot account) application session */ + web?: StatusType } export const createPresence = (data: unknown) => { diff --git a/structures/user.ts b/structures/user.ts index 60b6bf23a..fdd66e9dd 100644 --- a/structures/user.ts +++ b/structures/user.ts @@ -1,36 +1,7 @@ import { format_image_url } from '../utils/cdn.ts' import { endpoints } from '../constants/discord.ts' import { Image_Size, Image_Formats } from '../types/cdn.ts' - -export interface User_Payload { - /** 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 - - /** Whether the user belongs to an OAuth2 application */ - 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 - - // 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 -} +import { User_Payload } from '../types/guild.ts' export const enum PremiumType { NitroClassic = 1, diff --git a/types/guild.ts b/types/guild.ts index ca1d47501..86efbfa77 100644 --- a/types/guild.ts +++ b/types/guild.ts @@ -7,8 +7,57 @@ import { Activity } from "./message.ts" import { ClientStatusPayload } from "../structures/presence.ts" import { Channel_Create_Payload } from "./channel.ts" +export interface Guild_Role_Payload { + /** The id of the guild */ + guild_id: string + /** The role object of the role created, deleted, or updated */ + role: Role_Data +} + +export interface Guild_Member_Chunk_Payload { + /** The id of the guild */ + guild_id: string + /** The set of guild members */ + members: Member_Create_Payload[] + /** if passing an invalid id, it will be found here */ + not_found?: string[] + /** if passing true, presences of the members will be here */ + presences?: Presence[] +} + +export interface Guild_Member_Update_Payload { + /** The id of the guild */ + guild_id: string + /** The user's role ids */ + roles: string[] + /** The user */ + user: User_Payload + /** The nickname of the user in the guild */ + nick: string + /** When the user used their nitro boost on the guild. */ + premium_since: string | null +} + +export interface Guild_Member_Add_Payload extends Member_Create_Payload { + guild_id: string +} + +export interface Guild_Emojis_Update_Payload { + guild_id: string + emojis: Emoji[] +} + +export interface Guild_Ban_Payload { + /** The id of the guild */ + guild_id: string + /** The banned user. Not a member as you can ban users outside of your guild. */ + user: User_Payload +} + export interface Guild_Delete_Payload { + /** The id of the guild */ id: string + /** Whether this guild went unavailable. */ unavailable?: boolean } @@ -181,7 +230,7 @@ export interface Guild_Integration { /** The grace period before expiring subscribers */ expire_grace_period: number /** The user for this integration */ - user: User_Data + user: User_Payload /** The integration account information */ account: Account /** When this integration was last synced */ @@ -195,7 +244,7 @@ export interface Account { name: string } -export interface User_Data { +export interface User_Payload { /** The user's id */ id: string /** the user's username, not unique across the platform */ @@ -446,7 +495,7 @@ export interface Voice_State { export interface Presence { /** The user presence is being updated for */ - user: User_Data + user: User_Payload /** The roles this user is in */ roles: string[] /** null, or the user's current activity */ diff --git a/types/member.ts b/types/member.ts index 3dc66b95d..4bfe313d7 100644 --- a/types/member.ts +++ b/types/member.ts @@ -1,5 +1,5 @@ -import { User_Payload } from "../structures/user.ts" import { create_member } from "../structures/member.ts" +import { User_Payload } from "./guild.ts" export interface Edit_Member_Options { /** Value to set users nickname to. Requires MANAGE_NICKNAMES permission. */ diff --git a/types/message.ts b/types/message.ts index d535f928e..b5d230955 100644 --- a/types/message.ts +++ b/types/message.ts @@ -1,6 +1,7 @@ -import { ChannelType, User_Data } from './guild.ts' +import { ChannelType, User_Payload } from './guild.ts' import { User } from '../structures/user.ts' import { Member } from './member.ts' +import { Channel } from './return-type.ts' export interface MentionedUser extends User { member: Member @@ -224,7 +225,7 @@ export interface Message_Create_Options { /** 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 + author: User_Payload /** The member properties for this message's author. Can be partial. */ member?: Member /** The contents of the message */ @@ -266,3 +267,25 @@ export interface Message_Create_Options { /** The message flags combined like permission bits describe extra features of the message */ flags?: 1 | 2 | 4 | 8 | 16 } + +export interface Base_Message_Delete_Payload { + /** The id of the channel */ + channel_id: string + /** The id of the guild */ + guild_id?: string +} + +export interface Message_Delete_Payload extends Base_Message_Delete_Payload { + /** The id of the message */ + id: string +} + +export interface Message_Delete_Bulk_Payload extends Base_Message_Delete_Payload { + /** The ids of the messages */ + ids: string[] +} + +export interface Partial_Message { + id: string, + channel: Channel +} diff --git a/types/options.ts b/types/options.ts index 9512a7166..2d7e064e6 100644 --- a/types/options.ts +++ b/types/options.ts @@ -1,6 +1,10 @@ -import { Properties } from "./discord.ts" +import { Properties, Emoji, DiscordPayload } from "./discord.ts" import { Channel, Guild } from "./return-type.ts" import { Voice_State } from "./guild.ts" +import { User } from "../structures/user.ts" +import { Member } from "./member.ts" +import { Role } from "../structures/role.ts" +import { Message } from "../structures/message.ts" export interface FulfilledClientOptions { token: string @@ -22,12 +26,27 @@ export interface Event_Handlers { channel_create?: (channel: Channel) => unknown channel_update?: (channel: Channel, cached_channel: Channel) => unknown channel_delete?: (channel: Channel) => unknown + guild_ban_add?: (guild: Guild, user: User) => unknown + guild_ban_remove?: (guild: Guild, user: User) => unknown guild_create?: (guild: Guild) => unknown guild_update?: (guild: Guild, cached_guild: Guild) => unknown guild_delete?: (guild: Guild) => unknown + guild_emojis_update?: (guild: Guild, emojis: Emoji[], cached_emojis: Emoji[]) => unknown + guild_member_add?: (guild: Guild, member: Member) => unknown + guild_member_remove?: (guild: Guild, member: Member | User) => unknown + guild_member_update?: (guild: Guild, member: Member, cached_member?: Member) => unknown heartbeat?: () => unknown + message_create?: (message: Message) => unknown + message_delete?: (message: Message | Partial_Message) => unknown + nickname_update?: (guild: Guild, member: Member, nickname: string, old_nickname?: string) => unknown ready?: () => unknown + role_create?: (guild: Guild, role: Role) => unknown + role_delete?: (guild: Guild, role: Role) => unknown + role_update?: (guild: Guild, role: Role, cached_role: Role) => unknown + role_gained?: (guild: Guild, member: Member, role_id: string) => unknown + role_lost?: (guild: Guild, member: Member, role_id: string) => unknown voice_channel_leave?: (voice_state: Voice_State) => unknown + raw?: (data: DiscordPayload) => unknown } export enum Intents { diff --git a/utils/cache.ts b/utils/cache.ts index f0d243c42..ac1a116d8 100644 --- a/utils/cache.ts +++ b/utils/cache.ts @@ -5,4 +5,5 @@ export const cache = { guilds: new Map(), users: new Map(), channels: new Map(), + unavailableGuilds: new Map() }