Переезд на новую систему авторизации.

Обновлены примеры в README.
Исправлено отображение процента покрытия тестами.
このコミットが含まれているのは:
Il`ya Semyonov 2021-02-27 19:50:04 +01:00
コミット 365ca65e2c
12個のファイルの変更207行の追加246行の削除

ファイルの表示

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

ファイルの表示

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

ファイルの表示

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

ファイルの表示

@ -1,7 +0,0 @@
yandex_music.utils.captcha_response.CaptchaResponse
===================================================
.. autoclass:: yandex_music.utils.captcha_response.CaptchaResponse
:members:
:undoc-members:
:show-inheritance:

ファイルの表示

@ -5,5 +5,4 @@
yandex_music.utils.request
yandex_music.utils.response
yandex_music.utils.captcha_response
yandex_music.utils.difference

ファイルの表示

@ -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',
},
)

ファイルの表示

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

ファイルの表示

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

ファイルの表示

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

ファイルの表示

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

ファイルの表示

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

ファイルの表示

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