golang

Пишем Telegram-бота на Go(и заставляем его мотивировать нас каждые 30 минут)

  • воскресенье, 6 апреля 2025 г. в 00:00:10
https://habr.com/ru/articles/897802/

Я всегда хотел сделать что-то простое и полезное в Telegram, но чтобы не пришлось постоянно за этим следить. И вот пришла гениальная идея: чтобы быть всегда замотивированным, можно написать бота, который будет (с какой то периодичностью) сам отправлять мотивационные цитаты в канал?

Этот бот берёт случайные цитаты известных людей из интернета, переводит их на русский язык и отправляет в Telegram-канал по расписанию. Например, утром, днём, вечером и ночью. Звучит просто, правда? Но внутри этого проекта есть всё, что нужно для обучения: чистая архитектура, работа с API, планировщик задач и даже деплой на облачную платформу Railway.

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

Что в итоге получилось и код проекта можно найти по этим ссылкам.

1. Архитектура проекта

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

Я выделил четыре основных слоя:

  1. Entities (Сущности):

    • Это базовые структуры данных, например, Quote (цитата). Они описывают, как выглядят данные в программе.

    • Пример: цитата состоит из текста и автора.

  2. Use Cases (Бизнес-логика):

    • Здесь находится "сердце" приложения — логика работы с данными.

    • Например, получение цитаты, её перевод и отправка в Telegram.

  3. Interfaces (Интерфейсы):

    • Это абстракции для взаимодействия с внешними системами. Например, интерфейс для работы с API цитат или Telegram Bot API.

    • Интерфейсы позволяют легко менять реализацию без изменения остального кода.

  4. Adapters (Адаптеры):

    • Реализация интерфейсов. Например, адаптер для ZenQuotes API или Telegram Bot API.

    • Эти модули "общаются" с внешним миром, но скрывают детали от остальной программы.

Такое разделение делает проект гибким, поскольку позволяет заменять отдельные компоненты, например, источник цитат, без переписывания всего кода; тестируемым, так как каждый слой можно проверять независимо от других; и поддерживаемым, потому что чёткая структура помогает быстро разобраться в коде, даже если вы вернулись к проекту спустя время.

2.Создание Telegram-бота

Чтобы создать Telegram-бота, выполните следующие шаги:

Откройте Telegram и найдите бота @BotFather .

Начните диалог с ним и используйте команду /newbot, чтобы создать нового бота.

  1. Следуйте инструкциям:

    • Придумайте имя бота (например, MotivatorBot).

    • Укажите username бота (например, motivator_bot).

  2. После создания бота вы получите токен, который выглядит примерно так:

Этот токен понадобится для взаимодействия с Telegram Bot API.

Важно: Не делитесь токеном с другими людьми и не публикуйте его в открытом доступе!

3.Создание Telegram-канала

  1. Откройте Telegram и нажмите на значок меню (три полоски).

  2. Выберите "Новый канал".

  3. Укажите название канала (например, "Мотиватор") и добавьте описание.

  4. Установите тип канала: "Публичный" или "Приватный".

2. Добавление бота в администраторы

Чтобы бот мог отправлять сообщения в канал, его нужно сделать администратором:

  1. Откройте настройки канала.

  2. Перейдите в раздел "Администраторы".

  3. Нажмите "Добавить администратора".

  4. Найдите вашего бота по имени (например, motivator_bot) и добавьте его.

3. Как получить CHAT_ID

CHAT_ID — это уникальный идентификатор канала, который нужен для отправки сообщений. Вот как его получить:

  1. Добавьте бота @userinfobot в свой Telegram.

  2. Отправьте ему любое сообщение из созданного канала.

  3. Бот ответит информацией о chat_id вашего канала. Для каналов этот ID обычно начинается с -100.

Пример CHAT_ID:

-1001234567890

Важно: Сохраните CHAT_ID — он понадобится для настройки бота.

Итого

