+7(921)-851-18-76 Иконка телефона Запросить звонок
Опубликовано:

Спасаем бизнес: Создаём 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, мы должны:

  1. Извлечь peer_id — идентификатор чата или пользователя, от которого пришло сообщение (он лежит в object.message.peer_id).

  2. Вызвать нашу функцию sendMessage, передав ей этот peer_id и текст ответа.

  3. Вернуть 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', 200
        
        
    return '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. Оставайтесь с нами, чтобы превратить базовую заготовку в полноценный инструмент для бизнеса.

Получить коммерческое предложение

Оставьте заявку, и мы свяжемся с вами, чтобы обсудить ваши потребности и предоставить коммерческое предложение по разработке сайта. Наша команда поможет вам создать эффективный и привлекательный веб-сайт, адаптированный к вашему бизнесу.

Я не робот

Нажмите чтобы продолжить

*Согласие обязательно
Я ознакомлен с политикой конфиденциальности и согласен на обработку персональных данных
Оставьтезаявку
Кнопка закрытия формы
Стрелка вверх
Все получилось Заявкауспешно
отправлена
Спасибо, что доверяете нам! Наш специалист уже получил вашу заявку
и скоро свяжется с вами.
Кнопка закрытия формы