Merge branch 'design-fix' of https://github.com/DjDeveloperr/harmony into design-fix
This commit is contained in:
commit
258ec12906
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…
Reference in a new issue