diff --git a/script.module.vk/addon.xml b/script.module.vk/addon.xml
new file mode 100644
index 0000000..9aeeca7
--- /dev/null
+++ b/script.module.vk/addon.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+ all
+
+ vk.com API Python wrapper
+ vk.com API Python wrapper
+ Code taken from https://pypi.python.org/pypi/vk/
+ MIT License
+ https://pypi.python.org/pypi/vk/
+
+
+
\ No newline at end of file
diff --git a/script.module.vk/lib/vk/__init__.py b/script.module.vk/lib/vk/__init__.py
new file mode 100644
index 0000000..052fb16
--- /dev/null
+++ b/script.module.vk/lib/vk/__init__.py
@@ -0,0 +1,9 @@
+
+from vk.api import logger
+from vk.api import Session, AuthSession, InteractiveSession, InteractiveAuthSession
+from vk.api import VERSION
+from vk.api import API
+
+__version__ = version = VERSION
+
+# API = OAuthAPI
diff --git a/script.module.vk/lib/vk/__init__.pyc b/script.module.vk/lib/vk/__init__.pyc
new file mode 100644
index 0000000..e903b48
Binary files /dev/null and b/script.module.vk/lib/vk/__init__.pyc differ
diff --git a/script.module.vk/lib/vk/api.py b/script.module.vk/lib/vk/api.py
new file mode 100644
index 0000000..043c443
--- /dev/null
+++ b/script.module.vk/lib/vk/api.py
@@ -0,0 +1,185 @@
+# coding=utf8
+
+import logging
+import logging.config
+
+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 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_name = method_name
+
+ def __getattr__(self, method_name):
+ return Request(self._api, self._method_name + '.' + method_name)
+
+ def __call__(self, **method_args):
+ self._method_args = method_args
+ return self._api._session.make_request(self)
+
+
+class AuthSession(AuthMixin, Session):
+ pass
+
+
+class InteractiveSession(InteractiveMixin, Session):
+ pass
+
+
+class InteractiveAuthSession(InteractiveMixin, AuthSession):
+ pass
diff --git a/script.module.vk/lib/vk/api.pyc b/script.module.vk/lib/vk/api.pyc
new file mode 100644
index 0000000..d57dad0
Binary files /dev/null and b/script.module.vk/lib/vk/api.pyc differ
diff --git a/script.module.vk/lib/vk/exceptions.py b/script.module.vk/lib/vk/exceptions.py
new file mode 100644
index 0000000..55369e6
--- /dev/null
+++ b/script.module.vk/lib/vk/exceptions.py
@@ -0,0 +1,57 @@
+
+# API Error Codes
+AUTHORIZATION_FAILED = 5 # Invalid access token
+PERMISSION_IS_DENIED = 7
+CAPTCHA_IS_NEEDED = 14
+ACCESS_DENIED = 15 # No access to call this method
+ # User deactivated
+INVALID_USER_ID = 113
+
+
+class VkException(Exception):
+ pass
+
+
+class VkAuthError(VkException):
+ pass
+
+
+class VkAPIError(VkException):
+ __slots__ = ['error', 'code', 'message', 'request_params', 'redirect_uri']
+
+ CAPTCHA_NEEDED = 14
+ ACCESS_DENIED = 15
+
+ def __init__(self, error_data):
+ super(VkAPIError, self).__init__()
+ self.error_data = error_data
+ self.code = error_data.get('error_code')
+ self.message = error_data.get('error_msg')
+ self.request_params = self.get_pretty_request_params(error_data)
+ self.redirect_uri = error_data.get('redirect_uri')
+
+ @staticmethod
+ def get_pretty_request_params(error_data):
+ request_params = error_data.get('request_params', ())
+ request_params = {param['key']: param['value'] for param in request_params}
+ return request_params
+
+ def is_access_token_incorrect(self):
+ return self.code == self.ACCESS_DENIED and 'access_token' in self.message
+
+ def is_captcha_needed(self):
+ return self.code == self.CAPTCHA_NEEDED
+
+ @property
+ def captcha_sid(self):
+ return self.error_data.get('captcha_sid')
+
+ @property
+ def captcha_img(self):
+ return self.error_data.get('captcha_img')
+
+ def __str__(self):
+ error_message = '{self.code}. {self.message}. request_params = {self.request_params}'.format(self=self)
+ if self.redirect_uri:
+ error_message += ',\nredirect_uri = "{self.redirect_uri}"'.format(self=self)
+ return error_message
diff --git a/script.module.vk/lib/vk/exceptions.pyc b/script.module.vk/lib/vk/exceptions.pyc
new file mode 100644
index 0000000..fe7e690
Binary files /dev/null and b/script.module.vk/lib/vk/exceptions.pyc differ
diff --git a/script.module.vk/lib/vk/logs.py b/script.module.vk/lib/vk/logs.py
new file mode 100644
index 0000000..2a8cc35
--- /dev/null
+++ b/script.module.vk/lib/vk/logs.py
@@ -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',
+ },
+ },
+}
diff --git a/script.module.vk/lib/vk/logs.pyc b/script.module.vk/lib/vk/logs.pyc
new file mode 100644
index 0000000..6aaeb57
Binary files /dev/null and b/script.module.vk/lib/vk/logs.pyc differ
diff --git a/script.module.vk/lib/vk/mixins.py b/script.module.vk/lib/vk/mixins.py
new file mode 100644
index 0000000..f8c40a4
--- /dev/null
+++ b/script.module.vk/lib/vk/mixins.py
@@ -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'