diff --git a/src/client/client.ts b/src/client/client.ts index ea3650d..e8ea5ef 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -38,7 +38,7 @@ export interface ClientOptions { /** Token of the Bot/User */ token?: string /** Gateway Intents */ - intents?: GatewayIntents[] + intents?: Array /** Cache Adapter to use, defaults to Collections one */ cache?: ICacheAdapter /** Force New Session and don't use cached Session (by persistent caching) */ @@ -77,10 +77,29 @@ export class Client extends HarmonyEventEmitter { rest: RESTManager /** User which Client logs in to, undefined until logs in */ user?: User + + #token?: string + /** Token of the Bot/User */ - token?: string + get token(): string | undefined { + return this.#token + } + + set token(val: string | undefined) { + this.#token = val + } + /** Cache Adapter */ - cache: ICacheAdapter = new DefaultCacheAdapter() + get cache(): ICacheAdapter { + return this.#cache + } + + set cache(val: ICacheAdapter) { + this.#cache = val + } + + #cache: ICacheAdapter = new DefaultCacheAdapter() + /** Gateway Intents */ intents?: GatewayIntents[] /** Whether to force new session or not */ @@ -91,21 +110,23 @@ export class Client extends HarmonyEventEmitter { reactionCacheLifetime: number = 3600000 /** Whether to fetch Uncached Message of Reaction or not? */ fetchUncachedReactions: boolean = false + /** Client Properties */ - clientProperties: ClientProperties + readonly clientProperties!: ClientProperties + /** Slash-Commands Management client */ slash: SlashClient /** Whether to fetch Gateway info or not */ fetchGatewayInfo: boolean = true /** Users Manager, containing all Users cached */ - users: UsersManager = new UsersManager(this) + readonly users: UsersManager = new UsersManager(this) /** Guilds Manager, providing cache & API interface to Guilds */ - guilds: GuildManager = new GuildManager(this) + readonly guilds: GuildManager = new GuildManager(this) /** Channels Manager, providing cache interface to Channels */ - channels: ChannelsManager = new ChannelsManager(this) + readonly channels: ChannelsManager = new ChannelsManager(this) /** Channels Manager, providing cache interface to Channels */ - emojis: EmojisManager = new EmojisManager(this) + readonly emojis: EmojisManager = new EmojisManager(this) /** Last READY timestamp */ upSince?: Date @@ -146,7 +167,9 @@ export class Client extends HarmonyEventEmitter { super() this._id = options.id this.token = options.token - this.intents = options.intents + this.intents = options.intents?.map((e) => + typeof e === 'string' ? GatewayIntents[e] : e + ) this.shards = new ShardManager(this) this.forceNewSession = options.forceNewSession if (options.cache !== undefined) this.cache = options.cache @@ -172,14 +195,17 @@ export class Client extends HarmonyEventEmitter { ;(this as any)._decoratedEvents = undefined } - this.clientProperties = - options.clientProperties === undefined - ? { - os: Deno.build.os, - browser: 'harmony', - device: 'harmony' - } - : options.clientProperties + Object.defineProperty(this, 'clientProperties', { + value: + options.clientProperties === undefined + ? { + os: Deno.build.os, + browser: 'harmony', + device: 'harmony' + } + : options.clientProperties, + enumerable: false + }) if (options.shard !== undefined) this.shard = options.shard if (options.shardCount !== undefined) this.shardCount = options.shardCount diff --git a/src/gateway/mod.ts b/src/gateway/mod.ts index e31b527..81aa16a 100644 --- a/src/gateway/mod.ts +++ b/src/gateway/mod.ts @@ -62,7 +62,7 @@ export class Gateway extends HarmonyEventEmitter { lastPingTimestamp = 0 sessionID?: string private heartbeatServerResponded = false - client: Client + client!: Client cache: GatewayCache private timedIdentify: number | null = null shards?: number[] @@ -70,7 +70,7 @@ export class Gateway extends HarmonyEventEmitter { constructor(client: Client, shards?: number[]) { super() - this.client = client + Object.defineProperty(this, 'client', { value: client, enumerable: false }) this.cache = new GatewayCache(client) this.shards = shards } diff --git a/src/interactions/slashClient.ts b/src/interactions/slashClient.ts index ac76107..02f60c1 100644 --- a/src/interactions/slashClient.ts +++ b/src/interactions/slashClient.ts @@ -47,11 +47,21 @@ export type SlashClientEvents = { export class SlashClient extends HarmonyEventEmitter { id: string | (() => string) client?: Client - token?: string + + #token?: string + + get token(): string | undefined { + return this.#token + } + + set token(val: string | undefined) { + this.#token = val + } + enabled: boolean = true commands: SlashCommandsManager handlers: SlashCommandHandler[] = [] - rest: RESTManager + readonly rest!: RESTManager modules: SlashModule[] = [] publicKey?: string @@ -62,7 +72,14 @@ export class SlashClient extends HarmonyEventEmitter { if (id === undefined) throw new Error('ID could not be found. Pass at least client or token') this.id = id - this.client = options.client + + if (options.client !== undefined) { + Object.defineProperty(this, 'client', { + value: options.client, + enumerable: false + }) + } + this.token = options.token this.publicKey = options.publicKey @@ -85,14 +102,17 @@ export class SlashClient extends HarmonyEventEmitter { }) } - this.rest = - options.client === undefined - ? options.rest === undefined - ? new RESTManager({ - token: this.token - }) - : options.rest - : options.client.rest + Object.defineProperty(this, 'rest', { + value: + options.client === undefined + ? options.rest === undefined + ? new RESTManager({ + token: this.token + }) + : options.rest + : options.client.rest, + enumerable: false + }) this.client?.on( 'interactionCreate', diff --git a/src/interactions/slashCommand.ts b/src/interactions/slashCommand.ts index 26237d3..c05181c 100644 --- a/src/interactions/slashCommand.ts +++ b/src/interactions/slashCommand.ts @@ -198,12 +198,15 @@ export class SlashBuilder { /** Manages Slash Commands, allows fetching/modifying/deleting/creating Slash Commands. */ export class SlashCommandsManager { - slash: SlashClient - rest: RESTManager + readonly slash!: SlashClient + readonly rest!: RESTManager constructor(client: SlashClient) { - this.slash = client - this.rest = client.rest + Object.defineProperty(this, 'slash', { value: client, enumerable: false }) + Object.defineProperty(this, 'rest', { + enumerable: false, + value: client.rest + }) } /** Get all Global Slash Commands */ diff --git a/src/managers/base.ts b/src/managers/base.ts index ad5b3a1..7475160 100644 --- a/src/managers/base.ts +++ b/src/managers/base.ts @@ -1,4 +1,5 @@ import type { Client } from '../client/mod.ts' +import { Base } from '../structures/base.ts' import { Collection } from '../utils/collection.ts' /** @@ -6,15 +7,14 @@ import { Collection } from '../utils/collection.ts' * * You should not be making Managers yourself. */ -export class BaseManager { - client: Client +export class BaseManager extends Base { /** Caches Name or Key used to differentiate caches */ cacheName: string /** Which data type does this cache have */ DataType: any constructor(client: Client, cacheName: string, DataType: any) { - this.client = client + super(client) this.cacheName = cacheName this.DataType = DataType } @@ -87,4 +87,8 @@ export class BaseManager { flush(): any { return this.client.cache.deleteCache(this.cacheName) } + + [Deno.customInspect](): string { + return `Manager(${this.cacheName})` + } } diff --git a/src/managers/baseChild.ts b/src/managers/baseChild.ts index d8d0171..8fcc1b5 100644 --- a/src/managers/baseChild.ts +++ b/src/managers/baseChild.ts @@ -1,15 +1,15 @@ import type { Client } from '../client/mod.ts' +import { Base } from '../structures/base.ts' import { Collection } from '../utils/collection.ts' import { BaseManager } from './base.ts' /** Child Managers validate data from their parents i.e. from Managers */ -export class BaseChildManager { - client: Client +export class BaseChildManager extends Base { /** Parent Manager */ parent: BaseManager constructor(client: Client, parent: BaseManager) { - this.client = client + super(client) this.parent = parent } @@ -62,4 +62,8 @@ export class BaseChildManager { if (fetchValue !== undefined) return fetchValue } } + + [Deno.customInspect](): string { + return `ChildManager(${this.parent.cacheName})` + } } diff --git a/src/rest/manager.ts b/src/rest/manager.ts index 094ddc9..2ef004e 100644 --- a/src/rest/manager.ts +++ b/src/rest/manager.ts @@ -103,8 +103,18 @@ export class RESTManager { * ``` */ api: APIMap + + #token?: string | (() => string | undefined) + /** Token being used for Authorization */ - token?: string | (() => string | undefined) + get token(): string | (() => string | undefined) | undefined { + return this.#token + } + + set token(val: string | (() => string | undefined) | undefined) { + this.#token = val + } + /** Token Type of the Token if any */ tokenType: TokenType = TokenType.Bot /** Headers object which patch the current ones */ @@ -114,13 +124,13 @@ export class RESTManager { /** Whether REST Manager is using Canary API */ canary?: boolean /** Optional Harmony Client object */ - client?: Client + readonly client?: Client endpoints: RESTEndpoints requestTimeout = 30000 - timers: Set = new Set() + readonly timers!: Set apiURL = Constants.DISCORD_API_URL - handlers = new Collection() + readonly handlers!: Collection globalLimit = Infinity globalRemaining = this.globalLimit globalReset: number | null = null @@ -136,11 +146,28 @@ export class RESTManager { if (options?.tokenType !== undefined) this.tokenType = options.tokenType if (options?.userAgent !== undefined) this.userAgent = options.userAgent if (options?.canary !== undefined) this.canary = options.canary - if (options?.client !== undefined) this.client = options.client if (options?.retryLimit !== undefined) this.retryLimit = options.retryLimit if (options?.requestTimeout !== undefined) this.requestTimeout = options.requestTimeout + + if (options?.client !== undefined) { + Object.defineProperty(this, 'client', { + value: options.client, + enumerable: false + }) + } + this.endpoints = new RESTEndpoints(this) + + Object.defineProperty(this, 'timers', { + value: new Set(), + enumerable: false + }) + + Object.defineProperty(this, 'handlers', { + value: new Collection(), + enumerable: false + }) } setTimeout(fn: (...args: any[]) => any, ms: number): number { diff --git a/test/eval.ts b/test/eval.ts new file mode 100644 index 0000000..725e3b6 --- /dev/null +++ b/test/eval.ts @@ -0,0 +1,30 @@ +/* eslint-disable no-eval */ +import * as discord from '../mod.ts' +import { TOKEN } from './config.ts' + +const client = new discord.Client({ + token: TOKEN, + intents: ['GUILD_MESSAGES', 'GUILDS'] +}) + +client.on('messageCreate', async (msg) => { + if (msg.author.id !== '422957901716652033') return + + if (msg.content.startsWith('.eval') === true) { + let code = msg.content.slice(5).trim() + if (code.startsWith('```') === true) code = code.slice(3).trim() + if (code.endsWith('```') === true) code = code.substr(0, code.length - 3) + try { + const result = await eval(code) + msg.reply( + `\`\`\`js\n${Deno.inspect(result).substr(0, 2000 - 20)}\n\`\`\`` + ) + } catch (e) { + msg.reply(`\`\`\`js\n${e}\n\`\`\``, { + allowedMentions: { replied_user: false } + }) + } + } +}) + +client.connect().then(() => console.log('Ready!')) diff --git a/test/music.ts b/test/music.ts index ea7a463..9f621ee 100644 --- a/test/music.ts +++ b/test/music.ts @@ -8,12 +8,12 @@ import { CommandContext, Extension, Collection, - GuildTextChannel -} from '../../mod.ts' + GuildTextChannel, + Interaction, + slash +} from '../mod.ts' import { LL_IP, LL_PASS, LL_PORT, TOKEN } from './config.ts' import { Manager, Player } from 'https://deno.land/x/lavadeno/mod.ts' -import { Interaction } from '../structures/slash.ts' -import { slash } from '../client/mod.ts' // import { SlashCommandOptionType } from '../types/slash.ts' export const nodes = [ diff --git a/test/trigger.ts b/test/trigger.ts new file mode 100644 index 0000000..98ea1a2 --- /dev/null +++ b/test/trigger.ts @@ -0,0 +1,87 @@ +/* eslint-disable @typescript-eslint/strict-boolean-expressions */ +/* eslint-disable no-control-regex */ +import { Client, Embed } from '../mod.ts' +import { TOKEN } from './config.ts' + +const client = new Client({ + token: TOKEN, + intents: ['GUILDS', 'GUILD_MESSAGES'] +}) + +const NAME_MATCH = /[^a-zA-Z0-9_]/ +const STD_REGEX = /\/?std(@[\x00-\x2e\x30-\xff]+)?\/([a-zA-Z0-9]+)(\/[\S\s]+)?/ +const X_REGEX = /\/?x\/([a-zA-Z0-9]+)(@[\x00-\x2e\x30-\xff]+)?(\/[\S\s]+)?/ + +export async function fetchModule(name: string): Promise { + if (name.match(NAME_MATCH) !== null) return null + return fetch(`https://api.deno.land/modules/${name}`, { + credentials: 'omit', + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.0', + Accept: 'application/json', + 'Accept-Language': 'en-US,en;q=0.5', + Pragma: 'no-cache', + 'Cache-Control': 'no-cache' + }, + referrer: 'https://deno.land/x', + mode: 'cors' + }) + .then((res) => { + if (res.status !== 200) throw new Error('not found') + return res + }) + .then((r) => r.json()) + .then((json) => { + if (!json.success) throw new Error('failed') + return json + }) + .then((data) => data.data) + .catch(() => null) +} + +client.on('messageCreate', async (msg) => { + if (msg.author.bot === true) return + + let match + if ( + (match = msg.content.match(STD_REGEX)) || + (match = msg.content.match(X_REGEX)) + ) { + let x = match[0].trim() + + if (!x.startsWith('/')) x = `/${x}` + + let type + if (x.startsWith('/std')) { + x = x.slice(4) + type = 'std' + } else { + x = x.slice(3) + type = 'x' + } + + x = x.trim() + const name = x.split('/')[0].split('@')[0] + const mod = await fetchModule(type === 'std' ? 'std' : name) + if (mod === null) return + + msg.channel.send( + new Embed() + .setColor('#7289DA') + .setURL( + `https://deno.land/${type}${ + x.startsWith('/') || x.startsWith('@') ? '' : '/' + }${x}` + ) + .setTitle( + `${type}${x.startsWith('/') || x.startsWith('@') ? '' : '/'}${x}` + ) + .setDescription(mod.description ?? 'No description.') + .setFooter(`${mod.star_count ?? 0}`, 'https://kokoro.pw/colleague.png') + ) + } +}) + +console.log('Connecting...') +client.connect().then(() => console.log('Ready!'))