Advertisement
stuppid_bot

Untitled

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