221 lines
6.6 KiB
JavaScript
221 lines
6.6 KiB
JavaScript
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
|
|
}/* eslint brace-style: 0 */
|
|
|
|
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,
|
|
Telegram,
|
|
Stage,
|
|
BaseScene,
|
|
session
|
|
})
|