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

@neoxr/telegram

v0.0.1

Published

A lightweight and pluggable Telegram Bot API library for sending messages, managing chats, users, media, and bot interactions

Readme

@neoxr/telegram

A high-performance TypeScript/JavaScript framework for building production-grade Telegram bots with Telegraf. Designed for scalability, it features an automated long-polling manager, rich message serialization, instant hot-reloading plugin architecture, multi-tenant child bot virtualization, and reactive database proxy adapters.


Table of Contents


Features

  • Robust Telegram Connection: Long-polling engine with automatic backoff reconnection and TDLib/local Bot API server support.
  • Normalized Message Object (m): WhatsApp/Baileys-like normalized message interface with unified helpers (m.reply, m.edit, m.react, m.download, m.getProfilePhoto).
  • Interactive Messaging: Native support for inline buttons, chunked message delivery, and automatic Markdown/HTML fallbacks.
  • High-Speed Plugin Loader: Scans directories in parallel with high concurrency and instant hot-reloading via file system watchers.
  • Virtual Multi-Bot Architecture: Spin up dozens of child bots (subbots) within a single Node.js runtime process, each with its own isolated database scope and independent owners.
  • Unified Database Proxies: Reactive ORM-like proxies for JSON, SQLite (WAL mode), PostgreSQL, MySQL, MongoDB, and Redis with memory caching and background synchronization.
  • Extensive Moderation Suite: One-line helpers for bans, kicks, mutes, unpins, permissions, and group/channel metadata management.
  • Zero-Crash Safety: Graceful error handling, auto-fallback on malformed entity parsing, and safe shutdown handlers.

Requirements

  • Node.js 18.0.0 or higher (ESM support enabled)
  • A Telegram bot token obtained from @BotFather
  • Yarn, npm, or pnpm

Installation

Install the package:

yarn add @neoxr/telegram
# or
npm install @neoxr/telegram

Optional Database Drivers

Install the driver corresponding to the database proxy adapter you choose:

# For SQLite (Recommended for local low-latency)
yarn add better-sqlite3

# For PostgreSQL
yarn add pg

# For MySQL
yarn add mysql2

# For MongoDB
yarn add mongodb

# For Redis
yarn add redis

Quick Start

1. Configure Environment Variables

Create a .env file in the root directory:

BOT_TOKEN=123456789:AAHxxxxxxxxxxxxxxxxxxxxxxxxxxxx
DATABASE_URL=postgres://user:password@localhost:5432/bot_db

2. Create the Bot Entry Point (client.js)

import { Client } from '@neoxr/telegram'

const client = new Client({
  plugsdir: './plugins',
  debug: true
})

client.on('connect', (botInfo) => {
  console.log(`Connected to Telegram as @${botInfo.username}`)
})

client.on('ready', () => {
  console.log('Bot is ready to process updates')
})

client.on('message', async (ctx) => {
  const { m } = ctx
  if (m.text === '/ping') {
    await m.reply('Pong!')
  }
})

client.on('error', (err) => console.error('Bot Error:', err))

3. Run the Application

node client.js

Client Configuration

The Client class accepts configuration options to tune polling, plugin directories, multi-bot routing, and default prefixes:

import { Client } from '@neoxr/telegram'

const client = new Client({
  plugsdir: ['./plugins', './plugins/events'],
  create: {
    token: process.env.BOT_TOKEN
  },
  multiple: true,
  debug: false,
  prefix_data: 'db',
  setting: {
    multiprefix: true,
    prefix: ['.', '#', '!', '/'],
    onlyprefix: '/',
    hidden: [],
    pluginDisable: []
  },
  apiRoot: 'http://127.0.0.1:8081'
}, {
  telegraf: {
    handlerTimeout: 30000
  }
})

