Bug Fixes
This commit is contained in:
1 parent
82bc086e92
commit
b1af6e078c
131 files changed
+8597
-1930
No files matched your search
+50
-32
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user