Added CommandClient! And some improvements and TextChannel#send can be used more easily now
This commit is contained in:
parent
2f560d72e7
commit
071cc4f5ed
6 changed files with 330 additions and 1 deletions
2
mod.ts
2
mod.ts
|
@ -3,6 +3,8 @@ export * from './src/models/client.ts'
|
||||||
export * from './src/models/rest.ts'
|
export * from './src/models/rest.ts'
|
||||||
export * from './src/models/cacheAdapter.ts'
|
export * from './src/models/cacheAdapter.ts'
|
||||||
export * from './src/models/shard.ts'
|
export * from './src/models/shard.ts'
|
||||||
|
export * from './src/models/command.ts'
|
||||||
|
export * from './src/models/commandClient.ts'
|
||||||
export * from './src/managers/base.ts'
|
export * from './src/managers/base.ts'
|
||||||
export * from './src/managers/baseChild.ts'
|
export * from './src/managers/baseChild.ts'
|
||||||
export * from './src/managers/channels.ts'
|
export * from './src/managers/channels.ts'
|
||||||
|
|
126
src/models/command.ts
Normal file
126
src/models/command.ts
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
import { Message } from "../structures/message.ts"
|
||||||
|
import { TextChannel } from "../structures/textChannel.ts"
|
||||||
|
import { User } from "../structures/user.ts"
|
||||||
|
import { Collection } from "../utils/collection.ts"
|
||||||
|
import { CommandClient } from "./commandClient.ts"
|
||||||
|
|
||||||
|
export interface CommandContext {
|
||||||
|
/** The Client object */
|
||||||
|
client: CommandClient
|
||||||
|
/** Message which was parsed for Command */
|
||||||
|
message: Message
|
||||||
|
/** The Author of the Message */
|
||||||
|
author: User
|
||||||
|
/** The Channel in which Command was used */
|
||||||
|
channel: TextChannel
|
||||||
|
/** Prefix which was used */
|
||||||
|
prefix: string
|
||||||
|
/** Oject of Command which was used */
|
||||||
|
command: Command
|
||||||
|
/** Name of Command which was used */
|
||||||
|
name: string
|
||||||
|
/** Array of Arguments used with Command */
|
||||||
|
args: string[]
|
||||||
|
/** Complete Raw String of Arguments */
|
||||||
|
argString: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Command {
|
||||||
|
/** Name of the Command */
|
||||||
|
name: string = ""
|
||||||
|
/** Description of the Command */
|
||||||
|
description?: string
|
||||||
|
/** Array of Aliases of Command, or only string */
|
||||||
|
aliases?: string | string[]
|
||||||
|
/** Usage of Command, only Argument Names */
|
||||||
|
usage?: string | string[]
|
||||||
|
/** Usage Example of Command, only Arguments (without Prefix and Name) */
|
||||||
|
examples?: string | string[]
|
||||||
|
/** Does the Command take Arguments? Maybe number of required arguments? */
|
||||||
|
args?: number | boolean
|
||||||
|
/** Permission(s) required for using Command */
|
||||||
|
permissions?: string | string[]
|
||||||
|
/** Whether the Command can only be used in Guild (if allowed in DMs) */
|
||||||
|
guildOnly?: boolean
|
||||||
|
/** Whether the Command can only be used in Bot's DMs (if allowed) */
|
||||||
|
dmOnly?: boolean
|
||||||
|
/** Whether the Command can only be used by Bot Owners */
|
||||||
|
ownerOnly?: boolean
|
||||||
|
|
||||||
|
execute(ctx?: CommandContext): any {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CommandsManager {
|
||||||
|
client: CommandClient
|
||||||
|
list: Collection<string, Command> = new Collection()
|
||||||
|
|
||||||
|
constructor(client: CommandClient) {
|
||||||
|
this.client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Find a Command by name/alias */
|
||||||
|
find(search: string): Command | undefined {
|
||||||
|
if(this.client.caseSensitive === false) search = search.toLowerCase()
|
||||||
|
return this.list.find((cmd: Command): boolean => {
|
||||||
|
const name = this.client.caseSensitive === true ? cmd.name : cmd.name.toLowerCase()
|
||||||
|
if(name === search) return true
|
||||||
|
else if(cmd.aliases !== undefined) {
|
||||||
|
let aliases: string[]
|
||||||
|
if(typeof cmd.aliases === "string") aliases = [cmd.aliases]
|
||||||
|
else aliases = cmd.aliases
|
||||||
|
if(this.client.caseSensitive === false) aliases = aliases.map(e => e.toLowerCase())
|
||||||
|
return aliases.includes(search)
|
||||||
|
} else return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Check whether a Command exists or not */
|
||||||
|
exists(search: Command | string): boolean {
|
||||||
|
let exists = false
|
||||||
|
if(typeof search === "string") return this.find(search) !== undefined
|
||||||
|
else {
|
||||||
|
exists = this.find(search.name) !== undefined
|
||||||
|
if(search.aliases !== undefined) {
|
||||||
|
const aliases: string[] = typeof search.aliases === "string" ? [search.aliases] : search.aliases
|
||||||
|
exists = aliases.map(alias => this.find(alias) !== undefined).find(e => e) ?? false
|
||||||
|
}
|
||||||
|
return exists
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add a Command */
|
||||||
|
add(cmd: Command | typeof Command): boolean {
|
||||||
|
// eslint-disable-next-line new-cap
|
||||||
|
if(!(cmd instanceof Command)) cmd = new cmd()
|
||||||
|
if(this.exists(cmd)) return false
|
||||||
|
this.list.set(cmd.name, cmd)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Delete a Command */
|
||||||
|
delete(cmd: string | Command): boolean {
|
||||||
|
const find = typeof cmd === "string" ? this.find(cmd) : cmd
|
||||||
|
if(find === undefined) return false
|
||||||
|
else return this.list.delete(find.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ParsedCommand {
|
||||||
|
name: string
|
||||||
|
args: string[]
|
||||||
|
argString: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const parseCommand = (client: CommandClient, msg: Message, prefix: string): ParsedCommand => {
|
||||||
|
let content = msg.content.slice(prefix.length)
|
||||||
|
if(client.spacesAfterPrefix === true) content = content.trim()
|
||||||
|
const args = content.split(client.betterArgs === true ? /[\S\s]*/ : / +/)
|
||||||
|
const name = args.shift() as string
|
||||||
|
const argString = content.slice(name.length).trim()
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
args,
|
||||||
|
argString
|
||||||
|
}
|
||||||
|
}
|
161
src/models/commandClient.ts
Normal file
161
src/models/commandClient.ts
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
import { Embed, Message } from '../../mod.ts'
|
||||||
|
import { Client, ClientOptions } from './client.ts'
|
||||||
|
import { CommandContext, CommandsManager, parseCommand } from './command.ts'
|
||||||
|
|
||||||
|
type PrefixReturnType = string | string[] | Promise<string | string[]>
|
||||||
|
|
||||||
|
export interface CommandClientOptions extends ClientOptions {
|
||||||
|
prefix: string | string[]
|
||||||
|
mentionPrefix?: boolean
|
||||||
|
getGuildPrefix?: (guildID: string) => PrefixReturnType
|
||||||
|
getUserPrefix?: (userID: string) => PrefixReturnType
|
||||||
|
spacesAfterPrefix?: boolean
|
||||||
|
betterArgs?: boolean
|
||||||
|
owners?: string[]
|
||||||
|
allowBots?: boolean
|
||||||
|
allowDMs?: boolean
|
||||||
|
caseSensitive?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandText = string | Embed
|
||||||
|
|
||||||
|
export interface CommandTexts {
|
||||||
|
GUILD_ONLY?: CommandText
|
||||||
|
OWNER_ONLY?: CommandText
|
||||||
|
DMS_ONLY?: CommandText
|
||||||
|
ERROR?: CommandText
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DefaultCommandTexts: CommandTexts = {
|
||||||
|
GUILD_ONLY: 'This command can only be used in a Server!',
|
||||||
|
OWNER_ONLY: 'This command can only be used by Bot Owners!',
|
||||||
|
DMS_ONLY: 'This command can only be used in Bot\'s DMs!',
|
||||||
|
ERROR: 'An error occured while executing command!'
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Replaces {
|
||||||
|
[name: string]: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const massReplace = (text: string, replaces: Replaces): string => {
|
||||||
|
Object.entries(replaces).forEach(replace => {
|
||||||
|
text = text.replace(new RegExp(`{{${replace[0]}}}`, 'g'), replace[1])
|
||||||
|
})
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CommandClient extends Client {
|
||||||
|
prefix: string | string[]
|
||||||
|
mentionPrefix: boolean
|
||||||
|
getGuildPrefix: (guildID: string) => PrefixReturnType
|
||||||
|
getUserPrefix: (userID: string) => PrefixReturnType
|
||||||
|
spacesAfterPrefix: boolean
|
||||||
|
betterArgs: boolean
|
||||||
|
owners: string[]
|
||||||
|
allowBots: boolean
|
||||||
|
allowDMs: boolean
|
||||||
|
caseSensitive: boolean
|
||||||
|
commands: CommandsManager = new CommandsManager(this)
|
||||||
|
texts: CommandTexts = DefaultCommandTexts
|
||||||
|
|
||||||
|
constructor(options: CommandClientOptions) {
|
||||||
|
super(options)
|
||||||
|
this.prefix = options.prefix
|
||||||
|
this.mentionPrefix = options.mentionPrefix === undefined ? false : options.mentionPrefix
|
||||||
|
this.getGuildPrefix = options.getGuildPrefix === undefined ? (id: string) => this.prefix : options.getGuildPrefix
|
||||||
|
this.getUserPrefix = options.getUserPrefix === undefined ? (id: string) => this.prefix : options.getUserPrefix
|
||||||
|
this.spacesAfterPrefix = options.spacesAfterPrefix === undefined ? false : options.spacesAfterPrefix
|
||||||
|
this.betterArgs = options.betterArgs === undefined ? false : options.betterArgs
|
||||||
|
this.owners = options.owners === undefined ? [] : options.owners
|
||||||
|
this.allowBots = options.allowBots === undefined ? false : options.allowBots
|
||||||
|
this.allowDMs = options.allowDMs === undefined ? true : options.allowDMs
|
||||||
|
this.caseSensitive = options.caseSensitive === undefined ? false : options.caseSensitive
|
||||||
|
|
||||||
|
this.on('messageCreate', async (msg: Message) => await this.processMessage(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
async processMessage(msg: Message): Promise<any> {
|
||||||
|
if (!this.allowBots && msg.author.bot === true) return
|
||||||
|
|
||||||
|
let prefix: string | string[] = this.prefix
|
||||||
|
|
||||||
|
if (msg.guild !== undefined) {
|
||||||
|
let guildPrefix = this.getGuildPrefix(msg.guild.id)
|
||||||
|
if (guildPrefix instanceof Promise) guildPrefix = await guildPrefix
|
||||||
|
prefix = guildPrefix
|
||||||
|
} else {
|
||||||
|
let userPrefix = this.getUserPrefix(msg.author.id)
|
||||||
|
if (userPrefix instanceof Promise) userPrefix = await userPrefix
|
||||||
|
prefix = userPrefix
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof prefix === 'string') {
|
||||||
|
if (msg.content.startsWith(prefix) === false) return
|
||||||
|
} else {
|
||||||
|
const usedPrefix = prefix.find(v => msg.content.startsWith(v))
|
||||||
|
if (usedPrefix === undefined) return
|
||||||
|
else prefix = usedPrefix
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsed = parseCommand(this, msg, prefix)
|
||||||
|
const command = this.commands.find(parsed.name)
|
||||||
|
|
||||||
|
if (command === undefined) return
|
||||||
|
|
||||||
|
const baseReplaces: Replaces = {
|
||||||
|
command: command.name,
|
||||||
|
nameUsed: parsed.name,
|
||||||
|
prefix,
|
||||||
|
username: msg.author.username,
|
||||||
|
tag: msg.author.tag,
|
||||||
|
mention: msg.author.mention,
|
||||||
|
id: msg.author.id
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.guildOnly === true && msg.guild === undefined) {
|
||||||
|
if (this.texts.GUILD_ONLY !== undefined) return this.sendProcessedText(msg, this.texts.GUILD_ONLY, baseReplaces)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (command.dmOnly === true && msg.guild !== undefined) {
|
||||||
|
if (this.texts.DMS_ONLY !== undefined) return this.sendProcessedText(msg, this.texts.DMS_ONLY, baseReplaces)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (command.ownerOnly === true && !this.owners.includes(msg.author.id)) {
|
||||||
|
if (this.texts.OWNER_ONLY !== undefined) return this.sendProcessedText(msg, this.texts.OWNER_ONLY, baseReplaces)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctx: CommandContext = {
|
||||||
|
client: this,
|
||||||
|
name: parsed.name,
|
||||||
|
prefix,
|
||||||
|
args: parsed.args,
|
||||||
|
argString: parsed.argString,
|
||||||
|
message: msg,
|
||||||
|
author: msg.author,
|
||||||
|
command,
|
||||||
|
channel: msg.channel
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.emit('commandUsed', { context: ctx })
|
||||||
|
command.execute(ctx)
|
||||||
|
} catch (e) {
|
||||||
|
if (this.texts.ERROR !== undefined) return this.sendProcessedText(msg, this.texts.ERROR, Object.assign(baseReplaces, { error: e.message }))
|
||||||
|
this.emit('commandError', { command, parsed, error: e })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendProcessedText(msg: Message, text: CommandText, replaces: Replaces): any {
|
||||||
|
if (typeof text === "string") {
|
||||||
|
text = massReplace(text, replaces)
|
||||||
|
return msg.channel.send(text)
|
||||||
|
} else {
|
||||||
|
if (text.description !== undefined) text.description = massReplace(text.description, replaces)
|
||||||
|
if (text.title !== undefined) text.description = massReplace(text.title, replaces)
|
||||||
|
if (text.author?.name !== undefined) text.description = massReplace(text.author.name, replaces)
|
||||||
|
if (text.footer?.text !== undefined) text.description = massReplace(text.footer.text, replaces)
|
||||||
|
return msg.channel.send(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,9 +2,12 @@ import { Client } from '../models/client.ts'
|
||||||
import { MessageOption, TextChannelPayload } from '../types/channel.ts'
|
import { MessageOption, TextChannelPayload } from '../types/channel.ts'
|
||||||
import { CHANNEL_MESSAGE, CHANNEL_MESSAGES } from '../types/endpoint.ts'
|
import { CHANNEL_MESSAGE, CHANNEL_MESSAGES } from '../types/endpoint.ts'
|
||||||
import { Channel } from './channel.ts'
|
import { Channel } from './channel.ts'
|
||||||
|
import { Embed } from "./embed.ts"
|
||||||
import { Message } from './message.ts'
|
import { Message } from './message.ts'
|
||||||
import { MessageMentions } from './messageMentions.ts'
|
import { MessageMentions } from './messageMentions.ts'
|
||||||
|
|
||||||
|
type AllMessageOptions = MessageOption | Embed
|
||||||
|
|
||||||
export class TextChannel extends Channel {
|
export class TextChannel extends Channel {
|
||||||
lastMessageID?: string
|
lastMessageID?: string
|
||||||
lastPinTimestamp?: string
|
lastPinTimestamp?: string
|
||||||
|
@ -23,10 +26,18 @@ export class TextChannel extends Channel {
|
||||||
this.lastPinTimestamp = data.last_pin_timestamp ?? this.lastPinTimestamp
|
this.lastPinTimestamp = data.last_pin_timestamp ?? this.lastPinTimestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
async send (text?: string, option?: MessageOption): Promise<Message> {
|
async send (text?: string | AllMessageOptions, option?: AllMessageOptions): Promise<Message> {
|
||||||
|
if(typeof text === "object") {
|
||||||
|
option = text
|
||||||
|
text = undefined
|
||||||
|
}
|
||||||
if (text === undefined && option === undefined) {
|
if (text === undefined && option === undefined) {
|
||||||
throw new Error('Either text or option is necessary.')
|
throw new Error('Either text or option is necessary.')
|
||||||
}
|
}
|
||||||
|
if(option instanceof Embed) option = {
|
||||||
|
embed: option
|
||||||
|
}
|
||||||
|
|
||||||
const resp = await this.client.rest.post(CHANNEL_MESSAGES(this.id), {
|
const resp = await this.client.rest.post(CHANNEL_MESSAGES(this.id), {
|
||||||
content: text,
|
content: text,
|
||||||
embed: option?.embed,
|
embed: option?.embed,
|
||||||
|
|
18
src/test/cmd.ts
Normal file
18
src/test/cmd.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { CommandClient, Intents } from '../../mod.ts';
|
||||||
|
import PingCommand from "./cmds/ping.ts";
|
||||||
|
import { TOKEN } from './config.ts'
|
||||||
|
|
||||||
|
const client = new CommandClient({
|
||||||
|
prefix: [ "pls", "!" ],
|
||||||
|
spacesAfterPrefix: true
|
||||||
|
})
|
||||||
|
|
||||||
|
client.on('debug', console.log)
|
||||||
|
|
||||||
|
client.on('ready', () => {
|
||||||
|
console.log(`[Login] Logged in as ${client.user?.tag}!`)
|
||||||
|
})
|
||||||
|
|
||||||
|
client.commands.add(PingCommand)
|
||||||
|
|
||||||
|
client.connect(TOKEN, Intents.All)
|
11
src/test/cmds/ping.ts
Normal file
11
src/test/cmds/ping.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { Command } from "../../../mod.ts";
|
||||||
|
import { CommandContext } from "../../models/command.ts";
|
||||||
|
|
||||||
|
export default class PingCommand extends Command {
|
||||||
|
name = "ping"
|
||||||
|
dmOnly = true
|
||||||
|
|
||||||
|
execute(ctx: CommandContext): void {
|
||||||
|
ctx.message.reply(`pong! Latency: ${ctx.client.ping}ms`)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue