diff --git a/eslint.config.js b/eslint.config.js index 461563d6d26b..01b4ff7c00f7 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -165,10 +165,10 @@ export default tseslint.config( 'jsdoc/no-undefined-types': 0, 'jsdoc/no-defaults': 0, 'no-eq-null': 0, - strict: ['error', 'global'], + strict: [2, 'global'], 'no-restricted-syntax': [ - 'error', + 2, { selector: "AssignmentExpression[left.object.name='module'][left.property.name='exports']", message: 'Use named exports instead of module.exports', @@ -185,7 +185,7 @@ export default tseslint.config( files: [`packages/discord.js/src/client/websocket/handlers/*.js`], rules: { 'no-restricted-syntax': [ - 'error', + 2, { selector: "VariableDeclarator[init.callee.name='require'][init.arguments.0.value=/^\\./]:not([id.type='ObjectPattern'])", @@ -199,6 +199,7 @@ export default tseslint.config( rules: { '@typescript-eslint/no-unsafe-declaration-merging': 0, '@typescript-eslint/no-empty-object-type': 0, + '@typescript-eslint/no-unused-vars': 2, '@typescript-eslint/no-use-before-define': 0, '@typescript-eslint/consistent-type-imports': 0, '@stylistic/ts/lines-between-class-members': 0, @@ -245,7 +246,7 @@ export default tseslint.config( }, }, { - files: [`packages/structures/**/*${commonFiles}`], + files: [`packages/{next,structures}/**/*${commonFiles}`], rules: { '@typescript-eslint/no-empty-interface': 0, '@typescript-eslint/no-empty-object-type': 0, diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index d687f05ee662..91eb0dcd64f9 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -66,7 +66,6 @@ import { APIMessageTopLevelComponent, APIMessageUserSelectInteractionData, APIModalComponent, - APIModalInteractionResponseCallbackComponent, APIModalInteractionResponseCallbackData, APIModalSubmitInteraction, APIOverwrite, @@ -3508,7 +3507,7 @@ export interface PrivateThreadChannel extends ThreadChannel { type: ChannelType.PrivateThread; } -export interface ThreadChannel +export interface ThreadChannel extends TextBasedChannelFields, PinnableChannelFields, BulkDeleteMethod, @@ -3810,7 +3809,7 @@ export class VoiceState extends Base { public fetch(force?: boolean): Promise; } -export interface Webhook extends WebhookFields {} +export interface Webhook extends WebhookFields {} export class Webhook { private constructor(client: Client, data?: unknown); public avatar: string | null; diff --git a/packages/next/package.json b/packages/next/package.json index e8c411fa1fea..ed1027433427 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -70,6 +70,7 @@ "@discordjs/core": "workspace:^", "@discordjs/formatters": "workspace:^", "@discordjs/rest": "workspace:^", + "@discordjs/structures": "workspace:^", "@discordjs/util": "workspace:^", "@discordjs/ws": "workspace:^", "discord-api-types": "^0.38.29" diff --git a/packages/next/src/Client.ts b/packages/next/src/Client.ts new file mode 100644 index 000000000000..9dee8ce27ddf --- /dev/null +++ b/packages/next/src/Client.ts @@ -0,0 +1,57 @@ +import process from 'node:process'; +import type { RESTGetAPIGatewayBotResult, Snowflake } from '@discordjs/core'; +import { Client as CoreClient, Routes, GatewayDispatchEvents } from '@discordjs/core'; +import { REST } from '@discordjs/rest'; +import type { Structure } from '@discordjs/structures'; +import { WebSocketManager } from '@discordjs/ws'; +import { CollectionCache, type CacheConstructor, type StructureCache } from './util/cache.js'; +import { container } from './util/container.js'; + +export class Client { + private core!: CoreClient; + + public options: { token: string | undefined }; + + #token: string; + + public CacheConstructor< + Value extends Structure<{ id: Snowflake }>, + Raw extends { id: Snowflake } = Value extends Structure ? Type : never, + >() { + return new this.cacheConstructor() as StructureCache; + } + + protected cacheConstructor: CacheConstructor; + + public constructor( + { token = process.env.DISCORD_TOKEN }: { token?: string }, + cache: CacheConstructor = CollectionCache, + ) { + this.options = { token }; + this.#token = token!; + this.cacheConstructor = cache; + container.set('client', this); + } + + public async login(token?: string) { + if (token) { + this.#token = token; + } + + const rest = new REST().setToken(this.#token); + const gateway = new WebSocketManager({ + fetchGatewayInformation: async () => rest.get(Routes.gatewayBot()) as Promise, + intents: 0, + token: this.#token, + }); + this.core = new CoreClient({ + gateway, + rest, + }); + + // TODO: remove once we actually implement anything using this.core + this.core.on(GatewayDispatchEvents.Ready, console.log); + + await gateway.connect(); + } +} diff --git a/packages/next/src/managers/CachedManager.ts b/packages/next/src/managers/CachedManager.ts new file mode 100644 index 000000000000..1e99926236cc --- /dev/null +++ b/packages/next/src/managers/CachedManager.ts @@ -0,0 +1,37 @@ +import type { Structure } from '@discordjs/structures'; +import { kClone, kPatch } from '@discordjs/structures'; +import type { Snowflake } from 'discord-api-types/globals'; +import type { Client } from '../Client.js'; +import type { StructureCache } from '../util/cache.js'; + +export abstract class CachedManager< + Value extends Structure<{ id: Snowflake }> & { get id(): Snowflake }, + Raw extends { id: Snowflake } = Value extends Structure ? Type : never, +> { + public client: Client; + + public cache: StructureCache; + + protected abstract createStructure(data: Raw, ...extras: unknown[]): Value; + + public constructor(client: Client) { + this.client = client; + this.cache = client.CacheConstructor(); + } + + protected async _add(data: Raw, cache = true, { id, extras = [] }: { extras?: unknown[]; id?: Snowflake } = {}) { + const existing = await this.cache.get(id ?? data.id); + if (existing) { + if (cache) { + existing[kPatch](data); + return existing; + } + + return existing[kClone](data); + } + + const entry = this.createStructure(data, ...extras); + if (cache) await this.cache.set(id ?? entry.id, entry); + return entry; + } +} diff --git a/packages/next/src/managers/ChannelManager.ts b/packages/next/src/managers/ChannelManager.ts new file mode 100644 index 000000000000..f85f8e6891dc --- /dev/null +++ b/packages/next/src/managers/ChannelManager.ts @@ -0,0 +1,72 @@ +import { ChannelType, type APIChannel } from 'discord-api-types/v10'; +import type { Client } from '../Client.js'; +import { AnnouncementChannel } from '../structures/AnnouncementChannel.js'; +import { AnnouncementThreadChannel } from '../structures/AnnouncementThreadChannel.js'; +import { BaseChannel } from '../structures/BaseChannel.js'; +import { CategoryChannel } from '../structures/CategoryChannel.js'; +import { DMChannel } from '../structures/DMChannel.js'; +import { ForumChannel } from '../structures/ForumChannel.js'; +import { GroupDMChannel } from '../structures/GroupDMChannel.js'; +import { MediaChannel } from '../structures/MediaChannel.js'; +import { PrivateThreadChannel } from '../structures/PrivateThreadChannel.js'; +import { PublicThreadChannel } from '../structures/PublicThreadChannel.js'; +import { StageChannel } from '../structures/StageChannel.js'; +import { TextChannel } from '../structures/TextChannel.js'; +import { VoiceChannel } from '../structures/VoiceChannel.js'; +import { CachedManager } from './CachedManager.js'; + +export type Channel = + | AnnouncementChannel + | AnnouncementThreadChannel + | BaseChannel + | CategoryChannel + | DMChannel + | ForumChannel + | GroupDMChannel + | MediaChannel + | PrivateThreadChannel + | PublicThreadChannel + | StageChannel + | TextChannel + | VoiceChannel; + +export class ChannelManager extends CachedManager { + public constructor(data: APIChannel[], client: Client) { + super(client); + + for (const channel of data) { + this.cache.set(channel.id, this.createStructure(channel)); + } + } + + protected override createStructure(data: APIChannel): Channel { + switch (data.type) { + case ChannelType.AnnouncementThread: + return new AnnouncementThreadChannel(data); + case ChannelType.DM: + return new DMChannel(data); + case ChannelType.GroupDM: + return new GroupDMChannel(data); + case ChannelType.GuildAnnouncement: + return new AnnouncementChannel(data); + case ChannelType.GuildCategory: + return new CategoryChannel(data); + case ChannelType.GuildForum: + return new ForumChannel(data); + case ChannelType.GuildMedia: + return new MediaChannel(data); + case ChannelType.GuildStageVoice: + return new StageChannel(data); + case ChannelType.GuildText: + return new TextChannel(data); + case ChannelType.GuildVoice: + return new VoiceChannel(data); + case ChannelType.PrivateThread: + return new PrivateThreadChannel(data); + case ChannelType.PublicThread: + return new PublicThreadChannel(data); + default: + return new BaseChannel(data); + } + } +} diff --git a/packages/next/src/structures/AnnouncementChannel.ts b/packages/next/src/structures/AnnouncementChannel.ts new file mode 100644 index 000000000000..07ed98d0a1dd --- /dev/null +++ b/packages/next/src/structures/AnnouncementChannel.ts @@ -0,0 +1,51 @@ +import { + Channel, + ChannelParentMixin, + ChannelPermissionMixin, + ChannelPinMixin, + ChannelSlowmodeMixin, + ChannelTopicMixin, + Mixin, + type MixinTypes, + type Partialize, + TextChannelMixin, +} from '@discordjs/structures'; +import type { APINewsChannel, ChannelType } from 'discord-api-types/v10'; +import { BaseChannelMixin } from './mixins/BaseChannelMixin.js'; + +export interface AnnouncementChannel + extends MixinTypes< + Channel, + [ + BaseChannelMixin, + TextChannelMixin, + ChannelParentMixin, + ChannelPermissionMixin, + ChannelPinMixin, + ChannelSlowmodeMixin, + ChannelTopicMixin, + ] + > {} + +/** + * Sample Implementation of a structure for announcement channels, usable by direct end consumers. + */ +export class AnnouncementChannel extends Channel< + ChannelType.GuildAnnouncement, + Omitted +> { + public constructor(data: Partialize) { + super(data); + this.optimizeData(data); + } +} + +Mixin(AnnouncementChannel, [ + BaseChannelMixin, + TextChannelMixin, + ChannelParentMixin, + ChannelPermissionMixin, + ChannelPinMixin, + ChannelSlowmodeMixin, + ChannelTopicMixin, +]); diff --git a/packages/next/src/structures/AnnouncementThreadChannel.ts b/packages/next/src/structures/AnnouncementThreadChannel.ts new file mode 100644 index 000000000000..6e0bcdeaba24 --- /dev/null +++ b/packages/next/src/structures/AnnouncementThreadChannel.ts @@ -0,0 +1,54 @@ +import { + Mixin, + type MixinTypes, + type Partialize, + Channel, + ChannelOwnerMixin, + ChannelParentMixin, + ChannelPinMixin, + ChannelSlowmodeMixin, + GuildChannelMixin, + TextChannelMixin, + ThreadChannelMixin, +} from '@discordjs/structures'; +import type { APIAnnouncementThreadChannel, ChannelType } from 'discord-api-types/v10'; +import { BaseChannelMixin } from './mixins/BaseChannelMixin'; + +export interface AnnouncementThreadChannel + extends MixinTypes< + Channel, + [ + BaseChannelMixin, + TextChannelMixin, + ChannelOwnerMixin, + ChannelParentMixin, + ChannelPinMixin, + ChannelSlowmodeMixin, + GuildChannelMixin, + ThreadChannelMixin, + ] + > {} + +/** + * Sample Implementation of a structure for announcement threads, usable by direct end consumers. + */ +export class AnnouncementThreadChannel extends Channel< + ChannelType.AnnouncementThread, + Omitted +> { + public constructor(data: Partialize) { + super(data); + this.optimizeData?.(data); + } +} + +Mixin(AnnouncementThreadChannel, [ + BaseChannelMixin, + TextChannelMixin, + ChannelOwnerMixin, + ChannelParentMixin, + ChannelPinMixin, + ChannelSlowmodeMixin, + GuildChannelMixin, + ThreadChannelMixin, +]); diff --git a/packages/next/src/structures/BaseChannel.ts b/packages/next/src/structures/BaseChannel.ts new file mode 100644 index 000000000000..92652a225b3d --- /dev/null +++ b/packages/next/src/structures/BaseChannel.ts @@ -0,0 +1,14 @@ +import type { MixinTypes } from '@discordjs/structures'; +import { Mixin, Channel as StructureChannel } from '@discordjs/structures'; +import type { APIChannel } from 'discord-api-types/v10'; +import { BaseChannelMixin } from './mixins/BaseChannelMixin'; + +export interface BaseChannel extends MixinTypes {} + +export class BaseChannel extends StructureChannel { + public constructor(data: APIChannel) { + super(data); + } +} + +Mixin(BaseChannel, [BaseChannelMixin]); diff --git a/packages/next/src/structures/CategoryChannel.ts b/packages/next/src/structures/CategoryChannel.ts new file mode 100644 index 000000000000..72b4e9c522d0 --- /dev/null +++ b/packages/next/src/structures/CategoryChannel.ts @@ -0,0 +1,35 @@ +import { + Mixin, + type MixinTypes, + type Partialize, + Channel, + ChannelPermissionMixin, + GuildChannelMixin, +} from '@discordjs/structures'; +import type { APIGuildCategoryChannel, ChannelType } from 'discord-api-types/v10'; +import { BaseChannelMixin } from './mixins/BaseChannelMixin'; + +export interface CategoryChannel + extends MixinTypes< + Channel, + [ + BaseChannelMixin, + ChannelPermissionMixin, + GuildChannelMixin, + ] + > {} + +/** + * Sample Implementation of a structure for category channels, usable by direct end consumers. + */ +export class CategoryChannel extends Channel< + ChannelType.GuildCategory, + Omitted +> { + public constructor(data: Partialize) { + super(data); + this.optimizeData(data); + } +} + +Mixin(CategoryChannel, [BaseChannelMixin, ChannelPermissionMixin, GuildChannelMixin]); diff --git a/packages/next/src/structures/DMChannel.ts b/packages/next/src/structures/DMChannel.ts new file mode 100644 index 000000000000..5578b8dfca9c --- /dev/null +++ b/packages/next/src/structures/DMChannel.ts @@ -0,0 +1,34 @@ +import { + Mixin, + type MixinTypes, + type Partialize, + Channel, + ChannelPinMixin, + DMChannelMixin, + TextChannelMixin, +} from '@discordjs/structures'; +import type { APIDMChannel, ChannelType } from 'discord-api-types/v10'; +import { BaseChannelMixin } from './mixins/BaseChannelMixin'; + +export interface DMChannel + extends MixinTypes< + Channel, + [ + BaseChannelMixin, + DMChannelMixin, + TextChannelMixin, + ChannelPinMixin, + ] + > {} + +/** + * Sample Implementation of a structure for dm channels, usable by direct end consumers. + */ +export class DMChannel extends Channel { + public constructor(data: Partialize) { + super(data); + this.optimizeData(data); + } +} + +Mixin(DMChannel, [BaseChannelMixin, DMChannelMixin, TextChannelMixin, ChannelPinMixin]); diff --git a/packages/next/src/structures/ForumChannel.ts b/packages/next/src/structures/ForumChannel.ts new file mode 100644 index 000000000000..4ac03391d381 --- /dev/null +++ b/packages/next/src/structures/ForumChannel.ts @@ -0,0 +1,54 @@ +import { + Mixin, + type MixinTypes, + kData, + type Partialize, + Channel, + ChannelParentMixin, + ChannelPermissionMixin, + ChannelTopicMixin, + ThreadOnlyChannelMixin, +} from '@discordjs/structures'; +import type { APIGuildForumChannel, ChannelType } from 'discord-api-types/v10'; +import { BaseChannelMixin } from './mixins/BaseChannelMixin'; + +export interface ForumChannel + extends MixinTypes< + Channel, + [ + BaseChannelMixin, + ChannelParentMixin, + ChannelPermissionMixin, + ChannelTopicMixin, + ThreadOnlyChannelMixin, + ] + > {} + +/** + * Sample Implementation of a structure for forum channels, usable by direct end consumers. + */ +export class ForumChannel extends Channel< + ChannelType.GuildForum, + Omitted +> { + public constructor(data: Partialize) { + super(data); + this.optimizeData(data); + } + + /** + * The default forum layout view used to display posts in this channel. + * Defaults to 0, which indicates a layout view has not been set by a channel admin. + */ + public get defaultForumLayout() { + return this[kData].default_forum_layout; + } +} + +Mixin(ForumChannel, [ + BaseChannelMixin, + ChannelParentMixin, + ChannelPermissionMixin, + ChannelTopicMixin, + ThreadOnlyChannelMixin, +]); diff --git a/packages/next/src/structures/GroupDMChannel.ts b/packages/next/src/structures/GroupDMChannel.ts new file mode 100644 index 000000000000..c3dbf0201d1b --- /dev/null +++ b/packages/next/src/structures/GroupDMChannel.ts @@ -0,0 +1,37 @@ +import { + Mixin, + type MixinTypes, + type Partialize, + Channel, + ChannelOwnerMixin, + DMChannelMixin, + GroupDMMixin, + TextChannelMixin, +} from '@discordjs/structures'; +import type { APIGroupDMChannel, ChannelType } from 'discord-api-types/v10'; + +export interface GroupDMChannel + extends MixinTypes< + Channel, + [ + DMChannelMixin, + TextChannelMixin, + ChannelOwnerMixin, + GroupDMMixin, + ] + > {} + +/** + * Sample Implementation of a structure for group dm channels, usable by direct end consumers. + */ +export class GroupDMChannel extends Channel< + ChannelType.GroupDM, + Omitted +> { + public constructor(data: Partialize) { + super(data); + this.optimizeData(data); + } +} + +Mixin(GroupDMChannel, [DMChannelMixin, TextChannelMixin, ChannelOwnerMixin, GroupDMMixin]); diff --git a/packages/next/src/structures/MediaChannel.ts b/packages/next/src/structures/MediaChannel.ts new file mode 100644 index 000000000000..9f315ba3bb06 --- /dev/null +++ b/packages/next/src/structures/MediaChannel.ts @@ -0,0 +1,45 @@ +import { + Mixin, + type MixinTypes, + type Partialize, + Channel, + ChannelParentMixin, + ChannelPermissionMixin, + ChannelTopicMixin, + ThreadOnlyChannelMixin, +} from '@discordjs/structures'; +import type { APIGuildMediaChannel, ChannelType } from 'discord-api-types/v10'; +import { BaseChannelMixin } from './mixins/BaseChannelMixin'; + +export interface MediaChannel + extends MixinTypes< + Channel, + [ + BaseChannelMixin, + ChannelParentMixin, + ChannelPermissionMixin, + ChannelTopicMixin, + ThreadOnlyChannelMixin, + ] + > {} + +/** + * Sample Implementation of a structure for media channels, usable by direct end consumers. + */ +export class MediaChannel extends Channel< + ChannelType.GuildMedia, + Omitted +> { + public constructor(data: Partialize) { + super(data); + this.optimizeData(data); + } +} + +Mixin(MediaChannel, [ + BaseChannelMixin, + ChannelParentMixin, + ChannelPermissionMixin, + ChannelTopicMixin, + ThreadOnlyChannelMixin, +]); diff --git a/packages/next/src/structures/PrivateThreadChannel.ts b/packages/next/src/structures/PrivateThreadChannel.ts new file mode 100644 index 000000000000..3c1b0fed7abf --- /dev/null +++ b/packages/next/src/structures/PrivateThreadChannel.ts @@ -0,0 +1,51 @@ +import { + Mixin, + type MixinTypes, + type Partialize, + Channel, + ChannelOwnerMixin, + ChannelParentMixin, + ChannelPinMixin, + ChannelSlowmodeMixin, + TextChannelMixin, + ThreadChannelMixin, +} from '@discordjs/structures'; +import type { APIPrivateThreadChannel, ChannelType } from 'discord-api-types/v10'; +import { BaseChannelMixin } from './mixins/BaseChannelMixin'; + +export interface PrivateThreadChannel + extends MixinTypes< + Channel, + [ + BaseChannelMixin, + TextChannelMixin, + ChannelOwnerMixin, + ChannelParentMixin, + ChannelPinMixin, + ChannelSlowmodeMixin, + ThreadChannelMixin, + ] + > {} + +/** + * Sample Implementation of a structure for private thread channels, usable by direct end consumers. + */ +export class PrivateThreadChannel extends Channel< + ChannelType.PrivateThread, + Omitted +> { + public constructor(data: Partialize) { + super(data); + this.optimizeData(data); + } +} + +Mixin(PrivateThreadChannel, [ + BaseChannelMixin, + TextChannelMixin, + ChannelOwnerMixin, + ChannelParentMixin, + ChannelPinMixin, + ChannelSlowmodeMixin, + ThreadChannelMixin, +]); diff --git a/packages/next/src/structures/PublicThreadChannel.ts b/packages/next/src/structures/PublicThreadChannel.ts new file mode 100644 index 000000000000..d175504653e1 --- /dev/null +++ b/packages/next/src/structures/PublicThreadChannel.ts @@ -0,0 +1,54 @@ +import { + Mixin, + type MixinTypes, + type Partialize, + Channel, + AppliedTagsMixin, + ChannelOwnerMixin, + ChannelParentMixin, + ChannelPinMixin, + ChannelSlowmodeMixin, + TextChannelMixin, + ThreadChannelMixin, +} from '@discordjs/structures'; +import type { APIPublicThreadChannel, ChannelType } from 'discord-api-types/v10'; +import { BaseChannelMixin } from './mixins/BaseChannelMixin'; + +export interface PublicThreadChannel + extends MixinTypes< + Channel, + [ + BaseChannelMixin, + TextChannelMixin, + ChannelOwnerMixin, + ChannelParentMixin, + ChannelPinMixin, + ChannelSlowmodeMixin, + ThreadChannelMixin, + AppliedTagsMixin, + ] + > {} + +/** + * Sample Implementation of a structure for public thread channels, usable by direct end consumers. + */ +export class PublicThreadChannel extends Channel< + ChannelType.PublicThread, + Omitted +> { + public constructor(data: Partialize) { + super(data); + this.optimizeData(data); + } +} + +Mixin(PublicThreadChannel, [ + BaseChannelMixin, + TextChannelMixin, + ChannelOwnerMixin, + ChannelParentMixin, + ChannelPinMixin, + ChannelSlowmodeMixin, + ThreadChannelMixin, + AppliedTagsMixin, +]); diff --git a/packages/next/src/structures/StageChannel.ts b/packages/next/src/structures/StageChannel.ts new file mode 100644 index 000000000000..f0b711e572c6 --- /dev/null +++ b/packages/next/src/structures/StageChannel.ts @@ -0,0 +1,45 @@ +import { + Mixin, + type MixinTypes, + type Partialize, + Channel, + ChannelParentMixin, + ChannelPermissionMixin, + ChannelSlowmodeMixin, + ChannelWebhookMixin, + VoiceChannelMixin, +} from '@discordjs/structures'; +import type { APIGuildStageVoiceChannel, ChannelType } from 'discord-api-types/v10'; +import { BaseChannelMixin } from './mixins/BaseChannelMixin'; + +export interface StageChannel + extends MixinTypes< + Channel, + [ + BaseChannelMixin, + ChannelParentMixin, + ChannelPermissionMixin, + ChannelSlowmodeMixin, + ChannelWebhookMixin, + VoiceChannelMixin, + ] + > {} + +export class StageChannel extends Channel< + ChannelType.GuildStageVoice, + Omitted +> { + public constructor(data: Partialize) { + super(data); + this.optimizeData(data); + } +} + +Mixin(StageChannel, [ + BaseChannelMixin, + ChannelParentMixin, + ChannelPermissionMixin, + ChannelSlowmodeMixin, + ChannelWebhookMixin, + VoiceChannelMixin, +]); diff --git a/packages/next/src/structures/TextChannel.ts b/packages/next/src/structures/TextChannel.ts new file mode 100644 index 000000000000..3356279f21f7 --- /dev/null +++ b/packages/next/src/structures/TextChannel.ts @@ -0,0 +1,48 @@ +import { + Mixin, + type MixinTypes, + type Partialize, + Channel, + ChannelParentMixin, + ChannelPermissionMixin, + ChannelPinMixin, + ChannelSlowmodeMixin, + ChannelTopicMixin, + TextChannelMixin, +} from '@discordjs/structures'; +import type { APITextChannel, ChannelType } from 'discord-api-types/v10'; +import { BaseChannelMixin } from './mixins/BaseChannelMixin'; + +export interface TextChannel + extends MixinTypes< + Channel, + [ + BaseChannelMixin, + TextChannelMixin, + ChannelParentMixin, + ChannelPermissionMixin, + ChannelPinMixin, + ChannelSlowmodeMixin, + ChannelTopicMixin, + ] + > {} + +export class TextChannel extends Channel< + ChannelType.GuildText, + Omitted +> { + public constructor(data: Partialize) { + super(data); + this.optimizeData(data); + } +} + +Mixin(TextChannel, [ + BaseChannelMixin, + TextChannelMixin, + ChannelParentMixin, + ChannelPermissionMixin, + ChannelPinMixin, + ChannelSlowmodeMixin, + ChannelTopicMixin, +]); diff --git a/packages/next/src/structures/VoiceChannel.ts b/packages/next/src/structures/VoiceChannel.ts new file mode 100644 index 000000000000..97e095fc8d8f --- /dev/null +++ b/packages/next/src/structures/VoiceChannel.ts @@ -0,0 +1,45 @@ +import { + Mixin, + type MixinTypes, + type Partialize, + Channel, + ChannelParentMixin, + ChannelPermissionMixin, + ChannelSlowmodeMixin, + ChannelWebhookMixin, + VoiceChannelMixin, +} from '@discordjs/structures'; +import type { APIGuildVoiceChannel, ChannelType } from 'discord-api-types/v10'; +import { BaseChannelMixin } from './mixins/BaseChannelMixin'; + +export interface VoiceChannel + extends MixinTypes< + Channel, + [ + BaseChannelMixin, + ChannelParentMixin, + ChannelPermissionMixin, + ChannelSlowmodeMixin, + ChannelWebhookMixin, + VoiceChannelMixin, + ] + > {} + +export class VoiceChannel extends Channel< + ChannelType.GuildVoice, + Omitted +> { + public constructor(data: Partialize) { + super(data); + this.optimizeData(data); + } +} + +Mixin(VoiceChannel, [ + BaseChannelMixin, + ChannelParentMixin, + ChannelPermissionMixin, + ChannelSlowmodeMixin, + ChannelWebhookMixin, + VoiceChannelMixin, +]); diff --git a/packages/next/src/structures/mixins/BaseChannelMixin.ts b/packages/next/src/structures/mixins/BaseChannelMixin.ts new file mode 100644 index 000000000000..aa412f37f540 --- /dev/null +++ b/packages/next/src/structures/mixins/BaseChannelMixin.ts @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/dot-notation */ +import { equal } from 'node:assert'; +import type { ChannelType } from '@discordjs/core'; +import { kPatch, type Channel, type ChannelDataType } from '@discordjs/structures'; +import type { Client } from '../../Client.js'; +import { container } from '../../util/container.js'; + +export interface BaseChannelMixin extends Channel {} + +export class BaseChannelMixin { + declare public readonly client: Client; + + public async delete() { + await container.get('client')['core'].api.channels.delete(this.id); + return this; + } + + public async fetch() { + const data = await container.get('client')['core'].api.channels.get(this.id); + + equal(data.type, this.type); + return this[kPatch](data as ChannelDataType); + } +} diff --git a/packages/next/src/structures/mixins/ChannelPermissionsMixin.ts b/packages/next/src/structures/mixins/ChannelPermissionsMixin.ts new file mode 100644 index 000000000000..9873bd12f6cd --- /dev/null +++ b/packages/next/src/structures/mixins/ChannelPermissionsMixin.ts @@ -0,0 +1,13 @@ +import type { ChannelType, GuildChannelType, ThreadChannelType } from '@discordjs/core'; +import { ChannelPermissionMixin as StructurePermissionMixin } from '@discordjs/structures'; + +export class ChannelPermissionsMixin< + Type extends Exclude = Exclude< + GuildChannelType, + ChannelType.GuildDirectory | ThreadChannelType + >, +> extends StructurePermissionMixin { + // public permissionsFor(userOrRole: RoleResolvable | UserResolvable) { + // return new PermissionsBitField(); + // } +} diff --git a/packages/next/src/util/cache.ts b/packages/next/src/util/cache.ts new file mode 100644 index 000000000000..d4c8031a78a9 --- /dev/null +++ b/packages/next/src/util/cache.ts @@ -0,0 +1,25 @@ +import { Collection } from '@discordjs/collection'; +import { kPatch, type Structure } from '@discordjs/structures'; +import type { Awaitable } from '@discordjs/util'; +import type { Snowflake } from 'discord-api-types/globals'; + +export interface StructureCache> { + add(key: Snowflake, data: Partial): Value; + get(key: Snowflake): Awaitable; + set(key: Snowflake, value: Value): Awaitable; +} + +export type CacheConstructor = new (...args: any[]) => StructureCache>; + +export class CollectionCache> + extends Collection + implements StructureCache +{ + public add(_key: Snowflake, _data: Partial): Value { + throw new Error('Method not implemented.'); + } + + public patch(key: Snowflake, data: Partial): Value { + return this.get(key)?.[kPatch](data) ?? this.add(key, data); + } +} diff --git a/packages/next/src/util/container.ts b/packages/next/src/util/container.ts new file mode 100644 index 000000000000..ca5aad126432 --- /dev/null +++ b/packages/next/src/util/container.ts @@ -0,0 +1,30 @@ +import { Collection } from '@discordjs/collection'; +import type { Client } from '../Client'; + +export class Container extends Collection {} + +export const container = new Container(); + +export interface Container { + get(key: Key): ContainerEntries[Key]; + get(key: string): undefined; + has(key: ContainerKey): true; + has(key: string): false; +} + +/** + * A type utility to get the keys of {@link ContainerEntries}. + */ +export type ContainerKey = keyof ContainerEntries; + +/** + * A type utility to get the values of {@link ContainerEntries}. + */ +export type ContainerValue = ContainerEntries[ContainerKey]; + +/** + * The {@link Container}'s registry, use module augmentation against this interface when adding new entries. + */ +export interface ContainerEntries { + client: Client; +} diff --git a/packages/structures/src/index.ts b/packages/structures/src/index.ts index 5a2e6b407d7f..14cda8551a17 100644 --- a/packages/structures/src/index.ts +++ b/packages/structures/src/index.ts @@ -5,5 +5,6 @@ export * from './users/index.js'; export * from './Structure.js'; export * from './Mixin.js'; export * from './utils/optimization.js'; +export * from './utils/symbols.js'; export type * from './utils/types.js'; export type * from './MixinTypes.d.ts'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 470a6875531e..b30b55646715 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1226,6 +1226,9 @@ importers: '@discordjs/rest': specifier: workspace:^ version: link:../rest + '@discordjs/structures': + specifier: workspace:^ + version: link:../structures '@discordjs/util': specifier: workspace:^ version: link:../util