Ephemeral PaginatedMessages are unusable

https://ss.clanware.org/CdquvjYO.png It always says that it's someone else's message. @sapphire/framework@npm:4.7.2 @sapphire/discord.js-utilities@npm:7.0.2
Solution:
Unfollowing this because it's fixed in @sapphire/discord.js-utilities@7.1.2
Jump to solution
48 Replies
Lioness100
Lioness100โ€ข11mo ago
Please show your code ๐Ÿ™‚
disclosuure
disclosuureโ€ข11mo ago
The code for generating the embeds etc is 500 lines long but heres a relevant snippet
const embeds = [userInfoEmbed, accountsEmbed, evidenceEmbed, revisionsEmbed];

const page = new PaginatedMessage({
template: templateEmbed,
actions: [
{
customId: 'case_overview',
style: ButtonStyle.Secondary,
type: ComponentType.Button,
emoji: '๐Ÿ”Ž',
label: 'Overview',
run(context) {
context.handler.index = 0;
}
},
{
customId: 'case_accounts',
style: ButtonStyle.Secondary,
type: ComponentType.Button,
emoji: '๐Ÿ‘ฅ',
label: 'Accounts',
run(context) {
context.handler.index = 1;
}
},
{
customId: 'case_evidence',
style: ButtonStyle.Secondary,
type: ComponentType.Button,
emoji: '๐Ÿ“',
label: 'Evidence',
run(context) {
context.handler.index = 2;
}
},
{
customId: 'case_revisions',
style: ButtonStyle.Secondary,
type: ComponentType.Button,
emoji: '๐Ÿ“',
label: 'Revisions',
run(context) {
context.handler.index = 3;
}
}
]
});

for (const embed of embeds) {
page.addPageEmbed(embed);
}

return page.run(interaction);
const embeds = [userInfoEmbed, accountsEmbed, evidenceEmbed, revisionsEmbed];

const page = new PaginatedMessage({
template: templateEmbed,
actions: [
{
customId: 'case_overview',
style: ButtonStyle.Secondary,
type: ComponentType.Button,
emoji: '๐Ÿ”Ž',
label: 'Overview',
run(context) {
context.handler.index = 0;
}
},
{
customId: 'case_accounts',
style: ButtonStyle.Secondary,
type: ComponentType.Button,
emoji: '๐Ÿ‘ฅ',
label: 'Accounts',
run(context) {
context.handler.index = 1;
}
},
{
customId: 'case_evidence',
style: ButtonStyle.Secondary,
type: ComponentType.Button,
emoji: '๐Ÿ“',
label: 'Evidence',
run(context) {
context.handler.index = 2;
}
},
{
customId: 'case_revisions',
style: ButtonStyle.Secondary,
type: ComponentType.Button,
emoji: '๐Ÿ“',
label: 'Revisions',
run(context) {
context.handler.index = 3;
}
}
]
});

for (const embed of embeds) {
page.addPageEmbed(embed);
}

return page.run(interaction);
page.run(interaction) refers to a button interaction (when they hit 'View case') Note that when I use a slash command to select the case, this works perfectly still need help
Sawako
Sawakoโ€ข11mo ago
@Favna
disclosuure
disclosuureโ€ข11mo ago
๐Ÿ™
Sawako
Sawakoโ€ข11mo ago
Tag fav a @Favna
Favna
Favnaโ€ข11mo ago
I see the pings btw. I I've just been very busy I'll go through the posts not yet marked as solved in due time so to recap @cosigyn - You send something that has a button - When user clicks a button you create a new PaginatedMessage. So this is in a class that extends InteractionHandler. - You pass the interaction from the button to the run function is that correct?
disclosuure
disclosuureโ€ข11mo ago
Yes
Favna
Favnaโ€ข11mo ago
I dumbed it down to this: command.ts
import { Command } from '@sapphire/framework';
import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';

export class UserCommand extends Command {
public constructor(context: Command.Context) {
super(context, {
name: 'cip',
description: 'A basic chat input command that pings'
});
}

public override registerApplicationCommands(registry: Command.Registry) {
registry.registerChatInputCommand((builder) =>
builder //
.setName(this.name)
.setDescription(this.description)
);
}

public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
const btn = new ButtonBuilder().setCustomId('my-button').setLabel('Click me').setStyle(ButtonStyle.Primary);
const ar = new ActionRowBuilder<ButtonBuilder>().setComponents(btn);

return interaction.reply({
components: [ar],
ephemeral: true
});
}
}
import { Command } from '@sapphire/framework';
import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';

export class UserCommand extends Command {
public constructor(context: Command.Context) {
super(context, {
name: 'cip',
description: 'A basic chat input command that pings'
});
}

public override registerApplicationCommands(registry: Command.Registry) {
registry.registerChatInputCommand((builder) =>
builder //
.setName(this.name)
.setDescription(this.description)
);
}

public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
const btn = new ButtonBuilder().setCustomId('my-button').setLabel('Click me').setStyle(ButtonStyle.Primary);
const ar = new ActionRowBuilder<ButtonBuilder>().setComponents(btn);

return interaction.reply({
components: [ar],
ephemeral: true
});
}
}
button-handler.ts
import { ApplyOptions } from '@sapphire/decorators';
import { PaginatedMessage } from '@sapphire/discord.js-utilities';
import { InteractionHandler, InteractionHandlerTypes } from '@sapphire/framework';
import { ButtonInteraction, ButtonStyle, ComponentType } from 'discord.js';

@ApplyOptions<InteractionHandler.Options>({
interactionHandlerType: InteractionHandlerTypes.Button
})
export class SapphireButtonHandler extends InteractionHandler {
override run(interaction: ButtonInteraction) {
const page = new PaginatedMessage()
.setActions([
{
customId: 'case_overview',
style: ButtonStyle.Secondary,
type: ComponentType.Button,
emoji: '๐Ÿ”Ž',
label: 'Overview',
run(context) {
context.handler.index = 0;
}
},
{
customId: 'case_accounts',
style: ButtonStyle.Secondary,
type: ComponentType.Button,
emoji: '๐Ÿ‘ฅ',
label: 'Accounts',
run(context) {
context.handler.index = 1;
}
},
{
customId: 'case_evidence',
style: ButtonStyle.Secondary,
type: ComponentType.Button,
emoji: '๐Ÿ“',
label: 'Evidence',
run(context) {
context.handler.index = 2;
}
},
{
customId: 'case_revisions',
style: ButtonStyle.Secondary,
type: ComponentType.Button,
emoji: '๐Ÿ“',
label: 'Revisions',
run(context) {
context.handler.index = 3;
}
}
])
.addPageContent('Page 1')
.addPageContent('Page 2');

return page.run(interaction);
}

public override async parse(interaction: ButtonInteraction) {
if (interaction.customId === 'my-button') return this.some();
return this.none();
}
}
import { ApplyOptions } from '@sapphire/decorators';
import { PaginatedMessage } from '@sapphire/discord.js-utilities';
import { InteractionHandler, InteractionHandlerTypes } from '@sapphire/framework';
import { ButtonInteraction, ButtonStyle, ComponentType } from 'discord.js';

