UNPKG

@ayanaware/bentocord

Version:

Bentocord is a Bento plugin designed to rapidly build fully functional Discord Bots.

217 lines 6.88 kB
"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