| Option | Type | Default | Description | | --- | --- | --- | --- | | plugsdir | string \| string[] | '' | Directory path(s) scanned by the plugin loader. | | create.token | string | process.env.BOT_TOKEN | Overrides the bot token used for initialization. | | multiple | boolean | false | Enables virtual child bot creation and multi-instance orchestration. | | debug | boolean | false | Outputs connection diagnostics and update logs to standard output. | | prefix_data | string | 'db' | The global variable key (global.db) used for data persistence. | | setting | object | {} | Configures default command prefixes, disabled plugins, and hidden categories. | | apiRoot / tdlib | string \| object | undefined | Custom Telegram Bot API server endpoint for uploading files larger than 50MB. |


Message Object (Serialized m)

When a message arrives, the framework serializes it into a unified, clean m object:

client.on('message', async ({ m }) => {
  console.log({
    id: m.id,
    chatId: m.chat,
    senderId: m.sender.id,
    username: m.sender.username,
    text: m.text,
    body: m.body,
    fromMe: m.fromMe,
    isMedia: m.isMedia,
    mentionedJid: m.mentionedJid
  })
})

Properties of m

| Property | Type | Description | | --- | --- | --- | | m.id | number | Unique Telegram message identifier. | | m.chat | number \| string | Chat identifier where the message was sent. | | m.isGroup | boolean | true if the message originated in a group or supergroup. | | m.isPrivate | boolean | true if the message originated in a direct message conversation. | | m.isChannel | boolean | true if the update originated from a broadcast channel. | | m.fromMe | boolean | true if the message was dispatched by this bot instance. | | m.isMedia | boolean | true if the message contains an image, video, document, audio, or sticker. | | m.sender | object | Information about the sender: { id, jid, name, username, isBot }. | | m.text / m.body | string | The text content, caption, or callback query data payload. | | m.mentionedJid | string[] | Array of user IDs and usernames mentioned inside the message entities. | | m.file | object \| null | File metadata object if media is attached. | | m.quoted | object \| null | Serialized message object of the replied/quoted message. |

Instance Methods on m

  • await m.reply(text, options): Replies directly to this message. Defaults to Markdown formatting with automatic text chunking and error fallback.
  • await m.edit(text, options): Edits the text or caption of this message in-place.
  • await m.delete(): Deletes this message from the chat.
  • await m.react(emoji): Applies an emoji reaction to this message (Bot API 7.0+).
  • await m.download(): Downloads the attached media file into a memory Buffer.
  • await m.getProfilePhoto(options): Retrieves the direct URL or file_id of the message sender avatar.

Quoted Message Helpers (m.quoted)

When m.quoted is present, it contains identical helper functions scoped to the quoted message:

if (m.quoted) {
  await m.quoted.reply('Replying directly to your referenced message')
  await m.quoted.react('❤️')
  const buffer = await m.quoted.download()
  const avatarUrl = await m.quoted.getProfilePhoto()
}

Messaging & Bot Helpers (client.poll.*)

The poll property on Client is an extended Telegraf instance injected with convenience methods for rich content dispatch and chat moderation.

Chat Presence & Actions

await client.poll.presence(chatId, 'typing')
await client.poll.presence(chatId, 'upload_photo')
await client.poll.presence(chatId, 'record_voice')
await client.poll.presence(chatId, 'upload_document')

Text & Interactive Messaging

reply(chatId, text, quoted, options)

Dispatches a text message. Text exceeding 4096 characters is safely divided into multiple sequential chunks without breaking sentences.

await client.poll.reply(m.chat, '*Hello world!*', m)

replyButton(chatId, text, buttons, quoted, options)

Sends an interactive message with inline keyboard buttons.

const buttons = [
  [
    { text: 'Documentation', url: 'https://telegram.org' },
    { text: 'Profile', callback_data: '.profile' }
  ],
  [
    { text: 'Confirm Action', callback_data: 'confirm_yes' }
  ]
]

await client.poll.replyButton(m.chat, 'Select an option below:', buttons, m)

edit(chatId, text, msgId, quoted, options)

Edits an existing text message or media caption.

await client.poll.edit(m.chat, 'Updated text', messageId)

delete(chatId, msgId) and deleteMessages(chatId, messageIds)

Removes single or multiple messages in one operation.

