Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Full V13 support: Interactions (buttons and select menus) and reactions! #17

Open
wants to merge 41 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
959f7b6
Replace unary operators
psibean Jan 17, 2021
cc73965
Add custom footer and page resolvers, shift all optional and default …
psibean Jan 18, 2021
0e71be1
Ignore vscode
psibean Jan 18, 2021
12a3845
Remove the now redundant emojiList length check
psibean Jan 18, 2021
1c6ce1a
Expose user on the collect event, await the reaction removal.
psibean Jan 18, 2021
914781d
Remove unnecessary footerResolver param comment line
psibean Jan 18, 2021
57d0605
Prevent crash if message is deleted before reactions are applied.
psibean Jan 18, 2021
92b9cc8
Always filter against bots, always ensure emoji is in list, apply cus…
psibean Jan 18, 2021
e9308fd
Shift collection reactor setup prior to the bot adding reactions.
psibean Jan 18, 2021
94018c4
Expose the message to the pageResolver
psibean Jan 18, 2021
9f65ab3
Mark resolvers optional in typings
psibean Jan 18, 2021
1008e1d
Pass in the correct message to the pageResolver
psibean Jan 18, 2021
25ccae7
Fix emoji list typing
psibean Jan 18, 2021
a076510
Additional page message delete guard
psibean Jan 18, 2021
9350fca
Typing syntax ',' to ';', add sendMessage property of PaginationOptions.
psibean Jan 19, 2021
d03d148
Remove uinnecessary deleted check and try/catch, add defaultCollector…
psibean Jan 19, 2021
c9947e3
Add missing async, fix paghe > page typo
psibean Jan 19, 2021
aa984dc
Rename page > currentPageIndex
psibean Jan 20, 2021
770615c
Rename curPage > paginatedEmbedMessage
psibean Jan 20, 2021
0515379
Remove useUtils from PaginationOptions, type parameter name clarifica…
psibean Jan 20, 2021
06bfba1
Add collectErrorHandler
psibean Jan 20, 2021
db34587
Update example usage
psibean Jan 20, 2021
637147b
Remove redundant useUtil
psibean Jan 20, 2021
c5fcf45
Remove unnecessary explicit async/await.
psibean Jan 20, 2021
d347174
Re-add MessageEmbed require to example
psibean Jan 20, 2021
62a7c1b
Fix indentation
psibean Jan 20, 2021
4a221d9
Fix indentation
psibean Jan 20, 2021
a0087cc
amend merge
psibean Jan 20, 2021
1646e3a
Update index.js
psibean Jan 20, 2021
5b56e3a
Fix example usage syntax error
psibean Jan 20, 2021
8b71d00
Merge branch 'master' of https://github.com/psibean/discord.js-pagina…
psibean Jan 20, 2021
8091a75
Add deleted guard on end (in case ended by other means)
psibean Jan 22, 2021
9ee632a
Destructure params - optional, delegate end handler, expose messages …
psibean Feb 25, 2021
9d17bd3
Update error handler example
psibean Feb 25, 2021
44a4b0c
Fix collector filter typing, adjust consistent syntax
psibean Feb 25, 2021
1641156
Update with initial discordjs v13 support
psibean Aug 7, 2021
ab92780
Update index typings
psibean Aug 7, 2021
1f9349a
Update readme installation and version support information
psibean Aug 7, 2021
cbaf404
Bump major
psibean Aug 7, 2021
97fcb6d
Fix async/await defaults, fix collectorFilter destruct params
psibean Aug 7, 2021
4f774fe
Add missing filter return, add interaction typing
psibean Aug 8, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,6 @@ typings/

# next.js build output
.next

# vscode
.vscode/
29 changes: 18 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ A simple utility to paginate discord embeds. Built on discord.js@^12.0.0 (master
* `npm install discord.js-pagination`

# Usage

__Basic Bot Example__
```js
// Import the discord.js-pagination package
Expand All @@ -21,20 +22,26 @@ const paginationEmbed = require('discord.js-pagination');
// Use either MessageEmbed or RichEmbed to make pages
// Keep in mind that Embeds should't have their footers set since the pagination method sets page info there
const { MessageEmbed } = require('discord.js');
const embed1 = new MessageEmbed();

// Create an array of embeds
pages = [
embed1,
embed2,
//....
embedn
];
const myPages = [];

for (let i = 0; i < 10; i++) {
const pageEmbed = new MessageEmbed();
pageEmbed
.setTitle(`This embed is index ${i}!`)
.setDescription(`That means it is page #${i+1}`);
myPages.push(pageEmbed);
}

