Skip to content

Commit

Permalink
Add Webhook support
Browse files Browse the repository at this point in the history
  • Loading branch information
Borodin committed Jul 7, 2024
1 parent 8b37a8d commit f1a6c2a
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 2 deletions.
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,62 @@ bot.on('message:audio', (message) => {
});
```

## Webhooks
To use webhooks, you need to set up a server that will receive updates from Telegram. You can use the [express](https://www.npmjs.com/package/express) library for this purpose.

This example demonstrates a basic Telegram bot that responds with a reaction to any incoming message using Webhooks. The use of [ngrok](https://www.npmjs.com/package/ngrok) as a tunneling service simplifies the setup, allowing the bot to be easily deployed in various environments without complex network configuration. This approach is ideal for quick testing and development purposes. For production use, you should consider deploying the bot on a server with a public IP address and a valid SSL certificate.
```typescript
import 'dotenv/config';
import * as ngrok from 'ngrok';
import express from "express";
import {TelegramBot} from "./src";
import {Update} from "./src/types";

const port = 3001;

const bot = new TelegramBot({
botToken: process.env.TEST_TELEGRAM_TOKEN as string,
});

bot.on('message', async (message) => {
await bot.setMessageReaction({
chat_id: message.chat.id,
message_id: message.message_id,
reaction: [{
type: 'emoji', emoji: '👍'
}]
});
});

const app = express();
app.use(express.json());
app.post('/', async (req, res) => {
try {
await bot.processUpdate(req.body as Update);
res.sendStatus(200);
} catch (e) {
res.sendStatus(500);
}
});

(async () => {
app.listen(port, async () => {
const url = await ngrok.connect({
proto: 'http',
addr: port,
});
await bot.setWebhook({url});
console.log('Set Webhook to', url);
})
})();

process.on('SIGINT', async () => {
await bot.deleteWebhook();
await ngrok.disconnect();
console.log('Webhook deleted');
});
```


## Tests
```bash
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"type": "commonjs",
"name": "typescript-telegram-bot-api",
"version": "0.1.20",
"version": "0.1.21",
"description": "Telegram Bot API wrapper for Node.js written in TypeScript",
"repository": "github:Borodin/typescript-telegram-bot-api",
"main": "dist/index.js",
Expand Down
52 changes: 52 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import {
MessageTypes,
StarTransactions,
InputPaidMedia,
WebhookInfo,
} from './types/';
import * as TelegramTypes from './types/';
import { TelegramError } from './errors';
Expand Down Expand Up @@ -186,6 +187,10 @@ export class TelegramBot extends EventEmitter {
}
}

async processUpdate(update: Update) {
this.polling.emitUpdate(update);
}

/**
* ## getUpdates
* Use this method to receive incoming updates using long polling [wiki](https://en.wikipedia.org/wiki/Push_technology#Long_polling). Returns an Array of Update objects.
Expand All @@ -210,6 +215,53 @@ export class TelegramBot extends EventEmitter {
);
}

/**
* ## setWebhook
* Use this method to specify a URL and receive incoming updates via an outgoing webhook. Whenever there is an update
* for the bot, we will send an HTTPS POST request to the specified URL, containing a JSON-serialized Update. In case
* of an unsuccessful request, we will give up after a reasonable amount of attempts. Returns True on success.
*
* If you'd like to make sure that the webhook was set by you, you can specify secret data in the parameter
* ```secret_token```. If specified, the request will contain a header ```“X-Telegram-Bot-Api-Secret-Token”``` with
* the secret token as content.
* @see https://core.telegram.org/bots/api#setwebhook
*/
async setWebhook(options: {
url: string;
certificate?: InputFile;
ip_address?: string;
max_connections?: number;
allowed_updates?: UpdateType[];
drop_pending_updates?: boolean;
secret_token?: string;
}): Promise<true> {
return await this.callApi('setWebhook', {
...options,
allowed_updates: JSON.stringify(options?.allowed_updates),
});
}

