@wilcosp/rex
Version:
Rex is an automated command manager for discord js
556 lines (555 loc) • 22.1 kB
JavaScript
/*!
* 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);
}
}