Advertisement
stuppid_bot

Untitled

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