Skip to content

Instantly share code, notes, and snippets.

@waptik
Last active April 29, 2024 11:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save waptik/1a57faa9a6aa89702c23ade43ad8a08f to your computer and use it in GitHub Desktop.
Save waptik/1a57faa9a6aa89702c23ade43ad8a08f to your computer and use it in GitHub Desktop.
An example on how to use grammY's Interactive Menus plugin
// I'm using deno
import "https://deno.land/std@0.178.0/dotenv/load.ts";
import { green } from "https://deno.land/std@0.178.0/fmt/colors.ts";
import { itemsMenu } from './menus/example.menu.ts';
const grammy = new Bot<GrammyContext>(Deno.env.get("BOT_TOKEN")||""); // replace `BOT_TOKEN` inside `.env` file with your own telegram bot's token
// attaching the menu to bot to get it to work if not we'll get the following errors inside the terminal
// `Error: Cannot send menu 'bot-items-menu'! Did you forget to use bot.use() for it?`
grammy.use(itemsMenu);
// setting up a command to bring the menu in action
grammy.command('items', async (ctx) => {
const text = `Here are the items we currently have:`;
return await ctx.replyWithHTML(text, {
reply_markup: itemsMenu,
});
});
grammy.start({
drop_pending_updates: false,
onStart: ({ username }) =>
console.log(`[bot] @${green(username)} is up and running ๐Ÿฆ„`),
});
// Path: bot.ts
let pageIndex = 0;
/**
* A function that generates a paginated menu inspired by
* @see {@link https://github.com/ImOnlyFire/applications-bot/blob/main/bot/src/menus.ts#L41}
*
* @param index The index of the page to generate
* @returns A range of the menu
*/
function itemMenuRange(index: number, range: MenuRange<Context>) {
pageIndex = index;
// items is a dictionary of items such as a Macbook Pro or Air from 2016 to 2021 and an iPhone from 7 Plus to 12 Pro Max.
const items = {
mbp_2016: {
name: `Macbook Pro 13" 2016`,
message:
`An Apple Macbook Pro 13" 2016 with 16GB of RAM and 512GB of storage.`,
},
mbp_2017: {
name: `Macbook Pro 13" 2017`,
message:
`An Apple Macbook Pro 13" 2017 with 16GB of RAM and 512GB of storage.`,
},
mbp_2018: {
name: `Macbook Pro 13" 2018`,
message:
`An Apple Macbook Pro 13" 2018 with 16GB of RAM and 512GB of storage.`,
},
mbp_2019: {
name: `Macbook Pro 13" 2019`,
message:
`An Apple Macbook Pro 13" 2019 with 16GB of RAM and 512GB of storage.`,
},
mbp_2020: {
name: `Macbook Pro 13" 2020`,
message:
`An Apple Macbook Pro 13" 2020 with 16GB of RAM and 512GB of storage.`,
},
mbp_2021: {
name: `Macbook Pro 13" 2021`,
message:
`An Apple Macbook Pro 13" 2021 with 16GB of RAM and 512GB of storage.`,
},
iphone_7_plus: {
name: `iPhone 7 Plus`,
message:
`An Apple iPhone 7 Plus, black color with 256GB of storage.`,
},
iphone_8_plus: {
name: `iPhone 8 Plus`,
message:
`An Apple iPhone 8 Plus, black color with 256GB of storage.`,
},
iphone_x: {
name: `iPhone X`,
message:
`An Apple iPhone X, black color with 256GB of storage.`,
},
iphone_xr: {
name: `iPhone XR`,
message:
`An Apple iPhone XR, black color with 256GB of storage.`,
},
iphone_xs: {
name: `iPhone XS`,
message:
`An Apple iPhone XS, black color with 256GB of storage.`,
},
iphone_11: {
name: `iPhone 11`,
message:
`An Apple iPhone 11, black color with 256GB of storage.`,
},
iphone_11_pro: {
name: `iPhone 11 Pro`,
message:
`An Apple iPhone 11 Pro, black color with 256GB of storage.`,
},
iphone_11_pro_max: {
name: `iPhone 11 Pro Max`,
message:
`An Apple iPhone 11 Pro Max, black color with 256GB of storage.`,
},
iphone_12: {
name: `iPhone 12`,
message:
`An Apple iPhone 12, black color with 256GB of storage.`,
},
iphone_12_pro: {
name: `iPhone 12 Pro`,
message:
`An Apple iPhone 12 Pro, black color with 256GB of storage.`,
},
iphone_12_pro_max: {
name: `iPhone 12 Pro Max`,
message:
`An Apple iPhone 12 Pro Max, black color with 256GB of storage.`,
},
};
const itemsPerPage = 3;
const itemsKeys = Object.keys(items) as Array<
keyof typeof items
>;
const currentPagePersonasKeys = itemsKeys.slice(
pageIndex * itemsPerPage,
(pageIndex + 1) * itemsPerPage,
);
// Generate a part of the menu dynamically!
for (const itemKey of currentPagePersonasKeys) {
const item = items[itemKey];
range.text(item.name, async (ctx) => {
await ctx.editMessageText(item.message, {
parse_mode: 'HTML',
});
await ctx.menu.close();
}).row();
}
if (itemsKeys.length > itemsPerPage) {
const isFirstPage = pageIndex === 0;
const isLastPage = (pageIndex + 1) * itemsPerPage >= itemsKeys.length;
if (isFirstPage) {
range.submenu(
'๐Ÿ‘‰',
`pagination-example-menu`,
() => {
pageIndex++;
},
);
} else if (isLastPage) {
range.submenu(
'๐Ÿ‘ˆ',
`pagination-example-menu`,
() => {
pageIndex--;
},
);
} else {
range
.submenu(
'๐Ÿ‘ˆ',
`pagination-example-menu`,
() => {
pageIndex--;
},
)
.submenu(
'๐Ÿ‘‰',
`pagination-example-menu`,
() => {
pageIndex++;
},
);
}
}
return range;
}
export const itemsMenu = new Menu<Context>('bot-items-menu')
.dynamic((_, range) => itemMenuRange(0, range));
const paginationMenu = new Menu<Context>(
`pagination-example-menu`,
).dynamic((_, range) => itemMenuRange(pageIndex, range));
itemsMenu.register(paginationMenu);
// Path: menus/example.menu.ts
@waptik
Copy link
Author

waptik commented Oct 6, 2023

Here is a step by step instruction set on how to use grammY's Interactive Menus plugin.

Steps:

  1. open example.menu.ts and replace the contents of items set of object with your own values or you can use your database to populate them for you. You can rename example.menu.ts to whatever name you want.
  2. Make sure you attach the menu to your main menus handler composer or the bot directly by using composer.use and bot.use respectively.
  3. Now you can use the menu inside a command, callbackQuery, conversation or wherever you wish to use it then you're good to go ๐Ÿ˜‰

A video of it in action can be found here https://twitter.com/_waptik/status/1710217968548737172

@shakhrillodev
Copy link

Hi. I was wandering if two or more users are using pagination at the same time won't it affect the other user menu as pageIndex is registered globally?

@waptik
Copy link
Author

waptik commented Apr 29, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment