@igniter-js/bot
Version:
A modern, type-safe multi-platform bot framework for the Igniter.js ecosystem (adapters, middleware, commands, rich content).
521 lines (408 loc) • 14 kB
Markdown
# @igniter-js/bot (Alpha)
A modern, type‑safe, multi‑platform bot framework for the Igniter.js ecosystem.
It provides a clean abstraction layer for building chatbot-style integrations with **Telegram**, **WhatsApp** (Cloud API), and future providers—featuring middleware chains, command handling with aliases, logging, strong typing (TypeScript + Zod), and an extensible adapter architecture.
> Status: **alpha** – Public API may still evolve. Breaking changes may occur to improve DX and internal consistency before the first stable release.
## Table of Contents
1. [Key Features](#key-features)
2. [Installation](#installation)
3. [Quick Start](#quick-start)
4. [Configuration Overview](#configuration-overview)
5. [Adapters](#adapters)
- [Telegram](#telegram-adapter)
- [WhatsApp](#whatsapp-adapter)
6. [Commands](#commands)
7. [Mentions & Bot Handle Activation](#mentions--bot-handle-activation)
8. [Middleware](#middleware)
9. [Lifecycle Hooks](#lifecycle-hooks)
10. [Logging](#logging)
11. [Error Model](#error-model)
12. [API Reference (Core)](#api-reference-core)
13. [Types Overview](#types-overview)
14. [Sending Messages](#sending-messages)
15. [Adapter Authoring Guide](#adapter-authoring-guide)
16. [Testing Strategy (Planned)](#testing-strategy-planned)
17. [Security Considerations](#security-considerations)
18. [Performance Notes](#performance-notes)
19. [Roadmap](#roadmap)
20. [Contributing](#contributing)
21. [FAQ](#faq)
22. [License](#license)
## Key Features
- 🚀 **Multi-platform**: First-party adapters (Telegram, WhatsApp); more coming soon.
- 🧩 **Adapter Pattern**: Clean contract for adding new messaging providers.
- 🧠 **Type Safety**: End-to-end TypeScript with Zod runtime validation.
- ⚙️ **Middleware Pipeline**: Express-like chain for cross-cutting concerns (auth, metrics, rate limiting).
- 🗣️ **Command System**: Aliases, help text, structured command events.
- 🔔 **Mention Activation**: Customizable `handle` (username/keyword) for group contexts.
- 🪵 **Structured Logging**: Inject your logger, avoid hard-coded consoles.
- 🧱 **Error Codes & BotError**: Consistent error semantics for automation & observability.
- 🪶 **Lightweight & Tree-Shakeable**: Pure exports; no hidden side effects.
- 🔌 **Extensible**: Dynamic runtime registration: adapters, commands, middleware, hooks.
## Installation
```bash
npm install @igniter-js/bot
# or
yarn add @igniter-js/bot
# or
pnpm add @igniter-js/bot
```
Peer requirements: TypeScript >= 5.x
## Quick Start
```ts
import { Bot, telegram } from '@igniter-js/bot'
const bot = Bot.create({
id: 'demo-bot',
name: 'Demo Bot',
adapters: {
telegram: telegram({
token: process.env.TELEGRAM_TOKEN!,
handle: '@demo_bot', // used for group mention detection
webhook: {
url: process.env.TELEGRAM_WEBHOOK_URL!,
secret: process.env.TELEGRAM_WEBHOOK_SECRET
}
})
},
commands: {
start: {
name: 'start',
aliases: ['hello'],
description: 'Greets the user',
help: 'Use /start to receive a welcome message.',
async handle(ctx) {
await ctx.bot.send({
provider: ctx.provider,
channel: ctx.channel.id,
content: { type: 'text', content: '👋 Welcome, friend!' }
})
}
}
},
on: {
message: async (ctx) => {
if (ctx.message.content?.type === 'text') {
console.log('[inbound]', ctx.message.content.raw)
}
},
error: async (ctx) => {
// @ts-expect-error error injected internally
console.warn('[bot-error]', ctx.error?.code || ctx.error?.message)
}
},
logger: console // minimal, but you can bring pino/winston/etc
})
await bot.start()
// Example (Next.js / edge-like handler)
export async function POST(req: Request) {
return bot.handle('telegram', req)
}
```
## Configuration Overview
Each adapter defines its own Zod-based configuration schema. Core fields:
| Field | Purpose |
|------------|--------------------------------------|
| `token` | Provider API token |
| `handle` | Mention trigger (username/keyword) |
| `webhook` | (Telegram) Webhook URL + secret |
| `phone` | (WhatsApp) Phone number ID |
**Example (Telegram):**
```ts
telegram({
token: '123:ABC',
handle: '@my_bot',
webhook: { url: 'https://example.com/api/telegram', secret: 's3cr3t' }
})
```
**Example (WhatsApp Cloud API):**
```ts
whatsapp({
token: process.env.WHATSAPP_TOKEN!,
phone: process.env.WHATSAPP_PHONE_ID!,
handle: 'mybot' // keyword for group mention
})
```
## Adapters
### Telegram Adapter
Supports:
- Webhook registration (auto on `start()` if `webhook.url` provided)
- Command synchronization (`setMyCommands`)
- Text, photo, document, audio, voice
- MarkdownV2 escaping
Config fields:
| Field | Required | Description |
|-----------|----------|-------------|
| `token` | Yes | Bot API token |
| `handle` | Yes | `@<username>` used to detect mentions |
| `webhook.url` | Optional | HTTPS endpoint for updates |
| `webhook.secret` | Optional | Validates authenticity |
### WhatsApp Adapter
Supports:
- Cloud API send text
- Media parsing (image/document/audio)
- Mention keyword detection in group contexts
- Manual webhook management (you configure the endpoint at Meta dashboard)
Config fields:
| Field | Required | Description |
|----------|----------|-------------|
| `token` | Yes | Cloud API token |
| `phone` | Yes | Phone number (ID) |
| `handle` | Yes | Keyword to consider a group mention |
## Commands
A command is triggered by messages starting with `/` (Telegram style or custom typed message).
Structure:
```ts
const echo = {
name: 'echo',
aliases: ['repeat', 'say'],
description: 'Repeats your message back',
help: 'Usage: /echo <text>',
async handle(ctx, params) {
await ctx.bot.send({
provider: ctx.provider,
channel: ctx.channel.id,
content: { type: 'text', content: params.join(' ') || '(empty)' }
})
}
}
```
Register at creation or dynamically:
```ts
bot.registerCommand('echo', echo)
```
## Mentions & Bot Handle Activation
In group chats:
- **Telegram**: Message is considered addressing the bot if it contains `@<handle>` or starts with `/`.
- **WhatsApp**: We treat `handle` as a keyword; if it appears (case-insensitive) in a group message, `isMentioned = true`.
- In private chats: `isMentioned` defaults to `true`.
Use `ctx.message.isMentioned` inside listeners or middleware to implement mention-gated logic.
## Middleware
Signature:
```ts
type Middleware = (ctx: BotContext, next: () => Promise<void>) => Promise<void>
```
Example:
```ts
const metrics: Middleware = async (ctx, next) => {
const t0 = Date.now()
await next()
console.log('[latency]', ctx.event, Date.now() - t0)
}
bot.use(metrics)
```
Common use cases:
- Rate limiting
- Authorization
- Session loading
- Telemetry
- Caching attachments
---
## Lifecycle Hooks
```ts
bot
.onPreProcess(async (ctx) => {/* enrich context */})
.onPostProcess(async (ctx) => {/* audit / metrics */})
```
Execution order:
1. preProcess hooks
2. middleware chain
3. event listeners
4. command execution (if applicable)
5. postProcess hooks
---
## Logging
Inject a logger implementing:
```ts
interface BotLogger {
debug?: (...a: any[]) => void
info?: (...a: any[]) => void
warn?: (...a: any[]) => void
error?: (...a: any[]) => void
}
```
Adapters use `logger?.level?.()` – no console fallback if omitted (silent mode except thrown errors).
---
## Error Model
Core emits structured `BotError` instances internally:
| Code | Meaning |
|----------------------------|------------------------------------------|
| `PROVIDER_NOT_FOUND` | Adapter key not registered |
| `COMMAND_NOT_FOUND` | Command or alias not present |
| `INVALID_COMMAND_PARAMETERS` | Command handler threw / invalid usage |
| `ADAPTER_HANDLE_RETURNED_NULL` | Update intentionally ignored |
Listen to errors:
```ts
bot.on('error', async (ctx) => {
// @ts-expect-error internal injection
const err = ctx.error
console.error('[bot-error]', err.code, err.message)
})
```
---
## API Reference (Core)
### `Bot.create(options)`
Creates an instance with:
- `id`, `name`
- `adapters`: record of adapter instances
- `middlewares?`
- `commands?`
- `on?`: event handlers (`message`, `error`, `start`)
- `logger?`
### Instance Methods
| Method | Description |
|--------|-------------|
| `start()` | Initializes adapters (webhook setup, sync commands). |
| `handle(provider, request)` | Processes inbound HTTP webhook events. |
| `send({ provider, channel, content })` | Sends message using adapter. |
| `use(mw)` | Adds middleware dynamically. |
| `registerCommand(name, command)` | Adds command at runtime. |
| `registerAdapter(name, adapter)` | Dynamically adds an adapter. |
| `on(event, handler)` | Subscribes to an event. |
| `emit(event, ctx)` | Manually emits an event. |
| `onPreProcess(hook)` | Adds pre-process hook. |
| `onPostProcess(hook)` | Adds post-process hook. |
---
## Types Overview
Frequently used:
```ts
import type {
BotContext,
BotCommand,
BotEvent,
BotContent,
Middleware
} from '@igniter-js/bot/types'
```
Message content union: `BotTextContent | BotCommandContent | BotImageContent | BotAudioContent | BotDocumentContent`
## Sending Messages
```ts
await bot.send({
provider: 'telegram',
channel: '<chat_id>',
content: { type: 'text', content: 'Hello world' }
})
```
Content currently supports:
- `text` (outbound)
- Other types primarily parsed inbound (attachments extracted)
Future: structured replies, interactive elements, attachments sending.
## Adapter Authoring Guide
Create a new adapter:
```ts
import { Bot } from '@igniter-js/bot'
import { z } from 'zod'
const MyAdapterParams = z.object({
token: z.string(),
handle: z.string().optional()
})
export const myAdapter = Bot.adapter({
name: 'my-adapter',
parameters: MyAdapterParams,
async init({ config, commands, logger }) {
logger?.info?.('[my-adapter] init')
},
async send({ channel, content, config, logger }) {
logger?.debug?.('[my-adapter] send', { channel })
// ... send logic
},
async handle({ request, config, logger }) {
const body = await request.json()
if (!body.message) return null
return {
event: 'message',
provider: 'my-adapter',
channel: { id: body.channelId, name: body.channelName, isGroup: !!body.isGroup },
message: {
content: { type: 'text', content: body.message, raw: body.message },
author: { id: body.userId, name: body.userName, username: body.userHandle },
isMentioned: true
}
}
}
})
```
Rules:
1. Do not return `Response` – return context or `null`.
2. Perform validation (Zod schema).
3. Use logger instead of console.
4. Keep side effects inside `init` / functions (no top-level network calls).
## Testing Strategy (Planned)
Planned categories (to be introduced when exiting alpha):
| Layer | Scope |
|-------|-------|
| Unit | Command handlers, helpers |
| Adapter | Parsing & send logic (HTTP mocked) |
| Integration | Middleware + command pipeline |
| Contract | Type-level assertions (d.ts health) |
## Security Considerations
| Concern | Recommendation |
|---------|---------------|
| Tokens | Never commit; load via env |
| Webhooks | Use HTTPS + secrets |
| Markdown (Telegram) | Escape via provided helper |
| Rate limiting | Implement via middleware |
| Error leakage | Avoid echoing raw provider errors to users |
## Performance Notes
- Adapters avoid heavy processing; CPU cost dominated by network I/O.
- Media fetching is synchronous per request; consider caching large files externally.
- Middleware chain is linear; keep each middleware efficient.
- Logging can be the heaviest operation in high-throughput setups—use conditional log levels.
## Roadmap
| Area | Planned Enhancements |
|------|----------------------|
| Adapters | Discord, Slack, Matrix |
| Media | Outbound file sending helpers |
| Commands | Subcommand & parameter schema validation |
| Sessions | Pluggable session store (Redis / Memory) |
| Telemetry | OpenTelemetry integration |
| Rate Limiting | Official middleware |
| Plugin System | `bot.usePlugin()` style ecosystem |
| Testing | Official test harness utilities |
## Contributing
1. Fork the monorepo
2. Create a branch: `feat/bot-<feature>`
3. Align with `AGENT.md` guidelines
4. Run build & typecheck locally
5. Open a PR with clear description & rationale
We enforce:
- Clear naming
- Strong typing (no `any` unless justified)
- No unbounded console usage (use logger)
- Document all public APIs with JSDoc
## FAQ
**Q: Why does `handle` return a Response from `bot.handle()` but adapters return context?**
A: Separation of concerns. Adapters parse; core coordinates and finalizes the HTTP response.
**Q: How do I ignore irrelevant provider updates?**
Return `null` from the adapter’s `handle` method.
**Q: Can I mutate `ctx.message.content` in middleware?**
Yes, but preserve structural integrity (avoid deleting required fields).
**Q: How do I trigger custom events?**
Use `bot.emit('message', ctxLike)`—ensure the shape matches `BotContext`.
**Q: Are retries built-in?**
Not yet. Wrap `bot.send` externally if you need resilience.
## License
MIT © Felipe Barcelos & Igniter.js Contributors
## Support & Links
- Website: https://igniterjs.com
- Issues: https://github.com/felipebarcelospro/igniter-js/issues
- Future Community: Discord / Telegram (planned)
> This README reflects the **alpha** state. Expect rapid iteration. Feedback and issue reports are highly appreciated.