/**
* ## deleteWebhook
* Use this method to remove webhook integration if you decide to switch back to getUpdates. Returns True on success.
* @see https://core.telegram.org/bots/api#deletewebhook
*/
async deleteWebhook(options?: {
drop_pending_updates?: boolean;
}): Promise<true> {
return await this.callApi('deleteWebhook', options);
}

/**
* ## getWebhookInfo
* Use this method to get current webhook status. Requires no parameters. On success, returns a WebhookInfo object.
* If the bot is using getUpdates, will return an object with the url field empty.
* @see https://core.telegram.org/bots/api#getwebhookinfo
*/
async getWebhookInfo(): Promise<WebhookInfo | { url: '' }> {
return await this.callApi('getWebhookInfo');
}

/**
* ## getMe
* A simple method for testing your bot's authentication token. Requires no parameters. Returns basic information about the bot in form of a User object.
Expand Down
2 changes: 1 addition & 1 deletion src/pooling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class Polling {
});
}

private emitUpdate(update: Update) {
emitUpdate(update: Update) {
Object.keys(update).forEach((key) => {
if (key !== 'update_id') {
const eventType = key as keyof EventTypes;
Expand Down
52 changes: 52 additions & 0 deletions src/types/WebhookInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* ## getWebhookInfo
* Use this method to get current webhook status. Requires no parameters. On success, returns a WebhookInfo object. If the bot is using getUpdates, will return an object with the url field empty.
* @see https://core.telegram.org/bots/api#getwebhookinfo
*/

export type WebhookInfo = {
/**
* Webhook URL, may be empty if webhook is not set up
*/
url: string;

/**
* True, if a custom certificate was provided for webhook certificate checks
*/
has_custom_certificate: boolean;

/**
* Number of updates awaiting delivery
*/
pending_update_count: number;

/**
* Optional. Currently used webhook IP address
*/
ip_address?: string;

/**
* Optional. Unix time for the most recent error that happened when trying to deliver an update via webhook
*/
last_error_date?: number;

/**
* Optional. Error message in human-readable format for the most recent error that happened when trying to deliver an update via webhook
*/
last_error_message?: string;

/**
* Optional. Unix time of the most recent error that happened when trying to synchronize available updates with Telegram datacenters
*/
last_synchronization_error_date?: number;

/**
* Optional. The maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery
*/
max_connections?: string;

/**
* Optional. A list of update types the bot is subscribed to. Defaults to all update types except chat_member
*/
allowed_updates?: string[];
};
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ export * from './VideoNote';
export * from './Voice';
export * from './WebAppData';
export * from './WebAppInfo';
export * from './WebhookInfo';
export * from './WriteAccessAllowed';

import { ResponseParameters } from './ResponseParameters';
Expand Down
40 changes: 40 additions & 0 deletions tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,46 @@ describe('.stopPolling()', () => {
});
});

describe('.setWebhook()', () => {
it('should set webhook', async () => {
await expect(
bot.setWebhook({
url: 'https://example.com',
}),
).resolves.toBe(true);
});

afterAll(async () => {
await bot.deleteWebhook();
});
});

describe('.deleteWebhook()', () => {
it('should delete webhook', async () => {
await expect(bot.deleteWebhook()).resolves.toBe(true);
});
});

describe('.getWebhookInfo()', () => {
it('should get webhook info', async () => {
await bot.setWebhook({
url: 'https://example.com',
});
await expect(bot.getWebhookInfo()).resolves.toHaveProperty(
'url',
'https://example.com',
);
});

it('should return empty url when webhook is not set', async () => {
await expect(bot.getWebhookInfo()).resolves.toHaveProperty('url', '');
});

afterEach(async () => {
await bot.deleteWebhook();
});
});

describe('.isTelegramError()', () => {
it('should return true for Telegram-related errors', () => {
const telegramError = new TelegramError({
Expand Down

0 comments on commit f1a6c2a

Please sign in to comment.