From adc41c88f7532a76182bab2d34d8a853203367a0 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sun, 3 Jul 2022 23:10:50 -0500 Subject: [PATCH] feat: StageInstance --- mod.ts | 2 + structures/StageInstance.ts | 61 ++++++++++++++++++++++++ structures/channels/BaseVoiceChannel.ts | 62 +++++++++++++++++++++++++ structures/channels/ChannelFactory.ts | 6 ++- structures/channels/StageChannel.ts | 16 +++++++ structures/channels/TextChannel.ts | 39 ++++++++++------ structures/channels/VoiceChannel.ts | 61 ++++-------------------- structures/guilds/Guild.ts | 4 +- util/Routes.ts | 8 ++++ 9 files changed, 190 insertions(+), 69 deletions(-) create mode 100644 structures/StageInstance.ts create mode 100644 structures/channels/BaseVoiceChannel.ts create mode 100644 structures/channels/StageChannel.ts diff --git a/mod.ts b/mod.ts index d5cbe11..ab987f5 100644 --- a/mod.ts +++ b/mod.ts @@ -13,12 +13,14 @@ export * from "./structures/WelcomeChannel.ts"; export * from "./structures/WelcomeScreen.ts"; export * from "./structures/channels/BaseChannel.ts"; +export * from "./structures/channels/BaseVoiceChannel.ts"; export * from "./structures/channels/ChannelFactory.ts"; export * from "./structures/channels/DMChannel.ts"; export * from "./structures/channels/GuildChannel.ts"; export * from "./structures/channels/NewsChannel.ts"; export * from "./structures/channels/TextChannel.ts"; export * from "./structures/channels/ThreadChannel.ts"; +export * from "./structures/channels/StageChannel.ts"; export * from "./structures/channels/VoiceChannel.ts"; export * from "./structures/components/ActionRowComponent.ts"; diff --git a/structures/StageInstance.ts b/structures/StageInstance.ts new file mode 100644 index 0000000..c82bb56 --- /dev/null +++ b/structures/StageInstance.ts @@ -0,0 +1,61 @@ +import type { Model } from "./Base.ts"; +import type { Session } from "../session/Session.ts"; +import type { Snowflake } from "../util/Snowflake.ts"; +import type { DiscordStageInstance as DiscordAutoClosingStageInstance } from "../vendor/external.ts"; +import * as Routes from "../util/Routes.ts"; + +interface DiscordStageInstance extends DiscordAutoClosingStageInstance { + privacy_level: PrivacyLevels; + discoverable_disabled: boolean; + guild_scheduled_event_id: Snowflake; +} + +export enum PrivacyLevels { + Public = 1, + GuildOnly = 2, +} + +export class StageInstance implements Model { + constructor(session: Session, data: DiscordStageInstance) { + this.session = session; + this.id = data.id; + this.channelId = data.channel_id; + this.guildId = data.guild_id; + this.topic = data.topic; + this.privacyLevel = data.privacy_level; + this.discoverableDisabled = data.discoverable_disabled; + this.guildScheduledEventId = data.guild_scheduled_event_id; + } + + readonly session: Session; + readonly id: Snowflake; + + channelId: Snowflake; + guildId: Snowflake; + topic: string; + + // TODO: see if this works + privacyLevel: PrivacyLevels; + discoverableDisabled: boolean; + guildScheduledEventId: Snowflake; + + async edit(options: { topic?: string, privacyLevel?: PrivacyLevels }) { + const stageInstance = await this.session.rest.runMethod( + this.session.rest, + "PATCH", + Routes.STAGE_INSTANCE(this.id), + { + topic: options.topic, + privacy_level: options.privacyLevel + } + ); + + return new StageInstance(this.session, stageInstance); + } + + async delete() { + await this.session.rest.runMethod(this.session.rest, "DELETE", Routes.STAGE_INSTANCE(this.id)); + } +} + +export default StageInstance; diff --git a/structures/channels/BaseVoiceChannel.ts b/structures/channels/BaseVoiceChannel.ts new file mode 100644 index 0000000..7f7cfbe --- /dev/null +++ b/structures/channels/BaseVoiceChannel.ts @@ -0,0 +1,62 @@ +import type { Snowflake } from "../../util/Snowflake.ts"; +import type { Session } from "../../session/Session.ts"; +import type { ChannelTypes, DiscordChannel, VideoQualityModes } from "../../vendor/external.ts"; +import { GatewayOpcodes } from "../../vendor/external.ts"; +import { calculateShardId } from "../../vendor/gateway/calculateShardId.ts"; +import GuildChannel from "./GuildChannel.ts"; + +/** + * @link https://discord.com/developers/docs/topics/gateway#update-voice-state + */ +export interface UpdateVoiceState { + guildId: string; + channelId?: string; + selfMute: boolean; + selfDeaf: boolean; +} + +export abstract class BaseVoiceChannel extends GuildChannel { + constructor(session: Session, data: DiscordChannel, guildId: Snowflake) { + super(session, data, guildId); + this.bitRate = data.bitrate; + this.userLimit = data.user_limit ?? 0; + this.videoQuality = data.video_quality_mode; + this.nsfw = !!data.nsfw; + this.type = data.type as number; + + if (data.rtc_region) { + this.rtcRegion = data.rtc_region; + } + } + override type: ChannelTypes.GuildVoice | ChannelTypes.GuildStageVoice; + bitRate?: number; + userLimit: number; + rtcRegion?: Snowflake; + + videoQuality?: VideoQualityModes; + nsfw: boolean; + + /** + * This function was gathered from Discordeno it may not work + */ + async connect(options?: UpdateVoiceState) { + const shardId = calculateShardId(this.session.gateway, BigInt(super.guildId)); + const shard = this.session.gateway.manager.shards.get(shardId); + + if (!shard) { + throw new Error(`Shard (id: ${shardId} not found`); + } + + await shard.send({ + op: GatewayOpcodes.VoiceStateUpdate, + d: { + guild_id: super.guildId, + channel_id: super.id, + self_mute: Boolean(options?.selfMute), + self_deaf: options?.selfDeaf ?? true, + }, + }); + } +} + +export default BaseVoiceChannel; diff --git a/structures/channels/ChannelFactory.ts b/structures/channels/ChannelFactory.ts index 2603514..31a1578 100644 --- a/structures/channels/ChannelFactory.ts +++ b/structures/channels/ChannelFactory.ts @@ -7,13 +7,15 @@ import VoiceChannel from "./VoiceChannel.ts"; import DMChannel from "./DMChannel.ts"; import NewsChannel from "./NewsChannel.ts"; import ThreadChannel from "./ThreadChannel.ts"; +import StageChannel from "./StageChannel.ts"; export type Channel = | TextChannel | VoiceChannel | DMChannel | NewsChannel - | ThreadChannel; + | ThreadChannel + | StageChannel; export class ChannelFactory { static from(session: Session, channel: DiscordChannel): Channel { @@ -27,6 +29,8 @@ export class ChannelFactory { return new DMChannel(session, channel); case ChannelTypes.GuildVoice: return new VoiceChannel(session, channel, channel.guild_id!); + case ChannelTypes.GuildStageVoice: + return new StageChannel(session, channel, channel.guild_id!); default: if (textBasedChannels.includes(channel.type)) { return new TextChannel(session, channel); diff --git a/structures/channels/StageChannel.ts b/structures/channels/StageChannel.ts new file mode 100644 index 0000000..41b49f7 --- /dev/null +++ b/structures/channels/StageChannel.ts @@ -0,0 +1,16 @@ +import type { Snowflake } from "../../util/Snowflake.ts"; +import type { Session } from "../../session/Session.ts"; +import type { ChannelTypes, DiscordChannel } from "../../vendor/external.ts"; +import BaseVoiceChannel from "./BaseVoiceChannel.ts"; + +export class StageChannel extends BaseVoiceChannel { + constructor(session: Session, data: DiscordChannel, guildId: Snowflake) { + super(session, data, guildId); + this.type = data.type as number; + this.topic = data.topic ? data.topic : undefined; + } + override type: ChannelTypes.GuildStageVoice; + topic?: string; +} + +export default StageChannel; diff --git a/structures/channels/TextChannel.ts b/structures/channels/TextChannel.ts index 6f5e324..5b5bd7d 100644 --- a/structures/channels/TextChannel.ts +++ b/structures/channels/TextChannel.ts @@ -44,6 +44,7 @@ export const textBasedChannels = [ ChannelTypes.GuildPrivateThread, ChannelTypes.GuildPublicThread, ChannelTypes.GuildNews, + ChannelTypes.GuildVoice, ChannelTypes.GuildText, ]; @@ -53,6 +54,7 @@ export type TextBasedChannels = | ChannelTypes.GuildPrivateThread | ChannelTypes.GuildPublicThread | ChannelTypes.GuildNews + | ChannelTypes.GuildVoice | ChannelTypes.GuildText; export class TextChannel { @@ -85,21 +87,28 @@ export class TextChannel { /** * Mixin */ - static applyTo(klass: Function) { - klass.prototype.fetchPins = TextChannel.prototype.fetchPins; - klass.prototype.createInvite = TextChannel.prototype.createInvite; - klass.prototype.fetchMessages = TextChannel.prototype.fetchMessages; - klass.prototype.sendTyping = TextChannel.prototype.sendTyping; - klass.prototype.pinMessage = TextChannel.prototype.pinMessage; - klass.prototype.unpinMessage = TextChannel.prototype.unpinMessage; - klass.prototype.addReaction = TextChannel.prototype.addReaction; - klass.prototype.removeReaction = TextChannel.prototype.removeReaction; - klass.prototype.removeReactionEmoji = TextChannel.prototype.removeReactionEmoji; - klass.prototype.nukeReactions = TextChannel.prototype.nukeReactions; - klass.prototype.fetchReactions = TextChannel.prototype.fetchReactions; - klass.prototype.sendMessage = TextChannel.prototype.sendMessage; - klass.prototype.editMessage = TextChannel.prototype.editMessage; - klass.prototype.createWebhook = TextChannel.prototype.createWebhook; + static applyTo(klass: Function, ignore: Array = []) { + const methods: Array = [ + "fetchPins", + "createInvite", + "fetchMessages", + "sendTyping", + "pinMessage", + "unpinMessage", + "addReaction", + "removeReaction", + "nukeReactions", + "fetchPins", + "sendMessage", + "editMessage", + "createWebhook", + ]; + + for (const method of methods) { + if (ignore.includes(method)) continue; + + klass.prototype[method] = TextChannel.prototype[method]; + } } async fetchPins(): Promise { diff --git a/structures/channels/VoiceChannel.ts b/structures/channels/VoiceChannel.ts index 00da784..670e5fd 100644 --- a/structures/channels/VoiceChannel.ts +++ b/structures/channels/VoiceChannel.ts @@ -1,60 +1,19 @@ import type { Snowflake } from "../../util/Snowflake.ts"; import type { Session } from "../../session/Session.ts"; -import type { DiscordChannel, VideoQualityModes } from "../../vendor/external.ts"; -import { GatewayOpcodes } from "../../vendor/external.ts"; -import { calculateShardId } from "../../vendor/gateway/calculateShardId.ts"; -import GuildChannel from "./GuildChannel.ts"; +import type { ChannelTypes, DiscordChannel } from "../../vendor/external.ts"; +import BaseVoiceChannel from "./BaseVoiceChannel.ts"; +import TextChannel from "./TextChannel.ts"; -/** - * @link https://discord.com/developers/docs/topics/gateway#update-voice-state - */ -export interface UpdateVoiceState { - guildId: string; - channelId?: string; - selfMute: boolean; - selfDeaf: boolean; -} - -export class VoiceChannel extends GuildChannel { +export class VoiceChannel extends BaseVoiceChannel { constructor(session: Session, data: DiscordChannel, guildId: Snowflake) { super(session, data, guildId); - this.bitRate = data.bitrate; - this.userLimit = data.user_limit ?? 0; - this.videoQuality = data.video_quality_mode; - this.nsfw = !!data.nsfw; - - if (data.rtc_region) { - this.rtcRegion = data.rtc_region; - } - } - bitRate?: number; - userLimit: number; - rtcRegion?: Snowflake; - - videoQuality?: VideoQualityModes; - nsfw: boolean; - - /** - * This function was gathered from Discordeno it may not work - */ - async connect(options?: UpdateVoiceState) { - const shardId = calculateShardId(this.session.gateway, BigInt(super.guildId)); - const shard = this.session.gateway.manager.shards.get(shardId); - - if (!shard) { - throw new Error(`Shard (id: ${shardId} not found`); - } - - await shard.send({ - op: GatewayOpcodes.VoiceStateUpdate, - d: { - guild_id: super.guildId, - channel_id: super.id, - self_mute: Boolean(options?.selfMute), - self_deaf: options?.selfDeaf ?? true, - }, - }); + this.type = data.type as number; } + override type: ChannelTypes.GuildVoice; } +export interface VoiceChannel extends TextChannel, BaseVoiceChannel {} + +TextChannel.applyTo(VoiceChannel); + export default VoiceChannel; diff --git a/structures/guilds/Guild.ts b/structures/guilds/Guild.ts index f0440a6..2254e0a 100644 --- a/structures/guilds/Guild.ts +++ b/structures/guilds/Guild.ts @@ -128,7 +128,7 @@ export interface GuildCreateOptionsChannel { nsfw?: boolean; bitrate?: number; userLimit?: number; - region?: string | null; + rtcRegion?: string | null; videoQualityMode?: VideoQualityModes; permissionOverwrites?: MakeRequired, "id">[]; rateLimitPerUser?: number; @@ -527,7 +527,7 @@ export class Guild extends BaseGuild implements Model { bitrate: channel.bitrate, parent_id: channel.parentId, permission_overwrites: channel.permissionOverwrites, - region: channel.region, + rtc_region: channel.rtcRegion, user_limit: channel.userLimit, video_quality_mode: channel.videoQualityMode, rate_limit_per_user: channel.rateLimitPerUser, diff --git a/util/Routes.ts b/util/Routes.ts index 650b5a2..146f944 100644 --- a/util/Routes.ts +++ b/util/Routes.ts @@ -303,3 +303,11 @@ export function THREAD_ARCHIVED_PRIVATE_JOINED(channelId: Snowflake, options?: L export function FORUM_START(channelId: Snowflake) { return `/channels/${channelId}/threads?has_message=true`; } + +export function STAGE_INSTANCES() { + return `/stage-instances`; +} + +export function STAGE_INSTANCE(channelId: Snowflake) { + return `/stage-instances/${channelId}`; +}