diff --git a/src/cache/resources/voice-states.ts b/src/cache/resources/voice-states.ts index 92d868b..aaa2fa8 100644 --- a/src/cache/resources/voice-states.ts +++ b/src/cache/resources/voice-states.ts @@ -1,12 +1,32 @@ import type { GatewayVoiceState } from 'discord-api-types/v10'; +import type { ReturnCache } from '../..'; +import { fakePromise } from '../../common'; +import { VoiceState } from '../../structures'; import { GuildBasedResource } from './default/guild-based'; -export class VoiceStates extends GuildBasedResource { +export class VoiceStates extends GuildBasedResource { namespace = 'voice_state'; - override parse(data: any, id: string, guild_id: string): VoiceStateResource { + override get(memberId: string, guildId: string): ReturnCache { + return fakePromise(super.get(memberId, guildId)).then(state => + state ? new VoiceState(this.client, state) : undefined, + ); + } + + override bulk(ids: string[], guild: string): ReturnCache { + return fakePromise(super.bulk(ids, guild)).then( + states => + states.map(state => (state ? new VoiceState(this.client, state) : undefined)).filter(y => !!y) as VoiceState[], + ); + } + + override values(guildId: string): ReturnCache { + return fakePromise(super.values(guildId)).then(states => states.map(state => new VoiceState(this.client, state))); + } + + override parse(data: any, id: string, guild_id: string): ReturnCache { const { member, ...rest } = super.parse(data, id, guild_id); - return rest; + return new VoiceState(this.client, rest); } } diff --git a/src/common/shorters/channels.ts b/src/common/shorters/channels.ts index d4a644a..bdf1dde 100644 --- a/src/common/shorters/channels.ts +++ b/src/common/shorters/channels.ts @@ -1,6 +1,11 @@ -import { PermissionFlagsBits, type RESTPatchAPIChannelJSONBody } from 'discord-api-types/v10'; +import { + PermissionFlagsBits, + type RESTPatchAPIChannelJSONBody, + type RESTPostAPIChannelThreadsJSONBody, + type RESTPostAPIGuildForumThreadsJSONBody, +} from 'discord-api-types/v10'; import { BaseChannel, Message, type GuildMember, type GuildRole } from '../../structures'; -import channelFrom, { type AllChannels } from '../../structures/channels'; +import channelFrom, { type AllChannels, type ThreadChannel } from '../../structures/channels'; import { PermissionsBitField } from '../../structures/extra/Permissions'; import { BaseShorter } from './base'; @@ -100,6 +105,26 @@ export class ChannelShorter extends BaseShorter { return this.client.proxy.channels(channelId).pins(messageId).delete({ reason }); } + /** + * Creates a new thread in the channel (only guild based channels). + * @param channelId The ID of the parent channel. + * @param reason The reason for unpinning the message. + * @returns A promise that resolves when the thread is succesfully created. + */ + async thread( + channelId: string, + body: RESTPostAPIChannelThreadsJSONBody | RESTPostAPIGuildForumThreadsJSONBody, + reason?: string, + ) { + return ( + this.client.proxy + .channels(channelId) + .threads.post({ body, reason }) + // When testing this, discord returns the thread object, but in discord api types it does not. + .then(thread => channelFrom(thread, this.client) as ThreadChannel) + ); + } + async memberPermissions(channelId: string, member: GuildMember, checkAdmin = true): Promise { const permissions = await member.fetchPermissions(); diff --git a/src/common/shorters/members.ts b/src/common/shorters/members.ts index 25a1b88..64a86db 100644 --- a/src/common/shorters/members.ts +++ b/src/common/shorters/members.ts @@ -208,4 +208,12 @@ export class MemberShorter extends BaseShorter { return new PermissionsBitField(roles.map(x => BigInt(x.permissions.bits))); } + + presence(memberId: string) { + return this.client.cache.presences?.get(memberId); + } + + voice(guildId: string, memberId: string) { + return this.client.cache.voiceStates?.get(memberId, guildId); + } } diff --git a/src/common/shorters/messages.ts b/src/common/shorters/messages.ts index af20a65..486a82d 100644 --- a/src/common/shorters/messages.ts +++ b/src/common/shorters/messages.ts @@ -4,7 +4,8 @@ import type { RESTPostAPIChannelMessagesThreadsJSONBody, } from 'discord-api-types/v10'; import { resolveFiles } from '../../builders'; -import { Message, MessagesMethods, ThreadChannel } from '../../structures'; +import { Message, MessagesMethods, type ThreadChannel } from '../../structures'; +import channelFrom from '../../structures/channels'; import type { MessageCreateBodyRequest, MessageUpdateBodyRequest } from '../types/write'; import { BaseShorter } from './base'; @@ -50,7 +51,7 @@ export class MessageShorter extends BaseShorter { .messages(messageId) .delete({ reason }) .then(() => { - return this.client.components?.onMessageDelete(messageId); + void this.client.components?.onMessageDelete(messageId); }); } fetch(messageId: string, channelId: string) { @@ -64,7 +65,7 @@ export class MessageShorter extends BaseShorter { return this.client.proxy.channels(channelId).messages['bulk-delete'].post({ body: { messages }, reason }); } - thread( + async thread( channelId: string, messageId: string, options: RESTPostAPIChannelMessagesThreadsJSONBody & { reason?: string }, @@ -75,6 +76,6 @@ export class MessageShorter extends BaseShorter { .channels(channelId) .messages(messageId) .threads.post({ body, reason }) - .then(x => new ThreadChannel(this.client, x)); + .then(thread => channelFrom(thread, this.client) as ThreadChannel); } } diff --git a/src/structures/GuildMember.ts b/src/structures/GuildMember.ts index b5ddbf4..34bbc44 100644 --- a/src/structures/GuildMember.ts +++ b/src/structures/GuildMember.ts @@ -68,11 +68,11 @@ export class BaseGuildMember extends DiscordBase { } presence() { - return this.cache.presences?.get(this.id); + return this.client.members.presence(this.id); } voice() { - return this.cache.voiceStates?.get(this.id, this.guildId); + return this.client.members.voice(this.guildId, this.id); } toString() { diff --git a/src/structures/User.ts b/src/structures/User.ts index 0c76c04..3711f42 100644 --- a/src/structures/User.ts +++ b/src/structures/User.ts @@ -47,7 +47,7 @@ export class User extends DiscordBase { } presence() { - return this.cache.presences?.get(this.id); + return this.client.members.presence(this.id); } toString() { diff --git a/src/structures/VoiceState.ts b/src/structures/VoiceState.ts new file mode 100644 index 0000000..899b01b --- /dev/null +++ b/src/structures/VoiceState.ts @@ -0,0 +1,59 @@ +import type { GuildMember, UsingClient } from '../'; +import type { VoiceStateResource } from '../cache/resources/voice-states'; +import type { ObjectToLower } from '../common'; +import { Base } from './extra/Base'; + +export interface VoiceState extends Base, ObjectToLower {} + +export class VoiceState extends Base { + constructor( + client: UsingClient, + data: VoiceStateResource, + private withMember?: GuildMember, + ) { + super(client); + this.__patchThis(data); + } + + isMuted() { + return this.mute || this.selfMute; + } + + async member(force?: boolean) { + return (this.withMember ??= await this.client.members.fetch(this.guildId, this.userId, force)); + } + + async user(force?: boolean) { + return this.client.users.fetch(this.userId, force); + } + + async channel(force?: boolean) { + if (!this.channelId) return; + return this.client.channels.fetch(this.channelId, force); + } + + async setMute(mute = !this.mute, reason?: string) { + return this.client.members.edit(this.guildId, this.userId, { mute }, reason).then(member => { + this.mute = mute; + return member; + }); + } + + async setDeaf(deaf = !this.deaf, reason?: string) { + return this.client.members.edit(this.guildId, this.userId, { deaf }, reason).then(member => { + this.deaf = deaf; + return member; + }); + } + + async disconnect(reason?: string) { + return this.setChannel(null, reason); + } + + async setChannel(channel_id: null | string, reason?: string) { + return this.client.members.edit(this.guildId, this.userId, { channel_id }, reason).then(member => { + this.channelId = channel_id; + return member; + }); + } +} diff --git a/src/structures/channels.ts b/src/structures/channels.ts index bb8b298..9c6be70 100644 --- a/src/structures/channels.ts +++ b/src/structures/channels.ts @@ -20,6 +20,7 @@ import { type RESTPatchAPIGuildChannelPositionsJSONBody, type RESTPostAPIChannelWebhookJSONBody, type RESTPostAPIGuildChannelJSONBody, + type RESTPostAPIGuildForumThreadsJSONBody, type SortOrderType, type ThreadAutoArchiveDuration, } from 'discord-api-types/v10'; @@ -326,10 +327,15 @@ export class ThreadOnlyMethods extends DiscordBase { setThreadRateLimit(rate: number, reason?: string) { return this.edit({ default_thread_rate_limit_per_user: rate }, reason); } + + async thread(body: RESTPostAPIGuildForumThreadsJSONBody, reason?: string) { + return this.client.channels.thread(this.id, body, reason); + } } export interface VoiceChannelMethods extends BaseChannel {} export class VoiceChannelMethods extends DiscordBase { + guildId?: string; setBitrate(bitrate: number | null, reason?: string) { return this.edit({ bitrate }, reason); } @@ -345,6 +351,14 @@ export class VoiceChannelMethods extends DiscordBase { setVideoQuality(quality: keyof typeof VideoQualityMode, reason?: string) { return this.edit({ video_quality_mode: VideoQualityMode[quality] }, reason); } + + async states() { + if (!this.guildId) return []; + const states = await this.cache.voiceStates?.values(this.guildId); + if (!states?.length) return []; + const filter = states.filter(state => state.channelId === this.id); + return filter; + } } export class WebhookGuildMethods extends DiscordBase { diff --git a/src/structures/index.ts b/src/structures/index.ts index 3fd5ccb..b324def 100644 --- a/src/structures/index.ts +++ b/src/structures/index.ts @@ -11,5 +11,6 @@ export * from './Interaction'; export * from './Message'; export * from './Sticker'; export * from './User'; +export * from './VoiceState'; export * from './Webhook'; export * from './channels';