Bug Fixes

This commit is contained in:
Luis Fernando Molina committed 2020-02-09 16:35:17 +01:00
1 parent 82bc086e92
commit b1af6e078c
131 files changed
+8597 -1930

No files matched your search

+50 -32
View File
@@ -8,7 +8,7 @@ const TelegramError = require('./error')
const MultipartStream = require('./multipart-stream')
const { isStream } = MultipartStream
const WebhookBlacklist = [
const WEBHOOK_BLACKLIST = [
'getChat',
'getChatAdministrators',
'getChatMember',
@@ -22,7 +22,7 @@ const WebhookBlacklist = [
'exportChatInviteLink'
]
const DefaultExtensions = {
const DEFAULT_EXTENSIONS = {
audio: 'mp3',
photo: 'jpg',
sticker: 'webp',
@@ -32,7 +32,7 @@ const DefaultExtensions = {
voice: 'ogg'
}
const DefaultOptions = {
const DEFAULT_OPTIONS = {
apiRoot: 'https://api.telegram.org',
webhookReply: true,
agent: new https.Agent({
@@ -41,7 +41,7 @@ const DefaultOptions = {
})
}
const WebhookReplyStub = {
const WEBHOOK_REPLY_STUB = {
webhook: true,
details: 'https://core.telegram.org/bots/api#making-requests-when-getting-updates'
}
@@ -79,13 +79,23 @@ function buildJSONConfig (payload) {
})
}
function buildFormDataConfig (payload) {
if (payload.reply_markup && typeof payload.reply_markup !== 'string') {
payload.reply_markup = JSON.stringify(payload.reply_markup)
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]))
const tasks = Object.keys(payload).map((key) => attachFormValue(formData, key, payload[key], agent))
return Promise.all(tasks).then(() => {
return {
method: 'POST',
@@ -96,7 +106,7 @@ function buildFormDataConfig (payload) {
})
}
function attachFormValue (form, id, value) {
function attachFormValue (form, id, value, agent) {
if (!value) {
return Promise.resolve()
}
@@ -110,7 +120,7 @@ function attachFormValue (form, id, value) {
}
if (id === 'thumb') {
const attachmentId = crypto.randomBytes(16).toString('hex')
return attachFormMedia(form, value, attachmentId)
return attachFormMedia(form, value, attachmentId, agent)
.then(() => form.addPart({
headers: { 'content-disposition': `form-data; name="${id}"` },
body: `attach://${attachmentId}`
@@ -123,8 +133,8 @@ function attachFormValue (form, id, value) {
return Promise.resolve(item)
}
const attachmentId = crypto.randomBytes(16).toString('hex')
return attachFormMedia(form, item.media, attachmentId)
.then(() => Object.assign({}, item, { media: `attach://${attachmentId}` }))
return attachFormMedia(form, item.media, attachmentId, agent)
.then(() => ({ ...item, media: `attach://${attachmentId}` }))
})
).then((items) => form.addPart({
headers: { 'content-disposition': `form-data; name="${id}"` },
@@ -133,21 +143,22 @@ function attachFormValue (form, id, value) {
}
if (typeof value.media !== 'undefined' && typeof value.type !== 'undefined') {
const attachmentId = crypto.randomBytes(16).toString('hex')
return attachFormMedia(form, value.media, attachmentId)
return attachFormMedia(form, value.media, attachmentId, agent)
.then(() => form.addPart({
headers: { 'content-disposition': `form-data; name="${id}"` },
body: JSON.stringify(Object.assign(value, {
body: JSON.stringify({
...value,
media: `attach://${attachmentId}`
}))
})
}))
}
return attachFormMedia(form, value, id)
return attachFormMedia(form, value, id, agent)
}
function attachFormMedia (form, media, id) {
let fileName = media.filename || `${id}.${DefaultExtensions[id] || 'dat'}`
function attachFormMedia (form, media, id, agent) {
let fileName = media.filename || `${id}.${DEFAULT_EXTENSIONS[id] || 'dat'}`
if (media.url) {
return fetch(media.url).then((res) =>
return fetch(media.url, { agent }).then((res) =>
form.addPart({
headers: { 'content-disposition': `form-data; name="${id}"; filename="${fileName}"` },
body: res.body
@@ -173,32 +184,36 @@ function isKoaResponse (response) {
return typeof response.set === 'function' && typeof response.header === 'object'
}
function answerToWebhook (response, payload = {}) {
function answerToWebhook (response, payload = {}, options) {
if (!includesMedia(payload)) {
if (isKoaResponse(response)) {
response.body = payload
return Promise.resolve(WebhookReplyStub)
return Promise.resolve(WEBHOOK_REPLY_STUB)
}
if (!response.headersSent) {
response.setHeader('content-type', 'application/json')
}
return new Promise((resolve) =>
response.end(JSON.stringify(payload), 'utf-8', () => resolve(WebhookReplyStub))
)
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)
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(WebhookReplyStub)
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(WebhookReplyStub))
response.on('finish', () => resolve(WEBHOOK_REPLY_STUB))
body.pipe(response)
})
})
@@ -207,7 +222,10 @@ function answerToWebhook (response, payload = {}) {
class ApiClient {
constructor (token, options, webhookResponse) {
this.token = token
this.options = Object.assign({}, DefaultOptions, options)
this.options = {
...DEFAULT_OPTIONS,
...options
}
if (this.options.apiRoot.startsWith('http://')) {
this.options.agent = null
}
@@ -227,12 +245,12 @@ class ApiClient {
const payload = Object.keys(data)
.filter((key) => typeof data[key] !== 'undefined' && data[key] !== null)
.reduce((acc, key) => Object.assign(acc, { [key]: data[key] }), {})
.reduce((acc, key) => ({ ...acc, [key]: data[key] }), {})
if (options.webhookReply && response && !responseEnd && !WebhookBlacklist.includes(method)) {
if (options.webhookReply && response && !responseEnd && !WEBHOOK_BLACKLIST.includes(method)) {
debug('Call via webhook', method, payload)
this.responseEnd = true
return answerToWebhook(response, Object.assign({ method }, payload))
return answerToWebhook(response, { method, ...payload }, options)
}
if (!token) {
@@ -241,7 +259,7 @@ class ApiClient {
debug('HTTP call', method, payload)
const buildConfig = includesMedia(payload)
? buildFormDataConfig(Object.assign({ method }, payload))
? buildFormDataConfig({ method, ...payload }, options.agent)
: buildJSONConfig(payload)
return buildConfig
.then((config) => {
+31 -30
View File
@@ -1,26 +1,4 @@
function wrapEntity (content, entity) {
switch (entity.type) {
case 'bold':
return `<b>${content}</b>`
case 'italic':
return `<i>${content}</i>`
case 'code':
return `<code>${content}</code>`
case 'pre':
return `<pre>${content}</pre>`
case 'text_link':
return `<a href="${entity.url}">${content}</a>`
default:
return content
}
}
function applyEntity (text, entity) {
const head = text.substring(0, entity.offset)
const tail = text.substring(entity.offset + entity.length)
const content = wrapEntity(text.substring(entity.offset, entity.offset + entity.length), entity)
return `${head}${content}${tail}`
}
const { formatHTML } = require('../markup')
module.exports = {
copyMethods: {
@@ -35,17 +13,20 @@ module.exports = {
video: 'sendVideo',
video_note: 'sendVideoNote',
animation: 'sendAnimation',
voice: 'sendVoice'
voice: 'sendVoice',
poll: 'sendPoll'
},
text: (message) => {
const entities = message.entities || []
return {
reply_markup: message.reply_markup,
parse_mode: entities.length > 0 ? 'HTML' : '',
text: entities.reduceRight(applyEntity, message.text)
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
@@ -53,12 +34,14 @@ module.exports = {
},
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,
@@ -69,30 +52,33 @@ module.exports = {
voice: (message) => {
const entities = message.caption_entities || []
return {
reply_markup: message.reply_markup,
voice: message.voice.file_id,
duration: message.voice.duration,
caption: entities.reduceRight(applyEntity, message.caption),
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: entities.reduceRight(applyEntity, message.caption),
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: entities.reduceRight(applyEntity, message.caption),
caption: formatHTML(message.caption, entities),
parse_mode: entities.length > 0 ? 'HTML' : '',
duration: message.video.duration,
width: message.video.width,
@@ -103,26 +89,30 @@ module.exports = {
document: (message) => {
const entities = message.caption_entities || []
return {
reply_markup: message.reply_markup,
document: message.document.file_id,
caption: entities.reduceRight(applyEntity, message.caption),
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: entities.reduceRight(applyEntity, message.caption)
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,
@@ -131,9 +121,20 @@ module.exports = {
},
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)
}
}
}