const path = require('path'); const chalk = require('chalk'); const { Observer } = require("../../../interfaces"); const { Util, Collection } = require('../../../../util/'); const CONSTANTS = { IMAGES: { PREMIUM_LIMIT: 2, UPLOAD_LIMIT: { '0': 8, '1': 8, '2': 50, '3': 100 }, MB_DIVIDER: 1024*1024, PREMIUM_DELETE: { '0': 0, '1': 0, '2': 2, '3': 4 } }, DAY: 24 * 60 * 60 }; class MessageCache extends Observer { constructor(client) { super(client, { name: 'messageCache', priority: 0, disabled: false }); this.client = client; this.hooks = [ ['message', this.cache.bind(this)], ['messageDelete', this.cacheUpdate.bind(this)] ]; this.messages = new Collection(); setInterval(() => { this._sweepCache(); this._sweepDatabase(); }, 3600*1000); // 1 hour } async cache(message) { if(!this.client._built || message.webhookID || message.author.bot || !message.guild || !message.guild.available) return undefined; await message.guild.settings(); const data = await this._grabMessageData(message); this.messages.set(message.id, data); } async cacheUpdate(message) { const cachedMessage = this.messages.get(message.id); if(!cachedMessage) return undefined; cachedMessage.deleted = true; return this.messages.set(message.id, cachedMessage); } async _grabMessageData(message) { const metadata = { guild: message.guild.id, message: message.id, author: message.author.id, channel: message.channel.id, content: message.content, id: message.id, timestamp: message.createdTimestamp, deleted: false, attachments: [], removeAt: Math.floor(Date.now()/1000 + CONSTANTS.IMAGES.PREMIUM_DELETE[message.guild.premium]*24*60*60) }; const { _settings: guildSettings } = message.guild; const { messageLog } = guildSettings; let ignoredRole = false; if(messageLog.enabled && messageLog.ignoredRoles.length > 0) { for(const role of message.member.roles.cache.keys()) { if(messageLog.ignoredRoles.includes(role)) ignoredRole = true; } } if(message.attachments.size > 0 && message.guild.premium >= 2 && messageLog.channel && !messageLog.ignore.includes(message.channel.id) && !ignoredRole) { let size = 0; for(const attachment of message.attachments.values()) { const data = { size: attachment.size, dimensions: { x: attachment.width, y: attachment.height }, extension: path.extname(attachment.name), url: attachment.proxyURL || attachment.url, name: attachment.name, id: attachment.id }; const fsize = data.size/CONSTANTS.IMAGES.MB_DIVIDER; // File size in MB if(fsize > CONSTANTS.IMAGES.UPLOAD_LIMIT[message.guild.premiumTier]) { metadata.attachments.push(data); continue; //Cannot upload images larger than the guild's upload limit (Users w/ nitro can upload more than that, but the bot will be unable to post them.) } const buffer = await Util.downloadAsBuffer(attachment.proxyURL || attachment.url).catch((err) => { this.client.logger.error(`Failed to download buffer for "${chalk.bold(data.name)}".\n${err.stack || err}\nhttps://discord.com/channels/${message.guild.id}/${message.channel.id}/${message.id}`); return null; }); if(buffer) { if(fsize < 15) { const result = await this.client.storageManager.mongodb.attachments.insertOne({ attachmentId: attachment.id, buffer }); data.index = result?.insertedId; } else { //Upload using GridFS, not a priority right now. //this.client.logger.error(`Temporary logging; attachment "${chalk.bold(data.name)}" exceeds 15mb.`); } } metadata.attachments.push(data); size += fsize; } this.client.logger.debug(`${chalk.bold('[IMAGE]')} User ${message.author.tag} in guild ${message.guild.name} (#${message.channel.name}) uploaded ${message.attachments.size} attachment${message.attachments.size === 1 ? '' : 's'} (${size.toFixed(2)}mb).`); await this.client.storageManager.mongodb.messages.insertOne(metadata); } return metadata; //NOTE: It is NOT GARAUNTEED FOR EVERY ATTACHMENT TO HAVE AN INDEX. If theres no index, it either failed to be pushed to database or could not be saved. } async _sweepDatabase() { const messages = await this.client.storageManager.mongodb.messages.find({ removeAt: { $lt: Date.now()/1000 } }); if(messages.length > 0) { const attachmentIds = messages.map((m) => m.attachments).reduce((a, b) => b.concat(a)).filter((a) => a).map((a) => a.index); const deleteAttachments = await this.client.storageManager.mongodb.attachments.deleteMany({ _id: { $in: attachmentIds } }); const msgIds = messages.map((m) => m._id); const deleteMessages = await this.client.storageManager.mongodb.messages.deleteMany({ _id: { $in: msgIds } }); const messageCount = deleteMessages.deletedCount; const attachmentCount = deleteAttachments.deletedCount; this.client.logger.info(`${chalk.bold('[IMAGE]')} Trashed ${messageCount} message${messageCount === 1 ? '' : 's'} and ${attachmentCount} attachment${attachmentCount === 1 ? '' : 's'}.`); } } _sweepCache() { const ms = 3600000; // 1 hour in ms ( i think ) const filtered = this.messages.filter((m) => { const time = Date.now() - m.timestamp; return time < ms; }); const difference = this.messages.size-filtered.size; if(difference > 0) { this.client.logger.debug(`Trashed ${difference} items from the message cache.`); this.messages = filtered; } return filtered; } } module.exports = MessageCache;