stuppid_bot

Vk Api for Python 3 module vkapi.client

Jan 19th, 2016
737
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 12.20 KB | None | 0 0
  1. from .structures import JsonObject
  2. import hashlib
  3. import logging
  4. import os
  5. import pickle
  6. import re
  7. import requests
  8. import time
  9. import urllib.parse
  10.  
  11. __author__ = "Sergei Snegirev ([email protected])"
  12. __copyright__ = "Copyright (C) 2013-2016 Sergei Snegirev"
  13. __license__ = "MIT"
  14. __version__ = "3.0"
  15. __url__ = "https://github.com/***/vkapi/"
  16.  
  17. API_DELAY = 0.34
  18. API_HOST = "api.vk.com"
  19. API_METHOD = re.compile("[a-z]+([A-Z][a-z]+)*$")
  20. API_PATH = "/method/"
  21. API_VERSION = 5.44
  22. METHOD_GET = 'GET'
  23. METHOD_POST = 'POST'
  24.  
  25.  
  26. class Client:
  27.     def __init__(self,
  28.                  access_token=None,
  29.                  access_token_filename=None,
  30.                  api_delay=API_DELAY,
  31.                  extra_params={},
  32.                  api_version=API_VERSION,
  33.                  session=None):
  34.         """Клиент для работы с API Вконтакте.
  35.  
  36.        Способы вызова методов Api:
  37.            Client.api_request('<method_name>', params_dict)
  38.            Client.api.<method_name>(**kwargs)
  39.            Client.<method_name>(params_dict[, **kwargs])
  40.  
  41.        Пример:
  42.            >> c = Client()
  43.            >> users = c.api.users.get(user_id=100)
  44.            >> print(users[0].first_name)
  45.  
  46.        Если имя именованно параметра совпадает с ключевым словом, добавляем
  47.        перед ним подчеркивание (_global, _from).
  48.  
  49.        :param access_token: Токен доступа
  50.        :type access_token: vkapi.access_token.AccessToken instance
  51.  
  52.        :param access_token_filename: Имя файла где хранится токен
  53.        :type access_token_filename: str
  54.  
  55.        :param api_delay: Задержка между вызовами методов API
  56.        :type api_delay: int
  57.  
  58.        :param extra_params: Дополнительные параметры которые будут
  59.            передаваться при каждом запросе к API (помимо токена и версии API,
  60.            которые всегда добавляются).
  61.            Например: {'https': 1, 'lang': 'en'}
  62.        :type extra_params: dict
  63.  
  64.        :param api_version: Версия API
  65.        :type api_version: float
  66.  
  67.        :param session: Сессия requests
  68.        :type session: requests.Session instance
  69.        """
  70.         self.logger = logging.getLogger(
  71.             ".".join([self.__class__.__module__, self.__class__.__name__]))
  72.         self.access_token = access_token
  73.         self.access_token_filename = access_token_filename
  74.         self.extra_params = extra_params
  75.         self.api_version = api_version
  76.         self.api_delay = api_delay
  77.         self.session = session or requests.session()
  78.         self.last_api_request = 0
  79.         self.api = Api(self)
  80.         # Пробуем загрузить access_token
  81.         if self.access_token_filename:
  82.             self.load_access_token()
  83.  
  84.     def request(self,
  85.                 method,
  86.                 url,
  87.                 raise_on_error=False,
  88.                 error_class=None,
  89.                 **kw):
  90.         start_time = time.time()
  91.         response = self.session.request(method, url, **kw)
  92.         request_time = (time.time() - start_time) * 1000
  93.         self.logger.debug("Total Request Time: %dms", request_time)
  94.         response = response.json(object_hook=JsonObject)
  95.         if raise_on_error:
  96.             self.raise_on_error(response, error_class)
  97.         return response
  98.  
  99.     def raise_if_error(self, response, error_class):
  100.         if 'error' in response:
  101.             raise (error_class or VkError)(response.error)
  102.  
  103.     def get(self, url, params=None, **kw):
  104.         return self.request(METHOD_GET, url, params=params, **kw)
  105.  
  106.     def post(self, url, data=None, **kw):
  107.         return self.request(METHOD_POST, url, data=data, **kw)
  108.  
  109.     def api_request(self, method, params={}):
  110.         """Делает запрос к API.
  111.  
  112.        Список методов API: <https://vk.com/dev/methods>
  113.        Подробнее про запросы к API: <https://vk.com/dev/api_requests>
  114.  
  115.        :param method: Метод API
  116.        :type method: str
  117.  
  118.        :param params: Передаваемые параметры
  119.        :type params: dict
  120.  
  121.        :return: Возвращает результат запроса. Это содержимое поля `response`.
  122.            Заметьте, что к элементам словаря можно обращаться как к аттрибутам
  123.            через точечную нотацию.
  124.        """
  125.         request_params = dict(self.extra_params)
  126.         request_params.update(params)
  127.         if self.access_token:
  128.             request_params['access_token'] = self.access_token.token
  129.         # Позволяем в запросе выбрать версию API. Возможно, потребуется для
  130.         # обращения к каким-нибудь depractated методам
  131.         if 'v' not in request_params and self.api_version:
  132.             request_params['v'] = self.api_version
  133.         # path/to/api/ -> /path/to/api/
  134.         path = urllib.parse.urljoin(API_PATH, method)
  135.         # Кстати, через http ответы сервера приходят заметно быстрее
  136.         # <https://vk.com/dev/api_nohttps>
  137.         if hasattr(self.access_token, "secret") and self.access_token.secret:
  138.             scheme = 'http'
  139.             # Исправлен баг с неверной подписью.
  140.             # Нам важно чтобы порядок элементов словаря сохранился.
  141.             # Добавим новый элемент
  142.             request_params['sig'] = ''
  143.             # Сформировали query string из словаря
  144.             query = urllib.parse.urlencode(request_params)
  145.             # А теперь вырежем параметр sig
  146.             query = re.sub('^sig=&|&sig=', '', query)
  147.             uri = '{}?{}{}'.format(path, query, self.access_token.secret)
  148.             sig = hashlib.md5(uri.encode('ascii')).hexdigest()
  149.             request_params["sig"] = sig
  150.         else:
  151.             scheme = 'https'
  152.         # !!! params не должен изменяться после добавления sig
  153.         api_endpoint = "{}://{}{}".format(scheme, API_HOST, path)
  154.         delay = self.api_delay + self.last_api_request - time.time()
  155.         if delay > 0:
  156.             self.logger.debug("Wait %dms", delay * 1000)
  157.             time.sleep(delay)
  158.         self.logger.debug(
  159.             "Call method %r with parameters: %s", method, request_params)
  160.         response = self.post(api_endpoint, request_params)
  161.         self.last_api_request = time.time()
  162.         return self.process_api_response(response, method, params)
  163.  
  164.     def process_api_response(self, response, method, params):
  165.         """Можно переопределить в классах потомках, а можно что-то типа этого
  166.        сделать:
  167.  
  168.        def process_api_response(self, response, method, params):
  169.            if 'error' in response:
  170.                error = response.error
  171.                if error.error_code is ApiError.TOO_MANY_REQUESTS_PER_SECOND:
  172.                    time.sleep(5)
  173.                    # Отправляем повторный запрос
  174.                    return self.api_request(method, params)
  175.                if error.error_code is ApiError.CAPTCHA_NEEDED:
  176.                    # get captcha_key
  177.                    ...
  178.                    params.update({
  179.                        'captcha_key': captcha_key,
  180.                        'captcha_sid': error.captcha_sid
  181.                    })
  182.                    return self.api_request(method, params)
  183.                ...
  184.            return super().process_api_response(response, method, params)
  185.        """
  186.         self.raise_if_error(response, ApiError)
  187.         return response.response
  188.  
  189.     @property
  190.     def is_valid_access_token(self):
  191.         """Проверяет access_token."""
  192.         # Можно короче:
  193.         # return len(self.api.users.get()) == 1
  194.         # Но он не будет работать с серверными приложениями
  195.         # <https://vk.com/dev/auth_server>
  196.         try:
  197.             return self.api.execute(code="return true;")
  198.         except ApiError:
  199.             return False
  200.  
  201.     def upload(self, upload_url, files):
  202.         response = self.post(upload_url, files=files, raise_on_error=True)
  203.         return response
  204.  
  205.     def load_access_token(self, filename=None):
  206.         self.access_token_filename = filename or self.access_token_filename
  207.         if (self.access_token_filename and
  208.                 os.path.exists(self.access_token_filename)):
  209.             with open(self.access_token_filename, 'rb') as fp:
  210.                 self.access_token = pickle.load(fp)
  211.             self.logger.debug("access token loaded from %s",
  212.                               self.access_token_filename)
  213.  
  214.     def save_access_token(self, filename=None):
  215.         self.access_token_filename = filename or self.access_token_filename
  216.         if self.access_token_filename:
  217.             with open(self.access_token_filename, 'wb') as fp:
  218.                 data = pickle.dumps(self.access_token)
  219.                 fp.write(data)
  220.             self.logger.debug("access token saved %s",
  221.                               self.access_token_filename)
  222.  
  223.  
  224. class VkError(Exception):
  225.     def __init__(self, response):
  226.         super().__init__(response.error)
  227.  
  228.  
  229. class ApiError(VkError):
  230.     """Подробнее про ошибки API можно прочитать по ссылке
  231.    <https://vk.com/dev/errors>
  232.    """
  233.     UNKNOWN_ERROR_OCCURRED = 1
  234.     UNKNOWN_METHOD_PASSED = 3
  235.     INCORRECT_SIGNATURE = 4
  236.     USER_AUTHORIZATION_FAILED = 5
  237.     TOO_MANY_REQUESTS_PER_SECOND = 6
  238.     INVALID_REQUEST = 8
  239.     FLOOD_CONTROL = 9
  240.     INTERNAL_SERVER_ERROR = 10
  241.     CAPTCHA_NEEDED = 14
  242.     ACCESS_DENIED = 15
  243.     HTTP_AUTHORIZATION_FAILED = 16
  244.     VALIDATION_REQUIRED = 17
  245.     THIS_METHOD_WAS_DISABLED = 23
  246.     CONFIRMATION_REQUIRED = 24
  247.     INVALID_APPLICATION_API_ID = 101
  248.     INVALID_USER_ID = 113
  249.     INVALID_TIMESTAMP = 150
  250.     ACCESS_TO_ALBUM_DENIED = 200
  251.     ACCESS_TO_AUDIO_DENIED = 201
  252.     ACCESS_TO_GROUP_DENIED = 203
  253.     THIS_ALBUM_IS_FULL = 300
  254.     SOME_ADS_ERROR_OCCURED = 603
  255.  
  256.     def __init__(self, data):
  257.         self.error_code = data.error_code
  258.         self.error_msg = data.error_msg
  259.         self.request_params = {
  260.             param.key: param.value for param in data.request_params}
  261.         self.method = self.params.pop('method')
  262.         # self.params.pop('oauth')
  263.  
  264.     def __str__(self):
  265.         return (
  266.             "Error Code: {}, Message: {!r}. An error occured while calling "
  267.             "method {!r} with parameters: {}"
  268.         ).format(self.error_code, self.error_msg, self.method, self.params)
  269.  
  270.  
  271. class Api:
  272.     def __init__(self, client):
  273.         self._client = client
  274.  
  275.     def __getattr__(self, name):
  276.         if API_METHOD.match(name):
  277.             return ApiMethod(self._client, name)
  278.         raise AttributeError(name)
  279.  
  280.  
  281. class ApiMethod:
  282.     def __init__(self, client, method):
  283.         self._client = client
  284.         self._method = method
  285.  
  286.     def __getattr__(self, name):
  287.         if API_METHOD.match(name):
  288.             return ApiMethod(self._client, '.'.join([self._method, name]))
  289.         raise AttributeError(name)
  290.  
  291.     def __call__(self, *args, **kwargs):
  292.         # Если имя именованного параметра совпадает с ключевым словом, то
  293.         # добавляем перед именем подчеркивание:
  294.         params = {k[1:] if len(k) > 1 and k.startswith('_')
  295.                   else k: v for k, v in kwargs.items()}
  296.         if len(args):
  297.             # Первым аргументом является словарь
  298.             d = dict(args[0])
  299.             # Если переданы именованные параметры, то обновляем словарь
  300.             d.update(params)
  301.             params = d
  302.         return self._client.api_request(self._method, params)
Advertisement
Add Comment
Please, Sign In to add comment