Телеграм бот для ведения телеграм канала по тематике подкастов
Бот с определенным интервалом опрашивает сайты с подкастами на наличие новых. При обнаружении новых, они отправляются в указанный телеграм чат:
- если обнаружен новый эпизод, то создается новое сообщение
- если обнаружен новый источник, то обновляются ссылки на эпизоды
Конфигурация передается через переменные окружения:
POLL_INTERVAL_HOURS
- интервал работы приложения в часах. По умолчанию, 1. Не может быть отрицательным.BOT_TOKEN
- токен телеграм бота. ОбязателенCHAT_ID
- ID чата, куда будут присылаться новые подкастыSEED_PODCASTS_FILE
- путь к файлу с сидами подкастов, для изначального заполнения. Необязателен.DB_FILE
- путь к файлу БД с подкастами и треками. По умолчанию, 'podcasts.sqlite' в текущей директории проекта. Если не существовало, то создается новый и схема инициализируется
Бот разрабатывался для запуска в контейнере. Как он будет себя вести вне его - мне не известно.
Пример запуска можно найти в docker-compose.yaml.
При запуске создается внутренняя база данных для хранения самих подкастов и треков, которые уже были обработаны. БД хранится в контейнере, по пути
На данный момент можно добавлять подкасты из 3 провайдеров (откуда брать новые треки): Яндекс.Музыка, Google подкасты, Apple подкасты. Подкасты можно добавить с помощью yaml файла, в котором нужно указать название подкаста и ID, указанного в провайдере.
Сами подкасты хранятся в SQLite базе данных локально. БД нужна чтобы, отслеживать отправленные треки и обновлять их при необходимости.
Схема базы данных указана в db/schema.sql.
Файл базы данных указывается в переменной окружения DB_FILE
.
Если она не указана, то создается файл БД 'podcasts.sqlite' в рабочей директории.
Если важно сохранение состояния, то можно смаунтить путь к директории и в DB_FILE
указать файл в этой директории.
На старте приложения происходит попытка инициализации схемы БД. Если она неудачна, то предполагается, что БД уже инициализирована и работа продолжается.
Изначальные подкасты можно указывать через файл podcasts.yaml.
Этот файл можно замаунтить и указать через переменную окружения SEED_PODCASTS_FILE
.
Если он не указан, то БД заполнена не будет, но работа будет продолжена.
В этом файле должны быть:
- либо верхне уровневый массив объекта подкаста
- либо поле 'podcasts' с массивом объекта подкаста
Объект подкаста представляется структурой из 4 полей:
- 'name' - название подкаста
- 'yandex' - ID подкаста в Яндекс.Музыке
- 'google' - ID подкаста в Гугл Подкастах
- 'apple' - ID подкаста в Apple подкастах
Поле 'name' единственное обязательное. Остальные могут быть не указаны, это означает null.
Причем, любой ID обязан быть уникальным для своего провайдера, но 'name' уникальным может не быть (может повторяться). Пример можно найти в файле db/seeds/podcasts.yaml.
В случае, если при попытке добавления записи нашлась конфликтующая (одинаковые ID у провайдеров), то эта запись не добавляется, но другие будут добавлены. Если появились новые ID для другого провайдера, то нужно пересоздавать БД, либо обновлять вручную.
ID подкаста в Яндекс.Музыке:
- Получить URL страницы подкаста - https://music.yandex.ru/album/7570122
- Из пути получить ID альбома, число после /album/ - 7570122
Представление ID - число
ID подкаста в Гугл Подкастах:
- Получить URL страницы подкаста - https://podcasts.google.com/feed/aHR0cHM6Ly9mZWVkcy5zb3VuZGNsb3VkLmNvbS91c2Vycy9zb3VuZGNsb3VkOnVzZXJzOjI5MTMzNzEwNi9zb3VuZHMucnNz
- Из пути получить ID фида, строка после /feed/ - aHR0cHM6Ly9mZWVkcy5zb3VuZGNsb3VkLmNvbS91c2Vycy9zb3VuZGNsb3VkOnVzZXJzOjI5MTMzNzEwNi9zb3VuZHMucnNz
Представление ID - закодированная Base64 строка адреса из feeds.soundcloud.com. ID из примера десериализуется в https://feeds.soundcloud.com/users/soundcloud:users:291337106/sounds.rss
ID подкаста в Apple Подкастах:
- Получить URL страницы подкаста - https://podcasts.apple.com/ru/podcast/podlodka-podcast/id1209828744
- Из пути получить ID подкаста, строка после /podcast/{строка названия подкаста}/ - id1209828744
Представление ID - строка с 'id' в начале и числом после нее
Когда могут возникнуть ошибки:
- Передаваемые ID не проверяются на корректность. Это лежит на плечах пользователя
- Если подкаст будет удален, то приложение начнет работать неправильно. Такое уже случалось, когда Scalalaz Podcast был удален - несколько дней не публиковались подкасты
- Для работы, приложение использует парсинг HTML. В некоторых местах, полагается на эмпирические данные, например, вложенность 'div' тегов. Если верстка изменится, то приложение начнет сбоить.
Работа приложения происходит в итерациях. После каждой итерации приложение засыпает на указанное число часов. Алгоритм итерации:
- Из БД загружаются все
Podcast
- Для всех
PodcastProvider
каждого подкаста загружаются всеPublishedProviderProvider
- треки подкаста, выпущенные в текущий день - Создается
PublishedTrack
из полученныхPublishedProviderTrack
- Определяется, был ли текущий трек уже отправлен. Он считается отправленным, если хотя бы один из провайдеров его сохранил в БД.
- Этот трек не был ранее опубликован, то:
- Формируется новое сообщение для телеграмма
- Сообщение отправляется в телеграм и получается ID сообщения
- Создается
PublishedTrack
- Этот объект сохраняется в БД
- Иначе:
- Получить все новые треки - те, которых еще нет в БД
- Получить ID соответствующего сообщения из БД
- Обновить ссылки на сообщение
- Обновить запись в БД - добавить ID трека
В работе приложения применяется предположение, что за 1 день выпускается только 1 подкаст. Поэтому, если в один день будет выпущено несколько подкастов, то будет взят только последний трек.
Классы провайдеров подкастов располагаются в podcast_providers. На них можно ориентироваться при создании новых провайдеров.
Провайдер должен реализовать 3 подкласса:
PodcastProvider
- объект для получения опубликованных в определенную дату трековProviderTrack
- объект трека какого-то подкастаPublishedProviderTrack
- объект опубликованного трека какого-то провайдера
Разница между ProviderTrack
и PublishedProviderTrack
заключается в том, что первый используется при загрузке из БД.
В нем не указаны дата и специфичные для провайдера данные.
А последний - при загрузке трека после загрузки нового (сайт, API).
PodcastProvider используется для получения определенных треков.
Для этого есть метод get_track_published_at(publish_date)
. Ему передается дата публикации трека - сегодняшний день (хотел писать тесты, но не дошли руки, поэтому передача даты - уже рудимент).
Например, для Яндекс.Музыки реализованы:
Для работы с телеграмм используется TelegramTrackSender
.
У него есть 2 главных метода:
send_track
- отправить новый трек. Он отправляет новое сообщение, с прикрепленными к нему сообщением, названием и источниками. Возвращает ID для созданного сообщенияupdate_track_sources
- обновляет источники трека. По факту, он заменяет все текущие ссылки, поэтому ему надо передавать список всех источников, даже старых.
Все хранится в SQLite БД.
Для работы ним используется SqlitePodcastManager
.
Содержит все необходимые методы для работы с подкастами и треками.