ВК: Плагин по форматированию текста и кнопок
| Название плагина | VK |
| Разработчик | Официальные плагины от Metabot |
| Автор |
Илья Зыщик |
| Дата создания | 30 Июля 2026 |
| Последняя дата обновления | 31 Июля 2026 |
Описание
Плагин позволяет форматировать текст и кнопки отправляемые в ВК.
Подключение
snippet('Business.Integrations.VK')
var msg = new VkMessage()
Использование
Текст
msg.text = 'Привет'
msg.send()
Кнопки
DSL:
==- новый ряд||- кнопки в одном рядуТекст[команда]- кнопкаСайт[https://...]- ссылка
Подпись обрезается до 20 символов, кнопок не больше 7.
callback_data всегда { command: "..." }.
msg.text = 'Меню'
msg.keyboard = 'Да[yes]==Нет[no]'
msg.send()
return true
В VK кнопка часто приходит текстом подписи, не callback.
snippet('Business.Integrations.VK')
var msg = new VkMessage()
var MENU = 'Да[yes]==Нет[no]'
msg.keyboard = MENU
var p = msg.getMessagePayload()
if (!isFirstImmediateCall && p.callback_data && p.callback_data !== 'undefined') {
bot.runScriptByCodeForLead(p.callback_data, leadId)
return true
}
msg.text = 'Выберите:'
msg.send()
return true
Также можно прописывать номера кнопок: 1, 2
Картинки и файлы (отправка)
Плагин принимает только image и file. Файл должен быть указан как публичный URL.
msg.text = 'Фото'
msg.filesData = [
{ type: 'image', url: 'https://example.com/pic.jpg' }
]
msg.send()
msg.text = 'Документ'
msg.filesData = [
{ type: 'file', url: 'https://example.com/doc.pdf' }
]
msg.send()
После send():
lead.getJsonAttr('vk_last_send_result')lead.getAttr('vk_last_message_id')
Входящие вложения
msg.getImages()
msg.getFiles()
msg.getAllAttachments()
Видео из VK сейчас принимать нельзя
Исходный код плагина
/**
* Upload в VK: только image и file (photo/doc).
* Текст+кнопки с вложением платформа для VK отправляет раздельно (не как TG/Max).
*/
if (typeof VkMessage === 'undefined') {
var vkNormalizeCommand = function (data) {
var s
var o
if (data == null) {
return ''
}
if (typeof data === 'object') {
return String(data.command || data.callback_data || '')
}
s = String(data)
if (s.charAt(0) === '{') {
try {
o = JSON.parse(s)
return String(o.command || o.callback_data || '')
} catch (e) {}
}
if (s.indexOf('btn_static_') === 0) {
return s.slice('btn_static_'.length)
}
return s
}
var vkMakeCallback = function (command) {
return { command: String(command) }
}
var vkSafeCall = function (fn, args) {
try {
if (typeof fn !== 'function') {
return []
}
return fn.apply(bot, args || []) || []
} catch (e) {
return []
}
}
var vkMapAttachmentType = function (raw) {
var t = String(raw || 'file').toLowerCase()
if (t === 'photo' || t === 'image' || t === 'sticker') {
return 'image'
}
if (t === 'document' || t === 'doc' || t === 'file') {
return 'file'
}
return null
}
VkMessage = function () {
this._text = null
this._keyboard = null
this._attachments = []
this._answer_symbol = '>'
this._max_button_label = 20
this._max_buttons = 7
this._baseServerUrl = 'https://app.metabot24.com/' /*меняем на сервер который будет использоваться для отправки сообщений*/
this.webhookPayload = bot.getWebhookPayload()
}
Object.defineProperty(VkMessage.prototype, 'text', {
get: function () {
return this._text
},
set: function (value) {
this._text = value
}
})
Object.defineProperty(VkMessage.prototype, 'keyboard', {
get: function () {
return this._keyboard
},
set: function (value) {
this._keyboard = value ? this.createVkKeyboard(value) : null
}
})
Object.defineProperty(VkMessage.prototype, 'answer_symbol', {
set: function (value) {
this._answer_symbol = value
}
})
Object.defineProperty(VkMessage.prototype, 'filesData', {
get: function () {
return this._attachments
},
set: function (value) {
var i
var prepared
var item
var list
this._attachments = []
if (!value) {
return
}
if (Object.prototype.toString.call(value) !== '[object Array]') {
this.debug('filesData must be an array')
return
}
list = value
for (i = 0; i < list.length; i++) {
item = list[i]
prepared = this.prepareAttachment(item)
if (prepared) {
this._attachments.push(prepared)
}
}
}
})
Object.defineProperty(VkMessage.prototype, 'baseServerUrl', {
set: function (value) {
if (value) {
this._baseServerUrl = String(value)
if (this._baseServerUrl.charAt(this._baseServerUrl.length - 1) !== '/') {
this._baseServerUrl += '/'
}
}
}
})
VkMessage.prototype.createVkKeyboard = function (keyboard) {
var rows = String(keyboard).split('==')
var result = []
var count = 0
var i
var j
var cols
var row
var item
var btn
var code
for (i = 0; i < rows.length; i++) {
cols = rows[i].split('||')
row = []
for (j = 0; j < cols.length; j++) {
item = String(cols[j] || '').trim()
if (!item) {
continue
}
if (count >= this._max_buttons) {
break
}
btn = null
if (/https?:\/\//.test(item) && /\[[^\[\]]+\]/.test(item)) {
code = item.match(/\[[^\[\]]+\]/)[0].slice(1, -1)
btn = {
text: this._clip(item.replace(/\[[^\[\]]+\]/g, '')),
url: code,
callback_data: vkMakeCallback(code),
text_button_label: String(count + 1)
}
} else if (/\[[^\[\]]+\]/.test(item)) {
code = item.match(/\[[^\[\]]+\]/)[0].slice(1, -1)
btn = {
text: this._clip(item.replace(/\[[^\[\]]+\]/g, '')),
callback_data: vkMakeCallback(code),
text_button_label: String(count + 1)
}
} else {
btn = {
text: this._clip(item),
callback_data: vkMakeCallback(item),
text_button_label: String(count + 1)
}
}
row.push(btn)
count++
}
if (row.length) {
result.push(row)
}
}
return result
}
VkMessage.prototype._clip = function (label) {
var text = String(label || '').trim()
if (text.length > this._max_button_label) {
return text.slice(0, this._max_button_label)
}
return text
}
VkMessage.prototype.debug = function (data) {
bot.sendMessage(
'VK debug\n\n' + (typeof data === 'string' ? data : JSON.stringify(data))
)
}
VkMessage.prototype.processLocalFile = function (filePath) {
var attrs
var businessId
attrs = bot.getDebugAttributes ? bot.getDebugAttributes() : null
businessId = attrs && attrs.business_id != null ? attrs.business_id : ''
return this._baseServerUrl + 'uploads/business/' + businessId + '/' + filePath
}
/**
* Готовит вложение для bot.sendMessage(..., attachments).
* Поддерживаемые type: image, file (photo как image, document/doc как file).
*/
VkMessage.prototype.prepareAttachment = function (item) {
var type
var url
var out
if (!item || typeof item !== 'object') {
return null
}
type = vkMapAttachmentType(item.type || item.file_type)
if (!type) {
this.debug({
error: 'unsupported attachment type for VK upload',
type: item.type || item.file_type,
hint: 'use image or file only'
})
return null
}
url = item.url || item.file_url || item.file_id || null
if (!url && item.file_path) {
url = this.processLocalFile(item.file_path)
}
if (!url) {
this.debug({
error: 'attachment url missing',
item: item
})
return null
}
out = {
type: type,
url: String(url)
}
if (item.caption != null) {
out.caption = String(item.caption)
} else if (item.text != null) {
out.caption = String(item.text)
}
if (item.payload && typeof item.payload === 'object') {
out.payload = item.payload
}
if (item.file_id && item.file_id !== url) {
out.file_id = item.file_id
}
return out
}
VkMessage.prototype.getFiles = function (mimeInclude, mimeExclude) {
if (mimeExclude === undefined) {
return vkSafeCall(bot.getFiles, [mimeInclude])
}
return vkSafeCall(bot.getFiles, [mimeInclude, mimeExclude])
}
VkMessage.prototype.getImages = function (mimeInclude) {
if (mimeInclude === undefined) {
return vkSafeCall(bot.getImages, [])
}
return vkSafeCall(bot.getImages, [mimeInclude])
}
VkMessage.prototype.getVideos = function (mimeInclude) {
if (mimeInclude === undefined) {
return vkSafeCall(bot.getVideos, [])
}
return vkSafeCall(bot.getVideos, [mimeInclude])
}
VkMessage.prototype.getAllAttachments = function (filesMimeExclude) {
if (filesMimeExclude === undefined) {
return vkSafeCall(bot.getAllAttachments, [])
}
return vkSafeCall(bot.getAllAttachments, [filesMimeExclude])
}
VkMessage.prototype._resolveMessageId = function (resultPayload) {
var messageId = null
if (typeof bot.getLastMessageId === 'function') {
try {
messageId = bot.getLastMessageId()
} catch (e) {}
}
if (messageId != null) {
return messageId
}
if (!resultPayload) {
return null
}
if (resultPayload.response != null) {
return resultPayload.response
}
if (resultPayload.message_id != null) {
return resultPayload.message_id
}
if (resultPayload.result != null && typeof resultPayload.result !== 'object') {
return resultPayload.result
}
if (resultPayload.result && resultPayload.result.message_id != null) {
return resultPayload.result.message_id
}
return null
}
VkMessage.prototype.send = function () {
var text
var sent
var resultPayload
var messageId
var hasAttachments
var keyboard
hasAttachments =
Object.prototype.toString.call(this._attachments) === '[object Array]' &&
this._attachments.length > 0
if (!this._text && !hasAttachments) {
this.debug('text or filesData is required')
return false
}
text = this._text != null && String(this._text) !== '' ? String(this._text) : ' '
keyboard = this._keyboard && this._keyboard.length ? this._keyboard : null
if (hasAttachments) {
sent = bot.sendMessage(text, keyboard, this._attachments)
} else if (keyboard) {
sent = bot.sendMessage(text, keyboard)
} else {
sent = bot.sendMessage(text)
}
resultPayload = bot.getLastResultPayload()
lead.setJsonAttr('vk_last_send_result', {
sent: sent,
has_keyboard: !!keyboard,
has_attachments: hasAttachments,
attachments_count: hasAttachments ? this._attachments.length : 0,
result: resultPayload
})
messageId = this._resolveMessageId(resultPayload)
if (messageId != null) {
lead.setAttr('vk_last_message_id', String(messageId))
}
if (sent === false || (resultPayload && resultPayload.error)) {
this.debug({
error: 'send failed',
sent: sent,
has_attachments: hasAttachments,
result: resultPayload
})
}
return this.getMessagePayload()
}
VkMessage.prototype.addReplyToText = function () {
var payload = this.getMessagePayload()
if (payload.input_type === 'press' && payload.text) {
this._text = this._text + '\n\n' + this._answer_symbol + ' ' + payload.text
}
this._keyboard = null
this._attachments = []
return this.send()
}
VkMessage.prototype._attachIncoming = function (result) {
var attachments
var hasText
attachments = this.getAllAttachments()
if (Object.prototype.toString.call(attachments) !== '[object Array]') {
attachments = []
}
result.attachments = attachments
hasText = result.text != null && String(result.text) !== '' && result.text !== 'undefined'
if (attachments.length && !hasText && result.input_type !== 'press') {
result.input_type = 'attachment'
if (!result.text || result.text === 'undefined') {
result.text = ''
}
}
return result
}
VkMessage.prototype.getMessagePayload = function () {
var result = {}
var payload = this.webhookPayload || {}
var data
var mid
var eventObj
var raw
var incoming
var r
var b
var row
var btn
var cmd
if (payload.callback_query) {
data = payload.callback_query.data
mid = null
if (payload.callback_query.message) {
mid = payload.callback_query.message.message_id
}
cmd = vkNormalizeCommand(data)
result.message_id = mid || lead.getAttr('vk_last_message_id') || null
result.callback_data = cmd
result.text = this._buttonLabel(cmd) || cmd
result.input_type = 'press'
result.payload = payload
return this._attachIncoming(result)
}
eventObj = payload.object || payload
if (
payload.type === 'message_event' ||
eventObj.type === 'message_event' ||
(eventObj.event_id != null && eventObj.payload != null)
) {
raw = eventObj.payload
cmd = vkNormalizeCommand(raw)
result.message_id =
eventObj.conversation_message_id || lead.getAttr('vk_last_message_id') || null
result.callback_data = cmd
result.text = this._buttonLabel(cmd) || cmd
result.input_type = 'press'
result.payload = payload
return this._attachIncoming(result)
}
incoming =
(payload.message && payload.message.text) ||
(payload.object && payload.object.message && payload.object.message.text) ||
(payload.object && payload.object.text) ||
bot.getIncomingMessage()
if (incoming != null && String(incoming) !== '') {
result.message_id =
(payload.message && payload.message.id) ||
(payload.object && payload.object.message && payload.object.message.id) ||
(payload.object && payload.object.conversation_message_id) ||
null
result.text = String(incoming)
result.input_type = 'write'
result.payload = payload
if (Object.prototype.toString.call(this._keyboard) === '[object Array]') {
for (r = 0; r < this._keyboard.length; r++) {
row = this._keyboard[r]
for (b = 0; b < row.length; b++) {
btn = row[b]
if (!btn || !btn.callback_data) {
continue
}
// VK часто шлёт подпись кнопки текстом; также принимаем номер 1/2/…
if (
btn.text === result.text ||
String(btn.text_button_label || '') === result.text
) {
result.callback_data = vkNormalizeCommand(btn.callback_data)
result.input_type = 'press'
return this._attachIncoming(result)
}
}
}
}
return this._attachIncoming(result)
}
result.message_id =
(payload.message && payload.message.id) ||
(payload.object && payload.object.message && payload.object.message.id) ||
(payload.object && payload.object.conversation_message_id) ||
null
result.text = 'undefined'
result.input_type = 'undefined'
result.callback_data = 'undefined'
result.payload = payload
return this._attachIncoming(result)
}
VkMessage.prototype._buttonLabel = function (command) {
var needle
var r
var b
var row
var btn
var cb
if (
Object.prototype.toString.call(this._keyboard) !== '[object Array]' ||
command == null
) {
return null
}
needle = vkNormalizeCommand(command)
for (r = 0; r < this._keyboard.length; r++) {
row = this._keyboard[r]
for (b = 0; b < row.length; b++) {
btn = row[b]
cb = vkNormalizeCommand(btn.callback_data)
if (cb === needle) {
return btn.text
}
}
}
return null
}
}