mirror of
https://github.com/LBRYFoundation/curate.git
synced 2025-08-23 17:37:25 +00:00
Add pager and confirmation prompt
This commit is contained in:
parent
a736a0f140
commit
2aeacc0a77
5 changed files with 283 additions and 0 deletions
|
@ -36,6 +36,7 @@ module.exports = {
|
|||
messageLimit: 0,
|
||||
intents: [
|
||||
"guilds",
|
||||
"guildMembers",
|
||||
"guildMessages",
|
||||
"guildMessageReactions",
|
||||
"directMessages",
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
"config": "^3.3.6",
|
||||
"dexare": "^2.0.1",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"lodash.chunk": "^4.2.0",
|
||||
"quick.db": "^7.1.3",
|
||||
"steno": "^2.0.0"
|
||||
},
|
||||
|
@ -28,6 +29,7 @@
|
|||
"@types/common-tags": "^1.8.0",
|
||||
"@types/config": "^0.0.38",
|
||||
"@types/cron": "^1.7.2",
|
||||
"@types/lodash.chunk": "^4.2.6",
|
||||
"@types/needle": "^2.5.1",
|
||||
"@types/node": "^15.12.4",
|
||||
"@typescript-eslint/eslint-plugin": "^4.28.0",
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import { CommandContext } from 'dexare';
|
||||
import Eris from 'eris';
|
||||
|
||||
/**
|
||||
* Make a promise that resolves after some time
|
||||
* @param ms The time to resolve at
|
||||
|
@ -70,3 +73,89 @@ export function splitMessage(
|
|||
}
|
||||
return messages.concat(msg).filter((m) => m);
|
||||
}
|
||||
|
||||
export enum ConfirmEmoji {
|
||||
YES = '✔️',
|
||||
NO = '❌'
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a confirmation prompt.
|
||||
* @param ctx The context to use
|
||||
* @param content The content to send
|
||||
* @param file The file(s)
|
||||
*/
|
||||
export async function confirm(
|
||||
ctx: CommandContext,
|
||||
content: Eris.MessageContent,
|
||||
file?: Eris.MessageFile | Eris.MessageFile[]
|
||||
): Promise<boolean> {
|
||||
const botUser = ctx.client.bot.user.id;
|
||||
const message = await ctx.reply(content, file);
|
||||
const group = `confirm:${message.id}`;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const timeout = setTimeout(() => cb(false), 30000);
|
||||
const cb = (result: boolean, destroyed = false) => {
|
||||
clearTimeout(timeout);
|
||||
if (!destroyed) message.delete().catch(() => {});
|
||||
ctx.client.events.unregisterGroup(group);
|
||||
resolve(result);
|
||||
};
|
||||
|
||||
/* #region events */
|
||||
ctx.client.events.register(group, 'messageReactionAdd', (_, { id }, emoji, member) => {
|
||||
if (message.id === id && member.id === ctx.author.id) {
|
||||
if (emoji.name === ConfirmEmoji.YES) cb(true);
|
||||
if (emoji.name === ConfirmEmoji.NO) cb(false);
|
||||
}
|
||||
});
|
||||
|
||||
ctx.client.events.register(
|
||||
group,
|
||||
'messageCreate',
|
||||
(event, reply) => {
|
||||
if (reply.channel.id === message.channel.id && reply.author.id === ctx.author.id) {
|
||||
if (reply.content.toLowerCase() === 'yes') {
|
||||
event.skip('commands');
|
||||
cb(true);
|
||||
} else if (reply.content.toLowerCase() === 'no') {
|
||||
event.skip('commands');
|
||||
cb(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ before: ['commands'] }
|
||||
);
|
||||
|
||||
ctx.client.events.register(group, 'messageDelete', (_, { id }) => {
|
||||
if (message.id === id) cb(false, true);
|
||||
});
|
||||
|
||||
ctx.client.events.register(group, 'messageDeleteBulk', (_, messages) => {
|
||||
for (const { id } of messages) if (message.id === id) return cb(false, true);
|
||||
});
|
||||
|
||||
ctx.client.events.register(group, 'channelDelete', (_, channel) => {
|
||||
if (message.channel.id === channel.id) return cb(false, true);
|
||||
});
|
||||
|
||||
if (message.guildID) {
|
||||
ctx.client.events.register(group, 'guildDelete', (_, guild) => {
|
||||
if (message.guildID === guild.id) return cb(false, true);
|
||||
});
|
||||
|
||||
ctx.client.events.register(group, 'guildMemberRemove', (_, guild, member) => {
|
||||
if (message.guildID === guild.id && member.id === ctx.author.id) return cb(false);
|
||||
});
|
||||
|
||||
ctx.client.events.register(group, 'guildBanAdd', (_, guild, user) => {
|
||||
if (message.guildID === guild.id && user.id === ctx.author.id) return cb(false);
|
||||
});
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
if ('permissionsOf' in ctx.channel ? ctx.channel.permissionsOf(botUser).has('addReactions') : true)
|
||||
Promise.all([ConfirmEmoji.YES, ConfirmEmoji.NO].map(message.addReaction)).catch(() => {});
|
||||
});
|
||||
}
|
||||
|
|
174
src/util/pager.ts
Normal file
174
src/util/pager.ts
Normal file
|
@ -0,0 +1,174 @@
|
|||
import { ClientEvent, CommandContext } from 'dexare';
|
||||
import Eris from 'eris';
|
||||
import chunk from 'lodash.chunk';
|
||||
import { splitMessage } from '.';
|
||||
|
||||
export enum PagerEmoji {
|
||||
STOP = '🛑',
|
||||
NEXT = '➡️',
|
||||
PREVIOUS = '⬅️',
|
||||
FIRST = '⏮️',
|
||||
LAST = '⏭️'
|
||||
}
|
||||
|
||||
export interface PagerOptions {
|
||||
items: string[];
|
||||
itemsPerPage?: number | 'auto';
|
||||
itemSeparator?: string;
|
||||
characterLimit?: number;
|
||||
idleTime?: number;
|
||||
startPage?: number;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface InternalPagerStopOptions {
|
||||
destroy?: boolean;
|
||||
destroyed?: boolean;
|
||||
}
|
||||
|
||||
export interface InternalPagerTurnOptions {
|
||||
emoji?: Eris.Emoji;
|
||||
event?: ClientEvent;
|
||||
reply?: Eris.Message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a pagination.
|
||||
*/
|
||||
export async function paginate(
|
||||
ctx: CommandContext,
|
||||
{
|
||||
items,
|
||||
itemsPerPage = 'auto',
|
||||
itemSeparator = '\n',
|
||||
characterLimit = 2048,
|
||||
idleTime = 30000,
|
||||
startPage = 1,
|
||||
title = 'Items'
|
||||
}: PagerOptions,
|
||||
embed?: Eris.EmbedOptions
|
||||
) {
|
||||
/* #region pages */
|
||||
const pages: string[] =
|
||||
itemsPerPage === 'auto'
|
||||
? splitMessage(items.join(itemSeparator), { maxLength: characterLimit })
|
||||
: chunk(items, itemsPerPage).map((page) => page.join(itemSeparator));
|
||||
if (isNaN(startPage) || startPage <= 1) startPage = 1;
|
||||
else if (startPage > pages.length) startPage = pages.length;
|
||||
let page = startPage;
|
||||
|
||||
const render = (page: number): Eris.MessageContent => ({
|
||||
embed: {
|
||||
...(embed || {}),
|
||||
title: `${title} [${page}/${pages.length}]`,
|
||||
description: pages[page + 1]
|
||||
}
|
||||
});
|
||||
/* #endregion */
|
||||
|
||||
const botUser = ctx.client.bot.user.id;
|
||||
const canReact =
|
||||
'permissionsOf' in ctx.channel ? ctx.channel.permissionsOf(botUser).has('addReactions') : true;
|
||||
const canManage =
|
||||
'permissionsOf' in ctx.channel ? ctx.channel.permissionsOf(botUser).has('manageMessages') : false;
|
||||
const message = await ctx.reply(render(page));
|
||||
const group = `pager:${message.id}`;
|
||||
let reacted: string[] = [];
|
||||
|
||||
/* #region page functions */
|
||||
let idleTimeout = setTimeout(() => stop(), idleTime);
|
||||
const stop = ({ destroy = false, destroyed = false }: InternalPagerStopOptions = {}) => {
|
||||
clearTimeout(idleTimeout);
|
||||
if (!destroyed && destroy) message.delete().catch(() => {});
|
||||
// Remove reactions
|
||||
if (!destroyed && !destroy) {
|
||||
if (canManage) message.removeReactions().catch(() => {});
|
||||
else Promise.all(reacted.map((emoji) => message.removeReaction(emoji))).catch(() => {});
|
||||
}
|
||||
ctx.client.events.unregisterGroup(group);
|
||||
};
|
||||
const turn = (toPage: number, { emoji, event, reply }: InternalPagerTurnOptions) => {
|
||||
if (emoji && canManage) message.removeReaction(emoji.name, ctx.author.id).catch(() => {});
|
||||
if (event) event.skip('commands');
|
||||
if (reply && canManage) reply.delete().catch(() => {});
|
||||
clearTimeout(idleTimeout);
|
||||
idleTimeout = setTimeout(() => stop(), idleTime);
|
||||
page = toPage;
|
||||
message.edit(render(page)).catch(() => {});
|
||||
};
|
||||
/* #endregion */
|
||||
|
||||
/* #region events */
|
||||
ctx.client.events.register(group, 'messageReactionAdd', (_, { id }, emoji, member) => {
|
||||
if (message.id === id && member.id === ctx.author.id) {
|
||||
if (emoji.name === PagerEmoji.FIRST && page !== 1) turn(1, { emoji });
|
||||
if (emoji.name === PagerEmoji.LAST && page !== pages.length) turn(pages.length, { emoji });
|
||||
if (emoji.name === PagerEmoji.NEXT && page > pages.length) turn(page + 1, { emoji });
|
||||
if (emoji.name === PagerEmoji.PREVIOUS && page <= 1) turn(page - 1, { emoji });
|
||||
if (emoji.name === PagerEmoji.STOP) stop({ destroy: true });
|
||||
}
|
||||
});
|
||||
|
||||
ctx.client.events.register(
|
||||
group,
|
||||
'messageCreate',
|
||||
(event, reply) => {
|
||||
if (reply.channel.id === message.channel.id && reply.author.id === ctx.author.id) {
|
||||
if (['>>', 'first'].includes(reply.content.toLowerCase()) && page !== 1) turn(1, { event, reply });
|
||||
if (['<<', 'last'].includes(reply.content.toLowerCase()) && page !== pages.length)
|
||||
turn(pages.length, { event, reply });
|
||||
if (['>', 'next', 'forward'].includes(reply.content.toLowerCase()) && page > pages.length)
|
||||
turn(page + 1, { event, reply });
|
||||
if (['<', 'previous', 'prev', 'back'].includes(reply.content.toLowerCase()) && page <= 1)
|
||||
turn(page - 1, { event, reply });
|
||||
|
||||
if (reply.content.toLowerCase().startsWith('page ') && reply.content.length > 5) {
|
||||
let newPage = parseInt(reply.content.slice(4).trim());
|
||||
if (isNaN(startPage)) return;
|
||||
if (newPage <= 1) newPage = 1;
|
||||
else if (startPage > pages.length) startPage = pages.length;
|
||||
if (page !== newPage) {
|
||||
page = newPage;
|
||||
turn(newPage, { event, reply });
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ before: ['commands'] }
|
||||
);
|
||||
|
||||
ctx.client.events.register(group, 'messageDelete', (_, { id }) => {
|
||||
if (message.id === id) stop({ destroyed: true });
|
||||
});
|
||||
|
||||
ctx.client.events.register(group, 'messageDeleteBulk', (_, messages) => {
|
||||
for (const { id } of messages) if (message.id === id) stop({ destroyed: true });
|
||||
});
|
||||
|
||||
ctx.client.events.register(group, 'channelDelete', (_, channel) => {
|
||||
if (message.channel.id === channel.id) stop({ destroyed: true });
|
||||
});
|
||||
|
||||
if (message.guildID) {
|
||||
ctx.client.events.register(group, 'guildDelete', (_, guild) => {
|
||||
if (message.guildID === guild.id) stop({ destroyed: true });
|
||||
});
|
||||
|
||||
ctx.client.events.register(group, 'guildMemberRemove', (_, guild, member) => {
|
||||
if (message.guildID === guild.id && member.id === ctx.author.id) stop({ destroy: true });
|
||||
});
|
||||
|
||||
ctx.client.events.register(group, 'guildBanAdd', (_, guild, user) => {
|
||||
if (message.guildID === guild.id && user.id === ctx.author.id) stop({ destroy: true });
|
||||
});
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
if (canReact && pages.length > 1) {
|
||||
reacted =
|
||||
pages.length === 2
|
||||
? [PagerEmoji.PREVIOUS, PagerEmoji.STOP, PagerEmoji.NEXT]
|
||||
: [PagerEmoji.FIRST, PagerEmoji.PREVIOUS, PagerEmoji.STOP, PagerEmoji.NEXT, PagerEmoji.LAST];
|
||||
Promise.all(reacted.map(message.addReaction)).catch(() => {});
|
||||
}
|
||||
}
|
17
yarn.lock
17
yarn.lock
|
@ -151,6 +151,18 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad"
|
||||
integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==
|
||||
|
||||
"@types/lodash.chunk@^4.2.6":
|
||||
version "4.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash.chunk/-/lodash.chunk-4.2.6.tgz#9d35f05360b0298715d7f3d9efb34dd4f77e5d2a"
|
||||
integrity sha512-SPlusB7jxXyGcTXYcUdWr7WmhArO/rmTq54VN88iKMxGUhyg79I4Q8n4riGn3kjaTjOJrVlHhxgX/d7woak5BQ==
|
||||
dependencies:
|
||||
"@types/lodash" "*"
|
||||
|
||||
"@types/lodash@*":
|
||||
version "4.14.170"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.170.tgz#0d67711d4bf7f4ca5147e9091b847479b87925d6"
|
||||
integrity sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q==
|
||||
|
||||
"@types/needle@^2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/needle/-/needle-2.5.1.tgz#2923f4a63a66048aed3038d76e8bc83b6905c784"
|
||||
|
@ -1508,6 +1520,11 @@ lodash.camelcase@^4.3.0:
|
|||
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
||||
integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
|
||||
|
||||
lodash.chunk@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.chunk/-/lodash.chunk-4.2.0.tgz#66e5ce1f76ed27b4303d8c6512e8d1216e8106bc"
|
||||
integrity sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw=
|
||||
|
||||
lodash.clonedeep@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
||||
|
|
Loading…
Add table
Reference in a new issue