feat(messages): add message caching and messageDelete event
This commit is contained in:
parent
c92f2e3d49
commit
5dbd4a84f5
8 changed files with 100 additions and 22 deletions
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
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)
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue