{"id":21853643,"url":"https://github.com/incetarik/telegram-inline-menu","last_synced_at":"2025-04-14T16:40:58.831Z","repository":{"id":138329966,"uuid":"271867190","full_name":"incetarik/telegram-inline-menu","owner":"incetarik","description":"A library for providing inline menus with an ease for Telegraf library based applications.","archived":false,"fork":false,"pushed_at":"2024-12-10T11:27:46.000Z","size":143,"stargazers_count":7,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-28T05:23:17.291Z","etag":null,"topics":["telegraf","telegram","telegram-inline-menu"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/incetarik.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-06-12T18:41:03.000Z","updated_at":"2024-12-10T11:27:43.000Z","dependencies_parsed_at":null,"dependency_job_id":"5d709079-cf7c-42ed-95b2-108c27371b5f","html_url":"https://github.com/incetarik/telegram-inline-menu","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/incetarik%2Ftelegram-inline-menu","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/incetarik%2Ftelegram-inline-menu/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/incetarik%2Ftelegram-inline-menu/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/incetarik%2Ftelegram-inline-menu/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/incetarik","download_url":"https://codeload.github.com/incetarik/telegram-inline-menu/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248918065,"owners_count":21183110,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["telegraf","telegram","telegram-inline-menu"],"created_at":"2024-11-28T01:26:02.146Z","updated_at":"2025-04-14T16:40:58.821Z","avatar_url":"https://github.com/incetarik.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Telegraf Inline Menu (telegram-inline-menu)\n\nA package to build and handle telegraf inline keyboard menus.\n\nThis package provides you a way of structuring the menus as Javascript objects.\nYou can define `onPress` actions of the buttons, navigate through the menus\nby setting `navigate` property to a `path` of a menu or using relative\nnavigation such as \"`..`\" to go to parent menu, or numberic index indicating\nthe creation order of the menu or relative negative index.\n\n**NOTE**: To track last changes, please see the\n[CHANGELOG](https://github.com/incetarik/telegram-inline-menu/blob/master/CHANGELOG.md)\nfile.\n\nThe example has many examples of usages inside.\n\n# Examples\n```ts\nimport { CBHandler, inlineMenu, IMenu } from 'telegram-inline-menu'\n\nfunction sleep(ms: number) {\n  return new Promise(resolve =\u003e setTimeout(resolve, ms))\n}\n\nconst menuLayout: IMenu = {\n  id: 'main',\n  text: 'Main menu, this menu has random ID.',\n  buttons: {\n    hello: 'Hello!',\n    url: {\n      text: 'GitHub',\n      url: 'https://github.com',\n    },\n    countdown: {\n      text: 'Countdown from 5',\n      async * onPress(ctx, id, text, menu) {\n        yield { message: 'About to countdown…' }\n        await sleep(1000)\n        yield { message: 'See the button text now' }\n        for (let i = 5; i \u003e 0; --i) {\n          yield { text: `Last ${i}...` }\n          await sleep(1000)\n        }\n\n        yield { closeWith: 'Operation is completed' }\n      }\n    },\n    subMenu: {\n      text: 'This is the inner menu after main',\n      buttonText: 'Sub Menu',\n      full: true,\n      buttons: {\n        back: { text: 'Go Back', navigate: '..' },\n        nextMenu: { text: 'Go Next', navigate: '../thirdMenu' }\n      }\n    },\n    thirdMenu: {\n      text: 'This is the third menu belonging to the main, but not from main',\n      hide: true,\n      buttons: {\n        back: { text: 'Back', navigate: -1 },\n        update: {\n          text: 'Update Text',\n          onPress() {\n            return { message: 'Third menu text is updated' }\n          }\n        },\n        close: {\n          text: 'Close',\n          full: true,\n          onPress() {\n            return {\n              close: true\n            }\n          }\n        }\n      }\n    }\n  }\n}\n\n// Optionally you can register a method missing handler if any of your buttons\n// do not have `onPress` property.\nCBHandler.onMethodMissing((text, path, query) =\u003e {\n  console.log('A button with text', text, 'on', path, 'is pressed')\n})\n\nconst telegraf = new Telegraf('YOUR_TOKEN')\nCBHandler.attach(telegraf)\n\ntelegraf.on('text', async ctx =\u003e {\n  await CBHandler.showMenu(ctx, inlineMenu(layout))\n})\n\ntelegraf.startPolling()\ntelegraf.launch().catch(console.error)\n```\n\n# Setup\nSetup is as it is shown in the example.\n- Import the components of this package.\n- Optionally use `CBHandler.onMethodMissing`.\n- Get a menu builder instance by `inlineMenu(layout)` function by giving the\nmenu layout.\n\n- Register the menu to CBHandler to allow other registered menus use each others\npath and IDs.\n- Attach the `CBHandler` to the `Telegraf` instance. This will use\n`callback_query` handler to show the related menu or take the action.\n- Show menu by `CBHandler.showMenu(context, menuBuilder)` to send the menu\nas a separate message.\n\n# Components\nThe library has several components inside, which all should have their own part\nto interact with user and the other components.\n\n## CallbackQueryHandler\nA `CallbackQueryHandler` class is to keep/track the menus inside attached to a\nTelegraf instance so that it could manage the transitions between menus, like\nwhen a menu button has a navigation path such as `/secondary/` so that another\nmenu should appear, and also the class uses a \"`keeper`\" object which is used\nto relate the menu instances with it in a `WeakMap`. Hence, if you have another\nlibrary or some other object keeping the data of your bot, you can use it as\nthe `keeper` of the class instance so that whenever that `keeper` is garbage\ncollected, the other menu instances will also be able to be garbage collected.\nTo set a custom keeper, you can use `CallbackQueryHandler.setMenuKeeper(obj)`\nfunction. By default, the given instance by the library uses itself as a\n`keeper`.\n\nLikewise this component has `onMethodMissing` function which has\n`buttonText`, `buttonPath`, and the `CallbackQuery` object of the Telegraf.\nThis function could be used when your layout does not define a `onPress`\nfunction for your button. You can get the `ID` of the button from its path.\n\nYou should attach your `CallbackQueryHandler` instance to your `Telegraf`\ninstance. If you want to have more instance, just create with `new`. Normally,\nthis library provides you an instance already named `CBHandler`.\n\n## MenuBuilder\nThis class provides another way of building menus, you will have functions\nlike `button()`, `menu()`, `navigate()` and such. With these functions you\ncan build your menu likewise.\n\nDon't forget that you should `end()` your button builders before building\nanother one and also you should `end()`/`endMenu()` your menu before building\nsome other menu.\n\nBuilding a menu inside a menu is actually just having another button navigating\nto another menu builder.\n\nThere is also a `inlineMenu(layout)` function provided by the library that\ntakes a layout object and returns a builder. It returns a builder because\nyou may also want to continue building the menu from that part or pass the\ninstance around your functions. Or, you can change a several parts of your menu\nevery time, for example, a function is called.\n\nIt is why the library functions wants `MenuBuilder` instances instead of the\nbuilt `Menu` instance.\n\n### The same menu layout of above with MenuBuilder\n```ts\nnew MenuBuilder('Main menu, this menu has \"main\" id', 'main')\n  .button('Hello!', 'hello').end()\n  .button('GitHub', 'url').setUrl('https://github.com').end()\n  .button('Countdown from 5', 'countdown').setOnPress(async function* () {\n    yield { message: 'About to countdown…' }\n    await sleep(1000)\n    yield { message: 'See the button text now' }\n    for (let i = 5; i \u003e 0; --i) {\n      yield { text: `Last ${i}...` }\n      await sleep(1000)\n    }\n\n    yield { closeWith: 'Operation is completed' }\n  }).end()\n\n  .menu('This is the inner menu after main', 'Sub Menu', 'subMenu', true)\n  .navigation('Go Back', '..', 'back').end()\n  .navigation('Go Next', '../thirdMenu', 'nextMenu').end()\n  .endMenu()\n\n  .menu('This is the third menu belonging to the main, but not froun main', 'thirdMenu', false, true)\n  .navigation('Back', -1, 'back').end()\n  .button('Update Text', 'update').setOnPress(() =\u003e ({ message: 'Third menu text is updated' })).end()\n  .button('Close', 'close').setFull(true).setOnPress(() =\u003e ({ close: true })).end()\n  .endMenu()\n\n  .endMenu()\n```\n\n# Notes\n- Any object having `buttons` object property will be assumed as a menu.\n- `text` property of a menu indicates the `message` that will be shown to user.\n- `onPress()` functions might be async, generator-returning or async generator\nfunctions. If the function is (async) generator, an object affecting the next\nstep could be returned every time, as you can see in the example.\n- `navigate` property may be a string indicating the `id` of the menu, which\nis simply the property name of the menu, or a `path` of a menu such as\n`/main/subMenu/` or a relative path such as `../siblingMenu` or an index such\nas `-1` indicating the one previous from the menu that the button is in.\n- `full` property of buttons indicates whether the button should be full-width.\n- `full` and `hide` properties may have a (async) function determining the\nstate of the button.\n\n# Caveats\n- Since the `ContextMessageUpdate` of the Telegraf may be used in async function\nit will be blocked/prevented to do changes on the Telegram. So be careful about\nyour async functions, if possible use them with timeouts possibly with\n`Promise.race`.\n\n- To prevent collisions when any of the menus sent closed, the menus without\ntheir `id` property is set, will be set automatically. So if you want to use\nabsolute paths to open a menu, be sure that you set a unique `id` for that menu.\nOr, simply use relative paths.\n\n- If your menu is not closed, it is still kept in map so that if you send the\nsame menu again, the buttons and the message might not be shown as expected.\n\n---\n\nIf you want to support to the project:\n\n```md\n- Bitcoin     : 153jv3MQVNSvyi2i9UFr9L4ogFyJh2SNt6\n- Bitcoin Cash: qqkx22yyjqy4jz9nvzd3wfcvet6yazeaxq2k756hhf\n- Ether       : 0xf542BED91d0218D9c195286e660da2275EF8eC84\n- Stellar     : GATF6DAKFCYY3MLNAIWVISARP52EWPOPFFZT4JMFENPNPERCMTSDFNY5\n```\n\nThank You.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fincetarik%2Ftelegram-inline-menu","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fincetarik%2Ftelegram-inline-menu","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fincetarik%2Ftelegram-inline-menu/lists"}