npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zyntra-js/bot

v0.2.2

Published

A modern, type-safe multi-platform bot framework for the ZyntraJS ecosystem (adapters, middleware, commands, rich content).

Readme

@zyntra-js/bot

NPM Version License: MIT

A modern, type-safe, multi-platform bot framework for the ZyntraJS ecosystem. Build sophisticated chatbots for Telegram, WhatsApp, Discord, and other platforms with a clean, fluent API, powerful middleware system, session management, and extensive TypeScript support.

Status: Alpha - API is stabilizing. Breaking changes are minimized but may occur before v1.0.0.


Table of Contents


Key Features

  • 🎯 Builder Pattern - Fluent, chainable API inspired by modern frameworks
  • 🚀 Multi-Platform - Telegram, WhatsApp, Discord, and more
  • 🧩 Type-Safe - Full TypeScript support with end-to-end type inference
  • Zod Validation - Validate command arguments with Zod schemas
  • 💾 Session Management - Stateful conversations with pluggable storage
  • ⚙️ Middleware Pipeline - Express-like middleware for cross-cutting concerns
  • 🔌 Plugin System - Modular extensions for reusable functionality
  • 🛡️ Built-in Security - Rate limiting, authentication, and authorization
  • 📊 Observability - Structured logging and analytics support
  • 🎨 Excellent DX - Autocomplete, type inference, and helpful error messages
  • 🌳 Tree-Shakeable - Zero runtime overhead for unused features
  • 🔄 Capabilities System - Adapters declare what they support

Installation

npm install @zyntra-js/bot zod
# or
pnpm add @zyntra-js/bot zod
# or
yarn add @zyntra-js/bot zod
# or
bun add @zyntra-js/bot zod

Requirements:

  • Node.js >= 18
  • TypeScript >= 5.0
  • Zod >= 3.0

Import Paths

The package provides organized imports for better code organization and tree-shaking:

// Main exports (everything in one import)
import { ZyntraBot, telegram, memoryStore, rateLimitMiddleware } from '@zyntra-js/bot'

// Organized imports (recommended for large projects)
import { telegram, whatsapp, discord } from '@zyntra-js/bot/adapters'
import { rateLimitMiddleware, authMiddleware, loggingMiddleware } from '@zyntra-js/bot/middlewares'
import { analyticsPlugin } from '@zyntra-js/bot/plugins'
import { memoryStore } from '@zyntra-js/bot/stores'
import type { BotContext, BotCommand } from '@zyntra-js/bot/types'

Why use organized imports?

  • 📦 Better tree-shaking - Only import what you need
  • 🗂️ Clearer code - Explicit about where things come from
  • 🔍 Easier to find - Know exactly which module exports what
  • 🎯 Better autocomplete - IDE shows only relevant exports

Quick Start

Create your first bot in under 2 minutes:

import { ZyntraBot, telegram } from '@zyntra-js/bot'

const bot = ZyntraBot
  .create()
  .withHandle('@mybot')  // ← Your bot's handle (ID and name auto-derived)
  .addAdapter('telegram', telegram({
      token: process.env.TELEGRAM_TOKEN!,
    // handle inherited from global
  }))
  .addCommand('start', {
      name: 'start',
      aliases: ['hello'],
    description: 'Start the bot',
    help: 'Use /start to begin',
      async handle(ctx) {
      await ctx.reply('👋 Welcome! I am your bot.')
      }
  })
  .build()

// Initialize the bot (registers webhooks, commands, etc)
await bot.start()

// Use in Next.js API route
export async function POST(req: Request) {
  return bot.handle('telegram', req)
}

Builder Pattern API

The ZyntraBot builder provides a fluent API for configuring your bot:

Core Configuration

const bot = ZyntraBot
  .create()
  .withHandle('@mybot')              // Recommended: Bot handle (ID/name auto-derived)
  .withId('custom-id')               // Optional: Override auto-generated ID
  .withName('Custom Name')           // Optional: Override auto-generated name
  .withLogger(console)               // Optional: Logger instance
  .withOptions({                     // Optional: Advanced options
    timeout: 30000,
    retries: 3,
    autoRegisterCommands: true
  })
  .withSessionStore(memoryStore())   // Optional: Session storage