Теперь у вас есть всё необходимое для начала разработки:

  • Создан Telegram-бот и получен его токен.

  • Создан канал, добавлен бот в администраторы и получен CHAT_ID.

В следующем разделе мы перейдём к написанию кода и реализации логики бота.

4. Разработка бота

4.1. Структура проекта

Чтобы проект был организован логично и удобен для разработки, я разделил его на несколько папок и файлов. Вот как выглядит структура:

telegram-quotes-bot/
├── go.mod                  # Файл для управления зависимостями Go Modules.
├── go.sum                  # Список контрольных сумм для зависимостей.
├── cmd/                    # Директория с точкой входа в приложение.
│   └── main.go             # Главный файл, где запускается бот.
├── internal/               # Внутренние пакеты проекта.
│   ├── config/             # Конфигурация приложения.
│   │   └── config.go       # Загрузка переменных окружения.
│   ├── adapters/           # Адаптеры для работы с внешними сервисами.
│   │   ├── telegram_adapter.go  # Адаптер для Telegram Bot API.
│   │   ├── zenquotes.go    # Адаптер для ZenQuotes API (получение цитат).
│   │   └── mymemory.go     # Адаптер для MyMemory API (перевод текста).
│   ├── usecases/           # Бизнес-логика приложения.
│   │   ├── fetch_quote.go  # Получение цитаты.
│   │   ├── translate.go    # Перевод текста.
│   │   └── send_quote.go   # Отправка цитаты в Telegram.
│   └── interfaces/         # Интерфейсы для взаимодействия с внешними системами.
│       ├── api.go          # Общие интерфейсы для API.      
└── Dockerfile              # Файл для сборки Docker-образа.
  1. cmd/main.go: Это точка входа в программу. Здесь инициализируется всё приложение: загружаются конфигурации, создаются адаптеры и запускается планировщик Cron.

  2. internal/config: Здесь находится код для работы с переменными окружениями. Это позволяет легко управлять настройками проекта.

  3. internal/adapters: Адаптеры — это модули, которые "общаются" с внешними сервисами (например, Telegram, ZenQuotes, MyMemory). Если что-то изменится в API, нужно будет править только этот слой.

  4. internal/usecases: Здесь находится бизнес-логика приложения. Например, получение цитаты, её перевод и отправка в Telegram.

  5. internal/interfaces: Интерфейсы описывают, как должны работать адаптеры. Это делает код более гибким: можно заменить реализацию адаптера, не трогая остальной код.

  6. Dockerfile: Этот файл нужен для сборки Docker-образа, чтобы запустить бота на облачной платформе, например, Railway.

Такая организация помогает держать код в порядке, особенно если проект будет расти или меняться.

4.2. Бизнес-логика

Бизнес-логика — это сердце приложения, где происходят все основные действия: получение цитат, их перевод и отправка в Telegram.

1. Получение цитат

Чтобы получить случайную цитату, бот отправляет HTTP-запрос к ZenQuotes API. Вот как это работает:

Ссылка для запроса:

https://zenquotes.io/api/random

Этот запрос возвращает JSON с одной случайной цитатой.

Пример ответа:

[
  {
    "q": "The best way to predict the future is to create it.",
    "a": "Peter Drucker"
  }
]

Обработка ответа:

  • Бот извлекает текст цитаты (q) и имя автора (a).

  • Если что-то пошло не так (например, API недоступен), бот логирует ошибку и пробует снова.

2. Перевод текста

Цитаты на английском нужно перевести на русский язык. Для этого используется MyMemory API.

Ссылка для запроса:

https://api.mymemory.translated.net/get?q=текст&langpair=en|ru

Здесь текст — это текст цитаты, который нужно перевести.

Пример ответа:

{
  "responseData": {
    "translatedText": "Одна ошибка и ты ошибся"
  }
}

Обработка ошибок перевода:

  • Если API вернул пустой перевод или произошла ошибка, бот отправляет оригинальный текст цитаты.

  • Логируются все проблемы, чтобы можно было разобраться с ними позже.

3. Отправка сообщений

После того как цитата переведена, её нужно отправить в Telegram-канал. Для этого используется Telegram Bot API.

Форматирование сообщений с эмодзи:

Чтобы сообщения выглядели красиво, я добавил эмодзи:

📖 Одна ошибка и ты ошибся.

— Сократ ✍️

Отправка сообщения:

Бот использует метод sendMessage Telegram Bot API.

Пример запроса:

{
  "chat_id": "-1001234567890",
  "text": "📖 Одна ошибка и ты ошибся.\n\n— Сократ ✍️"
}

Обработка ошибок:

  • Если сообщение не отправилось (например, из-за проблем с интернетом), бот повторяет попытку.

  • Все ошибки логируются, чтобы можно было быстро найти проблему.

Как это всё работает вместе?

  1. Бот запрашивает случайную цитату у ZenQuotes API.

  2. Полученный текст отправляется в MyMemory API для перевода на русский язык.

  3. Переведённая цитата форматируется с эмодзи и отправляется в Telegram-канал через Telegram Bot API.

  4. Если что-то пошло не так (например, API недоступен), бот обрабатывает ошибку и продолжает работу.

Так устроена бизнес-логика бота. В следующем разделе рассмотрим, как настроить планировщик задач Cron, чтобы бот отправлял цитаты в определённое время.

4.3. Планировщик задач

Чтобы бот отправлял цитаты в Telegram-канал по расписанию, я использовал планировщик задач Cron. Этот инструмент позволяет запускать определённые действия в заданное время. Давайте разберём, как настроить Cron и как логировать его работу.

Настройка Cron

Cron работает с помощью специальных выражений, которые задают время выполнения задачи. В нашем случае бот отправляет цитаты утром, днём, вечером и ночью — это можно настроить с помощью Cron-выражения.

Формат Cron-выражения:

┌──────────── минуты (0–59)
│ ┌────────── часы (0–23)
│ │ ┌──────── день месяца (1–31)
│ │ │ ┌────── месяц (1–12)
│ │ │ │ ┌──── день недели (0–6, где 0 — воскресенье)
│ │ │ │ │
* * * * *

Пример Cron-выражения для нашего бота:

Если мы хотим отправлять цитаты в 8:00, 12:00, 18:00 и 22:00 UTC, выражение будет выглядеть так:

0 8,12,18,22 * * *
  • 0: Минута (в начале часа).

  • 8,12,18,22: Часы (8:00, 12:00, 18:00, 22:00 UTC).

  • * : Любой день месяца.

  • * : Любой месяц.

  • *: Любой день недели.

Если вы хотите, чтобы бот отправлял цитаты каждые 30 минут, используйте следующее Cron-выражение:

*/30 * * * *

Как перевести время в UTC? Если ваше время отличается от UTC (например, самарское время опережает UTC на 4 часа), нужно перевести расписание:

  • 8:00 SAMT → 4:00 UTC

  • 12:00 SAMT → 8:00 UTC

  • 18:00 SAMT → 14:00 UTC

  • 22:00 SAMT → 18:00 UTC

Тогда Cron-выражение для самарского времени будет:

0 4,8,14,18 * * *

Чтобы следить за работой планировщика, я добавил логирование всех ключевых шагов: при запуске задачи бот записывает в лог сообщение [INFO] Задача отправки цитаты запущена, при ошибках (например, недоступность API или сбой отправки) фиксируется проблема в формате [ERROR] Ошибка при получении цитаты: таймаут запроса, а при успешной отправке цитаты логируется информация вида [INFO] Цитата успешно отправлена: "Одна ошибка и ты ошибся.". Для удобства анализа я использую библиотеку log/slog из стандартной библиотеки Go, которая позволяет форматировать логи в JSON.

Как это работает вместе?

  1. Cron запускает задачу отправки цитаты в указанное время.

  2. Бот выполняет все шаги: получает цитату, переводит её и отправляет в Telegram.

  3. Все действия записываются в логи для дальнейшего анализа.

