diff --git a/README.md b/README.md
index 767b21b..585d405 100644
--- a/README.md
+++ b/README.md
@@ -10,15 +10,12 @@
- Lightweight and easy to use.
-- Built-in Command Framework,
- - Easily build Commands on the fly.
- - Completely Customizable.
- - Complete Object-Oriented approach.
-- 100% Discord API Coverage.
-- Customizable caching.
- - Built in support for Redis.
- - Write Custom Cache Adapters.
-- Complete TypeScript support.
+- Complete Object-Oriented approach.
+- Slash Commands supported.
+- Built-in Commands framework.
+- Customizable Caching, with Redis support.
+- Use `@decorators` to easily make things!
+- Made with ❤️ TypeScript.
## Table of Contents
@@ -102,13 +99,14 @@ client.connect('super secret token comes here', Intents.All)
```
Or with Decorators!
+
```ts
import {
Client,
event,
Intents,
command,
- CommandContext,
+ CommandContext
} from 'https://deno.land/x/harmony/mod.ts'
class MyClient extends CommandClient {
@@ -141,6 +139,7 @@ Documentation is available for `main` (branch) and `stable` (release).
- [Main](https://doc.deno.land/https/raw.githubusercontent.com/harmony-org/harmony/main/mod.ts)
- [Stable](https://doc.deno.land/https/deno.land/x/harmony/mod.ts)
+- [Guide](https://harmony-org.github.io)
## Found a bug or want support? Join our discord server!
diff --git a/deps.ts b/deps.ts
new file mode 100644
index 0000000..3e1dd63
--- /dev/null
+++ b/deps.ts
@@ -0,0 +1,13 @@
+export { EventEmitter } from 'https://deno.land/std@0.82.0/node/events.ts'
+export { unzlib } from 'https://deno.land/x/denoflate@1.1/mod.ts'
+export { fetchAuto } from 'https://raw.githubusercontent.com/DjDeveloperr/fetch-base64/main/mod.ts'
+export { parse } from 'https://deno.land/x/mutil@0.1.2/mod.ts'
+export { connect } from 'https://deno.land/x/redis@v0.14.1/mod.ts'
+export type {
+ Redis,
+ RedisConnectOptions
+} from 'https://deno.land/x/redis@v0.14.1/mod.ts'
+export {
+ Manager,
+ Player
+} from 'https://raw.githubusercontent.com/Lavaclient/lavadeno/master/mod.ts'
diff --git a/mod.ts b/mod.ts
index 2c2e779..55bee3e 100644
--- a/mod.ts
+++ b/mod.ts
@@ -1,5 +1,4 @@
export { GatewayIntents } from './src/types/gateway.ts'
-export { default as EventEmitter } from 'https://deno.land/std@0.74.0/node/events.ts'
export { Base } from './src/structures/base.ts'
export { Gateway } from './src/gateway/index.ts'
export type { ClientEvents } from './src/gateway/handlers/index.ts'
@@ -66,7 +65,7 @@ export {
ActivityTypes
} from './src/structures/presence.ts'
export { Role } from './src/structures/role.ts'
-export { Snowflake } from './src/structures/snowflake.ts'
+export { Snowflake } from './src/utils/snowflake.ts'
export { TextChannel, GuildTextChannel } from './src/structures/textChannel.ts'
export { MessageReaction } from './src/structures/messageReaction.ts'
export { User } from './src/structures/user.ts'
diff --git a/src/gateway/handlers/guildEmojiUpdate.ts b/src/gateway/handlers/guildEmojiUpdate.ts
index 5af394f..db5e254 100644
--- a/src/gateway/handlers/guildEmojiUpdate.ts
+++ b/src/gateway/handlers/guildEmojiUpdate.ts
@@ -17,23 +17,29 @@ export const guildEmojiUpdate: GatewayEventHandler = async (
const _updated: EmojiPayload[] = []
for (const raw of d.emojis) {
- const has = emojis.get(raw.id)
+ const emojiID = raw.id !== null ? raw.id : raw.name
+ const has = emojis.get(emojiID)
if (has === undefined) {
- await guild.emojis.set(raw.id, raw)
- const emoji = (await guild.emojis.get(raw.id)) as Emoji
+ await guild.emojis.set(emojiID, raw)
+ const emoji = (await guild.emojis.get(emojiID)) as Emoji
added.push(emoji)
} else _updated.push(raw)
}
for (const emoji of emojis.values()) {
- const find = _updated.find((e) => emoji.id === e.id)
+ const emojiID = emoji.id !== null ? emoji.id : emoji.name
+ const find = _updated.find((e) => {
+ const eID = e.id !== null ? e.id : e.name
+ return emojiID === eID
+ })
if (find === undefined) {
- await guild.emojis.delete(emoji.id)
+ await guild.emojis.delete(emojiID)
deleted.push(emoji)
} else {
- const before = (await guild.emojis.get(find.id)) as Emoji
- await guild.emojis.set(find.id, find)
- const after = (await guild.emojis.get(find.id)) as Emoji
+ const foundID = find.id !== null ? find.id : find.name
+ const before = (await guild.emojis.get(foundID)) as Emoji
+ await guild.emojis.set(foundID, find)
+ const after = (await guild.emojis.get(foundID)) as Emoji
updated.push({ before, after })
}
}
diff --git a/src/gateway/handlers/messageReactionAdd.ts b/src/gateway/handlers/messageReactionAdd.ts
index 17dbaf7..5b05ddd 100644
--- a/src/gateway/handlers/messageReactionAdd.ts
+++ b/src/gateway/handlers/messageReactionAdd.ts
@@ -29,15 +29,16 @@ export const messageReactionAdd: GatewayEventHandler = async (
} else return
}
- let reaction = await message.reactions.get(d.emoji.id)
+ const emojiID = d.emoji.id !== null ? d.emoji.id : d.emoji.name
+ let reaction = await message.reactions.get(emojiID)
if (reaction === undefined) {
- await message.reactions.set(d.emoji.id, {
+ await message.reactions.set(emojiID, {
count: 1,
emoji: d.emoji,
me: d.user_id === gateway.client.user?.id
})
reaction = ((await message.reactions.get(
- d.emoji.id
+ emojiID
)) as unknown) as MessageReaction
}
diff --git a/src/gateway/handlers/messageReactionRemove.ts b/src/gateway/handlers/messageReactionRemove.ts
index 56748da..9cf50fa 100644
--- a/src/gateway/handlers/messageReactionRemove.ts
+++ b/src/gateway/handlers/messageReactionRemove.ts
@@ -27,7 +27,8 @@ export const messageReactionRemove: GatewayEventHandler = async (
} else return
}
- const reaction = await message.reactions.get(d.emoji.id)
+ const emojiID = d.emoji.id !== null ? d.emoji.id : d.emoji.name
+ const reaction = await message.reactions.get(emojiID)
if (reaction === undefined) return
reaction.users.delete(d.user_id)
diff --git a/src/gateway/handlers/messageReactionRemoveEmoji.ts b/src/gateway/handlers/messageReactionRemoveEmoji.ts
index e62c172..0d1cd05 100644
--- a/src/gateway/handlers/messageReactionRemoveEmoji.ts
+++ b/src/gateway/handlers/messageReactionRemoveEmoji.ts
@@ -19,7 +19,8 @@ export const messageReactionRemoveEmoji: GatewayEventHandler = async (
} else return
}
- const reaction = await message.reactions.get(d.emoji.id)
+ const emojiID = d.emoji.id !== null ? d.emoji.id : d.emoji.name
+ const reaction = await message.reactions.get(emojiID)
if (reaction === undefined) return
await reaction.users.flush()
diff --git a/src/gateway/index.ts b/src/gateway/index.ts
index a8349a4..d678336 100644
--- a/src/gateway/index.ts
+++ b/src/gateway/index.ts
@@ -1,4 +1,4 @@
-import { unzlib } from 'https://deno.land/x/denoflate@1.1/mod.ts'
+import { unzlib, EventEmitter } from '../../deps.ts'
import { Client } from '../models/client.ts'
import {
DISCORD_GATEWAY_URL,
@@ -18,7 +18,6 @@ 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 EventEmitter from 'https://deno.land/std@0.74.0/node/events.ts'
export interface RequestMembersOptions {
limit?: number
@@ -177,55 +176,61 @@ export class Gateway extends EventEmitter {
}
}
- private async onclose(event: CloseEvent): Promise {
- if (event.reason === RECONNECT_REASON) return
- this.emit('close', event.code, event.reason)
- this.debug(`Connection Closed with code: ${event.code}`)
+ private async onclose({ reason, code }: CloseEvent): Promise {
+ if (reason === RECONNECT_REASON) return
+ this.emit('close', code, reason)
+ this.debug(`Connection Closed with code: ${code}`)
- if (event.code === GatewayCloseCodes.UNKNOWN_ERROR) {
- this.debug('API has encountered Unknown Error. Reconnecting...')
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.reconnect()
- } else if (event.code === GatewayCloseCodes.UNKNOWN_OPCODE) {
- throw new Error("Unknown OP Code was sent. This shouldn't happen!")
- } else if (event.code === GatewayCloseCodes.DECODE_ERROR) {
- throw new Error("Invalid Payload was sent. This shouldn't happen!")
- } else if (event.code === GatewayCloseCodes.NOT_AUTHENTICATED) {
- throw new Error('Not Authorized: Payload was sent before Identifying.')
- } else if (event.code === GatewayCloseCodes.AUTHENTICATION_FAILED) {
- throw new Error('Invalid Token provided!')
- } else if (event.code === GatewayCloseCodes.INVALID_SEQ) {
- this.debug('Invalid Seq was sent. Reconnecting.')
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.reconnect()
- } else if (event.code === GatewayCloseCodes.RATE_LIMITED) {
- throw new Error("You're ratelimited. Calm down.")
- } else if (event.code === GatewayCloseCodes.SESSION_TIMED_OUT) {
- this.debug('Session Timeout. Reconnecting.')
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.reconnect(true)
- } else if (event.code === GatewayCloseCodes.INVALID_SHARD) {
- this.debug('Invalid Shard was sent. Reconnecting.')
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.reconnect()
- } else if (event.code === GatewayCloseCodes.SHARDING_REQUIRED) {
- throw new Error("Couldn't connect. Sharding is required!")
- } else if (event.code === GatewayCloseCodes.INVALID_API_VERSION) {
- throw new Error("Invalid API Version was used. This shouldn't happen!")
- } else if (event.code === GatewayCloseCodes.INVALID_INTENTS) {
- throw new Error('Invalid Intents')
- } else if (event.code === GatewayCloseCodes.DISALLOWED_INTENTS) {
- throw new Error("Given Intents aren't allowed")
- } else {
- this.debug(
- 'Unknown Close code, probably connection error. Reconnecting in 5s.'
- )
- if (this.timedIdentify !== null) {
- clearTimeout(this.timedIdentify)
- this.debug('Timed Identify found. Cleared timeout.')
- }
- await delay(5000)
- await this.reconnect(true)
+ switch (code) {
+ case GatewayCloseCodes.UNKNOWN_ERROR:
+ this.debug('API has encountered Unknown Error. Reconnecting...')
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
+ this.reconnect()
+ break
+ case GatewayCloseCodes.UNKNOWN_OPCODE:
+ throw new Error("Unknown OP Code was sent. This shouldn't happen!")
+ case GatewayCloseCodes.DECODE_ERROR:
+ throw new Error("Invalid Payload was sent. This shouldn't happen!")
+ case GatewayCloseCodes.NOT_AUTHENTICATED:
+ throw new Error('Not Authorized: Payload was sent before Identifying.')
+ case GatewayCloseCodes.AUTHENTICATION_FAILED:
+ throw new Error('Invalid Token provided!')
+ case GatewayCloseCodes.INVALID_SEQ:
+ this.debug('Invalid Seq was sent. Reconnecting.')
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
+ this.reconnect()
+ break
+ case GatewayCloseCodes.RATE_LIMITED:
+ throw new Error("You're ratelimited. Calm down.")
+ case GatewayCloseCodes.SESSION_TIMED_OUT:
+ this.debug('Session Timeout. Reconnecting.')
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
+ this.reconnect(true)
+ break
+ case GatewayCloseCodes.INVALID_SHARD:
+ this.debug('Invalid Shard was sent. Reconnecting.')
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
+ this.reconnect()
+ break
+ case GatewayCloseCodes.SHARDING_REQUIRED:
+ throw new Error("Couldn't connect. Sharding is required!")
+ case GatewayCloseCodes.INVALID_API_VERSION:
+ throw new Error("Invalid API Version was used. This shouldn't happen!")
+ case GatewayCloseCodes.INVALID_INTENTS:
+ throw new Error('Invalid Intents')
+ case GatewayCloseCodes.DISALLOWED_INTENTS:
+ throw new Error("Given Intents aren't allowed")
+ default:
+ this.debug(
+ 'Unknown Close code, probably connection error. Reconnecting in 5s.'
+ )
+ if (this.timedIdentify !== null) {
+ clearTimeout(this.timedIdentify)
+ this.debug('Timed Identify found. Cleared timeout.')
+ }
+ await delay(5000)
+ await this.reconnect(true)
+ break
}
}
diff --git a/src/managers/guildEmojis.ts b/src/managers/guildEmojis.ts
index 30c1d49..dde2225 100644
--- a/src/managers/guildEmojis.ts
+++ b/src/managers/guildEmojis.ts
@@ -6,7 +6,7 @@ import { 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 { fetchAuto } from 'https://raw.githubusercontent.com/DjDeveloperr/fetch-base64/main/mod.ts'
+import { fetchAuto } from '../../deps.ts'
export class GuildEmojisManager extends BaseChildManager {
guild: Guild
@@ -87,7 +87,8 @@ export class GuildEmojisManager extends BaseChildManager {
async flush(): Promise {
const arr = await this.array()
for (const elem of arr) {
- this.parent.delete(elem.id)
+ const emojiID = elem.id !== null ? elem.id : elem.name
+ this.parent.delete(emojiID)
}
return true
}
diff --git a/src/managers/messageReactions.ts b/src/managers/messageReactions.ts
index 43d039c..7c43ae6 100644
--- a/src/managers/messageReactions.ts
+++ b/src/managers/messageReactions.ts
@@ -21,7 +21,9 @@ export class MessageReactionsManager extends BaseManager<
const raw = await this._get(id)
if (raw === undefined) return
- let emoji = await this.client.emojis.get(raw.emoji.id)
+ const emojiID = raw.emoji.id !== null ? raw.emoji.id : raw.emoji.name
+
+ let emoji = await this.client.emojis.get(emojiID)
if (emoji === undefined) emoji = new Emoji(this.client, raw.emoji)
const reaction = new MessageReaction(this.client, raw, this.message, emoji)
@@ -43,7 +45,8 @@ export class MessageReactionsManager extends BaseManager<
return await Promise.all(
arr.map(async (raw) => {
- let emoji = await this.client.emojis.get(raw.emoji.id)
+ const emojiID = raw.emoji.id !== null ? raw.emoji.id : raw.emoji.name
+ let emoji = await this.client.emojis.get(emojiID)
if (emoji === undefined) emoji = new Emoji(this.client, raw.emoji)
return new MessageReaction(this.client, raw, this.message, emoji)
diff --git a/src/models/cacheAdapter.ts b/src/models/cacheAdapter.ts
index fe9df03..41b7503 100644
--- a/src/models/cacheAdapter.ts
+++ b/src/models/cacheAdapter.ts
@@ -3,7 +3,7 @@ import {
connect,
Redis,
RedisConnectOptions
-} from 'https://denopkg.com/keroxp/deno-redis/mod.ts'
+} from '../../deps.ts'
/**
* ICacheAdapter is the interface to be implemented by Cache Adapters for them to be usable with Harmony.
diff --git a/src/models/client.ts b/src/models/client.ts
index 07955d5..f0bcc73 100644
--- a/src/models/client.ts
+++ b/src/models/client.ts
@@ -2,7 +2,7 @@ import { User } from '../structures/user.ts'
import { GatewayIntents } from '../types/gateway.ts'
import { Gateway } from '../gateway/index.ts'
import { RESTManager } from './rest.ts'
-import EventEmitter from 'https://deno.land/std@0.74.0/node/events.ts'
+import { EventEmitter } from '../../deps.ts'
import { DefaultCacheAdapter, ICacheAdapter } from './cacheAdapter.ts'
import { UsersManager } from '../managers/users.ts'
import { GuildManager } from '../managers/guilds.ts'
@@ -247,6 +247,7 @@ export function event(name?: string) {
}
}
+/** Decorator to create a Slash Command handler */
export function slash(name?: string, guild?: string) {
return function (client: Client | SlashModule, prop: string) {
if (client._decoratedSlash === undefined) client._decoratedSlash = []
@@ -262,6 +263,7 @@ export function slash(name?: string, guild?: string) {
}
}
+/** Decorator to create a Sub-Slash Command handler */
export function subslash(parent: string, name?: string, guild?: string) {
return function (client: Client | SlashModule, prop: string) {
if (client._decoratedSlash === undefined) client._decoratedSlash = []
@@ -279,13 +281,14 @@ export function subslash(parent: string, name?: string, guild?: string) {
}
}
+/** Decorator to create a Grouped Slash Command handler */
export function groupslash(
parent: string,
group: string,
name?: string,
guild?: string
) {
- return function (client: Client | SlashModule, prop: string) {
+ return function (client: Client | SlashModule | SlashClient, prop: string) {
if (client._decoratedSlash === undefined) client._decoratedSlash = []
const item = (client as { [name: string]: any })[prop]
if (typeof item !== 'function') {
@@ -303,6 +306,7 @@ export function groupslash(
}
}
+/** Decorator to add a Slash Module to Client */
export function slashModule() {
return function (client: Client, prop: string) {
if (client._decoratedSlashModules === undefined)
diff --git a/src/models/command.ts b/src/models/command.ts
index 2cbc033..3293229 100644
--- a/src/models/command.ts
+++ b/src/models/command.ts
@@ -5,7 +5,7 @@ import { User } from '../structures/user.ts'
import { Collection } from '../utils/collection.ts'
import { CommandClient } from './commandClient.ts'
import { Extension } from './extensions.ts'
-import { parse } from 'https://deno.land/x/mutil@0.1.2/mod.ts'
+import { parse } from '../../deps.ts'
export interface CommandContext {
/** The Client object */
diff --git a/src/models/rest.ts b/src/models/rest.ts
index 0e80c39..29b708c 100644
--- a/src/models/rest.ts
+++ b/src/models/rest.ts
@@ -97,6 +97,7 @@ export interface RESTOptions {
token?: string
headers?: { [name: string]: string | undefined }
canary?: boolean
+ version?: 6 | 7 | 8
}
export class RESTManager {
@@ -111,6 +112,7 @@ export class RESTManager {
constructor(client?: RESTOptions) {
this.client = client
this.api = builder(this)
+ if (client?.version !== undefined) this.version = client.version
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.handleRateLimits()
}
@@ -408,6 +410,7 @@ export class RESTManager {
const query =
method === 'get' && body !== undefined
? Object.entries(body as any)
+ .filter(([k, v]) => v !== undefined)
.map(
([key, value]) =>
`${encodeURIComponent(key)}=${encodeURIComponent(
diff --git a/src/models/shard.ts b/src/models/shard.ts
index 52927cb..436d01d 100644
--- a/src/models/shard.ts
+++ b/src/models/shard.ts
@@ -1,6 +1,6 @@
import { Collection } from '../utils/collection.ts'
import { Client, ClientOptions } from './client.ts'
-import EventEmitter from 'https://deno.land/std@0.74.0/node/events.ts'
+import {EventEmitter} from '../../deps.ts'
import { RESTManager } from './rest.ts'
// import { GATEWAY_BOT } from '../types/endpoint.ts'
// import { GatewayBotPayload } from '../types/gatewayBot.ts'
diff --git a/src/models/slashClient.ts b/src/models/slashClient.ts
index 5058660..e029e49 100644
--- a/src/models/slashClient.ts
+++ b/src/models/slashClient.ts
@@ -1,20 +1,24 @@
import { Guild } from '../structures/guild.ts'
import { Interaction } from '../structures/slash.ts'
-import {
- APPLICATION_COMMAND,
- APPLICATION_COMMANDS,
- APPLICATION_GUILD_COMMAND,
- APPLICATION_GUILD_COMMANDS
-} from '../types/endpoint.ts'
import {
InteractionType,
+ SlashCommandChoice,
SlashCommandOption,
+ SlashCommandOptionType,
SlashCommandPartial,
SlashCommandPayload
} from '../types/slash.ts'
import { Collection } from '../utils/collection.ts'
import { Client } from './client.ts'
import { RESTManager } from './rest.ts'
+import { SlashModule } from './slashModule.ts'
+import { verify as edverify } from 'https://deno.land/x/ed25519/mod.ts'
+import { Buffer } from 'https://deno.land/std@0.80.0/node/buffer.ts'
+import {
+ Request as ORequest,
+ Response as OResponse
+} from 'https://deno.land/x/opine@1.0.0/src/types.ts'
+import { Context } from 'https://deno.land/x/oak@v6.4.0/mod.ts'
export class SlashCommand {
slash: SlashCommandsManager
@@ -41,6 +45,158 @@ export class SlashCommand {
async edit(data: SlashCommandPartial): Promise {
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
+ choices?: Array
+}
+
+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
+ | {
+ [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] })
+ )
+}
+
+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
+ }
}
export class SlashCommandsManager {
@@ -58,9 +214,9 @@ export class SlashCommandsManager {
async all(): Promise> {
const col = new Collection()
- const res = (await this.rest.get(
- APPLICATION_COMMANDS(this.slash.getID())
- )) as SlashCommandPayload[]
+ 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) {
@@ -77,12 +233,9 @@ export class SlashCommandsManager {
): Promise> {
const col = new Collection()
- const res = (await this.rest.get(
- APPLICATION_GUILD_COMMANDS(
- this.slash.getID(),
- typeof guild === 'string' ? guild : guild.id
- )
- )) as SlashCommandPayload[]
+ 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
for (const raw of res) {
@@ -99,15 +252,14 @@ export class SlashCommandsManager {
data: SlashCommandPartial,
guild?: Guild | string
): Promise {
- const payload = await this.rest.post(
+ const route =
guild === undefined
- ? APPLICATION_COMMANDS(this.slash.getID())
- : APPLICATION_GUILD_COMMANDS(
- this.slash.getID(),
+ ? this.rest.api.applications[this.slash.getID()].commands
+ : this.rest.api.applications[this.slash.getID()].guilds[
typeof guild === 'string' ? guild : guild.id
- ),
- data
- )
+ ].commands
+
+ const payload = await route.post(data)
const cmd = new SlashCommand(this, payload)
cmd._guild =
@@ -122,16 +274,14 @@ export class SlashCommandsManager {
data: SlashCommandPartial,
guild?: Guild | string
): Promise {
- await this.rest.patch(
+ const route =
guild === undefined
- ? APPLICATION_COMMAND(this.slash.getID(), id)
- : APPLICATION_GUILD_COMMAND(
- this.slash.getID(),
- typeof guild === 'string' ? guild : guild.id,
- id
- ),
- data
- )
+ ? 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
}
@@ -140,29 +290,28 @@ export class SlashCommandsManager {
id: string,
guild?: Guild | string
): Promise {
- await this.rest.delete(
+ const route =
guild === undefined
- ? APPLICATION_COMMAND(this.slash.getID(), id)
- : APPLICATION_GUILD_COMMAND(
- this.slash.getID(),
- typeof guild === 'string' ? guild : guild.id,
- id
- )
- )
+ ? 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 {
- const data = await this.rest.get(
+ const route =
guild === undefined
- ? APPLICATION_COMMAND(this.slash.getID(), id)
- : APPLICATION_GUILD_COMMAND(
- this.slash.getID(),
- typeof guild === 'string' ? guild : guild.id,
- id
- )
- )
+ ? 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()
+
return new SlashCommand(this, data)
}
}
@@ -182,6 +331,7 @@ export interface SlashOptions {
enabled?: boolean
token?: string
rest?: RESTManager
+ publicKey?: string
}
export class SlashClient {
@@ -192,6 +342,18 @@ export class SlashClient {
commands: SlashCommandsManager
handlers: SlashCommandHandler[] = []
rest: RESTManager
+ modules: SlashModule[] = []
+ publicKey?: string
+
+ _decoratedSlash?: Array<{
+ name: string
+ guild?: string
+ parent?: string
+ group?: string
+ handler: (interaction: Interaction) => any
+ }>
+
+ _decoratedSlashModules?: SlashModule[]
constructor(options: SlashOptions) {
let id = options.id
@@ -202,6 +364,7 @@ export class SlashClient {
this.client = options.client
this.token = options.token
this.commands = new SlashCommandsManager(this)
+ this.publicKey = options.publicKey
if (options !== undefined) {
this.enabled = options.enabled ?? true
@@ -213,6 +376,24 @@ export class SlashClient {
})
}
+ if (this.client?._decoratedSlashModules !== undefined) {
+ this.client._decoratedSlashModules.forEach((e) => {
+ this.modules.push(e)
+ })
+ }
+
+ if (this._decoratedSlash !== undefined) {
+ this._decoratedSlash.forEach((e) => {
+ this.handlers.push(e)
+ })
+ }
+
+ if (this._decoratedSlashModules !== undefined) {
+ this._decoratedSlashModules.forEach((e) => {
+ this.modules.push(e)
+ })
+ }
+
this.rest =
options.client === undefined
? options.rest === undefined
@@ -237,8 +418,28 @@ export class SlashClient {
return this
}
+ loadModule(module: SlashModule): SlashClient {
+ this.modules.push(module)
+ return this
+ }
+
+ getHandlers(): SlashCommandHandler[] {
+ let res = this.handlers
+ for (const mod of this.modules) {
+ if (mod === undefined) continue
+ res = [
+ ...res,
+ ...mod.commands.map((cmd) => {
+ cmd.handler = cmd.handler.bind(mod)
+ return cmd
+ })
+ ]
+ }
+ return res
+ }
+
private _getCommand(i: Interaction): SlashCommandHandler | undefined {
- return this.handlers.find((e) => {
+ return this.getHandlers().find((e) => {
const hasGroupOrParent = e.group !== undefined || e.parent !== undefined
const groupMatched =
e.group !== undefined && e.parent !== undefined
@@ -271,4 +472,78 @@ export class SlashClient {
cmd.handler(interaction)
}
+
+ async verifyKey(
+ rawBody: string | Uint8Array | Buffer,
+ signature: string,
+ timestamp: string
+ ): Promise {
+ if (this.publicKey === undefined)
+ throw new Error('Public Key is not present')
+ return edverify(
+ signature,
+ Buffer.concat([
+ Buffer.from(timestamp, 'utf-8'),
+ Buffer.from(
+ rawBody instanceof Uint8Array
+ ? new TextDecoder().decode(rawBody)
+ : rawBody
+ )
+ ]),
+ this.publicKey
+ ).catch(() => false)
+ }
+
+ async verifyOpineRequest(req: ORequest): Promise {
+ const signature = req.headers.get('x-signature-ed25519')
+ const timestamp = req.headers.get('x-signature-timestamp')
+ const contentLength = req.headers.get('content-length')
+
+ if (signature === null || timestamp === null || contentLength === null)
+ return false
+
+ const body = new Uint8Array(parseInt(contentLength))
+ await req.body.read(body)
+
+ const verified = await this.verifyKey(body, signature, timestamp)
+ if (!verified) return false
+
+ return true
+ }
+
+ /** Middleware to verify request in Opine framework. */
+ async verifyOpineMiddleware(
+ req: ORequest,
+ res: OResponse,
+ next: CallableFunction
+ ): Promise {
+ const verified = await this.verifyOpineRequest(req)
+ if (!verified) return res.setStatus(401).end()
+
+ await next()
+ return true
+ }
+
+ // TODO: create verifyOakMiddleware too
+ /** Method to verify Request from Oak server "Context". */
+ async verifyOakRequest(ctx: Context): Promise {
+ const signature = ctx.request.headers.get('x-signature-ed25519')
+ const timestamp = ctx.request.headers.get('x-signature-timestamp')
+ const contentLength = ctx.request.headers.get('content-length')
+
+ if (
+ signature === null ||
+ timestamp === null ||
+ contentLength === null ||
+ ctx.request.hasBody !== true
+ ) {
+ return false
+ }
+
+ const body = await ctx.request.body().value
+
+ const verified = await this.verifyKey(body as any, signature, timestamp)
+ if (!verified) return false
+ return true
+ }
}
diff --git a/src/structures/emoji.ts b/src/structures/emoji.ts
index 3ac4bba..8715379 100644
--- a/src/structures/emoji.ts
+++ b/src/structures/emoji.ts
@@ -18,9 +18,13 @@ export class Emoji extends Base {
available?: boolean
get getEmojiString(): string {
- if (this.animated === false) {
- return `<:${this.name}:${this.id}>`
- } else return ``
+ if (this.id === null) {
+ return this.name as string;
+ } else {
+ if (this.animated === false) {
+ return `<:${this.name}:${this.id}>`
+ } else return ``
+ }
}
toString(): string {
diff --git a/src/structures/invite.ts b/src/structures/invite.ts
index 59d172d..f27f841 100644
--- a/src/structures/invite.ts
+++ b/src/structures/invite.ts
@@ -1,5 +1,6 @@
import { Client } from '../models/client.ts'
import { ChannelPayload } from '../types/channel.ts'
+import { INVITE } from '../types/endpoint.ts'
import { GuildPayload } from '../types/guild.ts'
import { InvitePayload } from '../types/invite.ts'
import { UserPayload } from '../types/user.ts'
@@ -31,6 +32,12 @@ export class Invite extends Base {
this.approximatePresenceCount = data.approximate_presence_count
}
+ /** Delete an invite. Requires the MANAGE_CHANNELS permission on the channel this invite belongs to, or MANAGE_GUILD to remove any invite across the guild. Returns an invite object on success. Fires a Invite Delete Gateway event. */
+ async delete(): Promise {
+ const res = await this.client.rest.delete(INVITE(this.code))
+ return new Invite(this.client, res)
+ }
+
readFromData(data: InvitePayload): void {
this.code = data.code ?? this.code
this.guild = data.guild ?? this.guild
diff --git a/src/structures/message.ts b/src/structures/message.ts
index d8e6325..920994a 100644
--- a/src/structures/message.ts
+++ b/src/structures/message.ts
@@ -17,6 +17,7 @@ import { TextChannel } from './textChannel.ts'
import { Guild } from './guild.ts'
import { MessageReactionsManager } from '../managers/messageReactions.ts'
import { MessageSticker } from './messageSticker.ts'
+import { Emoji } from './emoji.ts'
type AllMessageOptions = MessageOption | Embed
@@ -46,6 +47,10 @@ export class Message extends Base {
flags?: number
stickers?: MessageSticker[]
+ get createdAt(): Date {
+ return new Date(this.timestamp)
+ }
+
constructor(
client: Client,
data: MessagePayload,
@@ -108,25 +113,52 @@ export class Message extends Base {
}
/** Edits this message. */
- async edit(text?: string, option?: MessageOption): Promise {
+ async edit(
+ content?: string | AllMessageOptions,
+ option?: AllMessageOptions
+ ): Promise {
+ if (typeof content === 'object') {
+ option = content
+ content = undefined
+ }
+ if (content === undefined && option === undefined) {
+ throw new Error('Either text or option is necessary.')
+ }
+ if (option instanceof Embed) {
+ option = {
+ embed: option
+ }
+ }
if (
this.client.user !== undefined &&
this.author.id !== this.client.user?.id
- )
+ ) {
throw new Error("Cannot edit other users' messages")
- return this.channel.editMessage(this.id, text, option)
+ }
+ return this.channel.editMessage(this.id, content, option)
}
/** Creates a Reply to this Message. */
async reply(
- text?: string | AllMessageOptions,
+ content?: string | AllMessageOptions,
option?: AllMessageOptions
): Promise {
- return this.channel.send(text, option, this)
+ return this.channel.send(content, option, this)
}
/** Deletes the Message. */
async delete(): Promise {
return this.client.rest.delete(CHANNEL_MESSAGE(this.channelID, this.id))
}
+
+ async addReaction(emoji: string | Emoji): Promise {
+ return this.channel.addReaction(this, emoji)
+ }
+
+ async removeReaction(
+ emoji: string | Emoji,
+ user?: User | Member | string
+ ): Promise {
+ return this.channel.removeReaction(this, emoji, user)
+ }
}
diff --git a/src/structures/template.ts b/src/structures/template.ts
new file mode 100644
index 0000000..b137075
--- /dev/null
+++ b/src/structures/template.ts
@@ -0,0 +1,70 @@
+import { Client } from '../models/client.ts'
+import { TEMPLATE } from '../types/endpoint.ts'
+import { TemplatePayload } from '../types/template.ts'
+import { Base } from './base.ts'
+import { Guild } from './guild.ts'
+import { User } from './user.ts'
+
+export class Template extends Base {
+ /** The template code (unique ID) */
+ code: string
+ /** The template name */
+ name: string
+ /** The description for the template */
+ description: string | null
+ /** Number of times this template has been used */
+ usageCount: number
+ /** The ID of the user who created the template */
+ creatorID: string
+ /** The user who created the template */
+ creator: User
+ /** When this template was created (in ms) */
+ createdAt: number
+ /** When this template was last synced to the source guild (in ms) */
+ updatedAt: number
+ /** The ID of the guild this template is based on */
+ sourceGuildID: string
+ /** The guild snapshot this template contains */
+ serializedSourceGuild: Guild
+ /** Whether the template has unsynced changes */
+ isDirty: boolean | null
+
+ constructor(client: Client, data: TemplatePayload) {
+ super(client, data)
+ this.code = data.code
+ this.name = data.name
+ this.description = data.description
+ this.usageCount = data.usage_count
+ this.creatorID = data.creator_id
+ this.creator = new User(client, data.creator)
+ this.createdAt = Date.parse(data.created_at)
+ this.updatedAt = Date.parse(data.updated_at)
+ this.sourceGuildID = data.source_guild_id
+ this.serializedSourceGuild = new Guild(client, data.serialized_source_guild)
+ this.isDirty = Boolean(data.is_dirty)
+ }
+
+ /** Modifies the template's metadata. Requires the MANAGE_GUILD permission. Returns the template object on success. */
+ async edit(data: ModifyGuildTemplateParams): Promise {
+ const res = await this.client.rest.patch(TEMPLATE(this.code), data)
+ return new Template(this.client, res)
+ }
+
+ /** Deletes the template. Requires the MANAGE_GUILD permission. Returns the deleted template object on success. */
+ async delete(): Promise {
+ const res = await this.client.rest.delete(TEMPLATE(this.code))
+ return new Template(this.client, res)
+ }
+
+ /** Syncs the template to the guild's current state. Requires the MANAGE_GUILD permission. Returns the template object on success. */
+ async sync(): Promise {
+ const res = await this.client.rest.put(TEMPLATE(this.code))
+ return new Template(this.client, res)
+ }
+}
+
+/** https://discord.com/developers/docs/resources/template#modify-guild-template-json-params */
+export interface ModifyGuildTemplateParams {
+ name?: string
+ description?: string | null
+}
diff --git a/src/structures/textChannel.ts b/src/structures/textChannel.ts
index 699b4ad..398f2af 100644
--- a/src/structures/textChannel.ts
+++ b/src/structures/textChannel.ts
@@ -3,6 +3,7 @@ import { Client } from '../models/client.ts'
import {
GuildTextChannelPayload,
MessageOption,
+ MessagePayload,
MessageReference,
ModifyGuildTextChannelOption,
ModifyGuildTextChannelPayload,
@@ -12,12 +13,18 @@ import {
import {
CHANNEL,
CHANNEL_MESSAGE,
- CHANNEL_MESSAGES
+ CHANNEL_MESSAGES,
+ MESSAGE_REACTION_ME,
+ MESSAGE_REACTION_USER
} from '../types/endpoint.ts'
+import { Collection } from '../utils/collection.ts'
import { Channel } from './channel.ts'
import { Embed } from './embed.ts'
+import { Emoji } from './emoji.ts'
import { Guild } from './guild.ts'
+import { Member } from './member.ts'
import { Message } from './message.ts'
+import { User } from './user.ts'
export type AllMessageOptions = MessageOption | Embed
@@ -41,29 +48,30 @@ export class TextChannel extends Channel {
/**
*
- * @param text Text content of the Message to send.
+ * @param content Text content of the Message to send.
* @param option Various other Message options.
* @param reply Reference to a Message object to reply-to.
*/
async send(
- text?: string | AllMessageOptions,
+ content?: string | AllMessageOptions,
option?: AllMessageOptions,
reply?: Message
): Promise {
- if (typeof text === 'object') {
- option = text
- text = undefined
+ if (typeof content === 'object') {
+ option = content
+ content = undefined
}
- if (text === undefined && option === undefined) {
+ if (content === undefined && option === undefined) {
throw new Error('Either text or option is necessary.')
}
- if (option instanceof Embed)
+ if (option instanceof Embed) {
option = {
embed: option
}
+ }
const payload: any = {
- content: text,
+ content: content,
embed: option?.embed,
file: option?.file,
tts: option?.tts,
@@ -124,6 +132,96 @@ export class TextChannel extends Channel {
await res.mentions.fromPayload(newMsg)
return res
}
+
+ async addReaction(
+ message: Message | string,
+ emoji: Emoji | string
+ ): Promise {
+ if (emoji instanceof Emoji) {
+ emoji = emoji.getEmojiString
+ }
+ if (message instanceof Message) {
+ message = message.id
+ }
+
+ const encodedEmoji = encodeURI(emoji)
+
+ await this.client.rest.put(
+ MESSAGE_REACTION_ME(this.id, message, encodedEmoji)
+ )
+ }
+
+ async removeReaction(
+ message: Message | string,
+ emoji: Emoji | string,
+ user?: User | Member | string
+ ): Promise {
+ if (emoji instanceof Emoji) {
+ emoji = emoji.getEmojiString
+ }
+ if (message instanceof Message) {
+ message = message.id
+ }
+ if (user !== undefined) {
+ if (typeof user !== 'string') {
+ user = user.id
+ }
+ }
+
+ const encodedEmoji = encodeURI(emoji)
+
+ if (user === undefined) {
+ await this.client.rest.delete(
+ MESSAGE_REACTION_ME(this.id, message, encodedEmoji)
+ )
+ } else {
+ await this.client.rest.delete(
+ MESSAGE_REACTION_USER(this.id, message, encodedEmoji, user)
+ )
+ }
+ }
+
+ /**
+ * Fetch Messages of a Channel
+ * @param options Options to configure fetching Messages
+ */
+ async fetchMessages(options?: {
+ limit?: number
+ around?: Message | string
+ before?: Message | string
+ after?: Message | string
+ }): Promise> {
+ const res = new Collection()
+ const raws = (await this.client.rest.api.channels[this.id].messages.get({
+ limit: options?.limit ?? 50,
+ around:
+ options?.around === undefined
+ ? undefined
+ : typeof options.around === 'string'
+ ? options.around
+ : options.around.id,
+ before:
+ options?.before === undefined
+ ? undefined
+ : typeof options.before === 'string'
+ ? options.before
+ : options.before.id,
+ after:
+ options?.after === undefined
+ ? undefined
+ : typeof options.after === 'string'
+ ? options.after
+ : options.after.id
+ })) as MessagePayload[]
+
+ for (const raw of raws) {
+ await this.messages.set(raw.id, raw)
+ const msg = ((await this.messages.get(raw.id)) as unknown) as Message
+ res.set(msg.id, msg)
+ }
+
+ return res
+ }
}
export class GuildTextChannel extends TextChannel {
@@ -185,4 +283,40 @@ export class GuildTextChannel extends TextChannel {
return new GuildTextChannel(this.client, resp, this.guild)
}
+
+ /**
+ * Bulk Delete Messages in a Guild Text Channel
+ * @param messages Messages to delete. Can be a number, or Array of Message or IDs
+ */
+ async bulkDelete(
+ messages: Array | number
+ ): Promise {
+ let ids: string[] = []
+
+ if (Array.isArray(messages))
+ ids = messages.map((e) => (typeof e === 'string' ? e : e.id))
+ else {
+ let list = await this.messages.array()
+ if (list.length < messages) list = (await this.fetchMessages()).array()
+ ids = list
+ .sort((b, a) => a.createdAt.getTime() - b.createdAt.getTime())
+ .filter((e, i) => i < messages)
+ .filter(
+ (e) =>
+ new Date().getTime() - e.createdAt.getTime() <=
+ 1000 * 60 * 60 * 24 * 14
+ )
+ .map((e) => e.id)
+ }
+
+ ids = [...new Set(ids)]
+ if (ids.length < 2 || ids.length > 100)
+ throw new Error('bulkDelete can only delete messages in range 2-100')
+
+ await this.client.rest.api.channels[this.id].messages['bulk-delete'].post({
+ messages: ids
+ })
+
+ return this
+ }
}
diff --git a/src/structures/webhook.ts b/src/structures/webhook.ts
index dd5dc8c..2d571c5 100644
--- a/src/structures/webhook.ts
+++ b/src/structures/webhook.ts
@@ -11,7 +11,7 @@ import { Embed } from './embed.ts'
import { Message } from './message.ts'
import { TextChannel } from './textChannel.ts'
import { User } from './user.ts'
-import { fetchAuto } from 'https://raw.githubusercontent.com/DjDeveloperr/fetch-base64/main/mod.ts'
+import { fetchAuto } from '../../deps.ts'
import { WEBHOOK_MESSAGE } from '../types/endpoint.ts'
export interface WebhookMessageOptions extends MessageOption {
diff --git a/src/test/index.ts b/src/test/index.ts
index f42d145..c0d14c3 100644
--- a/src/test/index.ts
+++ b/src/test/index.ts
@@ -112,6 +112,17 @@ client.on('messageCreate', async (msg: Message) => {
} else {
msg.channel.send('Failed...')
}
+ } else if (msg.content === '!react') {
+ msg.addReaction('🤔')
+ }
+})
+
+client.on('messageReactionRemove', (reaction, user) => {
+ const msg = reaction.message
+
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
+ if (reaction.me && reaction.emoji.getEmojiString === '🤔') {
+ msg.removeReaction(reaction.emoji)
}
})
diff --git a/src/test/music.ts b/src/test/music.ts
index 07f65b6..af0333a 100644
--- a/src/test/music.ts
+++ b/src/test/music.ts
@@ -7,13 +7,14 @@ import {
groupslash,
CommandContext,
Extension,
- Collection
+ Collection,
+ GuildTextChannel
} from '../../mod.ts'
import { LL_IP, LL_PASS, LL_PORT, TOKEN } from './config.ts'
import {
Manager,
Player
-} from 'https://raw.githubusercontent.com/Lavaclient/lavadeno/master/mod.ts'
+} from '../../deps.ts'
import { Interaction } from '../structures/slash.ts'
import { slash } from '../models/client.ts'
// import { SlashCommandOptionType } from '../types/slash.ts'
@@ -69,6 +70,17 @@ class MyClient extends CommandClient {
d.respond({ content: 'sub-cmd-group worked' })
}
+ @command()
+ rmrf(ctx: CommandContext): any {
+ if (ctx.author.id !== '422957901716652033') return
+ ;((ctx.channel as any) as GuildTextChannel)
+ .bulkDelete(3)
+ .then((chan) => {
+ ctx.channel.send(`Bulk deleted 2 in ${chan}`)
+ })
+ .catch((e) => ctx.channel.send(`${e.message}`))
+ }
+
@slash()
run(d: Interaction): void {
console.log(d.name)
@@ -205,6 +217,10 @@ class VCExtension extends Extension {
const client = new MyClient()
+client.on('raw', (e, d) => {
+ if (e === 'GUILD_MEMBER_ADD' || e === 'GUILD_MEMBER_UPDATE') console.log(e, d)
+})
+
client.extensions.load(VCExtension)
-client.connect(TOKEN, Intents.None)
+client.connect(TOKEN, Intents.All)
diff --git a/src/test/slash-only.ts b/src/test/slash-only.ts
index cd326bf..1798eee 100644
--- a/src/test/slash-only.ts
+++ b/src/test/slash-only.ts
@@ -1,6 +1,16 @@
import { SlashClient } from '../models/slashClient.ts'
+import { SlashCommandPartial } from '../types/slash.ts'
import { TOKEN } from './config.ts'
-const slash = new SlashClient({ token: TOKEN })
+export const slash = new SlashClient({ token: TOKEN })
-slash.commands.all().then(console.log)
+// Cmd objects come here
+const commands: SlashCommandPartial[] = []
+
+console.log('Creating...')
+commands.forEach((cmd) => {
+ slash.commands
+ .create(cmd, '!! Your testing guild ID comes here !!')
+ .then((c) => console.log(`Created command ${c.name}!`))
+ .catch((e) => `Failed to create ${cmd.name} - ${e.message}`)
+})
diff --git a/src/types/guild.ts b/src/types/guild.ts
index b2b7b68..6825398 100644
--- a/src/types/guild.ts
+++ b/src/types/guild.ts
@@ -62,6 +62,7 @@ export interface MemberPayload {
premium_since?: string
deaf: boolean
mute: boolean
+ pending?: boolean
}
export enum MessageNotification {
@@ -113,6 +114,9 @@ export type GuildFeatures =
| 'FEATURABLE'
| 'ANIMATED_ICON'
| 'BANNER'
+ | 'WELCOME_SCREEN_ENABLED'
+ | 'MEMBER_VERIFICATION_GATE_ENABLED'
+ | 'PREVIEW_ENABLED'
export enum IntegrationExpireBehavior {
REMOVE_ROLE = 0,
diff --git a/src/types/slash.ts b/src/types/slash.ts
index d48f363..1bd83ba 100644
--- a/src/types/slash.ts
+++ b/src/types/slash.ts
@@ -50,7 +50,7 @@ export interface SlashCommandChoice {
/** (Display) name of the Choice */
name: string
/** Actual value to be sent in Interaction */
- value: string
+ value: any
}
export enum SlashCommandOptionType {
@@ -66,7 +66,8 @@ export enum SlashCommandOptionType {
export interface SlashCommandOption {
name: string
- description: string
+ /** Description not required in Sub-Command or Sub-Command-Group */
+ description?: string
type: SlashCommandOptionType
required?: boolean
default?: boolean
diff --git a/src/types/template.ts b/src/types/template.ts
index 8128515..05fb0a8 100644
--- a/src/types/template.ts
+++ b/src/types/template.ts
@@ -4,7 +4,7 @@ import { UserPayload } from './user.ts'
export interface TemplatePayload {
code: string
name: string
- description: string | undefined
+ description: string | null
usage_count: number
creator_id: string
creator: UserPayload
@@ -12,5 +12,5 @@ export interface TemplatePayload {
updated_at: string
source_guild_id: string
serialized_source_guild: GuildPayload
- is_dirty: boolean | undefined
+ is_dirty: boolean | null
}
diff --git a/src/structures/snowflake.ts b/src/utils/snowflake.ts
similarity index 100%
rename from src/structures/snowflake.ts
rename to src/utils/snowflake.ts