адаптация под Kodi 19
This commit is contained in:
		
							parent
							
								
									2a081a37f4
								
							
						
					
					
						commit
						184bb7f632
					
				@ -1,7 +1,7 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 | 
			
		||||
<addon id="script.module.vk" name="vk" version="2.0.2" provider-name="inpos">
 | 
			
		||||
  <requires>
 | 
			
		||||
    <import addon="xbmc.python" version="2.25.0"/>
 | 
			
		||||
    <import addon="xbmc.python" version="3.0.0"/>
 | 
			
		||||
    <import addon="script.module.requests" version="2.25.1"/>
 | 
			
		||||
    <import addon="script.module.simplejson"/>
 | 
			
		||||
  </requires>
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,9 @@
 | 
			
		||||
from .session import API, UserAPI, CommunityAPI
 | 
			
		||||
 | 
			
		||||
__version__ = '3.0-dev'
 | 
			
		||||
from vk.api import logger
 | 
			
		||||
from vk.api import Session, AuthSession, InteractiveSession, InteractiveAuthSession
 | 
			
		||||
from vk.api import VERSION
 | 
			
		||||
from vk.api import API
 | 
			
		||||
 | 
			
		||||
__all__ = (API, UserAPI, CommunityAPI)
 | 
			
		||||
__version__ = version = VERSION
 | 
			
		||||
 | 
			
		||||
# API = OAuthAPI
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										197
									
								
								lib/vk/api.py
									
									
									
									
									
								
							
							
						
						
									
										197
									
								
								lib/vk/api.py
									
									
									
									
									
								
							@ -1,38 +1,185 @@
 | 
			
		||||
# coding=utf8
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
import logging.config
 | 
			
		||||
 | 
			
		||||
from vk.utils import stringify_values
 | 
			
		||||
from vk.logs import LOGGING_CONFIG
 | 
			
		||||
from vk.utils import stringify_values, json_iter_parse, LoggingSession, str_type
 | 
			
		||||
from vk.exceptions import VkAuthError, VkAPIError
 | 
			
		||||
from vk.mixins import AuthMixin, InteractiveMixin
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
VERSION = '2.0.2'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
logging.config.dictConfig(LOGGING_CONFIG)
 | 
			
		||||
logger = logging.getLogger('vk')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class APINamespace:
 | 
			
		||||
    def __init__(self, api, method_common_params):
 | 
			
		||||
