-
-
Notifications
You must be signed in to change notification settings - Fork 4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
165 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import type { GatewayDispatchEvents, GatewayDispatchPayload, GatewaySendPayload } from 'discord-api-types/v10'; | ||
|
||
// need this to be its own type for some reason, the compiler doesn't behave the same way if we in-line it | ||
type _DiscordEvents = { | ||
[K in GatewayDispatchEvents]: GatewayDispatchPayload & { | ||
t: K; | ||
}; | ||
}; | ||
|
||
export type DiscordEvents = { | ||
// @ts-expect-error - unclear why this ignore is needed, might be because dapi-types is missing some events from the union again | ||
[K in keyof _DiscordEvents]: _DiscordEvents[K]['d']; | ||
} & { | ||
gateway_send: GatewaySendPayload; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import process from 'node:process'; | ||
import type { GatewayIntentBits } from 'discord-api-types/v10'; | ||
|
||
export class Env { | ||
public readonly redisUrl: string = process.env.REDIS_URL!; | ||
|
||
public readonly discordToken: string = process.env.DISCORD_TOKEN!; | ||
|
||
public readonly discordProxyURL: string | null = process.env.DISCORD_PROXY_URL ?? null; | ||
|
||
public readonly intents: GatewayIntentBits | 0 = Number(process.env.INTENTS ?? 0); | ||
|
||
public readonly shardCount: number | null = process.env.SHARD_COUNT ? Number(process.env.SHARD_COUNT) : null; | ||
|
||
public readonly shardIds: number[] | null = process.env.SHARD_IDS | ||
? process.env.SHARD_IDS.split(',').map(Number) | ||
: null; | ||
|
||
public readonly shardsPerWorker: number | 'all' | null = | ||
process.env.SHARDS_PER_WORKER === 'all' | ||
? 'all' | ||
: process.env.SHARDS_PER_WORKER | ||
? Number(process.env.SHARDS_PER_WORKER) | ||
: null; | ||
|
||
private readonly REQUIRED_ENV_VARS = ['REDIS_URL', 'DISCORD_TOKEN'] as const; | ||
|
||
public constructor() { | ||
for (const key of this.REQUIRED_ENV_VARS) { | ||
if (!(key in process.env)) { | ||
throw new Error(`Missing required environment variable: ${key}`); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,62 @@ | ||
console.log('Hello, from @discordjs/redis-gateway'); | ||
import { randomBytes } from 'node:crypto'; | ||
import { PubSubRedisBroker } from '@discordjs/brokers'; | ||
import type { RESTOptions } from '@discordjs/rest'; | ||
import { REST } from '@discordjs/rest'; | ||
import type { OptionalWebSocketManagerOptions, RequiredWebSocketManagerOptions } from '@discordjs/ws'; | ||
import { WorkerShardingStrategy, CompressionMethod, WebSocketManager, WebSocketShardEvents } from '@discordjs/ws'; | ||
import Redis from 'ioredis'; | ||
import { ProxyAgent } from 'undici'; | ||
import type { DiscordEvents } from './discordEvents.js'; | ||
import { Env } from './env.js'; | ||
|
||
const env = new Env(); | ||
|
||
const redisClient = new Redis(env.redisUrl); | ||
const broker = new PubSubRedisBroker<DiscordEvents>({ | ||
redisClient, | ||
}); | ||
|
||
const restOptions: Partial<RESTOptions> = {}; | ||
if (env.discordProxyURL) { | ||
restOptions.api = `${env.discordProxyURL}/api`; | ||
} | ||
|
||
const rest = new REST(restOptions).setToken(env.discordToken); | ||
if (env.discordProxyURL) { | ||
rest.setAgent(new ProxyAgent(env.discordProxyURL)); | ||
} | ||
|
||
const gatewayOptions: Partial<OptionalWebSocketManagerOptions> & RequiredWebSocketManagerOptions = { | ||
token: env.discordToken, | ||
rest, | ||
intents: env.intents, | ||
compression: CompressionMethod.ZlibStream, | ||
shardCount: env.shardCount, | ||
shardIds: env.shardIds, | ||
}; | ||
if (env.shardsPerWorker) { | ||
gatewayOptions.buildStrategy = (manager) => | ||
new WorkerShardingStrategy(manager, { shardsPerWorker: env.shardsPerWorker! }); | ||
} | ||
|
||
const gateway = new WebSocketManager(gatewayOptions); | ||
|
||
gateway | ||
.on(WebSocketShardEvents.Debug, ({ message, shardId }) => console.log(`[WS Shard ${shardId}] [DEBUG]`, message)) | ||
.on(WebSocketShardEvents.Hello, ({ shardId }) => console.log(`[WS Shard ${shardId}] [HELLO]`)) | ||
.on(WebSocketShardEvents.Ready, ({ shardId }) => console.log(`[WS Shard ${shardId}] [READY]`)) | ||
.on(WebSocketShardEvents.Resumed, ({ shardId }) => console.log(`[WS Shard ${shardId}] [RESUMED]`)) | ||
.on(WebSocketShardEvents.Dispatch, ({ data }) => void broker.publish(data.t, data.d)); | ||
|
||
broker.on('gateway_send', async ({ data, ack }) => { | ||
for (const shardId of await gateway.getShardIds()) { | ||
await gateway.send(shardId, data); | ||
} | ||
|
||
await ack(); | ||
}); | ||
|
||
// we use a random group name because we don't want work-balancing, | ||
// we need this to be fanned out so all shards get the payload | ||
await broker.subscribe(randomBytes(16).toString('hex'), ['gateway_send']); | ||
await gateway.connect(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters