#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # File: unicorn_binance_websocket_api/unicorn_binance_websocket_api_restclient.py # # Part of ‘UNICORN Binance WebSocket API’ # Project website: https://github.com/oliver-zehentleitner/unicorn-binance-websocket-api # Documentation: https://oliver-zehentleitner.github.io/unicorn-binance-websocket-api # PyPI: https://pypi.org/project/unicorn-binance-websocket-api/ # # Author: Oliver Zehentleitner # https://about.me/oliver-zehentleitner # # Copyright (c) 2019-2020, Oliver Zehentleitner # All rights reserved. # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, dis- # tribute, sublicense, and/or sell copies of the Software, and to permit # persons to whom the Software is furnished to do so, subject to the fol- # lowing conditions: # # The above copyright notice and this permission notice shall be included # in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. import copy import hashlib import hmac import json import logging import requests import socket import time class BinanceWebSocketApiRestclient(object): def __init__(self, exchange, binance_api_key, binance_api_secret, unicorn_binance_websocket_api_version, binance_api_status): self.exchange = exchange self.api_key = copy.deepcopy(binance_api_key) self.api_secret = copy.deepcopy(binance_api_secret) self.unicorn_binance_websocket_api_version = unicorn_binance_websocket_api_version if self.exchange == "binance.com": self.restful_base_uri = "https://api.binance.com/" self.path_userdata = "api/v3/userDataStream" elif self.exchange == "binance.com-margin": self.restful_base_uri = "https://api.binance.com/" self.path_userdata = "sapi/v1/userDataStream" elif self.exchange == "binance.com-futures": self.restful_base_uri = "https://fapi.binance.com/" self.path_userdata = "fapi/v1/listenKey" elif self.exchange == "binance.je": self.restful_base_uri = "https://api.binance.je/" self.path_userdata = "api/v1/userDataStream" elif self.exchange == "binance.us": self.restful_base_uri = "https://api.binance.us/" self.path_userdata = "api/v1/userDataStream" elif self.exchange == "jex.com": self.restful_base_uri = "https://www.jex.com/" self.path_userdata = "api/v1/userDataStream" self.listen_key = False self.binance_api_status = binance_api_status def _get_signature(self, data): """ Get the signature of 'data' :param data: the data you want to sign :type data: str :return: signature :rtype: str """ hmac_signature = hmac.new(self.api_secret.encode('utf-8'), data.encode('utf-8'), hashlib.sha256) return hmac_signature.hexdigest() def _request(self, method, path, query=False, data=False): """ Do the request :param method: choose the method to use (post, put or delete) :type method: str :param path: choose the path to use :type path: str :param query: choose the query to use :type query: str :param data: the payload for the post method :type data: str :return: the response :rtype: str or False """ requests_headers = {'Accept': 'application/json', 'User-Agent': 'oliver-zehentleitner/unicorn-binance-websocket-api/' + self.unicorn_binance_websocket_api_version, 'X-MBX-APIKEY': str(self.api_key)} if query is not False: uri = self.restful_base_uri + path + "?" + query else: uri = self.restful_base_uri + path try: if method == "post": request_handler = requests.post(uri, headers=requests_headers) elif method == "put": request_handler = requests.put(uri, headers=requests_headers, data=data) elif method == "delete": request_handler = requests.delete(uri, headers=requests_headers) else: request_handler = False except requests.exceptions.ConnectionError as error_msg: logging.critical("BinanceWebSocketApiRestclient->_request() - error_msg: " + str(error_msg)) return False except socket.gaierror as error_msg: logging.critical("BinanceWebSocketApiRestclient->_request() - error_msg: " + str(error_msg)) return False if request_handler.status_code == "418": logging.critical("BinanceWebSocketApiRestclient->_request() received status_code 418 from binance! You got" "banned from the binance api! Read this: https://github.com/binance-exchange/binance-" "official-api-sphinx/blob/master/rest-api.md#limits") elif request_handler.status_code == "429": logging.critical("BinanceWebSocketApiRestclient->_request() received status_code 429 from binance! Back off" "or you are going to get banned! Read this: https://github.com/binance-exchange/binance-" "official-api-sphinx/blob/master/rest-api.md#limits") try: respond = request_handler.json() except json.decoder.JSONDecodeError as error_msg: logging.error("BinanceWebSocketApiRestclient->_request() - error_msg: " + str(error_msg)) return False self.binance_api_status['weight'] = request_handler.headers.get('X-MBX-USED-WEIGHT') self.binance_api_status['timestamp'] = time.time() self.binance_api_status['status_code'] = request_handler.status_code request_handler.close() return respond def delete_listen_key(self, listen_key): """ Delete a specific listen key :param listen_key: the listenkey you want to delete :type listen_key: str :return: the response :rtype: str or False """ logging.debug("BinanceWebSocketApiRestclient->delete_listen_key(" + str(listen_key) + ")") method = "delete" try: return self._request(method, self.path_userdata, False, {'listenKey': str(listen_key)}) except KeyError: return False except TypeError: return False def get_listen_key(self): """ Request a valid listen_key from binance :return: listen_key :rtype: str or False """ logging.debug("BinanceWebSocketApiRestclient->get_listen_key()") method = "post" response = self._request(method, self.path_userdata) try: self.listen_key = response['listenKey'] return response except KeyError: return response except TypeError: return False def keepalive_listen_key(self, listen_key): """ Ping a listenkey to keep it alive :param listen_key: the listenkey you want to keepalive :type listen_key: str :return: the response :rtype: str or False """ logging.debug("BinanceWebSocketApiRestclient->keepalive_listen_key(" + str(listen_key) + ")") method = "put" try: return self._request(method, self.path_userdata, False, {'listenKey': str(listen_key)}) except KeyError: return False except TypeError: return False