Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- from .structures import JsonObject
- import hashlib
- import logging
- import os
- import pickle
- import re
- import requests
- import time
- import urllib.parse
- __author__ = "Sergei Snegirev ([email protected])"
- __copyright__ = "Copyright (C) 2013-2016 Sergei Snegirev"
- __license__ = "MIT"
- __version__ = "3.0"
- __url__ = "https://github.com/***/vkapi/"
- API_DELAY = 0.34
- API_HOST = "api.vk.com"
- API_METHOD = re.compile("[a-z]+([A-Z][a-z]+)*$")
- API_PATH = "/method/"
- API_VERSION = 5.44
- METHOD_GET = 'GET'
- METHOD_POST = 'POST'
- class Client:
- def __init__(self,
- access_token=None,
- access_token_filename=None,
- api_delay=API_DELAY,
- extra_params={},
- api_version=API_VERSION,
- session=None):
- """Клиент для работы с API Вконтакте.
- Способы вызова методов Api:
- Client.api_request('<method_name>', params_dict)
- Client.api.<method_name>(**kwargs)
- Client.<method_name>(params_dict[, **kwargs])
- Пример:
- >> c = Client()
- >> users = c.api.users.get(user_id=100)
- >> print(users[0].first_name)
- Если имя именованно параметра совпадает с ключевым словом, добавляем
- перед ним подчеркивание (_global, _from).
- :param access_token: Токен доступа
- :type access_token: vkapi.access_token.AccessToken instance
- :param access_token_filename: Имя файла где хранится токен
- :type access_token_filename: str
- :param api_delay: Задержка между вызовами методов API
- :type api_delay: int
- :param extra_params: Дополнительные параметры которые будут
- передаваться при каждом запросе к API (помимо токена и версии API,
- которые всегда добавляются).
- Например: {'https': 1, 'lang': 'en'}
- :type extra_params: dict
- :param api_version: Версия API
- :type api_version: float
- :param session: Сессия requests
- :type session: requests.Session instance
- """
- self.logger = logging.getLogger(
- ".".join([self.__class__.__module__, self.__class__.__name__]))
- self.access_token = access_token
- self.access_token_filename = access_token_filename
- self.extra_params = extra_params
- self.api_version = api_version
- self.api_delay = api_delay
- self.session = session or requests.session()
- self.last_api_request = 0
- self.api = Api(self)
- # Пробуем загрузить access_token
- if self.access_token_filename:
- self.load_access_token()
- def request(self,
- method,
- url,
- raise_on_error=False,
- error_class=None,
- **kw):
- start_time = time.time()
- response = self.session.request(method, url, **kw)
- request_time = (time.time() - start_time) * 1000
- self.logger.debug("Total Request Time: %dms", request_time)
- response = response.json(object_hook=JsonObject)
- if raise_on_error:
- self.raise_on_error(response, error_class)
- return response
- def raise_if_error(self, response, error_class):
- if 'error' in response:
- raise (error_class or VkError)(response.error)
- def get(self, url, params=None, **kw):
- return self.request(METHOD_GET, url, params=params, **kw)
- def post(self, url, data=None, **kw):
- return self.request(METHOD_POST, url, data=data, **kw)
- def api_request(self, method, params={}):
- """Делает запрос к API.
- Список методов API: <https://vk.com/dev/methods>
- Подробнее про запросы к API: <https://vk.com/dev/api_requests>
- :param method: Метод API
- :type method: str
- :param params: Передаваемые параметры
- :type params: dict
- :return: Возвращает результат запроса. Это содержимое поля `response`.
- Заметьте, что к элементам словаря можно обращаться как к аттрибутам
- через точечную нотацию.
- """
- request_params = dict(self.extra_params)
- request_params.update(params)
- if self.access_token:
- request_params['access_token'] = self.access_token.token
- # Позволяем в запросе выбрать версию API. Возможно, потребуется для
- # обращения к каким-нибудь depractated методам
- if 'v' not in request_params and self.api_version:
- request_params['v'] = self.api_version
- # path/to/api/ -> /path/to/api/
- path = urllib.parse.urljoin(API_PATH, method)
- # Кстати, через http ответы сервера приходят заметно быстрее
- # <https://vk.com/dev/api_nohttps>
- if hasattr(self.access_token, "secret") and self.access_token.secret:
- scheme = 'http'
- # Исправлен баг с неверной подписью.
- # Нам важно чтобы порядок элементов словаря сохранился.
- # Добавим новый элемент
- request_params['sig'] = ''
- # Сформировали query string из словаря
- query = urllib.parse.urlencode(request_params)
- # А теперь вырежем параметр sig
- query = re.sub('^sig=&|&sig=', '', query)
- uri = '{}?{}{}'.format(path, query, self.access_token.secret)
- sig = hashlib.md5(uri.encode('ascii')).hexdigest()
- request_params["sig"] = sig
- else:
- scheme = 'https'
- # !!! params не должен изменяться после добавления sig
- api_endpoint = "{}://{}{}".format(scheme, API_HOST, path)
- delay = self.api_delay + self.last_api_request - time.time()
- if delay > 0:
- self.logger.debug("Wait %dms", delay * 1000)
- time.sleep(delay)
- self.logger.debug(
- "Call method %r with parameters: %s", method, request_params)
- response = self.post(api_endpoint, request_params)
- self.last_api_request = time.time()
- return self.process_api_response(response, method, params)
- def process_api_response(self, response, method, params):
- """Можно переопределить в классах потомках, а можно что-то типа этого
- сделать:
- def process_api_response(self, response, method, params):
- if 'error' in response:
- error = response.error
- if error.error_code is ApiError.TOO_MANY_REQUESTS_PER_SECOND:
- time.sleep(5)
- # Отправляем повторный запрос
- return self.api_request(method, params)
- if error.error_code is ApiError.CAPTCHA_NEEDED:
- # get captcha_key
- ...
- params.update({
- 'captcha_key': captcha_key,
- 'captcha_sid': error.captcha_sid
- })
- return self.api_request(method, params)
- ...
- return super().process_api_response(response, method, params)
- """
- self.raise_if_error(response, ApiError)
- return response.response
- @property
- def is_valid_access_token(self):
- """Проверяет access_token."""
- # Можно короче:
- # return len(self.api.users.get()) == 1
- # Но он не будет работать с серверными приложениями
- # <https://vk.com/dev/auth_server>
- try:
- return self.api.execute(code="return true;")
- except ApiError:
- return False
- def upload(self, upload_url, files):
- response = self.post(upload_url, files=files, raise_on_error=True)
- return response
- def load_access_token(self, filename=None):
- self.access_token_filename = filename or self.access_token_filename
- if (self.access_token_filename and
- os.path.exists(self.access_token_filename)):
- with open(self.access_token_filename, 'rb') as fp:
- self.access_token = pickle.load(fp)
- self.logger.debug("access token loaded from %s",
- self.access_token_filename)
- def save_access_token(self, filename=None):
- self.access_token_filename = filename or self.access_token_filename
- if self.access_token_filename:
- with open(self.access_token_filename, 'wb') as fp:
- data = pickle.dumps(self.access_token)
- fp.write(data)
- self.logger.debug("access token saved %s",
- self.access_token_filename)
- class VkError(Exception):
- def __init__(self, response):
- super().__init__(response.error)
- class ApiError(VkError):
- """Подробнее про ошибки API можно прочитать по ссылке
- <https://vk.com/dev/errors>
- """
- UNKNOWN_ERROR_OCCURRED = 1
- UNKNOWN_METHOD_PASSED = 3
- INCORRECT_SIGNATURE = 4
- USER_AUTHORIZATION_FAILED = 5
- TOO_MANY_REQUESTS_PER_SECOND = 6
- INVALID_REQUEST = 8
- FLOOD_CONTROL = 9
- INTERNAL_SERVER_ERROR = 10
- CAPTCHA_NEEDED = 14
- ACCESS_DENIED = 15
- HTTP_AUTHORIZATION_FAILED = 16
- VALIDATION_REQUIRED = 17
- THIS_METHOD_WAS_DISABLED = 23
- CONFIRMATION_REQUIRED = 24
- INVALID_APPLICATION_API_ID = 101
- INVALID_USER_ID = 113
- INVALID_TIMESTAMP = 150
- ACCESS_TO_ALBUM_DENIED = 200
- ACCESS_TO_AUDIO_DENIED = 201
- ACCESS_TO_GROUP_DENIED = 203
- THIS_ALBUM_IS_FULL = 300
- SOME_ADS_ERROR_OCCURED = 603
- def __init__(self, data):
- self.error_code = data.error_code
- self.error_msg = data.error_msg
- self.request_params = {
- param.key: param.value for param in data.request_params}
- self.method = self.params.pop('method')
- # self.params.pop('oauth')
- def __str__(self):
- return (
- "Error Code: {}, Message: {!r}. An error occured while calling "
- "method {!r} with parameters: {}"
- ).format(self.error_code, self.error_msg, self.method, self.params)
- class Api:
- def __init__(self, client):
- self._client = client
- def __getattr__(self, name):
- if API_METHOD.match(name):
- return ApiMethod(self._client, name)
- raise AttributeError(name)
- class ApiMethod:
- def __init__(self, client, method):
- self._client = client
- self._method = method
- def __getattr__(self, name):
- if API_METHOD.match(name):
- return ApiMethod(self._client, '.'.join([self._method, name]))
- raise AttributeError(name)
- def __call__(self, *args, **kwargs):
- # Если имя именованного параметра совпадает с ключевым словом, то
- # добавляем перед именем подчеркивание:
- params = {k[1:] if len(k) > 1 and k.startswith('_')
- else k: v for k, v in kwargs.items()}
- if len(args):
- # Первым аргументом является словарь
- d = dict(args[0])
- # Если переданы именованные параметры, то обновляем словарь
- d.update(params)
- params = d
- return self._client.api_request(self._method, params)
Advertisement
Add Comment
Please, Sign In to add comment