await client.poll.delete(m.chat, m.id)
await client.poll.deleteMessages(m.chat, [m.id, m.id - 1, m.id - 2])

react(chatId, msgId, emoji)

Applies an emoji reaction to a specific message.

await client.poll.react(m.chat, m.id, '🔥')

answerCallbackQuery(callbackQueryId, text, showAlert, options)

Acknowledges an inline keyboard button press to dismiss the loading animation.

await client.poll.answerCallbackQuery(ctx.callbackQuery.id, 'Action completed!', true)

sendProgress(chatId, finalText, quoted, options)

Renders an animated in-place updating percentage bar.

await client.poll.sendProgress(m.chat, 'Download complete 100%!', m)

Media & File Dispatchers

All media helpers accept URLs, local file paths, Buffer objects, or existing Telegram file_id strings. Captions longer than 1024 characters are automatically split, with the remainder delivered as follow-up text messages.

sendPhoto(chatId, source, caption, quoted, options)

await client.poll.sendPhoto(m.chat, 'https://example.com/banner.jpg', 'Project Overview', m, {
  reply_markup: {
    inline_keyboard: [[{ text: 'Details', callback_data: 'view_details' }]]
  }
})

sendVideo(chatId, source, caption, quoted, options)

await client.poll.sendVideo(m.chat, './media/clip.mp4', 'Sample video', m)

sendAudio(chatId, source, caption, quoted, options)

await client.poll.sendAudio(m.chat, buffer, 'Track 01 - Intro', m)

sendVoice(chatId, source, quoted, options)

await client.poll.sendVoice(m.chat, './media/voice.ogg', m)

sendDocument(chatId, source, filename, caption, quoted, options)

await client.poll.sendDocument(m.chat, './data/report.pdf', 'Annual_Report.pdf', 'Confidential', m)

sendSticker(chatId, source, quoted, options)

await client.poll.sendSticker(m.chat, './stickers/animated.tgs', m)

sendMediaGroup(chatId, mediaArray, quoted, options)

Sends an album of up to 10 photos or videos.

await client.poll.sendMediaGroup(m.chat, [
  { type: 'photo', media: 'https://example.com/1.jpg', caption: 'Image 1' },
  { type: 'photo', media: 'https://example.com/2.jpg' }
], m)

sendFile(chatId, source, filename, caption, quoted, options)

Universal media detector. Automatically detects the MIME type of the given buffer or URL and routes it to sendPhoto, sendVideo, sendAudio, or sendDocument.

await client.poll.sendFile(m.chat, 'https://example.com/file.zip', 'Archive.zip', 'Backup files', m)

User Profile & Verification

getProfilePhoto(userId, options)

Retrieves the direct download URL of a user highest-resolution avatar. Returns a default placeholder URL if private or unavailable.

const photoUrl = await client.poll.getProfilePhoto(m.sender.id)
if (photoUrl) {
  await client.poll.sendPhoto(m.chat, photoUrl, 'Profile Photo', m)
}

isBlockedBy(userId)

Performs a silent check using sendChatAction to verify if a user has blocked the bot or deleted their account, catching 403 Forbidden without sending visible messages.

const isBlocked = await client.poll.isBlockedBy(targetUserId)
if (isBlocked) {
  console.log('User has blocked the bot')
}

Group & Channel Administration

await client.poll.banMember(m.chat, targetUserId)
await client.poll.unbanMember(m.chat, targetUserId)
await client.poll.kickMember(m.chat, targetUserId)

await client.poll.muteMember(m.chat, targetUserId, 3600)
await client.poll.unmuteMember(m.chat, targetUserId)

await client.poll.setChatPermissions(m.chat, {
  can_send_messages: true,
  can_send_photos: false
})

await client.poll.promoteMember(m.chat, targetUserId, {
  can_manage_chat: true,
  can_delete_messages: true
})
await client.poll.demoteMember(m.chat, targetUserId)

await client.poll.pinMessage(m.chat, m.id)
await client.poll.unpinMessage(m.chat, m.id)
await client.poll.unpinAllMessages(m.chat)

await client.poll.setChatTitle(m.chat, 'Official Community Group')
await client.poll.setChatDescription(m.chat, 'Community guidelines and rules')
await client.poll.setChatPhoto(m.chat, './logo.jpg')
await client.poll.deleteChatPhoto(m.chat)

const link = await client.poll.exportInviteLink(m.chat)
const customLink = await client.poll.createInviteLink(m.chat, { member_limit: 10 })
await client.poll.revokeInviteLink(m.chat, customLink.invite_link)

Forum & Topic Management

Full support for supergroup forum topics (Telegram Bot API 6.3+):

const topic = await client.poll.createForumTopic(m.chat, 'General Discussion', {
  icon_color: 7322096
})

await client.poll.editForumTopic(m.chat, topic.message_thread_id, {
  name: 'Updated Topic Name'
})

await client.poll.closeForumTopic(m.chat, topic.message_thread_id)
await client.poll.reopenForumTopic(m.chat, topic.message_thread_id)
await client.poll.deleteForumTopic(m.chat, topic.message_thread_id)

Bot Commands & Menu

Set or read the command menu displayed next to the chat bar in Telegram:

await client.poll.setMyCommands([
  { command: 'start', description: 'Start the bot' },
  { command: 'menu', description: 'Show command list' },
  { command: 'profile', description: 'View user profile' }
])

const commands = await client.poll.getMyCommands()

Plugin Architecture & Hot Reloading

Plugins reside in the folder specified by plugsdir. Each plugin is an ES module exporting a run object. Changes are automatically detected and hot-reloaded within milliseconds without restarting the process.

Basic Command Plugin (plugins/ping.js)

export const run = {
  usage: ['ping'],
  category: 'general',
  async: async (m, { client }) => {
    await client.poll.reply(m.chat, 'Pong!', m)
  },
  error: false
}

Parameterized Plugin with Target Resolution (plugins/profile.js)

export const run = {
  usage: ['profile', 'me'],
  category: 'user',
  async: async (m, { client, args, db, command }) => {
    let targetId = m.quoted?.sender?.id 
      || (m.mentionedJid && m.mentionedJid[0]) 
      || (args[0] && args[0].replace(/^@/, '')) 
      || m.sender.id

    const user = db.users.get(targetId)
    if (!user) {
      return client.poll.reply(m.chat, 'User not found in database.', m)
    }

    const caption = [
      'User Profile:',
      `• ID: ${user.id}`,
      `• Name: ${user.name || '-'}`,
      `• Limit: ${user.limit ?? 0}`,
      `• Premium: ${user.premium ? 'Yes' : 'No'}`
    ].join('\n')

    const avatar = await client.poll.getProfilePhoto(user.id)
    if (avatar) {
      return client.poll.sendPhoto(m.chat, avatar, caption, m)
    }

    return client.poll.reply(m.chat, caption, m)
  },
  error: false
}

Multi-Bot / Child Bot System

Enable multiple: true in your client options to turn your bot into a parent instance capable of orchestrating independent child bots inside the same process.

       [Main Bot Instance]
                │
    ┌───────────┼───────────┐
    ▼           ▼           ▼
[Child Bot 1] [Child Bot 2] [Child Bot 3]
  (@bot_a)      (@bot_b)      (@bot_c)
    │           │           │
    ▼           ▼           ▼
[DB Scope 1]  [DB Scope 2]  [DB Scope 3]

Subbot Virtualization

Child bots inherit plugins and loaders from the main instance without duplicate disk scans, while maintaining independent long-polling connections and isolated databases.

Isolated Owner Access

When creating a child bot, pass the owner parameter. The child bot will automatically inject this owner into its database setting.owners:

const child = await client.create({
  create: { token: userSubmittedToken },
  owner: m.sender.id
})

Lifecycle Management

await client.start('bailysbot')
await client.stop('bailysbot')
await client.restart('bailysbot')
await client.autoReconnectAllBots()

Database Proxy Adapters

The framework exports Proxy containing 6 database adapters that wrap data structures in reactive ES6 Proxies. Any mutation to an object immediately writes to storage or disk with debounce and transaction protection.

import { Proxy } from '@neoxr/telegram'

const { proxyJson, proxySqlite, proxyPgSql, proxyMySql, proxyMongo, proxyRedis } = Proxy

Reactive In-Memory Architecture

All collection rows are cached in memory and indexed by Primary Key. When modifying properties:

const user = db.users.get(m.sender.id)

user.limit += 5
user.premium = true

Available Proxies

| Adapter | Backend | Characteristics | | --- | --- | --- | | proxyJson | Local JSON files | Zero setup. Includes atomic temporary writes (.tmp), debouncing, and WeakMap proxy caching. | | proxySqlite | better-sqlite3 | In-memory cache backed by SQLite WAL mode. Fast, low memory footprint, crash-proof. | | proxyPgSql | pg (PostgreSQL) | Native connection pooling, background hydration, transactional bulk writes. | | proxyMySql | mysql2/promise | Connection pooling with keep-alive, transactional bulk writes, and optional zlib Gzip payload compression. | | proxyMongo | mongodb | Native document mapping, bulk write operations, isolated collection prefix routing. | | proxyRedis | redis | In-memory Redis Hashes (HSET/HGETALL) with optional Gzip compression for large values. |

Primary Key & @username Resolution

All adapters use an intelligent primary key resolution engine. Data can be retrieved interchangeably by numerical ID, @username, or plain username:

const userById = db.users.get('7865157900')
const userByTag = db.users.get('@johndoe')
const userByName = db.users.get('johndoe')

Production Handler Architecture

Here is the battle-tested dispatcher architecture integrating Client, database routing, dynamic child-bot scoping, and command execution:

client.js

import { Client } from '@neoxr/telegram'
import system from './lib/adapter.js'
import { models } from './lib/models.js'
import handler from './handler.js'

let clientInstance = null

const connect = async () => {
  try {
    const client = new Client({
      plugsdir: './plugins',
      multiple: true,
      debug: false
    })

    clientInstance = client

    await system.init(models, models.structure)

    const bindListener = (bot) => {
      bot.on('message', async (ctx) => {
        handler(bot.poll, { ...ctx, system, connector: bot })
      })
    }

    bindListener(client)

    client._bind((child) => {
      bindListener(child)
    })
  } catch (err) {
    console.error('Connection failed:', err)
    setTimeout(connect, 5000)
  }
}

connect()

const shutdown = async (signal) => {
  console.log(`\nShutting down (${signal})...`)
  if (clientInstance?.close) await clientInstance.close('SHUTDOWN')
  if (system.closeAll) await system.closeAll()
  if (system.flushAll) await system.flushAll()
  process.exit(0)
}

process.on('SIGINT', () => shutdown('SIGINT'))
process.on('SIGTERM', () => shutdown('SIGTERM'))

Graceful Shutdown & Data Persistence

Ensure no transactions or queued file writes are lost during container redeployments or server restarts by hooking into termination signals:

const gracefulShutdown = async () => {
  await client.close('MAIN_SHUTDOWN')

  if (typeof system.flushAll === 'function') {
    await system.flushAll()
  }

  if (typeof system.closeAll === 'function') {
    await system.closeAll()
  }

  process.exit(0)
}

process.on('SIGINT', gracefulShutdown)
process.on('SIGTERM', gracefulShutdown)

Utilities & Bundled Modules

import { Utils, Cooldown, Spam, Config, NeoxrApi } from '@neoxr/telegram'
  • Utils: Common helpers for string formatting, greeting detectors, size formatting, and delay timers (Utils.delay).
  • Cooldown: In-memory token bucket rate-limiter for command throttling.
  • Spam: Message frequency monitor with warning, mute, and temporary ban thresholds.
  • Config: Auto-loaded configuration object from config.json in the root folder.
  • NeoxrApi: API client wrapper for Neoxr network services.