Merge branch 'main' into design-fix
This commit is contained in:
		
						commit
						461e1557c5
					
				
					 16 changed files with 335 additions and 21 deletions
				
			
		
							
								
								
									
										6
									
								
								deps.ts
									
										
									
									
									
								
							
							
						
						
									
										6
									
								
								deps.ts
									
										
									
									
									
								
							|  | @ -1,6 +1,6 @@ | |||
| export { EventEmitter } from 'https://deno.land/x/event@0.2.1/mod.ts' | ||||
| export { EventEmitter } from 'https://deno.land/x/event@1.0.0/mod.ts' | ||||
| export { unzlib } from 'https://denopkg.com/DjDeveloperr/denoflate@1.2/mod.ts' | ||||
| export { fetchAuto } from 'https://deno.land/x/fetchbase64@1.0.0/mod.ts' | ||||
| export { walk } from 'https://deno.land/std@0.86.0/fs/walk.ts' | ||||
| export { join } from 'https://deno.land/std@0.86.0/path/mod.ts' | ||||
| export { walk } from 'https://deno.land/std@0.95.0/fs/walk.ts' | ||||
| export { join } from 'https://deno.land/std@0.95.0/path/mod.ts' | ||||
| export { Mixin } from 'https://esm.sh/ts-mixer@5.4.0' | ||||
|  |  | |||
							
								
								
									
										1
									
								
								mod.ts
									
										
									
									
									
								
							
							
						
						
									
										1
									
								
								mod.ts
									
										
									
									
									
								
							|  | @ -194,3 +194,4 @@ export { | |||
|   default as getChannelByType | ||||
| } from './src/utils/channel.ts' | ||||
| export * from './src/utils/interactions.ts' | ||||
| export * from "./src/utils/command.ts" | ||||
|  |  | |||
							
								
								
									
										2
									
								
								src/cache/redis.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/cache/redis.ts
									
										
									
									
										vendored
									
									
								
							|  | @ -4,7 +4,7 @@ import { | |||
|   connect, | ||||
|   Redis, | ||||
|   RedisConnectOptions | ||||
| } from 'https://deno.land/x/redis@v0.14.1/mod.ts' | ||||
| } from 'https://deno.land/x/redis@v0.22.0/mod.ts' | ||||
| 
 | ||||
