diff --git a/README.rst b/README.rst index 6c2c9ce..bab3a65 100644 --- a/README.rst +++ b/README.rst @@ -14,7 +14,7 @@ API Yandex Music - неофициальная Python библиотека :target: https://pypi.org/project/yandex-music/ :alt: Поддерживаемые Python версии -.. image:: https://codecov.io/gh/MarshalX/yandex-music-api/branch/development/graph/badge.svg +.. image:: https://codecov.io/gh/MarshalX/yandex-music-api/branch/main/graph/badge.svg :target: https://codecov.io/gh/MarshalX/yandex-music-api :alt: Покрытие кода тестами @@ -216,15 +216,13 @@ music.yandex.ru/album/**1193829**/track/**10994777** .. code:: python def init_client(): - client = captcha_key = captcha_answer = None + client = captcha_answer = None while not client: try: - client = Client.from_credentials('login', 'pass', captcha_answer, captcha_key) + client = Client.from_credentials('login', 'pass', captcha_answer) except Captcha as e: - e.captcha.download('captcha.png') - - captcha_key = e.captcha.x_captcha_key - captcha_answer = input('Число с картинки: ') + print(e.captcha_image_url) + captcha_answer = input('Код с картинки: ') return client @@ -232,9 +230,9 @@ music.yandex.ru/album/**1193829**/track/**10994777** .. code:: python - def proc_captcha(captcha): - captcha.download('captcha.png') - return input('Число с картинки: ') + def proc_captcha(captcha_image_url): + print(captcha_image_url) + return input('Код с картинки: ') client = Client.from_credentials('login', 'pass', captcha_callback=proc_captcha) diff --git a/docs/source/conf.py b/docs/source/conf.py index 6896d5c..5b085cf 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -12,6 +12,7 @@ # import os import sys + sys.path.insert(0, os.path.abspath('../..')) master_doc = 'index' @@ -29,10 +30,7 @@ language = 'ru' # 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' -] +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.napoleon'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/docs/source/yandex_music.exceptions.rst b/docs/source/yandex_music.exceptions.rst index 8867b9f..efe73cf 100644 --- a/docs/source/yandex_music.exceptions.rst +++ b/docs/source/yandex_music.exceptions.rst @@ -6,11 +6,6 @@ :undoc-members: :show-inheritance: -.. autoclass:: yandex_music.exceptions.InvalidToken - :members: - :undoc-members: - :show-inheritance: - .. autoclass:: yandex_music.exceptions.Unauthorized :members: :undoc-members: @@ -31,7 +26,7 @@ :undoc-members: :show-inheritance: -.. autoclass:: yandex_music.exceptions.CaptchaWrong +.. autoclass:: yandex_music.exceptions.CaptchaNotShown :members: :undoc-members: :show-inheritance: diff --git a/docs/source/yandex_music.utils.captcha_response.rst b/docs/source/yandex_music.utils.captcha_response.rst deleted file mode 100644 index 183d0b1..0000000 --- a/docs/source/yandex_music.utils.captcha_response.rst +++ /dev/null @@ -1,7 +0,0 @@ -yandex_music.utils.captcha_response.CaptchaResponse -=================================================== - -.. autoclass:: yandex_music.utils.captcha_response.CaptchaResponse - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/yandex_music.utils.rst b/docs/source/yandex_music.utils.rst index d196cc8..6f89caa 100644 --- a/docs/source/yandex_music.utils.rst +++ b/docs/source/yandex_music.utils.rst @@ -5,5 +5,4 @@ yandex_music.utils.request yandex_music.utils.response - yandex_music.utils.captcha_response yandex_music.utils.difference \ No newline at end of file diff --git a/setup.py b/setup.py index 8b466b8..a443391 100644 --- a/setup.py +++ b/setup.py @@ -9,6 +9,7 @@ from setuptools.command.test import test class PyTest(test): def run_tests(self): import pytest + sys.exit(pytest.main(['tests'])) @@ -17,8 +18,7 @@ def requirements(section): with open('Pipfile.lock') as pip_file: pipfile_json = json.load(pip_file) - return [package + detail.get('version', '') - for package, detail in pipfile_json[section].items()] + return [package + detail.get('version', '') for package, detail in pipfile_json[section].items()] packages = find_packages() @@ -33,46 +33,48 @@ with open('CHANGES.rst', 'r', encoding='utf-8') as f: changes = f.read() -setup(name='yandex-music', - version=version, - author='Il`ya Semyonov', - author_email='ilya@marshal.dev', - license='LGPLv3', - url='https://github.com/MarshalX/yandex-music-api/', - keywords='python yandex music api wrapper library client питон пайтон ' - 'яндекс музыка апи обёртка библиотека клиент', - description='Делаю то, что по определённым причинам не сделала компания Yandex.', - long_description=f'{readme}\n{changes}', - packages=packages, - install_requires=requirements('default'), - include_package_data=True, - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Natural Language :: Russian', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)', - 'Operating System :: OS Independent', - 'Topic :: Internet', - 'Topic :: Multimedia :: Sound/Audio', - "Topic :: Software Development :: Libraries", - "Topic :: Software Development :: Libraries :: Python Modules", - "Topic :: Software Development :: Libraries :: Application Frameworks", - 'Programming Language :: Python', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - "Programming Language :: Python :: Implementation", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy" - ], - python_requires="~=3.6", - cmdclass={'test': PyTest}, - tests_require=requirements('develop'), - project_urls={ - 'Code': 'https://github.com/MarshalX/yandex-music-api', - 'Documentation': 'https://yandex-music.readthedocs.io', - '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', - }) +setup( + name='yandex-music', + version=version, + author='Il`ya Semyonov', + author_email='ilya@marshal.dev', + license='LGPLv3', + url='https://github.com/MarshalX/yandex-music-api/', + keywords='python yandex music api wrapper library client питон пайтон ' + 'яндекс музыка апи обёртка библиотека клиент', + description='Делаю то, что по определённым причинам не сделала компания Yandex.', + long_description=f'{readme}\n{changes}', + packages=packages, + install_requires=requirements('default'), + include_package_data=True, + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Natural Language :: Russian', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)', + 'Operating System :: OS Independent', + 'Topic :: Internet', + 'Topic :: Multimedia :: Sound/Audio', + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Software Development :: Libraries :: Application Frameworks", + 'Programming Language :: Python', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + "Programming Language :: Python :: Implementation", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + ], + python_requires="~=3.6", + cmdclass={'test': PyTest}, + tests_require=requirements('develop'), + project_urls={ + 'Code': 'https://github.com/MarshalX/yandex-music-api', + 'Documentation': 'https://yandex-music.readthedocs.io', + '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', + }, +) diff --git a/yandex_music/account/account.py b/yandex_music/account/account.py index 9302c2b..d85a4fd 100644 --- a/yandex_music/account/account.py +++ b/yandex_music/account/account.py @@ -87,15 +87,6 @@ class Account(YandexMusicObject): super().handle_unknown_kwargs(self, **kwargs) - def download_avatar(self, filename: str, format_: str = 'normal') -> None: - """Загрузка изображения пользователя. - - Args: - filename (:obj:`str`): Путь для сохранения файла с названием и расширением. - format_ (:obj:`str`): Формат желаемого изображения (`normal`, `orig`, `small`, `big`). - """ - self.client.request.download(f'https://upics.yandex.net/{self.uid}/{format_}', filename) - @classmethod def de_json(cls, data: dict, client: 'Client') -> Optional['Account']: """Десериализация объекта. @@ -116,8 +107,3 @@ class Account(YandexMusicObject): data['passport_phones'] = PassportPhone.de_list(data.get('passport_phones'), client) return cls(client=client, **data) - - # camelCase псевдонимы - - #: Псевдоним для :attr:`download_avatar` - downloadAvatar = download_avatar diff --git a/yandex_music/client.py b/yandex_music/client.py index 3b88087..9364a36 100644 --- a/yandex_music/client.py +++ b/yandex_music/client.py @@ -1,7 +1,7 @@ import functools import logging from datetime import datetime -from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Union +from typing import Callable, Dict, List, Optional, Union from yandex_music import ( Album, @@ -42,15 +42,15 @@ from yandex_music import ( __license__, __version__, ) -from yandex_music.exceptions import Captcha, InvalidToken +from yandex_music.exceptions import BadRequest, Captcha, CaptchaNotShown, CaptchaRequired, Unauthorized from yandex_music.utils.difference import Difference from yandex_music.utils.request import Request -if TYPE_CHECKING: - from yandex_music.utils.captcha_response import CaptchaResponse CLIENT_ID = '23cabbbdc6cd418abb4b39c32c41195d' CLIENT_SECRET = '53bc75238f0c4d08a118e51fe9203300' +X_TOKEN_CLIENT_ID = 'c0ebe342af7d48fbbbfcf2d2eedb8f9e' +X_TOKEN_CLIENT_SECRET = 'ad0a908f0aa341a182a37ecd75bc319e' de_list = { 'artist': Artist.de_list, @@ -96,7 +96,7 @@ class Client(YandexMusicObject): logger (:obj:`logging.Logger`): Объект логгера. token (:obj:`str`): Уникальный ключ для аутентификации. base_url (:obj:`str`): Ссылка на API Yandex Music. - oauth_url (:obj:`str`): Ссылка на OAuth Yandex Music. + passport_url (:obj:`str`): Ссылка на Mobileproxy Passport Yandex Music. me (:obj:`yandex_music.Status`): Информация об аккаунте. device (:obj:`str`): Строка, содержащая сведения об устройстве, с которого выполняются запросы. report_new_fields (:obj:`bool`): Включены ли сообщения о новых полях от API, которых нет в библиотеке. @@ -107,7 +107,7 @@ class Client(YandexMusicObject): token (:obj:`str`, optional): Уникальный ключ для аутентификации. fetch_account_status (:obj:`bool`, optional): Получить ли информацию об аккаунте при инициализации объекта. base_url (:obj:`str`, optional): Ссылка на API Yandex Music. - oauth_url (:obj:`str`, optional): Ссылка на OAuth 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. @@ -123,7 +123,7 @@ class Client(YandexMusicObject): token: str = None, fetch_account_status: bool = True, base_url: str = None, - oauth_url: str = None, + passport_url: str = None, request: Request = None, language: str = 'ru', report_new_fields=True, @@ -139,11 +139,11 @@ class Client(YandexMusicObject): if base_url is None: base_url = 'https://api.music.yandex.net' - if oauth_url is None: - oauth_url = 'https://oauth.yandex.ru' + if passport_url is None: + passport_url = 'https://mobileproxy.passport.yandex.net/' self.base_url = base_url - self.oauth_url = oauth_url + self.passport_url = passport_url self.report_new_fields = report_new_fields @@ -156,7 +156,8 @@ class Client(YandexMusicObject): else: self._request = Request(self) - self._request.set_language(language) + self.language = language + self._request.set_language(self.language) self.device = ( 'os=Python; os_version=; manufacturer=Marshal; ' @@ -170,11 +171,11 @@ class Client(YandexMusicObject): @classmethod def from_credentials( cls, - username: str, + login: str, password: str, - x_captcha_answer: str = None, - x_captcha_key: str = None, - captcha_callback: Callable[['CaptchaResponse'], str] = None, + captcha_answer: str = None, + captcha_callback: Callable[[str], str] = None, + timeout: Union[int, float] = None, *args, **kwargs, ) -> 'Client': @@ -185,34 +186,41 @@ class Client(YandexMusicObject): и использовать при следующих инициализациях клиента. Не храните логины и пароли! Args: - username (:obj:`str`): Логин клиента (идентификатор). + login (:obj:`str`): Логин клиента (идентификатор). password (:obj:`str`): Пароль клиента (аутентификатор). - x_captcha_answer (:obj:`str`, optional): Ответ на капчу (цифры с картинки). - x_captcha_key (:obj:`str`, optional): Уникальный ключ капчи. + captcha_answer (:obj:`str`, optional): Ответ на капчу (цифры с картинки). captcha_callback (:obj:`function`, optional): Функция обратного вызова для обработки капчи, должна - принимать объект класса :class:`yandex_music.exceptions.Captcha` и возвращать проверочный код. - **kwargs (:obj:`dict`, 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`: Базовое исключение библиотеки. """ - token = None - while not token: + client = cls(*args, **kwargs) + track_id = client._start_authentication(login, timeout, *args, **kwargs) + + x_token = None + while not x_token: try: - token = cls(*args, **kwargs).generate_token_by_username_and_password( - username, password, x_captcha_answer=x_captcha_answer, x_captcha_key=x_captcha_key + x_token = client._send_authentication_password( + track_id, password, captcha_answer, timeout, *args, **kwargs ) except Captcha as e: - if not captcha_callback: + if not captcha_callback or not e.captcha_image_url: raise e - x_captcha_answer = captcha_callback(e.captcha) - x_captcha_key = e.captcha.x_captcha_key + 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 @@ -224,7 +232,8 @@ class Client(YandexMusicObject): Args: token (:obj:`str`, optional): Уникальный ключ для аутентификации. - **kwargs (:obj:`dict`, optional): Аргументы для конструктора клиента. + *args: Произвольные аргументы для `Client`. + **kwargs: Произвольные ключевые аргументы для `Client`. Returns: :obj:`yandex_music.Client`. @@ -233,74 +242,137 @@ class Client(YandexMusicObject): return cls(token, *args, **kwargs) @log - def generate_token_by_username_and_password( + 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, - username: str, + track_id: str, password: str, - grant_type: str = 'password', - x_captcha_answer: str = None, - x_captcha_key: str = None, + captcha_answer: str = None, timeout: Union[int, float] = None, *args, **kwargs, ) -> str: - """Метод получения OAuth токена по логину и паролю. + """Отправка пароля к существующему логину. + + Notes: + Капча появляется после 5 неудачных попыток ввода пароля. + + При получении капчи обязательно выполнить запрос на `captcha_image_url` (посмотреть капчу) перед + отправкой ответа. Args: - username (:obj:`str`): Логин клиента (идентификатор). + track_id (:obj:`str`): Идентификатор аутентификации (получен на шаге валидации логина). password (:obj:`str`): Пароль клиента (аутентификатор). - grant_type (:obj:`str`, optional): Тип разрешения OAuth. - x_captcha_answer (:obj:`str`, optional): Ответ на капчу (цифры с картинки). - x_captcha_key (:obj:`str`, optional): Уникальный ключ капчи. timeout (:obj:`int` | :obj:`float`, optional): Если это значение указано, используется как время ожидания ответа от сервера вместо указанного при создании пула. - **kwargs (:obj:`dict`, optional): Произвольные аргументы (будут переданы в запрос). + *args: Произвольные аргументы для `requests.request`. + **kwargs: Произвольные ключевые аргументы для `requests.request`. Returns: - :obj:`str`: OAuth токен. + :obj:`str`: `X-Token` необходимый для получения токена ЯМ. Raises: - :class:`yandex_music.exceptions.YandexMusicError`: Базовое исключение библиотеки. + :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.oauth_url}/token' + url = f'{self.passport_url}/1/bundle/mobile/auth/password' data = { - 'grant_type': grant_type, - 'client_id': CLIENT_ID, - 'client_secret': CLIENT_SECRET, - 'username': username, + 'track_id': track_id, 'password': password, } - if x_captcha_answer and x_captcha_key: - data.update({'x_captcha_answer': x_captcha_answer, 'x_captcha_key': x_captcha_key}) + if captcha_answer: + data['captcha_answer'] = captcha_answer result = self._request.post(url, data, timeout=timeout, *args, **kwargs) - return result.get('access_token') + status = result.get('status', 'error') + if status == 'ok': + return result['x_token'] - @staticmethod - def _validate_token(token: str) -> str: - """Примитивная валидация токена. + if 'password.not_matched' in result['errors']: + raise Unauthorized(result) + elif 'captcha.required' in result['errors']: + raise CaptchaRequired('captcha.required', result['captcha_image_url']) + elif 'captcha.not_shown' in result['errors']: + raise CaptchaNotShown('captcha.not_shown') + 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: - token (:obj:`str`): токен для проверки + x_token (:obj:`str`): X-Token полученный в ответ на отправленный пароль. + timeout (:obj:`int` | :obj:`float`, optional): Если это значение указано, используется как время ожидания + ответа от сервера вместо указанного при создании пула. + *args: Произвольные аргументы для `requests.request`. + **kwargs: Произвольные ключевые аргументы для `requests.request`. Returns: - :obj:`str`: Токен. + :obj:`str`: `access_token` токен Яндекс.Музыка пригодный для использования в клиенте. Raises: - :class:`yandex_music.exceptions.InvalidToken`: Если токен недействителен. + :class:`yandex_music.exceptions.BadRequest`: При неправильном запросе. """ - if any(x.isspace() for x in token): - raise InvalidToken() + url = f'{self.passport_url}/1/token' - if len(token) != 39: - raise InvalidToken() + data = { + 'access_token': x_token, + 'client_id': CLIENT_ID, + 'client_secret': CLIENT_SECRET, + 'grant_type': 'x-token', + } - return 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: @@ -2749,8 +2821,6 @@ class Client(YandexMusicObject): fromCredentials = from_credentials #: Псевдоним для :attr:`from_token` fromToken = from_token - #: Псевдоним для :attr:`generate_token_by_username_and_password` - generateTokenByUsernameAndPassword = generate_token_by_username_and_password #: Псевдоним для :attr:`account_status` accountStatus = account_status #: Псевдоним для :attr:`account_settings` diff --git a/yandex_music/exceptions.py b/yandex_music/exceptions.py index c62f971..c73d7b8 100644 --- a/yandex_music/exceptions.py +++ b/yandex_music/exceptions.py @@ -1,19 +1,10 @@ from typing import TYPE_CHECKING -if TYPE_CHECKING: - from yandex_music.utils.captcha_response import CaptchaResponse - class YandexMusicError(Exception): """Базовый класс, представляющий исключения общего характера. """ -class InvalidToken(YandexMusicError): - """Класс исключения, вызываемого для случаев недействительного - или неверного токена аутентификации. - """ - - class Unauthorized(YandexMusicError): """Класс исключения, вызываемого для случаев ошибок аутентификации и авторизации. @@ -30,24 +21,30 @@ class Captcha(YandexMusicError): """Базовый класс, представляющий исключение связанное с капчей. Attributes: - captcha (:obj:`yandex_music.utils.captcha_response.CaptchaResponse`): Капча. + captcha_image_url (:obj:`str` | :obj:`None`): Ссылка на изображение с капчей. Args: msg (:obj:`str`): Сообщение с ошибкой. - captcha (:obj:`yandex_music.utils.captcha_response.CaptchaResponse`): Капча. + captcha_image_url (:obj:`str`, optional): Ссылка на изображение с капчей. """ - def __init__(self, msg: str, captcha: 'CaptchaResponse', *args, **kwargs): - self.captcha = captcha - super().__init__(msg, *args, **kwargs) + def __init__(self, msg: str, captcha_image_url: str = None, *args): + self.captcha_image_url = captcha_image_url + super().__init__(msg, *args) class CaptchaRequired(Captcha): """Класс исключения, вызываемый в случае необходимости ввода проверочного кода.""" -class CaptchaWrong(Captcha): - """Класс исключения, вызываемый в случае неправильного ввода капчи.""" +class CaptchaNotShown(Captcha): + """Класс исключения, вызываемый в случае когда капча была получена, но изображение не было загружено. + + Notes: + Будет вызвано если не сделать запрос на `captcha_image_url` полученный в `CaptchaRequired`. Получив данное + исключение можно выполнить запрос еще раз для получения новой капчи. + + """ class NetworkError(YandexMusicError): diff --git a/yandex_music/utils/captcha_response.py b/yandex_music/utils/captcha_response.py deleted file mode 100644 index b7a0d6f..0000000 --- a/yandex_music/utils/captcha_response.py +++ /dev/null @@ -1,69 +0,0 @@ -from typing import TYPE_CHECKING, Optional - -from yandex_music import YandexMusicObject - -if TYPE_CHECKING: - from yandex_music import Client - - -class CaptchaResponse(YandexMusicObject): - """Класс, представляющий ответ сервера с запросом на ввод капчи. - - Attributes: - x_captcha_url (:obj:`str`): Ссылка на изображение с капчей. - x_captcha_key (:obj:`str`): Уникальный ключ капчи. - error_description (:obj:`str`): Описание ошибки. - error (:obj:`str`): Код ошибки. - client (:obj:`yandex_music.Client`): Клиент Yandex Music. - - Args: - x_captcha_url (:obj:`str`): Ссылка на изображение с капчей. - x_captcha_key (:obj:`str`): Уникальный ключ капчи. - error_description (:obj:`str`): Описание ошибки. - error (:obj:`str`): Код ошибки. - client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music. - **kwargs: Произвольные ключевые аргументы полученные от API. - """ - - def __init__( - self, x_captcha_url, x_captcha_key, error_description, error, client: Optional['Client'] = None, **kwargs - ) -> None: - self.x_captcha_url = x_captcha_url - self.x_captcha_key = x_captcha_key - self.error_description = error_description - self.error = error - - self.client = client - self._id_attrs = (self.x_captcha_key, self.x_captcha_url) - - super().handle_unknown_kwargs(self, **kwargs) - - def download(self, filename=None): - """Загрузка изображения с капчей. - - Args: - filename (:obj:`str`, optional): Путь и(или) название файла вместе с расширением. По умолчанию ключ - капчи и расширение `.gif`. - """ - if not filename: - filename = f'{self.x_captcha_key}.gif' - - self.client.request.download(self.x_captcha_url, filename) - - @classmethod - def de_json(cls, data: dict, client: 'Client'): - """Десериализация объекта. - - Args: - data (:obj:`dict`): Поля и значения десериализуемого объекта. - client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music. - - Returns: - :obj:`yandex_music.utils.captcha_response.CaptchaResponse`: Ответ сервера с запросом на ввод капчи. - """ - if not data: - return None - - data = super(CaptchaResponse, cls).de_json(data, client) - - return cls(client=client, **data) diff --git a/yandex_music/utils/request.py b/yandex_music/utils/request.py index d5b87ad..159026f 100644 --- a/yandex_music/utils/request.py +++ b/yandex_music/utils/request.py @@ -12,15 +12,12 @@ import json import requests -from yandex_music.utils.captcha_response import CaptchaResponse from yandex_music.utils.response import Response from yandex_music.exceptions import ( Unauthorized, BadRequest, NetworkError, YandexMusicError, - CaptchaRequired, - CaptchaWrong, TimedOut, ) @@ -185,8 +182,6 @@ class Request: :class:`yandex_music.exceptions.Unauthorized`: При невалидном токене, долгом ожидании прямой ссылки на файл. :class:`yandex_music.exceptions.BadRequest`: При неправильном запросе. :class:`yandex_music.exceptions.NetworkError`: При проблемах с сетью. - :class:`yandex_music.exceptions.CaptchaWrong`: При неправильной капче. - :class:`yandex_music.exceptions.CaptchaRequired`: При необходимости пройти капчу. """ if 'headers' not in kwargs: kwargs['headers'] = {} @@ -206,10 +201,7 @@ class Request: parse = self._parse(resp.content) message = parse.error or 'Unknown HTTPError' - if 'CAPTCHA' in message: - exception = CaptchaWrong if 'Wrong' in message else CaptchaRequired - raise exception(message, CaptchaResponse.de_json(parse.result, self.client)) - elif resp.status_code in (401, 403): + if resp.status_code in (401, 403): raise Unauthorized(message) elif resp.status_code == 400: raise BadRequest(message) diff --git a/yandex_music/utils/response.py b/yandex_music/utils/response.py index 2a382d1..8bfe9b7 100644 --- a/yandex_music/utils/response.py +++ b/yandex_music/utils/response.py @@ -18,8 +18,8 @@ class Response(YandexMusicObject): Attributes: data (:obj:`dict`): Ответ на запрос. Используется тогда, когда отсутствует `result`. invocation_info (:obj:`yandex_music.InvocationInfo` | :obj:`None`): Информация о запросе. - result (:obj:`dict`): Ответ на запрос (секция с результатом). - error (:obj:`str`): Код ошибки. + _result (:obj:`dict`): Ответ на запрос (секция с результатом). + _error (:obj:`str`): Код ошибки. error_description (:obj:`str`): Описание ошибки. client (:obj:`yandex_music.Client`): Клиент Yandex Music.