Merge pull request #36 from DjDeveloperr/main

implement: new Gateway Events, additions to Command Client, message caching
This commit is contained in:
DjDeveloper 2020-11-16 17:17:47 +05:30 committed by GitHub
commit c80f9dc1a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 741 additions and 204 deletions

View File

@ -1,12 +1,14 @@
import { Gateway, GatewayEventHandler } from '../index.ts'
import getChannelByType from '../../utils/getChannelByType.ts'
import { ChannelPayload } from '../../types/channel.ts'
import { Guild } from "../../structures/guild.ts"
export const channelCreate: GatewayEventHandler = async (
gateway: Gateway,
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) {
await gateway.client.channels.set(d.id, d)
gateway.client.emit('channelCreate', channel)

View File

@ -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)
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)
}
}

View File

@ -1,14 +1,53 @@
import cache from '../../models/cache.ts'
import { Emoji } from "../../structures/emoji.ts"
import { Guild } from '../../structures/guild.ts'
import { EmojiPayload } from "../../types/emoji.ts"
import { GuildEmojiUpdatePayload } from '../../types/gateway.ts'
import { Gateway, GatewayEventHandler } from '../index.ts'
export const guildEmojiUpdate: GatewayEventHandler = (
export const guildEmojiUpdate: GatewayEventHandler = async (
gateway: Gateway,
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) {
// 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)
}
}
}

View 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)
}

View 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)
}
}

View 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)
}
}

View 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)
}

View 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)
}

View 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)
}

View File

@ -13,6 +13,15 @@ import { guildBanRemove } from './guildBanRemove.ts'
import { messageCreate } from './messageCreate.ts'
import { resume } from './resume.ts'
import { reconnect } from './reconnect.ts'
import { messageDelete } from "./messageDelete.ts"
import { messageUpdate } from "./messageUpdate.ts"
import { guildEmojiUpdate } from "./guildEmojiUpdate.ts"
import { guildMemberAdd } from "./guildMemberAdd.ts"
import { guildMemberRemove } from "./guildMemberRemove.ts"
import { guildMemberUpdate } from "./guildMemberUpdate.ts"
import { guildRoleCreate } from "./guildRoleCreate.ts"
import { guildRoleDelete } from "./guildRoleDelete.ts"
import { guildRoleUpdate } from "./guildRoleUpdate.ts"
export const gatewayHandlers: {
[eventCode in GatewayEvents]: GatewayEventHandler | undefined
@ -29,20 +38,20 @@ export const gatewayHandlers: {
GUILD_UPDATE: guildUpdate,
GUILD_BAN_ADD: guildBanAdd,
GUILD_BAN_REMOVE: guildBanRemove,
GUILD_EMOJIS_UPDATE: undefined,
GUILD_EMOJIS_UPDATE: guildEmojiUpdate,
GUILD_INTEGRATIONS_UPDATE: undefined,
GUILD_MEMBER_ADD: undefined,
GUILD_MEMBER_REMOVE: undefined,
GUILD_MEMBER_UPDATE: undefined,
GUILD_MEMBER_ADD: guildMemberAdd,
GUILD_MEMBER_REMOVE: guildMemberRemove,
GUILD_MEMBER_UPDATE: guildMemberUpdate,
GUILD_MEMBERS_CHUNK: undefined,
GUILD_ROLE_CREATE: undefined,
GUILD_ROLE_UPDATE: undefined,
GUILD_ROLE_DELETE: undefined,
GUILD_ROLE_CREATE: guildRoleCreate,
GUILD_ROLE_UPDATE: guildRoleUpdate,
GUILD_ROLE_DELETE: guildRoleDelete,
INVITE_CREATE: undefined,
INVITE_DELETE: undefined,
MESSAGE_CREATE: messageCreate,
MESSAGE_UPDATE: undefined,
MESSAGE_DELETE: undefined,
MESSAGE_UPDATE: messageUpdate,
MESSAGE_DELETE: messageDelete,
MESSAGE_DELETE_BULK: undefined,
MESSAGE_REACTION_ADD: undefined,
MESSAGE_REACTION_REMOVE: undefined,

View File

@ -13,6 +13,7 @@ export const messageCreate: GatewayEventHandler = async (
if (channel === undefined)
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
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)
await gateway.client.users.set(d.author.id, d.author)
let guild

View 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)
}

