Merge pull request #67 from DjDeveloperr/slash
feat: HTTP based Slash Commands util
This commit is contained in:
commit
d46cd0cbe5
10 changed files with 461 additions and 65 deletions
19
README.md
19
README.md
|
@ -10,15 +10,12 @@
|
|||
<br>
|
||||
|
||||
- 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!
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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<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] })
|
||||
)
|
||||
}
|
||||
|
||||
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<Collection<string, SlashCommand>> {
|
||||
const col = new Collection<string, SlashCommand>()
|
||||
|
||||
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<Collection<string, SlashCommand>> {
|
||||
const col = new Collection<string, SlashCommand>()
|
||||
|
||||
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<SlashCommand> {
|
||||
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<SlashCommandsManager> {
|
||||
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<SlashCommandsManager> {
|
||||
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<SlashCommand> {
|
||||
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<boolean> {
|
||||
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<boolean> {
|
||||
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<any> {
|
||||
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<any> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,10 @@ export class Message extends Base {
|
|||
flags?: number
|
||||
stickers?: MessageSticker[]
|
||||
|
||||
get createdAt(): Date {
|
||||
return new Date(this.timestamp)
|
||||
}
|
||||
|
||||
constructor(
|
||||
client: Client,
|
||||
data: MessagePayload,
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Client } from '../models/client.ts'
|
|||
import {
|
||||
GuildTextChannelPayload,
|
||||
MessageOption,
|
||||
MessagePayload,
|
||||
MessageReference,
|
||||
ModifyGuildTextChannelOption,
|
||||
ModifyGuildTextChannelPayload,
|
||||
|
@ -14,6 +15,7 @@ import {
|
|||
CHANNEL_MESSAGE,
|
||||
CHANNEL_MESSAGES
|
||||
} from '../types/endpoint.ts'
|
||||
import { Collection } from '../utils/collection.ts'
|
||||
import { Channel } from './channel.ts'
|
||||
import { Embed } from './embed.ts'
|
||||
import { Guild } from './guild.ts'
|
||||
|
@ -125,6 +127,48 @@ export class TextChannel extends Channel {
|
|||
await res.mentions.fromPayload(newMsg)
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<Collection<string, Message>> {
|
||||
const res = new Collection<string, Message>()
|
||||
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 {
|
||||
|
@ -186,4 +230,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<Message | string> | number
|
||||
): Promise<GuildTextChannel> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@ import {
|
|||
groupslash,
|
||||
CommandContext,
|
||||
Extension,
|
||||
Collection
|
||||
Collection,
|
||||
GuildTextChannel
|
||||
} from '../../mod.ts'
|
||||
import { LL_IP, LL_PASS, LL_PORT, TOKEN } from './config.ts'
|
||||
import {
|
||||
|
@ -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)
|
||||
|
|
|
@ -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}`)
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue