Merge pull request #122 from DjDeveloperr/refactor
feat: refactor project structure
This commit is contained in:
commit
ce843b189b
162 changed files with 3963 additions and 2175 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -109,6 +109,7 @@ yarn.lock
|
|||
|
||||
# PRIVACY XDDDD
|
||||
src/test/config.ts
|
||||
test/config.ts
|
||||
.vscode
|
||||
|
||||
# macOS is shit xD
|
||||
|
@ -117,4 +118,4 @@ src/test/config.ts
|
|||
# Webstorm dont forget this duude :)
|
||||
.idea/
|
||||
|
||||
src/test/music.mp3
|
||||
src/test/music.mp3
|
42
deploy.ts
42
deploy.ts
|
@ -1,8 +1,9 @@
|
|||
import {
|
||||
SlashCommandsManager,
|
||||
SlashClient,
|
||||
SlashCommandHandlerCallback
|
||||
} from './src/models/slashClient.ts'
|
||||
SlashCommandHandlerCallback,
|
||||
SlashCommandHandler
|
||||
} from './src/interactions/mod.ts'
|
||||
import { InteractionResponseType, InteractionType } from './src/types/slash.ts'
|
||||
|
||||
export interface DeploySlashInitOptions {
|
||||
|
@ -41,7 +42,7 @@ export function init(options: DeploySlashInitOptions): void {
|
|||
try {
|
||||
const d = await client.verifyFetchEvent({
|
||||
respondWith: (...args: any[]) => evt.respondWith(...args),
|
||||
request: evt.request,
|
||||
request: evt.request
|
||||
})
|
||||
if (d === false) {
|
||||
await evt.respondWith(
|
||||
|
@ -60,7 +61,6 @@ export function init(options: DeploySlashInitOptions): void {
|
|||
|
||||
await (client as any)._process(d)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
await client.emit('interactionError', e)
|
||||
}
|
||||
}
|
||||
|
@ -69,38 +69,14 @@ export function init(options: DeploySlashInitOptions): void {
|
|||
}
|
||||
|
||||
export function handle(
|
||||
cmd:
|
||||
| string
|
||||
| {
|
||||
name: string
|
||||
parent?: string
|
||||
group?: string
|
||||
guild?: string
|
||||
},
|
||||
handler: SlashCommandHandlerCallback
|
||||
cmd: string | SlashCommandHandler,
|
||||
handler?: SlashCommandHandlerCallback
|
||||
): void {
|
||||
const handle = {
|
||||
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)
|
||||
client.handle(cmd, handler)
|
||||
}
|
||||
|
||||
export { commands, client }
|
||||
export * from './src/types/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
67
mod.ts
|
@ -1,20 +1,18 @@
|
|||
export { GatewayIntents } from './src/types/gateway.ts'
|
||||
export { Base } from './src/structures/base.ts'
|
||||
export { Gateway } from './src/gateway/index.ts'
|
||||
export type { GatewayTypedEvents } from './src/gateway/index.ts'
|
||||
export type { ClientEvents } from './src/gateway/handlers/index.ts'
|
||||
export * from './src/models/client.ts'
|
||||
export * from './src/models/slashClient.ts'
|
||||
export { Gateway } from './src/gateway/mod.ts'
|
||||
export type { GatewayTypedEvents } from './src/gateway/mod.ts'
|
||||
export type { ClientEvents } from './src/gateway/handlers/mod.ts'
|
||||
export * from './src/client/mod.ts'
|
||||
export * from './src/interactions/mod.ts'
|
||||
export {
|
||||
RESTManager,
|
||||
TokenType,
|
||||
HttpResponseCode,
|
||||
DiscordAPIError
|
||||
} from './src/models/rest.ts'
|
||||
export type { APIMap, DiscordAPIErrorPayload } from './src/models/rest.ts'
|
||||
export type { RequestHeaders } from './src/models/rest.ts'
|
||||
export type { RESTOptions } from './src/models/rest.ts'
|
||||
export * from './src/models/cacheAdapter.ts'
|
||||
} from './src/rest/mod.ts'
|
||||
export * from './src/rest/mod.ts'
|
||||
export * from './src/cache/adapter.ts'
|
||||
export {
|
||||
Command,
|
||||
CommandBuilder,
|
||||
|
@ -22,16 +20,16 @@ export {
|
|||
CommandsManager,
|
||||
CategoriesManager,
|
||||
CommandsLoader
|
||||
} from './src/models/command.ts'
|
||||
export type { CommandContext, CommandOptions } from './src/models/command.ts'
|
||||
} from './src/commands/command.ts'
|
||||
export type { CommandContext, CommandOptions } from './src/commands/command.ts'
|
||||
export {
|
||||
Extension,
|
||||
ExtensionCommands,
|
||||
ExtensionsManager
|
||||
} from './src/models/extensions.ts'
|
||||
export { SlashModule } from './src/models/slashModule.ts'
|
||||
export { CommandClient, command } from './src/models/commandClient.ts'
|
||||
export type { CommandClientOptions } from './src/models/commandClient.ts'
|
||||
} from './src/commands/extension.ts'
|
||||
export { SlashModule } from './src/interactions/slashModule.ts'
|
||||
export { CommandClient, command } from './src/commands/client.ts'
|
||||
export type { CommandClientOptions } from './src/commands/client.ts'
|
||||
export { BaseManager } from './src/managers/base.ts'
|
||||
export { BaseChildManager } from './src/managers/baseChild.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 { InviteManager } from './src/managers/invites.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 type { EditOverwriteOptions } from './src/structures/channel.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 { UserFlagsManager } from './src/utils/userFlags.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 type {
|
||||
ActivityGame,
|
||||
|
@ -106,7 +104,15 @@ export type {
|
|||
ClientStatus,
|
||||
StatusType
|
||||
} 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 { ImageFormats, ImageSize } from './src/types/cdn.ts'
|
||||
export type {
|
||||
|
@ -131,8 +137,7 @@ export type {
|
|||
MessageStickerPayload,
|
||||
MessageTypes,
|
||||
OverwriteAsArg,
|
||||
Overwrite,
|
||||
OverwriteAsOptions
|
||||
Overwrite
|
||||
} from './src/types/channel.ts'
|
||||
export type { EmojiPayload } from './src/types/emoji.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 type { VoiceStatePayload } from './src/types/voice.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 * from './src/models/redisCache.ts'
|
||||
export * from './src/cache/redis.ts'
|
||||
export { ColorUtil } 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
22
src/cache/adapter.ts
vendored
Normal 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
|
||||
}
|
122
src/models/cacheAdapter.ts → src/cache/default.ts
vendored
122
src/models/cacheAdapter.ts → src/cache/default.ts
vendored
|
@ -1,72 +1,50 @@
|
|||
import { Collection } from '../utils/collection.ts'
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
/** Default Cache Adapter for in-memory caching. */
|
||||
export class DefaultCacheAdapter implements ICacheAdapter {
|
||||
data: {
|
||||
[name: string]: Collection<string, any>
|
||||
} = {}
|
||||
|
||||
async get(cacheName: string, key: string): Promise<undefined | any> {
|
||||
const cache = this.data[cacheName]
|
||||
if (cache === undefined) return
|
||||
return cache.get(key)
|
||||
}
|
||||
|
||||
async set(
|
||||
cacheName: string,
|
||||
key: string,
|
||||
value: any,
|
||||
expire?: number
|
||||
): Promise<any> {
|
||||
let cache = this.data[cacheName]
|
||||
if (cache === undefined) {
|
||||
this.data[cacheName] = new Collection()
|
||||
cache = 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]
|
||||
}
|
||||
}
|
||||
import { Collection } from '../utils/collection.ts'
|
||||
import type { ICacheAdapter } from './adapter.ts'
|
||||
|
||||
/** Default Cache Adapter for in-memory caching. */
|
||||
export class DefaultCacheAdapter implements ICacheAdapter {
|
||||
data: {
|
||||
[name: string]: Collection<string, any>
|
||||
} = {}
|
||||
|
||||
async get(cacheName: string, key: string): Promise<undefined | any> {
|
||||
const cache = this.data[cacheName]
|
||||
if (cache === undefined) return
|
||||
return cache.get(key)
|
||||
}
|
||||
|
||||
async set(
|
||||
cacheName: string,
|
||||
key: string,
|
||||
value: any,
|
||||
expire?: number
|
||||
): Promise<any> {
|
||||
let cache = this.data[cacheName]
|
||||
if (cache === undefined) {
|
||||
this.data[cacheName] = new Collection()
|
||||
cache = 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
4
src/cache/mod.ts
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
export * from './adapter.ts'
|
||||
export * from './default.ts'
|
||||
// Not exported by default
|
||||
// export * from './redis.ts'
|
11
src/models/redisCache.ts → src/cache/redis.ts
vendored
11
src/models/redisCache.ts → src/cache/redis.ts
vendored
|
@ -1,5 +1,10 @@
|
|||
import { ICacheAdapter } from './cacheAdapter.ts'
|
||||
import { connect, Redis, RedisConnectOptions } from 'https://deno.land/x/redis@v0.14.1/mod.ts'
|
||||
import { ICacheAdapter } from './adapter.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. */
|
||||
export class RedisCacheAdapter implements ICacheAdapter {
|
||||
|
@ -102,4 +107,4 @@ export class RedisCacheAdapter implements ICacheAdapter {
|
|||
await this._checkReady()
|
||||
return (await this.redis?.del(cacheName)) !== 0
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,437 +1,437 @@
|
|||
/* eslint-disable @typescript-eslint/method-signature-style */
|
||||
import { User } from '../structures/user.ts'
|
||||
import { GatewayIntents } from '../types/gateway.ts'
|
||||
import { Gateway } from '../gateway/index.ts'
|
||||
import { RESTManager, RESTOptions, TokenType } from './rest.ts'
|
||||
import { DefaultCacheAdapter, ICacheAdapter } from './cacheAdapter.ts'
|
||||
import { UsersManager } from '../managers/users.ts'
|
||||
import { GuildManager } from '../managers/guilds.ts'
|
||||
import { ChannelsManager } from '../managers/channels.ts'
|
||||
import { ClientPresence } from '../structures/presence.ts'
|
||||
import { EmojisManager } from '../managers/emojis.ts'
|
||||
import { ActivityGame, ClientActivity } from '../types/presence.ts'
|
||||
import { Extension } from './extensions.ts'
|
||||
import { SlashClient } from './slashClient.ts'
|
||||
import { Interaction } from '../structures/slash.ts'
|
||||
import { ShardManager } from './shard.ts'
|
||||
import { Application } from '../structures/application.ts'
|
||||
import { Invite } from '../structures/invite.ts'
|
||||
import { INVITE } from '../types/endpoint.ts'
|
||||
import { ClientEvents } from '../gateway/handlers/index.ts'
|
||||
import type { Collector } from './collectors.ts'
|
||||
import { HarmonyEventEmitter } from '../utils/events.ts'
|
||||
import { VoiceRegion } from '../types/voice.ts'
|
||||
import { fetchAuto } from '../../deps.ts'
|
||||
import { DMChannel } from '../structures/dmChannel.ts'
|
||||
import { Template } from '../structures/template.ts'
|
||||
|
||||
/** OS related properties sent with Gateway Identify */
|
||||
export interface ClientProperties {
|
||||
os?: 'darwin' | 'windows' | 'linux' | 'custom_os' | string
|
||||
browser?: 'harmony' | string
|
||||
device?: 'harmony' | string
|
||||
}
|
||||
|
||||
/** Some Client Options to modify behaviour */
|
||||
export interface ClientOptions {
|
||||
/** ID of the Client/Application to initialize Slash Client REST */
|
||||
id?: string
|
||||
/** Token of the Bot/User */
|
||||
token?: string
|
||||
/** Gateway Intents */
|
||||
intents?: GatewayIntents[]
|
||||
/** Cache Adapter to use, defaults to Collections one */
|
||||
cache?: ICacheAdapter
|
||||
/** Force New Session and don't use cached Session (by persistent caching) */
|
||||
forceNewSession?: boolean
|
||||
/** Startup presence of client */
|
||||
presence?: ClientPresence | ClientActivity | ActivityGame
|
||||
/** Force all requests to Canary API */
|
||||
canary?: boolean
|
||||
/** Time till which Messages are to be cached, in MS. Default is 3600000 */
|
||||
messageCacheLifetime?: number
|
||||
/** Time till which Message Reactions are to be cached, in MS. Default is 3600000 */
|
||||
reactionCacheLifetime?: number
|
||||
/** Whether to fetch Uncached Message of Reaction or not? */
|
||||
fetchUncachedReactions?: boolean
|
||||
/** Client Properties */
|
||||
clientProperties?: ClientProperties
|
||||
/** Enable/Disable Slash Commands Integration (enabled by default) */
|
||||
enableSlash?: boolean
|
||||
/** Disable taking token from env if not provided (token is taken from env if present by default) */
|
||||
disableEnvToken?: boolean
|
||||
/** Override REST Options */
|
||||
restOptions?: RESTOptions
|
||||
/** Whether to fetch Gateway info or not */
|
||||
fetchGatewayInfo?: boolean
|
||||
/** ADVANCED: Shard ID to launch on */
|
||||
shard?: number
|
||||
/** ADVACNED: Shard count. */
|
||||
shardCount?: number | 'auto'
|
||||
}
|
||||
|
||||
/**
|
||||
* Discord Client.
|
||||
*/
|
||||
export class Client extends HarmonyEventEmitter<ClientEvents> {
|
||||
/** REST Manager - used to make all requests */
|
||||
rest: RESTManager
|
||||
/** User which Client logs in to, undefined until logs in */
|
||||
user?: User
|
||||
/** WebSocket ping of Client */
|
||||
ping = 0
|
||||
/** Token of the Bot/User */
|
||||
token?: string
|
||||
/** Cache Adapter */
|
||||
cache: ICacheAdapter = new DefaultCacheAdapter()
|
||||
/** Gateway Intents */
|
||||
intents?: GatewayIntents[]
|
||||
/** Whether to force new session or not */
|
||||
forceNewSession?: boolean
|
||||
/** Time till messages to stay cached, in MS. */
|
||||
messageCacheLifetime: number = 3600000
|
||||
/** Time till messages to stay cached, in MS. */
|
||||
reactionCacheLifetime: number = 3600000
|
||||
/** Whether to fetch Uncached Message of Reaction or not? */
|
||||
fetchUncachedReactions: boolean = false
|
||||
/** Client Properties */
|
||||
clientProperties: ClientProperties
|
||||
/** Slash-Commands Management client */
|
||||
slash: SlashClient
|
||||
/** Whether to fetch Gateway info or not */
|
||||
fetchGatewayInfo: boolean = true
|
||||
|
||||
/** Users Manager, containing all Users cached */
|
||||
users: UsersManager = new UsersManager(this)
|
||||
/** Guilds Manager, providing cache & API interface to Guilds */
|
||||
guilds: GuildManager = new GuildManager(this)
|
||||
/** Channels Manager, providing cache interface to Channels */
|
||||
channels: ChannelsManager = new ChannelsManager(this)
|
||||
/** Channels Manager, providing cache interface to Channels */
|
||||
emojis: EmojisManager = new EmojisManager(this)
|
||||
|
||||
/** Last READY timestamp */
|
||||
upSince?: Date
|
||||
|
||||
/** Client's presence. Startup one if set before connecting */
|
||||
presence: ClientPresence = new ClientPresence()
|
||||
_decoratedEvents?: {
|
||||
[name: string]: (...args: any[]) => void
|
||||
}
|
||||
|
||||
_decoratedSlash?: Array<{
|
||||
name: string
|
||||
guild?: string
|
||||
parent?: string
|
||||
group?: string
|
||||
handler: (interaction: Interaction) => any
|
||||
}>
|
||||
|
||||
_id?: string
|
||||
|
||||
/** Shard on which this Client is */
|
||||
shard?: number
|
||||
/** Shard Count */
|
||||
shardCount: number | 'auto' = 'auto'
|
||||
/** Shard Manager of this Client if Sharded */
|
||||
shards: ShardManager
|
||||
/** Collectors set */
|
||||
collectors: Set<Collector> = new Set()
|
||||
|
||||
/** Since when is Client online (ready). */
|
||||
get uptime(): number {
|
||||
if (this.upSince === undefined) return 0
|
||||
else {
|
||||
const dif = Date.now() - this.upSince.getTime()
|
||||
if (dif < 0) return 0
|
||||
else return dif
|
||||
}
|
||||
}
|
||||
|
||||
get gateway(): Gateway {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
return this.shards.list.get('0') as Gateway
|
||||
}
|
||||
|
||||
applicationID?: string
|
||||
applicationFlags?: number
|
||||
|
||||
constructor(options: ClientOptions = {}) {
|
||||
super()
|
||||
this._id = options.id
|
||||
this.token = options.token
|
||||
this.intents = options.intents
|
||||
this.shards = new ShardManager(this)
|
||||
this.forceNewSession = options.forceNewSession
|
||||
if (options.cache !== undefined) this.cache = options.cache
|
||||
if (options.presence !== undefined)
|
||||
this.presence =
|
||||
options.presence instanceof ClientPresence
|
||||
? options.presence
|
||||
: new ClientPresence(options.presence)
|
||||
if (options.messageCacheLifetime !== undefined)
|
||||
this.messageCacheLifetime = options.messageCacheLifetime
|
||||
if (options.reactionCacheLifetime !== undefined)
|
||||
this.reactionCacheLifetime = options.reactionCacheLifetime
|
||||
if (options.fetchUncachedReactions === true)
|
||||
this.fetchUncachedReactions = true
|
||||
|
||||
if (
|
||||
this._decoratedEvents !== undefined &&
|
||||
Object.keys(this._decoratedEvents).length !== 0
|
||||
) {
|
||||
Object.entries(this._decoratedEvents).forEach((entry) => {
|
||||
this.on(entry[0] as keyof ClientEvents, entry[1].bind(this))
|
||||
})
|
||||
this._decoratedEvents = undefined
|
||||
}
|
||||
|
||||
this.clientProperties =
|
||||
options.clientProperties === undefined
|
||||
? {
|
||||
os: Deno.build.os,
|
||||
browser: 'harmony',
|
||||
device: 'harmony'
|
||||
}
|
||||
: options.clientProperties
|
||||
|
||||
if (options.shard !== undefined) this.shard = options.shard
|
||||
if (options.shardCount !== undefined) this.shardCount = options.shardCount
|
||||
|
||||
this.fetchGatewayInfo = options.fetchGatewayInfo ?? true
|
||||
|
||||
if (this.token === undefined) {
|
||||
try {
|
||||
const token = Deno.env.get('DISCORD_TOKEN')
|
||||
if (token !== undefined) {
|
||||
this.token = token
|
||||
this.debug('Info', 'Found token in ENV')
|
||||
}
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
const restOptions: RESTOptions = {
|
||||
token: () => this.token,
|
||||
tokenType: TokenType.Bot,
|
||||
canary: options.canary,
|
||||
client: this
|
||||
}
|
||||
|
||||
if (options.restOptions !== undefined)
|
||||
Object.assign(restOptions, options.restOptions)
|
||||
this.rest = new RESTManager(restOptions)
|
||||
|
||||
this.slash = new SlashClient({
|
||||
id: () => this.getEstimatedID(),
|
||||
client: this,
|
||||
enabled: options.enableSlash
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets Cache Adapter
|
||||
*
|
||||
* Should NOT be set after bot is already logged in or using current cache.
|
||||
* Please look into using `cache` option.
|
||||
*/
|
||||
setAdapter(adapter: ICacheAdapter): Client {
|
||||
this.cache = adapter
|
||||
return this
|
||||
}
|
||||
|
||||
/** Changes Presence of Client */
|
||||
setPresence(presence: ClientPresence | ClientActivity | ActivityGame): void {
|
||||
if (presence instanceof ClientPresence) {
|
||||
this.presence = presence
|
||||
} else this.presence = new ClientPresence(presence)
|
||||
this.gateway?.sendPresence(this.presence.create())
|
||||
}
|
||||
|
||||
/** Emits debug event */
|
||||
debug(tag: string, msg: string): void {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.emit('debug', `[${tag}] ${msg}`)
|
||||
}
|
||||
|
||||
getEstimatedID(): string {
|
||||
if (this.user !== undefined) return this.user.id
|
||||
else if (this.token !== undefined) {
|
||||
try {
|
||||
return atob(this.token.split('.')[0])
|
||||
} catch (e) {
|
||||
return this._id ?? 'unknown'
|
||||
}
|
||||
} else {
|
||||
return this._id ?? 'unknown'
|
||||
}
|
||||
}
|
||||
|
||||
/** Fetch Application of the Client */
|
||||
async fetchApplication(): Promise<Application> {
|
||||
const app = await this.rest.api.oauth2.applications['@me'].get()
|
||||
return new Application(this, app)
|
||||
}
|
||||
|
||||
/** Fetch an Invite */
|
||||
async fetchInvite(id: string): Promise<Invite> {
|
||||
return await new Promise((resolve, reject) => {
|
||||
this.rest
|
||||
.get(INVITE(id))
|
||||
.then((data) => {
|
||||
resolve(new Invite(this, data))
|
||||
})
|
||||
.catch((e) => reject(e))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used for connecting to discord.
|
||||
* @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.
|
||||
*/
|
||||
async connect(token?: string, intents?: GatewayIntents[]): Promise<Client> {
|
||||
token ??= this.token
|
||||
if (token === undefined) throw new Error('No Token Provided')
|
||||
this.token = token
|
||||
if (intents !== undefined && this.intents !== undefined) {
|
||||
this.debug(
|
||||
'client',
|
||||
'Intents were set in both client and connect function. Using the one in the connect function...'
|
||||
)
|
||||
} else if (intents === undefined && this.intents !== undefined) {
|
||||
intents = this.intents
|
||||
} else if (intents !== undefined && this.intents === undefined) {
|
||||
this.intents = intents
|
||||
} else throw new Error('No Gateway Intents were provided')
|
||||
|
||||
this.rest.token = token
|
||||
if (this.shard !== undefined) {
|
||||
if (typeof this.shardCount === 'number')
|
||||
this.shards.cachedShardCount = this.shardCount
|
||||
await this.shards.launch(this.shard)
|
||||
} else await this.shards.connect()
|
||||
return this.waitFor('ready', () => true).then(() => this)
|
||||
}
|
||||
|
||||
/** Destroy the Gateway connection */
|
||||
async destroy(): Promise<Client> {
|
||||
this.gateway.initialized = false
|
||||
this.gateway.sequenceID = undefined
|
||||
this.gateway.sessionID = undefined
|
||||
await this.gateway.cache.delete('seq')
|
||||
await this.gateway.cache.delete('session_id')
|
||||
this.gateway.close()
|
||||
this.user = undefined
|
||||
this.upSince = undefined
|
||||
return this
|
||||
}
|
||||
|
||||
/** Attempt to Close current Gateway connection and Resume */
|
||||
async reconnect(): Promise<Client> {
|
||||
this.gateway.close()
|
||||
this.gateway.initWebsocket()
|
||||
return this.waitFor('ready', () => true).then(() => this)
|
||||
}
|
||||
|
||||
/** Add a new Collector */
|
||||
addCollector(collector: Collector): boolean {
|
||||
if (this.collectors.has(collector)) return false
|
||||
else {
|
||||
this.collectors.add(collector)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/** Remove a Collector */
|
||||
removeCollector(collector: Collector): boolean {
|
||||
if (!this.collectors.has(collector)) return false
|
||||
else {
|
||||
this.collectors.delete(collector)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
async emit(event: keyof ClientEvents, ...args: any[]): Promise<void> {
|
||||
const collectors: Collector[] = []
|
||||
for (const collector of this.collectors.values()) {
|
||||
if (collector.event === event) collectors.push(collector)
|
||||
}
|
||||
if (collectors.length !== 0) {
|
||||
this.collectors.forEach((collector) => collector._fire(...args))
|
||||
}
|
||||
// TODO(DjDeveloperr): Fix this ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
|
||||
// @ts-ignore
|
||||
return super.emit(event, ...args)
|
||||
}
|
||||
|
||||
/** Returns an array of voice region objects that can be used when creating servers. */
|
||||
async fetchVoiceRegions(): Promise<VoiceRegion[]> {
|
||||
return this.rest.api.voice.regions.get()
|
||||
}
|
||||
|
||||
/** Modify current (Client) User. */
|
||||
async editUser(data: {
|
||||
username?: string
|
||||
avatar?: string
|
||||
}): Promise<Client> {
|
||||
if (data.username === undefined && data.avatar === undefined)
|
||||
throw new Error(
|
||||
'Either username or avatar or both must be specified to edit'
|
||||
)
|
||||
|
||||
if (data.avatar?.startsWith('http') === true) {
|
||||
data.avatar = await fetchAuto(data.avatar)
|
||||
}
|
||||
|
||||
await this.rest.api.users['@me'].patch({
|
||||
username: data.username,
|
||||
avatar: data.avatar
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
/** Change Username of the Client User */
|
||||
async setUsername(username: string): Promise<Client> {
|
||||
return await this.editUser({ username })
|
||||
}
|
||||
|
||||
/** Change Avatar of the Client User */
|
||||
async setAvatar(avatar: string): Promise<Client> {
|
||||
return await this.editUser({ avatar })
|
||||
}
|
||||
|
||||
/** Create a DM Channel with a User */
|
||||
async createDM(user: User | string): Promise<DMChannel> {
|
||||
const id = typeof user === 'object' ? user.id : user
|
||||
const dmPayload = await this.rest.api.users['@me'].channels.post({
|
||||
recipient_id: id
|
||||
})
|
||||
await this.channels.set(dmPayload.id, dmPayload)
|
||||
return (this.channels.get<DMChannel>(dmPayload.id) as unknown) as DMChannel
|
||||
}
|
||||
|
||||
/** Returns a template object for the given code. */
|
||||
async fetchTemplate(code: string): Promise<Template> {
|
||||
const payload = await this.rest.api.guilds.templates[code].get()
|
||||
return new Template(this, payload)
|
||||
}
|
||||
}
|
||||
|
||||
/** Event decorator to create an Event handler from function */
|
||||
export function event(name?: keyof ClientEvents) {
|
||||
return function (
|
||||
client: Client | Extension,
|
||||
prop: keyof ClientEvents | string
|
||||
) {
|
||||
const listener = ((client as unknown) as {
|
||||
[name in keyof ClientEvents]: (...args: ClientEvents[name]) => any
|
||||
})[(prop as unknown) as keyof ClientEvents]
|
||||
if (typeof listener !== 'function')
|
||||
throw new Error('@event decorator requires a function')
|
||||
if (client._decoratedEvents === undefined) client._decoratedEvents = {}
|
||||
const key = name === undefined ? prop : name
|
||||
|
||||
client._decoratedEvents[key] = listener
|
||||
}
|
||||
}
|
||||
/* eslint-disable @typescript-eslint/method-signature-style */
|
||||
import type { User } from '../structures/user.ts'
|
||||
import { GatewayIntents } from '../types/gateway.ts'
|
||||
import { Gateway } from '../gateway/mod.ts'
|
||||
import { RESTManager, RESTOptions, TokenType } from '../rest/mod.ts'
|
||||
import { DefaultCacheAdapter, ICacheAdapter } from '../cache/mod.ts'
|
||||
import { UsersManager } from '../managers/users.ts'
|
||||
import { GuildManager } from '../managers/guilds.ts'
|
||||
import { ChannelsManager } from '../managers/channels.ts'
|
||||
import { ClientPresence } from '../structures/presence.ts'
|
||||
import { EmojisManager } from '../managers/emojis.ts'
|
||||
import { ActivityGame, ClientActivity } from '../types/presence.ts'
|
||||
import type { Extension } from '../commands/extension.ts'
|
||||
import { SlashClient } from '../interactions/slashClient.ts'
|
||||
import type { Interaction } from '../structures/slash.ts'
|
||||
import { ShardManager } from './shard.ts'
|
||||
import { Application } from '../structures/application.ts'
|
||||
import { Invite } from '../structures/invite.ts'
|
||||
import { INVITE } from '../types/endpoint.ts'
|
||||
import type { ClientEvents } from '../gateway/handlers/mod.ts'
|
||||
import type { Collector } from './collectors.ts'
|
||||
import { HarmonyEventEmitter } from '../utils/events.ts'
|
||||
import type { VoiceRegion } from '../types/voice.ts'
|
||||
import { fetchAuto } from '../../deps.ts'
|
||||
import type { DMChannel } from '../structures/dmChannel.ts'
|
||||
import { Template } from '../structures/template.ts'
|
||||
|
||||
/** OS related properties sent with Gateway Identify */
|
||||
export interface ClientProperties {
|
||||
os?: 'darwin' | 'windows' | 'linux' | 'custom_os' | string
|
||||
browser?: 'harmony' | string
|
||||
device?: 'harmony' | string
|
||||
}
|
||||
|
||||
/** Some Client Options to modify behaviour */
|
||||
export interface ClientOptions {
|
||||
/** ID of the Client/Application to initialize Slash Client REST */
|
||||
id?: string
|
||||
/** Token of the Bot/User */
|
||||
token?: string
|
||||
/** Gateway Intents */
|
||||
intents?: GatewayIntents[]
|
||||
/** Cache Adapter to use, defaults to Collections one */
|
||||
cache?: ICacheAdapter
|
||||
/** Force New Session and don't use cached Session (by persistent caching) */
|
||||
forceNewSession?: boolean
|
||||
/** Startup presence of client */
|
||||
presence?: ClientPresence | ClientActivity | ActivityGame
|
||||
/** Force all requests to Canary API */
|
||||
canary?: boolean
|
||||
/** Time till which Messages are to be cached, in MS. Default is 3600000 */
|
||||
messageCacheLifetime?: number
|
||||
/** Time till which Message Reactions are to be cached, in MS. Default is 3600000 */
|
||||
reactionCacheLifetime?: number
|
||||
/** Whether to fetch Uncached Message of Reaction or not? */
|
||||
fetchUncachedReactions?: boolean
|
||||
/** Client Properties */
|
||||
clientProperties?: ClientProperties
|
||||
/** Enable/Disable Slash Commands Integration (enabled by default) */
|
||||
enableSlash?: boolean
|
||||
/** Disable taking token from env if not provided (token is taken from env if present by default) */
|
||||
disableEnvToken?: boolean
|
||||
/** Override REST Options */
|
||||
restOptions?: RESTOptions
|
||||
/** Whether to fetch Gateway info or not */
|
||||
fetchGatewayInfo?: boolean
|
||||
/** ADVANCED: Shard ID to launch on */
|
||||
shard?: number
|
||||
/** ADVACNED: Shard count. */
|
||||
shardCount?: number | 'auto'
|
||||
}
|
||||
|
||||
/**
|
||||
* Discord Client.
|
||||
*/
|
||||
export class Client extends HarmonyEventEmitter<ClientEvents> {
|
||||
/** REST Manager - used to make all requests */
|
||||
rest: RESTManager
|
||||
/** User which Client logs in to, undefined until logs in */
|
||||
user?: User
|
||||
/** WebSocket ping of Client */
|
||||
ping = 0
|
||||
/** Token of the Bot/User */
|
||||
token?: string
|
||||
/** Cache Adapter */
|
||||
cache: ICacheAdapter = new DefaultCacheAdapter()
|
||||
/** Gateway Intents */
|
||||
intents?: GatewayIntents[]
|
||||
/** Whether to force new session or not */
|
||||
forceNewSession?: boolean
|
||||
/** Time till messages to stay cached, in MS. */
|
||||
messageCacheLifetime: number = 3600000
|
||||
/** Time till messages to stay cached, in MS. */
|
||||
reactionCacheLifetime: number = 3600000
|
||||
/** Whether to fetch Uncached Message of Reaction or not? */
|
||||
fetchUncachedReactions: boolean = false
|
||||
/** Client Properties */
|
||||
clientProperties: ClientProperties
|
||||
/** Slash-Commands Management client */
|
||||
slash: SlashClient
|
||||
/** Whether to fetch Gateway info or not */
|
||||
fetchGatewayInfo: boolean = true
|
||||
|
||||
/** Users Manager, containing all Users cached */
|
||||
users: UsersManager = new UsersManager(this)
|
||||
/** Guilds Manager, providing cache & API interface to Guilds */
|
||||
guilds: GuildManager = new GuildManager(this)
|
||||
/** Channels Manager, providing cache interface to Channels */
|
||||
channels: ChannelsManager = new ChannelsManager(this)
|
||||
/** Channels Manager, providing cache interface to Channels */
|
||||
emojis: EmojisManager = new EmojisManager(this)
|
||||
|
||||
/** Last READY timestamp */
|
||||
upSince?: Date
|
||||
|
||||
/** Client's presence. Startup one if set before connecting */
|
||||
presence: ClientPresence = new ClientPresence()
|
||||
_decoratedEvents?: {
|
||||
[name: string]: (...args: any[]) => void
|
||||
}
|
||||
|
||||
_decoratedSlash?: Array<{
|
||||
name: string
|
||||
guild?: string
|
||||
parent?: string
|
||||
group?: string
|
||||
handler: (interaction: Interaction) => any
|
||||
}>
|
||||
|
||||
_id?: string
|
||||
|
||||
/** Shard on which this Client is */
|
||||
shard?: number
|
||||
/** Shard Count */
|
||||
shardCount: number | 'auto' = 'auto'
|
||||
/** Shard Manager of this Client if Sharded */
|
||||
shards: ShardManager
|
||||
/** Collectors set */
|
||||
collectors: Set<Collector> = new Set()
|
||||
|
||||
/** Since when is Client online (ready). */
|
||||
get uptime(): number {
|
||||
if (this.upSince === undefined) return 0
|
||||
else {
|
||||
const dif = Date.now() - this.upSince.getTime()
|
||||
if (dif < 0) return 0
|
||||
else return dif
|
||||
}
|
||||
}
|
||||
|
||||
get gateway(): Gateway {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
return this.shards.list.get('0') as Gateway
|
||||
}
|
||||
|
||||
applicationID?: string
|
||||
applicationFlags?: number
|
||||
|
||||
constructor(options: ClientOptions = {}) {
|
||||
super()
|
||||
this._id = options.id
|
||||
this.token = options.token
|
||||
this.intents = options.intents
|
||||
this.shards = new ShardManager(this)
|
||||
this.forceNewSession = options.forceNewSession
|
||||
if (options.cache !== undefined) this.cache = options.cache
|
||||
if (options.presence !== undefined)
|
||||
this.presence =
|
||||
options.presence instanceof ClientPresence
|
||||
? options.presence
|
||||
: new ClientPresence(options.presence)
|
||||
if (options.messageCacheLifetime !== undefined)
|
||||
this.messageCacheLifetime = options.messageCacheLifetime
|
||||
if (options.reactionCacheLifetime !== undefined)
|
||||
this.reactionCacheLifetime = options.reactionCacheLifetime
|
||||
if (options.fetchUncachedReactions === true)
|
||||
this.fetchUncachedReactions = true
|
||||
|
||||
if (
|
||||
this._decoratedEvents !== undefined &&
|
||||
Object.keys(this._decoratedEvents).length !== 0
|
||||
) {
|
||||
Object.entries(this._decoratedEvents).forEach((entry) => {
|
||||
this.on(entry[0] as keyof ClientEvents, entry[1].bind(this))
|
||||
})
|
||||
this._decoratedEvents = undefined
|
||||
}
|
||||
|
||||
this.clientProperties =
|
||||
options.clientProperties === undefined
|
||||
? {
|
||||
os: Deno.build.os,
|
||||
browser: 'harmony',
|
||||
device: 'harmony'
|
||||
}
|
||||
: options.clientProperties
|
||||
|
||||
if (options.shard !== undefined) this.shard = options.shard
|
||||
if (options.shardCount !== undefined) this.shardCount = options.shardCount
|
||||
|
||||
this.fetchGatewayInfo = options.fetchGatewayInfo ?? true
|
||||
|
||||
if (this.token === undefined) {
|
||||
try {
|
||||
const token = Deno.env.get('DISCORD_TOKEN')
|
||||
if (token !== undefined) {
|
||||
this.token = token
|
||||
this.debug('Info', 'Found token in ENV')
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
const restOptions: RESTOptions = {
|
||||
token: () => this.token,
|
||||
tokenType: TokenType.Bot,
|
||||
canary: options.canary,
|
||||
client: this
|
||||
}
|
||||
|
||||
if (options.restOptions !== undefined)
|
||||
Object.assign(restOptions, options.restOptions)
|
||||
this.rest = new RESTManager(restOptions)
|
||||
|
||||
this.slash = new SlashClient({
|
||||
id: () => this.getEstimatedID(),
|
||||
client: this,
|
||||
enabled: options.enableSlash
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets Cache Adapter
|
||||
*
|
||||
* Should NOT be set after bot is already logged in or using current cache.
|
||||
* Please look into using `cache` option.
|
||||
*/
|
||||
setAdapter(adapter: ICacheAdapter): Client {
|
||||
this.cache = adapter
|
||||
return this
|
||||
}
|
||||
|
||||
/** Changes Presence of Client */
|
||||
setPresence(presence: ClientPresence | ClientActivity | ActivityGame): void {
|
||||
if (presence instanceof ClientPresence) {
|
||||
this.presence = presence
|
||||
} else this.presence = new ClientPresence(presence)
|
||||
this.gateway?.sendPresence(this.presence.create())
|
||||
}
|
||||
|
||||
/** Emits debug event */
|
||||
debug(tag: string, msg: string): void {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.emit('debug', `[${tag}] ${msg}`)
|
||||
}
|
||||
|
||||
getEstimatedID(): string {
|
||||
if (this.user !== undefined) return this.user.id
|
||||
else if (this.token !== undefined) {
|
||||
try {
|
||||
return atob(this.token.split('.')[0])
|
||||
} catch (e) {
|
||||
return this._id ?? 'unknown'
|
||||
}
|
||||
} else {
|
||||
return this._id ?? 'unknown'
|
||||
}
|
||||
}
|
||||
|
||||
/** Fetch Application of the Client */
|
||||
async fetchApplication(): Promise<Application> {
|
||||
const app = await this.rest.api.oauth2.applications['@me'].get()
|
||||
return new Application(this, app)
|
||||
}
|
||||
|
||||
/** Fetch an Invite */
|
||||
async fetchInvite(id: string): Promise<Invite> {
|
||||
return await new Promise((resolve, reject) => {
|
||||
this.rest
|
||||
.get(INVITE(id))
|
||||
.then((data) => {
|
||||
resolve(new Invite(this, data))
|
||||
})
|
||||
.catch((e) => reject(e))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used for connecting to discord.
|
||||
* @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.
|
||||
*/
|
||||
async connect(token?: string, intents?: GatewayIntents[]): Promise<Client> {
|
||||
token ??= this.token
|
||||
if (token === undefined) throw new Error('No Token Provided')
|
||||
this.token = token
|
||||
if (intents !== undefined && this.intents !== undefined) {
|
||||
this.debug(
|
||||
'client',
|
||||
'Intents were set in both client and connect function. Using the one in the connect function...'
|
||||
)
|
||||
} else if (intents === undefined && this.intents !== undefined) {
|
||||
intents = this.intents
|
||||
} else if (intents !== undefined && this.intents === undefined) {
|
||||
this.intents = intents
|
||||
} else throw new Error('No Gateway Intents were provided')
|
||||
|
||||
this.rest.token = token
|
||||
if (this.shard !== undefined) {
|
||||
if (typeof this.shardCount === 'number')
|
||||
this.shards.cachedShardCount = this.shardCount
|
||||
await this.shards.launch(this.shard)
|
||||
} else await this.shards.connect()
|
||||
return this.waitFor('ready', () => true).then(() => this)
|
||||
}
|
||||
|
||||
/** Destroy the Gateway connection */
|
||||
async destroy(): Promise<Client> {
|
||||
this.gateway.initialized = false
|
||||
this.gateway.sequenceID = undefined
|
||||
this.gateway.sessionID = undefined
|
||||
await this.gateway.cache.delete('seq')
|
||||
await this.gateway.cache.delete('session_id')
|
||||
this.gateway.close()
|
||||
this.user = undefined
|
||||
this.upSince = undefined
|
||||
return this
|
||||
}
|
||||
|
||||
/** Attempt to Close current Gateway connection and Resume */
|
||||
async reconnect(): Promise<Client> {
|
||||
this.gateway.close()
|
||||
this.gateway.initWebsocket()
|
||||
return this.waitFor('ready', () => true).then(() => this)
|
||||
}
|
||||
|
||||
/** Add a new Collector */
|
||||
addCollector(collector: Collector): boolean {
|
||||
if (this.collectors.has(collector)) return false
|
||||
else {
|
||||
this.collectors.add(collector)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/** Remove a Collector */
|
||||
removeCollector(collector: Collector): boolean {
|
||||
if (!this.collectors.has(collector)) return false
|
||||
else {
|
||||
this.collectors.delete(collector)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
async emit(event: keyof ClientEvents, ...args: any[]): Promise<void> {
|
||||
const collectors: Collector[] = []
|
||||
for (const collector of this.collectors.values()) {
|
||||
if (collector.event === event) collectors.push(collector)
|
||||
}
|
||||
if (collectors.length !== 0) {
|
||||
this.collectors.forEach((collector) => collector._fire(...args))
|
||||
}
|
||||
// TODO(DjDeveloperr): Fix this ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
|
||||
// @ts-ignore
|
||||
return super.emit(event, ...args)
|
||||
}
|
||||
|
||||
/** Returns an array of voice region objects that can be used when creating servers. */
|
||||
async fetchVoiceRegions(): Promise<VoiceRegion[]> {
|
||||
return this.rest.api.voice.regions.get()
|
||||
}
|
||||
|
||||
/** Modify current (Client) User. */
|
||||
async editUser(data: {
|
||||
username?: string
|
||||
avatar?: string
|
||||
}): Promise<Client> {
|
||||
if (data.username === undefined && data.avatar === undefined)
|
||||
throw new Error(
|
||||
'Either username or avatar or both must be specified to edit'
|
||||
)
|
||||
|
||||
if (data.avatar?.startsWith('http') === true) {
|
||||
data.avatar = await fetchAuto(data.avatar)
|
||||
}
|
||||
|
||||
await this.rest.api.users['@me'].patch({
|
||||
username: data.username,
|
||||
avatar: data.avatar
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
/** Change Username of the Client User */
|
||||
async setUsername(username: string): Promise<Client> {
|
||||
return await this.editUser({ username })
|
||||
}
|
||||
|
||||
/** Change Avatar of the Client User */
|
||||
async setAvatar(avatar: string): Promise<Client> {
|
||||
return await this.editUser({ avatar })
|
||||
}
|
||||
|
||||
/** Create a DM Channel with a User */
|
||||
async createDM(user: User | string): Promise<DMChannel> {
|
||||
const id = typeof user === 'object' ? user.id : user
|
||||
const dmPayload = await this.rest.api.users['@me'].channels.post({
|
||||
recipient_id: id
|
||||
})
|
||||
await this.channels.set(dmPayload.id, dmPayload)
|
||||
return (this.channels.get<DMChannel>(dmPayload.id) as unknown) as DMChannel
|
||||
}
|
||||
|
||||
/** Returns a template object for the given code. */
|
||||
async fetchTemplate(code: string): Promise<Template> {
|
||||
const payload = await this.rest.api.guilds.templates[code].get()
|
||||
return new Template(this, payload)
|
||||
}
|
||||
}
|
||||
|
||||
/** Event decorator to create an Event handler from function */
|
||||
export function event(name?: keyof ClientEvents) {
|
||||
return function (
|
||||
client: Client | Extension,
|
||||
prop: keyof ClientEvents | string
|
||||
) {
|
||||
const listener = ((client as unknown) as {
|
||||
[name in keyof ClientEvents]: (...args: ClientEvents[name]) => any
|
||||
})[(prop as unknown) as keyof ClientEvents]
|
||||
if (typeof listener !== 'function')
|
||||
throw new Error('@event decorator requires a function')
|
||||
if (client._decoratedEvents === undefined) client._decoratedEvents = {}
|
||||
const key = name === undefined ? prop : name
|
||||
|
||||
client._decoratedEvents[key] = listener
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
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'
|
||||
|
||||
export type CollectorFilter = (...args: any[]) => boolean | Promise<boolean>
|
3
src/client/mod.ts
Normal file
3
src/client/mod.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export * from './client.ts'
|
||||
export * from './collectors.ts'
|
||||
export * from './shard.ts'
|
|
@ -1,7 +1,7 @@
|
|||
import { Collection } from '../utils/collection.ts'
|
||||
import type { Client } from './client.ts'
|
||||
import { RESTManager } from './rest.ts'
|
||||
import { Gateway } from '../gateway/index.ts'
|
||||
import { RESTManager } from '../rest/mod.ts'
|
||||
import { Gateway } from '../gateway/mod.ts'
|
||||
import { HarmonyEventEmitter } from '../utils/events.ts'
|
||||
import { GatewayEvents } from '../types/gateway.ts'
|
||||
import { delay } from '../utils/delay.ts'
|
||||
|
@ -61,10 +61,24 @@ export class ShardManager extends HarmonyEventEmitter<ShardManagerEvents> {
|
|||
let shardCount: number
|
||||
if (this.cachedShardCount !== undefined) shardCount = this.cachedShardCount
|
||||
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()
|
||||
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
|
||||
} else shardCount = this.client.shardCount ?? 1
|
||||
} else
|
||||
shardCount =
|
||||
typeof this.client.shardCount === 'string'
|
||||
? 1
|
||||
: this.client.shardCount ?? 1
|
||||
}
|
||||
this.cachedShardCount = shardCount
|
||||
return this.cachedShardCount
|
|
@ -1,6 +1,6 @@
|
|||
import { Message } from '../structures/message.ts'
|
||||
import { GuildTextBasedChannel } from '../structures/guildTextChannel.ts'
|
||||
import { Client, ClientOptions } from './client.ts'
|
||||
import type { Message } from '../structures/message.ts'
|
||||
import type { GuildTextBasedChannel } from '../structures/guildTextChannel.ts'
|
||||
import { Client, ClientOptions } from '../client/mod.ts'
|
||||
import {
|
||||
CategoriesManager,
|
||||
Command,
|
||||
|
@ -9,7 +9,7 @@ import {
|
|||
CommandsManager,
|
||||
parseCommand
|
||||
} from './command.ts'
|
||||
import { Extension, ExtensionsManager } from './extensions.ts'
|
||||
import { Extension, ExtensionsManager } from './extension.ts'
|
||||
|
||||
type PrefixReturnType = string | string[] | Promise<string | string[]>
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import { Guild } from '../structures/guild.ts'
|
||||
import { Message } from '../structures/message.ts'
|
||||
import { TextChannel } from '../structures/textChannel.ts'
|
||||
import { User } from '../structures/user.ts'
|
||||
import type { Guild } from '../structures/guild.ts'
|
||||
import type { Message } from '../structures/message.ts'
|
||||
import type { TextChannel } from '../structures/textChannel.ts'
|
||||
import type { User } from '../structures/user.ts'
|
||||
import { Collection } from '../utils/collection.ts'
|
||||
import { CommandClient } from './commandClient.ts'
|
||||
import { Extension } from './extensions.ts'
|
||||
import type { CommandClient } from './client.ts'
|
||||
import type { Extension } from './extension.ts'
|
||||
import { join, walk } from '../../deps.ts'
|
||||
|
||||
export interface CommandContext {
|
||||
|
@ -72,6 +72,8 @@ export interface CommandOptions {
|
|||
}
|
||||
|
||||
export class Command implements CommandOptions {
|
||||
static meta?: CommandOptions
|
||||
|
||||
name: string = ''
|
||||
description?: string
|
||||
category?: string
|
||||
|
@ -486,12 +488,16 @@ export class CommandsManager {
|
|||
|
||||
/** Add a Command */
|
||||
add(cmd: Command | typeof Command): boolean {
|
||||
// eslint-disable-next-line new-cap
|
||||
if (!(cmd instanceof Command)) cmd = new cmd()
|
||||
if (!(cmd instanceof Command)) {
|
||||
const CmdClass = cmd
|
||||
cmd = new CmdClass()
|
||||
Object.assign(cmd, CmdClass.meta ?? {})
|
||||
}
|
||||
if (this.exists(cmd, cmd.extension?.subPrefix))
|
||||
throw new Error(
|
||||
`Failed to add Command '${cmd.toString()}' with name/alias already exists.`
|
||||
)
|
||||
if (cmd.name === '') throw new Error('Command has no name')
|
||||
this.list.set(
|
||||
`${cmd.name}-${
|
||||
this.list.filter((e) =>
|
|
@ -1,7 +1,7 @@
|
|||
import { ClientEvents } from '../../mod.ts'
|
||||
import { Collection } from '../utils/collection.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
|
||||
|
3
src/commands/mod.ts
Normal file
3
src/commands/mod.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export * from './client.ts'
|
||||
export * from './command.ts'
|
||||
export * from './extension.ts'
|
|
@ -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
|
|
@ -1,6 +1,6 @@
|
|||
import { SlashCommand } from '../../models/slashClient.ts'
|
||||
import { SlashCommand } from '../../interactions/slashCommand.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 (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { SlashCommand } from '../../models/slashClient.ts'
|
||||
import { SlashCommand } from '../../interactions/slashCommand.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 (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { SlashCommand } from '../../models/slashClient.ts'
|
||||
import { SlashCommand } from '../../interactions/slashCommand.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 (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import getChannelByType from '../../utils/getChannelByType.ts'
|
||||
import { ChannelPayload, GuildChannelPayload } from '../../types/channel.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import getChannelByType from '../../utils/channel.ts'
|
||||
import type {
|
||||
ChannelPayload,
|
||||
GuildChannelPayload
|
||||
} from '../../types/channel.ts'
|
||||
import type { Guild } from '../../structures/guild.ts'
|
||||
|
||||
export const channelCreate: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import { ChannelPayload } from '../../types/channel.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import type { ChannelPayload } from '../../types/channel.ts'
|
||||
|
||||
export const channelDelete: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import { TextChannel } from '../../structures/textChannel.ts'
|
||||
import { ChannelPinsUpdatePayload } from '../../types/gateway.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import type { TextChannel } from '../../structures/textChannel.ts'
|
||||
import type { ChannelPinsUpdatePayload } from '../../types/gateway.ts'
|
||||
|
||||
export const channelPinsUpdate: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Channel } from '../../structures/channel.ts'
|
||||
import { ChannelPayload } from '../../types/channel.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Channel } from '../../structures/channel.ts'
|
||||
import type { ChannelPayload } from '../../types/channel.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const channelUpdate: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { User } from '../../structures/user.ts'
|
||||
import { GuildBanAddPayload } from '../../types/gateway.ts'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { User } from '../../structures/user.ts'
|
||||
import { GuildBanRemovePayload } from '../../types/gateway.ts'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { GuildPayload } from '../../types/guild.ts'
|
||||
import { GuildChannelPayload } from '../../types/channel.ts'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Guild } from '../../structures/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 (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Emoji } from '../../structures/emoji.ts'
|
|||
import { Guild } from '../../structures/guild.ts'
|
||||
import { EmojiPayload } from '../../types/emoji.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 (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { GuildIntegrationsUpdatePayload } from '../../types/gateway.ts'
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { GuildMemberAddPayload } from '../../types/gateway.ts'
|
||||
import { Member } from '../../structures/member.ts'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { User } from '../../structures/user.ts'
|
||||
import { GuildMemberRemovePayload } from '../../types/gateway.ts'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { GuildMemberUpdatePayload } from '../../types/gateway.ts'
|
||||
import { MemberPayload } from '../../types/guild.ts'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { GuildMemberChunkPayload } from '../../types/gateway.ts'
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { GuildRoleCreatePayload } from '../../types/gateway.ts'
|
||||
import { Role } from '../../structures/role.ts'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { GuildRoleDeletePayload } from '../../types/gateway.ts'
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { GuildRoleUpdatePayload } from '../../types/gateway.ts'
|
||||
import { Role } from '../../structures/role.ts'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { GuildPayload } from '../../types/guild.ts'
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import { GuildTextBasedChannel } from '../../structures/guildTextChannel.ts'
|
|||
import { InteractionPayload } from '../../types/slash.ts'
|
||||
import { UserPayload } from '../../types/user.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 { Role } from '../../structures/role.ts'
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { InviteCreatePayload } from '../../types/gateway.ts'
|
||||
import { ChannelPayload } from '../../types/channel.ts'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { InviteDeletePayload } from '../../types/gateway.ts'
|
||||
import { PartialInvitePayload } from '../../types/invite.ts'
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
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 { MessagePayload } from '../../types/channel.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { MessagePayload } from '../../types/channel.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const messageCreate: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { TextChannel } from '../../structures/textChannel.ts'
|
||||
import { MessageDeletePayload } from '../../types/gateway.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { TextChannel } from '../../structures/textChannel.ts'
|
||||
import type { MessageDeletePayload } from '../../types/gateway.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const messageDelete: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Message } from '../../structures/message.ts'
|
||||
import { GuildTextBasedChannel } from '../../structures/guildTextChannel.ts'
|
||||
import { MessageDeleteBulkPayload } from '../../types/gateway.ts'
|
||||
import type { Message } from '../../structures/message.ts'
|
||||
import type { GuildTextBasedChannel } from '../../structures/guildTextChannel.ts'
|
||||
import type { MessageDeleteBulkPayload } from '../../types/gateway.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 (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import { MessageReactionAddPayload } from '../../types/gateway.ts'
|
||||
import { TextChannel } from '../../structures/textChannel.ts'
|
||||
import { MessageReaction } from '../../structures/messageReaction.ts'
|
||||
import { UserPayload } from '../../types/user.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import type { MessageReactionAddPayload } from '../../types/gateway.ts'
|
||||
import type { TextChannel } from '../../structures/textChannel.ts'
|
||||
import type { MessageReaction } from '../../structures/messageReaction.ts'
|
||||
import type { UserPayload } from '../../types/user.ts'
|
||||
|
||||
export const messageReactionAdd: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import { MessageReactionRemovePayload } from '../../types/gateway.ts'
|
||||
import { TextChannel } from '../../structures/textChannel.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import type { MessageReactionRemovePayload } from '../../types/gateway.ts'
|
||||
import type { TextChannel } from '../../structures/textChannel.ts'
|
||||
|
||||
export const messageReactionRemove: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import { MessageReactionRemoveAllPayload } from '../../types/gateway.ts'
|
||||
import { TextChannel } from '../../structures/textChannel.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import type { MessageReactionRemoveAllPayload } from '../../types/gateway.ts'
|
||||
import type { TextChannel } from '../../structures/textChannel.ts'
|
||||
|
||||
export const messageReactionRemoveAll: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import { MessageReactionRemoveEmojiPayload } from '../../types/gateway.ts'
|
||||
import { TextChannel } from '../../structures/textChannel.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import type { MessageReactionRemoveEmojiPayload } from '../../types/gateway.ts'
|
||||
import type { TextChannel } from '../../structures/textChannel.ts'
|
||||
|
||||
export const messageReactionRemoveEmoji: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Message } from '../../structures/message.ts'
|
||||
import { TextChannel } from '../../structures/textChannel.ts'
|
||||
import { MessagePayload } from '../../types/channel.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Message } from '../../structures/message.ts'
|
||||
import type { TextChannel } from '../../structures/textChannel.ts'
|
||||
import type { MessagePayload } from '../../types/channel.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const messageUpdate: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { GatewayEventHandler } from '../index.ts'
|
||||
import {
|
||||
import type { GatewayEventHandler } from '../mod.ts'
|
||||
import type {
|
||||
GatewayEvents,
|
||||
MessageDeletePayload,
|
||||
TypingStartGuildData
|
||||
|
@ -31,18 +31,18 @@ import { webhooksUpdate } from './webhooksUpdate.ts'
|
|||
import { messageDeleteBulk } from './messageDeleteBulk.ts'
|
||||
import { userUpdate } from './userUpdate.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 { Guild } from '../../structures/guild.ts'
|
||||
import { User } from '../../structures/user.ts'
|
||||
import { Emoji } from '../../structures/emoji.ts'
|
||||
import { Member } from '../../structures/member.ts'
|
||||
import { Role } from '../../structures/role.ts'
|
||||
import { Message } from '../../structures/message.ts'
|
||||
import { Collection } from '../../utils/collection.ts'
|
||||
import type { Guild } from '../../structures/guild.ts'
|
||||
import type { User } from '../../structures/user.ts'
|
||||
import type { Emoji } from '../../structures/emoji.ts'
|
||||
import type { Member } from '../../structures/member.ts'
|
||||
import type { Role } from '../../structures/role.ts'
|
||||
import type { Message } from '../../structures/message.ts'
|
||||
import type { Collection } from '../../utils/collection.ts'
|
||||
import { voiceServerUpdate } from './voiceServerUpdate.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 { messageReactionRemove } from './messageReactionRemove.ts'
|
||||
import { messageReactionRemoveAll } from './messageReactionRemoveAll.ts'
|
||||
|
@ -51,23 +51,23 @@ import { guildMembersChunk } from './guildMembersChunk.ts'
|
|||
import { presenceUpdate } from './presenceUpdate.ts'
|
||||
import { inviteCreate } from './inviteCreate.ts'
|
||||
import { inviteDelete } from './inviteDelete.ts'
|
||||
import { MessageReaction } from '../../structures/messageReaction.ts'
|
||||
import { Invite } from '../../structures/invite.ts'
|
||||
import { Presence } from '../../structures/presence.ts'
|
||||
import {
|
||||
import type { MessageReaction } from '../../structures/messageReaction.ts'
|
||||
import type { Invite } from '../../structures/invite.ts'
|
||||
import type { Presence } from '../../structures/presence.ts'
|
||||
import type {
|
||||
EveryChannelTypes,
|
||||
EveryTextChannelTypes
|
||||
} from '../../utils/getChannelByType.ts'
|
||||
} from '../../utils/channel.ts'
|
||||
import { interactionCreate } from './interactionCreate.ts'
|
||||
import { Interaction } from '../../structures/slash.ts'
|
||||
import { CommandContext } from '../../models/command.ts'
|
||||
import { RequestMethods } from '../../models/rest.ts'
|
||||
import { PartialInvitePayload } from '../../types/invite.ts'
|
||||
import { GuildChannels } from '../../types/guild.ts'
|
||||
import type { Interaction } from '../../structures/slash.ts'
|
||||
import type { CommandContext } from '../../commands/command.ts'
|
||||
import type { RequestMethods } from '../../rest/types.ts'
|
||||
import type { PartialInvitePayload } from '../../types/invite.ts'
|
||||
import type { GuildChannels } from '../../types/guild.ts'
|
||||
import { applicationCommandCreate } from './applicationCommandCreate.ts'
|
||||
import { applicationCommandDelete } from './applicationCommandDelete.ts'
|
||||
import { applicationCommandUpdate } from './applicationCommandUpdate.ts'
|
||||
import { SlashCommand } from '../../models/slashClient.ts'
|
||||
import type { SlashCommand } from '../../interactions/slashCommand.ts'
|
||||
|
||||
export const gatewayHandlers: {
|
||||
[eventCode in GatewayEvents]: GatewayEventHandler | undefined
|
||||
|
@ -393,7 +393,15 @@ export type ClientEvents = {
|
|||
}
|
||||
]
|
||||
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]
|
||||
voiceStateRemoveUncached: [data: { guild: Guild; member: Member }]
|
||||
userUpdateUncached: [user: User]
|
|
@ -1,5 +1,5 @@
|
|||
import { PresenceUpdatePayload } from '../../types/gateway.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { PresenceUpdatePayload } from '../../types/gateway.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const presenceUpdate: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { User } from '../../structures/user.ts'
|
||||
import { Ready } from '../../types/gateway.ts'
|
||||
import { GuildPayload } from '../../types/guild.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Ready } from '../../types/gateway.ts'
|
||||
import type { GuildPayload } from '../../types/guild.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const ready: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const reconnect: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { User } from '../../structures/user.ts'
|
||||
import { CLIENT_USER } from '../../types/endpoint.ts'
|
||||
import { Resume } from '../../types/gateway.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Resume } from '../../types/gateway.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const resume: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Member } from '../../structures/member.ts'
|
||||
import { TextChannel } from '../../structures/textChannel.ts'
|
||||
import { TypingStartPayload } from '../../types/gateway.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { TextChannel } from '../../structures/textChannel.ts'
|
||||
import type { TypingStartPayload } from '../../types/gateway.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
// TODO: Do we need to add uncached events here?
|
||||
export const typingStart: GatewayEventHandler = async (
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { User } from '../../structures/user.ts'
|
||||
import { UserPayload } from '../../types/user.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { User } from '../../structures/user.ts'
|
||||
import type { UserPayload } from '../../types/user.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const userUpdate: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Guild } from '../../structures/guild.ts'
|
||||
import { VoiceServerUpdatePayload } from '../../types/gateway.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Guild } from '../../structures/guild.ts'
|
||||
import type { VoiceServerUpdatePayload } from '../../types/gateway.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const voiceServerUpdate: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Guild } from '../../structures/guild.ts'
|
||||
import { VoiceState } from '../../structures/voiceState.ts'
|
||||
import { MemberPayload } from '../../types/guild.ts'
|
||||
import { VoiceStatePayload } from '../../types/voice.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Guild } from '../../structures/guild.ts'
|
||||
import type { VoiceState } from '../../structures/voiceState.ts'
|
||||
import type { MemberPayload } from '../../types/guild.ts'
|
||||
import type { VoiceStatePayload } from '../../types/voice.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const voiceStateUpdate: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { WebhooksUpdatePayload } from '../../types/gateway.ts'
|
||||
import { GuildTextBasedChannel } from '../../structures/guildTextChannel.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import type { Guild } from '../../structures/guild.ts'
|
||||
import type { WebhooksUpdatePayload } from '../../types/gateway.ts'
|
||||
import type { GuildTextBasedChannel } from '../../structures/guildTextChannel.ts'
|
||||
|
||||
export const webhooksUpdate: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
import { unzlib } from '../../deps.ts'
|
||||
import { Client } from '../models/client.ts'
|
||||
import {
|
||||
DISCORD_GATEWAY_URL,
|
||||
DISCORD_API_VERSION
|
||||
} from '../consts/urlsAndVersions.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import { GatewayResponse } from '../types/gatewayResponse.ts'
|
||||
import {
|
||||
GatewayOpcodes,
|
||||
|
@ -12,13 +8,14 @@ import {
|
|||
StatusUpdatePayload,
|
||||
GatewayEvents
|
||||
} from '../types/gateway.ts'
|
||||
import { gatewayHandlers } from './handlers/index.ts'
|
||||
import { gatewayHandlers } from './handlers/mod.ts'
|
||||
import { GatewayCache } from '../managers/gatewayCache.ts'
|
||||
import { delay } from '../utils/delay.ts'
|
||||
import { VoiceChannel } from '../structures/guildVoiceChannel.ts'
|
||||
import { Guild } from '../structures/guild.ts'
|
||||
import type { VoiceChannel } from '../structures/guildVoiceChannel.ts'
|
||||
import type { Guild } from '../structures/guild.ts'
|
||||
import { HarmonyEventEmitter } from '../utils/events.ts'
|
||||
import { decodeText } from '../utils/encoding.ts'
|
||||
import { Constants } from '../types/constants.ts'
|
||||
|
||||
export interface RequestMembersOptions {
|
||||
limit?: number
|
||||
|
@ -269,21 +266,6 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
|
|||
if (typeof this.client.intents !== 'object')
|
||||
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) {
|
||||
const sessionIDCached = await this.cache.get(
|
||||
`session_id_${this.shards?.join('-') ?? '0'}`
|
||||
|
@ -417,7 +399,7 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
|
|||
this.debug('Initializing WebSocket...')
|
||||
this.websocket = new WebSocket(
|
||||
// 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'
|
3
src/interactions/mod.ts
Normal file
3
src/interactions/mod.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export * from './slashClient.ts'
|
||||
export * from './slashModule.ts'
|
||||
export * from './slashCommand.ts'
|
|
@ -1,4 +1,3 @@
|
|||
import type { Guild } from '../structures/guild.ts'
|
||||
import {
|
||||
Interaction,
|
||||
InteractionApplicationCommandResolved
|
||||
|
@ -7,358 +6,16 @@ import {
|
|||
InteractionPayload,
|
||||
InteractionResponsePayload,
|
||||
InteractionType,
|
||||
SlashCommandChoice,
|
||||
SlashCommandOption,
|
||||
SlashCommandOptionType,
|
||||
SlashCommandPartial,
|
||||
SlashCommandPayload
|
||||
SlashCommandOptionType
|
||||
} from '../types/slash.ts'
|
||||
import { Collection } from '../utils/collection.ts'
|
||||
import type { Client } from './client.ts'
|
||||
import { RESTManager } from './rest.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import { RESTManager } from '../rest/mod.ts'
|
||||
import { SlashModule } from './slashModule.ts'
|
||||
import { verify as edverify } from 'https://deno.land/x/ed25519@1.0.1/mod.ts'
|
||||
import { User } from '../structures/user.ts'
|
||||
import { HarmonyEventEmitter } from '../utils/events.ts'
|
||||
import { encodeText, decodeText } from '../utils/encoding.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
|
||||
}
|
||||
}
|
||||
import { SlashCommandsManager } from './slashCommand.ts'
|
||||
|
||||
export type SlashCommandHandlerCallback = (interaction: Interaction) => unknown
|
||||
export interface SlashCommandHandler {
|
||||
|
@ -455,8 +112,38 @@ export class SlashClient extends HarmonyEventEmitter<SlashClientEvents> {
|
|||
}
|
||||
|
||||
/** Adds a new Slash Command Handler */
|
||||
handle(handler: SlashCommandHandler): SlashClient {
|
||||
this.handlers.push(handler)
|
||||
handle(
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -523,7 +210,9 @@ export class SlashClient extends HarmonyEventEmitter<SlashClientEvents> {
|
|||
)
|
||||
return
|
||||
|
||||
const cmd = this._getCommand(interaction)
|
||||
const cmd =
|
||||
this._getCommand(interaction) ??
|
||||
this.getHandlers().find((e) => e.name === '*')
|
||||
if (cmd?.group !== undefined)
|
||||
interaction.data.options = interaction.data.options[0].options ?? []
|
||||
if (cmd?.parent !== undefined)
|
||||
|
@ -564,7 +253,7 @@ export class SlashClient extends HarmonyEventEmitter<SlashClientEvents> {
|
|||
respond: (options: {
|
||||
status?: number
|
||||
headers?: Headers
|
||||
body?: string | Uint8Array | FormData
|
||||
body?: any
|
||||
}) => Promise<void>
|
||||
}): Promise<false | Interaction> {
|
||||
if (req.method.toLowerCase() !== 'post') return false
|
349
src/interactions/slashCommand.ts
Normal file
349
src/interactions/slashCommand.ts
Normal 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
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { SlashCommandHandler } from './slashClient.ts'
|
||||
import type { SlashCommandHandler } from './slashClient.ts'
|
||||
|
||||
export class SlashModule {
|
||||
name: string = ''
|
|
@ -1,4 +1,4 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import { Collection } from '../utils/collection.ts'
|
||||
|
||||
/**
|
||||
|
@ -63,7 +63,9 @@ export class BaseManager<T, T2> {
|
|||
async *[Symbol.asyncIterator](): AsyncIterableIterator<T2> {
|
||||
const arr = (await this.array()) ?? []
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import { Collection } from '../utils/collection.ts'
|
||||
import { BaseManager } from './base.ts'
|
||||
|
||||
|
@ -43,7 +43,9 @@ export class BaseChildManager<T, T2> {
|
|||
async *[Symbol.asyncIterator](): AsyncIterableIterator<T2> {
|
||||
const arr = (await this.array()) ?? []
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import { Client } from '../client/mod.ts'
|
||||
import { Channel } from '../structures/channel.ts'
|
||||
import { Embed } from '../structures/embed.ts'
|
||||
import { Message } from '../structures/message.ts'
|
||||
import { TextChannel } from '../structures/textChannel.ts'
|
||||
import {
|
||||
import type { TextChannel } from '../structures/textChannel.ts'
|
||||
import type {
|
||||
ChannelPayload,
|
||||
GuildChannelPayload,
|
||||
MessageOptions
|
||||
} from '../types/channel.ts'
|
||||
import { CHANNEL } from '../types/endpoint.ts'
|
||||
import getChannelByType from '../utils/getChannelByType.ts'
|
||||
import getChannelByType from '../utils/channel.ts'
|
||||
import { BaseManager } from './base.ts'
|
||||
|
||||
export type AllMessageOptions = MessageOptions | Embed
|
||||
|
@ -121,6 +121,10 @@ export class ChannelsManager extends BaseManager<ChannelPayload, Channel> {
|
|||
: undefined
|
||||
}
|
||||
|
||||
if (payload.content === undefined && payload.embed === undefined) {
|
||||
payload.content = ''
|
||||
}
|
||||
|
||||
const resp = await this.client.rest.api.channels[channelID].messages.post(
|
||||
payload
|
||||
)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import type { Client } from '../client/mod.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 { BaseManager } from './base.ts'
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import type { Client } from '../client/mod.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 { GuildVoiceStatesManager } from './guildVoiceStates.ts'
|
||||
import { VoiceChannel } from '../structures/guildVoiceChannel.ts'
|
||||
import type { VoiceChannel } from '../structures/guildVoiceChannel.ts'
|
||||
|
||||
export class GuildChannelVoiceStatesManager extends BaseChildManager<
|
||||
VoiceStatePayload,
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import { Channel } from '../structures/channel.ts'
|
||||
import { Guild } from '../structures/guild.ts'
|
||||
import { CategoryChannel } from '../structures/guildCategoryChannel.ts'
|
||||
import type { CategoryChannel } from '../structures/guildCategoryChannel.ts'
|
||||
import {
|
||||
ChannelTypes,
|
||||
GuildChannelPayload,
|
||||
OverwritePayload
|
||||
} 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 { BaseChildManager } from './baseChild.ts'
|
||||
import { ChannelsManager } from './channels.ts'
|
||||
import type { ChannelsManager } from './channels.ts'
|
||||
|
||||
export interface CreateChannelOptions {
|
||||
name: string
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import type { Client } from '../client/mod.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 { EmojiPayload } from '../types/emoji.ts'
|
||||
import type { EmojiPayload } from '../types/emoji.ts'
|
||||
import { CHANNEL, GUILD_EMOJI, GUILD_EMOJIS } from '../types/endpoint.ts'
|
||||
import { BaseChildManager } from './baseChild.ts'
|
||||
import { EmojisManager } from './emojis.ts'
|
||||
import type { EmojisManager } from './emojis.ts'
|
||||
import { fetchAuto } from '../../deps.ts'
|
||||
|
||||
export class GuildEmojisManager extends BaseChildManager<EmojiPayload, Emoji> {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import { Guild } from '../structures/guild.ts'
|
||||
import { VoiceChannel } from '../structures/guildVoiceChannel.ts'
|
||||
import { User } from '../structures/user.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import type { Guild } from '../structures/guild.ts'
|
||||
import type { VoiceChannel } from '../structures/guildVoiceChannel.ts'
|
||||
import type { User } from '../structures/user.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'
|
||||
|
||||
export class GuildVoiceStatesManager extends BaseManager<
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
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 { Template } from '../structures/template.ts'
|
||||
import type { Template } from '../structures/template.ts'
|
||||
import { Role } from '../structures/role.ts'
|
||||
import { GUILD, GUILDS, GUILD_PREVIEW } from '../types/endpoint.ts'
|
||||
import {
|
||||
import type {
|
||||
GuildPayload,
|
||||
MemberPayload,
|
||||
GuildCreateRolePayload,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { GuildTextChannel, User } from '../../mod.ts'
|
||||
import { Client } from '../models/client.ts'
|
||||
import { Guild } from '../structures/guild.ts'
|
||||
import type { GuildTextChannel, User } from '../../mod.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import type { Guild } from '../structures/guild.ts'
|
||||
import { Invite } from '../structures/invite.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'
|
||||
|
||||
export enum InviteTargetUserType {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import type { Client } from '../client/mod.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 { Member } from '../structures/member.ts'
|
||||
import { RolesManager } from './roles.ts'
|
||||
import { MemberPayload } from '../types/guild.ts'
|
||||
import type { Member } from '../structures/member.ts'
|
||||
import type { RolesManager } from './roles.ts'
|
||||
import type { MemberPayload } from '../types/guild.ts'
|
||||
import { GUILD_MEMBER_ROLE } from '../types/endpoint.ts'
|
||||
|
||||
export class MemberRolesManager extends BaseChildManager<RolePayload, Role> {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { User } from '../structures/user.ts'
|
||||
import { Client } from '../models/client.ts'
|
||||
import { Guild } from '../structures/guild.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import type { Guild } from '../structures/guild.ts'
|
||||
import { Member } from '../structures/member.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 { Permissions } from '../utils/permissions.ts'
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import { Emoji } from '../structures/emoji.ts'
|
||||
import { Guild } from '../structures/guild.ts'
|
||||
import { Message } from '../structures/message.ts'
|
||||
import type { Message } from '../structures/message.ts'
|
||||
import { MessageReaction } from '../structures/messageReaction.ts'
|
||||
import { User } from '../structures/user.ts'
|
||||
import { Reaction } from '../types/channel.ts'
|
||||
import type { User } from '../structures/user.ts'
|
||||
import type { Reaction } from '../types/channel.ts'
|
||||
import {
|
||||
MESSAGE_REACTION,
|
||||
MESSAGE_REACTIONS,
|
||||
|
@ -19,7 +18,7 @@ export class MessageReactionsManager extends BaseManager<
|
|||
message: Message
|
||||
|
||||
constructor(client: Client, message: Message) {
|
||||
super(client, `reactions:${message.id}`, Guild)
|
||||
super(client, `reactions:${message.id}`, MessageReaction)
|
||||
this.message = message
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import type { Client } from '../client/mod.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 { MessagePayload } from '../types/channel.ts'
|
||||
import type { MessagePayload } from '../types/channel.ts'
|
||||
import { CHANNEL_MESSAGE } from '../types/endpoint.ts'
|
||||
import { BaseManager } from './base.ts'
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import { Guild } from '../structures/guild.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import type { Guild } from '../structures/guild.ts'
|
||||
import { Presence } from '../structures/presence.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'
|
||||
|
||||
export class GuildPresencesManager extends BaseManager<
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import { MessageReaction } from '../structures/messageReaction.ts'
|
||||
import { User } from '../structures/user.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import type { MessageReaction } from '../structures/messageReaction.ts'
|
||||
import type { User } from '../structures/user.ts'
|
||||
import { UsersManager } from './users.ts'
|
||||
|
||||
export class ReactionUsersManager extends UsersManager {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Permissions } from '../../mod.ts'
|
||||
import { Client } from '../models/client.ts'
|
||||
import { Guild } from '../structures/guild.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import type { Guild } from '../structures/guild.ts'
|
||||
import { Role } from '../structures/role.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'
|
||||
|
||||
export interface CreateGuildRoleOptions {
|
||||
|
|
|
@ -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 '../types/endpoint.ts'
|
||||
import { UserPayload } from '../types/user.ts'
|
||||
import type { UserPayload } from '../types/user.ts'
|
||||
import { BaseManager } from './base.ts'
|
||||
|
||||
export class UsersManager extends BaseManager<UserPayload, User> {
|
||||
|
|
|
@ -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
239
src/rest/bucket.ts
Normal 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
1294
src/rest/endpoints.ts
Normal file
File diff suppressed because it is too large
Load diff
44
src/rest/error.ts
Normal file
44
src/rest/error.ts
Normal 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
302
src/rest/manager.ts
Normal 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
7
src/rest/mod.ts
Normal 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
37
src/rest/queue.ts
Normal 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
132
src/rest/request.ts
Normal 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
37
src/rest/types.ts
Normal 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']
|
|
@ -1,5 +1,5 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import { ApplicationPayload } from '../types/application.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import type { ApplicationPayload } from '../types/application.ts'
|
||||
import { SnowflakeBase } from './base.ts'
|
||||
import { User } from './user.ts'
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import { Snowflake } from '../utils/snowflake.ts'
|
||||
|
||||
export class Base {
|
||||
|
|
|
@ -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 */
|
||||
export const ImageURL = (
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import {
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import type {
|
||||
ChannelPayload,
|
||||
ChannelTypes,
|
||||
ModifyChannelOption,
|
||||
ModifyChannelPayload,
|
||||
Overwrite,
|
||||
OverwritePayload,
|
||||
OverwriteAsArg,
|
||||
OverrideType
|
||||
OverwriteAsArg
|
||||
} from '../types/channel.ts'
|
||||
import { OverrideType } from '../types/channel.ts'
|
||||
import { CHANNEL } from '../types/endpoint.ts'
|
||||
import { GuildChannelPayloads, GuildChannels } from '../types/guild.ts'
|
||||
import getChannelByType from '../utils/getChannelByType.ts'
|
||||
import type { GuildChannelPayloads, GuildChannels } from '../types/guild.ts'
|
||||
import getChannelByType from '../utils/channel.ts'
|
||||
import { Permissions } from '../utils/permissions.ts'
|
||||
import { SnowflakeBase } from './base.ts'
|
||||
import { Guild } from './guild.ts'
|
||||
import type { Guild } from './guild.ts'
|
||||
import { Member } from './member.ts'
|
||||
import { Role } from './role.ts'
|
||||
|
||||
|
@ -81,7 +81,7 @@ export class GuildChannel extends Channel {
|
|||
const stringToObject =
|
||||
typeof target === 'string'
|
||||
? (await this.guild.members.get(target)) ??
|
||||
(await this.guild.roles.get(target))
|
||||
(await this.guild.roles.get(target))
|
||||
: target
|
||||
|
||||
if (stringToObject === undefined) {
|
||||
|
@ -128,7 +128,7 @@ export class GuildChannel extends Channel {
|
|||
const stringToObject =
|
||||
typeof target === 'string'
|
||||
? (await this.guild.members.get(target)) ??
|
||||
(await this.guild.roles.get(target))
|
||||
(await this.guild.roles.get(target))
|
||||
: target
|
||||
|
||||
if (stringToObject === undefined) {
|
||||
|
@ -200,8 +200,8 @@ export class GuildChannel extends Channel {
|
|||
overwrite.id instanceof Role
|
||||
? 0
|
||||
: overwrite.id instanceof Member
|
||||
? 1
|
||||
: overwrite.type
|
||||
? 1
|
||||
: overwrite.type
|
||||
if (type === undefined) {
|
||||
throw new Error('Overwrite type is undefined.')
|
||||
}
|
||||
|
@ -233,8 +233,8 @@ export class GuildChannel extends Channel {
|
|||
overwrite.id instanceof Role
|
||||
? 0
|
||||
: overwrite.id instanceof Member
|
||||
? 1
|
||||
: overwrite.type
|
||||
? 1
|
||||
: overwrite.type
|
||||
if (type === undefined) {
|
||||
throw new Error('Overwrite type is undefined.')
|
||||
}
|
||||
|
@ -303,7 +303,10 @@ export class GuildChannel extends Channel {
|
|||
: overwrite.allow?.toJSON() ?? overwrites[index].allow
|
||||
}
|
||||
|
||||
if (overwrite.deny !== undefined && overwriteDeny !== OverrideType.REPLACE) {
|
||||
if (
|
||||
overwrite.deny !== undefined &&
|
||||
overwriteDeny !== OverrideType.REPLACE
|
||||
) {
|
||||
switch (overwriteDeny) {
|
||||
case OverrideType.ADD: {
|
||||
const originalDeny = new Permissions(overwrites[index].deny)
|
||||
|
@ -331,8 +334,8 @@ export class GuildChannel extends Channel {
|
|||
overwrite.id instanceof Role
|
||||
? 0
|
||||
: overwrite.id instanceof Member
|
||||
? 1
|
||||
: overwrite.type
|
||||
? 1
|
||||
: overwrite.type
|
||||
if (type === undefined) {
|
||||
throw new Error('Overwrite type is undefined.')
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import { DMChannelPayload } from '../types/channel.ts'
|
||||
import { UserPayload } from '../types/user.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import type { DMChannelPayload } from '../types/channel.ts'
|
||||
import type { UserPayload } from '../types/user.ts'
|
||||
import { TextChannel } from './textChannel.ts'
|
||||
|
||||
export class DMChannel extends TextChannel {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {
|
||||
import type {
|
||||
EmbedAuthor,
|
||||
EmbedField,
|
||||
EmbedFooter,
|
||||
|
@ -10,7 +10,7 @@ import {
|
|||
EmbedVideo
|
||||
} from '../types/channel.ts'
|
||||
import { Colors, ColorUtil } from '../utils/colorutil.ts'
|
||||
import { MessageAttachment } from './message.ts'
|
||||
import type { MessageAttachment } from './message.ts'
|
||||
|
||||
/** Message Embed Object */
|
||||
export class Embed {
|
||||
|
@ -56,44 +56,72 @@ export class Embed {
|
|||
|
||||
/** Convert Embed Object to Embed Payload JSON */
|
||||
toJSON(): EmbedPayload {
|
||||
let total = 0;
|
||||
if (this.title?.length !== undefined && this.title?.length > Embed.MAX_TITLE_LENGTH) {
|
||||
let total = 0
|
||||
if (
|
||||
this.title?.length !== undefined &&
|
||||
this.title?.length > Embed.MAX_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)
|
||||
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) {
|
||||
this.fields.forEach((field) => {
|
||||
if (field.name.length > Embed.MAX_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) {
|
||||
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)
|
||||
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)
|
||||
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 {
|
||||
title: this.title,
|
||||
type: this.type,
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import { ImageSize } from '../types/cdn.ts'
|
||||
import { EmojiPayload } from '../types/emoji.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import type { ImageSize } from '../types/cdn.ts'
|
||||
import type { EmojiPayload } from '../types/emoji.ts'
|
||||
import { CUSTOM_EMOJI, EMOJI } from '../types/endpoint.ts'
|
||||
import { Snowflake } from '../utils/snowflake.ts'
|
||||
import { Base } from './base.ts'
|
||||
import { ImageURL } from './cdn.ts'
|
||||
import { Guild } from './guild.ts'
|
||||
import type { Guild } from './guild.ts'
|
||||
import { Role } from './role.ts'
|
||||
import { User } from './user.ts'
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import { GroupDMChannelPayload } from '../types/channel.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import type { GroupDMChannelPayload } from '../types/channel.ts'
|
||||
import { Channel } from './channel.ts'
|
||||
|
||||
export class GroupDMChannel extends Channel {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import {
|
||||
GuildBanPayload,
|
||||
GuildFeatures,
|
||||
|
@ -41,12 +41,12 @@ import {
|
|||
GUILD_SPLASH
|
||||
} from '../types/endpoint.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 { TemplatePayload } from '../types/template.ts'
|
||||
import type { TemplatePayload } from '../types/template.ts'
|
||||
import { Template } from './template.ts'
|
||||
import { DiscordAPIError } from '../models/rest.ts'
|
||||
import { ImageFormats, ImageSize } from '../types/cdn.ts'
|
||||
import { DiscordAPIError } from '../rest/mod.ts'
|
||||
import type { ImageFormats, ImageSize } from '../types/cdn.ts'
|
||||
import { ImageURL } from './cdn.ts'
|
||||
|
||||
export class GuildBan extends Base {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue