Merge pull request #122 from DjDeveloperr/refactor

feat: refactor project structure
This commit is contained in:
DjDeveloper 2021-04-08 15:30:40 +05:30 committed by GitHub
commit ce843b189b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
162 changed files with 3963 additions and 2175 deletions

3
.gitignore vendored
View file

@ -109,6 +109,7 @@ yarn.lock
# PRIVACY XDDDD # PRIVACY XDDDD
src/test/config.ts src/test/config.ts
test/config.ts
.vscode .vscode
# macOS is shit xD # macOS is shit xD
@ -117,4 +118,4 @@ src/test/config.ts
# Webstorm dont forget this duude :) # Webstorm dont forget this duude :)
.idea/ .idea/
src/test/music.mp3 src/test/music.mp3

View file

@ -1,8 +1,9 @@
import { import {
SlashCommandsManager, SlashCommandsManager,
SlashClient, SlashClient,
SlashCommandHandlerCallback SlashCommandHandlerCallback,
} from './src/models/slashClient.ts' SlashCommandHandler
} from './src/interactions/mod.ts'
import { InteractionResponseType, InteractionType } from './src/types/slash.ts' import { InteractionResponseType, InteractionType } from './src/types/slash.ts'
export interface DeploySlashInitOptions { export interface DeploySlashInitOptions {
@ -41,7 +42,7 @@ export function init(options: DeploySlashInitOptions): void {
try { try {
const d = await client.verifyFetchEvent({ const d = await client.verifyFetchEvent({
respondWith: (...args: any[]) => evt.respondWith(...args), respondWith: (...args: any[]) => evt.respondWith(...args),
request: evt.request, request: evt.request
}) })
if (d === false) { if (d === false) {
await evt.respondWith( await evt.respondWith(
@ -60,7 +61,6 @@ export function init(options: DeploySlashInitOptions): void {
await (client as any)._process(d) await (client as any)._process(d)
} catch (e) { } catch (e) {
console.log(e)
await client.emit('interactionError', e) await client.emit('interactionError', e)
} }
} }
@ -69,38 +69,14 @@ export function init(options: DeploySlashInitOptions): void {
} }
export function handle( export function handle(
cmd: cmd: string | SlashCommandHandler,
| string handler?: SlashCommandHandlerCallback
| {
name: string
parent?: string
group?: string
guild?: string
},
handler: SlashCommandHandlerCallback
): void { ): void {
const handle = { client.handle(cmd, handler)
name: typeof cmd === 'string' ? cmd : cmd.name,
handler,
...(typeof cmd === 'string' ? {} : cmd)
}
if (typeof handle.name === 'string' && handle.name.includes(' ') && handle.parent === undefined && handle.group === undefined) {
const parts = handle.name.split(/ +/).filter(e => e !== '')
if (parts.length > 3 || parts.length < 1) throw new Error('Invalid command name')
const root = parts.shift() as string
const group = parts.length === 2 ? parts.shift() : undefined
const sub = parts.shift()
handle.name = sub ?? root
handle.group = group
handle.parent = sub === undefined ? undefined : root
}
client.handle(handle)
} }
export { commands, client } export { commands, client }
export * from './src/types/slash.ts' export * from './src/types/slash.ts'
export * from './src/structures/slash.ts' export * from './src/structures/slash.ts'
export * from './src/models/slashClient.ts' export * from './src/interactions/mod.ts'
export * from './src/types/channel.ts'

67
mod.ts
View file

@ -1,20 +1,18 @@
export { GatewayIntents } from './src/types/gateway.ts' export { GatewayIntents } from './src/types/gateway.ts'
export { Base } from './src/structures/base.ts' export { Base } from './src/structures/base.ts'
export { Gateway } from './src/gateway/index.ts' export { Gateway } from './src/gateway/mod.ts'
export type { GatewayTypedEvents } from './src/gateway/index.ts' export type { GatewayTypedEvents } from './src/gateway/mod.ts'
export type { ClientEvents } from './src/gateway/handlers/index.ts' export type { ClientEvents } from './src/gateway/handlers/mod.ts'
export * from './src/models/client.ts' export * from './src/client/mod.ts'
export * from './src/models/slashClient.ts' export * from './src/interactions/mod.ts'
export { export {
RESTManager, RESTManager,
TokenType, TokenType,
HttpResponseCode, HttpResponseCode,
DiscordAPIError DiscordAPIError
} from './src/models/rest.ts' } from './src/rest/mod.ts'
export type { APIMap, DiscordAPIErrorPayload } from './src/models/rest.ts' export * from './src/rest/mod.ts'
export type { RequestHeaders } from './src/models/rest.ts' export * from './src/cache/adapter.ts'
export type { RESTOptions } from './src/models/rest.ts'
export * from './src/models/cacheAdapter.ts'
export { export {
Command, Command,
CommandBuilder, CommandBuilder,
@ -22,16 +20,16 @@ export {
CommandsManager, CommandsManager,
CategoriesManager, CategoriesManager,
CommandsLoader CommandsLoader
} from './src/models/command.ts' } from './src/commands/command.ts'
export type { CommandContext, CommandOptions } from './src/models/command.ts' export type { CommandContext, CommandOptions } from './src/commands/command.ts'
export { export {
Extension, Extension,
ExtensionCommands, ExtensionCommands,
ExtensionsManager ExtensionsManager
} from './src/models/extensions.ts' } from './src/commands/extension.ts'
export { SlashModule } from './src/models/slashModule.ts' export { SlashModule } from './src/interactions/slashModule.ts'
export { CommandClient, command } from './src/models/commandClient.ts' export { CommandClient, command } from './src/commands/client.ts'
export type { CommandClientOptions } from './src/models/commandClient.ts' export type { CommandClientOptions } from './src/commands/client.ts'
export { BaseManager } from './src/managers/base.ts' export { BaseManager } from './src/managers/base.ts'
export { BaseChildManager } from './src/managers/baseChild.ts' export { BaseChildManager } from './src/managers/baseChild.ts'
export { ChannelsManager } from './src/managers/channels.ts' export { ChannelsManager } from './src/managers/channels.ts'
@ -51,7 +49,7 @@ export { RolesManager } from './src/managers/roles.ts'
export { UsersManager } from './src/managers/users.ts' export { UsersManager } from './src/managers/users.ts'
export { InviteManager } from './src/managers/invites.ts' export { InviteManager } from './src/managers/invites.ts'
export { Application } from './src/structures/application.ts' export { Application } from './src/structures/application.ts'
// export { ImageURL } from './src/structures/cdn.ts' export { ImageURL } from './src/structures/cdn.ts'
export { Channel, GuildChannel } from './src/structures/channel.ts' export { Channel, GuildChannel } from './src/structures/channel.ts'
export type { EditOverwriteOptions } from './src/structures/channel.ts' export type { EditOverwriteOptions } from './src/structures/channel.ts'
export { DMChannel } from './src/structures/dmChannel.ts' export { DMChannel } from './src/structures/dmChannel.ts'
@ -98,7 +96,7 @@ export { Intents } from './src/utils/intents.ts'
export * from './src/utils/permissions.ts' export * from './src/utils/permissions.ts'
export { UserFlagsManager } from './src/utils/userFlags.ts' export { UserFlagsManager } from './src/utils/userFlags.ts'
export { HarmonyEventEmitter } from './src/utils/events.ts' export { HarmonyEventEmitter } from './src/utils/events.ts'
export type { EveryChannelTypes } from './src/utils/getChannelByType.ts' export type { EveryChannelTypes } from './src/utils/channel.ts'
export * from './src/utils/bitfield.ts' export * from './src/utils/bitfield.ts'
export type { export type {
ActivityGame, ActivityGame,
@ -106,7 +104,15 @@ export type {
ClientStatus, ClientStatus,
StatusType StatusType
} from './src/types/presence.ts' } from './src/types/presence.ts'
export { ChannelTypes } from './src/types/channel.ts' export {
ChannelTypes,
OverwriteType,
OverrideType
} from './src/types/channel.ts'
export type {
OverwriteAsOptions,
OverwritePayload
} from './src/types/channel.ts'
export type { ApplicationPayload } from './src/types/application.ts' export type { ApplicationPayload } from './src/types/application.ts'
export type { ImageFormats, ImageSize } from './src/types/cdn.ts' export type { ImageFormats, ImageSize } from './src/types/cdn.ts'
export type { export type {
@ -131,8 +137,7 @@ export type {
MessageStickerPayload, MessageStickerPayload,
MessageTypes, MessageTypes,
OverwriteAsArg, OverwriteAsArg,
Overwrite, Overwrite
OverwriteAsOptions
} from './src/types/channel.ts' } from './src/types/channel.ts'
export type { EmojiPayload } from './src/types/emoji.ts' export type { EmojiPayload } from './src/types/emoji.ts'
export { Verification } from './src/types/guild.ts' export { Verification } from './src/types/guild.ts'
@ -165,8 +170,24 @@ export type { UserPayload } from './src/types/user.ts'
export { UserFlags } from './src/types/userFlags.ts' export { UserFlags } from './src/types/userFlags.ts'
export type { VoiceStatePayload } from './src/types/voice.ts' export type { VoiceStatePayload } from './src/types/voice.ts'
export type { WebhookPayload } from './src/types/webhook.ts' export type { WebhookPayload } from './src/types/webhook.ts'
export * from './src/models/collectors.ts' export * from './src/client/collectors.ts'
export type { Dict } from './src/utils/dict.ts' export type { Dict } from './src/utils/dict.ts'
export * from './src/models/redisCache.ts' export * from './src/cache/redis.ts'
export { ColorUtil } from './src/utils/colorutil.ts' export { ColorUtil } from './src/utils/colorutil.ts'
export type { Colors } from './src/utils/colorutil.ts' export type { Colors } from './src/utils/colorutil.ts'
export { StoreChannel } from './src/structures/guildStoreChannel.ts'
export { StageVoiceChannel } from './src/structures/guildStageVoiceChannel.ts'
export {
isCategoryChannel,
isDMChannel,
isGroupDMChannel,
isGuildBasedTextChannel,
isGuildChannel,
isGuildTextChannel,
isNewsChannel,
isStageVoiceChannel,
isStoreChannel,
isTextChannel,
isVoiceChannel,
default as getChannelByType
} from './src/utils/channel.ts'

22
src/cache/adapter.ts vendored Normal file
View file

@ -0,0 +1,22 @@
/**
* ICacheAdapter is the interface to be implemented by Cache Adapters for them to be usable with Harmony.
*
* Methods can return Promises too.
*/
export interface ICacheAdapter {
/** Gets a key from a Cache */
get: (cacheName: string, key: string) => Promise<any> | any
/** Sets a key to value in a Cache Name with optional expire value in MS */
set: (
cacheName: string,
key: string,
value: any,
expire?: number
) => Promise<any> | any
/** Deletes a key from a Cache */
delete: (cacheName: string, key: string) => Promise<boolean> | boolean
/** Gets array of all values in a Cache */
array: (cacheName: string) => undefined | any[] | Promise<any[] | undefined>
/** Entirely deletes a Cache */
deleteCache: (cacheName: string) => any
}

View file

@ -1,72 +1,50 @@
import { Collection } from '../utils/collection.ts' import { Collection } from '../utils/collection.ts'
import type { ICacheAdapter } from './adapter.ts'
/**
* ICacheAdapter is the interface to be implemented by Cache Adapters for them to be usable with Harmony. /** Default Cache Adapter for in-memory caching. */
* export class DefaultCacheAdapter implements ICacheAdapter {
* Methods can return Promises too. data: {
*/ [name: string]: Collection<string, any>
export interface ICacheAdapter { } = {}
/** Gets a key from a Cache */
get: (cacheName: string, key: string) => Promise<any> | any async get(cacheName: string, key: string): Promise<undefined | any> {
/** Sets a key to value in a Cache Name with optional expire value in MS */ const cache = this.data[cacheName]
set: ( if (cache === undefined) return
cacheName: string, return cache.get(key)
key: string, }
value: any,
expire?: number async set(
) => Promise<any> | any cacheName: string,
/** Deletes a key from a Cache */ key: string,
delete: (cacheName: string, key: string) => Promise<boolean> | boolean value: any,
/** Gets array of all values in a Cache */ expire?: number
array: (cacheName: string) => undefined | any[] | Promise<any[] | undefined> ): Promise<any> {
/** Entirely deletes a Cache */ let cache = this.data[cacheName]
deleteCache: (cacheName: string) => any if (cache === undefined) {
} this.data[cacheName] = new Collection()
cache = this.data[cacheName]
/** Default Cache Adapter for in-memory caching. */ }
export class DefaultCacheAdapter implements ICacheAdapter { cache.set(key, value)
data: { if (expire !== undefined)
[name: string]: Collection<string, any> setTimeout(() => {
} = {} cache.delete(key)
}, expire)
async get(cacheName: string, key: string): Promise<undefined | any> { }
const cache = this.data[cacheName]
if (cache === undefined) return async delete(cacheName: string, key: string): Promise<boolean> {
return cache.get(key) const cache = this.data[cacheName]
} if (cache === undefined) return false
return cache.delete(key)
async set( }
cacheName: string,
key: string, async array(cacheName: string): Promise<any[] | undefined> {
value: any, const cache = this.data[cacheName]
expire?: number if (cache === undefined) return
): Promise<any> { return cache.array()
let cache = this.data[cacheName] }
if (cache === undefined) {
this.data[cacheName] = new Collection() async deleteCache(cacheName: string): Promise<boolean> {
cache = this.data[cacheName] // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
} return delete this.data[cacheName]
cache.set(key, value) }
if (expire !== undefined) }
setTimeout(() => {
cache.delete(key)
}, expire)
}
async delete(cacheName: string, key: string): Promise<boolean> {
const cache = this.data[cacheName]
if (cache === undefined) return false
return cache.delete(key)
}
async array(cacheName: string): Promise<any[] | undefined> {
const cache = this.data[cacheName]
if (cache === undefined) return
return cache.array()
}
async deleteCache(cacheName: string): Promise<boolean> {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
return delete this.data[cacheName]
}
}

4
src/cache/mod.ts vendored Normal file
View file

@ -0,0 +1,4 @@
export * from './adapter.ts'
export * from './default.ts'
// Not exported by default
// export * from './redis.ts'

View file

@ -1,5 +1,10 @@
import { ICacheAdapter } from './cacheAdapter.ts' import { ICacheAdapter } from './adapter.ts'
import { connect, Redis, RedisConnectOptions } from 'https://deno.land/x/redis@v0.14.1/mod.ts' // Not in deps.ts to allow optional dep loading
import {
connect,
Redis,
RedisConnectOptions
} from 'https://deno.land/x/redis@v0.14.1/mod.ts'
/** Redis Cache Adapter for using Redis as a cache-provider. */ /** Redis Cache Adapter for using Redis as a cache-provider. */
export class RedisCacheAdapter implements ICacheAdapter { export class RedisCacheAdapter implements ICacheAdapter {
@ -102,4 +107,4 @@ export class RedisCacheAdapter implements ICacheAdapter {
await this._checkReady() await this._checkReady()
return (await this.redis?.del(cacheName)) !== 0 return (await this.redis?.del(cacheName)) !== 0
} }
} }

View file

@ -1,437 +1,437 @@
/* eslint-disable @typescript-eslint/method-signature-style */ /* eslint-disable @typescript-eslint/method-signature-style */
import { User } from '../structures/user.ts' import type { User } from '../structures/user.ts'
import { GatewayIntents } from '../types/gateway.ts' import { GatewayIntents } from '../types/gateway.ts'
import { Gateway } from '../gateway/index.ts' import { Gateway } from '../gateway/mod.ts'
import { RESTManager, RESTOptions, TokenType } from './rest.ts' import { RESTManager, RESTOptions, TokenType } from '../rest/mod.ts'
import { DefaultCacheAdapter, ICacheAdapter } from './cacheAdapter.ts' import { DefaultCacheAdapter, ICacheAdapter } from '../cache/mod.ts'
import { UsersManager } from '../managers/users.ts' import { UsersManager } from '../managers/users.ts'
import { GuildManager } from '../managers/guilds.ts' import { GuildManager } from '../managers/guilds.ts'
import { ChannelsManager } from '../managers/channels.ts' import { ChannelsManager } from '../managers/channels.ts'
import { ClientPresence } from '../structures/presence.ts' import { ClientPresence } from '../structures/presence.ts'
import { EmojisManager } from '../managers/emojis.ts' import { EmojisManager } from '../managers/emojis.ts'
import { ActivityGame, ClientActivity } from '../types/presence.ts' import { ActivityGame, ClientActivity } from '../types/presence.ts'
import { Extension } from './extensions.ts' import type { Extension } from '../commands/extension.ts'
import { SlashClient } from './slashClient.ts' import { SlashClient } from '../interactions/slashClient.ts'
import { Interaction } from '../structures/slash.ts' import type { Interaction } from '../structures/slash.ts'
import { ShardManager } from './shard.ts' import { ShardManager } from './shard.ts'
import { Application } from '../structures/application.ts' import { Application } from '../structures/application.ts'
import { Invite } from '../structures/invite.ts' import { Invite } from '../structures/invite.ts'
import { INVITE } from '../types/endpoint.ts' import { INVITE } from '../types/endpoint.ts'
import { ClientEvents } from '../gateway/handlers/index.ts' import type { ClientEvents } from '../gateway/handlers/mod.ts'
import type { Collector } from './collectors.ts' import type { Collector } from './collectors.ts'
import { HarmonyEventEmitter } from '../utils/events.ts' import { HarmonyEventEmitter } from '../utils/events.ts'
import { VoiceRegion } from '../types/voice.ts' import type { VoiceRegion } from '../types/voice.ts'
import { fetchAuto } from '../../deps.ts' import { fetchAuto } from '../../deps.ts'
import { DMChannel } from '../structures/dmChannel.ts' import type { DMChannel } from '../structures/dmChannel.ts'
import { Template } from '../structures/template.ts' import { Template } from '../structures/template.ts'
/** OS related properties sent with Gateway Identify */ /** OS related properties sent with Gateway Identify */
export interface ClientProperties { export interface ClientProperties {
os?: 'darwin' | 'windows' | 'linux' | 'custom_os' | string os?: 'darwin' | 'windows' | 'linux' | 'custom_os' | string
browser?: 'harmony' | string browser?: 'harmony' | string
device?: 'harmony' | string device?: 'harmony' | string
} }
/** Some Client Options to modify behaviour */ /** Some Client Options to modify behaviour */
export interface ClientOptions { export interface ClientOptions {
/** ID of the Client/Application to initialize Slash Client REST */ /** ID of the Client/Application to initialize Slash Client REST */
id?: string id?: string
/** Token of the Bot/User */ /** Token of the Bot/User */
token?: string token?: string
/** Gateway Intents */ /** Gateway Intents */
intents?: GatewayIntents[] intents?: GatewayIntents[]
/** Cache Adapter to use, defaults to Collections one */ /** Cache Adapter to use, defaults to Collections one */
cache?: ICacheAdapter cache?: ICacheAdapter
/** Force New Session and don't use cached Session (by persistent caching) */ /** Force New Session and don't use cached Session (by persistent caching) */
forceNewSession?: boolean forceNewSession?: boolean
/** Startup presence of client */ /** Startup presence of client */
presence?: ClientPresence | ClientActivity | ActivityGame presence?: ClientPresence | ClientActivity | ActivityGame
/** Force all requests to Canary API */ /** Force all requests to Canary API */
canary?: boolean canary?: boolean
/** Time till which Messages are to be cached, in MS. Default is 3600000 */ /** Time till which Messages are to be cached, in MS. Default is 3600000 */
messageCacheLifetime?: number messageCacheLifetime?: number
/** Time till which Message Reactions are to be cached, in MS. Default is 3600000 */ /** Time till which Message Reactions are to be cached, in MS. Default is 3600000 */
reactionCacheLifetime?: number reactionCacheLifetime?: number
/** Whether to fetch Uncached Message of Reaction or not? */ /** Whether to fetch Uncached Message of Reaction or not? */
fetchUncachedReactions?: boolean fetchUncachedReactions?: boolean
/** Client Properties */ /** Client Properties */
clientProperties?: ClientProperties clientProperties?: ClientProperties
/** Enable/Disable Slash Commands Integration (enabled by default) */ /** Enable/Disable Slash Commands Integration (enabled by default) */
enableSlash?: boolean enableSlash?: boolean
/** Disable taking token from env if not provided (token is taken from env if present by default) */ /** Disable taking token from env if not provided (token is taken from env if present by default) */
disableEnvToken?: boolean disableEnvToken?: boolean
/** Override REST Options */ /** Override REST Options */
restOptions?: RESTOptions restOptions?: RESTOptions
/** Whether to fetch Gateway info or not */ /** Whether to fetch Gateway info or not */
fetchGatewayInfo?: boolean fetchGatewayInfo?: boolean
/** ADVANCED: Shard ID to launch on */ /** ADVANCED: Shard ID to launch on */
shard?: number shard?: number
/** ADVACNED: Shard count. */ /** ADVACNED: Shard count. */
shardCount?: number | 'auto' shardCount?: number | 'auto'
} }
/** /**
* Discord Client. * Discord Client.
*/ */
export class Client extends HarmonyEventEmitter<ClientEvents> { export class Client extends HarmonyEventEmitter<ClientEvents> {
/** REST Manager - used to make all requests */ /** REST Manager - used to make all requests */
rest: RESTManager rest: RESTManager
/** User which Client logs in to, undefined until logs in */ /** User which Client logs in to, undefined until logs in */
user?: User user?: User
/** WebSocket ping of Client */ /** WebSocket ping of Client */
ping = 0 ping = 0
/** Token of the Bot/User */ /** Token of the Bot/User */
token?: string token?: string
/** Cache Adapter */ /** Cache Adapter */
cache: ICacheAdapter = new DefaultCacheAdapter() cache: ICacheAdapter = new DefaultCacheAdapter()
/** Gateway Intents */ /** Gateway Intents */
intents?: GatewayIntents[] intents?: GatewayIntents[]
/** Whether to force new session or not */ /** Whether to force new session or not */
forceNewSession?: boolean forceNewSession?: boolean
/** Time till messages to stay cached, in MS. */ /** Time till messages to stay cached, in MS. */
messageCacheLifetime: number = 3600000 messageCacheLifetime: number = 3600000
/** Time till messages to stay cached, in MS. */ /** Time till messages to stay cached, in MS. */
reactionCacheLifetime: number = 3600000 reactionCacheLifetime: number = 3600000
/** Whether to fetch Uncached Message of Reaction or not? */ /** Whether to fetch Uncached Message of Reaction or not? */
fetchUncachedReactions: boolean = false fetchUncachedReactions: boolean = false
/** Client Properties */ /** Client Properties */
clientProperties: ClientProperties clientProperties: ClientProperties
/** Slash-Commands Management client */ /** Slash-Commands Management client */
slash: SlashClient slash: SlashClient
/** Whether to fetch Gateway info or not */ /** Whether to fetch Gateway info or not */
fetchGatewayInfo: boolean = true fetchGatewayInfo: boolean = true
/** Users Manager, containing all Users cached */ /** Users Manager, containing all Users cached */
users: UsersManager = new UsersManager(this) users: UsersManager = new UsersManager(this)
/** Guilds Manager, providing cache & API interface to Guilds */ /** Guilds Manager, providing cache & API interface to Guilds */
guilds: GuildManager = new GuildManager(this) guilds: GuildManager = new GuildManager(this)
/** Channels Manager, providing cache interface to Channels */ /** Channels Manager, providing cache interface to Channels */
channels: ChannelsManager = new ChannelsManager(this) channels: ChannelsManager = new ChannelsManager(this)
/** Channels Manager, providing cache interface to Channels */ /** Channels Manager, providing cache interface to Channels */
emojis: EmojisManager = new EmojisManager(this) emojis: EmojisManager = new EmojisManager(this)
/** Last READY timestamp */ /** Last READY timestamp */
upSince?: Date upSince?: Date
/** Client's presence. Startup one if set before connecting */ /** Client's presence. Startup one if set before connecting */
presence: ClientPresence = new ClientPresence() presence: ClientPresence = new ClientPresence()
_decoratedEvents?: { _decoratedEvents?: {
[name: string]: (...args: any[]) => void [name: string]: (...args: any[]) => void
} }
_decoratedSlash?: Array<{ _decoratedSlash?: Array<{
name: string name: string
guild?: string guild?: string
parent?: string parent?: string
group?: string group?: string
handler: (interaction: Interaction) => any handler: (interaction: Interaction) => any
}> }>
_id?: string _id?: string
/** Shard on which this Client is */ /** Shard on which this Client is */
shard?: number shard?: number
/** Shard Count */ /** Shard Count */
shardCount: number | 'auto' = 'auto' shardCount: number | 'auto' = 'auto'
/** Shard Manager of this Client if Sharded */ /** Shard Manager of this Client if Sharded */
shards: ShardManager shards: ShardManager
/** Collectors set */ /** Collectors set */
collectors: Set<Collector> = new Set() collectors: Set<Collector> = new Set()
/** Since when is Client online (ready). */ /** Since when is Client online (ready). */
get uptime(): number { get uptime(): number {
if (this.upSince === undefined) return 0 if (this.upSince === undefined) return 0
else { else {
const dif = Date.now() - this.upSince.getTime() const dif = Date.now() - this.upSince.getTime()
if (dif < 0) return 0 if (dif < 0) return 0
else return dif else return dif
} }
} }
get gateway(): Gateway { get gateway(): Gateway {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
return this.shards.list.get('0') as Gateway return this.shards.list.get('0') as Gateway
} }
applicationID?: string applicationID?: string
applicationFlags?: number applicationFlags?: number
constructor(options: ClientOptions = {}) { constructor(options: ClientOptions = {}) {
super() super()
this._id = options.id this._id = options.id
this.token = options.token this.token = options.token
this.intents = options.intents this.intents = options.intents
this.shards = new ShardManager(this) this.shards = new ShardManager(this)
this.forceNewSession = options.forceNewSession this.forceNewSession = options.forceNewSession
if (options.cache !== undefined) this.cache = options.cache if (options.cache !== undefined) this.cache = options.cache
if (options.presence !== undefined) if (options.presence !== undefined)
this.presence = this.presence =
options.presence instanceof ClientPresence options.presence instanceof ClientPresence
? options.presence ? options.presence
: new ClientPresence(options.presence) : new ClientPresence(options.presence)
if (options.messageCacheLifetime !== undefined) if (options.messageCacheLifetime !== undefined)
this.messageCacheLifetime = options.messageCacheLifetime this.messageCacheLifetime = options.messageCacheLifetime
if (options.reactionCacheLifetime !== undefined) if (options.reactionCacheLifetime !== undefined)
this.reactionCacheLifetime = options.reactionCacheLifetime this.reactionCacheLifetime = options.reactionCacheLifetime
if (options.fetchUncachedReactions === true) if (options.fetchUncachedReactions === true)
this.fetchUncachedReactions = true this.fetchUncachedReactions = true
if ( if (
this._decoratedEvents !== undefined && this._decoratedEvents !== undefined &&
Object.keys(this._decoratedEvents).length !== 0 Object.keys(this._decoratedEvents).length !== 0
) { ) {
Object.entries(this._decoratedEvents).forEach((entry) => { Object.entries(this._decoratedEvents).forEach((entry) => {
this.on(entry[0] as keyof ClientEvents, entry[1].bind(this)) this.on(entry[0] as keyof ClientEvents, entry[1].bind(this))
}) })
this._decoratedEvents = undefined this._decoratedEvents = undefined
} }
this.clientProperties = this.clientProperties =
options.clientProperties === undefined options.clientProperties === undefined
? { ? {
os: Deno.build.os, os: Deno.build.os,
browser: 'harmony', browser: 'harmony',
device: 'harmony' device: 'harmony'
} }
: options.clientProperties : options.clientProperties
if (options.shard !== undefined) this.shard = options.shard if (options.shard !== undefined) this.shard = options.shard
if (options.shardCount !== undefined) this.shardCount = options.shardCount if (options.shardCount !== undefined) this.shardCount = options.shardCount
this.fetchGatewayInfo = options.fetchGatewayInfo ?? true this.fetchGatewayInfo = options.fetchGatewayInfo ?? true
if (this.token === undefined) { if (this.token === undefined) {
try { try {
const token = Deno.env.get('DISCORD_TOKEN') const token = Deno.env.get('DISCORD_TOKEN')
if (token !== undefined) { if (token !== undefined) {
this.token = token this.token = token
this.debug('Info', 'Found token in ENV') this.debug('Info', 'Found token in ENV')
} }
} catch (e) { } } catch (e) {}
} }
const restOptions: RESTOptions = { const restOptions: RESTOptions = {
token: () => this.token, token: () => this.token,
tokenType: TokenType.Bot, tokenType: TokenType.Bot,
canary: options.canary, canary: options.canary,
client: this client: this
} }
if (options.restOptions !== undefined) if (options.restOptions !== undefined)
Object.assign(restOptions, options.restOptions) Object.assign(restOptions, options.restOptions)
this.rest = new RESTManager(restOptions) this.rest = new RESTManager(restOptions)
this.slash = new SlashClient({ this.slash = new SlashClient({
id: () => this.getEstimatedID(), id: () => this.getEstimatedID(),
client: this, client: this,
enabled: options.enableSlash enabled: options.enableSlash
}) })
} }
/** /**
* Sets Cache Adapter * Sets Cache Adapter
* *
* Should NOT be set after bot is already logged in or using current cache. * Should NOT be set after bot is already logged in or using current cache.
* Please look into using `cache` option. * Please look into using `cache` option.
*/ */
setAdapter(adapter: ICacheAdapter): Client { setAdapter(adapter: ICacheAdapter): Client {
this.cache = adapter this.cache = adapter
return this return this
} }
/** Changes Presence of Client */ /** Changes Presence of Client */
setPresence(presence: ClientPresence | ClientActivity | ActivityGame): void { setPresence(presence: ClientPresence | ClientActivity | ActivityGame): void {
if (presence instanceof ClientPresence) { if (presence instanceof ClientPresence) {
this.presence = presence this.presence = presence
} else this.presence = new ClientPresence(presence) } else this.presence = new ClientPresence(presence)
this.gateway?.sendPresence(this.presence.create()) this.gateway?.sendPresence(this.presence.create())
} }
/** Emits debug event */ /** Emits debug event */
debug(tag: string, msg: string): void { debug(tag: string, msg: string): void {
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
this.emit('debug', `[${tag}] ${msg}`) this.emit('debug', `[${tag}] ${msg}`)
} }
getEstimatedID(): string { getEstimatedID(): string {
if (this.user !== undefined) return this.user.id if (this.user !== undefined) return this.user.id
else if (this.token !== undefined) { else if (this.token !== undefined) {
try { try {
return atob(this.token.split('.')[0]) return atob(this.token.split('.')[0])
} catch (e) { } catch (e) {
return this._id ?? 'unknown' return this._id ?? 'unknown'
} }
} else { } else {
return this._id ?? 'unknown' return this._id ?? 'unknown'
} }
} }
/** Fetch Application of the Client */ /** Fetch Application of the Client */
async fetchApplication(): Promise<Application> { async fetchApplication(): Promise<Application> {
const app = await this.rest.api.oauth2.applications['@me'].get() const app = await this.rest.api.oauth2.applications['@me'].get()
return new Application(this, app) return new Application(this, app)
} }
/** Fetch an Invite */ /** Fetch an Invite */
async fetchInvite(id: string): Promise<Invite> { async fetchInvite(id: string): Promise<Invite> {
return await new Promise((resolve, reject) => { return await new Promise((resolve, reject) => {
this.rest this.rest
.get(INVITE(id)) .get(INVITE(id))
.then((data) => { .then((data) => {
resolve(new Invite(this, data)) resolve(new Invite(this, data))
}) })
.catch((e) => reject(e)) .catch((e) => reject(e))
}) })
} }
/** /**
* This function is used for connecting to discord. * This function is used for connecting to discord.
* @param token Your token. This is required if not given in ClientOptions. * @param token Your token. This is required if not given in ClientOptions.
* @param intents Gateway intents in array. This is required if not given in ClientOptions. * @param intents Gateway intents in array. This is required if not given in ClientOptions.
*/ */
async connect(token?: string, intents?: GatewayIntents[]): Promise<Client> { async connect(token?: string, intents?: GatewayIntents[]): Promise<Client> {
token ??= this.token token ??= this.token
if (token === undefined) throw new Error('No Token Provided') if (token === undefined) throw new Error('No Token Provided')
this.token = token this.token = token
if (intents !== undefined && this.intents !== undefined) { if (intents !== undefined && this.intents !== undefined) {
this.debug( this.debug(
'client', 'client',
'Intents were set in both client and connect function. Using the one in the connect function...' 'Intents were set in both client and connect function. Using the one in the connect function...'
) )
} else if (intents === undefined && this.intents !== undefined) { } else if (intents === undefined && this.intents !== undefined) {
intents = this.intents intents = this.intents
} else if (intents !== undefined && this.intents === undefined) { } else if (intents !== undefined && this.intents === undefined) {
this.intents = intents this.intents = intents
} else throw new Error('No Gateway Intents were provided') } else throw new Error('No Gateway Intents were provided')
this.rest.token = token this.rest.token = token
if (this.shard !== undefined) { if (this.shard !== undefined) {
if (typeof this.shardCount === 'number') if (typeof this.shardCount === 'number')
this.shards.cachedShardCount = this.shardCount this.shards.cachedShardCount = this.shardCount
await this.shards.launch(this.shard) await this.shards.launch(this.shard)
} else await this.shards.connect() } else await this.shards.connect()
return this.waitFor('ready', () => true).then(() => this) return this.waitFor('ready', () => true).then(() => this)
} }
/** Destroy the Gateway connection */ /** Destroy the Gateway connection */
async destroy(): Promise<Client> { async destroy(): Promise<Client> {
this.gateway.initialized = false this.gateway.initialized = false
this.gateway.sequenceID = undefined this.gateway.sequenceID = undefined
this.gateway.sessionID = undefined this.gateway.sessionID = undefined
await this.gateway.cache.delete('seq') await this.gateway.cache.delete('seq')
await this.gateway.cache.delete('session_id') await this.gateway.cache.delete('session_id')
this.gateway.close() this.gateway.close()
this.user = undefined this.user = undefined
this.upSince = undefined this.upSince = undefined
return this return this
} }
/** Attempt to Close current Gateway connection and Resume */ /** Attempt to Close current Gateway connection and Resume */
async reconnect(): Promise<Client> { async reconnect(): Promise<Client> {
this.gateway.close() this.gateway.close()
this.gateway.initWebsocket() this.gateway.initWebsocket()
return this.waitFor('ready', () => true).then(() => this) return this.waitFor('ready', () => true).then(() => this)
} }
/** Add a new Collector */ /** Add a new Collector */
addCollector(collector: Collector): boolean { addCollector(collector: Collector): boolean {
if (this.collectors.has(collector)) return false if (this.collectors.has(collector)) return false
else { else {
this.collectors.add(collector) this.collectors.add(collector)
return true return true
} }
} }
/** Remove a Collector */ /** Remove a Collector */
removeCollector(collector: Collector): boolean { removeCollector(collector: Collector): boolean {
if (!this.collectors.has(collector)) return false if (!this.collectors.has(collector)) return false
else { else {
this.collectors.delete(collector) this.collectors.delete(collector)
return true return true
} }
} }
async emit(event: keyof ClientEvents, ...args: any[]): Promise<void> { async emit(event: keyof ClientEvents, ...args: any[]): Promise<void> {
const collectors: Collector[] = [] const collectors: Collector[] = []
for (const collector of this.collectors.values()) { for (const collector of this.collectors.values()) {
if (collector.event === event) collectors.push(collector) if (collector.event === event) collectors.push(collector)
} }
if (collectors.length !== 0) { if (collectors.length !== 0) {
this.collectors.forEach((collector) => collector._fire(...args)) this.collectors.forEach((collector) => collector._fire(...args))
} }
// TODO(DjDeveloperr): Fix this ts-ignore // TODO(DjDeveloperr): Fix this ts-ignore
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
// @ts-ignore // @ts-ignore
return super.emit(event, ...args) return super.emit(event, ...args)
} }
/** Returns an array of voice region objects that can be used when creating servers. */ /** Returns an array of voice region objects that can be used when creating servers. */
async fetchVoiceRegions(): Promise<VoiceRegion[]> { async fetchVoiceRegions(): Promise<VoiceRegion[]> {
return this.rest.api.voice.regions.get() return this.rest.api.voice.regions.get()
} }
/** Modify current (Client) User. */ /** Modify current (Client) User. */
async editUser(data: { async editUser(data: {
username?: string username?: string
avatar?: string avatar?: string
}): Promise<Client> { }): Promise<Client> {
if (data.username === undefined && data.avatar === undefined) if (data.username === undefined && data.avatar === undefined)
throw new Error( throw new Error(
'Either username or avatar or both must be specified to edit' 'Either username or avatar or both must be specified to edit'
) )
if (data.avatar?.startsWith('http') === true) { if (data.avatar?.startsWith('http') === true) {
data.avatar = await fetchAuto(data.avatar) data.avatar = await fetchAuto(data.avatar)
} }
await this.rest.api.users['@me'].patch({ await this.rest.api.users['@me'].patch({
username: data.username, username: data.username,
avatar: data.avatar avatar: data.avatar
}) })
return this return this
} }
/** Change Username of the Client User */ /** Change Username of the Client User */
async setUsername(username: string): Promise<Client> { async setUsername(username: string): Promise<Client> {
return await this.editUser({ username }) return await this.editUser({ username })
} }
/** Change Avatar of the Client User */ /** Change Avatar of the Client User */
async setAvatar(avatar: string): Promise<Client> { async setAvatar(avatar: string): Promise<Client> {
return await this.editUser({ avatar }) return await this.editUser({ avatar })
} }
/** Create a DM Channel with a User */ /** Create a DM Channel with a User */
async createDM(user: User | string): Promise<DMChannel> { async createDM(user: User | string): Promise<DMChannel> {
const id = typeof user === 'object' ? user.id : user const id = typeof user === 'object' ? user.id : user
const dmPayload = await this.rest.api.users['@me'].channels.post({ const dmPayload = await this.rest.api.users['@me'].channels.post({
recipient_id: id recipient_id: id
}) })
await this.channels.set(dmPayload.id, dmPayload) await this.channels.set(dmPayload.id, dmPayload)
return (this.channels.get<DMChannel>(dmPayload.id) as unknown) as DMChannel return (this.channels.get<DMChannel>(dmPayload.id) as unknown) as DMChannel
} }
/** Returns a template object for the given code. */ /** Returns a template object for the given code. */
async fetchTemplate(code: string): Promise<Template> { async fetchTemplate(code: string): Promise<Template> {
const payload = await this.rest.api.guilds.templates[code].get() const payload = await this.rest.api.guilds.templates[code].get()
return new Template(this, payload) return new Template(this, payload)
} }
} }
/** Event decorator to create an Event handler from function */ /** Event decorator to create an Event handler from function */
export function event(name?: keyof ClientEvents) { export function event(name?: keyof ClientEvents) {
return function ( return function (
client: Client | Extension, client: Client | Extension,
prop: keyof ClientEvents | string prop: keyof ClientEvents | string
) { ) {
const listener = ((client as unknown) as { const listener = ((client as unknown) as {
[name in keyof ClientEvents]: (...args: ClientEvents[name]) => any [name in keyof ClientEvents]: (...args: ClientEvents[name]) => any
})[(prop as unknown) as keyof ClientEvents] })[(prop as unknown) as keyof ClientEvents]
if (typeof listener !== 'function') if (typeof listener !== 'function')
throw new Error('@event decorator requires a function') throw new Error('@event decorator requires a function')
if (client._decoratedEvents === undefined) client._decoratedEvents = {} if (client._decoratedEvents === undefined) client._decoratedEvents = {}
const key = name === undefined ? prop : name const key = name === undefined ? prop : name
client._decoratedEvents[key] = listener client._decoratedEvents[key] = listener
} }
} }

View file

@ -1,5 +1,5 @@
import { Collection } from '../utils/collection.ts' import { Collection } from '../utils/collection.ts'
import type { Client } from './client.ts' import type { Client } from '../client/client.ts'
import { HarmonyEventEmitter } from '../utils/events.ts' import { HarmonyEventEmitter } from '../utils/events.ts'
export type CollectorFilter = (...args: any[]) => boolean | Promise<boolean> export type CollectorFilter = (...args: any[]) => boolean | Promise<boolean>

3
src/client/mod.ts Normal file
View file

@ -0,0 +1,3 @@
export * from './client.ts'
export * from './collectors.ts'
export * from './shard.ts'

View file

@ -1,7 +1,7 @@
import { Collection } from '../utils/collection.ts' import { Collection } from '../utils/collection.ts'
import type { Client } from './client.ts' import type { Client } from './client.ts'
import { RESTManager } from './rest.ts' import { RESTManager } from '../rest/mod.ts'
import { Gateway } from '../gateway/index.ts' import { Gateway } from '../gateway/mod.ts'
import { HarmonyEventEmitter } from '../utils/events.ts' import { HarmonyEventEmitter } from '../utils/events.ts'
import { GatewayEvents } from '../types/gateway.ts' import { GatewayEvents } from '../types/gateway.ts'
import { delay } from '../utils/delay.ts' import { delay } from '../utils/delay.ts'
@ -61,10 +61,24 @@ export class ShardManager extends HarmonyEventEmitter<ShardManagerEvents> {
let shardCount: number let shardCount: number
if (this.cachedShardCount !== undefined) shardCount = this.cachedShardCount if (this.cachedShardCount !== undefined) shardCount = this.cachedShardCount
else { else {
if (this.client.shardCount === 'auto') { if (
this.client.shardCount === 'auto' &&
this.client.fetchGatewayInfo !== false
) {
this.debug('Fetch /gateway/bot...')
const info = await this.client.rest.api.gateway.bot.get() const info = await this.client.rest.api.gateway.bot.get()
this.debug(`Recommended Shards: ${info.shards}`)
this.debug('=== Session Limit Info ===')
this.debug(
`Remaining: ${info.session_start_limit.remaining}/${info.session_start_limit.total}`
)
this.debug(`Reset After: ${info.session_start_limit.reset_after}ms`)
shardCount = info.shards as number shardCount = info.shards as number
} else shardCount = this.client.shardCount ?? 1 } else
shardCount =
typeof this.client.shardCount === 'string'
? 1
: this.client.shardCount ?? 1
} }
this.cachedShardCount = shardCount this.cachedShardCount = shardCount
return this.cachedShardCount return this.cachedShardCount

View file

@ -1,6 +1,6 @@
import { Message } from '../structures/message.ts' import type { Message } from '../structures/message.ts'
import { GuildTextBasedChannel } from '../structures/guildTextChannel.ts' import type { GuildTextBasedChannel } from '../structures/guildTextChannel.ts'
import { Client, ClientOptions } from './client.ts' import { Client, ClientOptions } from '../client/mod.ts'
import { import {
CategoriesManager, CategoriesManager,
Command, Command,
@ -9,7 +9,7 @@ import {
CommandsManager, CommandsManager,
parseCommand parseCommand
} from './command.ts' } from './command.ts'
import { Extension, ExtensionsManager } from './extensions.ts' import { Extension, ExtensionsManager } from './extension.ts'
type PrefixReturnType = string | string[] | Promise<string | string[]> type PrefixReturnType = string | string[] | Promise<string | string[]>

View file

@ -1,10 +1,10 @@
import { Guild } from '../structures/guild.ts' import type { Guild } from '../structures/guild.ts'
import { Message } from '../structures/message.ts' import type { Message } from '../structures/message.ts'
import { TextChannel } from '../structures/textChannel.ts' import type { TextChannel } from '../structures/textChannel.ts'
import { User } from '../structures/user.ts' import type { User } from '../structures/user.ts'
import { Collection } from '../utils/collection.ts' import { Collection } from '../utils/collection.ts'
import { CommandClient } from './commandClient.ts' import type { CommandClient } from './client.ts'
import { Extension } from './extensions.ts' import type { Extension } from './extension.ts'
import { join, walk } from '../../deps.ts' import { join, walk } from '../../deps.ts'
export interface CommandContext { export interface CommandContext {
@ -72,6 +72,8 @@ export interface CommandOptions {
} }
export class Command implements CommandOptions { export class Command implements CommandOptions {
static meta?: CommandOptions
name: string = '' name: string = ''
description?: string description?: string
category?: string category?: string
@ -486,12 +488,16 @@ export class CommandsManager {
/** Add a Command */ /** Add a Command */
add(cmd: Command | typeof Command): boolean { add(cmd: Command | typeof Command): boolean {
// eslint-disable-next-line new-cap if (!(cmd instanceof Command)) {
if (!(cmd instanceof Command)) cmd = new cmd() const CmdClass = cmd
cmd = new CmdClass()
Object.assign(cmd, CmdClass.meta ?? {})
}
if (this.exists(cmd, cmd.extension?.subPrefix)) if (this.exists(cmd, cmd.extension?.subPrefix))
throw new Error( throw new Error(
`Failed to add Command '${cmd.toString()}' with name/alias already exists.` `Failed to add Command '${cmd.toString()}' with name/alias already exists.`
) )
if (cmd.name === '') throw new Error('Command has no name')
this.list.set( this.list.set(
`${cmd.name}-${ `${cmd.name}-${
this.list.filter((e) => this.list.filter((e) =>

View file

@ -1,7 +1,7 @@
import { ClientEvents } from '../../mod.ts'
import { Collection } from '../utils/collection.ts' import { Collection } from '../utils/collection.ts'
import { Command } from './command.ts' import { Command } from './command.ts'
import { CommandClient } from './commandClient.ts' import { CommandClient } from './client.ts'
import type { ClientEvents } from '../gateway/handlers/mod.ts'
export type ExtensionEventCallback = (ext: Extension, ...args: any[]) => any export type ExtensionEventCallback = (ext: Extension, ...args: any[]) => any

3
src/commands/mod.ts Normal file
View file

@ -0,0 +1,3 @@
export * from './client.ts'
export * from './command.ts'
export * from './extension.ts'

View file

@ -1,9 +0,0 @@
export const DISCORD_API_URL: string = 'https://discord.com/api'
export const DISCORD_GATEWAY_URL: string = 'wss://gateway.discord.gg'
export const DISCORD_CDN_URL: string = 'https://cdn.discordapp.com'
export const DISCORD_API_VERSION: number = 8
export const DISCORD_VOICE_VERSION: number = 4

View file

@ -1,6 +1,6 @@
import { SlashCommand } from '../../models/slashClient.ts' import { SlashCommand } from '../../interactions/slashCommand.ts'
import { ApplicationCommandPayload } from '../../types/gateway.ts' import { ApplicationCommandPayload } from '../../types/gateway.ts'
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
export const applicationCommandCreate: GatewayEventHandler = async ( export const applicationCommandCreate: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -1,6 +1,6 @@
import { SlashCommand } from '../../models/slashClient.ts' import { SlashCommand } from '../../interactions/slashCommand.ts'
import { ApplicationCommandPayload } from '../../types/gateway.ts' import { ApplicationCommandPayload } from '../../types/gateway.ts'
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
export const applicationCommandDelete: GatewayEventHandler = async ( export const applicationCommandDelete: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -1,6 +1,6 @@
import { SlashCommand } from '../../models/slashClient.ts' import { SlashCommand } from '../../interactions/slashCommand.ts'
import { ApplicationCommandPayload } from '../../types/gateway.ts' import { ApplicationCommandPayload } from '../../types/gateway.ts'
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
export const applicationCommandUpdate: GatewayEventHandler = async ( export const applicationCommandUpdate: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -1,7 +1,10 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
import getChannelByType from '../../utils/getChannelByType.ts' import getChannelByType from '../../utils/channel.ts'
import { ChannelPayload, GuildChannelPayload } from '../../types/channel.ts' import type {
import { Guild } from '../../structures/guild.ts' ChannelPayload,
GuildChannelPayload
} from '../../types/channel.ts'
import type { Guild } from '../../structures/guild.ts'
export const channelCreate: GatewayEventHandler = async ( export const channelCreate: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -1,5 +1,5 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
import { ChannelPayload } from '../../types/channel.ts' import type { ChannelPayload } from '../../types/channel.ts'
export const channelDelete: GatewayEventHandler = async ( export const channelDelete: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -1,6 +1,6 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
import { TextChannel } from '../../structures/textChannel.ts' import type { TextChannel } from '../../structures/textChannel.ts'
import { ChannelPinsUpdatePayload } from '../../types/gateway.ts' import type { ChannelPinsUpdatePayload } from '../../types/gateway.ts'
export const channelPinsUpdate: GatewayEventHandler = async ( export const channelPinsUpdate: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -1,6 +1,6 @@
import { Channel } from '../../structures/channel.ts' import type { Channel } from '../../structures/channel.ts'
import { ChannelPayload } from '../../types/channel.ts' import type { ChannelPayload } from '../../types/channel.ts'
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
export const channelUpdate: GatewayEventHandler = async ( export const channelUpdate: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -1,4 +1,4 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
import { Guild } from '../../structures/guild.ts' import { Guild } from '../../structures/guild.ts'
import { User } from '../../structures/user.ts' import { User } from '../../structures/user.ts'
import { GuildBanAddPayload } from '../../types/gateway.ts' import { GuildBanAddPayload } from '../../types/gateway.ts'

View file

@ -1,4 +1,4 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
import { Guild } from '../../structures/guild.ts' import { Guild } from '../../structures/guild.ts'
import { User } from '../../structures/user.ts' import { User } from '../../structures/user.ts'
import { GuildBanRemovePayload } from '../../types/gateway.ts' import { GuildBanRemovePayload } from '../../types/gateway.ts'

View file

@ -1,4 +1,4 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
import { Guild } from '../../structures/guild.ts' import { Guild } from '../../structures/guild.ts'
import { GuildPayload } from '../../types/guild.ts' import { GuildPayload } from '../../types/guild.ts'
import { GuildChannelPayload } from '../../types/channel.ts' import { GuildChannelPayload } from '../../types/channel.ts'

View file

@ -1,6 +1,6 @@
import { Guild } from '../../structures/guild.ts' import { Guild } from '../../structures/guild.ts'
import { GuildPayload } from '../../types/guild.ts' import { GuildPayload } from '../../types/guild.ts'
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
export const guildDelete: GatewayEventHandler = async ( export const guildDelete: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -2,7 +2,7 @@ import { Emoji } from '../../structures/emoji.ts'
import { Guild } from '../../structures/guild.ts' import { Guild } from '../../structures/guild.ts'
import { EmojiPayload } from '../../types/emoji.ts' import { EmojiPayload } from '../../types/emoji.ts'
import { GuildEmojiUpdatePayload } from '../../types/gateway.ts' import { GuildEmojiUpdatePayload } from '../../types/gateway.ts'
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
export const guildEmojiUpdate: GatewayEventHandler = async ( export const guildEmojiUpdate: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -1,4 +1,4 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
import { Guild } from '../../structures/guild.ts' import { Guild } from '../../structures/guild.ts'
import { GuildIntegrationsUpdatePayload } from '../../types/gateway.ts' import { GuildIntegrationsUpdatePayload } from '../../types/gateway.ts'

View file

@ -1,4 +1,4 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
import { Guild } from '../../structures/guild.ts' import { Guild } from '../../structures/guild.ts'
import { GuildMemberAddPayload } from '../../types/gateway.ts' import { GuildMemberAddPayload } from '../../types/gateway.ts'
import { Member } from '../../structures/member.ts' import { Member } from '../../structures/member.ts'

View file

@ -1,4 +1,4 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
import { Guild } from '../../structures/guild.ts' import { Guild } from '../../structures/guild.ts'
import { User } from '../../structures/user.ts' import { User } from '../../structures/user.ts'
import { GuildMemberRemovePayload } from '../../types/gateway.ts' import { GuildMemberRemovePayload } from '../../types/gateway.ts'

View file

@ -1,4 +1,4 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
import { Guild } from '../../structures/guild.ts' import { Guild } from '../../structures/guild.ts'
import { GuildMemberUpdatePayload } from '../../types/gateway.ts' import { GuildMemberUpdatePayload } from '../../types/gateway.ts'
import { MemberPayload } from '../../types/guild.ts' import { MemberPayload } from '../../types/guild.ts'

View file

@ -1,4 +1,4 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
import { Guild } from '../../structures/guild.ts' import { Guild } from '../../structures/guild.ts'
import { GuildMemberChunkPayload } from '../../types/gateway.ts' import { GuildMemberChunkPayload } from '../../types/gateway.ts'

View file

@ -1,4 +1,4 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
import { Guild } from '../../structures/guild.ts' import { Guild } from '../../structures/guild.ts'
import { GuildRoleCreatePayload } from '../../types/gateway.ts' import { GuildRoleCreatePayload } from '../../types/gateway.ts'
import { Role } from '../../structures/role.ts' import { Role } from '../../structures/role.ts'

View file

@ -1,4 +1,4 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
import { Guild } from '../../structures/guild.ts' import { Guild } from '../../structures/guild.ts'
import { GuildRoleDeletePayload } from '../../types/gateway.ts' import { GuildRoleDeletePayload } from '../../types/gateway.ts'

View file

@ -1,4 +1,4 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
import { Guild } from '../../structures/guild.ts' import { Guild } from '../../structures/guild.ts'
import { GuildRoleUpdatePayload } from '../../types/gateway.ts' import { GuildRoleUpdatePayload } from '../../types/gateway.ts'
import { Role } from '../../structures/role.ts' import { Role } from '../../structures/role.ts'

View file

@ -1,4 +1,4 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
import { Guild } from '../../structures/guild.ts' import { Guild } from '../../structures/guild.ts'
import { GuildPayload } from '../../types/guild.ts' import { GuildPayload } from '../../types/guild.ts'

View file

@ -9,7 +9,7 @@ import { GuildTextBasedChannel } from '../../structures/guildTextChannel.ts'
import { InteractionPayload } from '../../types/slash.ts' import { InteractionPayload } from '../../types/slash.ts'
import { UserPayload } from '../../types/user.ts' import { UserPayload } from '../../types/user.ts'
import { Permissions } from '../../utils/permissions.ts' import { Permissions } from '../../utils/permissions.ts'
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
import { User } from '../../structures/user.ts' import { User } from '../../structures/user.ts'
import { Role } from '../../structures/role.ts' import { Role } from '../../structures/role.ts'

View file

@ -1,4 +1,4 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
import { Guild } from '../../structures/guild.ts' import { Guild } from '../../structures/guild.ts'
import { InviteCreatePayload } from '../../types/gateway.ts' import { InviteCreatePayload } from '../../types/gateway.ts'
import { ChannelPayload } from '../../types/channel.ts' import { ChannelPayload } from '../../types/channel.ts'

View file

@ -1,4 +1,4 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
import { Guild } from '../../structures/guild.ts' import { Guild } from '../../structures/guild.ts'
import { InviteDeletePayload } from '../../types/gateway.ts' import { InviteDeletePayload } from '../../types/gateway.ts'
import { PartialInvitePayload } from '../../types/invite.ts' import { PartialInvitePayload } from '../../types/invite.ts'

View file

@ -1,8 +1,8 @@
import { Message } from '../../structures/message.ts' import { Message } from '../../structures/message.ts'
import { TextChannel } from '../../structures/textChannel.ts' import type { TextChannel } from '../../structures/textChannel.ts'
import { User } from '../../structures/user.ts' import { User } from '../../structures/user.ts'
import { MessagePayload } from '../../types/channel.ts' import type { MessagePayload } from '../../types/channel.ts'
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
export const messageCreate: GatewayEventHandler = async ( export const messageCreate: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -1,6 +1,6 @@
import { TextChannel } from '../../structures/textChannel.ts' import type { TextChannel } from '../../structures/textChannel.ts'
import { MessageDeletePayload } from '../../types/gateway.ts' import type { MessageDeletePayload } from '../../types/gateway.ts'
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
export const messageDelete: GatewayEventHandler = async ( export const messageDelete: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -1,8 +1,8 @@
import { Message } from '../../structures/message.ts' import type { Message } from '../../structures/message.ts'
import { GuildTextBasedChannel } from '../../structures/guildTextChannel.ts' import type { GuildTextBasedChannel } from '../../structures/guildTextChannel.ts'
import { MessageDeleteBulkPayload } from '../../types/gateway.ts' import type { MessageDeleteBulkPayload } from '../../types/gateway.ts'
import { Collection } from '../../utils/collection.ts' import { Collection } from '../../utils/collection.ts'
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
export const messageDeleteBulk: GatewayEventHandler = async ( export const messageDeleteBulk: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -1,8 +1,8 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
import { MessageReactionAddPayload } from '../../types/gateway.ts' import type { MessageReactionAddPayload } from '../../types/gateway.ts'
import { TextChannel } from '../../structures/textChannel.ts' import type { TextChannel } from '../../structures/textChannel.ts'
import { MessageReaction } from '../../structures/messageReaction.ts' import type { MessageReaction } from '../../structures/messageReaction.ts'
import { UserPayload } from '../../types/user.ts' import type { UserPayload } from '../../types/user.ts'
export const messageReactionAdd: GatewayEventHandler = async ( export const messageReactionAdd: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -1,6 +1,6 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
import { MessageReactionRemovePayload } from '../../types/gateway.ts' import type { MessageReactionRemovePayload } from '../../types/gateway.ts'
import { TextChannel } from '../../structures/textChannel.ts' import type { TextChannel } from '../../structures/textChannel.ts'
export const messageReactionRemove: GatewayEventHandler = async ( export const messageReactionRemove: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -1,6 +1,6 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
import { MessageReactionRemoveAllPayload } from '../../types/gateway.ts' import type { MessageReactionRemoveAllPayload } from '../../types/gateway.ts'
import { TextChannel } from '../../structures/textChannel.ts' import type { TextChannel } from '../../structures/textChannel.ts'
export const messageReactionRemoveAll: GatewayEventHandler = async ( export const messageReactionRemoveAll: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -1,6 +1,6 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
import { MessageReactionRemoveEmojiPayload } from '../../types/gateway.ts' import type { MessageReactionRemoveEmojiPayload } from '../../types/gateway.ts'
import { TextChannel } from '../../structures/textChannel.ts' import type { TextChannel } from '../../structures/textChannel.ts'
export const messageReactionRemoveEmoji: GatewayEventHandler = async ( export const messageReactionRemoveEmoji: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -1,7 +1,7 @@
import { Message } from '../../structures/message.ts' import type { Message } from '../../structures/message.ts'
import { TextChannel } from '../../structures/textChannel.ts' import type { TextChannel } from '../../structures/textChannel.ts'
import { MessagePayload } from '../../types/channel.ts' import type { MessagePayload } from '../../types/channel.ts'
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
export const messageUpdate: GatewayEventHandler = async ( export const messageUpdate: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -1,5 +1,5 @@
import { GatewayEventHandler } from '../index.ts' import type { GatewayEventHandler } from '../mod.ts'
import { import type {
GatewayEvents, GatewayEvents,
MessageDeletePayload, MessageDeletePayload,
TypingStartGuildData TypingStartGuildData
@ -31,18 +31,18 @@ import { webhooksUpdate } from './webhooksUpdate.ts'
import { messageDeleteBulk } from './messageDeleteBulk.ts' import { messageDeleteBulk } from './messageDeleteBulk.ts'
import { userUpdate } from './userUpdate.ts' import { userUpdate } from './userUpdate.ts'
import { typingStart } from './typingStart.ts' import { typingStart } from './typingStart.ts'
import { TextChannel } from '../../structures/textChannel.ts' import type { TextChannel } from '../../structures/textChannel.ts'
import { GuildTextBasedChannel } from '../../structures/guildTextChannel.ts' import { GuildTextBasedChannel } from '../../structures/guildTextChannel.ts'
import { Guild } from '../../structures/guild.ts' import type { Guild } from '../../structures/guild.ts'
import { User } from '../../structures/user.ts' import type { User } from '../../structures/user.ts'
import { Emoji } from '../../structures/emoji.ts' import type { Emoji } from '../../structures/emoji.ts'
import { Member } from '../../structures/member.ts' import type { Member } from '../../structures/member.ts'
import { Role } from '../../structures/role.ts' import type { Role } from '../../structures/role.ts'
import { Message } from '../../structures/message.ts' import type { Message } from '../../structures/message.ts'
import { Collection } from '../../utils/collection.ts' import type { Collection } from '../../utils/collection.ts'
import { voiceServerUpdate } from './voiceServerUpdate.ts' import { voiceServerUpdate } from './voiceServerUpdate.ts'
import { voiceStateUpdate } from './voiceStateUpdate.ts' import { voiceStateUpdate } from './voiceStateUpdate.ts'
import { VoiceState } from '../../structures/voiceState.ts' import type { VoiceState } from '../../structures/voiceState.ts'
import { messageReactionAdd } from './messageReactionAdd.ts' import { messageReactionAdd } from './messageReactionAdd.ts'
import { messageReactionRemove } from './messageReactionRemove.ts' import { messageReactionRemove } from './messageReactionRemove.ts'
import { messageReactionRemoveAll } from './messageReactionRemoveAll.ts' import { messageReactionRemoveAll } from './messageReactionRemoveAll.ts'
@ -51,23 +51,23 @@ import { guildMembersChunk } from './guildMembersChunk.ts'
import { presenceUpdate } from './presenceUpdate.ts' import { presenceUpdate } from './presenceUpdate.ts'
import { inviteCreate } from './inviteCreate.ts' import { inviteCreate } from './inviteCreate.ts'
import { inviteDelete } from './inviteDelete.ts' import { inviteDelete } from './inviteDelete.ts'
import { MessageReaction } from '../../structures/messageReaction.ts' import type { MessageReaction } from '../../structures/messageReaction.ts'
import { Invite } from '../../structures/invite.ts' import type { Invite } from '../../structures/invite.ts'
import { Presence } from '../../structures/presence.ts' import type { Presence } from '../../structures/presence.ts'
import { import type {
EveryChannelTypes, EveryChannelTypes,
EveryTextChannelTypes EveryTextChannelTypes
} from '../../utils/getChannelByType.ts' } from '../../utils/channel.ts'
import { interactionCreate } from './interactionCreate.ts' import { interactionCreate } from './interactionCreate.ts'
import { Interaction } from '../../structures/slash.ts' import type { Interaction } from '../../structures/slash.ts'
import { CommandContext } from '../../models/command.ts' import type { CommandContext } from '../../commands/command.ts'
import { RequestMethods } from '../../models/rest.ts' import type { RequestMethods } from '../../rest/types.ts'
import { PartialInvitePayload } from '../../types/invite.ts' import type { PartialInvitePayload } from '../../types/invite.ts'
import { GuildChannels } from '../../types/guild.ts' import type { GuildChannels } from '../../types/guild.ts'
import { applicationCommandCreate } from './applicationCommandCreate.ts' import { applicationCommandCreate } from './applicationCommandCreate.ts'
import { applicationCommandDelete } from './applicationCommandDelete.ts' import { applicationCommandDelete } from './applicationCommandDelete.ts'
import { applicationCommandUpdate } from './applicationCommandUpdate.ts' import { applicationCommandUpdate } from './applicationCommandUpdate.ts'
import { SlashCommand } from '../../models/slashClient.ts' import type { SlashCommand } from '../../interactions/slashCommand.ts'
export const gatewayHandlers: { export const gatewayHandlers: {
[eventCode in GatewayEvents]: GatewayEventHandler | undefined [eventCode in GatewayEvents]: GatewayEventHandler | undefined
@ -393,7 +393,15 @@ export type ClientEvents = {
} }
] ]
guildMembersChunked: [guild: Guild, chunks: number] guildMembersChunked: [guild: Guild, chunks: number]
rateLimit: [data: { method: RequestMethods; url: string; body: any }] rateLimit: [
data: {
method: RequestMethods
path: string
global: boolean
timeout: number
limit: number
}
]
inviteDeleteUncached: [invite: PartialInvitePayload] inviteDeleteUncached: [invite: PartialInvitePayload]
voiceStateRemoveUncached: [data: { guild: Guild; member: Member }] voiceStateRemoveUncached: [data: { guild: Guild; member: Member }]
userUpdateUncached: [user: User] userUpdateUncached: [user: User]

View file

@ -1,5 +1,5 @@
import { PresenceUpdatePayload } from '../../types/gateway.ts' import type { PresenceUpdatePayload } from '../../types/gateway.ts'
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
export const presenceUpdate: GatewayEventHandler = async ( export const presenceUpdate: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -1,7 +1,7 @@
import { User } from '../../structures/user.ts' import { User } from '../../structures/user.ts'
import { Ready } from '../../types/gateway.ts' import type { Ready } from '../../types/gateway.ts'
import { GuildPayload } from '../../types/guild.ts' import type { GuildPayload } from '../../types/guild.ts'
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
export const ready: GatewayEventHandler = async ( export const ready: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -1,4 +1,4 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
export const reconnect: GatewayEventHandler = async ( export const reconnect: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -1,7 +1,7 @@
import { User } from '../../structures/user.ts' import { User } from '../../structures/user.ts'
import { CLIENT_USER } from '../../types/endpoint.ts' import { CLIENT_USER } from '../../types/endpoint.ts'
import { Resume } from '../../types/gateway.ts' import type { Resume } from '../../types/gateway.ts'
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
export const resume: GatewayEventHandler = async ( export const resume: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -1,7 +1,7 @@
import { Member } from '../../structures/member.ts' import { Member } from '../../structures/member.ts'
import { TextChannel } from '../../structures/textChannel.ts' import type { TextChannel } from '../../structures/textChannel.ts'
import { TypingStartPayload } from '../../types/gateway.ts' import type { TypingStartPayload } from '../../types/gateway.ts'
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
// TODO: Do we need to add uncached events here? // TODO: Do we need to add uncached events here?
export const typingStart: GatewayEventHandler = async ( export const typingStart: GatewayEventHandler = async (

View file

@ -1,6 +1,6 @@
import { User } from '../../structures/user.ts' import type { User } from '../../structures/user.ts'
import { UserPayload } from '../../types/user.ts' import type { UserPayload } from '../../types/user.ts'
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
export const userUpdate: GatewayEventHandler = async ( export const userUpdate: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -1,6 +1,6 @@
import { Guild } from '../../structures/guild.ts' import type { Guild } from '../../structures/guild.ts'
import { VoiceServerUpdatePayload } from '../../types/gateway.ts' import type { VoiceServerUpdatePayload } from '../../types/gateway.ts'
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
export const voiceServerUpdate: GatewayEventHandler = async ( export const voiceServerUpdate: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -1,8 +1,8 @@
import { Guild } from '../../structures/guild.ts' import type { Guild } from '../../structures/guild.ts'
import { VoiceState } from '../../structures/voiceState.ts' import type { VoiceState } from '../../structures/voiceState.ts'
import { MemberPayload } from '../../types/guild.ts' import type { MemberPayload } from '../../types/guild.ts'
import { VoiceStatePayload } from '../../types/voice.ts' import type { VoiceStatePayload } from '../../types/voice.ts'
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
export const voiceStateUpdate: GatewayEventHandler = async ( export const voiceStateUpdate: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -1,7 +1,7 @@
import { Gateway, GatewayEventHandler } from '../index.ts' import type { Gateway, GatewayEventHandler } from '../mod.ts'
import { Guild } from '../../structures/guild.ts' import type { Guild } from '../../structures/guild.ts'
import { WebhooksUpdatePayload } from '../../types/gateway.ts' import type { WebhooksUpdatePayload } from '../../types/gateway.ts'
import { GuildTextBasedChannel } from '../../structures/guildTextChannel.ts' import type { GuildTextBasedChannel } from '../../structures/guildTextChannel.ts'
export const webhooksUpdate: GatewayEventHandler = async ( export const webhooksUpdate: GatewayEventHandler = async (
gateway: Gateway, gateway: Gateway,

View file

@ -1,9 +1,5 @@
import { unzlib } from '../../deps.ts' import { unzlib } from '../../deps.ts'
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import {
DISCORD_GATEWAY_URL,
DISCORD_API_VERSION
} from '../consts/urlsAndVersions.ts'
import { GatewayResponse } from '../types/gatewayResponse.ts' import { GatewayResponse } from '../types/gatewayResponse.ts'
import { import {
GatewayOpcodes, GatewayOpcodes,
@ -12,13 +8,14 @@ import {
StatusUpdatePayload, StatusUpdatePayload,
GatewayEvents GatewayEvents
} from '../types/gateway.ts' } from '../types/gateway.ts'
import { gatewayHandlers } from './handlers/index.ts' import { gatewayHandlers } from './handlers/mod.ts'
import { GatewayCache } from '../managers/gatewayCache.ts' import { GatewayCache } from '../managers/gatewayCache.ts'
import { delay } from '../utils/delay.ts' import { delay } from '../utils/delay.ts'
import { VoiceChannel } from '../structures/guildVoiceChannel.ts' import type { VoiceChannel } from '../structures/guildVoiceChannel.ts'
import { Guild } from '../structures/guild.ts' import type { Guild } from '../structures/guild.ts'
import { HarmonyEventEmitter } from '../utils/events.ts' import { HarmonyEventEmitter } from '../utils/events.ts'
import { decodeText } from '../utils/encoding.ts' import { decodeText } from '../utils/encoding.ts'
import { Constants } from '../types/constants.ts'
export interface RequestMembersOptions { export interface RequestMembersOptions {
limit?: number limit?: number
@ -269,21 +266,6 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
if (typeof this.client.intents !== 'object') if (typeof this.client.intents !== 'object')
throw new Error('Intents not specified') throw new Error('Intents not specified')
if (this.client.fetchGatewayInfo === true) {
this.debug('Fetching /gateway/bot...')
const info = await this.client.rest.api.gateway.bot.get()
if (info.session_start_limit.remaining === 0)
throw new Error(
`Session Limit Reached. Retry After ${info.session_start_limit.reset_after}ms`
)
this.debug(`Recommended Shards: ${info.shards}`)
this.debug('=== Session Limit Info ===')
this.debug(
`Remaining: ${info.session_start_limit.remaining}/${info.session_start_limit.total}`
)
this.debug(`Reset After: ${info.session_start_limit.reset_after}ms`)
}
if (forceNewSession === undefined || !forceNewSession) { if (forceNewSession === undefined || !forceNewSession) {
const sessionIDCached = await this.cache.get( const sessionIDCached = await this.cache.get(
`session_id_${this.shards?.join('-') ?? '0'}` `session_id_${this.shards?.join('-') ?? '0'}`
@ -417,7 +399,7 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
this.debug('Initializing WebSocket...') this.debug('Initializing WebSocket...')
this.websocket = new WebSocket( this.websocket = new WebSocket(
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`${DISCORD_GATEWAY_URL}/?v=${DISCORD_API_VERSION}&encoding=json`, `${Constants.DISCORD_GATEWAY_URL}/?v=${Constants.DISCORD_API_VERSION}&encoding=json`,
[] []
) )
this.websocket.binaryType = 'arraybuffer' this.websocket.binaryType = 'arraybuffer'

3
src/interactions/mod.ts Normal file
View file

@ -0,0 +1,3 @@
export * from './slashClient.ts'
export * from './slashModule.ts'
export * from './slashCommand.ts'

View file

@ -1,4 +1,3 @@
import type { Guild } from '../structures/guild.ts'
import { import {
Interaction, Interaction,
InteractionApplicationCommandResolved InteractionApplicationCommandResolved
@ -7,358 +6,16 @@ import {
InteractionPayload, InteractionPayload,
InteractionResponsePayload, InteractionResponsePayload,
InteractionType, InteractionType,
SlashCommandChoice, SlashCommandOptionType
SlashCommandOption,
SlashCommandOptionType,
SlashCommandPartial,
SlashCommandPayload
} from '../types/slash.ts' } from '../types/slash.ts'
import { Collection } from '../utils/collection.ts' import type { Client } from '../client/mod.ts'
import type { Client } from './client.ts' import { RESTManager } from '../rest/mod.ts'
import { RESTManager } from './rest.ts'
import { SlashModule } from './slashModule.ts' import { SlashModule } from './slashModule.ts'
import { verify as edverify } from 'https://deno.land/x/ed25519@1.0.1/mod.ts' import { verify as edverify } from 'https://deno.land/x/ed25519@1.0.1/mod.ts'
import { User } from '../structures/user.ts' import { User } from '../structures/user.ts'
import { HarmonyEventEmitter } from '../utils/events.ts' import { HarmonyEventEmitter } from '../utils/events.ts'
import { encodeText, decodeText } from '../utils/encoding.ts' import { encodeText, decodeText } from '../utils/encoding.ts'
import { SlashCommandsManager } from './slashCommand.ts'
export class SlashCommand {
slash: SlashCommandsManager
id: string
applicationID: string
name: string
description: string
options: SlashCommandOption[]
guild?: Guild
_guild?: string
constructor(
manager: SlashCommandsManager,
data: SlashCommandPayload,
guild?: Guild
) {
this.slash = manager
this.id = data.id
this.applicationID = data.application_id
this.name = data.name
this.description = data.description
this.options = data.options ?? []
this.guild = guild
}
async delete(): Promise<void> {
await this.slash.delete(this.id, this._guild)
}
async edit(data: SlashCommandPartial): Promise<void> {
await this.slash.edit(this.id, data, this._guild)
}
/** Create a handler for this Slash Command */
handle(
func: SlashCommandHandlerCallback,
options?: { parent?: string; group?: string }
): SlashCommand {
this.slash.slash.handle({
name: this.name,
parent: options?.parent,
group: options?.group,
guild: this._guild,
handler: func
})
return this
}
}
export interface CreateOptions {
name: string
description?: string
options?: Array<SlashCommandOption | SlashOptionCallable>
choices?: Array<SlashCommandChoice | string>
}
function createSlashOption(
type: SlashCommandOptionType,
data: CreateOptions
): SlashCommandOption {
return {
name: data.name,
type,
description:
type === 0 || type === 1
? undefined
: data.description ?? 'No description.',
options: data.options?.map((e) =>
typeof e === 'function' ? e(SlashOption) : e
),
choices:
data.choices === undefined
? undefined
: data.choices.map((e) =>
typeof e === 'string' ? { name: e, value: e } : e
)
}
}
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class SlashOption {
static string(data: CreateOptions): SlashCommandOption {
return createSlashOption(SlashCommandOptionType.STRING, data)
}
static bool(data: CreateOptions): SlashCommandOption {
return createSlashOption(SlashCommandOptionType.BOOLEAN, data)
}
static subCommand(data: CreateOptions): SlashCommandOption {
return createSlashOption(SlashCommandOptionType.SUB_COMMAND, data)
}
static subCommandGroup(data: CreateOptions): SlashCommandOption {
return createSlashOption(SlashCommandOptionType.SUB_COMMAND_GROUP, data)
}
static role(data: CreateOptions): SlashCommandOption {
return createSlashOption(SlashCommandOptionType.ROLE, data)
}
static channel(data: CreateOptions): SlashCommandOption {
return createSlashOption(SlashCommandOptionType.CHANNEL, data)
}
static user(data: CreateOptions): SlashCommandOption {
return createSlashOption(SlashCommandOptionType.USER, data)
}
static number(data: CreateOptions): SlashCommandOption {
return createSlashOption(SlashCommandOptionType.INTEGER, data)
}
}
export type SlashOptionCallable = (o: typeof SlashOption) => SlashCommandOption
export type SlashBuilderOptionsData =
| Array<SlashCommandOption | SlashOptionCallable>
| {
[name: string]:
| {
description: string
type: SlashCommandOptionType
options?: SlashCommandOption[]
choices?: SlashCommandChoice[]
}
| SlashOptionCallable
}
function buildOptionsArray(
options: SlashBuilderOptionsData
): SlashCommandOption[] {
return Array.isArray(options)
? options.map((op) => (typeof op === 'function' ? op(SlashOption) : op))
: Object.entries(options).map((entry) =>
typeof entry[1] === 'function'
? entry[1](SlashOption)
: Object.assign(entry[1], { name: entry[0] })
)
}
/** Slash Command Builder */
export class SlashBuilder {
data: SlashCommandPartial
constructor(
name?: string,
description?: string,
options?: SlashBuilderOptionsData
) {
this.data = {
name: name ?? '',
description: description ?? 'No description.',
options: options === undefined ? [] : buildOptionsArray(options)
}
}
name(name: string): SlashBuilder {
this.data.name = name
return this
}
description(desc: string): SlashBuilder {
this.data.description = desc
return this
}
option(option: SlashOptionCallable | SlashCommandOption): SlashBuilder {
if (this.data.options === undefined) this.data.options = []
this.data.options.push(
typeof option === 'function' ? option(SlashOption) : option
)
return this
}
options(options: SlashBuilderOptionsData): SlashBuilder {
this.data.options = buildOptionsArray(options)
return this
}
export(): SlashCommandPartial {
if (this.data.name === '')
throw new Error('Name was not provided in Slash Builder')
return this.data
}
}
/** Manages Slash Commands, allows fetching/modifying/deleting/creating Slash Commands. */
export class SlashCommandsManager {
slash: SlashClient
rest: RESTManager
constructor(client: SlashClient) {
this.slash = client
this.rest = client.rest
}
/** Get all Global Slash Commands */
async all(): Promise<Collection<string, SlashCommand>> {
const col = new Collection<string, SlashCommand>()
const res = (await this.rest.api.applications[
this.slash.getID()
].commands.get()) as SlashCommandPayload[]
if (!Array.isArray(res)) return col
for (const raw of res) {
const cmd = new SlashCommand(this, raw)
col.set(raw.id, cmd)
}
return col
}
/** Get a Guild's Slash Commands */
async guild(
guild: Guild | string
): Promise<Collection<string, SlashCommand>> {
const col = new Collection<string, SlashCommand>()
const res = (await this.rest.api.applications[this.slash.getID()].guilds[
typeof guild === 'string' ? guild : guild.id
].commands.get()) as SlashCommandPayload[]
if (!Array.isArray(res)) return col
const _guild =
typeof guild === 'object'
? guild
: await this.slash.client?.guilds.get(guild)
for (const raw of res) {
const cmd = new SlashCommand(this, raw, _guild)
cmd._guild = typeof guild === 'string' ? guild : guild.id
col.set(raw.id, cmd)
}
return col
}
/** Create a Slash Command (global or Guild) */
async create(
data: SlashCommandPartial,
guild?: Guild | string
): Promise<SlashCommand> {
const route =
guild === undefined
? this.rest.api.applications[this.slash.getID()].commands
: this.rest.api.applications[this.slash.getID()].guilds[
typeof guild === 'string' ? guild : guild.id
].commands
const payload = await route.post(data)
const _guild =
typeof guild === 'object'
? guild
: guild === undefined
? undefined
: await this.slash.client?.guilds.get(guild)
const cmd = new SlashCommand(this, payload, _guild)
cmd._guild =
typeof guild === 'string' || guild === undefined ? guild : guild.id
return cmd
}
/** Edit a Slash Command (global or Guild) */
async edit(
id: string,
data: SlashCommandPartial,
guild?: Guild | string
): Promise<SlashCommandsManager> {
const route =
guild === undefined
? this.rest.api.applications[this.slash.getID()].commands[id]
: this.rest.api.applications[this.slash.getID()].guilds[
typeof guild === 'string' ? guild : guild.id
].commands[id]
await route.patch(data)
return this
}
/** Delete a Slash Command (global or Guild) */
async delete(
id: string,
guild?: Guild | string
): Promise<SlashCommandsManager> {
const route =
guild === undefined
? this.rest.api.applications[this.slash.getID()].commands[id]
: this.rest.api.applications[this.slash.getID()].guilds[
typeof guild === 'string' ? guild : guild.id
].commands[id]
await route.delete()
return this
}
/** Get a Slash Command (global or Guild) */
async get(id: string, guild?: Guild | string): Promise<SlashCommand> {
const route =
guild === undefined
? this.rest.api.applications[this.slash.getID()].commands[id]
: this.rest.api.applications[this.slash.getID()].guilds[
typeof guild === 'string' ? guild : guild.id
].commands[id]
const data = await route.get()
const _guild =
typeof guild === 'object'
? guild
: guild === undefined
? undefined
: await this.slash.client?.guilds.get(guild)
return new SlashCommand(this, data, _guild)
}
/** Bulk Edit Global or Guild Slash Commands */
async bulkEdit(
cmds: Array<SlashCommandPartial | SlashCommandPayload>,
guild?: Guild | string
): Promise<SlashCommandsManager> {
const route =
guild === undefined
? this.rest.api.applications[this.slash.getID()].commands
: this.rest.api.applications[this.slash.getID()].guilds[
typeof guild === 'string' ? guild : guild.id
].commands
await route.put(cmds)
return this
}
}
export type SlashCommandHandlerCallback = (interaction: Interaction) => unknown export type SlashCommandHandlerCallback = (interaction: Interaction) => unknown
export interface SlashCommandHandler { export interface SlashCommandHandler {
@ -455,8 +112,38 @@ export class SlashClient extends HarmonyEventEmitter<SlashClientEvents> {
} }
/** Adds a new Slash Command Handler */ /** Adds a new Slash Command Handler */
handle(handler: SlashCommandHandler): SlashClient { handle(
this.handlers.push(handler) cmd: string | SlashCommandHandler,
handler?: SlashCommandHandlerCallback
): SlashClient {
const handle = {
name: typeof cmd === 'string' ? cmd : cmd.name,
...(handler !== undefined ? { handler } : {}),
...(typeof cmd === 'string' ? {} : cmd)
}
if (handle.handler === undefined)
throw new Error('Invalid usage. Handler function not provided')
if (
typeof handle.name === 'string' &&
handle.name.includes(' ') &&
handle.parent === undefined &&
handle.group === undefined
) {
const parts = handle.name.split(/ +/).filter((e) => e !== '')
if (parts.length > 3 || parts.length < 1)
throw new Error('Invalid command name')
const root = parts.shift() as string
const group = parts.length === 2 ? parts.shift() : undefined
const sub = parts.shift()
handle.name = sub ?? root
handle.group = group
handle.parent = sub === undefined ? undefined : root
}
this.handlers.push(handle as any)
return this return this
} }
@ -523,7 +210,9 @@ export class SlashClient extends HarmonyEventEmitter<SlashClientEvents> {
) )
return return
const cmd = this._getCommand(interaction) const cmd =
this._getCommand(interaction) ??
this.getHandlers().find((e) => e.name === '*')
if (cmd?.group !== undefined) if (cmd?.group !== undefined)
interaction.data.options = interaction.data.options[0].options ?? [] interaction.data.options = interaction.data.options[0].options ?? []
if (cmd?.parent !== undefined) if (cmd?.parent !== undefined)
@ -564,7 +253,7 @@ export class SlashClient extends HarmonyEventEmitter<SlashClientEvents> {
respond: (options: { respond: (options: {
status?: number status?: number
headers?: Headers headers?: Headers
body?: string | Uint8Array | FormData body?: any
}) => Promise<void> }) => Promise<void>
}): Promise<false | Interaction> { }): Promise<false | Interaction> {
if (req.method.toLowerCase() !== 'post') return false if (req.method.toLowerCase() !== 'post') return false

View file

@ -0,0 +1,349 @@
import { RESTManager } from '../rest/manager.ts'
import type { Guild } from '../structures/guild.ts'
import {
SlashCommandChoice,
SlashCommandOption,
SlashCommandOptionType,
SlashCommandPartial,
SlashCommandPayload
} from '../types/slash.ts'
import { Collection } from '../utils/collection.ts'
import type { SlashClient, SlashCommandHandlerCallback } from './slashClient.ts'
export class SlashCommand {
slash: SlashCommandsManager
id: string
applicationID: string
name: string
description: string
options: SlashCommandOption[]
guild?: Guild
_guild?: string
constructor(
manager: SlashCommandsManager,
data: SlashCommandPayload,
guild?: Guild
) {
this.slash = manager
this.id = data.id
this.applicationID = data.application_id
this.name = data.name
this.description = data.description
this.options = data.options ?? []
this.guild = guild
}
async delete(): Promise<void> {
await this.slash.delete(this.id, this._guild)
}
async edit(data: SlashCommandPartial): Promise<void> {
await this.slash.edit(this.id, data, this._guild)
}
/** Create a handler for this Slash Command */
handle(
func: SlashCommandHandlerCallback,
options?: { parent?: string; group?: string }
): SlashCommand {
this.slash.slash.handle({
name: this.name,
parent: options?.parent,
group: options?.group,
guild: this._guild,
handler: func
})
return this
}
}
export interface CreateOptions {
name: string
description?: string
options?: Array<SlashCommandOption | SlashOptionCallable>
choices?: Array<SlashCommandChoice | string>
}
function createSlashOption(
type: SlashCommandOptionType,
data: CreateOptions
): SlashCommandOption {
return {
name: data.name,
type,
description:
type === 0 || type === 1
? undefined
: data.description ?? 'No description.',
options: data.options?.map((e) =>
typeof e === 'function' ? e(SlashOption) : e
),
choices:
data.choices === undefined
? undefined
: data.choices.map((e) =>
typeof e === 'string' ? { name: e, value: e } : e
)
}
}
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class SlashOption {
static string(data: CreateOptions): SlashCommandOption {
return createSlashOption(SlashCommandOptionType.STRING, data)
}
static bool(data: CreateOptions): SlashCommandOption {
return createSlashOption(SlashCommandOptionType.BOOLEAN, data)
}
static subCommand(data: CreateOptions): SlashCommandOption {
return createSlashOption(SlashCommandOptionType.SUB_COMMAND, data)
}
static subCommandGroup(data: CreateOptions): SlashCommandOption {
return createSlashOption(SlashCommandOptionType.SUB_COMMAND_GROUP, data)
}
static role(data: CreateOptions): SlashCommandOption {
return createSlashOption(SlashCommandOptionType.ROLE, data)
}
static channel(data: CreateOptions): SlashCommandOption {
return createSlashOption(SlashCommandOptionType.CHANNEL, data)
}
static user(data: CreateOptions): SlashCommandOption {
return createSlashOption(SlashCommandOptionType.USER, data)
}
static number(data: CreateOptions): SlashCommandOption {
return createSlashOption(SlashCommandOptionType.INTEGER, data)
}
}
export type SlashOptionCallable = (o: typeof SlashOption) => SlashCommandOption
export type SlashBuilderOptionsData =
| Array<SlashCommandOption | SlashOptionCallable>
| {
[name: string]:
| {
description: string
type: SlashCommandOptionType
options?: SlashCommandOption[]
choices?: SlashCommandChoice[]
}
| SlashOptionCallable
}
function buildOptionsArray(
options: SlashBuilderOptionsData
): SlashCommandOption[] {
return Array.isArray(options)
? options.map((op) => (typeof op === 'function' ? op(SlashOption) : op))
: Object.entries(options).map((entry) =>
typeof entry[1] === 'function'
? entry[1](SlashOption)
: Object.assign(entry[1], { name: entry[0] })
)
}
/** Slash Command Builder */
export class SlashBuilder {
data: SlashCommandPartial
constructor(
name?: string,
description?: string,
options?: SlashBuilderOptionsData
) {
this.data = {
name: name ?? '',
description: description ?? 'No description.',
options: options === undefined ? [] : buildOptionsArray(options)
}
}
name(name: string): SlashBuilder {
this.data.name = name
return this
}
description(desc: string): SlashBuilder {
this.data.description = desc
return this
}
option(option: SlashOptionCallable | SlashCommandOption): SlashBuilder {
if (this.data.options === undefined) this.data.options = []
this.data.options.push(
typeof option === 'function' ? option(SlashOption) : option
)
return this
}
options(options: SlashBuilderOptionsData): SlashBuilder {
this.data.options = buildOptionsArray(options)
return this
}
export(): SlashCommandPartial {
if (this.data.name === '')
throw new Error('Name was not provided in Slash Builder')
return this.data
}
}
/** Manages Slash Commands, allows fetching/modifying/deleting/creating Slash Commands. */
export class SlashCommandsManager {
slash: SlashClient
rest: RESTManager
constructor(client: SlashClient) {
this.slash = client
this.rest = client.rest
}
/** Get all Global Slash Commands */
async all(): Promise<Collection<string, SlashCommand>> {
const col = new Collection<string, SlashCommand>()
const res = (await this.rest.api.applications[
this.slash.getID()
].commands.get()) as SlashCommandPayload[]
if (!Array.isArray(res)) return col
for (const raw of res) {
const cmd = new SlashCommand(this, raw)
col.set(raw.id, cmd)
}
return col
}
/** Get a Guild's Slash Commands */
async guild(
guild: Guild | string
): Promise<Collection<string, SlashCommand>> {
const col = new Collection<string, SlashCommand>()
const res = (await this.rest.api.applications[this.slash.getID()].guilds[
typeof guild === 'string' ? guild : guild.id
].commands.get()) as SlashCommandPayload[]
if (!Array.isArray(res)) return col
const _guild =
typeof guild === 'object'
? guild
: await this.slash.client?.guilds.get(guild)
for (const raw of res) {
const cmd = new SlashCommand(this, raw, _guild)
cmd._guild = typeof guild === 'string' ? guild : guild.id
col.set(raw.id, cmd)
}
return col
}
/** Create a Slash Command (global or Guild) */
async create(
data: SlashCommandPartial,
guild?: Guild | string
): Promise<SlashCommand> {
const route =
guild === undefined
? this.rest.api.applications[this.slash.getID()].commands
: this.rest.api.applications[this.slash.getID()].guilds[
typeof guild === 'string' ? guild : guild.id
].commands
const payload = await route.post(data)
const _guild =
typeof guild === 'object'
? guild
: guild === undefined
? undefined
: await this.slash.client?.guilds.get(guild)
const cmd = new SlashCommand(this, payload, _guild)
cmd._guild =
typeof guild === 'string' || guild === undefined ? guild : guild.id
return cmd
}
/** Edit a Slash Command (global or Guild) */
async edit(
id: string,
data: SlashCommandPartial,
guild?: Guild | string
): Promise<SlashCommandsManager> {
const route =
guild === undefined
? this.rest.api.applications[this.slash.getID()].commands[id]
: this.rest.api.applications[this.slash.getID()].guilds[
typeof guild === 'string' ? guild : guild.id
].commands[id]
await route.patch(data)
return this
}
/** Delete a Slash Command (global or Guild) */
async delete(
id: string,
guild?: Guild | string
): Promise<SlashCommandsManager> {
const route =
guild === undefined
? this.rest.api.applications[this.slash.getID()].commands[id]
: this.rest.api.applications[this.slash.getID()].guilds[
typeof guild === 'string' ? guild : guild.id
].commands[id]
await route.delete()
return this
}
/** Get a Slash Command (global or Guild) */
async get(id: string, guild?: Guild | string): Promise<SlashCommand> {
const route =
guild === undefined
? this.rest.api.applications[this.slash.getID()].commands[id]
: this.rest.api.applications[this.slash.getID()].guilds[
typeof guild === 'string' ? guild : guild.id
].commands[id]
const data = await route.get()
const _guild =
typeof guild === 'object'
? guild
: guild === undefined
? undefined
: await this.slash.client?.guilds.get(guild)
return new SlashCommand(this, data, _guild)
}
/** Bulk Edit Global or Guild Slash Commands */
async bulkEdit(
cmds: Array<SlashCommandPartial | SlashCommandPayload>,
guild?: Guild | string
): Promise<SlashCommandsManager> {
const route =
guild === undefined
? this.rest.api.applications[this.slash.getID()].commands
: this.rest.api.applications[this.slash.getID()].guilds[
typeof guild === 'string' ? guild : guild.id
].commands
await route.put(cmds)
return this
}
}

View file

@ -1,4 +1,4 @@
import { SlashCommandHandler } from './slashClient.ts' import type { SlashCommandHandler } from './slashClient.ts'
export class SlashModule { export class SlashModule {
name: string = '' name: string = ''

View file

@ -1,4 +1,4 @@
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import { Collection } from '../utils/collection.ts' import { Collection } from '../utils/collection.ts'
/** /**
@ -63,7 +63,9 @@ export class BaseManager<T, T2> {
async *[Symbol.asyncIterator](): AsyncIterableIterator<T2> { async *[Symbol.asyncIterator](): AsyncIterableIterator<T2> {
const arr = (await this.array()) ?? [] const arr = (await this.array()) ?? []
const { readable, writable } = new TransformStream() const { readable, writable } = new TransformStream()
arr.forEach((el) => writable.getWriter().write(el)) const writer = writable.getWriter()
arr.forEach((el: unknown) => writer.write(el))
writer.close()
yield* readable yield* readable
} }

View file

@ -1,4 +1,4 @@
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import { Collection } from '../utils/collection.ts' import { Collection } from '../utils/collection.ts'
import { BaseManager } from './base.ts' import { BaseManager } from './base.ts'
@ -43,7 +43,9 @@ export class BaseChildManager<T, T2> {
async *[Symbol.asyncIterator](): AsyncIterableIterator<T2> { async *[Symbol.asyncIterator](): AsyncIterableIterator<T2> {
const arr = (await this.array()) ?? [] const arr = (await this.array()) ?? []
const { readable, writable } = new TransformStream() const { readable, writable } = new TransformStream()
arr.forEach((el: unknown) => writable.getWriter().write(el)) const writer = writable.getWriter()
arr.forEach((el: unknown) => writer.write(el))
writer.close()
yield* readable yield* readable
} }

View file

@ -1,15 +1,15 @@
import { Client } from '../models/client.ts' import { Client } from '../client/mod.ts'
import { Channel } from '../structures/channel.ts' import { Channel } from '../structures/channel.ts'
import { Embed } from '../structures/embed.ts' import { Embed } from '../structures/embed.ts'
import { Message } from '../structures/message.ts' import { Message } from '../structures/message.ts'
import { TextChannel } from '../structures/textChannel.ts' import type { TextChannel } from '../structures/textChannel.ts'
import { import type {
ChannelPayload, ChannelPayload,
GuildChannelPayload, GuildChannelPayload,
MessageOptions MessageOptions
} from '../types/channel.ts' } from '../types/channel.ts'
import { CHANNEL } from '../types/endpoint.ts' import { CHANNEL } from '../types/endpoint.ts'
import getChannelByType from '../utils/getChannelByType.ts' import getChannelByType from '../utils/channel.ts'
import { BaseManager } from './base.ts' import { BaseManager } from './base.ts'
export type AllMessageOptions = MessageOptions | Embed export type AllMessageOptions = MessageOptions | Embed
@ -121,6 +121,10 @@ export class ChannelsManager extends BaseManager<ChannelPayload, Channel> {
: undefined : undefined
} }
if (payload.content === undefined && payload.embed === undefined) {
payload.content = ''
}
const resp = await this.client.rest.api.channels[channelID].messages.post( const resp = await this.client.rest.api.channels[channelID].messages.post(
payload payload
) )

View file

@ -1,6 +1,6 @@
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import { Emoji } from '../structures/emoji.ts' import { Emoji } from '../structures/emoji.ts'
import { EmojiPayload } from '../types/emoji.ts' import type { EmojiPayload } from '../types/emoji.ts'
import { GUILD_EMOJI } from '../types/endpoint.ts' import { GUILD_EMOJI } from '../types/endpoint.ts'
import { BaseManager } from './base.ts' import { BaseManager } from './base.ts'

View file

@ -1,4 +1,4 @@
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
/** /**
* Cache Manager used for Caching values related to Gateway connection * Cache Manager used for Caching values related to Gateway connection

View file

@ -1,9 +1,9 @@
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import { BaseChildManager } from './baseChild.ts' import { BaseChildManager } from './baseChild.ts'
import { VoiceStatePayload } from '../types/voice.ts' import type { VoiceStatePayload } from '../types/voice.ts'
import { VoiceState } from '../structures/voiceState.ts' import { VoiceState } from '../structures/voiceState.ts'
import { GuildVoiceStatesManager } from './guildVoiceStates.ts' import { GuildVoiceStatesManager } from './guildVoiceStates.ts'
import { VoiceChannel } from '../structures/guildVoiceChannel.ts' import type { VoiceChannel } from '../structures/guildVoiceChannel.ts'
export class GuildChannelVoiceStatesManager extends BaseChildManager< export class GuildChannelVoiceStatesManager extends BaseChildManager<
VoiceStatePayload, VoiceStatePayload,

View file

@ -1,16 +1,16 @@
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import { Channel } from '../structures/channel.ts' import { Channel } from '../structures/channel.ts'
import { Guild } from '../structures/guild.ts' import { Guild } from '../structures/guild.ts'
import { CategoryChannel } from '../structures/guildCategoryChannel.ts' import type { CategoryChannel } from '../structures/guildCategoryChannel.ts'
import { import {
ChannelTypes, ChannelTypes,
GuildChannelPayload, GuildChannelPayload,
OverwritePayload OverwritePayload
} from '../types/channel.ts' } from '../types/channel.ts'
import { GuildChannels, GuildChannelPayloads } from '../types/guild.ts' import type { GuildChannels, GuildChannelPayloads } from '../types/guild.ts'
import { CHANNEL, GUILD_CHANNELS } from '../types/endpoint.ts' import { CHANNEL, GUILD_CHANNELS } from '../types/endpoint.ts'
import { BaseChildManager } from './baseChild.ts' import { BaseChildManager } from './baseChild.ts'
import { ChannelsManager } from './channels.ts' import type { ChannelsManager } from './channels.ts'
export interface CreateChannelOptions { export interface CreateChannelOptions {
name: string name: string

View file

@ -1,11 +1,11 @@
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import { Emoji } from '../structures/emoji.ts' import { Emoji } from '../structures/emoji.ts'
import { Guild } from '../structures/guild.ts' import type { Guild } from '../structures/guild.ts'
import { Role } from '../structures/role.ts' import { Role } from '../structures/role.ts'
import { EmojiPayload } from '../types/emoji.ts' import type { EmojiPayload } from '../types/emoji.ts'
import { CHANNEL, GUILD_EMOJI, GUILD_EMOJIS } from '../types/endpoint.ts' import { CHANNEL, GUILD_EMOJI, GUILD_EMOJIS } from '../types/endpoint.ts'
import { BaseChildManager } from './baseChild.ts' import { BaseChildManager } from './baseChild.ts'
import { EmojisManager } from './emojis.ts' import type { EmojisManager } from './emojis.ts'
import { fetchAuto } from '../../deps.ts' import { fetchAuto } from '../../deps.ts'
export class GuildEmojisManager extends BaseChildManager<EmojiPayload, Emoji> { export class GuildEmojisManager extends BaseChildManager<EmojiPayload, Emoji> {

View file

@ -1,9 +1,9 @@
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import { Guild } from '../structures/guild.ts' import type { Guild } from '../structures/guild.ts'
import { VoiceChannel } from '../structures/guildVoiceChannel.ts' import type { VoiceChannel } from '../structures/guildVoiceChannel.ts'
import { User } from '../structures/user.ts' import type { User } from '../structures/user.ts'
import { VoiceState } from '../structures/voiceState.ts' import { VoiceState } from '../structures/voiceState.ts'
import { VoiceStatePayload } from '../types/voice.ts' import type { VoiceStatePayload } from '../types/voice.ts'
import { BaseManager } from './base.ts' import { BaseManager } from './base.ts'
export class GuildVoiceStatesManager extends BaseManager< export class GuildVoiceStatesManager extends BaseManager<

View file

@ -1,10 +1,10 @@
import { fetchAuto } from '../../deps.ts' import { fetchAuto } from '../../deps.ts'
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import { Guild } from '../structures/guild.ts' import { Guild } from '../structures/guild.ts'
import { Template } from '../structures/template.ts' import type { Template } from '../structures/template.ts'
import { Role } from '../structures/role.ts' import { Role } from '../structures/role.ts'
import { GUILD, GUILDS, GUILD_PREVIEW } from '../types/endpoint.ts' import { GUILD, GUILDS, GUILD_PREVIEW } from '../types/endpoint.ts'
import { import type {
GuildPayload, GuildPayload,
MemberPayload, MemberPayload,
GuildCreateRolePayload, GuildCreateRolePayload,

View file

@ -1,9 +1,9 @@
import { GuildTextChannel, User } from '../../mod.ts' import type { GuildTextChannel, User } from '../../mod.ts'
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import { Guild } from '../structures/guild.ts' import type { Guild } from '../structures/guild.ts'
import { Invite } from '../structures/invite.ts' import { Invite } from '../structures/invite.ts'
import { CHANNEL_INVITES, GUILD_INVITES, INVITE } from '../types/endpoint.ts' import { CHANNEL_INVITES, GUILD_INVITES, INVITE } from '../types/endpoint.ts'
import { InvitePayload } from '../types/invite.ts' import type { InvitePayload } from '../types/invite.ts'
import { BaseManager } from './base.ts' import { BaseManager } from './base.ts'
export enum InviteTargetUserType { export enum InviteTargetUserType {

View file

@ -1,10 +1,10 @@
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import { BaseChildManager } from './baseChild.ts' import { BaseChildManager } from './baseChild.ts'
import { RolePayload } from '../types/role.ts' import type { RolePayload } from '../types/role.ts'
import { Role } from '../structures/role.ts' import { Role } from '../structures/role.ts'
import { Member } from '../structures/member.ts' import type { Member } from '../structures/member.ts'
import { RolesManager } from './roles.ts' import type { RolesManager } from './roles.ts'
import { MemberPayload } from '../types/guild.ts' import type { MemberPayload } from '../types/guild.ts'
import { GUILD_MEMBER_ROLE } from '../types/endpoint.ts' import { GUILD_MEMBER_ROLE } from '../types/endpoint.ts'
export class MemberRolesManager extends BaseChildManager<RolePayload, Role> { export class MemberRolesManager extends BaseChildManager<RolePayload, Role> {

View file

@ -1,9 +1,9 @@
import { User } from '../structures/user.ts' import { User } from '../structures/user.ts'
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import { Guild } from '../structures/guild.ts' import type { Guild } from '../structures/guild.ts'
import { Member } from '../structures/member.ts' import { Member } from '../structures/member.ts'
import { GUILD_MEMBER } from '../types/endpoint.ts' import { GUILD_MEMBER } from '../types/endpoint.ts'
import { MemberPayload } from '../types/guild.ts' import type { MemberPayload } from '../types/guild.ts'
import { BaseManager } from './base.ts' import { BaseManager } from './base.ts'
import { Permissions } from '../utils/permissions.ts' import { Permissions } from '../utils/permissions.ts'

View file

@ -1,10 +1,9 @@
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import { Emoji } from '../structures/emoji.ts' import { Emoji } from '../structures/emoji.ts'
import { Guild } from '../structures/guild.ts' import type { Message } from '../structures/message.ts'
import { Message } from '../structures/message.ts'
import { MessageReaction } from '../structures/messageReaction.ts' import { MessageReaction } from '../structures/messageReaction.ts'
import { User } from '../structures/user.ts' import type { User } from '../structures/user.ts'
import { Reaction } from '../types/channel.ts' import type { Reaction } from '../types/channel.ts'
import { import {
MESSAGE_REACTION, MESSAGE_REACTION,
MESSAGE_REACTIONS, MESSAGE_REACTIONS,
@ -19,7 +18,7 @@ export class MessageReactionsManager extends BaseManager<
message: Message message: Message
constructor(client: Client, message: Message) { constructor(client: Client, message: Message) {
super(client, `reactions:${message.id}`, Guild) super(client, `reactions:${message.id}`, MessageReaction)
this.message = message this.message = message
} }

View file

@ -1,8 +1,8 @@
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import { Message } from '../structures/message.ts' import { Message } from '../structures/message.ts'
import { TextChannel } from '../structures/textChannel.ts' import type { TextChannel } from '../structures/textChannel.ts'
import { User } from '../structures/user.ts' import { User } from '../structures/user.ts'
import { MessagePayload } from '../types/channel.ts' import type { MessagePayload } from '../types/channel.ts'
import { CHANNEL_MESSAGE } from '../types/endpoint.ts' import { CHANNEL_MESSAGE } from '../types/endpoint.ts'
import { BaseManager } from './base.ts' import { BaseManager } from './base.ts'

View file

@ -1,8 +1,8 @@
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import { Guild } from '../structures/guild.ts' import type { Guild } from '../structures/guild.ts'
import { Presence } from '../structures/presence.ts' import { Presence } from '../structures/presence.ts'
import { User } from '../structures/user.ts' import { User } from '../structures/user.ts'
import { PresenceUpdatePayload } from '../types/gateway.ts' import type { PresenceUpdatePayload } from '../types/gateway.ts'
import { BaseManager } from './base.ts' import { BaseManager } from './base.ts'
export class GuildPresencesManager extends BaseManager< export class GuildPresencesManager extends BaseManager<

View file

@ -1,6 +1,6 @@
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import { MessageReaction } from '../structures/messageReaction.ts' import type { MessageReaction } from '../structures/messageReaction.ts'
import { User } from '../structures/user.ts' import type { User } from '../structures/user.ts'
import { UsersManager } from './users.ts' import { UsersManager } from './users.ts'
export class ReactionUsersManager extends UsersManager { export class ReactionUsersManager extends UsersManager {

View file

@ -1,9 +1,9 @@
import { Permissions } from '../../mod.ts' import { Permissions } from '../../mod.ts'
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import { Guild } from '../structures/guild.ts' import type { Guild } from '../structures/guild.ts'
import { Role } from '../structures/role.ts' import { Role } from '../structures/role.ts'
import { GUILD_ROLE, GUILD_ROLES } from '../types/endpoint.ts' import { GUILD_ROLE, GUILD_ROLES } from '../types/endpoint.ts'
import { RoleModifyPayload, RolePayload } from '../types/role.ts' import type { RoleModifyPayload, RolePayload } from '../types/role.ts'
import { BaseManager } from './base.ts' import { BaseManager } from './base.ts'
export interface CreateGuildRoleOptions { export interface CreateGuildRoleOptions {

View file

@ -1,7 +1,7 @@
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import { User } from '../structures/user.ts' import { User } from '../structures/user.ts'
import { USER } from '../types/endpoint.ts' import { USER } from '../types/endpoint.ts'
import { UserPayload } from '../types/user.ts' import type { UserPayload } from '../types/user.ts'
import { BaseManager } from './base.ts' import { BaseManager } from './base.ts'
export class UsersManager extends BaseManager<UserPayload, User> { export class UsersManager extends BaseManager<UserPayload, User> {

View file

@ -1,676 +0,0 @@
import * as baseEndpoints from '../consts/urlsAndVersions.ts'
import { Embed } from '../structures/embed.ts'
import { MessageAttachment } from '../structures/message.ts'
import { Collection } from '../utils/collection.ts'
import { Client } from './client.ts'
import { simplifyAPIError } from '../utils/err_fmt.ts'
export type RequestMethods =
| 'get'
| 'post'
| 'put'
| 'patch'
| 'head'
| 'delete'
export enum HttpResponseCode {
Ok = 200,
Created = 201,
NoContent = 204,
NotModified = 304,
BadRequest = 400,
Unauthorized = 401,
Forbidden = 403,
NotFound = 404,
MethodNotAllowed = 405,
TooManyRequests = 429,
GatewayUnavailable = 502
}
export interface RequestHeaders {
[name: string]: string
}
export interface DiscordAPIErrorPayload {
url: string
status: number
method: string
code?: number
message?: string
errors: object
requestData: { [key: string]: any }
}
export class DiscordAPIError extends Error {
name = 'DiscordAPIError'
error?: DiscordAPIErrorPayload
constructor(error: string | DiscordAPIErrorPayload) {
super()
const fmt = Object.entries(
typeof error === 'object' ? simplifyAPIError(error.errors) : {}
)
this.message =
typeof error === 'string'
? `${error} `
: `\n${error.method} ${error.url.slice(7)} returned ${error.status}\n(${
error.code ?? 'unknown'
}) ${error.message}${
fmt.length === 0
? ''
: `\n${fmt
.map(
(e) =>
` at ${e[0]}:\n${e[1]
.map((e) => ` - ${e}`)
.join('\n')}`
)
.join('\n')}\n`
}`
if (typeof error === 'object') this.error = error
}
}
export interface QueuedItem {
bucket?: string | null
url: string
onComplete: () => Promise<
| {
rateLimited: any
bucket?: string | null
before: boolean
}
| undefined
>
}
export interface RateLimit {
url: string
resetAt: number
bucket: string | null
}
const METHODS = ['get', 'post', 'patch', 'put', 'delete', 'head']
export type MethodFunction = (
body?: unknown,
maxRetries?: number,
bucket?: string | null,
rawResponse?: boolean
) => Promise<any>
export interface APIMap extends MethodFunction {
/** Make a GET request to current route */
get: APIMap
/** Make a POST request to current route */
post: APIMap
/** Make a PATCH request to current route */
patch: APIMap
/** Make a PUT request to current route */
put: APIMap
/** Make a DELETE request to current route */
delete: APIMap
/** Make a HEAD request to current route */
head: APIMap
/** Continue building API Route */
[name: string]: APIMap
}
/** API Route builder function */
export const builder = (rest: RESTManager, acum = '/'): APIMap => {
const routes = {}
const proxy = new Proxy(routes, {
get: (_, p, __) => {
if (p === 'toString') return () => acum
if (METHODS.includes(String(p))) {
const method = ((rest as unknown) as {
[name: string]: MethodFunction
})[String(p)]
return async (...args: any[]) =>
await method.bind(rest)(
`${baseEndpoints.DISCORD_API_URL}/v${rest.version}${acum.substring(
0,
acum.length - 1
)}`,
...args
)
}
return builder(rest, acum + String(p) + '/')
}
})
return (proxy as unknown) as APIMap
}
export interface RESTOptions {
/** Token to use for authorization */
token?: string | (() => string | undefined)
/** Headers to patch with if any */
headers?: { [name: string]: string | undefined }
/** Whether to use Canary instance of Discord API or not */
canary?: boolean
/** Discord REST API version to use */
version?: 6 | 7 | 8
/** Token Type to use for Authorization */
tokenType?: TokenType
/** User Agent to use (Header) */
userAgent?: string
/** Optional Harmony client */
client?: Client
}
/** Token Type for REST API. */
export enum TokenType {
/** Token type for Bot User */
Bot = 'Bot',
/** Token Type for OAuth2 */
Bearer = 'Bearer',
/** No Token Type. Can be used for User accounts. */
None = ''
}
/** An easier to use interface for interacting with Discord REST API. */
export class RESTManager {
queues: { [key: string]: QueuedItem[] } = {}
rateLimits = new Collection<string, RateLimit>()
/** Whether we are globally ratelimited or not */
globalRateLimit: boolean = false
/** Whether requests are being processed or not */
processing: boolean = false
/** API Version being used by REST Manager */
version: number = 8
/**
* API Map - easy to use way for interacting with Discord API.
*
* Examples:
* * ```ts
* rest.api.users['123'].get().then(userPayload => doSomething)
* ```
* * ```ts
* rest.api.guilds['123'].channels.post({ name: 'my-channel', type: 0 }).then(channelPayload => {})
* ```
*/
api: APIMap
/** Token being used for Authorization */
token?: string | (() => string | undefined)
/** Token Type of the Token if any */
tokenType: TokenType = TokenType.Bot
/** Headers object which patch the current ones */
headers: any = {}
/** Optional custom User Agent (header) */
userAgent?: string
/** Whether REST Manager is using Canary API */
canary?: boolean
/** Optional Harmony Client object */
client?: Client
constructor(options?: RESTOptions) {
this.api = builder(this)
if (options?.token !== undefined) this.token = options.token
if (options?.version !== undefined) this.version = options.version
if (options?.headers !== undefined) this.headers = options.headers
if (options?.tokenType !== undefined) this.tokenType = options.tokenType
if (options?.userAgent !== undefined) this.userAgent = options.userAgent
if (options?.canary !== undefined) this.canary = options.canary
if (options?.client !== undefined) this.client = options.client
this.handleRateLimits()
}
/** Checks the queues of buckets, if empty, delete entry */
private checkQueues(): void {
Object.entries(this.queues).forEach(([key, value]) => {
if (value.length === 0) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete this.queues[key]
}
})
}
/** Adds a Request to Queue */
private queue(request: QueuedItem): void {
const route = request.url.substring(
Number(baseEndpoints.DISCORD_API_URL.length) + 1
)
const parts = route.split('/')
parts.shift()
const [id] = parts
if (this.queues[id] !== undefined) {
this.queues[id].push(request)
} else {
this.queues[id] = [request]
}
}
private async processQueue(): Promise<void> {
if (Object.keys(this.queues).length !== 0 && !this.globalRateLimit) {
await Promise.allSettled(
Object.values(this.queues).map(async (pathQueue) => {
const request = pathQueue.shift()
if (request === undefined) return
const rateLimitedURLResetIn = await this.isRateLimited(request.url)
if (typeof request.bucket === 'string') {
const rateLimitResetIn = await this.isRateLimited(request.bucket)
if (rateLimitResetIn !== false) {
this.queue(request)
} else {
const result = await request.onComplete()
if (result?.rateLimited !== undefined) {
this.queue({
...request,
bucket: result.bucket ?? request.bucket
})
}
}
} else {
if (rateLimitedURLResetIn !== false) {
this.queue(request)
} else {
const result = await request.onComplete()
if (result?.rateLimited !== undefined) {
this.queue({
...request,
bucket: result.bucket ?? request.bucket
})
}
}
}
})
)
}
if (Object.keys(this.queues).length !== 0) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.processQueue()
this.checkQueues()
} else this.processing = false
}
private prepare(body: any, method: RequestMethods): { [key: string]: any } {
const headers: RequestHeaders = {
'User-Agent':
this.userAgent ??
`DiscordBot (harmony, https://github.com/harmonyland/harmony)`
}
if (this.token !== undefined) {
const token = typeof this.token === 'string' ? this.token : this.token()
if (token !== undefined)
headers.Authorization = `${this.tokenType} ${token}`.trim()
}
if (method === 'get' || method === 'head' || method === 'delete')
body = undefined
if (body?.reason !== undefined) {
headers['X-Audit-Log-Reason'] = encodeURIComponent(body.reason)
}
let _files: undefined | MessageAttachment[]
if (body?.embed?.files !== undefined && Array.isArray(body?.embed?.files)) {
_files = body?.embed?.files
}
if (body?.embeds !== undefined && Array.isArray(body?.embeds)) {
const files1 = body?.embeds
.map((e: Embed) => e.files)
.filter((e: MessageAttachment[]) => e !== undefined)
for (const files of files1) {
for (const file of files) {
if (_files === undefined) _files = []
_files?.push(file)
}
}
}
if (
body?.file !== undefined ||
body?.files !== undefined ||
_files !== undefined
) {
const files: Array<{ blob: Blob; name: string }> = []
if (body?.file !== undefined) files.push(body.file)
if (body?.files !== undefined && Array.isArray(body.files)) {
for (const file of body.files) {
files.push(file)
}
}
if (_files !== undefined) {
for (const file of _files) {
files.push(file)
}
}
const form = new FormData()
files.forEach((file, index) =>
form.append(`file${index + 1}`, file.blob, file.name)
)
const json = JSON.stringify(body)
form.append('payload_json', json)
if (body === undefined) body = {}
body.file = form
} else if (
body !== undefined &&
!['get', 'delete'].includes(method.toLowerCase())
) {
headers['Content-Type'] = 'application/json'
}
if (this.headers !== undefined) Object.assign(headers, this.headers)
const data: { [name: string]: any } = {
headers,
body: body?.file ?? JSON.stringify(body),
method: method.toUpperCase()
}
return data
}
private isRateLimited(url: string): number | false {
const global = this.rateLimits.get('global')
const rateLimited = this.rateLimits.get(url)
const now = Date.now()
if (rateLimited !== undefined && now < rateLimited.resetAt) {
return rateLimited.resetAt - now
}
if (global !== undefined && now < global.resetAt) {
return global.resetAt - now
}
return false
}
/** Processes headers of the Response */
private processHeaders(
url: string,
headers: Headers
): string | null | undefined {
let rateLimited = false
const global = headers.get('x-ratelimit-global')
const bucket = headers.get('x-ratelimit-bucket')
const remaining = headers.get('x-ratelimit-remaining')
const resetAt = headers.get('x-ratelimit-reset')
const retryAfter = headers.get('retry-after')
if (remaining !== null && remaining === '0') {
rateLimited = true
this.rateLimits.set(url, {
url,
resetAt: Number(resetAt) * 1000,
bucket
})
if (bucket !== null) {
this.rateLimits.set(bucket, {
url,
resetAt: Number(resetAt) * 1000,
bucket
})
}
}
if (global !== null) {
const reset = Date.now() + Number(retryAfter)
this.globalRateLimit = true
rateLimited = true
this.rateLimits.set('global', {
url: 'global',
resetAt: reset,
bucket
})
if (bucket !== null) {
this.rateLimits.set(bucket, {
url: 'global',
resetAt: reset,
bucket
})
}
}
return rateLimited ? bucket : undefined
}
/** Handles status code of response and acts as required */
private handleStatusCode(
response: Response,
body: any,
data: { [key: string]: any },
reject: CallableFunction
): void {
const status = response.status
// We have hit ratelimit - this should not happen
if (status === HttpResponseCode.TooManyRequests) {
if (this.client !== undefined)
this.client.emit('rateLimit', {
method: data.method,
url: response.url,
body
})
reject(new Error('RateLimited'))
return
}
// It's a normal status code... just continue
if (
(status >= 200 && status < 400) ||
status === HttpResponseCode.NoContent
)
return
let text: undefined | string = Deno.inspect(
body.errors === undefined ? body : body.errors
)
if (text === 'undefined') text = undefined
if (status === HttpResponseCode.Unauthorized)
reject(
new DiscordAPIError(`Request was Unauthorized. Invalid Token.\n${text}`)
)
const _data = { ...data }
if (_data?.headers !== undefined) delete _data.headers
if (_data?.method !== undefined) delete _data.method
// At this point we know it is error
const error: DiscordAPIErrorPayload = {
url: new URL(response.url).pathname,
status,
method: data.method,
code: body?.code,
message: body?.message,
errors: body?.errors ?? {},
requestData: _data
}
if (
[
HttpResponseCode.BadRequest,
HttpResponseCode.NotFound,
HttpResponseCode.Forbidden,
HttpResponseCode.MethodNotAllowed
].includes(status)
) {
reject(new DiscordAPIError(error))
} else if (status === HttpResponseCode.GatewayUnavailable) {
reject(new DiscordAPIError(error))
} else reject(new DiscordAPIError('Request - Unknown Error'))
}
/**
* Makes a Request to Discord API.
* @param method HTTP Method to use
* @param url URL of the Request
* @param body Body to send with Request
* @param maxRetries Number of Max Retries to perform
* @param bucket BucketID of the Request
* @param rawResponse Whether to get Raw Response or body itself
*/
async make(
method: RequestMethods,
url: string,
body?: unknown,
maxRetries = 0,
bucket?: string | null,
rawResponse?: boolean
): Promise<any> {
return await new Promise((resolve, reject) => {
const onComplete = async (): Promise<undefined | any> => {
try {
const rateLimitResetIn = await this.isRateLimited(url)
if (rateLimitResetIn !== false) {
return {
rateLimited: rateLimitResetIn,
before: true,
bucket
}
}
const query =
method === 'get' && body !== undefined
? Object.entries(body as any)
.filter(([k, v]) => v !== undefined)
.map(
([key, value]) =>
`${encodeURIComponent(key)}=${encodeURIComponent(
value as any
)}`
)
.join('&')
: ''
let urlToUse =
method === 'get' && query !== '' ? `${url}?${query}` : url
// It doesn't start with HTTP, that means it's an incomplete URL
if (!urlToUse.startsWith('http')) {
if (!urlToUse.startsWith('/')) urlToUse = `/${urlToUse}`
urlToUse =
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
baseEndpoints.DISCORD_API_URL +
'/v' +
baseEndpoints.DISCORD_API_VERSION +
urlToUse
}
if (this.canary === true && urlToUse.startsWith('http')) {
const split = urlToUse.split('//')
urlToUse = split[0] + '//canary.' + split[1]
}
const requestData = this.prepare(body, method)
const response = await fetch(urlToUse, requestData)
const bucketFromHeaders = this.processHeaders(url, response.headers)
if (response.status === 204)
return resolve(
rawResponse === true ? { response, body: null } : undefined
)
const json: any = await response.json()
await this.handleStatusCode(response, json, requestData, reject)
if (
json.retry_after !== undefined ||
json.message === 'You are being rate limited.'
) {
if (maxRetries > 10) {
throw new Error('Max RateLimit Retries hit')
}
return {
rateLimited: json.retry_after,
before: false,
bucket: bucketFromHeaders
}
}
return resolve(rawResponse === true ? { response, body: json } : json)
} catch (error) {
return reject(error)
}
}
this.queue({
onComplete,
bucket,
url
})
if (!this.processing) {
this.processing = true
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.processQueue()
}
})
}
/** Checks for RateLimits times and deletes if already over */
private handleRateLimits(): void {
const now = Date.now()
this.rateLimits.forEach((value, key) => {
// Ratelimit has not ended
if (value.resetAt > now) return
// It ended, so delete
this.rateLimits.delete(key)
if (key === 'global') this.globalRateLimit = false
})
}
/** Makes a GET Request to API */
async get(
url: string,
body?: unknown,
maxRetries = 0,
bucket?: string | null,
rawResponse?: boolean
): Promise<any> {
return await this.make('get', url, body, maxRetries, bucket, rawResponse)
}
/** Makes a POST Request to API */
async post(
url: string,
body?: unknown,
maxRetries = 0,
bucket?: string | null,
rawResponse?: boolean
): Promise<any> {
return await this.make('post', url, body, maxRetries, bucket, rawResponse)
}
/** Makes a DELETE Request to API */
async delete(
url: string,
body?: unknown,
maxRetries = 0,
bucket?: string | null,
rawResponse?: boolean
): Promise<any> {
return await this.make('delete', url, body, maxRetries, bucket, rawResponse)
}
/** Makes a PATCH Request to API */
async patch(
url: string,
body?: unknown,
maxRetries = 0,
bucket?: string | null,
rawResponse?: boolean
): Promise<any> {
return await this.make('patch', url, body, maxRetries, bucket, rawResponse)
}
/** Makes a PUT Request to API */
async put(
url: string,
body?: unknown,
maxRetries = 0,
bucket?: string | null,
rawResponse?: boolean
): Promise<any> {
return await this.make('put', url, body, maxRetries, bucket, rawResponse)
}
}

239
src/rest/bucket.ts Normal file
View file

@ -0,0 +1,239 @@
// based on https://github.com/discordjs/discord.js/blob/master/src/rest/RequestHandler.js
// adapted to work with harmony rest manager
/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
import { delay } from '../utils/delay.ts'
import { DiscordAPIError, HTTPError } from './error.ts'
import type { RESTManager } from './manager.ts'
import { RequestQueue } from './queue.ts'
import { APIRequest } from './request.ts'
function parseResponse(res: Response, raw: boolean): any {
if (raw) return res
if (res.status === 204) return undefined
if (res.headers.get('content-type')?.startsWith('application/json') === true)
return res.json()
return res.arrayBuffer().then((e) => new Uint8Array(e))
}
function getAPIOffset(serverDate: number | string): number {
return new Date(serverDate).getTime() - Date.now()
}
function calculateReset(
reset: number | string,
serverDate: number | string
): number {
return new Date(Number(reset) * 1000).getTime() - getAPIOffset(serverDate)
}
let invalidCount = 0
let invalidCountResetTime: number | null = null
export class BucketHandler {
queue = new RequestQueue()
reset = -1
remaining = -1
limit = -1
constructor(public manager: RESTManager) {}
async push(request: APIRequest): Promise<any> {
await this.queue.wait()
try {
return await this.execute(request)
} finally {
this.queue.shift()
}
}
get globalLimited(): boolean {
return (
this.manager.globalRemaining <= 0 &&
Date.now() < Number(this.manager.globalReset)
)
}
get localLimited(): boolean {
return this.remaining <= 0 && Date.now() < this.reset
}
get limited(): boolean {
return this.globalLimited || this.localLimited
}
get inactive(): boolean {
return this.queue.remaining === 0 && !this.limited
}
async globalDelayFor(ms: number): Promise<void> {
return await new Promise((resolve) => {
this.manager.setTimeout(() => {
this.manager.globalDelay = null
resolve()
}, ms)
})
}
async execute(request: APIRequest): Promise<any> {
while (this.limited) {
const isGlobal = this.globalLimited
let limit, timeout, delayPromise
if (isGlobal) {
limit = this.manager.globalLimit
timeout =
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
Number(this.manager.globalReset) +
this.manager.restTimeOffset -
Date.now()
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (!this.manager.globalDelay) {
this.manager.globalDelay = this.globalDelayFor(timeout) as any
}
delayPromise = this.manager.globalDelay
} else {
limit = this.limit
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
timeout = this.reset + this.manager.restTimeOffset - Date.now()
delayPromise = delay(timeout)
}
this.manager.client?.emit('rateLimit', {
timeout,
limit,
method: request.method,
path: request.path,
global: isGlobal
})
await delayPromise
}
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (!this.manager.globalReset || this.manager.globalReset < Date.now()) {
this.manager.globalReset = Date.now() + 1000
this.manager.globalRemaining = this.manager.globalLimit
}
this.manager.globalRemaining--
// Perform the request
let res
try {
res = await request.execute()
} catch (error) {
if (request.retries === this.manager.retryLimit) {
throw new HTTPError(
error.message,
error.constructor.name,
error.status,
request.method,
request.path
)
}
request.retries++
return await this.execute(request)
}
let sublimitTimeout
if (res?.headers !== undefined) {
const serverDate = res.headers.get('date')
const limit = res.headers.get('x-ratelimit-limit')
const remaining = res.headers.get('x-ratelimit-remaining')
const reset = res.headers.get('x-ratelimit-reset')
this.limit = limit !== null ? Number(limit) : Infinity
this.remaining = remaining !== null ? Number(remaining) : 1
this.reset =
reset !== null ? calculateReset(reset, serverDate!) : Date.now()
if (request.path.includes('reactions') === true) {
this.reset =
new Date(serverDate!).getTime() - getAPIOffset(serverDate!) + 250
}
let retryAfter: number | null | string = res.headers.get('retry-after')
retryAfter = retryAfter !== null ? Number(retryAfter) * 1000 : -1
if (retryAfter > 0) {
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (res.headers.get('x-ratelimit-global')) {
this.manager.globalRemaining = 0
this.manager.globalReset = Date.now() + retryAfter
} else if (!this.localLimited) {
sublimitTimeout = retryAfter
}
}
}
if (res.status === 401 || res.status === 403 || res.status === 429) {
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (!invalidCountResetTime || invalidCountResetTime < Date.now()) {
invalidCountResetTime = Date.now() + 1000 * 60 * 10
invalidCount = 0
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
invalidCount++
}
if (res.ok === true) {
return parseResponse(res, request.options.rawResponse ?? false)
}
if (res.status >= 400 && res.status < 500) {
if (res.status === 429) {
this.manager.client?.emit(
'debug',
`Rate-Limited on route ${request.path}${
sublimitTimeout !== undefined ? ' for sublimit' : ''
}`
)
if (sublimitTimeout !== undefined) {
await delay(sublimitTimeout)
}
return await this.execute(request)
}
let data
try {
data = await parseResponse(res, request.options.rawResponse ?? false)
} catch (err) {
throw new HTTPError(
err.message,
err.constructor.name,
err.status,
request.method,
request.path
)
}
throw new DiscordAPIError({
url: request.path,
errors: data?.errors,
status: res.status,
method: request.method,
message: data?.message,
code: data?.code,
requestData: request.options.data
})
}
if (res.status >= 500 && res.status < 600) {
if (request.retries === this.manager.retryLimit) {
throw new HTTPError(
res.statusText,
res.constructor.name,
res.status,
request.method,
request.path
)
}
request.retries++
return await this.execute(request)
}
return null
}
}

1294
src/rest/endpoints.ts Normal file

File diff suppressed because it is too large Load diff

44
src/rest/error.ts Normal file
View file

@ -0,0 +1,44 @@
import { simplifyAPIError } from '../utils/err_fmt.ts'
import { DiscordAPIErrorPayload } from './types.ts'
export class DiscordAPIError extends Error {
name = 'DiscordAPIError'
error?: DiscordAPIErrorPayload
constructor(error: string | DiscordAPIErrorPayload) {
super()
const fmt = Object.entries(
typeof error === 'object' ? simplifyAPIError(error.errors ?? {}) : {}
)
this.message =
typeof error === 'string'
? `${error} `
: `\n${error.method.toUpperCase()} ${error.url.slice(7)} returned ${
error.status
}\n(${error.code ?? 'unknown'}) ${error.message}${
fmt.length === 0
? ''
: `\n${fmt
.map(
(e) =>
` at ${e[0]}:\n${e[1]
.map((e) => ` - ${e}`)
.join('\n')}`
)
.join('\n')}\n`
}`
if (typeof error === 'object') this.error = error
}
}
export class HTTPError extends Error {
constructor(
public message: string,
public name: string,
public code: number,
public method: string,
public path: string
) {
super(message)
}
}

302
src/rest/manager.ts Normal file
View file

@ -0,0 +1,302 @@
import { Collection } from '../utils/collection.ts'
import type { Client } from '../client/mod.ts'
import { RequestMethods, METHODS } from './types.ts'
import { Constants } from '../types/constants.ts'
import { RESTEndpoints } from './endpoints.ts'
import { BucketHandler } from './bucket.ts'
import { APIRequest, RequestOptions } from './request.ts'
export type MethodFunction = (
body?: unknown,
maxRetries?: number,
bucket?: string | null,
rawResponse?: boolean,
options?: RequestOptions
) => Promise<any>
export interface APIMap extends MethodFunction {
/** Make a GET request to current route */
get: APIMap
/** Make a POST request to current route */
post: APIMap
/** Make a PATCH request to current route */
patch: APIMap
/** Make a PUT request to current route */
put: APIMap
/** Make a DELETE request to current route */
delete: APIMap
/** Make a HEAD request to current route */
head: APIMap
/** Continue building API Route */
[name: string]: APIMap
}
/** API Route builder function */
export const builder = (rest: RESTManager, acum = '/'): APIMap => {
const routes = {}
const proxy = new Proxy(routes, {
get: (_, p, __) => {
if (p === 'toString') return () => acum
if (METHODS.includes(String(p)) === true) {
const method = ((rest as unknown) as {
[name: string]: MethodFunction
})[String(p)]
return async (...args: any[]) =>
await method.bind(rest)(
`${Constants.DISCORD_API_URL}/v${rest.version}${acum.substring(
0,
acum.length - 1
)}`,
...args
)
}
return builder(rest, acum + String(p) + '/')
}
})
return (proxy as unknown) as APIMap
}
export interface RESTOptions {
/** Token to use for authorization */
token?: string | (() => string | undefined)
/** Headers to patch with if any */
headers?: { [name: string]: string | undefined }
/** Whether to use Canary instance of Discord API or not */
canary?: boolean
/** Discord REST API version to use */
version?: 6 | 7 | 8
/** Token Type to use for Authorization */
tokenType?: TokenType
/** User Agent to use (Header) */
userAgent?: string
/** Optional Harmony client */
client?: Client
/** Requests Timeout (in MS, default 30s) */
requestTimeout?: number
/** Retry Limit (default 1) */
retryLimit?: number
}
/** Token Type for REST API. */
export enum TokenType {
/** Token type for Bot User */
Bot = 'Bot',
/** Token Type for OAuth2 */
Bearer = 'Bearer',
/** No Token Type. Can be used for User accounts. */
None = ''
}
/** An easier to use interface for interacting with Discord REST API. */
export class RESTManager {
/** API Version being used by REST Manager */
version: number = 8
/**
* API Map - easy to use way for interacting with Discord API.
*
* Examples:
* * ```ts
* rest.api.users['123'].get().then(userPayload => doSomething)
* ```
* * ```ts
* rest.api.guilds['123'].channels.post({ name: 'my-channel', type: 0 }).then(channelPayload => {})
* ```
*/
api: APIMap
/** Token being used for Authorization */
token?: string | (() => string | undefined)
/** Token Type of the Token if any */
tokenType: TokenType = TokenType.Bot
/** Headers object which patch the current ones */
headers: any = {}
/** Optional custom User Agent (header) */
userAgent?: string
/** Whether REST Manager is using Canary API */
canary?: boolean
/** Optional Harmony Client object */
client?: Client
endpoints: RESTEndpoints
requestTimeout = 30000
timers: Set<number> = new Set()
apiURL = Constants.DISCORD_API_URL
handlers = new Collection<string, BucketHandler>()
globalLimit = Infinity
globalRemaining = this.globalLimit
globalReset: number | null = null
globalDelay: number | null = null
retryLimit = 1
restTimeOffset = 0
constructor(options?: RESTOptions) {
this.api = builder(this)
if (options?.token !== undefined) this.token = options.token
if (options?.version !== undefined) this.version = options.version
if (options?.headers !== undefined) this.headers = options.headers
if (options?.tokenType !== undefined) this.tokenType = options.tokenType
if (options?.userAgent !== undefined) this.userAgent = options.userAgent
if (options?.canary !== undefined) this.canary = options.canary
if (options?.client !== undefined) this.client = options.client
if (options?.retryLimit !== undefined) this.retryLimit = options.retryLimit
if (options?.requestTimeout !== undefined)
this.requestTimeout = options.requestTimeout
this.endpoints = new RESTEndpoints(this)
}
setTimeout(fn: (...args: any[]) => any, ms: number): number {
const timer = setTimeout(async () => {
this.timers.delete(timer)
await fn()
}, ms)
this.timers.add(timer)
return timer
}
async request<T = any>(
method: RequestMethods,
path: string,
options: RequestOptions = {}
): Promise<T> {
const req = new APIRequest(this, method, path, options)
let handler = this.handlers.get(req.path)
if (handler === undefined) {
handler = new BucketHandler(this)
this.handlers.set(req.route, handler)
}
return handler.push(req)
}
/**
* Makes a Request to Discord API.
* @param method HTTP Method to use
* @param url URL of the Request
* @param body Body to send with Request
* @param maxRetries Number of Max Retries to perform
* @param bucket BucketID of the Request
* @param rawResponse Whether to get Raw Response or body itself
*/
async make(
method: RequestMethods,
url: string,
body?: unknown,
_maxRetries = 0,
bucket?: string | null,
rawResponse?: boolean,
options: RequestOptions = {}
): Promise<any> {
return await this.request(
method,
url,
Object.assign(
{
data: body,
rawResponse,
route: bucket ?? undefined
},
options
)
)
}
/** Makes a GET Request to API */
async get(
url: string,
body?: unknown,
maxRetries = 0,
bucket?: string | null,
rawResponse?: boolean,
options?: RequestOptions
): Promise<any> {
return await this.make(
'get',
url,
body,
maxRetries,
bucket,
rawResponse,
options
)
}
/** Makes a POST Request to API */
async post(
url: string,
body?: unknown,
maxRetries = 0,
bucket?: string | null,
rawResponse?: boolean,
options?: RequestOptions
): Promise<any> {
return await this.make(
'post',
url,
body,
maxRetries,
bucket,
rawResponse,
options
)
}
/** Makes a DELETE Request to API */
async delete(
url: string,
body?: unknown,
maxRetries = 0,
bucket?: string | null,
rawResponse?: boolean,
options?: RequestOptions
): Promise<any> {
return await this.make(
'delete',
url,
body,
maxRetries,
bucket,
rawResponse,
options
)
}
/** Makes a PATCH Request to API */
async patch(
url: string,
body?: unknown,
maxRetries = 0,
bucket?: string | null,
rawResponse?: boolean,
options?: RequestOptions
): Promise<any> {
return await this.make(
'patch',
url,
body,
maxRetries,
bucket,
rawResponse,
options
)
}
/** Makes a PUT Request to API */
async put(
url: string,
body?: unknown,
maxRetries = 0,
bucket?: string | null,
rawResponse?: boolean,
options?: RequestOptions
): Promise<any> {
return await this.make(
'put',
url,
body,
maxRetries,
bucket,
rawResponse,
options
)
}
}

7
src/rest/mod.ts Normal file
View file

@ -0,0 +1,7 @@
export * from './manager.ts'
export * from './types.ts'
export * from './endpoints.ts'
export * from './error.ts'
export * from './bucket.ts'
export * from './queue.ts'
export * from './request.ts'

37
src/rest/queue.ts Normal file
View file

@ -0,0 +1,37 @@
// based on https://github.com/discordjs/discord.js/blob/master/src/rest/AsyncQueue.js
export interface RequestPromise {
resolve: CallableFunction
promise: Promise<any>
}
export class RequestQueue {
promises: RequestPromise[] = []
get remaining(): number {
return this.promises.length
}
async wait(): Promise<any> {
const next =
this.promises.length !== 0
? this.promises[this.promises.length - 1].promise
: Promise.resolve()
let resolveFn: CallableFunction | undefined
const promise = new Promise((resolve) => {
resolveFn = resolve
})
this.promises.push({
resolve: resolveFn!,
promise
})
return next
}
shift(): void {
const deferred = this.promises.shift()
if (typeof deferred !== 'undefined') deferred.resolve()
}
}

132
src/rest/request.ts Normal file
View file

@ -0,0 +1,132 @@
import type { Embed } from '../structures/embed.ts'
import type { MessageAttachment } from '../structures/message.ts'
import type { RESTManager } from './manager.ts'
import type { RequestMethods } from './types.ts'
export interface RequestOptions {
headers?: { [name: string]: string }
query?: { [name: string]: string }
files?: MessageAttachment[]
data?: any
reason?: string
rawResponse?: boolean
route?: string
}
export class APIRequest {
retries = 0
route: string
constructor(
public rest: RESTManager,
public method: RequestMethods,
public path: string,
public options: RequestOptions
) {
this.route = options.route ?? path
if (typeof options.query === 'object') {
const entries = Object.entries(options.query)
if (entries.length > 0) {
this.path += '?'
entries.forEach((entry, i) => {
this.path += `${i === 0 ? '' : '&'}${encodeURIComponent(
entry[0]
)}=${encodeURIComponent(entry[1])}`
})
}
}
let _files: undefined | MessageAttachment[]
if (
options.data?.embed?.files !== undefined &&
Array.isArray(options.data?.embed?.files)
) {
_files = [...options.data?.embed?.files]
}
if (
options.data?.embeds !== undefined &&
Array.isArray(options.data?.embeds)
) {
const files1 = options.data?.embeds
.map((e: Embed) => e.files)
.filter((e: MessageAttachment[]) => e !== undefined)
for (const files of files1) {
for (const file of files) {
if (_files === undefined) _files = []
_files?.push(file)
}
}
}
if (options.data?.file !== undefined) {
if (_files === undefined) _files = []
_files.push(options.data?.file)
}
if (
options.data?.files !== undefined &&
Array.isArray(options.data?.files)
) {
if (_files === undefined) _files = []
options.data?.files.forEach((file: any) => {
_files!.push(file)
})
}
if (_files !== undefined && _files.length > 0) {
if (options.files === undefined) options.files = _files
else options.files = [...options.files, ..._files]
}
}
async execute(): Promise<Response> {
let contentType: string | undefined
let body: any = this.options.data
if (this.options.files !== undefined && this.options.files.length > 0) {
contentType = undefined
const form = new FormData()
this.options.files.forEach((file, i) =>
form.append(`file${i === 0 ? '' : i}`, file.blob, file.name)
)
form.append('payload_json', JSON.stringify(body))
body = form
} else {
contentType = 'application/json'
body = JSON.stringify(body)
}
const controller = new AbortController()
const timer = setTimeout(() => {
controller.abort()
}, this.rest.requestTimeout)
this.rest.timers.add(timer)
const url = this.path.startsWith('http')
? this.path
: `${this.rest.apiURL}/v${this.rest.version}${this.path}`
const headers: any = {
'User-Agent':
this.rest.userAgent ??
`DiscordBot (harmony, https://github.com/harmonyland/harmony)`,
Authorization:
this.rest.token === undefined
? undefined
: `${this.rest.tokenType} ${this.rest.token}`.trim()
}
if (contentType !== undefined) headers['Content-Type'] = contentType
const init: RequestInit = {
method: this.method.toUpperCase(),
signal: controller.signal,
headers: Object.assign(headers, this.rest.headers, this.options.headers),
body
}
return fetch(url, init).finally(() => {
clearTimeout(timer)
this.rest.timers.delete(timer)
})
}
}

37
src/rest/types.ts Normal file
View file

@ -0,0 +1,37 @@
export type RequestMethods =
| 'get'
| 'post'
| 'put'
| 'patch'
| 'head'
| 'delete'
export enum HttpResponseCode {
Ok = 200,
Created = 201,
NoContent = 204,
NotModified = 304,
BadRequest = 400,
Unauthorized = 401,
Forbidden = 403,
NotFound = 404,
MethodNotAllowed = 405,
TooManyRequests = 429,
GatewayUnavailable = 502
}
export interface RequestHeaders {
[name: string]: string
}
export interface DiscordAPIErrorPayload {
url: string
status: number
method: string
code?: number
message?: string
errors: object
requestData: { [key: string]: any }
}
export const METHODS = ['get', 'post', 'patch', 'put', 'delete', 'head']

View file

@ -1,5 +1,5 @@
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import { ApplicationPayload } from '../types/application.ts' import type { ApplicationPayload } from '../types/application.ts'
import { SnowflakeBase } from './base.ts' import { SnowflakeBase } from './base.ts'
import { User } from './user.ts' import { User } from './user.ts'

View file

@ -1,4 +1,4 @@
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import { Snowflake } from '../utils/snowflake.ts' import { Snowflake } from '../utils/snowflake.ts'
export class Base { export class Base {

View file

@ -1,4 +1,4 @@
import { ImageFormats, ImageSize } from '../types/cdn.ts' import type { ImageFormats, ImageSize } from '../types/cdn.ts'
/** Function to get Image URL from a resource on Discord CDN */ /** Function to get Image URL from a resource on Discord CDN */
export const ImageURL = ( export const ImageURL = (

View file

@ -1,20 +1,20 @@
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import { import type {
ChannelPayload, ChannelPayload,
ChannelTypes, ChannelTypes,
ModifyChannelOption, ModifyChannelOption,
ModifyChannelPayload, ModifyChannelPayload,
Overwrite, Overwrite,
OverwritePayload, OverwritePayload,
OverwriteAsArg, OverwriteAsArg
OverrideType
} from '../types/channel.ts' } from '../types/channel.ts'
import { OverrideType } from '../types/channel.ts'
import { CHANNEL } from '../types/endpoint.ts' import { CHANNEL } from '../types/endpoint.ts'
import { GuildChannelPayloads, GuildChannels } from '../types/guild.ts' import type { GuildChannelPayloads, GuildChannels } from '../types/guild.ts'
import getChannelByType from '../utils/getChannelByType.ts' import getChannelByType from '../utils/channel.ts'
import { Permissions } from '../utils/permissions.ts' import { Permissions } from '../utils/permissions.ts'
import { SnowflakeBase } from './base.ts' import { SnowflakeBase } from './base.ts'
import { Guild } from './guild.ts' import type { Guild } from './guild.ts'
import { Member } from './member.ts' import { Member } from './member.ts'
import { Role } from './role.ts' import { Role } from './role.ts'
@ -81,7 +81,7 @@ export class GuildChannel extends Channel {
const stringToObject = const stringToObject =
typeof target === 'string' typeof target === 'string'
? (await this.guild.members.get(target)) ?? ? (await this.guild.members.get(target)) ??
(await this.guild.roles.get(target)) (await this.guild.roles.get(target))
: target : target
if (stringToObject === undefined) { if (stringToObject === undefined) {
@ -128,7 +128,7 @@ export class GuildChannel extends Channel {
const stringToObject = const stringToObject =
typeof target === 'string' typeof target === 'string'
? (await this.guild.members.get(target)) ?? ? (await this.guild.members.get(target)) ??
(await this.guild.roles.get(target)) (await this.guild.roles.get(target))
: target : target
if (stringToObject === undefined) { if (stringToObject === undefined) {
@ -200,8 +200,8 @@ export class GuildChannel extends Channel {
overwrite.id instanceof Role overwrite.id instanceof Role
? 0 ? 0
: overwrite.id instanceof Member : overwrite.id instanceof Member
? 1 ? 1
: overwrite.type : overwrite.type
if (type === undefined) { if (type === undefined) {
throw new Error('Overwrite type is undefined.') throw new Error('Overwrite type is undefined.')
} }
@ -233,8 +233,8 @@ export class GuildChannel extends Channel {
overwrite.id instanceof Role overwrite.id instanceof Role
? 0 ? 0
: overwrite.id instanceof Member : overwrite.id instanceof Member
? 1 ? 1
: overwrite.type : overwrite.type
if (type === undefined) { if (type === undefined) {
throw new Error('Overwrite type is undefined.') throw new Error('Overwrite type is undefined.')
} }
@ -303,7 +303,10 @@ export class GuildChannel extends Channel {
: overwrite.allow?.toJSON() ?? overwrites[index].allow : overwrite.allow?.toJSON() ?? overwrites[index].allow
} }
if (overwrite.deny !== undefined && overwriteDeny !== OverrideType.REPLACE) { if (
overwrite.deny !== undefined &&
overwriteDeny !== OverrideType.REPLACE
) {
switch (overwriteDeny) { switch (overwriteDeny) {
case OverrideType.ADD: { case OverrideType.ADD: {
const originalDeny = new Permissions(overwrites[index].deny) const originalDeny = new Permissions(overwrites[index].deny)
@ -331,8 +334,8 @@ export class GuildChannel extends Channel {
overwrite.id instanceof Role overwrite.id instanceof Role
? 0 ? 0
: overwrite.id instanceof Member : overwrite.id instanceof Member
? 1 ? 1
: overwrite.type : overwrite.type
if (type === undefined) { if (type === undefined) {
throw new Error('Overwrite type is undefined.') throw new Error('Overwrite type is undefined.')
} }

View file

@ -1,6 +1,6 @@
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import { DMChannelPayload } from '../types/channel.ts' import type { DMChannelPayload } from '../types/channel.ts'
import { UserPayload } from '../types/user.ts' import type { UserPayload } from '../types/user.ts'
import { TextChannel } from './textChannel.ts' import { TextChannel } from './textChannel.ts'
export class DMChannel extends TextChannel { export class DMChannel extends TextChannel {

View file

@ -1,4 +1,4 @@
import { import type {
EmbedAuthor, EmbedAuthor,
EmbedField, EmbedField,
EmbedFooter, EmbedFooter,
@ -10,7 +10,7 @@ import {
EmbedVideo EmbedVideo
} from '../types/channel.ts' } from '../types/channel.ts'
import { Colors, ColorUtil } from '../utils/colorutil.ts' import { Colors, ColorUtil } from '../utils/colorutil.ts'
import { MessageAttachment } from './message.ts' import type { MessageAttachment } from './message.ts'
/** Message Embed Object */ /** Message Embed Object */
export class Embed { export class Embed {
@ -56,44 +56,72 @@ export class Embed {
/** Convert Embed Object to Embed Payload JSON */ /** Convert Embed Object to Embed Payload JSON */
toJSON(): EmbedPayload { toJSON(): EmbedPayload {
let total = 0; let total = 0
if (this.title?.length !== undefined && this.title?.length > Embed.MAX_TITLE_LENGTH) { if (
this.title?.length !== undefined &&
this.title?.length > Embed.MAX_TITLE_LENGTH
) {
total += Number(this.title.length) total += Number(this.title.length)
throw new Error(`Embed title cannot exceed ${Embed.MAX_TITLE_LENGTH} characters.`) throw new Error(
`Embed title cannot exceed ${Embed.MAX_TITLE_LENGTH} characters.`
)
} }
if (this.description?.length !== undefined && this.description?.length > Embed.MAX_DESCRIPTION_LENGTH) { if (
this.description?.length !== undefined &&
this.description?.length > Embed.MAX_DESCRIPTION_LENGTH
) {
total += Number(this.description.length) total += Number(this.description.length)
throw new Error(`Embed description cannot exceed ${Embed.MAX_DESCRIPTION_LENGTH} characters.`) throw new Error(
`Embed description cannot exceed ${Embed.MAX_DESCRIPTION_LENGTH} characters.`
)
} }
if (this.fields?.length !== undefined) { if (this.fields?.length !== undefined) {
this.fields.forEach((field) => { this.fields.forEach((field) => {
if (field.name.length > Embed.MAX_FIELD_NAME_LENGTH) { if (field.name.length > Embed.MAX_FIELD_NAME_LENGTH) {
total += Number(field.name.length) total += Number(field.name.length)
throw new Error(`Embed field name cannot exceed ${Embed.MAX_FIELD_NAME_LENGTH} characters.`) throw new Error(
`Embed field name cannot exceed ${Embed.MAX_FIELD_NAME_LENGTH} characters.`
)
} }
if (field.value.length > Embed.MAX_FIELD_VALUE_LENGTH) { if (field.value.length > Embed.MAX_FIELD_VALUE_LENGTH) {
total += Number(field.value.length) total += Number(field.value.length)
throw new Error(`Embed field value cannot exceed ${Embed.MAX_FIELD_VALUE_LENGTH} characters.`) throw new Error(
`Embed field value cannot exceed ${Embed.MAX_FIELD_VALUE_LENGTH} characters.`
)
} }
}) })
if (this.fields.length > Embed.MAX_FIELDS_LENGTH) throw new Error('Embed fields cannot exceed 25 field objects.') if (this.fields.length > Embed.MAX_FIELDS_LENGTH)
throw new Error('Embed fields cannot exceed 25 field objects.')
} }
if (this.footer?.text?.length !== undefined && this.footer?.text?.length > Embed.MAX_FOOTER_TEXT_LENGTH) { if (
this.footer?.text?.length !== undefined &&
this.footer?.text?.length > Embed.MAX_FOOTER_TEXT_LENGTH
) {
total += Number(this.footer?.text?.length) total += Number(this.footer?.text?.length)
throw new Error(`Embed footer text cannot exceed ${Embed.MAX_FOOTER_TEXT_LENGTH}.`) throw new Error(
`Embed footer text cannot exceed ${Embed.MAX_FOOTER_TEXT_LENGTH}.`
)
} }
if (this.author?.name?.length !== undefined && this.author?.name?.length > Embed.MAX_AUTHOR_NAME_LENGTH) { if (
this.author?.name?.length !== undefined &&
this.author?.name?.length > Embed.MAX_AUTHOR_NAME_LENGTH
) {
total += Number(this.author?.name?.length) total += Number(this.author?.name?.length)
throw new Error(`Embed author name cannot exceed ${Embed.MAX_AUTHOR_NAME_LENGTH}.`) throw new Error(
`Embed author name cannot exceed ${Embed.MAX_AUTHOR_NAME_LENGTH}.`
)
} }
if (total > Embed.MAX_EMBED_LENGTH) throw new Error(`Embed characters cannot exceed ${Embed.MAX_EMBED_LENGTH} characters in total.`) if (total > Embed.MAX_EMBED_LENGTH)
throw new Error(
`Embed characters cannot exceed ${Embed.MAX_EMBED_LENGTH} characters in total.`
)
return { return {
title: this.title, title: this.title,
type: this.type, type: this.type,

View file

@ -1,11 +1,11 @@
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import { ImageSize } from '../types/cdn.ts' import type { ImageSize } from '../types/cdn.ts'
import { EmojiPayload } from '../types/emoji.ts' import type { EmojiPayload } from '../types/emoji.ts'
import { CUSTOM_EMOJI, EMOJI } from '../types/endpoint.ts' import { CUSTOM_EMOJI, EMOJI } from '../types/endpoint.ts'
import { Snowflake } from '../utils/snowflake.ts' import { Snowflake } from '../utils/snowflake.ts'
import { Base } from './base.ts' import { Base } from './base.ts'
import { ImageURL } from './cdn.ts' import { ImageURL } from './cdn.ts'
import { Guild } from './guild.ts' import type { Guild } from './guild.ts'
import { Role } from './role.ts' import { Role } from './role.ts'
import { User } from './user.ts' import { User } from './user.ts'

View file

@ -1,5 +1,5 @@
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import { GroupDMChannelPayload } from '../types/channel.ts' import type { GroupDMChannelPayload } from '../types/channel.ts'
import { Channel } from './channel.ts' import { Channel } from './channel.ts'
export class GroupDMChannel extends Channel { export class GroupDMChannel extends Channel {

View file

@ -1,4 +1,4 @@
import { Client } from '../models/client.ts' import type { Client } from '../client/mod.ts'
import { import {
GuildBanPayload, GuildBanPayload,
GuildFeatures, GuildFeatures,
@ -41,12 +41,12 @@ import {
GUILD_SPLASH GUILD_SPLASH
} from '../types/endpoint.ts' } from '../types/endpoint.ts'
import { GuildVoiceStatesManager } from '../managers/guildVoiceStates.ts' import { GuildVoiceStatesManager } from '../managers/guildVoiceStates.ts'
import { RequestMembersOptions } from '../gateway/index.ts' import type { RequestMembersOptions } from '../gateway/mod.ts'
import { GuildPresencesManager } from '../managers/presences.ts' import { GuildPresencesManager } from '../managers/presences.ts'
import { TemplatePayload } from '../types/template.ts' import type { TemplatePayload } from '../types/template.ts'
import { Template } from './template.ts' import { Template } from './template.ts'
import { DiscordAPIError } from '../models/rest.ts' import { DiscordAPIError } from '../rest/mod.ts'
import { ImageFormats, ImageSize } from '../types/cdn.ts' import type { ImageFormats, ImageSize } from '../types/cdn.ts'
import { ImageURL } from './cdn.ts' import { ImageURL } from './cdn.ts'
export class GuildBan extends Base { export class GuildBan extends Base {

Some files were not shown because too many files have changed in this diff Show more