logicSchema

После нескольких писем, отправленных с сайта себе на почту, понял что это достаточно неудобно, не современно (возможно), как минимум не прикольно. Задался целью отказаться от использования smtp для формы в пользу api Телеграма.

Так как мое приложение работает на ноде, подумал почему бы не прокачать форму. Общая логика до боли проста. При отправке формы делается запрос к api приложения, где хранится токен бота, обрабатываются данные и далее делается запрос к api телеграмма, который отправляет сообщение в чат.

Но давайте обо всем по порядку.

Для начала, естественно, необходимо создать бота, который будет получать данные из формы и отправлять вам. По сути, он является всего лишь посредником между вами и api телеграмма.

Итак, стучимся к родителю всех ботов, а именно к @BotFather и просим его создать нам одного (вводим /newbot). Вводим имя, ник и получаем токен бота. Как раз он нам и нужен. Заметьте, что ник бота должен быть <your>_bot или <Your>Bot.

@BotFather

Создали, хорошо, но надо оживить его. Ищем его в поиске по нику и пишем /start. Все, теперь можем обращаться к нему через api.

@BotFather

Далее необходимо создать группу, куда бот будет кидать сообщения, не забываем добавить его в чат.

createChat


addMembers


Вводим /join @ник_бота в созданном чате, потому что бывает, что не добавляется в логи запись о приглашении бота в группу.

Идем в браузер и в адресной строке вводим:

https://api.telegram.org/botXXXXXXXXXXXXXXXXXXXXXXX/getUpdates

где XXXXXXXXXXXXXXXXXXXXXXX — токен бота, который любезно дал вам @BotFather.

Если все прошло успешно, то получим примерно такую простыню из букв, где необходимо найти объект «chat»:{«id:XXXXXXXXXX…}. Обычно id группового чата начинается с минуса.

getUpdates

Отлично, получили токен бота и id чата, куда будут приходить сообщения.
Теперь давайте приступим к приложению.

Front


Начнем сначала с фронта.

Я использовал для работы Node обёртку Express, который в свою очередь умеет рендерить файлы различных шаблонизаторов. Решил воспользоваться Pug. Он достаточно прост в освоении, поэтому если впервые сталкиваетесь с ним, труда познакомится с ним не возникнет. Для примера не стал использовать сборщики, поэтому скрипты подключаются по старинке.
Структура приложения сгенерирована с помощью Express Generator.

Разметка формы


views/layout.pug:

doctype html
html
  head
    title= title
    link(rel="stylesheet", href="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.1.3/sweetalert.min.css")
    link(rel='stylesheet', href='/stylesheets/style.css')
  body
    block content

views/index.pug:

extends layout

block content
  .wrapper
    .wrapper__bg
        img.wrapper__bg-img(src='/images/bg.jpg' alt='bg')
    form(action="/telegram", method="post" class="form" id='telegramForm' enctype="application/x-www-form-urlencoded")
      .form__container
          .form__blur
          .form__title
              .form__title-line
              h3.form__title-text Связаться со мной
              .form__title-line
          .form__inputs
              input(type="text" name='name' placeholder="Имя" class="form__input" required)
              input(type="email" name='email' placeholder="Email" class="form__input" required)
              textarea(name="text" placeholder="Ваше сообщение" class="form__input form__message" required)
      .form__buttons
          input(type="submit" class="form__submit" value="Отправить")
          .form__clean Очистить
  
  script(src="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.1.3/sweetalert.min.js")
  script(src="/javascripts/app.js")

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

Добавляем стили и вот такая форма у меня получилась.

form

Сообщение будет отправляться без перезагрузки страницы, поэтому вешаем обработчик на форму, собираем данные, преобразуем в json и отправляем их асинхронно себе в api + выводим сообщение о статусе запроса.

public/javascripts/app.js:

const formId = 'telegramForm'
const form = document.getElementById(formId)
//функция для захвата данных из тегов формы и синтеза JSON-обьекта 
function toJSONString(form) {
  var obj = {}
  var elements = form.querySelectorAll('input, select, textarea')
  for (var i = 0; i < elements.length; ++i) {
    var element = elements[i]
    var name = element.name
    var value = element.value
    if (name) {
      obj[ name ] = value
    }
  }
  return JSON.stringify(obj)
}
if (form) {
  form.addEventListener('submit', event => {
    event.preventDefault()
    //получаем данные из формы
    const json = toJSONString(form)
    //создаем соединение
    const formReq = new XMLHttpRequest()
    formReq.open('POST', '/telegram', true)
    ///////////////////////////////////
    /////////////SweetAlert//////////
    ///////////////////////////////////
    //обрабатываем ответ сервера
    formReq.onload = function(oEvent) {
      if (formReq.status === 200) {
        swal({
          title: 'Успешно отправлено!',
          icon: 'success',
          timer: 2000
        })
        document.querySelector('.sa-success').style.display = 'block'
        document.querySelector('.sa-button-container').style.opacity = '0'
      }
      if (formReq.status !== 200) {
        swal({
          title: 'Произошла ошибка!',
          icon: 'error',
          timer: 2000
        })
        document.querySelector('.sa-error').style.display = 'block'
        document.querySelector('.sa-button-container').style.opacity = '0'
      }
    }
    ////////////////////////////
    ////////////////////////////
    formReq.setRequestHeader('Content-Type', 'application/json')
    //отправляем
    formReq.send(json)
  })
}