View 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)
}

View File

@ -3,6 +3,7 @@ import { GuildPayload } from '../../types/guild.ts'
import { Gateway, GatewayEventHandler } from '../index.ts'
export const ready: GatewayEventHandler = async (gateway: Gateway, d: any) => {
await gateway.client.guilds.flush()
gateway.client.user = new User(gateway.client, d.user)
gateway.sessionID = d.session_id
gateway.debug(`Received READY. Session: ${gateway.sessionID}`)

View File

@ -115,6 +115,8 @@ class Gateway {
await this.cache.set('seq', s)
}
if (t !== null && t !== undefined) {
this.client.emit('raw', t, d)
const handler = gatewayHandlers[t]
if (handler !== undefined) {

View File

@ -20,7 +20,7 @@ export class MemberRolesManager extends BaseChildManager<
async get (id: string): Promise<Role | undefined> {
const res = await this.parent.get(id)
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
}
@ -28,7 +28,7 @@ export class MemberRolesManager extends BaseChildManager<
const arr = (await this.parent.array()) as Role[]
const mem = await (this.parent as any).guild.members._get(this.member.id) as MemberPayload
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
}

View File

@ -5,6 +5,7 @@ import { Member } from '../structures/member.ts'
import { GUILD_MEMBER } from '../types/endpoint.ts'
import { MemberPayload } from '../types/guild.ts'
import { BaseManager } from './base.ts'
import { Permissions } from "../utils/permissions.ts"
export class MembersManager extends BaseManager<MemberPayload, Member> {
guild: Guild
@ -14,11 +15,17 @@ export class MembersManager extends BaseManager<MemberPayload, Member> {
this.guild = guild
}
async get (key: string): Promise<Member | undefined> {
async get(key: string): Promise<Member | undefined> {
const raw = await this._get(key)
if (raw === undefined) return
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
}
@ -27,7 +34,13 @@ export class MembersManager extends BaseManager<MemberPayload, Member> {
this.client.rest.get(GUILD_MEMBER(this.guild.id, id)).then(async data => {
await this.set(id, data as MemberPayload)
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)
}).catch(e => reject(e))
})

View File

@ -7,8 +7,11 @@ import { CHANNEL_MESSAGE } from '../types/endpoint.ts'
import { BaseManager } from './base.ts'
export class MessagesManager extends BaseManager<MessagePayload, Message> {
constructor (client: Client) {
channel: TextChannel
constructor (client: Client, channel: TextChannel) {
super(client, 'messages', Message)
this.channel = channel
}
async get (key: string): Promise<Message | undefined> {
@ -26,18 +29,22 @@ export class MessagesManager extends BaseManager<MessagePayload, Message> {
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) => {
this.client.rest
.get(CHANNEL_MESSAGE(channelID, id))
.get(CHANNEL_MESSAGE(this.channel.id, id))
.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>(
channelID
this.channel.id
)
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)
await this.client.users.set(

View File

@ -7,7 +7,7 @@ import {
export interface ICacheAdapter {
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
array: (cacheName: string) => undefined | any[] | Promise<any[] | undefined>
deleteCache: (cacheName: string) => any
@ -18,28 +18,31 @@ export class DefaultCacheAdapter implements ICacheAdapter {
[name: string]: Collection<string, any>
} = {}
async get (cacheName: string, key: string): Promise<undefined | any> {
async get(cacheName: string, key: string): Promise<undefined | any> {
const cache = this.data[cacheName]
if (cache === undefined) return
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]
if (cache === undefined) {
this.data[cacheName] = new Collection()
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> {
const cache = this.data[cacheName]
if (cache === undefined) return false
return cache.delete(key)
}
async array (cacheName: string): Promise<any[] | undefined> {
async array(cacheName: string): Promise<any[] | undefined> {
const cache = this.data[cacheName]
if (cache === undefined) return
return cache.array()
@ -55,13 +58,16 @@ export class RedisCacheAdapter implements ICacheAdapter {
_redis: Promise<Redis>
redis?: Redis
ready: boolean = false
readonly _expireIntervalTimer: number = 5000
private _expireInterval?: number
constructor (options: RedisConnectOptions) {
constructor(options: RedisConnectOptions) {
this._redis = connect(options)
this._redis.then(
redis => {
this.redis = redis
this.ready = true
this._startExpireInterval()
},
() => {
// TODO: Make error for this
@ -69,11 +75,31 @@ export class RedisCacheAdapter implements ICacheAdapter {
)
}
async _checkReady (): Promise<void> {
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> {
if (!this.ready) await this._redis
}
async get (cacheName: string, key: string): Promise<string | undefined> {
async get(cacheName: string, key: string): Promise<string | undefined> {
await this._checkReady()
const cache = await this.redis?.hget(cacheName, key)
if (cache === undefined) return
@ -84,10 +110,11 @@ export class RedisCacheAdapter implements ICacheAdapter {
}
}
async set (
async set(
cacheName: string,
key: string,
value: any
value: any,
expire?: number
): Promise<number | undefined> {
await this._checkReady()
const result = await this.redis?.hset(
@ -95,10 +122,17 @@ export class RedisCacheAdapter implements ICacheAdapter {
key,
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
}
async delete (cacheName: string, key: string): Promise<boolean> {
async delete(cacheName: string, key: string): Promise<boolean> {
await this._checkReady()
const exists = await this.redis?.hexists(cacheName, key)
if (exists === 0) return false
@ -106,7 +140,7 @@ export class RedisCacheAdapter implements ICacheAdapter {
return true
}
async array (cacheName: string): Promise<any[] | undefined> {
async array(cacheName: string): Promise<any[] | undefined> {
await this._checkReady()
const data = await this.redis?.hvals(cacheName)
return data?.map((e: string) => JSON.parse(e))

View File

@ -7,7 +7,6 @@ import { DefaultCacheAdapter, ICacheAdapter } from './cacheAdapter.ts'
import { UserManager } from '../managers/users.ts'
import { GuildManager } from '../managers/guilds.ts'
import { ChannelsManager } from '../managers/channels.ts'
import { MessagesManager } from '../managers/messages.ts'
import {
ActivityGame,
ClientActivity,
@ -31,6 +30,8 @@ export interface ClientOptions {
bot?: boolean
/** Force all requests to Canary API */
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[]
/** Whether to force new session or not */
forceNewSession?: boolean
/** Time till messages to stay cached, in MS. */
messageCacheLifetime: number = 3600000
users: UserManager = new UserManager(this)
guilds: GuildManager = new GuildManager(this)
channels: ChannelsManager = new ChannelsManager(this)
messages: MessagesManager = new MessagesManager(this)
emojis: EmojisManager = new EmojisManager(this)
/** Whether this client will login as bot user or not */
@ -80,6 +82,7 @@ export class Client extends EventEmitter {
: new ClientPresence(options.presence)
if (options.bot === false) this.bot = false
if (options.canary === true) this.canary = true
if (options.messageCacheLifetime !== undefined) this.messageCacheLifetime = options.messageCacheLifetime
}
/** Set Cache Adapter */

View File

@ -4,6 +4,7 @@ import { TextChannel } from '../structures/textChannel.ts'
import { User } from '../structures/user.ts'
import { Collection } from '../utils/collection.ts'
import { CommandClient } from './commandClient.ts'
import { Extension } from "./extensions.ts"
export interface CommandContext {
/** The Client object */
@ -35,8 +36,8 @@ export class Command {
description?: string
/** Array of Aliases of Command, or only string */
aliases?: string | string[]
/** Category of the Command */
category?: string
/** Extension (Parent) of the Command */
extension?: Extension
/** Usage of Command, only Argument Names */
usage?: string | string[]
/** Usage Example of Command, only Arguments (without Prefix and Name) */
@ -45,6 +46,16 @@ export class Command {
args?: number | boolean
/** Permission(s) required for using Command */
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) */
guildOnly?: boolean
/** Whether the Command can only be used in Bot's DMs (if allowed) */
@ -58,13 +69,16 @@ export class Command {
execute(ctx: CommandContext): any { }
/** Method executed after executing command, passes on CommandContext and the value returned by execute too. (optional) */
afterExecute(ctx: CommandContext, executeResult: any): any { }
toString(): string {
return `Command: ${this.name}${this.extension !== undefined ? ` [${this.extension.name}]` : ''}`
}
}
export class CommandsManager {
client: CommandClient
list: Collection<string, Command> = new Collection()
disabled: Set<string> = new Set()
disabledCategories: Set<string> = new Set()
constructor(client: CommandClient) {
this.client = client
@ -96,7 +110,6 @@ export class CommandsManager {
const cmd = this.find(name)
if (cmd === undefined) return
if (this.isDisabled(cmd) && bypassDisable !== true) return
if (cmd.category !== undefined && this.isCategoryDisabled(cmd.category) && bypassDisable !== true) return
return cmd
}
@ -121,7 +134,7 @@ export class CommandsManager {
add(cmd: Command | typeof Command): boolean {
// eslint-disable-next-line new-cap
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)
return true
}
@ -133,11 +146,6 @@ export class CommandsManager {
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 */
isDisabled(name: string | Command): boolean {
const cmd = typeof name === "string" ? this.find(name) : name
@ -155,18 +163,6 @@ export class CommandsManager {
this.disabled.add(cmd.name)
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 {

View File

@ -1,7 +1,8 @@
import { Embed, Message } from '../../mod.ts'
import { Message } from "../../mod.ts"
import { awaitSync } from "../utils/mixedPromise.ts"
import { Client, ClientOptions } from './client.ts'
import { CommandContext, CommandsManager, parseCommand } from './command.ts'
import { ExtensionsManager } from "./extensions.ts"
type PrefixReturnType = string | string[] | Promise<string | string[]>
@ -21,33 +22,6 @@ export interface CommandClientOptions extends ClientOptions {
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 {
prefix: string | string[]
mentionPrefix: boolean
@ -62,8 +36,8 @@ export class CommandClient extends Client implements CommandClientOptions {
allowBots: boolean
allowDMs: boolean
caseSensitive: boolean
extensions: ExtensionsManager = new ExtensionsManager(this)
commands: CommandsManager = new CommandsManager(this)
texts: CommandTexts = DefaultCommandTexts
constructor(options: CommandClientOptions) {
super(options)
@ -159,31 +133,9 @@ export class CommandClient extends Client implements CommandClientOptions {
if (command === undefined) return
const baseReplaces: Replaces = {
command: command.name,
nameUsed: parsed.name,
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
}
if (command.whitelistedGuilds !== undefined && msg.guild !== undefined && command.whitelistedGuilds.includes(msg.guild.id) === false) return;
if (command.whitelistedChannels !== undefined && command.whitelistedChannels.includes(msg.channel.id) === false) return;
if (command.whitelistedUsers !== undefined && command.whitelistedUsers.includes(msg.author.id) === false) return;
const ctx: CommandContext = {
client: this,
@ -198,8 +150,41 @@ export class CommandClient extends Client implements CommandClientOptions {
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 {
this.emit('commandUsed', { context: ctx })
this.emit('commandUsed', ctx, command)
const beforeExecute = await awaitSync(command.beforeExecute(ctx))
if (beforeExecute === false) return
@ -207,30 +192,7 @@ export class CommandClient extends Client implements CommandClientOptions {
const result = await awaitSync(command.execute(ctx))
command.afterExecute(ctx, result)
} catch (e) {
if (this.texts.ERROR !== undefined)
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)
this.emit('commandError', command, ctx, e)
}
}
}

112
src/models/extensions.ts Normal file
View 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)
}
}

View File

@ -8,6 +8,7 @@ import { GuildChannelsManager } from '../managers/guildChannels.ts'
import { MembersManager } from '../managers/members.ts'
import { Role } from './role.ts'
import { GuildEmojisManager } from '../managers/guildEmojis.ts'
import { Member } from "./member.ts"
export class Guild extends Base {
id: string
@ -214,4 +215,10 @@ export class Guild extends Base {
async getEveryoneRole (): Promise<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
}
}

View File

@ -1,6 +1,7 @@
import { MemberRolesManager } from "../managers/memberRoles.ts"
import { Client } from '../models/client.ts'
import { MemberPayload } from '../types/guild.ts'
import { Permissions } from "../utils/permissions.ts"
import { Base } from './base.ts'
import { Guild } from "./guild.ts"
import { User } from './user.ts'
@ -15,13 +16,12 @@ export class Member extends Base {
deaf: boolean
mute: boolean
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)
this.id = data.user.id
this.user = user
// this.user =
// cache.get('user', data.user.id) ?? new User(this.client, data.user)
this.nick = data.nick
this.guild = guild
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.deaf = data.deaf
this.mute = data.mute
// TODO: Cache in Gateway Event Code
// cache.set('member', this.id, this)
if (perms !== undefined) this.permissions = perms
else this.permissions = new Permissions(Permissions.DEFAULT)
}
protected readFromData (data: MemberPayload): void {

View File

@ -1,6 +1,7 @@
import { Client } from '../models/client.ts'
import { Base } from './base.ts'
import { RolePayload } from '../types/role.ts'
import { Permissions } from "../utils/permissions.ts"
export class Role extends Base {
id: string
@ -8,7 +9,7 @@ export class Role extends Base {
color: number
hoist: boolean
position: number
permissions: string
permissions: Permissions
managed: boolean
mentionable: boolean
@ -25,11 +26,9 @@ export class Role extends Base {
this.color = data.color
this.hoist = data.hoist
this.position = data.position
this.permissions = data.permissions
this.permissions = new Permissions(data.permissions)
this.managed = data.managed
this.mentionable = data.mentionable
// TODO: Cache in Gateway Event Code
// cache.set('role', this.id, this)
}
protected readFromData (data: RolePayload): void {
@ -38,7 +37,7 @@ export class Role extends Base {
this.color = data.color ?? this.color
this.hoist = data.hoist ?? this.hoist
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.mentionable = data.mentionable ?? this.mentionable
}

View File

@ -1,3 +1,4 @@
import { MessagesManager } from "../../mod.ts"
import { Client } from '../models/client.ts'
import { GuildTextChannelPayload, MessageOption, Overwrite, TextChannelPayload } from '../types/channel.ts'
import { CHANNEL_MESSAGE, CHANNEL_MESSAGES } from '../types/endpoint.ts'
@ -11,9 +12,11 @@ type AllMessageOptions = MessageOption | Embed
export class TextChannel extends Channel {
lastMessageID?: string
lastPinTimestamp?: string
messages: MessagesManager
constructor (client: Client, data: TextChannelPayload) {
super(client, data)
this.messages = new MessagesManager(this.client, this)
this.lastMessageID = data.last_message_id
this.lastPinTimestamp = data.last_pin_timestamp
}

View File

@ -1,5 +1,6 @@
import { Client } from '../models/client.ts'
import { UserPayload } from '../types/user.ts'
import { UserFlagsManager } from "../utils/userFlags.ts"
import { Base } from './base.ts'
export class User extends Base {
@ -13,9 +14,9 @@ export class User extends Base {
locale?: string
verified?: boolean
email?: string
flags?: number
flags?: UserFlagsManager
premiumType?: 0 | 1 | 2
publicFlags?: number
publicFlags?: UserFlagsManager
get tag (): string {
return `${this.username}#${this.discriminator}`
@ -41,11 +42,9 @@ export class User extends Base {
this.locale = data.locale
this.verified = data.verified
this.email = data.email
this.flags = data.flags
this.flags = new UserFlagsManager(data.flags)
this.premiumType = data.premium_type
this.publicFlags = data.public_flags
// TODO: Cache in Gateway Event Code
// cache.set('user', this.id, this)
this.publicFlags = new UserFlagsManager(data.public_flags)
}
protected readFromData (data: UserPayload): void {
@ -59,9 +58,9 @@ export class User extends Base {
this.locale = data.locale ?? this.locale
this.verified = data.verified ?? this.verified
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.publicFlags = data.public_flags ?? this.publicFlags
this.publicFlags = new UserFlagsManager(data.public_flags) ?? this.publicFlags
}
toString (): string {

View File

@ -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'
const client = new CommandClient({
@ -13,10 +20,71 @@ client.on('ready', () => {
console.log(`[Login] Logged in as ${client.user?.tag}!`)
})
client.on('messageDelete', (msg: Message) => {
console.log(`Message Deleted: ${msg.id}, ${msg.author.tag}, ${msg.content}`)
})
client.on('messageDeleteUncached', (d: MessageDeletePayload) => {
console.log(`Uncached Message Deleted: ${d.id} in ${d.channel_id}`)
})
client.on('messageUpdate', (before: Message, after: Message) => {
console.log('Message Update')
console.log(`Before: ${before.author.tag}: ${before.content}`)
console.log(`After: ${after.author.tag}: ${after.content}`)
})
client.on('messageUpdateUncached', (msg: Message) => {
console.log(`Message: ${msg.author.tag}: ${msg.content}`)
})
client.on('guildMemberAdd', (member: Member) => {
console.log(`Member Join: ${member.user.tag}`)
})
client.on('guildMemberRemove', (member: Member) => {
console.log(`Member Leave: ${member.user.tag}`)
})
client.on('guildRoleCreate', (role: Role) => {
console.log(`Role Create: ${role.name}`)
})
client.on('guildRoleDelete', (role: Role) => {
console.log(`Role Delete: ${role.name}`)
})
client.on('guildRoleUpdate', (role: Role, after: Role) => {
console.log(`Role Update: ${role.name}, ${after.name}`)
})
// client.on('messageCreate', msg => console.log(`${msg.author.tag}: ${msg.content}`))
client.on("commandError", console.error)
class ChannelLog extends Extension {
onChannelCreate(ext: Extension, channel: GuildChannel): void {
console.log(`Channel Created: ${channel.name}`)
}
load(): void {
this.listen('channelCreate', this.onChannelCreate)
class Pong extends Command {
name = 'Pong'
execute(ctx: CommandContext): any {
ctx.message.reply('Ping!')
}
}
this.commands.add(Pong)
}
}
client.extensions.load(ChannelLog)
// eslint-disable-next-line @typescript-eslint/no-floating-promises
;(async() => {
const files = Deno.readDirSync('./src/test/cmds')

View File

@ -13,6 +13,7 @@ export default class UserinfoCommand extends Command {
.setAuthor({ name: member.user.tag })
.addField("ID", member.id)
.addField("Roles", roles.map(r => r.name).join(", "))
.addField('Permissions', JSON.stringify(member.permissions.has('ADMINISTRATOR')))
.setColor(0xff00ff)
ctx.channel.send(embed)
}

View File

@ -186,7 +186,7 @@ export interface GuildBanRemovePayload {
export interface GuildEmojiUpdatePayload {
guild_id: string
emojis: []
emojis: EmojiPayload[]
}
export interface GuildIntegrationsUpdatePayload {

View File

@ -1,43 +1,35 @@
// https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags
enum PermissionFlags {
CREATE_INSTANT_INVITE = 0x00000001,
KICK_MEMBERS = 0x00000002,
BAN_MEMBERS = 0x00000004,
ADMINISTRATOR = 0x00000008,
MANAGE_CHANNELS = 0x00000010,
MANAGE_GUILD = 0x00000020,
ADD_REACTIONS = 0x00000040,
VIEW_AUDIT_LOG = 0x00000080,
PRIORITY_SPEAKER = 0x00000100,
STREAM = 0x00000200,
VIEW_CHANNEL = 0x00000400,
SEND_MESSAGES = 0x00000800,
SEND_TTS_MESSAGES = 0x00001000,
MANAGE_MESSAGES = 0x00002000,
EMBED_LINKS = 0x00004000,
ATTACH_FILES = 0x00008000,
READ_MESSAGE_HISTORY = 0x00010000,
MENTION_EVERYONE = 0x00020000,
USE_EXTERNAL_EMOJIS = 0x00040000,
VIEW_GUILD_INSIGHTS = 0x00080000,
CONNECT = 0x00100000,
SPEAK = 0x00200000,
MUTE_MEMBERS = 0x00400000,
DEAFEN_MEMBERS = 0x00800000,
MOVE_MEMBERS = 0x01000000,
USE_VAD = 0x02000000,
CHANGE_NICKNAME = 0x04000000,
MANAGE_NICKNAMES = 0x08000000,
MANAGE_ROLES = 0x10000000,
MANAGE_WEBHOOKS = 0x20000000,
MANAGE_EMOJIS = 0x40000000
}
export { PermissionFlags }
export const PermissionFlags: { [key: string]: number } = {
CREATE_INSTANT_INVITE: 1 << 0,
KICK_MEMBERS: 1 << 1,
BAN_MEMBERS: 1 << 2,
ADMINISTRATOR: 1 << 3,
MANAGE_CHANNELS: 1 << 4,
MANAGE_GUILD: 1 << 5,
ADD_REACTIONS: 1 << 6,
VIEW_AUDIT_LOG: 1 << 7,
PRIORITY_SPEAKER: 1 << 8,
STREAM: 1 << 9,
VIEW_CHANNEL: 1 << 10,
SEND_MESSAGES: 1 << 11,
SEND_TTS_MESSAGES: 1 << 12,
MANAGE_MESSAGES: 1 << 13,
EMBED_LINKS: 1 << 14,
ATTACH_FILES: 1 << 15,
READ_MESSAGE_HISTORY: 1 << 16,
MENTION_EVERYONE: 1 << 17,
USE_EXTERNAL_EMOJIS: 1 << 18,
VIEW_GUILD_INSIGHTS: 1 << 19,
CONNECT: 1 << 20,
SPEAK: 1 << 21,
MUTE_MEMBERS: 1 << 22,
DEAFEN_MEMBERS: 1 << 23,
MOVE_MEMBERS: 1 << 24,
USE_VAD: 1 << 25,
CHANGE_NICKNAME: 1 << 26,
MANAGE_NICKNAMES: 1 << 27,
MANAGE_ROLES: 1 << 28,
MANAGE_WEBHOOKS: 1 << 29,
MANAGE_EMOJIS: 1 << 30,
}

16
src/types/userFlags.ts Normal file
View 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
View 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
}
}

View File

@ -33,7 +33,7 @@ export const getBuildInfo = (
if (Deno.build.os === 'darwin') {
os = 'MacOS'
os_version = '10.15.6'
browser = 'Safari'
browser = 'Firefox'
browser_version = '14.0.1'
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'

22
src/utils/permissions.ts Normal file
View 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
View 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)
}
}