feat(messages): add message caching and messageDelete event

This commit is contained in:
DjDeveloperr 2020-11-15 14:46:36 +05:30
parent c92f2e3d49
commit 5dbd4a84f5
8 changed files with 100 additions and 22 deletions

View file

@ -13,6 +13,7 @@ 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"
export const gatewayHandlers: {
[eventCode in GatewayEvents]: GatewayEventHandler | undefined
@ -42,7 +43,7 @@ export const gatewayHandlers: {
INVITE_DELETE: undefined,
MESSAGE_CREATE: messageCreate,
MESSAGE_UPDATE: undefined,
MESSAGE_DELETE: undefined,
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

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

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

@ -2,6 +2,8 @@ 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 { Message } from "../structures/message.ts"
import { MessageDeletePayload } from "../types/gateway.ts"
import { TOKEN } from './config.ts'
const client = new CommandClient({
@ -16,6 +18,14 @@ 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('messageCreate', msg => console.log(`${msg.author.tag}: ${msg.content}`))
client.on("commandError", console.error)