UNPKG

@wilcosp/rex

Version:

Rex is an automated command manager for discord js

556 lines (555 loc) 22.1 kB
/*! * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { RESTJSONErrorCodes } from "discord-api-types/v10"; import { DiscordAPIError, Message, RateLimitError, } from "discord.js"; import { RexInteractionBase } from "./interactionBase.js"; const OG_MSG = "@original"; export class RexReplyInteractionBase extends RexInteractionBase { constructor(inter, { autoDefer, deferEphemeral, debounceDelay, auditDelay, useFailOver } = {}) { super(inter); this.lastRequest = Promise.resolve(); this.debounceDelay = debounceDelay ?? 1000; this.auditDelay = auditDelay ?? 1000; this.useFailOver = useFailOver; if (autoDefer) { this.deferEphemeral = deferEphemeral; this.autoDefer(); } } get deferred() { return this.inter.deferred; } get ephemeral() { return this.inter.ephemeral; } get replied() { return this.inter.replied; } get webhook() { return this.inter.webhook; } get response() { return this._response; } reply(options) { let fetchReply = false; if (this.useFailOver && Object.values(options).includes("fetchReply")) { fetchReply = options.fetchReply ?? false; options.fetchReply = false; } return this.asignLastRequest(this.inter.reply(options).then(async (r) => { this.responseId = r.id; if (r instanceof Message) { this._response = r; return r; } if (this.useFailOver && !this.ephemeral) { const msg = await this.fetchChannelMessage(r.id); if (msg) { this._response = msg; if (fetchReply) { return msg; } } } if (fetchReply) { return this.fetchReply().then(msg => { this._response = msg; return msg; }); } return r; })); } deferReply(options) { let fetchReply = false; if (this.useFailOver && typeof options == "object") { fetchReply = options.fetchReply ?? false; options.fetchReply = false; } return this.asignLastRequest(this.inter.deferReply(options).then(async (r) => { this.responseId = r.id; if (r instanceof Message) { this._response = r; return r; } if (this.useFailOver) { const msg = await this.fetchChannelMessage(r.id); if (msg) { this._response = msg; if (fetchReply) { return msg; } } } if (fetchReply) { return this.fetchReply(); } return r; })); } deleteReply() { return this.lastRequest.then(() => this.asignLastRequest(this.inter .deleteReply() .catch(e => { if (this.useFailOver && !this.ephemeral && this._response && this._response.deletable && (e instanceof RateLimitError || (e instanceof DiscordAPIError && (e.status == 429 || e.code == RESTJSONErrorCodes.InvalidWebhookToken || e.code == RESTJSONErrorCodes.UnknownInteraction)))) { return this._response.delete().catch(er => { throw [e, er]; }); } throw e; }) .then(r => { this._response = undefined; return r; }))); } editReply(options, triedbefore = false) { const debounce = this.debounceMap?.get(this._response?.id ?? OG_MSG); const audit = this.auditMap?.get(this._response?.id ?? OG_MSG); clearTimeout(debounce?.timeout); clearTimeout(audit?.timeout); if (this._response?.id) this.auditMap?.delete(this._response?.id); this.auditMap?.delete(OG_MSG); return this.lastRequest.then(() => this.asignLastRequest(this.inter .editReply(options) .then(msg => { this._response = msg; return msg; }) .catch(error => { if (error instanceof DiscordAPIError || error instanceof RateLimitError) { if (error instanceof DiscordAPIError && error.code == RESTJSONErrorCodes.UnknownInteraction && !triedbefore) { return this.editReply(options, true); } if (this.useFailOver && !this.inter.ephemeral && this._response && (error instanceof RateLimitError || error.status == 429 || error.code == RESTJSONErrorCodes.InvalidWebhookToken || error.code == RESTJSONErrorCodes.UnknownInteraction)) { return this._response .edit(options) .then(r => { if (!r) { throw error; } this._response = r; return r; }) .catch(er => { throw [error, er]; }); } } throw error; }) .finally(() => { setTimeout(() => { debounce?.resolve(); audit?.resolve(); }, 30); }))); } editReplyDebounce(options) { this.debounceMap ?? (this.debounceMap = new Map()); const debounce = this.debounceMap.get(this._response?.id ?? OG_MSG); clearTimeout(debounce?.timeout); return new Promise((resolve, reject) => { this.debounceMap.set(this._response?.id ?? OG_MSG, { resolve, timeout: setTimeout(() => { this.editReply(options) .then(msg => { if (!this._response && msg instanceof Message) { this._response = msg; } resolve(msg); }) .catch(e => reject(e)); }, this.debounceDelay), }); }).finally(() => debounce?.resolve()); } editReplyAudit(options) { this.auditMap ?? (this.auditMap = new Map()); const audit = this.auditMap.get(this._response?.id ?? OG_MSG); if (audit) { return new Promise((resolve, reject) => { this.auditMap.set(this._response?.id ?? OG_MSG, { timeout: audit.timeout, resolve, lastOptions: options, reject, }); }).finally(audit?.resolve); } return new Promise((resolve, reject) => { this.auditMap.set(this._response?.id ?? OG_MSG, { reject, resolve, lastOptions: options, timeout: setTimeout(() => { this.lastRequest.then(() => { const lastAudit = this.auditMap?.get(this._response?.id ?? OG_MSG); if (lastAudit) { this.inter .editReply(lastAudit.lastOptions) .then(msg => { lastAudit.resolve(msg); this._response = msg; }) .catch(e => { if (this.useFailOver && !this.inter.ephemeral && this._response?.id && (e instanceof RateLimitError || (e instanceof DiscordAPIError && (e.status == 429 || e.code == RESTJSONErrorCodes.InvalidWebhookToken || e.code == RESTJSONErrorCodes.UnknownInteraction)))) { return this.editChannelMessage(this._response?.id, options) .then(r => { if (r) { lastAudit.resolve(r); return; } lastAudit.reject(e); }) .catch(er => { lastAudit.reject([e, er]); }); } lastAudit.reject(e); }); } if (this._response?.id) this.auditMap?.delete(this._response?.id); this.auditMap?.delete(OG_MSG); }); }, this.auditDelay), }); }); } fetchReply() { return this.lastRequest.then(() => { if (this._response) { return this._response; } return this.inter .fetchReply() .then(fetched => { this._response = fetched; return fetched; }) .catch(async (e) => { if (this._response) { return this._response; } if (this.useFailOver && this.responseId && (e instanceof RateLimitError || (e instanceof DiscordAPIError && (e.status == 429 || e.code == RESTJSONErrorCodes.InvalidWebhookToken || e.code == RESTJSONErrorCodes.UnknownInteraction || e.code == RESTJSONErrorCodes.UnknownMessage)))) { return this.fetchChannelMessage(this.responseId).then(msg => { if (!msg) { throw e; } this._response = msg; return msg; }); } throw e; }); }); } fetchMessage(id) { if (this._response?.id == id) { return Promise.resolve(this._response); } return this.webhook.fetchMessage(id).catch(e => { if (this.useFailOver && (e instanceof RateLimitError || (e instanceof DiscordAPIError && (e.status == 429 || e.code == RESTJSONErrorCodes.InvalidWebhookToken || e.code == RESTJSONErrorCodes.UnknownInteraction || e.code == RESTJSONErrorCodes.UnknownMessage)))) { return (this.fetchChannel() .catch(() => null) .then(channel => { if (!channel) { throw e; } return channel.messages.fetch(id).catch(er => { throw [e, er]; }); })); } throw e; }); } followUp(options, triedbefore = false) { return this.lastRequest.then(() => this.asignLastRequest(this.inter .followUp(options) .then(msg => { this.lastMessage = msg.id; return msg; }) .catch(async (e) => { if (e instanceof DiscordAPIError && e.code == RESTJSONErrorCodes.UnknownInteraction && !triedbefore) { return this.followUp(options, true); } if (this.useFailOver && !this.inter.ephemeral && (e instanceof RateLimitError || (e instanceof DiscordAPIError && (e.status == 429 || e.code == RESTJSONErrorCodes.InvalidWebhookToken || e.code == RESTJSONErrorCodes.UnknownInteraction)))) { if (this._response) { return this._response.reply(options).catch(er => { throw [e, er]; }); } return this.fetchChannelMessage(this.responseId).then(msg => { if (!msg) { throw e; } return msg.reply(options).catch(er => { throw [e, er]; }); }, () => { throw e; }); } throw e; }))); } replyOrEdit(options) { return this.lastRequest.then(async () => { if (this.replied || this.deferred) { return this.editReply(options); } return this.reply(options); }); } replyOrEditDebounce(options) { return this.lastRequest.then(() => { if (this.replied || this.deferred) { return this.editReplyDebounce(options); } return this.reply(options); }); } replyOrEditAudit(options) { return this.lastRequest.then(() => { if (this.replied || this.deferred) { return this.editReplyAudit(options); } return this.reply(options); }); } replyOrFollowUp(options) { return this.lastRequest.then(async () => { if (this.inter.replied) { return this.followUp(options); } if (this.inter.deferred) { return this.editReply(options); } return this.reply(options); }); } editMessage(options, message, triedbefore = false) { return this.lastRequest.then(() => { if (!message) { message = this.lastMessage; } const msgID = message?.id ?? message; if (!message || msgID == OG_MSG) { return this.editReply(options); } const debounce = this.debounceMap?.get(msgID); clearTimeout(debounce?.timeout); const audit = this.auditMap?.get(msgID); clearTimeout(audit?.timeout); this.auditMap?.delete(msgID); return this.asignLastRequest(this.webhook .editMessage(message?.id ?? message, options) .catch(e => { if (e instanceof DiscordAPIError || e instanceof RateLimitError) { if (e instanceof DiscordAPIError && !triedbefore && e.code == RESTJSONErrorCodes.UnknownInteraction) { return this.editMessage(options, message, true); } if (this.useFailOver && !this.inter.ephemeral && (e instanceof RateLimitError || e.status == 429 || e.code == RESTJSONErrorCodes.InvalidWebhookToken || e.code == RESTJSONErrorCodes.UnknownInteraction)) { return this.editChannelMessage(msgID, options) .then(r => { if (!r) { throw e; } return r; }) .catch(er => { throw [e, er]; }); } } throw e; }) .finally(() => setTimeout(() => { audit?.resolve(); debounce?.resolve(); }, 30))); }); } editMessageDebounce(options, message) { if (!message) { message = this.lastMessage; } const msgID = message?.id ?? message; if (!message || msgID == OG_MSG) { return this.editReplyDebounce(options); } this.debounceMap ?? (this.debounceMap = new Map()); const debounce = this.debounceMap.get(msgID); clearTimeout(debounce?.timeout); return new Promise((resolve, reject) => { this.debounceMap.set(msgID, { resolve, timeout: setTimeout(() => { this.editMessage(options, msgID) .then(msg => resolve(msg)) .catch(e => reject(e)); }, this.debounceDelay), }); }).finally(() => debounce?.resolve()); } editMessageAudit(options, message) { if (!message) { message = this.lastMessage; } const msgID = message?.id ?? message; if (!message || msgID == OG_MSG) { return this.editReplyAudit(options); } this.auditMap ?? (this.auditMap = new Map()); const audit = this.auditMap.get(msgID); if (audit) { return new Promise((resolve, reject) => { this.auditMap.set(msgID, { lastOptions: options, reject, resolve, timeout: audit.timeout, }); }).finally(audit.resolve); } return new Promise((resolve, reject) => { this.auditMap.set(msgID, { resolve, reject, lastOptions: options, timeout: setTimeout(() => { this.lastRequest.then(() => { const lastAudit = this.auditMap?.get(msgID); if (lastAudit) { this.webhook .editMessage(msgID, lastAudit.lastOptions) .then(r => { lastAudit.resolve(r); }) .catch(e => { if (this.useFailOver && !this.inter.ephemeral && (e instanceof RateLimitError || (e instanceof DiscordAPIError && (e.status == 429 || e.code == RESTJSONErrorCodes.InvalidWebhookToken || e.code == RESTJSONErrorCodes.UnknownInteraction)))) { return this.editChannelMessage(msgID, lastAudit.lastOptions) .then(msg => { if (msg) { lastAudit.resolve(msg); } lastAudit.reject(e); }) .catch(er => lastAudit.reject([e, er])); } lastAudit.reject(e); }); this.auditMap?.delete(msgID); } }); }, this.auditDelay), }); }); } deleteMessage(message) { return this.lastRequest.then(() => { if (!message) { message = this.lastMessage; } if (!message || message == OG_MSG || message == this._response?.id || message.id == this._response?.id) { return this.deleteReply(); } return this.asignLastRequest(this.webhook.deleteMessage(message).catch(e => { if (this.useFailOver && !this.ephemeral && message && (e instanceof RateLimitError || (e instanceof DiscordAPIError && (e.status == 429 || e.code == RESTJSONErrorCodes.InvalidWebhookToken || e.code == RESTJSONErrorCodes.UnknownInteraction)))) { return this.fetchChannel() .catch(() => null) .then(ch => { if (!ch) { throw e; } return ch.messages.delete(message.id ?? message).catch(er => { throw [e, er]; }); }); } throw e; })); }); } setDeferEphemeral(ephemeral) { this.deferEphemeral = ephemeral; } autoDefer() { global.setTimeout(inter => { this.lastRequest.then(() => { if (!(inter.replied || inter.deferred)) { inter.deferReply({ ephemeral: this.deferEphemeral ?? false }); } }); }, 2800 - this.inter.client.ws.ping * 2, this); } asignLastRequest(value) { this.lastRequest = new Promise(resolve => { value.then(() => resolve(), () => resolve()); }); return value; } fetchChannelMessage(id) { return this.fetchChannel() .then(ch => ch.messages.fetch({ around: id })) .then(mesCol => { for (const msg of mesCol.values()) { if (msg.interaction?.id == this.inter.id) { mesCol.sweep(() => true); this._response = msg; return msg; } } throw Error("MSG not found"); }) .catch(() => null); } editChannelMessage(messageId, options) { return this.fetchChannel().then(channel => { return channel.messages.edit(messageId, options); }, () => null); } }