2021-05-31 21:12:24 +02:00
|
|
|
import {
|
|
|
|
APIMessage,
|
|
|
|
Client,
|
|
|
|
Message,
|
|
|
|
MessageOptions,
|
|
|
|
MessageReaction,
|
|
|
|
PartialUser,
|
|
|
|
TextChannel,
|
|
|
|
User,
|
|
|
|
} from "discord.js";
|
2020-12-15 15:13:31 +02:00
|
|
|
import { Awaitable } from "knub/dist/utils";
|
|
|
|
import { MINUTES, noop } from "../utils";
|
|
|
|
import Timeout = NodeJS.Timeout;
|
|
|
|
|
2021-05-31 21:12:24 +02:00
|
|
|
export type LoadPageFn = (page: number) => Awaitable<MessageOptions>;
|
2020-12-15 15:13:31 +02:00
|
|
|
|
|
|
|
export interface PaginateMessageOpts {
|
|
|
|
timeout: number;
|
|
|
|
limitToUserId: string | null;
|
|
|
|
}
|
|
|
|
|
|
|
|
const defaultOpts: PaginateMessageOpts = {
|
|
|
|
timeout: 5 * MINUTES,
|
|
|
|
limitToUserId: null,
|
|
|
|
};
|
|
|
|
|
|
|
|
export async function createPaginatedMessage(
|
|
|
|
client: Client,
|
2021-05-31 03:30:55 +02:00
|
|
|
channel: TextChannel | User,
|
2020-12-15 15:13:31 +02:00
|
|
|
totalPages: number,
|
|
|
|
loadPageFn: LoadPageFn,
|
|
|
|
opts: Partial<PaginateMessageOpts> = {},
|
|
|
|
): Promise<Message> {
|
|
|
|
const fullOpts = { ...defaultOpts, ...opts } as PaginateMessageOpts;
|
|
|
|
const firstPageContent = await loadPageFn(1);
|
2021-05-31 21:12:24 +02:00
|
|
|
const message = await channel.send({ content: firstPageContent });
|
2020-12-15 15:13:31 +02:00
|
|
|
|
|
|
|
let page = 1;
|
|
|
|
let pageLoadId = 0; // Used to avoid race conditions when rapidly switching pages
|
2021-05-31 03:30:55 +02:00
|
|
|
const reactionListener = async (reactionMessage: MessageReaction, reactor: User | PartialUser) => {
|
|
|
|
if (reactionMessage.message.id !== message.id) {
|
2020-12-15 15:13:31 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fullOpts.limitToUserId && reactor.id !== fullOpts.limitToUserId) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-05-31 03:30:55 +02:00
|
|
|
if (reactor.id === client.user!.id) {
|
2020-12-15 15:13:31 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let pageDelta = 0;
|
2021-05-31 03:30:55 +02:00
|
|
|
if (reactionMessage.emoji.name === "⬅️") {
|
2020-12-15 15:13:31 +02:00
|
|
|
pageDelta = -1;
|
2021-05-31 03:30:55 +02:00
|
|
|
} else if (reactionMessage.emoji.name === "➡️") {
|
2020-12-15 15:13:31 +02:00
|
|
|
pageDelta = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!pageDelta) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const newPage = Math.max(Math.min(page + pageDelta, totalPages), 1);
|
|
|
|
if (newPage === page) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
page = newPage;
|
|
|
|
const thisPageLoadId = ++pageLoadId;
|
|
|
|
const newPageContent = await loadPageFn(page);
|
|
|
|
if (thisPageLoadId !== pageLoadId) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
message.edit(newPageContent).catch(noop);
|
2021-05-31 03:30:55 +02:00
|
|
|
reactionMessage.users.remove(reactor.id).catch(noop);
|
2020-12-15 15:13:31 +02:00
|
|
|
refreshTimeout();
|
|
|
|
};
|
|
|
|
client.on("messageReactionAdd", reactionListener);
|
|
|
|
|
|
|
|
// The timeout after which reactions are removed and the pagination stops working
|
|
|
|
// is refreshed each time the page is changed
|
|
|
|
let timeout: Timeout;
|
|
|
|
const refreshTimeout = () => {
|
|
|
|
clearTimeout(timeout);
|
|
|
|
timeout = setTimeout(() => {
|
2021-05-31 03:30:55 +02:00
|
|
|
message.reactions.removeAll().catch(noop);
|
2020-12-15 15:13:31 +02:00
|
|
|
client.off("messageReactionAdd", reactionListener);
|
|
|
|
}, fullOpts.timeout);
|
|
|
|
};
|
|
|
|
|
|
|
|
refreshTimeout();
|
|
|
|
|
|
|
|
// Add reactions
|
2021-05-31 03:30:55 +02:00
|
|
|
message.react("⬅️").catch(noop);
|
|
|
|
message.react("➡️").catch(noop);
|
2020-12-15 15:13:31 +02:00
|
|
|
|
|
|
|
return message;
|
|
|
|
}
|