Спасаем бизнес: Создаём VK-бота на PHP, Python, JS и C#.
Часть 2 — Учим бота отвечать на сообщения
В прошлой части мы подготовили площадку: создали сообщество, получили ключи API, настроили Callback-сервер и подтвердили его. Наш бот уже умеет
принимать запросы от VK и честно отвечать ok на все события. Но пока он глух и нем — на message_new он просто кивает, но ничего не делает.
Пришло
время это исправить.
Что будем делать
В этой части мы научим бота:
Распознавать входящие сообщения (событие message_new).
Отправлять ответ обратно пользователю.
Обрабатывать базовые команды, например /start или «Привет».
Мы по-прежнему будем давать примеры на четырёх языках, так что вы сможете выбрать свой стек.
Включаем нужные события
Прежде чем писать код, нужно сказать VK, какие события нас интересуют. Зайдите в управление сообществом → «Работа с API» → вкладка «Типы
событий».
Убедитесь, что напротив события «Входящее сообщение» стоит галочка:
Выбираем пока только эту часть, остальные галочки оставляем пустыми
Теперь VK будет присылать на ваш сервер уведомления с типом message_new каждый раз, когда пользователь пишет боту.
Логика обработки
Принцип остаётся тем же, что и в первой части: мы получаем POST-запрос с JSON, смотрим на поле type и, если это message_new, извлекаем из объекта
данные (текст сообщения, ID пользователя) и отправляем ответ.
Ответ отправляется отдельным запросом к VK API но об этом позже.
Пример данных, которые отправляет VK API по событию сообщения:
{
"type": "тип отправленного события",
"event_id": "идентификатор события",
"v": "версия API, под которую событие сформировано",
"object": "объект, вызвавший событие",
"group_id": "Идентификатор сообщества, в котором произошло событие"
}
Вот так могут выглядеть данные при событии нового сообщения:
{
"group_id": 111111111,
"type": "message_new",
"event_id": "067ba91c03cfd888532208794a257f95a34dad0b",
"v": "5.199",
"object": {
"client_info": {
"button_actions": [
"text",
"vkpay",
"open_app",
"location",
"open_link",
"open_photo",
"callback",
"intent_subscribe",
"intent_unsubscribe"
],
"keyboard": true,
"inline_keyboard": true,
"carousel": true,
"lang_id": 0
},
"message": {
"date": 1773940815,
"from_id": 111111111,
"id": 5,
"version": 10000020,
"out": 0,
"fwd_messages": [],
"important": false,
"is_hidden": false,
"attachments": [],
"conversation_message_id": 2,
"text": "А вот здесь будет текст нашего сообщения",
"peer_id": 111111111,
"random_id": 0
}
},
"secret": "Об этом поговорим позже"
}
Теперь, зная поля, мы можем составить логику обработки. Мы можем, ориентируясь на тип события, принять решение об ответе.
Так как способы
и пути обработки разных событий - разные, начнем с простого, с того что нам точно будет необходимо - составим функцию ответа в чат:
// $peerId мы забираем из данных
// -> object -> message -> peer_id
function sendMessage($peerId, $message)
{
// Собираем данные
$params = [
'peer_id' => $peerId,
'message' => $message,
'access_token' => ACCESS_TOKEN,
'v' => VERSION,
'random_id' => rand(1, 1000000) // обязательно для отправки
];
// Формируем URL
$url = ENDPOINT . 'messages.send?' . http_build_query($params);
// Отправляем запрос на API
$response = file_get_contents($url);
// Возвращаем распаршенный json
return json_decode($response);
}
# peer_id мы забираем из данных
# -> object -> message -> peer_id
def send_message(peer_id, message):
# Собираем данные
params = {
'peer_id': peer_id,
'message': message,
'access_token': ACCESS_TOKEN,
'v': VERSION,
'random_id': random.randint(1, 1000000) # обязательно для отправки
}
# Формируем URL
url = ENDPOINT + 'messages.send'
# Отправляем запрос на API
response = requests.get(url, params=params)
# Возвращаем распаршенный json
return response.json()
// peer_id мы забираем из данных
// -> object -> message -> peer_id
async function sendMessage(peerId, message) {
// Собираем данные
const params = {
peer_id: peerId,
message: message,
access_token: ACCESS_TOKEN,
v: VERSION,
random_id: Math.floor(Math.random() * 1000000) + 1, // обязательно для отправки
};
// Формируем URL
const url = ENDPOINT + "messages.send?" + new URLSearchParams(params).toString();
// Отправляем запрос на API
const response = await fetch(url);
// Возвращаем распаршенный json
return response.json();
}
public static async Task<VkResponse?> SendMessage(long peerId, string message)
{
// Собираем данные
var parameters = new Dictionary<string, string>
{
["peer_id"] = peerId.ToString(),
["message"] = message,
["access_token"] = ACCESS_TOKEN,
["v"] = VERSION,
["random_id"] = new Random().Next(1, 1000000).ToString() // обязательно для отправки
};
// Формируем URL
string url = ENDPOINT + "messages.send?" + string.Join("&",
parameters.Select(p => $"{HttpUtility.UrlEncode(p.Key)}={HttpUtility.UrlEncode(p.Value)}"));
// Отправляем запрос на API
HttpClient httpClient = new HttpClient();
HttpResponseMessage response = await httpClient.GetAsync(url);
// Возвращаем распаршенный json
return await response.Content.ReadFromJsonAsync<VkResponse>();
}
// И так как это C#, нам понадобится куча классов для типизации с промаппленными полями:
public class VkRequest
{
public string? Type { get; set; }
[JsonPropertyName("event_id")]
public string? EventId { get; set; }
[JsonPropertyName("group_id")]
public long? GroupId { get; set; }
[JsonPropertyName("v")]
public string? V { get; set; }
[JsonPropertyName("object")]
public VkRequestObject? Object { get; set; }
}
public class VkRequestObject
{
[JsonPropertyName("client_info")]
public VkRequestObjectClientInfo? ClientInfo { get; set; }
[JsonPropertyName("message")]
public VkRequestObjectMessage? Message { get; set; }
}
public class VkRequestObjectClientInfo
{
[JsonPropertyName("button_actions")]
public string[]? ButtonActions { get; set; }
[JsonPropertyName("keyboard")]
public bool Keyboard { get; set; }
[JsonPropertyName("inline_keyboard")]
public bool InlineKeyboard { get; set; }
[JsonPropertyName("carousel")]
public bool Carousel { get; set; }
[JsonPropertyName("lang_id")]
public long LangId { get; set; }
}
public class VkRequestObjectMessage
{
[JsonPropertyName("date")]
public long Date { get; set; }
[JsonPropertyName("from_id")]
public long FromId { get; set; }
[JsonPropertyName("id")]
public long Id { get; set; }
[JsonPropertyName("version")]
public long MessageVersion { get; set; }
[JsonPropertyName("out")]
public long Outgoing { get; set; }
[JsonPropertyName("fwd_messages")]
public string[]? FwdMessages { get; set; }
[JsonPropertyName("important")]
public bool Important { get; set; }
[JsonPropertyName("is_hidden")]
public bool IsHidden { get; set; }
[JsonPropertyName("attachments")]
public string[]? Attachments { get; set; }
[JsonPropertyName("conversation_message_id")]
public long ConversationMessageId { get; set; }
[JsonPropertyName("text")]
public string? Text { get; set; }
[JsonPropertyName("peer_id")]
public long PeerId { get; set; }
[JsonPropertyName("random_id")]
public long RandomId { get; set; }
}
public class VkResponse
{
[JsonPropertyName("response")]
public long Response { get; set; }
}
Вызываем функцию отправки
Теперь, когда у нас есть готовая функция sendMessage, которая умеет отправлять ответы через VK API, осталось лишь применить её в нужном месте —
внутри обработчика события message_new.
Вспомним логику: VK присылает POST-запрос с JSON, в котором поле type указывает тип события. Если
это message_new, мы должны:
Извлечь peer_id — идентификатор чата или пользователя, от которого пришло сообщение (он лежит в object.message.peer_id).
Вызвать нашу функцию sendMessage, передав ей этот peer_id и текст ответа.
-
Вернуть VK правильный HTTP-ответ: как и обозначили в первой части, для события confirmation — только токен, для всех остальных — "ok" с кодом 200.
Важно не перепутать: функция отправки сообщения — это отдельный запрос к API VK, а ответ на callback-запрос должен быть быстрым и всегда возвращать
"ok" (кроме confirmation). Это гарантирует, что VK не будет повторно присылать одно и то же событие.
Теперь переходим к полным примерам
обработчиков, в которые уже встроен вызов sendMessage:
switch ($data['type']) { case 'confirmation': echo CONFIRMATION_TOKEN; exit();// Добавляем обработку нового сообщения case 'message_new': sendMessage($data['object']['message']['peer_id'], 'Сообщение с PHP сервера'); http_response_code(200); exit('ok');default: http_response_code(200); exit('ok'); }
@app.route('/', methods=['POST']) def callback(): data = request.get_json(force=True, silent=True) if data is None or 'type' not in data: return 'ok', 200 if data['type'] == 'confirmation': return CONFIRMATION_TOKEN, 200# Добавляем обработку нового сообщения if data['type'] == 'message_new': send_message(data['object']['message']['peer_id'], 'Сообщение с Python сервера') return 'ok', 200return 'ok', 200
app.post("/", (req, res) => { const data = req.body; if (!data || !data.type) { return res.status(200).send("ok"); } switch (data.type) { case "confirmation": return res.send(CONFIRMATION_TOKEN);// Добавляем обработку нового сообщения case "message_new": sendMessage(data.object.message["peer_id"], "Сообщение с JS сервера"); return res.status(200).send("ok");default: return res.status(200).send("ok"); } });
app.MapPost("/", async (HttpContext context) => { VkRequest? data = null; try { data = await context.Request.ReadFromJsonAsync<VkResponse?>(); } catch (Exception) { return Results.Ok("ok"); } if (data is null) return Results.Ok("ok"); switch (data.Type) { case "confirmation": return Results.Ok(CONFIRMATION_TOKEN);// Добавляем обработку нового сообщения case "message_new": await SendMessage(data.Object.Message.PeerId, "Сообщение с C# сервера"); return Results.Ok("ok");default: return Results.Ok("ok"); } });
База готова, теперь бот получает тексты сообщений в чатах и может на них ответить:
Реакция бота на разных серверах на сообщение в чате
Заключение: фундамент заложен — переходим к логике
В этой части мы реализовали ключевую функцию любого диалогового бота - отправку ответных сообщений. Теперь ваше приложение корректно обрабатывает
входящие события message_new, извлекает идентификатор диалога и отправляет через API VK осмысленный ответ.
В следующей, третьей части мы
сосредоточимся на построении диалоговой логики и повышении безопасности взаимодействия с VK API. Оставайтесь с нами, чтобы превратить базовую
заготовку в полноценный инструмент для бизнеса.