texchange rates: enforce https APIs. store exchanges list in json file. - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit 13b16e9d4f0a0795f6c8ace22d9db3d2c3c05214 DIR parent fa26ac7e681d3bee7e64c7d018c5a8a38468c126 HTML Author: ThomasV <thomasv@electrum.org> Date: Mon, 23 Jan 2017 14:56:49 +0100 exchange rates: enforce https APIs. store exchanges list in json file. Diffstat: M gui/kivy/uix/dialogs/fx_dialog.py | 4 ++-- M gui/qt/main_window.py | 7 ++++--- M lib/exchange_rate.py | 104 ++++++++++++++++--------------- 3 files changed, 59 insertions(+), 56 deletions(-) --- DIR diff --git a/gui/kivy/uix/dialogs/fx_dialog.py b/gui/kivy/uix/dialogs/fx_dialog.py t@@ -94,7 +94,7 @@ class FxDialog(Factory.Popup): Clock.schedule_once(lambda dt: self.add_currencies()) def add_exchanges(self): - exchanges = sorted(self.fx.exchanges_by_ccy.get(self.fx.get_currency())) if self.fx.is_enabled() else [] + exchanges = sorted(self.fx.get_exchanges_by_ccy(self.fx.get_currency(), True)) if self.fx.is_enabled() else [] mx = self.fx.exchange.name() if self.fx.is_enabled() else '' ex = self.ids.exchanges ex.values = exchanges t@@ -107,7 +107,7 @@ class FxDialog(Factory.Popup): self.fx.set_exchange(text) def add_currencies(self): - currencies = sorted(self.fx.exchanges_by_ccy.keys()) if self.fx else [] + currencies = sorted(self.fx.get_currencies()) if self.fx else [] my_ccy = self.fx.get_currency() if self.fx.is_enabled() else '' self.ids.ccy.values = currencies self.ids.ccy.text = my_ccy DIR diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py t@@ -2578,7 +2578,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): ex_combo = QComboBox() def update_currencies(): - currencies = sorted(self.fx.exchanges_by_ccy.keys()) + currencies = sorted(self.fx.get_currencies()) ccy_combo.clear() ccy_combo.addItems([_('None')] + currencies) if self.fx.is_enabled(): t@@ -2596,7 +2596,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): c = self.fx.get_currency() exchanges = self.fx.get_exchanges_by_ccy(c, h) else: - exchanges = self.fx.exchanges.keys() + exchanges = self.fx.get_exchanges_by_ccy('USD', False) ex_combo.clear() ex_combo.addItems(sorted(exchanges)) ex_combo.setCurrentIndex(ex_combo.findText(self.fx.config_exchange())) t@@ -2613,11 +2613,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): def on_exchange(idx): exchange = str(ex_combo.currentText()) - if self.fx.is_enabled() and exchange != self.fx.exchange.name(): + if self.fx.is_enabled() and exchange and exchange != self.fx.exchange.name(): self.fx.set_exchange(exchange) def on_history(checked): self.fx.set_history_config(checked) + update_exchanges() self.history_list.refresh_headers() if self.fx.is_enabled() and checked: # reset timeout to get historical rates DIR diff --git a/lib/exchange_rate.py b/lib/exchange_rate.py t@@ -30,19 +30,15 @@ class ExchangeBase(PrintError): self.on_quotes = on_quotes self.on_history = on_history - def protocol(self): - return "https" - def get_json(self, site, get_string): - url = "".join([self.protocol(), '://', site, get_string]) - response = requests.request('GET', url, - headers={'User-Agent' : 'Electrum'}) + # APIs must have https + url = ''.join(['https://', site, get_string]) + response = requests.request('GET', url, headers={'User-Agent' : 'Electrum'}) return response.json() def get_csv(self, site, get_string): - url = "".join([self.protocol(), '://', site, get_string]) - response = requests.request('GET', url, - headers={'User-Agent' : 'Electrum'}) + url = ''.join(['https://', site, get_string]) + response = requests.request('GET', url, headers={'User-Agent' : 'Electrum'}) reader = csv.DictReader(response.content.split('\n')) return list(reader) t@@ -86,6 +82,10 @@ class ExchangeBase(PrintError): def historical_rate(self, ccy, d_t): return self.history.get(ccy, {}).get(d_t.strftime('%Y-%m-%d')) + def get_currencies(self): + rates = self.get_rates('') + return [str(a) for (a, b) in rates.iteritems() if b is not None] + class BitcoinAverage(ExchangeBase): def get_rates(self, ccy): t@@ -105,15 +105,13 @@ class BitcoinAverage(ExchangeBase): for h in history]) class BitcoinVenezuela(ExchangeBase): + def get_rates(self, ccy): json = self.get_json('api.bitcoinvenezuela.com', '/') rates = [(r, json['BTC'][r]) for r in json['BTC'] if json['BTC'][r] is not None] # Giving NULL for LTC return dict(rates) - def protocol(self): - return "http" - def history_ccys(self): return ['ARS', 'EUR', 'USD', 'VEF'] t@@ -122,26 +120,17 @@ class BitcoinVenezuela(ExchangeBase): "/historical/index.php?coin=BTC")[ccy +'_BTC'] class BTCParalelo(ExchangeBase): + def get_rates(self, ccy): json = self.get_json('btcparalelo.com', '/api/price') return {'VEF': Decimal(json['price'])} - def protocol(self): - return "http" class Bitso(ExchangeBase): def get_rates(self, ccy): json = self.get_json('api.bitso.com', '/v2/ticker') return {'MXN': Decimal(json['last'])} - def protocol(self): - return "http" - -class Bitcurex(ExchangeBase): - def get_rates(self, ccy): - json = self.get_json('pln.bitcurex.com', '/data/ticker.json') - pln_price = json['last'] - return {'PLN': Decimal(pln_price)} class Bitmarket(ExchangeBase): def get_rates(self, ccy): t@@ -254,17 +243,13 @@ class Winkdex(ExchangeBase): for h in history]) class MercadoBitcoin(ExchangeBase): - def get_rates(self,ccy): - json = requests.get('http://api.bitvalor.com/v1/ticker.json').json() + def get_rates(self, ccy): + json = self.get_json('api.bitvalor.com', '/v1/ticker.json') return {'BRL': Decimal(json['ticker_1h']['exchanges']['MBT']['last'])} - - def history_ccys(self): - return ['BRL'] class Bitcointoyou(ExchangeBase): - def get_rates(self,ccy): - json = self.get_json('bitcointoyou.com', - "/API/ticker.aspx") + def get_rates(self, ccy): + json = self.get_json('bitcointoyou.com', "/API/ticker.aspx") return {'BRL': Decimal(json['ticker']['last'])} def history_ccys(self): t@@ -272,24 +257,18 @@ class Bitcointoyou(ExchangeBase): class Bitvalor(ExchangeBase): def get_rates(self,ccy): - json = requests.get('http://api.bitvalor.com/v1/ticker.json').json() + json = self.get_json('api.bitvalor.com', '/v1/ticker.json') return {'BRL': Decimal(json['ticker_1h']['total']['last'])} - def history_ccys(self): - return ['BRL'] - class Foxbit(ExchangeBase): def get_rates(self,ccy): - json = requests.get('http://api.bitvalor.com/v1/ticker.json').json() + json = self.get_json('api.bitvalor.com', '/v1/ticker.json') return {'BRL': Decimal(json['ticker_1h']['exchanges']['FOX']['last'])} - def history_ccys(self): - return ['BRL'] - class NegocieCoins(ExchangeBase): def get_rates(self,ccy): - json = requests.get('http://api.bitvalor.com/v1/ticker.json').json() + json = self.get_json('api.bitvalor.com', '/v1/ticker.json') return {'BRL': Decimal(json['ticker_1h']['exchanges']['NEG']['last'])} def history_ccys(self): t@@ -304,17 +283,39 @@ def dictinvert(d): keys.append(k) return inv -def get_exchanges(): +def get_exchanges_and_currencies(): + import os, json + path = os.path.join(os.path.dirname(__file__), 'currencies.json') + try: + return json.loads(open(path, 'r').read()) + except: + pass + d = {} is_exchange = lambda obj: (inspect.isclass(obj) and issubclass(obj, ExchangeBase) and obj != ExchangeBase) - return dict(inspect.getmembers(sys.modules[__name__], is_exchange)) + exchanges = dict(inspect.getmembers(sys.modules[__name__], is_exchange)) + for name, klass in exchanges.items(): + exchange = klass(None, None) + try: + d[name] = exchange.get_currencies() + except: + continue + with open(path, 'w') as f: + f.write(json.dumps(d, indent=4, sort_keys=True)) + return d + + +CURRENCIES = get_exchanges_and_currencies() + -def get_exchanges_by_ccy(): - "return only the exchanges that have history rates (which is hardcoded)" +def get_exchanges_by_ccy(history=True): + if not history: + return dictinvert(CURRENCIES) d = {} - exchanges = get_exchanges() - for name, klass in exchanges.items(): + exchanges = CURRENCIES.keys() + for name in exchanges: + klass = globals()[name] exchange = klass(None, None) d[name] = exchange.history_ccys() return dictinvert(d) t@@ -330,12 +331,15 @@ class FxThread(ThreadJob): self.history_used_spot = False self.ccy_combo = None self.hist_checkbox = None - self.exchanges = get_exchanges() - self.exchanges_by_ccy = get_exchanges_by_ccy() self.set_exchange(self.config_exchange()) + def get_currencies(self): + d = get_exchanges_by_ccy(False) + return sorted(d.keys()) + def get_exchanges_by_ccy(self, ccy, h): - return self.exchanges_by_ccy.get(ccy, []) + d = get_exchanges_by_ccy(h) + return d.get(ccy, []) def ccy_amount_str(self, amount, commas): prec = CCY_PRECISIONS.get(self.ccy, 2) t@@ -380,12 +384,10 @@ class FxThread(ThreadJob): self.on_quotes() def set_exchange(self, name): - class_ = self.exchanges.get(name) or self.exchanges.values()[0] - name = class_.__name__ + class_ = globals()[name] self.print_error("using exchange", name) if self.config_exchange() != name: self.config.set_key('use_exchange', name, True) - self.exchange = class_(self.on_quotes, self.on_history) # A new exchange means new fx quotes, initially empty. Force # a quote refresh