Desplegando App
This commit is contained in:
288
node_modules/telegraf/core/network/client.js
generated
vendored
Normal file
288
node_modules/telegraf/core/network/client.js
generated
vendored
Normal file
@@ -0,0 +1,288 @@
|
||||
const debug = require('debug')('telegraf:client')
|
||||
const crypto = require('crypto')
|
||||
const fetch = require('node-fetch').default
|
||||
const fs = require('fs')
|
||||
const https = require('https')
|
||||
const path = require('path')
|
||||
const TelegramError = require('./error')
|
||||
const MultipartStream = require('./multipart-stream')
|
||||
const { isStream } = MultipartStream
|
||||
|
||||
const WEBHOOK_BLACKLIST = [
|
||||
'getChat',
|
||||
'getChatAdministrators',
|
||||
'getChatMember',
|
||||
'getChatMembersCount',
|
||||
'getFile',
|
||||
'getFileLink',
|
||||
'getGameHighScores',
|
||||
'getMe',
|
||||
'getUserProfilePhotos',
|
||||
'getWebhookInfo',
|
||||
'exportChatInviteLink'
|
||||
]
|
||||
|
||||
const DEFAULT_EXTENSIONS = {
|
||||
audio: 'mp3',
|
||||
photo: 'jpg',
|
||||
sticker: 'webp',
|
||||
video: 'mp4',
|
||||
animation: 'mp4',
|
||||
video_note: 'mp4',
|
||||
voice: 'ogg'
|
||||
}
|
||||
|
||||
const DEFAULT_OPTIONS = {
|
||||
apiRoot: 'https://api.telegram.org',
|
||||
webhookReply: true,
|
||||
agent: new https.Agent({
|
||||
keepAlive: true,
|
||||
keepAliveMsecs: 10000
|
||||
})
|
||||
}
|
||||
|
||||
const WEBHOOK_REPLY_STUB = {
|
||||
webhook: true,
|
||||
details: 'https://core.telegram.org/bots/api#making-requests-when-getting-updates'
|
||||
}
|
||||
|
||||
function safeJSONParse (text) {
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch (err) {
|
||||
debug('JSON parse failed', err)
|
||||
}
|
||||
}
|
||||
|
||||
function includesMedia (payload) {
|
||||
return Object.keys(payload).some(
|
||||
(key) => {
|
||||
const value = payload[key]
|
||||
if (Array.isArray(value)) {
|
||||
return value.some(({ media }) => media && typeof media === 'object' && (media.source || media.url))
|
||||
}
|
||||
return (typeof value === 'object') && (
|
||||
value.source ||
|
||||
value.url ||
|
||||
(typeof value.media === 'object' && (value.media.source || value.media.url))
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function buildJSONConfig (payload) {
|
||||
return Promise.resolve({
|
||||
method: 'POST',
|
||||
compress: true,
|
||||
headers: { 'content-type': 'application/json', connection: 'keep-alive' },
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
}
|
||||
|
||||
const FORM_DATA_JSON_FIELDS = [
|
||||
'results',
|
||||
'reply_markup',
|
||||
'mask_position',
|
||||
'shipping_options',
|
||||
'errors'
|
||||
]
|
||||
|
||||
function buildFormDataConfig (payload, agent) {
|
||||
for (const field of FORM_DATA_JSON_FIELDS) {
|
||||
if (field in payload && typeof payload[field] !== 'string') {
|
||||
payload[field] = JSON.stringify(payload[field])
|
||||
}
|
||||
}
|
||||
const boundary = crypto.randomBytes(32).toString('hex')
|
||||
const formData = new MultipartStream(boundary)
|
||||
const tasks = Object.keys(payload).map((key) => attachFormValue(formData, key, payload[key], agent))
|
||||
return Promise.all(tasks).then(() => {
|
||||
return {
|
||||
method: 'POST',
|
||||
compress: true,
|
||||
headers: { 'content-type': `multipart/form-data; boundary=${boundary}`, connection: 'keep-alive' },
|
||||
body: formData
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function attachFormValue (form, id, value, agent) {
|
||||
if (!value) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
const valueType = typeof value
|
||||
if (valueType === 'string' || valueType === 'boolean' || valueType === 'number') {
|
||||
form.addPart({
|
||||
headers: { 'content-disposition': `form-data; name="${id}"` },
|
||||
body: `${value}`
|
||||
})
|
||||
return Promise.resolve()
|
||||
}
|
||||
if (id === 'thumb') {
|
||||
const attachmentId = crypto.randomBytes(16).toString('hex')
|
||||
return attachFormMedia(form, value, attachmentId, agent)
|
||||
.then(() => form.addPart({
|
||||
headers: { 'content-disposition': `form-data; name="${id}"` },
|
||||
body: `attach://${attachmentId}`
|
||||
}))
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return Promise.all(
|
||||
value.map((item) => {
|
||||
if (typeof item.media !== 'object') {
|
||||
return Promise.resolve(item)
|
||||
}
|
||||
const attachmentId = crypto.randomBytes(16).toString('hex')
|
||||
return attachFormMedia(form, item.media, attachmentId, agent)
|
||||
.then(() => ({ ...item, media: `attach://${attachmentId}` }))
|
||||
})
|
||||
).then((items) => form.addPart({
|
||||
headers: { 'content-disposition': `form-data; name="${id}"` },
|
||||
body: JSON.stringify(items)
|
||||
}))
|
||||
}
|
||||
if (typeof value.media !== 'undefined' && typeof value.type !== 'undefined') {
|
||||
const attachmentId = crypto.randomBytes(16).toString('hex')
|
||||
return attachFormMedia(form, value.media, attachmentId, agent)
|
||||
.then(() => form.addPart({
|
||||
headers: { 'content-disposition': `form-data; name="${id}"` },
|
||||
body: JSON.stringify({
|
||||
...value,
|
||||
media: `attach://${attachmentId}`
|
||||
})
|
||||
}))
|
||||
}
|
||||
return attachFormMedia(form, value, id, agent)
|
||||
}
|
||||
|
||||
function attachFormMedia (form, media, id, agent) {
|
||||
let fileName = media.filename || `${id}.${DEFAULT_EXTENSIONS[id] || 'dat'}`
|
||||
if (media.url) {
|
||||
return fetch(media.url, { agent }).then((res) =>
|
||||
form.addPart({
|
||||
headers: { 'content-disposition': `form-data; name="${id}"; filename="${fileName}"` },
|
||||
body: res.body
|
||||
})
|
||||
)
|
||||
}
|
||||
if (media.source) {
|
||||
if (fs.existsSync(media.source)) {
|
||||
fileName = media.filename || path.basename(media.source)
|
||||
media.source = fs.createReadStream(media.source)
|
||||
}
|
||||
if (isStream(media.source) || Buffer.isBuffer(media.source)) {
|
||||
form.addPart({
|
||||
headers: { 'content-disposition': `form-data; name="${id}"; filename="${fileName}"` },
|
||||
body: media.source
|
||||
})
|
||||
}
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
function isKoaResponse (response) {
|
||||
return typeof response.set === 'function' && typeof response.header === 'object'
|
||||
}
|
||||
|
||||
function answerToWebhook (response, payload = {}, options) {
|
||||
if (!includesMedia(payload)) {
|
||||
if (isKoaResponse(response)) {
|
||||
response.body = payload
|
||||
return Promise.resolve(WEBHOOK_REPLY_STUB)
|
||||
}
|
||||
if (!response.headersSent) {
|
||||
response.setHeader('content-type', 'application/json')
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
if (response.end.length === 2) {
|
||||
response.end(JSON.stringify(payload), 'utf-8')
|
||||
return resolve(WEBHOOK_REPLY_STUB)
|
||||
}
|
||||
response.end(JSON.stringify(payload), 'utf-8', () => resolve(WEBHOOK_REPLY_STUB))
|
||||
})
|
||||
}
|
||||
|
||||
return buildFormDataConfig(payload, options.agent)
|
||||
.then(({ headers, body }) => {
|
||||
if (isKoaResponse(response)) {
|
||||
Object.keys(headers).forEach(key => response.set(key, headers[key]))
|
||||
response.body = body
|
||||
return Promise.resolve(WEBHOOK_REPLY_STUB)
|
||||
}
|
||||
if (!response.headersSent) {
|
||||
Object.keys(headers).forEach(key => response.setHeader(key, headers[key]))
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
response.on('finish', () => resolve(WEBHOOK_REPLY_STUB))
|
||||
body.pipe(response)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
class ApiClient {
|
||||
constructor (token, options, webhookResponse) {
|
||||
this.token = token
|
||||
this.options = {
|
||||
...DEFAULT_OPTIONS,
|
||||
...options
|
||||
}
|
||||
if (this.options.apiRoot.startsWith('http://')) {
|
||||
this.options.agent = null
|
||||
}
|
||||
this.response = webhookResponse
|
||||
}
|
||||
|
||||
set webhookReply (enable) {
|
||||
this.options.webhookReply = enable
|
||||
}
|
||||
|
||||
get webhookReply () {
|
||||
return this.options.webhookReply
|
||||
}
|
||||
|
||||
callApi (method, data = {}) {
|
||||
const { token, options, response, responseEnd } = this
|
||||
|
||||
const payload = Object.keys(data)
|
||||
.filter((key) => typeof data[key] !== 'undefined' && data[key] !== null)
|
||||
.reduce((acc, key) => ({ ...acc, [key]: data[key] }), {})
|
||||
|
||||
if (options.webhookReply && response && !responseEnd && !WEBHOOK_BLACKLIST.includes(method)) {
|
||||
debug('Call via webhook', method, payload)
|
||||
this.responseEnd = true
|
||||
return answerToWebhook(response, { method, ...payload }, options)
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
throw new TelegramError({ error_code: 401, description: 'Bot Token is required' })
|
||||
}
|
||||
|
||||
debug('HTTP call', method, payload)
|
||||
const buildConfig = includesMedia(payload)
|
||||
? buildFormDataConfig({ method, ...payload }, options.agent)
|
||||
: buildJSONConfig(payload)
|
||||
return buildConfig
|
||||
.then((config) => {
|
||||
const apiUrl = `${options.apiRoot}/bot${token}/${method}`
|
||||
config.agent = options.agent
|
||||
return fetch(apiUrl, config)
|
||||
})
|
||||
.then((res) => res.text())
|
||||
.then((text) => {
|
||||
return safeJSONParse(text) || {
|
||||
error_code: 500,
|
||||
description: 'Unsupported http response from Telegram',
|
||||
response: text
|
||||
}
|
||||
})
|
||||
.then((data) => {
|
||||
if (!data.ok) {
|
||||
debug('API call failed', data)
|
||||
throw new TelegramError(data, { method, payload })
|
||||
}
|
||||
return data.result
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ApiClient
|
||||
12
node_modules/telegraf/core/network/error.js
generated
vendored
Normal file
12
node_modules/telegraf/core/network/error.js
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
class TelegramError extends Error {
|
||||
constructor (payload = {}, on) {
|
||||
super(`${payload.error_code}: ${payload.description}`)
|
||||
this.code = payload.error_code
|
||||
this.response = payload
|
||||
this.description = payload.description
|
||||
this.parameters = payload.parameters || {}
|
||||
this.on = on || {}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TelegramError
|
||||
37
node_modules/telegraf/core/network/multipart-stream.js
generated
vendored
Normal file
37
node_modules/telegraf/core/network/multipart-stream.js
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
const stream = require('stream')
|
||||
const { SandwichStream } = require('sandwich-stream')
|
||||
const CRNL = '\r\n'
|
||||
|
||||
class MultipartStream extends SandwichStream {
|
||||
constructor (boundary) {
|
||||
super({
|
||||
head: `--${boundary}${CRNL}`,
|
||||
tail: `${CRNL}--${boundary}--`,
|
||||
separator: `${CRNL}--${boundary}${CRNL}`
|
||||
})
|
||||
}
|
||||
|
||||
addPart (part) {
|
||||
part = part || {}
|
||||
const partStream = new stream.PassThrough()
|
||||
if (part.headers) {
|
||||
for (const key in part.headers) {
|
||||
const header = part.headers[key]
|
||||
partStream.write(`${key}:${header}${CRNL}`)
|
||||
}
|
||||
}
|
||||
partStream.write(CRNL)
|
||||
if (MultipartStream.isStream(part.body)) {
|
||||
part.body.pipe(partStream)
|
||||
} else {
|
||||
partStream.end(part.body)
|
||||
}
|
||||
this.add(partStream)
|
||||
}
|
||||
|
||||
static isStream (stream) {
|
||||
return stream && typeof stream === 'object' && typeof stream.pipe === 'function'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MultipartStream
|
||||
39
node_modules/telegraf/core/network/webhook.js
generated
vendored
Normal file
39
node_modules/telegraf/core/network/webhook.js
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
const debug = require('debug')('telegraf:webhook')
|
||||
|
||||
module.exports = function (hookPath, updateHandler, errorHandler) {
|
||||
return (req, res, next) => {
|
||||
debug('Incoming request', req.method, req.url)
|
||||
if (req.method !== 'POST' || req.url !== hookPath) {
|
||||
if (typeof next === 'function') {
|
||||
return next()
|
||||
}
|
||||
res.statusCode = 403
|
||||
return res.end()
|
||||
}
|
||||
let body = ''
|
||||
req.on('data', (chunk) => {
|
||||
body += chunk.toString()
|
||||
})
|
||||
req.on('end', () => {
|
||||
let update = {}
|
||||
try {
|
||||
update = JSON.parse(body)
|
||||
} catch (error) {
|
||||
res.writeHead(415)
|
||||
res.end()
|
||||
return errorHandler(error)
|
||||
}
|
||||
updateHandler(update, res)
|
||||
.then(() => {
|
||||
if (!res.finished) {
|
||||
res.end()
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
debug('Webhook error', err)
|
||||
res.writeHead(500)
|
||||
res.end()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
140
node_modules/telegraf/core/replicators.js
generated
vendored
Normal file
140
node_modules/telegraf/core/replicators.js
generated
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
const { formatHTML } = require('../markup')
|
||||
|
||||
module.exports = {
|
||||
copyMethods: {
|
||||
audio: 'sendAudio',
|
||||
contact: 'sendContact',
|
||||
document: 'sendDocument',
|
||||
location: 'sendLocation',
|
||||
photo: 'sendPhoto',
|
||||
sticker: 'sendSticker',
|
||||
text: 'sendMessage',
|
||||
venue: 'sendVenue',
|
||||
video: 'sendVideo',
|
||||
video_note: 'sendVideoNote',
|
||||
animation: 'sendAnimation',
|
||||
voice: 'sendVoice',
|
||||
poll: 'sendPoll'
|
||||
},
|
||||
text: (message) => {
|
||||
const entities = message.entities || []
|
||||
return {
|
||||
reply_markup: message.reply_markup,
|
||||
parse_mode: entities.length > 0 ? 'HTML' : '',
|
||||
text: formatHTML(message.text, entities)
|
||||
}
|
||||
},
|
||||
contact: (message) => {
|
||||
return {
|
||||
reply_markup: message.reply_markup,
|
||||
phone_number: message.contact.phone_number,
|
||||
first_name: message.contact.first_name,
|
||||
last_name: message.contact.last_name
|
||||
}
|
||||
},
|
||||
location: (message) => {
|
||||
return {
|
||||
reply_markup: message.reply_markup,
|
||||
latitude: message.location.latitude,
|
||||
longitude: message.location.longitude
|
||||
}
|
||||
},
|
||||
venue: (message) => {
|
||||
return {
|
||||
reply_markup: message.reply_markup,
|
||||
latitude: message.venue.location.latitude,
|
||||
longitude: message.venue.location.longitude,
|
||||
title: message.venue.title,
|
||||
address: message.venue.address,
|
||||
foursquare_id: message.venue.foursquare_id
|
||||
}
|
||||
},
|
||||
voice: (message) => {
|
||||
const entities = message.caption_entities || []
|
||||
return {
|
||||
reply_markup: message.reply_markup,
|
||||
voice: message.voice.file_id,
|
||||
duration: message.voice.duration,
|
||||
caption: formatHTML(message.caption, entities),
|
||||
parse_mode: entities.length > 0 ? 'HTML' : ''
|
||||
}
|
||||
},
|
||||
audio: (message) => {
|
||||
const entities = message.caption_entities || []
|
||||
return {
|
||||
reply_markup: message.reply_markup,
|
||||
audio: message.audio.file_id,
|
||||
thumb: message.audio.thumb,
|
||||
duration: message.audio.duration,
|
||||
performer: message.audio.performer,
|
||||
title: message.audio.title,
|
||||
caption: formatHTML(message.caption, entities),
|
||||
parse_mode: entities.length > 0 ? 'HTML' : ''
|
||||
}
|
||||
},
|
||||
video: (message) => {
|
||||
const entities = message.caption_entities || []
|
||||
return {
|
||||
reply_markup: message.reply_markup,
|
||||
video: message.video.file_id,
|
||||
thumb: message.video.thumb,
|
||||
caption: formatHTML(message.caption, entities),
|
||||
parse_mode: entities.length > 0 ? 'HTML' : '',
|
||||
duration: message.video.duration,
|
||||
width: message.video.width,
|
||||
height: message.video.height,
|
||||
supports_streaming: !!message.video.supports_streaming
|
||||
}
|
||||
},
|
||||
document: (message) => {
|
||||
const entities = message.caption_entities || []
|
||||
return {
|
||||
reply_markup: message.reply_markup,
|
||||
document: message.document.file_id,
|
||||
caption: formatHTML(message.caption, entities),
|
||||
parse_mode: entities.length > 0 ? 'HTML' : ''
|
||||
}
|
||||
},
|
||||
sticker: (message) => {
|
||||
return {
|
||||
reply_markup: message.reply_markup,
|
||||
sticker: message.sticker.file_id
|
||||
}
|
||||
},
|
||||
photo: (message) => {
|
||||
const entities = message.caption_entities || []
|
||||
return {
|
||||
reply_markup: message.reply_markup,
|
||||
photo: message.photo[message.photo.length - 1].file_id,
|
||||
parse_mode: entities.length > 0 ? 'HTML' : '',
|
||||
caption: formatHTML(message.caption, entities)
|
||||
}
|
||||
},
|
||||
video_note: (message) => {
|
||||
return {
|
||||
reply_markup: message.reply_markup,
|
||||
video_note: message.video_note.file_id,
|
||||
thumb: message.video_note.thumb,
|
||||
length: message.video_note.length,
|
||||
duration: message.video_note.duration
|
||||
}
|
||||
},
|
||||
animation: (message) => {
|
||||
return {
|
||||
reply_markup: message.reply_markup,
|
||||
animation: message.animation.file_id,
|
||||
thumb: message.animation.thumb,
|
||||
duration: message.animation.duration
|
||||
}
|
||||
},
|
||||
poll: (message) => {
|
||||
return {
|
||||
question: message.poll.question,
|
||||
type: message.poll.type,
|
||||
is_anonymous: message.poll.is_anonymous,
|
||||
allows_multiple_answers: message.poll.allows_multiple_answers,
|
||||
correct_option_id: message.poll.correct_option_id,
|
||||
options: message.poll.options.map(({ text }) => text)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user