@ayanaware/bentocord
Version:
Bentocord is a Bento plugin designed to rapidly build fully functional Discord Bots.
217 lines • 6.88 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ComponentOperation = void 0;
/* eslint-disable @typescript-eslint/naming-convention */
const eris_1 = require("eris");
const { ComponentTypes } = eris_1.Constants;
class ComponentOperation {
constructor(ctx) {
this.isClosing = false;
this._files = [];
this._rows = [];
this.maxRowCount = 5;
this.timeoutSeconds = 60;
this.ctx = ctx;
// using entity name to prevent circular depends
this.cm = ctx.api.getEntity('@ayanaware/bentocord:ComponentsManager');
}
content(content) {
if (typeof content === 'string')
content = { content };
this._content = content;
return this;
}
async contentTranslated(key, repl, backup) {
const content = await this.ctx.formatTranslation(key, repl, backup);
return this.content(content);
}
files(files) {
this._files = files;
return this;
}
clearRows() {
this._rows = [];
return this;
}
setRows(rows) {
if (rows.length > this.maxRowCount)
throw new Error('Too many rows');
this._rows = rows;
return this;
}
setRow(n, row) {
if (n > this.maxRowCount - 1)
throw new Error('Invalid row id');
this._rows[n] = row;
return this;
}
addRows(rows) {
for (const row of rows) {
let placed = false;
for (let i = 0; i < this.maxRowCount; i++) {
if (this._rows[i])
continue;
this._rows[i] = row;
placed = true;
break;
}
if (!placed)
throw new Error('Not enough row space');
}
return this;
}
addRow(row) {
return this.addRows([row]);
}
/**
* A helper function to help build more complex/dynamic operations
* Some examples would be a button that re-renders itself etc
*/
async update() {
/* NO-OP */
}
async draw() {
/* NO-OP */
}
/**
* Merge & build the final message content object
*/
async build() {
if (!this.isClosing) {
try {
await this.draw();
await this.update();
}
catch (e) {
await this.close(e);
throw e;
}
}
const rows = [];
for (const row of this._rows) {
const components = row.map(c => c.definition);
if (components.length < 1)
continue; // prevent empty components action rows
rows.push({ type: ComponentTypes.ACTION_ROW, components });
}
// TODO: Think of a better way to solve this
// smash objects together
let content = Object.assign({}, this._content ?? {}, this._merge ?? {});
content.components = rows;
// join content & embeds as needed
if (this._content && this._merge) {
// merge content
if (this._content.content && this._merge.content)
content.content = `${this._content.content}\n${this._merge.content}`;
// merge embeds
if (this._content.embeds && this._merge.embeds)
content.embeds = [...this._content.embeds ?? [], ...this._merge.embeds ?? []];
}
// no embeds, make sure to send a empty array so discord removes them from the message
if (!Array.isArray(content.embeds))
content.embeds = [];
// run transformer
if (typeof this.transformer === 'function')
content = await this.transformer(content);
return content;
}
/**
* Actually "writes/makes visible" the state of this operation to the user.
* When overriding this function take care that you always super.render();
*/
async render() {
// ensure refreshTimeout is called
try {
await this.ctx.createResponse(await this.build(), this._files);
const message = await this.ctx.getResponse();
// update our handler if need be
if (this.messageId !== message.id) {
if (this.messageId)
this.cm.removeMessageHandler(this.messageId);
this.cm.addMessageHandler(message.id, this.handleInteraction.bind(this), this.cleanup.bind(this));
this.messageId = message.id;
}
}
finally {
// refresh timeout
if (!this.isClosing)
this.refreshTimeout();
}
}
async handleInteraction(ctx) {
let found;
for (const row of this._rows) {
for (const component of row) {
if (!('custom_id' in component.definition))
continue;
if (component.definition.custom_id !== ctx.customId)
continue;
found = component;
break;
}
if (found)
break;
}
// No component found
if (!found)
return;
if (!found.handler)
return;
// refresh timeout
this.refreshTimeout();
await found.handler(ctx);
}
refreshTimeout() {
if (this.isClosing)
return;
if (this.timeout)
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
if (this.isClosing)
return;
this.handleTimeout().catch(() => { });
}, this.timeoutSeconds * 1000);
}
async handleTimeout() {
return this.cleanup();
}
/**
* Promise won't resolve until the ComponentMessage is closed
* Can be used to block further execution
*/
async start() {
const promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
await this.render();
return promise;
}
async close(reason) {
await this.cleanup();
if (reason)
return this.reject(reason);
return this.resolve();
}
async cleanup() {
this.isClosing = true;
// disable all components
this._rows.forEach(components => {
components.forEach(c => {
c.disable();
});
});
// preform final render
try {
await this.render();
}
catch { /* NO-OP */ }
// detach handler
if (this.messageId)
this.cm.removeMessageHandler(this.messageId);
// clearTimeout
clearTimeout(this.timeout);
}
}
exports.ComponentOperation = ComponentOperation;
//# sourceMappingURL=ComponentOperation.js.map