Как я собрал новостного Telegram‑бота с ИИ‑редактором, который живёт в GitHub Actions и не стоит ни копейки
Обычно автоматический новостной канал в Telegram делают так: скрипт на feedparser, копеечный VPS, cron раз в час — и в канал падает безликая простыня текста со ссылкой. Через день от такого канала все отписываются.
Я решил сделать по‑другому и закрыть три проблемы сразу:
Качество контента. Не парсить всё подряд, а отбирать, переводить, сжимать длинные статьи до 3–5 предложений и подбирать картинку.
Частоту постов. Никакого спама каждый час — только точечные посты плюс структурированные дайджесты утром и вечером.
Стоимость. Хостинг, база и обработка текста — 0 рублей.
Получился скрипт на Python, который живёт в GitHub Actions, хранит состояние прямо в Git‑репозитории и работает на бесплатных лимитах LLM.
База данных — это просто Git
Главный вопрос при отказе от постоянного сервера: где хранить состояние? Бот должен помнить, какие статьи уже опубликовал и на каком RSS‑источнике остановился в прошлый раз (round‑robin по лентам).
Вместо облачной БД я использовал файловую систему самого репозитория:
позиции курсора по RSS‑лентам — в
data/state.json;хэши и URL уже опубликованных статей — в обычном текстовом файле
data/seen_urls.txt(защита от дублей).
Скрипт выполняется на раннере GitHub Actions (бесплатный лимит — 2000 минут в месяц), а в конце воркфлоу идёт шаг, который коммитит изменённые файлы из data/ и пушит их обратно в main.
Для этого в настройках репозитория нужно выдать раннеру права на запись:
Settings → Actions → General → Workflow permissions → Read and write permissionsЧтобы два параллельных запуска не подрались за один и тот же файл состояния, в воркфлоу задан concurrency.group:
concurrency:
group: post-news
cancel-in-progress: falseCron в GitHub Actions ненадёжен — пришлось подстраховаться снаружи
Встроенный cron в GitHub Actions — штука с сюрпризами: слот может опоздать на пару часов, а иногда запуск просто пропадает без объяснений.
Решение — workflow_dispatch плюс внешний бесплатный сервис cron‑job.org. Раз в час он шлёт POST‑запрос с токеном авторизации на API GitHub и принудительно запускает раннер вовремя. Один прогон занимает чуть больше минуты, так что лимита Actions хватает с огромным запасом.
Как устроен пайплайн
Отбор статьи
Бот собирает 5 свежих кандидатов из RSS‑лент, заданных в конфиге. Заголовки и короткие описания этих пяти статей уходят в LLM на OpenRouter — на бесплатном тарифе там нормально работают модели вроде nvidia/nemotron.
Промпт просит модель проанализировать список и вернуть строго один ID статьи — той, что потенциально интереснее IT‑аудитории. Если модель отвечает мусором или не попадает в регулярку — срабатывает фолбэк: берётся первая рабочая ссылка из списка по порядку.
Извлечение текста и сжатие
По ссылке‑победителю бот достаёт «голый» текст страницы без рекламы и сайдбаров через trafilatura. Этот текст одним запросом уходит в LLM, которая решает три задачи за раз:
определяет язык источника и переводит на русский, если нужно;
выдаёт заголовок и выжимку в 3–5 предложений;
формирует короткий промпт на английском для генерации картинки.
Картинка — с фолбэком в четыре шага
Пост без картинки в Telegram почти не читают, а внешние сервисы генерации любят таймаутить. Поэтому в main.py — цепочка из четырёх попыток:
картинка из самого RSS‑элемента;
если её нет —
og:imageиз мета‑тегов страницы (без повторного запроса к сайту, парсится заодно с текстом);если сайт закрыт для парсинга картинок — поиск по ключевым словам новости через Pexels API;
если и это не сработало — сгенерированный промпт уходит в Pollinations.ai (без токенов и ключей).
Если падает вообще всё — пост уходит просто текстом. Картинка при этом качается напрямую в память и отправляется в Telegram как байты, а не как ссылка: на медленных прокси Telegram часто режет превью по ссылке, и пост выглядит криво.
Почему пост раз в час — плохая идея
Изначально бот молотил каждый час и сразу постил результат. Через сутки стало ясно: 15–20 постов в день превращают канал в спам, и люди жмут «Заглушить» почти сразу.
Логику распределения переписал на два режима:
Дневной режим. Бот запускается каждый час, но лимит на отправку (
MAX_ARTICLES_PER_RUN) жёстко зажат — в канал за день уходит всего 3–4 отфильтрованных события.Режим дайджеста. В 08:00 и 20:00 по Минску/Москве бот переключается на
digest.py: собирает повестку из пула мировых медиа (BBC, CNN, Guardian), агрегирует и выдаёт один структурированный пост по 5 главным темам. Это закрывает потребность «быть в курсе», не заваливая ленту уведомлениями.
Сколько это стоит
Ничего, и без привязки зарубежной карты для трайлов:
хостинг скрипта — GitHub Actions, 0 ₽;
триггер по расписанию — cron‑job.org, 0 ₽;
текст и перевод — OpenRouter Free API (квоты хватает впритык на 16 сессий по ~3 запроса в сутки);
картинки — лимиты Pexels API и Pollinations.ai, 0 ₽.
Код открыт
Архитектура модульная: чтобы пересобрать пайплайн под другую тематику (крипта, гаджеты, региональные новости), достаточно поменять RSS‑фиды в config.py.
Репозиторий: github.com/BlackDeepSky/news_bot
Живой канал: t.me/yourpocketnews
Буду рад фидбеку в комментариях. Главный вопрос, который меня самого интересует: насколько коммиты в Git — это адекватная замена БД для пет‑проектов, и какие подводные камни я мог упустить?