class Session(object):
 | 
			
		||||
    API_URL = 'https://api.vk.com/method/'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, access_token=None):
 | 
			
		||||
 | 
			
		||||
        logger.debug('API.__init__(access_token=%(access_token)r)', {'access_token': access_token})
 | 
			
		||||
 | 
			
		||||
        self.access_token = access_token
 | 
			
		||||
        self.access_token_is_needed = False
 | 
			
		||||
 | 
			
		||||
        self.requests_session = LoggingSession()
 | 
			
		||||
        self.requests_session.headers['Accept'] = 'application/json'
 | 
			
		||||
        self.requests_session.headers['Content-Type'] = 'application/x-www-form-urlencoded'
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def access_token(self):
 | 
			
		||||
        logger.debug('Check that we need new access token')
 | 
			
		||||
        if self.access_token_is_needed:
 | 
			
		||||
            logger.debug('We need new access token. Try to get it.')
 | 
			
		||||
            self.access_token = self.get_access_token()
 | 
			
		||||
        else:
 | 
			
		||||
            logger.debug('Use old access token')
 | 
			
		||||
        return self._access_token
 | 
			
		||||
 | 
			
		||||
    @access_token.setter
 | 
			
		||||
    def access_token(self, value):
 | 
			
		||||
        self._access_token = value
 | 
			
		||||
        if isinstance(value, str_type) and len(value) >= 12:
 | 
			
		||||
            self.censored_access_token = '{}***{}'.format(value[:4], value[-4:])
 | 
			
		||||
        else:
 | 
			
		||||
            self.censored_access_token = value
 | 
			
		||||
        logger.debug('access_token = %r', self.censored_access_token)
 | 
			
		||||
        self.access_token_is_needed = not self._access_token
 | 
			
		||||
 | 
			
		||||
    def get_user_login(self):
 | 
			
		||||
        logger.debug('Do nothing to get user login')
 | 
			
		||||
 | 
			
		||||
    def get_access_token(self):
 | 
			
		||||
        """
 | 
			
		||||
        Dummy method
 | 
			
		||||
        """
 | 
			
		||||
        logger.debug('API.get_access_token()')
 | 
			
		||||
        return self._access_token
 | 
			
		||||
 | 
			
		||||
    def make_request(self, method_request, captcha_response=None):
 | 
			
		||||
 | 
			
		||||
        logger.debug('Prepare API Method request')
 | 
			
		||||
 | 
			
		||||
        response = self.send_api_request(method_request, captcha_response=captcha_response)
 | 
			
		||||
        # todo Replace with something less exceptional
 | 
			
		||||
        response.raise_for_status()
 | 
			
		||||
 | 
			
		||||
        # there are may be 2 dicts in one JSON
 | 
			
		||||
        # for example: "{'error': ...}{'response': ...}"
 | 
			
		||||
        for response_or_error in json_iter_parse(response.text):
 | 
			
		||||
            if 'response' in response_or_error:
 | 
			
		||||
                # todo Can we have error and response simultaneously
 | 
			
		||||
                # for error in errors:
 | 
			
		||||
                #     logger.warning(str(error))
 | 
			
		||||
 | 
			
		||||
                return response_or_error['response']
 | 
			
		||||
 | 
			
		||||
            elif 'error' in response_or_error:
 | 
			
		||||
                error_data = response_or_error['error']
 | 
			
		||||
                error = VkAPIError(error_data)
 | 
			
		||||
 | 
			
		||||
                if error.is_captcha_needed():
 | 
			
		||||
                    captcha_key = self.get_captcha_key(error.captcha_img)
 | 
			
		||||
                    if not captcha_key:
 | 
			
		||||
                        raise error
 | 
			
		||||
 | 
			
		||||
                    captcha_response = {
 | 
			
		||||
                        'sid': error.captcha_sid,
 | 
			
		||||
                        'key': captcha_key,
 | 
			
		||||
                    }
 | 
			
		||||
                    return self.make_request(method_request, captcha_response=captcha_response)
 | 
			
		||||
 | 
			
		||||
                elif error.is_access_token_incorrect():
 | 
			
		||||
                    logger.info('Authorization failed. Access token will be dropped')
 | 
			
		||||
                    self.access_token = None
 | 
			
		||||
                    return self.make_request(method_request)
 | 
			
		||||
 | 
			
		||||
                else:
 | 
			
		||||
                    raise error
 | 
			
		||||
 | 
			
		||||
    def send_api_request(self, request, captcha_response=None):
 | 
			
		||||
        url = self.API_URL + request._method_name
 | 
			
		||||
        method_args = request._api._method_default_args.copy()
 | 
			
		||||
        method_args.update(stringify_values(request._method_args))
 | 
			
		||||
        access_token = self.access_token
 | 
			
		||||
        if access_token:
 | 
			
		||||
            method_args['access_token'] = access_token
 | 
			
		||||
        if captcha_response:
 | 
			
		||||
            method_args['captcha_sid'] = captcha_response['sid']
 | 
			
		||||
            method_args['captcha_key'] = captcha_response['key']
 | 
			
		||||
        timeout = request._api._timeout
 | 
			
		||||
        response = self.requests_session.post(url, method_args, timeout=timeout)
 | 
			
		||||
        return response
 | 
			
		||||
 | 
			
		||||
    def get_captcha_key(self, captcha_image_url):
 | 
			
		||||
        """
 | 
			
		||||
        Default behavior on CAPTCHA is to raise exception
 | 
			
		||||
        Reload this in child
 | 
			
		||||
        """
 | 
			
		||||
        return None
 | 
			
		||||
    
 | 
			
		||||
    def auth_code_is_needed(self, content, session):
 | 
			
		||||
        """
 | 
			
		||||
        Default behavior on 2-AUTH CODE is to raise exception
 | 
			
		||||
        Reload this in child
 | 
			
		||||
        """           
 | 
			
		||||
        raise VkAuthError('Authorization error (2-factor code is needed)')
 | 
			
		||||
    
 | 
			
		||||
    def auth_captcha_is_needed(self, content, session):
 | 
			
		||||
        """
 | 
			
		||||
        Default behavior on CAPTCHA is to raise exception
 | 
			
		||||
        Reload this in child
 | 
			
		||||
        """              
 | 
			
		||||
        raise VkAuthError('Authorization error (captcha)')
 | 
			
		||||
    
 | 
			
		||||
    def phone_number_is_needed(self, content, session):
 | 
			
		||||
        """
 | 
			
		||||
        Default behavior on PHONE NUMBER is to raise exception
 | 
			
		||||
        Reload this in child
 | 
			
		||||
        """
 | 
			
		||||
        logger.error('Authorization error (phone number is needed)')
 | 
			
		||||
        raise VkAuthError('Authorization error (phone number is needed)')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class API(object):
 | 
			
		||||
    def __init__(self, session, timeout=10, **method_default_args):
 | 
			
		||||
        self._session = session
 | 
			
		||||
        self._timeout = timeout
 | 
			
		||||
        self._method_default_args = method_default_args
 | 
			
		||||
 | 
			
		||||
    def __getattr__(self, method_name):
 | 
			
		||||
        return Request(self, method_name)
 | 
			
		||||
 | 
			
		||||
    def __call__(self, method_name, **method_kwargs):
 | 
			
		||||
        return getattr(self, method_name)(**method_kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Request(object):
 | 
			
		||||
    __slots__ = ('_api', '_method_name', '_method_args')
 | 
			
		||||
 | 
			
		||||
    def __init__(self, api, method_name):
 | 
			
		||||
        self._api = api
 | 
			
		||||
        self._method_common_params = method_common_params
 | 
			
		||||
        self._method_name = method_name
 | 
			
		||||
 | 
			
		||||
    def __call__(self, method):
 | 
			
		||||
        return APIMethod(self._api, method, self._method_common_params)
 | 
			
		||||
    def __getattr__(self, method_name):
 | 
			
		||||
        return Request(self._api, self._method_name + '.' + method_name)
 | 
			
		||||
 | 
			
		||||
    __getattr__ = __call__
 | 
			
		||||
    def __call__(self, **method_args):
 | 
			
		||||
        self._method_args = method_args
 | 
			
		||||
        return self._api._session.make_request(self)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class APIMethod:
 | 
			
		||||
    def __init__(self, api, method, method_common_params):
 | 
			
		||||
        self._api = api
 | 
			
		||||
        self._method = method
 | 
			
		||||
        self._method_common_params = method_common_params
 | 
			
		||||
 | 
			
		||||
    def __getattr__(self, method):
 | 
			
		||||
        return self.__class__(self._api, self._method + '.' + method, self._method_common_params)
 | 
			
		||||
 | 
			
		||||
    def __call__(self, **method_params):
 | 
			
		||||
        request_method_params = self._method_common_params.copy()
 | 
			
		||||
        request_method_params.update(stringify_values(method_params))
 | 
			
		||||
 | 
			
		||||
        return self._api.send(APIRequest(self._method, request_method_params))
 | 
			
		||||
class AuthSession(AuthMixin, Session):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class APIRequest:
 | 
			
		||||
    def __init__(self, method, method_params):
 | 
			
		||||
        self.method = method
 | 
			
		||||
        self.method_params = method_params
 | 
			
		||||
class InteractiveSession(InteractiveMixin, Session):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InteractiveAuthSession(InteractiveMixin, AuthSession):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,8 @@ AUTHORIZATION_FAILED = 5    # Invalid access token
 | 
			
		||||
PERMISSION_IS_DENIED = 7
 | 
			
		||||
CAPTCHA_IS_NEEDED = 14
 | 
			
		||||
ACCESS_DENIED = 15          # No access to call this method
 | 
			
		||||
INVALID_USER_ID = 113       # User deactivated
 | 
			
		||||
                            # User deactivated
 | 
			
		||||
INVALID_USER_ID = 113
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class VkException(Exception):
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										27
									
								
								lib/vk/logs.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								lib/vk/logs.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
LOGGING_CONFIG = {
 | 
			
		||||
    'version': 1,
 | 
			
		||||
    'disable_existing_loggers': False,
 | 
			
		||||
    'loggers': {
 | 
			
		||||
        'vk': {
 | 
			
		||||
            'level': 'INFO',
 | 
			
		||||
            'handlers': ['vk-stdout'],
 | 
			
		||||
            'propagate': False,
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    'handlers': {
 | 
			
		||||
        'vk-stdout': {
 | 
			
		||||
            'class': 'logging.StreamHandler',
 | 
			
		||||
            'stream': sys.stdout,
 | 
			
		||||
            'formatter': 'vk-verbose',
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    'formatters': {
 | 
			
		||||
        'vk-verbose': {
 | 
			
		||||
            'format': '%(asctime)s %(name) -5s %(module)s:%(lineno)d %(levelname)s: %(message)s',
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										218
									
								
								lib/vk/mixins.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										218
									
								
								lib/vk/mixins.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,218 @@
 | 
			
		||||
# coding=utf8
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from vk.exceptions import VkAuthError
 | 
			
		||||
from vk.utils import raw_input, get_url_query, LoggingSession, get_form_action
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
logger = logging.getLogger('vk')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuthMixin(object):
 | 
			
		||||
    LOGIN_URL = 'https://m.vk.com'
 | 
			
		||||
    # REDIRECT_URI = 'https://oauth.vk.com/blank.html'
 | 
			
		||||
    AUTHORIZE_URL = 'https://oauth.vk.com/authorize'
 | 
			
		||||
    CAPTCHA_URI = 'https://m.vk.com/captcha.php'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, app_id=None, user_login='', user_password='', scope='offline', **kwargs):
 | 
			
		||||
        logger.debug('AuthMixin.__init__(app_id=%(app_id)r, user_login=%(user_login)r, user_password=%(user_password)r, **kwargs=%(kwargs)s)',
 | 
			
		||||
            dict(app_id=app_id, user_login=user_login, user_password=user_password, kwargs=kwargs))
 | 
			
		||||
 | 
			
		||||
        super(AuthMixin, self).__init__(**kwargs)
 | 
			
		||||
 | 
			
		||||
        self.app_id = app_id
 | 
			
		||||
        self.user_login = user_login
 | 
			
		||||
        self.user_password = user_password
 | 
			
		||||
        self.scope = scope
 | 
			
		||||
 | 
			
		||||
        # Some API methods get args (e.g. user id) from access token.
 | 
			
		||||
        # If we define user login, we need get access token now.
 | 
			
		||||
        if self.user_login:
 | 
			
		||||
            self.access_token = self.get_access_token()
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def user_login(self):
 | 
			
		||||
        if not self._user_login:
 | 
			
		||||
            self._user_login = self.get_user_login()
 | 
			
		||||
        return self._user_login
 | 
			
		||||
 | 
			
		||||
    @user_login.setter
 | 
			
		||||
    def user_login(self, value):
 | 
			
		||||
        self._user_login = value
 | 
			
		||||
 | 
			
		||||
    def get_user_login(self):
 | 
			
		||||
        return self._user_login
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def user_password(self):
 | 
			
		||||
        if not self._user_password:
 | 
			
		||||
            self._user_password = self.get_user_password()
 | 
			
		||||
        return self._user_password
 | 
			
		||||
 | 
			
		||||
    @user_password.setter
 | 
			
		||||
    def user_password(self, value):
 | 
			
		||||
        self._user_password = value
 | 
			
		||||
 | 
			
		||||
    def get_user_password(self):
 | 
			
		||||
        return self._user_password
 | 
			
		||||
    
 | 
			
		||||
    def get_access_token(self):
 | 
			
		||||
        """
 | 
			
		||||
        Get access token using app id and user login and password.
 | 
			
		||||
        """
 | 
			
		||||
        logger.debug('AuthMixin.get_access_token()')
 | 
			
		||||
 | 
			
		||||
        auth_session = LoggingSession()
 | 
			
		||||
        with auth_session as self.auth_session:
 | 
			
		||||
            self.auth_session = auth_session
 | 
			
		||||
            self.login()
 | 
			
		||||
            auth_response_url_query = self.oauth2_authorization()
 | 
			
		||||
 | 
			
		||||
        if 'access_token' in auth_response_url_query:
 | 
			
		||||
            return auth_response_url_query['access_token']
 | 
			
		||||
        else:
 | 
			
		||||
            raise VkAuthError('OAuth2 authorization error')
 | 
			
		||||
 | 
			
		||||
    def login(self):
 | 
			
		||||
        """
 | 
			
		||||
        Login
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        response = self.auth_session.get(self.LOGIN_URL)
 | 
			
		||||
        login_form_action = get_form_action(response.text)
 | 
			
		||||
        if not login_form_action:
 | 
			
		||||
            raise VkAuthError('VK changed login flow')
 | 
			
		||||
 | 
			
		||||
        login_form_data = {
 | 
			
		||||
            'email': self.user_login,
 | 
			
		||||
            'pass': self.user_password,
 | 
			
		||||
        }
 | 
			
		||||
        response = self.auth_session.post(login_form_action, login_form_data)
 | 
			
		||||
        logger.debug('Cookies: %s', self.auth_session.cookies)
 | 
			
		||||
 | 
			
		||||
        response_url_query = get_url_query(response.url)
 | 
			
		||||
 | 
			
		||||
        if 'remixsid' in self.auth_session.cookies or 'remixsid6' in self.auth_session.cookies:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        if 'sid' in response_url_query:
 | 
			
		||||
            self.auth_captcha_is_needed(response, login_form_data)
 | 
			
		||||
        elif response_url_query.get('act') == 'authcheck':
 | 
			
		||||
            self.auth_check_is_needed(response.text)
 | 
			
		||||
        elif 'security_check' in response_url_query:
 | 
			
		||||
            self.phone_number_is_needed(response.text)
 | 
			
		||||
        else:
 | 
			
		||||
            message = 'Authorization error (incorrect password)'
 | 
			
		||||
            logger.error(message)
 | 
			
		||||
            raise VkAuthError(message)
 | 
			
		||||
 | 
			
		||||
    def oauth2_authorization(self):
 | 
			
		||||
        """
 | 
			
		||||
        OAuth2
 | 
			
		||||
        """
 | 
			
		||||
        auth_data = {
 | 
			
		||||
            'client_id': self.app_id,
 | 
			
		||||
            'display': 'mobile',
 | 
			
		||||
            'response_type': 'token',
 | 
			
		||||
            'scope': self.scope,
 | 
			
		||||
            'v': '5.28',
 | 
			
		||||
        }
 | 
			
		||||
        response = self.auth_session.post(self.AUTHORIZE_URL, auth_data)
 | 
			
		||||
        response_url_query = get_url_query(response.url)
 | 
			
		||||
        if 'access_token' in response_url_query:
 | 
			
		||||
            return response_url_query
 | 
			
		||||
 | 
			
		||||
        # Permissions is needed
 | 
			
		||||
        logger.info('Getting permissions')
 | 
			
		||||
        # form_action = re.findall(r'<form method="post" action="(.+?)">', auth_response.text)[0]
 | 
			
		||||
        form_action = get_form_action(response.text)
 | 
			
		||||
        logger.debug('Response form action: %s', form_action)
 | 
			
		||||
        if form_action:
 | 
			
		||||
            response = self.auth_session.get(form_action)
 | 
			
		||||
            response_url_query = get_url_query(response.url)
 | 
			
		||||
            return response_url_query
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            response_json = response.json()
 | 
			
		||||
        except ValueError:  # not JSON in response
 | 
			
		||||
            error_message = 'OAuth2 grant access error'
 | 
			
		||||
        else:
 | 
			
		||||
            error_message = 'VK error: [{}] {}'.format(response_json['error'], response_json['error_description'])
 | 
			
		||||
        logger.error('Permissions obtained')
 | 
			
		||||
        raise VkAuthError(error_message)
 | 
			
		||||
 | 
			
		||||
    def auth_check_is_needed(self, html):
 | 
			
		||||
        logger.info('User enabled 2 factors authorization. Auth check code is needed')
 | 
			
		||||
        auth_check_form_action = get_form_action(html)
 | 
			
		||||
        auth_check_code = self.get_auth_check_code()
 | 
			
		||||
        auth_check_data = {
 | 
			
		||||
            'code': auth_check_code,
 | 
			
		||||
            '_ajax': '1',
 | 
			
		||||
            'remember': '1'
 | 
			
		||||
        }
 | 
			
		||||
        response = self.auth_session.post(auth_check_form_action, data=auth_check_data)
 | 
			
		||||
 | 
			
		||||
    def auth_captcha_is_needed(self, response, login_form_data):
 | 
			
		||||
        logger.info('Captcha is needed')
 | 
			
		||||
 | 
			
		||||
        response_url_dict = get_url_query(response.url)
 | 
			
		||||
 | 
			
		||||
        # form_url = re.findall(r'<form method="post" action="(.+)" novalidate>', response.text)
 | 
			
		||||
        captcha_form_action = get_form_action(response.text)
 | 
			
		||||
        logger.debug('form_url %s', captcha_form_action)
 | 
			
		||||
        if not captcha_form_action:
 | 
			
		||||
            raise VkAuthError('Cannot find form url')
 | 
			
		||||
 | 
			
		||||
        # todo Are we sure that `response_url_dict` doesn't contain CAPTCHA image url?
 | 
			
		||||
        captcha_url = '%s?s=%s&sid=%s' % (self.CAPTCHA_URI, response_url_dict['s'], response_url_dict['sid'])
 | 
			
		||||
        # logger.debug('Captcha url %s', captcha_url)
 | 
			
		||||
 | 
			
		||||
        login_form_data['captcha_sid'] = response_url_dict['sid']
 | 
			
		||||
        login_form_data['captcha_key'] = self.get_captcha_key(captcha_url)
 | 
			
		||||
 | 
			
		||||
        response = self.auth_session.post(captcha_form_action, login_form_data)
 | 
			
		||||
 | 
			
		||||
        # logger.debug('Cookies %s', self.auth_session.cookies)
 | 
			
		||||
        # if 'remixsid' not in self.auth_session.cookies and 'remixsid6' not in self.auth_session.cookies:
 | 
			
		||||
        #     raise VkAuthError('Authorization error (Bad password or captcha key)')
 | 
			
		||||
 | 
			
		||||
    def phone_number_is_needed(self, text):
 | 
			
		||||
        raise VkAuthError('Phone number is needed')
 | 
			
		||||
 | 
			
		||||
    def get_auth_check_code(self):
 | 
			
		||||
        raise VkAuthError('Auth check code is needed')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InteractiveMixin(object):
 | 
			
		||||
    def get_user_login(self):
 | 
			
		||||
        user_login = raw_input('VK user login: ')
 | 
			
		||||
        return user_login.strip()
 | 
			
		||||
 | 
			
		||||
    def get_user_password(self):
 | 
			
		||||
        import getpass
 | 
			
		||||
 | 
			
		||||
        user_password = getpass.getpass('VK user password: ')
 | 
			
		||||
        return user_password
 | 
			
		||||
 | 
			
		||||
    def get_access_token(self):
 | 
			
		||||
        logger.debug('InteractiveMixin.get_access_token()')
 | 
			
		||||
        access_token = super(InteractiveMixin, self).get_access_token()
 | 
			
		||||
        if not access_token:
 | 
			
		||||
            access_token = raw_input('VK API access token: ')
 | 
			
		||||
        return access_token
 | 
			
		||||
 | 
			
		||||
    def get_captcha_key(self, captcha_image_url):
 | 
			
		||||
        """
 | 
			
		||||
        Read CAPTCHA key from shell
 | 
			
		||||
        """
 | 
			
		||||
        print('Open CAPTCHA image url: ', captcha_image_url)
 | 
			
		||||
        captcha_key = raw_input('Enter CAPTCHA key: ')
 | 
			
		||||
        return captcha_key
 | 
			
		||||
 | 
			
		||||
    def get_auth_check_code(self):
 | 
			
		||||
        """
 | 
			
		||||
        Read Auth code from shell
 | 
			
		||||
        """
 | 
			
		||||
        auth_check_code = raw_input('Auth check code: ')
 | 
			
		||||
        return auth_check_code.strip()
 | 
			
		||||
@ -1,281 +0,0 @@
 | 
			
		||||
import re
 | 
			
		||||
import urllib
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
import requests
 | 
			
		||||
 | 
			
		||||
from .exceptions import VkAuthError, VkAPIError
 | 
			
		||||
from .api import APINamespace
 | 
			
		||||
from .utils import json_iter_parse, stringify
 | 
			
		||||
 | 
			
		||||
logger = logging.getLogger('vk')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class APIBase:
 | 
			
		||||
    METHOD_COMMON_PARAMS = {'v', 'lang', 'https', 'test_mode'}
 | 
			
		||||
 | 
			
		||||
    API_URL = 'https://api.vk.com/method/'
 | 
			
		||||
    CAPTCHA_URL = 'https://m.vk.com/captcha.php'
 | 
			
		||||
 | 
			
		||||
    def __new__(cls, *args, **kwargs):
 | 
			
		||||
        method_common_params = {key: kwargs.pop(key) for key in tuple(kwargs) if key in cls.METHOD_COMMON_PARAMS}
 | 
			
		||||
 | 
			
		||||
        api = object.__new__(cls)
 | 
			
		||||
        api.__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
        return APINamespace(api, method_common_params)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, timeout=10):
 | 
			
		||||
        self.timeout = timeout
 | 
			
		||||
 | 
			
		||||
        self.session = requests.Session()
 | 
			
		||||
        self.session.headers['Accept'] = 'application/json'
 | 
			
		||||
        self.session.headers['Content-Type'] = 'application/x-www-form-urlencoded'
 | 
			
		||||
 | 
			
		||||
    def send(self, request):
 | 
			
		||||
 | 
			
		||||
        logger.debug('Prepare API Method request')
 | 
			
		||||
 | 
			
		||||
        self.prepare_request(request)
 | 
			
		||||
 | 
			
		||||
        method_url = self.API_URL + request.method
 | 
			
		||||
        response = self.session.post(method_url, request.method_params, timeout=self.timeout)
 | 
			
		||||
 | 
			
		||||
        # todo Replace with something less exceptional
 | 
			
		||||
        response.raise_for_status()
 | 
			
		||||
 | 
			
		||||
        # TODO: there are may be 2 dicts in one JSON
 | 
			
		||||
        # for example: "{'error': ...}{'response': ...}"
 | 
			
		||||
        for response_or_error in json_iter_parse(response.text):
 | 
			
		||||
            request.response = response_or_error
 | 
			
		||||
 | 
			
		||||
            if 'response' in response_or_error:
 | 
			
		||||
                # todo Can we have error and response simultaneously
 | 
			
		||||
                # for error in errors:
 | 
			
		||||
                #     logger.warning(str(error))
 | 
			
		||||
                return response_or_error['response']
 | 
			
		||||
 | 
			
		||||
            elif 'error' in response_or_error:
 | 
			
		||||
                api_error = VkAPIError(request.response['error'])
 | 
			
		||||
                request.api_error = api_error
 | 
			
		||||
                return self.handle_api_error(request)
 | 
			
		||||
 | 
			
		||||
    def prepare_request(self, request):
 | 
			
		||||
        request.method_params['access_token'] = self.access_token
 | 
			
		||||
 | 
			
		||||
    def get_access_token(self):
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    def handle_api_error(self, request):
 | 
			
		||||
        logger.error('Handle API error: %s', request.api_error)
 | 
			
		||||
 | 
			
		||||
        api_error_handler_name = 'on_api_error_' + str(request.api_error.code)
 | 
			
		||||
        api_error_handler = getattr(self, api_error_handler_name, self.on_api_error)
 | 
			
		||||
 | 
			
		||||
        return api_error_handler(request)
 | 
			
		||||
 | 
			
		||||
    def on_api_error_14(self, request):
 | 
			
		||||
        """
 | 
			
		||||
        14. Captcha needed
 | 
			
		||||
        """
 | 
			
		||||
        request.method_params['captcha_key'] = self.get_captcha_key(request)
 | 
			
		||||
        request.method_params['captcha_sid'] = request.api_error.captcha_sid
 | 
			
		||||
 | 
			
		||||
        return self.send(request)
 | 
			
		||||
 | 
			
		||||
    def on_api_error_15(self, request):
 | 
			
		||||
        """
 | 
			
		||||
        15. Access denied
 | 
			
		||||
            - due to scope
 | 
			
		||||
        """
 | 
			
		||||
        logger.error('Authorization failed. Access token will be dropped')
 | 
			
		||||
        self.access_token = self.get_access_token()
 | 
			
		||||
        return self.send(request)
 | 
			
		||||
 | 
			
		||||
    def on_api_error(self, request):
 | 
			
		||||
        logger.error('API error: %s', request.api_error)
 | 
			
		||||
        raise request.api_error
 | 
			
		||||
 | 
			
		||||
    def get_captcha_key(self, request):
 | 
			
		||||
        """
 | 
			
		||||
        Default behavior on CAPTCHA is to raise exception
 | 
			
		||||
        Reload this in child
 | 
			
		||||
        """
 | 
			
		||||
        # request.api_error.captcha_img
 | 
			
		||||
        raise request.api_error
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class API(APIBase):
 | 
			
		||||
    def __init__(self, access_token, **kwargs):
 | 
			
		||||
        super().__init__(**kwargs)
 | 
			
		||||
        self.access_token = access_token
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserAPI(APIBase):
 | 
			
		||||
    LOGIN_URL = 'https://m.vk.com'
 | 
			
		||||
    AUTHORIZE_URL = 'https://oauth.vk.com/authorize'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, user_login='', user_password='', app_id=None, scope='offline', **kwargs):
 | 
			
		||||
        super().__init__(**kwargs)
 | 
			
		||||
 | 
			
		||||
        self.user_login = user_login
 | 
			
		||||
        self.user_password = user_password
 | 
			
		||||
        self.app_id = app_id
 | 
			
		||||
        self.scope = scope
 | 
			
		||||
 | 
			
		||||
        self.access_token = self.get_access_token()
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_form_action(response):
 | 
			
		||||
        form_action = re.findall(r'<form(?= ).* action="(.+)"', response.text)
 | 
			
		||||
        if form_action:
 | 
			
		||||
            return form_action[0]
 | 
			
		||||
        else:
 | 
			
		||||
            raise VkAuthError('No form on page {}'.format(response.url))
 | 
			
		||||
 | 
			
		||||
    def get_response_url_queries(self, response):
 | 
			
		||||
        if not response.ok:
 | 
			
		||||
            if response.status_code == 401:
 | 
			
		||||
                raise VkAuthError(response.json()['error_description'])
 | 
			
		||||
            else:
 | 
			
		||||
                response.raise_for_status()
 | 
			
		||||
 | 
			
		||||
        return self.get_url_queries(response.url)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_url_queries(url):
 | 
			
		||||
        parsed_url = urllib.parse.urlparse(url)
 | 
			
		||||
        url_queries = urllib.parse.parse_qsl(parsed_url.fragment)
 | 
			
		||||
        # We lose repeating keys values
 | 
			
		||||
        return dict(url_queries)
 | 
			
		||||
 | 
			
		||||
    def get_access_token(self):
 | 
			
		||||
        auth_session = requests.Session()
 | 
			
		||||
 | 
			
		||||
        if self.login(auth_session):
 | 
			
		||||
            return self.authorize(auth_session)
 | 
			
		||||
 | 
			
		||||
    def get_login_form_data(self):
 | 
			
		||||
        return {
 | 
			
		||||
            'email': self.user_login,
 | 
			
		||||
            'pass': self.user_password,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def login(self, auth_session):
 | 
			
		||||
        # Get login page
 | 
			
		||||
        login_page_response = auth_session.get(self.LOGIN_URL)
 | 
			
		||||
        # Get login form action. It must contains ip_h and lg_h values
 | 
			
		||||
        login_action = self.get_form_action(login_page_response)
 | 
			
		||||
        # Login using user credentials
 | 
			
		||||
        login_response = auth_session.post(login_action, self.get_login_form_data())
 | 
			
		||||
 | 
			
		||||
        if 'remixsid' in auth_session.cookies or 'remixsid6' in auth_session.cookies:
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
        url_queries = self.get_url_queries(login_response.url)
 | 
			
		||||
        if 'sid' in url_queries:
 | 
			
		||||
            self.auth_captcha_is_needed(login_response)
 | 
			
		||||
 | 
			
		||||
        elif url_queries.get('act') == 'authcheck':
 | 
			
		||||
            self.auth_check_is_needed(login_response.text)
 | 
			
		||||
 | 
			
		||||
        elif 'security_check' in url_queries:
 | 
			
		||||
            self.phone_number_is_needed(login_response.text)
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            raise VkAuthError('Login error (e.g. incorrect password)')
 | 
			
		||||
 | 
			
		||||
    def get_auth_params(self):
 | 
			
		||||
        return {
 | 
			
		||||
            'client_id': self.app_id,
 | 
			
		||||
            'scope': self.scope,
 | 
			
		||||
            'display': 'mobile',
 | 
			
		||||
            'response_type': 'token',
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def authorize(self, auth_session):
 | 
			
		||||
        """
 | 
			
		||||
        OAuth2
 | 
			
		||||
        """
 | 
			
		||||
        # Ask access
 | 
			
		||||
        ask_access_response = auth_session.post(self.AUTHORIZE_URL, self.get_auth_params())
 | 
			
		||||
        url_queries = self.get_response_url_queries(ask_access_response)
 | 
			
		||||
 | 
			
		||||
        if 'access_token' not in url_queries:
 | 
			
		||||
            # Grant access
 | 
			
		||||
            grant_access_action = self.get_form_action(ask_access_response)
 | 
			
		||||
            grant_access_response = auth_session.post(grant_access_action)
 | 
			
		||||
            url_queries = self.get_response_url_queries(grant_access_response)
 | 
			
		||||
 | 
			
		||||
        return self.process_auth_url_queries(url_queries)
 | 
			
		||||
 | 
			
		||||
    def process_auth_url_queries(self, url_queries):
 | 
			
		||||
        self.expires_in = url_queries.get('expires_in')
 | 
			
		||||
        self.user_id = url_queries.get('user_id')
 | 
			
		||||
        return url_queries.get('access_token')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CommunityAPI(UserAPI):
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        self.group_ids = kwargs.pop('group_ids', None)
 | 
			
		||||
        self.default_group_id = None
 | 
			
		||||
 | 
			
		||||
        self.access_tokens = {}
 | 
			
		||||
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_auth_params(self):
 | 
			
		||||
        auth_params = super().get_auth_params()
 | 
			
		||||
        auth_params['group_ids'] = stringify(self.group_ids)
 | 
			
		||||
        return auth_params
 | 
			
		||||
 | 
			
		||||
    def process_auth_url_queries(self, url_queries):
 | 
			
		||||
        super().process_auth_url_queries(url_queries)
 | 
			
		||||
 | 
			
		||||
        self.access_tokens = {}
 | 
			
		||||
        for key, value in url_queries.items():
 | 
			
		||||
            # access_token_GROUP-ID: ACCESS-TOKEN
 | 
			
		||||
            if key.startswith('access_token_'):
 | 
			
		||||
                group_id = int(key[len('access_token_'):])
 | 
			
		||||
                self.access_tokens[group_id] = value
 | 
			
		||||
 | 
			
		||||
        self.default_group_id = self.group_ids[0]
 | 
			
		||||
 | 
			
		||||
    def prepare_request(self, request):
 | 
			
		||||
        group_id = request.method_params.get('group_id', self.default_group_id)
 | 
			
		||||
        request.method_params['access_token'] = self.access_tokens[group_id]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InteractiveMixin:
 | 
			
		||||
 | 
			
		||||
    def get_user_login(self):
 | 
			
		||||
        user_login = input('VK user login: ')
 | 
			
		||||
        return user_login.strip()
 | 
			
		||||
 | 
			
		||||
    def get_user_password(self):
 | 
			
		||||
        import getpass
 | 
			
		||||
 | 
			
		||||
        user_password = getpass.getpass('VK user password: ')
 | 
			
		||||
        return user_password
 | 
			
		||||
 | 
			
		||||
    def get_access_token(self):
 | 
			
		||||
        logger.debug('InteractiveMixin.get_access_token()')
 | 
			
		||||
        access_token = super().get_access_token()
 | 
			
		||||
        if not access_token:
 | 
			
		||||
            access_token = input('VK API access token: ')
 | 
			
		||||
        return access_token
 | 
			
		||||
 | 
			
		||||
    def get_captcha_key(self, captcha_image_url):
 | 
			
		||||
        """
 | 
			
		||||
        Read CAPTCHA key from shell
 | 
			
		||||
        """
 | 
			
		||||
        print('Open CAPTCHA image url: ', captcha_image_url)
 | 
			
		||||
        captcha_key = input('Enter CAPTCHA key: ')
 | 
			
		||||
        return captcha_key
 | 
			
		||||
 | 
			
		||||
    def get_auth_check_code(self):
 | 
			
		||||
        """
 | 
			
		||||
        Read Auth code from shell
 | 
			
		||||
        """
 | 
			
		||||
        auth_check_code = input('Auth check code: ')
 | 
			
		||||
        return auth_check_code.strip()
 | 
			
		||||
							
								
								
									
										60
									
								
								lib/vk/tests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								lib/vk/tests.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,60 @@
 | 
			
		||||
# coding=utf8
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
import unittest
 | 
			
		||||
 | 
			
		||||
import vk
 | 
			
		||||
import utils
 | 
			
		||||
 | 
			
		||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
 | 
			
		||||
 | 
			
		||||
# copy to test_props.py and fill it
 | 
			
		||||
USER_LOGIN = ''         # user email or phone number
 | 
			
		||||
USER_PASSWORD = ''      # user password
 | 
			
		||||
APP_ID = ''             # aka API/Client ID
 | 
			
		||||
 | 
			
		||||
from test_props import USER_LOGIN, USER_PASSWORD, APP_ID
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UtilsTestCase(unittest.TestCase):
 | 
			
		||||
    def test_stringify(self):
 | 
			
		||||
        self.assertEqual({1: 'str,str2'}, utils.stringify_values({1: ['str', 'str2']}))
 | 
			
		||||
 | 
			
		||||
    def test_stringify_2(self):
 | 
			
		||||
        self.assertEqual({1: u'str,стр2'}, utils.stringify_values({1: ['str', u'стр2']}))
 | 
			
		||||
 | 
			
		||||
    def test_stringify_3(self):
 | 
			
		||||
        self.assertEqual({1: u'стр,стр2'}, utils.stringify_values({1: [u'стр', u'стр2']}))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class VkTestCase(unittest.TestCase):
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        auth_session = vk.AuthSession(app_id=APP_ID, user_login=USER_LOGIN, user_password=USER_PASSWORD)
 | 
			
		||||
        access_token, _ = auth_session.get_access_token()
 | 
			
		||||
 | 
			
		||||
        session = vk.Session(access_token=access_token)
 | 
			
		||||
        self.vk_api = vk.API(session, lang='ru')
 | 
			
		||||
 | 
			
		||||
    def test_get_server_time(self):
 | 
			
		||||
        time_1 = time.time() - 1
 | 
			
		||||
        time_2 = time_1 + 10
 | 
			
		||||
        server_time = self.vk_api.getServerTime()
 | 
			
		||||
        self.assertTrue(time_1 <= server_time <= time_2)
 | 
			
		||||
 | 
			
		||||
    def test_get_server_time_via_token_api(self):
 | 
			
		||||
        time_1 = time.time() - 1
 | 
			
		||||
        time_2 = time_1 + 10
 | 
			
		||||
        server_time = self.vk_api.getServerTime()
 | 
			
		||||
        self.assertTrue(time_1 <= server_time <= time_2)
 | 
			
		||||
 | 
			
		||||
    def test_get_profiles_via_token(self):
 | 
			
		||||
        profiles = self.vk_api.users.get(user_id=1)
 | 
			
		||||
        self.assertEqual(profiles[0]['last_name'], u'Дуров')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    unittest.main()
 | 
			
		||||
@ -1,9 +1,29 @@
 | 
			
		||||
import re
 | 
			
		||||
import logging
 | 
			
		||||
from typing import Iterable
 | 
			
		||||
from collections import Iterable
 | 
			
		||||
 | 
			
		||||
import requests
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
logger = logging.getLogger('vk')
 | 
			
		||||
 | 
			
		||||
STRING_LIKE_TYPES = (str, bytes, bytearray)
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    # Python 2
 | 
			
		||||
    str_type = unicode
 | 
			
		||||
except NameError:
 | 
			
		||||
    # Python 3
 | 
			
		||||
    str_type = str
 | 
			
		||||
 | 
			
		||||
STRING_TYPES = (str_type, bytes, bytearray)
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    # Python 2
 | 
			
		||||
    from urllib import urlencode
 | 
			
		||||
    from urlparse import urlparse, parse_qsl
 | 
			
		||||
except ImportError:
 | 
			
		||||
    # Python 3
 | 
			
		||||
    from urllib.parse import urlparse, parse_qsl, urlencode
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
@ -12,6 +32,14 @@ except ImportError:
 | 
			
		||||
    import json
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    # Python 2
 | 
			
		||||
    raw_input = raw_input
 | 
			
		||||
except NameError:
 | 
			
		||||
    # Python 3
 | 
			
		||||
    raw_input = input
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def json_iter_parse(response_text):
 | 
			
		||||
    decoder = json.JSONDecoder(strict=False)
 | 
			
		||||
    idx = 0
 | 
			
		||||
@ -20,28 +48,32 @@ def json_iter_parse(response_text):
 | 
			
		||||
        yield obj
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def stringify(value):
 | 
			
		||||
    if isinstance(value, Iterable) and not isinstance(value, STRING_LIKE_TYPES):
 | 
			
		||||
        return ','.join(map(str, value))
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def stringify_values(dictionary):
 | 
			
		||||
    return {key: stringify(value) for key, value in dictionary.items()}
 | 
			
		||||
    stringified_values_dict = {}
 | 
			
		||||
    for key, value in dictionary.items():
 | 
			
		||||
        if isinstance(value, Iterable) and not isinstance(value, STRING_TYPES):
 | 
			
		||||
            value = u','.join(map(str_type, value))
 | 
			
		||||
        stringified_values_dict[key] = value
 | 
			
		||||
    return stringified_values_dict
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# class LoggingSession(requests.Session):
 | 
			
		||||
#     def request(self, method, url, **kwargs):
 | 
			
		||||
#         logger.debug('Request: %s %s, params=%r, data=%r', method, url, kwargs.get('params'), kwargs.get('data'))
 | 
			
		||||
#         response = super(LoggingSession, self).request(method, url, **kwargs)
 | 
			
		||||
#         logger.debug('Response: %s %s', response.status_code, response.url)
 | 
			
		||||
#         return response
 | 
			
		||||
def get_url_query(url):
 | 
			
		||||
    parsed_url = urlparse(url)
 | 
			
		||||
    url_query = parse_qsl(parsed_url.fragment)
 | 
			
		||||
    # login_response_url_query can have multiple key
 | 
			
		||||
    url_query = dict(url_query)
 | 
			
		||||
    return url_query
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def censor_access_token(access_token):
 | 
			
		||||
    if isinstance(access_token, str) and len(access_token) >= 12:
 | 
			
		||||
        return '{}***{}'.format(access_token[:4], access_token[-4:])
 | 
			
		||||
    elif access_token:
 | 
			
		||||
        return '***'
 | 
			
		||||
    else:
 | 
			
		||||
        return access_token
 | 
			
		||||
def get_form_action(html):
 | 
			
		||||
    form_action = re.findall(r'<form(?= ).* action="(.+)"', html)
 | 
			
		||||
    if form_action:
 | 
			
		||||
        return form_action[0]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LoggingSession(requests.Session):
 | 
			
		||||
    def request(self, method, url, **kwargs):
 | 
			
		||||
        logger.debug('Request: %s %s, params=%r, data=%r', method, url, kwargs.get('params'), kwargs.get('data'))
 | 
			
		||||
        response = super(LoggingSession, self).request(method, url, **kwargs)
 | 
			
		||||
        logger.debug('Response: %s %s', response.status_code, response.url)
 | 
			
		||||
        return response
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user