Skip to content

A simple (or advanced - it's your choice) utility to paginate discord embeds. Configure, customise, and control!

License

Notifications You must be signed in to change notification settings

psibean/discord.js-pagination

 
 

Repository files navigation

NPM info

discord.js-pagination

A simple utility (or advanced - it's your choice) to paginate discord embeds. Built on discord.js@^13.0.0.

To see how the example paginations look, checkout the example bot (the readme has gifs)!

Key Features

  • Custom emoji reactions with the ReactionPaginator (see the example app).
  • Custom button pagination with the ButtonPaginator (see the example app).
  • Custom select menu pagination with the SelectPaginator (see the example app).
  • If you have a global interaction listener you can ignore paginator interactions by checking: interaction.customId.startsWith("paginator") - this prefix is customizable, set it on all of your paginators.
  • Multiple Action Row (combined select and button) pagination!

Table of Contents

Installation

  • npm install @psibean/discord.js-pagination

Updating

To update from version 3 to version 4 you may want to take a look at the difference between the two versions in the example app.

However, for SIMPLE cases, you likely just need to move your pages parameter into the options object in the constructor call, see the basic usage below!

If you want to make use of the dynamic embed creation you'll want to learn how to use the identifiersResolver and the pageEmbedResolver, read their descriptions below and check the example app for further demonstration.

Basic Usage

For all paginators, the default messageSender will call interaction#editReply then return interaction#fetchReply, this means your command (as per the example bot) must call interaction#deferReply.

You should use interaction#deferReply when you receive an interaction that will send an embed. The paginators default messageSender uses interaction#editReply. If you do not wish to do this you'll need to pass in your own messageSender.

For the below examples, the pages can be constructed as per the example bot:

const pages = [];
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}`);
  pages.push(pageEmbed);
}

You will of course want to construct your own pages.

For more complex usage see the example bot.

ReactionPaginator

This allows pages to be navigated by reacting to a message.

const { PaginatorEvents, ReactionPaginator } = require('@psibean/discord.js-pagination');

const reactionPaginator = new ReactionPaginator(interaction, { pages })
	.on(PaginatorEvents.COLLECT_ERROR, ({ error }) => console.log(error));
await reactionPaginator.send();

ButtonPaginator

This allows pages to be navigated by clicking buttons.

const { ButtonPaginator } = require('@psibean/discord.js-pagination');

const buttonPaginator = new ButtonPaginator(interaction, { pages });
await buttonPaginator.send();

SelectPaginator

This allows pages to be navigated using a select menu.

For the select pagination embed you'll need to supply an array of MessageSelectOptions to the pagination options via selectOptions. By default the pagination will map the value of the provided options to your pages index based on their ordering. Then the page change will be determined by the selected value and this mapping. If you want to map your select options and pages differently you can provide the pagesMap ({ selectOptions, paginator }) function which should return a dictionary. You'll then want to provide a custom pageIndexResolver.

const { SelectPaginator } = require('@psibean/discord.js-pagination');

// The default pagesMap will map these option values to the index
const selectOptions = [];
for (let i = 0; i < 10; i++)
	selectOptions.push({
		label: `"Page #${i + 1}`,
		value: `${i}`,
		description: `This will take you to page #${i + 1}`
	});

const selectPaginator = new SelectPaginator(interaction, pages);
await selectPaginator.send();

Types

PaginatorIdentifiers

This object is returned by the identifiersResolver and can contain any number of identifiers but at bare minimum it must contain a pageIdentifier.

{ ...args, pageIdentifier: string | number }

  • args: Any additional identifiers you wish to use.
  • pageIdentifier: Used as a unique identifier to resolve a page

PaginatorCollectorArgs

{ ...args, paginator }

  • args: All arguments provided by the collect listener.
  • paginator : This is a reference to the paginator instance.

For the ReactionCollector this is:

{ reaction, user, paginator }

For ActionRowPaginator, ButtonPaginator and SelectPaginator this is:

