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