Merge pull request #575 from MarshalX/dev

2.1.0
このコミットが含まれているのは:
Ilya Siamionau 2023-04-23 02:40:44 +02:00 committed by GitHub
コミット 84d0aabb91
この署名に対応する既知のキーがデータベースに存在しません
GPGキーID: 4AEE18F83AFDEB23
117個のファイルの変更4088行の追加1853行の削除

ファイルの表示

@ -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

390
CHANGES.md ノーマルファイル
ファイルの表示

@ -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)).
- Исправлена загрузка обложки у трека.
- Исправлены сравнения объектов.

ファイルの表示

@ -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

ファイルの表示

@ -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

31
Makefile ノーマルファイル
ファイルの表示

@ -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

301
README.md ノーマルファイル
ファイルの表示

@ -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, но приложения, которые используют библиотеку, необязательно.

ファイルの表示

@ -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

2
docs/source/changes.md ノーマルファイル
ファイルの表示

@ -0,0 +1,2 @@
```{include} ../../CHANGES.md
```

ファイルの表示

@ -1 +0,0 @@
.. include:: ../../CHANGES.rst

22
docs/source/client.md ノーマルファイル
ファイルの表示

@ -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
```

46
docs/source/client_async.md ノーマルファイル
ファイルの表示

@ -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
```

2
docs/source/code_of_conduct.md ノーマルファイル
ファイルの表示

@ -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 -------------------------------------------------

2
docs/source/contributing.md ノーマルファイル
ファイルの表示

@ -0,0 +1,2 @@
```{include} ../../CONTRIBUTING.md
```

40
docs/source/examples.chart.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))
```

44
docs/source/examples.get_album_with_tracks.md ノーマルファイル
ファイルの表示

@ -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)
```

32
docs/source/examples.like_and_dislike.md ノーマルファイル
ファイルの表示

@ -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)
# и т.д. Читайте документацию.
```

34
docs/source/examples.lyrics_playing_track.md ノーマルファイル
ファイルの表示

@ -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('Текст песни отсутствует')
```

25
docs/source/examples.md ノーマルファイル
ファイルの表示

@ -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)!

32
docs/source/examples.proxy.md ノーマルファイル
ファイルの表示

@ -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)
```

70
docs/source/examples.search.md ノーマルファイル
ファイルの表示

@ -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

173
docs/source/licence.md ノーマルファイル
ファイルの表示

@ -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.
```

5
docs/source/module.md ノーマルファイル
ファイルの表示

@ -0,0 +1,5 @@
# Модуль
```{eval-rst}
.. include:: yandex_music.rst
```

247
docs/source/readme.content.md ノーマルファイル
ファイルの表示

@ -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)

20
docs/source/readme.md ノーマルファイル
ファイルの表示

@ -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

2
docs/source/security.md ノーマルファイル
ファイルの表示

@ -0,0 +1,2 @@
```{include} ../../SECURITY.md
```

17
docs/source/token.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:

7
docs/source/yandex_music.track.r128.rst ノーマルファイル
ファイルの表示

@ -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)
Получение цепочки треков определённой станции. Читайте примечание.
Примеры:

24
generate_async_version.py ノーマルファイル → 実行可能ファイル
ファイルの表示

@ -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)

99
generate_camel_case_aliases.py 実行可能ファイル
ファイルの表示

@ -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()

ファイルの表示

@ -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)

14
tests/test_convert_track_id.py ノーマルファイル
ファイルの表示

@ -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

52
tests/test_custom_wave.py ノーマルファイル
ファイルの表示

@ -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)

34
tests/test_lyrics_info.py ノーマルファイル
ファイルの表示

@ -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

42
tests/test_lyrics_major.py ノーマルファイル
ファイルの表示

@ -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)

33
tests/test_r128.py ノーマルファイル
ファイルの表示

@ -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

23
tests/test_sign_request.py ノーマルファイル
ファイルの表示

@ -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)

61
tests/test_track_lyrics.py ノーマルファイル
ファイルの表示

@ -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

48
yandex_music/playlist/custom_wave.py ノーマルファイル
ファイルの表示

@ -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.
"""

変更されたファイルが多すぎるため、一部のファイルは表示されません さらに表示