Advertisement
stuppid_bot

Untitled

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