@ApplyOptions<InteractionHandler.Options>({
interactionHandlerType: InteractionHandlerTypes.Button
})
export class SapphireButtonHandler extends InteractionHandler {
override run(interaction: ButtonInteraction) {
const page = new PaginatedMessage()
.setActions([
{
customId: 'case_overview',
style: ButtonStyle.Secondary,
type: ComponentType.Button,
emoji: '๐Ÿ”Ž',
label: 'Overview',
run(context) {
context.handler.index = 0;
}
},
{
customId: 'case_accounts',
style: ButtonStyle.Secondary,
type: ComponentType.Button,
emoji: '๐Ÿ‘ฅ',
label: 'Accounts',
run(context) {
context.handler.index = 1;
}
},
{
customId: 'case_evidence',
style: ButtonStyle.Secondary,
type: ComponentType.Button,
emoji: '๐Ÿ“',
label: 'Evidence',
run(context) {
context.handler.index = 2;
}
},
{
customId: 'case_revisions',
style: ButtonStyle.Secondary,
type: ComponentType.Button,
emoji: '๐Ÿ“',
label: 'Revisions',
run(context) {
context.handler.index = 3;
}
}
])
.addPageContent('Page 1')
.addPageContent('Page 2');

return page.run(interaction);
}

public override async parse(interaction: ButtonInteraction) {
if (interaction.customId === 'my-button') return this.some();
return this.none();
}
}
and it works just fine for me @cosigyn. The only way I can actually break it is by doing this in the button handler:
return page.run({
...interaction,
ephemeral: true
});
return page.run({
...interaction,
ephemeral: true
});
but that is also not how PM works. To make a PM ephemeral the interaction it is being ran on has to be ephemeral. Ergo, starting a PM from a button cannot be ephemeral.
disclosuure
disclosuureโ€ข11mo ago
I used deferReply to make it ephemeral Incase it wasn't clear, the paginated message works fine normally, when it's created from a command rather than a button, but when its created from a button, I'm unable to click the PM buttons, as it always says 'its for someone else' after someone else clicks on the button and creates a new PM
WhacK
WhacKโ€ข10mo ago
I am having a similiar issue so I am going to ask here. My paginated message is not being generated from a button but the original slash command. I use them to call a "shop's" inventory. Certain channels/threads represent shops with varying inventories. I defer the interaction w/ await interaction.deferReply({ ephemeral: true }). Then I get the data from our API and reply with the paginated message. I use the default actions (less the selection menu) only. I'm using the latest dependencies.
public async chatInputViewInventory(interaction: ChatInputCommand.Interaction) {
try {
await interaction.deferReply({ ephemeral: true });
const { channel: intChannel } = interaction;
const shopInventoryResponse: ShopInventoryItem[] = await getShopInventory(intChannel.id).catch(() => null);
if (!shopInventoryResponse) {
throw new Error('Unable to retrieve shop inventory data.');
}
const shopName = shopInventoryResponse[0].shop.name;
let pagesNeeded = Math.ceil(shopInventoryResponse.length / 24);
let pageIndex = 0;
let fieldData = shopInventoryResponse;

const inventoryEmbed = new BeholderPaginatedMessage({
template: new EmbedBuilder()
.setColor('#2f3136')
.setDescription('**Shop Inventory:**')
.setTitle(shopName)
.setFooter(
{ text: ` You can use \`/shop [list | buy | sell ] <item name> <qty>\` to conduct a transaction.` }),
actions: []
});
inventoryEmbed.setActions(
PaginatedMessage.defaultActions.filter(
(action) =>
'customId' in action &&
[
'@sapphire/paginated-messages.previousPage',
'@sapphire/paginated-messages.stop',
'@sapphire/paginated-messages.nextPage'
].includes(action.customId)
)
);

while (pageIndex < pagesNeeded) {
inventoryEmbed.addPageBuilder((builder) => {
const page = new EmbedBuilder();
for (let x = 0; x < fieldData.length; x++) {
if (x === 24) break;

const i = fieldData[x];

if (i.qty > 0) {
page.addFields({
name: `${RarityGemEnum[i.item.rarity]} ${i.item.name} \`x${i.qty}\` `,
value: `*${i.price} gp*`,
inline: true
});
}
}
return builder.setEmbeds([page]);
});

fieldData = fieldData.slice(24);
pageIndex += 1;
}
// send response
return inventoryEmbed.run(interaction);
} catch (error) {
console.error(error);
return interaction.editReply({
content: error.message || 'An unexpected error occurred. If the problem persists, please contact a Code Weaver.',
ephemeral: true
});
}
}
public async chatInputViewInventory(interaction: ChatInputCommand.Interaction) {
try {
await interaction.deferReply({ ephemeral: true });
const { channel: intChannel } = interaction;
const shopInventoryResponse: ShopInventoryItem[] = await getShopInventory(intChannel.id).catch(() => null);
if (!shopInventoryResponse) {
throw new Error('Unable to retrieve shop inventory data.');
}
const shopName = shopInventoryResponse[0].shop.name;
let pagesNeeded = Math.ceil(shopInventoryResponse.length / 24);
let pageIndex = 0;
let fieldData = shopInventoryResponse;

const inventoryEmbed = new BeholderPaginatedMessage({
template: new EmbedBuilder()
.setColor('#2f3136')
.setDescription('**Shop Inventory:**')
.setTitle(shopName)
.setFooter(
{ text: ` You can use \`/shop [list | buy | sell ] <item name> <qty>\` to conduct a transaction.` }),
actions: []
});
inventoryEmbed.setActions(
PaginatedMessage.defaultActions.filter(
(action) =>
'customId' in action &&
[
'@sapphire/paginated-messages.previousPage',
'@sapphire/paginated-messages.stop',
'@sapphire/paginated-messages.nextPage'
].includes(action.customId)
)
);

while (pageIndex < pagesNeeded) {
inventoryEmbed.addPageBuilder((builder) => {
const page = new EmbedBuilder();
for (let x = 0; x < fieldData.length; x++) {
if (x === 24) break;

const i = fieldData[x];

if (i.qty > 0) {
page.addFields({
name: `${RarityGemEnum[i.item.rarity]} ${i.item.name} \`x${i.qty}\` `,
value: `*${i.price} gp*`,
inline: true
});
}
}
return builder.setEmbeds([page]);
});

fieldData = fieldData.slice(24);
pageIndex += 1;
}
// send response
return inventoryEmbed.run(interaction);
} catch (error) {
console.error(error);
return interaction.editReply({
content: error.message || 'An unexpected error occurred. If the problem persists, please contact a Code Weaver.',
ephemeral: true
});
}
}
The BeholderPaginatedMessage class I copied the example of Dragonite just to lower the default idle. If someone checks the inventory in the same shop they get a message to stop interacting with Y user's buttons
WhacK
WhacKโ€ข10mo ago
No description
Favna
Favnaโ€ข10mo ago
Changing the command to
public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
await interaction.deferReply({ ephemeral: true });
const btn = new ButtonBuilder().setCustomId('my-button').setLabel('Click me').setStyle(ButtonStyle.Primary);
const ar = new ActionRowBuilder<ButtonBuilder>().setComponents(btn);