5. Безопасность и переменные окружения

Для безопасной работы бота важно правильно хранить данные, такие как токен Telegram-бота и ID канала. Эти данные не должны попадать в открытый доступ, например, в репозиторий GitHub. Разберём, как организовать их хранение и загрузку.

1.Хранение данных

Для локальной разработки я использую файл .env, который хранит все переменные окружения. Вот пример такого файла:

BOT_TOKEN=123456789:AAFwz3QxYzZzZzZzZzZzZzZzZzZzZzZz
CHAT_ID=-1001234567890

Файл .env добавляется в .gitignore, чтобы он не попал в репозиторий. Для загрузки переменных из .env используется библиотека github.com/joho/godotenv.

2.Настройка переменных окружения на Railway

Когда проект развёрнут на Railway, переменные окружения настраиваются через веб-интерфейс.

Безопасная загрузка переменных в код

Чтобы избежать проблем с отсутствием переменных окружения, я добавил проверки и обработку ошибок:

1. Проверка наличия переменных:

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

botToken := os.Getenv("BOT_TOKEN")
chatIDStr := os.Getenv("CHAT_ID")
if botToken == "" || chatIDStr == "" {
    logger.Error("Необходимые переменные окружения отсутствуют", "BOT_TOKEN", botToken, "CHAT_ID", chatIDStr)
    os.Exit(1)
}

2.Обработка ошибок:

Если значение переменной (например, CHAT_ID) не может быть преобразовано в нужный формат, бот также завершает работу с логированием ошибки:

chatID, err := strconv.ParseInt(chatIDStr, 10, 64)
if err != nil {
    logger.Error("Ошибка преобразования CHAT_ID в int64", "error", err)
    os.Exit(1)
}

3.Логирование:

Все действия с переменными окружениями логируются для удобства отладки:

logger.Info("Переменные окружения загружены", "BOT_TOKEN", botToken, "CHAT_ID", chatID)

7. Деплой на Railway

Что такое Railway и почему его использовать?

Railway — это облачная платформа для развёртывания приложений, которая позволяет быстро запускать проекты без необходимости настройки серверов. Она поддерживает автоматический деплой из GitHub, что делает процесс максимально простым. Railway идеально подходит для небольших проектов, таких как Telegram-бот, благодаря удобному интерфейсу, бесплатному тарифу и встроенной поддержке Docker.

Шаги деплоя

1. Создание проекта на Railway

  • Зарегистрируйтесь на Railway или войдите в свой аккаунт.

  • Нажмите "New Project" и выберите "Deploy from GitHub Repo".

  • Выберите репозиторий с вашим проектом.

2. Настройка переменных окружения

  • Перейдите в раздел "Variables" вашего проекта на Railway.

  • Добавьте переменные окружения:

BOT_TOKEN=ваш_токен_бота
CHAT_ID=ваш_chat_id

3. Dockerfile для сборки образа

FROM golang:1.20-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o bot ./cmd

FROM alpine:latest
COPY --from=builder /app/bot /bot
CMD ["/bot"]

Railway автоматически найдёт Dockerfile и соберёт образ.

4. Автоматический деплой через GitHub

  • Свяжите Railway с вашим GitHub-репозиторием.

  • Настройте автоматический деплой при каждом обновлении кода.

  • После первого коммита Railway соберёт и запустит проект.

Итоги

Мы создали Telegram-бота, который автоматически отправляет мотивационные цитаты в канал по расписанию. В процессе разработки мы научились работать с Telegram Bot API для отправки сообщений, использовали внешние API (ZenQuotes и MyMemory) для получения и перевода цитат, реализовали планировщик задач с помощью Cron и освоили развёртывание приложения на облачной платформе Railway. Этот проект помог нам приобрести важные навыки: работу с API, чистую архитектуру, логирование, безопасное хранение данных и деплой приложений. Теперь у вас есть готовый инструмент, который можно дорабатывать и адаптировать под свои нужды, а также база для создания новых Telegram-ботов.

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