Back


На стороне сервера для начала нужно отловить запрос со стороны клиента, для этого в роутере пишем:

routes/index.js:

//Я вынес логику обработки данных в отдельный файл
const ctrlTelegram = require('../api/telegramMsg');
router.post('/telegram', ctrlTelegram.sendMsg);

api/telegramMsg.js:

module.exports.sendMsg = (req, res) => {
  //токен и id чата берутся из config.json
  const config = require('../config/config.json');
  let http = require('request')
  let reqBody = req.body
  //каждый элемент обьекта запихиваем в массив
  let fields = [
    '<b>Name</b>: ' + reqBody.name,
    '<b>Email</b>: ' + reqBody.email,
    reqBody.text
  ]
  let msg = ''
  //проходимся по массиву и склеиваем все в одну строку
  fields.forEach(field => {
    msg += field + '\n'
  });
  //кодируем результат в текст, понятный адресной строке
  msg = encodeURI(msg)
  //делаем запрос
  http.post(`https://api.telegram.org/bot${config.telegram.token}/sendMessage?chat_id=${config.telegram.chat}&parse_mode=html&text=${msg}`, function (error, response, body) {  
    //не забываем обработать ответ
    console.log('error:', error); 
    console.log('statusCode:', response && response.statusCode); 
    console.log('body:', body); 
    if(response.statusCode===200){
      res.status(200).json({status: 'ok', message: 'Успешно отправлено!'});
    }
    if(response.statusCode!==200){
      res.status(400).json({status: 'error', message: 'Произошла ошибка!'});
    }
  });

}

Для упрощения процесса запроса установлен пакет 'request'.

npm i request

?config/config.json:

{
  "telegram": {
    "token": "bot_token",
    "chat": "chat_id"
  }
}

Итак, что же здесь происходит?


В запросе мы передали json, поэтому на стороне сервера с данными можем работать как с обычным объектом.

Для удобства каждое значение объекта запихиваем в массив.
API телеграмма позволяет передать данные посредством текста в адресной строке, поэтому проходим по массиву и создаём длинную строку. Чтобы можно было передать HTML теги, необходимо закодировать строку в универсальный идентификатор (метод encodeURI()), чтобы не вылезала ошибка.

Теперь можно наконец отправить это всё на сервер телеграмма. Делаем запрос (нажимаем кнопку 'Отправить') и вуаля, сообщение отправлено. Не забываем обработать ответ, а то мало ли что.

После всех манипуляций, ответ приходит на фронт и уведомляет прошло все норм или не очень.
Из-за того, что для примера не использовал сборщик, а библиотечка всплывашки рассчитана на модульную сборку, пришлось немного поколхозить при ее вызове на фронте.

formSent

message

Если посмотреть в логи приложения на сервере, можно увидеть примерно следующее:

image

Поздравляю! Теперь вы знаете как отправлять сообщения с вашего сайта в Telegram.

Я описал только общую концепцию данного процесса, поэтому настоятельно рекомендую ознакомится с исходным кодом данного примера.

Комментарии (14)


  1. aavezel
    05.02.2018 16:28

    1. Лучше использовать приватный канал для таких дел
    2. Зачем прокси через node.js? В простейшем случае можно писать напрямую с сайта в api.telegram.org, «благо» Access-Control-Allow-Origin:*.


    1. RoMan_111 Автор
      05.02.2018 17:04

      1. Наверное лучше использовать канал, да. Я хотел организовать все быстро и чтобы работало.
      2. Мое приложение работает на ноде, поэтому решил почему бы не свалить этот процесс на нее.

      P.S.
      Практика и опыт в решении разных задач — хорошо)


    1. Miju
      05.02.2018 17:09
      +1

      2. Зачем прокси через node.js? В простейшем случае можно писать напрямую с сайта в api.telegram.org, «благо» Access-Control-Allow-Origin:*.

      Злобный хацкер спалил в консоли браузера токен и начнёт использовать бота в своих хацкеравских целях.


      1. RoMan_111 Автор
        05.02.2018 17:13

        Именно \O/


      1. aavezel
        07.02.2018 13:58

        Каких? Послать рекламу казино в канал для 5 человек? Или вы еще для чего то будете использовать бота? Завести 2х ботов не позволяет религия?


        1. RoMan_111 Автор
          07.02.2018 14:37

          Злобный хацкер например может всякие незаконные анонимные действия совершать(продажа оружия, веществ, террор, поддельные документы), а бот зарегистрирован на тебя и если телега прогнется все-таки под государство, то анонимности конец.
          deepweb
          Возможно можно сформулировать мысль иначе, но смысл думаю понятен


    1. max_rip
      05.02.2018 17:12
      +1

      2, т.е. вы предлагаете использовать токен от бота в фронте?


    1. Sergey78
      06.02.2018 15:03
      +1

      А в чем преимущество канала?
      В обычном чате с ботом можно добавить какую-то функциональность к сообщениям. Ответить там, или переслать или сохранить где-то. Зачем в данном случае нужен канал?


      1. aavezel
        07.02.2018 14:08

        Канал тоже позволяет сохранить или переслать куда-то. Притом имя бота не будет светиться в канале в отличии от чата, т.е. пересылать с закрытого канала более секьюрно.
        Сама суть в том, что на канал можно не мьютить, в отличии от чата, в котором постоянно будут возникать флуды не по делу, что в конце концов заставит вас замьютить чат и пропустить важное сообщение.
        Ну и токен бота, который постит в канал можно распечатать хоть на заборе, читать из канала чужие сообщения он не сможет, посмотреть кто подписан на канал — тоже.


  1. sdwvit
    05.02.2018 16:58

    Отправить и очистить следует сменить местами и разукрасить в разный цвет. Я, смотря на гифку, мысленно представил как каждый раз жму очистить вместо отправить X)
    Проверить как правильно делается можно открыв консоль хрома и ввести confirm(1)


  1. ExploZeR
    06.02.2018 01:22

    Всё намного проще. На самом деле, сообщение можно отправить прямо из JS на страничке. Не нужен node.js, не нужны запросы на сервер. Просто 20 коротких строк JS.
    Понятно, что светить токен бота и чат айди — не кошерно. Однако, знать это всё равно неплохо.
    При желании, функцию sendMsg (из этого скрипта) для отправки JSON данных методом POST по такой же схеме можно реализовать на php. Например, через curl.

    const TOKEN = xxx; // токен от BotFather
    const CHAT_ID = yyy; // chat_id для телеграм
    
    var form = document.querySelector('.form'); // находим в DOM нашу лид-форму
    form.addEventListener("submit", function (e) { // прослушиваем форму
        e.preventDefault(); // перехватываем стандартный ответ
        data = new FormData(this); // вместо serialize на jQuery
        sendMsg(data); // передаём данные из формы на отправку
    })
    
    function sendMsg(data) {
        var url = 'https://api.telegram.org/bot' + TOKEN + '/sendMessage'; // токен бота
        var body = JSON.stringify({ // склеиваем объект в JSON строку
            chat_id: CHAT_ID,
            parse_mode: 'Markdown', // разметка сообщений вкл (чтобы использовать *жирный текст*)
            text: '*Новый лид*\n' + data.get("title") + '\n\n*Имя:* ' + data.get("name") + '\n*Телефон:* ' + data.get("phone") + '\n*Откуда:* [' + window.location.href + '](' + window.location.href + ')'
        });
        var xhr = new XMLHttpRequest(); // инициализируем AJAX запрос
        xhr.open('POST', url, true); // отправляем наше сообщение методом POST на сервак телеги
        xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8'); // на всякий случай, оповестим телеграм, что отправили JSON
        xhr.send(body);
    }
    

    Разметка формы:
    <form method="post" class="form">
        <input type="hidden" name="title" value="Есть вопросы?" />
        <input type="text" name="phone" placeholder="Телефон (+7 ...)"/>
        <input type="text" name="name" placeholder="Имя" />
        <button>БЕСПЛАТНАЯ КОНСУЛЬТАЦИЯ</button>
    </form>
    


    1. your_eyes_lie
      06.02.2018 08:57

      Отдавать токен бота всем — наверное, не лучшая идея


  1. vlx
    06.02.2018 12:14

    можно еще посмотреть в сторону
    npm install node-telegram-bot-api

    github.com/yagop/node-telegram-bot-api


  1. Methos
    06.02.2018 14:10

    Угу, ничего сложного.
    Делал чат для сайта с использованием телеграма.