Пример календаря в Телеграм Бот на NodeJS и PHP

Отвечая на вопросы о выводе календаря в бот, предлагаю посмотреть примеры на PHP и нативном NodeJS

Были вопросы в бот обратной связи по теме календаря, приведу пример для webHook на PHP и на NodeJS

Пример на PHP

<?php
// токен бота
$bot_token = "Токен вашего бота";

// получаем данные от телеграм
$data = json_decode(file_get_contents("php://input"));

/** Запрос в Телеграм
 * @param $method
 * @param array $fields
 * @return mixed
 */
$query = function ($method, $fields = []) use ($bot_token) {
    // откроем соединение
    $ch = curl_init("https://api.telegram.org/bot" . $bot_token . "/" . $method);
    // определим опции
    curl_setopt_array($ch, [
        CURLOPT_POST => count($fields),
        CURLOPT_POSTFIELDS => http_build_query($fields),
        CURLOPT_SSL_VERIFYPEER => 0,
        CURLOPT_RETURNTRANSFER => 1,
        CURLOPT_TIMEOUT => 10
    ]);
    // спарсим в объект результат запроса
    $result = json_decode(curl_exec($ch), true);
    // закроем соединение
    curl_close($ch);
    // вернем результат
    return $result;
};

/**
 * Выводим уведомление
 * @param $cbq_id
 * @param $text
 */
$notice = function ($cbq_id, $text = null) use ($query) {
    // определим данные
    $data = [
        "callback_query_id" => $cbq_id,
        "alert" => false
    ];
    // если есть текст то добавим
    if (!is_null($text)) {
        $data['text'] = $text;
    }
    // отправим в Телеграм
    $query("answerCallbackQuery", $data);
};

/** Переопределим номер дня недели
 * @param $date DateTime
 * @return int
 */
$getNumDayOfWeek = function ($date) {
    // получим день недели
    $day = $date->format("w");
    // вернем на 1 меньше [0 - вск]
    return ($day == 0) ? 6 : $day - 1;
};

/** Получим массив дней разбитых по неделям
 * @param $month
 * @param $year
 * @return array
 * @throws Exception
 */
$getDays = function ($month, $year) use ($getNumDayOfWeek) {
    // создаем дату на начало месяца
    $date = new DateTime($year . "-" . $month . "-01");
    // массив дней
    $days = [];
    // начало массива
    $line = 0;
    // заполним начало если нужно пустыми значениями
    for ($i = 0; $i < $getNumDayOfWeek($date); $i++) {
        $days[$line][] = "-";
    }
    // перебираем дни пока месяц совпадает с переданным
    while ($date->format("m") == $month) {
        // добавляем в строку дни
        $days[$line][] = $date->format("d");
        // вс, последний день - перевод строки
        if ($getNumDayOfWeek($date) % 7 == 6) {
            // добавляем новую строку
            $line += 1;
        }
        // переходим на следующий день
        $date->modify('+1 day');
    }
    // дозаполняем последнюю строку пустыми значениями
    if ($getNumDayOfWeek($date) != 0) {
        for ($i = $getNumDayOfWeek($date); $i < 7; $i++) {
            $days[$line][] = "-";
        }
    }
    // вернем массив дней
    return $days;
};

/** Выводим календарь
 * @param $month
 * @param $year
 * @param $chat_id
 * @param null $cbq_id
 * @param null $message_id
 */
$viewCal = function ($month, $year, $chat_id, $cbq_id = null, $message_id = null) use ($getDays, $notice, $query) {
    // получаем массив дней месяца
    $dayLines = $getDays($month, $year);
    // определим переданную дату
    $current = new DateTime($year . "-" . $month . "-01");
    // определим параметры переданного месяца
    $current_info = $current->format("m-Y");
    // определим кнопки
    $buttons = [];
    // первый ряд кнопок это навигация календаря
    $buttons[] = [
        [
            "text" => "<<<",
            "callback_data" => "cal_" . date("m_Y", strtotime('-1 month', $current->getTimestamp()))
        ],
        [
            "text" => $current_info,
            "callback_data" => "info_" . $current_info
        ],
        [
            "text" => ">>>",
            "callback_data" => "cal_" . date("m_Y", strtotime('+1 month', $current->getTimestamp()))
        ]
    ];
    // выводим дни месяца
    foreach ($dayLines as $line => $days) {
        // переберем линию
        foreach ($days as $day) {
            // добавим кнопку в линию
            $buttons[$line + 1][] = [
                // выведем день
                "text" => $day,
                // поределим параметры
                "callback_data" => $day > 0
                    // если это день
                    ? "info_" . $day . "-" . $current_info
                    // другое значение
                    : "inline"
            ];
        }
    }
    // готовим данные
    $data = [
        "chat_id" => $chat_id,
        "text" => "<b>Календарь:</b>\n\n" . $current->format("F Y"),
        "parse_mode" => "html",
        "reply_markup" => json_encode(['inline_keyboard' => $buttons])
    ];
    // проверим как отправлять: как новое или замена содержимого
    if (!is_null($message_id)) {
        // гасим запрос
        $notice($cbq_id);
        // добавим message_id
        $data["message_id"] = $message_id;
        // направим в Телеграм на изменение сообщения
        $query("editMessageText", $data);
    } else {
        // направим сообщение в чат
        $query("sendMessage", $data);
    }
};

/**
 * Простой роутер бота
 */
if (isset($data->message)) {
    // получим id чата
    $chat_id = $data->message->from->id;
    // проверим что это текстовое сообщение
    if (isset($data->message->text)) {
        // проверим что это старт бота
        if ($data->message->text == "/start") {
            // получим инфо о текущей дете
            $now_date = getdate();
            // запустим вывод календаря текущего месяца
            $viewCal($now_date['mon'], $now_date['year'], $chat_id);
        }
    }
// если это нажатие по кнопке
} elseif (isset($data->callback_query)) {
    // получим id чата
    $chat_id = $data->callback_query->from->id;
    // получим callBackQuery_id
    $cbq_id = $data->callback_query->id;
    // получим переданное значение в кнопке
    $c_data = $data->callback_query->data;
    // спарсим значения
    $params = explode("_", $c_data);
    // если это переключение между месяцами
    if ($params[0] == "cal") {
        // выведем календарь на экран по переданным параметрам
        $viewCal($params[1], $params[2], $chat_id, $cbq_id, $data->callback_query->message->message_id);
    }
    // если это нажатие по кнопке для информации
    elseif ($params[0] == "info") {
        // выведем информацию
        $notice($cbq_id, $params[1]);
    }
    // если это другие кнопки
    else {
        // заглушим просто запрос
        $notice($cbq_id, "This is notice for bot");
    }
}

* * *

Пример на NodeJS

package.json
{
  "name": "Calendar",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "iMakeBots.ru",
  "license": "",
  "dependencies": {
    "request": "^2.88.0",
  }
}
index.js
const request = require('request');
const http = require('http');

let config = {
  token: "Токен вашего бота"
};

/**
 * Создадим сервер на порту 8080
 * @param request_
 * @param response_
 */
http.createServer(function (request_, response_) {
  // ответим 200 кодом
  response_.writeHead(200);
  // определим кодировку
  request_.setEncoding('utf8');
  // объявим
  let body = '';
  // обработаем
  request_
    // соберем body
    .on('data', (chunk) => {
      body += chunk;
    })
    // после
    .on('end', () => {
      // запустим роутер
      route(JSON.parse(body));
    });
  // закроем
  response_.end();
}).listen(8080);

/**
 * Простой роутер
 * @param data
 */
