image

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

Мессенджеры, это отличная альтернатива, и это путь, который я выбрал. Telegram предоставляет один самых простых способов создания ботов (если не самый простой). На его месте может быть что угодно — другие мессенджеры, телефонные звонки (Amazon Connect) или Яндекс Диалоги.

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

Шаг 1-й: бот в Telegram, проще быть не может


Отправляем сообщение @BotFather с командой /newbot, задаем имя и всё! Мы получили токен. Держим его при себе.

Дополнительно нам понадобится chat_id — идентификация чата, на который будут отправлены сообщения. Узнать свой chat_id немного сложнее, чем получить токен, к счастью, существует бот под именем @my_id_bot, который раскроет вам ваш chat_id.

В итоге, мы имеем токен, который мы будем использовать для «разговора» с нашим ботом, а chat_id будет использован для отправки сообщения лично вам. Теперь можете отправить нашему боту команду /start и можем идти дальше.

Telegram показан как пример и из-за простоты использования ботов, на его месте может быть другой мессенджер или какой-либо другой триггер.

Шаг 2-й: Lambda как посредник


Один из вариантов настройки Telegram, это внедрить весь код запроса и «разговора» с API Telegram в сам код приложения React Native — хороший вариант для теста. В моем случае уже был настроен API с несколькими активными функциями Lambda, поэтому мой вариант включает Lambda как слой-посредник между приложением и API Telegram. Код и инструкции ниже могут быть использованы в самом проекте React Native.

Я использовал фреймворк Serverless для создания новой функции с параметрами ниже (формат yaml).

sendTelegramMessage:
  description: used for contact form (${self:custom.stage})
  handler: lib/additional/sendMessageTelegram.handler
  environment: ${file(env_contact.yml):${self:custom.stage}}
  events:
    - http:
      path: contact
      method: POST
      private: false
  package:
    include:
      - lib/additional/sendMessageTelegram.js

Помним, что токен и chat_id должны быть строго скрыты. В данном случае я использую файл переменных среды, который будет использован в Lambda. Как можно заметить из строк выше, Serverless настроит API Gateway для запросов POST. Также заметьте, что в данном случае API открыт для всех без каких-либо ограничений.

А вот часть кода Lambda, которая примет наш запрос. Наш фронт-энд всего-лишь соберет информацию — электронную почту и сообщение, и отравит запрос в нашу функцию, которая обработает запрос в API Telegram.

exports.handler = async (event) => {
  if (!event.body) {
    return createResponse(400, "запрос пуст");
  }

  const data = JSON.parse(event.body);

  if (!data.email || !data.message) {
    return createResponse(400, "поля 'email' и 'message' обязательны");
  }

  try {
    const telegram = getTelegramConfig();

    await fetch(`${telegram.url}/bot${telegram.token}/sendMessage?${telegram.params(data.email, data.message)}`)

    return createResponse(200, "сообщение отправлено");
  } catch (e) {
    return createResponse(500, e.message);
  }
};

Две функции-помощники, getTelegramConfig для создания URL запроса и createResponse для генерации стандартного ответа с кодом статуса и сообщением.

const getTelegramConfig = () => {
  const token = process.env.TELEGRAM_BOT_TOKEN;
  const chatId = process.env.TELEGRAM_MAIN_CONTACT;

  if (!token || !chatId) {
    throw new Error("Бот не настроен");
  }

  return {
    url: "https://api.telegram.org",
    token,
    params: (email, message) => qp.encode({
      chat_id: chatId,
      parse_mode: "Markdown",
      text: "Сообщение от " + email + ": ```" + message + "```"
    })
  };
};

const createResponse = (statusCode, message) => ({
  statusCode,
  body: JSON.stringify({ message }, null, 2)
});

qp.encode используется для конвертации объекта в строку вроде ?param1=value&param2=value.

Теперь, если мы сделаем запрос POST c содержанием ниже, мы получим наше сообщение в Telegram.

{
    "email": "privet@gmail.com",
    "message": "Случайный текст для хабр. Структурализм абстрактен. Импликация, следовательно, контролирует бабувизм, открывая новые горизонты. Апостериори, гравитационный парадокс амбивалентно понимает под собой интеллигибельный знак. Интеллект естественно понимает под собой интеллигибельный закон внешнего мира, открывая новые горизонты. Смысл жизни, структурализм абстрактен. Структурализм абстрактен. Отсюда естественно следует, что автоматизация дискредитирует предмет деятельности. Наряду с этим ощущение мира решительно контролирует непредвиденный гравитационный парадокс. Созерцание непредсказуемо. Согласно мнению известных философов, дедуктивный метод естественно"
}

image

Шаг 3-й: «Свяжись с нами» в приложении React Native


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

Часть JSX. Button — это свой компонент с дополнительными параметрами неактивного (disabled) статуса и статуса загрузки (loading). Стиль также по вашему усмотрению.

<View style={styles.container}>
  <TextInput
    value={sendersEmail}
    keyboardType="email-address"
    autoCapitalize="none"
    autoCompleteType="email"
    style={styles.input}
    onChangeText={onEmailChange}
    selectionColor="#555"
    placeholder="Ваша почта"
    placeholderTextColor="#CCC"
    clearButtonMode="always"
    returnKeyType="done"
    disableFullscreenUI
  />
  <TextInput
    value={message}
    style={{ ...styles.input, ...styles.messageInput }}
    onChangeText={onMessageChange}
    selectionColor="#555"
    placeholder="Ваше сообщение..."
    placeholderTextColor="#CCC"
    clearButtonMode="always"
    returnKeyType="done"
    multiline
    textAlignVertical="top"
    disableFullscreenUI
  />
  <Button
    title="Отправить"
    loading={loading}
    disabled={disabled}
    onPress={onSendPress}
  />
</View>

Часть JavaScript.

const [ sendersEmail, setSendersEmail ] = useState("");
  const [ message, setMessage ] = useState("");

  const [ loading, setLoading ] = useState(false);

  const onEmailChange = (value) => {
    setSendersEmail(value);
  };

  const onMessageChange = (value) => {
    setMessage(value);
  };

  const onSendPress = () => {
    setLoading(true);

    const url = "https://api.example.com/contact";

    fetch(url, {
      method: "POST",
      body: JSON.stringify({
        email: sendersEmail,
        message
      })
    })
    .then(() => {
      setLoading(false);
    })
    .catch(() => {
      setLoading(false);

      Alert.alert("Извините", "Не удалось отправить ваше сообщение.");
    });
  };

  const isValidEmail = () => {
    const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

    return emailRegex.test(sendersEmail.toLowerCase());
  };


  const disabled = message.length < 10 || !isValidEmail();

Кнопка «Отправить» будет неактивна пока пользователь не введет правильный адрес электронной почты и, как минимум, 10-знаковое сообщение. Помним, что в данном примере API открыт.

image

Вот так.

Чего не хватает


  • Я не затрагивал часть API, это отдельная тема, об этом можно найти много других статей. Также рекомендую ознакомиться с фреймворком Serverless.
  • В этом примере мы получаем сообщение в Telegram, но отвечать придется на электронную почту. Здесь много разных вариантов и это только пример.

Полезные ссылки


На английском
https://core.telegram.org/bots/api#sendmessage
https://core.telegram.org/bots/api#markdown-style
https://facebook.github.io/react-native/docs/textinput