UNPKG

telegraf-ts

Version:
248 lines (230 loc) 7.03 kB
const debug = require('debug')('telegraf:core'); const Telegram = require('./telegram'); const Extra = require('./extra'); const Composer = require('./composer'); const Markup = require('./markup'); const session = require('./session'); const Router = require('./router'); const Stage = require('./stage'); const BaseScene = require('./scenes/base'); const Context = require('./context'); const generateCallback = require('./core/network/webhook'); const crypto = require('crypto'); const { URL } = require('url'); const DEFAULT_OPTIONS = { retryAfter: 1, handlerTimeout: 0, contextType: Context, }; const noop = () => {}; class Telegraf extends Composer { constructor(token, options) { super(); this.options = { ...DEFAULT_OPTIONS, ...options, }; this.token = token; this.handleError = err => { console.error(); console.error((err.stack || err.toString()).replace(/^/gm, ' ')); console.error(); throw err; }; this.context = {}; this.polling = { offset: 0, started: false, }; } set token(token) { this.telegram = new Telegram( token, this.telegram ? this.telegram.options : this.options.telegram, ); } get token() { return this.telegram.token; } set webhookReply(webhookReply) { this.telegram.webhookReply = webhookReply; } get webhookReply() { return this.telegram.webhookReply; } catch(handler) { this.handleError = handler; return this; } webhookCallback(path = '/') { return generateCallback( path, (update, res) => this.handleUpdate(update, res), debug, ); } startPolling(timeout = 30, limit = 100, allowedUpdates, stopCallback = noop) { this.polling.timeout = timeout; this.polling.limit = limit; this.polling.allowedUpdates = allowedUpdates ? Array.isArray(allowedUpdates) ? allowedUpdates : [`${allowedUpdates}`] : null; this.polling.stopCallback = stopCallback; if (!this.polling.started) { this.polling.started = true; this.fetchUpdates(); } return this; } startWebhook(hookPath, tlsOptions, port, host, cb) { const webhookCb = this.webhookCallback(hookPath); const callback = cb && typeof cb === 'function' ? (req, res) => webhookCb(req, res, () => cb(req, res)) : webhookCb; this.webhookServer = tlsOptions ? require('https').createServer(tlsOptions, callback) : require('http').createServer(callback); this.webhookServer.listen(port, host, () => { debug('Webhook listening on port: %s', port); }); return this; } launch(config = {}) { debug('Connecting to Telegram'); return this.telegram .getMe() .then(botInfo => { debug(`Launching @${botInfo.username}`); this.options.username = botInfo.username; this.context.botInfo = botInfo; if (!config.webhook) { const { timeout, limit, allowedUpdates, stopCallback } = config.polling || {}; return this.telegram .deleteWebhook() .then(() => this.startPolling(timeout, limit, allowedUpdates, stopCallback), ) .then(() => debug('Bot started with long-polling')); } if ( typeof config.webhook.domain !== 'string' && typeof config.webhook.hookPath !== 'string' ) { throw new Error('Webhook domain or webhook path is required'); } let domain = config.webhook.domain || ''; if (domain.startsWith('https://') || domain.startsWith('http://')) { domain = new URL(domain).host; } const hookPath = config.webhook.hookPath || `/telegraf/${crypto.randomBytes(32).toString('hex')}`; const { port, host, tlsOptions, cb } = config.webhook; this.startWebhook(hookPath, tlsOptions, port, host, cb); if (!domain) { debug('Bot started with webhook'); return; } return this.telegram .setWebhook(`https://${domain}${hookPath}`) .then(() => debug(`Bot started with webhook @ https://${domain}`)); }) .catch(err => { console.error('Launch failed'); console.error(err.stack || err.toString()); }); } stop(cb = noop) { debug('Stopping bot...'); return new Promise(resolve => { const done = () => resolve() & cb(); if (this.webhookServer) { return this.webhookServer.close(done); } else if (!this.polling.started) { return done(); } this.polling.stopCallback = done; this.polling.started = false; }); } handleUpdates(updates) { if (!Array.isArray(updates)) { return Promise.reject(new Error('Updates must be an array')); } const processAll = Promise.all( updates.map(update => this.handleUpdate(update)), ); if (this.options.handlerTimeout === 0) { return processAll; } return Promise.race([ processAll, new Promise(resolve => setTimeout(resolve, this.options.handlerTimeout)), ]); } handleUpdate(update, webhookResponse) { debug('Processing update', update.update_id); const tg = new Telegram(this.token, this.telegram.options, webhookResponse); const TelegrafContext = this.options.contextType; const ctx = new TelegrafContext(update, tg, this.options); Object.assign(ctx, this.context); return this.middleware()(ctx).catch(err => this.handleError(err, ctx)); } fetchUpdates() { if (!this.polling.started) { this.polling.stopCallback && this.polling.stopCallback(); return; } const { timeout, limit, offset, allowedUpdates } = this.polling; this.telegram .getUpdates(timeout, limit, offset, allowedUpdates) .catch(err => { if (err.code === 401 || err.code === 409) { throw err; } const wait = (err.parameters && err.parameters.retry_after) || this.options.retryAfter; console.error( `Failed to fetch updates. Waiting: ${wait}s`, err.message, ); return new Promise(resolve => setTimeout(resolve, wait * 1000, [])); }) .then(updates => this.polling.started ? this.handleUpdates(updates).then(() => updates) : [], ) .catch(err => { console.error('Failed to process updates.', err); this.polling.started = false; this.polling.offset = 0; this.polling.stopCallback && this.polling.stopCallback(); return []; }) .then(updates => { if (updates.length > 0) { this.polling.offset = updates[updates.length - 1].update_id + 1; } this.fetchUpdates(); }); } } module.exports = Object.assign(Telegraf, { Context, Composer, default: Telegraf, Extra, Markup, Router, Telegraf, Telegram, Stage, BaseScene, session, });