Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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'])",
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 2 additions & 3 deletions packages/discord.js/typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ import {
APIMessageTopLevelComponent,
APIMessageUserSelectInteractionData,
APIModalComponent,
APIModalInteractionResponseCallbackComponent,
APIModalInteractionResponseCallbackData,
APIModalSubmitInteraction,
APIOverwrite,
Expand Down Expand Up @@ -3508,7 +3507,7 @@ export interface PrivateThreadChannel extends ThreadChannel<false> {
type: ChannelType.PrivateThread;
}

export interface ThreadChannel<ThreadOnly extends boolean = boolean>
export interface ThreadChannel
extends TextBasedChannelFields<true>,
PinnableChannelFields,
BulkDeleteMethod,
Expand Down Expand Up @@ -3810,7 +3809,7 @@ export class VoiceState extends Base {
public fetch(force?: boolean): Promise<VoiceState>;
}

export interface Webhook<Type extends WebhookType = WebhookType> extends WebhookFields {}
export interface Webhook extends WebhookFields {}
export class Webhook<Type extends WebhookType = WebhookType> {
private constructor(client: Client<true>, data?: unknown);
public avatar: string | null;
Expand Down
1 change: 1 addition & 0 deletions packages/next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
57 changes: 57 additions & 0 deletions packages/next/src/Client.ts
Original file line number Diff line number Diff line change
@@ -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<infer Type> ? Type : never,
>() {
return new this.cacheConstructor() as StructureCache<Raw, Value>;
}

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<RESTGetAPIGatewayBotResult>,
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();
}
}
37 changes: 37 additions & 0 deletions packages/next/src/managers/CachedManager.ts
Original file line number Diff line number Diff line change
@@ -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<infer Type> ? Type : never,
> {
public client: Client;

public cache: StructureCache<Raw, Value>;

protected abstract createStructure(data: Raw, ...extras: unknown[]): Value;

public constructor(client: Client) {
this.client = client;
this.cache = client.CacheConstructor<Value, Raw>();
}

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;
}
}
72 changes: 72 additions & 0 deletions packages/next/src/managers/ChannelManager.ts
Original file line number Diff line number Diff line change
@@ -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<Channel> {
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);
}
}
}
51 changes: 51 additions & 0 deletions packages/next/src/structures/AnnouncementChannel.ts
Original file line number Diff line number Diff line change
@@ -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<Omitted extends keyof APINewsChannel | '' = ''>
extends MixinTypes<
Channel<ChannelType.GuildAnnouncement>,
[
BaseChannelMixin<ChannelType.GuildAnnouncement>,
TextChannelMixin<ChannelType.GuildAnnouncement>,
ChannelParentMixin<ChannelType.GuildAnnouncement>,
ChannelPermissionMixin<ChannelType.GuildAnnouncement>,
ChannelPinMixin<ChannelType.GuildAnnouncement>,
ChannelSlowmodeMixin<ChannelType.GuildAnnouncement>,
ChannelTopicMixin<ChannelType.GuildAnnouncement>,
]
> {}

/**
* Sample Implementation of a structure for announcement channels, usable by direct end consumers.
*/
export class AnnouncementChannel<Omitted extends keyof APINewsChannel | '' = ''> extends Channel<
ChannelType.GuildAnnouncement,
Omitted
> {
public constructor(data: Partialize<APINewsChannel, Omitted>) {
super(data);
this.optimizeData(data);
}
}

Mixin(AnnouncementChannel, [
BaseChannelMixin,
TextChannelMixin,
ChannelParentMixin,
ChannelPermissionMixin,
ChannelPinMixin,
ChannelSlowmodeMixin,
ChannelTopicMixin,
]);
54 changes: 54 additions & 0 deletions packages/next/src/structures/AnnouncementThreadChannel.ts
Original file line number Diff line number Diff line change
@@ -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<Omitted extends keyof APIAnnouncementThreadChannel | '' = ''>
extends MixinTypes<
Channel<ChannelType.AnnouncementThread>,
[
BaseChannelMixin<ChannelType.AnnouncementThread>,
TextChannelMixin<ChannelType.AnnouncementThread>,
ChannelOwnerMixin<ChannelType.AnnouncementThread>,
ChannelParentMixin<ChannelType.AnnouncementThread>,
ChannelPinMixin<ChannelType.AnnouncementThread>,
ChannelSlowmodeMixin<ChannelType.AnnouncementThread>,
GuildChannelMixin<ChannelType.AnnouncementThread>,
ThreadChannelMixin<ChannelType.AnnouncementThread>,
]
> {}

/**
* Sample Implementation of a structure for announcement threads, usable by direct end consumers.
*/
export class AnnouncementThreadChannel<Omitted extends keyof APIAnnouncementThreadChannel | '' = ''> extends Channel<
ChannelType.AnnouncementThread,
Omitted
> {
public constructor(data: Partialize<APIAnnouncementThreadChannel, Omitted>) {
super(data);
this.optimizeData?.(data);
}
}

Mixin(AnnouncementThreadChannel, [
BaseChannelMixin,
TextChannelMixin,
ChannelOwnerMixin,
ChannelParentMixin,
ChannelPinMixin,
ChannelSlowmodeMixin,
GuildChannelMixin,
ThreadChannelMixin,
]);
14 changes: 14 additions & 0 deletions packages/next/src/structures/BaseChannel.ts
Original file line number Diff line number Diff line change
@@ -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<StructureChannel, [BaseChannelMixin]> {}

export class BaseChannel extends StructureChannel {
public constructor(data: APIChannel) {
super(data);
}
}

Mixin(BaseChannel, [BaseChannelMixin]);
35 changes: 35 additions & 0 deletions packages/next/src/structures/CategoryChannel.ts
Original file line number Diff line number Diff line change
@@ -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<Omitted extends keyof APIGuildCategoryChannel | '' = ''>
extends MixinTypes<
Channel<ChannelType.GuildCategory>,
[
BaseChannelMixin<ChannelType.GuildCategory>,
ChannelPermissionMixin<ChannelType.GuildCategory>,
GuildChannelMixin<ChannelType.GuildCategory>,
]
> {}

/**
* Sample Implementation of a structure for category channels, usable by direct end consumers.
*/
export class CategoryChannel<Omitted extends keyof APIGuildCategoryChannel | '' = ''> extends Channel<
ChannelType.GuildCategory,
Omitted
> {
public constructor(data: Partialize<APIGuildCategoryChannel, Omitted>) {
super(data);
this.optimizeData(data);
}
}

Mixin(CategoryChannel, [BaseChannelMixin, ChannelPermissionMixin, GuildChannelMixin]);
Loading