const footerResolver = (currentPageIndex, pagesLength) =>
`Page ${currentPageIndex + 1} / ${pagesLength}: ${(currentPageIndex % 2 === 0) ? 'This page is even!' : 'This page is odd!'}`;

const collectErrorHandler = ({ error }) => console.log(error);

// Call the paginationEmbed method, first two arguments are required
// emojiList is the pageturners defaults to ['⏪', '⏩']
// timeout is the time till the reaction collectors are active, after this you can't change pages (in ms), defaults to 120000
paginationEmbed(msg, pages, emojiList, timeout);
// The third argument is the PaginationOptions - all optional
// Any additional arguments in PaginationOptions are passed down as ReactionCollectorOptions
paginationEmbed(msg, myPages, { emojiList, footerResolver, collectErrorHandler, timeout: 120000, idle: 60000 });
// There you go, now you have paged embeds
```
# Preview
Expand Down
44 changes: 44 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { EmojiIdentifierResolvable, Message, MessageEmbed, MessageReaction, ReactionCollector, ReactionCollectorOptions, User } from "discord.js";

interface PageResolverParameters {
paginatedEmbedMessage?: MessageEmbed;
pages?: MessageEmbed[];
emojiList?: string[];
currentPageIndex?: number;
reaction?: MessageReaction;
}

interface CollectorFilterParameters {
reaction?: MessageReaction;
user?: User;
emojiList: string[];
}

interface BaseHandlerParameters {
receivedMessage?: Message;
paginatedEmbedMessage?: MessageEmbed;
}

interface CollectErrorHandlerParameters extends BaseHandlerParameters {
error?: Error;
reactionCollector?: ReactionCollector;
}

interface CollectEndHandlerParameters extends BaseHandlerParameters {
collected?: Collection<Snowflake, MessageReaction>;
reason?: string;
}

interface PaginationOptions extends ReactionCollectorOptions {
emojiList?: EmojiIdentifierResolvable[];
footerResolver?(pageIndex: number, pagesLength: number): string;
sendMessage?(receivedMessage: Message, pageEmbed: MessageEmbed): Promise<Message>;
collectorFilter?(collectorFilterParameters?: CollectorFilterParameters): boolean | Promise<boolean>;
pageResolver?(pageResolverParameters?: PageResolverParameters): number | Promise<number>;
collectErrorHandler?(collectErrorHandlerParameters?: CollectErrorHandlerParameters): void | Promise<void>;
collectorEndHandler?(collectorEndHandlerParameters?: CollectEndHandlerParameters): void | Promise<void>;
}

declare function paginationEmbed(receivedMessage: Message, pages: MessageEmbed[], paginationOptions?: PaginationOptions): Promise<Message>;

export = paginationEmbed;
116 changes: 85 additions & 31 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,87 @@
const paginationEmbed = async (msg, pages, emojiList = ['⏪', '⏩'], timeout = 120000) => {
if (!msg && !msg.channel) throw new Error('Channel is inaccessible.');
if (!pages) throw new Error('Pages are not given.');
if (emojiList.length !== 2) throw new Error('Need two emojis.');
let page = 0;
const curPage = await msg.channel.send(pages[page].setFooter(`Page ${page + 1} / ${pages.length}`));
for (const emoji of emojiList) await curPage.react(emoji);
const reactionCollector = curPage.createReactionCollector(
(reaction, user) => emojiList.includes(reaction.emoji.name) && !user.bot,
{ time: timeout }
);
reactionCollector.on('collect', reaction => {
reaction.users.remove(msg.author);
switch (reaction.emoji.name) {
case emojiList[0]:
page = page > 0 ? --page : pages.length - 1;
break;
case emojiList[1]:
page = page + 1 < pages.length ? ++page : 0;
break;
default:
break;
}
curPage.edit(pages[page].setFooter(`Page ${page + 1} / ${pages.length}`));
});
reactionCollector.on('end', () => {
if (!curPage.deleted) {
curPage.reactions.removeAll()
}
});
return curPage;
/**
*
* @param {MessageEmbed[]} pages
* @param {EmojiIdentifierResolvable[]} emojiList
* @param {number} currentPageIndex
* @param {MessageReaction} reaction
* @returns {number} - the new page index.
*/
const defaultPageResolver = async ({ pages, emojiList, currentPageIndex, reaction }) => {
let newPage = currentPageIndex;
switch (reaction.emoji.name) {
case emojiList[0]:
newPage = currentPageIndex > 0 ? currentPageIndex - 1 : pages.length - 1;
break;
case emojiList[1]:
newPage = currentPageIndex + 1 < pages.length ? currentPageIndex + 1 : 0;
break;
default:
return currentPageIndex;
}
return newPage;
};

const defaultFooterResolver = (currentPageIndex, pagesLength) => `Page ${currentPageIndex + 1} / ${pagesLength}`;

const defaultSendMessage = (message, pageEmbed) => message.channel.send(pageEmbed);

const defaultCollectorFilter = ({ reaction, user, emojiList }) => emojiList.includes(reaction.emoji.name) && !user.bot;

const defaultCollectorEndHandler = ({ paginatedEmbedMessage }) => {
if (!paginatedEmbedMessage.deleted)
await paginatedEmbedMessage.reactions.removeAll();
}

/**
*
* @param {Message} receivedMessage - the received message
* @param {MessageEmbed[]} pages - array of message embeds to use as each page.
* @param {PaginationOptions} paginationOptions - exposes collector options, provides customization.
*/
const paginationEmbed = async (receivedMessage, pages,
{
emojiList = ['⏪', '⏩'],
footerResolver = defaultFooterResolver,
sendMessage = defaultSendMessage,
collectorFilter = defaultCollectorFilter,
pageResolver = defaultPageResolver,
collectErrorHandler = () => {},
collectorEndHandler = defaultCollectorEndHandler,
timeout = 120000,
...rest
} = {}
) => {
if (!receivedMessage && !receivedMessage.channel) throw new Error('Channel is inaccessible.');
if (!pages) throw new Error('Pages are not given.');
let currentPageIndex = 0;
pages[currentPageIndex].setFooter(footerResolver(currentPageIndex, pages.length));
const paginatedEmbedMessage = await sendMessage(receivedMessage, pages[currentPageIndex]);
const reactionCollector = paginatedEmbedMessage.createReactionCollector(
async (reaction, user) => {
await collectorFilter(reaction, user, emojiList)
},
{ time: timeout, ...rest }
);
reactionCollector.on('collect', async (reaction, user) => {
// this try / catch is to handle the edge case where a collect event is fired after a message delete call
// but before the delete is complete, handling is offloaded to the user via collectErrorHandler
try {
await reaction.users.remove(user.id);
const currentPage = currentPageIndex;

currentPageIndex = await pageResolver({ paginatedEmbedMessage, pages, emojiList, currentPageIndex, reaction });
if ( !paginatedEmbedMessage.deleted && currentPage != currentPageIndex && currentPageIndex >= 0 && currentPageIndex < pages.length)
await paginatedEmbedMessage.edit(pages[currentPageIndex].setFooter(footerResolver(currentPageIndex, pages.length)));
} catch(error) {
await collectErrorHandler({ error, receivedMessage, paginatedEmbedMessage });
}
});
reactionCollector.on('end', async (collected, reason) => {
await collectorEndHandler({ collected, reason, paginatedEmbedMessage, receivedMessage });
});
for (const emoji of emojiList)
await paginatedEmbedMessage.react(emoji);
return paginatedEmbedMessage;
};

module.exports = paginationEmbed;
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "1.0.3",
"description": "A simple utility to paginate discord embeds. ",
"main": "index.js",
"types": "index.d.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
Expand Down