From 417854b1bb9209359e39088e8db1465ef7819662 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Thu, 31 Dec 2020 10:19:58 +0530 Subject: [PATCH 01/13] quick fix - use _delete for cache delete --- src/gateway/handlers/channelDelete.ts | 2 +- src/gateway/handlers/guildDelete.ts | 2 +- src/gateway/handlers/guildRoleDelete.ts | 1 + src/gateway/handlers/inviteDelete.ts | 3 +-- src/gateway/handlers/messageDelete.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/gateway/handlers/channelDelete.ts b/src/gateway/handlers/channelDelete.ts index 37cd2b4..114fcdc 100644 --- a/src/gateway/handlers/channelDelete.ts +++ b/src/gateway/handlers/channelDelete.ts @@ -7,7 +7,7 @@ export const channelDelete: GatewayEventHandler = async ( ) => { const channel = await gateway.client.channels.get(d.id) if (channel !== undefined) { - await gateway.client.channels.delete(d.id) + await gateway.client.channels._delete(d.id) gateway.client.emit('channelDelete', channel) } } diff --git a/src/gateway/handlers/guildDelete.ts b/src/gateway/handlers/guildDelete.ts index 9355ab2..5685a70 100644 --- a/src/gateway/handlers/guildDelete.ts +++ b/src/gateway/handlers/guildDelete.ts @@ -13,7 +13,7 @@ export const guildDelete: GatewayEventHandler = async ( await guild.channels.flush() await guild.roles.flush() await guild.presences.flush() - await gateway.client.guilds.delete(d.id) + await gateway.client.guilds._delete(d.id) gateway.client.emit('guildDelete', guild) } diff --git a/src/gateway/handlers/guildRoleDelete.ts b/src/gateway/handlers/guildRoleDelete.ts index b1e63da..3abd0c6 100644 --- a/src/gateway/handlers/guildRoleDelete.ts +++ b/src/gateway/handlers/guildRoleDelete.ts @@ -13,6 +13,7 @@ export const guildRoleDelete: GatewayEventHandler = async ( const role = await guild.roles.get(d.role_id) // Shouldn't happen either if (role === undefined) return + await guild.roles._delete(d.role_id) gateway.client.emit('guildRoleDelete', role) } diff --git a/src/gateway/handlers/inviteDelete.ts b/src/gateway/handlers/inviteDelete.ts index b05c18c..486c040 100644 --- a/src/gateway/handlers/inviteDelete.ts +++ b/src/gateway/handlers/inviteDelete.ts @@ -19,7 +19,6 @@ export const inviteDelete: GatewayEventHandler = async ( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const cachedGuild = await gateway.client.guilds.get(d.guild_id!) - // TODO(DjDeveloperr): Make it support self-bots and make Guild not always defined if (cachedInvite === undefined) { const uncachedInvite: PartialInvitePayload = { guild: (cachedGuild as unknown) as Guild, @@ -28,7 +27,7 @@ export const inviteDelete: GatewayEventHandler = async ( } return gateway.client.emit('inviteDeleteUncached', uncachedInvite) } else { - await guild.invites.delete(d.code) + await guild.invites._delete(d.code) gateway.client.emit('inviteDelete', cachedInvite) } } diff --git a/src/gateway/handlers/messageDelete.ts b/src/gateway/handlers/messageDelete.ts index efccc70..601beaa 100644 --- a/src/gateway/handlers/messageDelete.ts +++ b/src/gateway/handlers/messageDelete.ts @@ -15,6 +15,6 @@ export const messageDelete: GatewayEventHandler = async ( const message = await channel.messages.get(d.id) if (message === undefined) return gateway.client.emit('messageDeleteUncached', d) - await channel.messages.delete(d.id) + await channel.messages._delete(d.id) gateway.client.emit('messageDelete', message) } From 7a2b71b648637b92f2eedbcde766bed8ab65a7d2 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Thu, 31 Dec 2020 10:37:40 +0530 Subject: [PATCH 02/13] feat: full invite support --- src/managers/invites.ts | 71 +++++++++++++++++++++++++++++++++-- src/structures/textChannel.ts | 7 ++++ 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/src/managers/invites.ts b/src/managers/invites.ts index e5baf27..44ea8f9 100644 --- a/src/managers/invites.ts +++ b/src/managers/invites.ts @@ -1,10 +1,24 @@ +import { GuildTextChannel, User } from '../../mod.ts' import { Client } from '../models/client.ts' import { Guild } from '../structures/guild.ts' import { Invite } from '../structures/invite.ts' -import { INVITE } from '../types/endpoint.ts' +import { CHANNEL_INVITES, GUILD_INVITES, INVITE } from '../types/endpoint.ts' import { InvitePayload } from '../types/invite.ts' import { BaseManager } from './base.ts' +export enum InviteTargetUserType { + STREAM = 1 +} + +export interface CreateInviteOptions { + maxAge?: number + maxUses?: number + temporary?: boolean + unique?: boolean + targetUser?: string | User + targetUserType?: InviteTargetUserType +} + export class InviteManager extends BaseManager { guild: Guild @@ -20,10 +34,10 @@ export class InviteManager extends BaseManager { } /** Fetch an Invite */ - async fetch(id: string): Promise { + async fetch(id: string, withCounts: boolean = true): Promise { return await new Promise((resolve, reject) => { this.client.rest - .get(INVITE(id)) + .get(`${INVITE(id)}${withCounts ? '?with_counts=true' : ''}`) .then(async (data) => { this.set(id, data as InvitePayload) const newInvite = await this.get(data.code) @@ -33,6 +47,57 @@ export class InviteManager extends BaseManager { }) } + /** Fetch all Invites of a Guild or a specific Channel */ + async fetchAll(channel?: string | GuildTextChannel): Promise { + const rawInvites = (await this.client.rest.get( + channel === undefined + ? GUILD_INVITES(this.guild.id) + : CHANNEL_INVITES(typeof channel === 'string' ? channel : channel.id) + )) as InvitePayload[] + + const res: Invite[] = [] + + for (const raw of rawInvites) { + await this.set(raw.code, raw) + res.push(new Invite(this.client, raw)) + } + + return res + } + + /** Delete an Invite */ + async delete(invite: string | Invite): Promise { + await this.client.rest.delete( + INVITE(typeof invite === 'string' ? invite : invite.code) + ) + return true + } + + /** Create an Invite */ + async create( + channel: string | GuildTextChannel, + options?: CreateInviteOptions + ): Promise { + const raw = ((await this.client.rest.post( + CHANNEL_INVITES(typeof channel === 'string' ? channel : channel.id), + { + max_age: options?.maxAge, + max_uses: options?.maxUses, + temporary: options?.temporary, + unique: options?.unique, + target_user: + options?.targetUser === undefined + ? undefined + : typeof options.targetUser === 'string' + ? options.targetUser + : options.targetUser.id, + target_user_type: options?.targetUser + } + )) as unknown) as InvitePayload + + return new Invite(this.client, raw) + } + async fromPayload(invites: InvitePayload[]): Promise { for (const invite of invites) { await this.set(invite.code, invite) diff --git a/src/structures/textChannel.ts b/src/structures/textChannel.ts index 398f2af..0546dcd 100644 --- a/src/structures/textChannel.ts +++ b/src/structures/textChannel.ts @@ -1,3 +1,4 @@ +import { CreateInviteOptions } from '../managers/invites.ts' import { MessagesManager } from '../managers/messages.ts' import { Client } from '../models/client.ts' import { @@ -22,6 +23,7 @@ import { Channel } from './channel.ts' import { Embed } from './embed.ts' import { Emoji } from './emoji.ts' import { Guild } from './guild.ts' +import { Invite } from './invite.ts' import { Member } from './member.ts' import { Message } from './message.ts' import { User } from './user.ts' @@ -319,4 +321,9 @@ export class GuildTextChannel extends TextChannel { return this } + + /** Create an Invite for this Channel */ + async createInvite(options?: CreateInviteOptions): Promise { + return this.guild.invites.create(this.id, options) + } } From 8edef36eadd824a1f2705a355080acdc2764b16b Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Thu, 31 Dec 2020 10:42:13 +0530 Subject: [PATCH 03/13] fix --- src/managers/invites.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/managers/invites.ts b/src/managers/invites.ts index 44ea8f9..0cbf055 100644 --- a/src/managers/invites.ts +++ b/src/managers/invites.ts @@ -91,7 +91,7 @@ export class InviteManager extends BaseManager { : typeof options.targetUser === 'string' ? options.targetUser : options.targetUser.id, - target_user_type: options?.targetUser + target_user_type: options?.targetUserType } )) as unknown) as InvitePayload From b344c2e24a337be00786e9b997faf6c6d0942e3f Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 1 Jan 2021 10:18:18 +0530 Subject: [PATCH 04/13] fix: Guild#chunk --- src/gateway/index.ts | 23 ++++++++++++----------- src/structures/guild.ts | 1 - src/structures/invite.ts | 35 +++++++++++++++++++++++++++++++++++ src/test/chunk.ts | 24 ++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 12 deletions(-) create mode 100644 src/test/chunk.ts diff --git a/src/gateway/index.ts b/src/gateway/index.ts index d678336..d13a1f7 100644 --- a/src/gateway/index.ts +++ b/src/gateway/index.ts @@ -188,7 +188,9 @@ export class Gateway extends EventEmitter { this.reconnect() break case GatewayCloseCodes.UNKNOWN_OPCODE: - throw new Error("Unknown OP Code was sent. This shouldn't happen!") + throw new Error( + "Invalid OP Code or Payload was sent. This shouldn't happen!" + ) case GatewayCloseCodes.DECODE_ERROR: throw new Error("Invalid Payload was sent. This shouldn't happen!") case GatewayCloseCodes.NOT_AUTHENTICATED: @@ -320,8 +322,8 @@ export class Gateway extends EventEmitter { op: GatewayOpcodes.REQUEST_GUILD_MEMBERS, d: { guild_id: guild, - query: options.query, - limit: options.limit, + query: options.query ?? '', + limit: options.limit ?? 0, presences: options.presences, user_ids: options.users, nonce @@ -387,14 +389,13 @@ export class Gateway extends EventEmitter { send(data: GatewayResponse): boolean { if (this.websocket.readyState !== this.websocket.OPEN) return false - this.websocket.send( - JSON.stringify({ - op: data.op, - d: data.d, - s: typeof data.s === 'number' ? data.s : null, - t: data.t === undefined ? null : data.t - }) - ) + const packet = JSON.stringify({ + op: data.op, + d: data.d, + s: typeof data.s === 'number' ? data.s : null, + t: data.t === undefined ? null : data.t + }) + this.websocket.send(packet) return true } diff --git a/src/structures/guild.ts b/src/structures/guild.ts index 730c143..0e7e98d 100644 --- a/src/structures/guild.ts +++ b/src/structures/guild.ts @@ -327,7 +327,6 @@ export class Guild extends Base { } }, timeout) } - resolve(this) }) } } diff --git a/src/structures/invite.ts b/src/structures/invite.ts index f27f841..b17b4fd 100644 --- a/src/structures/invite.ts +++ b/src/structures/invite.ts @@ -16,10 +16,32 @@ export class Invite extends Base { approximatePresenceCount?: number approximateMemberCount?: number + /** Number of times Invite was used. This is an Invite Metadata property (not always available) */ + uses?: number + /** Max number of times this Invite can be used. This is an Invite Metadata property (not always available) */ + maxUses?: number + /** Max age of the Invite in seconds. This is an Invite Metadata property (not always available) */ + maxAge?: number + /** Whether Invite is temporary or not. This is an Invite Metadata property (not always available) */ + temporary?: boolean + /** Timestamp (string) when Invite was created. This is an Invite Metadata property (not always available) */ + createdAtTimestamp?: string + + /** Timestamp (Date) when Invite was created. This is an Invite Metadata property (not always available) */ + get createdAt(): Date | undefined { + return this.createdAtTimestamp === undefined + ? undefined + : new Date(this.createdAtTimestamp) + } + get link(): string { return `https://discord.gg/${this.code}` } + toString(): string { + return this.link + } + constructor(client: Client, data: InvitePayload) { super(client) this.code = data.code @@ -30,6 +52,12 @@ export class Invite extends Base { this.targetUserType = data.target_user_type this.approximateMemberCount = data.approximate_member_count this.approximatePresenceCount = data.approximate_presence_count + + this.uses = (data as any).uses + this.maxUses = (data as any).maxUses + this.maxAge = (data as any).maxAge + this.temporary = (data as any).temporary + this.createdAtTimestamp = (data as any).createdAtTimestamp } /** Delete an invite. Requires the MANAGE_CHANNELS permission on the channel this invite belongs to, or MANAGE_GUILD to remove any invite across the guild. Returns an invite object on success. Fires a Invite Delete Gateway event. */ @@ -49,5 +77,12 @@ export class Invite extends Base { data.approximate_member_count ?? this.approximateMemberCount this.approximatePresenceCount = data.approximate_presence_count ?? this.approximatePresenceCount + + this.uses = (data as any).uses ?? this.uses + this.maxUses = (data as any).maxUses ?? this.maxUses + this.maxAge = (data as any).maxAge ?? this.maxAge + this.temporary = (data as any).temporary ?? this.temporary + this.createdAtTimestamp = + (data as any).createdAtTimestamp ?? this.createdAtTimestamp } } diff --git a/src/test/chunk.ts b/src/test/chunk.ts new file mode 100644 index 0000000..7e4aab8 --- /dev/null +++ b/src/test/chunk.ts @@ -0,0 +1,24 @@ +import { Client, Intents } from '../../mod.ts' +import { TOKEN } from './config.ts' + +const client = new Client() + +client.on('debug', console.log) + +client.on('ready', () => { + console.log(`Logged in as ${client.user?.tag}!`) + client.guilds.get('783319033205751809').then((guild) => { + if (guild === undefined) return console.log('Guild not found') + guild + .chunk({ presences: true }, true) + .then((guild) => { + console.log(`Chunked guild:`, guild.id) + }) + .catch((e) => { + console.log(`Failed to Chunk: ${guild.id} - ${e}`) + }) + }) +}) + +console.log('Connecting...') +client.connect(TOKEN, Intents.All) From e3bce85f097ea9779483ddf3334e969616b11463 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 1 Jan 2021 10:30:11 +0530 Subject: [PATCH 05/13] fix channels.array and add guildLoaded event --- src/gateway/handlers/guildCreate.ts | 2 +- src/gateway/handlers/index.ts | 5 +++++ src/managers/channels.ts | 1 + src/models/cacheAdapter.ts | 6 +----- src/test/chunk.ts | 25 ++++++++++++++----------- 5 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/gateway/handlers/guildCreate.ts b/src/gateway/handlers/guildCreate.ts index b2251c5..4549834 100644 --- a/src/gateway/handlers/guildCreate.ts +++ b/src/gateway/handlers/guildCreate.ts @@ -30,5 +30,5 @@ export const guildCreate: GatewayEventHandler = async ( if (hasGuild === undefined) { // It wasn't lazy load, so emit event gateway.client.emit('guildCreate', guild) - } + } else gateway.client.emit('guildLoaded', guild) } diff --git a/src/gateway/handlers/index.ts b/src/gateway/handlers/index.ts index 6dacb68..e305d9c 100644 --- a/src/gateway/handlers/index.ts +++ b/src/gateway/handlers/index.ts @@ -154,6 +154,11 @@ export interface ClientEvents { * @param guild The new Guild object */ guildCreate: [guild: Guild] + /** + * A Guild was successfully loaded. + * @param guild The Guild object + */ + guildLoaded: [guild: Guild] /** * A Guild in which Client was either deleted, or bot was kicked * @param guild The Guild object diff --git a/src/managers/channels.ts b/src/managers/channels.ts index a729983..f7bdae4 100644 --- a/src/managers/channels.ts +++ b/src/managers/channels.ts @@ -29,6 +29,7 @@ export class ChannelsManager extends BaseManager { const arr = await (this.client.cache.array( this.cacheName ) as ChannelPayload[]) + if (arr === undefined) return [] const result: any[] = [] for (const elem of arr) { let guild diff --git a/src/models/cacheAdapter.ts b/src/models/cacheAdapter.ts index 6f3ff82..b9f5193 100644 --- a/src/models/cacheAdapter.ts +++ b/src/models/cacheAdapter.ts @@ -1,9 +1,5 @@ import { Collection } from '../utils/collection.ts' -import { - connect, - Redis, - RedisConnectOptions -} from '../../deps.ts' +import { connect, Redis, RedisConnectOptions } from '../../deps.ts' /** * ICacheAdapter is the interface to be implemented by Cache Adapters for them to be usable with Harmony. diff --git a/src/test/chunk.ts b/src/test/chunk.ts index 7e4aab8..d1fdbad 100644 --- a/src/test/chunk.ts +++ b/src/test/chunk.ts @@ -7,17 +7,20 @@ client.on('debug', console.log) client.on('ready', () => { console.log(`Logged in as ${client.user?.tag}!`) - client.guilds.get('783319033205751809').then((guild) => { - if (guild === undefined) return console.log('Guild not found') - guild - .chunk({ presences: true }, true) - .then((guild) => { - console.log(`Chunked guild:`, guild.id) - }) - .catch((e) => { - console.log(`Failed to Chunk: ${guild.id} - ${e}`) - }) - }) +}) + +client.on('guildLoaded', async (guild) => { + if (guild.id !== '783319033205751809') return + const arr = await guild.channels.array() + console.log(arr.length) + guild + .chunk({ presences: true }, true) + .then((guild) => { + console.log(`Chunked guild:`, guild.id) + }) + .catch((e) => { + console.log(`Failed to Chunk: ${guild.id} - ${e}`) + }) }) console.log('Connecting...') From c3fafdfcf06ec3533ad289ddcedaca3b0922f28a Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 1 Jan 2021 11:25:23 +0530 Subject: [PATCH 06/13] more things --- src/managers/guildChannels.ts | 48 ++++++++++++++++++++++++++++++-- src/managers/guildVoiceStates.ts | 7 +++++ src/managers/messageReactions.ts | 26 ++++++++++++++++- src/managers/reactionUsers.ts | 11 ++++++++ src/models/client.ts | 12 ++++---- src/models/commandClient.ts | 7 ++--- src/models/slashClient.ts | 29 +++++++++++++++---- src/structures/groupChannel.ts | 2 -- src/structures/guild.ts | 18 ++++++++++-- src/types/endpoint.ts | 2 +- 10 files changed, 138 insertions(+), 24 deletions(-) diff --git a/src/managers/guildChannels.ts b/src/managers/guildChannels.ts index 3bdaed8..bbc279e 100644 --- a/src/managers/guildChannels.ts +++ b/src/managers/guildChannels.ts @@ -5,11 +5,14 @@ import { CategoryChannel } from '../structures/guildCategoryChannel.ts' import { GuildTextChannel } from '../structures/textChannel.ts' import { VoiceChannel } from '../structures/guildVoiceChannel.ts' import { + ChannelTypes, GuildCategoryChannelPayload, + GuildChannelPayload, GuildTextChannelPayload, - GuildVoiceChannelPayload + GuildVoiceChannelPayload, + Overwrite } from '../types/channel.ts' -import { CHANNEL } from '../types/endpoint.ts' +import { CHANNEL, GUILD_CHANNELS } from '../types/endpoint.ts' import { BaseChildManager } from './baseChild.ts' import { ChannelsManager } from './channels.ts' @@ -19,6 +22,19 @@ export type GuildChannelPayloads = | GuildCategoryChannelPayload export type GuildChannel = GuildTextChannel | VoiceChannel | CategoryChannel +export interface CreateChannelOptions { + name: string + type?: ChannelTypes + topic?: string + bitrate?: number + userLimit?: number + rateLimitPerUser?: number + position?: number + permissionOverwrites?: Overwrite[] + parent?: CategoryChannel | string + nsfw?: boolean +} + export class GuildChannelsManager extends BaseChildManager< GuildChannelPayloads, GuildChannel @@ -55,4 +71,32 @@ export class GuildChannelsManager extends BaseChildManager< } return true } + + /** Create a new Guild Channel */ + async create(options: CreateChannelOptions): Promise { + if (options.name === undefined) + throw new Error('name is required for GuildChannelsManager#create') + const res = ((await this.client.rest.post(GUILD_CHANNELS(this.guild.id)), + { + name: options.name, + type: options.type, + topic: options.topic, + bitrate: options.bitrate, + user_limit: options.userLimit, + rate_limit_per_user: options.rateLimitPerUser, + position: options.position, + permission_overwrites: options.permissionOverwrites, + parent_id: + options.parent === undefined + ? undefined + : typeof options.parent === 'object' + ? options.parent.id + : options.parent, + nsfw: options.nsfw + }) as unknown) as GuildChannelPayload + + await this.set(res.id, res) + const channel = await this.get(res.id) + return (channel as unknown) as GuildChannel + } } diff --git a/src/managers/guildVoiceStates.ts b/src/managers/guildVoiceStates.ts index 677caef..4b1c5bd 100644 --- a/src/managers/guildVoiceStates.ts +++ b/src/managers/guildVoiceStates.ts @@ -17,6 +17,13 @@ export class GuildVoiceStatesManager extends BaseManager< this.guild = guild } + /** Get Client's Voice State in the Guild */ + async me(): Promise { + const member = await this.guild.me() + return await this.get(member.id) + } + + /** Get a Voice State by User ID */ async get(key: string): Promise { const raw = await this._get(key) if (raw === undefined) return diff --git a/src/managers/messageReactions.ts b/src/managers/messageReactions.ts index 000c5f9..30c0672 100644 --- a/src/managers/messageReactions.ts +++ b/src/managers/messageReactions.ts @@ -3,8 +3,13 @@ import { Emoji } from '../structures/emoji.ts' import { Guild } from '../structures/guild.ts' import { Message } from '../structures/message.ts' import { MessageReaction } from '../structures/messageReaction.ts' +import { User } from '../structures/user.ts' import { Reaction } from '../types/channel.ts' -import { MESSAGE_REACTION, MESSAGE_REACTIONS } from '../types/endpoint.ts' +import { + MESSAGE_REACTION, + MESSAGE_REACTIONS, + MESSAGE_REACTION_USER +} from '../types/endpoint.ts' import { BaseManager } from './base.ts' export class MessageReactionsManager extends BaseManager< @@ -77,4 +82,23 @@ export class MessageReactionsManager extends BaseManager< ) return this } + + /** Remove a specific Emoji from Reactions */ + async removeUser( + emoji: Emoji | string, + user: User | string + ): Promise { + const val = encodeURIComponent( + (typeof emoji === 'object' ? emoji.id ?? emoji.name : emoji) as string + ) + await this.client.rest.delete( + MESSAGE_REACTION_USER( + this.message.channel.id, + this.message.id, + val, + typeof user === 'string' ? user : user.id + ) + ) + return this + } } diff --git a/src/managers/reactionUsers.ts b/src/managers/reactionUsers.ts index be78e13..5e38b2a 100644 --- a/src/managers/reactionUsers.ts +++ b/src/managers/reactionUsers.ts @@ -1,5 +1,6 @@ import { Client } from '../models/client.ts' import { MessageReaction } from '../structures/messageReaction.ts' +import { User } from '../structures/user.ts' import { UsersManager } from './users.ts' export class ReactionUsersManager extends UsersManager { @@ -10,4 +11,14 @@ export class ReactionUsersManager extends UsersManager { this.cacheName = `reaction_users:${reaction.message.id}` this.reaction = reaction } + + /** Remove all Users from this Reaction */ + async removeAll(): Promise { + await this.reaction.message.reactions.removeEmoji(this.reaction.emoji) + } + + /** Remove a specific User from this Reaction */ + async remove(user: User | string): Promise { + await this.reaction.message.reactions.removeUser(this.reaction.emoji, user) + } } diff --git a/src/models/client.ts b/src/models/client.ts index ee00401..1e4e002 100644 --- a/src/models/client.ts +++ b/src/models/client.ts @@ -134,7 +134,7 @@ export class Client extends EventEmitter { handler: (interaction: Interaction) => any }> - _decoratedSlashModules?: SlashModule[] + _decoratedSlashModules?: string[] _id?: string /** Shard on which this Client is */ @@ -291,6 +291,7 @@ export class Client extends EventEmitter { } } +/** Event decorator to create an Event handler from function */ export function event(name?: keyof ClientEvents) { return function (client: Client | Extension, prop: keyof ClientEvents) { const listener = ((client as unknown) as { @@ -311,7 +312,7 @@ export function slash(name?: string, guild?: string) { if (client._decoratedSlash === undefined) client._decoratedSlash = [] const item = (client as { [name: string]: any })[prop] if (typeof item !== 'function') { - client._decoratedSlash.push(item) + throw new Error('@slash decorator requires a function') } else client._decoratedSlash.push({ name: name ?? prop, @@ -327,8 +328,7 @@ export function subslash(parent: string, name?: string, guild?: string) { if (client._decoratedSlash === undefined) client._decoratedSlash = [] const item = (client as { [name: string]: any })[prop] if (typeof item !== 'function') { - item.parent = parent - client._decoratedSlash.push(item) + throw new Error('@subslash decorator requires a function') } else client._decoratedSlash.push({ parent, @@ -350,9 +350,7 @@ export function groupslash( if (client._decoratedSlash === undefined) client._decoratedSlash = [] const item = (client as { [name: string]: any })[prop] if (typeof item !== 'function') { - item.parent = parent - item.group = group - client._decoratedSlash.push(item) + throw new Error('@groupslash decorator requires a function') } else client._decoratedSlash.push({ group, diff --git a/src/models/commandClient.ts b/src/models/commandClient.ts index 72da0fe..28fba90 100644 --- a/src/models/commandClient.ts +++ b/src/models/commandClient.ts @@ -382,6 +382,7 @@ export class CommandClient extends Client implements CommandClientOptions { } } +/** Command decorator */ export function command(options?: CommandOptions) { return function (target: CommandClient | Extension, name: string) { if (target._decoratedCommands === undefined) target._decoratedCommands = {} @@ -390,10 +391,8 @@ export function command(options?: CommandOptions) { [name: string]: (ctx: CommandContext) => any })[name] - if (prop instanceof Command) { - target._decoratedCommands[prop.name] = prop - return - } + if (typeof prop !== 'function') + throw new Error('@command decorator can only be used on functions') const command = new Command() diff --git a/src/models/slashClient.ts b/src/models/slashClient.ts index e029e49..f2e550b 100644 --- a/src/models/slashClient.ts +++ b/src/models/slashClient.ts @@ -14,11 +14,11 @@ import { RESTManager } from './rest.ts' import { SlashModule } from './slashModule.ts' import { verify as edverify } from 'https://deno.land/x/ed25519/mod.ts' import { Buffer } from 'https://deno.land/std@0.80.0/node/buffer.ts' -import { +import type { Request as ORequest, Response as OResponse } from 'https://deno.land/x/opine@1.0.0/src/types.ts' -import { Context } from 'https://deno.land/x/oak@v6.4.0/mod.ts' +import type { Context } from 'https://deno.land/x/oak@v6.4.0/mod.ts' export class SlashCommand { slash: SlashCommandsManager @@ -353,7 +353,7 @@ export class SlashClient { handler: (interaction: Interaction) => any }> - _decoratedSlashModules?: SlashModule[] + _decoratedSlashModules?: string[] constructor(options: SlashOptions) { let id = options.id @@ -378,7 +378,14 @@ export class SlashClient { if (this.client?._decoratedSlashModules !== undefined) { this.client._decoratedSlashModules.forEach((e) => { - this.modules.push(e) + const mod = ((this.client as unknown) as { + [name: string]: SlashModule + })[e] + if (!(mod instanceof SlashModule)) + throw new Error( + '@slashModule can only be used on SlashModule instances' + ) + this.modules.push(mod) }) } @@ -390,7 +397,12 @@ export class SlashClient { if (this._decoratedSlashModules !== undefined) { this._decoratedSlashModules.forEach((e) => { - this.modules.push(e) + const mod = ((this as unknown) as { [name: string]: SlashModule })[e] + if (!(mod instanceof SlashModule)) + throw new Error( + '@slashModule can only be used on SlashModule instances' + ) + this.modules.push(mod) }) } @@ -418,11 +430,13 @@ export class SlashClient { return this } + /** Load a Slash Module */ loadModule(module: SlashModule): SlashClient { this.modules.push(module) return this } + /** Get all Handlers. Including Slash Modules */ getHandlers(): SlashCommandHandler[] { let res = this.handlers for (const mod of this.modules) { @@ -438,6 +452,7 @@ export class SlashClient { return res } + /** Get Handler for an Interaction. Supports nested sub commands and sub command groups. */ private _getCommand(i: Interaction): SlashCommandHandler | undefined { return this.getHandlers().find((e) => { const hasGroupOrParent = e.group !== undefined || e.parent !== undefined @@ -467,6 +482,10 @@ export class SlashClient { if (interaction.type !== InteractionType.APPLICATION_COMMAND) return const cmd = this._getCommand(interaction) + if (cmd?.group !== undefined) + interaction.data.options = interaction.data.options[0].options ?? [] + if (cmd?.parent !== undefined) + interaction.data.options = interaction.data.options[0].options ?? [] if (cmd === undefined) return diff --git a/src/structures/groupChannel.ts b/src/structures/groupChannel.ts index 33a31b2..bd0cb15 100644 --- a/src/structures/groupChannel.ts +++ b/src/structures/groupChannel.ts @@ -13,8 +13,6 @@ export class GroupDMChannel extends Channel { this.name = data.name this.icon = data.icon this.ownerID = data.owner_id - // TODO: Cache in Gateway Event Code - // cache.set('groupchannel', this.id, this) } readFromData(data: GroupDMChannelPayload): void { diff --git a/src/structures/guild.ts b/src/structures/guild.ts index 0e7e98d..d50f500 100644 --- a/src/structures/guild.ts +++ b/src/structures/guild.ts @@ -8,9 +8,13 @@ import { IntegrationExpireBehavior } from '../types/guild.ts' import { Base } from './base.ts' -import { RolesManager } from '../managers/roles.ts' +import { CreateGuildRoleOptions, RolesManager } from '../managers/roles.ts' import { InviteManager } from '../managers/invites.ts' -import { GuildChannelsManager } from '../managers/guildChannels.ts' +import { + CreateChannelOptions, + GuildChannel, + GuildChannelsManager +} from '../managers/guildChannels.ts' import { MembersManager } from '../managers/members.ts' import { Role } from './role.ts' import { GuildEmojisManager } from '../managers/guildEmojis.ts' @@ -297,6 +301,16 @@ export class Guild extends Base { return raw.map((e) => new GuildIntegration(this.client, e)) } + /** Create a new Guild Channel */ + async createChannel(options: CreateChannelOptions): Promise { + return this.channels.create(options) + } + + /** Create a new Guild Role */ + async createRole(options?: CreateGuildRoleOptions): Promise { + return this.roles.create(options) + } + /** * Chunks the Guild Members, i.e. cache them. * @param options Options regarding the Members Request diff --git a/src/types/endpoint.ts b/src/types/endpoint.ts index 69d363b..c3cb0da 100644 --- a/src/types/endpoint.ts +++ b/src/types/endpoint.ts @@ -32,7 +32,7 @@ const GUILD_BANS = (guildID: string): string => `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/bans` const GUILD_CHANNEL = (channelID: string): string => `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/channels/${channelID}` -const GUILD_CHANNELS = (guildID: string, channelID: string): string => +const GUILD_CHANNELS = (guildID: string): string => `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/channels` const GUILD_MEMBER = (guildID: string, memberID: string): string => `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/members/${memberID}` From 8f4433dc9f823f091f22208c36fcac67a2f2692f Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 1 Jan 2021 13:30:13 +0530 Subject: [PATCH 07/13] allow SlashClient in slash decorators --- src/models/client.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/models/client.ts b/src/models/client.ts index 1e4e002..4aff966 100644 --- a/src/models/client.ts +++ b/src/models/client.ts @@ -266,6 +266,7 @@ export class Client extends EventEmitter { this.gateway = new Gateway(this, token, intents) } + /** Wait for an Event (optionally satisfying an event) to occur */ async waitFor( event: K, checkFunction: (...args: ClientEvents[K]) => boolean, @@ -308,7 +309,7 @@ export function event(name?: keyof ClientEvents) { /** Decorator to create a Slash Command handler */ export function slash(name?: string, guild?: string) { - return function (client: Client | SlashModule, prop: string) { + return function (client: Client | SlashClient | SlashModule, prop: string) { if (client._decoratedSlash === undefined) client._decoratedSlash = [] const item = (client as { [name: string]: any })[prop] if (typeof item !== 'function') { @@ -324,7 +325,7 @@ export function slash(name?: string, guild?: string) { /** Decorator to create a Sub-Slash Command handler */ export function subslash(parent: string, name?: string, guild?: string) { - return function (client: Client | SlashModule, prop: string) { + return function (client: Client | SlashModule | SlashClient, prop: string) { if (client._decoratedSlash === undefined) client._decoratedSlash = [] const item = (client as { [name: string]: any })[prop] if (typeof item !== 'function') { @@ -364,7 +365,7 @@ export function groupslash( /** Decorator to add a Slash Module to Client */ export function slashModule() { - return function (client: Client, prop: string) { + return function (client: Client | SlashClient, prop: string) { if (client._decoratedSlashModules === undefined) client._decoratedSlashModules = [] From 432555e2fb593318ce9afdcdf911a69a96d698ad Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 1 Jan 2021 13:41:44 +0530 Subject: [PATCH 08/13] try fix --- src/models/slashClient.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/models/slashClient.ts b/src/models/slashClient.ts index f2e550b..06beaef 100644 --- a/src/models/slashClient.ts +++ b/src/models/slashClient.ts @@ -398,10 +398,6 @@ export class SlashClient { if (this._decoratedSlashModules !== undefined) { this._decoratedSlashModules.forEach((e) => { const mod = ((this as unknown) as { [name: string]: SlashModule })[e] - if (!(mod instanceof SlashModule)) - throw new Error( - '@slashModule can only be used on SlashModule instances' - ) this.modules.push(mod) }) } From 33f103279dd28aa2827b30fc600c2d4782a89be5 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 1 Jan 2021 13:46:22 +0530 Subject: [PATCH 09/13] try fix again --- src/models/client.ts | 4 +--- src/models/slashClient.ts | 4 ++++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/models/client.ts b/src/models/client.ts index 4aff966..f1ff54a 100644 --- a/src/models/client.ts +++ b/src/models/client.ts @@ -368,8 +368,6 @@ export function slashModule() { return function (client: Client | SlashClient, prop: string) { if (client._decoratedSlashModules === undefined) client._decoratedSlashModules = [] - - const mod = ((client as unknown) as { [key: string]: any })[prop] - client._decoratedSlashModules.push(mod) + client._decoratedSlashModules.push(prop) } } diff --git a/src/models/slashClient.ts b/src/models/slashClient.ts index 06beaef..f2e550b 100644 --- a/src/models/slashClient.ts +++ b/src/models/slashClient.ts @@ -398,6 +398,10 @@ export class SlashClient { if (this._decoratedSlashModules !== undefined) { this._decoratedSlashModules.forEach((e) => { const mod = ((this as unknown) as { [name: string]: SlashModule })[e] + if (!(mod instanceof SlashModule)) + throw new Error( + '@slashModule can only be used on SlashModule instances' + ) this.modules.push(mod) }) } From b112b6ae36925a4436fb1f1b7aa940d120a300f1 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 1 Jan 2021 14:22:15 +0530 Subject: [PATCH 10/13] remove slashModule decorator --- src/models/client.ts | 10 ---------- src/models/slashClient.ts | 26 -------------------------- src/test/slash-only.ts | 12 +++++++++++- 3 files changed, 11 insertions(+), 37 deletions(-) diff --git a/src/models/client.ts b/src/models/client.ts index f1ff54a..a0b9272 100644 --- a/src/models/client.ts +++ b/src/models/client.ts @@ -134,7 +134,6 @@ export class Client extends EventEmitter { handler: (interaction: Interaction) => any }> - _decoratedSlashModules?: string[] _id?: string /** Shard on which this Client is */ @@ -362,12 +361,3 @@ export function groupslash( }) } } - -/** Decorator to add a Slash Module to Client */ -export function slashModule() { - return function (client: Client | SlashClient, prop: string) { - if (client._decoratedSlashModules === undefined) - client._decoratedSlashModules = [] - client._decoratedSlashModules.push(prop) - } -} diff --git a/src/models/slashClient.ts b/src/models/slashClient.ts index f2e550b..020179b 100644 --- a/src/models/slashClient.ts +++ b/src/models/slashClient.ts @@ -353,8 +353,6 @@ export class SlashClient { handler: (interaction: Interaction) => any }> - _decoratedSlashModules?: string[] - constructor(options: SlashOptions) { let id = options.id if (options.token !== undefined) id = atob(options.token?.split('.')[0]) @@ -376,36 +374,12 @@ export class SlashClient { }) } - if (this.client?._decoratedSlashModules !== undefined) { - this.client._decoratedSlashModules.forEach((e) => { - const mod = ((this.client as unknown) as { - [name: string]: SlashModule - })[e] - if (!(mod instanceof SlashModule)) - throw new Error( - '@slashModule can only be used on SlashModule instances' - ) - this.modules.push(mod) - }) - } - if (this._decoratedSlash !== undefined) { this._decoratedSlash.forEach((e) => { this.handlers.push(e) }) } - if (this._decoratedSlashModules !== undefined) { - this._decoratedSlashModules.forEach((e) => { - const mod = ((this as unknown) as { [name: string]: SlashModule })[e] - if (!(mod instanceof SlashModule)) - throw new Error( - '@slashModule can only be used on SlashModule instances' - ) - this.modules.push(mod) - }) - } - this.rest = options.client === undefined ? options.rest === undefined diff --git a/src/test/slash-only.ts b/src/test/slash-only.ts index 1798eee..df844d9 100644 --- a/src/test/slash-only.ts +++ b/src/test/slash-only.ts @@ -1,8 +1,18 @@ import { SlashClient } from '../models/slashClient.ts' import { SlashCommandPartial } from '../types/slash.ts' import { TOKEN } from './config.ts' +import { SlashModule, slashModule } from '../../mod.ts' -export const slash = new SlashClient({ token: TOKEN }) +class MyMod extends SlashModule {} + +class MySlashClient extends SlashClient { + @slashModule() + mod = new MyMod() +} + +export const slash = new MySlashClient({ token: TOKEN }) + +console.log(slash.modules) // Cmd objects come here const commands: SlashCommandPartial[] = [] From e9f461fef4ece40e8218b52d69e61fb17bd973ae Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 1 Jan 2021 15:05:53 +0530 Subject: [PATCH 11/13] ok linter --- src/test/slash-only.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/test/slash-only.ts b/src/test/slash-only.ts index df844d9..44b8a4f 100644 --- a/src/test/slash-only.ts +++ b/src/test/slash-only.ts @@ -1,16 +1,8 @@ import { SlashClient } from '../models/slashClient.ts' import { SlashCommandPartial } from '../types/slash.ts' import { TOKEN } from './config.ts' -import { SlashModule, slashModule } from '../../mod.ts' -class MyMod extends SlashModule {} - -class MySlashClient extends SlashClient { - @slashModule() - mod = new MyMod() -} - -export const slash = new MySlashClient({ token: TOKEN }) +export const slash = new SlashClient({ token: TOKEN }) console.log(slash.modules) From cff738b2e9cc16ca36304e3894e7983ed984a773 Mon Sep 17 00:00:00 2001 From: DjDeveloper <43033058+DjDeveloperr@users.noreply.github.com> Date: Sat, 2 Jan 2021 08:14:16 +0530 Subject: [PATCH 12/13] feat: add CNAME --- CNAME | 1 + 1 file changed, 1 insertion(+) create mode 100644 CNAME diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..9a40703 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +harmony.mod.land From 39c7761a962fb0070fa264de46fee06e37d613d6 Mon Sep 17 00:00:00 2001 From: DjDeveloper <43033058+DjDeveloperr@users.noreply.github.com> Date: Sat, 2 Jan 2021 08:14:52 +0530 Subject: [PATCH 13/13] feat: delete cname --- CNAME | 1 - 1 file changed, 1 deletion(-) delete mode 100644 CNAME diff --git a/CNAME b/CNAME deleted file mode 100644 index 9a40703..0000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -harmony.mod.land