Как я библиотеку для сервиса «Яндекс.Музыка» писал

в 16:42, , рубрики: api, python, Яндекс API

Введение

Обо мне

Всем привет, я обычный учащийся по специальности "техник-программист". С детства увлекаюсь компьютерами, с класса 7-го начал познавать само программирование. Являюсь владельцем подписки на Яндексу Музыку уже больше года и в целом доволен сервисом (правда сейчас в плейлисте дня сплошные повторы).

Предыстория

Уж не помню точно, из-за чего я решил поискать официальную документацию API данного сервиса, вроде бота хотел для Telegram написать, но столкнулся с тем, что её нет… Спустя некоторое время наткнулся на issue в репозитории yandex/audio-js. Там ребятки задают точно такой же вопрос, как и я: "А где API?". Не многие горят желанием слушать музыку через браузер, они хотят приложение, но приложения под Linux тоже нет! Интегрировать к своему любимому плееру невозможно!

Тут я загорелся идеей сделать это. Естественно, мне нужно как-то работать с сервисом, городить костыли вокруг веб-приложения не вариант. Я понимал, что имея такой сервис, имея мобильные приложения и приложения под Windows (из Microsoft Store) просто невозможно не иметь своё внутреннее API для взаимодействия. Я оказался прав!

Обязательно к прочтению перед основной частью

Я отдаю себе отчёт в том, что, изучая их непубличное API я роюсь в чужих грязных вещах. Ниже будут описаны различные спорные моменты, решения разработчиков и в целом то, как это написали, как они этим пользуются. Местами я был просто шокирован, но я уверен, что если они так сделали, то на это были свои причины! Не будем забывать, что это никто не должен был видеть. Так же хочу сказать, что всё написанное ниже моё мнение. Вы можете с ним согласить или нет.

Основная часть

Подготовка

API веб-приложения

Выше я уже написал, что нашёл API. Это было вовсе не сложно. Первым делом я глянул на их веб-приложение, их эндпоинт на момент написания статьи находится здесь: https://music.yandex.ru/api/v2.1/. У них достаточно длинные урлы получаются в которых участвую данные, а еще и форму отправляют. Так же прошу обратить внимание на указание версии API, оно есть.

Надо понимать, что то, что я нашел, используется ими только в веб-приложении. Тут нет OAuth. Точнее оно скорее и есть, но там, в недрах нашей сессии на сайте. В общем для библиотеки не годится в связи с костылями в авторизации.

API приложений

Отправился я искать дальше. Телефон брать было лень, следовательно, до мобильных приложений я дошёл бы в последнюю очередь. На то время компьютер работал под ОС Windows 10, и я активно пользовался официальным приложением Яндекс Музыки из Microsoft Store. Вследствие чего я приступил к изучению того, как оно работает.

Для изучения мне понадобился сниффер, чтобы отслеживать весь трафик приложения. Можно было использовать Wireshark, но я остановился на HTTP Analyzer. Он мне кажется более легковесным и отлично подходящим под мою задачу.

Включаем сниффер, заходим в приложение и готово. Запросы текут ручьем. Сидим, разбираемся, пытаемся вызвать каждый обработчик что есть в этом приложении и узнать о всех существующих методах, их аргументах и конечно же JSON ответах.

Скриншот одного из запросов

Из скриншота выше сразу можно заметить совершенно другой адрес API — api.music.yandex.net. Более того, обратите внимание на заголовки. Помимо информации о моём клиенте с которого был выполнен запрос там есть OAuth токен — то, что надо!

Изучение API

Изучение проходило совместно с написанием кода. Я писал классы-обёртки для объектов сервиса получаемого от API, реализовывал отправку запросов, разбирался с параметрами и местами просто догадывался что это название может означать. На этом этапе я и повстречал различные вещи, которые не ожидал тут увидеть.

На момент написания статьи библиотека содержит 83 класса и лишь некоторые из них вспомогательные. Остальные же являются классами Яндекс Музыки, что говорит о масштабности данного сервиса и уровню абстракций.

Была реализована отправка ~47 методов. И это далеко не все, что есть в API (об этом ниже).

Боль