{ interaction, paginator }

ChangePageArgs

{ collectorArgs, previousIdentifiers, currentIdentifiers, newIdentifiers, paginator }

Paginator Properties

The paginator has a variety of properties you can acces -and should, especially if you're customising!

Properties can be accessed via paginator#propertyName

Base Properties

These properties are common to all paginators.

  • client : The client that instantiated the paginator.
  • interaction : The interaction that initiated the instantiation of the paginator.
  • user : The user who sent the interaction that instantiated the paginator.
  • channel : The channel where the interaction came from, this is also where the paginator message will be sent.
  • pages : The cache of MessageEmbeds mapped by their respective PaginationIdentifiers#pageIdentifier.
    • If pages are provided in the options of the constructor, this will be all of the pages provided mapped by their index.
  • initialIdentifiers : The initial PaginatorIdentifiers provided in the paginator options.
  • numberOfPages : The number of pages.
  • previousPageIdentifiers : The PaginatorIdentifiers from the previous navigation, null indicates no page change yet.
  • currentPageIdentifiers : The PaginatorIdentifiers of the current page.
  • options : The options provided to the paginator after merging defaults.

ActionRowPaginator Properties

These properties are common to the ButtonPaginator and SelectPaginator.

  • customIdPrefix : The customIdPrefix provided in options. Used to prefix all MessageComponent#customId's.
  • customIdSuffix : The interaction#id. Used to suffix all MessageComponent#customId's.

SelectPaginator Properties

These properties are specific to the SelectPaginator.

  • selectMenu : The select menu on the paginator message action row.

Paginator Options

The following options are available for the respective paginators.

Base Options

All paginators can take the options of their respective collector, see discord.js documentation for those. Instead of filter, provide collectorFilter. The options listed here are available to all paginators.

messageSender

The function used to send the intial page, it returns the message.

({ interaction, messageOptions }): Promise<Message> | Message

  • interaction : This is a reference to the initial interaction that instantiated the paginator.
  • messageOptions : The MessageOptions for the initial page.

Defaults to:

	messageSender: async ({ interaction, messageOptions }) => {
		await paginator.interaction.editReply(messageOptions);
		return paginator.interaction.fetchReply();
	}

pageEmbedResolver

This is used to determine the current pages embed based on the change page args.

(changePageArgs: ChangePageArgs): Promise<MessageEmbed> | MessageEmbed

If using cache this function will only be called if the embed isn't already in the cache and the embed will be added to the cache mapped to it's corresponding pageIdentifier.

useCache

Whether or not embeds should be cached, keyed by their pageIdentifier.

Defaults to true.

maxPageCache

The maximum number of pages that should be cached. If this number is reached the cache will be emptied before a new page is cached.

Defaults to 100.

messageOptionsResolver

This function is used to determine additional MessageOptions for the page being changed to.

(options: ChangePageArgs): Promise<MessageOptions> | MessageOptions

Defaults to returning an empty object. Optional.

collectorOptions

This is the options that will be passed to the collector when it is created.

For the ReactionPaginator, defaults to:

{
	idle: 6e4,
	filter: ({ reaction, user, paginator }) =>
		user === paginator.user && paginator.emojiList.includes(reaction.emoji.name) && !user.bot,
}

For the ActionRowPaginator, ButtonPaginator and SelectPaginator, defaults to:

{
	idle: 6e4,
	filter: ({ interaction, paginator }) =>
		interaction.isMessageComponent() &&
		interaction.component.customId.startsWith(paginator.customIdPrefix) &&
		interaction.user === paginator.user &&
		!interaction.user.bot,
	},
}

identifiersResolver

This is the function used to determine the new identifiers object based on the interaction that occurred. The object must contain a pageIdentifier but can otherwise contain anything else.

(collectorArgs: PaginatorCollectorArgs): Promise<PaginatorIdentifiers> | PaginatorIdentifiers

