URI: 
       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