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