Powerful and epic overall,
puregram
allows you to
easily interact
with
Telegram Bot API
via
Node.js/TypeScript
😎👍
Introduction
First, what are Telegram bots? Telegram has their own bot accounts. Bots are special Telegram accounts that can be only accessed via code and were designed to handle messages, inline queries and callback queries automatically. Users can interact with bots by sending them messages, commands and inline requests.
Example
const { Telegram } = require('puregram');
const bot = Telegram.fromToken(process.env.TOKEN);
bot.updates.on('message', context => context.reply('Hey!'));
bot.updates.on('callback_query', context => context.answerCallbackQuery());
bot.updates.startPolling();
More examples here
Table of Contents
Why puregram
?
- Written by nitreojs ⚠
- Very cool package name
- Package itself is cool (at least I think so)
- Works (I guess)
- I understand only about 20% of my code
- Because why not?
That's why this package is epic
Getting started
Getting token
If you want to develop a bot, firstly you need to create it via @BotFather and get token from it via /newbot
command.
Token looks like this: 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
Installation
$ yarn add puregram
$ npm i -S puregram
Usage
Initializing Telegram
instance
Let's start with creating a Telegram
instance:
const { Telegram } = require('puregram');
const bot = new Telegram({
token: '123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11'
});
You can also initialize it via Telegram.fromToken
:
const bot = Telegram.fromToken('123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11');
Now, we want to get updates from the bot. How can we do it?
Getting updates
There are only two ways of getting updates right now:
- Polling via
getUpdates
method... or just using puregram
's built-in polling logic:
bot.updates.startPolling();
- Setting up a Webhook via
setWebhook
method:
const { createServer } = require('http');
bot.api.setWebhook({
url: 'https://www.example.com/'
});
const server = createServer(bot.updates.getWebhookMiddleware());
server.listen(8443, () => console.log('Started'));
Remember that there are only four accepted ports for now: 443
, 80
, 88
and 8443
. They are listed here under the Notes section.
More webhook examples are available here
Handling updates
Now with this setup we can catch updates like this:
bot.updates.on('message', context => context.reply('Yoooo!'));
List of supported updates will be somewhere here when it's done
Manual updates handling
Available from 2.0.8-rc.2 and later
If you want to handle updates by yourself, you can use Updates.handleUpdate
method, which takes one argument and this argument is raw Telegram update:
const update = await getUpdate(...);
let context;
try {
context = await bot.updates.handleUpdate(update);
} catch (error) {
console.log('Update is not supported', update);
}
Calling API methods
There are three ways of calling Telegram Bot API methods:
- Using the
bot.callApi(method, params?)
(useful when new Bot API update is released and the package is not updated yet):
const me = await bot.callApi('getMe');
- Using
bot.api.method(params?)
:
const me = await bot.api.getMe();
- Using context methods:
bot.updates.on('message', context => context.send('13² = 169! I mean 169, not 169!'));
Sending media
puregram
allows you to send your local media via path, Buffer
, Stream
and URLs.
const { createReadStream } = require('fs');
const path = './puppy.jpg';
const stream = createReadStream(path);
const buffer = getBuffer(path);
const url = 'https://puppies.com/random-puppy';
bot.updates.on('message', (context) => {
context.sendPhoto(path, { caption: 'Puppy via path!' });
context.sendDocument(stream, { caption: 'More puppies via stream!', filename: 'puppy.jpg' });
context.sendPhoto(buffer, { caption: 'One more puppy via buffer!' });
context.sendPhoto(url, { caption: 'Some random puppy sent using an URL!!!' });
});
This works for every method that can send media.
Using markdown
If you want to use Markdown or HTML, there are two ways of doing that:
- Using built-in
HTML
, Markdown
and MarkdownV2
classes:
const message = HTML.bold('Very bold, such HTML');
- Writing tags manually as it is told here:
const message = '*Very bold, such Markdown*';
Anyways, after writing the text you need to add parse_mode
field. There are also two ways of doing that ¯\_(ツ)_/¯:
- Writing actual parse mode code like a boss:
{ parse_mode: 'Markdown' }
- Passing parse mode class like a cheems:
{ parse_mode: HTML }
Final API request will look like this:
const message = `Some ${HTML.bold('bold')} and ${HTML.italic('italic')} here`;
context.send(message, { parse_mode: HTML });
context.send(`Imagine using _classes_ for parse mode, *lol*!`, { parse_mode: 'Markdown' });
The truth...
More markdown examples are available here
Keyboards
puregram
has built-in classes for creating basic, inline, force-reply etc. keyboards. They are pretty much easy to use and are definitely more comfortable than building a JSON.
InlineKeyboard
, Keyboard
and so on
To create a keyboard, you need to call keyboard
method from the keyboard class you chose. This method accepts an array of button rows.
const { InlineKeyboard, Keyboard } = require('puregram');
const keyboard = InlineKeyboard.keyboard([
[
InlineKeyboard.textButton({
text: 'Some text here',
payload: 'Such payload'
}),
InlineKeyboard.textButton({
text: 'Some more text here',
payload: { json: true }
})
],
[
InlineKeyboard.urlButton({
text: 'Some URL button',
url: 'https://example.com'
})
]
]);
const keyboard = Keyboard.keyboard([
Keyboard.textButton('Some one-row keyboard'),
Keyboard.textButton('with some buttons')
]);
Keyboard builders
There are also keyboard builders which are designed to be building a keyboard step by step:
const { KeyboardBuilder } = require('puregram');
const keyboard = new KeyboardBuilder()
.textButton('First row, first button')
.row()
.textButton('Second row, first button')
.textButton('Second row, second button')
.resize();
Sending keyboards
To send keyboard, you simply need to pass the generated value in reply_markup
field:
context.send('Look, here\'s a keyboard!', { reply_markup: keyboard });
More keyboards examples are available here
Bot information
If you are using puregram
's built-in polling logic, after Updates.startPolling()
is called you have access to Telegram.bot
property:
bot.updates.startPolling().then(
() => console.log(`@${bot.bot.username} started polling!`)
);
What are contexts?
Context is a class, containing current update
object and it's payload (via update[updateType]
). It is loaded with a ton of useful (maybe?) getters and methods that were made to shorten your code while being same efficient and executing the same code.
bot.updates.on('message', (context) => {
const id = context.senderId;
const id = context.from?.id;
});
bot.updates.on('message', (context) => {
context.send('Hey!');
bot.api.sendMessage({
chat_id: context.chat?.id,
text: 'Hey!'
});
});
Every context has telegram
property, so you can call API methods almost everywhere if you have a context nearby.
bot.updates.on('message', async (context) => {
const me = await context.telegram.api.getMe();
});
Context
and its varieties
Every update in puregram
is handled by a special context, which is detected via the update key.
Every context (except for manually created ones and some that were created after methods like sendMessage
) will have updateId
and update
properties.
Property | Required | Description |
---|
updateId | No | Unique update ID. Used as an offset when getting new updates |
update | No | Update object. Current context was created via this.update[this.updateType] |
For example, if we have the message
update, we will get MessageContext
on this update, CallbackQueryContext
for callback_query
update and so on.
Every context requires one argument:
interface ContextOptions {
telegram: Telegram;
updateType: UpdateName;
update?: TelegramUpdate;
updateId?: number;
}
You can also create any context manually:
const { MessageContext } = require('puregram');
const update = await getUpdate();
const context = new MessageContext({
telegram: bot,
update,
updateType: 'message',
updateId: update.update_id
});
Every context is listed here
Middlewares
puregram
uses middlewares, so you can use them to expand your context
variables or measure other middlewares.
next()
is used to call the next middleware on the chain and wait until it's done
Measuring the time it takes to proceed the update:
bot.updates.use(async (context, next) => {
const start = Date.now();
await next();
const end = Date.now();
console.log(`${context.updateId ?? '[unknown]'} proceeded in ${end - start}ms`);
});
Extending the context:
bot.updates.use((context, next) => {
context.user = await getUser(context.senderId);
return next();
});
bot.updates.on('message', (context) => {
return context.send(`Hey, ${context.user.name}!`);
});
TypeScript usage
Importing Telegram interfaces
All Telegram interfaces and method types are auto-generated and put in different files: telegram-interfaces.ts
for interfaces and methods.ts
+ api-methods.ts
for API methods. They all exist at the path puregram/lib/
.
import { TelegramUpdate, TelegramMessage } from 'puregram/lib/telegram-interfaces';
import { SendDocumentParams } from 'puregram/lib/methods';
Extending contexts
Surely enough, you can extend contexts with extra fields and properties you need by intersectioning base context with new properties.
interface ExtraData {
name: string;
id?: number;
}
bot.updates.use((context, next) => {
const user = await getUser(context.senderId);
bot.name = user.name;
bot.id = user.id;
return next();
});
bot.updates.on<ExtraData>('message', (context) => {
assert(context.name !== undefined);
});
FAQ
TypeError: Cannot read property '__scene' of undefined
You are trying to use @puregram/scenes
or @puregram/hear
with @puregram/session
, but you're the confusing middlewares order.
You should firstly initialize @puregram/session
's middleware and only then initialize other middlewares, depending on it:
const sessionManager = new SessionManager();
const hearManager = new HearManager();
bot.updates.on('message', sessionManager.middleware);
bot.updates.on('message', hearManager.middleware);
How do I enable debugging?
If you want to inspect out- and ingoing requests made by puregram
, you will need to enable DEBUG
environment variable so the package understands you are ready for logs.
How to enable DEBUG
Type | Example (Unix) | Description |
---|
api | DEBUG=puregram:api | Enables debugging API out- and ingoing requests |
updates | DEBUG=puregram:updates | Enables debugging ingoing updates |
* | DEBUG=puregram:* | Enables debugging all of the listed types above |
cmd
> set "DEBUG=puregram:*" & node index
PowerShell
> $env:DEBUG = "puregram:*"; node index
Linux
$ DEBUG=puregram:* node index
These packages are created by the puregram
community (and not only) and are expanding packages functionality (I guess).
Some official packages
Non-official ones
Oh no, it's empty there... Maybe you could add your community package here?
Thanks to