Handle Benefits:

  • 🎯 One handle for all platforms (DRY principle)
  • 🤖 Auto-generates id and name from handle
  • 🔄 Each adapter can override if needed
  • 📝 Simpler configuration

Adding Adapters

// Single adapter (inherits global handle)
.addAdapter('telegram', telegram({
  token: '...',
  // handle: '@custom' ← Optional override
}))

// Multiple adapters (all inherit global handle)
.addAdapters({
  telegram: telegram({ token: '...' }),
  whatsapp: whatsapp({ token: '...', phone: '...' }),
  discord: discord({ token: '...', applicationId: '...' })
})

// Override handle for specific adapter
.addAdapter('telegram', telegram({
  token: '...',
  handle: '@custom_telegram_handle' // ← Platform-specific override
}))

Adding Commands

// Single command
.addCommand('start', {
  name: 'start',
  aliases: ['hello'],
  description: 'Start the bot',
  help: 'Use /start',
  async handle(ctx) {
    await ctx.reply('Hello!')
  }
})

// Multiple commands
.addCommands({
  start: { ... },
  help: { ... }
})

// Command with Zod validation
.addCommand('ban', {
  name: 'ban',
  description: 'Ban a user',
  args: z.object({
    userId: z.string(),
    reason: z.string().optional()
  }),
  async handle(ctx, args) {
    // args is fully typed!
    await ctx.reply(`Banned ${args.userId}`)
  }
})

Adding Middlewares

// Import from main package
import { rateLimitMiddleware, authMiddleware, loggingMiddleware } from '@zyntra-js/bot'

// Or use organized imports (recommended)
import {
  rateLimitMiddleware,
  authMiddleware,
  loggingMiddleware
} from '@zyntra-js/bot/middlewares'

.addMiddleware(loggingMiddleware({ logCommands: true }))
.addMiddleware(rateLimitMiddleware({ maxRequests: 10, windowMs: 60000 }))
.addMiddleware(authMiddleware({ allowedUsers: ['admin_id'] }))

// Or add multiple at once
.addMiddlewares([
  loggingMiddleware({ ... }),
  rateLimitMiddleware({ ... })
])

Event Handlers

.onMessage(async (ctx) => {
  console.log('Message:', ctx.message.content)
})
.onError(async (ctx) => {
  console.error('Error:', ctx.error)
})
.onCommand(async (ctx) => {
  console.log('Command executed')
})
.onStart(async () => {
  console.log('Bot started!')
})

Using Plugins

// Import from main package
import { analyticsPlugin } from '@zyntra-js/bot'

// Or use organized imports (recommended)
import { analyticsPlugin } from '@zyntra-js/bot/plugins'

.usePlugin(analyticsPlugin({
  trackMessages: true,
  trackCommands: true
}))

Building

.build() // Creates immutable bot instance

Adapters

Telegram

Full-featured Telegram Bot API adapter with webhook support:

import { telegram } from '@zyntra-js/bot/adapters'

// Minimal (uses global bot handle)
.addAdapter('telegram', telegram({
  token: 'your_bot_token'
}))

// With webhook
.addAdapter('telegram', telegram({
  token: 'your_bot_token',
  webhook: {
    url: 'https://example.com/api/telegram',
    secret: 'webhook_secret'
  }
}))

// Override global handle for this platform
.addAdapter('telegram', telegram({
  token: 'your_bot_token',
  handle: '@custom_telegram_bot' // ← Override
}))

Capabilities:

  • ✅ Text, images, videos, audio, documents, stickers
  • ✅ Locations, contacts, polls
  • ✅ Interactive buttons and inline keyboards
  • ✅ Edit and delete messages
  • ✅ Webhooks and long polling
  • ✅ Slash commands
  • Limits: 4096 chars, 50MB files, 8 buttons

WhatsApp

WhatsApp Cloud API adapter:

import { whatsapp } from '@zyntra-js/bot/adapters'

// Minimal (uses global bot handle)
.addAdapter('whatsapp', whatsapp({
  token: 'your_whatsapp_token',
  phone: 'phone_number_id'
}))

// Override global handle for this platform
.addAdapter('whatsapp', whatsapp({
  token: 'your_whatsapp_token',
  phone: 'phone_number_id',
  handle: 'custom_keyword' // ← Override for WhatsApp-specific keyword
}))

Capabilities:

  • ✅ Text, images, videos, audio, documents
  • ✅ Locations, contacts
  • ✅ Interactive buttons and lists
  • ✅ Message reactions
  • ❌ No edit/delete via API
  • Limits: 4096 chars, 100MB files, 3 buttons

Discord

Discord Interactions API adapter with slash commands support:

import { discord } from '@zyntra-js/bot/adapters'

// Minimal (uses global bot handle)
.addAdapter('discord', discord({
  token: 'your_discord_token',
  applicationId: 'your_application_id',
  publicKey: 'your_public_key' // Optional but recommended for signature verification
}))

// Override global handle for this platform
.addAdapter('discord', discord({
  token: 'your_discord_token',
  applicationId: 'your_application_id',
  publicKey: 'your_public_key',
  handle: '@custom_discord_bot' // ← Override
}))

Capabilities:

  • ✅ Text, images, videos, audio, documents
  • ✅ Interactive buttons and message components
  • ✅ Edit and delete messages
  • ✅ Message reactions
  • ✅ Slash commands (APPLICATION_COMMAND)
  • ✅ Message components (MESSAGE_COMPONENT - button clicks)
  • ✅ Ed25519 signature verification
  • ❌ No stickers, location, contact, or polls via API
  • Limits: 2000 chars, 25MB files (100MB for verified bots), 5 buttons per row

Creating Custom Adapters

import { Bot } from '@zyntra-js/bot'
import { z } from 'zod'

const myAdapter = Bot.adapter({
  name: 'my-platform',
  parameters: z.object({
    token: z.string(),
    handle: z.string()
  }),
  capabilities: {
    content: { text: true, image: true, ... },
    actions: { edit: true, delete: false, ... },
    features: { webhooks: true, ... },
    limits: { maxMessageLength: 2000, ... }
  },
  async init({ config, commands, logger }) {
    // Initialize (register webhooks, commands, etc)
  },
  async send({ channel, content, config, logger }) {
    // Send message via platform API
  },
  async handle({ request, config, logger }) {
    // Parse webhook and return context
    return {
      event: 'message',
      provider: 'my-platform',
      channel: { ... },
      message: { ... }
    }
  }
})

Commands

Basic Command

.addCommand('ping', {
  name: 'ping',
  aliases: ['pong'],
  description: 'Check if bot is alive',
  help: 'Use /ping to test',
  async handle(ctx) {
    await ctx.reply('🏓 Pong!')
  }
})

Command with Validation

import { z } from 'zod'

.addCommand('remind', {
  name: 'remind',
  description: 'Set a reminder',
  help: 'Use /remind <time> <message>',
  args: z.object({
    time: z.number().positive(),
    message: z.string().min(1)
  }),
  async handle(ctx, args) {
    // args is fully typed!
    await ctx.reply(`Reminder set for ${args.time} minutes: ${args.message}`)
  }
})

Command with Subcommands

.addCommand('config', {
  name: 'config',
  description: 'Bot configuration',
  subcommands: {
    set: {
      args: z.object({ key: z.string(), value: z.string() }),
      async handle(ctx, args) {
        await ctx.reply(`Set ${args.key} = ${args.value}`)
  }
    },
    get: {
      args: z.object({ key: z.string() }),
      async handle(ctx, args) {
        await ctx.reply(`Value of ${args.key}`)
      }
    }
  }
})

Session Management

Manage stateful conversations across messages:

// Import from main package
import { memoryStore } from '@zyntra-js/bot'

// Or use organized imports (recommended)
import { memoryStore } from '@zyntra-js/bot/stores'

const bot = ZyntraBot
  .create()
  .withSessionStore(memoryStore()) // or redisStore(), prismaStore()
  .addCommand('survey', {
    name: 'survey',
    async handle(ctx) {
      const session = ctx.session
      
      if (!session.data.step) {
        session.data.step = 1
        session.data.answers = {}
        await ctx.reply('What is your name?')
        await session.save()
        return
      }
      
      if (session.data.step === 1) {
        session.data.answers.name = ctx.message.content?.content
        session.data.step = 2
        await ctx.reply('What is your email?')
        await session.save()
        return
      }
      
      // Complete survey
      await ctx.reply('Thank you!')
      await session.delete()
    }
  })
  .build()

Session API:

  • session.data - Store arbitrary data
  • session.save() - Persist changes
  • session.delete() - Remove session
  • session.update(data) - Merge partial data

Middlewares

Official Middlewares

Rate Limiting

// Import from main package
import { rateLimitMiddleware, rateLimitPresets } from '@zyntra-js/bot'

// Or use organized imports (recommended)
import { rateLimitMiddleware, rateLimitPresets } from '@zyntra-js/bot/middlewares'

// Custom configuration
.addMiddleware(rateLimitMiddleware({
  maxRequests: 10,
  windowMs: 60000,
  message: 'Too many requests!',
  skip: (ctx) => isAdmin(ctx.message.author.id)
}))

// Or use presets
.addMiddleware(rateLimitPresets.moderate())

Presets: strict, moderate, lenient, perCommand

Authentication

// Organized imports (recommended)
import { authMiddleware, authPresets } from '@zyntra-js/bot/middlewares'

// Whitelist users
.addMiddleware(authMiddleware({
  allowedUsers: ['user1', 'user2'],
  unauthorizedMessage: 'Access denied'
}))

// Or use presets
.addMiddleware(authPresets.adminsOnly(['admin1']))
.addMiddleware(authPresets.privateOnly())
.addMiddleware(authPresets.groupsOnly())

Logging

// Organized imports (recommended)
import { loggingMiddleware, loggingPresets } from '@zyntra-js/bot/middlewares'

// Custom logging
.addMiddleware(loggingMiddleware({
  logMessages: true,
  logCommands: true,
  logMetrics: true,
  includeUserInfo: true
}))

// Or use presets
.addMiddleware(loggingPresets.production())

Presets: minimal, standard, verbose, debug, production

Custom Middleware

const myMiddleware: Middleware = async (ctx, next) => {
  console.log('Before')
  await next() // Call next middleware/handler
  console.log('After')
}

.addMiddleware(myMiddleware)

Plugins

Plugins package commands, middlewares, and hooks into reusable modules:

// Import from main package
import { analyticsPlugin } from '@zyntra-js/bot'

// Or use organized imports (recommended)
import { analyticsPlugin } from '@zyntra-js/bot/plugins'

.usePlugin(analyticsPlugin({
  trackEvent: async (event, properties) => {
    await analytics.track(event, properties)
  },
  trackMessages: true,
  trackCommands: true
}))

The analytics plugin automatically adds a /stats command!

Creating Custom Plugins

import type { BotPlugin } from '@zyntra-js/bot'

const myPlugin: BotPlugin = {
  name: 'my-plugin',
  version: '1.0.0',
  description: 'My custom plugin',
  
  commands: {
    custom: {
      name: 'custom',
      aliases: [],
      description: 'Custom command',
      help: 'Use /custom',
      async handle(ctx) {
        await ctx.reply('From plugin!')
      }
    }
  },
  
  middlewares: [
    async (ctx, next) => {
      console.log('Plugin middleware')
      await next()
    }
  ],
  
  hooks: {
    onStart: async () => console.log('Plugin started'),
    onMessage: async (ctx) => console.log('Message'),
    onError: async (ctx) => console.error('Error')
  }
}

.usePlugin(myPlugin)

Context Helpers

The BotContext includes convenient helper methods:

Sending Messages

// Simple text reply
await ctx.reply('Hello!')

// Reply with buttons
await ctx.replyWithButtons('Choose an option:', [
  { id: '1', label: 'Option 1', action: 'callback', data: 'opt1' },
  { id: '2', label: 'Option 2', action: 'callback', data: 'opt2' }
])

// Reply with image
await ctx.replyWithImage('https://example.com/image.jpg', 'Caption')

// Reply with document
await ctx.replyWithDocument(file, 'Document caption')

Message Actions

// Edit message (if adapter supports)
await ctx.editMessage?.('message_id', { type: 'text', content: 'Updated!' })

// Delete message (if adapter supports)
await ctx.deleteMessage?.('message_id')

// React to message (if adapter supports)
await ctx.react?.('👍')

Session Access

// Access session
const step = ctx.session.data.step || 0
ctx.session.data.step = step + 1
await ctx.session.save()

Capabilities System

Adapters declare their capabilities, allowing you to check support before using features:

.addCommand('feature', {
  name: 'feature',
  async handle(ctx) {
    const adapter = ctx.bot.getAdapter?.(ctx.provider)
    
    if (!adapter?.capabilities.content.image) {
      await ctx.reply('This platform does not support images')
      return
    }
    
    await ctx.replyWithImage('...')
  }
})

Capability Categories:

  • content - Text, image, video, audio, document, sticker, location, contact, poll, interactive
  • actions - Edit, delete, react, pin, thread
  • features - Webhooks, long polling, commands, mentions, groups, channels, users, files
  • limits - maxMessageLength, maxFileSize, maxButtonsPerMessage

Examples

Multi-Platform Bot

const bot = ZyntraBot
  .create()
  .withId('multi-bot')
  .withName('Multi-Platform Bot')
  .addAdapters({
    telegram: telegram({ token: '...', handle: '@bot' }),
    whatsapp: whatsapp({ token: '...', phone: '...' }),
    discord: discord({ token: '...', applicationId: '...' })
})
  .addCommand('broadcast', {
    name: 'broadcast',
    description: 'Send to all platforms',
    args: z.object({ message: z.string() }),
    async handle(ctx, args) {
      const adapters = ctx.bot.getAdapters?.() || {}
      for (const [key, adapter] of Object.entries(adapters)) {
        await ctx.bot.send({
          provider: key,
          channel: ctx.channel.id,
          content: { type: 'text', content: args.message }
        })
      }
    }
  })
  .build()

E-commerce Bot

import { memoryStore, rateLimitMiddleware } from '@zyntra-js/bot'

const bot = ZyntraBot
  .create()
  .withId('shop-bot')
  .withName('Shop Bot')
  .withSessionStore(memoryStore())
  .addAdapter('telegram', telegram({ ... }))
  .addMiddleware(rateLimitMiddleware({ maxRequests: 20 }))
  .addCommand('catalog', {
    name: 'catalog',
    async handle(ctx) {
      const products = await getProducts()
      await ctx.replyWithButtons(
        'Choose a product:',
        products.map(p => ({
          id: p.id,
          label: p.name,
          action: 'callback',
          data: `product:${p.id}`
        }))
      )
    }
  })
  .addCommand('cart', {
    name: 'cart',
    async handle(ctx) {
      const cart = ctx.session.data.cart || []
      const total = cart.reduce((s, item) => s + item.price, 0)
      await ctx.reply(`Cart total: $${total}`)
    }
  })
  .build()

More examples in examples/ directory.


API Reference

ZyntraBot Builder

| Method | Description | |--------|-------------| | .create() | Creates new builder instance | | .withId(id) | Sets bot ID (required) | | .withName(name) | Sets bot name (required) | | .withLogger(logger) | Configures logger | | .withSessionStore(store) | Configures session storage | | .withOptions(options) | Advanced options | | .addAdapter(key, adapter) | Adds platform adapter | | .addAdapters(adapters) | Adds multiple adapters | | .addCommand(name, command) | Adds command | | .addCommands(commands) | Adds multiple commands | | .addCommandGroup(prefix, commands) | Adds prefixed command group | | .addMiddleware(middleware) | Adds middleware | | .addMiddlewares(middlewares) | Adds multiple middlewares | | .usePlugin(plugin) | Loads plugin | | .onMessage(handler) | Message event listener | | .onError(handler) | Error event listener | | .onCommand(handler) | Command event listener | | .onStart(handler) | Start hook | | .build() | Builds bot instance |

