Переезд на новую систему авторизации.
Обновлены примеры в README. Исправлено отображение процента покрытия тестами.
このコミットが含まれているのは:
コミット
365ca65e2c
18
README.rst
18
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)
|
||||
|
||||
|
|
|
@ -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
|
92
setup.py
92
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',
|
||||
},
|
||||
)
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
読み込み中…
新しいイシューから参照