
Security News
Deno 2.2 Improves Dependency Management and Expands Node.js Compatibility
Deno 2.2 enhances Node.js compatibility, improves dependency management, adds OpenTelemetry support, and expands linting and task automation for developers.
powerful and epic overall,
puregram
allows you to
easily interact
with
telegram bot api
via
node.js
ππ
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.
const { Telegram } = require('puregram')
const telegram = Telegram.fromToken(process.env.TOKEN)
telegram.updates.on('message', context => context.reply('hey!'))
telegram.updates.on('callback_query', context => context.answerCallbackQuery())
telegram.updates.startPolling()
you can find more examples here
puregram
? (very important!)Context
and its varietiespuregram
?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
node.js version must be greater or equal than LTS (
16.15.0
atm)
$ yarn add puregram
$ npm i -S puregram
Telegram
instancelet'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?
there are only two ways of getting updates right now:
getUpdates
method... or just using puregram
's built-in polling logic:telegram.updates.startPolling()
setWebhook
method:const { createServer } = require('http')
// you need to send this request only once
telegram.api.setWebhook({
url: 'https://www.example.com/'
})
const server = createServer(telegram.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
now with this setup we can catch updates like this:
telegram.updates.on('message', context => context.reply('yoo!'))
list of supported updates will be somewhere here when it's done
mergeMediaEvents
if you've had to handle multiple attachments at once you'd know that in telegram every single attachment is a separate message. that makes it pretty hard for us to handle multiple attachs at once. here it comes - the mergeMediaEvents
option in Telegram
's constructor
const telegram = new Telegram({
token: process.env.TOKEN,
mergeMediaEvents: true
})
what's changed? if you'd set up a handler like this:
telegram.updates.on('message', (context) => {
console.log(context)
})
and then sent an album, you'd see that there will be some mediaGroup
field in the MessageContext
. that mediaGroup
(instance of a MediaGroup
class) contains some getters:
getter | type | description |
---|---|---|
id | string | media group's id |
contexts | MessageContext[] | list of received (and processed) contexts which contain an attachment |
attachments | Attachment[] | list of attachments mapped through contexts (described earlier) |
telegram.updates.on('message', (context) => {
if (context.isMediaGroup) {
return context.reply(`this album contains ${context.mediaGroup.attachments.length} attachments!`)
}
})
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:
/** let's pretend i'm polling updates manually... */
const update = await getUpdate(...)
let context
try {
context = await telegram.updates.handleUpdate(update)
} catch (error) {
console.log('update is not supported', update)
}
// voila! now you have the right context
// (or you don't if the event is not supported π’)
there are three ways of calling telegram bot api methods:
telegram.api.call(method, params?)
(useful when new bot api update is released and the package is not updated yet):const me = await telegram.api.call('getMe')
telegram.api.method(params?)
:const me = await telegram.api.getMe()
telegram.updates.on('message', context => context.send('13Β² = 169! i mean "169", not "169!'))
puregram
allows you to send your local media by using MediaSource
class.
you can put URLs, Buffer
s, streams and paths in it.
/** let's imagine we have an image called puppy.jpg in this directory... */
const { createReadStream } = require('fs')
const path = './puppy.jpg'
const stream = createReadStream(path)
const buffer = getBuffer(path)
const url = 'https://puppies.com/random-puppy'
telegram.updates.on('message', (context) => {
await Promise.all([
context.sendPhoto(MediaSource.path(path), { caption: 'puppy via path!' })
context.sendDocument(MediaSource.stream(stream, /* filename: */ 'puppy.jpg'), { caption: 'more puppies via stream!' })
context.sendPhoto(MediaSource.buffer(buffer), { caption: 'one more puppy via buffer!' })
context.sendPhoto(MediaSource.url(url), { caption: 'some random puppy sent using an url!!!' })
])
})
this works for every method that can send media.
if you want to use markdown or html, there are two ways of doing that:
HTML
, Markdown
and MarkdownV2
classes:const message = HTML.bold('very bold, such html')
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 Β―\_(γ)_/Β―:
{ parse_mode: 'markdown' }
{ 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' })
since markdown-v2 requires a lot of chars to be escaped, i've came up with a beautiful idea...
const message = MarkdownV2.build`
damn that's a cool usage of ${MarkdownV2.bold('template strings')}!
${MarkdownV2.italic('foo')} bar ${MarkdownV2.underline('baz')}
starkow v3 when
`
more markdown examples are available here
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 onto 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([
[ // first row
InlineKeyboard.textButton({ // first row, first button
text: 'some text here',
payload: 'such payload'
}),
InlineKeyboard.textButton({ // first row, second button
text: 'some more text here',
payload: { json: true }
})
],
[ // second row
InlineKeyboard.urlButton({ // second row, first button
text: 'some url button',
url: 'https://example.com'
})
]
])
// one-row keyboard with two buttons, no brackets for rows needed
const keyboard = Keyboard.keyboard([
Keyboard.textButton('some one-row keyboard'),
Keyboard.textButton('with some buttons')
])
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() // keyboard will be much smaller
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 keyboard examples are available here
if you are using puregram
's built-in polling logic, after Updates.startPolling()
is called you have access to Telegram.bot
property:
telegram.updates.startPolling().then(
() => console.log(`@${telegram.bot.username} started polling`)
)
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.
telegram.updates.on('message', (context) => {
const id = context.senderId
// is the same as
const id = context.from?.id
})
telegram.updates.on('message', (context) => {
context.send('hey!')
// equals to
telegram.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.
telegram.updates.on('message', async (context) => {
const me = await context.telegram.api.getMe()
})
Context
and its varietiesevery 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 {
// main Telegram instance
telegram: Telegram
// update type, e.g. 'message', 'callback_query'
updateType: UpdateName
// whole update object
// optional, allows user to do the `context.update` to get the whole update object
update?: TelegramUpdate
// update id, located at TelegramUpdate
// optional, allows user to get this update's id
updateId?: number
}
you can also create any context manually:
const { MessageContext } = require('puregram')
const update = await getUpdate()
const context = new MessageContext({
telegram,
update,
updateType: 'message',
updateId: update.update_id
})
every context is listed here
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 donemeasuring the time it takes to proceed the update:
telegram.updates.use(async (context, next) => {
const start = Date.now()
await next() // next() is async, so we need to await it
const end = Date.now()
console.log(`${context.updateId ?? '[unknown]'} proceeded in ${end - start}ms`)
})
extending the context:
telegram.updates.use(async (context, next) => {
context.user = await getUser(context.senderId)
return next()
})
telegram.updates.on('message', (context) => {
// here we can access property we made in the middleware
return context.send(`hey, ${context.user.name}!`)
})
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 paths puregram/telegram-interfaces
, puregram/methods
and puregram/api-methods
respectively.
also there's a puregram/generated
export which exports everything from lib/generated
folder (all of those listed before).
import { TelegramUpdate, TelegramMessage } from 'puregram/generated'
import { SendDocumentParams } from 'puregram/generated'
import { CopyMessageParams } from 'puregram/methods'
import { InputFile, TelegramUpdate } from 'puregram/telegram-interfaces'
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
}
/** ... */
telegram.updates.use(async (context, next) => {
const user = await getUser(context.senderId)
context.name = user.name
context.id = user.id
return next()
})
/**
* there are 2 ways of updating context's type:
* 1. external type override:
* `(context: MessageContext & ExtraData) => ...`
* 2. using generics:
* `telegram.updates.on<ExtraData>(...)`
*
* below I will be using the second way.
*/
telegram.updates.on<ExtraData>('message', (context) => {
assert(context.name !== undefined)
})
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()
// 1. session middleware first
telegram.updates.on('message', sessionManager.middleware)
// 2. hear middleware second
telegram.updates.on('message', hearManager.middleware)
ExperimentalWarning: buffer.Blob is an experimental feature.
yes, this is because ^2.5.0
versions are using undici
's fetch
which is experimental atm.
you can look it up here for future changes
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.
DEBUG
namespace | example (unix) | description |
---|---|---|
api | DEBUG=puregram:api | enables debugging api out- and ingoing requests |
api/getMe | DEBUG=puregram:api/getMe | enables debugging getMe update (you can set whichever method you want to debug) |
updates | DEBUG=puregram:updates | enables debugging ingoing updates |
all | DEBUG=puregram:all | enables debugging all of the listed types above |
> set "DEBUG=puregram:all" & node index
> $env:DEBUG = "puregram:all"; node index
$ DEBUG=puregram:all node index
yeah, there are.
what | how to get here |
---|---|
channel π’ | click |
russian chat π·πΊ | press |
english chat π¬π§ | β: .q. o(β§β½β¦)o .q.:β |
puregram chats list π | d=====(οΏ£β½οΏ£*)b |
offtop chat π | α¦( β‘ η β‘ )α€ |
because i dont like doing anything that looks official so i do my own styling π
btw did you see these issues?
they confirm im against anything that looks kinda too official π
these packages are created by the puregram
community (and not only) and are expanding packages functionality (i guess).
@puregram/hear
: simple implementation of hear system@puregram/scenes
: simple implementation of middleware-based scene management@puregram/session
: simple implementation of sessions@puregram/utils
: useful utilities@puregram/prompt
: basic prompt system implementationnestjs-puregram
: puregram
sdk for nestjsFAQs
powerful and modern telegram bot api sdk for node.js and typescript π
The npm package puregram receives a total of 465 weekly downloads. As such, puregram popularity was classified as not popular.
We found that puregram demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Β It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Deno 2.2 enhances Node.js compatibility, improves dependency management, adds OpenTelemetry support, and expands linting and task automation for developers.
Security News
React's CRA deprecation announcement sparked community criticism over framework recommendations, leading to quick updates acknowledging build tools like Vite as valid alternatives.
Security News
Ransomware payment rates hit an all-time low in 2024 as law enforcement crackdowns, stronger defenses, and shifting policies make attacks riskier and less profitable.