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

Обновлены примеры в 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/ :target: https://pypi.org/project/yandex-music/
:alt: Поддерживаемые Python версии :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 :target: https://codecov.io/gh/MarshalX/yandex-music-api
:alt: Покрытие кода тестами :alt: Покрытие кода тестами
@ -216,15 +216,13 @@ music.yandex.ru/album/**1193829**/track/**10994777**
.. code:: python .. code:: python
def init_client(): def init_client():
client = captcha_key = captcha_answer = None client = captcha_answer = None
while not client: while not client:
try: try:
client = Client.from_credentials('login', 'pass', captcha_answer, captcha_key) client = Client.from_credentials('login', 'pass', captcha_answer)
except Captcha as e: except Captcha as e:
e.captcha.download('captcha.png') print(e.captcha_image_url)
captcha_answer = input('Код с картинки: ')
captcha_key = e.captcha.x_captcha_key
captcha_answer = input('Число с картинки: ')
return client return client
@ -232,9 +230,9 @@ music.yandex.ru/album/**1193829**/track/**10994777**
.. code:: python .. code:: python
def proc_captcha(captcha): def proc_captcha(captcha_image_url):
captcha.download('captcha.png') print(captcha_image_url)
return input('Число с картинки: ') return input('Код с картинки: ')
client = Client.from_credentials('login', 'pass', captcha_callback=proc_captcha) client = Client.from_credentials('login', 'pass', captcha_callback=proc_captcha)

ファイルの表示

@ -12,6 +12,7 @@
# #
import os import os
import sys import sys
sys.path.insert(0, os.path.abspath('../..')) sys.path.insert(0, os.path.abspath('../..'))
master_doc = 'index' master_doc = 'index'
@ -29,10 +30,7 @@ language = 'ru'
# Add any Sphinx extension module names here, as strings. They can be # Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones. # ones.
extensions = [ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.napoleon']
'sphinx.ext.autodoc',
'sphinx.ext.napoleon'
]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates'] templates_path = ['_templates']

ファイルの表示

@ -6,11 +6,6 @@
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
.. autoclass:: yandex_music.exceptions.InvalidToken
:members:
:undoc-members:
:show-inheritance:
.. autoclass:: yandex_music.exceptions.Unauthorized .. autoclass:: yandex_music.exceptions.Unauthorized
:members: :members:
:undoc-members: :undoc-members:
@ -31,7 +26,7 @@
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
.. autoclass:: yandex_music.exceptions.CaptchaWrong .. autoclass:: yandex_music.exceptions.CaptchaNotShown
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :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.request
yandex_music.utils.response yandex_music.utils.response
yandex_music.utils.captcha_response
yandex_music.utils.difference yandex_music.utils.difference

ファイルの表示

@ -9,6 +9,7 @@ from setuptools.command.test import test
class PyTest(test): class PyTest(test):
def run_tests(self): def run_tests(self):
import pytest import pytest
sys.exit(pytest.main(['tests'])) sys.exit(pytest.main(['tests']))
@ -17,8 +18,7 @@ def requirements(section):
with open('Pipfile.lock') as pip_file: with open('Pipfile.lock') as pip_file:
pipfile_json = json.load(pip_file) pipfile_json = json.load(pip_file)
return [package + detail.get('version', '') return [package + detail.get('version', '') for package, detail in pipfile_json[section].items()]
for package, detail in pipfile_json[section].items()]
packages = find_packages() packages = find_packages()
@ -33,46 +33,48 @@ with open('CHANGES.rst', 'r', encoding='utf-8') as f:
changes = f.read() changes = f.read()
setup(name='yandex-music', setup(
version=version, name='yandex-music',
author='Il`ya Semyonov', version=version,
author_email='ilya@marshal.dev', author='Il`ya Semyonov',
license='LGPLv3', author_email='ilya@marshal.dev',
url='https://github.com/MarshalX/yandex-music-api/', license='LGPLv3',
keywords='python yandex music api wrapper library client питон пайтон ' url='https://github.com/MarshalX/yandex-music-api/',
'яндекс музыка апи обёртка библиотека клиент', keywords='python yandex music api wrapper library client питон пайтон '
description='Делаю то, что по определённым причинам не сделала компания Yandex.', 'яндекс музыка апи обёртка библиотека клиент',
long_description=f'{readme}\n{changes}', description='Делаю то, что по определённым причинам не сделала компания Yandex.',
packages=packages, long_description=f'{readme}\n{changes}',
install_requires=requirements('default'), packages=packages,
include_package_data=True, install_requires=requirements('default'),
classifiers=[ include_package_data=True,
'Development Status :: 5 - Production/Stable', classifiers=[
'Natural Language :: Russian', 'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers', 'Natural Language :: Russian',
'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)', 'Intended Audience :: Developers',
'Operating System :: OS Independent', 'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)',
'Topic :: Internet', 'Operating System :: OS Independent',
'Topic :: Multimedia :: Sound/Audio', 'Topic :: Internet',
"Topic :: Software Development :: Libraries", 'Topic :: Multimedia :: Sound/Audio',
"Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Libraries",
"Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules",
'Programming Language :: Python', "Topic :: Software Development :: Libraries :: Application Frameworks",
'Programming Language :: Python :: 3.6', 'Programming Language :: Python',
'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.8',
"Programming Language :: Python :: Implementation", 'Programming Language :: Python :: 3.9',
"Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation",
"Programming Language :: Python :: Implementation :: PyPy" "Programming Language :: Python :: Implementation :: CPython",
], "Programming Language :: Python :: Implementation :: PyPy",
python_requires="~=3.6", ],
cmdclass={'test': PyTest}, python_requires="~=3.6",
tests_require=requirements('develop'), cmdclass={'test': PyTest},
project_urls={ tests_require=requirements('develop'),
'Code': 'https://github.com/MarshalX/yandex-music-api', project_urls={
'Documentation': 'https://yandex-music.readthedocs.io', 'Code': 'https://github.com/MarshalX/yandex-music-api',
'Chat': 'https://t.me/yandex_music_api', 'Documentation': 'https://yandex-music.readthedocs.io',
'Codecov': 'https://codecov.io/gh/MarshalX/yandex-music-api', 'Chat': 'https://t.me/yandex_music_api',
'Codacy': 'https://www.codacy.com/manual/MarshalX/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) 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 @classmethod
def de_json(cls, data: dict, client: 'Client') -> Optional['Account']: 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) data['passport_phones'] = PassportPhone.de_list(data.get('passport_phones'), client)
return cls(client=client, **data) return cls(client=client, **data)
# camelCase псевдонимы
#: Псевдоним для :attr:`download_avatar`
downloadAvatar = download_avatar

ファイルの表示

@ -1,7 +1,7 @@
import functools import functools
import logging import logging
from datetime import datetime 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 ( from yandex_music import (
Album, Album,
@ -42,15 +42,15 @@ from yandex_music import (
__license__, __license__,
__version__, __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.difference import Difference
from yandex_music.utils.request import Request from yandex_music.utils.request import Request
if TYPE_CHECKING:
from yandex_music.utils.captcha_response import CaptchaResponse
CLIENT_ID = '23cabbbdc6cd418abb4b39c32c41195d' CLIENT_ID = '23cabbbdc6cd418abb4b39c32c41195d'
CLIENT_SECRET = '53bc75238f0c4d08a118e51fe9203300' CLIENT_SECRET = '53bc75238f0c4d08a118e51fe9203300'
X_TOKEN_CLIENT_ID = 'c0ebe342af7d48fbbbfcf2d2eedb8f9e'
X_TOKEN_CLIENT_SECRET = 'ad0a908f0aa341a182a37ecd75bc319e'
de_list = { de_list = {
'artist': Artist.de_list, 'artist': Artist.de_list,
@ -96,7 +96,7 @@ class Client(YandexMusicObject):
logger (:obj:`logging.Logger`): Объект логгера. logger (:obj:`logging.Logger`): Объект логгера.
token (:obj:`str`): Уникальный ключ для аутентификации. token (:obj:`str`): Уникальный ключ для аутентификации.
base_url (:obj:`str`): Ссылка на API Yandex Music. 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`): Информация об аккаунте. me (:obj:`yandex_music.Status`): Информация об аккаунте.
device (:obj:`str`): Строка, содержащая сведения об устройстве, с которого выполняются запросы. device (:obj:`str`): Строка, содержащая сведения об устройстве, с которого выполняются запросы.
report_new_fields (:obj:`bool`): Включены ли сообщения о новых полях от API, которых нет в библиотеке. report_new_fields (:obj:`bool`): Включены ли сообщения о новых полях от API, которых нет в библиотеке.
@ -107,7 +107,7 @@ class Client(YandexMusicObject):
token (:obj:`str`, optional): Уникальный ключ для аутентификации. token (:obj:`str`, optional): Уникальный ключ для аутентификации.
fetch_account_status (:obj:`bool`, optional): Получить ли информацию об аккаунте при инициализации объекта. fetch_account_status (:obj:`bool`, optional): Получить ли информацию об аккаунте при инициализации объекта.
base_url (:obj:`str`, optional): Ссылка на API Yandex Music. 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): Пре-инициализация request (:obj:`yandex_music.utils.request.Request`, optional): Пре-инициализация
:class:`yandex_music.utils.request.Request`. :class:`yandex_music.utils.request.Request`.
language (:obj:`str`, optional): Язык, на котором будут приходить ответы от API. language (:obj:`str`, optional): Язык, на котором будут приходить ответы от API.
@ -123,7 +123,7 @@ class Client(YandexMusicObject):
token: str = None, token: str = None,
fetch_account_status: bool = True, fetch_account_status: bool = True,
base_url: str = None, base_url: str = None,
oauth_url: str = None, passport_url: str = None,
request: Request = None, request: Request = None,
language: str = 'ru', language: str = 'ru',
report_new_fields=True, report_new_fields=True,
@ -139,11 +139,11 @@ class Client(YandexMusicObject):
if base_url is None: if base_url is None:
base_url = 'https://api.music.yandex.net' base_url = 'https://api.music.yandex.net'
if oauth_url is None: if passport_url is None:
oauth_url = 'https://oauth.yandex.ru' passport_url = 'https://mobileproxy.passport.yandex.net/'
self.base_url = base_url self.base_url = base_url
self.oauth_url = oauth_url self.passport_url = passport_url
self.report_new_fields = report_new_fields self.report_new_fields = report_new_fields
@ -156,7 +156,8 @@ class Client(YandexMusicObject):
else: else:
self._request = Request(self) self._request = Request(self)
self._request.set_language(language) self.language = language
self._request.set_language(self.language)
self.device = ( self.device = (
'os=Python; os_version=; manufacturer=Marshal; ' 'os=Python; os_version=; manufacturer=Marshal; '
@ -170,11 +171,11 @@ class Client(YandexMusicObject):
@classmethod @classmethod
def from_credentials( def from_credentials(
cls, cls,
username: str, login: str,
password: str, password: str,
x_captcha_answer: str = None, captcha_answer: str = None,
x_captcha_key: str = None, captcha_callback: Callable[[str], str] = None,
captcha_callback: Callable[['CaptchaResponse'], str] = None, timeout: Union[int, float] = None,
*args, *args,
**kwargs, **kwargs,
) -> 'Client': ) -> 'Client':
@ -185,34 +186,41 @@ class Client(YandexMusicObject):
и использовать при следующих инициализациях клиента. Не храните логины и пароли! и использовать при следующих инициализациях клиента. Не храните логины и пароли!
Args: Args:
username (:obj:`str`): Логин клиента (идентификатор). login (:obj:`str`): Логин клиента (идентификатор).
password (:obj:`str`): Пароль клиента (аутентификатор). password (:obj:`str`): Пароль клиента (аутентификатор).
x_captcha_answer (:obj:`str`, optional): Ответ на капчу (цифры с картинки). captcha_answer (:obj:`str`, optional): Ответ на капчу (цифры с картинки).
x_captcha_key (:obj:`str`, optional): Уникальный ключ капчи.
captcha_callback (:obj:`function`, 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: Returns:
:obj:`yandex_music.Client`. :obj:`yandex_music.Client`.
Raises: Raises:
:class:`yandex_music.exceptions.CaptchaRequired`: При необходимости пройти капчу и отсутствию коллбека.
:class:`yandex_music.exceptions.CaptchaNotShown`: При попытке отправить ответ на капчу не посмотрев её.
:class:`yandex_music.exceptions.YandexMusicError`: Базовое исключение библиотеки. :class:`yandex_music.exceptions.YandexMusicError`: Базовое исключение библиотеки.
""" """
token = None client = cls(*args, **kwargs)
while not token: track_id = client._start_authentication(login, timeout, *args, **kwargs)
x_token = None
while not x_token:
try: try:
token = cls(*args, **kwargs).generate_token_by_username_and_password( x_token = client._send_authentication_password(
username, password, x_captcha_answer=x_captcha_answer, x_captcha_key=x_captcha_key track_id, password, captcha_answer, timeout, *args, **kwargs
) )
except Captcha as e: except Captcha as e:
if not captcha_callback: if not captcha_callback or not e.captcha_image_url:
raise e raise e
x_captcha_answer = captcha_callback(e.captcha) captcha_answer = captcha_callback(e.captcha_image_url)
x_captcha_key = e.captcha.x_captcha_key
token = client._generate_yandex_music_token_by_x_token(x_token, timeout, *args, **kwargs)
return cls(token, *args, **kwargs) return cls(token, *args, **kwargs)
@classmethod @classmethod
@ -224,7 +232,8 @@ class Client(YandexMusicObject):
Args: Args:
token (:obj:`str`, optional): Уникальный ключ для аутентификации. token (:obj:`str`, optional): Уникальный ключ для аутентификации.
**kwargs (:obj:`dict`, optional): Аргументы для конструктора клиента. *args: Произвольные аргументы для `Client`.
**kwargs: Произвольные ключевые аргументы для `Client`.
Returns: Returns:
:obj:`yandex_music.Client`. :obj:`yandex_music.Client`.
@ -233,74 +242,137 @@ class Client(YandexMusicObject):
return cls(token, *args, **kwargs) return cls(token, *args, **kwargs)
@log @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, self,
username: str, track_id: str,
password: str, password: str,
grant_type: str = 'password', captcha_answer: str = None,
x_captcha_answer: str = None,
x_captcha_key: str = None,
timeout: Union[int, float] = None, timeout: Union[int, float] = None,
*args, *args,
**kwargs, **kwargs,
) -> str: ) -> str:
"""Метод получения OAuth токена по логину и паролю. """Отправка пароля к существующему логину.
Notes:
Капча появляется после 5 неудачных попыток ввода пароля.
При получении капчи обязательно выполнить запрос на `captcha_image_url` (посмотреть капчу) перед
отправкой ответа.
Args: Args:
username (:obj:`str`): Логин клиента (идентификатор). track_id (:obj:`str`): Идентификатор аутентификации (получен на шаге валидации логина).
password (: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): Если это значение указано, используется как время ожидания timeout (:obj:`int` | :obj:`float`, optional): Если это значение указано, используется как время ожидания
ответа от сервера вместо указанного при создании пула. ответа от сервера вместо указанного при создании пула.
**kwargs (:obj:`dict`, optional): Произвольные аргументы (будут переданы в запрос). *args: Произвольные аргументы для `requests.request`.
**kwargs: Произвольные ключевые аргументы для `requests.request`.
Returns: Returns:
:obj:`str`: OAuth токен. :obj:`str`: `X-Token` необходимый для получения токена ЯМ.
Raises: 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.passport_url}/1/bundle/mobile/auth/password'
url = f'{self.oauth_url}/token'
data = { data = {
'grant_type': grant_type, 'track_id': track_id,
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET,
'username': username,
'password': password, 'password': password,
} }
if x_captcha_answer and x_captcha_key: if captcha_answer:
data.update({'x_captcha_answer': x_captcha_answer, 'x_captcha_key': x_captcha_key}) data['captcha_answer'] = captcha_answer
result = self._request.post(url, data, timeout=timeout, *args, **kwargs) 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 if 'password.not_matched' in result['errors']:
def _validate_token(token: str) -> str: 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: Args:
token (:obj:`str`): токен для проверки x_token (:obj:`str`): X-Token полученный в ответ на отправленный пароль.
timeout (:obj:`int` | :obj:`float`, optional): Если это значение указано, используется как время ожидания
ответа от сервера вместо указанного при создании пула.
*args: Произвольные аргументы для `requests.request`.
**kwargs: Произвольные ключевые аргументы для `requests.request`.
Returns: Returns:
:obj:`str`: Токен. :obj:`str`: `access_token` токен Яндекс.Музыка пригодный для использования в клиенте.
Raises: Raises:
:class:`yandex_music.exceptions.InvalidToken`: Если токен недействителен. :class:`yandex_music.exceptions.BadRequest`: При неправильном запросе.
""" """
if any(x.isspace() for x in token): url = f'{self.passport_url}/1/token'
raise InvalidToken()
if len(token) != 39: data = {
raise InvalidToken() '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 @property
def request(self) -> Request: def request(self) -> Request:
@ -2749,8 +2821,6 @@ class Client(YandexMusicObject):
fromCredentials = from_credentials fromCredentials = from_credentials
#: Псевдоним для :attr:`from_token` #: Псевдоним для :attr:`from_token`
fromToken = from_token fromToken = from_token
#: Псевдоним для :attr:`generate_token_by_username_and_password`
generateTokenByUsernameAndPassword = generate_token_by_username_and_password
#: Псевдоним для :attr:`account_status` #: Псевдоним для :attr:`account_status`
accountStatus = account_status accountStatus = account_status
#: Псевдоним для :attr:`account_settings` #: Псевдоним для :attr:`account_settings`

ファイルの表示

@ -1,19 +1,10 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING:
from yandex_music.utils.captcha_response import CaptchaResponse
class YandexMusicError(Exception): class YandexMusicError(Exception):
"""Базовый класс, представляющий исключения общего характера. """ """Базовый класс, представляющий исключения общего характера. """
class InvalidToken(YandexMusicError):
"""Класс исключения, вызываемого для случаев недействительного
или неверного токена аутентификации.
"""
class Unauthorized(YandexMusicError): class Unauthorized(YandexMusicError):
"""Класс исключения, вызываемого для случаев ошибок """Класс исключения, вызываемого для случаев ошибок
аутентификации и авторизации. аутентификации и авторизации.
@ -30,24 +21,30 @@ class Captcha(YandexMusicError):
"""Базовый класс, представляющий исключение связанное с капчей. """Базовый класс, представляющий исключение связанное с капчей.
Attributes: Attributes:
captcha (:obj:`yandex_music.utils.captcha_response.CaptchaResponse`): Капча. captcha_image_url (:obj:`str` | :obj:`None`): Ссылка на изображение с капчей.
Args: Args:
msg (:obj:`str`): Сообщение с ошибкой. 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): def __init__(self, msg: str, captcha_image_url: str = None, *args):
self.captcha = captcha self.captcha_image_url = captcha_image_url
super().__init__(msg, *args, **kwargs) super().__init__(msg, *args)
class CaptchaRequired(Captcha): class CaptchaRequired(Captcha):
"""Класс исключения, вызываемый в случае необходимости ввода проверочного кода.""" """Класс исключения, вызываемый в случае необходимости ввода проверочного кода."""
class CaptchaWrong(Captcha): class CaptchaNotShown(Captcha):
"""Класс исключения, вызываемый в случае неправильного ввода капчи.""" """Класс исключения, вызываемый в случае когда капча была получена, но изображение не было загружено.
Notes:
Будет вызвано если не сделать запрос на `captcha_image_url` полученный в `CaptchaRequired`. Получив данное
исключение можно выполнить запрос еще раз для получения новой капчи.
"""
class NetworkError(YandexMusicError): 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 import requests
from yandex_music.utils.captcha_response import CaptchaResponse
from yandex_music.utils.response import Response from yandex_music.utils.response import Response
from yandex_music.exceptions import ( from yandex_music.exceptions import (
Unauthorized, Unauthorized,
BadRequest, BadRequest,
NetworkError, NetworkError,
YandexMusicError, YandexMusicError,
CaptchaRequired,
CaptchaWrong,
TimedOut, TimedOut,
) )
@ -185,8 +182,6 @@ class Request:
:class:`yandex_music.exceptions.Unauthorized`: При невалидном токене, долгом ожидании прямой ссылки на файл. :class:`yandex_music.exceptions.Unauthorized`: При невалидном токене, долгом ожидании прямой ссылки на файл.
:class:`yandex_music.exceptions.BadRequest`: При неправильном запросе. :class:`yandex_music.exceptions.BadRequest`: При неправильном запросе.
:class:`yandex_music.exceptions.NetworkError`: При проблемах с сетью. :class:`yandex_music.exceptions.NetworkError`: При проблемах с сетью.
:class:`yandex_music.exceptions.CaptchaWrong`: При неправильной капче.
:class:`yandex_music.exceptions.CaptchaRequired`: При необходимости пройти капчу.
""" """
if 'headers' not in kwargs: if 'headers' not in kwargs:
kwargs['headers'] = {} kwargs['headers'] = {}
@ -206,10 +201,7 @@ class Request:
parse = self._parse(resp.content) parse = self._parse(resp.content)
message = parse.error or 'Unknown HTTPError' message = parse.error or 'Unknown HTTPError'
if 'CAPTCHA' in message: if resp.status_code in (401, 403):
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):
raise Unauthorized(message) raise Unauthorized(message)
elif resp.status_code == 400: elif resp.status_code == 400:
raise BadRequest(message) raise BadRequest(message)

ファイルの表示

@ -18,8 +18,8 @@ class Response(YandexMusicObject):
Attributes: Attributes:
data (:obj:`dict`): Ответ на запрос. Используется тогда, когда отсутствует `result`. data (:obj:`dict`): Ответ на запрос. Используется тогда, когда отсутствует `result`.
invocation_info (:obj:`yandex_music.InvocationInfo` | :obj:`None`): Информация о запросе. invocation_info (:obj:`yandex_music.InvocationInfo` | :obj:`None`): Информация о запросе.
result (:obj:`dict`): Ответ на запрос (секция с результатом). _result (:obj:`dict`): Ответ на запрос (секция с результатом).
error (:obj:`str`): Код ошибки. _error (:obj:`str`): Код ошибки.
error_description (:obj:`str`): Описание ошибки. error_description (:obj:`str`): Описание ошибки.
client (:obj:`yandex_music.Client`): Клиент Yandex Music. client (:obj:`yandex_music.Client`): Клиент Yandex Music.