На первых порах я старался не обращать внимание, просто удивлялся, ведь это Яндекс, как такое может быть. Но потом, в один прекрасный момент, всё, бомбанул. Начну, пожалуй, с него.

  1. Два объекта с разным уровнем вложения поля

    Новая версия объекта

    Сам объект является что ль "ссылкой" на самого себя. На свою полную версию. При запросе списка треков нам отдают их ID, по которым мы можем получить более подробную информацию. Хорошая практика, так делают многие, но она не везде соблюдается (пункт 9).

    Старая версия объекта

    Реализовав в самом начале класс для данного объекта я думал, что буду использовать его везде, но как бы не так! Мне кажется, комментарии излишни и всё видно на скриншотах.

    Я никак не исправлял подобного рода косяки в своей библиотеке, поэтому имея класс TrackShort теперь есть TrackShortOld.

    Кстати, оба этих объекта живут в одном методе, в методе получения landing'a.

  2. Версии API, методов

    Я не просто так попросил Вас обратить внимание на то, как указывается версия в API для веб-приложения. Вообще, как мы обычно указываем версию? Наверное, одним из следующих способов:

    • вынести версию на отдельный поддомен;
    • вынести версию в часть запроса;
    • передавать желаемую версию API параметром к запросу.

    В Яндекс решили в данном случае сделать иначе. У нас есть метод landing3 — актуальная его версия на момент написания статьи. Но никто не запрещает отправить запрос на landing2 — совершенно другая структура, другие объекты.

    Обнаружил я это совершенно случайно, просто забыв дописать цифру в конец названия метода и словив груду исключений.

  3. Работая с новым, не отказываемся от старого

    Увидел я это когда писал отправку методов "Мне нравится" для всех объектов что есть. Их на самом деле не много (плейлист, артист, трек, альбом). Какого было моё удивление, когда я увидел разные подходы к одному и тому же действию.

    Артистов мы лайкаем так: https://api.music.yandex.net/users/<USER_ID>/likes/artists/add и в форме передаем artist-id.

    Треки мы лайкаем так: https://api.music.yandex.net/users/<USER_ID>/likes/tracks/add-multiple и в форме track-ids.

    Если Вы не заметили, то при лайке трека используется метод add-multiple, а не add. Ни с какими другими типами этот метод не используется, но они все существуют (стоило просто попробовать отправить запрос)! И именно их я реализовал в своей библиотеке вместо add. Ведь данный метод универсален. Можно добавить как один трек, так и несколько.

  4. Что такое уникальный идентификатор трека

    Прошло уже много времени, но я до сих пор не пойму, когда надо отправлять просто id трека, а когда конкатенацию id и album_id через двоеточие (id:album_id). Иногда трек в нескольких альбомах, иногда альбома нет. Слишком непонятные кейсы глядя со стороны, я не знаю как они с этим справляются (или не справляются, багусик 2).

  5. Необязательность многих полей

    Мне накидали пару issues. Если есть проблема, то она связана с обязательностью поля. Я не перестаю удивляться, как, на мой взгляд, обязательные поля просто не возвращаются API.

    • album_id класса TrackID и TrackShort;
    • order_id класса AutoRenewable (подписка);
    • next_revision в Feed;
    • cover_uri в Track;
    • birthday в Account;
    • tags в Playlist.

    Список можно продолжать дальше, но всё есть в истории коммитов. Возможно, данный пункт высосан из пальца.

  6. Схожесть методов за исключением некоторых полей в ответе

    Ответ статуса аккаунта (api.music.yandex.net/account/status):

    Ответ статуса аккаунта

    Ответ радио статуса аккаунта (https://api.music.yandex.net/rotor/account/status):

    Ответ радио статуса аккаунта

    Я понимаю, что права разные, поля теперь с лимитом не на количество треков в кеше, а на количество скипов в час, но больше смахивает на какое-то дублирование.

    Не знаю как в Яндексе, но я у себя слил в один класс.

  7. Дык один или много?

    Я всегда считал, что если метод возвращает список, то даже если результатом является один элемент, то вернётся список содержащий этот элемент и никак иначе, а тут и то, и другое.

    feature и features

    То feature вернется, то features, то feature и features.

  8. Неправильное использование методов

    Выше я написал о том, что используют то один, то другой метод для осуществления одного действия. Они пошли дальше.

    Отправка удаляемых треков, когда бек прекрасно знает о них

    На метод удаления треков из плейлиста, помимо самого ID плейлиста и рамок с и по какой трек удалить, они зачем-то передают треки, которые будут удалены. Вполне возможно, что это я не понял, как и всё остальное, но метод работает и без лишней информации. А какие треки были удалены лучше узнать на беке, нежели передавать параметром.

  9. Очень тяжёлые запросы

    Выше я писал, что отдавать список с ID треков является хорошей практикой, Вы получаете подробную информацию о треке только тогда, когда она Вам реально нужна. Это используется тут далеко не всегда.

    Гляньте как они беспощадно отдают подробную информацию всех моих треков из плейлиста "Мне нравится" в одном запросе:

    Тяжёлый запрос

    Оно отдало все 396 треков! Bytes Received: 3,75M, а это ещё обложки загрузить!

Багусики

  1. Загрузка всех треков в кеш из "Мне нравится"

    При достижении лимита происходило добавление в конец и удалялось с начала. Спасибо за визуализацию стека, но я думал мне просто загрузит 100 последних треков из плейлиста. Произошло это в мобильном клиенте под Android (смотреть видео).

  2. Видать не один я путаюсь, когда надо отправить id, а когда id:album_id

    Дубликаты треков

Заметки

Количество попыток на активацию подарочного кода — 10. Дальше бан на 24 часа.

В зависимости от приложения, с которого Вы сидите, Вам делают разные предложения о покупке подписки.

Лимит на количество треков в кеше иллюзия, просто число, а уже приложение не дает загрузить больше (багусик 2).

Все эти умные плейлисты, предложения, текста и цвета кнопок приходят от API — вот он, настоящий RESTFull.

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

Ссылка на XML, содержащий данные о расположении файла для загрузки живёт 1 минуту, потом 410 ошибка.

Заключение

Написал лишь то, что вспомнил. Ведь сталкивался со всем этим я на протяжении нескольких месяцев. Все мои записи — это сообщения в телеграмме, ибо когда я натыкался на что-то этакое, я делился с друзьями. Постарался восстановить ключевые моменты.

Я ни в коем случае не хотел сказать как всё плохо, как-то выставить специально косяки на публику. Возможно, это и не косяки вовсе, но всё что я написал выше кажется лично для меня странным.

Поделился с Вами тем, как писал библиотеку для закрытого API сервиса "Яндекс.Музыка" и с какими вещами столкнулся во время разработки.

Теперь Вы знаете, как и на чём работает их приложение для Windows, а следовательно, и моя библиотека.

Кстати, сейчас я стараюсь это всё задокументировать, документируя свою библиотеку я автоматически документирую их API. Туго идёт, да и надо себе ещё успеть компанию для прохождения производственной технологической практики найти.

Спасибо что дочитали аж до сюда!

Автор: MarshalX

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js