{"id":47810003,"url":"https://github.com/almeidx/discore","last_synced_at":"2026-07-02T02:04:48.454Z","repository":{"id":342153369,"uuid":"1173029886","full_name":"almeidx/discore","owner":"almeidx","description":"A lightweight, functional Discord bot framework built on @discordjs/core","archived":false,"fork":false,"pushed_at":"2026-06-28T11:12:19.000Z","size":445,"stargazers_count":0,"open_issues_count":1,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-28T13:08:54.917Z","etag":null,"topics":["bot","discord","discordjs","framework","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/almeidx.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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-05T00:00:41.000Z","updated_at":"2026-06-28T11:12:21.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/almeidx/discore","commit_stats":null,"previous_names":["almeidx/discore"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/almeidx/discore","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/almeidx%2Fdiscore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/almeidx%2Fdiscore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/almeidx%2Fdiscore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/almeidx%2Fdiscore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/almeidx","download_url":"https://codeload.github.com/almeidx/discore/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/almeidx%2Fdiscore/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35029799,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-02T02:00:06.368Z","response_time":173,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["bot","discord","discordjs","framework","typescript"],"created_at":"2026-04-03T18:06:48.137Z","updated_at":"2026-07-02T02:04:48.442Z","avatar_url":"https://github.com/almeidx.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @almeidx/discore\n\nA lightweight, functional Discord bot framework built on [`@discordjs/core`](https://github.com/discordjs/discord.js/tree/main/packages/core).\n\n## Features\n\n- **Functional API** — No classes, just functions. `defineCommand`, `defineEvent`, `defineButton`, etc.\n- **Type-safe options** — Command options inferred from definitions. `ctx.options.user` is typed automatically.\n- **Component routing** — Regex-based `customId` matching with named capture groups as `ctx.params`.\n- **Collectors** — `awaitComponent` and `awaitModal` (single, Promise-based) and `collectComponents` (async iterator).\n- **Hooks** — Per-command and global `beforeCommand`/`afterCommand`/`onError` hooks; per-definition `onError` on components, events, and autocomplete.\n- **Command publishing** — `publishCommands` maps definitions to the Discord API format.\n\n## Requirements\n\n- Node.js \u003e= 24.0.0\n- `@discordjs/core` \u003e= 3.0.0-dev\n\n## Install\n\n```sh\nnpm install @almeidx/discore @discordjs/core @discordjs/rest @discordjs/ws\n```\n\n## Quick start\n\n```ts\nimport { REST } from \"@discordjs/rest\";\nimport { WebSocketManager } from \"@discordjs/ws\";\nimport { GatewayIntentBits, Routes, type RESTGetAPIGatewayBotResult } from \"discord-api-types/v10\";\nimport { createBot, defineCommand, publishCommands } from \"@almeidx/discore\";\n\nconst token = process.env.DISCORD_TOKEN!;\n\nconst ping = defineCommand({\n\tdata: {\n\t\tname: \"ping\",\n\t\tdescription: \"Pong!\",\n\t},\n\thandler: async (ctx) =\u003e {\n\t\tawait ctx.reply({ content: \"Pong!\" });\n\t},\n});\n\nconst rest = new REST().setToken(token);\nconst gateway = new WebSocketManager({\n\ttoken,\n\tintents: GatewayIntentBits.Guilds,\n\tfetchGatewayInformation: () =\u003e rest.get(Routes.gatewayBot()) as Promise\u003cRESTGetAPIGatewayBotResult\u003e,\n});\n\nconst bot = createBot({ rest, gateway, commands: [ping] });\nawait publishCommands({ api: bot.api, applicationId: process.env.DISCORD_APP_ID!, commands: [ping] });\nawait gateway.connect();\n```\n\n## Error handling\n\nHandler errors are surfaced by default. Discore sends the configured error response, or empty autocomplete choices for autocomplete handlers, then rethrows the original error. Return `false` from an `onError` hook only when the hook has handled the error and should suppress both the fallback response and the rethrow.\n\nThe rethrown error rejects the gateway dispatch listener. By default that becomes an unhandled promise rejection, which terminates a Node.js process -- register an `onError` hook that returns `false`, a `process.on(\"unhandledRejection\")` handler, or enable `captureRejections` on the `WebSocketManager` and listen to its `error` event if you want the bot to keep running. An error thrown by one handler no longer prevents other handlers (such as `defineEvent` handlers for the same gateway payload) from running; when several handlers fail on one payload, the rejection is an `AggregateError`.\n\n## Typed options\n\n```ts\nimport { ApplicationCommandOptionType } from \"discord-api-types/v10\";\nimport { defineCommand } from \"@almeidx/discore\";\n\nconst ban = defineCommand({\n\tdata: {\n\t\tname: \"ban\",\n\t\tdescription: \"Ban a member\",\n\t\toptions: [\n\t\t\t{ name: \"user\", type: ApplicationCommandOptionType.User, description: \"Target user\", required: true },\n\t\t\t{ name: \"reason\", type: ApplicationCommandOptionType.String, description: \"Ban reason\" },\n\t\t],\n\t},\n\thandler: async (ctx) =\u003e {\n\t\t// ctx.options.user: { user, member } (required — always present)\n\t\t// ctx.options.reason: string | undefined (optional)\n\t\tawait ctx.reply({ content: `Banned \u003c@${ctx.options.user.user.id}\u003e` });\n\t},\n});\n```\n\n## Components\n\nButtons, select menus, and modals use regex patterns with named capture groups:\n\n```ts\nimport { MessageFlags } from \"discord-api-types/v10\";\nimport { defineButton } from \"@almeidx/discore\";\n\nconst verify = defineButton({\n\tcustomId: /^verify:(?\u003cuserId\u003e\\d+)$/,\n\thandler: async (ctx) =\u003e {\n\t\tconst userId = ctx.params.userId; // extracted from the regex match\n\t\tawait ctx.reply({ content: `Verified ${userId}!`, flags: MessageFlags.Ephemeral });\n\t},\n});\n```\n\n## Collectors\n\n```ts\nconst response = await ctx.awaitComponent({\n\tfilter: (i) =\u003e i.customId === \"confirm\",\n\ttimeout: 30_000,\n});\n\n// Or iterate over multiple interactions:\nconst collector = ctx.collectComponents({\n\tfilter: (i) =\u003e i.customId.startsWith(\"vote:\"),\n\ttimeout: 60_000,\n});\n\nfor await (const interaction of collector) {\n\tawait interaction.reply({ content: \"Recorded!\", flags: MessageFlags.Ephemeral });\n}\n```\n\nModals are collected the same way -- show the modal, then await its submission:\n\n```ts\nawait ctx.showModal({\n\ttitle: \"Feedback\",\n\tcustom_id: \"feedback-modal\",\n\tcomponents: [\n\t\t/* ... */\n\t],\n});\n\nconst submission = await ctx.awaitModal({\n\tfilter: (i) =\u003e i.customId === \"feedback-modal\",\n\ttimeout: 300_000,\n});\n\nconst message = submission.fields.getRequired(\"message\");\nawait submission.reply({ content: \"Thanks for the feedback!\" });\n```\n\n`awaitComponent`, `awaitModal`, and `collectComponents` reject (or end) with a `CollectorTimeoutError` when the timeout elapses -- wrap them in `try/catch` if a timeout is an expected outcome (see `examples/config-command.ts`).\n\n## More examples\n\nSee the [`examples/`](./examples) directory.\n\n## Development\n\n```sh\npnpm install\npnpm test              # node:test suite\npnpm run lint          # oxfmt --check \u0026\u0026 oxlint\npnpm run build:typecheck\npnpm build             # tsdown -\u003e dist/\n```\n\n## License\n\n[Apache-2.0](./LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falmeidx%2Fdiscore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falmeidx%2Fdiscore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falmeidx%2Fdiscore/lists"}