feat(almost-everything): a lot of things
This commit is contained in:
		
							parent
							
								
									c1bce28334
								
							
						
					
					
						commit
						1de5692120
					
				
					 22 changed files with 800 additions and 437 deletions
				
			
		
							
								
								
									
										3
									
								
								.vscode/settings.json
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.vscode/settings.json
									
										
									
									
										vendored
									
									
								
							|  | @ -5,5 +5,6 @@ | |||
|   "deepscan.enable": true, | ||||
|   "deno.import_intellisense_origins": { | ||||
|     "https://deno.land": true | ||||
|   } | ||||
|   }, | ||||
|   "editor.tabSize": 2 | ||||
| } | ||||
|  | @ -1,7 +1,5 @@ | |||
| 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 { ChannelPayload } from "../../types/channel.ts" | ||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | ||||
| 
 | ||||
| export const channelUpdate: GatewayEventHandler = async ( | ||||
|  | @ -9,20 +7,15 @@ export const channelUpdate: GatewayEventHandler = async ( | |||
|   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) { | ||||
|     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) | ||||
|     } | ||||
|   } | ||||
|     // (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) | ||||
| } | ||||
|  |  | |||
|  | @ -39,15 +39,15 @@ export const guildEmojiUpdate: GatewayEventHandler = async ( | |||
|     } | ||||
| 
 | ||||
|     for (const emoji of deleted) { | ||||
|       gateway.client.emit('guildEmojiDelete', emoji) | ||||
|       gateway.client.emit('guildEmojiDelete', guild, emoji) | ||||
|     } | ||||
| 
 | ||||
|     for (const emoji of added) { | ||||
|       gateway.client.emit('guildEmojiAdd', emoji) | ||||
|       gateway.client.emit('guildEmojiAdd', guild, emoji) | ||||
|     } | ||||
| 
 | ||||
|     for (const emoji of updated) { | ||||
|       gateway.client.emit('guildEmojiUpdate', emoji.before, emoji.after) | ||||
|       gateway.client.emit('guildEmojiUpdate', guild, emoji.before, emoji.after) | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										14
									
								
								src/gateway/handlers/guildIntegrationsUpdate.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/gateway/handlers/guildIntegrationsUpdate.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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) | ||||
| } | ||||
|  | @ -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) | ||||
| } | ||||
|  | @ -1,5 +1,5 @@ | |||
| import { GatewayEventHandler } from '../index.ts' | ||||
| import { GatewayEvents } from '../../types/gateway.ts' | ||||
| import { GatewayEvents, TypingStartGuildData } from '../../types/gateway.ts' | ||||
| import { channelCreate } from './channelCreate.ts' | ||||
| import { channelDelete } from './channelDelete.ts' | ||||
| import { channelUpdate } from './channelUpdate.ts' | ||||
|  | @ -22,6 +22,20 @@ 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 | ||||
|  | @ -39,7 +53,7 @@ export const gatewayHandlers: { | |||
|   GUILD_BAN_ADD: guildBanAdd, | ||||
|   GUILD_BAN_REMOVE: guildBanRemove, | ||||
|   GUILD_EMOJIS_UPDATE: guildEmojiUpdate, | ||||
|   GUILD_INTEGRATIONS_UPDATE: undefined, | ||||
|   GUILD_INTEGRATIONS_UPDATE: guildIntegrationsUpdate, | ||||
|   GUILD_MEMBER_ADD: guildMemberAdd, | ||||
|   GUILD_MEMBER_REMOVE: guildMemberRemove, | ||||
|   GUILD_MEMBER_UPDATE: guildMemberUpdate, | ||||
|  | @ -52,14 +66,50 @@ export const gatewayHandlers: { | |||
|   MESSAGE_CREATE: messageCreate, | ||||
|   MESSAGE_UPDATE: messageUpdate, | ||||
|   MESSAGE_DELETE: messageDelete, | ||||
|   MESSAGE_DELETE_BULK: undefined, | ||||
|   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: undefined, | ||||
|   USER_UPDATE: undefined, | ||||
|   TYPING_START: typingStart, | ||||
|   USER_UPDATE: userUpdate, | ||||
|   VOICE_SERVER_UPDATE: undefined, | ||||
|   WEBHOOKS_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<string, Message>, uncached: Set<string>) => 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 | ||||
| } | ||||
							
								
								
									
										29
									
								
								src/gateway/handlers/messageDeleteBulk.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/gateway/handlers/messageDeleteBulk.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<GuildTextChannel>(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<string, Message>() | ||||
|   const uncached = new Set<string>() | ||||
|   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) | ||||
| } | ||||
							
								
								
									
										23
									
								
								src/gateway/handlers/typingStart.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/gateway/handlers/typingStart.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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) | ||||
| } | ||||
							
								
								
									
										16
									
								
								src/gateway/handlers/userUpdate.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/gateway/handlers/userUpdate.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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) | ||||
