Advertisement
stuppid_bot

Untitled

Jan 27th, 2016
192
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 14.46 KB | None | 0 0
  1. from . import defaults
  2. from .browser import Browser
  3. from .structures import AttrDict
  4. from .ui_captcha import Ui_Captcha
  5. from .utils import parse_hash
  6. from PyQt5.QtGui import QImage, QPixmap
  7. from PyQt5.QtWidgets import QApplication, QDialog
  8. import hashlib
  9. import json
  10. import logging
  11. import os
  12. import re
  13. import requests
  14. import sys
  15. import time
  16. import urllib.parse
  17.  
  18. __author__ = "Sergei Snegirev (tz4678@gmail.com)"
  19. __copyright__ = "Copyright (C) 2013-2016 Sergei Snegirev"
  20. __license__ = "MIT"
  21. __version__ = "3.0"
  22. __url__ = "github.com/tz4678/vkapi/"
  23.  
  24.  
  25. API_METHOD_REGEXP = re.compile("[a-z]+([A-Z][a-z]+)*$")
  26.  
  27.  
  28. class Client:
  29.     """Клиент для работы с Api Вконтакте.
  30.  
  31.    Способы вызова методов Api:
  32.        Client.api.<method_name>(**kwargs)
  33.        Client.api.<method_name>(params_dict, **kwargs)
  34.        Client.api_request('<method_name>', params_dict)
  35.  
  36.    Usage::
  37.        >> from vkapi import Client
  38.        >> vk = Client()
  39.        >> users = vk.api.users.get(user_id=100)
  40.        >> print(users[0].first_name)
  41.  
  42.    Если имя именованно параметра совпадает с ключевым словом, добавляем
  43.    к нему подчеркивание (global_, from_).
  44.    """
  45.     SAVED_ATTRS = ['access_token', 'user_id', 'secret_token', 'token_expiry']
  46.  
  47.     def __init__(self,
  48.                  session_filename=None,
  49.                  access_token=None,
  50.                  user_id=None,
  51.                  token_expiry=None,
  52.                  secret_token=None,
  53.                  api_params=None,
  54.                  api_delay=None,
  55.                  api_version=None,
  56.                  http=None):
  57.         """Конструктор.
  58.  
  59.        :param session_filename: Имя файла куда будут сохранены данные от
  60.            токена
  61.        :type session_filename: str
  62.        :param access_token: Токен доступа
  63.        :type access_token: str
  64.        :param user_id: ID пользователя
  65.        :type user_id: int
  66.        :param token_expiry: Время истечения срока действия токена в формате
  67.            timestamp
  68.        :type token_expiry: int
  69.        :param secret_token: Секретный токен
  70.        :type secret_token: str
  71.        :param api_delay: Задержка между вызовами методов Api
  72.        :type api_delay: int
  73.        :param api_version: Версия Api
  74.        :type api_version: float
  75.        :param api_params: Дополнительные параметры, которые будут
  76.            передаваться при каждом запросе (помимо токена и версии Api).
  77.            Например: {'https': 1, 'lang': 'en'}
  78.        :type api_params: dict
  79.        :param http: Сессия requests
  80.        :type http: requests.Session instance
  81.        """
  82.         self.logger = logging.getLogger('.'.join([
  83.             self.__class__.__module__, self.__class__.__name__]))
  84.         self.session_filename = session_filename
  85.         self.access_token = access_token
  86.         self.user_id = user_id
  87.         self.token_expiry = token_expiry
  88.         self.secret_token = secret_token
  89.         self.api_params = api_params or {}
  90.         self.api_delay = api_delay or defaults.API_DELAY
  91.         self.api_version = api_version or defaults.API_VERSION
  92.         if not http:
  93.             http = requests.session()
  94.             # Mozilla/5.0 (compatible; vkapi.client/3.0; Python/3.4.3;
  95.             # +github.com/tz4678/vkapi/)
  96.             http.headers['User-Agent'] = defaults.USER_AGENT_FORMAT.format(
  97.                 __name__, __version__, sys.version.split(' ')[0], __url__)
  98.         self.http = http
  99.         self.last_api_request = 0
  100.         # Сахар над вызовом api_request
  101.         self.api = Api(self)
  102.         self.load_session()
  103.         self.qapp = QApplication.instance() or QApplication(sys.argv)
  104.  
  105.     def request(self, method, url, **kwargs):
  106.         start_time = time.time()
  107.         response = self.http.request(method, url, **kwargs)
  108.         request_time = (time.time() - start_time) * 1000
  109.         self.logger.debug("Total Request Time: %dms", request_time)
  110.         return response.json(object_hook=AttrDict)
  111.         # try:
  112.         #     return response.json(object_hook=AttrDict)
  113.         # else:
  114.         #     return response.text
  115.  
  116.     def get(self, url, params=None, **kwargs):
  117.         return self.request('GET', url, params=params, **kwargs)
  118.  
  119.     def post(self, url, data=None, **kwargs):
  120.         return self.request('POST', url, data=data, **kwargs)
  121.  
  122.     def api_request(self, method, params={}):
  123.         """Делает запрос к Api.
  124.  
  125.        Список методов Api: <https://vk.com/dev/methods>
  126.        Подробнее про запросы к Api: <https://vk.com/dev/api_requests>
  127.  
  128.        :param method: Метод Api
  129.        :type method: str
  130.        :param params: Передаваемые параметры
  131.        :type params: dict
  132.        :return: Возвращает содержимое поля `response`. Заметьте, что к
  133.            элементам словаря можно обращаться как к аттрибутам через точечную
  134.            нотацию. Вместо обычного словаря используется
  135.            :class:``vkapi.structures.AttrDict``.
  136.        """
  137.         q = dict(self.api_params)
  138.         q.update(params)
  139.         params = q
  140.         params['v'] = self.api_version
  141.         if self.access_token:
  142.             params['access_token'] = self.access_token
  143.         # >>> re.sub('^(/|)|(/|)$', '/', 'foo')
  144.         # /foo/
  145.         path = re.sub('^(/|)|(/|)$', '/', defaults.API_PATH)
  146.         path = urllib.parse.urljoin(path, method)
  147.         # <https://vk.com/dev/api_nohttps>
  148.         if self.secret_token:
  149.             # Исправлен баг с неверной подписью. Дело в том, что словари в
  150.             # python неупорядоченные. Новый элемент может быть добавлен как в
  151.             # конец так и любое другое место, да и еще элементы могут
  152.             # поменяться местами. И в итоге имеем, что:
  153.             # md5('foo=bar&baz=qux') != md5('baz=qux&foo=bar')
  154.             # Чтобы закрепить порядок элементов, добавим sig
  155.             params['sig'] = ''
  156.             # Сформировали Query String из словаря
  157.             query = urllib.parse.urlencode(params)
  158.             # А теперь вырежем параметр sig
  159.             query = re.sub('^sig=&|&sig=', '', query)
  160.             uri = '{}?{}{}'.format(path, query, self.secret_token)
  161.             sig = hashlib.md5(uri.encode('ascii')).hexdigest()
  162.             params["sig"] = sig
  163.             scheme = 'http'
  164.         else:
  165.             scheme = 'https'
  166.         # !!! params не должен изменяться после добавления sig
  167.         api_endpoint = "{}://{}{}".format(scheme, defaults.API_HOST, path)
  168.         delay = self.api_delay + self.last_api_request - time.time()
  169.         if delay > 0:
  170.             self.logger.debug("Wait %dms", delay * 1000)
  171.             time.sleep(delay)
  172.         self.logger.debug(
  173.             "Calling Api method %r with parameters: %s", method, params)
  174.         response = self.post(api_endpoint, params)
  175.         self.last_api_request = time.time()
  176.         error = response.get('error')
  177.         if error:
  178.             if 'captcha_img' in error:
  179.                 return self.handle_captcha(
  180.                     error.captcha_img,
  181.                     error.captcha_sid,
  182.                     method,
  183.                     params
  184.                 )
  185.             if 'redirect_uri' in error:
  186.                 return self.handle_validation(
  187.                     error.redirect_uri,
  188.                     method,
  189.                     params
  190.                 )
  191.             # Кастомные ошибки
  192.             error = ApiError(error)
  193.             return self.handle_error(error, method, params)
  194.         return response.response
  195.  
  196.     # Обработчики ошибок
  197.  
  198.     def handle_captcha(self, captcha_img, captcha_sid, method, params):
  199.         c = Captcha(self, captcha_img)
  200.         if c.exec_():
  201.             params['captcha_sid'] = captcha_sid
  202.             params['captcha_key'] = c.ui.captcha_line.text()
  203.             return self.api_request(method, params)
  204.         raise ClientError("Action canceled by user")
  205.  
  206.     def handle_validation(self, redirect_uri, method, params):
  207.         if not Validation(self, redirect_uri).exec_():  # raises ClientError
  208.             raise ClientError("Action canceled by user")
  209.         self.save_session()
  210.         return self.api_request(method, params)
  211.  
  212.     def handle_error(self, error, method, params):
  213.         """Обработчик всех ошибок кроме капчи и валидации"""
  214.         # Для переопределения в классах потомках.
  215.         # if error.code == vkapi.errors.TOO_MANY_REQUESTS_PER_SECOND:
  216.         #     time.sleep(5)
  217.         #     return self.api_request(method, params)
  218.         raise error
  219.  
  220.     # Загрузка файлов
  221.  
  222.     def upload(self, upload_url, files):
  223.         response = self.post(upload_url, files=files)
  224.         if 'error' in response:
  225.             raise ClientError(response.error)
  226.         return response
  227.  
  228.     # Работа с токеном
  229.  
  230.     @property
  231.     def test_token(self):
  232.         """Проверяет access_token."""
  233.         # Можно короче:
  234.         # return self.api.users.get() == []
  235.         # Но этот способ не будет работать с серверными приложениями
  236.         try:
  237.             return self.api.execute(code="return true;")
  238.         except ApiError:
  239.             return False
  240.  
  241.     @property
  242.     def token_expired(self):
  243.         if self.access_token:
  244.             # Токен может выдаваться на неопределенный срок и тогда
  245.             # token_expiry равно None либо 0
  246.             if self.token_expiry:
  247.                 return time.time() > self.token_expiry
  248.         return False
  249.  
  250.     # Сессия
  251.  
  252.     def load_session(self):
  253.         if self.session_filename and os.path.exists(self.session_filename):
  254.             with open(self.session_filename, encoding='utf-8') as fp:
  255.                 dct = json.load(fp)
  256.                 for attr in self.SAVED_ATTRS:
  257.                     setattr(self, attr, dct.get(attr))
  258.             self.logger.debug(
  259.                 "Session loaded %s", os.path.realpath(self.session_filename))
  260.  
  261.     def save_session(self):
  262.         with open(self.session_filename, 'w', encoding='utf-8') as fp:
  263.             dct = {k: v for k, v in self.__dict__.items()
  264.                    if k in self.SAVED_ATTRS and v is not None}
  265.             json.dump(dct, fp, ensure_ascii=False, indent=4, sort_keys=True)
  266.         self.logger.debug(
  267.             "Session saved %s", os.path.realpath(self.session_filename))
  268.  
  269.     def delete_session(self):
  270.         os.remove(self.session_filename)
  271.         self.logger.debug(
  272.             "Session deleted %s", os.path.realpath(self.session_filename))
  273.  
  274.     # Разное
  275.  
  276.     def from_dict(self, data):
  277.         if 'access_token' not in data:
  278.             raise TypeError("missing access_token")
  279.         self.access_token = data['access_token']
  280.         self.user_id = data.get('user_id')
  281.         self.token_expiry = data.get('expires_in')
  282.         if not isinstance(self.token_expiry, (type(None), int)):
  283.             self.token_expiry = int(self.token_expiry)
  284.         if self.token_expiry:
  285.             self.token_expiry += time.time()
  286.         if not isinstance(self.user_id, int):
  287.             self.user_id = int(self.user_id)
  288.         self.secret_token = data.get('secret')
  289.  
  290.  
  291. class Captcha(QDialog):
  292.     def __init__(self, client, captcha_img):
  293.         self.client = client
  294.         self.captcha_img = captcha_img
  295.         super().__init__()
  296.         self.ui = Ui_Captcha()
  297.         self.ui.setupUi(self)
  298.         self.ui.refresh_captcha_button.clicked.connect(self.load_captcha)
  299.  
  300.     def load_captcha(self):
  301.         data = self.client.http.get(self.captcha_img).content
  302.         pix = QPixmap.fromImage(QImage.fromData(data))
  303.         self.ui.captcha_image.setPixmap(pix)
  304.         self.ui.captcha_line.clear()
  305.         self.ui.captcha_line.setFocus()
  306.  
  307.  
  308. class Validation(Browser):
  309.     def __init__(self, client, url):
  310.         self.client = client
  311.         super().__init__(url)
  312.  
  313.     def on_url_changed(self, url):
  314.         self.logger.debug("URL changed: %s", str(url))
  315.         if not str(url).startswith(defaults.REDIRECT_URI):
  316.             return
  317.         result = parse_hash(url.fragment())
  318.         if 'error' in result:
  319.             self.reject()
  320.             raise ClientError(result['error_description'])
  321.         self.client.from_dict(result)
  322.         self.accept()
  323.         self.logger.info('Validation successful')
  324.  
  325.  
  326. class Api:
  327.     def __init__(self, client, method=None):
  328.         self._client = client
  329.         self._method = method
  330.  
  331.     def __getattr__(self, name):
  332.         if API_METHOD_REGEXP.match(name):
  333.             method = name if not self._method \
  334.                              else '.'.join([self._method, name])
  335.             return Api(self._client, method)
  336.         raise AttributeError(name)
  337.  
  338.     def __call__(self, *args, **kwargs):
  339.         # Если имя именованного параметра совпадает с ключевым словом, то
  340.         # добавляем подчеркивание (from_, global_)
  341.         params = {k[:-1] if len(k) > 1 and k.endswith('_')
  342.                   else k: v for k, v in kwargs.items()}
  343.         if len(args):
  344.             # Первым аргументом является словарь
  345.             d = dict(args[0])
  346.             # Если переданы именованные параметры, то обновляем словарь
  347.             d.update(params)
  348.             params = d
  349.         return self._client.api_request(self._method, params)
  350.  
  351.  
  352. class ClientError(Exception):
  353.     pass
  354.  
  355.  
  356. class ApiError(ClientError):
  357.     def __init__(self, data):
  358.         self.code = data.error_code
  359.         self.msg = data.error_msg
  360.  
  361.     def __str__(self):
  362.         return "{}: {}".format(self.code, self.msg)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement