diff --git a/README.md b/README.md index 72af4c8..15fbcde 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,20 @@ api.getMe() }); ``` +## TypeScript support + +This package comes with type declarations included and is ready to be used in TypeScript projects too: + +```typescript +import TelegramBotApi from 'telegram-bot-api'; + +let api = new TelegramBotApi({ + token: '' +}); +``` + +Supporting editors will then show available autocompletions and type annotations and type check your code. + ## Supported methods For method parameters and description, please refer to official documentation diff --git a/lib/telegram-bot.d.ts b/lib/telegram-bot.d.ts new file mode 100644 index 0000000..ed976d4 --- /dev/null +++ b/lib/telegram-bot.d.ts @@ -0,0 +1,1061 @@ +import Bluebird from 'bluebird'; +import { EventEmitter } from 'events'; + +import { + CallbackQuery, + Chat, + ChatMember, + ChosenInlineResult, + File, + ForceReply, + InlineKeyboardMarkup, + InlineQuery, + InlineQueryResult, + InputFile, + Message, + ReplyKeyboardMarkup, + ReplyKeyboardRemove, + Update, + User, + UserProfilePhotos, + WebhookInfo, +} from 'telegram-typings'; + +/** + * type alias for often used ReplyMarkup data type + */ +type ReplyMarkup = InlineKeyboardMarkup | ReplyKeyboardMarkup | ReplyKeyboardRemove | ForceReply; + +/** + * common signature for simple api methods + */ +type ApiMethod = (callback?: (err: any, value?: T) => void) => Bluebird; + +/** + * common signature for api methods with additional parameters + */ +type ApiMethodWithParams = (params: P, callback?: (err: any, value?: T) => void) => Bluebird; + +/** + * Configuration parameters for a new telegram bot instance + */ +interface ApiParams { + /** + * access token for bot + */ + token: string; + + /** + * proxy settings (optional) + */ + http_proxy?: { + /** + * proxy host + */ + host: string; + + /** + * proxy port + */ + port: number; + + /** + * username for proxy + */ + user?: string; + + /** + * password for proxy + */ + password?: string; + + /** + * true/false whether to use https + */ + https?: boolean; + }; + + updates?: { + /** + * True if you want to receive updates from telegram (default false) + */ + enabled?: boolean; + + /** + * We will fetch updates from Telegram each number of milliseconds + * (default 1000) + */ + get_interval?: number; + + /** + * We will wait for updates during this num of milliseconds at each + * attempt before quit (default 0) + */ + pooling_timeout?: number; + }; + + webhook?: { + /** + * True if you want to receive updates via webhook (one one of + * updates.enabled or webhook.enabled could be true) + */ + enabled?: boolean; + + /** + * URL to the webhook; if not provided `host` is required + */ + url?: string; + + /** + * path to the https certificate for local server + */ + certificate: string; + + /** + * path to the https certificate's private key + */ + privateKey: string; + + /** + * Maximum allowed number of simultaneous HTTPS connections to the + * webhook for update delivery, 1-100. Defaults to 40. Use lower values + * to limit the load on your bot‘s server, and higher values to increase + * your bot’s throughput. + */ + max_connections?: string; + + /** + * List the types of updates you want your bot to receive. Specify an + * empty list to receive all updates regardless of type (default). + */ + allowed_updates?: string[]; + + /** + * hostname/local IP to listen on for webhooks + * if not provided `url` is required + */ + host?: string; + + /** + * port to listen on for webhooks, defaults to 8443 + */ + port?: string; + }; + +} + +declare class TelegramApi extends EventEmitter { + /** + * Instantiate a new bot and start fetching updates if configured so + */ + public constructor(params: ApiParams); + + /** + * add listener for all updates + */ + public on(event: 'update', listener: (update: Update) => void): this; + + /** + * add listener for inline callback queries + */ + public on(event: 'inline.callback.query', listener: (query: CallbackQuery) => void): this; + + /** + * add listener for message edits + */ + public on(event: 'edited.message', listener: (message: Message) => void): this; + + /** + * add listener for inline queries + */ + public on(event: 'inline.query', listener: (query: InlineQuery) => void): this; + + /** + * add listener for chosen inline results + */ + public on(event: 'inline.result', listener: (result: ChosenInlineResult) => void): this; + + /** + * add listener for new incoming messages + */ + public on(event: 'message', listener: (message: Message) => void): this; + + /** + * add listener for generic events (see EventEmitter) + */ + public on(event: string | symbol, listener: (...args: any[]) => void): this; + + /** + * Returns basic information about the bot in form of a User object. + */ + public getMe: ApiMethod; + + /** + * Use this method to send text messages. + * On success, the sent Message is returned. + */ + public sendMessage: ApiMethodWithParams; + + /** + * Use this method to forward messages of any kind. On success, the sent + * Message is returned. + */ + public forwardMessage: ApiMethodWithParams; + + /** + * Use this method to send photos. On success, the sent Message is returned. + */ + public sendPhoto: ApiMethodWithParams; + + /** + * Use this method to send audio files, if you want Telegram clients to + * display them in the music player. + * + * For sending voice messages, use the sendVoice method instead. + */ + public sendAudio: ApiMethodWithParams; + + /** + * Use this method to send audio files, if you want Telegram clients to + * display the file as a playable voice message. + */ + public sendVoice: ApiMethodWithParams; + + /** + * Use this method to send general files. + */ + public sendDocument: ApiMethodWithParams; + + /** + * Use this method to send .webp stickers. + */ + public sendSticker: ApiMethodWithParams; + + /** + * Use this method to send video files, Telegram clients support mp4 videos + * (other formats may be sent as Document). + */ + public sendVideo: ApiMethodWithParams; + + /** + * Use this method to send point on the map. + */ + public sendLocation: ApiMethodWithParams; + + /** + * Use this method to send information about a venue. + */ + public sendVenue: ApiMethodWithParams; + + /** + * Use this method to send phone contacts. + */ + public sendContact: ApiMethodWithParams; + + /** + * Use this method to kick a user from a group, a supergroup or a channel. + */ + public kickChatMember: ApiMethodWithParams; + + /** + * Use this method to unban a previously kicked user in a supergroup or + * channel. + */ + public unbanChatMember: ApiMethodWithParams; + + /** + * Use this method for your bot to leave a group, supergroup or channel. + */ + public leaveChat: ApiMethodWithParams; + + /** + * Use this method to get up to date information about the chat (current + * name of the user for one-on-one conversations, current username of a + * user, group or channel, etc.) + */ + public getChat: ApiMethodWithParams; + + /** + * Use this method to get a list of administrators in a chat. + */ + public getChatAdministrators: ApiMethodWithParams; + + /** + * Use this method to get the number of members in a chat. + */ + public getChatMembersCount: ApiMethodWithParams; + + /** + * Use this method to get information about a member of a chat. + */ + public getChatMember: ApiMethodWithParams; + + /** + * Use this method when you need to tell the user that something is + * happening on the bot's side. + */ + public sendChatAction: ApiMethodWithParams; + + /** + * Use this method to get a list of profile pictures for a user. + */ + public getUserProfilePhotos: ApiMethodWithParams; + + /** + * Use this method to receive incoming updates using long polling. + */ + public getUpdates: ApiMethodWithParams; + + /** + * Use this method to specify a url and receive incoming updates via an + * outgoing webhook. + */ + public setWebhook: ApiMethodWithParams; + + /** + * Use this method to remove webhook integration if you decide to switch + * back to getUpdates. + */ + public deleteWebhook: ApiMethod; + + /** + * Use this method to get current webhook status. + */ + public getWebhookInfo: ApiMethod; + + /** + * Use this method to get basic info about a file and prepare it for + * downloading. + */ + public getFile: ApiMethodWithParams; + + /** + * Use this method to send answers to an inline query. + */ + public answerInlineQuery: ApiMethodWithParams; + + /** + * Use this method to send answers to callback queries sent from inline + * keyboards. + */ + public answerCallbackQuery: ApiMethodWithParams; + + /** + * Use this method to edit text and game messages sent by the bot or via the + * bot (for inline bots). + */ + public editMessageText: ApiMethodWithParams; + + /** + * Use this method to edit captions of messages sent by the bot or via the + * bot (for inline bots). + */ + public editMessageCaption: ApiMethodWithParams; + + /** + * Use this method to edit only the reply markup of messages sent by the bot + * or via the bot (for inline bots). + */ + public editMessageReplyMarkup: ApiMethodWithParams; + + /** + * Use this method to generate a new invite link for a chat; any previously + * generated link is revoked. + */ + public exportChatInviteLink: ApiMethodWithParams; + + /** + * Use this method to delete a message, including service messages + */ + public deleteMessage: ApiMethodWithParams; + + /** + * Use this method to send a game. + */ + public sendGame: ApiMethodWithParams; +} + +export = TelegramApi; diff --git a/lib/telegram-bot.js b/lib/telegram-bot.js index b552a87..658667f 100644 --- a/lib/telegram-bot.js +++ b/lib/telegram-bot.js @@ -21,7 +21,7 @@ Promise.onPossiblyUnhandledRejection(function(error) { * port proxy port * user username for proxy * password password for proxy - * https true/false + * https true/false whether to use https * updates * enabled True if you want to receive updates from telegram (default false) * get_interval We will fetch updates from Telegram @@ -31,6 +31,18 @@ Promise.onPossiblyUnhandledRejection(function(error) { * webhook * enabled True if you want to receive updates via webhook * (one one of updates.enabled or webhook.enabled could be true) + * url URL to the webhook; if not provided `host` is required + * certificate path to the https certificate for local server (required) + * privateKey path to the https certificate's private key (required) + * max_connections Maximum allowed number of simultaneous HTTPS connections to the + * webhook for update delivery, 1-100. Defaults to 40. Use lower values + * to limit the load on your bot‘s server, and higher values to increase + * your bot’s throughput. + * allowed_updates List the types of updates you want your bot to receive. Specify an + * empty list to receive all updates regardless of type (default). + * host hostname/local IP to listen on for webhooks + * if not provided `url` is required + * port port to listen on for webhooks, defaults to 8443 */ var TelegramApi = function (params) @@ -75,10 +87,9 @@ var TelegramApi = function (params) webhook: { enabled: false, url: undefined, - certificate: undefined, max_connections: 40, allowed_updates: [], - host: '0.0.0.0', + host: undefined, port: 8443 } }; @@ -244,6 +255,10 @@ var TelegramApi = function (params) if (params.disable_web_page_preview !== undefined) args.disable_web_page_preview = params.disable_web_page_preview; if (params.reply_to_message_id !== undefined) args.reply_to_message_id = params.reply_to_message_id; if (params.reply_markup !== undefined) args.reply_markup = params.reply_markup; + + if (typeof args.reply_markup === 'object') { + args.reply_markup = JSON.stringify(args.reply_markup); + } if (params.parse_mode !== undefined) args.parse_mode = params.parse_mode; return new Promise(function(resolve, reject) @@ -251,7 +266,7 @@ var TelegramApi = function (params) _rest({ method: 'POST', json: true, - formData: args, + body: args, uri: _baseurl + 'sendMessage' }) .then(function(body) @@ -289,7 +304,7 @@ var TelegramApi = function (params) _rest({ method: 'POST', json: true, - formData: args, + body: args, uri: _baseurl + 'forwardMessage' }) .then(function(body) @@ -356,12 +371,15 @@ var TelegramApi = function (params) if (params.reply_markup !== undefined) { args.reply_markup = params.reply_markup; + if (typeof args.reply_markup === 'object') { + args.reply_markup = JSON.stringify(args.reply_markup); + } } _rest({ method: 'POST', json: true, - formData: args, + body: args, uri: _baseurl + 'sendPhoto' }) .then(function(body) @@ -426,6 +444,9 @@ var TelegramApi = function (params) if (params.reply_markup !== undefined) { args.reply_markup = params.reply_markup; + if (typeof args.reply_markup === 'object') { + args.reply_markup = JSON.stringify(args.reply_markup); + } } if (params.duration !== undefined) @@ -446,7 +467,7 @@ var TelegramApi = function (params) _rest({ method: 'POST', json: true, - formData: args, + body: args, uri: _baseurl + 'sendAudio' }) .then(function(body) @@ -509,6 +530,9 @@ var TelegramApi = function (params) if (params.reply_markup !== undefined) { args.reply_markup = params.reply_markup; + if (typeof args.reply_markup === 'object') { + args.reply_markup = JSON.stringify(args.reply_markup); + } } if (params.duration !== undefined) @@ -519,7 +543,7 @@ var TelegramApi = function (params) _rest({ method: 'POST', json: true, - formData: args, + body: args, uri: _baseurl + 'sendVoice' }) .then(function(body) @@ -581,12 +605,15 @@ var TelegramApi = function (params) if (params.reply_markup !== undefined) { args.reply_markup = params.reply_markup; + if (typeof args.reply_markup === 'object') { + args.reply_markup = JSON.stringify(args.reply_markup); + } } _rest({ method: 'POST', json: true, - formData: args, + body: args, uri: _baseurl + 'sendDocument' }) .then(function(body) @@ -648,12 +675,15 @@ var TelegramApi = function (params) if (params.reply_markup !== undefined) { args.reply_markup = params.reply_markup; + if (typeof args.reply_markup === 'object') { + args.reply_markup = JSON.stringify(args.reply_markup); + } } _rest({ method: 'POST', json: true, - formData: args, + body: args, uri: _baseurl + 'sendSticker' }) .then(function(body) @@ -717,6 +747,9 @@ var TelegramApi = function (params) if (params.reply_markup !== undefined) { args.reply_markup = params.reply_markup; + if (typeof args.reply_markup === 'object') { + args.reply_markup = JSON.stringify(args.reply_markup); + } } if (params.duration !== undefined) @@ -732,7 +765,7 @@ var TelegramApi = function (params) _rest({ method: 'POST', json: true, - formData: args, + body: args, uri: _baseurl + 'sendVideo' }) .then(function(body) @@ -779,12 +812,15 @@ var TelegramApi = function (params) if (params.reply_markup !== undefined) { args.reply_markup = params.reply_markup; + if (typeof args.reply_markup === 'object') { + args.reply_markup = JSON.stringify(args.reply_markup); + } } _rest({ method: 'POST', json: true, - formData: args, + body: args, uri: _baseurl + 'sendLocation' }) .then(function(body) @@ -824,7 +860,7 @@ var TelegramApi = function (params) _rest({ method: 'POST', json: true, - formData: params, + body: params, uri: _baseurl + 'sendVenue' }) .then(function(body) @@ -862,7 +898,7 @@ var TelegramApi = function (params) _rest({ method: 'POST', json: true, - formData: params, + body: params, uri: _baseurl + 'sendContact' }) .then(function(body) @@ -894,7 +930,7 @@ var TelegramApi = function (params) _rest({ method: 'POST', json: true, - formData: params, + body: params, uri: _baseurl + 'kickChatMember' }) .then(function(body) @@ -926,7 +962,7 @@ var TelegramApi = function (params) _rest({ method: 'POST', json: true, - formData: params, + body: params, uri: _baseurl + 'unbanChatMember' }) .then(function(body) @@ -957,7 +993,7 @@ var TelegramApi = function (params) _rest({ method: 'POST', json: true, - formData: params, + body: params, uri: _baseurl + 'leaveChat' }) .then(function(body) @@ -988,7 +1024,7 @@ var TelegramApi = function (params) _rest({ method: 'POST', json: true, - formData: params, + body: params, uri: _baseurl + 'getChat' }) .then(function(body) @@ -1019,7 +1055,7 @@ var TelegramApi = function (params) _rest({ method: 'POST', json: true, - formData: params, + body: params, uri: _baseurl + 'getChatAdministrators' }) .then(function(body) @@ -1050,7 +1086,7 @@ var TelegramApi = function (params) _rest({ method: 'POST', json: true, - formData: params, + body: params, uri: _baseurl + 'getChatMembersCount' }) .then(function(body) @@ -1082,7 +1118,7 @@ var TelegramApi = function (params) _rest({ method: 'POST', json: true, - formData: params, + body: params, uri: _baseurl + 'getChatMember' }) .then(function(body) @@ -1121,7 +1157,7 @@ var TelegramApi = function (params) _rest({ method: 'POST', json: true, - formData: args, + body: args, uri: _baseurl + 'sendChatAction' }) .then(function(body) @@ -1159,7 +1195,7 @@ var TelegramApi = function (params) _rest({ method: 'GET', json: true, - formData: args, + body: args, uri: _baseurl + 'getUserProfilePhotos' }) .then(function(body) @@ -1200,7 +1236,7 @@ var TelegramApi = function (params) _rest({ method: 'GET', json: true, - formData: args, + body: args, uri: _baseurl + 'getUpdates' }) .then(function(body) @@ -1223,6 +1259,11 @@ var TelegramApi = function (params) * PARAMS: * url HTTPS url to send updates to. Use an empty string to remove webhook integration * certificate Filename of public key certificate (optional) + * allowed_updates List the types of updates you want your bot to receive. Specify an empty list to + * receive all updates regardless of type (default). + * max_connections Maximum allowed number of simultaneous HTTPS connections to the webhook for + * update delivery, 1-100. Defaults to 40. Use lower values to limit the load on + * your bot‘s server, and higher values to increase your bot’s throughput. */ this.setWebhook = function (params, cb) { @@ -1231,12 +1272,15 @@ var TelegramApi = function (params) var args = {}; if (params.url !== undefined) args.url = params.url; + if (params.allowed_updates !== undefined) args.allowed_updates = params.allowed_updates; + if (params.max_connections !== undefined) args.max_connections = params.max_connections; + if (!params.certificate) { return _rest({ method: 'POST', json: true, - formData: args, + body: args, uri: _baseurl + 'setWebhook' }) .then(commonResponseHandler) @@ -1256,7 +1300,7 @@ var TelegramApi = function (params) _rest({ method: 'POST', json: true, - formData: args, + body: args, uri: _baseurl + 'setWebhook' }) .then(function(body) @@ -1353,7 +1397,7 @@ var TelegramApi = function (params) _rest({ method: 'GET', json: true, - formData: args, + body: args, uri: _baseurl + 'getFile' }) .then(function(body) @@ -1384,6 +1428,11 @@ var TelegramApi = function (params) * next_offset Pass the offset that a client should send in the next query with * the same text to receive more results. Pass an empty string if there are * no more results or if you don‘t support pagination. Offset length can’t exceed 64 bytes. + * switch_pm_text If passed, clients will display a button with specified text that + * switches the user to a private chat with the bot and sends the bot a + * start message with the parameter switch_pm_parameter + * switch_pm_parameter Deep-linking parameter for the /start message sent to the bot + * when user presses the switch button. */ this.answerInlineQuery = function (params, cb) { @@ -1402,7 +1451,7 @@ var TelegramApi = function (params) _rest({ method: 'POST', json: true, - formData: args, + body: args, uri: _baseurl + 'answerInlineQuery' }) .then(function(body) @@ -1427,6 +1476,10 @@ var TelegramApi = function (params) * text Text of the notification. If not specified, nothing will be shown to the user * show_alert If true, an alert will be shown by the client instead of a notificaiton at the * top of the chat screen. Defaults to false. + * url URL that will be opened by the user's client. If you have created a + * Game and accepted the conditions via @Botfather, specify the URL that + * opens your game – note that this will only work if the query comes + * from a callback_game button. */ this.answerCallbackQuery = function (params, cb) { return new Promise(function (resolve, reject) { @@ -1440,7 +1493,7 @@ var TelegramApi = function (params) _rest({ method: 'POST', json: true, - formData: args, + body: args, uri: _baseurl + 'answerCallbackQuery' }) .then(function (body) { @@ -1480,11 +1533,15 @@ var TelegramApi = function (params) if (params.disable_web_page_preview !== undefined) args.disable_web_page_preview = params.disable_web_page_preview; if (params.reply_markup !== undefined) args.reply_markup = params.reply_markup; + if (typeof args.reply_markup === 'object') { + args.reply_markup = JSON.stringify(args.reply_markup); + } + return new Promise(function (resolve, reject) { _rest({ method: 'POST', json: true, - formData: args, + body: args, uri: _baseurl + 'editMessageText' }) .then(function (body) { @@ -1519,11 +1576,15 @@ var TelegramApi = function (params) if (params.caption !== undefined) args.caption = params.caption; if (params.reply_markup !== undefined) args.reply_markup = params.reply_markup; + if (typeof args.reply_markup === 'object') { + args.reply_markup = JSON.stringify(args.reply_markup); + } + return new Promise(function (resolve, reject) { _rest({ method: 'POST', json: true, - formData: args, + body: args, uri: _baseurl + 'editMessageCaption' }) .then(function (body) { @@ -1556,11 +1617,15 @@ var TelegramApi = function (params) if (params.inline_message_id !== undefined) args.inline_message_id = params.inline_message_id; if (params.reply_markup !== undefined) args.reply_markup = params.reply_markup; + if (typeof args.reply_markup === 'object') { + args.reply_markup = JSON.stringify(args.reply_markup); + } + return new Promise(function (resolve, reject) { _rest({ method: 'POST', json: true, - formData: args, + body: args, uri: _baseurl + 'editMessageReplyMarkup' }) .then(function (body) { @@ -1589,7 +1654,7 @@ var TelegramApi = function (params) _rest({ method: 'POST', json: true, - formData: args, + body: args, uri: _baseurl + 'exportChatInviteLink' }) .then(function(body) { @@ -1620,7 +1685,7 @@ var TelegramApi = function (params) _rest({ method: 'POST', json: true, - formData: args, + body: args, uri: _baseurl + 'deleteMessage' }) .then(function (body) { @@ -1651,11 +1716,15 @@ var TelegramApi = function (params) if (params.disable_notification !== undefined) args.disable_notification = params.disable_notification; if (params.reply_markup !== undefined) args.reply_markup = params.reply_markup; + if (typeof args.reply_markup === 'object') { + args.reply_markup = JSON.stringify(args.reply_markup); + } + return new Promise(function (resolve, reject) { _rest({ method: 'POST', json: true, - formData: args, + body: args, uri: _baseurl + 'sendGame' }) .then(function (body) { @@ -1687,6 +1756,14 @@ var TelegramApi = function (params) // Start webhook if (_settings.webhook.enabled == true) { + if (!_settings.webhook.certificate || !_settings.webhook.privateKey) { + throw new Error('when using webhooks you must provide a certificate and private key in the configuration'); + } + + if (!_settings.webhook.host && !_settings.webhook.url) { + throw new Error('when using webhooks you must provide either a host or a url in the configuration'); + } + webapp.use(require('body-parser').json()) webapp.post('/' + _settings.token, (req, res) => { var data = req.body diff --git a/package.json b/package.json index cf514eb..ed6a040 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "url": "https://github.com/mast/telegram-bot-api.git" }, "main": "./lib/telegram-bot.js", + "types": "./lib/telegram-bot.d.ts", "keywords": [ "telegram", "nodejs", @@ -32,9 +33,12 @@ ], "license": "MIT", "dependencies": { + "@types/bluebird": "^3.5.25", + "@types/node": "^10.12.24", "bluebird": "^3.4.0", "extend": "2.0.1", "request-promise": "4.2.2", + "telegram-typings": "^4.0.0-beta", "express": "4.16.3", "body-parser": "1.18.2" },