Opengram~ Opengram

The main class that implements receiving updates and setting up the bot before starting

A Opengram bot is an object containing an array of middlewares which are composed and executed in a stack-like manner upon request. Is similar to many other middleware systems that you may have encountered such as Koa, Ruby's Rack, Connect.

Middleware is an essential part of any modern framework. It allows you to modify requests and responses as they pass between the Telegram and your bot.

You can imagine middleware as a chain of logic connection your bot to the Telegram request.

Middleware normally takes two parameters (ctx, next), ctx is the context for one Telegram update, next is a function that is invoked to execute the downstream middleware. It returns a Promise with a then function for running code after completion.

const bot = new Opengram(process.env.BOT_TOKEN)

bot.use(async (ctx, next) => {
  const start = new Date()
  await next()
  const ms = new Date() - start
  console.log('Response time: %sms', ms)
})

bot.on('text', (ctx) => ctx.reply('Hello World'))
bot.launch()

Constructor

new Opengram(token, optionsopt)

Parameters:
NameTypeAttributesDescription
tokenstring

Bot token given by @BotFather

optionsOpengramOptions<optional>

Opengram options

Extends

Members

token

Setter for bot token

token

Getter for bot token

username

Setter for bot username

username

Getter for bot username

webhookReply

Setter for enabling / disabling for webhook reply. if assigned true - webhook reply enabled

webhookReply

Returns the status of the webhook reply (enabled / disabled). if true is returned, the webhook reply is enabled

Methods

action(triggers, …fns) → {Composer}

Registers some middleware(s) for callback queries, i.e. the updates that Telegram delivers to your bot when a user clicks an inline button (that is a button under a message).

This method is essentially the same as calling

bot.on('callback_query', ctx => { ... })

but it also allows you to match the query data against a given text or regular expression.

// Create an inline keyboard
const keyboard = Markup.inlineKeyboard([
  Markup.callbackButton('Go!', 'button-payload')
])
// Send a message with the keyboard
await bot.telegram.sendMessage(chat_id, 'Press a button!', keyboard.extra())
// Listen to users pressing buttons with that specific payload
bot.action('button-payload', ctx => { ... })

// Listen to users pressing any button your bot ever sent
bot.on('callback_query', ctx => { ... })

Always remember to call Telegram#answerCbQuery or OpengramContext#answerCbQuery — even if you don't perform any action: https://core.telegram.org/bots/api#answercallbackquery

bot.on('callback_query', async ctx => {
  await ctx.answerCbQuery()
})

You can pass one or an array of triggers (Regexp / strings). Your middleware(s) will be executed if at least one of them matches.

Note how ctx.match will contain the result of the regular expression. So ctx.match[1] refers to the part of the regexp that was matched by ([0-9]+), i.e. the text that comes after "button:".

bot.action(/button:([0-9]+)/, ctx => ctx.reply(`You choose button with number ${ctx.match[1]} in payload`))
const keyboard = Markup.inlineKeyboard([
 Markup.callbackButton('Button 1', 'button:1'),
 Markup.callbackButton('Button 2', 'button:2'),
 Markup.callbackButton('Button 3', 'button:3')
])
await bot.telegram.sendMessage(chat_id, 'Press a button!', keyboard.extra())

You can also paste function (or array of functions) that takes the value and context as arguments and returns true or false (or some Truthy result) based on them. This can be used, for example, for dynamic text matching at i18n. The result returned by the function will be available from ctx.match

bot.action(
  (value, ctx) => {
    //... some checks ...
    return ['some', 'data']
  },
  // Show cb query answer for all queries with "I love some data"
  ctx => ctx.answerCbQuery(`I love ${ctx.match[0]} ${ctx.match[1]}`)
)
Parameters:
NameTypeAttributesDescription
triggersTrigger | Array.<Trigger>

One or an array of regular expressions / strings to search in the payload

fnsMiddleware<repeatable>

The middleware(s) to register as arguments

Returns:
Type: 
Composer

cashtag(…args) → {Composer}

Registers some middleware(s) that will only be executed if hashtag entity is present in the update

Shortcut to Composer.entity('cashtag', ...)

This method matches entity in channel post, message and media caption

Parameters:
NameTypeAttributesDescription
argsMiddleware<repeatable>

The middleware(s) to register

Returns:
Type: 
Composer

catch(handler) → {Opengram}

Sets default error handler for Opengram errors. For example if you throw synchronous error or return / await promise was rejected, it calls this handler, if one is set. If it has not been set - the bot may crash, if some handler like process.on does not handle the error.

const { Opengram } = require('opengram')
const bot = new Opengram('token')

async function errorFirst() { throw new Error('Some error') }
function second() { return Promise.reject(new Error('Some error')) }

// Handled with bot.catch()
bot.on('message', ctx => { throw new Error('Some error') })

// Handled with bot.catch(), because you return promise (async function with await on async ops)
bot.hears('asyncFirst', async ctx => { await errorFirst()  })

// Handled with bot.catch(), because you return promise (async function with await on async ops)
bot.action('asyncSecond', async ctx => { await second()  })

// Handled with bot.catch(), because you return promise
bot.action('asyncSecond', ctx => errorFirst())

// Handled with bot.catch(), because you return promise
bot.on('message', ctx => second())

// Bot crashed, error not handled
bot.action('asyncSecond', ctx => { errorFirst()  })

// Bot crashed, error not handled
bot.on('message', async ctx => { second()  })

bot.catch((err, ctx) => console.log(err, ctx)) // Print error and error context to console, no crash

bot.launch() // Start the bot
Parameters:
NameTypeDescription
handlerfunction

Error handler accepting error and context

Returns:
Type: 
Opengram

command(commands, …fns)

Registers some middleware(s) that will only be executed when a certain command is found.

// Reacts to /start commands
bot.command('start', ctx => { ... })
// Reacts to /help commands
bot.command('help', ctx => { ... })

Note: Commands are not matched in the middle of the text.

bot.command('start', ctx => { ... })
// ... does not match:
// A message saying: “some text /start some more text”
// A photo message with the caption “some text /start some more text”

By default, commands are detected in channel posts and media captions, too. This means that ctx.message for channel post or ctx.message.text for media is potentially undefined, so you should use ctx.channelPost and ctx.message.caption accordingly for channel posts. Alternatively, if you want to limit your bot to finding commands only in private and group chats, you can use

const { Opengram, Composer: { command } } = require('opengram')
// ...
bot.on('message', command('start', ctx => ctx.reply('Only private / group messages or media with caption')))`

or using Composer.chatType:

const { Opengram, Composer, Composer: { command } } = require('opengram')
// ...
bot.use(
  Composer.chatType(
    ["private", "group", "supergroup"],
    command('start', ctx => ctx.reply('Only private / group messages or media with caption'))
  )
)

for match all message exclude channel posts, or

const { Opengram, Composer: { command } } = require('opengram')
// ...
bot.on('text', command('start', ctx => ctx.reply('Math commands only text, not media captions')))

for match only text message, not media caption or even store a message-only version of your bot in a variable like so:

Be careful, the example above may not work as expected if channelMode is enabled.

By default text type not match channel posts, but channel_post matched as text type and ctx.message potentially undefined when channelMode enabled. You can add additional chat type check for this case

Parameters:
NameTypeAttributesDescription
commandsstring | Array.<string> | 'start' | 'settings' | 'help'

The command or array of commands to look for

fnsMiddleware<repeatable>

The middleware(s) to register as arguments

customEmoji(…args) → {Composer}

Registers some middleware(s) that will only be executed if custom_emoji entity is present in the update

Shortcut to Composer.entity('custom_emoji', ...)

This method matches entity in channel post, message and media caption

Parameters:
NameTypeAttributesDescription
argsMiddleware<repeatable>

The middleware(s) to register

Returns:
Type: 
Composer

drop(predicate) → {Composer}

Registers middleware behind a custom filter function that operates on the context object and decides whether to execute the middleware.

In other words, the middleware(s) after that middleware will only be executed if the given predicate returns false for the given context object. Note that the predicate may be asynchronous, i.e. it can return a Promise of a boolean.

This method is the same using filter (normal usage) with a negated predicate.

// Drop all message updates sent more than 6 hr in all middlewares / handlers registered after bot.drop(...)
bot.drop(ctx => {
  if(!ctx.message) return false // Drop only messages
  return (Date.now() / 1000) - ctx.message.date < 60 * 60 * 6
})
// Called only for messages with date < 6 hr after send
bot.on('message', () => ctx.reply('Good, update date less then 6 hours!'))
Parameters:
NameTypeDescription
predicatePredicateFn | boolean

The predicate to check. Can be async, returns boolean or Promise with boolean

Overrides
Returns:
Type: 
Composer

email(…args) → {Composer}

Registers some middleware(s) that will only be executed if email entity is present in the update

Shortcut to Composer.entity('email', ...)

This method matches entity in channel post, message and media caption

Parameters:
NameTypeAttributesDescription
argsMiddleware<repeatable>

The middleware(s) to register

Returns:
Type: 
Composer

entity(predicate, …fns) → {Composer}

Registers some middleware(s) that will only be executed if a certain entity is present in the update

This method matches entity in channel post, message and media caption

Parameters:
NameTypeAttributesDescription
predicateEntityPredicate

The predicate to check. Can be async, returns boolean or Promise with boolean

fnsMiddleware<repeatable>

The middleware(s) to register

Returns:
Type: 
Composer

filter(predicate) → {Composer}

Registers middleware(s) behind a custom filter function that operates on the context object and decides whether to execute the middleware. In other words, the middleware will only be executed if the given predicate returns true for the given context object. Otherwise, it will be skipped and the next middleware will be executed.

In other words, the middleware after that middleware will only be executed if the given predicate returns true for the given context object. Note that the predicate may be asynchronous, i.e. it can return a Promise of a boolean.

// Only process every second update
bot.filter(ctx => ctx.update.update_id % 2 === 0)
bot.on('message', ctx => ctx.reply('Update id of this message is divided by two without a remainder'))
Parameters:
NameTypeDescription
predicatePredicateFn | boolean

The predicate to check. Can be async, returns boolean or Promise with boolean

Returns:
Type: 
Composer

gameQuery(…fns) → {Composer}

Registers some middleware(s) for game queries, i.e. the updates that Telegram delivers to your bot when a user clicks an inline button for the HTML5 games platform on Telegram.

This method is essentially the same as calling

bot.on('callback_query', ctx => {
 if (ctx.callbackQuery.game_short_name) {
   ...
 }
})
Parameters:
NameTypeAttributesDescription
fnsMiddleware<repeatable>

The middleware to register as arguments

Returns:
Type: 
Composer

(async) handleUpdate(update, webhookResponseopt) → {Promise}

Starting processing one update

Parameters:
NameTypeAttributesDescription
updateUpdate

Update object

webhookResponsehttp.ServerResponse<optional>

Response object for send webhook reply

Throws:
Returns:
Type: 
Promise

(async) handleUpdates(updates) → {Promise}

Starting processing array of updates

Parameters:
NameTypeDescription
updatesArray.<Update>

Array of updates

Throws:

Error

Returns:
Type: 
Promise

hashtag(…args) → {Composer}

Registers some middleware(s) that will only be executed if hashtag entity is present in the update

Shortcut to Composer.entity('hashtag', ...)

This method matches entity in channel post, message and media caption

Parameters:
NameTypeAttributesDescription
argsMiddleware<repeatable>

The middleware(s) to register

Returns:
Type: 
Composer

hears(triggers, …fns)

Registers some middleware(s) that will only be executed when the message / channel post contains some text (in media caption too). Is it possible to pass a regular expression to match:

// Match some text (exact match)
bot.hears('I love anime', ctx => ctx.reply('I love too'))

// Match a regular expression
bot.hears(/\/echo (.+)/, ctx => ctx.reply(ctx.match[1]))

Note how ctx.match will contain the result of the regular expression. So ctx.match[1] refers to the part of the regex that was matched by (.+), i.e. the text that comes after "/echo".

You can also paste function (or array of functions) that takes the value and context as arguments and returns true or false (or some Truthy result) based on them. This can be used, for example, for dynamic text matching at i18n. The result returned by the function will be available from ctx.match

bot.hears(
  (value, ctx) => {
    //... some checks ...
    return ['some', 'data']
  },
  ctx => ctx.reply(`I love ${ctx.match[0]} ${ctx.match[1]}`) // Replies at all with "I love some data"
)

You can pass an array of triggers. Your middleware will be executed if at least one of them matches.

Both text and captions of the received messages will be scanned. For example, when a photo is sent to the chat and its caption matches the trigger, your middleware will be executed.

If you only want to match text messages and not captions, you can do this:

const { Composer: { hears } } = require('opengram')
// Only matches text messages for the regex
bot.on('text', hears(/\/echo (.+)/, ctx => { ... }))

Be careful, the example above may not work as expected if channelMode is enabled.

By default text type not match channel posts, but channel_post matched as text type and ctx.message potentially undefined when channelMode enabled. You can add additional chat type check for this case

Parameters:
NameTypeAttributesDescription
triggersTrigger | Array.<Trigger>

The text / array of texts / regex / function to look for

fnsMiddleware<repeatable>

The middleware(s) to register as argument(s)

help(…fns) → {Middleware}

Registers some middleware that will only be executed when /help command is found.

Shortcut to Composer.command('help', ...)

Parameters:
NameTypeAttributesDescription
fnsMiddleware<repeatable>

The middleware(s) to register

Overrides
Returns:
Type: 
Middleware

inlineQuery(triggers, …fns) → {Composer}

Registers middleware for inline queries. Telegram sends an inline query to your bot whenever a user types @your_bot_name ... into a text field in Telegram.

Your bot will then receive the entered search query and can respond with a number of results (text, images, etc.) that the user can pick from to send a message via your bot to the respective chat. Check here to read more about inline bots.

Note that you have to enable inline mode for you bot by contacting @BotFather first.

// Listen for users typing `@your_bot_name query`
bot.inlineQuery('query', async ctx => {
  // Answer the inline query, confer https://core.telegram.org/bots/api#answerinlinequery
  await ctx.answerInlineQuery( ... )
})

You can pass one or an array of triggers (Regexp / strings). Your middleware(s) will be executed if at least one of them matches.

Note how ctx.match will contain the result of the regular expression. So ctx.match[1] refers to the part of the regexp that was matched by ([0-9]+), i.e. the text that comes after "query:".

// Listen for users typing `@your_bot_name query`
bot.inlineQuery(/query:([0-9]+)/, async ctx => {
  // Answer the inline query, confer https://core.telegram.org/bots/api#answerinlinequery
  await ctx.answerInlineQuery([{
    type: 'article',
    id: Math.random(),
    title: 'Regex test',
    cache_time: 1,
    description: `Query Regex result: ${ctx.match[1]}`,
    input_message_content: {
      message_text: `Query Regex result: ${ctx.match[1]}`,
    }
  }])
})

You can also paste function (or array of functions) that takes the value and context as arguments and returns true or false (or some Truthy result) based on them. This can be used, for example, for dynamic text matching at i18n. The result returned by the function will be available from ctx.match

bot.inlineQuery(
  (value, ctx) => {
    //... some checks ...
    return ['some', 'data']
  },
  // Show cb query answer for all queries with "I love some data"
  ctx => ctx.answerInlineQuery([{
    type: 'article',
    id: Math.random(),
    title: 'Regex test',
    cache_time: 1,
    description: `I love ${ctx.match[0]} ${ctx.match[1]}`,
    input_message_content: {
      message_text: `I love ${ctx.match[0]} ${ctx.match[1]}`,
    }
  }])
})
Parameters:
NameTypeAttributesDescription
triggersTrigger | Array.<Trigger>

The inline query text or array of text to match

fnsMiddleware<repeatable>

The middleware(s) to register

Returns:
Type: 
Composer

(async) launch(configopt) → {Promise.<Opengram>}

Launching a bot with a given config

Parameters:
NameTypeAttributesDescription
configLaunchConfig<optional>

Launch configuration

Throws:

Error

Returns:
Type: 
Promise.<Opengram>

mention(…args) → {Composer}

Registers some middleware(s) that will only be executed if mention entity is present in the update

Shortcut to Composer.entity('mention', ...)

This method matches entity in channel post, message and media caption

Parameters:
NameTypeAttributesDescription
argsMiddleware<repeatable>

The middleware(s) to register

Returns:
Type: 
Composer

middleware() → {Middleware}

Returns the middleware to embed

Returns:
Type: 
Middleware

on(updateTypes, …fns) → {Composer}

Registers some middleware(s) that will only be executed for some specific updates, namely those matching the provided filter query. Filter queries are a concise way to specify which updates you are interested in.

Here are some examples of valid filter queries:

// All kinds of message updates
bot.on('message', ctx => { ... })

// Text messages
bot.on('text', ctx => { ... })

// Messages with document
bot.on('document', ctx => { ... })

It is possible to pass multiple filter queries in an array, i.e.

// Matches all messages that contain a video or audio
bot.on(['audio', 'video'], ctx => { ... })

Your middleware will be executed if any of the provided filter queries matches (logical OR).

This method returns same as Composer#use.

Parameters:
NameTypeAttributesDescription
updateTypesUpdateType | UpdateSubtype | Array.<(UpdateType|UpdateSubtype)>

The update type or array of update types to use, may also be an array or string

fnsMiddleware<repeatable>

The middleware(s) to register with the given types as argument(s)

Overrides
Returns:
Type: 
Composer

phone(…args) → {Composer}

Registers some middleware(s) that will only be executed if phone entity is present in the update

Shortcut to Composer.entity('phone', ...)

This method matches entity in channel post, message and media caption

Parameters:
NameTypeAttributesDescription
argsMiddleware<repeatable>

The middleware(s) to register

Returns:
Type: 
Composer

secretPathComponent() → {string}

Generate secret token for webhook path

Returns the same result with the same token and process.version

Returns:
  • 256bit hex string, not really random, based on bot token and process.version.
Type: 
string

settings(…fns) → {Middleware}

Registers some middleware that will only be executed when /settings command is found.

Shortcut to Composer.command('settings', ...)

Parameters:
NameTypeAttributesDescription
fnsMiddleware<repeatable>

The middleware(s) to register

Returns:
Type: 
Middleware

spoiler(…args) → {Composer}

Registers some middleware(s) that will only be executed if spoiler entity is present in the update

Shortcut to Composer.entity('spoiler', ...)

This method matches entity in channel post, message and media caption

Parameters:
NameTypeAttributesDescription
argsMiddleware<repeatable>

The middleware(s) to register

Returns:
Type: 
Composer

start(…fns) → {Middleware}

Registers some middleware that will only be executed when /start command is found.

Shortcut to Composer.command('start', ...), but with additional functionally, when you use this and deep linking, you can get start payload from ctx.startPayload

For example if user start the bot from link like this: http://t.me/examplebot?start=1234

With this code, bot reply user with text of start payload:

bot.start(ctx => ctx.reply(`Start payload: ${ctx.startPayload}`)) // Reply with "Start payload: 1234"
Parameters:
NameTypeAttributesDescription
fnsMiddleware<repeatable>

The middleware(s) to register

Returns:
Type: 
Middleware

startPolling(timeoutopt, limitopt, allowedUpdatesopt, stopCallbackopt) → {Opengram}

Starts long polling and updates processing with given configuration

Parameters:
NameTypeAttributesDefaultDescription
timeoutnumber<optional>
30

Timeout in seconds for long polling. Defaults to 30

limitnumber<optional>
100

Limits the number of updates to be retrieved. Values between 1-100 are accepted. Defaults to 100.

allowedUpdatesUpdateType | Array.<UpdateType><optional>

Array of allowed updates or update name For example, specify ["message", "edited_channel_post", "callback_query"] to only receive updates of these types. Please note that this parameter doesn't affect updates created before the call to the getUpdates, so unwanted updates may be received for a short period of time.

stopCallbackfunction<optional>

Function called when bot fully stopped. If you call bot.stop() it be rewritten with other function and never called, for using with bot.stop, you can pass callback into bot.stop argument, for example bot.stop(() => console.log('Stopped'))

Returns:
Type: 
Opengram

startWebhook(optionsopt, tlsOptionsopt, portopt, hostopt, nextCbopt) → {Opengram}

Start webhook server based on NodeJS http or https modules on given host, port

const { Opengram } = require('opengram')
const fs = require('fs')
const bot = new Opengram(process.env.BOT_TOKEN)

bot.hears('hello', => ctx.reply('hi'))
bot.catch(console.log) // Setup error handler

// TLS options
const tlsOptions = {
    key: fs.readFileSync('server-key.pem'), // Private key file
    cert: fs.readFileSync('server-cert.pem') // Certificate file,
    ca: [
      // This is necessary only if the client uses a self-signed certificate.
      fs.readFileSync('client-cert.pem')
    ]
}

// Start webhook server
bot.startWebhook(
  {
    path: '/bot',
    secret: '1234567890'
  },
  tlsOptions,
  3000 // Port
  '0.0.0.0', // Host, may use if server have multiple IP adders
  (req, res) => { // Custom next handler
    res.statusCode = 403
    res.end('Not allowed!')
  }
)

// Set telegram webhook
// The second argument is necessary only if the client uses a self-signed
// certificate. Including it for a verified certificate may cause things to break.
bot.telegram.setWebhook('https://example.com:3000/bot', {
  source: 'server-cert.pem'
})
Parameters:
NameTypeAttributesDescription
optionsStartWebhookOptions<optional>

Webhook options

tlsOptionsobject | null<optional>

Options for https NodeJS module, see official docs

portnumber<optional>

Port to listen. Be careful, Telegram only supports 443, 80, 88, 8443 for now.

hoststring | null<optional>

If host is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available, or the unspecified IPv4 address (0.0.0.0) otherwise.

nextCbfunction<optional>

Next handler function, called when webhook handler not match path string or request method. May have two arguments - req, res. If not specified, by default connection being closed with HTTP status Forbidden 403

Returns:
Type: 
Opengram

(async) stop(cbopt) → {Promise.<void>}

Stopping the bot. For webhook, it will close the server, for long polling stop getting updates

Parameters:
NameTypeAttributesDescription
cbfunction<optional>

Callback function, which called when bot fully stopped

Returns:
Type: 
Promise.<void>

Registers some middleware(s) that will only be executed if text_link entity is present in the update

Shortcut to Composer.entity('text_link', ...)

This method matches entity in channel post, message and media caption

Parameters:
NameTypeAttributesDescription
argsMiddleware<repeatable>

The middleware(s) to register

Returns:
Type: 
Composer

textMention(…args) → {Composer}

Registers some middleware(s) that will only be executed if text_mention entity is present in the update

Shortcut to Composer.entity('text_mention', ...)

This method matches entity in channel post, message and media caption

Parameters:
NameTypeAttributesDescription
argsMiddleware<repeatable>

The middleware(s) to register

Returns:
Type: 
Composer

url(…args) → {Composer}

Registers some middleware(s) that will only be executed if url entity is present in the update

Shortcut to Composer.entity('url', ...)

This method matches entity in channel post, message and media caption

Parameters:
NameTypeAttributesDescription
argsMiddleware<repeatable>

The middleware(s) to register

Overrides
Returns:
Type: 
Composer

use(…fns) → {Composer}

Registers some middleware(s) that receives all updates. It is installed by concatenating it to the end of all previously installed middleware.

Often, this method is used to install middleware(s) that behaves like a plugin, for example session middleware.

bot.use(session())

You can pass middleware separated by commas as arguments or as a chain of calls:

const { Opengram, Stage, session } = require('opengram')
const bot = require('opengram')
const stage = new Stage([...])
bot.use(session(), stage) // As arguments

or

const { Opengram, Stage, session } = require('opengram')
const bot = require('opengram')
const stage = new Stage([...])
bot // As chain of calls
  .use(session())
  .use(stage)

This method returns a new instance of Composer.

Parameters:
NameTypeAttributesDescription
fnsMiddleware<repeatable>

The middleware(s) to register as arguments

Overrides
Returns:
Type: 
Composer

webhookCallback(config) → {function}

Generates webhook handler middleware for specified path for Koa / Express / Fastify / NodeJS http or https modules

Using example for express:

const { Opengram } = require('opengram')
const express = require('express')
const app = express()
const bot = new Opengram(process.env.BOT_TOKEN)

// Set the bot response
bot.on('text', ({ replyWithHTML }) => replyWithHTML('<b>Hello</b>'))

// Set telegram webhook
bot.telegram.setWebhook('https://exmaple.com/secret-path')

app.get('/', (req, res) => res.send('Hello World!'))
// Set the bot API endpoint
app.use(bot.webhookCallback('/secret-path'))
app.listen(3000, () => console.log('Bot listening on port 3000!'))
Parameters:
NameTypeDescription
configWebhookCallbackOptions

Options

Returns:
Type: 
function