const route = function (data) {
  // если это сообщение
  if ('message' in data) {
    // получим id чата
    let chat_id = data.message.from.id;
    // проверим что это текстовое сообщение
    if ('text' in data.message) {
      // проверим что это старт бота
      if (data.message.text === "/start") {
        // запустим вывод календаря текущего месяца
        viewCal((new Date()).getFullYear(), (new Date()).getMonth(), chat_id);
      }
    }
  }
  // если это нажатие по кнопке
  else if ('callback_query' in data) {
    // получим id чата
    let chat_id = data.callback_query.from.id;
    // получим callBackQuery_id
    let cbq_id = data.callback_query.id;
    // получим переданное значение в кнопке
    let c_data = data.callback_query.data;
    // спарсим значения
    let params = c_data.split("_");
    // если это переключение между месяцами
    if (params[0] === "cal") {
      // выведем календарь на экран по переданным параметрам
      viewCal(params[1], params[2], chat_id, cbq_id, data.callback_query.message.message_id);
    }
    // если это нажатие по кнопке для информации
    else if (params[0] === "info") {
      // выведем информацию
      notice(cbq_id, params[1]);
    } else {
      // заглушим просто запрос
      notice(cbq_id, "This is notice for bot");
    }
  }
};

/**
 * Выведем календарь
 * @param month
 * @param year
 * @param chat_id
 * @param cbq_id
 * @param message_id
 */
function viewCal(year, month, chat_id, cbq_id = null, message_id = null) {
  // получаем массив дней месяца
  let dayLines = getDays(year, month);
  // определим переданную дату
  let currentMonthDate = new Date(+year, +month);
  // дата предыдущего месяца
  let prevMonthDate = (new Date((new Date(currentMonthDate)).setMonth(currentMonthDate.getMonth() - 1)))
  // дата следующего месяца
  let nextMonthDate = (new Date((new Date(currentMonthDate)).setMonth(currentMonthDate.getMonth() + 1)))
  // определим параметры переданного месяца
  let current_info = setBeforeZero(currentMonthDate.getMonth() + 1) + "-" + currentMonthDate.getFullYear();
  // определим кнопки
  let buttons = [];
  // первый ряд кнопок это навигация календаря
  buttons.push([
    {
      text: "<<<",
      callback_data: "cal_" + prevMonthDate.getFullYear() + "_" + prevMonthDate.getMonth()
    },
    {
      text: current_info,
      callback_data: "info_" + current_info
    },
    {
      text: ">>>",
      callback_data: "cal_" + nextMonthDate.getFullYear() + "_" + nextMonthDate.getMonth()
    }
  ]);
  // переберем дни
  dayLines.forEach(function(line) {
    // добавим ряд кнопок
    buttons[buttons.length] = [];
    // переберем линию дней
    line.forEach(function(day) {
      // добавим кнопку
      buttons[buttons.length - 1].push({
        text: day,
        callback_data: day > 0
          ? "info_" + setBeforeZero(day) + "-" + current_info
          : "inline"
      });
    });
  });
  // готовим данные
  let data = {
    chat_id: chat_id,
    text: "<b>Календарь:</b>\n\n" + currentMonthDate.toLocaleString('ru', {month: 'long', year: 'numeric'}),
    parse_mode: "html",
    reply_markup: {inline_keyboard: buttons}
  };
  // проверим как отправлять: как новое или замена содержимого
  if (message_id !== null) {
    // гасим запрос
    notice(cbq_id);
    // добавим message_id
    data.message_id = message_id;
    // направим в Телеграм на изменение сообщения
    query("editMessageText", data);
  } else {
    // направим сообщение в чат
    query("sendMessage", data);
  }
}

/**
 * Получаем массив дней для календаря
 * @param year
 * @param month
 * @returns {Array}
 */
