Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/ashenblade/podcast-aggregator-tg-bot

Телеграм бот для отслеживания новых выпусков подкастов на различных платформах
https://github.com/ashenblade/podcast-aggregator-tg-bot

aggregator daemon parsing podcast python scraper telegram-bot

Last synced: about 14 hours ago
JSON representation

Телеграм бот для отслеживания новых выпусков подкастов на различных платформах

Awesome Lists containing this project

README

        

# Podcast Aggregator Telegram Bot

Телеграм бот для ведения телеграм канала по тематике подкастов

# Алгоритм работы

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

# Как запустить

Конфигурация передается через переменные окружения:
- `POLL_INTERVAL_HOURS` - интервал работы приложения в часах. По умолчанию, 1. Не может быть отрицательным.
- `BOT_TOKEN` - токен телеграм бота. Обязателен
- `CHAT_ID` - ID чата, куда будут присылаться новые подкасты
- `SEED_PODCASTS_FILE` - путь к файлу с сидами подкастов, для изначального заполнения. Необязателен.
- `DB_FILE` - путь к файлу БД с подкастами и треками. По умолчанию, 'podcasts.sqlite' в текущей директории проекта. Если не существовало, то создается новый и схема инициализируется

Бот разрабатывался для запуска в контейнере.
Как он будет себя вести вне его - мне не известно.

Пример запуска можно найти в [docker-compose.yaml](./docker-compose.yaml).

При запуске создается внутренняя база данных для хранения самих подкастов и треков, которые уже были обработаны.
БД хранится в контейнере, по пути

# Работа с подкастами

На данный момент можно добавлять подкасты из 3 провайдеров (откуда брать новые треки): Яндекс.Музыка, Google подкасты, Apple подкасты.
Подкасты можно добавить с помощью yaml файла, в котором нужно указать название подкаста и ID, указанного в провайдере.

## База данных подкастов

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

Схема базы данных указана в [db/schema.sql](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](db/seeds/podcasts.yaml).

В случае, если при попытке добавления записи нашлась конфликтующая (одинаковые ID у провайдеров),
то эта запись не добавляется, но другие будут добавлены.
Если появились новые ID для другого провайдера, то нужно пересоздавать БД, либо обновлять вручную.

## Яндекс.Музыка

