удалено получение токена по логину и паролю
このコミットが含まれているのは:
コミット
4a11b73e57
66
README.rst
66
README.rst
|
@ -89,17 +89,13 @@ Yandex Music API
|
|||
классов-обёрток объектов высокого уровня дабы сделать разработку клиентов
|
||||
и скриптов простой и понятной.
|
||||
|
||||
--------------------------------
|
||||
Доступ к вашим данным на Яндексе
|
||||
--------------------------------
|
||||
-----------------------------------
|
||||
Доступ к вашим данным Яндекс.Музыка
|
||||
-----------------------------------
|
||||
|
||||
Значения констант
|
||||
`CLIENT_ID и CLIENT_SECRET <https://github.com/MarshalX/yandex-music-api/blob/main/yandex_music/client.py#L52>`_
|
||||
позаимствовано у официального приложения-клиента сервиса Яндекс.Музыка из магазина
|
||||
Microsoft Store. Так как API является закрытым и используется только внутри
|
||||
компании Яндекс сейчас невозможно зарегистрировать своё собственное приложение на
|
||||
`oauth.yandex.ru <https://oauth.yandex.ru/>`_, а следовательно, использовать свои
|
||||
значения констант.
|
||||
Начиная с версии 2.0.0 библиотека больше не предоставляет интерфейсы для работы
|
||||
с OAuth Яндекс и Яндекс.Паспорт. Задача по получению токена для доступа к данным
|
||||
на плечах разработчиков использующих данную библиотеку.
|
||||
|
||||
=========
|
||||
Установка
|
||||
|
@ -133,26 +129,20 @@ Microsoft Store. Так как API является закрытым и испо
|
|||
|
||||
client = Client()
|
||||
|
||||
Работа без авторизации ограничена. Так, например, для загрузки будут доступны
|
||||
только первые 30 секунд аудиофайла. Для понимания всех ограничений зайдите на
|
||||
сайт Яндекс.Музыка под инкогнито и воспользуйтесь сервисом.
|
||||
|
||||
Для доступа к своим личным данным следует авторизоваться.
|
||||
Это можно осуществить через OAuth токен или логин с паролем.
|
||||
Это осуществляется через токен аккаунта Яндекс.Музыка.
|
||||
|
||||
Авторизация по логину и паролю:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from yandex_music import Client
|
||||
|
||||
client = Client.from_credentials('example@yandex.com', 'password')
|
||||
|
||||
Авторизация по токену:
|
||||
Авторизация:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from yandex_music import Client
|
||||
|
||||
client = Client('token')
|
||||
# или
|
||||
client = Client.from_token('token')
|
||||
|
||||
После успешного создания клиента Вы вольны в выборе необходимого метода
|
||||
из API. Все они доступны у объекта класса ``Client``. Подробнее в методах клиента
|
||||
|
@ -164,7 +154,7 @@ Microsoft Store. Так как API является закрытым и испо
|
|||
|
||||
from yandex_music import Client
|
||||
|
||||
client = Client.from_credentials('example@yandex.com', 'password')
|
||||
client = Client('token')
|
||||
client.users_likes_tracks()[0].fetch_track().download('example.mp3')
|
||||
|
||||
В примере выше клиент получает список треков которые были отмечены как
|
||||
|
@ -208,36 +198,6 @@ music.yandex.ru/album/**1193829**/track/**10994777**
|
|||
|
||||
Больше примеров тут: `proxies - advanced usage - requests <https://2.python-requests.org/en/master/user/advanced/#proxies>`_
|
||||
|
||||
Пример инициализации клиента с обработкой капчи при помощи callback-функции:
|
||||
|
||||
.. code:: python
|
||||
|
||||
def proc_captcha(captcha_image_url):
|
||||
print(captcha_image_url)
|
||||
return input('Код с картинки: ')
|
||||
|
||||
client = Client.from_credentials('login', 'pass', captcha_callback=proc_captcha)
|
||||
|
||||
Пример инициализации клиента с обработкой капчи:
|
||||
|
||||
.. code:: python
|
||||
|
||||
def init_client():
|
||||
client = track_id = captcha_image_url = captcha_answer = None
|
||||
while not client:
|
||||
try:
|
||||
client = Client.from_credentials('login', 'pass', track_id, captcha_answer)
|
||||
except Captcha as e:
|
||||
track_id = e.track_id
|
||||
if e.captcha_image_url:
|
||||
captcha_image_url = e.captcha_image_url
|
||||
else:
|
||||
print('Вы отправили ответ не посмотрев на картинку..')
|
||||
|
||||
captcha_answer = input(f'{captcha_image_url}\nВведите код с картинки: ')
|
||||
|
||||
return client
|
||||
|
||||
--------------------
|
||||
Изучение по примерам
|
||||
--------------------
|
||||
|
|
|
@ -6,15 +6,10 @@ from yandex_music.client import Client
|
|||
if len(sys.argv) == 1 or len(sys.argv) > 3:
|
||||
print('Usage: DailyPlaylistUpdater.py token')
|
||||
print('token - Authentication token')
|
||||
print('\nUsage: DailyPlaylistUpdater.py username password')
|
||||
print('username - username in format \'example@yandex.ru\'')
|
||||
print('password - your password')
|
||||
quit()
|
||||
# Authorization
|
||||
elif len(sys.argv) == 2:
|
||||
client = Client.fromToken(sys.argv[1])
|
||||
elif len(sys.argv) == 3:
|
||||
client = Client.fromCredentials(sys.argv[1], sys.argv[2])
|
||||
client = Client(sys.argv[1])
|
||||
|
||||
# Current daily playlist
|
||||
PersonalPlaylistBlocks = client.landing(blocks=['personalplaylists']).blocks[0]
|
||||
|
|
|
@ -53,7 +53,7 @@ else:
|
|||
print('Config file not found. Use --token to create it')
|
||||
sys.exit(2)
|
||||
|
||||
client = Client.from_token(args.token, report_new_fields=False)
|
||||
client = Client(args.token, report_unknown_fields=False)
|
||||
|
||||
print('Hello,', client.me.account.first_name)
|
||||
if client.me.account.now and client.me.account.now.split('T')[0] == client.me.account.birthday:
|
||||
|
@ -65,7 +65,7 @@ if args.playlist == 'user':
|
|||
print('specify --playlist-name', list(p.title for p in user_playlists))
|
||||
sys.exit(1)
|
||||
playlist = next((p for p in user_playlists if p.title == args.playlist_name), None)
|
||||
if playlist == None:
|
||||
if playlist is None:
|
||||
print(f'playlist "{args.playlist_name}" not found')
|
||||
sys.exit(1)
|
||||
total_tracks = playlist.track_count
|
||||
|
|
|
@ -7,7 +7,7 @@ from yandex_music import Client
|
|||
from radio import Radio
|
||||
|
||||
# API instance
|
||||
client = Client(token="YOUR_TOKEN_HERE")
|
||||
client = Client(token='YOUR_TOKEN_HERE')
|
||||
|
||||
# Get random station
|
||||
_stations = client.rotor_stations_list()
|
||||
|
@ -21,10 +21,10 @@ radio = Radio(client)
|
|||
|
||||
# start radio and get first track
|
||||
first_track = radio.start_radio(_station_id, _station_from)
|
||||
print("[Radio] First track is:", first_track)
|
||||
print('[Radio] First track is:', first_track)
|
||||
|
||||
# get new track every 5 sec
|
||||
while True:
|
||||
sleep(5)
|
||||
next_track = radio.play_next()
|
||||
print("[Radio] Next track is:", next_track)
|
||||
print('[Radio] Next track is:', next_track)
|
||||
|
|
|
@ -5,7 +5,7 @@ from yandex_music import Client
|
|||
from radio import Radio
|
||||
|
||||
# API instance
|
||||
client = Client(token="YOUR_API_KEY_HERE")
|
||||
client = Client(token='YOUR_API_KEY_HERE')
|
||||
|
||||
# get some track
|
||||
track = client.tracks(['2816574:303266'])[0]
|
||||
|
@ -26,10 +26,10 @@ radio = Radio(client)
|
|||
|
||||
# start radio and get first track
|
||||
first_track = radio.start_radio(_station_id, _station_from)
|
||||
print("[Radio] First track is:", first_track)
|
||||
print('[Radio] First track is:', first_track)
|
||||
|
||||
# get new track every 5 sec
|
||||
while True:
|
||||
sleep(5)
|
||||
next_track = radio.play_next()
|
||||
print("[Radio] Next track is:", next_track)
|
||||
print('[Radio] Next track is:', next_track)
|
||||
|
|
|
@ -42,16 +42,9 @@ from yandex_music import (
|
|||
__license__,
|
||||
__version__,
|
||||
)
|
||||
from yandex_music.exceptions import BadRequest, Captcha, CaptchaNotShown, CaptchaRequired, Unauthorized
|
||||
from yandex_music.utils.difference import Difference
|
||||
from yandex_music.utils.request import Request
|
||||
|
||||
|
||||
CLIENT_ID = '23cabbbdc6cd418abb4b39c32c41195d'
|
||||
CLIENT_SECRET = '53bc75238f0c4d08a118e51fe9203300'
|
||||
X_TOKEN_CLIENT_ID = 'c0ebe342af7d48fbbbfcf2d2eedb8f9e'
|
||||
X_TOKEN_CLIENT_SECRET = 'ad0a908f0aa341a182a37ecd75bc319e'
|
||||
|
||||
de_list = {
|
||||
'artist': Artist.de_list,
|
||||
'album': Album.de_list,
|
||||
|
@ -96,7 +89,6 @@ class Client(YandexMusicObject):
|
|||
logger (:obj:`logging.Logger`): Объект логгера.
|
||||
token (:obj:`str`): Уникальный ключ для аутентификации.
|
||||
base_url (:obj:`str`): Ссылка на API Yandex Music.
|
||||
passport_url (:obj:`str`): Ссылка на Mobileproxy Passport Yandex Music.
|
||||
me (:obj:`yandex_music.Status`): Информация об аккаунте.
|
||||
device (:obj:`str`): Строка, содержащая сведения об устройстве, с которого выполняются запросы.
|
||||
report_unknown_fields (:obj:`bool`): Включены ли предупреждения о неизвестных полях от API,
|
||||
|
@ -106,7 +98,6 @@ class Client(YandexMusicObject):
|
|||
token (:obj:`str`, optional): Уникальный ключ для аутентификации.
|
||||
fetch_account_status (:obj:`bool`, optional): Получить ли информацию об аккаунте при инициализации объекта.
|
||||
base_url (:obj:`str`, optional): Ссылка на API Yandex Music.
|
||||
passport_url (:obj:`str`, optional): Ссылка на Mobileproxy Passport Yandex Music.
|
||||
request (:obj:`yandex_music.utils.request.Request`, optional): Пре-инициализация
|
||||
:class:`yandex_music.utils.request.Request`.
|
||||
language (:obj:`str`, optional): Язык, на котором будут приходить ответы от API.
|
||||
|
@ -121,7 +112,6 @@ class Client(YandexMusicObject):
|
|||
token: str = None,
|
||||
fetch_account_status: bool = True,
|
||||
base_url: str = None,
|
||||
passport_url: str = None,
|
||||
request: Request = None,
|
||||
language: str = 'ru',
|
||||
report_unknown_fields=True,
|
||||
|
@ -136,11 +126,8 @@ class Client(YandexMusicObject):
|
|||
|
||||
if base_url is None:
|
||||
base_url = 'https://api.music.yandex.net'
|
||||
if passport_url is None:
|
||||
passport_url = 'https://mobileproxy.passport.yandex.net/'
|
||||
|
||||
self.base_url = base_url
|
||||
self.passport_url = passport_url
|
||||
|
||||
self.report_new_fields = report_unknown_fields
|
||||
|
||||
|
@ -157,222 +144,11 @@ class Client(YandexMusicObject):
|
|||
'os=Python; os_version=; manufacturer=Marshal; '
|
||||
'model=Yandex Music API; clid=; device_id=random; uuid=random'
|
||||
)
|
||||
self._auth_sdk_params = 'app_id=ru.yandex.mobile.music&app_version_name=5.18&app_platform=iPad'
|
||||
|
||||
self.me = None
|
||||
if fetch_account_status:
|
||||
self.me = self.account_status()
|
||||
|
||||
@classmethod
|
||||
def from_credentials(
|
||||
cls,
|
||||
login: str,
|
||||
password: str,
|
||||
track_id: str = None,
|
||||
captcha_answer: str = None,
|
||||
captcha_callback: Callable[[str], str] = None,
|
||||
timeout: Union[int, float] = None,
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> 'Client':
|
||||
"""Инициализция клиента по логину и паролю.
|
||||
|
||||
Note:
|
||||
Данный метод получает токен каждый раз при вызове. Рекомендуется сгенерировать его самостоятельно, сохранить
|
||||
и использовать при следующих инициализациях клиента. Не храните логины и пароли!
|
||||
|
||||
Args:
|
||||
login (:obj:`str`): Логин клиента (идентификатор).
|
||||
password (:obj:`str`): Пароль клиента (аутентификатор).
|
||||
track_id (:obj:`str`): Идентификатора сессии аутентификации (для ввода капчи на старой сессии).
|
||||
captcha_answer (:obj:`str`, optional): Ответ на капчу (цифры с картинки).
|
||||
captcha_callback (:obj:`function`, optional): Функция обратного вызова для обработки капчи, должна
|
||||
принимать строку с ссылкой на капчу и возвращать проверочный код.
|
||||
timeout (:obj:`int` | :obj:`float`, optional): Если это значение указано, используется как время ожидания
|
||||
ответа от сервера вместо указанного при создании пула.
|
||||
*args: Произвольные аргументы для `requests.request` и `Client`.
|
||||
**kwargs: Произвольные ключевые аргументы для `requests.request` и `Client`.
|
||||
|
||||
Returns:
|
||||
:obj:`yandex_music.Client`.
|
||||
|
||||
Raises:
|
||||
:class:`yandex_music.exceptions.CaptchaRequired`: При необходимости пройти капчу и отсутствию коллбека.
|
||||
:class:`yandex_music.exceptions.CaptchaNotShown`: При попытке отправить ответ на капчу не посмотрев её.
|
||||
:class:`yandex_music.exceptions.YandexMusicError`: Базовое исключение библиотеки.
|
||||
"""
|
||||
|
||||
client = cls(*args, **kwargs)
|
||||
|
||||
if not track_id:
|
||||
track_id = client._start_authentication(login, timeout, *args, **kwargs)
|
||||
|
||||
x_token = None
|
||||
while not x_token:
|
||||
try:
|
||||
x_token = client._send_authentication_password(
|
||||
track_id, password, captcha_answer, timeout, *args, **kwargs
|
||||
)
|
||||
except Captcha as e:
|
||||
if not captcha_callback or not e.captcha_image_url:
|
||||
raise e
|
||||
|
||||
captcha_answer = captcha_callback(e.captcha_image_url)
|
||||
|
||||
token = client._generate_yandex_music_token_by_x_token(x_token, timeout, *args, **kwargs)
|
||||
return cls(token, *args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_token(cls, token: str, *args, **kwargs) -> 'Client':
|
||||
"""Инициализация клиента по токену.
|
||||
|
||||
Note:
|
||||
Ничем не отличается от `Client(token)`. Так исторически сложилось.
|
||||
|
||||
Args:
|
||||
token (:obj:`str`, optional): Уникальный ключ для аутентификации.
|
||||
*args: Произвольные аргументы для `Client`.
|
||||
**kwargs: Произвольные ключевые аргументы для `Client`.
|
||||
|
||||
Returns:
|
||||
:obj:`yandex_music.Client`.
|
||||
"""
|
||||
|
||||
return cls(token, *args, **kwargs)
|
||||
|
||||
@log
|
||||
def _start_authentication(self, login: Union[str, int], timeout: Union[int, float] = None, *args, **kwargs) -> str:
|
||||
"""Отправка логина и проверка на его существование. Получение идентификатора аутентификации.
|
||||
|
||||
Args:
|
||||
login (:obj:`str`): Логин клиента (идентификатор).
|
||||
timeout (:obj:`int` | :obj:`float`, optional): Если это значение указано, используется как время ожидания
|
||||
ответа от сервера вместо указанного при создании пула.
|
||||
*args: Произвольные аргументы для `requests.request`.
|
||||
**kwargs: Произвольные ключевые аргументы для `requests.request`.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: `track_id` необходимый для отправки пароля к логину.
|
||||
|
||||
Raises:
|
||||
:class:`yandex_music.exceptions.BadRequest`: При неправильном запросе.
|
||||
"""
|
||||
|
||||
url = f'{self.passport_url}/2/bundle/mobile/start'
|
||||
|
||||
data = {
|
||||
'client_id': CLIENT_ID,
|
||||
'client_secret': CLIENT_SECRET,
|
||||
'display_language': self.language,
|
||||
'login': login,
|
||||
'x_token_client_id': X_TOKEN_CLIENT_ID,
|
||||
'x_token_client_secret': X_TOKEN_CLIENT_SECRET,
|
||||
}
|
||||
|
||||
result = self._request.post(url, data, timeout=timeout, *args, **kwargs)
|
||||
|
||||
if result.get('status', 'error') == 'error':
|
||||
raise BadRequest(result)
|
||||
|
||||
return result['track_id']
|
||||
|
||||
@log
|
||||
def _send_authentication_password(
|
||||
self,
|
||||
track_id: str,
|
||||
password: str,
|
||||
captcha_answer: str = None,
|
||||
timeout: Union[int, float] = None,
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> str:
|
||||
"""Отправка пароля к существующему логину.
|
||||
|
||||
Notes:
|
||||
Капча появляется после 5 неудачных попыток ввода пароля.
|
||||
|
||||
При получении капчи обязательно выполнить запрос на `captcha_image_url` (посмотреть капчу) перед
|
||||
отправкой ответа.
|
||||
|
||||
Args:
|
||||
track_id (:obj:`str`): Идентификатор аутентификации (получен на шаге валидации логина).
|
||||
password (:obj:`str`): Пароль клиента (аутентификатор).
|
||||
timeout (:obj:`int` | :obj:`float`, optional): Если это значение указано, используется как время ожидания
|
||||
ответа от сервера вместо указанного при создании пула.
|
||||
*args: Произвольные аргументы для `requests.request`.
|
||||
**kwargs: Произвольные ключевые аргументы для `requests.request`.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: `X-Token` необходимый для получения токена ЯМ.
|
||||
|
||||
Raises:
|
||||
:class:`yandex_music.exceptions.TimedOut`: При превышении времени ожидания.
|
||||
:class:`yandex_music.exceptions.BadRequest`: При неправильном или неизвестном библиотеке запросе.
|
||||
:class:`yandex_music.exceptions.Unauthorized`: При неправильном пароле.
|
||||
:class:`yandex_music.exceptions.CaptchaRequired`: При необходимости пройти капчу.
|
||||
:class:`yandex_music.exceptions.CaptchaNotShown`: При попытке отправить ответ на капчу не посмотрев её.
|
||||
"""
|
||||
url = f'{self.passport_url}/1/bundle/mobile/auth/password'
|
||||
|
||||
data = {
|
||||
'track_id': track_id,
|
||||
'password': password,
|
||||
}
|
||||
|
||||
if captcha_answer:
|
||||
data['captcha_answer'] = captcha_answer
|
||||
|
||||
result = self._request.post(url, data, timeout=timeout, *args, **kwargs)
|
||||
|
||||
status = result.get('status', 'error')
|
||||
if status == 'ok':
|
||||
return result['x_token']
|
||||
|
||||
if 'password.not_matched' in result['errors']:
|
||||
raise Unauthorized(result)
|
||||
elif 'captcha.required' in result['errors']:
|
||||
raise CaptchaRequired('captcha.required', track_id, result['captcha_image_url'])
|
||||
elif 'captcha.not_shown' in result['errors']:
|
||||
raise CaptchaNotShown('captcha.not_shown', track_id)
|
||||
else:
|
||||
raise BadRequest(result)
|
||||
|
||||
@log
|
||||
def _generate_yandex_music_token_by_x_token(
|
||||
self, x_token: str, timeout: Union[int, float] = None, *args, **kwargs
|
||||
) -> str:
|
||||
"""Получения токена для Яндекс.Музыка по X-Token.
|
||||
|
||||
Args:
|
||||
x_token (:obj:`str`): X-Token полученный в ответ на отправленный пароль.
|
||||
timeout (:obj:`int` | :obj:`float`, optional): Если это значение указано, используется как время ожидания
|
||||
ответа от сервера вместо указанного при создании пула.
|
||||
*args: Произвольные аргументы для `requests.request`.
|
||||
**kwargs: Произвольные ключевые аргументы для `requests.request`.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: `access_token` токен Яндекс.Музыка пригодный для использования в клиенте.
|
||||
|
||||
Raises:
|
||||
:class:`yandex_music.exceptions.BadRequest`: При неправильном запросе.
|
||||
"""
|
||||
|
||||
url = f'{self.passport_url}/1/token/?{self._auth_sdk_params}'
|
||||
|
||||
data = {
|
||||
'access_token': x_token,
|
||||
'client_id': CLIENT_ID,
|
||||
'client_secret': CLIENT_SECRET,
|
||||
'grant_type': 'x-token',
|
||||
}
|
||||
|
||||
result = self._request.post(url, data, timeout=timeout, *args, **kwargs)
|
||||
|
||||
if 'access_token' in result:
|
||||
return result['access_token']
|
||||
|
||||
raise BadRequest(result)
|
||||
|
||||
@property
|
||||
def request(self) -> Request:
|
||||
""":obj:`yandex_music.utils.request.Request`: Объект вспомогательного класса для отправки запросов."""
|
||||
|
@ -2816,10 +2592,6 @@ class Client(YandexMusicObject):
|
|||
|
||||
# camelCase псевдонимы
|
||||
|
||||
#: Псевдоним для :attr:`from_credentials`
|
||||
fromCredentials = from_credentials
|
||||
#: Псевдоним для :attr:`from_token`
|
||||
fromToken = from_token
|
||||
#: Псевдоним для :attr:`account_status`
|
||||
accountStatus = account_status
|
||||
#: Псевдоним для :attr:`account_settings`
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
class YandexMusicError(Exception):
|
||||
"""Базовый класс, представляющий исключения общего характера."""
|
||||
|
||||
|
@ -17,39 +14,6 @@ class InvalidBitrate(YandexMusicError):
|
|||
"""
|
||||
|
||||
|
||||
class Captcha(YandexMusicError):
|
||||
"""Базовый класс, представляющий исключение связанное с капчей.
|
||||
|
||||
Attributes:
|
||||
track_id (:obj:`str`): Идентификатора сессии аутентификации.
|
||||
captcha_image_url (:obj:`str` | :obj:`None`): Ссылка на изображение с капчей.
|
||||
|
||||
Args:
|
||||
msg (:obj:`str`): Сообщение с ошибкой.
|
||||
track_id (:obj:`str`, optional): Идентификатора сессии аутентификации.
|
||||
captcha_image_url (:obj:`str`, optional): Ссылка на изображение с капчей.
|
||||
"""
|
||||
|
||||
def __init__(self, msg: str, track_id: str = None, captcha_image_url: str = None, *args):
|
||||
super().__init__(msg, *args)
|
||||
self.track_id = track_id
|
||||
self.captcha_image_url = captcha_image_url
|
||||
|
||||
|
||||
class CaptchaRequired(Captcha):
|
||||
"""Класс исключения, вызываемый в случае необходимости ввода проверочного кода."""
|
||||
|
||||
|
||||
class CaptchaNotShown(Captcha):
|
||||
"""Класс исключения, вызываемый в случае когда капча была получена, но изображение не было загружено.
|
||||
|
||||
Notes:
|
||||
Будет вызвано если не сделать запрос на `captcha_image_url` полученный в `CaptchaRequired`. Получив данное
|
||||
исключение можно выполнить запрос еще раз для получения новой капчи.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class NetworkError(YandexMusicError):
|
||||
"""Базовый класс исключений, вызываемых для ошибок, связанных с
|
||||
запросами к серверу.
|
||||
|
|
読み込み中…
新しいイシューから参照