Whatever this function returns will be used as the newIdentifiers object for it's collect event.

For the ReactionPaginator, defaults to:

({ reaction, paginator }) => {
	let { pageIdentifier } = paginator.currentIdentifiers;
	switch (reaction.emoji.name) {
		case paginator.emojiList[0]:
			pageIdentifier -= 1;
			break;
		case paginator.emojiList[1]:
			pageIdentifier += 1;
			break;
	}
	// The default identifier is a cyclic index.
	if (pageIdentifier < 0) {
		pageIdentifier = paginator.maxNumberOfPages + (pageIdentifier % paginator.maxNumberOfPages);
	} else if (pageIdentifier >= paginator.maxNumberOfPages) {
		pageIdentifier %= paginator.maxNumberOfPages;
	}
	return { ...paginator.currentIdentifiers, pageIdentifier };
}

For the ButtonPaginator, defaults to:

({ interaction, paginator }) => {
	const val = interaction.component.label.toLowerCase();
	let { pageIdentifier } = paginator.currentIdentifiers;
	if (val === 'previous') pageIdentifier -= 1;
	else if (val === 'next') pageIdentifier += 1;
	// The default identifier is a cyclic index.
	if (pageIdentifier < 0) {
		pageIdentifier = paginator.maxNumberOfPages + (pageIdentifier % paginator.maxNumberOfPages);
	} else if (pageIdentifier >= paginator.maxNumberOfPages) {
		pageIdentifier %= paginator.maxNumberOfPages;
	}
	return { ...paginator.currentIdentifiers, pageIdentifier };
}

For the SelectPaginator, defaults to:

({ interaction, paginator }) => {
	const [selectedValue] = interaction.values;
	return { ...paginator.currentIdentifiers, pageIdentifier: parseInt(selectedValue) };
},

shouldChangePage

This is the function used to determine whether or not a page change should occur during a collect event. If not provided, a page change will always occur during a collect event.

(args: ChangePageArgs): Promise<boolean> | boolean

Defaults to:

({ newIdentifiers, currentIdentifiers, paginator }) =>
		paginator.message.deletable && newIdentifiers.pageIdentifier !== currentIdentifiers.pageIdentifier,

footerResolver

This is the function used to set the footer of each page. If not provided the embeds will use whatever footers were originally set on them.

(paginator): Promise<string> | string

  • paginator : This is a reference to the paginator instance.

Defaults to undefined. Optional.

initialIdentifiers

This is the initial PageIdentifiers which is passed in as the newIdentifiers in the very first send.

ReactionPaginator Options

These options are specific to the ReactionPaginator.

emojiList

An array of emoji resolvables to use as reactions.

Defaults to: ['⏪', '⏩']

ActionRowPaginator Options

These options are common to the ActionRowPaginator, ButtonPaginator, and SelectPaginator.

messageActionRows

An array of messageActionRows (they will have their type set to ACTION_ROW). Any components provided up front will have their customId generated.

Defaults to:

[
	{
		type: 'ACTION_ROW',
		components: [],
	},
],

customIdPrefix

This is used to prefix all of the MessageComponent#customId's.

Defaults to: "paginator"

ButtonPaginator Options

These options are specific to the ButtonPaginator.

buttons

An array of MessageButtonOptions which will be added to the paginators action row. Can be skipped if you're degining the ActionRowPaginator#messageActionRows option. Otherwise these will be added to either the first action row, or added based on a row property.

Note:

  • customId is not required, the paginator will update the customId to be: <prefix>-[<customId>-]<suffix> where the suffix is the id of the received interaction that initiated the paginator.
  • type will be updated to BUTTON
  • style will default to PRIMARY if not set

Defaults to:

[
	{
		label: 'Previous',
	},
	{
		label: 'Next',
	}
],

SelectPaginator Options

These options are specific to the SelectPaginator.

selectOptions

An array of MessageSelectOptions to be added to the select menu.