| /** Redis Cache Adapter for using Redis as a cache-provider. */ | ||||
| export class RedisCacheAdapter implements ICacheAdapter { | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ import { | |||
|   CommandsManager, | ||||
|   parseCommand | ||||
| } from './command.ts' | ||||
| import { parseArgs } from '../utils/command.ts' | ||||
| import { Extension, ExtensionsManager } from './extension.ts' | ||||
| 
 | ||||
| type PrefixReturnType = string | string[] | Promise<string | string[]> | ||||
|  | @ -239,7 +240,7 @@ export class CommandClient extends Client implements CommandClientOptions { | |||
|       client: this, | ||||
|       name: parsed.name, | ||||
|       prefix, | ||||
|       args: parsed.args, | ||||
|       args: parseArgs(command.args, parsed.args), | ||||
|       argString: parsed.argString, | ||||
|       message: msg, | ||||
|       author: msg.author, | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ import { Collection } from '../utils/collection.ts' | |||
| import type { CommandClient } from './client.ts' | ||||
| import type { Extension } from './extension.ts' | ||||
| import { join, walk } from '../../deps.ts' | ||||
| 
 | ||||
| import type { Args } from '../utils/command.ts' | ||||
| export interface CommandContext { | ||||
|   /** The Client object */ | ||||
|   client: CommandClient | ||||
|  | @ -23,7 +23,7 @@ export interface CommandContext { | |||
|   /** Name of Command which was used */ | ||||
|   name: string | ||||
|   /** Array of Arguments used with Command */ | ||||
|   args: string[] | ||||
|   args: Record<string, unknown> | null | ||||
|   /** Complete Raw String of Arguments */ | ||||
|   argString: string | ||||
|   /** Guild which the command has called */ | ||||
|  | @ -46,7 +46,7 @@ export interface CommandOptions { | |||
|   /** Usage Example of Command, only Arguments (without Prefix and Name) */ | ||||
|   examples?: string | string[] | ||||
|   /** Does the Command take Arguments? Maybe number of required arguments? Or list of arguments? */ | ||||
|   args?: number | boolean | string[] | ||||
|   args?: Args[] | ||||
|   /** Permissions(s) required by both User and Bot in order to use Command */ | ||||
|   permissions?: string | string[] | ||||
|   /** Permission(s) required for using Command */ | ||||
|  | @ -81,7 +81,7 @@ export class Command implements CommandOptions { | |||
|   extension?: Extension | ||||
|   usage?: string | string[] | ||||
|   examples?: string | string[] | ||||
|   args?: number | boolean | string[] | ||||
|   args?: Args[] | ||||
|   permissions?: string | string[] | ||||
|   userPermissions?: string | string[] | ||||
|   botPermissions?: string | string[] | ||||
|  |  | |||
|  | @ -399,7 +399,7 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> { | |||
|       await this.cache.delete(`seq_${this.shards?.join('-') ?? '0'}`) | ||||
|     } | ||||
| 
 | ||||
|     this.close(1000, RECONNECT_REASON) | ||||
|     this.closeGateway(1000, RECONNECT_REASON) | ||||
|     this.initWebsocket() | ||||
|   } | ||||
| 
 | ||||
|  | @ -418,7 +418,7 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> { | |||
|     this.websocket.onerror = this.onerror.bind(this) as any | ||||
|   } | ||||
| 
 | ||||
|   close(code: number = 1000, reason?: string): void { | ||||
|   closeGateway(code: number = 1000, reason?: string): void { | ||||
|     this.debug( | ||||
|       `Closing with code ${code}${ | ||||
|         reason !== undefined && reason !== '' ? ` and reason ${reason}` : '' | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ import { Channel } from '../structures/channel.ts' | |||
| import { Embed } from '../structures/embed.ts' | ||||
| import { Message } from '../structures/message.ts' | ||||
| import type { TextChannel } from '../structures/textChannel.ts' | ||||
| import type { User } from '../structures/user.ts' | ||||
| import type { | ||||
|   ChannelPayload, | ||||
|   GuildChannelPayload, | ||||
|  | @ -19,6 +20,21 @@ export class ChannelsManager extends BaseManager<ChannelPayload, Channel> { | |||
|     super(client, 'channels', Channel) | ||||
|   } | ||||
| 
 | ||||
|   async getUserDM(user: User | string): Promise<string | undefined> { | ||||
|     return this.client.cache.get( | ||||
|       'user_dms', | ||||
|       typeof user === 'string' ? user : user.id | ||||
|     ) | ||||
|   } | ||||
| 
 | ||||
|   async setUserDM(user: User | string, id: string): Promise<void> { | ||||
|     await this.client.cache.set( | ||||
|       'user_dms', | ||||
|       typeof user === 'string' ? user : user.id, | ||||
|       id | ||||
|     ) | ||||
|   } | ||||
| 
 | ||||
|   // Override get method as Generic
 | ||||
|   async get<T = Channel>(key: string): Promise<T | undefined> { | ||||
|     const data = await this._get(key) | ||||
|  | @ -97,7 +113,7 @@ export class ChannelsManager extends BaseManager<ChannelPayload, Channel> { | |||
|     } | ||||
| 
 | ||||
|     const payload: any = { | ||||
|       content: content, | ||||
|       content: content ?? option?.content, | ||||
|       embed: option?.embed, | ||||
|       file: option?.file, | ||||
|       files: option?.files, | ||||
|  | @ -163,7 +179,7 @@ export class ChannelsManager extends BaseManager<ChannelPayload, Channel> { | |||
|     const newMsg = await this.client.rest.api.channels[channelID].messages[ | ||||
|       typeof message === 'string' ? message : message.id | ||||
|     ].patch({ | ||||
|       content: text, | ||||
|       content: text ?? option?.content, | ||||
|       embed: option?.embed !== undefined ? option.embed.toJSON() : undefined, | ||||
|       // Cannot upload new files with Message
 | ||||
|       // file: option?.file,
 | ||||
|  |  | |||
|  | @ -10,11 +10,17 @@ import { RequestQueue } from './queue.ts' | |||
| import { APIRequest } from './request.ts' | ||||
| 
 | ||||
| function parseResponse(res: Response, raw: boolean): any { | ||||
|   if (raw) return res | ||||
|   if (res.status === 204) return undefined | ||||
|   if (res.headers.get('content-type')?.startsWith('application/json') === true) | ||||
|     return res.json() | ||||
|   return res.arrayBuffer().then((e) => new Uint8Array(e)) | ||||
|   let result | ||||
|   if (res.status === 204) result = Promise.resolve(undefined) | ||||
|   else if ( | ||||
|     res.headers.get('content-type')?.startsWith('application/json') === true | ||||
|   ) | ||||
|     result = res.json() | ||||
|   else result = res.arrayBuffer().then((e) => new Uint8Array(e)) | ||||
| 
 | ||||
|   if (raw) { | ||||
|     return { response: res, body: result } | ||||
|   } else return result | ||||
| } | ||||
| 
 | ||||
| function getAPIOffset(serverDate: number | string): number { | ||||
|  | @ -197,7 +203,7 @@ export class BucketHandler { | |||
| 
 | ||||
|       let data | ||||
|       try { | ||||
|         data = await parseResponse(res, request.options.rawResponse ?? false) | ||||
|         data = await parseResponse(res, false) | ||||
|       } catch (err) { | ||||
|         throw new HTTPError( | ||||
|           err.message, | ||||
|  |  | |||
|  | @ -6,6 +6,8 @@ import { ImageURL } from './cdn.ts' | |||
| import type { ImageSize, ImageFormats } from '../types/cdn.ts' | ||||
| import { DEFAULT_USER_AVATAR, USER_AVATAR } from '../types/endpoint.ts' | ||||
| import type { DMChannel } from './dmChannel.ts' | ||||
| import { AllMessageOptions } from './textChannel.ts' | ||||
| import { Message } from './message.ts' | ||||
| 
 | ||||
| export class User extends SnowflakeBase { | ||||
|   id: string | ||||
|  | @ -94,4 +96,25 @@ export class User extends SnowflakeBase { | |||
|   async createDM(): Promise<DMChannel> { | ||||
|     return this.client.createDM(this) | ||||
|   } | ||||
| 
 | ||||
|   async resolveDM(): Promise<DMChannel> { | ||||
|     const dmID = await this.client.channels.getUserDM(this.id) | ||||
|     const dm = | ||||
|       (dmID !== undefined | ||||
|         ? await this.client.channels.get<DMChannel>(dmID) | ||||
|         : undefined) ?? | ||||
|       (await this.createDM().then((chan) => | ||||
|         this.client.channels.setUserDM(this.id, chan.id).then(() => chan) | ||||
|       )) | ||||
|     // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
 | ||||
|     return dm! | ||||
|   } | ||||
| 
 | ||||
|   async send( | ||||
|     content: string | AllMessageOptions, | ||||
|     options?: AllMessageOptions | ||||
|   ): Promise<Message> { | ||||
|     const dm = await this.resolveDM() | ||||
|     return dm.send(content, options) | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -204,6 +204,7 @@ export interface AllowedMentionsPayload { | |||
| } | ||||
| 
 | ||||
| export interface MessageOptions { | ||||
|   content?: string | ||||
|   tts?: boolean | ||||
|   embed?: Embed | ||||
|   file?: MessageAttachment | ||||
|  |  | |||
							
								
								
									
										107
									
								
								src/utils/command.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								src/utils/command.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,107 @@ | |||
| interface MentionToRegex { | ||||
|   [key: string]: RegExp | ||||
|   mentionUser: RegExp | ||||
|   mentionRole: RegExp | ||||
|   mentionChannel: RegExp | ||||
| } | ||||
| 
 | ||||
| const mentionToRegex: MentionToRegex = { | ||||
|   mentionUser: /<@!?(\d{17,19})>/, | ||||
|   mentionRole: /<@&(\d{17,19})>/, | ||||
|   mentionChannel: /<#(\d{17,19})>/ | ||||
| } | ||||
| 
 | ||||
| export type CommandArgumentMatchTypes = | ||||
|   | 'flag' | ||||
|   | 'mentionUser' | ||||
|   | 'mentionRole' | ||||
|   | 'mentionChannel' | ||||
|   | 'content' | ||||
|   | 'rest' | ||||
| 
 | ||||
| export interface Args<T = unknown> { | ||||
|   name: string | ||||
|   match: CommandArgumentMatchTypes | ||||
|   defaultValue?: T | ||||
|   flag?: string | ||||
| } | ||||
| 
 | ||||
| export function parseArgs( | ||||
|   commandArgs: Args[] | undefined, | ||||
|   messageArgs: string[] | ||||
| ): Record<string, unknown> | null { | ||||
|   if (commandArgs === undefined) return null | ||||
| 
 | ||||
|   const messageArgsNullableCopy: Array<string | null> = [...messageArgs] | ||||
|   const args: Record<string, unknown> = {} | ||||
| 
 | ||||
|   for (const entry of commandArgs) { | ||||
|     switch (entry.match) { | ||||
|       case 'flag': | ||||
|         parseFlags(args, entry, messageArgsNullableCopy) | ||||
|         break | ||||
|       case 'mentionUser': | ||||
|       case 'mentionRole': | ||||
|       case 'mentionChannel': | ||||
|         parseMention(args, entry, messageArgsNullableCopy) | ||||
|         break | ||||
|       case 'content': | ||||
|         parseContent(args, entry, messageArgs) | ||||
|         break | ||||
|       case 'rest': | ||||
|         parseRest(args, entry, messageArgsNullableCopy) | ||||
|         break | ||||
|     } | ||||
|   } | ||||
|   return args | ||||
| } | ||||
| 
 | ||||
| function parseFlags( | ||||
|   args: Record<string, unknown>, | ||||
|   entry: Args, | ||||
|   argsNullable: Array<string | null> | ||||
| ): void { | ||||
|   for (let i = 0; i < argsNullable.length; i++) { | ||||
|     if (entry.flag === argsNullable[i]) { | ||||
|       argsNullable[i] = null | ||||
|       args[entry.name] = true | ||||
|       break | ||||
|     } else args[entry.name] = entry.defaultValue ?? false | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function parseMention( | ||||
|   args: Record<string, unknown>, | ||||
|   entry: Args, | ||||
|   argsNullable: Array<string | null> | ||||
| ): void { | ||||
|   const regex = mentionToRegex[entry.match] | ||||
|   const index = argsNullable.findIndex( | ||||
|     (x) => typeof x === 'string' && regex.test(x) | ||||
|   ) | ||||
|   const regexMatches = regex.exec(argsNullable[index]!) | ||||
|   args[entry.name] = | ||||
|     regexMatches !== null | ||||
|       ? regexMatches[0].replace(regex, '$1') | ||||
|       : entry.defaultValue | ||||
|   argsNullable[index] = null | ||||
| } | ||||
| 
 | ||||
| function parseContent( | ||||
|   args: Record<string, unknown>, | ||||
|   entry: Args, | ||||
|   argsNonNullable: Array<string | null> | ||||
| ): void { | ||||
|   args[entry.name] = | ||||
|     argsNonNullable.length > 0 ? argsNonNullable : entry.defaultValue | ||||
| } | ||||
| 
 | ||||
| function parseRest( | ||||
|   args: Record<string, unknown>, | ||||
|   entry: Args, | ||||
|   argsNullable: Array<string | null> | ||||
| ): void { | ||||
|   const restValues = argsNullable.filter((x) => typeof x === 'string') | ||||
|   args[entry.name] = | ||||
|     restValues !== null ? restValues?.join(' ') : entry.defaultValue | ||||
| } | ||||
							
								
								
									
										150
									
								
								test/argsparser_test.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								test/argsparser_test.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,150 @@ | |||
| import { Args, parseArgs } from '../src/utils/command.ts' | ||||
| import { | ||||
|   assertEquals, | ||||
|   assertNotEquals | ||||
| } from 'https://deno.land/std@0.95.0/testing/asserts.ts' | ||||
| 
 | ||||
| const commandArgs: Args[] = [ | ||||
|   { | ||||
|     name: 'originalMessage', | ||||
|     match: 'content' | ||||
|   }, | ||||
|   { | ||||
|     name: 'permaban', | ||||
|     match: 'flag', | ||||
|     flag: '--permanent', | ||||
|     defaultValue: true | ||||
|   }, | ||||
|   { | ||||
|     name: 'user', | ||||
|     match: 'mentionUser' | ||||
|   }, | ||||
|   { | ||||
|     name: 'reason', | ||||
|     match: 'rest', | ||||
|     defaultValue: 'ree' | ||||
|   } | ||||
| ] | ||||
| 
 | ||||
| const messageArgs1: string[] = [ | ||||
|   '<@!708544768342229012>', | ||||
|   '--permanent', | ||||
|   'bye', | ||||
|   'bye', | ||||
|   'Skyler' | ||||
| ] | ||||
| const expectedResult1 = { | ||||
|   originalMessage: [ | ||||
|     '<@!708544768342229012>', | ||||
|     '--permanent', | ||||
|     'bye', | ||||
|     'bye', | ||||
|     'Skyler' | ||||
|   ], | ||||
|   permaban: true, | ||||
|   user: '708544768342229012', | ||||
|   reason: 'bye bye Skyler' | ||||
| } | ||||
| 
 | ||||
| Deno.test({ | ||||
|   only: false, | ||||
|   name: 'parse command arguments 1 (assertEquals)', | ||||
|   fn: () => { | ||||
|     const result = parseArgs(commandArgs, messageArgs1) | ||||
|     assertEquals(result, expectedResult1) | ||||
|   }, | ||||
|   sanitizeOps: true, | ||||
|   sanitizeResources: true, | ||||
|   sanitizeExit: true | ||||
| }) | ||||
| 
 | ||||
| const messageArgs2: string[] = [ | ||||
|   '<@!708544768342229012>', | ||||
|   'bye', | ||||
|   'bye', | ||||
|   'Skyler' | ||||
| ] | ||||
| const expectedResult2 = { | ||||
|   originalMessage: ['<@!708544768342229012>', 'bye', 'bye', 'Skyler'], | ||||
|   permaban: true, | ||||
|   user: '708544768342229012', | ||||
|   reason: 'bye bye Skyler' | ||||
| } | ||||
| 
 | ||||
| Deno.test({ | ||||
|   name: 'parse command arguments 2 (assertEquals)', | ||||
|   fn: () => { | ||||
|     const result = parseArgs(commandArgs, messageArgs2) | ||||
|     assertEquals(result, expectedResult2) | ||||
|   }, | ||||
|   sanitizeOps: true, | ||||
|   sanitizeResources: true, | ||||
|   sanitizeExit: true | ||||
| }) | ||||
| 
 | ||||
| const messageArgs3: string[] = [ | ||||
|   '<@!708544768342229012>', | ||||
|   'bye', | ||||
|   'bye', | ||||
|   'Skyler' | ||||
| ] | ||||
| const expectedResult3 = { | ||||
|   permaban: false, | ||||
|   user: '708544768342229012', | ||||
|   reason: 'bye bye Skyler' | ||||
| } | ||||
| 
 | ||||
| Deno.test({ | ||||
|   name: 'parse command arguments default value (assertNotEquals)', | ||||
|   fn: () => { | ||||
|     const result = parseArgs(commandArgs, messageArgs3) | ||||
|     assertNotEquals(result, expectedResult3) | ||||
|   }, | ||||
|   sanitizeOps: true, | ||||
|   sanitizeResources: true, | ||||
|   sanitizeExit: true | ||||
| }) | ||||
| 
 | ||||
| const commandArgs2: Args[] = [ | ||||
|   { | ||||
|     name: 'user', | ||||
|     match: 'mentionUser' | ||||
|   }, | ||||
|   { | ||||
|     name: 'channel', | ||||
|     match: 'mentionChannel' | ||||
|   }, | ||||
|   { | ||||
|     name: 'role', | ||||
|     match: 'mentionRole' | ||||
|   }, | ||||
|   { | ||||
|     name: 'reason', | ||||
|     match: 'rest', | ||||
|     defaultValue: 'ree' | ||||
|   } | ||||
| ] | ||||
| 
 | ||||
| const messageArgs4: string[] = [ | ||||
|   '<@!708544768342229012>', | ||||
|   'bye', | ||||
|   '<#783319033730564098>', | ||||
|   '<@&836715188690092032>' | ||||
| ] | ||||
| const expectedResult4 = { | ||||
|   channel: '783319033730564098', | ||||
|   role: '836715188690092032', | ||||
|   user: '708544768342229012', | ||||
|   reason: 'bye' | ||||
| } | ||||
| 
 | ||||
| Deno.test({ | ||||
|   name: 'parse command arguments mentions (assertEquals)', | ||||
|   fn: () => { | ||||
|     const result = parseArgs(commandArgs2, messageArgs4) | ||||
|     assertEquals(result, expectedResult4) | ||||
|   }, | ||||
|   sanitizeOps: true, | ||||
|   sanitizeResources: true, | ||||
|   sanitizeExit: true | ||||
| }) | ||||
							
								
								
									
										2
									
								
								test/deps.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								test/deps.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | |||
| export * from 'https://deno.land/std@0.95.0/testing/asserts.ts' | ||||
| export * from 'https://deno.land/std@0.95.0/http/server.ts' | ||||
|  | @ -254,6 +254,13 @@ client.on('messageCreate', async (msg: Message) => { | |||
|       buf += `\n${role.name === '@everyone' ? 'everyone' : role.name}` | ||||
|     } | ||||
|     msg.reply(buf) | ||||
|   } else if (msg.content === '!addrole') { | ||||
|     msg.member?.roles.add('837255383759716362') | ||||
|   } else if (msg.content === '!dm') { | ||||
|     console.log('wtf') | ||||
|     msg.author.send('UwU').then((m) => { | ||||
|       msg.reply(`Done, ${m.id}`) | ||||
|     }) | ||||
|   } else if (msg.content === '!timer') { | ||||
|     msg.channel.send('3...').then((msg) => { | ||||
|       setTimeout(() => { | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import { SlashClient } from '../mod.ts' | ||||
| import { SLASH_ID, SLASH_PUB_KEY, SLASH_TOKEN } from './config.ts' | ||||
| import { listenAndServe } from 'https://deno.land/std@0.90.0/http/server.ts' | ||||
| import { listenAndServe } from './deps.ts' | ||||
| 
 | ||||
| const slash = new SlashClient({ | ||||
|   id: SLASH_ID, | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ import { TOKEN } from '../src/test/config.ts' | |||
| import { | ||||
|   assertEquals, | ||||
|   assertExists | ||||
| } from 'https://deno.land/std@0.84.0/testing/asserts.ts' | ||||
| } from './deps.ts' | ||||
| 
 | ||||
| //#region Lib Tests
 | ||||
| Deno.test({ | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue