コミット
84d0aabb91
|
@ -16,7 +16,7 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
python-version: [3.7, 3.8, 3.9, "3.10"]
|
||||
python-version: [3.7, 3.8, 3.9, "3.10", "3.11"]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository.
|
||||
|
|
|
@ -3,9 +3,16 @@
|
|||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
version: 2
|
||||
|
||||
build:
|
||||
os: ubuntu-20.04
|
||||
tools:
|
||||
python: "3.11"
|
||||
|
||||
sphinx:
|
||||
configuration: docs/source/conf.py
|
||||
|
||||
python:
|
||||
version: "3.7"
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
- requirements: requirements-dev.txt
|
||||
|
|
|
@ -0,0 +1,390 @@
|
|||
# Список изменений
|
||||
|
||||
## Версия 2.1.0
|
||||
|
||||
**23.04.2023**
|
||||
|
||||
**Переломные изменения**
|
||||
|
||||
При работе над [#547](https://github.com/MarshalX/yandex-music-api/issues/547) и
|
||||
[#550](https://github.com/MarshalX/yandex-music-api/issues/550)
|
||||
были удалены `*args` параметры, у методов класса `Client`, которые не имели никакого эффекта.
|
||||
Передать через позиционные аргументы что-то в конечный запрос не было возможно.
|
||||
Удаление данной конструкции **могло** затронуть код в котором ошибочно передавались лишние аргументы.
|
||||
При корректном использовании библиотеки новая версия полностью совместима со старым кодом.
|
||||
|
||||
**Крупные изменения**
|
||||
|
||||
- Добавлена поддержка Python 3.11.
|
||||
- В модели добавлены методы `download_bytes` и `download_bytes_async`, для получения файлов в виде байтов ([#539](https://github.com/MarshalX/yandex-music-api/issues/539)).
|
||||
- Добавлен новый метод получения текста и синхронного текста треков ([#568](https://github.com/MarshalX/yandex-music-api/pull/568)).
|
||||
- Добавлена возможность задать `timeout` по умолчанию для `Client` ([#362](https://github.com/MarshalX/yandex-music-api/issues/362)).
|
||||
- Использование настройки языка клиента во всех методах ([#554](https://github.com/MarshalX/yandex-music-api/issues/554)).
|
||||
- Добавлено поле `preview_description` классу `GeneratedPlaylist`.
|
||||
- Добавлены поля `pretrial_active` и `userhash` классу `Status`.
|
||||
- Добавлено поле `had_any_subscription` классу `Subscription`.
|
||||
- Добавлено поле `child` классу `Account`.
|
||||
- Добавлены новые поля `up_title`, `rup_description`, `custom_name` классу `StationResult`.
|
||||
- Добавлены новые модели: `CustomWave`, `R128`, `LyricsInfo`.
|
||||
- Классу `Track` добавлены новые поля: `track_source`, `available_for_options`, `r128`, `lyrics_info`, `track_sharing_flag`.
|
||||
- Классу `TrackShort` добавлены новые поля: `original_index`.
|
||||
- Классу `Playlist` добавлены новые поля: `custom_wave`, `pager`.
|
||||
- Классу `Album` добавлены новые поля: `available_for_options`.
|
||||
- Поле `cover_white` класса `MixLink` теперь опциональное.
|
||||
|
||||
**Незначительные изменения и/или исправления**
|
||||
|
||||
- Добавлен генератор Camel Case псевдонимов для методов ([#542](https://github.com/MarshalX/yandex-music-api/issues/542)).
|
||||
- Добавлен Makefile с сокращениями удобными при разработке библиотеки.
|
||||
- Добавлено отображение модуля при нахождении неизвестного поля.
|
||||
- Добавлена поддержка MD файлов для документации.
|
||||
- Добавлена страница в документацию по получению токена.
|
||||
- Добавлены примеры в документацию.
|
||||
- Переделана структура и обновлена документации.
|
||||
- Исправлен запуск генератора async клиента на Windows.
|
||||
- Исправлен метод `fetch_tracks_async` у класса `Playlist`.
|
||||
- Исправлены type hints у декоратора `log`.
|
||||
- Исправлены type hints для `SearchResult` в классе `Search`.
|
||||
- Исправлено отображение название класса в `report_unknown_fields_callback`.
|
||||
- Исправлены методы-сокращения `like` и `dislike` класса `Playlist` ([#516](https://github.com/MarshalX/yandex-music-api/pull/516)).
|
||||
|
||||
## Версия 2.0.0
|
||||
|
||||
**23.02.2022**
|
||||
|
||||
**Поддержка asyncio и модели на dataclasses**
|
||||
|
||||
**Переломные изменения**
|
||||
|
||||
- Убрана поддержка `Python 3.6`.
|
||||
- Удалено получение авторизационного токена по логину и паролю (метод `from_credentials` класса `Client`).
|
||||
- Удалена возможность задать свой обработчик на полученные неизвестные поля от API (аргумент `report_new_fields_callback` конструктора класса `Client`.
|
||||
- Удалён аргумент `fetch_account_status` из конструктора класса `Client`. Теперь необходимо вызывать метод `init` для получения ID аккаунта который будет использоваться в последующих запросах. В противном случае передача `user_id` при вызове многих методов класса `Client` становится обязательной.
|
||||
- Исключение `BadRequest` переименовано в `BadRequestError`.
|
||||
- Исключение `Unauthorized` переименовано в `UnauthorizedError`.
|
||||
- Исключение `InvalidBitrate` переименовано в `InvalidBitrateError`.
|
||||
- Исключение `TimedOut` переименовано в `TimedOutError`.
|
||||
- Свойство `result` класса `Response` удалено. Вместо него добавлен метод `get_result`.
|
||||
- Свойство `error` класса `Response` удалено. Вместо него добавлен метоl `get_error`.
|
||||
- В JSON представлении моделей к полям, чьё имя совпадает с именем стандартных функций, больше не добавляется нижнее подчеркивание в конец (пример: `id`, а не `id_`; `max`, а не `max_`). Теперь нижнее подчеркивание добавляется только к зарезервированным словам (пример: `from` будет `from_`).
|
||||
|
||||
**Крупные изменения**
|
||||
|
||||
- Добавлена асинхронная версия клиента и всех методов-сокращений (класс `ClientAsync`).
|
||||
- Добавлено новое исключение `NotFoundError` (наследник `NetworkError`). Будет сгенерировано при получении статус кода 404.
|
||||
- Проект больше не использует `pipenv`.
|
||||
- Зависимости проекта больше не требуют конкретных версий.
|
||||
- Для генерации исходных файлов `Sphinx` теперь используется `sphinx-apidoc`.
|
||||
|
||||
**Незначительные изменения и/или исправления**
|
||||
|
||||
- Исправлена обработка серверных ошибок которые вернулись в отличном от JSON формате.
|
||||
- Исправлена обработка серверных ошибок метода `search` класса `Client`.
|
||||
- Предупреждения о пришедших неизвестных полях от API отключены по умолчанию.
|
||||
- Используется английская локализация `Sphinx`.
|
||||
- Изменена тема документации.
|
||||
|
||||
## Версия 1.0.0
|
||||
|
||||
**06.02.2021**
|
||||
|
||||
**Стабильная версия библиотеки**
|
||||
|
||||
**Переломные изменения**
|
||||
|
||||
- Поле `error` класса `Artist` теперь называется `reason`.
|
||||
- Метод `users_playlists` класса `Client` теперь возвращает один объект плейлиста, когда был передан один `kind`. При передаче списка в `kind` вернётся список плейлистов ([#318](https://github.com/MarshalX/yandex-music-api/issues/318)).
|
||||
- Поле `labels` класса `Album` теперь может содержать список из строк, а не только список объектов класса `Label`.
|
||||
|
||||
**Крупные изменения**
|
||||
|
||||
- Добавлены примеры в папку `examples`.
|
||||
- **Добавлена поддержка рекомендаций для плейлистов ([#324](https://github.com/MarshalX/yandex-music-api/issues/324))**:
|
||||
- Добавлен класс `PlaylistRecommendations`.
|
||||
- Добавлен метод клиента для получения рекомендаций(`users_playlists_recommendations`).
|
||||
- Добавлен метод `get_recommendations` классу `Playlist` для
|
||||
- **Добавлено получение чартов ([#294](https://github.com/MarshalX/yandex-music-api/issues/294))**:
|
||||
- Добавлены новые классы: `ChartInfo`, `ChartInfoMenu`,`ChartInfoMenuItem`.
|
||||
- Добавлен метод клиента для получения чарта (`chart`).
|
||||
- **Добавлена поддержка тегов/подборок ([#192](https://github.com/MarshalX/yandex-music-api/issues/192))**:
|
||||
- Добавлены новые классы: `TagResult`, `Tag`.
|
||||
- Добавлен новый метод клиента для получения тегов (`tags`).
|
||||
- **Добавлено присоединение к коллективному плейлисту ([#317](https://github.com/MarshalX/yandex-music-api/issues/317))**:
|
||||
- Добавлен новый метод клиента для присоединения(`playlists_collective_join`).
|
||||
- **Добавлена поддержка очередей прослушивания ([#246](https://github.com/MarshalX/yandex-music-api/issues/246))**:
|
||||
- Добавлены новые классы: `Context`, `Queue`, `QueueItem`.
|
||||
- Добавлены новые методы в `Client`: `queues_list`, `queue`,`queue_update_position`, `queue_create`.
|
||||
- Добавлены поля `track_id` и `from_` в класс `TrackId`.
|
||||
- Добавлена возможность смены языка у клиента для ответов от API.
|
||||
- Добавлена десериализация любого объекта в `JSON` пригодного для отправки в запросе на Яндекс API.
|
||||
- **Добавлены следующие методы для `Client`**:
|
||||
- `new_releases` – получение полного списка всех новых релизов.
|
||||
- `new_playlists` – получение полного списка всех новый плейлистов.
|
||||
- `podcasts` – получение подкаста с лендинга.
|
||||
- **Добавлены новые сокращения в модели**:
|
||||
- `download_cover_white`, `download_cover_uri` в `MixLink`.
|
||||
- `download_image` в `Promotion`.
|
||||
- `artists_name` в `Album` и `Track`.
|
||||
- `fetch_track`, `track_full_id` в `TrackId`.
|
||||
- `fetch_tracks` в `TracksList`.
|
||||
- `insert_track`, `delete_tracks`, `delete` в `Playlist`.
|
||||
- `playlist_id`, `fetch_playlist` в `PlaylistId`.
|
||||
- `get_current_track` в `Queue`.
|
||||
- `fetch_queue` в `QueueItem`.
|
||||
- `next_page`, `get_page`, `prev_page` в `Search`.
|
||||
- и другие...
|
||||
- Добавлена поддержка новых типов поиска: подкасты, выпуски, пользователи.
|
||||
- Добавлен callback для обработки новых полей.
|
||||
- Добавлена информацию по поводу запуска потока по треку, плейлисту и др.
|
||||
- Добавлена десериализация `decomposed` у `Artist` ([#10](https://github.com/MarshalX/yandex-music-api/issues/10)).
|
||||
- Добавлен `__len__` для `TracksList` ([#380](https://github.com/MarshalX/yandex-music-api/issues/380)).
|
||||
- Добавлены `__iter__`, `__len__` и `__getitem__` для классов представляющих список каких-либо объектов.
|
||||
- Добавлено сокращение `fetch_tracks` классу `Playlist` для получения треков плейлиста.
|
||||
- Добавлен метод `get_url` классу `Icon` для получения прямой ссылки на изображение.
|
||||
- Класс `User` расширен для поддержки поля `user_info` из `Track`(поля `full_name`, `display_name`).
|
||||
- **Добавлены новые классы по отчётам с Telegram бота ([#306](https://github.com/MarshalX/yandex-music-api/issues/306), [#398](https://github.com/MarshalX/yandex-music-api/issues/398))**:
|
||||
- `LandingList`.
|
||||
- `RenewableRemainder`.
|
||||
- `Alert`.
|
||||
- `AlertButton`.
|
||||
- `StationData`.
|
||||
- `Brand`.
|
||||
- `Contest`.
|
||||
- `OpenGraphData`.
|
||||
- `NonAutoRenewable`.
|
||||
- `Operator`.
|
||||
- `Deactivation`.
|
||||
- `PoetryLoverMatch`.
|
||||
- `Deprecation`.
|
||||
- **Добавлены новые поля классам по отчётам с Telegram бота ([#306](https://github.com/MarshalX/yandex-music-api/issues/306), [#398](https://github.com/MarshalX/yandex-music-api/issues/398))**:
|
||||
- `plus` в `Product`.
|
||||
- `non_auto_renewable_remainder` в `Subscription`.
|
||||
- `og_image` в `Artist`.
|
||||
- `meta_type` в `Album`.
|
||||
- `advertisement` в `Status`.
|
||||
- `best` в `Track`.
|
||||
- `offer_id` и `artist_ids` в `Vinyl`.
|
||||
- `playlists` в `BriefInfo`.
|
||||
- `is_custom` в `Cover`.
|
||||
- `play_count`, `recent`, `chart`, `track` в `TrackShort`.
|
||||
- `url_part`, `og_title`, `image`, `cover_without_text`, `background_color`, `text_color`, `id_for_from`,`similar_playlists`, `last_owner_playlists` в `Playlist`.
|
||||
- `bg_color` в `Chart`.
|
||||
- `error` в `Artist`.
|
||||
- `substituted`, `matched_track`, `can_publish`, `state`, `desired_visibility`, `filename`, `user_info`, `meta_data` в`Track`.
|
||||
- `copyright_name`, `copyright_cline` в `Cover`.
|
||||
- `direct` в `DownloadInfo`.
|
||||
- `cheapest`, `title`, `family_sub`, `fb_image`, `fb_name`,`family`, `intro_period_duration`, `intro_price`, `start_period_duration`, `start_price`, `licence_text_parts` в `Product`.
|
||||
- `storage_dir`, `duplicates` в `Album`.
|
||||
- `subscribed` в `ArtistEvent`.
|
||||
- `description` в `GeneratedPlaylist`.
|
||||
- `genre` в `Event`.
|
||||
- `show_in_regions` в `Genre`.
|
||||
- `cover_uri` в `MixLink`.
|
||||
- `og_description`, `top_artist` в `Playlist`.
|
||||
- `full_image_url`, `mts_full_image_url` в `Station`.
|
||||
- `coauthors` и `recent_tracks` в `Playlist`.
|
||||
- `regions` в `User`.
|
||||
- `users`, `podcasts`, `podcast_episodes`, `type_`, `page`, `per_page` в `Search`.
|
||||
- `short_description`, `description`, `is_premiere`, `is_banner` в `Like`.
|
||||
- `master_info` в `AutoRenewable`.
|
||||
- `station_data` и `bar_below` в `Status`.
|
||||
- `family_auto_renewable` в `Subscription`.
|
||||
- `misspell_result` и `misspell_original` в `Search`.
|
||||
- `experiment` в класс `Status`.
|
||||
- `operator` и `non_auto_renewable` в `Subscription`.
|
||||
- `text_color`, `short_description`, `description`, `is_premiere` и `is_banner` в `Album`.
|
||||
- `hand_made_description` в `Artist`.
|
||||
- `metrika_id` в `Playlist`.
|
||||
- `og_image` в `Tag`.
|
||||
- `url` в `Lyrics`.
|
||||
- `number`, `genre` в `MetaData`.
|
||||
- `poetry_lover_matches` в `Track`.
|
||||
- `contest`, `dummy_description`, `dummy_page_description`, `dummy_cover`, `dummy_rollover_cover`, `og_data`, `branding` в `Playlist`.
|
||||
- `available_as_rbt`, `lyrics_available`, `remember_position`, `albums`, `duration_ms`, `explicit`, `start_date`, `likes_count`, `deprecation` в `Album`.
|
||||
- `lyricist`, `version`, `composer` в `MetaData`.
|
||||
- `last_releases` в `BriefInfo`.
|
||||
- `ya_money_id` в `Artist` ([#351](https://github.com/MarshalX/yandex-music-api/issues/351), [#370](https://github.com/MarshalX/yandex-music-api/issues/370)).
|
||||
- `playlist_uuid` в `Playlist`.
|
||||
- `sync_queue_enabled` в `UserSettings`.
|
||||
- `background_video_uri`, `short_description`, `is_suitable_for_children` в `Track` ([#376](https://github.com/MarshalX/yandex-music-api/issues/376)).
|
||||
- `meta_type`, `likes_count` в `Album` ([#386](https://github.com/MarshalX/yandex-music-api/issues/386)).
|
||||
- `deprecation` в `Album`.
|
||||
- `available_regions` в `Album`.
|
||||
- `type`, `ready` в `Playlist`.
|
||||
- `description` в `Supplement`.
|
||||
|
||||
**Незначительные изменения и/или исправления**
|
||||
|
||||
- **Добавлена опциональность следующим полям**:
|
||||
- все поля в `MetaData`.
|
||||
- `advertisement` в `Status`.
|
||||
- `text_language` в `Lyrics`.
|
||||
- `provider_video_id` в `VideoSupplement`.
|
||||
- `title` в `VideoSupplement` ([#403](https://github.com/MarshalX/yandex-music-api/issues/403)).
|
||||
- `instructions` в `Deactivation` ([#402](https://github.com/MarshalX/yandex-music-api/issues/402)).
|
||||
- `id` в `Album` ([#401](https://github.com/MarshalX/yandex-music-api/issues/401)).
|
||||
|
||||
- Исправлена десериализация подкастов, эпизодов подкастов и пользователей в лучшем результате поиска.
|
||||
- Исправлена десериализация альбомов. В зависимости от запроса содержимое лейблов может быть списком объектом или списком строк (в поиске).
|
||||
- Исправлен выбор настроек радио.
|
||||
- Исправлены ошибки в документации.
|
||||
- Протестирована работа на Python 3.9.
|
||||
|
||||
## Версия 0.1.1
|
||||
|
||||
**25.03.2020**
|
||||
|
||||
**Закончено документирование всех классов и основных методов!**
|
||||
|
||||
**Переломные изменения**
|
||||
|
||||
- **Классы отметок "мне нравится" для альбомов, плейлистов и исполнителей обобщены. Теперь представлены одним классом**.
|
||||
- **Удаленные классы**:
|
||||
- `ArtistsLikes`.
|
||||
- `AlbumsLikes`.
|
||||
- `PlaylistsLikes`.
|
||||
- Новый класс: `Like` (поле `type` для определения содержимого).
|
||||
- Изменено название пакета с `status` на `account` ([#195](https://github.com/MarshalX/yandex-music-api/issues/195)).
|
||||
- **Исправлено выбрасываемое исключение при таймауте**:
|
||||
- Прошлое исключение: `TimeoutError` (built-in).
|
||||
- Новое исключение: `TimedOut` (`yandex_music.exceptions`).
|
||||
- Удалены следующие файлы: `requirements.txt`, `requirements-dev.txt`,
|
||||
`requirements-docs.txt`.
|
||||
|
||||
**Крупные изменения**
|
||||
|
||||
- **Добавлено обнаружение новых полей с просьбой сообщить о них ([#216](https://github.com/MarshalX/yandex-music-api/issues/216))**.
|
||||
- Добавлена проверка на неизвестные поля.
|
||||
- Добавлен вывод отладочной информации в виде warning'a.
|
||||
- Добавлен шаблон issue для отправки логов.
|
||||
- Добавлено поле `type` для класса `SearchResult` для определения типа результата поиска по объекту.
|
||||
- **Добавлены настройки пользователя ([#195](https://github.com/MarshalX/yandex-music-api/issues/195))**:
|
||||
- Добавлен класс `UserSettings`.
|
||||
- Добавлен метод для получения своих настроек (`account_settings`).
|
||||
- Добавлен метод для получения настроек другого пользователя (`users_settings`).
|
||||
- Добавлен метод для изменения настроек (`account_settings_set`).
|
||||
- **Добавлен возможность получить похожие треки ([#197](https://github.com/MarshalX/yandex-music-api/issues/197))**:
|
||||
- Добавлен класс `TracksSimilar` с полями трека и списка похожих треков.
|
||||
- Добавлен метод для получения похожих треков (`tracks_similar`).
|
||||
- **Добавлены шоты от Алисы ([#185](https://github.com/MarshalX/yandex-music-api/issues/185))**:
|
||||
- Добавлен метод `after_track` в класс `Client` для получения контента для воспроизведения после трека (реклама, шот).
|
||||
- Добавлены методы для загрузки обложки и аудиоверсии шота.
|
||||
- **Добавлены новые классы**:
|
||||
- `Shot`
|
||||
- `ShotData`
|
||||
- `ShotEvent`
|
||||
- `ShotType`
|
||||
- Добавлен метод для изменения видимости плейлиста ([#179](https://github.com/MarshalX/yandex-music-api/issues/179)).
|
||||
- **Добавлена поддержка Яндекс.Радио ([#20](https://github.com/MarshalX/yandex-music-api/issues/20))**:
|
||||
- Исправлена отправка фидбека.
|
||||
- Написана инструкция по использованию (в доке к методу).
|
||||
- Добавлен аргумент для перехода по цепочке треков.
|
||||
- Добавлен метод для изменения настроек станции.
|
||||
|
||||
**Незначительные изменения и/или исправления**
|
||||
|
||||
- Убрано дублирование информации в документации ([#247](https://github.com/MarshalX/yandex-music-api/issues/247)).
|
||||
- Добавлены новые поля в класс `Track`: `version`, `remember_position` ([#238](https://github.com/MarshalX/yandex-music-api/issues/238)).
|
||||
- Добавлено исключение `InvalidBitrate` при попытке загрузить недопустимый трек по критериям (кодек, битрейт).
|
||||
- Исправлено получение прямой ссылки на файл с кодеком AAC ([#237](https://github.com/MarshalX/yandex-music-api/issues/237), [#25](https://github.com/MarshalX/yandex-music-api/issues/25)).
|
||||
- Исправлено получение плейлиста с Алисой в лендинге ([#185](https://github.com/MarshalX/yandex-music-api/issues/185)).
|
||||
- Исправлено название поля с ссылкой на источник в классе `Description` (с `url` на `uri`).
|
||||
- Исправлена десериализация несуществующего исполнителя.
|
||||
- Добавлено поле `version` в класс `Album` ([#178](https://github.com/MarshalX/yandex-music-api/issues/178)).
|
||||
- Поле `picture` класса `Vinyl` теперь опциональное.
|
||||
- Поле `week` класса `Ratings` теперь опциональное.
|
||||
- Поле `product_id` класса `AutoRenewable` теперь опциональное ([#182](https://github.com/MarshalX/yandex-music-api/issues/182)).
|
||||
- Правки замечаний по codacy.
|
||||
|
||||
## Версия 0.0.16
|
||||
|
||||
**29.12.2019**
|
||||
|
||||
**Переломные изменения**
|
||||
|
||||
- Поле `account` переименовано в `me` и теперь содержит объект `Status`, вместо `Account` ([#162](https://github.com/MarshalX/yandex-music-api/issues/162)).
|
||||
- Убрано использование зарезервированных имён в аргументах конструкторов (теперь они с `_` на конце). Имена с нижними подчёркиваниями есть как при сериализации так и при десериализации ([#168](https://github.com/MarshalX/yandex-music-api/issues/168)).
|
||||
|
||||
**Крупные изменения**
|
||||
|
||||
- **Добавлены аннотации типов во всей библиотеке!**
|
||||
|
||||
**Незначительные изменения и/или исправления**
|
||||
|
||||
- Добавлен аргумент `fetch_account_status` для опциональности получения информации об аккаунте при инициализации клиента ([#162](https://github.com/MarshalX/yandex-music-api/issues/162)).
|
||||
- Добавлены тесты c передачей пустого словаря в `de_json` и `de_list` ([#174](https://github.com/MarshalX/yandex-music-api/issues/174)).
|
||||
- Использование `ujson` при наличии, обновлены зависимости ([#161](https://github.com/MarshalX/yandex-music-api/issues/161)).
|
||||
- Добавлен в зависимости для разработки `importlib_metadata` для поддержки старых версий (в новой версии `pytest` его больше не используют, в угоду `importlib.metadata` [#pytest-5537](https://github.com/pytest-dev/pytest/issues/5537))) ([#161](https://github.com/MarshalX/yandex-music-api/issues/161)).
|
||||
- Добавлен в зависимости для разработки `atomicwrites`, который используется `pytest` теперь только на `Windows` - [#pytest-6148](https://github.com/pytest-dev/pytest/pull/6148) ([#161](https://github.com/MarshalX/yandex-music-api/issues/161)).
|
||||
- Исправлен баг с передачей `timeout` аргумента в аргумент `params` в следующих методах: `artists`, `albums`, `playlists_list` ([#120](https://github.com/MarshalX/yandex-music-api/issues/120)).
|
||||
- Исправлена инициализация клиента при помощи логина и пароля с использованием прокси ([#159](https://github.com/MarshalX/yandex-music-api/issues/159)).
|
||||
- Исправлен баг в загрузке обложки альбома.
|
||||
|
||||
## Версия 0.0.15
|
||||
|
||||
**01.12.2019**
|
||||
|
||||
**Переломные изменения**
|
||||
|
||||
- У классов `Artist`, `Track` и `Playlist` изменился перечень полей для генерации хеша.
|
||||
|
||||
**Крупные изменения**
|
||||
|
||||
- **Добавлена возможность выполнять запросы через прокси-сервер для использовании библиотеки на зарубежных серверах ([#139](https://github.com/MarshalX/yandex-music-api/issues/139))**.
|
||||
- Добавлен пример использования в `README`.
|
||||
- **Добавлена обработка капчи при авторизации с возможностью использования callback-функции для её обработки ([#140](https://github.com/MarshalX/yandex-music-api/issues/140))**:
|
||||
- **Новые исключения**:
|
||||
- **Captcha**:
|
||||
- CaptchaRequired.
|
||||
- CaptchaWrong.
|
||||
- **Новые классы**:
|
||||
- CaptchaResponse.
|
||||
- **Новые примеры в `README`**:
|
||||
- Пример обработки с использованием callback-функции.
|
||||
- Пример полностью своей обработки капчи.
|
||||
- Добавлена документация для класса `Search` ([#83](https://github.com/MarshalX/yandex-music-api/issues/83)).
|
||||
- **Добавлена возможность получения всех альбомов исполнителя ([#141](https://github.com/MarshalX/yandex-music-api/issues/141))**:
|
||||
- **Новые классы**:
|
||||
- ArtistAlbums.
|
||||
- **Новые методы**:
|
||||
- `artists_direct_albums` у `Client`.
|
||||
- `get_albums` у `Artist`.
|
||||
- **Добавлена обработка несуществующего плейлиста ([#147](https://github.com/MarshalX/yandex-music-api/issues/147))**:
|
||||
- **Новые классы**:
|
||||
- `PlaylistAbsence`.
|
||||
|
||||
**Незначительные изменения и/или исправления**
|
||||
|
||||
- Исправлен баг с загрузкой файлов ([#149](https://github.com/MarshalX/yandex-music-api/issues/149)).
|
||||
- Исправлен баг некорректной десериализации плейлиста при отсутствии прав на него ([#147](https://github.com/MarshalX/yandex-music-api/issues/147)).
|
||||
- Исправлен баг неправильной десериализации треков и артистов у собственных загруженных файлов ([#154](https://github.com/MarshalX/yandex-music-api/issues/154)).
|
||||
|
||||
## Версия 0.0.14
|
||||
|
||||
**10.11.2019**
|
||||
|
||||
**Переломные изменения**
|
||||
|
||||
- Практически у всех классов был обновлён список полей участвующих при сравнении объектов.
|
||||
- Если в атрибутах для сравнения объектов присутствуют списки, то они будут преобразованы к frozenset.
|
||||
- Убрано конвертирование даты из строки в объект. Теперь все даты представлены строками в ISO формате.
|
||||
- Классы `AlbumSearchResult`, `ArtistSearchResult`, `PlaylistSearchResult`, `TrackSearchResult`, `VideoSearchResult` были объединены в один – `SearchResult`.
|
||||
|
||||
**Крупные изменения**
|
||||
|
||||
- Добавлен метод получения треков исполнителя ([#123](https://github.com/MarshalX/yandex-music-api/pull/123)).
|
||||
- Добавлены классы-обёртки над пагинацией (`Pager`) и списка треков артиста (`ArtistsTracks`).
|
||||
- Добавлено **554** unit-теста для всех классов-обёрток над объектами API.
|
||||
- Добавлен codecov и workflows для GitHub Actions.
|
||||
|
||||
**Незначительные изменения и/или исправления**
|
||||
|
||||
- Поле `cover_uri` класса `Album` теперь опциональное.
|
||||
- Поле `region` у класса `Account` теперь не обязательное.
|
||||
- Исправлен баг в `.to_dict()` методе, связанный с десериализацией объектов списков и словарей.
|
||||
- Исправлен баг в `.to_dict()` методе, связанный с не рекурсивной десериализацией.
|
||||
- Исправлена десериализация `similar_artists` в `BriefInfo`.
|
||||
- Исправлен баг с десериализацией `artist` в классе `ArtistEvent`.
|
||||
- Исправлен баг десериализации списка альбомов и артистов у класса `Track` ([#122](https://github.com/MarshalX/yandex-music-api/pull/122)).
|
||||
- Исправлена загрузка обложки у трека.
|
||||
- Исправлены сравнения объектов.
|
400
CHANGES.rst
400
CHANGES.rst
|
@ -1,400 +0,0 @@
|
|||
================
|
||||
Список изменений
|
||||
================
|
||||
|
||||
Версия 2.0.0
|
||||
============
|
||||
|
||||
**23.02.2022**
|
||||
|
||||
**Поддержка asyncio и модели на dataclasses**
|
||||
|
||||
**Переломные изменения**
|
||||
|
||||
- Убрана поддержка ``Python 3.6``.
|
||||
- Удалено получение авторизационного токена по логину и паролю (метод ``from_credentials`` класса ``Client``).
|
||||
- Удалена возможность задать свой обработчик на полученные неизвестные поля от API (аргумент ``report_new_fields_callback`` конструктора класса ``Client``.
|
||||
- Удалён аргумент ``fetch_account_status`` из конструктора класса ``Client``. Теперь необходимо вызывать метод ``init`` для получения ID аккаунта который будет использоваться в последующих запросах. В противном случае, передача ``user_id`` при вызове многих методов класса ``Client`` становится обязательной.
|
||||
- Исключение ``BadRequest`` переименовано в ``BadRequestError``.
|
||||
- Исключение ``Unauthorized`` переименовано в ``UnauthorizedError``.
|
||||
- Исключение ``InvalidBitrate`` переименовано в ``InvalidBitrateError``.
|
||||
- Исключение ``TimedOut`` переименовано в ``TimedOutError``.
|
||||
- Свойство ``result`` класса ``Response`` удалено. Вместо него добавлен метод ``get_result``.
|
||||
- Свойство ``error`` класса ``Response`` удалено. Вместо него добавлен метод ``get_error``.
|
||||
- В JSON представлении моделей к полям, чьё имя совпадает с именем стандартных функций, больше не добавляется нижнее подчеркивание в конец (пример: ``id``, а не ``id_``; ``max``, а не ``max_``). Теперь нижнее подчеркивание добавляется только к зарезервированным словам (пример: ``from`` будет ``from_``).
|
||||
|
||||
**Крупные изменения**
|
||||
|
||||
- Добавлена асинхронная версия клиента и всех методов-сокращений (класс ``ClientAsync``).
|
||||
- Добавлено новое исключение ``NotFoundError`` (наследник ``NetworkError``). Будет сгенерировано при получении статус кода 404.
|
||||
- Проект больше не использует ``pipenv``.
|
||||
- Зависимости проекта больше не требуют конкретных версий.
|
||||
- Для генерации исходных файлов ``Sphinx`` теперь используется ``sphinx-apidoc``.
|
||||
|
||||
**Незначительные изменения и/или исправления**
|
||||
|
||||
- Исправлена обработка серверных ошибок которые вернулись в отличном от JSON формате.
|
||||
- Исправлена обработка серверных ошибок метода ``search`` класса ``Client``.
|
||||
- Предупреждения о пришедших неизвестных полях от API отключены по умолчанию.
|
||||
- Используется английская локализация ``Sphinx``.
|
||||
- Изменена тема документации.
|
||||
|
||||
Версия 1.0.0
|
||||
============
|
||||
|
||||
**06.02.2021**
|
||||
|
||||
**Стабильная версия библиотеки**
|
||||
|
||||
**Переломные изменения**
|
||||
|
||||
- Поле ``error`` класса ``Artist`` теперь называется ``reason``.
|
||||
- Метод ``users_playlists`` класса ``Client`` теперь возвращает один объект плейлиста, когда был передан один ``kind``. При передаче списка в ``kind`` вернётся список плейлистов (`#318`_).
|
||||
- Поле ``labels`` класса ``Album`` теперь может содержать список из строк, а не только список объектов класса ``Label``.
|
||||
|
||||
**Крупные изменения**
|
||||
|
||||
- Добавлены примеры в папку ``examples``.
|
||||
- Добавлена поддержка рекомендаций для плейлистов (`#324`_):
|
||||
- Добавлен класс ``PlaylistRecommendations``.
|
||||
- Добавлен метод клиента для получения рекомендаций (``users_playlists_recommendations``).
|
||||
- Добавлен метод ``get_recommendations`` классу ``Playlist`` для получения рекомендаций.
|
||||
- Добавлено получение чартов (`#294`_):
|
||||
- Добавлены новые классы: ``ChartInfo``, ``ChartInfoMenu``, ``ChartInfoMenuItem``.
|
||||
- Добавлен метод клиента для получения чарта (``chart``).
|
||||
- Добавлена поддержка тегов/подборок (`#192`_):
|
||||
- Добавлены новые классы: ``TagResult``, ``Tag``.
|
||||
- Добавлен новый метод клиента для получения тегов (``tags``).
|
||||
- Добавлено присоединение к коллективному плейлисту (`#317`_):
|
||||
- Добавлен новый метод клиента для присоединения (``playlists_collective_join``).
|
||||
- Добавлена поддержка очередей прослушивания (`#246`_):
|
||||
- Добавлены новые классы: ``Context``, ``Queue``, ``QueueItem``.
|
||||
- Добавлены новые методы в ``Client``: ``queues_list``, ``queue``, ``queue_update_position``, ``queue_create``.
|
||||
- Добавлены поля ``track_id`` и ``from_`` в класс ``TrackId``.
|
||||
- Добавлена возможность смены языка у клиента для ответов от API.
|
||||
- Добавлена десериализация любого объекта в ``JSON`` пригодного для отправки в запросе на Яндекс API.
|
||||
- Добавлены следующие методы для ``Client``:
|
||||
- ``new_releases`` – получение полного списка всех новых релизов.
|
||||
- ``new_playlists`` – получение полного списка всех новый плейлистов.
|
||||
- ``podcasts`` – получение подкаста с лендинга.
|
||||
- Добавлены новые сокращения в модели:
|
||||
- ``download_cover_white``, ``download_cover_uri`` в ``MixLink``.
|
||||
- ``download_image`` в ``Promotion``.
|
||||
- ``artists_name`` в ``Album`` и ``Track``.
|
||||
- ``fetch_track``, ``track_full_id`` в ``TrackId``.
|
||||
- ``fetch_tracks`` в ``TracksList``.
|
||||
- ``insert_track``, ``delete_tracks``, ``delete`` в ``Playlist``.
|
||||
- ``playlist_id``, ``fetch_playlist`` в ``PlaylistId``.
|
||||
- ``get_current_track`` в ``Queue``.
|
||||
- ``fetch_queue`` в ``QueueItem``.
|
||||
- ``next_page``, ``get_page``, ``prev_page`` в ``Search``.
|
||||
- и другие...
|
||||
- Добавлена поддержка новых типов поиска: подкасты, выпуски, пользователи.
|
||||
- Добавлен коллбек для обработки новых полей.
|
||||
- Добавлена информацию по поводу запуска потока по треку, плейлисту и др.
|
||||
- Добавлена десериализация ``decomposed`` у ``Artist`` (`#10`_).
|
||||
- Добавлен ``__len__`` для ``TracksList`` (`#380`_).
|
||||
- Добавлены ``__iter__``, ``__len__`` и ``__getitem__`` для классов представляющих список каких-либо объектов.
|
||||
- Добавлено сокращение ``fetch_tracks`` классу ``Playlist`` для получения треков плейлиста.
|
||||
- Добавлен метод ``get_url`` классу ``Icon`` для получения прямой ссылки на изображение.
|
||||
- Класс ``User`` расширен для поддержки поля ``user_info`` из ``Track`` (поля ``full_name``, ``display_name``).
|
||||
- Добавлены новые классы по отчётам с Telegram бота (`#306`_, `#398`_):
|
||||
- ``LandingList``.
|
||||
- ``RenewableRemainder``.
|
||||
- ``Alert``.
|
||||
- ``AlertButton``.
|
||||
- ``StationData``.
|
||||
- ``Brand``.
|
||||
- ``Contest``.
|
||||
- ``OpenGraphData``.
|
||||
- ``NonAutoRenewable``.
|
||||
- ``Operator``.
|
||||
- ``Deactivation``.
|
||||
- ``PoetryLoverMatch``.
|
||||
- ``Deprecation``.
|
||||
- Добавлены новые поля классам по отчётам с Telegram бота (`#306`_, `#398`_):
|
||||
- ``plus`` в ``Product``.
|
||||
- ``non_auto_renewable_remainder`` в ``Subscription``.
|
||||
- ``og_image`` в ``Artist``.
|
||||
- ``meta_type`` в ``Album``.
|
||||
- ``advertisement`` в ``Status``.
|
||||
- ``best`` в ``Track``.
|
||||
- ``offer_id`` и ``artist_ids`` в ``Vinyl``.
|
||||
- ``playlists`` в ``BriefInfo``.
|
||||
- ``is_custom`` в ``Cover``.
|
||||
- ``play_count``, ``recent``, ``chart``, ``track`` в ``TrackShort``.
|
||||
- ``url_part``, ``og_title``, ``image``, ``cover_without_text``, ``background_color``, ``text_color``, ``id_for_from``, ``similar_playlists``, ``last_owner_playlists`` в ``Playlist``.
|
||||
- ``bg_color`` в ``Chart``.
|
||||
- ``error`` в ``Artist``.
|
||||
- ``substituted``, ``matched_track``, ``can_publish``, ``state``, ``desired_visibility``, ``filename``, ``user_info``, ``meta_data`` в ``Track``.
|
||||
- ``copyright_name``, ``copyright_cline`` в ``Cover``.
|
||||
- ``direct`` в ``DownloadInfo``.
|
||||
- ``cheapest``, ``title``, ``family_sub``, ``fb_image``, ``fb_name``, ``family``, ``intro_period_duration``, ``intro_price``, ``start_period_duration``, ``start_price``, ``licence_text_parts`` в ``Product``.
|
||||
- ``storage_dir``, ``duplicates`` в ``Album``.
|
||||
- ``subscribed`` в ``ArtistEvent``.
|
||||
- ``description`` в ``GeneratedPlaylist``.
|
||||
- ``genre`` в ``Event``.
|
||||
- ``show_in_regions`` в ``Genre``.
|
||||
- ``cover_uri`` в ``MixLink``.
|
||||
- ``og_description``, ``top_artist`` в ``Playlist``.
|
||||
- ``full_image_url``, ``mts_full_image_url`` в ``Station``.
|
||||
- ``coauthors`` и ``recent_tracks`` в ``Playlist``.
|
||||
- ``regions`` в ``User``.
|
||||
- ``users``, ``podcasts``, ``podcast_episodes``, ``type_``, ``page``, ``per_page`` в ``Search``.
|
||||
- ``short_description``, ``description``, ``is_premiere``, ``is_banner`` в ``Like``.
|
||||
- ``master_info`` в ``AutoRenewable``.
|
||||
- ``station_data`` и ``bar_below`` в ``Status``.
|
||||
- ``family_auto_renewable`` в ``Subscription``.
|
||||
- ``misspell_result`` и ``misspell_original`` в ``Search``.
|
||||
- ``experiment`` в класс ``Status``.
|
||||
- ``operator`` и ``non_auto_renewable`` в ``Subscription``.
|
||||
- ``text_color``, ``short_description``, ``description``, ``is_premiere`` и ``is_banner`` в ``Album``.
|
||||
- ``hand_made_description`` в ``Artist``.
|
||||
- ``metrika_id`` в ``Playlist``.
|
||||
- ``og_image`` в ``Tag``.
|
||||
- ``url`` в ``Lyrics``.
|
||||
- ``number``, ``genre`` в ``MetaData``.
|
||||
- ``poetry_lover_matches`` в ``Track``.
|
||||
- ``contest``, ``dummy_description``, ``dummy_page_description``, ``dummy_cover``, ``dummy_rollover_cover``, ``og_data``, ``branding`` в ``Playlist``.
|
||||
- ``available_as_rbt``, ``lyrics_available``, ``remember_position``, ``albums``, ``duration_ms``, ``explicit``, ``start_date``, ``likes_count``, ``deprecation`` в ``Album``.
|
||||
- ``lyricist``, ``version``, ``composer`` в ``MetaData``.
|
||||
- ``last_releases`` в ``BriefInfo``.
|
||||
- ``ya_money_id`` в ``Artist`` (`#351`_, `#370`_).
|
||||
- ``playlist_uuid`` в ``Playlist``.
|
||||
- ``sync_queue_enabled`` в ``UserSettings``.
|
||||
- ``background_video_uri``, ``short_description``, ``is_suitable_for_children`` в ``Track`` (`#376`_).
|
||||
- ``meta_type``, ``likes_count`` в ``Album`` (`#386`_).
|
||||
- ``deprecation`` в ``Album``.
|
||||
- ``available_regions`` в ``Album``.
|
||||
- ``type``, ``ready`` в ``Playlist``.
|
||||
- ``description`` в ``Supplement``.
|
||||
|
||||
**Незначительные изменения и/или исправления**
|
||||
|
||||
- Добавлена опциональность следующим полям:
|
||||
- все поля в ``MetaData``.
|
||||
- ``advertisement`` в ``Status``.
|
||||
- ``text_language`` в ``Lyrics``.
|
||||
- ``provider_video_id`` в ``VideoSupplement``.
|
||||
- ``title`` в ``VideoSupplement`` (`#403`_).
|
||||
- ``instructions`` в ``Deactivation`` (`#402`_).
|
||||
- ``id`` в ``Album`` (`#401`_).
|
||||
- Исправлена десериализация подкастов, эпизодов подкастов и пользователей в лучшем результате поиска.
|
||||
- Исправлена десериализация альбомов. В зависимости от запроса содержимое лейблов может быть списком объектом или списком строк (в поиске).
|
||||
- Исправлен выбор настроек радио.
|
||||
- Исправлены ошибки в документации.
|
||||
- Протестирована работа на Python 3.9.
|
||||
|
||||
.. _`#318`: https://github.com/MarshalX/yandex-music-api/issues/318
|
||||
.. _`#306`: https://github.com/MarshalX/yandex-music-api/issues/306
|
||||
.. _`#324`: https://github.com/MarshalX/yandex-music-api/issues/324
|
||||
.. _`#294`: https://github.com/MarshalX/yandex-music-api/issues/294
|
||||
.. _`#192`: https://github.com/MarshalX/yandex-music-api/issues/192
|
||||
.. _`#317`: https://github.com/MarshalX/yandex-music-api/issues/317
|
||||
.. _`#10`: https://github.com/MarshalX/yandex-music-api/issues/10
|
||||
.. _`#386`: https://github.com/MarshalX/yandex-music-api/issues/386
|
||||
.. _`#246`: https://github.com/MarshalX/yandex-music-api/issues/246
|
||||
.. _`#376`: https://github.com/MarshalX/yandex-music-api/issues/376
|
||||
.. _`#351`: https://github.com/MarshalX/yandex-music-api/issues/351
|
||||
.. _`#370`: https://github.com/MarshalX/yandex-music-api/issues/370
|
||||
.. _`#380`: https://github.com/MarshalX/yandex-music-api/issues/380
|
||||
.. _`#398`: https://github.com/MarshalX/yandex-music-api/issues/398
|
||||
.. _`#401`: https://github.com/MarshalX/yandex-music-api/issues/401
|
||||
.. _`#402`: https://github.com/MarshalX/yandex-music-api/issues/402
|
||||
.. _`#403`: https://github.com/MarshalX/yandex-music-api/issues/403
|
||||
|
||||
Версия 0.1.1
|
||||
============
|
||||
|
||||
**25.03.2020**
|
||||
|
||||
**Закончено документирование всех классов и основных методов!**
|
||||
|
||||
**Переломные изменения**
|
||||
|
||||
- Классы отметок "мне нравится" для альбомов, плейлистов и исполнителей обобщены. Теперь представлены одним классом.
|
||||
- Удаленные классы:
|
||||
- ``ArtistsLikes``.
|
||||
- ``AlbumsLikes``.
|
||||
- ``PlaylistsLikes``.
|
||||
- Новый класс: ``Like`` (поле ``type`` для определения содержимого).
|
||||
- Изменено название пакета с ``status`` на ``account`` (`#195`_).
|
||||
- Исправлено выбрасываемое исключение при таймауте:
|
||||
- Прошлое исключение: ``TimeoutError`` (built-in).
|
||||
- Новое исключение: ``TimedOut`` (``yandex_music.exceptions``).
|
||||
- Удалены следующие файлы: ``requirements.txt``, ``requirements-dev.txt``, ``requirements-docs.txt``.
|
||||
|
||||
**Крупные изменения**
|
||||
|
||||
- Добавлено обнаружение новых полей с просьбой сообщить о них (`#216`_).
|
||||
- Добавлена проверка на неизвестные поля.
|
||||
- Добавлен вывод отладочной информации в виде warning'a.
|
||||
- Добавлен шаблон issue для отправки логов.
|
||||
- Добавлено поле ``type`` для класса ``SearchResult`` для определения типа результата поиска по объекту.
|
||||
- Добавлены настройки пользователя (`#195`_):
|
||||
- Добавлен класс ``UserSettings``.
|
||||
- Добавлен метод для получения своих настроек (``account_settings``).
|
||||
- Добавлен метод для получения настроек другого пользователя (``users_settings``).
|
||||
- Добавлен метод для изменения настроек (``account_settings_set``).
|
||||
- Добавлен возможность получить похожие треки (`#197`_):
|
||||
- Добавлен класс ``TracksSimilar`` с полями трека и списка похожих треков.
|
||||
- Добавлен метод для получения похожих треков (``tracks_similar``).
|
||||
- Добавлены шоты от Алисы (`#185`_):
|
||||
- Добавлен метод ``after_track`` в класс ``Client`` для получения контента для воспроизведения после трека (реклама, шот).
|
||||
- Добавлены методы для загрузки обложки и аудиоверсии шота.
|
||||
- Добавлены новые классы:
|
||||
- ``Shot``
|
||||
- ``ShotData``
|
||||
- ``ShotEvent``
|
||||
- ``ShotType``
|
||||
- Добавлен метод для изменения видимости плейлиста (`#179`_).
|
||||
- Добавлена поддержка Яндекс.Радио (`#20`_):
|
||||
- Исправлена отправка фидбека.
|
||||
- Написана инструкция по использованию (в доке к методу).
|
||||
- Добавлен аргумент для перехода по цепочке треков.
|
||||
- Добавлен метод для изменения настроек станции.
|
||||
|
||||
**Незначительные изменения и/или исправления**
|
||||
|
||||
- Убрано дублирование информации в документации (`#247`_).
|
||||
- Добавлены новые поля в класс ``Track``: ``version``, ``remember_position`` (`#238`_).
|
||||
- Добавлено исключение ``InvalidBitrate`` при попытке загрузить недопустимый трек по критериям (кодек, битрейт).
|
||||
- Исправлено получение прямой ссылки на файл с кодеком AAC (`#237`_, `#25`_).
|
||||
- Исправлено получение плейлиста с Алисой в лендинге (`#185`_).
|
||||
- Исправлено название поля с ссылкой на источник в классе ``Description`` (с ``url`` на ``uri``).
|
||||
- Исправлена десериализация несуществующего исполнителя.
|
||||
- Добавлено поле ``version`` в класс ``Album`` (`#178`_).
|
||||
- Поле ``picture`` класса ``Vinyl`` теперь опциональное.
|
||||
- Поле ``week`` класса ``Ratings`` теперь опциональное.
|
||||
- Поле ``product_id`` класса ``AutoRenewable`` теперь опциональное (`#182`_).
|
||||
- Правки замечаний по codacy.
|
||||
|
||||
.. _`#216`: https://github.com/MarshalX/yandex-music-api/issues/216
|
||||
.. _`#247`: https://github.com/MarshalX/yandex-music-api/issues/247
|
||||
.. _`#237`: https://github.com/MarshalX/yandex-music-api/issues/237
|
||||
.. _`#25`: https://github.com/MarshalX/yandex-music-api/issues/25
|
||||
.. _`#238`: https://github.com/MarshalX/yandex-music-api/issues/238
|
||||
.. _`#182`: https://github.com/MarshalX/yandex-music-api/issues/182
|
||||
.. _`#195`: https://github.com/MarshalX/yandex-music-api/issues/195
|
||||
.. _`#197`: https://github.com/MarshalX/yandex-music-api/issues/197
|
||||
.. _`#20`: https://github.com/MarshalX/yandex-music-api/issues/20
|
||||
.. _`#185`: https://github.com/MarshalX/yandex-music-api/issues/185
|
||||
.. _`#179`: https://github.com/MarshalX/yandex-music-api/issues/179
|
||||
.. _`#178`: https://github.com/MarshalX/yandex-music-api/issues/178
|
||||
|
||||
Версия 0.0.16
|
||||
=============
|
||||
|
||||
**29.12.2019**
|
||||
|
||||
**Переломные изменения**
|
||||
|
||||
- Поле ``account`` переименовано в ``me`` и теперь содержит объект ``Status``, вместо ``Account`` (`#162`_).
|
||||
- Убрано использование зарезервированных имён в аргументах конструкторов (теперь они с ``_`` на конце). Имена с нижними подчёркиваниями есть как при сериализации так и при десериализации (`#168`_).
|
||||
|
||||
**Крупные изменения**
|
||||
|
||||
- **Добавлены аннотации типов во всей библиотеке!**
|
||||
|
||||
**Незначительные изменения и/или исправления**
|
||||
|
||||
- Добавлен аргумент ``fetch_account_status`` для опциональности получения информации об аккаунте при инициализации клиента (`#162`_).
|
||||
- Добавлены тесты c передачей пустого словаря в ``de_json`` и ``de_list`` (`#174`_).
|
||||
- Использование ``ujson`` при наличии, обновлены зависимости (`#161`_).
|
||||
- Добавлен в зависимости для разработки ``importlib_metadata`` для поддержки старых версий (в новой версии ``pytest`` его больше не используют, в угоду ``importlib.metadata`` `#pytest-5537`_)) (`#161`_).
|
||||
- Добавлен в зависимости для разработки ``atomicwrites``, который используется ``pytest`` теперь только на ``Windows`` - `#pytest-6148`_ (`#161`_).
|
||||
- Исправлен баг с передачей ``timeout`` аргумента в аргумент ``params`` в следующих методах: ``artists``, ``albums``, ``playlists_list`` (`#120`_).
|
||||
- Исправлена инициализация клиента при помощи логина и пароля с использованием прокси (`#159`_).
|
||||
- Исправлен баг в загрузке обложки альбома.
|
||||
|
||||
.. _`#162`: https://github.com/MarshalX/yandex-music-api/issues/162
|
||||
.. _`#161`: https://github.com/MarshalX/yandex-music-api/issues/161
|
||||
.. _`#159`: https://github.com/MarshalX/yandex-music-api/issues/159
|
||||
.. _`#168`: https://github.com/MarshalX/yandex-music-api/issues/168
|
||||
.. _`#120`: https://github.com/MarshalX/yandex-music-api/issues/120
|
||||
.. _`#174`: https://github.com/MarshalX/yandex-music-api/issues/174
|
||||
.. _`#pytest-5537`: https://github.com/pytest-dev/pytest/issues/5537
|
||||
.. _`#pytest-6148`: https://github.com/pytest-dev/pytest/pull/6148
|
||||
|
||||
Версия 0.0.15
|
||||
=============
|
||||
|
||||
**01.12.2019**
|
||||
|
||||
**Переломные изменения**
|
||||
|
||||
- У классов ``Artist``, ``Track`` и ``Playlist`` изменился перечень полей для генерации хеша.
|
||||
|
||||
**Крупные изменения**
|
||||
|
||||
- Добавлена возможность выполнять запросы через прокси-сервер для использовании библиотеки на зарубежных серверах (`#139`_).
|
||||
- Добавлен пример использования в ``README``.
|
||||
- Добавлена обработка капчи при авторизации с возможностью использования callback-функции для её обработки (`#140`_):
|
||||
- Новые исключения:
|
||||
- Captcha:
|
||||
- CaptchaRequired.
|
||||
- CaptchaWrong.
|
||||
- Новые классы:
|
||||
- CaptchaResponse.
|
||||
- Новые примеры в ``README``:
|
||||
- Пример обработки с использованием callback-функции.
|
||||
- Пример полностью своей обработки капчи.
|
||||
- Добавлена документация для класса ``Search`` (`#83`_).
|
||||
- Добавлена возможность получения всех альбомов исполнителя (`#141`_):
|
||||
- Новые классы:
|
||||
- ArtistAlbums.
|
||||
- Новые методы:
|
||||
- ``artists_direct_albums`` у ``Client``.
|
||||
- ``get_albums`` у ``Artist``.
|
||||
- Добавлена обработка несуществующего плейлиста (`#147`_):
|
||||
- Новые классы:
|
||||
- ``PlaylistAbsence``.
|
||||
|
||||
**Незначительные изменения и/или исправления**
|
||||
|
||||
- Исправлен баг с загрузкой файлов (`#149`_).
|
||||
- Исправлен баг некорректной десериализации плейлиста при отсутствии прав на него (`#147`_).
|
||||
- Исправлен баг неправильной десериализации треков и артистов у собственных загруженных файлов (`#154`_).
|
||||
|
||||
.. _`#139`: https://github.com/MarshalX/yandex-music-api/issues/139
|
||||
.. _`#140`: https://github.com/MarshalX/yandex-music-api/issues/140
|
||||
.. _`#83`: https://github.com/MarshalX/yandex-music-api/issues/83
|
||||
.. _`#141`: https://github.com/MarshalX/yandex-music-api/issues/141
|
||||
.. _`#149`: https://github.com/MarshalX/yandex-music-api/issues/149
|
||||
.. _`#147`: https://github.com/MarshalX/yandex-music-api/issues/147
|
||||
.. _`#154`: https://github.com/MarshalX/yandex-music-api/issues/154
|
||||
|
||||
Версия 0.0.14
|
||||
=============
|
||||
|
||||
**10.11.2019**
|
||||
|
||||
**Переломные изменения**
|
||||
|
||||
- Практически у всех классов был обновлён список полей участвующих при сравнении объектов.
|
||||
- Если в атрибутах для сравнения объектов присутствуют списки, то они будут преобразованы к frozenset.
|
||||
- Убрано конвертирование даты из строки в объект. Теперь все даты представлены строками в ISO формате.
|
||||
- Классы ``AlbumSearchResult``, ``ArtistSearchResult``, ``PlaylistSearchResult``, ``TrackSearchResult``, ``VideoSearchResult`` были объединены в один - ``SearchResult``.
|
||||
|
||||
**Крупные изменения**
|
||||
|
||||
- Добавлен метод получения треков исполнителя (`#123`_).
|
||||
- Добавлены классы-обёртки над пагинацией (``Pager``) и списка треков артиста (``ArtistsTracks``).
|
||||
- Добавлено **554** unit-теста для всех классов-обёрток над объектами API.
|
||||
- Добавлен codecov и workflows для GitHub Actions.
|
||||
|
||||
.. _`#123`: https://github.com/MarshalX/yandex-music-api/pull/123
|
||||
|
||||
**Незначительные изменения и/или исправления**
|
||||
|
||||
- Поле ``cover_uri`` класса ``Album`` теперь опциональное.
|
||||
- Поле ``region`` у класса ``Account`` теперь не обязательное.
|
||||
- Исправлен баг в ``.to_dict()`` методе, связанный с десериализцией объектов списков и словарей.
|
||||
- Исправлен баг в ``.to_dict()`` методе, связанный с не рекурсивной десериализацией.
|
||||
- Исправлена десериализация ``similar_artists`` в ``BriefInfo``.
|
||||
- Исправлен баг с десериализацией ``artist`` в классе ``ArtistEvent``.
|
||||
- Исправлен баг десериализации списка альбомов и артистов у класса ``Track`` (`#122`_).
|
||||
- Исправлена загрузка обложки у трека.
|
||||
- Исправлены сравнения объектов.
|
||||
|
||||
.. _`#122`: https://github.com/MarshalX/yandex-music-api/pull/122
|
108
CONTRIBUTING.md
108
CONTRIBUTING.md
|
@ -1,73 +1,97 @@
|
|||
# Как внести свой вклад
|
||||
|
||||
Внесение своего вклада в этот проект ничем не отличается внесения в других
|
||||
open source проекты на GitHub'e, но есть несколько ключевых моментов, о которых
|
||||
стоит знать и помнить.
|
||||
Внесение своего вклада в этот проект ничем не отличается внесения в других open source проекты на GitHub'e, но есть несколько ключевых моментов, о которых стоит знать и помнить.
|
||||
|
||||
Все необходимые пакеты для разработки есть в `requirements-dev.txt`.
|
||||
Установить их можно с помощью следующей команды:
|
||||
```
|
||||
Все необходимые пакеты для разработки есть в `requirements-dev.txt`. Установить их можно с помощью следующей команды:
|
||||
```shell
|
||||
pip install -r requirements-dev.txt
|
||||
```
|
||||
|
||||
Основные типы вклада:
|
||||
- добавление новых полей к существующим классам;
|
||||
- добавление новых классов;
|
||||
- добавление новых полей к существующим моделям;
|
||||
- добавление новых моделей;
|
||||
- установка опциональности какого-то поля;
|
||||
- добавление нового метода в `Client` (новый запрос на API);
|
||||
- добавление нового метода-сокращения в модель;
|
||||
- добавление примера использование в папку [examples](examples).
|
||||
|
||||
Ваш вклад может быть более грандиозным. Так, например, можно написать
|
||||
интеграционные тесты для класса `Client` и всех методов-сокращений в моделях.
|
||||
Написать тесты для класса запросов. Написать генератор кода для быстрого добавления
|
||||
новых полей.
|
||||
Ваш вклад может быть более грандиозным. Так, например, можно написать интеграционные тесты для класса `Client` и всех методов-сокращений в моделях. Написать тесты для класса запросов. Написать генератор кода для быстрого добавления новых полей.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
PR'ы должны быть сделаны в `dev` ветку. Определённого шаблона оформления
|
||||
нет. Если это закрывает какое-то issue, то стоит сослаться на него с ключевым
|
||||
словом "close". Например, "close #123". Обращайте внимание прошёл ли Ваш PR все
|
||||
проверки GitHub Actions (тесты, проверка стиля кода).
|
||||
PR'ы должны быть сделаны в `dev` ветку. Определённого шаблона оформления нет. Если это закрывает какое-то issue, то стоит сослаться на него с ключевым словом "close". Например, "close #123". Обращайте внимание прошёл ли Ваш PR все проверки GitHub Actions (тесты, проверка стиля кода).
|
||||
|
||||
## Тесты
|
||||
|
||||
Для тестирования используется `pytest`. На данный момент отсутствуют
|
||||
интеграционные тесты. Поэтому всё что сейчас
|
||||
требутеся — это юнит тесты классов-обёрток и их дополнительных методов
|
||||
(если имеются), которые не являются сокращениями для методов клиента.
|
||||
Для тестирования используется `pytest`. На данный момент отсутствуют интеграционные тесты. Поэтому всё что сейчас требуется — это модульные тесты классов-обёрток и их дополнительных методов (если имеются), которые не являются сокращениями для методов клиента.
|
||||
|
||||
Тесты модели должны покрывать 5 основных вещей: конструктор, десериализацию
|
||||
только обязательных полей, десериализацию всех полей, сравнение
|
||||
объектов модели, десериализацию пустого словаря. По необходимости десериализацию
|
||||
списка объектов.
|
||||
Тесты модели должны покрывать 5 основных вещей: конструктор, десериализацию только обязательных полей, десериализацию всех полей, сравнение объектов модели, десериализацию пустого словаря. По необходимости десериализацию списка объектов.
|
||||
|
||||
Примеры доступны в папке [tests](tests).
|
||||
|
||||
## Документация
|
||||
|
||||
Для документации используется `Sphinx`. Документация ведется в [google style](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html).
|
||||
К каждому классу и методу должна быть написана документация на русском языке.
|
||||
Типы каждого значения. По возможности описано предназначение поля. Если
|
||||
невозможно логически понять из названия — ставим `TODO`. Обязательно отмечаем
|
||||
какие поля являются опциональными. Пишем заметки по известным значениям полей,
|
||||
чтобы из контекста догадываться о предназначении.
|
||||
Для документации используется `Sphinx`. Документация ведется в [google style](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html). К каждому классу и методу должна быть написана документация на русском языке. Указаны типы каждого значения. По возможности описано предназначение поля. Если невозможно логически понять из названия или данных — ставим `TODO`. Обязательно отмечаем какие поля являются опциональными. Пишем заметки по известным значениям полей, чтобы из контекста догадываться о предназначении.
|
||||
|
||||
Если добавлен новый файл, то не забудьте сгенерировать `.rst` файл
|
||||
выполнив команду `make gen` в папке `docs` и добавить его в GIT.
|
||||
Если добавлен новый класс или функция, то не забудьте сгенерировать `.rst` файл выполнив команду `make gen` в папке `docs` и добавить его в GIT.
|
||||
|
||||
Чтобы собрать документацию выполните `make html` в папке `docs`.
|
||||
Чтобы собрать документацию выполните `make html` в папке `docs`. Для отчистки используйте `make clean`.
|
||||
|
||||
## Форматирование кода (стиль написания)
|
||||
|
||||
В проекте используется `black`. Не забывайте перед публикацией
|
||||
отформатировать код и проверить его на работоспособность.
|
||||
В проекте используется `black`. Не забывайте перед публикацией отформатировать код и проверить его на работоспособность. Используются одинарные кавычки. Запускайте `black` с конфигом из основной директории:
|
||||
|
||||
```shell
|
||||
black --config=black.toml yandex_music
|
||||
```
|
||||
|
||||
или
|
||||
|
||||
```shell
|
||||
make black
|
||||
```
|
||||
|
||||
## Создание новых моделей
|
||||
|
||||
Исходите из логики при помещении модели в определённый пакет.
|
||||
Добавьте свою модель для короткого импорта в одну из секций `__init__.py` файла
|
||||
и пропишите название в `__all__`. Обязательно следите за тем, какие поля
|
||||
присутствуют всегда, а какие нет. По возможности минимизируйте количество
|
||||
опциональных полей. Не забывайте перечислить поля, по которым можно отличить
|
||||
один объект от другого в `_id_attrs` (метод `__post_init__`). Экземпляр класса
|
||||
`Client` передаётся в каждую модель для возможности написания методов-сокращений.
|
||||
Исходите из логики при помещении модели в определённый пакет. Добавьте свою модель для короткого импорта в одну из секций `__init__.py` файла и пропишите название в `__all__`.
|
||||
|
||||
Обязательно следите за тем, какие поля присутствуют всегда, а какие нет. По возможности минимизируйте количество опциональных полей.
|
||||
|
||||
Не забывайте перечислить поля, по которым можно отличить один объект от другого в `_id_attrs` (метод `__post_init__`).
|
||||
|
||||
Экземпляр класса `Client` передаётся в каждую модель для возможности написания методов-сокращений. При наличии дополнительных методов у модели не забудьте создать асинхронную версию метода добавив в название суффикс `_async`.
|
||||
|
||||
Кроме этого, если у вашей модели есть метод, например, `download_async()`, то такому методу следует создать camel case псевдоним (`downloadAsync()`). Для создания псевдонимов существует генератор. Всё что вам надо сделать это поместить в конце файла с моделью маркер:
|
||||
|
||||
```
|
||||
# camelCase псевдонимы
|
||||
```
|
||||
([пример](https://github.com/MarshalX/yandex-music-api/blob/a30082f4929e56381c870cb03103777ae29bcc6b/yandex_music/tracks_list.py#L80))
|
||||
|
||||
После чего запустить генератор:
|
||||
```shell
|
||||
python generate_camel_case_aliases.py
|
||||
```
|
||||
|
||||
### Создание новых методов клиента
|
||||
|
||||
Если ваша задача включает добавление нового API метода, то не забудьте сгенерировать асинхронную версию клиента. Сделать это можно следующей командой:
|
||||
|
||||
```shell
|
||||
python generate_async_version.py
|
||||
```
|
||||
|
||||
Ни в коем случае не редактируйте файл `client_async.py` и `request_async.py` руками!
|
||||
|
||||
## Makefile
|
||||
|
||||
_Используйте WSL если вы на Windows._
|
||||
|
||||
Для упрощения жизни в корне проекта существует [Makefile](Makefile).
|
||||
|
||||
Команда
|
||||
```shell
|
||||
make all
|
||||
```
|
||||
|
||||
Выполнит за вас black для основного модуля и тестов, сгенерирует асинхронную версию клиента, сгенерирует camel case псевдонимы.
|
||||
|
|
|
@ -1 +1 @@
|
|||
include LICENSE CHANGES.rst
|
||||
include LICENSE CHANGES.md
|
|
@ -0,0 +1,31 @@
|
|||
# makefile for Yandex Music project
|
||||
|
||||
black:
|
||||
black --config=black.toml yandex_music
|
||||
|
||||
black_test:
|
||||
black --config=black.toml tests
|
||||
|
||||
gen_async:
|
||||
python generate_async_version.py
|
||||
|
||||
gen_alias:
|
||||
python generate_camel_case_aliases.py
|
||||
|
||||
gen:
|
||||
make gen_async && make gen_alias
|
||||
|
||||
b:
|
||||
make black
|
||||
|
||||
bt:
|
||||
make black_test
|
||||
|
||||
g:
|
||||
make gen
|
||||
|
||||
all:
|
||||
make g && make b && make bt
|
||||
|
||||
a:
|
||||
make all
|
|
@ -0,0 +1,301 @@
|
|||
## Yandex Music API
|
||||
|
||||
> Делаю то, что по определённым причинам не сделала компания Yandex.
|
||||
|
||||
⚠️ Это неофициальная библиотека.
|
||||
|
||||
Сообщество разработчиков общаются и помогают друг другу в [Telegram чате](https://t.me/yandex_music_api), присоединяйтесь!
|
||||
|
||||
[![Поддерживаемые Python версии](https://img.shields.io/badge/python-3.7+-blue.svg)](https://pypi.org/project/yandex-music/)
|
||||
[![Покрытие кода тестами](https://codecov.io/gh/MarshalX/yandex-music-api/branch/main/graph/badge.svg)](https://codecov.io/gh/MarshalX/yandex-music-api)
|
||||
[![Качество кода](https://api.codacy.com/project/badge/Grade/27011a5a8d9f4b278d1bfe2fe8725fed)](https://app.codacy.com/gh/MarshalX/yandex-music-api)
|
||||
[![Статус тестов](https://github.com/MarshalX/yandex-music-api/actions/workflows/pytest_full.yml/badge.svg)](https://github.com/MarshalX/yandex-music-api/actions/workflows/pytest_full.yml)
|
||||
[![Статус документации](https://readthedocs.org/projects/yandex-music/badge/?version=latest)](https://yandex-music.readthedocs.io/en/latest/?badge=latest)
|
||||
[![Лицензия LGPLv3](https://img.shields.io/badge/license-LGPLv3-lightgrey.svg)](https://www.gnu.org/licenses/lgpl-3.0.html)
|
||||
|
||||
### Содержание
|
||||
- [Введение](#введение)
|
||||
1. [Доступ к вашим данным Яндекс.Музыка](#доступ-к-вашим-данным-яндексмузыка)
|
||||
- [Установка](#установка)
|
||||
- [Начало работы](#начало-работы)
|
||||
1. [Изучение по примерам](#изучение-по-примерам)
|
||||
2. [Особенности использования асинхронного клиента](#особенности-использования-асинхронного-клиента)
|
||||
3. [Логирование](#логирование)
|
||||
4. [Документация](#документация)
|
||||
- [Получение помощи](#получение-помощи)
|
||||
- [Список изменений](#список-изменений)
|
||||
- [Реализации на других языках](#реализации-на-других-языках)
|
||||
1. [C#](#c)
|
||||
2. [PHP](#php)
|
||||
3. [JavaScript](#javascript)
|
||||
- [Разработанные проекты](#разработанные-проекты)
|
||||
1. [Плагин для Kodi](#плагин-для-kodi)
|
||||
2. [Telegram бот-клиент](#telegram-бот-клиент)
|
||||
- [Благодарность](#благодарность)
|
||||
- [Внесение своего вклада в проект](#внесение-своего-вклада-в-проект)
|
||||
- [Спонсоры](#спонсоры)
|
||||
- [Лицензия](#лицензия)
|
||||
|
||||
### Введение
|
||||
|
||||
Эта библиотека предоставляется Python интерфейс для никем незадокументированного и сделанного только для себя API Яндекс Музыки.
|
||||
|
||||
Она совместима с версиями Python 3.7+ и поддерживает работу как с синхронном, так и асинхронным (asyncio) кодом.
|
||||
|
||||
В дополнение к реализации чистого API данная библиотека имеет ряд классов-обёрток объектов высокого уровня дабы сделать разработку клиентов и скриптов простой и понятной. Вся документация была написана с нуля исходя из логического анализа в ходе обратной разработки(reverse engineering) API.
|
||||
|
||||
#### Доступ к вашим данным Яндекс.Музыка
|
||||
|
||||
Начиная с версии [2.0.0](https://github.com/MarshalX/yandex-music-api/blob/a30082f4929e56381c870cb03103777ae29bcc6b/CHANGES.rst#%D0%B2%D0%B5%D1%80%D1%81%D0%B8%D1%8F-200) библиотека больше не предоставляет интерфейсы для работы с OAuth Яндекс и Яндекс.Паспорт. Задача по получению токена для доступа к данным на плечах разработчиков использующих данную библиотеку. О том как получить токен читайте в документации.
|
||||
|
||||
### Установка
|
||||
|
||||
Вы можете установить или обновить Yandex Music API при помощи:
|
||||
|
||||
``` shell
|
||||
pip install -U yandex-music
|
||||
```
|
||||
|
||||
Или Вы можете установить из исходного кода с помощью:
|
||||
|
||||
``` shell
|
||||
git clone https://github.com/MarshalX/yandex-music-api
|
||||
cd yandex-music-api
|
||||
python setup.py install
|
||||
```
|
||||
|
||||
### Начало работы
|
||||
|
||||
Приступив к работе первым делом необходимо создать экземпляр клиента.
|
||||
|
||||
Инициализация синхронного клиента:
|
||||
|
||||
``` python
|
||||
from yandex_music import Client
|
||||
|
||||
client = Client()
|
||||
client.init()
|
||||
|
||||
# или
|
||||
|
||||
client = Client().init()
|
||||
```
|
||||
|
||||
Инициализация асинхронного клиента:
|
||||
|
||||
``` python
|
||||
from yandex_music import ClientAsync
|
||||
|
||||
client = ClientAsync()
|
||||
await client.init()
|
||||
|
||||
# или
|
||||
|
||||
client = await Client().init()
|
||||
```
|
||||
|
||||
Вызов `init()` необходим для получение информации для упрощения будущих запросов.
|
||||
|
||||
Работа без авторизации ограничена. Так, например, для загрузки будут доступны только первые 30 секунд аудиофайла. Для понимания всех ограничений зайдите на сайт Яндекс.Музыка под инкогнито и воспользуйтесь сервисом.
|
||||
|
||||
Для доступа к своим личным данным следует авторизоваться. Это осуществляется через токен аккаунта Яндекс.Музыка.
|
||||
|
||||
Авторизация:
|
||||
|
||||
``` python
|
||||
from yandex_music import Client
|
||||
|
||||
client = Client('token').init()
|
||||
```
|
||||
|
||||
После успешного создания клиента Вы вольны в выборе необходимого метода из API. Все они доступны у объекта класса `Client`. Подробнее в методах клиента в [документации](https://yandex-music.readthedocs.io/en/latest/yandex_music.client.html).
|
||||
|
||||
Пример получения первого трека из плейлиста "Мне нравится" и его загрузка:
|
||||
|
||||
``` python
|
||||
from yandex_music import Client
|
||||
|
||||
client = Client('token').init()
|
||||
client.users_likes_tracks()[0].fetch_track().download('example.mp3')
|
||||
```
|
||||
|
||||
В примере выше клиент получает список треков которые были отмечены как понравившиеся. API возвращает объект [TracksList](https://yandex-music.readthedocs.io/en/latest/yandex_music.tracks_list.html) в котором содержится список с треками класса [TrackShort](https://yandex-music.readthedocs.io/en/latest/yandex_music.track_short.html). Данный класс содержит наиважнейшую информацию о треке и никаких подробностей, поэтому для получения полной версии трека со всей информацией необходимо обратиться к методу `fetch_track()`. Затем можно скачать трек методом `download()`.
|
||||
|
||||
Пример получения треков по ID:
|
||||
|
||||
``` python
|
||||
from yandex_music import Client
|
||||
|
||||
client = Client().init()
|
||||
client.tracks(['10994777:1193829', '40133452:5206873', '48966383:6693286', '51385674:7163467'])
|
||||
```
|
||||
|
||||
В качестве ID трека выступает его уникальный номер и номер альбома. Первым треком из примера является следующий трек:music.yandex.ru/album/**1193829**/track/**10994777**
|
||||
|
||||
Выполнение запросов с использование прокси в синхронной версии:
|
||||
|
||||
``` python
|
||||
from yandex_music.utils.request import Request
|
||||
from yandex_music import Client
|
||||
|
||||
request = Request(proxy_url='socks5://user:password@host:port')
|
||||
client = Client(request=request).init()
|
||||
```
|
||||
|
||||
Примеры proxy url:
|
||||
- socks5://user:<password@host>:port
|
||||
- <http://host:port>
|
||||
- <https://host:port>
|
||||
- <http://user:password@host>
|
||||
|
||||
Больше примеров тут: [proxies - advanced usage - requests](https://2.python-requests.org/en/master/user/advanced/#proxies)
|
||||
|
||||
Выполнение запросов с использование прокси в асинхронной версии:
|
||||
|
||||
``` python
|
||||
from yandex_music.utils.request_async import Request
|
||||
from yandex_music import ClientAsync
|
||||
|
||||
request = Request(proxy_url='http://user:pass@some.proxy.com')
|
||||
client = await ClientAsync(request=request).init()
|
||||
```
|
||||
|
||||
Socks прокси не поддерживаются в асинхронной версии.
|
||||
|
||||
Про поддерживаемые прокси тут: [proxy support - advanced usage - aiohttp](https://docs.aiohttp.org/en/stable/client_advanced.html#proxy-support)
|
||||
|
||||
#### Изучение по примерам
|
||||
|
||||
Вот несколько примеров для обзора. Даже если это не Ваш подход к обучению, пожалуйста, возьмите и бегло просмотрите их.
|
||||
|
||||
Код примеров опубликован в открытом доступе, поэтому Вы можете взять его и начать писать вокруг своё.
|
||||
|
||||
Посетите [эту страницу](https://github.com/MarshalX/yandex-music-api/blob/main/examples/), чтобы изучить официальные примеры.
|
||||
|
||||
#### Особенности использования асинхронного клиента
|
||||
|
||||
При работе с асинхронной версией библиотеке стоит всегда помнить
|
||||
следующие особенности:
|
||||
- Клиент следует импортировать с названием `ClientAsync`, а не просто `Client`.
|
||||
- При использовании методов-сокращений нужно выбирать метод с суффиксом `_async`.
|
||||
|
||||
Пояснение ко второму пункту:
|
||||
|
||||
``` python
|
||||
from yandex_music import ClientAsync
|
||||
|
||||
client = await ClientAsync('token').init()
|
||||
liked_short_track = (await client.users_likes_tracks())[0]
|
||||
|
||||
# правильно
|
||||
full_track = await liked_short_track.fetch_track_async()
|
||||
await full_track.download_async()
|
||||
|
||||
# НЕПРАВИЛЬНО
|
||||
full_track = await liked_short_track.fetch_track()
|
||||
await full_track.download()
|
||||
```
|
||||
|
||||
#### Логирование
|
||||
|
||||
Данная библиотека использует `logging` модуль. Чтобы настроить логирование на стандартный вывод, поместите
|
||||
|
||||
``` python
|
||||
import logging
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
```
|
||||
|
||||
в начало вашего скрипта.
|
||||
|
||||
Вы также можете использовать логирование в вашем приложении, вызвав `logging.getLogger()` и установить уровень какой Вы хотите:
|
||||
|
||||
``` python
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
```
|
||||
|
||||
Если Вы хотите `DEBUG` логирование:
|
||||
|
||||
``` python
|
||||
logger.setLevel(logging.DEBUG)
|
||||
```
|
||||
|
||||
### Документация
|
||||
|
||||
Документация `yandex-music-api` расположена на [readthedocs.io](https://yandex-music.readthedocs.io/). Вашей отправной точкой должен быть класс `Client`, а точнее его методы. Именно они выполняют все запросы на API и возвращают Вам готовые объекты. [Класс Client на readthedocs.io](https://yandex-music.readthedocs.io/en/latest/yandex_music.client.html).
|
||||
|
||||
### Получение помощи
|
||||
|
||||
Получить помощь можно несколькими путями:
|
||||
- Задать вопрос в [Telegram чате](https://t.me/yandex_music_api), где мы помогаем друг другу, присоединяйтесь\!
|
||||
- Сообщить о баге можно [создав Bug Report](https://github.com/MarshalX/yandex-music-api/issues/new?assignees=MarshalX&labels=bug&template=bug-report.md&title=).
|
||||
- Предложить новую фичу или задать вопрос можно [создав discussion](https://github.com/MarshalX/yandex-music-api/discussions/new).
|
||||
- Найти ответ на вопрос в [документации библиотеки](https://yandex-music.readthedocs.io/en/latest/).
|
||||
|
||||
### Список изменений
|
||||
|
||||
Весь список изменений ведётся в файле [CHANGES.md](https://github.com/MarshalX/yandex-music-api/blob/main/CHANGES.md).
|
||||
|
||||
### Реализации на других языках
|
||||
|
||||
#### C#
|
||||
|
||||
Реализация с совершенно другим подходом, так как используется API для frontend'a, а не мобильных и десктопных приложений: [Winster332/Yandex.Music.Api](https://github.com/Winster332/Yandex.Music.Api).
|
||||
|
||||
[@Winster332](https://github.com/Winster332) не сильно проявляет активность, но существует форк, который продолжил начатое. Эндпоинты изменены с фронтовых на мобильные: [K1llMan/Yandex.Music.Api](https://github.com/K1llMan/Yandex.Music.Api).
|
||||
|
||||
#### PHP
|
||||
|
||||
Частично переписанная текущая библиотека на PHP: [LuckyWins/yandex-music-api](https://github.com/LuckyWins/yandex-music-api).
|
||||
|
||||
#### JavaScript
|
||||
|
||||
API wrapper на Node.JS. Не обновлялся больше двух лет: [itsmepetrov/yandex-music-api](https://github.com/itsmepetrov/yandex-music-api). Продолжение разработки заброшенной библиотеки: [kontsevoye/ym-api](https://github.com/kontsevoye/ym-api).
|
||||
|
||||
### Разработанные проекты
|
||||
|
||||
#### Плагин для Kodi
|
||||
|
||||
Плагин может проигрывать пользовательские плейлисты и плейлисты Яндекса, поиск по Яндекс Музыке, радио.
|
||||
|
||||
Сайт проекта: [ymkodi.ru](https://ymkodi.ru/). Исходный код: [kodi.plugin.yandex-music](https://github.com/Angel777d/kodi.plugin.yandex-music).
|
||||
Автор: [@Angel777d](https://github.com/Angel777d).
|
||||
|
||||
[![Плагин для Kodi](https://raw.githubusercontent.com/Angel777d/kodi.plugin.yandex-music/master/assets/img/kody_yandex_music_plugin.png)](https://ymkodi.ru/)
|
||||
|
||||
#### Telegram бот-клиент
|
||||
|
||||
Неофициальный бот. Умные и ваши плейлисты, понравившиеся треки. Лайки, дизлайки, текста песен, поиск, распознавание песен, похожие треки! Полноценный клиент на базе мессенджера.
|
||||
|
||||
Сайт проекта: [music-yandex-bot.ru](https://music-yandex-bot.ru/). Бот в Telegram: [@music\_yandex\_bot](https://t.me/music_yandex_bot). Автор: [@MarshalX](https://github.com/MarshalX).
|
||||
|
||||
Статья на habr.com с описанием реализации: [Под капотом бота-клиента Яндекс.Музыки](https://habr.com/ru/post/487428/).
|
||||
|
||||
[![Telegram бот-клиент](https://hsto.org/webt/uv/4s/a3/uv4sa3pslohuzlmuzrjzteju2dk.png)](https://music-yandex-bot.ru/)
|
||||
|
||||
### Благодарность
|
||||
|
||||
Спасибо разработчикам `python-telegram-bot`. Выбрал Вас в качестве примера.
|
||||
|
||||
### Внесение своего вклада в проект
|
||||
|
||||
Внесение своего вклада максимально приветствуется! Есть перечень пунктов, который стоит соблюдать. Каждый пункт перечня расписан в [CONTRIBUTING.md](https://github.com/MarshalX/yandex-music-api/blob/main/CONTRIBUTING.md).
|
||||
|
||||
Вы можете помочь и сообщив о [баге](https://github.com/MarshalX/yandex-music-api/issues/new?assignees=MarshalX&labels=bug&template=bug-report.md&title=) или о [новом поле пришедшем от API](https://github.com/MarshalX/yandex-music-api/issues/new?assignees=&labels=feature&template=found-unknown-fields.md&title=%D0%9D%D0%BE%D0%B2%D0%BE%D0%B5+%D0%BD%D0%B5%D0%B8%D0%B7%D0%B2%D0%B5%D1%81%D1%82%D0%BD%D0%BE%D0%B5+%D0%BF%D0%BE%D0%BB%D0%B5+%D0%BE%D1%82+API).
|
||||
|
||||
### Спонсоры
|
||||
|
||||
#### JetBrains
|
||||
|
||||
<img height="150" width="150" src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png" alt="JetBrains Logo (Main) logo.">
|
||||
|
||||
> JetBrains предоставляет бесплатный набор инструментов для разработки активным контрибьюторам некоммерческих проектов с открытым исходным кодом.
|
||||
|
||||
[Лицензии для проектов с открытым исходным кодом — Программы поддержки](https://jb.gg/OpenSourceSupport)
|
||||
|
||||
### Лицензия
|
||||
|
||||
Вы можете копировать, распространять и модифицировать программное обеспечение при условии, что модификации описаны и лицензированы бесплатно в соответствии с [LGPL-3](https://www.gnu.org/licenses/lgpl-3.0.html). Произведения производных (включая модификации или что-либо статически связанное с библиотекой) могут распространяться только в соответствии с LGPL-3, но приложения, которые используют библиотеку, необязательно.
|
429
README.rst
429
README.rst
|
@ -1,429 +0,0 @@
|
|||
================
|
||||
Yandex Music API
|
||||
================
|
||||
|
||||
Делаю то, что по определённым причинам не сделала компания Yandex.
|
||||
|
||||
⚠️ Это неофициальная библиотека.
|
||||
|
||||
Сообщество разработчиков общаются и помогают друг другу
|
||||
в `Telegram чате <https://t.me/yandex_music_api>`_, присоединяйтесь!
|
||||
|
||||
.. image:: https://img.shields.io/badge/python-3.7+-blue.svg
|
||||
:target: https://pypi.org/project/yandex-music/
|
||||
:alt: Поддерживаемые Python версии
|
||||
|
||||
.. image:: https://codecov.io/gh/MarshalX/yandex-music-api/branch/main/graph/badge.svg
|
||||
:target: https://codecov.io/gh/MarshalX/yandex-music-api
|
||||
:alt: Покрытие кода тестами
|
||||
|
||||
.. image:: https://api.codacy.com/project/badge/Grade/27011a5a8d9f4b278d1bfe2fe8725fed
|
||||
:target: https://www.codacy.com/manual/MarshalX/yandex-music-api
|
||||
:alt: Качество кода
|
||||
|
||||
.. image:: https://github.com/MarshalX/yandex-music-api/actions/workflows/pytest_full.yml/badge.svg
|
||||
:target: https://github.com/MarshalX/yandex-music-api/actions/workflows/pytest_full.yml
|
||||
:alt: Статус тестов
|
||||
|
||||
.. image:: https://readthedocs.org/projects/yandex-music/badge/?version=latest
|
||||
:target: https://yandex-music.readthedocs.io/en/latest/?badge=latest
|
||||
:alt: Статус документации
|
||||
|
||||
.. image:: https://img.shields.io/badge/license-LGPLv3-lightgrey.svg
|
||||
:target: https://www.gnu.org/licenses/lgpl-3.0.html
|
||||
:alt: Лицензия LGPLv3
|
||||
|
||||
|
||||
==========
|
||||
Содержание
|
||||
==========
|
||||
|
||||
- `Введение`_
|
||||
|
||||
#. `Доступ к вашим данным Яндекс.Музыка`_
|
||||
|
||||
- `Установка`_
|
||||
|
||||
- `Начало работы`_
|
||||
|
||||
#. `Изучение по примерам`_
|
||||
|
||||
#. `Особенности использования асинхронного клиента`_
|
||||
|
||||
#. `Логирование`_
|
||||
|
||||
#. `Документация`_
|
||||
|
||||
- `Получение помощи`_
|
||||
|
||||
- `Список изменений`_
|
||||
|
||||
- `Реализации на других языках`_
|
||||
|
||||
#. `C#`_
|
||||
|
||||
#. `PHP`_
|
||||
|
||||
#. `JavaScript`_
|
||||
|
||||
- `Разработанные проекты`_
|
||||
|
||||
#. `Плагин для Kodi`_
|
||||
|
||||
#. `Telegram бот-клиент`_
|
||||
|
||||
- `Благодарность`_
|
||||
|
||||
- `Внесение своего вклада в проект`_
|
||||
|
||||
- `Лицензия`_
|
||||
|
||||
========
|
||||
Введение
|
||||
========
|
||||
|
||||
Эта библиотека предоставляется Python интерфейс для никем
|
||||
незадокументированного и сделанного только для себя API Яндекс Музыки.
|
||||
|
||||
Она совместима с версиями Python 3.7+ и поддерживает работу как с синхронном,
|
||||
так и асинхронным (asyncio) кодом.
|
||||
|
||||
В дополнение к реализации чистого API данная библиотека имеет ряд
|
||||
классов-обёрток объектов высокого уровня дабы сделать разработку клиентов
|
||||
и скриптов простой и понятной. Вся документация была написана с нуля исходя
|
||||
из логического анализа в ходе обратной разработки (reverse engineering) API.
|
||||
|
||||
-----------------------------------
|
||||
Доступ к вашим данным Яндекс.Музыка
|
||||
-----------------------------------
|
||||
|
||||
Начиная с версии `2.0.0 <https://github.com/MarshalX/yandex-music-api/blob/main/CHANGES.rst#%D0%B2%D0%B5%D1%80%D1%81%D0%B8%D1%8F-200>`_ библиотека больше не предоставляет интерфейсы для работы
|
||||
с OAuth Яндекс и Яндекс.Паспорт. Задача по получению токена для доступа к данным
|
||||
на плечах разработчиков использующих данную библиотеку.
|
||||
|
||||
=========
|
||||
Установка
|
||||
=========
|
||||
|
||||
Вы можете установить или обновить Yandex Music API при помощи:
|
||||
|
||||
.. code:: shell
|
||||
|
||||
pip install yandex-music --upgrade
|
||||
|
||||
Или Вы можете установить из исходного кода с помощью:
|
||||
|
||||
.. code:: shell
|
||||
|
||||
git clone https://github.com/MarshalX/yandex-music-api
|
||||
cd yandex-music-api
|
||||
python setup.py install
|
||||
|
||||
=============
|
||||
Начало работы
|
||||
=============
|
||||
|
||||
Приступив к работе первым делом необходимо создать экземпляр клиента.
|
||||
|
||||
Инициализация синхронного клиента:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from yandex_music import Client
|
||||
|
||||
client = Client()
|
||||
client.init()
|
||||
|
||||
# или
|
||||
|
||||
client = Client().init()
|
||||
|
||||
Инициализация асинхронного клиента:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from yandex_music import ClientAsync
|
||||
|
||||
client = ClientAsync()
|
||||
await client.init()
|
||||
|
||||
# или
|
||||
|
||||
client = await Client().init()
|
||||
|
||||
Вызов ``init()`` необходим для получение информации для упрощения будущих запросов.
|
||||
|
||||
Работа без авторизации ограничена. Так, например, для загрузки будут доступны
|
||||
только первые 30 секунд аудиофайла. Для понимания всех ограничений зайдите на
|
||||
сайт Яндекс.Музыка под инкогнито и воспользуйтесь сервисом.
|
||||
|
||||
Для доступа к своим личным данным следует авторизоваться.
|
||||
Это осуществляется через токен аккаунта Яндекс.Музыка.
|
||||
|
||||
Авторизация:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from yandex_music import Client
|
||||
|
||||
client = Client('token').init()
|
||||
|
||||
После успешного создания клиента Вы вольны в выборе необходимого метода
|
||||
из API. Все они доступны у объекта класса ``Client``. Подробнее в методах клиента
|
||||
в `документации <https://yandex-music.readthedocs.io/en/latest/yandex_music.client.html>`_.
|
||||
|
||||
Пример получения первого трека из плейлиста "Мне нравится" и его загрузка:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from yandex_music import Client
|
||||
|
||||
client = Client('token').init()
|
||||
client.users_likes_tracks()[0].fetch_track().download('example.mp3')
|
||||
|
||||
В примере выше клиент получает список треков которые были отмечены как
|
||||
понравившиеся. API возвращает объект
|
||||
`TracksList <https://yandex-music.readthedocs.io/en/latest/yandex_music.tracks_list.html>`_
|
||||
в котором содержится список с треками класса
|
||||
`TrackShort <https://yandex-music.readthedocs.io/en/latest/yandex_music.track_short.html>`_.
|
||||
Данный класс содержит наиважнейшую информацию о треке и никаких подробностей,
|
||||
поэтому для получения полной версии трека со всей информацией необходимо
|
||||
обратиться к методу ``fetch_track()``. Затем можно скачать трек методом ``download()``.
|
||||
|
||||
Пример получения треков по ID:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from yandex_music import Client
|
||||
|
||||
client = Client().init()
|
||||
client.tracks(['10994777:1193829', '40133452:5206873', '48966383:6693286', '51385674:7163467'])
|
||||
|
||||
В качестве ID трека выступает его уникальный номер и номер альбома.
|
||||
Первым треком из примера является следующий трек:
|
||||
music.yandex.ru/album/**1193829**/track/**10994777**
|
||||
|
||||
Выполнение запросов с использование прокси в синхронной версии:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from yandex_music.utils.request import Request
|
||||
from yandex_music import Client
|
||||
|
||||
request = Request(proxy_url='socks5://user:password@host:port')
|
||||
client = Client(request=request).init()
|
||||
|
||||
Примеры proxy url:
|
||||
|
||||
- socks5://user:password@host:port
|
||||
- http://host:port
|
||||
- https://host:port
|
||||
- http://user:password@host
|
||||
|
||||
Больше примеров тут: `proxies - advanced usage - requests <https://2.python-requests.org/en/master/user/advanced/#proxies>`_
|
||||
|
||||
Выполнение запросов с использование прокси в асинхронной версии:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from yandex_music.utils.request_async import Request
|
||||
from yandex_music import ClientAsync
|
||||
|
||||
request = Request(proxy_url='http://user:pass@some.proxy.com')
|
||||
client = await ClientAsync(request=request).init()
|
||||
|
||||
Socks прокси не поддерживаются в асинхронной версии.
|
||||
|
||||
Про поддерживаемые прокси тут: `proxy support - advanced usage - aiohttp <https://docs.aiohttp.org/en/stable/client_advanced.html#proxy-support>`_
|
||||
|
||||
--------------------
|
||||
Изучение по примерам
|
||||
--------------------
|
||||
|
||||
Вот несколько примеров для обзора. Даже если это не Ваш подход к
|
||||
обучению, пожалуйста, возьмите и бегло просмотрите их.
|
||||
|
||||
Код примеров опубликован в открытом доступе, поэтому
|
||||
Вы можете взять его и начать писать вокруг своё.
|
||||
|
||||
Посетите `эту страницу <https://github.com/MarshalX/yandex-music-api/blob/main/examples/>`_
|
||||
чтобы изучить официальные примеры.
|
||||
|
||||
----------------------------------------------
|
||||
Особенности использования асинхронного клиента
|
||||
----------------------------------------------
|
||||
|
||||
При работе с асинхронной версией библиотеке стоит всегда помнить
|
||||
следующие особенности:
|
||||
|
||||
- Клиент следует импортировать с названием ``ClientAsync``, а не просто ``Client``.
|
||||
- При использовании методов-сокращений нужно выбирать метод с суффиксом ``_async``.
|
||||
|
||||
Пояснение ко второму пункту:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from yandex_music import ClientAsync
|
||||
|
||||
client = await ClientAsync('token').init()
|
||||
liked_short_track = (await client.users_likes_tracks())[0]
|
||||
|
||||
# правильно
|
||||
full_track = await liked_short_track.fetch_track_async()
|
||||
await full_track.download_async()
|
||||
|
||||
# НЕПРАВИЛЬНО
|
||||
full_track = await liked_short_track.fetch_track()
|
||||
await full_track.download()
|
||||
|
||||
-----------
|
||||
Логирование
|
||||
-----------
|
||||
|
||||
Данная библиотека использует ``logging`` модуль. Чтобы настроить логирование на
|
||||
стандартный вывод, поместите
|
||||
|
||||
.. code:: python
|
||||
|
||||
import logging
|
||||
logging.basicConfig(level=logging.DEBUG,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
|
||||
в начало вашего скрипта.
|
||||
|
||||
Вы также можете использовать логирование в вашем приложении, вызвав
|
||||
``logging.getLogger()`` и установить уровень какой Вы хотите:
|
||||
|
||||
.. code:: python
|
||||
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
Если Вы хотите ``DEBUG`` логирование:
|
||||
|
||||
.. code:: python
|
||||
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
============
|
||||
Документация
|
||||
============
|
||||
|
||||
Документация ``yandex-music-api`` расположена на
|
||||
`readthedocs.io <https://yandex-music.readthedocs.io/>`_.
|
||||
Вашей отправной точкой должен быть класс ``Client``, а точнее его методы.
|
||||
Именно они выполняют все
|
||||
запросы на API и возвращают Вам готовые объекты.
|
||||
`Класс Client на readthedocs.io <https://yandex-music.readthedocs.io/en/latest/yandex_music.client.html>`_.
|
||||
|
||||
================
|
||||
Получение помощи
|
||||
================
|
||||
|
||||
Получить помощь можно несколькими путями:
|
||||
|
||||
- Задать вопрос в `Telegram чате <https://t.me/yandex_music_api>`_, где мы помогаем друг другу, присоединяйтесь!
|
||||
- Сообщить о баге можно `создав Bug Report <https://github.com/MarshalX/yandex-music-api/issues/new?assignees=MarshalX&labels=bug&template=bug-report.md&title=>`_.
|
||||
- Предложить новую фичу или задать вопрос можно `создав discussion <https://github.com/MarshalX/yandex-music-api/discussions/new>`_.
|
||||
- Найти ответ на вопрос в `документации библиотеки <https://yandex-music.readthedocs.io/en/latest/>`_.
|
||||
|
||||
================
|
||||
Список изменений
|
||||
================
|
||||
|
||||
Весь список изменений ведётся в файле `CHANGES.rst <https://github.com/MarshalX/yandex-music-api/blob/main/CHANGES.rst>`_.
|
||||
|
||||
|
||||
===========================
|
||||
Реализации на других языках
|
||||
===========================
|
||||
|
||||
--
|
||||
C#
|
||||
--
|
||||
|
||||
Реализация с совершенно другим подходом, так как используется API для frontend'a,
|
||||
а не мобильных и десктопных приложений:
|
||||
`Winster332/Yandex.Music.Api <https://github.com/Winster332/Yandex.Music.Api>`_.
|
||||
|
||||
`@Winster332 <https://github.com/Winster332>`_ не сильно проявляет активность,
|
||||
но существует форк, который продолжил начатое. Эндпоинты изменены с фронтовых на
|
||||
мобильные: `K1llMan/Yandex.Music.Api <https://github.com/K1llMan/Yandex.Music.Api>`_.
|
||||
|
||||
---
|
||||
PHP
|
||||
---
|
||||
|
||||
Частично переписанная текущая библиотека на PHP:
|
||||
`LuckyWins/yandex-music-api <https://github.com/LuckyWins/yandex-music-api>`_.
|
||||
|
||||
----------
|
||||
JavaScript
|
||||
----------
|
||||
|
||||
API wrapper на Node.JS. Не обновлялся больше двух лет:
|
||||
`itsmepetrov/yandex-music-api <https://github.com/itsmepetrov/yandex-music-api>`_.
|
||||
Продолжение разработки заброшенной библиотеки: `kontsevoye/ym-api <https://github.com/kontsevoye/ym-api>`_.
|
||||
|
||||
=====================
|
||||
Разработанные проекты
|
||||
=====================
|
||||
|
||||
---------------
|
||||
Плагин для Kodi
|
||||
---------------
|
||||
|
||||
Плагин может проигрывать пользовательские плейлисты и плейлисты Яндекса, поиск
|
||||
по Яндекс Музыке, радио.
|
||||
|
||||
Сайт проекта: `ymkodi.ru <https://ymkodi.ru/>`_.
|
||||
Исходный код: `kodi.plugin.yandex-music <https://github.com/Angel777d/kodi.plugin.yandex-music>`_.
|
||||
Автор: `@Angel777d <https://github.com/Angel777d>`_.
|
||||
|
||||
.. image:: https://raw.githubusercontent.com/Angel777d/kodi.plugin.yandex-music/master/assets/img/kody_yandex_music_plugin.png
|
||||
:target: https://ymkodi.ru/
|
||||
:alt: Плагин для Kodi
|
||||
|
||||
-------------------
|
||||
Telegram бот-клиент
|
||||
-------------------
|
||||
|
||||
Неофициальный бот. Умные и ваши плейлисты, понравившиеся треки. Лайки, дизлайки, текста песен,
|
||||
поиск, распознавание песен, похожие треки! Полноценный клиент на базе мессенджера.
|
||||
|
||||
Сайт проекта: `music-yandex-bot.ru <https://music-yandex-bot.ru/>`_.
|
||||
Бот в Telegram: `@music_yandex_bot <https://t.me/music_yandex_bot>`_.
|
||||
Автор: `@MarshalX <https://github.com/MarshalX>`_.
|
||||
|
||||
Статья на habr.com с описанием реализации: `Под капотом бота-клиента Яндекс.Музыки <https://habr.com/ru/post/487428/>`_.
|
||||
|
||||
.. image:: https://hsto.org/webt/uv/4s/a3/uv4sa3pslohuzlmuzrjzteju2dk.png
|
||||
:target: https://music-yandex-bot.ru/
|
||||
:alt: Telegram бот-клиент
|
||||
|
||||
=============
|
||||
Благодарность
|
||||
=============
|
||||
|
||||
Спасибо разработчикам ``python-telegram-bot``. Выбрал Вас в качестве примера.
|
||||
|
||||
===============================
|
||||
Внесение своего вклада в проект
|
||||
===============================
|
||||
|
||||
Внесение своего вклада максимально приветствуется! Есть перечень пунктов,
|
||||
который стоит соблюдать. Каждый пункт перечня расписан в `CONTRIBUTING.md <https://github.com/MarshalX/yandex-music-api/blob/main/CONTRIBUTING.md>`_.
|
||||
|
||||
Вы можете помочь и сообщив о `баге <https://github.com/MarshalX/yandex-music-api/issues/new?assignees=MarshalX&labels=bug&template=bug-report.md&title=>`_
|
||||
или о `новом поле пришедшем от API <https://github.com/MarshalX/yandex-music-api/issues/new?assignees=&labels=feature&template=found-unknown-fields.md&title=%D0%9D%D0%BE%D0%B2%D0%BE%D0%B5+%D0%BD%D0%B5%D0%B8%D0%B7%D0%B2%D0%B5%D1%81%D1%82%D0%BD%D0%BE%D0%B5+%D0%BF%D0%BE%D0%BB%D0%B5+%D0%BE%D1%82+API>`_.
|
||||
|
||||
========
|
||||
Лицензия
|
||||
========
|
||||
|
||||
Вы можете копировать, распространять и модифицировать программное обеспечение
|
||||
при условии, что модификации описаны и лицензированы бесплатно в соответствии
|
||||
с `LGPL-3 <https://www.gnu.org/licenses/lgpl-3.0.html>`_. Произведения
|
||||
производных (включая модификации или что-либо статически связанное с библиотекой)
|
||||
могут распространяться только в соответствии с LGPL-3, но приложения, которые
|
||||
используют библиотеку, необязательно.
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
## Поддерживаемые версии
|
||||
|
||||
| Версия | Поддержка |
|
||||
| ------- | ------------------ |
|
||||
| 2.0.0 | :white_check_mark: |
|
||||
| < 2.0.0 | :x: |
|
||||
| Версия | Поддержка |
|
||||
|---------|-----------|
|
||||
| 2.1.0 | ✅ |
|
||||
| < 2.1.0 | ❌ |
|
||||
|
||||
## Сообщение об уязвимости
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
sphinx
|
||||
sphinx-copybutton
|
||||
furo
|
||||
furo
|
||||
myst-parser
|
|
@ -0,0 +1,2 @@
|
|||
```{include} ../../CHANGES.md
|
||||
```
|
|
@ -1 +0,0 @@
|
|||
.. include:: ../../CHANGES.rst
|
|
@ -0,0 +1,22 @@
|
|||
# Клиент
|
||||
|
||||
Приступив к работе первым делом необходимо создать экземпляр клиента.
|
||||
|
||||
Инициализация синхронного клиента:
|
||||
|
||||
``` python
|
||||
from yandex_music import Client
|
||||
|
||||
client = Client()
|
||||
client.init()
|
||||
|
||||
# или
|
||||
|
||||
client = Client().init()
|
||||
```
|
||||
|
||||
После успешного создания клиента вы вольны в выборе необходимого метода из API. Все они доступны у объекта класса `Client` и описаны ниже. Используйте навигацию из меню справа для быстрого доступа.
|
||||
|
||||
```{eval-rst}
|
||||
.. include:: yandex_music.client.rst
|
||||
```
|
|
@ -0,0 +1,46 @@
|
|||
# Асинхронный клиент
|
||||
|
||||
Приступив к работе первым делом необходимо создать экземпляр клиента.
|
||||
|
||||
Инициализация асинхронного клиента:
|
||||
|
||||
``` python
|
||||
from yandex_music import ClientAsync
|
||||
|
||||
client = ClientAsync()
|
||||
await client.init()
|
||||
|
||||
# или
|
||||
|
||||
client = await Client().init()
|
||||
```
|
||||
|
||||
После успешного создания клиента вы вольны в выборе необходимого метода из API. Все они доступны у объекта класса `ClientAsync` и описаны ниже. Используйте навигацию из меню справа для быстрого доступа.
|
||||
|
||||
**Особенности использования асинхронного клиента**
|
||||
|
||||
При работе с асинхронной версией библиотеке стоит всегда помнить
|
||||
следующие особенности:
|
||||
- Клиент следует импортировать с названием `ClientAsync`, а не просто `Client`.
|
||||
- При использовании методов-сокращений нужно выбирать метод с суффиксом `_async`.
|
||||
|
||||
Пояснение ко второму пункту:
|
||||
|
||||
``` python
|
||||
from yandex_music import ClientAsync
|
||||
|
||||
client = await ClientAsync('token').init()
|
||||
liked_short_track = (await client.users_likes_tracks())[0]
|
||||
|
||||
# правильно
|
||||
full_track = await liked_short_track.fetch_track_async()
|
||||
await full_track.download_async()
|
||||
|
||||
# НЕПРАВИЛЬНО
|
||||
full_track = await liked_short_track.fetch_track()
|
||||
await full_track.download()
|
||||
```
|
||||
|
||||
```{eval-rst}
|
||||
.. include:: yandex_music.client_async.rst
|
||||
```
|
|
@ -0,0 +1,2 @@
|
|||
```{include} ../../CODE_OF_CONDUCT.md
|
||||
```
|
|
@ -17,11 +17,16 @@ sys.path.insert(0, os.path.abspath('../..'))
|
|||
|
||||
master_doc = 'index'
|
||||
|
||||
source_suffix = {
|
||||
'.rst': 'restructuredtext',
|
||||
'.md': 'markdown',
|
||||
}
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'Yandex Music API'
|
||||
copyright = '2019-2022 Il`ya (Marshal) <https://github.com/MarshalX>'
|
||||
author = 'Il`ya Semyonov'
|
||||
copyright = '2019-2023 Ilya (Marshal) <https://github.com/MarshalX>'
|
||||
author = 'Ilya (Marshal)'
|
||||
|
||||
language = 'en'
|
||||
|
||||
|
@ -30,7 +35,7 @@ language = 'en'
|
|||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.napoleon', 'sphinx_copybutton']
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.napoleon', 'sphinx_copybutton', 'myst_parser']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
@ -40,6 +45,12 @@ templates_path = ['_templates']
|
|||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = []
|
||||
|
||||
# myst
|
||||
|
||||
myst_heading_anchors = 4
|
||||
# https://myst-parser.readthedocs.io/en/latest/syntax/optional.html?highlight=header-anchors#code-fences-using-colons
|
||||
myst_enable_extensions = ["colon_fence"]
|
||||
# TODO add substitution https://myst-parser.readthedocs.io/en/latest/syntax/optional.html?highlight=header-anchors#substitutions-with-jinja2
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
```{include} ../../CONTRIBUTING.md
|
||||
```
|
|
@ -0,0 +1,40 @@
|
|||
# Получение чарта
|
||||
|
||||
Пример работы с чартом ЯМ. Получение треков, отображение позиций и их изменения с использованием эмодзи.
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
from yandex_music import Client
|
||||
|
||||
|
||||
CHART_ID = 'world'
|
||||
TOKEN = os.environ.get('TOKEN')
|
||||
|
||||
client = Client(TOKEN).init()
|
||||
chart = client.chart(CHART_ID).chart
|
||||
|
||||
text = [f'🏆 {chart.title}', chart.description, '', 'Треки:']
|
||||
|
||||
for track_short in chart.tracks:
|
||||
track, chart = track_short.track, track_short.chart
|
||||
artists = ''
|
||||
if track.artists:
|
||||
artists = ' - ' + ', '.join(artist.name for artist in track.artists)
|
||||
|
||||
track_text = f'{track.title}{artists}'
|
||||
|
||||
if chart.progress == 'down':
|
||||
track_text = '🔻 ' + track_text
|
||||
elif chart.progress == 'up':
|
||||
track_text = '🔺 ' + track_text
|
||||
elif chart.progress == 'new':
|
||||
track_text = '🆕 ' + track_text
|
||||
elif chart.position == 1:
|
||||
track_text = '👑 ' + track_text
|
||||
|
||||
track_text = f'{chart.position} {track_text}'
|
||||
text.append(track_text)
|
||||
|
||||
print('\n'.join(text))
|
||||
```
|
|
@ -0,0 +1,43 @@
|
|||
# Обновление стрика дейлика
|
||||
|
||||
Отмечает "Плейлист дня" как прослушанный сегодня (добавляет +1 к счетчику).
|
||||
|
||||
```python
|
||||
import sys
|
||||
import datetime
|
||||
from yandex_music.client import Client
|
||||
|
||||
# Help text
|
||||
if len(sys.argv) == 1 or len(sys.argv) > 3:
|
||||
print('Usage: DailyPlaylistUpdater.py token')
|
||||
print('token - Authentication token')
|
||||
quit()
|
||||
# Authorization
|
||||
elif len(sys.argv) == 2:
|
||||
client = Client(sys.argv[1]).init()
|
||||
|
||||
# Current daily playlist
|
||||
PersonalPlaylistBlocks = client.landing(blocks=['personalplaylists']).blocks[0]
|
||||
DailyPlaylist = next(
|
||||
x.data.data for x in PersonalPlaylistBlocks.entities if x.data.data.generated_playlist_type == 'playlistOfTheDay'
|
||||
)
|
||||
|
||||
# Check if we don't need to update it
|
||||
if DailyPlaylist.play_counter.updated:
|
||||
modifiedDate = datetime.datetime.strptime(DailyPlaylist.modified, "%Y-%m-%dT%H:%M:%S%z").date()
|
||||
if datetime.datetime.now().date() == modifiedDate:
|
||||
print('\x1b[6;30;43m' + 'Looks like it has been already updated today' + '\x1b[0m')
|
||||
quit()
|
||||
|
||||
# Updated playlist
|
||||
updatedPlaylist = client.users_playlists(user_id=DailyPlaylist.uid, kind=DailyPlaylist.kind)[0]
|
||||
|
||||
if updatedPlaylist.play_counter.updated and not DailyPlaylist.play_counter.updated:
|
||||
print('\x1b[6;30;42m' + 'Success!' + '\x1b[0m')
|
||||
else:
|
||||
print('\x1b[6;30;41m' + 'Something has gone wrong and nothing updated' + '\x1b[0m')
|
||||
|
||||
# Debug information
|
||||
print('Before:\n modified: %s\n PlayCounter: %s' % (DailyPlaylist.modified, DailyPlaylist.play_counter))
|
||||
print('After:\n modified: %s\n PlayCounter: %s' % (updatedPlaylist.modified, updatedPlaylist.play_counter))
|
||||
```
|
|
@ -0,0 +1,44 @@
|
|||
# Получение альбома с треками
|
||||
|
||||
Пример получения информации об альбоме. Пример отображения треков вместе с исполнителями и названием.
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
from yandex_music import Client
|
||||
|
||||
# без авторизации недоступен список треков альбома
|
||||
TOKEN = os.environ.get('TOKEN')
|
||||
ALBUM_ID = 2832563
|
||||
|
||||
client = Client(TOKEN).init()
|
||||
|
||||
album = client.albums_with_tracks(ALBUM_ID)
|
||||
tracks = []
|
||||
for i, volume in enumerate(album.volumes):
|
||||
if len(album.volumes) > 1:
|
||||
tracks.append(f'💿 Диск {i + 1}')
|
||||
tracks += volume
|
||||
|
||||
text = 'АЛЬБОМ\n\n'
|
||||
text += f'{album.title}\n'
|
||||
text += f"Исполнитель: {', '.join([artist.name for artist in album.artists])}\n"
|
||||
text += f'{album.year} · {album.genre}\n'
|
||||
|
||||
cover = album.cover_uri
|
||||
if cover:
|
||||
text += f'Обложка: {cover.replace("%%", "400x400")}\n\n'
|
||||
|
||||
text += 'Список треков:'
|
||||
|
||||
print(text)
|
||||
|
||||
for track in tracks:
|
||||
if isinstance(track, str):
|
||||
print(track)
|
||||
else:
|
||||
artists = ''
|
||||
if track.artists:
|
||||
artists = ' - ' + ', '.join(artist.name for artist in track.artists)
|
||||
print(track.title + artists)
|
||||
```
|
|
@ -0,0 +1,32 @@
|
|||
# Лайки и дизлайки сущностей
|
||||
|
||||
Пример установки отметок "Мне нравится" и "Мне не нравится" на альбомы, треки, плейлисты и исполнителей.
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
from yandex_music import Client
|
||||
|
||||
|
||||
TOKEN = os.environ.get('TOKEN')
|
||||
ALBUM_ID = 2832563
|
||||
|
||||
client = Client(TOKEN).init()
|
||||
|
||||
success = client.users_likes_albums_add(ALBUM_ID)
|
||||
answer = 'Лайкнут' if success else 'Произошла ошибка'
|
||||
|
||||
print(answer)
|
||||
|
||||
success = client.users_likes_albums_remove(ALBUM_ID)
|
||||
answer = 'Дизлайкнут' if success else 'Произошла ошибка'
|
||||
|
||||
print(answer)
|
||||
|
||||
# Тоже самое и в другими сущностями (плейлист, трек, исполнитель)
|
||||
# client.users_likes_playlists_add(f'{user_id}:{playlist_id}')
|
||||
# client.users_likes_playlists_remove(f'{user_id}:{playlist_id}')
|
||||
# client.users_likes_tracks_add(track_id)
|
||||
# client.users_likes_tracks_remove(track_id)
|
||||
# и т.д. Читайте документацию.
|
||||
```
|
|
@ -0,0 +1,34 @@
|
|||
# Текст текущего играющего трека
|
||||
|
||||
Пример работы с очередями и получением текста. Выводит текущий проигрываемый трек и его текст.
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
from yandex_music import Client
|
||||
from yandex_music.exceptions import NotFoundError
|
||||
|
||||
|
||||
TOKEN = os.environ.get('TOKEN')
|
||||
|
||||
client = Client(TOKEN).init()
|
||||
|
||||
queues = client.queues_list()
|
||||
# Последняя проигрываемая очередь всегда в начале списка
|
||||
last_queue = client.queue(queues[0].id)
|
||||
|
||||
last_track_id = last_queue.get_current_track()
|
||||
last_track = last_track_id.fetch_track()
|
||||
|
||||
artists = ', '.join(last_track.artists_name())
|
||||
title = last_track.title
|
||||
print(f'Сейчас играет: {artists} - {title}')
|
||||
|
||||
try:
|
||||
lyrics = last_track.get_lyrics('LRC')
|
||||
print(lyrics.fetch_lyrics())
|
||||
|
||||
print(f'\nИсточник: {lyrics.major.pretty_name}')
|
||||
except NotFoundError:
|
||||
print('Текст песни отсутствует')
|
||||
```
|
|
@ -0,0 +1,25 @@
|
|||
# Примеры
|
||||
|
||||
В этом разделе есть небольшие примеры, чтобы показать, как выглядят скрипты,
|
||||
написанные с помощью `yandex-music-api`.
|
||||
|
||||
Перед просмотром примеров обязательно прочитайте секцию "[Начало работы](https://github.com/MarshalX/yandex-music-api#%D0%B8%D0%B7%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5-%D0%BF%D0%BE-%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80%D0%B0%D0%BC)"
|
||||
в основном README файле. Там есть сниппеты, которые помогут разобраться.
|
||||
|
||||
Все примеры лицензированы в соответствии с
|
||||
[Лицензией CC0](https://github.com/MarshalX/yandex-music-api/blob/master/examples/LICENSE.txt)
|
||||
и поэтому полностью предназначены для общественного достояния.
|
||||
Вы можете использовать их в качестве базы для своих собственных скриптов,
|
||||
не беспокоясь об авторских правах.
|
||||
|
||||
```{toctree}
|
||||
examples.chart.md
|
||||
examples.daily_playlist_updater.md
|
||||
examples.get_album_with_tracks.md
|
||||
examples.like_and_dislike.md
|
||||
examples.lyrics_playing_track.md
|
||||
examples.proxy.md
|
||||
examples.search.md
|
||||
```
|
||||
|
||||
Больше примеров доступно в папке [examples](https://github.com/MarshalX/yandex-music-api/tree/main/examples)!
|
|
@ -0,0 +1,32 @@
|
|||
# Использование прокси
|
||||
|
||||
Пример использования прокси, когда у пользователя нет подписки
|
||||
(так как Яндекс.Музыка недоступна за пределами СНГ. Актуально для расположения
|
||||
скрипта на зарубежном сервере). При проблемах с авторизацией или токеном
|
||||
использование клиента без авторизации.
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
from yandex_music import Client
|
||||
from yandex_music.exceptions import YandexMusicError
|
||||
from yandex_music.utils.request import Request
|
||||
|
||||
yandex_music_token = os.environ.get('YANDEX_MUSIC_TOKEN')
|
||||
proxied_request = Request(proxy_url=os.environ.get('PROXY_URL'))
|
||||
|
||||
try:
|
||||
if not yandex_music_token:
|
||||
raise YandexMusicError()
|
||||
|
||||
# подключаемся без прокси для получения информации об аккаунте (доступно из других стран)
|
||||
client = Client(yandex_music_token, request=Request()).init()
|
||||
# проверяем отсутствие подписки у пользователя
|
||||
if client.me and client.me.plus and not client.me.plus.has_plus:
|
||||
# если подписки нет - пересоздаем клиент с использованием прокси
|
||||
client = Client(yandex_music_token, request=proxied_request).init()
|
||||
except YandexMusicError:
|
||||
# если есть проблемы с авторизацией, токеном или чем-либо еще, то инициализируем клиент без авторизации
|
||||
# так как сервисом можно пользоваться будучи гостем, но со своими ограничениями
|
||||
client = Client(request=proxied_request)
|
||||
```
|
|
@ -0,0 +1,70 @@
|
|||
# Работа с поиском
|
||||
|
||||
Пример работы с поиском. Осуществление поисковых запросов, обработка лучшего результата и отображение статистики по найденным данным.
|
||||
|
||||
```python
|
||||
from yandex_music import Client
|
||||
|
||||
|
||||
client = Client().init()
|
||||
|
||||
type_to_name = {
|
||||
'track': 'трек',
|
||||
'artist': 'исполнитель',
|
||||
'album': 'альбом',
|
||||
'playlist': 'плейлист',
|
||||
'video': 'видео',
|
||||
'user': 'пользователь',
|
||||
'podcast': 'подкаст',
|
||||
'podcast_episode': 'эпизод подкаста',
|
||||
}
|
||||
|
||||
|
||||
def send_search_request_and_print_result(query):
|
||||
search_result = client.search(query)
|
||||
|
||||
text = [f'Результаты по запросу "{query}":', '']
|
||||
|
||||
best_result_text = ''
|
||||
if search_result.best:
|
||||
type_ = search_result.best.type
|
||||
best = search_result.best.result
|
||||
|
||||
text.append(f'❗️Лучший результат: {type_to_name.get(type_)}')
|
||||
|
||||
if type_ in ['track', 'podcast_episode']:
|
||||
artists = ''
|
||||
if best.artists:
|
||||
artists = ' - ' + ', '.join(artist.name for artist in best.artists)
|
||||
best_result_text = best.title + artists
|
||||
elif type_ == 'artist':
|
||||
best_result_text = best.name
|
||||
elif type_ in ['album', 'podcast']:
|
||||
best_result_text = best.title
|
||||
elif type_ == 'playlist':
|
||||
best_result_text = best.title
|
||||
elif type_ == 'video':
|
||||
best_result_text = f'{best.title} {best.text}'
|
||||
|
||||
text.append(f'Содержимое лучшего результата: {best_result_text}\n')
|
||||
|
||||
if search_result.artists:
|
||||
text.append(f'Исполнителей: {search_result.artists.total}')
|
||||
if search_result.albums:
|
||||
text.append(f'Альбомов: {search_result.albums.total}')
|
||||
if search_result.tracks:
|
||||
text.append(f'Треков: {search_result.tracks.total}')
|
||||
if search_result.playlists:
|
||||
text.append(f'Плейлистов: {search_result.playlists.total}')
|
||||
if search_result.videos:
|
||||
text.append(f'Видео: {search_result.videos.total}')
|
||||
|
||||
text.append('')
|
||||
print('\n'.join(text))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
while True:
|
||||
input_query = input('Введите поисковой запрос: ')
|
||||
send_search_request_and_print_result(input_query)
|
||||
```
|
|
@ -6,10 +6,20 @@
|
|||
Документация библиотеки
|
||||
=======================
|
||||
|
||||
.. include:: ../../README.rst
|
||||
.. include:: ../../README.md
|
||||
:parser: myst_parser.sphinx_
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
readme
|
||||
yandex_music
|
||||
token
|
||||
client
|
||||
client_async
|
||||
examples
|
||||
module
|
||||
changes
|
||||
contributing
|
||||
code_of_conduct
|
||||
security
|
||||
licence
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
# Лицензия
|
||||
|
||||
Вы можете копировать, распространять и модифицировать программное обеспечение при условии, что модификации описаны и лицензированы бесплатно в соответствии с [LGPL-3](https://www.gnu.org/licenses/lgpl-3.0.html). Произведения производных (включая модификации или что-либо статически связанное с библиотекой) могут распространяться только в соответствии с LGPL-3, но приложения, которые используют библиотеку, необязательно.
|
||||
|
||||
Лицензия в репозитории: [LICENSE](https://github.com/MarshalX/yandex-music-api/blob/main/LICENSE)
|
||||
|
||||
```text
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
```
|
|
@ -0,0 +1,5 @@
|
|||
# Модуль
|
||||
|
||||
```{eval-rst}
|
||||
.. include:: yandex_music.rst
|
||||
```
|
|
@ -0,0 +1,247 @@
|
|||
# Введение
|
||||
|
||||
Эта библиотека предоставляется Python интерфейс для никем незадокументированного и сделанного только для себя API Яндекс Музыки.
|
||||
|
||||
Она совместима с версиями Python 3.7+ и поддерживает работу как с синхронном, так и асинхронным (asyncio) кодом.
|
||||
|
||||
В дополнение к реализации чистого API данная библиотека имеет ряд классов-обёрток объектов высокого уровня дабы сделать разработку клиентов и скриптов простой и понятной. Вся документация была написана с нуля исходя из логического анализа в ходе обратной разработки(reverse engineering) API.
|
||||
|
||||
## Доступ к вашим данным Яндекс.Музыка
|
||||
|
||||
Начиная с версии [2.0.0](https://github.com/MarshalX/yandex-music-api/blob/a30082f4929e56381c870cb03103777ae29bcc6b/CHANGES.rst#%D0%B2%D0%B5%D1%80%D1%81%D0%B8%D1%8F-200) библиотека больше не предоставляет интерфейсы для работы с OAuth Яндекс и Яндекс.Паспорт. Задача по получению токена для доступа к данным на плечах разработчиков использующих данную библиотеку. О том как получить токен читайте в следующем разделе.
|
||||
|
||||
# Установка
|
||||
|
||||
Вы можете установить или обновить Yandex Music API при помощи:
|
||||
|
||||
``` shell
|
||||
pip install -U yandex-music
|
||||
```
|
||||
|
||||
Или Вы можете установить из исходного кода с помощью:
|
||||
|
||||
``` shell
|
||||
git clone https://github.com/MarshalX/yandex-music-api
|
||||
cd yandex-music-api
|
||||
python setup.py install
|
||||
```
|
||||
|
||||
# Начало работы
|
||||
|
||||
Приступив к работе первым делом необходимо создать экземпляр клиента.
|
||||
|
||||
Инициализация синхронного клиента:
|
||||
|
||||
``` python
|
||||
from yandex_music import Client
|
||||
|
||||
client = Client()
|
||||
client.init()
|
||||
|
||||
# или
|
||||
|
||||
client = Client().init()
|
||||
```
|
||||
|
||||
Инициализация асинхронного клиента:
|
||||
|
||||
``` python
|
||||
from yandex_music import ClientAsync
|
||||
|
||||
client = ClientAsync()
|
||||
await client.init()
|
||||
|
||||
# или
|
||||
|
||||
client = await Client().init()
|
||||
```
|
||||
|
||||
Вызов `init()` необходим для получение информации для упрощения будущих запросов.
|
||||
|
||||
Работа без авторизации ограничена. Так, например, для загрузки будут доступны только первые 30 секунд аудиофайла. Для понимания всех ограничений зайдите на сайт Яндекс.Музыка под инкогнито и воспользуйтесь сервисом.
|
||||
|
||||
Для доступа к своим личным данным следует авторизоваться. Это осуществляется через токен аккаунта Яндекс.Музыка.
|
||||
|
||||
Авторизация:
|
||||
|
||||
``` python
|
||||
from yandex_music import Client
|
||||
|
||||
client = Client('token').init()
|
||||
```
|
||||
|
||||
После успешного создания клиента Вы вольны в выборе необходимого метода из API. Все они доступны у объекта класса `Client`. Подробнее в методах клиента в [документации](https://yandex-music.readthedocs.io/en/latest/yandex_music.client.html).
|
||||
|
||||
Пример получения первого трека из плейлиста "Мне нравится" и его загрузка:
|
||||
|
||||
``` python
|
||||
from yandex_music import Client
|
||||
|
||||
client = Client('token').init()
|
||||
client.users_likes_tracks()[0].fetch_track().download('example.mp3')
|
||||
```
|
||||
|
||||
В примере выше клиент получает список треков которые были отмечены как понравившиеся. API возвращает объект [TracksList](https://yandex-music.readthedocs.io/en/latest/yandex_music.tracks_list.html) в котором содержится список с треками класса [TrackShort](https://yandex-music.readthedocs.io/en/latest/yandex_music.track_short.html). Данный класс содержит наиважнейшую информацию о треке и никаких подробностей, поэтому для получения полной версии трека со всей информацией необходимо обратиться к методу `fetch_track()`. Затем можно скачать трек методом `download()`.
|
||||
|
||||
Пример получения треков по ID:
|
||||
|
||||
``` python
|
||||
from yandex_music import Client
|
||||
|
||||
client = Client().init()
|
||||
client.tracks(['10994777:1193829', '40133452:5206873', '48966383:6693286', '51385674:7163467'])
|
||||
```
|
||||
|
||||
В качестве ID трека выступает его уникальный номер и номер альбома. Первым треком из примера является следующий трек:music.yandex.ru/album/**1193829**/track/**10994777**
|
||||
|
||||
Выполнение запросов с использование прокси в синхронной версии:
|
||||
|
||||
``` python
|
||||
from yandex_music.utils.request import Request
|
||||
from yandex_music import Client
|
||||
|
||||
request = Request(proxy_url='socks5://user:password@host:port')
|
||||
client = Client(request=request).init()
|
||||
```
|
||||
|
||||
Примеры proxy url:
|
||||
- socks5://user:<password@host>:port
|
||||
- <http://host:port>
|
||||
- <https://host:port>
|
||||
- <http://user:password@host>
|
||||
|
||||
Больше примеров тут: [proxies - advanced usage - requests](https://2.python-requests.org/en/master/user/advanced/#proxies)
|
||||
|
||||
Выполнение запросов с использование прокси в асинхронной версии:
|
||||
|
||||
``` python
|
||||
from yandex_music.utils.request_async import Request
|
||||
from yandex_music import ClientAsync
|
||||
|
||||
request = Request(proxy_url='http://user:pass@some.proxy.com')
|
||||
client = await ClientAsync(request=request).init()
|
||||
```
|
||||
|
||||
Socks прокси не поддерживаются в асинхронной версии.
|
||||
|
||||
Про поддерживаемые прокси тут: [proxy support - advanced usage - aiohttp](https://docs.aiohttp.org/en/stable/client_advanced.html#proxy-support)
|
||||
|
||||
## Изучение по примерам
|
||||
|
||||
Вот несколько примеров для обзора. Даже если это не Ваш подход к обучению, пожалуйста, возьмите и бегло просмотрите их.
|
||||
|
||||
Код примеров опубликован в открытом доступе, поэтому Вы можете взять его и начать писать вокруг своё.
|
||||
|
||||
Посетите [эту страницу](https://github.com/MarshalX/yandex-music-api/blob/main/examples/), чтобы изучить официальные примеры.
|
||||
|
||||
## Особенности использования асинхронного клиента
|
||||
|
||||
При работе с асинхронной версией библиотеке стоит всегда помнить
|
||||
следующие особенности:
|
||||
- Клиент следует импортировать с названием `ClientAsync`, а не просто `Client`.
|
||||
- При использовании методов-сокращений нужно выбирать метод с суффиксом `_async`.
|
||||
|
||||
Пояснение ко второму пункту:
|
||||
|
||||
``` python
|
||||
from yandex_music import ClientAsync
|
||||
|
||||
client = await ClientAsync('token').init()
|
||||
liked_short_track = (await client.users_likes_tracks())[0]
|
||||
|
||||
# правильно
|
||||
full_track = await liked_short_track.fetch_track_async()
|
||||
await full_track.download_async()
|
||||
|
||||
# НЕПРАВИЛЬНО
|
||||
full_track = await liked_short_track.fetch_track()
|
||||
await full_track.download()
|
||||
```
|
||||
|
||||
## Логирование
|
||||
|
||||
Данная библиотека использует `logging` модуль. Чтобы настроить логирование на стандартный вывод, поместите
|
||||
|
||||
``` python
|
||||
import logging
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
```
|
||||
|
||||
в начало вашего скрипта.
|
||||
|
||||
Вы также можете использовать логирование в вашем приложении, вызвав `logging.getLogger()` и установить уровень какой Вы хотите:
|
||||
|
||||
``` python
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
```
|
||||
|
||||
Если Вы хотите `DEBUG` логирование:
|
||||
|
||||
``` python
|
||||
logger.setLevel(logging.DEBUG)
|
||||
```
|
||||
|
||||
# Получение помощи
|
||||
|
||||
Получить помощь можно несколькими путями:
|
||||
- Задать вопрос в [Telegram чате](https://t.me/yandex_music_api), где мы помогаем друг другу, присоединяйтесь\!
|
||||
- Сообщить о баге можно [создав Bug Report](https://github.com/MarshalX/yandex-music-api/issues/new?assignees=MarshalX&labels=bug&template=bug-report.md&title=).
|
||||
- Предложить новую фичу или задать вопрос можно [создав discussion](https://github.com/MarshalX/yandex-music-api/discussions/new).
|
||||
- Найти ответ на вопрос в [документации библиотеки](https://yandex-music.readthedocs.io/en/latest/).
|
||||
|
||||
# Список изменений
|
||||
|
||||
Весь список изменений ведётся в файле [CHANGES.md](https://github.com/MarshalX/yandex-music-api/blob/main/CHANGES.md).
|
||||
|
||||
# Реализации на других языках
|
||||
|
||||
## C#
|
||||
|
||||
Реализация с совершенно другим подходом, так как используется API для frontend'a, а не мобильных и десктопных приложений: [Winster332/Yandex.Music.Api](https://github.com/Winster332/Yandex.Music.Api).
|
||||
|
||||
[@Winster332](https://github.com/Winster332) не сильно проявляет активность, но существует форк, который продолжил начатое. Эндпоинты изменены с фронтовых на мобильные: [K1llMan/Yandex.Music.Api](https://github.com/K1llMan/Yandex.Music.Api).
|
||||
|
||||
## PHP
|
||||
|
||||
Частично переписанная текущая библиотека на PHP: [LuckyWins/yandex-music-api](https://github.com/LuckyWins/yandex-music-api).
|
||||
|
||||
## JavaScript
|
||||
|
||||
API wrapper на Node.JS. Не обновлялся больше двух лет: [itsmepetrov/yandex-music-api](https://github.com/itsmepetrov/yandex-music-api). Продолжение разработки заброшенной библиотеки: [kontsevoye/ym-api](https://github.com/kontsevoye/ym-api).
|
||||
|
||||
# Разработанные проекты
|
||||
|
||||
## Плагин для Kodi
|
||||
|
||||
Плагин может проигрывать пользовательские плейлисты и плейлисты Яндекса, поиск по Яндекс Музыке, радио.
|
||||
|
||||
Сайт проекта: [ymkodi.ru](https://ymkodi.ru/). Исходный код: [kodi.plugin.yandex-music](https://github.com/Angel777d/kodi.plugin.yandex-music).
|
||||
Автор: [@Angel777d](https://github.com/Angel777d).
|
||||
|
||||
[![Плагин для Kodi](https://raw.githubusercontent.com/Angel777d/kodi.plugin.yandex-music/master/assets/img/kody_yandex_music_plugin.png)](https://ymkodi.ru/)
|
||||
|
||||
## Telegram бот-клиент
|
||||
|
||||
Неофициальный бот. Умные и ваши плейлисты, понравившиеся треки. Лайки, дизлайки, текста песен, поиск, распознавание песен, похожие треки! Полноценный клиент на базе мессенджера.
|
||||
|
||||
Сайт проекта: [music-yandex-bot.ru](https://music-yandex-bot.ru/). Бот в Telegram: [@music\_yandex\_bot](https://t.me/music_yandex_bot). Автор: [@MarshalX](https://github.com/MarshalX).
|
||||
|
||||
Статья на habr.com с описанием реализации: [Под капотом бота-клиента Яндекс.Музыки](https://habr.com/ru/post/487428/).
|
||||
|
||||
[![Telegram бот-клиент](https://hsto.org/webt/uv/4s/a3/uv4sa3pslohuzlmuzrjzteju2dk.png)](https://music-yandex-bot.ru/)
|
||||
|
||||
# Благодарность и спонсоры
|
||||
|
||||
Спасибо разработчикам `python-telegram-bot`. Выбрал Вас в качестве примера.
|
||||
|
||||
## JetBrains
|
||||
|
||||
<img height="150" width="150" src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png" alt="JetBrains Logo (Main) logo.">
|
||||
|
||||
> JetBrains предоставляет бесплатный набор инструментов для разработки активным контрибьюторам некоммерческих проектов с открытым исходным кодом.
|
||||
|
||||
[Лицензии для проектов с открытым исходным кодом — Программы поддержки](https://jb.gg/OpenSourceSupport)
|
|
@ -0,0 +1,20 @@
|
|||
# Беглый обзор
|
||||
|
||||
## Yandex Music API
|
||||
|
||||
> Делаю то, что по определённым причинам не сделала компания Yandex.
|
||||
|
||||
⚠️ Это неофициальная библиотека.
|
||||
|
||||
Сообщество разработчиков общаются и помогают друг другу в [Telegram чате](https://t.me/yandex_music_api), присоединяйтесь!
|
||||
|
||||
[![Поддерживаемые Python версии](https://img.shields.io/badge/python-3.7+-blue.svg)](https://pypi.org/project/yandex-music/)
|
||||
[![Покрытие кода тестами](https://codecov.io/gh/MarshalX/yandex-music-api/branch/main/graph/badge.svg)](https://codecov.io/gh/MarshalX/yandex-music-api)
|
||||
[![Качество кода](https://api.codacy.com/project/badge/Grade/27011a5a8d9f4b278d1bfe2fe8725fed)](https://app.codacy.com/gh/MarshalX/yandex-music-api)
|
||||
[![Статус тестов](https://github.com/MarshalX/yandex-music-api/actions/workflows/pytest_full.yml/badge.svg)](https://github.com/MarshalX/yandex-music-api/actions/workflows/pytest_full.yml)
|
||||
[![Статус документации](https://readthedocs.org/projects/yandex-music/badge/?version=latest)](https://yandex-music.readthedocs.io/en/latest/?badge=latest)
|
||||
[![Лицензия LGPLv3](https://img.shields.io/badge/license-LGPLv3-lightgrey.svg)](https://www.gnu.org/licenses/lgpl-3.0.html)
|
||||
|
||||
```{toctree}
|
||||
readme.content.md
|
||||
```
|
|
@ -1 +0,0 @@
|
|||
.. include:: ../../README.rst
|
|
@ -0,0 +1,2 @@
|
|||
```{include} ../../SECURITY.md
|
||||
```
|
|
@ -0,0 +1,17 @@
|
|||
# Получение токена
|
||||
|
||||
**Своё OAuth приложение создать нельзя.** Единственный вариант это использовать приложения официальных клиентов Яндекс.Музыка.
|
||||
|
||||
**Существует основные варианты получения токена:**
|
||||
- [Вебсайт](https://music-yandex-bot.ru/) (работает не для всех аккаунтов)
|
||||
- Android приложение: [APK файл](https://github.com/MarshalX/yandex-music-token/releases)
|
||||
- Расширение для [Google Chrome](https://chrome.google.com/webstore/detail/yandex-music-token/lcbjeookjibfhjjopieifgjnhlegmkib)
|
||||
- Расширение для [Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/yandex-music-token/)
|
||||
|
||||
Каждый вариант выше позволяет скопировать токен. Код каждого варианта [открыт](https://github.com/MarshalX/yandex-music-token).
|
||||
|
||||
**Полезные ссылки:**
|
||||
- [Способ вместо расширения для продвинутых](https://github.com/MarshalX/yandex-music-api/discussions/513#discussioncomment-2729781)
|
||||
- [Скрипт получения токена из другого проекта для Яндекс Станции](https://github.com/AlexxIT/YandexStation/blob/master/custom_components/yandex_station/core/yandex_session.py)
|
||||
|
||||
Полученный токен можно передавать в конструктор классов `yandex_music.Client` и `yandex_client.ClientAsync`.
|
|
@ -0,0 +1,7 @@
|
|||
yandex\_music.playlist.custom\_wave
|
||||
===================================
|
||||
|
||||
.. automodule:: yandex_music.playlist.custom_wave
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
|
@ -15,6 +15,7 @@ Submodules
|
|||
yandex_music.playlist.brand
|
||||
yandex_music.playlist.case_forms
|
||||
yandex_music.playlist.contest
|
||||
yandex_music.playlist.custom_wave
|
||||
yandex_music.playlist.made_for
|
||||
yandex_music.playlist.open_graph_data
|
||||
yandex_music.playlist.play_counter
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
yandex\_music.track.lyrics\_info
|
||||
================================
|
||||
|
||||
.. automodule:: yandex_music.track.lyrics_info
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
|
@ -0,0 +1,7 @@
|
|||
yandex\_music.track.lyrics\_major
|
||||
=================================
|
||||
|
||||
.. automodule:: yandex_music.track.lyrics_major
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
|
@ -0,0 +1,7 @@
|
|||
yandex\_music.track.r128
|
||||
========================
|
||||
|
||||
.. automodule:: yandex_music.track.r128
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
|
@ -13,9 +13,13 @@ Submodules
|
|||
:maxdepth: 4
|
||||
|
||||
yandex_music.track.licence_text_part
|
||||
yandex_music.track.lyrics_info
|
||||
yandex_music.track.lyrics_major
|
||||
yandex_music.track.major
|
||||
yandex_music.track.meta_data
|
||||
yandex_music.track.normalization
|
||||
yandex_music.track.poetry_lover_match
|
||||
yandex_music.track.r128
|
||||
yandex_music.track.track
|
||||
yandex_music.track.track_lyrics
|
||||
yandex_music.track.tracks_similar
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
yandex\_music.track.track\_lyrics
|
||||
=================================
|
||||
|
||||
.. automodule:: yandex_music.track.track_lyrics
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
|
@ -0,0 +1,7 @@
|
|||
yandex\_music.utils.convert\_track\_id
|
||||
======================================
|
||||
|
||||
.. automodule:: yandex_music.utils.convert_track_id
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
|
@ -12,7 +12,9 @@ Submodules
|
|||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
yandex_music.utils.convert_track_id
|
||||
yandex_music.utils.difference
|
||||
yandex_music.utils.request
|
||||
yandex_music.utils.request_async
|
||||
yandex_music.utils.response
|
||||
yandex_music.utils.sign_request
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
yandex\_music.utils.sign\_request
|
||||
=================================
|
||||
|
||||
.. automodule:: yandex_music.utils.sign_request
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
|
@ -1,6 +1,7 @@
|
|||
import os
|
||||
|
||||
from yandex_music import Client
|
||||
from yandex_music.exceptions import NotFoundError
|
||||
|
||||
|
||||
TOKEN = os.environ.get('TOKEN')
|
||||
|
@ -18,8 +19,10 @@ artists = ', '.join(last_track.artists_name())
|
|||
title = last_track.title
|
||||
print(f'Сейчас играет: {artists} - {title}')
|
||||
|
||||
supplement = last_track.get_supplement()
|
||||
if supplement.lyrics:
|
||||
print(supplement.lyrics.full_lyrics)
|
||||
else:
|
||||
try:
|
||||
lyrics = last_track.get_lyrics('LRC')
|
||||
print(lyrics.fetch_lyrics())
|
||||
|
||||
print(f'\nИсточник: {lyrics.major.pretty_name}')
|
||||
except NotFoundError:
|
||||
print('Текст песни отсутствует')
|
||||
|
|
|
@ -43,7 +43,7 @@ if args.print_args:
|
|||
print(args)
|
||||
sys.exit()
|
||||
|
||||
if type(args.token) is str and re.match(r'^[A-z0-9]{39}$', args.token):
|
||||
if type(args.token) is str and re.match(r'^[A-Za-z0-9]{39}$', args.token):
|
||||
if not args.no_save_token:
|
||||
parser.get_default('token').write_text(args.token)
|
||||
else:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Пример работы с радио
|
||||
|
||||
Документация:
|
||||
- [rotor_station_tracks](https://yandex-music.readthedocs.io/ru/latest/yandex_music.client.html#yandex_music.Client.rotor_station_tracks) –
|
||||
- [rotor_station_tracks](https://yandex-music.readthedocs.io/en/latest/yandex_music.client.html#yandex_music.client.Client.rotor_station_tracks) –
|
||||
Получение цепочки треков определённой станции. Читайте примечание.
|
||||
|
||||
Примеры:
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
import subprocess
|
||||
|
||||
|
||||
|
@ -8,7 +9,7 @@ REQUEST_METHODS = ('_request_wrapper', 'get', 'post', 'retrieve', 'download')
|
|||
|
||||
|
||||
def gen_request(output_request_filename):
|
||||
with open('yandex_music/utils/request.py', 'r') as f:
|
||||
with open('yandex_music/utils/request.py', 'r', encoding='UTF-8') as f:
|
||||
code = f.read()
|
||||
|
||||
code = code.replace('import requests', 'import asyncio\nimport aiohttp\nimport aiofiles')
|
||||
|
@ -29,10 +30,9 @@ def gen_request(output_request_filename):
|
|||
code = code.replace(f'self.{method}(', f'await self.{method}(')
|
||||
|
||||
code = code.replace('proxies=self.proxies', 'proxy=self.proxy_url')
|
||||
code = code.replace('timeout=timeout', 'timeout=aiohttp.ClientTimeout(total=timeout)')
|
||||
# undo one specific case
|
||||
code = code.replace(
|
||||
'self.retrieve(url, timeout=aiohttp.ClientTimeout(total=timeout)', 'self.retrieve(url, timeout=timeout'
|
||||
"kwargs['timeout'] = self._timeout",
|
||||
f"kwargs['timeout'] = aiohttp.ClientTimeout(total=self._timeout)\n{' ' * 8}else:\n{' ' * 12}kwargs['timeout'] = aiohttp.ClientTimeout(total=kwargs['timeout'])",
|
||||
)
|
||||
|
||||
# download method
|
||||
|
@ -44,12 +44,12 @@ def gen_request(output_request_filename):
|
|||
code = code.replace('requests.request', 'aiohttp.request')
|
||||
|
||||
code = DISCLAIMER + code
|
||||
with open(output_request_filename, 'w') as f:
|
||||
with open(output_request_filename, 'w', encoding='UTF-8') as f:
|
||||
f.write(code)
|
||||
|
||||
|
||||
def gen_client(output_client_filename):
|
||||
with open('yandex_music/client.py', 'r') as f:
|
||||
with open('yandex_music/client.py', 'r', encoding='UTF-8') as f:
|
||||
code = f.read()
|
||||
|
||||
code = code.replace('Client', 'ClientAsync')
|
||||
|
@ -69,18 +69,12 @@ def gen_client(output_client_filename):
|
|||
code = code.replace(f'self.{method}(', f'await self.{method}(')
|
||||
|
||||
# specific cases
|
||||
code = code.replace(
|
||||
'self.users_playlists_change(',
|
||||
'await self.users_playlists_change('
|
||||
)
|
||||
code = code.replace(
|
||||
'self.rotor_station_feedback(',
|
||||
'await self.rotor_station_feedback('
|
||||
)
|
||||
code = code.replace('self.users_playlists_change(', 'await self.users_playlists_change(')
|
||||
code = code.replace('self.rotor_station_feedback(', 'await self.rotor_station_feedback(')
|
||||
code = code.replace('return DownloadInfo.de_list', 'return await DownloadInfo.de_list_async')
|
||||
|
||||
code = DISCLAIMER + code
|
||||
with open(output_client_filename, 'w') as f:
|
||||
with open(output_client_filename, 'w', encoding='UTF-8') as f:
|
||||
f.write(code)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
#!/usr/bin/env python3
|
||||
import os
|
||||
import ast
|
||||
|
||||
SOURCE_FOLDER = 'yandex_music'
|
||||
EXCLUDED_FUNCTIONS = {'de_dict', 'de_json', 'de_list', 'de_json_async', 'de_list_async'}
|
||||
|
||||
ALIAS_TEMPLATE = '''
|
||||
#: Псевдоним для :attr:`{name}`
|
||||
{camel_case_name} = {name}
|
||||
'''
|
||||
|
||||
ALIAS_SECTION_MARKER = ' # camelCase псевдонимы'
|
||||
|
||||
|
||||
def _validate_function_name(function_name: str) -> bool:
|
||||
if function_name.startswith('_'):
|
||||
return False
|
||||
|
||||
if function_name in EXCLUDED_FUNCTIONS:
|
||||
return False
|
||||
|
||||
# camel case will be the same
|
||||
if '_' not in function_name:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def convert_snake_case_to_camel_case(string: str) -> str:
|
||||
camel_case = ''.join(word.title() for word in string.split('_'))
|
||||
return camel_case[0].lower() + camel_case[1:]
|
||||
|
||||
|
||||
def _generate_code(function_name: str, intent=0) -> str:
|
||||
camel_case_name = convert_snake_case_to_camel_case(function_name)
|
||||
code = ALIAS_TEMPLATE.format(name=function_name, camel_case_name=camel_case_name)
|
||||
|
||||
code_lines = [line for line in code.split('\n') if line]
|
||||
code_lines = [f'{" " * intent}{line}' for line in code_lines]
|
||||
code = '\n'.join(code_lines)
|
||||
|
||||
return code
|
||||
|
||||
|
||||
def _process_file(file: str) -> None:
|
||||
with open(file, 'r', encoding='UTF-8') as f:
|
||||
count_of_class_def = 0
|
||||
file_aliases_code_fragments = []
|
||||
tree = ast.parse(f.read())
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.ClassDef):
|
||||
count_of_class_def += 1
|
||||
|
||||
if isinstance(node, ast.FunctionDef) or isinstance(node, ast.AsyncFunctionDef):
|
||||
if _validate_function_name(node.name):
|
||||
alias_code = _generate_code(node.name, node.col_offset)
|
||||
file_aliases_code_fragments.append(alias_code)
|
||||
|
||||
# there are no such cases in data models yet
|
||||
# only in yandex_music/exceptions.py and yandex_music/utils/difference.py
|
||||
if count_of_class_def != 1:
|
||||
return
|
||||
|
||||
f.seek(0)
|
||||
file_code_lines = f.read().splitlines()
|
||||
|
||||
marker_lineno = None
|
||||
for lineno, code_line in enumerate(file_code_lines):
|
||||
if code_line == ALIAS_SECTION_MARKER:
|
||||
marker_lineno = lineno
|
||||
break
|
||||
|
||||
# we can't process files without markers now
|
||||
if marker_lineno is None:
|
||||
return
|
||||
|
||||
# remove prev aliases
|
||||
file_code_lines = file_code_lines[:marker_lineno + 1]
|
||||
file_code_lines.append('')
|
||||
file_code_lines.extend(file_aliases_code_fragments)
|
||||
file_code_lines.append('')
|
||||
|
||||
new_file_code = '\n'.join(file_code_lines)
|
||||
|
||||
with open(file, 'w', encoding='UTF-8') as f:
|
||||
f.write(new_file_code)
|
||||
|
||||
|
||||
def main():
|
||||
for root, _, files in os.walk(SOURCE_FOLDER):
|
||||
for file in files:
|
||||
if file.endswith('.py') and file != '__init__.py':
|
||||
filepath = os.path.join(root, file)
|
||||
_process_file(filepath)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
13
setup.py
13
setup.py
|
@ -15,13 +15,13 @@ class PyTest(test):
|
|||
with open('yandex_music/__init__.py', encoding='utf-8') as f:
|
||||
version = re.findall(r"__version__ = '(.+)'", f.read())[0]
|
||||
|
||||
with open('README.rst', 'r', encoding='utf-8') as f:
|
||||
with open('README.md', 'r', encoding='utf-8') as f:
|
||||
readme = f.read()
|
||||
|
||||
setup(
|
||||
name='yandex-music',
|
||||
version=version,
|
||||
author='Il`ya Semyonov',
|
||||
author='Ilya (Marshal)',
|
||||
author_email='ilya@marshal.dev',
|
||||
license='LGPLv3',
|
||||
url='https://github.com/MarshalX/yandex-music-api/',
|
||||
|
@ -29,6 +29,7 @@ setup(
|
|||
'яндекс музыка апи обёртка библиотека клиент',
|
||||
description='Неофициальная Python библиотека для работы с API сервиса Яндекс.Музыка.',
|
||||
long_description=readme,
|
||||
long_description_content_type='text/markdown',
|
||||
packages=find_packages(),
|
||||
install_requires=['requests[socks]', 'aiohttp', 'aiofiles'],
|
||||
include_package_data=True,
|
||||
|
@ -48,6 +49,7 @@ setup(
|
|||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
"Programming Language :: Python :: Implementation",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
|
@ -56,10 +58,9 @@ setup(
|
|||
cmdclass={'test': PyTest},
|
||||
tests_require=['pytest'],
|
||||
project_urls={
|
||||
'Code': 'https://github.com/MarshalX/yandex-music-api',
|
||||
'Documentation': 'https://yandex-music.readthedocs.io',
|
||||
'Chat': 'https://t.me/yandex_music_api',
|
||||
'Documentation': 'https://yandex-music.rtfd.io',
|
||||
'Telegram chat': 'https://t.me/yandex_music_api',
|
||||
'Codecov': 'https://codecov.io/gh/MarshalX/yandex-music-api',
|
||||
'Codacy': 'https://www.codacy.com/manual/MarshalX/yandex-music-api',
|
||||
'Codacy': 'https://app.codacy.com/gh/MarshalX/yandex-music-api',
|
||||
},
|
||||
)
|
||||
|
|
|
@ -93,3 +93,8 @@ from .test_brand import TestBrand
|
|||
from .test_context import TestContext
|
||||
from .test_queue_item import TestQueueItem
|
||||
from .test_deprecation import TestDeprecation
|
||||
from .test_lyrics_major import TestLyricsMajor
|
||||
from .test_track_lyrics import TestTrackLyrics
|
||||
from .test_custom_wave import TestCustomWave
|
||||
from .test_r128 import TestR128
|
||||
from .test_lyrics_info import TestLyricsInfo
|
||||
|
|
|
@ -20,6 +20,7 @@ from yandex_music import (
|
|||
Client,
|
||||
Counts,
|
||||
Cover,
|
||||
CustomWave,
|
||||
Day,
|
||||
Description,
|
||||
DiscreteScale,
|
||||
|
@ -92,6 +93,10 @@ from yandex_music import (
|
|||
Brand,
|
||||
Context,
|
||||
Deprecation,
|
||||
TrackLyrics,
|
||||
LyricsMajor,
|
||||
R128,
|
||||
LyricsInfo,
|
||||
)
|
||||
from . import (
|
||||
TestAccount,
|
||||
|
@ -108,6 +113,7 @@ from . import (
|
|||
TestChartInfoMenuItem,
|
||||
TestCounts,
|
||||
TestCover,
|
||||
TestCustomWave,
|
||||
TestDay,
|
||||
TestDescription,
|
||||
TestDiscreteScale,
|
||||
|
@ -178,6 +184,10 @@ from . import (
|
|||
TestBrand,
|
||||
TestContext,
|
||||
TestDeprecation,
|
||||
TestLyricsMajor,
|
||||
TestTrackLyrics,
|
||||
TestR128,
|
||||
TestLyricsInfo,
|
||||
)
|
||||
|
||||
|
||||
|
@ -242,7 +252,7 @@ def artist_decomposed(artist_without_nested_artist):
|
|||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def track_factory(major, normalization, user, meta_data, poetry_lover_match):
|
||||
def track_factory(major, normalization, user, meta_data, poetry_lover_match, r_128, lyrics_info):
|
||||
class TrackFactory:
|
||||
def get(self, artists, albums, track_without_nested_tracks=None):
|
||||
return Track(
|
||||
|
@ -284,6 +294,11 @@ def track_factory(major, normalization, user, meta_data, poetry_lover_match):
|
|||
TestTrack.background_video_uri,
|
||||
TestTrack.short_description,
|
||||
TestTrack.is_suitable_for_children,
|
||||
TestTrack.track_source,
|
||||
TestTrack.available_for_options,
|
||||
r_128,
|
||||
lyrics_info,
|
||||
TestTrack.track_sharing_flag,
|
||||
)
|
||||
|
||||
return TrackFactory()
|
||||
|
@ -314,6 +329,26 @@ def track_without_nested_tracks(artist, album, track_factory):
|
|||
return track_factory.get([artist], [album])
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def lyrics_major():
|
||||
return LyricsMajor(
|
||||
TestLyricsMajor.id,
|
||||
TestLyricsMajor.name,
|
||||
TestLyricsMajor.pretty_name,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def track_lyrics(lyrics_major):
|
||||
return TrackLyrics(
|
||||
TestTrackLyrics.download_url,
|
||||
TestTrackLyrics.lyric_id,
|
||||
TestTrackLyrics.external_lyric_id,
|
||||
TestTrackLyrics.writer,
|
||||
lyrics_major,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def album_factory(label, track_position):
|
||||
class AlbumFactory:
|
||||
|
@ -364,6 +399,7 @@ def album_factory(label, track_position):
|
|||
TestAlbum.likes_count,
|
||||
deprecation,
|
||||
TestAlbum.available_regions,
|
||||
TestAlbum.available_for_options,
|
||||
)
|
||||
|
||||
return AlbumFactory()
|
||||
|
@ -399,6 +435,8 @@ def playlist_factory(
|
|||
contest,
|
||||
open_graph_data,
|
||||
brand,
|
||||
custom_wave,
|
||||
pager,
|
||||
):
|
||||
class PlaylistFactory:
|
||||
def get(self, similar_playlists, last_owner_playlists):
|
||||
|
@ -458,6 +496,8 @@ def playlist_factory(
|
|||
TestPlaylist.ready,
|
||||
TestPlaylist.is_for_from,
|
||||
TestPlaylist.regions,
|
||||
custom_wave,
|
||||
pager,
|
||||
)
|
||||
|
||||
return PlaylistFactory()
|
||||
|
@ -481,6 +521,7 @@ def generated_playlist(playlist):
|
|||
TestGeneratedPlaylist.notify,
|
||||
playlist,
|
||||
TestGeneratedPlaylist.description,
|
||||
TestGeneratedPlaylist.preview_description,
|
||||
)
|
||||
|
||||
|
||||
|
@ -860,6 +901,7 @@ def account(passport_phone):
|
|||
[passport_phone],
|
||||
TestAccount.registered_at,
|
||||
TestAccount.has_info_for_app_metrica,
|
||||
TestAccount.child,
|
||||
)
|
||||
|
||||
|
||||
|
@ -879,6 +921,7 @@ def subscription(renewable_remainder, auto_renewable, operator, non_auto_renewab
|
|||
renewable_remainder,
|
||||
[auto_renewable],
|
||||
[auto_renewable],
|
||||
TestSubscription.had_any_subscription,
|
||||
[operator],
|
||||
non_auto_renewable,
|
||||
TestSubscription.can_start_trial,
|
||||
|
@ -998,6 +1041,8 @@ def status(account, permissions, subscription, plus, station_data, alert):
|
|||
alert,
|
||||
TestStatus.premium_region,
|
||||
TestStatus.experiment,
|
||||
TestStatus.pretrial_active,
|
||||
TestStatus.userhash,
|
||||
)
|
||||
|
||||
|
||||
|
@ -1131,7 +1176,15 @@ def chart_item(track, chart):
|
|||
@pytest.fixture(scope='session')
|
||||
def station_result(station, rotor_settings, ad_params):
|
||||
return StationResult(
|
||||
station, rotor_settings, rotor_settings, ad_params, TestStationResult.explanation, TestStationResult.prerolls
|
||||
station,
|
||||
rotor_settings,
|
||||
rotor_settings,
|
||||
ad_params,
|
||||
TestStationResult.explanation,
|
||||
TestStationResult.prerolls,
|
||||
TestStationResult.rup_title,
|
||||
TestStationResult.rup_description,
|
||||
TestStationResult.custom_name,
|
||||
)
|
||||
|
||||
|
||||
|
@ -1262,3 +1315,18 @@ def search_result_with_results_and_type(request, types, results):
|
|||
[results[request.param]],
|
||||
types[request.param],
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def custom_wave():
|
||||
return CustomWave(TestCustomWave.title, TestCustomWave.animation_url, TestCustomWave.position)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def r_128():
|
||||
return R128(TestR128.i, TestR128.tp)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def lyrics_info():
|
||||
return LyricsInfo(TestLyricsInfo.has_available_sync_lyrics, TestLyricsInfo.has_available_text_lyrics)
|
||||
|
|
|
@ -15,6 +15,7 @@ class TestAccount:
|
|||
birthday = '1999-08-10'
|
||||
registered_at = '2018-06-10T09:34:22+00:00'
|
||||
has_info_for_app_metrica = False
|
||||
child = False
|
||||
|
||||
def test_expected_values(self, account, passport_phone):
|
||||
assert account.now == self.now
|
||||
|
@ -31,16 +32,18 @@ class TestAccount:
|
|||
assert account.passport_phones == [passport_phone]
|
||||
assert account.registered_at == self.registered_at
|
||||
assert account.has_info_for_app_metrica == self.has_info_for_app_metrica
|
||||
assert account.child == self.child
|
||||
|
||||
def test_de_json_none(self, client):
|
||||
assert Account.de_json({}, client) is None
|
||||
|
||||
def test_de_json_required(self, client):
|
||||
json_dict = {'now': self.now, 'service_available': self.service_available}
|
||||
json_dict = {'now': self.now, 'service_available': self.service_available, 'child': self.child}
|
||||
account = Account.de_json(json_dict, client)
|
||||
|
||||
assert account.now == self.now
|
||||
assert account.service_available == self.service_available
|
||||
assert account.child == self.child
|
||||
|
||||
def test_de_json_all(self, client, passport_phone):
|
||||
json_dict = {
|
||||
|
@ -58,6 +61,7 @@ class TestAccount:
|
|||
'passport_phones': [passport_phone.to_dict()],
|
||||
'registered_at': self.registered_at,
|
||||
'has_info_for_app_metrica': self.has_info_for_app_metrica,
|
||||
'child': self.child,
|
||||
}
|
||||
account = Account.de_json(json_dict, client)
|
||||
|
||||
|
@ -75,9 +79,10 @@ class TestAccount:
|
|||
assert account.passport_phones == [passport_phone]
|
||||
assert account.registered_at == self.registered_at
|
||||
assert account.has_info_for_app_metrica == self.has_info_for_app_metrica
|
||||
assert account.child == self.child
|
||||
|
||||
def test_equality(self, user):
|
||||
a = Account(self.now, self.service_available)
|
||||
a = Account(self.now, self.service_available, self.child)
|
||||
|
||||
assert a != user
|
||||
assert hash(a) != hash(user)
|
||||
|
|
|
@ -46,6 +46,7 @@ class TestAlbum:
|
|||
start_date = '2020-06-30'
|
||||
likes_count = 2
|
||||
available_regions = ['kg', 'tm', 'by', 'kz', 'md', 'ru', 'am', 'ge', 'uz', 'tj', 'il', 'az', 'ua']
|
||||
available_for_options = ['bookmate']
|
||||
|
||||
def test_expected_values(
|
||||
self,
|
||||
|
@ -101,6 +102,7 @@ class TestAlbum:
|
|||
assert album.likes_count == self.likes_count
|
||||
assert album.deprecation == deprecation
|
||||
assert album.available_regions == self.available_regions
|
||||
assert album.available_for_options == self.available_for_options
|
||||
|
||||
def test_de_json_none(self, client):
|
||||
assert Album.de_json({}, client) is None
|
||||
|
@ -160,6 +162,7 @@ class TestAlbum:
|
|||
'likes_count': self.likes_count,
|
||||
'deprecation': deprecation.to_dict(),
|
||||
'available_regions': self.available_regions,
|
||||
'available_for_options': self.available_for_options,
|
||||
}
|
||||
album = Album.de_json(json_dict, client)
|
||||
|
||||
|
@ -207,6 +210,7 @@ class TestAlbum:
|
|||
assert album.likes_count == self.likes_count
|
||||
assert album.deprecation == deprecation
|
||||
assert album.available_regions == self.available_regions
|
||||
assert album.available_for_options == self.available_for_options
|
||||
|
||||
def test_equality(self, artist, label):
|
||||
a = Album(self.id)
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
from yandex_music.utils.convert_track_id import convert_track_id_to_number
|
||||
|
||||
|
||||
class TestConvertTrackId:
|
||||
track_id = 37696396
|
||||
album_id = 4784420
|
||||
|
||||
def test_convert_from_str(self):
|
||||
assert convert_track_id_to_number(f'{self.track_id}:{self.album_id}') == self.track_id
|
||||
assert convert_track_id_to_number(f'{self.track_id}:') == self.track_id
|
||||
assert convert_track_id_to_number(f'{self.track_id}') == self.track_id
|
||||
|
||||
def test_convert_from_int(self):
|
||||
assert convert_track_id_to_number(self.track_id) == self.track_id
|
|
@ -0,0 +1,52 @@
|
|||
import pytest
|
||||
|
||||
from yandex_music import CustomWave
|
||||
|
||||
|
||||
class TestCustomWave:
|
||||
title = 'В стиле: Трибунал'
|
||||
animation_url = 'https://music-custom-wave-media.s3.yandex.net/base.json'
|
||||
position = 'default'
|
||||
|
||||
def test_expected_values(self, custom_wave):
|
||||
assert custom_wave.title == self.title
|
||||
assert custom_wave.animation_url == self.animation_url
|
||||
assert custom_wave.position == self.position
|
||||
|
||||
def test_de_json_none(self, client):
|
||||
assert CustomWave.de_json({}, client) is None
|
||||
|
||||
def test_de_json_required(self, client):
|
||||
json_dict = {
|
||||
'title': self.title,
|
||||
'animation_url': self.animation_url,
|
||||
'position': self.position,
|
||||
}
|
||||
customwave = CustomWave.de_json(json_dict, client)
|
||||
|
||||
assert customwave.title == self.title
|
||||
assert customwave.animation_url == self.animation_url
|
||||
assert customwave.position == self.position
|
||||
|
||||
def test_de_json_all(self, client):
|
||||
json_dict = {
|
||||
'title': self.title,
|
||||
'animation_url': self.animation_url,
|
||||
'position': self.position,
|
||||
}
|
||||
customwave = CustomWave.de_json(json_dict, client)
|
||||
|
||||
assert customwave.title == self.title
|
||||
assert customwave.animation_url == self.animation_url
|
||||
assert customwave.position == self.position
|
||||
|
||||
def test_equality(self):
|
||||
a = CustomWave(self.title, self.animation_url, self.position)
|
||||
b = CustomWave('', self.animation_url, self.position)
|
||||
c = CustomWave(self.title, self.animation_url, self.position)
|
||||
|
||||
assert a != b
|
||||
assert hash(a) != hash(b)
|
||||
assert a is not b
|
||||
|
||||
assert a == c
|
|
@ -6,6 +6,7 @@ class TestGeneratedPlaylist:
|
|||
ready = True
|
||||
notify = False
|
||||
description = []
|
||||
preview_description = 'Звучит по-вашему каждый день'
|
||||
|
||||
def test_expected_values(self, generated_playlist, playlist):
|
||||
assert generated_playlist.type == self.type
|
||||
|
@ -13,6 +14,7 @@ class TestGeneratedPlaylist:
|
|||
assert generated_playlist.notify == self.notify
|
||||
assert generated_playlist.data == playlist
|
||||
assert generated_playlist.description == self.description
|
||||
assert generated_playlist.preview_description == self.preview_description
|
||||
|
||||
def test_de_json_none(self, client):
|
||||
assert GeneratedPlaylist.de_json({}, client) is None
|
||||
|
@ -36,6 +38,7 @@ class TestGeneratedPlaylist:
|
|||
'notify': self.notify,
|
||||
'data': playlist.to_dict(),
|
||||
'description': self.description,
|
||||
'preview_description': self.preview_description,
|
||||
}
|
||||
generated_playlist = GeneratedPlaylist.de_json(json_dict, client)
|
||||
|
||||
|
@ -44,6 +47,7 @@ class TestGeneratedPlaylist:
|
|||
assert generated_playlist.notify == self.notify
|
||||
assert generated_playlist.data == playlist
|
||||
assert generated_playlist.description == self.description
|
||||
assert generated_playlist.preview_description == self.preview_description
|
||||
|
||||
def test_equality(self, playlist):
|
||||
a = GeneratedPlaylist(self.type, self.ready, self.notify, playlist)
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
from yandex_music import LyricsInfo
|
||||
|
||||
|
||||
class TestLyricsInfo:
|
||||
has_available_sync_lyrics = False
|
||||
has_available_text_lyrics = True
|
||||
|
||||
def test_expected_values(self, lyrics_info):
|
||||
assert lyrics_info.has_available_sync_lyrics == self.has_available_sync_lyrics
|
||||
assert lyrics_info.has_available_text_lyrics == self.has_available_text_lyrics
|
||||
|
||||
def test_de_json_none(self, client):
|
||||
assert LyricsInfo.de_json({}, client) is None
|
||||
|
||||
def test_de_json_required(self, client):
|
||||
json_dict = {
|
||||
'has_available_sync_lyrics': self.has_available_sync_lyrics,
|
||||
'has_available_text_lyrics': self.has_available_text_lyrics,
|
||||
}
|
||||
lyrics_info = LyricsInfo.de_json(json_dict, client)
|
||||
|
||||
assert lyrics_info.has_available_sync_lyrics == self.has_available_sync_lyrics
|
||||
assert lyrics_info.has_available_text_lyrics == self.has_available_text_lyrics
|
||||
|
||||
def test_equality(self):
|
||||
a = LyricsInfo(self.has_available_sync_lyrics, self.has_available_text_lyrics)
|
||||
b = LyricsInfo(True, self.has_available_text_lyrics)
|
||||
c = LyricsInfo(self.has_available_sync_lyrics, self.has_available_text_lyrics)
|
||||
|
||||
assert a != b
|
||||
assert hash(a) != hash(b)
|
||||
assert a is not b
|
||||
|
||||
assert a == c
|
|
@ -0,0 +1,42 @@
|
|||
from yandex_music import LyricsMajor
|
||||
|
||||
|
||||
class TestLyricsMajor:
|
||||
id = 560
|
||||
name = 'MUSIXMATCH'
|
||||
pretty_name = 'Musixmatch'
|
||||
|
||||
def test_expected_values(self, lyrics_major):
|
||||
assert lyrics_major.id == self.id
|
||||
assert lyrics_major.name == self.name
|
||||
assert lyrics_major.pretty_name == self.pretty_name
|
||||
|
||||
def test_de_json_none(self, client):
|
||||
assert LyricsMajor.de_json({}, client) is None
|
||||
|
||||
def test_de_json_required(self, client):
|
||||
json_dict = {'id': self.id, 'name': self.name, 'pretty_name': self.pretty_name}
|
||||
|
||||
lyrics_major = LyricsMajor.de_json(json_dict, client)
|
||||
assert lyrics_major.id == self.id
|
||||
assert lyrics_major.name == self.name
|
||||
assert lyrics_major.pretty_name == self.pretty_name
|
||||
|
||||
def test_de_json_all(self, client):
|
||||
json_dict = {'id': self.id, 'name': self.name, 'pretty_name': self.pretty_name}
|
||||
|
||||
lyrics_major = LyricsMajor.de_json(json_dict, client)
|
||||
assert lyrics_major.id == self.id
|
||||
assert lyrics_major.name == self.name
|
||||
assert lyrics_major.pretty_name == self.pretty_name
|
||||
|
||||
def test_equality(self):
|
||||
a = LyricsMajor(self.id, self.name, self.pretty_name)
|
||||
b = LyricsMajor(10, self.name, self.pretty_name)
|
||||
c = LyricsMajor(self.id, self.name, self.pretty_name)
|
||||
|
||||
assert a != b
|
||||
assert hash(a) != hash(b)
|
||||
assert a is not b
|
||||
|
||||
assert a == c
|
|
@ -37,7 +37,6 @@ class TestMixLink:
|
|||
'text_color': self.text_color,
|
||||
'background_color': self.background_color,
|
||||
'background_image_uri': self.background_image_uri,
|
||||
'cover_white': self.cover_white,
|
||||
}
|
||||
mix_link = MixLink.de_json(json_dict, client)
|
||||
|
||||
|
@ -47,7 +46,6 @@ class TestMixLink:
|
|||
assert mix_link.text_color == self.text_color
|
||||
assert mix_link.background_color == self.background_color
|
||||
assert mix_link.background_image_uri == self.background_image_uri
|
||||
assert mix_link.cover_white == self.cover_white
|
||||
|
||||
def test_de_json_all(self, client):
|
||||
json_dict = {
|
||||
|
@ -79,7 +77,6 @@ class TestMixLink:
|
|||
self.text_color,
|
||||
self.background_color,
|
||||
self.background_image_uri,
|
||||
self.cover_white,
|
||||
)
|
||||
b = MixLink(
|
||||
self.title,
|
||||
|
@ -88,7 +85,6 @@ class TestMixLink:
|
|||
self.text_color,
|
||||
self.background_color,
|
||||
self.background_image_uri,
|
||||
self.cover_white,
|
||||
)
|
||||
c = MixLink(
|
||||
self.title,
|
||||
|
@ -97,7 +93,6 @@ class TestMixLink:
|
|||
'#000000',
|
||||
self.background_color,
|
||||
self.background_image_uri,
|
||||
self.cover_white,
|
||||
)
|
||||
d = MixLink(
|
||||
self.title,
|
||||
|
@ -106,7 +101,6 @@ class TestMixLink:
|
|||
self.text_color,
|
||||
self.background_color,
|
||||
self.background_image_uri,
|
||||
self.cover_white,
|
||||
)
|
||||
|
||||
assert a != b != c
|
||||
|
|
|
@ -59,6 +59,8 @@ class TestPlaylist:
|
|||
contest,
|
||||
open_graph_data,
|
||||
brand,
|
||||
custom_wave,
|
||||
pager,
|
||||
):
|
||||
assert playlist.owner == user
|
||||
assert playlist.uid == self.uid
|
||||
|
@ -115,6 +117,8 @@ class TestPlaylist:
|
|||
assert playlist.ready == self.ready
|
||||
assert playlist.is_for_from == self.is_for_from
|
||||
assert playlist.regions == self.regions
|
||||
assert playlist.custom_wave == custom_wave
|
||||
assert playlist.pager == pager
|
||||
|
||||
def test_de_json_none(self, client):
|
||||
assert Playlist.de_json({}, client) is None
|
||||
|
@ -153,6 +157,8 @@ class TestPlaylist:
|
|||
contest,
|
||||
open_graph_data,
|
||||
brand,
|
||||
custom_wave,
|
||||
pager,
|
||||
):
|
||||
json_dict = {
|
||||
'owner': user.to_dict(),
|
||||
|
@ -210,6 +216,8 @@ class TestPlaylist:
|
|||
'playlist_uuid': self.playlist_uuid,
|
||||
'type': self.type,
|
||||
'ready': self.ready,
|
||||
'custom_wave': custom_wave.to_dict(),
|
||||
'pager': pager.to_dict(),
|
||||
}
|
||||
playlist = Playlist.de_json(json_dict, client)
|
||||
|
||||
|
@ -268,6 +276,8 @@ class TestPlaylist:
|
|||
assert playlist.ready == self.ready
|
||||
assert playlist.is_for_from == self.is_for_from
|
||||
assert playlist.regions == self.regions
|
||||
assert playlist.custom_wave == custom_wave
|
||||
assert playlist.pager == pager
|
||||
|
||||
def test_equality(self, user, cover, made_for, play_counter, playlist_absence):
|
||||
a = Playlist(user, cover, made_for, play_counter, playlist_absence)
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import pytest
|
||||
|
||||
from yandex_music import R128
|
||||
|
||||
|
||||
class TestR128:
|
||||
i = -13.12
|
||||
tp = 0.63
|
||||
|
||||
def test_expected_values(self, r_128):
|
||||
assert r_128.i == self.i
|
||||
assert r_128.tp == self.tp
|
||||
|
||||
def test_de_json_none(self, client):
|
||||
assert R128.de_json({}, client) is None
|
||||
|
||||
def test_de_json_required(self, client):
|
||||
json_dict = {'i': self.i, 'tp': self.tp}
|
||||
r128 = R128.de_json(json_dict, client)
|
||||
|
||||
assert r128.i == self.i
|
||||
assert r128.tp == self.tp
|
||||
|
||||
def test_equality(self):
|
||||
a = R128(self.i, self.tp)
|
||||
b = R128(-8.98, self.tp)
|
||||
c = R128(self.i, self.tp)
|
||||
|
||||
assert a != b
|
||||
assert hash(a) != hash(b)
|
||||
assert a is not b
|
||||
|
||||
assert a == c
|
|
@ -0,0 +1,23 @@
|
|||
import datetime
|
||||
|
||||
from yandex_music.utils.sign_request import get_sign_request
|
||||
|
||||
|
||||
class TestSignRequest:
|
||||
timestamp = 1668687184
|
||||
track_id = 4784420
|
||||
key = 'SUPER_SECRET_KEY'
|
||||
|
||||
sign_value = 'vssEEweZhgv2Aud0rdH9maOXUC03ZkZ/hlo6bSRN8Qg='
|
||||
|
||||
def test_sign_request(self, monkeypatch):
|
||||
class FakeDatetime(datetime.datetime):
|
||||
@classmethod
|
||||
def now(cls):
|
||||
return datetime.datetime.fromtimestamp(self.timestamp)
|
||||
|
||||
monkeypatch.setattr('datetime.datetime', FakeDatetime)
|
||||
sign = get_sign_request(self.track_id, self.key)
|
||||
|
||||
assert sign.timestamp == self.timestamp
|
||||
assert sign.value == self.sign_value
|
|
@ -4,6 +4,9 @@ from yandex_music import StationResult
|
|||
class TestStationResult:
|
||||
explanation = ''
|
||||
prerolls = []
|
||||
rup_title = 'Моя волна'
|
||||
rup_description = 'Волна подстраивается под жанр и\xa0вас. Слушайте только то, что\xa0нравится!'
|
||||
custom_name = "R'n'B"
|
||||
|
||||
def test_expected_values(self, station_result, station, rotor_settings, ad_params):
|
||||
assert station_result.station == station
|
||||
|
@ -12,6 +15,9 @@ class TestStationResult:
|
|||
assert station_result.ad_params == ad_params
|
||||
assert station_result.explanation == self.explanation
|
||||
assert station_result.prerolls == self.prerolls
|
||||
assert station_result.rup_title == self.rup_title
|
||||
assert station_result.rup_description == self.rup_description
|
||||
assert station_result.custom_name == self.custom_name
|
||||
|
||||
def test_de_json_none(self, client):
|
||||
assert StationResult.de_json({}, client) is None
|
||||
|
@ -25,6 +31,8 @@ class TestStationResult:
|
|||
'settings': rotor_settings.to_dict(),
|
||||
'settings2': rotor_settings.to_dict(),
|
||||
'ad_params': ad_params.to_dict(),
|
||||
'rup_title': self.rup_title,
|
||||
'rup_description': self.rup_description,
|
||||
}
|
||||
station_result = StationResult.de_json(json_dict, client)
|
||||
|
||||
|
@ -32,6 +40,8 @@ class TestStationResult:
|
|||
assert station_result.settings == rotor_settings
|
||||
assert station_result.settings2 == rotor_settings
|
||||
assert station_result.ad_params == ad_params
|
||||
assert station_result.rup_title == self.rup_title
|
||||
assert station_result.rup_description == self.rup_description
|
||||
|
||||
def test_de_json_all(self, client, station, rotor_settings, ad_params):
|
||||
json_dict = {
|
||||
|
@ -41,6 +51,9 @@ class TestStationResult:
|
|||
'ad_params': ad_params.to_dict(),
|
||||
'explanation': self.explanation,
|
||||
'prerolls': self.prerolls,
|
||||
'rup_title': self.rup_title,
|
||||
'rup_description': self.rup_description,
|
||||
'custom_name': self.custom_name,
|
||||
}
|
||||
station_result = StationResult.de_json(json_dict, client)
|
||||
|
||||
|
@ -50,6 +63,9 @@ class TestStationResult:
|
|||
assert station_result.ad_params == ad_params
|
||||
assert station_result.explanation == self.explanation
|
||||
assert station_result.prerolls == self.prerolls
|
||||
assert station_result.rup_title == self.rup_title
|
||||
assert station_result.rup_description == self.rup_description
|
||||
assert station_result.custom_name == self.custom_name
|
||||
|
||||
def test_equality(self, station, rotor_settings, ad_params):
|
||||
a = StationResult(station, rotor_settings, rotor_settings, ad_params)
|
||||
|
|
|
@ -6,11 +6,13 @@ class TestStatus:
|
|||
cache_limit = 99
|
||||
subeditor = False
|
||||
subeditor_level = 0
|
||||
default_email = 'Ilya@marshal.by'
|
||||
default_email = 'yandex_music@yandex.com'
|
||||
skips_per_hour = None
|
||||
station_exists = None
|
||||
premium_region = None
|
||||
experiment = 109
|
||||
pretrial_active = False
|
||||
userhash = '2a1d970ce4dadc3333280aa8727d1c41a380a7622521ecef67928cd4213adb8f'
|
||||
|
||||
def test_expected_values(self, status, account, permissions, subscription, plus, alert):
|
||||
assert status.account == account
|
||||
|
@ -27,6 +29,8 @@ class TestStatus:
|
|||
assert status.bar_below == alert
|
||||
assert status.premium_region == self.premium_region
|
||||
assert status.experiment == self.experiment
|
||||
assert status.pretrial_active == self.pretrial_active
|
||||
assert status.userhash == self.userhash
|
||||
|
||||
def test_de_json_none(self, client):
|
||||
assert Status.de_json({}, client) is None
|
||||
|
@ -54,6 +58,8 @@ class TestStatus:
|
|||
'advertisement': self.advertisement,
|
||||
'bar_below': alert.to_dict(),
|
||||
'experiment': self.experiment,
|
||||
'pretrial_active': self.pretrial_active,
|
||||
'userhash': self.userhash,
|
||||
}
|
||||
status = Status.de_json(json_dict, client)
|
||||
|
||||
|
@ -71,6 +77,8 @@ class TestStatus:
|
|||
assert status.bar_below == alert
|
||||
assert status.premium_region == self.premium_region
|
||||
assert status.experiment == self.experiment
|
||||
assert status.pretrial_active == self.pretrial_active
|
||||
assert status.userhash == self.userhash
|
||||
|
||||
def test_equality(self, account, permissions, subscription):
|
||||
a = Status(account, permissions)
|
||||
|
|
|
@ -5,6 +5,7 @@ class TestSubscription:
|
|||
can_start_trial = False
|
||||
mcdonalds = False
|
||||
end = None
|
||||
had_any_subscription = False
|
||||
|
||||
def test_expected_values(self, subscription, renewable_remainder, auto_renewable, non_auto_renewable, operator):
|
||||
assert subscription.non_auto_renewable_remainder == renewable_remainder
|
||||
|
@ -15,6 +16,7 @@ class TestSubscription:
|
|||
assert subscription.can_start_trial == self.can_start_trial
|
||||
assert subscription.mcdonalds == self.mcdonalds
|
||||
assert subscription.end == self.end
|
||||
assert subscription.had_any_subscription == self.had_any_subscription
|
||||
|
||||
def test_de_json_none(self, client):
|
||||
assert Subscription.de_json({}, client) is None
|
||||
|
@ -24,12 +26,14 @@ class TestSubscription:
|
|||
'non_auto_renewable_remainder': renewable_remainder.to_dict(),
|
||||
'auto_renewable': [auto_renewable.to_dict()],
|
||||
'family_auto_renewable': [auto_renewable.to_dict()],
|
||||
'had_any_subscription': self.had_any_subscription,
|
||||
}
|
||||
subscription = Subscription.de_json(json_dict, client)
|
||||
|
||||
assert subscription.non_auto_renewable_remainder == renewable_remainder
|
||||
assert subscription.auto_renewable == [auto_renewable]
|
||||
assert subscription.family_auto_renewable == [auto_renewable]
|
||||
assert subscription.had_any_subscription == self.had_any_subscription
|
||||
|
||||
def test_de_json_all(self, client, renewable_remainder, auto_renewable, non_auto_renewable, operator):
|
||||
json_dict = {
|
||||
|
@ -41,6 +45,7 @@ class TestSubscription:
|
|||
'family_auto_renewable': [auto_renewable.to_dict()],
|
||||
'non_auto_renewable': non_auto_renewable.to_dict(),
|
||||
'operator': [operator.to_dict()],
|
||||
'had_any_subscription': self.had_any_subscription,
|
||||
}
|
||||
subscription = Subscription.de_json(json_dict, client)
|
||||
|
||||
|
@ -52,10 +57,11 @@ class TestSubscription:
|
|||
assert subscription.can_start_trial == self.can_start_trial
|
||||
assert subscription.mcdonalds == self.mcdonalds
|
||||
assert subscription.end == self.end
|
||||
assert subscription.had_any_subscription == self.had_any_subscription
|
||||
|
||||
def test_equality(self, renewable_remainder, auto_renewable):
|
||||
a = Subscription(renewable_remainder, [auto_renewable], [auto_renewable])
|
||||
b = Subscription(renewable_remainder, [], [auto_renewable])
|
||||
a = Subscription(renewable_remainder, [auto_renewable], [auto_renewable], self.had_any_subscription)
|
||||
b = Subscription(renewable_remainder, [], [auto_renewable], self.had_any_subscription)
|
||||
|
||||
assert a != b != auto_renewable
|
||||
assert hash(a) != hash(b) != hash(auto_renewable)
|
||||
|
|
|
@ -36,6 +36,9 @@ class TestTrack:
|
|||
' желания познакомиться с девушкой, достаточно много. Обычно они сводятся к опасениям,'
|
||||
)
|
||||
is_suitable_for_children = True
|
||||
track_source = 'OWN'
|
||||
available_for_options = ['bookmate']
|
||||
track_sharing_flag = 'VIDEO_ALLOWED'
|
||||
|
||||
def test_expected_values(
|
||||
self,
|
||||
|
@ -48,6 +51,8 @@ class TestTrack:
|
|||
user,
|
||||
meta_data,
|
||||
poetry_lover_match,
|
||||
r_128,
|
||||
lyrics_info,
|
||||
):
|
||||
assert track.id == self.id
|
||||
assert track.title == self.title
|
||||
|
@ -87,6 +92,11 @@ class TestTrack:
|
|||
assert track.background_video_uri == self.background_video_uri
|
||||
assert track.short_description == self.short_description
|
||||
assert track.is_suitable_for_children == self.is_suitable_for_children
|
||||
assert track.track_source == self.track_source
|
||||
assert track.available_for_options == self.available_for_options
|
||||
assert track.r128 == r_128
|
||||
assert track.lyrics_info == lyrics_info
|
||||
assert track.track_sharing_flag == self.track_sharing_flag
|
||||
|
||||
def test_de_json_none(self, client):
|
||||
assert Track.de_json({}, client) is None
|
||||
|
@ -111,6 +121,8 @@ class TestTrack:
|
|||
user,
|
||||
meta_data,
|
||||
poetry_lover_match,
|
||||
r_128,
|
||||
lyrics_info,
|
||||
):
|
||||
json_dict = {
|
||||
'id': self.id,
|
||||
|
@ -151,6 +163,11 @@ class TestTrack:
|
|||
'background_video_uri': self.background_video_uri,
|
||||
'short_description': self.short_description,
|
||||
'is_suitable_for_children': self.is_suitable_for_children,
|
||||
'track_source': self.track_source,
|
||||
'available_for_options': self.available_for_options,
|
||||
'r128': r_128.to_dict(),
|
||||
'lyrics_info': lyrics_info.to_dict(),
|
||||
'track_sharing_flag': self.track_sharing_flag,
|
||||
}
|
||||
track = Track.de_json(json_dict, client)
|
||||
|
||||
|
@ -192,6 +209,11 @@ class TestTrack:
|
|||
assert track.background_video_uri == self.background_video_uri
|
||||
assert track.short_description == self.short_description
|
||||
assert track.is_suitable_for_children == self.is_suitable_for_children
|
||||
assert track.track_source == self.track_source
|
||||
assert track.available_for_options == self.available_for_options
|
||||
assert track.r128 == r_128
|
||||
assert track.lyrics_info == lyrics_info
|
||||
assert track.track_sharing_flag == self.track_sharing_flag
|
||||
|
||||
def test_equality(self):
|
||||
a = Track(self.id)
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
from yandex_music import TrackLyrics
|
||||
|
||||
|
||||
class TestTrackLyrics:
|
||||
download_url = 'https://music-lyrics.s3-private.mds.yandex.net/8145339.f0f2e9e0/37320085?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20221113T085654Z&X-Amz-SignedHeaders=host&X-Amz-Expires=86400&X-Amz-Credential=B8LQDON9RSp6Pcbw1Hxz%2F20221113%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=148de126a9ca9a91cb157e6032d178c39bf604d2e4acec6155d81e51090533ac'
|
||||
lyric_id = 8145339
|
||||
external_lyric_id = '8638863'
|
||||
writer = ['Mother Mother']
|
||||
|
||||
def test_expected_values(self, track_lyrics, lyrics_major):
|
||||
assert track_lyrics.download_url == self.download_url
|
||||
assert track_lyrics.lyric_id == self.lyric_id
|
||||
assert track_lyrics.external_lyric_id == self.external_lyric_id
|
||||
assert track_lyrics.writers == self.writer
|
||||
assert track_lyrics.major == lyrics_major
|
||||
|
||||
def test_de_json_none(self, client):
|
||||
assert TrackLyrics.de_json({}, client) is None
|
||||
|
||||
def test_de_json_required(self, client, lyrics_major):
|
||||
json_dict = {
|
||||
'download_url': self.download_url,
|
||||
'lyric_id': self.lyric_id,
|
||||
'external_lyric_id': self.external_lyric_id,
|
||||
'writers': self.writer,
|
||||
'major': lyrics_major.to_dict(),
|
||||
}
|
||||
|
||||
track_lyrics = TrackLyrics.de_json(json_dict, client)
|
||||
assert track_lyrics.download_url == self.download_url
|
||||
assert track_lyrics.lyric_id == self.lyric_id
|
||||
assert track_lyrics.external_lyric_id == self.external_lyric_id
|
||||
assert track_lyrics.writers == self.writer
|
||||
assert track_lyrics.major == lyrics_major
|
||||
|
||||
def test_de_json_all(self, client, lyrics_major):
|
||||
json_dict = {
|
||||
'download_url': self.download_url,
|
||||
'lyric_id': self.lyric_id,
|
||||
'external_lyric_id': self.external_lyric_id,
|
||||
'writers': self.writer,
|
||||
'major': lyrics_major.to_dict(),
|
||||
}
|
||||
|
||||
track_lyrics = TrackLyrics.de_json(json_dict, client)
|
||||
assert track_lyrics.download_url == self.download_url
|
||||
assert track_lyrics.lyric_id == self.lyric_id
|
||||
assert track_lyrics.external_lyric_id == self.external_lyric_id
|
||||
assert track_lyrics.writers == self.writer
|
||||
assert track_lyrics.major == lyrics_major
|
||||
|
||||
def test_equality(self, lyrics_major):
|
||||
a = TrackLyrics(self.download_url, self.lyric_id, self.external_lyric_id, self.writer, lyrics_major)
|
||||
b = TrackLyrics(self.download_url, 50, self.external_lyric_id, self.writer, lyrics_major)
|
||||
c = TrackLyrics(self.download_url, self.lyric_id, self.external_lyric_id, self.writer, lyrics_major)
|
||||
|
||||
assert a != b
|
||||
assert hash(a) != hash(b)
|
||||
assert a is not b
|
||||
|
||||
assert a == c
|
|
@ -13,6 +13,7 @@ def track_short(track, chart):
|
|||
TestTrackShort.recent,
|
||||
chart,
|
||||
track,
|
||||
TestTrackShort.original_index,
|
||||
)
|
||||
|
||||
|
||||
|
@ -22,6 +23,7 @@ class TestTrackShort:
|
|||
album_id = None
|
||||
play_count = 0
|
||||
recent = False
|
||||
original_index = 23
|
||||
|
||||
def test_expected_values(self, track_short, track, chart):
|
||||
assert track_short.id == self.id
|
||||
|
@ -31,6 +33,7 @@ class TestTrackShort:
|
|||
assert track_short.recent == self.recent
|
||||
assert track_short.track == track
|
||||
assert track_short.chart == chart
|
||||
assert track_short.original_index == self.original_index
|
||||
|
||||
def test_de_json_none(self, client):
|
||||
assert TrackShort.de_json({}, client) is None
|
||||
|
@ -54,6 +57,7 @@ class TestTrackShort:
|
|||
'recent': self.recent,
|
||||
'track': track.to_dict(),
|
||||
'chart': chart.to_dict(),
|
||||
'original_index': self.original_index,
|
||||
}
|
||||
track_short = TrackShort.de_json(json_dict, client)
|
||||
|
||||
|
@ -64,6 +68,7 @@ class TestTrackShort:
|
|||
assert track_short.recent == self.recent
|
||||
assert track_short.track == track
|
||||
assert track_short.chart == chart
|
||||
assert track_short.original_index == self.original_index
|
||||
|
||||
def test_equality(self):
|
||||
a = TrackShort(self.id, self.timestamp, self.album_id)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
__version__ = '2.0.1'
|
||||
__version__ = '2.1.0'
|
||||
__license__ = 'GNU Lesser General Public License v3 (LGPLv3)'
|
||||
__copyright__ = 'Copyright (C) 2019-2022 Il`ya (Marshal) <https://github.com/MarshalX>'
|
||||
__copyright__ = 'Copyright (C) 2019-2023 Ilya (Marshal) <https://github.com/MarshalX>'
|
||||
|
||||
from .base import YandexMusicObject
|
||||
|
||||
|
@ -44,6 +44,7 @@ from .playlist.case_forms import CaseForms
|
|||
from .playlist.made_for import MadeFor
|
||||
from .playlist.user import User
|
||||
from .playlist.contest import Contest
|
||||
from .playlist.custom_wave import CustomWave
|
||||
from .playlist.open_graph_data import OpenGraphData
|
||||
from .playlist.brand import Brand
|
||||
from .playlist.play_counter import PlayCounter
|
||||
|
@ -62,11 +63,15 @@ from .shot.shot_event import ShotEvent
|
|||
from .tracks_list import TracksList
|
||||
from .track.major import Major
|
||||
from .track.licence_text_part import LicenceTextPart
|
||||
from .track.track_lyrics import TrackLyrics
|
||||
from .track.lyrics_major import LyricsMajor
|
||||
from .track.poetry_lover_match import PoetryLoverMatch
|
||||
from .track.meta_data import MetaData
|
||||
from .track.normalization import Normalization
|
||||
from .track.track import Track
|
||||
from .track.tracks_similar import SimilarTracks
|
||||
from .track.r128 import R128
|
||||
from .track.lyrics_info import LyricsInfo
|
||||
|
||||
from .feed.generated_playlist import GeneratedPlaylist
|
||||
from .feed.album_event import AlbumEvent
|
||||
|
@ -260,4 +265,8 @@ __all__ = [
|
|||
'Queue',
|
||||
'QueueItem',
|
||||
'Deprecation',
|
||||
'TrackLyrics',
|
||||
'CustomWave',
|
||||
'R128',
|
||||
'LyricsInfo',
|
||||
]
|
||||
|
|
|
@ -26,6 +26,7 @@ class Account(YandexMusicObject):
|
|||
passport_phones (:obj:`list` из :obj:`yandex_music.PassportPhone`): Мобильные номера.
|
||||
registered_at (:obj:`str`, optional): Дата создания учётной записи.
|
||||
has_info_for_app_metrica (:obj:`bool`, optional): Наличие информации для App Metrica.
|
||||
child (:obj:`bool`): Дочерний / детский аккаунт (скорее детский, позволяет ограничить доступный контент ребенку на Кинопоиске).
|
||||
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.
|
||||
**kwargs: Произвольные ключевые аргументы полученные от API.
|
||||
"""
|
||||
|
@ -43,7 +44,8 @@ class Account(YandexMusicObject):
|
|||
birthday: Optional[str] = None
|
||||
passport_phones: List['PassportPhone'] = None
|
||||
registered_at: Optional[str] = None
|
||||
has_info_for_app_metrica: bool = False
|
||||
has_info_for_app_metrica: bool = None
|
||||
child: bool = None
|
||||
client: Optional['Client'] = None
|
||||
|
||||
def __post_init__(self):
|
||||
|
|
|
@ -27,6 +27,8 @@ class Status(YandexMusicObject):
|
|||
bar_below (:obj:`yandex_music.Alert`, optional): Блок с предупреждениями о конце подписке и подарках.
|
||||
premium_region (:obj:`int`, optional): Регион TODO.
|
||||
experiment (:obj:`int`, optional): Включенная новая фича на аккаунте (её ID) TODO.
|
||||
pretrial_active (:obj:`bool`, optional): TODO.
|
||||
userhash (:obj:`str`, optional): Хэш-код идентификатора пользователя.
|
||||
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.
|
||||
"""
|
||||
|
||||
|
@ -45,6 +47,8 @@ class Status(YandexMusicObject):
|
|||
bar_below: Optional['Alert'] = None
|
||||
premium_region: Optional[int] = None
|
||||
experiment: Optional[int] = None
|
||||
pretrial_active: Optional[bool] = None
|
||||
userhash: Optional[str] = None
|
||||
client: Optional['Client'] = None
|
||||
|
||||
def __post_init__(self):
|
||||
|
|
|
@ -20,12 +20,14 @@ class Subscription(YandexMusicObject):
|
|||
can_start_trial (:obj:`bool`, optional): Есть ли возможность начать пробный период.
|
||||
mcdonalds (:obj:`bool`, optional): mcdonalds TODO.
|
||||
end (:obj:`str`, optional): Дата окончания.
|
||||
had_any_subscription (:obj:'bool'): Наличие какой-либо подписки в прошлом.
|
||||
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.
|
||||
"""
|
||||
|
||||
non_auto_renewable_remainder: 'RenewableRemainder'
|
||||
auto_renewable: List['AutoRenewable']
|
||||
family_auto_renewable: List['AutoRenewable']
|
||||
had_any_subscription: bool
|
||||
operator: List['Operator'] = None
|
||||
non_auto_renewable: Optional['NonAutoRenewable'] = None
|
||||
can_start_trial: Optional[bool] = None
|
||||
|
|
|
@ -16,7 +16,7 @@ class UserSettings(YandexMusicObject):
|
|||
|
||||
Доступные значения для полей `user_music_visibility` и `user_social_visibility`: `private`, `public`.
|
||||
|
||||
Notes:
|
||||
Note:
|
||||
`promos_disabled`, `ads_disabled`, `rbt_disabled` устарели и не работают.
|
||||
|
||||
`last_fm_scrobbling_enabled`, `facebook_scrobbling_enabled` выглядят устаревшими.
|
||||
|
|
|
@ -20,8 +20,10 @@ class Album(YandexMusicObject):
|
|||
|
||||
Известные значения поля `meta_type`: `music`.
|
||||
|
||||
Известные значения поля `available_for_options`: `bookmate`.
|
||||
|
||||
Attributes:
|
||||
id_(:obj:`int`, optional): Идентификатор альбома.
|
||||
id (:obj:`int`, optional): Идентификатор альбома.
|
||||
error (:obj:`str`, optional): Ошибка получения альбома.
|
||||
title (:obj:`str`, optional): Название альбома.
|
||||
track_count (:obj:`int`, optional): Количество треков.
|
||||
|
@ -65,7 +67,8 @@ class Album(YandexMusicObject):
|
|||
start_date (:obj:`str`, optional): Дата начала в формате ISO 8601 TODO.
|
||||
likes_count (:obj:`int`, optional): Количество лайков TODO.
|
||||
deprecation (:obj:`yandex_music.Deprecation`, optional): TODO.
|
||||
available_regions (:obj:`list` из :obj:`str`, optional): Регионы, где доступн альбом.
|
||||
available_regions (:obj:`list` из :obj:`str`, optional): Регионы, где доступен альбом.
|
||||
available_for_options (:obj:`list` из :obj:`str`, optional): Возможные опции для альбома.
|
||||
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.
|
||||
"""
|
||||
|
||||
|
@ -114,6 +117,7 @@ class Album(YandexMusicObject):
|
|||
likes_count: Optional[int] = None
|
||||
deprecation: Optional['Deprecation'] = None
|
||||
available_regions: Optional[List[str]] = None
|
||||
available_for_options: Optional[List[str]] = None
|
||||
client: Optional['Client'] = None
|
||||
|
||||
def __post_init__(self):
|
||||
|
@ -133,6 +137,28 @@ class Album(YandexMusicObject):
|
|||
"""
|
||||
return await self.client.albums_with_tracks(self.id, *args, **kwargs)
|
||||
|
||||
def get_cover_url(self, size: str = '200x200') -> str:
|
||||
"""Возвращает URL обложки.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: URL обложки.
|
||||
"""
|
||||
return f'https://{self.cover_uri.replace("%%", size)}'
|
||||
|
||||
def get_og_image_url(self, size: str = '200x200') -> str:
|
||||
"""Возвращает URL OG обложки.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: URL обложки.
|
||||
"""
|
||||
return f'https://{self.og_image.replace("%%", size)}'
|
||||
|
||||
def download_cover(self, filename: str, size: str = '200x200') -> None:
|
||||
"""Загрузка обложки.
|
||||
|
||||
|
@ -140,7 +166,7 @@ class Album(YandexMusicObject):
|
|||
filename (:obj:`str`): Путь для сохранения файла с названием и расширением.
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
"""
|
||||
self.client.request.download(f'https://{self.cover_uri.replace("%%", size)}', filename)
|
||||
self.client.request.download(self.get_cover_url(size), filename)
|
||||
|
||||
async def download_cover_async(self, filename: str, size: str = '200x200') -> None:
|
||||
"""Загрузка обложки.
|
||||
|
@ -149,7 +175,7 @@ class Album(YandexMusicObject):
|
|||
filename (:obj:`str`): Путь для сохранения файла с названием и расширением.
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
"""
|
||||
await self.client.request.download(f'https://{self.cover_uri.replace("%%", size)}', filename)
|
||||
await self.client.request.download(self.get_cover_url(size), filename)
|
||||
|
||||
def download_og_image(self, filename: str, size: str = '200x200') -> None:
|
||||
"""Загрузка обложки.
|
||||
|
@ -160,7 +186,7 @@ class Album(YandexMusicObject):
|
|||
filename (:obj:`str`): Путь для сохранения файла с названием и расширением.
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
"""
|
||||
self.client.request.download(f'https://{self.og_image.replace("%%", size)}', filename)
|
||||
self.client.request.download(self.get_og_image_url(size), filename)
|
||||
|
||||
async def download_og_image_async(self, filename: str, size: str = '200x200') -> None:
|
||||
"""Загрузка обложки.
|
||||
|
@ -171,7 +197,55 @@ class Album(YandexMusicObject):
|
|||
filename (:obj:`str`): Путь для сохранения файла с названием и расширением.
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
"""
|
||||
await self.client.request.download(f'https://{self.og_image.replace("%%", size)}', filename)
|
||||
await self.client.request.download(self.get_og_image_url(size), filename)
|
||||
|
||||
def download_cover_bytes(self, size: str = '200x200') -> bytes:
|
||||
"""Загрузка обложки и возврат в виде байтов.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Обложка в виде байтов.
|
||||
"""
|
||||
return self.client.request.retrieve(self.get_cover_url(size))
|
||||
|
||||
async def download_cover_bytes_async(self, size: str = '200x200') -> bytes:
|
||||
"""Загрузка обложки и возврат в виде байтов.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Обложка в виде байтов.
|
||||
"""
|
||||
return await self.client.request.retrieve(self.get_cover_url(size))
|
||||
|
||||
def download_og_image_bytes(self, size: str = '200x200') -> bytes:
|
||||
"""Загрузка обложки и возврат в виде байтов.
|
||||
|
||||
Предпочтительнее использовать `self.download_cover()`.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Обложка в виде байтов.
|
||||
"""
|
||||
return self.client.request.retrieve(self.get_og_image_url(size))
|
||||
|
||||
async def download_og_image_bytes_async(self, size: str = '200x200') -> bytes:
|
||||
"""Загрузка обложки и возврат в виде байтов.
|
||||
|
||||
Предпочтительнее использовать `self.download_cover_async()`.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Обложка в виде байтов.
|
||||
"""
|
||||
return await self.client.request.retrieve(self.get_og_image_url(size))
|
||||
|
||||
def like(self, *args, **kwargs) -> bool:
|
||||
"""Сокращение для::
|
||||
|
@ -260,6 +334,10 @@ class Album(YandexMusicObject):
|
|||
withTracks = with_tracks
|
||||
#: Псевдоним для :attr:`with_tracks_async`
|
||||
withTracksAsync = with_tracks_async
|
||||
#: Псевдоним для :attr:`get_cover_url`
|
||||
getCoverUrl = get_cover_url
|
||||
#: Псевдоним для :attr:`get_og_image_url`
|
||||
getOgImageUrl = get_og_image_url
|
||||
#: Псевдоним для :attr:`download_cover`
|
||||
downloadCover = download_cover
|
||||
#: Псевдоним для :attr:`download_cover_async`
|
||||
|
@ -268,5 +346,17 @@ class Album(YandexMusicObject):
|
|||
downloadOgImage = download_og_image
|
||||
#: Псевдоним для :attr:`download_og_image_async`
|
||||
downloadOgImageAsync = download_og_image_async
|
||||
#: Псевдоним для :attr:`download_cover_bytes`
|
||||
downloadCoverBytes = download_cover_bytes
|
||||
#: Псевдоним для :attr:`download_cover_bytes_async`
|
||||
downloadCoverBytesAsync = download_cover_bytes_async
|
||||
#: Псевдоним для :attr:`download_og_image_bytes`
|
||||
downloadOgImageBytes = download_og_image_bytes
|
||||
#: Псевдоним для :attr:`download_og_image_bytes_async`
|
||||
downloadOgImageBytesAsync = download_og_image_bytes_async
|
||||
#: Псевдоним для :attr:`like_async`
|
||||
likeAsync = like_async
|
||||
#: Псевдоним для :attr:`dislike_async`
|
||||
dislikeAsync = dislike_async
|
||||
#: Псевдоним для :attr:`artists_name`
|
||||
artistsName = artists_name
|
||||
|
|
|
@ -11,7 +11,7 @@ if TYPE_CHECKING:
|
|||
class TrackPosition(YandexMusicObject):
|
||||
"""Класс, представляющий позицию трека.
|
||||
|
||||
None:
|
||||
Note:
|
||||
Позиция трека в альбоме, который возвращается при получении самого трека.
|
||||
|
||||
Volume на фронте именуется как "Диск".
|
||||
|
|
|
@ -29,7 +29,7 @@ class Artist(YandexMusicObject):
|
|||
links (:obj:`list` из :obj:`yandex_music.Link`, optional): Ссылки на ресурсы исполнителя.
|
||||
tickets_available (:obj:`bool`, optional): Имеются ли в продаже билеты на концерт.
|
||||
likes_count (:obj:`int`, optional): Количество лайков.
|
||||
popular_tracks (:obj:`list` :obj:`yandex_music.Track`, optional): Популярные треки.
|
||||
popular_tracks (:obj:`list` из :obj:`yandex_music.Track`, optional): Популярные треки.
|
||||
regions (:obj:`list` из :obj:`str`, optional): Регион TODO.
|
||||
decomposed (:obj:`list` из :obj:`str` и :obj:`yandex_music.Artist`, optional): Декомпозиция всех исполнителей.
|
||||
Лист, где чередуется разделитель и артист. Фиты и прочее.
|
||||
|
@ -81,6 +81,28 @@ class Artist(YandexMusicObject):
|
|||
def __post_init__(self):
|
||||
self._id_attrs = (self.id, self.name, self.cover)
|
||||
|
||||
def get_op_image_url(self, size: str = '200x200') -> str:
|
||||
"""Возвращает URL OP обложки.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: URL обложки.
|
||||
"""
|
||||
return f'https://{self.op_image.replace("%%", size)}'
|
||||
|
||||
def get_og_image_url(self, size: str = '200x200') -> str:
|
||||
"""Возвращает URL OG обложки.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: URL обложки.
|
||||
"""
|
||||
return f'https://{self.og_image.replace("%%", size)}'
|
||||
|
||||
def download_og_image(self, filename: str, size: str = '200x200') -> None:
|
||||
"""Загрузка изображения для Open Graph.
|
||||
|
||||
|
@ -88,7 +110,7 @@ class Artist(YandexMusicObject):
|
|||
filename (:obj:`str`): Путь для сохранения файла с названием и расширением.
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
"""
|
||||
self.client.request.download(f'https://{self.og_image.replace("%%", size)}', filename)
|
||||
self.client.request.download(self.get_og_image_url(size), filename)
|
||||
|
||||
async def download_og_image_async(self, filename: str, size: str = '200x200') -> None:
|
||||
"""Загрузка изображения для Open Graph.
|
||||
|
@ -97,7 +119,7 @@ class Artist(YandexMusicObject):
|
|||
filename (:obj:`str`): Путь для сохранения файла с названием и расширением.
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
"""
|
||||
await self.client.request.download(f'https://{self.og_image.replace("%%", size)}', filename)
|
||||
await self.client.request.download(self.get_og_image_url(size), filename)
|
||||
|
||||
def download_op_image(self, filename: str, size: str = '200x200') -> None:
|
||||
"""Загрузка обложки.
|
||||
|
@ -109,7 +131,7 @@ class Artist(YandexMusicObject):
|
|||
filename (:obj:`str`): Путь для сохранения файла с названием и расширением.
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
"""
|
||||
self.client.request.download(f'https://{self.op_image.replace("%%", size)}', filename)
|
||||
self.client.request.download(self.get_op_image_url(size), filename)
|
||||
|
||||
async def download_op_image_async(self, filename: str, size: str = '200x200') -> None:
|
||||
"""Загрузка обложки.
|
||||
|
@ -121,7 +143,57 @@ class Artist(YandexMusicObject):
|
|||
filename (:obj:`str`): Путь для сохранения файла с названием и расширением.
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
"""
|
||||
await self.client.request.download(f'https://{self.op_image.replace("%%", size)}', filename)
|
||||
await self.client.request.download(self.get_op_image_url(size), filename)
|
||||
|
||||
def download_og_image_bytes(self, size: str = '200x200') -> bytes:
|
||||
"""Загрузка изображения для Open Graph и возврат в виде байтов.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Изображение в виде байтов.
|
||||
"""
|
||||
return self.client.request.retrieve(self.get_og_image_url(size))
|
||||
|
||||
async def download_og_image_bytes_async(self, size: str = '200x200') -> bytes:
|
||||
"""Загрузка изображения для Open Graph и возврат в виде байтов.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Изображение в виде байтов.
|
||||
"""
|
||||
return await self.client.request.retrieve(self.get_og_image_url(size))
|
||||
|
||||
def download_op_image_bytes(self, size: str = '200x200') -> bytes:
|
||||
"""Загрузка обложки и возврат в виде байтов.
|
||||
|
||||
Notes:
|
||||
Используйте это только когда нет self.cover!
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Обложка в виде байтов.
|
||||
"""
|
||||
return self.client.request.retrieve(self.get_op_image_url(size))
|
||||
|
||||
async def download_op_image_bytes_async(self, size: str = '200x200') -> bytes:
|
||||
"""Загрузка обложки и возврат в виде байтов.
|
||||
|
||||
Notes:
|
||||
Используйте это только когда нет self.cover!
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Обложка в виде байтов.
|
||||
"""
|
||||
return await self.client.request.retrieve(self.get_op_image_url(size))
|
||||
|
||||
def like(self, *args, **kwargs) -> bool:
|
||||
"""Сокращение для::
|
||||
|
@ -233,6 +305,10 @@ class Artist(YandexMusicObject):
|
|||
|
||||
# camelCase псевдонимы
|
||||
|
||||
#: Псевдоним для :attr:`get_op_image_url`
|
||||
getOpImageUrl = get_op_image_url
|
||||
#: Псевдоним для :attr:`get_og_image_url`
|
||||
getOgImageUrl = get_og_image_url
|
||||
#: Псевдоним для :attr:`download_og_image`
|
||||
downloadOgImage = download_og_image
|
||||
#: Псевдоним для :attr:`download_og_image_async`
|
||||
|
@ -241,6 +317,18 @@ class Artist(YandexMusicObject):
|
|||
downloadOpImage = download_op_image
|
||||
#: Псевдоним для :attr:`download_op_image_async`
|
||||
downloadOpImageAsync = download_op_image_async
|
||||
#: Псевдоним для :attr:`download_og_image_bytes`
|
||||
downloadOgImageBytes = download_og_image_bytes
|
||||
#: Псевдоним для :attr:`download_og_image_bytes_async`
|
||||
downloadOgImageBytesAsync = download_og_image_bytes_async
|
||||
#: Псевдоним для :attr:`download_op_image_bytes`
|
||||
downloadOpImageBytes = download_op_image_bytes
|
||||
#: Псевдоним для :attr:`download_op_image_bytes_async`
|
||||
downloadOpImageBytesAsync = download_op_image_bytes_async
|
||||
#: Псевдоним для :attr:`like_async`
|
||||
likeAsync = like_async
|
||||
#: Псевдоним для :attr:`dislike_async`
|
||||
dislikeAsync = dislike_async
|
||||
#: Псевдоним для :attr:`get_tracks`
|
||||
getTracks = get_tracks
|
||||
#: Псевдоним для :attr:`get_tracks_async`
|
||||
|
@ -249,7 +337,3 @@ class Artist(YandexMusicObject):
|
|||
getAlbums = get_albums
|
||||
#: Псевдоним для :attr:`get_albums_async`
|
||||
getAlbumsAsync = get_albums_async
|
||||
#: Псевдоним для :attr:`like_async`
|
||||
likeAsync = like_async
|
||||
#: Псевдоним для :attr:`dislike_async`
|
||||
dislikeAsync = dislike_async
|
||||
|
|
|
@ -35,12 +35,12 @@ class YandexMusicObject:
|
|||
return self.__dict__[item]
|
||||
|
||||
@staticmethod
|
||||
def report_unknown_fields_callback(obj, unknown_fields):
|
||||
def report_unknown_fields_callback(cls, unknown_fields):
|
||||
logger.warning(
|
||||
f'Found unknown fields received from API! Please copy warn message '
|
||||
f'and send to {new_issue_by_template_url} (github issue), thank you!'
|
||||
)
|
||||
logger.warning(f'Type: {type(obj)}; fields: {unknown_fields}')
|
||||
logger.warning(f'Type: {cls.__module__}.{cls.__name__}; fields: {unknown_fields}')
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: dict, client: Optional['Client']) -> Optional[dict]:
|
||||
|
@ -70,7 +70,7 @@ class YandexMusicObject:
|
|||
unknown_data[k] = v
|
||||
|
||||
if client.report_unknown_fields and unknown_data:
|
||||
YandexMusicObject.report_unknown_fields_callback(cls, unknown_data)
|
||||
cls.report_unknown_fields_callback(cls, unknown_data)
|
||||
|
||||
return cleaned_data
|
||||
|
||||
|
|
ファイル差分が大きすぎるため省略します
差分を読み込み
ファイル差分が大きすぎるため省略します
差分を読み込み
|
@ -42,6 +42,20 @@ class Cover(YandexMusicObject):
|
|||
def __post_init__(self):
|
||||
self._id_attrs = (self.prefix, self.version, self.uri, self.items_uri)
|
||||
|
||||
def get_url(self, index: int = 0, size: str = '200x200') -> str:
|
||||
"""Возвращает URL обложки.
|
||||
|
||||
Args:
|
||||
index (:obj:`int`, optional): Индекс элемента в списке ссылок на обложки если нет `self.uri`.
|
||||
size (:obj:`str`, optional): Размер изображения.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: URL адрес.
|
||||
"""
|
||||
uri = self.uri or self.items_uri[index]
|
||||
|
||||
return f'https://{uri.replace("%%", size)}'
|
||||
|
||||
def download(self, filename: str, index: int = 0, size: str = '200x200') -> None:
|
||||
"""Загрузка обложки.
|
||||
|
||||
|
@ -50,9 +64,7 @@ class Cover(YandexMusicObject):
|
|||
index (:obj:`int`, optional): Индекс элемента в списке ссылок на обложки если нет `self.uri`.
|
||||
size (:obj:`str`, optional): Размер изображения.
|
||||
"""
|
||||
uri = self.uri or self.items_uri[index]
|
||||
|
||||
self.client.request.download(f'https://{uri.replace("%%", size)}', filename)
|
||||
self.client.request.download(self.get_url(index, size), filename)
|
||||
|
||||
async def download_async(self, filename: str, index: int = 0, size: str = '200x200') -> None:
|
||||
"""Загрузка обложки.
|
||||
|
@ -62,9 +74,31 @@ class Cover(YandexMusicObject):
|
|||
index (:obj:`int`, optional): Индекс элемента в списке ссылок на обложки если нет `self.uri`.
|
||||
size (:obj:`str`, optional): Размер изображения.
|
||||
"""
|
||||
uri = self.uri or self.items_uri[index]
|
||||
await self.client.request.download(self.get_url(index, size), filename)
|
||||
|
||||
await self.client.request.download(f'https://{uri.replace("%%", size)}', filename)
|
||||
def download_bytes(self, index: int = 0, size: str = '200x200') -> bytes:
|
||||
"""Загрузка обложки и возврат в виде байтов.
|
||||
|
||||
Args:
|
||||
index (:obj:`int`, optional): Индекс элемента в списке ссылок на обложки если нет `self.uri`.
|
||||
size (:obj:`str`, optional): Размер изображения.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Обложка в виде байтов.
|
||||
"""
|
||||
return self.client.request.retrieve(self.get_url(index, size))
|
||||
|
||||
async def download_bytes_async(self, index: int = 0, size: str = '200x200') -> bytes:
|
||||
"""Загрузка обложки и возврат в виде байтов.
|
||||
|
||||
Args:
|
||||
index (:obj:`int`, optional): Индекс элемента в списке ссылок на обложки если нет `self.uri`.
|
||||
size (:obj:`str`, optional): Размер изображения.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Обложка в виде байтов.
|
||||
"""
|
||||
return await self.client.request.retrieve(self.get_url(index, size))
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: dict, client: 'Client') -> Optional['Cover']:
|
||||
|
@ -106,5 +140,11 @@ class Cover(YandexMusicObject):
|
|||
|
||||
# camelCase псевдонимы
|
||||
|
||||
#: Псевдоним для :attr:`get_url`
|
||||
getUrl = get_url
|
||||
#: Псевдоним для :attr:`download_async`
|
||||
downloadAsync = download_async
|
||||
#: Псевдоним для :attr:`download_bytes`
|
||||
downloadBytes = download_bytes
|
||||
#: Псевдоним для :attr:`download_bytes_async`
|
||||
downloadBytesAsync = download_bytes_async
|
||||
|
|
|
@ -10,6 +10,8 @@ if TYPE_CHECKING:
|
|||
from yandex_music import Client
|
||||
from xml.dom.minicompat import NodeList
|
||||
|
||||
SIGN_SALT = 'XGRlBW9FXlekgbPrRHuSiA'
|
||||
|
||||
|
||||
@model
|
||||
class DownloadInfo(YandexMusicObject):
|
||||
|
@ -52,7 +54,7 @@ class DownloadInfo(YandexMusicObject):
|
|||
path = self._get_text_node_data(doc.getElementsByTagName('path'))
|
||||
ts = self._get_text_node_data(doc.getElementsByTagName('ts'))
|
||||
s = self._get_text_node_data(doc.getElementsByTagName('s'))
|
||||
sign = md5(('XGRlBW9FXlekgbPrRHuSiA' + path[1::] + s).encode('utf-8')).hexdigest()
|
||||
sign = md5((SIGN_SALT + path[1::] + s).encode('utf-8')).hexdigest()
|
||||
|
||||
return f'https://{host}/get-mp3/{sign}/{ts}{path}'
|
||||
|
||||
|
@ -108,6 +110,28 @@ class DownloadInfo(YandexMusicObject):
|
|||
|
||||
await self.client.request.download(self.direct_link, filename)
|
||||
|
||||
def download_bytes(self) -> bytes:
|
||||
"""Загрузка трека и возврат в виде байтов.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Трек в виде байтов.
|
||||
"""
|
||||
if self.direct_link is None:
|
||||
self.get_direct_link()
|
||||
|
||||
return self.client.request.retrieve(self.direct_link)
|
||||
|
||||
async def download_bytes_async(self) -> bytes:
|
||||
"""Загрузка трека и возврат в виде байтов.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Трек в виде байтов.
|
||||
"""
|
||||
if self.direct_link is None:
|
||||
await self.get_direct_link_async()
|
||||
|
||||
return await self.client.request.retrieve(self.direct_link)
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: dict, client: 'Client') -> Optional['DownloadInfo']:
|
||||
"""Десериализация объекта.
|
||||
|
@ -184,3 +208,7 @@ class DownloadInfo(YandexMusicObject):
|
|||
getDirectLinkAsync = get_direct_link_async
|
||||
#: Псевдоним для :attr:`download_async`
|
||||
downloadAsync = download_async
|
||||
#: Псевдоним для :attr:`download_bytes`
|
||||
downloadBytes = download_bytes
|
||||
#: Псевдоним для :attr:`download_bytes_async`
|
||||
downloadBytesAsync = download_bytes_async
|
||||
|
|
|
@ -8,6 +8,8 @@ class UnauthorizedError(YandexMusicError):
|
|||
"""
|
||||
|
||||
|
||||
# TODO (MarshalX) На самом деле поиск еще происходит по кодеку
|
||||
# https://github.com/MarshalX/yandex-music-api/issues/552
|
||||
class InvalidBitrateError(YandexMusicError):
|
||||
"""Класс исключения, вызываемого при попытке загрузки трека
|
||||
с недоступным битрейтом.
|
||||
|
@ -28,7 +30,7 @@ class NotFoundError(NetworkError):
|
|||
"""Класс исключения, вызываемый в случае ответа от сервера со статус кодом 404."""
|
||||
|
||||
|
||||
# TimeoutError builtin. И не знаю хотим ли использовать его для синк и asyncio.TimeoutError для асинк
|
||||
# TimeoutError builtin. Пока не знаю хотим ли использовать его для синхронной и asyncio.TimeoutError для асинхронной
|
||||
class TimedOutError(NetworkError):
|
||||
"""Класс исключения, вызываемого для случаев истечения времени ожидания."""
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ if TYPE_CHECKING:
|
|||
|
||||
@model
|
||||
class Experiments(YandexMusicObject):
|
||||
"""Класс, представляющий какие-то свистелки-перделки, флажки, режимы экспериментальных функций.
|
||||
"""Класс, представляющий какие-то свистелки и перделки, флажки, режимы экспериментальных функций.
|
||||
|
||||
Attributes:
|
||||
client (:obj:`yandex_music.Client`): Клиент Yandex Music.
|
||||
|
@ -35,7 +35,7 @@ class Experiments(YandexMusicObject):
|
|||
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.
|
||||
|
||||
Returns:
|
||||
:obj:`yandex_music.Experiments`: Какие-то свистелки-перделки, флажки, режимы экспериментальных функций.
|
||||
:obj:`yandex_music.Experiments`: Какие-то свистелки и перделки, флажки, режимы экспериментальных функций.
|
||||
"""
|
||||
if not data:
|
||||
return None
|
||||
|
|
|
@ -12,7 +12,7 @@ class AlbumEvent(YandexMusicObject):
|
|||
"""Класс, представляющий альбом в событии фида.
|
||||
|
||||
Attributes:
|
||||
album (:obj:`yandex_music.Album` | :obj:`None`): Альбом.
|
||||
album (:obj:`yandex_music.Album`, optional): Альбом.
|
||||
tracks (:obj:`list` из :obj:`yandex_music.Track`): Треки.
|
||||
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.
|
||||
"""
|
||||
|
|
|
@ -12,9 +12,9 @@ class ArtistEvent(YandexMusicObject):
|
|||
"""Класс, представляющий артиста в событии фида.
|
||||
|
||||
Attributes:
|
||||
artist (:obj:`yandex_music.Artist` | :obj:`None`): Артист.
|
||||
tracks (:obj:`list` :obj:`yandex_music.Track`): Треки.
|
||||
similar_to_artists_from_history (:obj:`list` :obj:`yandex_music.Artist`): Похожие артисты из истории.
|
||||
artist (:obj:`yandex_music.Artist`, optional): Артист.
|
||||
tracks (:obj:`list` из :obj:`yandex_music.Track`): Треки.
|
||||
similar_to_artists_from_history (:obj:`list` из :obj:`yandex_music.Artist`): Похожие артисты из истории.
|
||||
subscribed (:obj:`bool`): Подписан ли на событие.
|
||||
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.
|
||||
"""
|
||||
|
|
|
@ -21,6 +21,7 @@ class GeneratedPlaylist(YandexMusicObject):
|
|||
notify (:obj:`bool`): Уведомлён ли пользователь об обновлении содержания.
|
||||
data (:obj:`yandex_music.Playlist`, optional): Сгенерированный плейлист.
|
||||
description (:obj:`list`, optional): Описание TODO.
|
||||
preview_description (:obj:`str`, optional): Короткое описание под блоком лендинга.
|
||||
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.
|
||||
"""
|
||||
|
||||
|
@ -29,6 +30,7 @@ class GeneratedPlaylist(YandexMusicObject):
|
|||
notify: bool
|
||||
data: Optional['Playlist']
|
||||
description: Optional[list] = None
|
||||
preview_description: Optional[str] = None
|
||||
client: Optional['Client'] = None
|
||||
|
||||
def __post_init__(self):
|
||||
|
|
|
@ -40,6 +40,22 @@ class Images(YandexMusicObject):
|
|||
"""
|
||||
self.client.request.download(self._300x300, filename)
|
||||
|
||||
def download_208x208_bytes(self) -> bytes:
|
||||
"""Загрузка изображения 208x208 и возврат в виде байтов.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Изображение в виде байтов.
|
||||
"""
|
||||
return self.client.request.retrieve(self._208x208)
|
||||
|
||||
def download_300x300_bytes(self) -> bytes:
|
||||
"""Загрузка изображения 300x300 и возврат в виде байтов.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Изображение в виде байтов.
|
||||
"""
|
||||
return self.client.request.retrieve(self._300x300)
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: dict, client: 'Client') -> Optional['Images']:
|
||||
"""Десериализация объекта.
|
||||
|
@ -57,3 +73,14 @@ class Images(YandexMusicObject):
|
|||
data = super(Images, cls).de_json(data, client)
|
||||
|
||||
return cls(client=client, **data)
|
||||
|
||||
# camelCase псевдонимы
|
||||
|
||||
#: Псевдоним для :attr:`download_208x208`
|
||||
download208X208 = download_208x208
|
||||
#: Псевдоним для :attr:`download_300x300`
|
||||
download300X300 = download_300x300
|
||||
#: Псевдоним для :attr:`download_208x208_bytes`
|
||||
download208X208Bytes = download_208x208_bytes
|
||||
#: Псевдоним для :attr:`download_300x300_bytes`
|
||||
download300X300Bytes = download_300x300_bytes
|
||||
|
|
|
@ -42,6 +42,28 @@ class Icon(YandexMusicObject):
|
|||
"""
|
||||
await self.client.request.download(self.get_url(size), filename)
|
||||
|
||||
def download_bytes(self, size: str = '200x200') -> bytes:
|
||||
"""Загрузка иконки и возврат в виде байтов.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер иконки.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Иконка в виде байтов.
|
||||
"""
|
||||
return self.client.request.retrieve(self.get_url(size))
|
||||
|
||||
async def download_bytes_async(self, size: str = '200x200') -> bytes:
|
||||
"""Загрузка иконки и возврат в виде байтов.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер иконки.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Иконка в виде байтов.
|
||||
"""
|
||||
return await self.client.request.retrieve(self.get_url(size))
|
||||
|
||||
def get_url(self, size: str = '200x200'):
|
||||
"""Получение URL иконки.
|
||||
|
||||
|
@ -72,3 +94,9 @@ class Icon(YandexMusicObject):
|
|||
|
||||
#: Псевдоним для :attr:`download_async`
|
||||
downloadAsync = download_async
|
||||
#: Псевдоним для :attr:`download_bytes`
|
||||
downloadBytes = download_bytes
|
||||
#: Псевдоним для :attr:`download_bytes_async`
|
||||
downloadBytesAsync = download_bytes_async
|
||||
#: Псевдоним для :attr:`get_url`
|
||||
getUrl = get_url
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from typing import TYPE_CHECKING, Optional, List
|
||||
|
||||
from yandex_music import YandexMusicObject
|
||||
from yandex_music.exceptions import YandexMusicError
|
||||
from yandex_music.utils import model
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -23,7 +24,7 @@ class MixLink(YandexMusicObject):
|
|||
text_color (:obj:`str`): Цвет текста (HEX).
|
||||
background_color (:obj:`str`): Цвет заднего фона.
|
||||
background_image_uri (:obj:`str`): Ссылка на изображение заднего фона.
|
||||
cover_white (:obj:`str`): Ссылка на изображение с обложкой TODO.
|
||||
cover_white (:obj:`str`, optional): Ссылка на изображение с обложкой TODO.
|
||||
cover_uri (:obj:`str`, optional): Ссылка на изображение с обложкой.
|
||||
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.
|
||||
"""
|
||||
|
@ -34,7 +35,7 @@ class MixLink(YandexMusicObject):
|
|||
text_color: str
|
||||
background_color: str
|
||||
background_image_uri: str
|
||||
cover_white: str
|
||||
cover_white: Optional[str] = None
|
||||
cover_uri: Optional[str] = None
|
||||
client: Optional['Client'] = None
|
||||
|
||||
|
@ -46,9 +47,45 @@ class MixLink(YandexMusicObject):
|
|||
self.text_color,
|
||||
self.background_color,
|
||||
self.background_image_uri,
|
||||
self.cover_white,
|
||||
)
|
||||
|
||||
def get_cover_url(self, size: str = '200x200') -> str:
|
||||
"""Возвращает URL обложки.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: URL обложки.
|
||||
"""
|
||||
return f'https://{self.cover_uri.replace("%%", size)}'
|
||||
|
||||
def get_cover_white_url(self, size: str = '200x200') -> str:
|
||||
"""Возвращает URL обложки white.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: URL обложки.
|
||||
"""
|
||||
|
||||
if not self.cover_white:
|
||||
raise YandexMusicError('You can\'t get cover white because it\'s None.')
|
||||
|
||||
return f'https://{self.cover_white.replace("%%", size)}'
|
||||
|
||||
def get_background_url(self, size: str = '200x200') -> str:
|
||||
"""Возвращает URL заднего фона.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер заднего фона.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: URL заднего фона.
|
||||
"""
|
||||
return f'https://{self.background_image_uri.replace("%%", size)}'
|
||||
|
||||
def download_background_image(self, filename: str, size: str = '200x200') -> None:
|
||||
"""Загрузка заднего фона.
|
||||
|
||||
|
@ -56,7 +93,7 @@ class MixLink(YandexMusicObject):
|
|||
filename (:obj:`str`): Путь для сохранения файла с названием и расширением.
|
||||
size (:obj:`str`, optional): Размер заднего фона.
|
||||
"""
|
||||
self.client.request.download(f'https://{self.background_image_uri.replace("%%", size)}', filename)
|
||||
self.client.request.download(self.get_background_url(size), filename)
|
||||
|
||||
async def download_background_image_async(self, filename: str, size: str = '200x200') -> None:
|
||||
"""Загрузка заднего фона.
|
||||
|
@ -65,7 +102,7 @@ class MixLink(YandexMusicObject):
|
|||
filename (:obj:`str`): Путь для сохранения файла с названием и расширением.
|
||||
size (:obj:`str`, optional): Размер заднего фона.
|
||||
"""
|
||||
await self.client.request.download(f'https://{self.background_image_uri.replace("%%", size)}', filename)
|
||||
await self.client.request.download(self.get_background_url(size), filename)
|
||||
|
||||
def download_cover_white(self, filename: str, size: str = '200x200') -> None:
|
||||
"""Загрузка обложки TODO.
|
||||
|
@ -74,7 +111,7 @@ class MixLink(YandexMusicObject):
|
|||
filename (:obj:`str`): Путь для сохранения файла с названием и расширением.
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
"""
|
||||
self.client.request.download(f'https://{self.cover_white.replace("%%", size)}', filename)
|
||||
self.client.request.download(self.get_cover_white_url(size), filename)
|
||||
|
||||
async def download_cover_white_async(self, filename: str, size: str = '200x200') -> None:
|
||||
"""Загрузка обложки TODO.
|
||||
|
@ -83,7 +120,7 @@ class MixLink(YandexMusicObject):
|
|||
filename (:obj:`str`): Путь для сохранения файла с названием и расширением.
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
"""
|
||||
await self.client.request.download(f'https://{self.cover_white.replace("%%", size)}', filename)
|
||||
await self.client.request.download(self.get_cover_white_url(size), filename)
|
||||
|
||||
def download_cover_uri(self, filename: str, size: str = '200x200') -> None:
|
||||
"""Загрузка обложки.
|
||||
|
@ -92,7 +129,7 @@ class MixLink(YandexMusicObject):
|
|||
filename (:obj:`str`): Путь для сохранения файла с названием и расширением.
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
"""
|
||||
self.client.request.download(f'https://{self.cover_uri.replace("%%", size)}', filename)
|
||||
self.client.request.download(self.get_cover_url(size), filename)
|
||||
|
||||
async def download_cover_uri_async(self, filename: str, size: str = '200x200') -> None:
|
||||
"""Загрузка обложки.
|
||||
|
@ -101,7 +138,73 @@ class MixLink(YandexMusicObject):
|
|||
filename (:obj:`str`): Путь для сохранения файла с названием и расширением.
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
"""
|
||||
await self.client.request.download(f'https://{self.cover_uri.replace("%%", size)}', filename)
|
||||
await self.client.request.download(self.get_cover_url(size), filename)
|
||||
|
||||
def download_background_image_bytes(self, size: str = '200x200') -> bytes:
|
||||
"""Загрузка заднего фона и возврат в виде байтов.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер заднего фона.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Задний фон в виде байтов.
|
||||
"""
|
||||
return self.client.request.retrieve(self.get_background_url(size))
|
||||
|
||||
async def download_background_image_bytes_async(self, size: str = '200x200') -> bytes:
|
||||
"""Загрузка заднего фона и возврат в виде байтов.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер заднего фона.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Задний фон в виде байтов.
|
||||
"""
|
||||
return await self.client.request.retrieve(self.get_background_url(size))
|
||||
|
||||
def download_cover_white_bytes(self, size: str = '200x200') -> bytes:
|
||||
"""Загрузка обложки и возврат в виде байтов TODO.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Обложка в виде байтов.
|
||||
"""
|
||||
return self.client.request.retrieve(self.get_cover_white_url(size))
|
||||
|
||||
async def download_cover_white_bytes_async(self, size: str = '200x200') -> bytes:
|
||||
"""Загрузка обложки и возврат в виде байтов TODO.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Обложка в виде байтов.
|
||||
"""
|
||||
return await self.client.request.retrieve(self.get_cover_white_url(size))
|
||||
|
||||
def download_cover_uri_bytes(self, size: str = '200x200') -> bytes:
|
||||
"""Загрузка обложки и возврат в виде байтов.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Обложка в виде байтов.
|
||||
"""
|
||||
return self.client.request.retrieve(self.get_cover_url(size))
|
||||
|
||||
async def download_cover_uri_bytes_async(self, size: str = '200x200') -> bytes:
|
||||
"""Загрузка обложки и возврат в виде байтов.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Обложка в виде байтов.
|
||||
"""
|
||||
return await self.client.request.retrieve(self.get_cover_url(size))
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: dict, client: 'Client') -> Optional['MixLink']:
|
||||
|
@ -143,6 +246,12 @@ class MixLink(YandexMusicObject):
|
|||
|
||||
# camelCase псевдонимы
|
||||
|
||||
#: Псевдоним для :attr:`get_cover_url`
|
||||
getCoverUrl = get_cover_url
|
||||
#: Псевдоним для :attr:`get_cover_white_url`
|
||||
getCoverWhiteUrl = get_cover_white_url
|
||||
#: Псевдоним для :attr:`get_background_url`
|
||||
getBackgroundUrl = get_background_url
|
||||
#: Псевдоним для :attr:`download_background_image`
|
||||
downloadBackgroundImage = download_background_image
|
||||
#: Псевдоним для :attr:`download_background_image_async`
|
||||
|
@ -155,3 +264,15 @@ class MixLink(YandexMusicObject):
|
|||
downloadCoverUri = download_cover_uri
|
||||
#: Псевдоним для :attr:`download_cover_uri_async`
|
||||
downloadCoverUriAsync = download_cover_uri_async
|
||||
#: Псевдоним для :attr:`download_background_image_bytes`
|
||||
downloadBackgroundImageBytes = download_background_image_bytes
|
||||
#: Псевдоним для :attr:`download_background_image_bytes_async`
|
||||
downloadBackgroundImageBytesAsync = download_background_image_bytes_async
|
||||
#: Псевдоним для :attr:`download_cover_white_bytes`
|
||||
downloadCoverWhiteBytes = download_cover_white_bytes
|
||||
#: Псевдоним для :attr:`download_cover_white_bytes_async`
|
||||
downloadCoverWhiteBytesAsync = download_cover_white_bytes_async
|
||||
#: Псевдоним для :attr:`download_cover_uri_bytes`
|
||||
downloadCoverUriBytes = download_cover_uri_bytes
|
||||
#: Псевдоним для :attr:`download_cover_uri_bytes_async`
|
||||
downloadCoverUriBytesAsync = download_cover_uri_bytes_async
|
||||
|
|
|
@ -53,6 +53,17 @@ class Promotion(YandexMusicObject):
|
|||
self.image,
|
||||
)
|
||||
|
||||
def get_image_url(self, size: str = '300x300') -> str:
|
||||
"""Возвращает URL изображения.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер изображения.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: URL изображения.
|
||||
"""
|
||||
return f'https://{self.image.replace("%%", size)}'
|
||||
|
||||
def download_image(self, filename: str, size: str = '300x300') -> None:
|
||||
"""Загрузка рекламного изображения.
|
||||
|
||||
|
@ -60,7 +71,7 @@ class Promotion(YandexMusicObject):
|
|||
filename (:obj:`str`): Путь для сохранения файла с названием и расширением.
|
||||
size (:obj:`str`, optional): Размер изображения.
|
||||
"""
|
||||
self.client.request.download(f'https://{self.image.replace("%%", size)}', filename)
|
||||
self.client.request.download(self.get_image_url(size), filename)
|
||||
|
||||
async def download_image_async(self, filename: str, size: str = '300x300') -> None:
|
||||
"""Загрузка рекламного изображения.
|
||||
|
@ -69,7 +80,29 @@ class Promotion(YandexMusicObject):
|
|||
filename (:obj:`str`): Путь для сохранения файла с названием и расширением.
|
||||
size (:obj:`str`, optional): Размер изображения.
|
||||
"""
|
||||
await self.client.request.download(f'https://{self.image.replace("%%", size)}', filename)
|
||||
await self.client.request.download(self.get_image_url(size), filename)
|
||||
|
||||
def download_image_bytes(self, size: str = '300x300') -> bytes:
|
||||
"""Загрузка рекламного изображения и возврат в виде байтов.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер изображения.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Рекламное изображение в виде байтов.
|
||||
"""
|
||||
return self.client.request.retrieve(self.get_image_url(size))
|
||||
|
||||
async def download_image_bytes_async(self, size: str = '300x300') -> bytes:
|
||||
"""Загрузка рекламного изображения и возврат в виде байтов.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер изображения.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Рекламное изображение в виде байтов.
|
||||
"""
|
||||
return await self.client.request.retrieve(self.get_image_url(size))
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: dict, client: 'Client') -> Optional['Promotion']:
|
||||
|
@ -111,7 +144,13 @@ class Promotion(YandexMusicObject):
|
|||
|
||||
# camelCase псевдонимы
|
||||
|
||||
#: Псевдоним для :attr:`get_image_url`
|
||||
getImageUrl = get_image_url
|
||||
#: Псевдоним для :attr:`download_image`
|
||||
downloadImage = download_image
|
||||
#: Псевдоним для :attr:`download_image_async`
|
||||
downloadImageAsync = download_image_async
|
||||
#: Псевдоним для :attr:`download_image_bytes`
|
||||
downloadImageBytes = download_image_bytes
|
||||
#: Псевдоним для :attr:`download_image_bytes_async`
|
||||
downloadImageBytesAsync = download_image_bytes_async
|
||||
|
|
|
@ -95,9 +95,9 @@ class TrackId(YandexMusicObject):
|
|||
|
||||
# camelCase псевдонимы
|
||||
|
||||
#: Псевдоним для :attr:`track_full_id`
|
||||
trackFullId = track_full_id
|
||||
#: Псевдоним для :attr:`fetch_track`
|
||||
fetchTrack = fetch_track
|
||||
#: Псевдоним для :attr:`fetch_track_async`
|
||||
fetchTrackAsync = fetch_track_async
|
||||
#: Псевдоним для :attr:`track_full_id`
|
||||
trackFullId = track_full_id
|
||||
|
|
|
@ -9,7 +9,7 @@ if TYPE_CHECKING:
|
|||
|
||||
@model
|
||||
class Pager(YandexMusicObject):
|
||||
"""Класс, представляющий пагинатор.
|
||||
"""Класс, представляющий пагинацию.
|
||||
|
||||
Attributes:
|
||||
total (:obj:`int`): Всего треков.
|
||||
|
@ -35,7 +35,7 @@ class Pager(YandexMusicObject):
|
|||
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.
|
||||
|
||||
Returns:
|
||||
:obj:`yandex_music.Pager`: Пагинатор.
|
||||
:obj:`yandex_music.Pager`: Пагинация.
|
||||
"""
|
||||
if not data:
|
||||
return None
|
||||
|
|
|
@ -48,7 +48,7 @@ class CaseForms(YandexMusicObject):
|
|||
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.
|
||||
|
||||
Returns:
|
||||
:obj:`yandex_music.CaseForms`: TODO.
|
||||
:obj:`yandex_music.CaseForms`: Склонение имени.
|
||||
"""
|
||||
if not data:
|
||||
return None
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
from typing import Any, TYPE_CHECKING, Optional, List
|
||||
|
||||
from yandex_music import YandexMusicObject
|
||||
from yandex_music.utils import model
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from yandex_music import Client
|
||||
|
||||
|
||||
@model
|
||||
class CustomWave(YandexMusicObject):
|
||||
"""Класс, представляющий дополнительное описание плейлиста.
|
||||
|
||||
Note:
|
||||
Известные значения `position`: `default`.
|
||||
|
||||
Attributes:
|
||||
title (:obj:`str`): Название плейлиста.
|
||||
animation_url (:obj:`str`): JSON анимация Lottie.
|
||||
position (:obj:`str`): Позиция TODO.
|
||||
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.
|
||||
"""
|
||||
|
||||
title: str
|
||||
animation_url: str
|
||||
position: str
|
||||
client: Optional['Client'] = None
|
||||
|
||||
def __post_init__(self):
|
||||
self._id_attrs = (self.title, self.animation_url, self.position)
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: dict, client: 'Client') -> Optional['CustomWave']:
|
||||
"""Десериализация объекта.
|
||||
|
||||
Args:
|
||||
data (:obj:`dict`): Поля и значения десериализуемого объекта.
|
||||
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.
|
||||
|
||||
Returns:
|
||||
:obj:`yandex_music.CustomWave`: Описание плейлиста.
|
||||
"""
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data = super(CustomWave, cls).de_json(data, client)
|
||||
|
||||
return cls(client=client, **data)
|
|
@ -18,6 +18,8 @@ if TYPE_CHECKING:
|
|||
Contest,
|
||||
OpenGraphData,
|
||||
Brand,
|
||||
Pager,
|
||||
CustomWave,
|
||||
)
|
||||
|
||||
|
||||
|
@ -94,6 +96,8 @@ class Playlist(YandexMusicObject):
|
|||
ready (:obj:`bool`, optional): Готовность TODO.
|
||||
is_for_from: TODO.
|
||||
regions: Регион TODO.
|
||||
custom_wave (:obj:'yandex_music.CustomWave`, optional): Описание плейлиста. TODO.
|
||||
pager (:obj:`yandex_music.Pager`, optional): Пагинатор.
|
||||
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.
|
||||
"""
|
||||
|
||||
|
@ -152,6 +156,8 @@ class Playlist(YandexMusicObject):
|
|||
ready: Optional[bool] = None
|
||||
is_for_from: Any = None
|
||||
regions: Any = None
|
||||
custom_wave: Optional['CustomWave'] = None
|
||||
pager: Optional['Pager'] = None
|
||||
client: Optional['Client'] = None
|
||||
|
||||
def __post_init__(self):
|
||||
|
@ -179,6 +185,28 @@ class Playlist(YandexMusicObject):
|
|||
"""
|
||||
return await self.client.users_playlists_recommendations(self.kind, self.owner.uid, *args, **kwargs)
|
||||
|
||||
def get_animated_cover_url(self, size: str = '300x300') -> str:
|
||||
"""Возвращает URL анимированной обложки.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер анимированной обложки.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: URL анимированной обложки.
|
||||
"""
|
||||
return f'https://{self.animated_cover_uri.replace("%%", size)}'
|
||||
|
||||
def get_og_image_url(self, size: str = '300x300') -> str:
|
||||
"""Возвращает URL обложки.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: URL обложки.
|
||||
"""
|
||||
return f'https://{self.og_image.replace("%%", size)}'
|
||||
|
||||
def download_animated_cover(self, filename: str, size: str = '200x200') -> None:
|
||||
"""Загрузка анимированной обложки.
|
||||
|
||||
|
@ -186,7 +214,7 @@ class Playlist(YandexMusicObject):
|
|||
filename (:obj:`str`): Путь для сохранения файла с названием и расширением (GIF).
|
||||
size (:obj:`str`, optional): Размер анимированной обложки.
|
||||
"""
|
||||
self.client.request.download(f'https://{self.animated_cover_uri.replace("%%", size)}', filename)
|
||||
self.client.request.download(self.get_animated_cover_url(size), filename)
|
||||
|
||||
async def download_animated_cover_async(self, filename: str, size: str = '200x200') -> None:
|
||||
"""Загрузка анимированной обложки.
|
||||
|
@ -195,7 +223,7 @@ class Playlist(YandexMusicObject):
|
|||
filename (:obj:`str`): Путь для сохранения файла с названием и расширением (GIF).
|
||||
size (:obj:`str`, optional): Размер анимированной обложки.
|
||||
"""
|
||||
await self.client.request.download(f'https://{self.animated_cover_uri.replace("%%", size)}', filename)
|
||||
await self.client.request.download(self.get_animated_cover_url(size), filename)
|
||||
|
||||
def download_og_image(self, filename: str, size: str = '200x200') -> None:
|
||||
"""Загрузка обложки.
|
||||
|
@ -206,7 +234,7 @@ class Playlist(YandexMusicObject):
|
|||
filename (:obj:`str`): Путь для сохранения файла с названием и расширением.
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
"""
|
||||
self.client.request.download(f'https://{self.og_image.replace("%%", size)}', filename)
|
||||
self.client.request.download(self.get_og_image_url(size), filename)
|
||||
|
||||
async def download_og_image_async(self, filename: str, size: str = '200x200') -> None:
|
||||
"""Загрузка обложки.
|
||||
|
@ -217,7 +245,55 @@ class Playlist(YandexMusicObject):
|
|||
filename (:obj:`str`): Путь для сохранения файла с названием и расширением.
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
"""
|
||||
await self.client.request.download(f'https://{self.og_image.replace("%%", size)}', filename)
|
||||
await self.client.request.download(self.get_og_image_url(size), filename)
|
||||
|
||||
def download_animated_cover_bytes(self, size: str = '200x200') -> bytes:
|
||||
"""Загрузка анимированной обложки и возврат в виде байтов.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер анимированной обложки.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Анимированная обложка в виде байтов.
|
||||
"""
|
||||
return self.client.request.retrieve(self.get_animated_cover_url(size))
|
||||
|
||||
async def download_animated_cover_bytes_async(self, size: str = '200x200') -> bytes:
|
||||
"""Загрузка анимированной обложки и возврат в виде байтов.
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер анимированной обложки.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Анимированная обложка в виде байтов.
|
||||
"""
|
||||
return await self.client.request.retrieve(self.get_animated_cover_url(size))
|
||||
|
||||
def download_og_image_bytes(self, size: str = '200x200') -> bytes:
|
||||
"""Загрузка обложки и возврат в виде байтов.
|
||||
|
||||
Используйте это только когда нет self.cover!
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Обложка в виде байтов.
|
||||
"""
|
||||
return self.client.request.retrieve(self.get_og_image_url(size))
|
||||
|
||||
async def download_og_image_bytes_async(self, size: str = '200x200') -> bytes:
|
||||
"""Загрузка обложки и возврат в виде байтов.
|
||||
|
||||
Используйте это только когда нет self.cover!
|
||||
|
||||
Args:
|
||||
size (:obj:`str`, optional): Размер обложки.
|
||||
|
||||
Returns:
|
||||
:obj:`bytes`: Обложка в виде байтов.
|
||||
"""
|
||||
return await self.client.request.retrieve(self.get_og_image_url(size))
|
||||
|
||||
def rename(self, name: str) -> None:
|
||||
client, kind = self.client, self.kind
|
||||
|
@ -275,7 +351,7 @@ class Playlist(YandexMusicObject):
|
|||
|
||||
await client.users_playlists(playlist.kind, playlist.owner.id, *args, **kwargs).tracks
|
||||
"""
|
||||
return await self.client.users_playlists(self.kind, self.owner.uid, *args, **kwargs).tracks
|
||||
return (await self.client.users_playlists(self.kind, self.owner.uid, *args, **kwargs)).tracks
|
||||
|
||||
def insert_track(self, track_id: int, album_id: int, *args, **kwargs) -> Optional['Playlist']:
|
||||
"""Сокращение для::
|
||||
|
@ -356,6 +432,8 @@ class Playlist(YandexMusicObject):
|
|||
Contest,
|
||||
OpenGraphData,
|
||||
Brand,
|
||||
CustomWave,
|
||||
Pager,
|
||||
)
|
||||
|
||||
data['owner'] = User.de_json(data.get('owner'), client)
|
||||
|
@ -380,6 +458,9 @@ class Playlist(YandexMusicObject):
|
|||
data['playlist_absence'] = PlaylistAbsence.de_json(data.get('playlist_absense'), client)
|
||||
data.pop('playlist_absense')
|
||||
|
||||
data['custom_wave'] = CustomWave.de_json(data.get('custom_wave'), client)
|
||||
data['pager'] = Pager.de_json(data.get('pager'), client)
|
||||
|
||||
return cls(client=client, **data)
|
||||
|
||||
@classmethod
|
||||
|
@ -408,6 +489,10 @@ class Playlist(YandexMusicObject):
|
|||
getRecommendations = get_recommendations
|
||||
#: Псевдоним для :attr:`get_recommendations_async`
|
||||
getRecommendationsAsync = get_recommendations_async
|
||||
#: Псевдоним для :attr:`get_animated_cover_url`
|
||||
getAnimatedCoverUrl = get_animated_cover_url
|
||||
#: Псевдоним для :attr:`get_og_image_url`
|
||||
getOgImageUrl = get_og_image_url
|
||||
#: Псевдоним для :attr:`download_animated_cover`
|
||||
downloadAnimatedCover = download_animated_cover
|
||||
#: Псевдоним для :attr:`download_animated_cover_async`
|
||||
|
@ -416,6 +501,20 @@ class Playlist(YandexMusicObject):
|
|||
downloadOgImage = download_og_image
|
||||
#: Псевдоним для :attr:`download_og_image_async`
|
||||
downloadOgImageAsync = download_og_image_async
|
||||
#: Псевдоним для :attr:`download_animated_cover_bytes`
|
||||
downloadAnimatedCoverBytes = download_animated_cover_bytes
|
||||
#: Псевдоним для :attr:`download_animated_cover_bytes_async`
|
||||
downloadAnimatedCoverBytesAsync = download_animated_cover_bytes_async
|
||||
#: Псевдоним для :attr:`download_og_image_bytes`
|
||||
downloadOgImageBytes = download_og_image_bytes
|
||||
#: Псевдоним для :attr:`download_og_image_bytes_async`
|
||||
downloadOgImageBytesAsync = download_og_image_bytes_async
|
||||
#: Псевдоним для :attr:`rename_async`
|
||||
renameAsync = rename_async
|
||||
#: Псевдоним для :attr:`like_async`
|
||||
likeAsync = like_async
|
||||
#: Псевдоним для :attr:`dislike_async`
|
||||
dislikeAsync = dislike_async
|
||||
#: Псевдоним для :attr:`fetch_tracks`
|
||||
fetchTracks = fetch_tracks
|
||||
#: Псевдоним для :attr:`fetch_tracks_async`
|
||||
|
@ -428,11 +527,5 @@ class Playlist(YandexMusicObject):
|
|||
deleteTracks = delete_tracks
|
||||
#: Псевдоним для :attr:`delete_tracks_async`
|
||||
deleteTracksAsync = delete_tracks_async
|
||||
#: Псевдоним для :attr:`rename_async`
|
||||
renameAsync = rename_async
|
||||
#: Псевдоним для :attr:`like_async`
|
||||
likeAsync = like_async
|
||||
#: Псевдоним для :attr:`dislike_async`
|
||||
dislikeAsync = dislike_async
|
||||
#: Псевдоним для :attr:`delete_async`
|
||||
deleteAsync = delete_async
|
||||
|
|
|
@ -49,3 +49,4 @@ class Tag(YandexMusicObject):
|
|||
return cls(client=client, **data)
|
||||
|
||||
# TODO (MarshalX) add download_og_image shortcut?
|
||||
# https://github.com/MarshalX/yandex-music-api/issues/556
|
||||
|
|
|
@ -47,3 +47,4 @@ class TagResult(YandexMusicObject):
|
|||
return cls(client=client, **data)
|
||||
|
||||
# TODO (MarshalX) add fetch_playlists shortcut?
|
||||
# https://github.com/MarshalX/yandex-music-api/issues/551
|
||||
|
|
|
@ -19,7 +19,7 @@ class DiscreteScale(YandexMusicObject):
|
|||
name (:obj:`str`): Название.
|
||||
min (:obj:`yandex_music.Value`): Минимальное значение.
|
||||
max (:obj:`yandex_music.Value`): Максимальное значение.
|
||||
client (:obj:`yandex_music.Client` optional): Клиент Yandex Music.
|
||||
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.
|
||||
"""
|
||||
|
||||
type: str
|
||||
|
|
|
@ -14,7 +14,7 @@ class Enum(YandexMusicObject):
|
|||
Attributes:
|
||||
type (:obj:`str`): Тип перечисления.
|
||||
name (:obj:`str`): Название перечисления.
|
||||
possible_values (:obj:`list` из :obj:`yandex_Music.Value`): Доступные значения.
|
||||
possible_values (:obj:`list` из :obj:`yandex_music.Value`): Доступные значения.
|
||||
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.
|
||||
**kwargs: Произвольные ключевые аргументы полученные от API.
|
||||
"""
|
||||
|
|
変更されたファイルが多すぎるため、一部のファイルは表示されません さらに表示
読み込み中…
新しいイシューから参照