const { Infraction } = require('../interfaces/'); const { Collection } = require('../../../util/'); const Arguments = ['users', 'bots', 'text', 'startswith', 'endswith', 'emojis', 'reactions', 'emojis', 'reactions', 'images', 'attachments']; class Prune extends Infraction { constructor(client, opts = {}) { super(client, { type: 'PRUNE', targetType: 'channel', message: opts.message, arguments: opts.arguments, executor: opts.executor.user, target: opts.target, reason: opts.reason || 'N/A', guild: opts.guild, channel: opts.channel, silent: opts.silent, color: 0xdb36fc, dictionary: { past: 'pruned', present: 'prune' }, points: opts.points, expiration: opts.expiration, data: opts.data }); this.client = client; this.channel = opts.target; } async execute() { const { amount, message } = this.data; let messages = await this.fetchMessages(message, amount); //Collection, not array. if(messages.size === 0) return this._fail('C_PRUNE_NOTFETCHED'); const hasOld = messages.some((m) => m.createdTimestamp >= Date.now()+1209600000); //I hope Node.js can handle these large of numbers.. e_e (2 weeks) const hasUndeletable = messages.some((m) => m.deletable); messages = messages.filter((m) => m.deletable); if(messages.size === 0) return this._fail('C_PRUNE_NOTDELETABLE'); const filtered = await this.filterMessages(messages); if(filtered.size === 0) return this._fail('C_PRUNE_NOFILTERRESULTS'); const deleted = await this.deleteMessages(filtered); if(deleted === 0) return this._fail('C_PRUNE_NODELETE'); await this.handle(); return this._succeed('C_PRUNE_AMOUNT', { hasOld: deleted > amount ? hasOld : false, hasUndeletable: deleted > amount ? hasUndeletable : false, amount: deleted }); } async deleteMessages(messages) { let amount = 0; const bulkDelete = async(messages) => { try { const deleteThese = messages.splice(0, 100); const result = await this.target.bulkDelete(deleteThese, true); //Filtering old just incase, d.js uses Snowflake times instead of our Client's timestamp. if(result && result.size > 0) amount += result.size; } catch(error) {} //eslint-disable-line no-empty if(messages.length > 0) { await bulkDelete(messages); } }; await bulkDelete(messages.array()); return amount; } async filterMessages(messages) { const a = this.arguments; const filters = { AND: (m) => { return (a.users ? a.users.value.map((u) => u.id).includes(m.author.id) : true) && (a.bots ? m.author.bot : true) && (a.text ? m.content.toLowerCase().includes(a.text.value.toLowerCase()) : true) && (a.startswith ? m.content.toLowerCase().startsWith(a.startswith.value.toLowerCase()) : true) && (a.endswith ? m.content.toLowerCase().endsWith(a.endswith.value.toLowerCase()) : true) && (a.emojis ? (/?/u).match(m.content) : true) //does not support unicode emojis... might want to support? idk && (a.reactions ? m.reactions.cache.size > 0 : true) && (a.images ? m.attachments.some((a) => a.height && a.width) : true) && (a.attachments ? m.attachments.size > 0 : true); }, OR: (m) => { return (a.users ? a.users.value.map((u) => u.id).includes(m.author.id) : false) || (a.bots ? m.author.bot : false) || (a.text ? m.content.toLowerCase().includes(a.text.value.toLowerCase()) : false) || (a.startswith ? m.content.toLowerCase().startsWith(a.startswith.value.toLowerCase()) : false) || (a.endswith ? m.content.toLowerCase().endsWith(a.endswith.value.toLowerCase()) : false) || (a.emojis ? (/?/u).match(m.content) : false) //does not support unicode emojis... might want to support? idk || (a.reactions ? m.reactions.cache.size > 0 : false) || (a.images ? m.attachments.some((a) => a.height && a.width) : false) || (a.attachments ? m.attachments.size > 0 : false); }, NAND: (m) => { return (a.users ? !a.users.value.map((u) => u.id).includes(m.author.id) : true) && (a.bots ? !m.author.bot : true) && (a.text ? !m.content.toLowerCase().includes(a.text.value.toLowerCase()) : true) && (a.startswith ? !m.content.toLowerCase().startsWith(a.startswith.value.toLowerCase()) : true) && (a.endswith ? !m.content.toLowerCase().endsWith(a.endswith.value.toLowerCase()) : true) && (a.emojis ? !(/?/u).match(m.content) : true) //does not support unicode emojis... might want to support? idk && (a.reactions ? m.reactions.cache.size === 0 : true) && (a.images ? !m.attachments.some((a) => a.height && a.width) : true) && (a.attachments ? m.attachments.size === 0 : true); }, NOR: (m) => { return (a.users ? !a.users.value.map((u) => u.id).includes(m.author.id) : false) || (a.bots ? !m.author.bot : false) || (a.text ? !m.content.toLowerCase().includes(a.text.value.toLowerCase()) : false) || (a.startswith ? !m.content.toLowerCase().startsWith(a.startswith.value.toLowerCase()) : false) || (a.endswith ? !m.content.toLowerCase().endsWith(a.endswith.value.toLowerCase()) : false) || (a.emojis ? !(/?/u).match(m.content) : false) //does not support unicode emojis... might want to support? idk || (a.reactions ? m.reactions.cache.size === 0 : false) || (a.images ? !m.attachments.some((a) => a.height && a.width) : false) || (a.attachments ? m.attachments.size === 0 : false); } }; const type = a.and ? 'AND' : 'OR'; const method = a.not ? `N${type}` : type; let found = false; for(const arg of Object.keys(a)) { if(Arguments.includes(arg)) found = true; } if(!found) return messages; return messages.filter((m) => { return filters[method](m); }); } async fetchMessages(message, amount) { let fetched = new Collection(); const fetch = async (lastMessage, amount) => { const messages = await this.target.messages.fetch({ limit: amount > 100 ? 100 : amount, before: lastMessage, after: this.arguments.after ? this.arguments.after.value : null }); fetched = fetched.concat(messages); const remaining = amount - 100; if(remaining > 0) { await fetch(messages.lastKey(), remaining); } }; await fetch(this.arguments.before ? this.arguments.before.value : message, amount); return fetched; } description() { return `\n${this.guild.format('INFRACTION_DESCRIPTIONAMOUNT', { amount: this.data.amount })}`; } } module.exports = Prune;