From 1de5692120855de7060a0b6bc54baf4d6a8e7abe Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sat, 28 Nov 2020 10:44:37 +0530 Subject: [PATCH] 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 + } +}