| } | ||||
							
								
								
									
										16
									
								
								src/gateway/handlers/webhooksUpdate.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/gateway/handlers/webhooksUpdate.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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) | ||||
| } | ||||
|  | @ -20,7 +20,6 @@ export class GatewayCache { | |||
|   } | ||||
| 
 | ||||
|   async delete (key: string): Promise<boolean> { | ||||
|     console.log(`[GatewayCache] DEL ${key}`) | ||||
|     const result = await this.client.cache.delete(this.cacheName, key) | ||||
|     return result | ||||
|   } | ||||
|  |  | |||
|  | @ -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: <U extends string>( | ||||
|     event: U, listener: ClientEvents[U] | ||||
|   ) => this | ||||
| 
 | ||||
|   emit: <U extends string>( | ||||
|     event: U, ...args: Parameters<ClientEvents[U]> | ||||
|   ) => boolean | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * Discord Client. | ||||
|  */ | ||||
|  |  | |||
|  | @ -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<string, RateLimit>() | ||||
|   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' | ||||
|  | @ -290,7 +290,8 @@ export class RESTManager { | |||
|     url: string, | ||||
|     body?: unknown, | ||||
|     maxRetries = 0, | ||||
|     bucket?: string | null | ||||
|     bucket?: string | null, | ||||
|     rawResponse?: boolean, | ||||
|   ): Promise<any> { | ||||
|     return await new Promise((resolve, reject) => { | ||||
|       const onComplete = async (): Promise<undefined | any> => { | ||||
|  | @ -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<any> { | ||||
|     return await this.make('get', url, body) | ||||
|   async get(url: string, body?: unknown, maxRetries = 0, bucket?: string | null, rawResponse?: boolean): Promise<any> { | ||||
|     return await this.make('get', url, body, maxRetries, bucket, rawResponse) | ||||
|   } | ||||
| 
 | ||||
|   async post(url: string, body?: unknown): Promise<any> { | ||||
|     return await this.make('post', url, body) | ||||
|   async post(url: string, body?: unknown, maxRetries = 0, bucket?: string | null, rawResponse?: boolean): Promise<any> { | ||||
|     return await this.make('post', url, body, maxRetries, bucket, rawResponse) | ||||
|   } | ||||
| 
 | ||||
|   async delete(url: string, body?: unknown): Promise<any> { | ||||
|     return await this.make('delete', url, body) | ||||
|   async delete(url: string, body?: unknown, maxRetries = 0, bucket?: string | null, rawResponse?: boolean): Promise<any> { | ||||
|     return await this.make('delete', url, body, maxRetries, bucket, rawResponse) | ||||
|   } | ||||
| 
 | ||||
|   async patch(url: string, body?: unknown): Promise<any> { | ||||
|     return await this.make('patch', url, body) | ||||
|   async patch(url: string, body?: unknown, maxRetries = 0, bucket?: string | null, rawResponse?: boolean): Promise<any> { | ||||
|     return await this.make('patch', url, body, maxRetries, bucket, rawResponse) | ||||
|   } | ||||
| 
 | ||||
|   async put(url: string, body?: unknown): Promise<any> { | ||||
|     return await this.make('put', url, body) | ||||
|   async put(url: string, body?: unknown, maxRetries = 0, bucket?: string | null, rawResponse?: boolean): Promise<any> { | ||||
|     return await this.make('put', url, body, maxRetries, bucket, rawResponse) | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -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<Message> { | ||||
|     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 | ||||
|       } | ||||
|     ) | ||||
| 
 | ||||
|  |  | |||
|  | @ -15,6 +15,13 @@ export class User extends Base { | |||
|   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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,23 +1,158 @@ | |||
| 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 { Base } from './base.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 class Webhook extends Base { | ||||
| 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?: UserPayload | ||||
|   user?: User | ||||
|   userRaw?: UserPayload | ||||
|   name?: string | ||||
|   avatar?: string | ||||
|   token?: string | ||||
|   applicationID?: string | ||||
|   rest: RESTManager | ||||
| 
 | ||||
|   constructor (client: Client, data: WebhookPayload) { | ||||
|     super(client) | ||||
|   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<Message> { | ||||
|     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<Webhook> { | ||||
|     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<Webhook> { | ||||
|     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<boolean> { | ||||
|     const resp = await this.rest.delete(this.url, undefined, 0, undefined, true) | ||||
|     if (resp.response.status !== 204) return false | ||||
|     else return true | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,11 +1,4 @@ | |||
| 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 { Command, CommandClient, Intents, GuildChannel, CommandContext, Extension } from '../../mod.ts' | ||||
| import { TOKEN } from './config.ts' | ||||
| 
 | ||||
| const client = new CommandClient({ | ||||
|  | @ -20,45 +13,47 @@ client.on('ready', () => { | |||
|   console.log(`[Login] Logged in as ${client.user?.tag}!`) | ||||
| }) | ||||
| 
 | ||||
| client.on('messageDelete', (msg: Message) => { | ||||
| client.on('messageDelete', (msg) => { | ||||
|   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) => { | ||||
| 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: Message) => { | ||||
| client.on('messageUpdateUncached', (msg) => { | ||||
|   console.log(`Message: ${msg.author.tag}: ${msg.content}`) | ||||
| }) | ||||
| 
 | ||||
| client.on('guildMemberAdd', (member: Member) => { | ||||
| client.on('guildMemberAdd', (member) => { | ||||
|   console.log(`Member Join: ${member.user.tag}`) | ||||
| }) | ||||
| 
 | ||||
| client.on('guildMemberRemove', (member: Member) => { | ||||
| client.on('guildMemberRemove', (member) => { | ||||
|   console.log(`Member Leave: ${member.user.tag}`) | ||||
| }) | ||||
| 
 | ||||
| client.on('guildRoleCreate', (role: Role) => { | ||||
| client.on('guildRoleCreate', (role) => { | ||||
|   console.log(`Role Create: ${role.name}`) | ||||
| }) | ||||
| 
 | ||||
| client.on('guildRoleDelete', (role: Role) => { | ||||
| client.on('guildRoleDelete', (role) => { | ||||
|   console.log(`Role Delete: ${role.name}`) | ||||
| }) | ||||
| 
 | ||||
| client.on('guildRoleUpdate', (role: Role, after: Role) => { | ||||
| client.on('guildRoleUpdate', (role, after) => { | ||||
|   console.log(`Role Update: ${role.name}, ${after.name}`) | ||||
| }) | ||||
| 
 | ||||
| // client.on('messageCreate', msg => console.log(`${msg.author.tag}: ${msg.content}`))
 | ||||
| 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) | ||||
| 
 | ||||
|  | @ -85,8 +80,20 @@ class ChannelLog extends Extension { | |||
| 
 | ||||
| client.extensions.load(ChannelLog) | ||||
| 
 | ||||
| // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||
| ;(async() => { | ||||
| 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) { | ||||
|  | @ -99,5 +106,4 @@ client.extensions.load(ChannelLog) | |||
| 
 | ||||
| console.log(`Loaded ${client.commands.count} commands!`) | ||||
| 
 | ||||
|   client.connect(TOKEN, Intents.All) | ||||
| })() | ||||
| client.connect(TOKEN, Intents.create(['GUILD_MEMBERS', 'GUILD_PRESENCES'])) | ||||
							
								
								
									
										9
									
								
								src/test/hook.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/test/hook.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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!') | ||||
|  | @ -102,7 +102,7 @@ export interface MessageOption { | |||
|   tts?: boolean | ||||
|   embed?: Embed | ||||
|   file?: Attachment | ||||
|   allowedMention?: { | ||||
|   allowedMentions?: { | ||||
|     parse: 'everyone' | 'users' | 'roles' | ||||
|     roles: string[] | ||||
|     users: string[] | ||||
|  |  | |||
|  | @ -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 | ||||
| } | ||||
|  | @ -1,10 +1,10 @@ | |||
| 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 All: number[] = [ | ||||
|     GatewayIntents.GUILD_MEMBERS, | ||||
|     GatewayIntents.GUILD_PRESENCES, | ||||
|   static NonPriviliged: number[] = [ | ||||
|     GatewayIntents.GUILD_MESSAGES, | ||||
|     GatewayIntents.DIRECT_MESSAGES, | ||||
|     GatewayIntents.DIRECT_MESSAGE_REACTIONS, | ||||
|  | @ -20,17 +20,40 @@ export class Intents { | |||
|     GatewayIntents.GUILD_WEBHOOKS | ||||
|   ] | ||||
| 
 | ||||
|   static All: number[] = [ | ||||
|     GatewayIntents.GUILD_MEMBERS, | ||||
|     GatewayIntents.GUILD_PRESENCES, | ||||
|     ...Intents.NonPriviliged | ||||
|   ] | ||||
| 
 | ||||
|   static Presence: number[] = [ | ||||
|     GatewayIntents.GUILD_PRESENCES, | ||||
|     GatewayIntents.GUILDS | ||||
|     ...Intents.NonPriviliged | ||||
|   ] | ||||
| 
 | ||||
|   static GuildMembers: number[] = [ | ||||
|     GatewayIntents.GUILD_MEMBERS, | ||||
|     GatewayIntents.GUILDS, | ||||
|     GatewayIntents.GUILD_BANS, | ||||
|     GatewayIntents.GUILD_VOICE_STATES | ||||
|     ...Intents.NonPriviliged | ||||
|   ] | ||||
| 
 | ||||
|   static None: number[] = [] | ||||
|   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 | ||||
|   } | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue