diff --git a/src/gateway/handlers/guildCreate.ts b/src/gateway/handlers/guildCreate.ts index a40d0ac..b2251c5 100644 --- a/src/gateway/handlers/guildCreate.ts +++ b/src/gateway/handlers/guildCreate.ts @@ -22,6 +22,8 @@ export const guildCreate: GatewayEventHandler = async ( await guild.roles.fromPayload(d.roles) + if (d.presences !== undefined) await guild.presences.fromPayload(d.presences) + if (d.voice_states !== undefined) await guild.voiceStates.fromPayload(d.voice_states) diff --git a/src/gateway/handlers/guildDelete.ts b/src/gateway/handlers/guildDelete.ts index 369181d..1104840 100644 --- a/src/gateway/handlers/guildDelete.ts +++ b/src/gateway/handlers/guildDelete.ts @@ -14,6 +14,7 @@ export const guildDelte: GatewayEventHandler = async ( await guild.members.flush() await guild.channels.flush() await guild.roles.flush() + await guild.presences.flush() await gateway.client.guilds.delete(d.id) gateway.client.emit('guildDelete', guild) diff --git a/src/gateway/handlers/guildMembersChunk.ts b/src/gateway/handlers/guildMembersChunk.ts index 3cf3f3c..3a7e988 100644 --- a/src/gateway/handlers/guildMembersChunk.ts +++ b/src/gateway/handlers/guildMembersChunk.ts @@ -14,7 +14,13 @@ export const guildMembersChunk: GatewayEventHandler = async ( await guild.members.set(member.user.id, member) } - // TODO: Cache Presences + if (d.chunk_index === 0) await guild.presences.flush() + + if (d.presences !== undefined) { + for (const pres of d.presences) { + await guild.presences.set(pres.user.id, pres) + } + } gateway.client.emit('guildMembersChunk', guild, { members: d.members.map((m) => m.user.id), diff --git a/src/gateway/handlers/index.ts b/src/gateway/handlers/index.ts index 65937bc..7cc0879 100644 --- a/src/gateway/handlers/index.ts +++ b/src/gateway/handlers/index.ts @@ -49,6 +49,7 @@ import { inviteCreate } from './inviteCreate.ts' import { inviteDelete } from './inviteDelete.ts' import { MessageReaction } from '../../structures/messageReaction.ts' import { Invite } from '../../structures/invite.ts' +import { Presence } from '../../structures/presence.ts' export const gatewayHandlers: { [eventCode in GatewayEvents]: GatewayEventHandler | undefined @@ -150,5 +151,6 @@ export interface ClientEvents extends EventTypes { voiceStateAdd: (state: VoiceState) => void voiceStateRemove: (state: VoiceState) => void voiceStateUpdate: (state: VoiceState, after: VoiceState) => void + presenceUpdate: (presence: Presence) => void webhooksUpdate: (guild: Guild, channel: GuildTextChannel) => void } diff --git a/src/gateway/handlers/presenceUpdate.ts b/src/gateway/handlers/presenceUpdate.ts index 27d0ad2..58dbe84 100644 --- a/src/gateway/handlers/presenceUpdate.ts +++ b/src/gateway/handlers/presenceUpdate.ts @@ -1,6 +1,16 @@ +import { PresenceUpdatePayload } from '../../types/gateway.ts' import { Gateway, GatewayEventHandler } from '../index.ts' export const presenceUpdate: GatewayEventHandler = async ( gateway: Gateway, - d: any -) => {} + d: PresenceUpdatePayload +) => { + const guild = await gateway.client.guilds.get(d.guild_id) + if (guild === undefined) return + + await guild.presences.set(d.user.id, d) + const presence = await guild.presences.get(d.user.id) + if (presence === undefined) return + + gateway.client.emit('presenceUpdate', presence) +} diff --git a/src/managers/presences.ts b/src/managers/presences.ts new file mode 100644 index 0000000..1dda7d2 --- /dev/null +++ b/src/managers/presences.ts @@ -0,0 +1,39 @@ +import { Client } from '../models/client.ts' +import { Guild } from '../structures/guild.ts' +import { Presence } from '../structures/presence.ts' +import { User } from '../structures/user.ts' +import { PresenceUpdatePayload } from '../types/gateway.ts' +import { BaseManager } from './base.ts' + +export class GuildPresencesManager extends BaseManager< + PresenceUpdatePayload, + Presence +> { + guild: Guild + + constructor(client: Client, guild: Guild) { + super(client, `presences:${guild.id}`, Presence) + this.guild = guild + } + + async get(id: string): Promise { + const raw = await this._get(id) + if (raw === undefined) return + let user = await this.client.users.get(raw.user.id) + if (user === undefined) user = new User(this.client, raw.user) + const guild = await this.client.guilds.get(raw.guild_id) + if (guild === undefined) return + const presence = new Presence(this.client, raw, user, guild) + return presence + } + + async fromPayload( + data: PresenceUpdatePayload[] + ): Promise { + await this.flush() + for (const pres of data) { + await this.set(pres.user.id, pres) + } + return this + } +} diff --git a/src/structures/guild.ts b/src/structures/guild.ts index d21ceb1..29ecf07 100644 --- a/src/structures/guild.ts +++ b/src/structures/guild.ts @@ -7,7 +7,6 @@ import { IntegrationAccountPayload, IntegrationExpireBehavior, } from '../types/guild.ts' -import { PresenceUpdatePayload } from '../types/gateway.ts' import { Base } from './base.ts' import { RolesManager } from '../managers/roles.ts' import { InviteManager } from '../managers/invites.ts' @@ -21,6 +20,7 @@ import { Application } from './application.ts' import { GUILD_BAN, GUILD_BANS, GUILD_INTEGRATIONS } from '../types/endpoint.ts' import { GuildVoiceStatesManager } from '../managers/guildVoiceStates.ts' import { RequestMembersOptions } from '../gateway/index.ts' +import { GuildPresencesManager } from '../managers/presences.ts' export class GuildBan extends Base { guild: Guild @@ -147,7 +147,7 @@ export class Guild extends Base { voiceStates: GuildVoiceStatesManager members: MembersManager channels: GuildChannelsManager - presences?: PresenceUpdatePayload[] + presences: GuildPresencesManager maxPresences?: number maxMembers?: number vanityURLCode?: string @@ -169,6 +169,7 @@ export class Guild extends Base { this.unavailable = data.unavailable this.members = new MembersManager(this.client, this) this.voiceStates = new GuildVoiceStatesManager(client, this) + this.presences = new GuildPresencesManager(client, this) this.channels = new GuildChannelsManager( this.client, this.client.channels, @@ -203,7 +204,6 @@ export class Guild extends Base { this.joinedAt = data.joined_at this.large = data.large this.memberCount = data.member_count - this.presences = data.presences this.maxPresences = data.max_presences this.maxMembers = data.max_members this.vanityURLCode = data.vanity_url_code @@ -252,7 +252,6 @@ export class Guild extends Base { this.joinedAt = data.joined_at ?? this.joinedAt this.large = data.large ?? this.large this.memberCount = data.member_count ?? this.memberCount - this.presences = data.presences ?? this.presences this.maxPresences = data.max_presences ?? this.maxPresences this.maxMembers = data.max_members ?? this.maxMembers this.vanityURLCode = data.vanity_url_code ?? this.vanityURLCode diff --git a/src/structures/presence.ts b/src/structures/presence.ts index 5fd5b2e..65b84be 100644 --- a/src/structures/presence.ts +++ b/src/structures/presence.ts @@ -1,5 +1,15 @@ -import { ActivityGame, ClientActivity, StatusType } from '../types/presence.ts' -import { StatusUpdatePayload } from '../types/gateway.ts' +import { + ActivityGame, + ActivityPayload, + ClientActivity, + ClientStatus, + StatusType, +} from '../types/presence.ts' +import { PresenceUpdatePayload, StatusUpdatePayload } from '../types/gateway.ts' +import { Base } from './base.ts' +import { Guild } from './guild.ts' +import { User } from './user.ts' +import { Client } from '../models/client.ts' enum ActivityTypes { PLAYING = 0, @@ -7,7 +17,37 @@ enum ActivityTypes { LISTENING = 2, WATCHING = 3, CUSTOM_STATUS = 4, - COMPETING = 5 + COMPETING = 5, +} + +export class Presence extends Base { + user: User + guild: Guild + status: StatusType + // TODO: Maybe a new structure for this? + activities: ActivityPayload[] + clientStatus: ClientStatus + + constructor( + client: Client, + data: PresenceUpdatePayload, + user: User, + guild: Guild + ) { + super(client, data) + this.user = user + this.guild = guild + this.status = data.status + this.activities = data.activities + this.clientStatus = data.client_status + } + + fromPayload(data: PresenceUpdatePayload): Presence { + this.status = data.status + this.activities = data.activities + this.clientStatus = data.client_status + return this + } } export class ClientPresence { @@ -16,7 +56,7 @@ export class ClientPresence { since?: number | null afk?: boolean - constructor (data?: ClientActivity | StatusUpdatePayload | ActivityGame) { + constructor(data?: ClientActivity | StatusUpdatePayload | ActivityGame) { if (data !== undefined) { if ((data as ClientActivity).activity !== undefined) { Object.assign(this, data) @@ -31,7 +71,7 @@ export class ClientPresence { } } - parse (payload: StatusUpdatePayload): ClientPresence { + parse(payload: StatusUpdatePayload): ClientPresence { this.afk = payload.afk this.activity = payload.activities ?? undefined this.since = payload.since @@ -39,20 +79,20 @@ export class ClientPresence { return this } - static parse (payload: StatusUpdatePayload): ClientPresence { + static parse(payload: StatusUpdatePayload): ClientPresence { return new ClientPresence().parse(payload) } - create (): StatusUpdatePayload { + create(): StatusUpdatePayload { return { afk: this.afk === undefined ? false : this.afk, activities: this.createActivity(), since: this.since === undefined ? null : this.since, - status: this.status === undefined ? 'online' : this.status + status: this.status === undefined ? 'online' : this.status, } } - createActivity (): ActivityGame[] | null { + createActivity(): ActivityGame[] | null { // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions const activity = this.activity === undefined @@ -62,7 +102,7 @@ export class ClientPresence { : [this.activity] if (activity === null) return activity else { - activity.map(e => { + activity.map((e) => { if (typeof e.type === 'string') e.type = ActivityTypes[e.type] return e }) @@ -70,37 +110,37 @@ export class ClientPresence { } } - setStatus (status: StatusType): ClientPresence { + setStatus(status: StatusType): ClientPresence { this.status = status return this } - setActivity (activity: ActivityGame): ClientPresence { + setActivity(activity: ActivityGame): ClientPresence { this.activity = activity return this } - setActivities (activities: ActivityGame[]): ClientPresence { + setActivities(activities: ActivityGame[]): ClientPresence { this.activity = activities return this } - setAFK (afk: boolean): ClientPresence { + setAFK(afk: boolean): ClientPresence { this.afk = afk return this } - removeAFK (): ClientPresence { + removeAFK(): ClientPresence { this.afk = false return this } - toggleAFK (): ClientPresence { + toggleAFK(): ClientPresence { this.afk = this.afk === undefined ? true : !this.afk return this } - setSince (since?: number): ClientPresence { + setSince(since?: number): ClientPresence { this.since = since return this } diff --git a/src/types/presence.ts b/src/types/presence.ts index 0897488..fd81317 100644 --- a/src/types/presence.ts +++ b/src/types/presence.ts @@ -56,7 +56,7 @@ export enum ActivityFlags { SPECTATE = 1 << 2, JOIN_REQUEST = 1 << 3, SYNC = 1 << 4, - PLAY = 1 << 5 + PLAY = 1 << 5, } export type ActivityType =