Merge pull request #36 from DjDeveloperr/main
implement: new Gateway Events, additions to Command Client, message caching
This commit is contained in:
		
						commit
						c80f9dc1a5
					
				
					 37 changed files with 741 additions and 204 deletions
				
			
		|  | @ -1,12 +1,14 @@ | ||||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
| import getChannelByType from '../../utils/getChannelByType.ts' | import getChannelByType from '../../utils/getChannelByType.ts' | ||||||
| import { ChannelPayload } from '../../types/channel.ts' | import { ChannelPayload } from '../../types/channel.ts' | ||||||
|  | import { Guild } from "../../structures/guild.ts" | ||||||
| 
 | 
 | ||||||
| export const channelCreate: GatewayEventHandler = async ( | export const channelCreate: GatewayEventHandler = async ( | ||||||
|   gateway: Gateway, |   gateway: Gateway, | ||||||
|   d: ChannelPayload |   d: ChannelPayload | ||||||
| ) => { | ) => { | ||||||
|   const channel = getChannelByType(gateway.client, d) |   const guild: undefined | Guild = (d as any).guild_id !== undefined ? await gateway.client.guilds.get((d as any).guild_id) : undefined | ||||||
|  |   const channel = getChannelByType(gateway.client, d, guild) | ||||||
|   if (channel !== undefined) { |   if (channel !== undefined) { | ||||||
|     await gateway.client.channels.set(d.id, d) |     await gateway.client.channels.set(d.id, d) | ||||||
|     gateway.client.emit('channelCreate', channel) |     gateway.client.emit('channelCreate', channel) | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ export const guildBanAdd: GatewayEventHandler = async ( | ||||||
|   const user: User = await gateway.client.users.get(d.user.id) ?? new User(gateway.client, d.user) |   const user: User = await gateway.client.users.get(d.user.id) ?? new User(gateway.client, d.user) | ||||||
| 
 | 
 | ||||||
|   if (guild !== undefined) { |   if (guild !== undefined) { | ||||||
|     await guild.members.delete(user.id) |     // We don't have to delete member, already done with guildMemberRemove event
 | ||||||
|     gateway.client.emit('guildBanAdd', guild, user) |     gateway.client.emit('guildBanAdd', guild, user) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,14 +1,53 @@ | ||||||
| import cache from '../../models/cache.ts' | import { Emoji } from "../../structures/emoji.ts" | ||||||
| import { Guild } from '../../structures/guild.ts' | import { Guild } from '../../structures/guild.ts' | ||||||
|  | import { EmojiPayload } from "../../types/emoji.ts" | ||||||
| import { GuildEmojiUpdatePayload } from '../../types/gateway.ts' | import { GuildEmojiUpdatePayload } from '../../types/gateway.ts' | ||||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
| 
 | 
 | ||||||
| export const guildEmojiUpdate: GatewayEventHandler = ( | export const guildEmojiUpdate: GatewayEventHandler = async ( | ||||||
|   gateway: Gateway, |   gateway: Gateway, | ||||||
|   d: GuildEmojiUpdatePayload |   d: GuildEmojiUpdatePayload | ||||||
| ) => { | ) => { | ||||||
|   const guild: Guild = cache.get('guild', d.guild_id) |   const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id) | ||||||
|   if (guild !== undefined) { |   if (guild !== undefined) { | ||||||
|     // const emojis = guild.emojis
 |     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) | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										15
									
								
								src/gateway/handlers/guildMemberAdd.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/gateway/handlers/guildMemberAdd.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | ||||||
|  | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
|  | import { Guild } from '../../structures/guild.ts' | ||||||
|  | 
 | ||||||
|  | export const guildMemberAdd: GatewayEventHandler = async ( | ||||||
|  |   gateway: Gateway, | ||||||
|  |   d: any | ||||||
|  | ) => { | ||||||
|  |   const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id) | ||||||
|  |   // Weird case, shouldn't happen
 | ||||||
|  |   if (guild === undefined) return | ||||||
|  | 
 | ||||||
|  |   await guild.members.set(d.id, d) | ||||||
|  |   const member = await guild.members.get(d.id) | ||||||
|  |   gateway.client.emit('guildMemberAdd', member) | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								src/gateway/handlers/guildMemberRemove.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/gateway/handlers/guildMemberRemove.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | ||||||
|  | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
|  | import { Guild } from '../../structures/guild.ts' | ||||||
|  | import { User } from "../../structures/user.ts" | ||||||
|  | 
 | ||||||
|  | export const guildMemberRemove: GatewayEventHandler = async ( | ||||||
|  |   gateway: Gateway, | ||||||
|  |   d: any | ||||||
|  | ) => { | ||||||
|  |   const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id) | ||||||
|  |   // Weird case, shouldn't happen
 | ||||||
|  |   if (guild === undefined) return | ||||||
|  | 
 | ||||||
|  |   const member = await guild.members.get(d.id) | ||||||
|  |   await guild.members.delete(d.id) | ||||||
|  | 
 | ||||||
|  |   if (member !== undefined) gateway.client.emit('guildMemberRemove', member) | ||||||
|  |   else { | ||||||
|  |     const user = new User(gateway.client, d.user) | ||||||
|  |     gateway.client.emit('guildMemberRemoveUncached', user) | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								src/gateway/handlers/guildMemberUpdate.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/gateway/handlers/guildMemberUpdate.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | ||||||
|  | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
|  | import { Guild } from '../../structures/guild.ts' | ||||||
|  | 
 | ||||||
|  | export const guildMemberUpdate: GatewayEventHandler = async ( | ||||||
|  |   gateway: Gateway, | ||||||
|  |   d: any | ||||||
|  | ) => { | ||||||
|  |   const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id) | ||||||
|  |   // Weird case, shouldn't happen
 | ||||||
|  |   if (guild === undefined) return | ||||||
|  | 
 | ||||||
|  |   const member = await guild.members.get(d.id) | ||||||
|  |   await guild.members.set(d.id, d) | ||||||
|  |   const newMember = await guild.members.get(d.id) | ||||||
|  | 
 | ||||||
|  |   if (member !== undefined) gateway.client.emit('guildMemberRemove', member, newMember) | ||||||
|  |   else { | ||||||
|  |       gateway.client.emit('guildMemberUpdateUncached', newMember) | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								src/gateway/handlers/guildRoleCreate.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/gateway/handlers/guildRoleCreate.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | ||||||
|  | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
|  | import { Guild } from '../../structures/guild.ts' | ||||||
|  | import { GuildRoleCreatePayload } from "../../types/gateway.ts" | ||||||
|  | 
 | ||||||
|  | export const guildRoleCreate: GatewayEventHandler = async ( | ||||||
|  |   gateway: Gateway, | ||||||
|  |   d: GuildRoleCreatePayload | ||||||
|  | ) => { | ||||||
|  |   const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id) | ||||||
|  |   // Weird case, shouldn't happen
 | ||||||
|  |   if (guild === undefined) return | ||||||
|  | 
 | ||||||
|  |   await guild.roles.set(d.role.id, d.role) | ||||||
|  |   const role = await guild.roles.get(d.role.id) | ||||||
|  |   gateway.client.emit('guildRoleCreate', role) | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								src/gateway/handlers/guildRoleDelete.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/gateway/handlers/guildRoleDelete.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | ||||||
|  | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
|  | import { Guild } from '../../structures/guild.ts' | ||||||
|  | import { GuildRoleDeletePayload } from "../../types/gateway.ts" | ||||||
|  | 
 | ||||||
|  | export const guildRoleDelete: GatewayEventHandler = async ( | ||||||
|  |   gateway: Gateway, | ||||||
|  |   d: GuildRoleDeletePayload | ||||||
|  | ) => { | ||||||
|  |   const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id) | ||||||
|  |   // Weird case, shouldn't happen
 | ||||||
|  |   if (guild === undefined) return | ||||||
|  | 
 | ||||||
|  |   const role = await guild.roles.get(d.role_id) | ||||||
|  |   // Shouldn't happen either
 | ||||||
|  |   if(role === undefined) return | ||||||
|  | 
 | ||||||
|  |   gateway.client.emit('guildRoleDelete', role) | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								src/gateway/handlers/guildRoleUpdate.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/gateway/handlers/guildRoleUpdate.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | ||||||
|  | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
|  | import { Guild } from '../../structures/guild.ts' | ||||||
|  | import { GuildRoleUpdatePayload } from "../../types/gateway.ts" | ||||||
|  | 
 | ||||||
|  | export const guildRoleUpdate: GatewayEventHandler = async ( | ||||||
|  |   gateway: Gateway, | ||||||
|  |   d: GuildRoleUpdatePayload | ||||||
|  | ) => { | ||||||
|  |   const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id) | ||||||
|  |   // Weird case, shouldn't happen
 | ||||||
|  |   if (guild === undefined) return | ||||||
|  | 
 | ||||||
|  |   const role = await guild.roles.get(d.role.id) | ||||||
|  |   await guild.roles.set(d.role.id, d.role) | ||||||
|  |   const newRole = await guild.roles.get(d.role.id) | ||||||
|  | 
 | ||||||
|  |   // Shouldn't happen either
 | ||||||
|  |   if(role === undefined) return gateway.client.emit('guildRoleUpdateUncached', newRole) | ||||||
|  | 
 | ||||||
|  |   gateway.client.emit('guildRoleUpdate', role, newRole) | ||||||
|  | } | ||||||
|  | @ -13,6 +13,15 @@ import { guildBanRemove } from './guildBanRemove.ts' | ||||||
| import { messageCreate } from './messageCreate.ts' | import { messageCreate } from './messageCreate.ts' | ||||||
| import { resume } from './resume.ts' | import { resume } from './resume.ts' | ||||||
| import { reconnect } from './reconnect.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: { | export const gatewayHandlers: { | ||||||
|   [eventCode in GatewayEvents]: GatewayEventHandler | undefined |   [eventCode in GatewayEvents]: GatewayEventHandler | undefined | ||||||
|  | @ -29,20 +38,20 @@ export const gatewayHandlers: { | ||||||
|   GUILD_UPDATE: guildUpdate, |   GUILD_UPDATE: guildUpdate, | ||||||
|   GUILD_BAN_ADD: guildBanAdd, |   GUILD_BAN_ADD: guildBanAdd, | ||||||
|   GUILD_BAN_REMOVE: guildBanRemove, |   GUILD_BAN_REMOVE: guildBanRemove, | ||||||
|   GUILD_EMOJIS_UPDATE: undefined, |   GUILD_EMOJIS_UPDATE: guildEmojiUpdate, | ||||||
|   GUILD_INTEGRATIONS_UPDATE: undefined, |   GUILD_INTEGRATIONS_UPDATE: undefined, | ||||||
|   GUILD_MEMBER_ADD: undefined, |   GUILD_MEMBER_ADD: guildMemberAdd, | ||||||
|   GUILD_MEMBER_REMOVE: undefined, |   GUILD_MEMBER_REMOVE: guildMemberRemove, | ||||||
|   GUILD_MEMBER_UPDATE: undefined, |   GUILD_MEMBER_UPDATE: guildMemberUpdate, | ||||||
|   GUILD_MEMBERS_CHUNK: undefined, |   GUILD_MEMBERS_CHUNK: undefined, | ||||||
|   GUILD_ROLE_CREATE: undefined, |   GUILD_ROLE_CREATE: guildRoleCreate, | ||||||
|   GUILD_ROLE_UPDATE: undefined, |   GUILD_ROLE_UPDATE: guildRoleUpdate, | ||||||
|   GUILD_ROLE_DELETE: undefined, |   GUILD_ROLE_DELETE: guildRoleDelete, | ||||||
|   INVITE_CREATE: undefined, |   INVITE_CREATE: undefined, | ||||||
|   INVITE_DELETE: undefined, |   INVITE_DELETE: undefined, | ||||||
|   MESSAGE_CREATE: messageCreate, |   MESSAGE_CREATE: messageCreate, | ||||||
|   MESSAGE_UPDATE: undefined, |   MESSAGE_UPDATE: messageUpdate, | ||||||
|   MESSAGE_DELETE: undefined, |   MESSAGE_DELETE: messageDelete, | ||||||
|   MESSAGE_DELETE_BULK: undefined, |   MESSAGE_DELETE_BULK: undefined, | ||||||
|   MESSAGE_REACTION_ADD: undefined, |   MESSAGE_REACTION_ADD: undefined, | ||||||
|   MESSAGE_REACTION_REMOVE: undefined, |   MESSAGE_REACTION_REMOVE: undefined, | ||||||
|  |  | ||||||
|  | @ -13,6 +13,7 @@ export const messageCreate: GatewayEventHandler = async ( | ||||||
|   if (channel === undefined) |   if (channel === undefined) | ||||||
|     // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
 |     // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
 | ||||||
|     channel = (await gateway.client.channels.fetch(d.channel_id)) as TextChannel |     channel = (await gateway.client.channels.fetch(d.channel_id)) as TextChannel | ||||||
|  |   await channel.messages.set(d.id, d) | ||||||
|   const user = new User(gateway.client, d.author) |   const user = new User(gateway.client, d.author) | ||||||
|   await gateway.client.users.set(d.author.id, d.author) |   await gateway.client.users.set(d.author.id, d.author) | ||||||
|   let guild |   let guild | ||||||
|  |  | ||||||
							
								
								
									
										19
									
								
								src/gateway/handlers/messageDelete.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/gateway/handlers/messageDelete.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | ||||||
|  | import { TextChannel } from '../../structures/textChannel.ts' | ||||||
|  | import { MessageDeletePayload } from "../../types/gateway.ts" | ||||||
|  | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
|  | 
 | ||||||
|  | export const messageDelete: GatewayEventHandler = async ( | ||||||
|  |   gateway: Gateway, | ||||||
|  |   d: MessageDeletePayload | ||||||
|  | ) => { | ||||||
|  |   let channel = await gateway.client.channels.get<TextChannel>(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 TextChannel | ||||||
|  | 
 | ||||||
|  |   const message = await channel.messages.get(d.id) | ||||||
|  |   if (message === undefined) return gateway.client.emit('messageDeleteUncached', d) | ||||||
|  |   await channel.messages.delete(d.id) | ||||||
|  |   gateway.client.emit('messageDelete', message) | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								src/gateway/handlers/messageUpdate.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/gateway/handlers/messageUpdate.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | ||||||
|  | import { Message } from "../../structures/message.ts" | ||||||
|  | import { TextChannel } from '../../structures/textChannel.ts' | ||||||
|  | import { User } from "../../structures/user.ts" | ||||||
|  | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
|  | 
 | ||||||
|  | export const messageUpdate: GatewayEventHandler = async ( | ||||||
|  |   gateway: Gateway, | ||||||
|  |   d: any | ||||||
|  | ) => { | ||||||
|  |   let channel = await gateway.client.channels.get<TextChannel>(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 TextChannel | ||||||
|  | 
 | ||||||
|  |   const message = await channel.messages.get(d.id) | ||||||
|  |   const author = message?.author !== undefined ? message.author : new User(gateway.client, d) | ||||||
|  |   const newMsg = new Message(gateway.client, d, channel, author) | ||||||
|  |   if (message === undefined) { | ||||||
|  |     await channel.messages.set(d.id, d) | ||||||
|  |     return gateway.client.emit('messageUpdateUncached', newMsg) | ||||||
|  |   } | ||||||
|  |   await channel.messages.set(d.id, d) | ||||||
|  |   gateway.client.emit('messageUpdate', message, newMsg) | ||||||
|  | } | ||||||
|  | @ -3,6 +3,7 @@ import { GuildPayload } from '../../types/guild.ts' | ||||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
| 
 | 
 | ||||||
| export const ready: GatewayEventHandler = async (gateway: Gateway, d: any) => { | export const ready: GatewayEventHandler = async (gateway: Gateway, d: any) => { | ||||||
|  |   await gateway.client.guilds.flush() | ||||||
|   gateway.client.user = new User(gateway.client, d.user) |   gateway.client.user = new User(gateway.client, d.user) | ||||||
|   gateway.sessionID = d.session_id |   gateway.sessionID = d.session_id | ||||||
|   gateway.debug(`Received READY. Session: ${gateway.sessionID}`) |   gateway.debug(`Received READY. Session: ${gateway.sessionID}`) | ||||||
|  |  | ||||||
|  | @ -115,6 +115,8 @@ class Gateway { | ||||||
|           await this.cache.set('seq', s) |           await this.cache.set('seq', s) | ||||||
|         } |         } | ||||||
|         if (t !== null && t !== undefined) { |         if (t !== null && t !== undefined) { | ||||||
|  |           this.client.emit('raw', t, d) | ||||||
|  |            | ||||||
|           const handler = gatewayHandlers[t] |           const handler = gatewayHandlers[t] | ||||||
| 
 | 
 | ||||||
|           if (handler !== undefined) { |           if (handler !== undefined) { | ||||||
|  |  | ||||||
|  | @ -20,7 +20,7 @@ export class MemberRolesManager extends BaseChildManager< | ||||||
|   async get (id: string): Promise<Role | undefined> { |   async get (id: string): Promise<Role | undefined> { | ||||||
|     const res = await this.parent.get(id) |     const res = await this.parent.get(id) | ||||||
|     const mem = await (this.parent as any).guild.members._get(this.member.id) as MemberPayload |     const mem = await (this.parent as any).guild.members._get(this.member.id) as MemberPayload | ||||||
|     if (res !== undefined && mem.roles.includes(res.id) === true) return res |     if (res !== undefined && (mem.roles.includes(res.id) === true || res.id === this.member.guild.id)) return res | ||||||
|     else return undefined |     else return undefined | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -28,7 +28,7 @@ export class MemberRolesManager extends BaseChildManager< | ||||||
|     const arr = (await this.parent.array()) as Role[] |     const arr = (await this.parent.array()) as Role[] | ||||||
|     const mem = await (this.parent as any).guild.members._get(this.member.id) as MemberPayload |     const mem = await (this.parent as any).guild.members._get(this.member.id) as MemberPayload | ||||||
|     return arr.filter( |     return arr.filter( | ||||||
|       (c: any) => mem.roles.includes(c.id) |       (c: any) => mem.roles.includes(c.id) as boolean || c.id === this.member.guild.id | ||||||
|     ) as any |     ) as any | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ import { Member } from '../structures/member.ts' | ||||||
| import { GUILD_MEMBER } from '../types/endpoint.ts' | import { GUILD_MEMBER } from '../types/endpoint.ts' | ||||||
| import { MemberPayload } from '../types/guild.ts' | import { MemberPayload } from '../types/guild.ts' | ||||||
| import { BaseManager } from './base.ts' | import { BaseManager } from './base.ts' | ||||||
|  | import { Permissions } from "../utils/permissions.ts" | ||||||
| 
 | 
 | ||||||
| export class MembersManager extends BaseManager<MemberPayload, Member> { | export class MembersManager extends BaseManager<MemberPayload, Member> { | ||||||
|   guild: Guild |   guild: Guild | ||||||
|  | @ -18,7 +19,13 @@ export class MembersManager extends BaseManager<MemberPayload, Member> { | ||||||
|     const raw = await this._get(key) |     const raw = await this._get(key) | ||||||
|     if (raw === undefined) return |     if (raw === undefined) return | ||||||
|     const user = new User(this.client, raw.user) |     const user = new User(this.client, raw.user) | ||||||
|     const res = new this.DataType(this.client, raw, user, this.guild) |     const roles = await this.guild.roles.array() | ||||||
|  |     let permissions = new Permissions(Permissions.DEFAULT) | ||||||
|  |     if (roles !== undefined) { | ||||||
|  |       const mRoles = roles.filter(r => raw.roles.includes(r.id) as boolean || r.id === this.guild.id) | ||||||
|  |       permissions = new Permissions(mRoles.map(r => r.permissions)) | ||||||
|  |     } | ||||||
|  |     const res = new this.DataType(this.client, raw, user, this.guild, permissions) | ||||||
|     return res |     return res | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -27,7 +34,13 @@ export class MembersManager extends BaseManager<MemberPayload, Member> { | ||||||
|       this.client.rest.get(GUILD_MEMBER(this.guild.id, id)).then(async data => { |       this.client.rest.get(GUILD_MEMBER(this.guild.id, id)).then(async data => { | ||||||
|         await this.set(id, data as MemberPayload) |         await this.set(id, data as MemberPayload) | ||||||
|         const user: User = new User(this.client, data.user) |         const user: User = new User(this.client, data.user) | ||||||
|         const res = new Member(this.client, data as MemberPayload, user, this.guild) |         const roles = await this.guild.roles.array() | ||||||
|  |         let permissions = new Permissions(Permissions.DEFAULT) | ||||||
|  |         if (roles !== undefined) { | ||||||
|  |           const mRoles = roles.filter(r => data.roles.includes(r.id) as boolean || r.id === this.guild.id) | ||||||
|  |           permissions = new Permissions(mRoles.map(r => r.permissions)) | ||||||
|  |         } | ||||||
|  |         const res = new Member(this.client, data as MemberPayload, user, this.guild, permissions) | ||||||
|         resolve(res) |         resolve(res) | ||||||
|       }).catch(e => reject(e)) |       }).catch(e => reject(e)) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|  | @ -7,8 +7,11 @@ import { CHANNEL_MESSAGE } from '../types/endpoint.ts' | ||||||
| import { BaseManager } from './base.ts' | import { BaseManager } from './base.ts' | ||||||
| 
 | 
 | ||||||
| export class MessagesManager extends BaseManager<MessagePayload, Message> { | export class MessagesManager extends BaseManager<MessagePayload, Message> { | ||||||
|   constructor (client: Client) { |   channel: TextChannel | ||||||
|  | 
 | ||||||
|  |   constructor (client: Client, channel: TextChannel) { | ||||||
|     super(client, 'messages', Message) |     super(client, 'messages', Message) | ||||||
|  |     this.channel = channel | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async get (key: string): Promise<Message | undefined> { |   async get (key: string): Promise<Message | undefined> { | ||||||
|  | @ -26,18 +29,22 @@ export class MessagesManager extends BaseManager<MessagePayload, Message> { | ||||||
|     return res |     return res | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async fetch (channelID: string, id: string): Promise<Message> { |   async set (key: string, value: MessagePayload): Promise<any> { | ||||||
|  |     return this.client.cache.set(this.cacheName, key, value, this.client.messageCacheLifetime) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async fetch (id: string): Promise<Message> { | ||||||
|     return await new Promise((resolve, reject) => { |     return await new Promise((resolve, reject) => { | ||||||
|       this.client.rest |       this.client.rest | ||||||
|         .get(CHANNEL_MESSAGE(channelID, id)) |         .get(CHANNEL_MESSAGE(this.channel.id, id)) | ||||||
|         .then(async data => { |         .then(async data => { | ||||||
|           this.set(id, data as MessagePayload) |           await this.set(id, data as MessagePayload) | ||||||
| 
 | 
 | ||||||
|           let channel: any = await this.client.channels.get<TextChannel>( |           let channel: any = await this.client.channels.get<TextChannel>( | ||||||
|             channelID |             this.channel.id | ||||||
|           ) |           ) | ||||||
|           if (channel === undefined) |           if (channel === undefined) | ||||||
|             channel = await this.client.channels.fetch(channelID) |             channel = await this.client.channels.fetch(this.channel.id) | ||||||
| 
 | 
 | ||||||
|           const author = new User(this.client, (data as MessagePayload).author) |           const author = new User(this.client, (data as MessagePayload).author) | ||||||
|           await this.client.users.set( |           await this.client.users.set( | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ import { | ||||||
| 
 | 
 | ||||||
| export interface ICacheAdapter { | export interface ICacheAdapter { | ||||||
|   get: (cacheName: string, key: string) => Promise<any> | any |   get: (cacheName: string, key: string) => Promise<any> | any | ||||||
|   set: (cacheName: string, key: string, value: any) => Promise<any> | any |   set: (cacheName: string, key: string, value: any, expire?: number) => Promise<any> | any | ||||||
|   delete: (cacheName: string, key: string) => Promise<boolean> | boolean |   delete: (cacheName: string, key: string) => Promise<boolean> | boolean | ||||||
|   array: (cacheName: string) => undefined | any[] | Promise<any[] | undefined> |   array: (cacheName: string) => undefined | any[] | Promise<any[] | undefined> | ||||||
|   deleteCache: (cacheName: string) => any |   deleteCache: (cacheName: string) => any | ||||||
|  | @ -24,13 +24,16 @@ export class DefaultCacheAdapter implements ICacheAdapter { | ||||||
|     return cache.get(key) |     return cache.get(key) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async set (cacheName: string, key: string, value: any): Promise<any> { |   async set(cacheName: string, key: string, value: any, expire?: number): Promise<any> { | ||||||
|     let cache = this.data[cacheName] |     let cache = this.data[cacheName] | ||||||
|     if (cache === undefined) { |     if (cache === undefined) { | ||||||
|       this.data[cacheName] = new Collection() |       this.data[cacheName] = new Collection() | ||||||
|       cache = this.data[cacheName] |       cache = this.data[cacheName] | ||||||
|     } |     } | ||||||
|     return cache.set(key, value) |     cache.set(key, value) | ||||||
|  |     if (expire !== undefined) setTimeout(() => { | ||||||
|  |       cache.delete(key) | ||||||
|  |     }, expire) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async delete(cacheName: string, key: string): Promise<boolean> { |   async delete(cacheName: string, key: string): Promise<boolean> { | ||||||
|  | @ -55,6 +58,8 @@ export class RedisCacheAdapter implements ICacheAdapter { | ||||||
|   _redis: Promise<Redis> |   _redis: Promise<Redis> | ||||||
|   redis?: Redis |   redis?: Redis | ||||||
|   ready: boolean = false |   ready: boolean = false | ||||||
|  |   readonly _expireIntervalTimer: number = 5000 | ||||||
|  |   private _expireInterval?: number | ||||||
| 
 | 
 | ||||||
|   constructor(options: RedisConnectOptions) { |   constructor(options: RedisConnectOptions) { | ||||||
|     this._redis = connect(options) |     this._redis = connect(options) | ||||||
|  | @ -62,6 +67,7 @@ export class RedisCacheAdapter implements ICacheAdapter { | ||||||
|       redis => { |       redis => { | ||||||
|         this.redis = redis |         this.redis = redis | ||||||
|         this.ready = true |         this.ready = true | ||||||
|  |         this._startExpireInterval() | ||||||
|       }, |       }, | ||||||
|       () => { |       () => { | ||||||
|         // TODO: Make error for this
 |         // TODO: Make error for this
 | ||||||
|  | @ -69,6 +75,26 @@ export class RedisCacheAdapter implements ICacheAdapter { | ||||||
|     ) |     ) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   private _startExpireInterval(): void { | ||||||
|  |     this._expireInterval = setInterval(() => { | ||||||
|  |       this.redis?.scan(0, { pattern: '*:expires' }).then(([_, names]) => { | ||||||
|  |         for (const name of names) { | ||||||
|  |           this.redis?.hvals(name).then(vals => { | ||||||
|  |             for (const val of vals) { | ||||||
|  |               const expireVal: { | ||||||
|  |                 name: string, | ||||||
|  |                 key: string, | ||||||
|  |                 at: number | ||||||
|  |               } = JSON.parse(val) | ||||||
|  |               const expired = new Date().getTime() > expireVal.at | ||||||
|  |               if (expired) this.redis?.hdel(expireVal.name, expireVal.key) | ||||||
|  |             } | ||||||
|  |           }) | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  |     }, this._expireIntervalTimer) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   async _checkReady(): Promise<void> { |   async _checkReady(): Promise<void> { | ||||||
|     if (!this.ready) await this._redis |     if (!this.ready) await this._redis | ||||||
|   } |   } | ||||||
|  | @ -87,7 +113,8 @@ export class RedisCacheAdapter implements ICacheAdapter { | ||||||
|   async set( |   async set( | ||||||
|     cacheName: string, |     cacheName: string, | ||||||
|     key: string, |     key: string, | ||||||
|     value: any |     value: any, | ||||||
|  |     expire?: number | ||||||
|   ): Promise<number | undefined> { |   ): Promise<number | undefined> { | ||||||
|     await this._checkReady() |     await this._checkReady() | ||||||
|     const result = await this.redis?.hset( |     const result = await this.redis?.hset( | ||||||
|  | @ -95,6 +122,13 @@ export class RedisCacheAdapter implements ICacheAdapter { | ||||||
|       key, |       key, | ||||||
|       typeof value === 'object' ? JSON.stringify(value) : value |       typeof value === 'object' ? JSON.stringify(value) : value | ||||||
|     ) |     ) | ||||||
|  |     if (expire !== undefined) { | ||||||
|  |       await this.redis?.hset( | ||||||
|  |         `${cacheName}:expires`, | ||||||
|  |         key, | ||||||
|  |         JSON.stringify({ name: cacheName, key, at: new Date().getTime() + expire }) | ||||||
|  |       ) | ||||||
|  |     } | ||||||
|     return result |     return result | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -7,7 +7,6 @@ import { DefaultCacheAdapter, ICacheAdapter } from './cacheAdapter.ts' | ||||||
| import { UserManager } from '../managers/users.ts' | import { UserManager } from '../managers/users.ts' | ||||||
| import { GuildManager } from '../managers/guilds.ts' | import { GuildManager } from '../managers/guilds.ts' | ||||||
| import { ChannelsManager } from '../managers/channels.ts' | import { ChannelsManager } from '../managers/channels.ts' | ||||||
| import { MessagesManager } from '../managers/messages.ts' |  | ||||||
| import { | import { | ||||||
|   ActivityGame, |   ActivityGame, | ||||||
|   ClientActivity, |   ClientActivity, | ||||||
|  | @ -31,6 +30,8 @@ export interface ClientOptions { | ||||||
|   bot?: boolean |   bot?: boolean | ||||||
|   /** Force all requests to Canary API */ |   /** Force all requests to Canary API */ | ||||||
|   canary?: boolean |   canary?: boolean | ||||||
|  |   /** Time till which Messages are to be cached, in MS. Default is 3600000 */ | ||||||
|  |   messageCacheLifetime?: number | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | @ -53,11 +54,12 @@ export class Client extends EventEmitter { | ||||||
|   intents?: GatewayIntents[] |   intents?: GatewayIntents[] | ||||||
|   /** Whether to force new session or not */ |   /** Whether to force new session or not */ | ||||||
|   forceNewSession?: boolean |   forceNewSession?: boolean | ||||||
|  |   /** Time till messages to stay cached, in MS. */ | ||||||
|  |   messageCacheLifetime: number = 3600000 | ||||||
| 
 | 
 | ||||||
|   users: UserManager = new UserManager(this) |   users: UserManager = new UserManager(this) | ||||||
|   guilds: GuildManager = new GuildManager(this) |   guilds: GuildManager = new GuildManager(this) | ||||||
|   channels: ChannelsManager = new ChannelsManager(this) |   channels: ChannelsManager = new ChannelsManager(this) | ||||||
|   messages: MessagesManager = new MessagesManager(this) |  | ||||||
|   emojis: EmojisManager = new EmojisManager(this) |   emojis: EmojisManager = new EmojisManager(this) | ||||||
|    |    | ||||||
|   /** Whether this client will login as bot user or not */ |   /** Whether this client will login as bot user or not */ | ||||||
|  | @ -80,6 +82,7 @@ export class Client extends EventEmitter { | ||||||
|           : new ClientPresence(options.presence) |           : new ClientPresence(options.presence) | ||||||
|     if (options.bot === false) this.bot = false |     if (options.bot === false) this.bot = false | ||||||
|     if (options.canary === true) this.canary = true |     if (options.canary === true) this.canary = true | ||||||
|  |     if (options.messageCacheLifetime !== undefined) this.messageCacheLifetime = options.messageCacheLifetime | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** Set Cache Adapter */ |   /** Set Cache Adapter */ | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ import { TextChannel } from '../structures/textChannel.ts' | ||||||
| import { User } from '../structures/user.ts' | import { User } from '../structures/user.ts' | ||||||
| import { Collection } from '../utils/collection.ts' | import { Collection } from '../utils/collection.ts' | ||||||
| import { CommandClient } from './commandClient.ts' | import { CommandClient } from './commandClient.ts' | ||||||
|  | import { Extension } from "./extensions.ts" | ||||||
| 
 | 
 | ||||||
| export interface CommandContext { | export interface CommandContext { | ||||||
|   /** The Client object */ |   /** The Client object */ | ||||||
|  | @ -35,8 +36,8 @@ export class Command { | ||||||
|   description?: string |   description?: string | ||||||
|   /** Array of Aliases of Command, or only string */ |   /** Array of Aliases of Command, or only string */ | ||||||
|   aliases?: string | string[] |   aliases?: string | string[] | ||||||
|   /** Category of the Command */ |   /** Extension (Parent) of the Command */ | ||||||
|   category?: string |   extension?: Extension | ||||||
|   /** Usage of Command, only Argument Names */ |   /** Usage of Command, only Argument Names */ | ||||||
|   usage?: string | string[] |   usage?: string | string[] | ||||||
|   /** Usage Example of Command, only Arguments (without Prefix and Name) */ |   /** Usage Example of Command, only Arguments (without Prefix and Name) */ | ||||||
|  | @ -45,6 +46,16 @@ export class Command { | ||||||
|   args?: number | boolean |   args?: number | boolean | ||||||
|   /** Permission(s) required for using Command */ |   /** Permission(s) required for using Command */ | ||||||
|   permissions?: string | string[] |   permissions?: string | string[] | ||||||
|  |   /** Permission(s) bot will need in order to execute Command */ | ||||||
|  |   botPermissions?: string | string[] | ||||||
|  |   /** Role(s) user will require in order to use Command. List or one of ID or name */ | ||||||
|  |   roles?: string | string[] | ||||||
|  |   /** Whitelisted Guilds. Only these Guild(s) can execute Command. (List or one of IDs) */ | ||||||
|  |   whitelistedGuilds?: string | string[] | ||||||
|  |   /** Whitelisted Channels. Command can be executed only in these channels. (List or one of IDs) */ | ||||||
|  |   whitelistedChannels?: string | string[] | ||||||
|  |   /** Whitelisted Users. Command can be executed only by these Users (List or one of IDs) */ | ||||||
|  |   whitelistedUsers?: string | string[] | ||||||
|   /** Whether the Command can only be used in Guild (if allowed in DMs) */ |   /** Whether the Command can only be used in Guild (if allowed in DMs) */ | ||||||
|   guildOnly?: boolean |   guildOnly?: boolean | ||||||
|   /** Whether the Command can only be used in Bot's DMs (if allowed) */ |   /** Whether the Command can only be used in Bot's DMs (if allowed) */ | ||||||
|  | @ -58,13 +69,16 @@ export class Command { | ||||||
|   execute(ctx: CommandContext): any { } |   execute(ctx: CommandContext): any { } | ||||||
|   /** Method executed after executing command, passes on CommandContext and the value returned by execute too. (optional) */ |   /** Method executed after executing command, passes on CommandContext and the value returned by execute too. (optional) */ | ||||||
|   afterExecute(ctx: CommandContext, executeResult: any): any { } |   afterExecute(ctx: CommandContext, executeResult: any): any { } | ||||||
|  | 
 | ||||||
|  |   toString(): string { | ||||||
|  |     return `Command: ${this.name}${this.extension !== undefined ? ` [${this.extension.name}]` : ''}` | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export class CommandsManager { | export class CommandsManager { | ||||||
|   client: CommandClient |   client: CommandClient | ||||||
|   list: Collection<string, Command> = new Collection() |   list: Collection<string, Command> = new Collection() | ||||||
|   disabled: Set<string> = new Set() |   disabled: Set<string> = new Set() | ||||||
|   disabledCategories: Set<string> = new Set() |  | ||||||
| 
 | 
 | ||||||
|   constructor(client: CommandClient) { |   constructor(client: CommandClient) { | ||||||
|     this.client = client |     this.client = client | ||||||
|  | @ -96,7 +110,6 @@ export class CommandsManager { | ||||||
|     const cmd = this.find(name) |     const cmd = this.find(name) | ||||||
|     if (cmd === undefined) return |     if (cmd === undefined) return | ||||||
|     if (this.isDisabled(cmd) && bypassDisable !== true) return |     if (this.isDisabled(cmd) && bypassDisable !== true) return | ||||||
|     if (cmd.category !== undefined && this.isCategoryDisabled(cmd.category) && bypassDisable !== true) return |  | ||||||
|     return cmd |     return cmd | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -121,7 +134,7 @@ export class CommandsManager { | ||||||
|   add(cmd: Command | typeof Command): boolean { |   add(cmd: Command | typeof Command): boolean { | ||||||
|     // eslint-disable-next-line new-cap
 |     // eslint-disable-next-line new-cap
 | ||||||
|     if (!(cmd instanceof Command)) cmd = new cmd() |     if (!(cmd instanceof Command)) cmd = new cmd() | ||||||
|     if (this.exists(cmd)) return false |     if (this.exists(cmd)) throw new Error(`Failed to add Command '${cmd.toString()}' with name/alias already exists.`) | ||||||
|     this.list.set(cmd.name, cmd) |     this.list.set(cmd.name, cmd) | ||||||
|     return true |     return true | ||||||
|   } |   } | ||||||
|  | @ -133,11 +146,6 @@ export class CommandsManager { | ||||||
|     else return this.list.delete(find.name) |     else return this.list.delete(find.name) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** Get all Commands of given Category */ |  | ||||||
|   category(name: string): Collection<string, Command> { |  | ||||||
|     return this.list.filter(c => c.category === name) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   /** Check whether a Command is disabled or not */ |   /** Check whether a Command is disabled or not */ | ||||||
|   isDisabled(name: string | Command): boolean { |   isDisabled(name: string | Command): boolean { | ||||||
|     const cmd = typeof name === "string" ? this.find(name) : name |     const cmd = typeof name === "string" ? this.find(name) : name | ||||||
|  | @ -155,18 +163,6 @@ export class CommandsManager { | ||||||
|     this.disabled.add(cmd.name) |     this.disabled.add(cmd.name) | ||||||
|     return true |     return true | ||||||
|   } |   } | ||||||
| 
 |  | ||||||
|   /** Check whether a Category is disabled */ |  | ||||||
|   isCategoryDisabled(name: string): boolean { |  | ||||||
|     return this.disabledCategories.has(name) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   /** Disable a Category of Commands */ |  | ||||||
|   disableCategory(name: string): boolean { |  | ||||||
|     if (this.isCategoryDisabled(name)) return false |  | ||||||
|     this.disabledCategories.add(name) |  | ||||||
|     return true |  | ||||||
|   } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface ParsedCommand { | export interface ParsedCommand { | ||||||
|  |  | ||||||
|  | @ -1,7 +1,8 @@ | ||||||
| import { Embed, Message } from '../../mod.ts' | import { Message } from "../../mod.ts" | ||||||
| import { awaitSync } from "../utils/mixedPromise.ts" | import { awaitSync } from "../utils/mixedPromise.ts" | ||||||
| import { Client, ClientOptions } from './client.ts' | import { Client, ClientOptions } from './client.ts' | ||||||
| import { CommandContext, CommandsManager, parseCommand } from './command.ts' | import { CommandContext, CommandsManager, parseCommand } from './command.ts' | ||||||
|  | import { ExtensionsManager } from "./extensions.ts" | ||||||
| 
 | 
 | ||||||
| type PrefixReturnType = string | string[] | Promise<string | string[]> | type PrefixReturnType = string | string[] | Promise<string | string[]> | ||||||
| 
 | 
 | ||||||
|  | @ -21,33 +22,6 @@ export interface CommandClientOptions extends ClientOptions { | ||||||
|   caseSensitive?: boolean |   caseSensitive?: boolean | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type CommandText = string | Embed |  | ||||||
| 
 |  | ||||||
| export interface CommandTexts { |  | ||||||
|   GUILD_ONLY?: CommandText |  | ||||||
|   OWNER_ONLY?: CommandText |  | ||||||
|   DMS_ONLY?: CommandText |  | ||||||
|   ERROR?: CommandText |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export const DefaultCommandTexts: CommandTexts = { |  | ||||||
|   GUILD_ONLY: 'This command can only be used in a Server!', |  | ||||||
|   OWNER_ONLY: 'This command can only be used by Bot Owners!', |  | ||||||
|   DMS_ONLY: "This command can only be used in Bot's DMs!", |  | ||||||
|   ERROR: 'An error occured while executing command!' |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| interface Replaces { |  | ||||||
|   [name: string]: string |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export const massReplace = (text: string, replaces: Replaces): string => { |  | ||||||
|   Object.entries(replaces).forEach(replace => { |  | ||||||
|     text = text.replace(new RegExp(`{{${replace[0]}}}`, 'g'), replace[1]) |  | ||||||
|   }) |  | ||||||
|   return text |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export class CommandClient extends Client implements CommandClientOptions { | export class CommandClient extends Client implements CommandClientOptions { | ||||||
|   prefix: string | string[] |   prefix: string | string[] | ||||||
|   mentionPrefix: boolean |   mentionPrefix: boolean | ||||||
|  | @ -62,8 +36,8 @@ export class CommandClient extends Client implements CommandClientOptions { | ||||||
|   allowBots: boolean |   allowBots: boolean | ||||||
|   allowDMs: boolean |   allowDMs: boolean | ||||||
|   caseSensitive: boolean |   caseSensitive: boolean | ||||||
|  |   extensions: ExtensionsManager = new ExtensionsManager(this) | ||||||
|   commands: CommandsManager = new CommandsManager(this) |   commands: CommandsManager = new CommandsManager(this) | ||||||
|   texts: CommandTexts = DefaultCommandTexts |  | ||||||
| 
 | 
 | ||||||
|   constructor(options: CommandClientOptions) { |   constructor(options: CommandClientOptions) { | ||||||
|     super(options) |     super(options) | ||||||
|  | @ -159,31 +133,9 @@ export class CommandClient extends Client implements CommandClientOptions { | ||||||
| 
 | 
 | ||||||
|     if (command === undefined) return |     if (command === undefined) return | ||||||
| 
 | 
 | ||||||
|     const baseReplaces: Replaces = { |     if (command.whitelistedGuilds !== undefined && msg.guild !== undefined && command.whitelistedGuilds.includes(msg.guild.id) === false) return; | ||||||
|       command: command.name, |     if (command.whitelistedChannels !== undefined && command.whitelistedChannels.includes(msg.channel.id) === false) return; | ||||||
|       nameUsed: parsed.name, |     if (command.whitelistedUsers !== undefined && command.whitelistedUsers.includes(msg.author.id) === false) return; | ||||||
|       prefix, |  | ||||||
|       username: msg.author.username, |  | ||||||
|       tag: msg.author.tag, |  | ||||||
|       mention: msg.author.mention, |  | ||||||
|       id: msg.author.id |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (command.guildOnly === true && msg.guild === undefined) { |  | ||||||
|       if (this.texts.GUILD_ONLY !== undefined) |  | ||||||
|         return this.sendProcessedText(msg, this.texts.GUILD_ONLY, baseReplaces) |  | ||||||
|       return |  | ||||||
|     } |  | ||||||
|     if (command.dmOnly === true && msg.guild !== undefined) { |  | ||||||
|       if (this.texts.DMS_ONLY !== undefined) |  | ||||||
|         return this.sendProcessedText(msg, this.texts.DMS_ONLY, baseReplaces) |  | ||||||
|       return |  | ||||||
|     } |  | ||||||
|     if (command.ownerOnly === true && !this.owners.includes(msg.author.id)) { |  | ||||||
|       if (this.texts.OWNER_ONLY !== undefined) |  | ||||||
|         return this.sendProcessedText(msg, this.texts.OWNER_ONLY, baseReplaces) |  | ||||||
|       return |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const ctx: CommandContext = { |     const ctx: CommandContext = { | ||||||
|       client: this, |       client: this, | ||||||
|  | @ -198,8 +150,41 @@ export class CommandClient extends Client implements CommandClientOptions { | ||||||
|       guild: msg.guild |       guild: msg.guild | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if (command.ownerOnly === true && !this.owners.includes(msg.author.id)) return this.emit('commandOwnerOnly', ctx, command) | ||||||
|  |     if (command.guildOnly === true && msg.guild === undefined) return this.emit('commandGuildOnly', ctx, command) | ||||||
|  |     if (command.dmOnly === true && msg.guild !== undefined) return this.emit('commandDmOnly', ctx, command) | ||||||
|  |      | ||||||
|  |     if (command.botPermissions !== undefined && msg.guild !== undefined) { | ||||||
|  |       // TODO: Check Overwrites too
 | ||||||
|  |       const me = await msg.guild.me() | ||||||
|  |       const missing: string[] = [] | ||||||
|  |        | ||||||
|  |       for (const perm of command.botPermissions) { | ||||||
|  |         if (me.permissions.has(perm) === false) missing.push(perm) | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (missing.length !== 0) return this.emit('commandBotMissingPermissions', ctx, command, missing) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (command.permissions !== undefined && msg.guild !== undefined) { | ||||||
|  |       const missing: string[] = [] | ||||||
|  |       let perms: string[] = [] | ||||||
|  |       if (typeof command.permissions === 'string') perms = [command.permissions] | ||||||
|  |       else perms = command.permissions | ||||||
|  |       for (const perm of perms) { | ||||||
|  |         const has = msg.member?.permissions.has(perm) | ||||||
|  |         if (has !== true) missing.push(perm) | ||||||
|  |       }  | ||||||
|  |       if (missing.length !== 0) return this.emit('commandMissingPermissions', command, missing, ctx) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (command.args !== undefined) { | ||||||
|  |       if (typeof command.args === 'boolean' && parsed.args.length === 0) return this.emit('commandMissingArgs', ctx, command) | ||||||
|  |       else if (typeof command.args === 'number' && parsed.args.length < command.args) this.emit('commandMissingArgs', ctx, command)  | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     try { |     try { | ||||||
|       this.emit('commandUsed', { context: ctx }) |       this.emit('commandUsed', ctx, command) | ||||||
| 
 | 
 | ||||||
|       const beforeExecute = await awaitSync(command.beforeExecute(ctx)) |       const beforeExecute = await awaitSync(command.beforeExecute(ctx)) | ||||||
|       if (beforeExecute === false) return |       if (beforeExecute === false) return | ||||||
|  | @ -207,30 +192,7 @@ export class CommandClient extends Client implements CommandClientOptions { | ||||||
|       const result = await awaitSync(command.execute(ctx)) |       const result = await awaitSync(command.execute(ctx)) | ||||||
|       command.afterExecute(ctx, result) |       command.afterExecute(ctx, result) | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|       if (this.texts.ERROR !== undefined) |       this.emit('commandError', command, ctx, e) | ||||||
|         this.sendProcessedText( |  | ||||||
|           msg, |  | ||||||
|           this.texts.ERROR, |  | ||||||
|           Object.assign(baseReplaces, { error: e.message }) |  | ||||||
|         ) |  | ||||||
|       this.emit('commandError', { command, parsed, error: e }) |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   sendProcessedText(msg: Message, text: CommandText, replaces: Replaces): any { |  | ||||||
|     if (typeof text === 'string') { |  | ||||||
|       text = massReplace(text, replaces) |  | ||||||
|       return msg.channel.send(text) |  | ||||||
|     } else { |  | ||||||
|       if (text.description !== undefined) |  | ||||||
|         text.description = massReplace(text.description, replaces) |  | ||||||
|       if (text.title !== undefined) |  | ||||||
|         text.description = massReplace(text.title, replaces) |  | ||||||
|       if (text.author?.name !== undefined) |  | ||||||
|         text.description = massReplace(text.author.name, replaces) |  | ||||||
|       if (text.footer?.text !== undefined) |  | ||||||
|         text.description = massReplace(text.footer.text, replaces) |  | ||||||
|       return msg.channel.send(text) |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										112
									
								
								src/models/extensions.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/models/extensions.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,112 @@ | ||||||
|  | import { Collection } from "../utils/collection.ts"; | ||||||
|  | import { Command } from "./command.ts"; | ||||||
|  | import { CommandClient } from "./commandClient.ts"; | ||||||
|  | 
 | ||||||
|  | export type ExtensionEventCallback = (ext: Extension, ...args: any[]) => any | ||||||
|  | 
 | ||||||
|  | export class ExtensionCommands { | ||||||
|  |   extension: Extension | ||||||
|  | 
 | ||||||
|  |   constructor(ext: Extension) { | ||||||
|  |     this.extension = ext | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get list(): Collection<string, Command> { | ||||||
|  |     return this.extension.client.commands.list.filter(c => c.extension?.name === this.extension.name) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get(cmd: string): Command | undefined { | ||||||
|  |     const find = this.extension.client.commands.find(cmd) | ||||||
|  |     // linter sucks
 | ||||||
|  |     if (find === undefined) return undefined | ||||||
|  |     else if (find.extension === undefined) return undefined | ||||||
|  |     else if (find.extension.name !== this.extension.name) return undefined | ||||||
|  |     else return find | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   add(Cmd: Command | typeof Command): boolean { | ||||||
|  |     const cmd = Cmd instanceof Command ? Cmd : new Cmd() | ||||||
|  |     cmd.extension = this.extension | ||||||
|  |     return this.extension.client.commands.add(cmd) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   delete(cmd: Command | string): boolean { | ||||||
|  |     const find = this.extension.client.commands.find(typeof cmd === 'string' ? cmd : cmd.name) | ||||||
|  |     if (find === undefined) return false | ||||||
|  |     if (find.extension !== undefined && find.extension.name !== this.extension.name) return false | ||||||
|  |     else return this.extension.client.commands.delete(find) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   deleteAll(): void { | ||||||
|  |     for (const [cmd] of this.list) { | ||||||
|  |       this.delete(cmd) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class Extension { | ||||||
|  |   client: CommandClient | ||||||
|  |   name: string = '' | ||||||
|  |   description?: string | ||||||
|  |   commands: ExtensionCommands = new ExtensionCommands(this) | ||||||
|  |   events: { [name: string]: (...args: any[]) => {} } = {} | ||||||
|  | 
 | ||||||
|  |   constructor(client: CommandClient) { | ||||||
|  |     this.client = client | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   listen(event: string, cb: ExtensionEventCallback): boolean { | ||||||
|  |     if (this.events[event] !== undefined) return false | ||||||
|  |     else { | ||||||
|  |       const fn = (...args: any[]): any => { | ||||||
|  |         // eslint-disable-next-line standard/no-callback-literal
 | ||||||
|  |         cb(this, ...args) | ||||||
|  |       }; | ||||||
|  |       this.client.on(event, fn) | ||||||
|  |       this.events[event] = fn | ||||||
|  |       return true | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   load(): any {} | ||||||
|  |   unload(): any {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class ExtensionsManager { | ||||||
|  |   client: CommandClient | ||||||
|  |   list: Collection<string, Extension> = new Collection() | ||||||
|  | 
 | ||||||
|  |   constructor(client: CommandClient) { | ||||||
|  |     this.client = client | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get(ext: string): Extension | undefined { | ||||||
|  |     return this.list.get(ext) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   exists(ext: string): boolean { | ||||||
|  |     return this.get(ext) !== undefined | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   load(ext: Extension | typeof Extension): void { | ||||||
|  |     // eslint-disable-next-line new-cap
 | ||||||
|  |     if (!(ext instanceof Extension)) ext = new ext(this.client) | ||||||
|  |     if (this.exists(ext.name)) throw new Error(`Extension with name '${ext.name}' already exists`) | ||||||
|  |     this.list.set(ext.name, ext) | ||||||
|  |     ext.load() | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   unload(ext: Extension | string): boolean { | ||||||
|  |     const name = typeof ext === 'string' ? ext : ext.name | ||||||
|  |     const extension = this.get(name) | ||||||
|  |     if (extension === undefined) return false | ||||||
|  |     extension.commands.deleteAll() | ||||||
|  |     for (const [k, v] of Object.entries(extension.events)) { | ||||||
|  |       this.client.removeListener(k, v) | ||||||
|  |       // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
 | ||||||
|  |       delete extension.events[k] | ||||||
|  |     } | ||||||
|  |     extension.unload() | ||||||
|  |     return this.list.delete(name) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -8,6 +8,7 @@ import { GuildChannelsManager } from '../managers/guildChannels.ts' | ||||||
| import { MembersManager } from '../managers/members.ts' | import { MembersManager } from '../managers/members.ts' | ||||||
| import { Role } from './role.ts' | import { Role } from './role.ts' | ||||||
| import { GuildEmojisManager } from '../managers/guildEmojis.ts' | import { GuildEmojisManager } from '../managers/guildEmojis.ts' | ||||||
|  | import { Member } from "./member.ts" | ||||||
| 
 | 
 | ||||||
| export class Guild extends Base { | export class Guild extends Base { | ||||||
|   id: string |   id: string | ||||||
|  | @ -214,4 +215,10 @@ export class Guild extends Base { | ||||||
|   async getEveryoneRole (): Promise<Role> { |   async getEveryoneRole (): Promise<Role> { | ||||||
|     return (await this.roles.array().then(arr => arr?.sort((b, a) => a.position - b.position)[0]) as any) as Role |     return (await this.roles.array().then(arr => arr?.sort((b, a) => a.position - b.position)[0]) as any) as Role | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   async me(): Promise<Member> { | ||||||
|  |     const get = await this.members.get(this.client.user?.id as string) | ||||||
|  |     if (get === undefined) throw new Error('Guild#me is not cached') | ||||||
|  |     return get | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| import { MemberRolesManager } from "../managers/memberRoles.ts" | import { MemberRolesManager } from "../managers/memberRoles.ts" | ||||||
| import { Client } from '../models/client.ts' | import { Client } from '../models/client.ts' | ||||||
| import { MemberPayload } from '../types/guild.ts' | import { MemberPayload } from '../types/guild.ts' | ||||||
|  | import { Permissions } from "../utils/permissions.ts" | ||||||
| import { Base } from './base.ts' | import { Base } from './base.ts' | ||||||
| import { Guild } from "./guild.ts" | import { Guild } from "./guild.ts" | ||||||
| import { User } from './user.ts' | import { User } from './user.ts' | ||||||
|  | @ -15,13 +16,12 @@ export class Member extends Base { | ||||||
|   deaf: boolean |   deaf: boolean | ||||||
|   mute: boolean |   mute: boolean | ||||||
|   guild: Guild |   guild: Guild | ||||||
|  |   permissions: Permissions | ||||||
| 
 | 
 | ||||||
|   constructor (client: Client, data: MemberPayload, user: User, guild: Guild) { |   constructor (client: Client, data: MemberPayload, user: User, guild: Guild, perms?: Permissions) { | ||||||
|     super(client) |     super(client) | ||||||
|     this.id = data.user.id |     this.id = data.user.id | ||||||
|     this.user = user |     this.user = user | ||||||
|     // this.user =
 |  | ||||||
|     //   cache.get('user', data.user.id) ?? new User(this.client, data.user)
 |  | ||||||
|     this.nick = data.nick |     this.nick = data.nick | ||||||
|     this.guild = guild |     this.guild = guild | ||||||
|     this.roles = new MemberRolesManager(this.client, this.guild.roles, this) |     this.roles = new MemberRolesManager(this.client, this.guild.roles, this) | ||||||
|  | @ -29,8 +29,8 @@ export class Member extends Base { | ||||||
|     this.premiumSince = data.premium_since |     this.premiumSince = data.premium_since | ||||||
|     this.deaf = data.deaf |     this.deaf = data.deaf | ||||||
|     this.mute = data.mute |     this.mute = data.mute | ||||||
|     // TODO: Cache in Gateway Event Code
 |     if (perms !== undefined) this.permissions = perms | ||||||
|     // cache.set('member', this.id, this)
 |     else this.permissions = new Permissions(Permissions.DEFAULT) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   protected readFromData (data: MemberPayload): void { |   protected readFromData (data: MemberPayload): void { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| import { Client } from '../models/client.ts' | import { Client } from '../models/client.ts' | ||||||
| import { Base } from './base.ts' | import { Base } from './base.ts' | ||||||
| import { RolePayload } from '../types/role.ts' | import { RolePayload } from '../types/role.ts' | ||||||
|  | import { Permissions } from "../utils/permissions.ts" | ||||||
| 
 | 
 | ||||||
| export class Role extends Base { | export class Role extends Base { | ||||||
|   id: string |   id: string | ||||||
|  | @ -8,7 +9,7 @@ export class Role extends Base { | ||||||
|   color: number |   color: number | ||||||
|   hoist: boolean |   hoist: boolean | ||||||
|   position: number |   position: number | ||||||
|   permissions: string |   permissions: Permissions | ||||||
|   managed: boolean |   managed: boolean | ||||||
|   mentionable: boolean |   mentionable: boolean | ||||||
| 
 | 
 | ||||||
|  | @ -25,11 +26,9 @@ export class Role extends Base { | ||||||
|     this.color = data.color |     this.color = data.color | ||||||
|     this.hoist = data.hoist |     this.hoist = data.hoist | ||||||
|     this.position = data.position |     this.position = data.position | ||||||
|     this.permissions = data.permissions |     this.permissions = new Permissions(data.permissions) | ||||||
|     this.managed = data.managed |     this.managed = data.managed | ||||||
|     this.mentionable = data.mentionable |     this.mentionable = data.mentionable | ||||||
|     // TODO: Cache in Gateway Event Code
 |  | ||||||
|     // cache.set('role', this.id, this)
 |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   protected readFromData (data: RolePayload): void { |   protected readFromData (data: RolePayload): void { | ||||||
|  | @ -38,7 +37,7 @@ export class Role extends Base { | ||||||
|     this.color = data.color ?? this.color |     this.color = data.color ?? this.color | ||||||
|     this.hoist = data.hoist ?? this.hoist |     this.hoist = data.hoist ?? this.hoist | ||||||
|     this.position = data.position ?? this.position |     this.position = data.position ?? this.position | ||||||
|     this.permissions = data.permissions ?? this.permissions |     this.permissions = new Permissions(data.permissions) ?? this.permissions | ||||||
|     this.managed = data.managed ?? this.managed |     this.managed = data.managed ?? this.managed | ||||||
|     this.mentionable = data.mentionable ?? this.mentionable |     this.mentionable = data.mentionable ?? this.mentionable | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | import { MessagesManager } from "../../mod.ts" | ||||||
| import { Client } from '../models/client.ts' | import { Client } from '../models/client.ts' | ||||||
| import { GuildTextChannelPayload, MessageOption, Overwrite, TextChannelPayload } from '../types/channel.ts' | import { GuildTextChannelPayload, MessageOption, Overwrite, TextChannelPayload } from '../types/channel.ts' | ||||||
| import { CHANNEL_MESSAGE, CHANNEL_MESSAGES } from '../types/endpoint.ts' | import { CHANNEL_MESSAGE, CHANNEL_MESSAGES } from '../types/endpoint.ts' | ||||||
|  | @ -11,9 +12,11 @@ type AllMessageOptions = MessageOption | Embed | ||||||
| export class TextChannel extends Channel { | export class TextChannel extends Channel { | ||||||
|   lastMessageID?: string |   lastMessageID?: string | ||||||
|   lastPinTimestamp?: string |   lastPinTimestamp?: string | ||||||
|  |   messages: MessagesManager | ||||||
| 
 | 
 | ||||||
|   constructor (client: Client, data: TextChannelPayload) { |   constructor (client: Client, data: TextChannelPayload) { | ||||||
|     super(client, data) |     super(client, data) | ||||||
|  |     this.messages = new MessagesManager(this.client, this) | ||||||
|     this.lastMessageID = data.last_message_id |     this.lastMessageID = data.last_message_id | ||||||
|     this.lastPinTimestamp = data.last_pin_timestamp |     this.lastPinTimestamp = data.last_pin_timestamp | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| import { Client } from '../models/client.ts' | import { Client } from '../models/client.ts' | ||||||
| import { UserPayload } from '../types/user.ts' | import { UserPayload } from '../types/user.ts' | ||||||
|  | import { UserFlagsManager } from "../utils/userFlags.ts" | ||||||
| import { Base } from './base.ts' | import { Base } from './base.ts' | ||||||
| 
 | 
 | ||||||
| export class User extends Base { | export class User extends Base { | ||||||
|  | @ -13,9 +14,9 @@ export class User extends Base { | ||||||
|   locale?: string |   locale?: string | ||||||
|   verified?: boolean |   verified?: boolean | ||||||
|   email?: string |   email?: string | ||||||
|   flags?: number |   flags?: UserFlagsManager | ||||||
|   premiumType?: 0 | 1 | 2 |   premiumType?: 0 | 1 | 2 | ||||||
|   publicFlags?: number |   publicFlags?: UserFlagsManager | ||||||
| 
 | 
 | ||||||
|   get tag (): string { |   get tag (): string { | ||||||
|     return `${this.username}#${this.discriminator}` |     return `${this.username}#${this.discriminator}` | ||||||
|  | @ -41,11 +42,9 @@ export class User extends Base { | ||||||
|     this.locale = data.locale |     this.locale = data.locale | ||||||
|     this.verified = data.verified |     this.verified = data.verified | ||||||
|     this.email = data.email |     this.email = data.email | ||||||
|     this.flags = data.flags |     this.flags = new UserFlagsManager(data.flags) | ||||||
|     this.premiumType = data.premium_type |     this.premiumType = data.premium_type | ||||||
|     this.publicFlags = data.public_flags |     this.publicFlags = new UserFlagsManager(data.public_flags) | ||||||
|     // TODO: Cache in Gateway Event Code
 |  | ||||||
|     // cache.set('user', this.id, this)
 |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   protected readFromData (data: UserPayload): void { |   protected readFromData (data: UserPayload): void { | ||||||
|  | @ -59,9 +58,9 @@ export class User extends Base { | ||||||
|     this.locale = data.locale ?? this.locale |     this.locale = data.locale ?? this.locale | ||||||
|     this.verified = data.verified ?? this.verified |     this.verified = data.verified ?? this.verified | ||||||
|     this.email = data.email ?? this.email |     this.email = data.email ?? this.email | ||||||
|     this.flags = data.flags ?? this.flags |     this.flags = new UserFlagsManager(data.flags) ?? this.flags | ||||||
|     this.premiumType = data.premium_type ?? this.premiumType |     this.premiumType = data.premium_type ?? this.premiumType | ||||||
|     this.publicFlags = data.public_flags ?? this.publicFlags |     this.publicFlags = new UserFlagsManager(data.public_flags) ?? this.publicFlags | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   toString (): string { |   toString (): string { | ||||||
|  |  | ||||||
|  | @ -1,4 +1,11 @@ | ||||||
| import { CommandClient, Intents } from '../../mod.ts' | 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' | import { TOKEN } from './config.ts' | ||||||
| 
 | 
 | ||||||
| const client = new CommandClient({ | const client = new CommandClient({ | ||||||
|  | @ -13,10 +20,71 @@ client.on('ready', () => { | ||||||
|   console.log(`[Login] Logged in as ${client.user?.tag}!`) |   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('messageCreate', msg => console.log(`${msg.author.tag}: ${msg.content}`))
 | ||||||
| 
 | 
 | ||||||
| client.on("commandError", console.error) | 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
 | // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||||
| ;(async() => { | ;(async() => { | ||||||
|   const files = Deno.readDirSync('./src/test/cmds') |   const files = Deno.readDirSync('./src/test/cmds') | ||||||
|  |  | ||||||
|  | @ -13,6 +13,7 @@ export default class UserinfoCommand extends Command { | ||||||
|         .setAuthor({ name: member.user.tag }) |         .setAuthor({ name: member.user.tag }) | ||||||
|         .addField("ID", member.id) |         .addField("ID", member.id) | ||||||
|         .addField("Roles", roles.map(r => r.name).join(", ")) |         .addField("Roles", roles.map(r => r.name).join(", ")) | ||||||
|  |         .addField('Permissions', JSON.stringify(member.permissions.has('ADMINISTRATOR'))) | ||||||
|         .setColor(0xff00ff) |         .setColor(0xff00ff) | ||||||
|         ctx.channel.send(embed) |         ctx.channel.send(embed) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -186,7 +186,7 @@ export interface GuildBanRemovePayload { | ||||||
| 
 | 
 | ||||||
| export interface GuildEmojiUpdatePayload { | export interface GuildEmojiUpdatePayload { | ||||||
|   guild_id: string |   guild_id: string | ||||||
|   emojis: [] |   emojis: EmojiPayload[] | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface GuildIntegrationsUpdatePayload { | export interface GuildIntegrationsUpdatePayload { | ||||||
|  |  | ||||||
|  | @ -1,43 +1,35 @@ | ||||||
| // https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags
 | // https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags
 | ||||||
| 
 | 
 | ||||||
| enum PermissionFlags { | export const PermissionFlags: { [key: string]: number } = { | ||||||
|   CREATE_INSTANT_INVITE = 0x00000001, |   CREATE_INSTANT_INVITE: 1 << 0, | ||||||
| 
 |   KICK_MEMBERS: 1 << 1, | ||||||
|   KICK_MEMBERS = 0x00000002, |   BAN_MEMBERS: 1 << 2, | ||||||
|   BAN_MEMBERS = 0x00000004, |   ADMINISTRATOR: 1 << 3, | ||||||
|   ADMINISTRATOR = 0x00000008, |   MANAGE_CHANNELS: 1 << 4, | ||||||
|   MANAGE_CHANNELS = 0x00000010, |   MANAGE_GUILD: 1 << 5, | ||||||
|   MANAGE_GUILD = 0x00000020, |   ADD_REACTIONS: 1 << 6, | ||||||
| 
 |   VIEW_AUDIT_LOG: 1 << 7, | ||||||
|   ADD_REACTIONS = 0x00000040, |   PRIORITY_SPEAKER: 1 << 8, | ||||||
|   VIEW_AUDIT_LOG = 0x00000080, |   STREAM: 1 << 9, | ||||||
|   PRIORITY_SPEAKER = 0x00000100, |   VIEW_CHANNEL: 1 << 10, | ||||||
|   STREAM = 0x00000200, |   SEND_MESSAGES: 1 << 11, | ||||||
| 
 |   SEND_TTS_MESSAGES: 1 << 12, | ||||||
|   VIEW_CHANNEL = 0x00000400, |   MANAGE_MESSAGES: 1 << 13, | ||||||
|   SEND_MESSAGES = 0x00000800, |   EMBED_LINKS: 1 << 14, | ||||||
|   SEND_TTS_MESSAGES = 0x00001000, |   ATTACH_FILES: 1 << 15, | ||||||
|   MANAGE_MESSAGES = 0x00002000, |   READ_MESSAGE_HISTORY: 1 << 16, | ||||||
| 
 |   MENTION_EVERYONE: 1 << 17, | ||||||
|   EMBED_LINKS = 0x00004000, |   USE_EXTERNAL_EMOJIS: 1 << 18, | ||||||
|   ATTACH_FILES = 0x00008000, |   VIEW_GUILD_INSIGHTS: 1 << 19, | ||||||
|   READ_MESSAGE_HISTORY = 0x00010000, |   CONNECT: 1 << 20, | ||||||
|   MENTION_EVERYONE = 0x00020000, |   SPEAK: 1 << 21, | ||||||
|   USE_EXTERNAL_EMOJIS = 0x00040000, |   MUTE_MEMBERS: 1 << 22, | ||||||
|   VIEW_GUILD_INSIGHTS = 0x00080000, |   DEAFEN_MEMBERS: 1 << 23, | ||||||
| 
 |   MOVE_MEMBERS: 1 << 24, | ||||||
|   CONNECT = 0x00100000, |   USE_VAD: 1 << 25, | ||||||
|   SPEAK = 0x00200000, |   CHANGE_NICKNAME: 1 << 26, | ||||||
|   MUTE_MEMBERS = 0x00400000, |   MANAGE_NICKNAMES: 1 << 27, | ||||||
|   DEAFEN_MEMBERS = 0x00800000, |   MANAGE_ROLES: 1 << 28, | ||||||
|   MOVE_MEMBERS = 0x01000000, |   MANAGE_WEBHOOKS: 1 << 29, | ||||||
|   USE_VAD = 0x02000000, |   MANAGE_EMOJIS: 1 << 30, | ||||||
| 
 |  | ||||||
|   CHANGE_NICKNAME = 0x04000000, |  | ||||||
|   MANAGE_NICKNAMES = 0x08000000, |  | ||||||
|   MANAGE_ROLES = 0x10000000, |  | ||||||
|   MANAGE_WEBHOOKS = 0x20000000, |  | ||||||
|   MANAGE_EMOJIS = 0x40000000 |  | ||||||
| } | } | ||||||
| 
 |  | ||||||
| export { PermissionFlags } |  | ||||||
|  |  | ||||||
							
								
								
									
										16
									
								
								src/types/userFlags.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/types/userFlags.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | ||||||
|  | export const UserFlags = { | ||||||
|  |   DISCORD_EMPLOYEE: 1 << 0, | ||||||
|  |   PARTNERED_SERVER_OWNER: 1 << 1, | ||||||
|  |   DISCORD_PARTNER: 1 << 1, | ||||||
|  |   HYPESQUAD_EVENTS: 1 << 2, | ||||||
|  |   BUGHUNTER_LEVEL_1: 1 << 3, | ||||||
|  |   HOUSE_BRAVERY: 1 << 6, | ||||||
|  |   HOUSE_BRILLIANCE: 1 << 7, | ||||||
|  |   HOUSE_BALANCE: 1 << 8, | ||||||
|  |   EARLY_SUPPORTER: 1 << 9, | ||||||
|  |   TEAM_USER: 1 << 10, | ||||||
|  |   SYSTEM: 1 << 12, | ||||||
|  |   BUGHUNTER_LEVEL_2: 1 << 14, | ||||||
|  |   VERIFIED_BOT: 1 << 16, | ||||||
|  |   EARLY_VERIFIED_DEVELOPER: 1 << 17 | ||||||
|  | } | ||||||
							
								
								
									
										86
									
								
								src/utils/bitfield.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/utils/bitfield.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,86 @@ | ||||||
|  | // Ported from https://github.com/discordjs/discord.js/blob/master/src/util/BitField.js
 | ||||||
|  | export type BitFieldResolvable = number | BitField | string | BitField[] | ||||||
|  | 
 | ||||||
|  | export class BitField { | ||||||
|  |   flags: { [name: string]: number } = {} | ||||||
|  |   bitfield: number | ||||||
|  | 
 | ||||||
|  |   constructor(flags: { [name: string]: number }, bits: any) { | ||||||
|  |     this.flags = flags | ||||||
|  |     this.bitfield = BitField.resolve(this.flags, bits) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   any(bit: BitFieldResolvable): boolean { | ||||||
|  |     return (this.bitfield & BitField.resolve(this.flags, bit)) !== 0 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   equals(bit: BitFieldResolvable): boolean { | ||||||
|  |     return this.bitfield === BitField.resolve(this.flags, bit) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   has(bit: BitFieldResolvable, ...args: any[]): boolean { | ||||||
|  |     if (Array.isArray(bit)) return bit.every(p => this.has(p)) | ||||||
|  |     return (this.bitfield & BitField.resolve(this.flags, bit)) === bit | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   missing(bits: any, ...hasParams: any[]): string[] { | ||||||
|  |     if (!Array.isArray(bits)) bits = new BitField(this.flags, bits).toArray(false) | ||||||
|  |     return bits.filter((p: any) => !this.has(p, ...hasParams)) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   freeze(): Readonly<BitField> { | ||||||
|  |     return Object.freeze(this) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   add(...bits: BitFieldResolvable[]): BitField { | ||||||
|  |     let total = 0 | ||||||
|  |     for (const bit of bits) { | ||||||
|  |       total |= BitField.resolve(this.flags, bit) | ||||||
|  |     } | ||||||
|  |     if (Object.isFrozen(this)) return new BitField(this.flags, this.bitfield | total) | ||||||
|  |     this.bitfield |= total | ||||||
|  |     return this | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   remove(...bits: BitFieldResolvable[]): BitField { | ||||||
|  |     let total = 0 | ||||||
|  |     for (const bit of bits) { | ||||||
|  |       total |= BitField.resolve(this.flags, bit) | ||||||
|  |     } | ||||||
|  |     if (Object.isFrozen(this)) return new BitField(this.flags, this.bitfield & ~total) | ||||||
|  |     this.bitfield &= ~total | ||||||
|  |     return this | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   serialize(...hasParams: any[]): { [key: string]: any } { | ||||||
|  |     const serialized: { [key: string]: any } = {} | ||||||
|  |     for (const [flag, bit] of Object.entries(this.flags)) serialized[flag] = this.has(BitField.resolve(this.flags, bit), ...hasParams) | ||||||
|  |     return serialized | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   toArray(...hasParams: any[]): string[] { | ||||||
|  |     return Object.keys(this.flags).filter(bit => this.has(BitField.resolve(this.flags, bit), ...hasParams)) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   toJSON(): number { | ||||||
|  |     return this.bitfield | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   valueOf(): number { | ||||||
|  |     return this.bitfield | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   *[Symbol.iterator](): Iterator<string> { | ||||||
|  |     yield* this.toArray() | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static resolve(flags: any, bit: BitFieldResolvable = 0): number { | ||||||
|  |     if (typeof bit === 'string' && !isNaN(parseInt(bit))) return parseInt(bit) | ||||||
|  |     if (typeof bit === 'number' && bit >= 0) return bit | ||||||
|  |     if (bit instanceof BitField) return this.resolve(flags, bit.bitfield) | ||||||
|  |     if (Array.isArray(bit)) return bit.map(p => this.resolve(flags, p)).reduce((prev, p) => prev | p, 0) | ||||||
|  |     if (typeof bit === 'string' && typeof flags[bit] !== 'undefined') return flags[bit] | ||||||
|  |     const error = new RangeError('BITFIELD_INVALID') | ||||||
|  |     throw error | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -33,7 +33,7 @@ export const getBuildInfo = ( | ||||||
|   if (Deno.build.os === 'darwin') { |   if (Deno.build.os === 'darwin') { | ||||||
|     os = 'MacOS' |     os = 'MacOS' | ||||||
|     os_version = '10.15.6' |     os_version = '10.15.6' | ||||||
|     browser = 'Safari' |     browser = 'Firefox' | ||||||
|     browser_version = '14.0.1' |     browser_version = '14.0.1' | ||||||
|     browser_user_agent = |     browser_user_agent = | ||||||
|       'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.1 Safari/605.1.15' |       'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.1 Safari/605.1.15' | ||||||
|  |  | ||||||
							
								
								
									
										22
									
								
								src/utils/permissions.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/utils/permissions.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | ||||||
|  | // Ported from https://github.com/discordjs/discord.js/blob/master/src/util/Permissions.js
 | ||||||
|  | import { PermissionFlags } from "../types/permissionFlags.ts" | ||||||
|  | import { BitField } from "./bitfield.ts" | ||||||
|  | 
 | ||||||
|  | export type PermissionResolvable = string | number | Permissions | PermissionResolvable[] | ||||||
|  | 
 | ||||||
|  | export class Permissions extends BitField { | ||||||
|  |   static DEFAULT = 104324673 | ||||||
|  |   static ALL = Object.values(PermissionFlags).reduce((all, p) => all | p, 0) | ||||||
|  | 
 | ||||||
|  |   constructor(bits: any) { | ||||||
|  |     super(PermissionFlags, bits) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   any(permission: PermissionResolvable, checkAdmin = true): boolean { | ||||||
|  |     return (checkAdmin && super.has(this.flags.ADMINISTRATOR)) || super.any(permission as any) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   has(permission: PermissionResolvable, checkAdmin = true): boolean { | ||||||
|  |     return (checkAdmin && super.has(this.flags.ADMINISTRATOR)) || super.has(permission as any) | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										8
									
								
								src/utils/userFlags.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/utils/userFlags.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | ||||||
|  | import { UserFlags } from "../types/userFlags.ts"; | ||||||
|  | import { BitField } from "./bitfield.ts"; | ||||||
|  | 
 | ||||||
|  | export class UserFlagsManager extends BitField { | ||||||
|  |     constructor(bits: any) { | ||||||
|  |         super(UserFlags, bits) | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue