Comparing version 2.0.8-rc.1 to 2.0.8-rc.2
@@ -14,7 +14,7 @@ /// <reference types="node" /> | ||
apiRetryLimit?: number; | ||
/** How much will library wait for API to answer before aborting the request? */ | ||
/** How much will library wait for API to answer before aborting the request? In milliseconds */ | ||
apiTimeout?: number; | ||
/** Headers of requests */ | ||
apiHeaders?: Record<string, string>; | ||
/** How much will library wait before retrying to get updates? */ | ||
/** How much will library wait before retrying to get updates? In milliseconds */ | ||
apiWait?: number; | ||
@@ -21,0 +21,0 @@ } |
@@ -98,17 +98,10 @@ "use strict"; | ||
debug(`[${method}] Params: ${body}`); | ||
let response; | ||
try { | ||
response = await node_fetch_1.default(url, { | ||
agent: this.options.agent, | ||
compress: false, | ||
method: 'POST', | ||
signal: controller.signal, | ||
headers, | ||
body | ||
}); | ||
} | ||
catch (e) { | ||
debug(e); | ||
/// TODO: request is invalid, what to do? | ||
} | ||
const response = await node_fetch_1.default(url, { | ||
agent: this.options.agent, | ||
compress: false, | ||
method: 'POST', | ||
signal: controller.signal, | ||
headers, | ||
body | ||
}); | ||
debug(`[${method}] <- HTTP ${(_a = response === null || response === void 0 ? void 0 : response.status) !== null && _a !== void 0 ? _a : '[not set]'}`); | ||
@@ -206,3 +199,3 @@ if (response !== undefined) { | ||
} | ||
// hack: remove 'media', 'photo', 'video' etc. keys from contextData | ||
// hack: remove 'media' key from `contextData` | ||
let { media: _, ...tempContextData } = contextData; | ||
@@ -209,0 +202,0 @@ for (const tempValue of values) { |
@@ -66,3 +66,3 @@ /// <reference types="node" /> | ||
private fetchUpdates; | ||
updateHandler(update: TelegramUpdate): Promise<void>; | ||
handleUpdate(update: TelegramUpdate): Promise<Context | undefined>; | ||
getKoaMiddleware(): Function; | ||
@@ -69,0 +69,0 @@ getWebhookMiddleware(): (req: http.IncomingMessage, res: http.ServerResponse) => Promise<void>; |
@@ -206,2 +206,3 @@ "use strict"; | ||
this.isStarted = false; | ||
this.retries = 0; | ||
} | ||
@@ -214,9 +215,19 @@ /** Start polling */ | ||
if (!this.telegram.options.token) { | ||
debug('Bot token is not set. Perhaps you forgot to set it?'); | ||
throw new TypeError('Token is not set. Perhaps you forgot to set it?'); | ||
} | ||
debug('Fetching bot data...'); | ||
const bot = new user_1.User(await this.telegram.api.getMe()); | ||
this.telegram.bot = bot; | ||
debug('Telegram bot data fetched.'); | ||
debug(bot); | ||
if (!this.telegram.bot) { | ||
debug('Fetching bot data...'); | ||
let me; | ||
try { | ||
me = await this.telegram.api.getMe(); | ||
} | ||
catch (error) { | ||
debug('Unable to fetch bot info, stopping right away'); | ||
return this.stopPolling(); | ||
} | ||
const bot = new user_1.User(me); | ||
this.telegram.bot = bot; | ||
debug('Bot data fetched successfully:'); | ||
debug(bot); | ||
} | ||
this.isStarted = true; | ||
@@ -226,22 +237,25 @@ try { | ||
} | ||
catch (e) { | ||
catch (error) { | ||
this.isStarted = false; | ||
throw e; | ||
throw error; | ||
} | ||
} | ||
async startFetchLoop(options) { | ||
while (this.isStarted) { | ||
try { | ||
try { | ||
while (this.isStarted) { | ||
await this.fetchUpdates(options); | ||
} | ||
catch (e) { | ||
debug('startFetchLoop:', e); | ||
if (this.retries === this.telegram.options.apiRetryLimit) { | ||
return; | ||
} | ||
this.retries += 1; | ||
await helpers_1.delay(this.telegram.options.apiWait); | ||
this.stopPolling(); | ||
this.startPolling(); | ||
} | ||
catch (error) { | ||
debug(error); | ||
if (this.retries === this.telegram.options.apiRetryLimit) { | ||
debug(`Tried to reconnect ${this.retries} times, but it didn't work, cya next time`); | ||
return; | ||
} | ||
this.retries += 1; | ||
debug(`Trying to reconnect, ${this.retries}/${this.telegram.options.apiRetryLimit} try`); | ||
await helpers_1.delay(this.telegram.options.apiWait); | ||
// not this.stopPolling() because it resets this.retries | ||
this.isStarted = false; | ||
this.startPolling(); | ||
} | ||
@@ -259,14 +273,22 @@ } | ||
const updates = await this.telegram.api.getUpdates(params); | ||
if (!updates.length) | ||
if (!updates) { | ||
/// Something is wrong with the internet connection I can feel it... | ||
debug('`fetchUpdates` error: unable to get updates'); | ||
this.stopPolling(); | ||
this.startPolling(); | ||
return; | ||
} | ||
if (!updates || !updates.length) | ||
return; | ||
updates.forEach(async (update) => { | ||
try { | ||
await this.updateHandler(update); | ||
await this.handleUpdate(update); | ||
} | ||
catch (e) { | ||
debug('fetchUpdates:', e); | ||
catch (error) { | ||
debug('`fetchUpdates` error:'); | ||
debug(error); | ||
} | ||
}); | ||
} | ||
async updateHandler(update) { | ||
async handleUpdate(update) { | ||
this.offset = update.update_id + 1; | ||
@@ -277,6 +299,7 @@ const type = Object.keys(update)[1]; | ||
if (!UpdateContext) { | ||
debug(`Unsupported context type ${type}`); | ||
debug(`Unsupported context type \`${type}\``); | ||
return; | ||
} | ||
debug('Update payload:', update[type]); | ||
debug('Update payload:'); | ||
debug(update[type]); | ||
let context = new UpdateContext({ | ||
@@ -303,2 +326,3 @@ telegram: this.telegram, | ||
this.dispatchMiddleware(context); | ||
return context; | ||
} | ||
@@ -314,3 +338,3 @@ getKoaMiddleware() { | ||
context.set('connection', 'keep-alive'); | ||
setImmediate(() => this.updateHandler(update)); | ||
setImmediate(() => this.handleUpdate(update)); | ||
}; | ||
@@ -339,3 +363,3 @@ } | ||
res.end(); | ||
setImmediate(() => this.updateHandler(update)); | ||
setImmediate(() => this.handleUpdate(update)); | ||
}; | ||
@@ -342,0 +366,0 @@ } |
import http from 'http'; | ||
export declare const applyMixins: (derivedCtor: any, baseCtors: any[]) => void; | ||
export declare const isPlainObject: (object: object) => boolean; | ||
export declare const isPlainObject: (object: object) => object is Record<string, any>; | ||
export declare const filterPayload: (payload: Record<string, any>) => Record<string, any>; | ||
@@ -5,0 +5,0 @@ export declare const isParseable: (source: string) => boolean; |
@@ -20,4 +20,3 @@ "use strict"; | ||
for (const [key, value] of Object.entries(payload)) { | ||
const notEmpty = (value !== undefined | ||
&& value !== null); | ||
const notEmpty = value !== undefined && value !== null; | ||
const isEmptyArray = (Array.isArray(value) | ||
@@ -40,3 +39,2 @@ && value.length === 0); | ||
JSON.parse(source); | ||
return true; | ||
} | ||
@@ -46,7 +44,6 @@ catch (e) { | ||
} | ||
return true; | ||
}; | ||
exports.isParseable = isParseable; | ||
const delay = (delayed) => (new Promise((resolve) => { | ||
setTimeout(resolve, delayed); | ||
})); | ||
const delay = (delayed) => (new Promise(resolve => setTimeout(resolve, delayed))); | ||
exports.delay = delay; | ||
@@ -53,0 +50,0 @@ const useLazyLoad = (fn) => { |
{ | ||
"name": "puregram", | ||
"version": "2.0.8-rc.1", | ||
"description": "Powerful Node.js package written on TypeScript that allows you to work with Telegram API", | ||
"version": "2.0.8-rc.2", | ||
"description": "Powerful and modern Telegram Bot API SDK for Node.js and TypeScript 😁", | ||
"main": "lib/index", | ||
@@ -12,4 +12,4 @@ "author": "nitrojs", | ||
"form-data": "^3.0.0", | ||
"inspectable": "^1.0.0", | ||
"middleware-io": "^2.5.0", | ||
"inspectable": "^1.2.0", | ||
"middleware-io": "^2.8.0", | ||
"node-fetch": "^2.6.1" | ||
@@ -19,10 +19,12 @@ }, | ||
"scripts": { | ||
"build": "tsc --project tsconfig.json" | ||
"clear-lib": "rm -rf lib/", | ||
"build": "yarn clear-lib && tsc --project tsconfig.json" | ||
}, | ||
"keywords": [ | ||
"telegram", | ||
"api", | ||
"sdk", | ||
"node", | ||
"js", | ||
"telegram-api", | ||
"telegram-bot-api", | ||
"session", | ||
"scenes", | ||
"typescript", | ||
"bot" | ||
@@ -35,12 +37,4 @@ ], | ||
"@types/debug": "^4.1.5", | ||
"@types/node-fetch": "^2.5.7", | ||
"@typescript-eslint/eslint-plugin": "^3.6.0", | ||
"@typescript-eslint/parser": "^3.6.0", | ||
"babel-jest": "^26.2.1", | ||
"eslint": "^7.4.0", | ||
"eslint-plugin-import": "^2.22.0", | ||
"jest": "^26.2.1", | ||
"ts-jest": "^26.1.4", | ||
"ts-node": "github:TypeStrong/ts-node#master", | ||
"typescript": "^4.3.2" | ||
"@types/node-fetch": "^2.5.10", | ||
"typescript": "^4.3.4" | ||
}, | ||
@@ -53,8 +47,7 @@ "directories": { | ||
"type": "git", | ||
"url": "git+https://github.com/nitreojs/puregram.git" | ||
"url": "git+https://github.com/nitreojs/puregram.git", | ||
"directory": "packages/puregram" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/nitreojs/puregram/issues" | ||
}, | ||
"bugs": "https://github.com/nitreojs/puregram/issues", | ||
"homepage": "https://github.com/nitreojs/puregram#readme" | ||
} |
666
README.md
@@ -1,48 +0,107 @@ | ||
<p align='center'> | ||
<img src='https://github.com/nitreojs/puregram/blob/master/docs/logo.png' /> | ||
</p> | ||
<!-- Inspired by prisma.io & Telegraf docs --> | ||
<div align='center'> | ||
<img src='https://i.imgur.com/ZzjmE8i.png' /> | ||
</div> | ||
<br /> | ||
<p align='center'> | ||
<b>Powerful</b> | ||
<a href='nodejs.org'>Node.js</a> | ||
package that allows you to | ||
<b>easily</b> | ||
interact with | ||
<a src='https://core.telegram.org/bots/api'>Telegram API</a> | ||
🚀 | ||
Powerful and epic overall, | ||
<code>puregram</code> | ||
allows you to | ||
<b>easily interact</b> | ||
with | ||
<a href='https://core.telegram.org/bots/api'>Telegram Bot API</a> | ||
via | ||
<a href='https://nodejs.org'>Node.js</a>/<a href='https://www.typescriptlang.org'>TypeScript</a> | ||
😎👍 | ||
</p> | ||
<table align='center'> | ||
<tr> | ||
<th> | ||
<a href='https://github.com/nitreojs/puregram/tree/master/docs/examples'> | ||
🤖 Examples | ||
</a> | ||
</th> | ||
<th> | ||
<a href='https://github.com/nitreojs/puregram/tree/master/docs'> | ||
📖 Documentation | ||
</a> | ||
</th> | ||
<th> | ||
<a href='https://t.me/puregram_chat'> | ||
💬 Chat | ||
</a> | ||
</th> | ||
<th> | ||
<a href='https://t.me/puregram_channel'> | ||
💬 Channel | ||
</a> | ||
</th> | ||
</tr> | ||
</table> | ||
<div align='center'> | ||
<a href='https://github.com/nitreojs/puregram/tree/master/docs'><b>Docs</b></a> | ||
<span> • </span> | ||
<a href='https://github.com/nitreojs/puregram/tree/master/docs/examples'><b>Examples</b></a> | ||
<span> • </span> | ||
<a href='#typescript-usage'><b>TypeScript usage</b></a> | ||
<span> • </span> | ||
<a href='https://t.me/puregram_chat'><b>Chat</b></a> | ||
<span> • </span> | ||
<a href='https://t.me/puregram_channel'><b>Channel</b></a> | ||
</div> | ||
## Features | ||
## Introduction | ||
* 100% [**Telegram Bot API**](https://core.telegram.org/bots/api) coverage | ||
* Works with JavaScript and TypeScript | ||
**First, what are Telegram bots?** [Telegram][telegram] has their own [bot accounts][telegram/bots]. **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._ | ||
## Installation | ||
> **[Node.js](https://nodejs.org/) 12.0.0 or newer is required** | ||
[telegram]: https://t.me | ||
[telegram/bots]: https://core.telegram.org/bots | ||
### Example | ||
```js | ||
const { Telegram } = require('puregram'); | ||
const bot = new Telegram({ | ||
token: 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][examples] | ||
[examples]: https://github.com/nitreojs/puregram/tree/master/docs/examples | ||
--- | ||
## Table of Contents | ||
- [Why `puregram`?](#why-puregram) _(very important!!)_ | ||
- [**Getting started**](#getting-started) | ||
- [Getting token](#getting-token) | ||
- [Installation](#installation) | ||
- [Usage](#usage) | ||
- [Calling API methods](#calling-api-methods) | ||
- [Sending media](#sending-media) | ||
- [Using markdown (`parse_mode`)](#using-markdown) | ||
- [Keyboards (`reply_markup`)](#keyboards) | ||
- [Bot information](#bot-information) | ||
- [What are contexts?](#what-are-contexts) | ||
- [`Context` and its varieties](#context-and-its-varieties) | ||
- [Middlewares](#middlewares) | ||
- [**TypeScript usage**](#typescript-usage) | ||
- [**FAQ**](#faq) | ||
--- | ||
## Why `puregram`? | ||
- Written **by [nitreojs](https://github.com/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][telegram/bots/botfather] via [@BotFather][botfather] and get token from it via `/newbot` command. | ||
[telegram/bots/botfather]: https://core.telegram.org/bots#6-botfather | ||
[botfather]: https://t.me/botfather | ||
Token looks like this: `123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11` | ||
### Installation | ||
```sh | ||
@@ -53,66 +112,519 @@ $ yarn add puregram | ||
## Polling example | ||
### Usage | ||
#### Initializing `Telegram` instance | ||
Let's start with creating a `Telegram` instance: | ||
```js | ||
import { Telegram } from 'puregram'; | ||
const { Telegram } = require('puregram'); | ||
const telegram = new Telegram({ | ||
token: process.env.TOKEN | ||
const bot = new Telegram({ | ||
token: '123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11' | ||
}); | ||
``` | ||
// Message received | ||
telegram.updates.on( | ||
'message', | ||
(context) => context.send('Hello, World!') | ||
); | ||
Now, we want to [get updates][getting-updates] from the bot. **How can we do it?** | ||
// Inline button pressed | ||
telegram.updates.on( | ||
'callback_query', | ||
(context) => ( | ||
context.message.editMessageText('*You just clicked the inline button!*', { | ||
parse_mode: 'Markdown' | ||
#### Getting updates | ||
There are only **two ways** of getting updates right now: | ||
1. Polling via [`getUpdates` method][getUpdates]... or just using `puregram`'s built-in polling logic: | ||
```js | ||
bot.updates.startPolling(); | ||
``` | ||
2. Setting up a Webhook via [`setWebhook` method][setWebhook]: | ||
```js | ||
const { createServer } = require('http'); | ||
// you need to send this request only once | ||
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][setWebhook] under the **Notes** section. | ||
More webhook examples are available [here][webhook-examples] | ||
[getting-updates]: https://core.telegram.org/bots/api#getting-updates | ||
[getUpdates]: https://core.telegram.org/bots/api#getupdates | ||
[setWebhook]: https://core.telegram.org/bots/api#setwebhook | ||
[webhook-examples]: https://github.com/nitreojs/puregram/tree/master/docs/examples/webhook | ||
#### Handling updates | ||
Now with this setup we can catch updates like this: | ||
```js | ||
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: | ||
```js | ||
/** Let's pretend I am polling updates manually... */ | ||
const update = await getUpdate(...); | ||
let context; | ||
try { | ||
context = await bot.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 😢) | ||
``` | ||
### Calling API methods | ||
There are **three ways** of calling Telegram Bot API methods: | ||
1. Using the `bot.callApi(method, params?)` _(useful when new Bot API update is released and the package is not updated yet)_: | ||
```js | ||
const me = await bot.callApi('getMe'); | ||
``` | ||
2. Using `bot.api.method(params?)`: | ||
```js | ||
const me = await bot.api.getMe(); | ||
``` | ||
3. Using context methods: | ||
```js | ||
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. | ||
```js | ||
/** 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'; | ||
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: | ||
1. Using built-in `HTML`, `Markdown` and `MarkdownV2` classes: | ||
```js | ||
const message = HTML.bold('Very bold, such HTML'); | ||
``` | ||
3. Writing tags manually as it is told [here][formatting-options]: | ||
```js | ||
const message = '*Very bold, such Markdown*'; | ||
``` | ||
[formatting-options]: https://core.telegram.org/bots/api#formatting-options | ||
Anyways, after writing the text you **need** to add `parse_mode` field. There are also **two ways** of doing that _¯\\\_(ツ)\_/¯_: | ||
3. Writing actual parse mode code _like a boss_: | ||
```js | ||
{ parse_mode: 'Markdown' } | ||
``` | ||
7. Passing parse mode class _like a cheems_: | ||
```js | ||
{ parse_mode: HTML } | ||
``` | ||
Final API request will look like this: | ||
```js | ||
const message = `Some ${HTML.bold('bold')} and ${HTML.italic('italic')} here`; | ||
context.send(message, { parse_mode: HTML }); | ||
``` | ||
```js | ||
context.send(`Imagine using _classes_ for parse mode, *lol*!`, { parse_mode: 'Markdown' }); | ||
``` | ||
<details> | ||
<summary><i>The truth...</i></summary> | ||
<br /> | ||
<img src="https://i.imgur.com/x6EFfCH.png" /> | ||
</details> | ||
More markdown examples are available [here][markdown] | ||
[markdown]: https://github.com/nitreojs/puregram/tree/master/docs/examples/markdown | ||
--- | ||
### 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. | ||
```js | ||
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' | ||
}) | ||
] | ||
]); | ||
``` | ||
```js | ||
// 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') | ||
]); | ||
``` | ||
#### Keyboard builders | ||
There are also keyboard **builders** which are designed to be building a keyboard step by step: | ||
```js | ||
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 | ||
``` | ||
#### Sending keyboards | ||
To send keyboard, you simply need to pass the generated value in `reply_markup` field: | ||
```js | ||
context.send('Look, here\'s a keyboard!', { reply_markup: keyboard }); | ||
``` | ||
More keyboards examples are available [here][keyboards] | ||
[keyboards]: https://github.com/nitreojs/puregram/tree/master/docs/examples/keyboards | ||
--- | ||
## Bot information | ||
If you are using `puregram`'s built-in polling logic, after `Updates.startPolling()` is called you have access to `Telegram.bot` property: | ||
```js | ||
bot.updates.startPolling().then( | ||
() => console.log(`@${bot.bot.username} started polling!`) | ||
// yeah, `bot.bot` ¯\_(ツ)_/¯ | ||
// that's why I prefer naming `telegram` instead of `bot` | ||
); | ||
``` | ||
telegram.updates.startPolling().catch(console.error); | ||
--- | ||
## 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. | ||
```js | ||
bot.updates.on('message', (context) => { | ||
const id = context.senderId; | ||
// is the same as | ||
const id = context.from?.id; | ||
}); | ||
bot.updates.on('message', (context) => { | ||
context.send('Hey!'); | ||
// equals to | ||
bot.api.sendMessage({ | ||
chat_id: context.chat?.id, | ||
text: 'Hey!' | ||
}); | ||
}); | ||
``` | ||
## Webhook example (express) | ||
Every context has `telegram` property, so you can call API methods almost everywhere if you have a context nearby. | ||
```js | ||
import express from 'express'; | ||
import { Telegram } from 'puregram'; | ||
bot.updates.on('message', async (context) => { | ||
const me = await context.telegram.api.getMe(); | ||
}); | ||
``` | ||
const telegram = new Telegram({ | ||
token: process.env.TOKEN | ||
--- | ||
## `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**: | ||
```ts | ||
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: | ||
```js | ||
const { MessageContext } = require('puregram'); | ||
const update = await getUpdate(); | ||
const context = new MessageContext({ | ||
telegram: bot, | ||
update, | ||
updateType: 'message', | ||
updateId: update.update_id | ||
}); | ||
``` | ||
const app = express(); | ||
Every context is listed [here][contexts] | ||
app.use(bodyParser.json()); | ||
app.use(telegram.updates.getWebhookMiddleware()); | ||
[contexts]: https://github.com/nitreojs/puregram/tree/master/packages/puregram/src/contexts | ||
telegram.updates.on( | ||
'message', | ||
(context) => context.send('Hello, World!') | ||
); | ||
--- | ||
// Available ports: 80, 88, 443, 8443 | ||
app.listen(443, () => console.log('Webhook started handling!')); | ||
## 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: | ||
```js | ||
bot.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`); | ||
}); | ||
``` | ||
[**🤖 Click to see more examples!**](https://github.com/nitreojs/puregram/tree/master/docs/examples) | ||
Extending the context: | ||
```js | ||
bot.updates.use((context, next) => { | ||
context.user = await getUser(context.senderId); | ||
return next(); | ||
}); | ||
bot.updates.on('message', (context) => { | ||
// here we can access property we made in the middleware | ||
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/`. | ||
```ts | ||
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. | ||
```ts | ||
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(); | ||
}); | ||
/** | ||
* There are 2 ways of updating context's type: | ||
* 1. External type override: | ||
* `(context: MessageContext & ExtraData) => ...` | ||
* 2. Using generics: | ||
* `bot.updates.on<ExtraData>(...)` | ||
* | ||
* Below I will be using the second way. | ||
*/ | ||
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`][@scenes] or [`@puregram/hear`][@hear] with [`@puregram/session`][@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: | ||
```js | ||
const sessionManager = new SessionManager(); | ||
const hearManager = new HearManager(); | ||
// 1. Session middleware first | ||
bot.updates.on('message', sessionManager.middleware); | ||
// 2. Hear middleware second | ||
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 | ||
```cmd | ||
> set "DEBUG=puregram:*" & node index | ||
``` | ||
##### PowerShell | ||
```ps | ||
> $env:DEBUG = "puregram:*"; node index | ||
``` | ||
##### Linux | ||
```sh | ||
$ DEBUG=puregram:* node index | ||
``` | ||
--- | ||
## Community | ||
### Packages that might be useful to you | ||
These packages are created by the `puregram` community _(and not only)_ and are expanding packages functionality _(I guess)_. | ||
- [**@puregram/hear**](https://github.com/nitreojs/puregram/tree/master/packages/hear): Simple implementation of hear system | ||
- [**@puregram/scenes**](https://github.com/nitreojs/puregram/tree/master/packages/scenes): Simple implementation of middleware-based scene management | ||
- [**@puregram/session**](https://github.com/nitreojs/puregram/tree/master/packages/session): Simple implementation of sessions | ||
- [**@puregram/utils**](https://github.com/nitreojs/puregram/tree/master/packages/utils): _Almost_ useful utilities | ||
### Some official packages | ||
- [`@puregram/hear`][@hear]: Simple implementation of hear system | ||
- [`@puregram/scenes`][@scenes]: Simple implementation of middleware-based scene management | ||
- [`@puregram/session`][@session]: Simple implementation of sessions | ||
- [`@puregram/utils`][@utils]: _Almost_ useful utilities | ||
[@hear]: https://github.com/nitreojs/puregram/tree/master/packages/hear | ||
[@scenes]: https://github.com/nitreojs/puregram/tree/master/packages/scenes | ||
[@session]: https://github.com/nitreojs/puregram/tree/master/packages/session | ||
[@utils]: https://github.com/nitreojs/puregram/tree/master/packages/utils | ||
### Non-official ones | ||
_Oh no, it's empty there..._ Maybe _you_ could add _your_ community package here? | ||
--- | ||
## Thanks to | ||
Biggest thanks to [Negezor](https://github.com/negezor) for his [vk-io](https://github.com/negezor/vk-io) library that helped me with this package! | ||
- [Negezor][negezor] ([negezor/vk-io][negezor/vk-io]) — for inspiration, package idea (!) and some code and implementation ideas | ||
[negezor]: https://github.com/negezor | ||
[negezor/vk-io]: https://github.com/negezor/vk-io |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1111150
3
28604
630
1
Updatedinspectable@^1.2.0
Updatedmiddleware-io@^2.8.0