return interaction.editReply({
components: [ar]
});
}
public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
await interaction.deferReply({ ephemeral: true });
const btn = new ButtonBuilder().setCustomId('my-button').setLabel('Click me').setStyle(ButtonStyle.Primary);
const ar = new ActionRowBuilder<ButtonBuilder>().setComponents(btn);

return interaction.editReply({
components: [ar]
});
}
but keeping the rest the same I still cannot replicate the issue. Please provide a small reproduction GitHub repository which I can run after providing only a Discord token so I can analyse the issue further. FYI you dont need to do actions: []. setActions by default already overrides the default actions. So you can just do:
const inventoryEmbed = new PaginatedMessage({
template: new EmbedBuilder()
.setColor('#2f3136')
.setDescription('**Shop Inventory:**')
.setTitle('shopName')
.setFooter({ text: ` You can use \`/shop [list | buy | sell ] <item name> <qty>\` to conduct a transaction.` })
})
.setActions(
PaginatedMessage.defaultActions.filter(
(action) =>
'customId' in action &&
['@sapphire/paginated-messages.previousPage', '@sapphire/paginated-messages.stop', '@sapphire/paginated-messages.nextPage'].includes(
action.customId
)
)
);
const inventoryEmbed = new PaginatedMessage({
template: new EmbedBuilder()
.setColor('#2f3136')
.setDescription('**Shop Inventory:**')
.setTitle('shopName')
.setFooter({ text: ` You can use \`/shop [list | buy | sell ] <item name> <qty>\` to conduct a transaction.` })
})
.setActions(
PaginatedMessage.defaultActions.filter(
(action) =>
'customId' in action &&
['@sapphire/paginated-messages.previousPage', '@sapphire/paginated-messages.stop', '@sapphire/paginated-messages.nextPage'].includes(
action.customId
)
)
);
As for your particular issue, that should definitely work. For example @Dragonite uses the exact same process (https://github.com/favware/dragonite). The same will go for you to provide a small reproduction repository.
WhacK
WhacKโ€ข10mo ago
Ok thanks Iโ€™ll get on it
disclosuure
disclosuureโ€ข10mo ago
I will provide a reproduction repo, but did you get someone else to click the button after you did, and then try to use the paginated message again?
Favna
Favnaโ€ข10mo ago
I did not. I'll try that later.
Want results from more Discord servers?
Add your server