ID подкаста в Яндекс.Музыке:
1. Получить URL страницы подкаста - [https://music.yandex.ru/album/7570122](https://music.yandex.ru/album/7570122)
2. Из пути получить ID альбома, число после /album/ - 7570122

Представление ID - число

## Гугл Подкасты

ID подкаста в Гугл Подкастах:
1. Получить URL страницы подкаста - [https://podcasts.google.com/feed/aHR0cHM6Ly9mZWVkcy5zb3VuZGNsb3VkLmNvbS91c2Vycy9zb3VuZGNsb3VkOnVzZXJzOjI5MTMzNzEwNi9zb3VuZHMucnNz](https://podcasts.google.com/feed/aHR0cHM6Ly9mZWVkcy5zb3VuZGNsb3VkLmNvbS91c2Vycy9zb3VuZGNsb3VkOnVzZXJzOjI5MTMzNzEwNi9zb3VuZHMucnNz)
2. Из пути получить ID фида, строка после /feed/ - aHR0cHM6Ly9mZWVkcy5zb3VuZGNsb3VkLmNvbS91c2Vycy9zb3VuZGNsb3VkOnVzZXJzOjI5MTMzNzEwNi9zb3VuZHMucnNz

Представление ID - закодированная Base64 строка адреса из feeds.soundcloud.com.
ID из примера десериализуется в [https://feeds.soundcloud.com/users/soundcloud:users:291337106/sounds.rss](https://feeds.soundcloud.com/users/soundcloud:users:291337106/sounds.rss)

## Apple Подкасты

ID подкаста в Apple Подкастах:
1. Получить URL страницы подкаста - [https://podcasts.apple.com/ru/podcast/podlodka-podcast/id1209828744](https://podcasts.apple.com/ru/podcast/podlodka-podcast/id1209828744)
2. Из пути получить ID подкаста, строка после /podcast/{строка названия подкаста}/ - id1209828744

Представление ID - строка с 'id' в начале и числом после нее

# Замечания

Когда могут возникнуть ошибки:
- Передаваемые ID не проверяются на корректность. Это лежит на плечах пользователя
- Если подкаст будет удален, то приложение начнет работать неправильно. Такое уже случалось, когда Scalalaz Podcast был удален - несколько дней не публиковались подкасты
- Для работы, приложение использует парсинг HTML. В некоторых местах, полагается на эмпирические данные, например, вложенность 'div' тегов. Если верстка изменится, то приложение начнет сбоить.

# Техническая реализация

## Основная работа

Работа приложения происходит в итерациях. После каждой итерации приложение засыпает на указанное число часов.
Алгоритм итерации:
1. Из БД загружаются все [`Podcast`](src/models/podcast.py)
2. Для всех [`PodcastProvider`](src/models/podcast_provider.py) каждого подкаста загружаются все [`PublishedProviderProvider`](src/models/provider_track.py) - треки подкаста, выпущенные в текущий день
3. Создается [`PublishedTrack`](src/models/published_track.py) из полученных `PublishedProviderTrack`
4. Определяется, был ли текущий трек уже отправлен. Он считается отправленным, если хотя бы один из провайдеров его сохранил в БД.
5. Этот трек не был ранее опубликован, то:
1. Формируется новое сообщение для телеграмма
2. Сообщение отправляется в телеграм и получается ID сообщения
3. Создается [`PublishedTrack`](src/models/published_track.py)
4. Этот объект сохраняется в БД
6. Иначе:
1. Получить все новые треки - те, которых еще нет в БД
2. Получить ID соответствующего сообщения из БД
3. Обновить ссылки на сообщение
4. Обновить запись в БД - добавить ID трека

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

## Провайдеры подкастов

Классы провайдеров подкастов располагаются в [podcast_providers](src/podcast_providers).
На них можно ориентироваться при создании новых провайдеров.

Провайдер должен реализовать 3 подкласса:
- `PodcastProvider` - объект для получения опубликованных в определенную дату треков
- `ProviderTrack` - объект трека какого-то подкаста
- `PublishedProviderTrack` - объект опубликованного трека какого-то провайдера

Разница между `ProviderTrack` и `PublishedProviderTrack` заключается в том, что первый используется при загрузке из БД.
В нем не указаны дата и специфичные для провайдера данные.
А последний - при загрузке трека после загрузки нового (сайт, API).

PodcastProvider используется для получения определенных треков.
Для этого есть метод `get_track_published_at(publish_date)`. Ему передается дата публикации трека - сегодняшний день (хотел писать тесты, но не дошли руки, поэтому передача даты - уже рудимент).

Например, для Яндекс.Музыки реализованы:
- [`YandexMusicProvider`](src/podcast_providers/yandex_podcast_provider/yandex_music_provider.py),
- [`YandexPublishedProviderTrack`](src/podcast_providers/yandex_podcast_provider/yandex_published_provider_track.py)
- [`YandexProviderTrack`](src/podcast_providers/yandex_podcast_provider/yandex_provider_track.py).

## Работа с телеграмм

Для работы с телеграмм используется [`TelegramTrackSender`](src/telegram_track_sender/telegram_track_sender.py).

У него есть 2 главных метода:
- `send_track` - отправить новый трек. Он отправляет новое сообщение, с прикрепленными к нему сообщением, названием и источниками.
Возвращает ID для созданного сообщения
- `update_track_sources` - обновляет источники трека. По факту, он заменяет все текущие ссылки, поэтому ему надо передавать список всех источников, даже старых.

## Работа с хранилищем

Все хранится в SQLite БД.
Для работы ним используется [`SqlitePodcastManager`](src/sqlite_podcast_manager/sqlite_podcast_manager.py).

Содержит все необходимые методы для работы с подкастами и треками.