From c1bce2833412374c31096834514a4538ccb5302a Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Wed, 25 Nov 2020 18:11:51 +0530 Subject: [PATCH 1/5] feat(integrations|application): implementation --- mod.ts | 124 +++++---- src/models/client.ts | 4 + src/structures/application.ts | 24 ++ src/structures/guild.ts | 494 +++++++++++++++++++--------------- src/types/application.ts | 10 + src/types/guild.ts | 257 ++++++++++-------- 6 files changed, 518 insertions(+), 395 deletions(-) create mode 100644 src/structures/application.ts create mode 100644 src/types/application.ts diff --git a/mod.ts b/mod.ts index 9edcc67..dfeee02 100644 --- a/mod.ts +++ b/mod.ts @@ -1,57 +1,67 @@ -export * from './src/gateway/index.ts' -export * from './src/models/client.ts' -export * from './src/models/rest.ts' -export * from './src/models/cacheAdapter.ts' -export * from './src/models/shard.ts' -export * from './src/models/command.ts' -export * from './src/models/commandClient.ts' -export * from './src/managers/base.ts' -export * from './src/managers/baseChild.ts' -export * from './src/managers/channels.ts' -export * from './src/managers/emojis.ts' -export * from './src/managers/gatewayCache.ts' -export * from './src/managers/guildChannels.ts' -export * from './src/managers/guilds.ts' -export * from './src/managers/members.ts' -export * from './src/managers/messages.ts' -export * from './src/managers/roles.ts' -export * from './src/managers/users.ts' -export * from './src/structures/base.ts' -export * from './src/structures/cdn.ts' -export * from './src/structures/channel.ts' -export * from './src/structures/dmChannel.ts' -export * from './src/structures/embed.ts' -export * from './src/structures/emoji.ts' -export * from './src/structures/groupChannel.ts' -export * from './src/structures/guild.ts' -export * from './src/structures/guildCategoryChannel.ts' -export * from './src/structures/guildNewsChannel.ts' -export * from './src/structures/guildVoiceChannel.ts' -export * from './src/structures/invite.ts' -export * from './src/structures/member.ts' -export * from './src/structures/message.ts' -export * from './src/structures/messageMentions.ts' -export * from './src/structures/presence.ts' -export * from './src/structures/role.ts' -export * from './src/structures/snowflake.ts' -export * from './src/structures/textChannel.ts' -export * from './src/structures/user.ts' -export * from './src/structures/webhook.ts' -export * from './src/types/cdn.ts' -export * from './src/types/channel.ts' -export * from './src/types/emoji.ts' -export * from './src/types/endpoint.ts' -export * from './src/types/gateway.ts' -export * from './src/types/gatewayBot.ts' -export * from './src/types/gatewayResponse.ts' -export * from './src/types/guild.ts' -export * from './src/types/invite.ts' -export * from './src/types/permissionFlags.ts' -export * from './src/types/presence.ts' -export * from './src/types/role.ts' -export * from './src/types/template.ts' -export * from './src/types/user.ts' -export * from './src/types/voice.ts' -export * from './src/types/webhook.ts' -export * from './src/utils/collection.ts' -export * from './src/utils/intents.ts' \ No newline at end of file +export * from './src/gateway/index.ts' +export * from './src/models/client.ts' +export * from './src/models/rest.ts' +export * from './src/models/cacheAdapter.ts' +export * from './src/models/shard.ts' +export * from './src/models/command.ts' +export * from './src/models/extensions.ts' +export * from './src/models/commandClient.ts' +export * from './src/managers/base.ts' +export * from './src/managers/baseChild.ts' +export * from './src/managers/channels.ts' +export * from './src/managers/emojis.ts' +export * from './src/managers/gatewayCache.ts' +export * from './src/managers/guildChannels.ts' +export * from './src/managers/guilds.ts' +export * from './src/managers/guildChannels.ts' +export * from './src/managers/guildEmojis.ts' +export * from './src/managers/members.ts' +export * from './src/managers/messages.ts' +export * from './src/managers/roles.ts' +export * from './src/managers/users.ts' +export * from './src/structures/application.ts' +export * from './src/structures/base.ts' +export * from './src/structures/cdn.ts' +export * from './src/structures/channel.ts' +export * from './src/structures/dmChannel.ts' +export * from './src/structures/embed.ts' +export * from './src/structures/emoji.ts' +export * from './src/structures/groupChannel.ts' +export * from './src/structures/guild.ts' +export * from './src/structures/guildCategoryChannel.ts' +export * from './src/structures/guildNewsChannel.ts' +export * from './src/structures/guildVoiceChannel.ts' +export * from './src/structures/invite.ts' +export * from './src/structures/member.ts' +export * from './src/structures/message.ts' +export * from './src/structures/messageMentions.ts' +export * from './src/structures/presence.ts' +export * from './src/structures/role.ts' +export * from './src/structures/snowflake.ts' +export * from './src/structures/textChannel.ts' +export * from './src/structures/user.ts' +export * from './src/structures/webhook.ts' +export * from './src/types/application.ts' +export * from './src/types/cdn.ts' +export * from './src/types/channel.ts' +export * from './src/types/emoji.ts' +export * from './src/types/endpoint.ts' +export * from './src/types/gateway.ts' +export * from './src/types/gatewayBot.ts' +export * from './src/types/gatewayResponse.ts' +export * from './src/types/guild.ts' +export * from './src/types/invite.ts' +export * from './src/types/permissionFlags.ts' +export * from './src/types/presence.ts' +export * from './src/types/role.ts' +export * from './src/types/template.ts' +export * from './src/types/user.ts' +export * from './src/types/voice.ts' +export * from './src/types/webhook.ts' +export * from './src/utils/collection.ts' +export * from './src/utils/intents.ts' +export * from './src/utils/buildInfo.ts' +export * from './src/utils/permissions.ts' +export * from './src/utils/userFlags.ts' +export * from './src/utils/bitfield.ts' +export * from './src/utils/getChannelByType.ts' \ No newline at end of file diff --git a/src/models/client.ts b/src/models/client.ts index 56e7949..cf5aaa8 100644 --- a/src/models/client.ts +++ b/src/models/client.ts @@ -12,6 +12,7 @@ import { } from '../structures/presence.ts' import { EmojisManager } from '../managers/emojis.ts' import { ActivityGame, ClientActivity } from "../types/presence.ts" +// import { Application } from "../../mod.ts" /** Some Client Options to modify behaviour */ export interface ClientOptions { @@ -103,6 +104,9 @@ export class Client extends EventEmitter { this.emit('debug', `[${tag}] ${msg}`) } + // TODO(DjDeveloperr): Implement this + // fetchApplication(): Promise + /** * This function is used for connect to discord. * @param token Your token. This is required. diff --git a/src/structures/application.ts b/src/structures/application.ts new file mode 100644 index 0000000..c4d5cf1 --- /dev/null +++ b/src/structures/application.ts @@ -0,0 +1,24 @@ +import { Client } from "../models/client.ts"; +import { ApplicationPayload } from "../types/application.ts"; +import { Base } from "./base.ts"; +import { User } from "./user.ts"; + +export class Application extends Base { + id: string + name: string + icon: string + description: string + summary: string + bot?: User + + constructor(client: Client, data: ApplicationPayload) { + super(client, data) + + this.id = data.id + this.name = data.name + this.icon = data.icon + this.description = data.description + this.summary = data.summary + this.bot = data.bot !== undefined ? new User(client, data.bot) : undefined + } +} \ No newline at end of file diff --git a/src/structures/guild.ts b/src/structures/guild.ts index 19dab5a..395d3cb 100644 --- a/src/structures/guild.ts +++ b/src/structures/guild.ts @@ -1,224 +1,270 @@ -import { Client } from '../models/client.ts' -import { GuildFeatures, GuildPayload } from '../types/guild.ts' -import { PresenceUpdatePayload } from '../types/gateway.ts' -import { Base } from './base.ts' -import { VoiceState } from './voiceState.ts' -import { RolesManager } from '../managers/roles.ts' -import { GuildChannelsManager } from '../managers/guildChannels.ts' -import { MembersManager } from '../managers/members.ts' -import { Role } from './role.ts' -import { GuildEmojisManager } from '../managers/guildEmojis.ts' -import { Member } from "./member.ts" - -export class Guild extends Base { - id: string - name?: string - icon?: string - iconHash?: string - splash?: string - discoverySplash?: string - owner?: boolean - ownerID?: string - permissions?: string - region?: string - afkChannelID?: string - afkTimeout?: number - widgetEnabled?: boolean - widgetChannelID?: string - verificationLevel?: string - defaultMessageNotifications?: string - explicitContentFilter?: string - roles: RolesManager - emojis: GuildEmojisManager - features?: GuildFeatures[] - mfaLevel?: string - applicationID?: string - systemChannelID?: string - systemChannelFlags?: string - rulesChannelID?: string - joinedAt?: string - large?: boolean - unavailable: boolean - memberCount?: number - voiceStates?: VoiceState[] - members: MembersManager - channels: GuildChannelsManager - presences?: PresenceUpdatePayload[] - maxPresences?: number - maxMembers?: number - vanityURLCode?: string - description?: string - banner?: string - premiumTier?: number - premiumSubscriptionCount?: number - preferredLocale?: string - publicUpdatesChannelID?: string - maxVideoChannelUsers?: number - approximateNumberCount?: number - approximatePresenceCount?: number - - constructor (client: Client, data: GuildPayload) { - super(client, data) - this.id = data.id - this.unavailable = data.unavailable - this.members = new MembersManager(this.client, this) - this.channels = new GuildChannelsManager( - this.client, - this.client.channels, - this - ) - this.roles = new RolesManager(this.client, this) - this.emojis = new GuildEmojisManager(this.client, this.client.emojis, this) - - if (!this.unavailable) { - this.name = data.name - this.icon = data.icon - this.iconHash = data.icon_hash - this.splash = data.splash - this.discoverySplash = data.discovery_splash - this.owner = data.owner - this.ownerID = data.owner_id - this.permissions = data.permissions - this.region = data.region - this.afkTimeout = data.afk_timeout - this.afkChannelID = data.afk_channel_id - this.widgetEnabled = data.widget_enabled - this.widgetChannelID = data.widget_channel_id - this.verificationLevel = data.verification_level - this.defaultMessageNotifications = data.default_message_notifications - this.explicitContentFilter = data.explicit_content_filter - // this.roles = data.roles.map( - // v => cache.get('role', v.id) ?? new Role(client, v) - // ) - // data.roles.forEach(role => { - // this.roles.set(role.id, new Role(client, role)) - // }) - // this.emojis = data.emojis.map( - // v => cache.get('emoji', v.id) ?? new Emoji(client, v) - // ) - this.features = data.features - this.mfaLevel = data.mfa_level - this.systemChannelID = data.system_channel_id - this.systemChannelFlags = data.system_channel_flags - this.rulesChannelID = data.rules_channel_id - this.joinedAt = data.joined_at - this.large = data.large - this.memberCount = data.member_count - // TODO: Cache in Gateway Event code - // this.voiceStates = data.voice_states?.map( - // v => - // cache.get('voiceState', `${v.guild_id}:${v.user_id}`) ?? - // new VoiceState(client, v) - // ) - // this.members = data.members?.map( - // v => - // cache.get('member', `${this.id}:${v.user.id}`) ?? - // new Member(client, v) - // ) - // this.channels = data.channels?.map( - // v => cache.get('channel', v.id) ?? getChannelByType(this.client, v) - // ) - this.presences = data.presences - this.maxPresences = data.max_presences - this.maxMembers = data.max_members - this.vanityURLCode = data.vanity_url_code - this.description = data.description - this.banner = data.banner - this.premiumTier = data.premium_tier - this.premiumSubscriptionCount = data.premium_subscription_count - this.preferredLocale = data.preferred_locale - this.publicUpdatesChannelID = data.public_updates_channel_id - this.maxVideoChannelUsers = data.max_video_channel_users - this.approximateNumberCount = data.approximate_number_count - this.approximatePresenceCount = data.approximate_presence_count - } - } - - protected readFromData (data: GuildPayload): void { - super.readFromData(data) - this.id = data.id ?? this.id - this.unavailable = data.unavailable ?? this.unavailable - - if (!this.unavailable) { - this.name = data.name ?? this.name - this.icon = data.icon ?? this.icon - this.iconHash = data.icon_hash ?? this.iconHash - this.splash = data.splash ?? this.splash - this.discoverySplash = data.discovery_splash ?? this.discoverySplash - this.owner = data.owner ?? this.owner - this.ownerID = data.owner_id ?? this.ownerID - this.permissions = data.permissions ?? this.permissions - this.region = data.region ?? this.region - this.afkTimeout = data.afk_timeout ?? this.afkTimeout - this.afkChannelID = data.afk_channel_id ?? this.afkChannelID - this.widgetEnabled = data.widget_enabled ?? this.widgetEnabled - this.widgetChannelID = data.widget_channel_id ?? this.widgetChannelID - this.verificationLevel = data.verification_level ?? this.verificationLevel - this.defaultMessageNotifications = - data.default_message_notifications ?? this.defaultMessageNotifications - this.explicitContentFilter = - data.explicit_content_filter ?? this.explicitContentFilter - // this.roles = - // data.roles.map( - // v => cache.get('role', v.id) ?? new Role(this.client, v) - // ) ?? this.roles - // this.emojis = - // data.emojis.map( - // v => cache.get('emoji', v.id) ?? new Emoji(this.client, v) - // ) ?? this.emojis - this.features = data.features ?? this.features - this.mfaLevel = data.mfa_level ?? this.mfaLevel - this.systemChannelID = data.system_channel_id ?? this.systemChannelID - this.systemChannelFlags = - data.system_channel_flags ?? this.systemChannelFlags - this.rulesChannelID = data.rules_channel_id ?? this.rulesChannelID - this.joinedAt = data.joined_at ?? this.joinedAt - this.large = data.large ?? this.large - this.memberCount = data.member_count ?? this.memberCount - // this.voiceStates = - // data.voice_states?.map( - // v => - // cache.get('voiceState', `${v.guild_id}:${v.user_id}`) ?? - // new VoiceState(this.client, v) - // ) ?? this.voiceStates - // this.members = - // data.members?.map( - // v => - // cache.get('member', `${this.id}:${v.user.id}`) ?? - // new Member(this.client, v) - // ) ?? this.members - // this.channels = - // data.channels?.map( - // v => cache.get('channel', v.id) ?? getChannelByType(this.client, v, this) - // ) ?? this.members - this.presences = data.presences ?? this.presences - this.maxPresences = data.max_presences ?? this.maxPresences - this.maxMembers = data.max_members ?? this.maxMembers - this.vanityURLCode = data.vanity_url_code ?? this.vanityURLCode - this.description = data.description ?? this.description - this.banner = data.banner ?? this.banner - this.premiumTier = data.premium_tier ?? this.premiumTier - this.premiumSubscriptionCount = - data.premium_subscription_count ?? this.premiumSubscriptionCount - this.preferredLocale = data.preferred_locale ?? this.preferredLocale - this.publicUpdatesChannelID = - data.public_updates_channel_id ?? this.publicUpdatesChannelID - this.maxVideoChannelUsers = - data.max_video_channel_users ?? this.maxVideoChannelUsers - this.approximateNumberCount = - data.approximate_number_count ?? this.approximateNumberCount - this.approximatePresenceCount = - data.approximate_presence_count ?? this.approximatePresenceCount - } - } - - async getEveryoneRole (): Promise { - return (await this.roles.array().then(arr => arr?.sort((b, a) => a.position - b.position)[0]) as any) as Role - } - - async me(): Promise { - const get = await this.members.get(this.client.user?.id as string) - if (get === undefined) throw new Error('Guild#me is not cached') - return get - } -} +import { Client } from '../models/client.ts' +import { GuildFeatures, GuildIntegrationPayload, GuildPayload, IntegrationAccountPayload, IntegrationExpireBehavior } from '../types/guild.ts' +import { PresenceUpdatePayload } from '../types/gateway.ts' +import { Base } from './base.ts' +import { VoiceState } from './voiceState.ts' +import { RolesManager } from '../managers/roles.ts' +import { GuildChannelsManager } from '../managers/guildChannels.ts' +import { MembersManager } from '../managers/members.ts' +import { Role } from './role.ts' +import { GuildEmojisManager } from '../managers/guildEmojis.ts' +import { Member } from "./member.ts" +import { User } from "./user.ts" +import { Application } from "./application.ts" +import { GUILD_INTEGRATIONS } from "../types/endpoint.ts" + +export class Guild extends Base { + id: string + name?: string + icon?: string + iconHash?: string + splash?: string + discoverySplash?: string + owner?: boolean + ownerID?: string + permissions?: string + region?: string + afkChannelID?: string + afkTimeout?: number + widgetEnabled?: boolean + widgetChannelID?: string + verificationLevel?: string + defaultMessageNotifications?: string + explicitContentFilter?: string + roles: RolesManager + emojis: GuildEmojisManager + features?: GuildFeatures[] + mfaLevel?: string + applicationID?: string + systemChannelID?: string + systemChannelFlags?: string + rulesChannelID?: string + joinedAt?: string + large?: boolean + unavailable: boolean + memberCount?: number + voiceStates?: VoiceState[] + members: MembersManager + channels: GuildChannelsManager + presences?: PresenceUpdatePayload[] + maxPresences?: number + maxMembers?: number + vanityURLCode?: string + description?: string + banner?: string + premiumTier?: number + premiumSubscriptionCount?: number + preferredLocale?: string + publicUpdatesChannelID?: string + maxVideoChannelUsers?: number + approximateNumberCount?: number + approximatePresenceCount?: number + + constructor (client: Client, data: GuildPayload) { + super(client, data) + this.id = data.id + this.unavailable = data.unavailable + this.members = new MembersManager(this.client, this) + this.channels = new GuildChannelsManager( + this.client, + this.client.channels, + this + ) + this.roles = new RolesManager(this.client, this) + this.emojis = new GuildEmojisManager(this.client, this.client.emojis, this) + + if (!this.unavailable) { + this.name = data.name + this.icon = data.icon + this.iconHash = data.icon_hash + this.splash = data.splash + this.discoverySplash = data.discovery_splash + this.owner = data.owner + this.ownerID = data.owner_id + this.permissions = data.permissions + this.region = data.region + this.afkTimeout = data.afk_timeout + this.afkChannelID = data.afk_channel_id + this.widgetEnabled = data.widget_enabled + this.widgetChannelID = data.widget_channel_id + this.verificationLevel = data.verification_level + this.defaultMessageNotifications = data.default_message_notifications + this.explicitContentFilter = data.explicit_content_filter + // this.roles = data.roles.map( + // v => cache.get('role', v.id) ?? new Role(client, v) + // ) + // data.roles.forEach(role => { + // this.roles.set(role.id, new Role(client, role)) + // }) + // this.emojis = data.emojis.map( + // v => cache.get('emoji', v.id) ?? new Emoji(client, v) + // ) + this.features = data.features + this.mfaLevel = data.mfa_level + this.systemChannelID = data.system_channel_id + this.systemChannelFlags = data.system_channel_flags + this.rulesChannelID = data.rules_channel_id + this.joinedAt = data.joined_at + this.large = data.large + this.memberCount = data.member_count + // TODO: Cache in Gateway Event code + // this.voiceStates = data.voice_states?.map( + // v => + // cache.get('voiceState', `${v.guild_id}:${v.user_id}`) ?? + // new VoiceState(client, v) + // ) + // this.members = data.members?.map( + // v => + // cache.get('member', `${this.id}:${v.user.id}`) ?? + // new Member(client, v) + // ) + // this.channels = data.channels?.map( + // v => cache.get('channel', v.id) ?? getChannelByType(this.client, v) + // ) + this.presences = data.presences + this.maxPresences = data.max_presences + this.maxMembers = data.max_members + this.vanityURLCode = data.vanity_url_code + this.description = data.description + this.banner = data.banner + this.premiumTier = data.premium_tier + this.premiumSubscriptionCount = data.premium_subscription_count + this.preferredLocale = data.preferred_locale + this.publicUpdatesChannelID = data.public_updates_channel_id + this.maxVideoChannelUsers = data.max_video_channel_users + this.approximateNumberCount = data.approximate_number_count + this.approximatePresenceCount = data.approximate_presence_count + } + } + + protected readFromData (data: GuildPayload): void { + super.readFromData(data) + this.id = data.id ?? this.id + this.unavailable = data.unavailable ?? this.unavailable + + if (!this.unavailable) { + this.name = data.name ?? this.name + this.icon = data.icon ?? this.icon + this.iconHash = data.icon_hash ?? this.iconHash + this.splash = data.splash ?? this.splash + this.discoverySplash = data.discovery_splash ?? this.discoverySplash + this.owner = data.owner ?? this.owner + this.ownerID = data.owner_id ?? this.ownerID + this.permissions = data.permissions ?? this.permissions + this.region = data.region ?? this.region + this.afkTimeout = data.afk_timeout ?? this.afkTimeout + this.afkChannelID = data.afk_channel_id ?? this.afkChannelID + this.widgetEnabled = data.widget_enabled ?? this.widgetEnabled + this.widgetChannelID = data.widget_channel_id ?? this.widgetChannelID + this.verificationLevel = data.verification_level ?? this.verificationLevel + this.defaultMessageNotifications = + data.default_message_notifications ?? this.defaultMessageNotifications + this.explicitContentFilter = + data.explicit_content_filter ?? this.explicitContentFilter + // this.roles = + // data.roles.map( + // v => cache.get('role', v.id) ?? new Role(this.client, v) + // ) ?? this.roles + // this.emojis = + // data.emojis.map( + // v => cache.get('emoji', v.id) ?? new Emoji(this.client, v) + // ) ?? this.emojis + this.features = data.features ?? this.features + this.mfaLevel = data.mfa_level ?? this.mfaLevel + this.systemChannelID = data.system_channel_id ?? this.systemChannelID + this.systemChannelFlags = + data.system_channel_flags ?? this.systemChannelFlags + this.rulesChannelID = data.rules_channel_id ?? this.rulesChannelID + this.joinedAt = data.joined_at ?? this.joinedAt + this.large = data.large ?? this.large + this.memberCount = data.member_count ?? this.memberCount + // this.voiceStates = + // data.voice_states?.map( + // v => + // cache.get('voiceState', `${v.guild_id}:${v.user_id}`) ?? + // new VoiceState(this.client, v) + // ) ?? this.voiceStates + // this.members = + // data.members?.map( + // v => + // cache.get('member', `${this.id}:${v.user.id}`) ?? + // new Member(this.client, v) + // ) ?? this.members + // this.channels = + // data.channels?.map( + // v => cache.get('channel', v.id) ?? getChannelByType(this.client, v, this) + // ) ?? this.members + this.presences = data.presences ?? this.presences + this.maxPresences = data.max_presences ?? this.maxPresences + this.maxMembers = data.max_members ?? this.maxMembers + this.vanityURLCode = data.vanity_url_code ?? this.vanityURLCode + this.description = data.description ?? this.description + this.banner = data.banner ?? this.banner + this.premiumTier = data.premium_tier ?? this.premiumTier + this.premiumSubscriptionCount = + data.premium_subscription_count ?? this.premiumSubscriptionCount + this.preferredLocale = data.preferred_locale ?? this.preferredLocale + this.publicUpdatesChannelID = + data.public_updates_channel_id ?? this.publicUpdatesChannelID + this.maxVideoChannelUsers = + data.max_video_channel_users ?? this.maxVideoChannelUsers + this.approximateNumberCount = + data.approximate_number_count ?? this.approximateNumberCount + this.approximatePresenceCount = + data.approximate_presence_count ?? this.approximatePresenceCount + } + } + + async getEveryoneRole (): Promise { + return (await this.roles.get(this.id) as unknown) as Role + } + + async me(): Promise { + const get = await this.members.get(this.client.user?.id as string) + if (get === undefined) throw new Error('Guild#me is not cached') + return get + } + + async fetchIntegrations(): Promise { + const raw = await this.client.rest.get(GUILD_INTEGRATIONS(this.id)) as GuildIntegrationPayload[] + return raw.map(e => new GuildIntegration(this.client, e)) + } +} + +export class GuildIntegration extends Base { + id: string + name: string + type: string + enabled: boolean + syncing?: boolean + roleID?: string + enableEmoticons?: boolean + expireBehaviour?: IntegrationExpireBehavior + expireGracePeriod?: number + user?: User + account: IntegrationAccountPayload + syncedAt?: string // Actually a ISO Timestamp, but we parse in constructor' + subscriberCount?: number + revoked?: boolean + application?: Application + + constructor(client: Client, data: GuildIntegrationPayload) { + super(client, data) + + this.id = data.id + this.name= data.name + this.type = data.type + this.enabled = data.enabled + this.syncing = data.syncing + this.roleID = data.role_id + this.enableEmoticons = data.enable_emoticons + this.expireBehaviour = data.expire_behaviour + this.expireGracePeriod = data.expire_grace_period + this.user = data.user !== undefined ? new User(client, data.user) : undefined + this.account = data.account + this.syncedAt = data.synced_at + this.subscriberCount = data.subscriber_count + this.revoked = data.revoked + this.application = data.application !== undefined ? new Application(client, data.application) : undefined + } +} \ No newline at end of file diff --git a/src/types/application.ts b/src/types/application.ts new file mode 100644 index 0000000..888de73 --- /dev/null +++ b/src/types/application.ts @@ -0,0 +1,10 @@ +import { UserPayload } from "./user.ts"; + +export interface ApplicationPayload { + id: string + name: string + icon: string + description: string + summary: string + bot?: UserPayload +} \ No newline at end of file diff --git a/src/types/guild.ts b/src/types/guild.ts index d8438d6..1f92a00 100644 --- a/src/types/guild.ts +++ b/src/types/guild.ts @@ -1,114 +1,143 @@ -import { ChannelPayload } from './channel.ts' -import { EmojiPayload } from './emoji.ts' -import { PresenceUpdatePayload } from './gateway.ts' -import { RolePayload } from './role.ts' -import { UserPayload } from './user.ts' -import { VoiceStatePayload } from './voice.ts' - -export interface GuildPayload { - id: string - name: string - icon?: string - icon_hash?: string - splash?: string - discovery_splash?: string - owner?: boolean - owner_id: string - permissions?: string - region: string - afk_channel_id?: string - afk_timeout: number - widget_enabled?: boolean - widget_channel_id?: string - verification_level: string - default_message_notifications: string - explicit_content_filter: string - roles: RolePayload[] - emojis: EmojiPayload[] - features: GuildFeatures[] - mfa_level: string - application_id?: string - system_channel_id?: string - system_channel_flags: string - rules_channel_id?: string - joined_at?: string - large?: boolean - unavailable: boolean - member_count?: number - voice_states?: VoiceStatePayload[] - members?: MemberPayload[] - channels?: ChannelPayload[] - presences?: PresenceUpdatePayload[] - max_presences?: number - max_members?: number - vanity_url_code?: string - description?: string - banner?: string - premium_tier: number - premium_subscription_count?: number - preferred_locale: string - public_updates_channel_id?: string - max_video_channel_users?: number - approximate_number_count?: number - approximate_presence_count?: number -} - -export interface MemberPayload { - user: UserPayload - nick?: string - roles: string[] - joined_at: string - premium_since?: string - deaf: boolean - mute: boolean -} - -export enum MessageNotification { - ALL_MESSAGES = 0, - ONLY_MENTIONS = 1 -} - -export enum ContentFilter { - DISABLED = 0, - MEMBERS_WITHOUT_ROLES = 1, - ALL_MEMBERS = 3 -} - -export enum MFA { - NONE = 0, - ELEVATED = 1 -} - -export enum Verification { - NONE = 0, - LOW = 1, - MEDIUM = 2, - HIGH = 3, - VERY_HIGH = 4 -} - -export enum PremiumTier { - NONE = 0, - TIER_1 = 1, - TIER_2 = 2, - TIER_3 = 3 -} - -export enum SystemChannelFlags { - SUPPRESS_JOIN_NOTIFICATIONS = 1 << 0, - SUPPRESS_PREMIUM_SUBSCRIPTIONS = 1 << 1 -} - -export type GuildFeatures = - | 'INVITE_SPLASH' - | 'VIP_REGIONS' - | 'VANITY_URL' - | 'VERIFIED' - | 'PARTNERED' - | 'PUBLIC' - | 'COMMERCE' - | 'NEWS' - | 'DISCOVERABLE' - | 'FEATURABLE' - | 'ANIMATED_ICON' - | 'BANNER' +import { ApplicationPayload } from "./application.ts" +import { ChannelPayload } from './channel.ts' +import { EmojiPayload } from './emoji.ts' +import { PresenceUpdatePayload } from './gateway.ts' +import { RolePayload } from './role.ts' +import { UserPayload } from './user.ts' +import { VoiceStatePayload } from './voice.ts' + +export interface GuildPayload { + id: string + name: string + icon?: string + icon_hash?: string + splash?: string + discovery_splash?: string + owner?: boolean + owner_id: string + permissions?: string + region: string + afk_channel_id?: string + afk_timeout: number + widget_enabled?: boolean + widget_channel_id?: string + verification_level: string + default_message_notifications: string + explicit_content_filter: string + roles: RolePayload[] + emojis: EmojiPayload[] + features: GuildFeatures[] + mfa_level: string + application_id?: string + system_channel_id?: string + system_channel_flags: string + rules_channel_id?: string + joined_at?: string + large?: boolean + unavailable: boolean + member_count?: number + voice_states?: VoiceStatePayload[] + members?: MemberPayload[] + channels?: ChannelPayload[] + presences?: PresenceUpdatePayload[] + max_presences?: number + max_members?: number + vanity_url_code?: string + description?: string + banner?: string + premium_tier: number + premium_subscription_count?: number + preferred_locale: string + public_updates_channel_id?: string + max_video_channel_users?: number + approximate_number_count?: number + approximate_presence_count?: number +} + +export interface MemberPayload { + user: UserPayload + nick?: string + roles: string[] + joined_at: string + premium_since?: string + deaf: boolean + mute: boolean +} + +export enum MessageNotification { + ALL_MESSAGES = 0, + ONLY_MENTIONS = 1 +} + +export enum ContentFilter { + DISABLED = 0, + MEMBERS_WITHOUT_ROLES = 1, + ALL_MEMBERS = 3 +} + +export enum MFA { + NONE = 0, + ELEVATED = 1 +} + +export enum Verification { + NONE = 0, + LOW = 1, + MEDIUM = 2, + HIGH = 3, + VERY_HIGH = 4 +} + +export enum PremiumTier { + NONE = 0, + TIER_1 = 1, + TIER_2 = 2, + TIER_3 = 3 +} + +export enum SystemChannelFlags { + SUPPRESS_JOIN_NOTIFICATIONS = 1 << 0, + SUPPRESS_PREMIUM_SUBSCRIPTIONS = 1 << 1 +} + +export type GuildFeatures = + | 'INVITE_SPLASH' + | 'VIP_REGIONS' + | 'VANITY_URL' + | 'VERIFIED' + | 'PARTNERED' + | 'PUBLIC' + | 'COMMERCE' + | 'NEWS' + | 'DISCOVERABLE' + | 'FEATURABLE' + | 'ANIMATED_ICON' + | 'BANNER' + +export enum IntegrationExpireBehavior { + REMOVE_ROLE = 0, + KICK = 1 +} + +export interface IntegrationAccountPayload { + id: string + name: string +} + +export interface GuildIntegrationPayload { + id: string + name: string + type: string + enabled: boolean + syncing?: boolean + role_id?: string + enable_emoticons?: boolean + expire_behaviour?: IntegrationExpireBehavior + expire_grace_period?: number + user?: UserPayload + account: IntegrationAccountPayload + synced_at?: string // Actually a ISO Timestamp, but we parse in constructor' + subscriber_count?: number + revoked?: boolean + application?: ApplicationPayload +} \ No newline at end of file From 1de5692120855de7060a0b6bc54baf4d6a8e7abe Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sat, 28 Nov 2020 10:44:37 +0530 Subject: [PATCH 2/5] feat(almost-everything): a lot of things --- .vscode/settings.json | 17 +- src/gateway/handlers/channelPinsUpdate.ts | 42 ++-- src/gateway/handlers/channelUpdate.ts | 49 ++-- src/gateway/handlers/guildEmojiUpdate.ts | 106 ++++----- .../handlers/guildIntegrationsUpdate.ts | 14 ++ src/gateway/handlers/guildMemberAdd.ts | 7 +- src/gateway/handlers/index.ts | 180 +++++++++------ src/gateway/handlers/messageDeleteBulk.ts | 29 +++ src/gateway/handlers/typingStart.ts | 23 ++ src/gateway/handlers/userUpdate.ts | 16 ++ src/gateway/handlers/webhooksUpdate.ts | 16 ++ src/managers/gatewayCache.ts | 1 - src/models/client.ts | 12 + src/models/rest.ts | 45 ++-- src/structures/textChannel.ts | 21 +- src/structures/user.ts | 145 ++++++------ src/structures/webhook.ts | 181 +++++++++++++-- src/test/cmd.ts | 212 +++++++++--------- src/test/hook.ts | 9 + src/types/channel.ts | 2 +- src/types/gateway.ts | 15 ++ src/utils/intents.ts | 95 +++++--- 22 files changed, 800 insertions(+), 437 deletions(-) create mode 100644 src/gateway/handlers/guildIntegrationsUpdate.ts create mode 100644 src/gateway/handlers/messageDeleteBulk.ts create mode 100644 src/gateway/handlers/typingStart.ts create mode 100644 src/gateway/handlers/userUpdate.ts create mode 100644 src/gateway/handlers/webhooksUpdate.ts create mode 100644 src/test/hook.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 7848ab6..7db4502 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,10 @@ -{ - "deno.enable": true, - "deno.lint": false, - "deno.unstable": false, - "deepscan.enable": true, - "deno.import_intellisense_origins": { - "https://deno.land": true - } +{ + "deno.enable": true, + "deno.lint": false, + "deno.unstable": false, + "deepscan.enable": true, + "deno.import_intellisense_origins": { + "https://deno.land": true + }, + "editor.tabSize": 2 } \ No newline at end of file diff --git a/src/gateway/handlers/channelPinsUpdate.ts b/src/gateway/handlers/channelPinsUpdate.ts index 3a7a0fd..a05dd11 100644 --- a/src/gateway/handlers/channelPinsUpdate.ts +++ b/src/gateway/handlers/channelPinsUpdate.ts @@ -1,21 +1,21 @@ -import { Gateway, GatewayEventHandler } from '../index.ts' -import { TextChannel } from '../../structures/textChannel.ts' -import { ChannelPinsUpdatePayload } from '../../types/gateway.ts' - -export const channelPinsUpdate: GatewayEventHandler = async ( - gateway: Gateway, - d: ChannelPinsUpdatePayload -) => { - const after: TextChannel | undefined = await gateway.client.channels.get(d.channel_id) - if (after !== undefined) { - const before = after.refreshFromData({ - last_pin_timestamp: d.last_pin_timestamp - }) - const raw = await gateway.client.channels._get(d.channel_id) - await gateway.client.channels.set( - after.id, - Object.assign(raw, { last_pin_timestamp: d.last_pin_timestamp }) - ) - gateway.client.emit('channelPinsUpdate', before, after) - } -} +import { Gateway, GatewayEventHandler } from '../index.ts' +import { TextChannel } from '../../structures/textChannel.ts' +import { ChannelPinsUpdatePayload } from '../../types/gateway.ts' + +export const channelPinsUpdate: GatewayEventHandler = async ( + gateway: Gateway, + d: ChannelPinsUpdatePayload +) => { + const after: TextChannel | undefined = await gateway.client.channels.get(d.channel_id) + if (after !== undefined) { + const before = after.refreshFromData({ + last_pin_timestamp: d.last_pin_timestamp + }) + const raw = await gateway.client.channels._get(d.channel_id) + await gateway.client.channels.set( + after.id, + Object.assign(raw, { last_pin_timestamp: d.last_pin_timestamp }) + ) + gateway.client.emit('channelPinsUpdate', before, after) + } +} diff --git a/src/gateway/handlers/channelUpdate.ts b/src/gateway/handlers/channelUpdate.ts index b19e99b..8b27ca9 100644 --- a/src/gateway/handlers/channelUpdate.ts +++ b/src/gateway/handlers/channelUpdate.ts @@ -1,28 +1,21 @@ -import { Channel } from '../../structures/channel.ts' -import { Guild } from '../../structures/guild.ts' -import { ChannelPayload, GuildChannelPayload } from '../../types/channel.ts' -import getChannelByType from '../../utils/getChannelByType.ts' -import { Gateway, GatewayEventHandler } from '../index.ts' - -export const channelUpdate: GatewayEventHandler = async ( - gateway: Gateway, - d: ChannelPayload -) => { - const oldChannel: Channel | undefined = await gateway.client.channels.get(d.id) - - if (oldChannel !== undefined) { - await gateway.client.channels.set(d.id, d) - let guild: undefined | Guild; - if ('guild_id' in d) { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - guild = await gateway.client.guilds.get((d as GuildChannelPayload).guild_id) as Guild | undefined - } - if (oldChannel.type !== d.type) { - const channel: Channel = getChannelByType(gateway.client, d, guild) ?? oldChannel - gateway.client.emit('channelUpdate', oldChannel, channel) - } else { - const before = oldChannel.refreshFromData(d) - gateway.client.emit('channelUpdate', before, oldChannel) - } - } -} +import { Channel } from '../../structures/channel.ts' +import { ChannelPayload } from "../../types/channel.ts" +import { Gateway, GatewayEventHandler } from '../index.ts' + +export const channelUpdate: GatewayEventHandler = async ( + gateway: Gateway, + d: ChannelPayload +) => { + const oldChannel: Channel | undefined = await gateway.client.channels.get(d.id) + await gateway.client.channels.set(d.id, d) + const newChannel: Channel = (await gateway.client.channels.get(d.id) as unknown) as Channel + + if (oldChannel !== undefined) { + // (DjDeveloperr): Already done by ChannelsManager. I'll recheck later + // if ('guild_id' in d) { + // // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + // (newChannel as GuildChannel).guild = await gateway.client.guilds.get((d as GuildChannelPayload).guild_id) as Guild + // } + gateway.client.emit('channelUpdate', oldChannel, newChannel) + } else gateway.client.emit('channelUpdateUncached', newChannel) +} diff --git a/src/gateway/handlers/guildEmojiUpdate.ts b/src/gateway/handlers/guildEmojiUpdate.ts index 9fe8213..53b6502 100644 --- a/src/gateway/handlers/guildEmojiUpdate.ts +++ b/src/gateway/handlers/guildEmojiUpdate.ts @@ -1,53 +1,53 @@ -import { Emoji } from "../../structures/emoji.ts" -import { Guild } from '../../structures/guild.ts' -import { EmojiPayload } from "../../types/emoji.ts" -import { GuildEmojiUpdatePayload } from '../../types/gateway.ts' -import { Gateway, GatewayEventHandler } from '../index.ts' - -export const guildEmojiUpdate: GatewayEventHandler = async ( - gateway: Gateway, - d: GuildEmojiUpdatePayload -) => { - const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id) - if (guild !== undefined) { - const emojis = await guild.emojis.collection() - const deleted: Emoji[] = [] - const added: Emoji[] = [] - const updated: Array<{ before: Emoji, after: Emoji }> = [] - const _updated: EmojiPayload[] = [] - - for (const raw of d.emojis) { - const has = emojis.get(raw.id) - if (has === undefined) { - await guild.emojis.set(raw.id, raw) - const emoji = await guild.emojis.get(raw.id) as Emoji - added.push(emoji) - } else _updated.push(raw) - } - - for (const emoji of emojis.values()) { - const find = _updated.find(e => emoji.id === e.id) - if (find === undefined) { - await guild.emojis.delete(emoji.id) - deleted.push(emoji) - } else { - const before = await guild.emojis.get(find.id) as Emoji - await guild.emojis.set(find.id, find) - const after = await guild.emojis.get(find.id) as Emoji - updated.push({ before, after }) - } - } - - for (const emoji of deleted) { - gateway.client.emit('guildEmojiDelete', emoji) - } - - for (const emoji of added) { - gateway.client.emit('guildEmojiAdd', emoji) - } - - for (const emoji of updated) { - gateway.client.emit('guildEmojiUpdate', emoji.before, emoji.after) - } - } -} +import { Emoji } from "../../structures/emoji.ts" +import { Guild } from '../../structures/guild.ts' +import { EmojiPayload } from "../../types/emoji.ts" +import { GuildEmojiUpdatePayload } from '../../types/gateway.ts' +import { Gateway, GatewayEventHandler } from '../index.ts' + +export const guildEmojiUpdate: GatewayEventHandler = async ( + gateway: Gateway, + d: GuildEmojiUpdatePayload +) => { + const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id) + if (guild !== undefined) { + const emojis = await guild.emojis.collection() + const deleted: Emoji[] = [] + const added: Emoji[] = [] + const updated: Array<{ before: Emoji, after: Emoji }> = [] + const _updated: EmojiPayload[] = [] + + for (const raw of d.emojis) { + const has = emojis.get(raw.id) + if (has === undefined) { + await guild.emojis.set(raw.id, raw) + const emoji = await guild.emojis.get(raw.id) as Emoji + added.push(emoji) + } else _updated.push(raw) + } + + for (const emoji of emojis.values()) { + const find = _updated.find(e => emoji.id === e.id) + if (find === undefined) { + await guild.emojis.delete(emoji.id) + deleted.push(emoji) + } else { + const before = await guild.emojis.get(find.id) as Emoji + await guild.emojis.set(find.id, find) + const after = await guild.emojis.get(find.id) as Emoji + updated.push({ before, after }) + } + } + + for (const emoji of deleted) { + gateway.client.emit('guildEmojiDelete', guild, emoji) + } + + for (const emoji of added) { + gateway.client.emit('guildEmojiAdd', guild, emoji) + } + + for (const emoji of updated) { + gateway.client.emit('guildEmojiUpdate', guild, emoji.before, emoji.after) + } + } +} diff --git a/src/gateway/handlers/guildIntegrationsUpdate.ts b/src/gateway/handlers/guildIntegrationsUpdate.ts new file mode 100644 index 0000000..a115efc --- /dev/null +++ b/src/gateway/handlers/guildIntegrationsUpdate.ts @@ -0,0 +1,14 @@ +import { Gateway, GatewayEventHandler } from '../index.ts' +import { Guild } from '../../structures/guild.ts' +import { GuildIntegrationsUpdatePayload } from "../../types/gateway.ts" + +export const guildIntegrationsUpdate: GatewayEventHandler = async ( + gateway: Gateway, + d: GuildIntegrationsUpdatePayload +) => { + console.log(d) + const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id) + if (guild === undefined) return + + gateway.client.emit('guildIntegrationsUpdate', guild) +} \ No newline at end of file diff --git a/src/gateway/handlers/guildMemberAdd.ts b/src/gateway/handlers/guildMemberAdd.ts index 4524b3f..36d2bf9 100644 --- a/src/gateway/handlers/guildMemberAdd.ts +++ b/src/gateway/handlers/guildMemberAdd.ts @@ -1,6 +1,7 @@ import { Gateway, GatewayEventHandler } from '../index.ts' import { Guild } from '../../structures/guild.ts' -import { GuildMemberAddPayload } from "../../../mod.ts" +import { GuildMemberAddPayload } from "../../types/gateway.ts" +import { Member } from "../../structures/member.ts" export const guildMemberAdd: GatewayEventHandler = async ( gateway: Gateway, @@ -11,6 +12,6 @@ export const guildMemberAdd: GatewayEventHandler = async ( if (guild === undefined) return await guild.members.set(d.user.id, d) - const member = await guild.members.get(d.user.id) - gateway.client.emit('guildMemberAdd', member) + const member = await guild.members.get(d.user.id) as unknown + gateway.client.emit('guildMemberAdd', member as Member) } \ No newline at end of file diff --git a/src/gateway/handlers/index.ts b/src/gateway/handlers/index.ts index b63bb16..319c3f9 100644 --- a/src/gateway/handlers/index.ts +++ b/src/gateway/handlers/index.ts @@ -1,65 +1,115 @@ -import { GatewayEventHandler } from '../index.ts' -import { GatewayEvents } from '../../types/gateway.ts' -import { channelCreate } from './channelCreate.ts' -import { channelDelete } from './channelDelete.ts' -import { channelUpdate } from './channelUpdate.ts' -import { channelPinsUpdate } from './channelPinsUpdate.ts' -import { guildCreate } from './guildCreate.ts' -import { guildDelte as guildDelete } from './guildDelete.ts' -import { guildUpdate } from './guildUpdate.ts' -import { guildBanAdd } from './guildBanAdd.ts' -import { ready } from './ready.ts' -import { guildBanRemove } from './guildBanRemove.ts' -import { messageCreate } from './messageCreate.ts' -import { resume } from './resume.ts' -import { reconnect } from './reconnect.ts' -import { messageDelete } from "./messageDelete.ts" -import { messageUpdate } from "./messageUpdate.ts" -import { guildEmojiUpdate } from "./guildEmojiUpdate.ts" -import { guildMemberAdd } from "./guildMemberAdd.ts" -import { guildMemberRemove } from "./guildMemberRemove.ts" -import { guildMemberUpdate } from "./guildMemberUpdate.ts" -import { guildRoleCreate } from "./guildRoleCreate.ts" -import { guildRoleDelete } from "./guildRoleDelete.ts" -import { guildRoleUpdate } from "./guildRoleUpdate.ts" - -export const gatewayHandlers: { - [eventCode in GatewayEvents]: GatewayEventHandler | undefined -} = { - READY: ready, - RECONNECT: reconnect, - RESUMED: resume, - CHANNEL_CREATE: channelCreate, - CHANNEL_DELETE: channelDelete, - CHANNEL_UPDATE: channelUpdate, - CHANNEL_PINS_UPDATE: channelPinsUpdate, - GUILD_CREATE: guildCreate, - GUILD_DELETE: guildDelete, - GUILD_UPDATE: guildUpdate, - GUILD_BAN_ADD: guildBanAdd, - GUILD_BAN_REMOVE: guildBanRemove, - GUILD_EMOJIS_UPDATE: guildEmojiUpdate, - GUILD_INTEGRATIONS_UPDATE: undefined, - GUILD_MEMBER_ADD: guildMemberAdd, - GUILD_MEMBER_REMOVE: guildMemberRemove, - GUILD_MEMBER_UPDATE: guildMemberUpdate, - GUILD_MEMBERS_CHUNK: undefined, - GUILD_ROLE_CREATE: guildRoleCreate, - GUILD_ROLE_UPDATE: guildRoleUpdate, - GUILD_ROLE_DELETE: guildRoleDelete, - INVITE_CREATE: undefined, - INVITE_DELETE: undefined, - MESSAGE_CREATE: messageCreate, - MESSAGE_UPDATE: messageUpdate, - MESSAGE_DELETE: messageDelete, - MESSAGE_DELETE_BULK: undefined, - MESSAGE_REACTION_ADD: undefined, - MESSAGE_REACTION_REMOVE: undefined, - MESSAGE_REACTION_REMOVE_ALL: undefined, - MESSAGE_REACTION_REMOVE_EMOJI: undefined, - PRESENCE_UPDATE: undefined, - TYPING_START: undefined, - USER_UPDATE: undefined, - VOICE_SERVER_UPDATE: undefined, - WEBHOOKS_UPDATE: undefined -} +import { GatewayEventHandler } from '../index.ts' +import { GatewayEvents, TypingStartGuildData } from '../../types/gateway.ts' +import { channelCreate } from './channelCreate.ts' +import { channelDelete } from './channelDelete.ts' +import { channelUpdate } from './channelUpdate.ts' +import { channelPinsUpdate } from './channelPinsUpdate.ts' +import { guildCreate } from './guildCreate.ts' +import { guildDelte as guildDelete } from './guildDelete.ts' +import { guildUpdate } from './guildUpdate.ts' +import { guildBanAdd } from './guildBanAdd.ts' +import { ready } from './ready.ts' +import { guildBanRemove } from './guildBanRemove.ts' +import { messageCreate } from './messageCreate.ts' +import { resume } from './resume.ts' +import { reconnect } from './reconnect.ts' +import { messageDelete } from "./messageDelete.ts" +import { messageUpdate } from "./messageUpdate.ts" +import { guildEmojiUpdate } from "./guildEmojiUpdate.ts" +import { guildMemberAdd } from "./guildMemberAdd.ts" +import { guildMemberRemove } from "./guildMemberRemove.ts" +import { guildMemberUpdate } from "./guildMemberUpdate.ts" +import { guildRoleCreate } from "./guildRoleCreate.ts" +import { guildRoleDelete } from "./guildRoleDelete.ts" +import { guildRoleUpdate } from "./guildRoleUpdate.ts" +import { guildIntegrationsUpdate } from "./guildIntegrationsUpdate.ts" +import { webhooksUpdate } from "./webhooksUpdate.ts" +import { messageDeleteBulk } from "./messageDeleteBulk.ts" +import { userUpdate } from "./userUpdate.ts" +import { typingStart } from "./typingStart.ts" +import { Channel } from "../../structures/channel.ts" +import { GuildTextChannel, TextChannel } from "../../structures/textChannel.ts" +import { Guild } from "../../structures/guild.ts" +import { User } from "../../structures/user.ts" +import { Emoji } from "../../structures/emoji.ts" +import { Member } from "../../structures/member.ts" +import { Role } from "../../structures/role.ts" +import { Message } from "../../structures/message.ts" +import { Collection } from "../../utils/collection.ts" + +export const gatewayHandlers: { + [eventCode in GatewayEvents]: GatewayEventHandler | undefined +} = { + READY: ready, + RECONNECT: reconnect, + RESUMED: resume, + CHANNEL_CREATE: channelCreate, + CHANNEL_DELETE: channelDelete, + CHANNEL_UPDATE: channelUpdate, + CHANNEL_PINS_UPDATE: channelPinsUpdate, + GUILD_CREATE: guildCreate, + GUILD_DELETE: guildDelete, + GUILD_UPDATE: guildUpdate, + GUILD_BAN_ADD: guildBanAdd, + GUILD_BAN_REMOVE: guildBanRemove, + GUILD_EMOJIS_UPDATE: guildEmojiUpdate, + GUILD_INTEGRATIONS_UPDATE: guildIntegrationsUpdate, + GUILD_MEMBER_ADD: guildMemberAdd, + GUILD_MEMBER_REMOVE: guildMemberRemove, + GUILD_MEMBER_UPDATE: guildMemberUpdate, + GUILD_MEMBERS_CHUNK: undefined, + GUILD_ROLE_CREATE: guildRoleCreate, + GUILD_ROLE_UPDATE: guildRoleUpdate, + GUILD_ROLE_DELETE: guildRoleDelete, + INVITE_CREATE: undefined, + INVITE_DELETE: undefined, + MESSAGE_CREATE: messageCreate, + MESSAGE_UPDATE: messageUpdate, + MESSAGE_DELETE: messageDelete, + MESSAGE_DELETE_BULK: messageDeleteBulk, + MESSAGE_REACTION_ADD: undefined, + MESSAGE_REACTION_REMOVE: undefined, + MESSAGE_REACTION_REMOVE_ALL: undefined, + MESSAGE_REACTION_REMOVE_EMOJI: undefined, + PRESENCE_UPDATE: undefined, + TYPING_START: typingStart, + USER_UPDATE: userUpdate, + VOICE_SERVER_UPDATE: undefined, + WEBHOOKS_UPDATE: webhooksUpdate +} + +export interface EventTypes { + [name: string]: (...args: any[]) => void +} + +export interface ClientEvents extends EventTypes { + 'ready': () => void + 'reconnect': () => void + 'resumed': () => void + 'channelCreate': (channel: Channel) => void + 'channelDelete': (channel: Channel) => void + 'channelPinsUpdate': (before: TextChannel, after: TextChannel) => void + 'channelUpdate': (before: Channel, after: Channel) => void + 'guildBanAdd': (guild: Guild, user: User) => void + 'guildBanRemove': (guild: Guild, user: User) => void + 'guildCreate': (guild: Guild) => void + 'guildDelete': (guild: Guild) => void + 'guildEmojiAdd': (guild: Guild, emoji: Emoji) => void + 'guildEmojiDelete': (guild: Guild, emoji: Emoji) => void + 'guildEmojiUpdate': (guild: Guild, before: Emoji, after: Emoji) => void + 'guildIntegrationsUpdate': (guild: Guild) => void + 'guildMemberAdd': (member: Member) => void + 'guildMemberRemove': (member: Member) => void + 'guildMemberUpdate': (before: Member, after: Member) => void + 'guildRoleCreate': (role: Role) => void + 'guildRoleDelete': (role: Role) => void + 'guildRoleUpdate': (before: Role, after: Role) => void + 'guildUpdate': (before: Guild, after: Guild) => void + 'messageCreate': (message: Message) => void + 'messageDelete': (message: Message) => void + 'messageDeleteBulk': (channel: GuildTextChannel, messages: Collection, uncached: Set) => void + 'messageUpdate': (before: Message, after: Message) => void + 'typingStart': (user: User, channel: TextChannel, at: Date, guildData?: TypingStartGuildData) => void + 'userUpdate': (before: User, after: User) => void + 'webhooksUpdate': (guild: Guild, channel: GuildTextChannel) => void +} \ No newline at end of file diff --git a/src/gateway/handlers/messageDeleteBulk.ts b/src/gateway/handlers/messageDeleteBulk.ts new file mode 100644 index 0000000..8f15718 --- /dev/null +++ b/src/gateway/handlers/messageDeleteBulk.ts @@ -0,0 +1,29 @@ +import { Message } from "../../structures/message.ts" +import { GuildTextChannel } from '../../structures/textChannel.ts' +import { MessageDeleteBulkPayload } from "../../types/gateway.ts" +import { Collection } from "../../utils/collection.ts" +import { Gateway, GatewayEventHandler } from '../index.ts' + +export const messageDeleteBulk: GatewayEventHandler = async ( + gateway: Gateway, + d: MessageDeleteBulkPayload +) => { + let channel = await gateway.client.channels.get(d.channel_id) + // Fetch the channel if not cached + if (channel === undefined) + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + channel = (await gateway.client.channels.fetch(d.channel_id)) as GuildTextChannel + + const messages = new Collection() + const uncached = new Set() + for (const id of d.ids) { + const message = await channel.messages.get(id) + if (message === undefined) uncached.add(id) + else { + messages.set(id, message) + await channel.messages.delete(id) + } + } + + gateway.client.emit('messageDeleteBulk', channel, messages, uncached) +} diff --git a/src/gateway/handlers/typingStart.ts b/src/gateway/handlers/typingStart.ts new file mode 100644 index 0000000..4847b83 --- /dev/null +++ b/src/gateway/handlers/typingStart.ts @@ -0,0 +1,23 @@ +import { Member } from "../../structures/member.ts" +import { TextChannel } from "../../structures/textChannel.ts" +import { TypingStartPayload } from "../../types/gateway.ts" +import { Gateway, GatewayEventHandler } from '../index.ts' + +// TODO: Do we need to add uncached events here? +export const typingStart: GatewayEventHandler = async ( + gateway: Gateway, + d: TypingStartPayload +) => { + const user = await gateway.client.users.get(d.user_id) + if (user === undefined) return console.log('user not cached') + + const channel = await gateway.client.channels.get(d.channel_id) + if (channel === undefined) return console.log(`channel not cached`) + + const guild = d.guild_id !== undefined ? await gateway.client.guilds.get(d.guild_id) : undefined + if(guild === undefined && d.guild_id !== undefined) return console.log('guild not cached') + + const member = d.member !== undefined && guild !== undefined ? new Member(gateway.client, d.member, user, guild) : undefined + + gateway.client.emit('typingStart', user, (channel as unknown) as TextChannel, new Date(d.timestamp), guild !== undefined && member !== undefined ? { guild, member } : undefined) +} diff --git a/src/gateway/handlers/userUpdate.ts b/src/gateway/handlers/userUpdate.ts new file mode 100644 index 0000000..8f397ae --- /dev/null +++ b/src/gateway/handlers/userUpdate.ts @@ -0,0 +1,16 @@ +import { User } from "../../structures/user.ts" +import { UserPayload } from "../../types/user.ts" +import { Gateway, GatewayEventHandler } from '../index.ts' + +export const userUpdate: GatewayEventHandler = async ( + gateway: Gateway, + d: UserPayload +) => { + const oldUser: User | undefined = await gateway.client.users.get(d.id) + await gateway.client.users.set(d.id, d) + const newUser: User = (await gateway.client.users.get(d.id) as unknown) as User + + if (oldUser !== undefined) { + gateway.client.emit('userUpdate', oldUser, newUser) + } else gateway.client.emit('userUpdateUncached', newUser) +} diff --git a/src/gateway/handlers/webhooksUpdate.ts b/src/gateway/handlers/webhooksUpdate.ts new file mode 100644 index 0000000..876fb7f --- /dev/null +++ b/src/gateway/handlers/webhooksUpdate.ts @@ -0,0 +1,16 @@ +import { Gateway, GatewayEventHandler } from '../index.ts' +import { Guild } from '../../structures/guild.ts' +import { WebhooksUpdatePayload } from "../../types/gateway.ts" +import { GuildTextChannel } from "../../structures/textChannel.ts" + +export const webhooksUpdate: GatewayEventHandler = async ( + gateway: Gateway, + d: WebhooksUpdatePayload +) => { + const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id) + if (guild === undefined) return + + const channel: GuildTextChannel | undefined = await guild.channels.get(d.channel_id) as GuildTextChannel + if (channel === undefined) gateway.client.emit('webhooksUpdateUncached', guild, d.channel_id) + else gateway.client.emit('webhooksUpdate', guild, channel) +} \ No newline at end of file diff --git a/src/managers/gatewayCache.ts b/src/managers/gatewayCache.ts index 82c3f47..3c6fba2 100644 --- a/src/managers/gatewayCache.ts +++ b/src/managers/gatewayCache.ts @@ -20,7 +20,6 @@ export class GatewayCache { } async delete (key: string): Promise { - console.log(`[GatewayCache] DEL ${key}`) const result = await this.client.cache.delete(this.cacheName, key) return result } diff --git a/src/models/client.ts b/src/models/client.ts index cf5aaa8..7c62137 100644 --- a/src/models/client.ts +++ b/src/models/client.ts @@ -12,6 +12,7 @@ import { } from '../structures/presence.ts' import { EmojisManager } from '../managers/emojis.ts' import { ActivityGame, ClientActivity } from "../types/presence.ts" +import { ClientEvents } from "../gateway/handlers/index.ts" // import { Application } from "../../mod.ts" /** Some Client Options to modify behaviour */ @@ -34,6 +35,17 @@ export interface ClientOptions { messageCacheLifetime?: number } +export declare interface Client { + on: ( + event: U, listener: ClientEvents[U] + ) => this + + emit: ( + event: U, ...args: Parameters + ) => boolean +} + + /** * Discord Client. */ diff --git a/src/models/rest.ts b/src/models/rest.ts index 417d2fe..3dfc0e8 100644 --- a/src/models/rest.ts +++ b/src/models/rest.ts @@ -1,4 +1,3 @@ -import { delay } from '../utils/index.ts' import * as baseEndpoints from '../consts/urlsAndVersions.ts' import { Client } from './client.ts' import { getBuildInfo } from '../utils/buildInfo.ts' @@ -45,13 +44,13 @@ export interface RateLimit { } export class RESTManager { - client: Client + client?: Client queues: { [key: string]: QueuedItem[] } = {} rateLimits = new Collection() globalRateLimit: boolean = false processing: boolean = false - constructor(client: Client) { + constructor(client?: Client) { this.client = client // eslint-disable-next-line @typescript-eslint/no-floating-promises this.handleRateLimits() @@ -125,7 +124,7 @@ export class RESTManager { } if (Object.keys(this.queues).length !== 0) { - await delay(1000) + // await delay(100) // eslint-disable-next-line @typescript-eslint/no-floating-promises this.processQueue() // eslint-disable-next-line @typescript-eslint/no-floating-promises @@ -139,11 +138,12 @@ export class RESTManager { ): { [key: string]: any } { const headers: RequestHeaders = { - 'Authorization': `Bot ${this.client.token}`, 'User-Agent': `DiscordBot (harmony, https://github.com/harmony-org/harmony)` } - if (this.client.token === undefined) delete headers.Authorization + if (this.client !== undefined) headers.Authorization = `Bot ${this.client.token}` + + if (this.client?.token === undefined) delete headers.Authorization if (method === 'get' || method === 'head' || method === 'delete') body = undefined @@ -166,7 +166,7 @@ export class RESTManager { method: method.toUpperCase() } - if (this.client.bot === false) { + if (this.client?.bot === false) { // This is a selfbot. Use requests similar to Discord Client data.headers.authorization = this.client.token as string data.headers['accept-language'] = 'en-US' @@ -259,7 +259,7 @@ export class RESTManager { if ( (status >= 200 && status < 400) - || status === HttpResponseCode.NoContent + || status === HttpResponseCode.NoContent || status === HttpResponseCode.TooManyRequests ) return @@ -290,7 +290,8 @@ export class RESTManager { url: string, body?: unknown, maxRetries = 0, - bucket?: string | null + bucket?: string | null, + rawResponse?: boolean, ): Promise { return await new Promise((resolve, reject) => { const onComplete = async (): Promise => { @@ -318,7 +319,7 @@ export class RESTManager { let urlToUse = method === 'get' && query !== '' ? `${url}?${query}` : url - if (this.client.canary === true) { + if (this.client?.canary === true) { const split = urlToUse.split('//') urlToUse = split[0] + '//canary.' + split[1] } @@ -328,7 +329,7 @@ export class RESTManager { const response = await fetch(urlToUse, requestData) const bucketFromHeaders = this.processHeaders(url, response.headers) - if (response.status === 204) return resolve(undefined) + if (response.status === 204) return resolve(rawResponse === true ? { response, body: null } : undefined) const json: any = await response.json() await this.handleStatusCode(response, json, requestData) @@ -347,7 +348,7 @@ export class RESTManager { bucket: bucketFromHeaders } } - return resolve(json) + return resolve(rawResponse === true ? { response, body: json } : json) } catch (error) { return reject(error) } @@ -375,23 +376,23 @@ export class RESTManager { }) } - async get(url: string, body?: unknown): Promise { - return await this.make('get', url, body) + async get(url: string, body?: unknown, maxRetries = 0, bucket?: string | null, rawResponse?: boolean): Promise { + return await this.make('get', url, body, maxRetries, bucket, rawResponse) } - async post(url: string, body?: unknown): Promise { - return await this.make('post', url, body) + async post(url: string, body?: unknown, maxRetries = 0, bucket?: string | null, rawResponse?: boolean): Promise { + return await this.make('post', url, body, maxRetries, bucket, rawResponse) } - async delete(url: string, body?: unknown): Promise { - return await this.make('delete', url, body) + async delete(url: string, body?: unknown, maxRetries = 0, bucket?: string | null, rawResponse?: boolean): Promise { + return await this.make('delete', url, body, maxRetries, bucket, rawResponse) } - async patch(url: string, body?: unknown): Promise { - return await this.make('patch', url, body) + async patch(url: string, body?: unknown, maxRetries = 0, bucket?: string | null, rawResponse?: boolean): Promise { + return await this.make('patch', url, body, maxRetries, bucket, rawResponse) } - async put(url: string, body?: unknown): Promise { - return await this.make('put', url, body) + async put(url: string, body?: unknown, maxRetries = 0, bucket?: string | null, rawResponse?: boolean): Promise { + return await this.make('put', url, body, maxRetries, bucket, rawResponse) } } diff --git a/src/structures/textChannel.ts b/src/structures/textChannel.ts index 26b8f88..cc85b11 100644 --- a/src/structures/textChannel.ts +++ b/src/structures/textChannel.ts @@ -7,7 +7,7 @@ import { Embed } from './embed.ts' import { Guild } from "./guild.ts" import { Message } from './message.ts' -type AllMessageOptions = MessageOption | Embed +export type AllMessageOptions = MessageOption | Embed export class TextChannel extends Channel { lastMessageID?: string @@ -27,6 +27,12 @@ export class TextChannel extends Channel { this.lastPinTimestamp = data.last_pin_timestamp ?? this.lastPinTimestamp } + /** + * + * @param text Text content of the Message to send. + * @param option Various other Message options. + * @param reply Reference to a Message object to reply-to. + */ async send(text?: string | AllMessageOptions, option?: AllMessageOptions, reply?: Message): Promise { if (typeof text === "object") { option = text @@ -44,7 +50,7 @@ export class TextChannel extends Channel { embed: option?.embed, file: option?.file, tts: option?.tts, - allowed_mentions: option?.allowedMention + allowed_mentions: option?.allowedMentions } if (reply !== undefined) { @@ -63,6 +69,12 @@ export class TextChannel extends Channel { return res } + /** + * + * @param message Message to edit. ID or the Message object itself. + * @param text New text contents of the Message. + * @param option Other options to edit the message. + */ async editMessage( message: Message | string, text?: string, @@ -84,9 +96,10 @@ export class TextChannel extends Channel { { content: text, embed: option?.embed !== undefined ? option.embed.toJSON() : undefined, - file: option?.file, + // Cannot upload new files with Message + // file: option?.file, tts: option?.tts, - allowed_mentions: option?.allowedMention + allowed_mentions: option?.allowedMentions } ) diff --git a/src/structures/user.ts b/src/structures/user.ts index 697e7c6..56a5339 100644 --- a/src/structures/user.ts +++ b/src/structures/user.ts @@ -1,69 +1,76 @@ -import { Client } from '../models/client.ts' -import { UserPayload } from '../types/user.ts' -import { UserFlagsManager } from "../utils/userFlags.ts" -import { Base } from './base.ts' - -export class User extends Base { - id: string - username: string - discriminator: string - avatar?: string - bot?: boolean - system?: boolean - mfaEnabled?: boolean - locale?: string - verified?: boolean - email?: string - flags?: UserFlagsManager - premiumType?: 0 | 1 | 2 - publicFlags?: UserFlagsManager - - get tag (): string { - return `${this.username}#${this.discriminator}` - } - - get nickMention (): string { - return `<@!${this.id}>` - } - - get mention (): string { - return `<@${this.id}>` - } - - constructor (client: Client, data: UserPayload) { - super(client, data) - this.id = data.id - this.username = data.username - this.discriminator = data.discriminator - this.avatar = data.avatar - this.bot = data.bot - this.system = data.system - this.mfaEnabled = data.mfa_enabled - this.locale = data.locale - this.verified = data.verified - this.email = data.email - this.flags = new UserFlagsManager(data.flags) - this.premiumType = data.premium_type - this.publicFlags = new UserFlagsManager(data.public_flags) - } - - protected readFromData (data: UserPayload): void { - super.readFromData(data) - this.username = data.username ?? this.username - this.discriminator = data.discriminator ?? this.discriminator - this.avatar = data.avatar ?? this.avatar - this.bot = data.bot ?? this.bot - this.system = data.system ?? this.system - this.mfaEnabled = data.mfa_enabled ?? this.mfaEnabled - this.locale = data.locale ?? this.locale - this.verified = data.verified ?? this.verified - this.email = data.email ?? this.email - this.flags = new UserFlagsManager(data.flags) ?? this.flags - this.premiumType = data.premium_type ?? this.premiumType - this.publicFlags = new UserFlagsManager(data.public_flags) ?? this.publicFlags - } - - toString (): string { - return this.mention - } -} +import { Client } from '../models/client.ts' +import { UserPayload } from '../types/user.ts' +import { UserFlagsManager } from "../utils/userFlags.ts" +import { Base } from './base.ts' + +export class User extends Base { + id: string + username: string + discriminator: string + avatar?: string + bot?: boolean + system?: boolean + mfaEnabled?: boolean + locale?: string + verified?: boolean + email?: string + flags?: UserFlagsManager + /** + * Nitro type of the User. + * + * 0 = No Nitro + * 1 = Classic Nitro + * 2 = Regular Nitro + */ + premiumType?: 0 | 1 | 2 + publicFlags?: UserFlagsManager + + get tag (): string { + return `${this.username}#${this.discriminator}` + } + + get nickMention (): string { + return `<@!${this.id}>` + } + + get mention (): string { + return `<@${this.id}>` + } + + constructor (client: Client, data: UserPayload) { + super(client, data) + this.id = data.id + this.username = data.username + this.discriminator = data.discriminator + this.avatar = data.avatar + this.bot = data.bot + this.system = data.system + this.mfaEnabled = data.mfa_enabled + this.locale = data.locale + this.verified = data.verified + this.email = data.email + this.flags = new UserFlagsManager(data.flags) + this.premiumType = data.premium_type + this.publicFlags = new UserFlagsManager(data.public_flags) + } + + protected readFromData (data: UserPayload): void { + super.readFromData(data) + this.username = data.username ?? this.username + this.discriminator = data.discriminator ?? this.discriminator + this.avatar = data.avatar ?? this.avatar + this.bot = data.bot ?? this.bot + this.system = data.system ?? this.system + this.mfaEnabled = data.mfa_enabled ?? this.mfaEnabled + this.locale = data.locale ?? this.locale + this.verified = data.verified ?? this.verified + this.email = data.email ?? this.email + this.flags = new UserFlagsManager(data.flags) ?? this.flags + this.premiumType = data.premium_type ?? this.premiumType + this.publicFlags = new UserFlagsManager(data.public_flags) ?? this.publicFlags + } + + toString (): string { + return this.mention + } +} diff --git a/src/structures/webhook.ts b/src/structures/webhook.ts index 205065c..0c9a9c0 100644 --- a/src/structures/webhook.ts +++ b/src/structures/webhook.ts @@ -1,23 +1,158 @@ -import { Client } from '../models/client.ts' -import { UserPayload } from '../types/user.ts' -import { WebhookPayload } from '../types/webhook.ts' -import { Base } from './base.ts' - -export class Webhook extends Base { - id: string - type: 1 | 2 - guildID?: string - channelID: string - user?: UserPayload - name?: string - avatar?: string - token?: string - applicationID?: string - - constructor (client: Client, data: WebhookPayload) { - super(client) - this.id = data.id - this.type = data.type - this.channelID = data.channel_id - } -} +import { DISCORD_API_URL, DISCORD_API_VERSION } from "../consts/urlsAndVersions.ts" +import { Client } from '../models/client.ts' +import { RESTManager } from "../models/rest.ts" +import { MessageOption } from "../types/channel.ts" +import { UserPayload } from '../types/user.ts' +import { WebhookPayload } from '../types/webhook.ts' +import { Embed } from "./embed.ts" +import { Message } from "./message.ts" +import { TextChannel } from "./textChannel.ts" +import { User } from "./user.ts" +import { fetchAuto } from 'https://raw.githubusercontent.com/DjDeveloperr/fetch-base64/main/mod.ts' + +export interface WebhookMessageOptions extends MessageOption { + embeds?: Embed[] + name?: string + avatar?: string +} + +export type AllWebhookMessageOptions = string | WebhookMessageOptions + +export interface WebhookEditOptions { + /** New name to set for Webhook. */ + name?: string + /** New avatar to set for Webhook. URL of image or base64 encoded data. */ + avatar?: string + /** New channel for Webhook. Requires authentication. */ + channelID?: string +} + +/** Webhook follows different way of instantiation */ +export class Webhook { + client?: Client + id: string + type: 1 | 2 + guildID?: string + channelID: string + user?: User + userRaw?: UserPayload + name?: string + avatar?: string + token?: string + applicationID?: string + rest: RESTManager + + get url(): string { + return `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/webhooks/${this.id}/${this.token}` + } + + constructor(data: WebhookPayload, client?: Client, rest?: RESTManager) { + this.id = data.id + this.type = data.type + this.channelID = data.channel_id + this.guildID = data.guild_id + this.user = data.user === undefined || client === undefined ? undefined : new User(client, data.user) + if (data.user !== undefined && client === undefined) this.userRaw = data.user + this.name = data.name + this.avatar = data.avatar + this.token = data.token + this.applicationID = data.application_id + + if (rest !== undefined) this.rest = rest + else if (client !== undefined) this.rest = client.rest + else this.rest = new RESTManager() + } + + private fromPayload(data: WebhookPayload): Webhook { + this.id = data.id + this.type = data.type + this.channelID = data.channel_id + this.guildID = data.guild_id + this.user = data.user === undefined || this.client === undefined ? undefined : new User(this.client, data.user) + if (data.user !== undefined && this.client === undefined) this.userRaw = data.user + this.name = data.name + this.avatar = data.avatar + this.token = data.token + this.applicationID = data.application_id + + return this + } + + /** Send a Message through Webhook. */ + async send(text?: string | AllWebhookMessageOptions, option?: AllWebhookMessageOptions): Promise { + if (typeof text === "object") { + option = text + text = undefined + } + + if (text === undefined && option === undefined) { + throw new Error('Either text or option is necessary.') + } + + if (option instanceof Embed) option = { + embeds: [ option ], + } + + const payload: any = { + content: text, + embeds: (option as WebhookMessageOptions)?.embed !== undefined ? [ (option as WebhookMessageOptions).embed ] : ((option as WebhookMessageOptions)?.embeds !== undefined ? (option as WebhookMessageOptions).embeds : undefined), + file: (option as WebhookMessageOptions)?.file, + tts: (option as WebhookMessageOptions)?.tts, + allowed_mentions: (option as WebhookMessageOptions)?.allowedMentions + } + + if ((option as WebhookMessageOptions).name !== undefined) { + payload.username = (option as WebhookMessageOptions)?.name + } + + if ((option as WebhookMessageOptions).avatar !== undefined) { + payload.avatar = (option as WebhookMessageOptions)?.avatar + } + + if (payload.embeds !== undefined && payload.embeds instanceof Array && payload.embeds.length > 10) throw new Error(`Cannot send more than 10 embeds through Webhook`) + + const resp = await this.rest.post(this.url + '?wait=true', payload) + + const res = new Message(this.client as Client, resp, (this as unknown) as TextChannel, (this as unknown) as User) + await res.mentions.fromPayload(resp) + return res + } + + /** + * Create a Webhook object from URL + * @param url URL of the Webhook + * @param client Client (bot) object, if any. + */ + static async fromURL(url: string | URL, client?: Client): Promise { + const rest = client !== undefined ? client.rest : new RESTManager() + + const raw = await rest.get(typeof url === 'string' ? url : url.toString()) + if (typeof raw !== 'object') throw new Error(`Failed to load Webhook from URL: ${url}`) + + const webhook = new Webhook(raw, client, rest) + return webhook + } + + /** + * Edit the Webhook name, avatar, or channel (requires authentication). + * @param options Options to edit the Webhook. + */ + async edit(options: WebhookEditOptions): Promise { + if (options.channelID !== undefined && this.rest.client === undefined) throw new Error('Authentication is required for editing Webhook Channel') + if (options.avatar !== undefined && (options.avatar.startsWith('http:') || options.avatar.startsWith('https:'))) { + options.avatar = await fetchAuto(options.avatar) + } + + const data = await this.rest.patch(this.url, options) + this.fromPayload(data) + + return this + } + + /** Delete the Webhook. */ + async delete(): Promise { + const resp = await this.rest.delete(this.url, undefined, 0, undefined, true) + if (resp.response.status !== 204) return false + else return true + } +} diff --git a/src/test/cmd.ts b/src/test/cmd.ts index 2b0df81..85efd56 100644 --- a/src/test/cmd.ts +++ b/src/test/cmd.ts @@ -1,103 +1,109 @@ -import { Command, CommandClient, Intents } from '../../mod.ts' -import { GuildChannel } from "../managers/guildChannels.ts" -import { CommandContext } from "../models/command.ts" -import { Extension } from "../models/extensions.ts" -import { Member } from "../structures/member.ts" -import { Message } from "../structures/message.ts" -import { Role } from "../structures/role.ts" -import { MessageDeletePayload } from "../types/gateway.ts" -import { TOKEN } from './config.ts' - -const client = new CommandClient({ - prefix: ["pls", "!"], - spacesAfterPrefix: true, - mentionPrefix: true -}) - -client.on('debug', console.log) - -client.on('ready', () => { - console.log(`[Login] Logged in as ${client.user?.tag}!`) -}) - -client.on('messageDelete', (msg: Message) => { - console.log(`Message Deleted: ${msg.id}, ${msg.author.tag}, ${msg.content}`) -}) - -client.on('messageDeleteUncached', (d: MessageDeletePayload) => { - console.log(`Uncached Message Deleted: ${d.id} in ${d.channel_id}`) -}) - -client.on('messageUpdate', (before: Message, after: Message) => { - console.log('Message Update') - console.log(`Before: ${before.author.tag}: ${before.content}`) - console.log(`After: ${after.author.tag}: ${after.content}`) -}) - -client.on('messageUpdateUncached', (msg: Message) => { - console.log(`Message: ${msg.author.tag}: ${msg.content}`) -}) - -client.on('guildMemberAdd', (member: Member) => { - console.log(`Member Join: ${member.user.tag}`) -}) - -client.on('guildMemberRemove', (member: Member) => { - console.log(`Member Leave: ${member.user.tag}`) -}) - -client.on('guildRoleCreate', (role: Role) => { - console.log(`Role Create: ${role.name}`) -}) - -client.on('guildRoleDelete', (role: Role) => { - console.log(`Role Delete: ${role.name}`) -}) - -client.on('guildRoleUpdate', (role: Role, after: Role) => { - console.log(`Role Update: ${role.name}, ${after.name}`) -}) - -// client.on('messageCreate', msg => console.log(`${msg.author.tag}: ${msg.content}`)) - -client.on("commandError", console.error) - -class ChannelLog extends Extension { - - onChannelCreate(ext: Extension, channel: GuildChannel): void { - console.log(`Channel Created: ${channel.name}`) - } - - load(): void { - this.listen('channelCreate', this.onChannelCreate) - - class Pong extends Command { - name = 'Pong' - - execute(ctx: CommandContext): any { - ctx.message.reply('Ping!') - } - } - - this.commands.add(Pong) - } -} - -client.extensions.load(ChannelLog) - -// eslint-disable-next-line @typescript-eslint/no-floating-promises -;(async() => { - const files = Deno.readDirSync('./src/test/cmds') - - for (const file of files) { - const module = await import(`./cmds/${file.name}`) - // eslint-disable-next-line new-cap - const cmd = new module.default() - client.commands.add(cmd) - console.log(`Loaded command ${cmd.name}!`) - } - - console.log(`Loaded ${client.commands.count} commands!`) - - client.connect(TOKEN, Intents.All) -})() \ No newline at end of file +import { Command, CommandClient, Intents, GuildChannel, CommandContext, Extension } from '../../mod.ts' +import { TOKEN } from './config.ts' + +const client = new CommandClient({ + prefix: ["pls", "!"], + spacesAfterPrefix: true, + mentionPrefix: true +}) + +client.on('debug', console.log) + +client.on('ready', () => { + console.log(`[Login] Logged in as ${client.user?.tag}!`) +}) + +client.on('messageDelete', (msg) => { + console.log(`Message Deleted: ${msg.id}, ${msg.author.tag}, ${msg.content}`) +}) + +client.on('messageUpdate', (before, after) => { + console.log('Message Update') + console.log(`Before: ${before.author.tag}: ${before.content}`) + console.log(`After: ${after.author.tag}: ${after.content}`) +}) + +client.on('messageUpdateUncached', (msg) => { + console.log(`Message: ${msg.author.tag}: ${msg.content}`) +}) + +client.on('guildMemberAdd', (member) => { + console.log(`Member Join: ${member.user.tag}`) +}) + +client.on('guildMemberRemove', (member) => { + console.log(`Member Leave: ${member.user.tag}`) +}) + +client.on('guildRoleCreate', (role) => { + console.log(`Role Create: ${role.name}`) +}) + +client.on('guildRoleDelete', (role) => { + console.log(`Role Delete: ${role.name}`) +}) + +client.on('guildRoleUpdate', (role, after) => { + console.log(`Role Update: ${role.name}, ${after.name}`) +}) + +client.on('guildIntegrationsUpdate', (guild) => { + console.log(`Guild Integrations Update: ${guild.name}`) +}) + +client.on('webhooksUpdate', (guild, channel) => { + console.log(`Webhooks Updated in #${channel.name} from ${guild.name}`) +}) + +client.on("commandError", console.error) + +class ChannelLog extends Extension { + + onChannelCreate(ext: Extension, channel: GuildChannel): void { + console.log(`Channel Created: ${channel.name}`) + } + + load(): void { + this.listen('channelCreate', this.onChannelCreate) + + class Pong extends Command { + name = 'Pong' + + execute(ctx: CommandContext): any { + ctx.message.reply('Ping!') + } + } + + this.commands.add(Pong) + } +} + +client.extensions.load(ChannelLog) + +client.on('messageDeleteBulk', (channel, messages, uncached) => { + console.log(`=== Message Delete Bulk ===\nMessages: ${messages.map(m => m.id).join(', ')}\nUncached: ${[...uncached.values()].join(', ')}`) +}) + +client.on('channelUpdate', (before, after) => { + console.log(`Channel Update: ${(before as GuildChannel).name}, ${(after as GuildChannel).name}`) +}) + +client.on('typingStart', (user, channel, at, guildData) => { + console.log(`${user.tag} started typing in ${channel.id} at ${at}${guildData !== undefined ? `\nGuild: ${guildData.guild.name}` : ''}`) +}) + +// client.on('raw', (evt: string) => console.log(`EVENT: ${evt}`)) + +const files = Deno.readDirSync('./src/test/cmds') + +for (const file of files) { + const module = await import(`./cmds/${file.name}`) + // eslint-disable-next-line new-cap + const cmd = new module.default() + client.commands.add(cmd) + console.log(`Loaded command ${cmd.name}!`) +} + +console.log(`Loaded ${client.commands.count} commands!`) + +client.connect(TOKEN, Intents.create(['GUILD_MEMBERS', 'GUILD_PRESENCES'])) \ No newline at end of file diff --git a/src/test/hook.ts b/src/test/hook.ts new file mode 100644 index 0000000..150eb28 --- /dev/null +++ b/src/test/hook.ts @@ -0,0 +1,9 @@ +import { Webhook } from '../../mod.ts' +import { WEBHOOK } from "./config.ts" + +const webhook = await Webhook.fromURL(WEBHOOK) +console.log('Fetched webhook!') + +webhook.send('Hello World', { + name: 'OwO' +}).then(() => 'Sent message!') \ No newline at end of file diff --git a/src/types/channel.ts b/src/types/channel.ts index d814a9f..f33a478 100644 --- a/src/types/channel.ts +++ b/src/types/channel.ts @@ -102,7 +102,7 @@ export interface MessageOption { tts?: boolean embed?: Embed file?: Attachment - allowedMention?: { + allowedMentions?: { parse: 'everyone' | 'users' | 'roles' roles: string[] users: string[] diff --git a/src/types/gateway.ts b/src/types/gateway.ts index 723ad22..aebf1e9 100644 --- a/src/types/gateway.ts +++ b/src/types/gateway.ts @@ -1,5 +1,7 @@ // https://discord.com/developers/docs/topics/opcodes-and-status-codes#gateway // https://discord.com/developers/docs/topics/gateway#commands-and-events-gateway-events +import { Guild } from "../structures/guild.ts" +import { Member } from "../structures/member.ts" import { EmojiPayload } from './emoji.ts' import { MemberPayload } from './guild.ts' import { @@ -320,3 +322,16 @@ export interface WebhooksUpdatePayload { guild_id: string channel_id: string } + +export interface TypingStartPayload { + channel_id: string + user_id: string + guild_id?: string + timestamp: number + member?: MemberPayload +} + +export interface TypingStartGuildData { + guild: Guild + member: Member +} \ No newline at end of file diff --git a/src/utils/intents.ts b/src/utils/intents.ts index 246f78a..ad4b051 100644 --- a/src/utils/intents.ts +++ b/src/utils/intents.ts @@ -1,36 +1,59 @@ -import { GatewayIntents } from '../types/gateway.ts' - -// eslint-disable-next-line @typescript-eslint/no-extraneous-class -export class Intents { - static All: number[] = [ - GatewayIntents.GUILD_MEMBERS, - GatewayIntents.GUILD_PRESENCES, - GatewayIntents.GUILD_MESSAGES, - GatewayIntents.DIRECT_MESSAGES, - GatewayIntents.DIRECT_MESSAGE_REACTIONS, - GatewayIntents.DIRECT_MESSAGE_TYPING, - GatewayIntents.GUILDS, - GatewayIntents.GUILD_BANS, - GatewayIntents.GUILD_EMOJIS, - GatewayIntents.GUILD_INTEGRATIONS, - GatewayIntents.GUILD_INVITES, - GatewayIntents.GUILD_MESSAGE_REACTIONS, - GatewayIntents.GUILD_MESSAGE_TYPING, - GatewayIntents.GUILD_VOICE_STATES, - GatewayIntents.GUILD_WEBHOOKS - ] - - static Presence: number[] = [ - GatewayIntents.GUILD_PRESENCES, - GatewayIntents.GUILDS - ] - - static GuildMembers: number[] = [ - GatewayIntents.GUILD_MEMBERS, - GatewayIntents.GUILDS, - GatewayIntents.GUILD_BANS, - GatewayIntents.GUILD_VOICE_STATES - ] - - static None: number[] = [] -} +import { GatewayIntents } from '../types/gateway.ts' + +export type PriviligedIntents = 'GUILD_MEMBERS' | 'GUILD_PRESENCES' + +// eslint-disable-next-line @typescript-eslint/no-extraneous-class +export class Intents { + static NonPriviliged: number[] = [ + GatewayIntents.GUILD_MESSAGES, + GatewayIntents.DIRECT_MESSAGES, + GatewayIntents.DIRECT_MESSAGE_REACTIONS, + GatewayIntents.DIRECT_MESSAGE_TYPING, + GatewayIntents.GUILDS, + GatewayIntents.GUILD_BANS, + GatewayIntents.GUILD_EMOJIS, + GatewayIntents.GUILD_INTEGRATIONS, + GatewayIntents.GUILD_INVITES, + GatewayIntents.GUILD_MESSAGE_REACTIONS, + GatewayIntents.GUILD_MESSAGE_TYPING, + GatewayIntents.GUILD_VOICE_STATES, + GatewayIntents.GUILD_WEBHOOKS + ] + + static All: number[] = [ + GatewayIntents.GUILD_MEMBERS, + GatewayIntents.GUILD_PRESENCES, + ...Intents.NonPriviliged + ] + + static Presence: number[] = [ + GatewayIntents.GUILD_PRESENCES, + ...Intents.NonPriviliged + ] + + static GuildMembers: number[] = [ + GatewayIntents.GUILD_MEMBERS, + ...Intents.NonPriviliged + ] + + static None: number[] = [ + ...Intents.NonPriviliged + ] + + static create(priviliged?: PriviligedIntents[], disable?: number[]): number[] { + let intents: number[] = [ + ...Intents.NonPriviliged + ] + + if (priviliged !== undefined && priviliged.length !== 0) { + if (priviliged.includes('GUILD_MEMBERS')) intents.push(GatewayIntents.GUILD_MEMBERS) + if (priviliged.includes('GUILD_PRESENCES')) intents.push(GatewayIntents.GUILD_PRESENCES) + } + + if (disable !== undefined) { + intents = intents.filter(intent => !disable.includes(intent)) + } + + return intents + } +} From 1db6aaac8d88e027b267182f76c9d8dce2e43895 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sat, 28 Nov 2020 10:51:49 +0530 Subject: [PATCH 3/5] fix: errors --- src/gateway/handlers/guildMemberUpdate.ts | 3 +- src/gateway/handlers/guildRoleCreate.ts | 31 ++++++++--------- src/gateway/handlers/guildRoleUpdate.ts | 41 ++++++++++++----------- 3 files changed, 39 insertions(+), 36 deletions(-) diff --git a/src/gateway/handlers/guildMemberUpdate.ts b/src/gateway/handlers/guildMemberUpdate.ts index 6d06a45..38c7f1b 100644 --- a/src/gateway/handlers/guildMemberUpdate.ts +++ b/src/gateway/handlers/guildMemberUpdate.ts @@ -2,6 +2,7 @@ import { Gateway, GatewayEventHandler } from '../index.ts' import { Guild } from '../../structures/guild.ts' import { GuildMemberUpdatePayload } from '../../types/gateway.ts' import { MemberPayload } from '../../types/guild.ts' +import { Member } from "../../structures/member.ts" export const guildMemberUpdate: GatewayEventHandler = async ( gateway: Gateway, @@ -25,7 +26,7 @@ export const guildMemberUpdate: GatewayEventHandler = async ( const newMember = await guild.members.get(d.user.id) if (member !== undefined) - gateway.client.emit('guildMemberRemove', member, newMember) + gateway.client.emit('guildMemberUpdate', member, (newMember as unknown) as Member) else { gateway.client.emit('guildMemberUpdateUncached', newMember) } diff --git a/src/gateway/handlers/guildRoleCreate.ts b/src/gateway/handlers/guildRoleCreate.ts index 90f91c1..c1470a8 100644 --- a/src/gateway/handlers/guildRoleCreate.ts +++ b/src/gateway/handlers/guildRoleCreate.ts @@ -1,16 +1,17 @@ -import { Gateway, GatewayEventHandler } from '../index.ts' -import { Guild } from '../../structures/guild.ts' -import { GuildRoleCreatePayload } from "../../types/gateway.ts" - -export const guildRoleCreate: GatewayEventHandler = async ( - gateway: Gateway, - d: GuildRoleCreatePayload -) => { - const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id) - // Weird case, shouldn't happen - if (guild === undefined) return - - await guild.roles.set(d.role.id, d.role) - const role = await guild.roles.get(d.role.id) - gateway.client.emit('guildRoleCreate', role) +import { Gateway, GatewayEventHandler } from '../index.ts' +import { Guild } from '../../structures/guild.ts' +import { GuildRoleCreatePayload } from "../../types/gateway.ts" +import { Role } from "../../structures/role.ts" + +export const guildRoleCreate: GatewayEventHandler = async ( + gateway: Gateway, + d: GuildRoleCreatePayload +) => { + const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id) + // Weird case, shouldn't happen + if (guild === undefined) return + + await guild.roles.set(d.role.id, d.role) + const role = await guild.roles.get(d.role.id) + gateway.client.emit('guildRoleCreate', (role as unknown) as Role) } \ No newline at end of file diff --git a/src/gateway/handlers/guildRoleUpdate.ts b/src/gateway/handlers/guildRoleUpdate.ts index af271f6..4ef01ed 100644 --- a/src/gateway/handlers/guildRoleUpdate.ts +++ b/src/gateway/handlers/guildRoleUpdate.ts @@ -1,21 +1,22 @@ -import { Gateway, GatewayEventHandler } from '../index.ts' -import { Guild } from '../../structures/guild.ts' -import { GuildRoleUpdatePayload } from "../../types/gateway.ts" - -export const guildRoleUpdate: GatewayEventHandler = async ( - gateway: Gateway, - d: GuildRoleUpdatePayload -) => { - const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id) - // Weird case, shouldn't happen - if (guild === undefined) return - - const role = await guild.roles.get(d.role.id) - await guild.roles.set(d.role.id, d.role) - const newRole = await guild.roles.get(d.role.id) - - // Shouldn't happen either - if(role === undefined) return gateway.client.emit('guildRoleUpdateUncached', newRole) - - gateway.client.emit('guildRoleUpdate', role, newRole) +import { Gateway, GatewayEventHandler } from '../index.ts' +import { Guild } from '../../structures/guild.ts' +import { GuildRoleUpdatePayload } from "../../types/gateway.ts" +import { Role } from "../../structures/role.ts" + +export const guildRoleUpdate: GatewayEventHandler = async ( + gateway: Gateway, + d: GuildRoleUpdatePayload +) => { + const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id) + // Weird case, shouldn't happen + if (guild === undefined) return + + const role = await guild.roles.get(d.role.id) + await guild.roles.set(d.role.id, d.role) + const newRole = await guild.roles.get(d.role.id) + + // Shouldn't happen either + if(role === undefined) return gateway.client.emit('guildRoleUpdateUncached', newRole) + + gateway.client.emit('guildRoleUpdate', role, (newRole as unknown) as Role) } \ No newline at end of file From e9490ff7779b8ed1e0348abd1915959141768b0d Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sat, 28 Nov 2020 10:54:36 +0530 Subject: [PATCH 4/5] fix: error --- src/managers/channels.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/managers/channels.ts b/src/managers/channels.ts index 7339434..4953020 100644 --- a/src/managers/channels.ts +++ b/src/managers/channels.ts @@ -43,7 +43,7 @@ export class ChannelsManager extends BaseManager { return result } - async fetch (id: string): Promise { + async fetch (id: string): Promise { return await new Promise((resolve, reject) => { this.client.rest .get(CHANNEL(id)) @@ -53,7 +53,7 @@ export class ChannelsManager extends BaseManager { if (data.guild_id !== undefined) { guild = await this.client.guilds.get(data.guild_id) } - resolve(getChannelByType(this.client, data as ChannelPayload, guild)) + resolve((getChannelByType(this.client, data as ChannelPayload, guild) as unknown) as T) }) .catch(e => reject(e)) }) From 6f60bd50402d80af138b677cf84075d3b0c5c86a Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sat, 28 Nov 2020 12:38:14 +0530 Subject: [PATCH 5/5] feat(guildMemberAdd.ts): change unknown to Member --- src/gateway/handlers/guildMemberAdd.ts | 4 ++-- src/models/voice.ts | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 src/models/voice.ts diff --git a/src/gateway/handlers/guildMemberAdd.ts b/src/gateway/handlers/guildMemberAdd.ts index 36d2bf9..9407ace 100644 --- a/src/gateway/handlers/guildMemberAdd.ts +++ b/src/gateway/handlers/guildMemberAdd.ts @@ -12,6 +12,6 @@ export const guildMemberAdd: GatewayEventHandler = async ( if (guild === undefined) return await guild.members.set(d.user.id, d) - const member = await guild.members.get(d.user.id) as unknown - gateway.client.emit('guildMemberAdd', member as Member) + const member = await guild.members.get(d.user.id) as Member + gateway.client.emit('guildMemberAdd', member) } \ No newline at end of file diff --git a/src/models/voice.ts b/src/models/voice.ts new file mode 100644 index 0000000..b4c02be --- /dev/null +++ b/src/models/voice.ts @@ -0,0 +1,10 @@ +import { Client } from './client.ts' + +export class VoiceClient { + client: Client + + constructor(client: Client) { + this.client = client + + } +} \ No newline at end of file