function getDays(year, month) {
  // получаем дату
  let d = new Date(year, month);
  // объявляем массив
  let days = [];
  // добавляем первую строку
  days[days.length] = [];
  // добавляем в первую строку пустые значения
  for (let i = 0; i < getNumDayOfWeek(d); i++) {
    days[days.length - 1].push("-");
  }
  // выходим пока месяц не перешел на другой
  while (d.getMonth() === +month) {
    // добавляем в строку дни
    days[days.length - 1].push(d.getDate());
    // вс, последний день - перевод строки
    if (getNumDayOfWeek(d) % 7 === 6) {
      // добавляем новую строку
      days[days.length] = [];
    }
    // переходим на следующий день
    d.setDate(d.getDate() + 1);
  }
  // дозаполняем последнюю строку пустыми значениями
  if (getNumDayOfWeek(d) !== 0) {
    for (let i = getNumDayOfWeek(d); i < 7; i++) {
      days[days.length - 1].push("-");
    }
  }
  // вернем массив
  return days;
}

/**
 * Переопределим номер дня недели
 * @param date
 * @return int
 */
function getNumDayOfWeek(date) {
  // получим день недели
  let day = date.getDay();
  // вернем на 1 меньше [0 - вск]
  return (day === 0) ? 6 : day - 1;
}

/**
 * Выводим уведомление
 * @param cbq_id
 * @param text
 */
function notice(cbq_id, text = null) {
  // определим данные
  let data = {
    callback_query_id: cbq_id,
    alert: false
  };
  // если есть текст то добавим
  if (text !== null) {
    data.text = text;
  }
  // отправим в Телеграм
  query("answerCallbackQuery", data);
}

/**
 * Отправим запрос в Ттелеграм
 * @param method
 * @param fields
 */
function query(method, fields) {
  request({
    url: 'https://api.telegram.org/bot' + config.token + '/' + method,
    method: 'post',
    headers: {"content-type": "application/json"},
    json: fields
  });
}

/**
 * Добавим ноль вперед
 * @param num
 * @returns {string}
 */
function setBeforeZero(num) {
  return ("0" + (num)).slice(-2);
}
10 комментариев
Авторизуйтесь через Telegram, чтобы оставить комментарий.
Откройте по ссылке или QR бот @iMakeBot, нажмите кнопку Старт/Start.
Следуйте инструкциям бота.

  • Р̲а̲в̲и̲л̲Ь̲ [1 год назад]

    Что делает этот скрипт?

  • iMakeBots [1 год назад → Р̲а̲в̲и̲л̲Ь̲]

    Просто пример вывода календаря в бот как на картинке в статье.

  • Сергей Ларичев(Череповец) [1 год назад]

    а вообще возможно вывести новое сообщение какое мне нужно при нажатии на число? 

  • iMakeBots [1 год назад → Сергей Ларичев(Череповец)]

    Конечно, это же для примера статья. Вы можете добавить любой функционал.

  • Сергей Ларичев(Череповец) [1 год назад → iMakeBots]

    можете помочь пожалуйста, плохо разбираюсь, не понимаю какую строку надо править ((

  • iMakeBots [1 год назад → Сергей Ларичев(Череповец)]

    В роутере замените строчку:

    // ...
        // если это нажатие по кнопке для информации
        elseif ($params[0] == "info") {
            // выведем информацию
            // $notice($cbq_id, $params[1]);
            $query("sendMessage", ["chat_id" => $chat_id, "text" => "Проверка вывода текста"]);
        }
    // ...
  • Сергей Ларичев(Череповец) [1 год назад → iMakeBots]

    Спасибо огромное, очень помогли 

  • Andrew° [1 год назад]

    Спасибо огромное за вариант с php, но он точно рабочий? Ни как не могу понять, в чем дело, создал чистейшего бота, вписал /start, указал его токен в файле и тишина.

  • iMakeBots [1 год назад → Andrew°]

    Webhook настроили?

  • Andrew° [1 год назад → iMakeBots]

    Спасибо, я только начал изучать эту тему, про это даже бы не допёр. Всего самого лучшего в этой жизни тебе