Merge branch 'main' into design-fix

This commit is contained in:
Helloyunho 2021-04-30 23:47:13 +09:00 committed by GitHub
commit 461e1557c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 335 additions and 21 deletions

2
src/cache/redis.ts vendored
View file

@ -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 {

View file

@ -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,

View file

@ -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[]

View file

@ -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}` : ''

View file

@ -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,

View 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,

View file

@ -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)
}
}

View file

@ -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
View 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
}