Note:

  • customId is not required, the paginator will update the customId to be: <prefix>-[<customId>-]<suffix> where the prefix is paginator#customIdPrefix and the suffix is the id of the received interaction that initiated the paginator.

pagesMap

A function that returns a dictionary, or a dictionary. The dictionary should provide a way to map the provided selectOptions to your provided pages.

If a function:

({ selectOptions, paginator}): Dictionary<K, V>

  • selectOptions : The selectOptions provided to the paginator after having their customId updated.
  • paginator : This is a reference to the paginator instance.

Paginator Events

Events can be imported by:

const { PaginatorEvents } = require('@psibean/discord.js-pagination');

And accessed by PaginatorEvents#EventName

To listen to an event:

paginator.on(PaginatorEvents#EventName, eventHandler);

All paginators have the following events (by EventName):

BEFORE_PAGE_CHANGED

'beforePageChanged'

This event is raised in the paginator collectors collect event before the message is edited with a new page.

Parameters: ({ ...*, newPageIdentifier, currentPageIdentifier, paginator })

  • ...* : Includes any args provided by the collect listener.
  • newPageIdentifier : This is the index of the page being changed to.
  • currentPageIdentifier : This is the index of the current page (before being changed).
  • paginator : This is a reference to the paginator instance.

COLLECT_END

'collectEnd'

This event is raised at the end of each collect handled by the paginator collector. Note that this won't be called if an error is thrown or if the paginator does not change page. Use the COLLECT_ERROR and PAGE_CHANGED events respectively in those cases.

Parameters: ({ error, paginator })

  • error : This is the error that was caught.
  • paginator : This is a reference to the paginator instance.

COLLECT_ERROR

'collectError'

This event is raised when an error occurs within the collect event of the paginators collector.

Parameters: ({ error, paginator })

  • error : This is the error that was caught.
  • paginator : This is a reference to the paginator instance.

COLLECT_START

'collectStart'

This event is raised when an error occurs within the collect event of the paginators collector.

Parameters: ({ error, paginator })

  • error : This is the error that was caught.
  • paginator : This is a reference to the paginator instance.

PAGE_CHANGED

'pageChanged'

This event is raised in the paginator collectors collect event after the message is edited with a new page

Parameters: ({ ...*, newPageIdentifier, currentPageIdentifier, paginator })

  • ...* : Includes any args provided by the collect listener.
  • newPageIdentifier : This is the index of the page being changed to.
  • currentPageIdentifier : This is the index of the current page (before being changed).
  • paginator : This is a reference to the paginator instance.

It should be noted: paginator#currentPageIdentifier, paginator#previousPageIdentifier and paginator#currentPage will now reflect values based on the newPageIdentifier, which is not the case for BEFORE_PAGE_CHANGED.

PAGE_UNCHANGED

'pageUnchanged'

This event is raised in the paginator collectors collect event when paginator#shouldChangePage has returned false. For example if the new page index matches the current index, a collect event occurred but did not cause a page change.

Parameters: ({ ...*, newPageIdentifier, currentPageIdentifier, paginator })

  • ...* : Includes any args provided by the collect listener.
  • newPageIdentifier : This is the index of the page being changed to.
  • currentPageIdentifier : This is the index of the current page (before being changed).
  • paginator : This is a reference to the paginator instance.

PAGINATION_END

'paginationEnd'

This event is raised in the paginators collector end event.

Parameters: ({ collected, reason, paginator })

  • collected : The elements collected by the paginators collector.
  • reason : The reason the paginators collector ended.
  • paginator : This is a reference to the paginator instance.

PAGINATION_READY

'paginationReady'

This event is raised once the paginators collector has been setup and the message has been sent with the starting page.

Parameters: (paginator)

  • paginator : This is a reference to the paginator instance.

About

A simple (or advanced - it's your choice) utility to paginate discord embeds. Configure, customise, and control!

Resources

License

Stars

Watchers

Forks

Languages

  • JavaScript 100.0%