Bot Instance

| Method | Description | |--------|-------------| | start() | Initialize bot (webhooks, commands) | | handle(provider, request) | Handle webhook request | | send(params) | Send message via adapter | | registerAdapter(key, adapter) | Add adapter at runtime | | registerCommand(name, command) | Add command at runtime | | use(middleware) | Add middleware at runtime | | on(event, handler) | Subscribe to event | | emit(event, ctx) | Emit event manually |

Context Object

| Property/Method | Description | |--------|-------------| | event | Event type (message, error, start) | | provider | Platform name (telegram, whatsapp) | | channel | Channel info (id, name, isGroup) | | message | Message data (content, author, etc) | | session | Session helper | | bot | Bot instance methods | | reply(content) | Send reply | | replyWithButtons(text, buttons) | Interactive reply | | replyWithImage(image, caption) | Image reply | | replyWithDocument(file, caption) | Document reply | | editMessage(id, content) | Edit message | | deleteMessage(id) | Delete message | | react(emoji) | Add reaction |


Testing

import { describe, it, expect } from 'vitest'
import { ZyntraBot, telegram } from '@zyntra-js/bot'

describe('My Bot', () => {
  it('should respond to /start', async () => {
    const bot = ZyntraBot
      .create()
      .withId('test-bot')
      .withName('Test')
      .addAdapter('telegram', telegram({ token: 'test', handle: '@test' }))
      .addCommand('start', {
        name: 'start',
        async handle(ctx) {
          await ctx.reply('Started!')
        }
      })
      .build()
    
    expect(bot).toBeDefined()
  })
})

Security Best Practices

  1. Never commit tokens - Use environment variables
  2. Use webhook secrets - Validate incoming requests
  3. Enable rate limiting - Prevent abuse
  4. Implement auth - Whitelist authorized users
  5. Validate inputs - Use Zod schemas
  6. Handle errors gracefully - Use onError handler
  7. Sanitize output - Escape special characters
  8. Log securely - Don't log sensitive data

Performance Tips

  • Use memory store for development, Redis for production
  • Enable rate limiting to prevent spam
  • Use logging presets appropriate for environment
  • Check capabilities before attempting unsupported operations
  • Cache expensive operations in session data
  • Use middleware to avoid repeating logic
  • Monitor with analytics plugin

Roadmap

| Feature | Status | |---------|--------| | ✅ Builder Pattern | Completed | | ✅ Session Management | Completed | | ✅ Plugin System | Completed | | ✅ Official Middlewares | Completed | | ✅ Capabilities System | Completed | | ✅ Discord Adapter | Completed | | 📋 Slack Adapter | Planned | | 📋 Redis Session Store | Planned | | 📋 Prisma Session Store | Planned | | 📋 Interactive Components (advanced) | Planned | | 📋 Test Utilities | Planned | | 📋 CLI for scaffolding | Planned |


Contributing

We welcome contributions! Please:

  1. Fork the repository
  2. Create a feature branch: feat/bot-<feature>
  3. Follow the AGENTS.md guidelines
  4. Run npm run build and npm run typecheck
  5. Submit a PR with clear description

Documentation


Support

  • Website: https://zyntrajs.com
  • Issues: https://github.com/matheuszwilk/zyntra-js/issues
  • Discord: Coming soon
  • Email: [email protected]

License

MIT © Matheus Pereira & ZyntraJS Contributors


Acknowledgments

This package is part of the ZyntraJS ecosystem - a modern, type-safe HTTP framework for TypeScript applications.

Built with ❤️ for developers who love type safety and excellent DX.