Мердж капчи в дев

このコミットが含まれているのは:
Marshal 2019-11-20 20:58:19 +03:00
コミット 633057b8ba
7個のファイルの変更153行の追加9行の削除

ファイルの表示

@ -195,6 +195,23 @@ music.yandex.ru/album/**1193829**/track/**10994777**
Больше примеров тут: `proxies - advanced usage - requests <https://2.python-requests.org/en/master/user/advanced/#proxies>`_
Пример инициализации клиента с обработкой капчи:
.. code:: python
def init_client():
client = captcha_key = captcha_answer = None
while not client:
try:
client = Client.from_credentials('login', 'pass', captcha_answer, captcha_key)
except Captcha as e:
e.captcha.download('captcha.png')
captcha_key = e.captcha.x_captcha_key
captcha_answer = input('Число с картинки: ')
return client
--------------------
Изучение по примерам
--------------------

ファイルの表示

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

ファイルの表示

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

ファイルの表示

@ -86,7 +86,7 @@ class Client(YandexMusicObject):
self.account = self.account_status().account
@classmethod
def from_credentials(cls, username, password, *args, **kwargs):
def from_credentials(cls, username, password, x_captcha_answer=None, x_captcha_key=None, *args, **kwargs):
"""Инициализция клиента по логину и паролю.
Данный метод получает токен каждый раз при вызове. Рекомендуется сгенерировать его самостоятельно, сохранить и
@ -95,13 +95,16 @@ class Client(YandexMusicObject):
Args:
username (:obj:`str`): Логин клиента (идентификатор).
password (:obj:`str`): Пароль клиента (аутентификатор).
x_captcha_answer (:obj:`str`, optional): Ответ на капчу (цифры с картинки).
x_captcha_key (:obj:`str`, optional): Уникальный ключ капчи.
**kwargs (:obj:`dict`, optional): Аргументы для конструктора клиента.
Returns:
:obj:`yandex_music.Client`.
"""
return cls(cls().generate_token_by_username_and_password(username, password), *args, **kwargs)
return cls(cls().generate_token_by_username_and_password(username, password, x_captcha_answer=x_captcha_answer,
x_captcha_key=x_captcha_key), *args, **kwargs)
@classmethod
def from_token(cls, token, *args, **kwargs):
@ -120,14 +123,16 @@ class Client(YandexMusicObject):
return cls(token=token, *args, **kwargs)
@log
def generate_token_by_username_and_password(self, username, password, grant_type='password',
timeout=None, *args, **kwargs):
def generate_token_by_username_and_password(self, username, password, grant_type='password', x_captcha_answer=None,
x_captcha_key=None, timeout=None, *args, **kwargs):
"""Метод получения OAuth токена по логину и паролю.
Args:
username (: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): Произвольные аргументы (будут переданы в запрос).
@ -149,6 +154,9 @@ class Client(YandexMusicObject):
'password': password
}
if x_captcha_answer and x_captcha_key:
data.update({'x_captcha_answer': x_captcha_answer, 'x_captcha_key': x_captcha_key})
result = self._request.post(url, data, timeout=timeout, *args, **kwargs)
return result.get('access_token')

ファイルの表示

@ -20,6 +20,38 @@ class Unauthorized(YandexMusicError):
pass
class Captcha(YandexMusicError):
"""Базовый класс, представляющий исключение связанное с капчей.
Attributes:
captcha (:obj:`yandex_music.utils.captcha_response.CaptchaResponse`): Объект класса
:class:`yandex_music.utils.captcha_response.CaptchaResponse` представляющий капчу.
Args:
msg (:obj:`str`): Сообщение с ошибкой.
captcha (:obj:`yandex_music.utils.captcha_response.CaptchaResponse`): Объект класса
:class:`yandex_music.utils.captcha_response.CaptchaResponse` представляющий капчу.
"""
def __init__(self, msg, captcha, *args, **kwargs):
self.captcha = captcha
super().__init__(msg, *args, **kwargs)
class CaptchaRequired(Captcha):
"""Класс исключения, вызываемый в случае необходимости ввода проверочного кода.
"""
pass
class CaptchaWrong(Captcha):
"""Класс исключения, вызываемый в случае неправильного ввода капчи.
"""
pass
class NetworkError(YandexMusicError):
"""Базовый класс исключений, вызываемых для ошибок, связанных с
запросами к серверу.
@ -37,4 +69,4 @@ class TimedOut(NetworkError):
"""
def __init__(self):
super(TimedOut, self).__init__('Timed out')
super().__init__('Timed out')

71
yandex_music/utils/captcha_response.py ノーマルファイル
ファイルの表示

@ -0,0 +1,71 @@
from yandex_music import YandexMusicObject
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`): Объект класса :class:`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): Объект класса :class:`yandex_music.Client` представляющий клиент
Yandex Music.
**kwargs: Произвольные ключевые аргументы полученные от API.
"""
def __init__(self,
x_captcha_url,
x_captcha_key,
error_description,
error,
client=None,
**kwargs):
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)
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, client):
"""Десериализация объекта.
Args:
data (:obj:`dict`): Поля и значения десериализуемого объекта.
client (:obj:`yandex_music.Client`): Объект класса :class:`yandex_music.Client` представляющий клиент Yandex
Music.
Returns:
:obj:`yandex_music.utils.captcha_response.CaptchaResponse`: Объект класса
:class:`yandex_music.utils.captcha_response.CaptchaResponse`.
"""
if not data:
return None
data = super(CaptchaResponse, cls).de_json(data, client)
return cls(client=client, **data)

ファイルの表示

@ -3,9 +3,10 @@ import json
import logging
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
from yandex_music.exceptions import Unauthorized, BadRequest, NetworkError, YandexMusicError, CaptchaRequired, \
CaptchaWrong
USER_AGENT = 'Yandex-Music-API'
HEADERS = {
@ -78,6 +79,9 @@ class Request:
except (AttributeError, ValueError):
raise YandexMusicError('Invalid server response')
if not data.get('result'):
data = {'result': data, 'error': data.get('error'), 'error_description': data.get('error_description')}
return Response.de_json(data, self.client)
def _request_wrapper(self, *args, **kwargs):
@ -96,9 +100,13 @@ class Request:
if 200 <= resp.status_code <= 299:
return resp
message = self._parse(resp.content).error or 'Unknown HTTPError'
parse = self._parse(resp.content)
message = parse.error or 'Unknown HTTPError'
if resp.status_code in (401, 403):
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):
raise Unauthorized(message)
elif resp.status_code == 400:
raise BadRequest(message)