mod bitcoin: multiple exchange support (#353)

* mod bitcoin: add 'volume_percent'

* mod bitcoin: Fix exception on url opening (#304)

Calling user_open as a 'Python callback' raises an exception because
this function doesn't expects 'self'.
Wrote a wrapper function as a 'Member callback' to filter it out.

* mod bitcoin: add specific exchange support

* mod bitcoin: add request age attribute

* mod bitcoin: refactor

* mod bitcoin: btc volume divisor

* bitcoin: Deal with diffrent locales

* Fixing PEP8

* mod bitcoin: Updated docs
This commit is contained in:
Maximiliano 2016-04-07 23:20:37 +02:00 committed by enkore
parent c93bfe16b6
commit c0cdfae1f8

View File

@ -1,10 +1,27 @@
import urllib.request import urllib.request
import json import json
import time from datetime import datetime
from i3pystatus import IntervalModule from i3pystatus import IntervalModule
from i3pystatus.core.util import internet, require, user_open from i3pystatus.core.util import internet, require, user_open
import locale
import threading
from contextlib import contextmanager
LOCALE_LOCK = threading.Lock()
@contextmanager
def setlocale(name):
# To deal with locales only in this module and keep it thread save
with LOCALE_LOCK:
saved = locale.setlocale(locale.LC_ALL)
try:
yield locale.setlocale(locale.LC_ALL, name)
finally:
locale.setlocale(locale.LC_ALL, saved)
class Bitcoin(IntervalModule): class Bitcoin(IntervalModule):
@ -12,8 +29,10 @@ class Bitcoin(IntervalModule):
This module fetches and displays current Bitcoin market prices and This module fetches and displays current Bitcoin market prices and
optionally monitors transactions to and from a list of user-specified optionally monitors transactions to and from a list of user-specified
wallet addresses. Market data is pulled from the BitcoinAverage Price wallet addresses. Market data is pulled from the BitcoinAverage Price
Index API <https://bitcoinaverage.com> while transaction data is pulled Index API <https://bitcoinaverage.com> and it is possible to specify
from blockchain.info <https://blockchain.info/api/blockchain_api>. the exchange to be monitored.
Transaction data is pulled from blockchain.info
<https://blockchain.info/api/blockchain_api>.
.. rubric:: Available formatters .. rubric:: Available formatters
@ -22,6 +41,9 @@ class Bitcoin(IntervalModule):
* {bid_price} * {bid_price}
* {daily_average} * {daily_average}
* {volume} * {volume}
* {volume_thousend}
* {volume_percent}
* {age}
* {status} * {status}
* {last_tx_type} * {last_tx_type}
* {last_tx_addr} * {last_tx_addr}
@ -37,6 +59,7 @@ class Bitcoin(IntervalModule):
("currency", "Base fiat currency used for pricing."), ("currency", "Base fiat currency used for pricing."),
("wallet_addresses", "List of wallet address(es) to monitor."), ("wallet_addresses", "List of wallet address(es) to monitor."),
("color", "Standard color"), ("color", "Standard color"),
("exchange", "Get ticker from a custom exchange instead"),
("colorize", "Enable color change on price increase/decrease"), ("colorize", "Enable color change on price increase/decrease"),
("color_up", "Color for price increases"), ("color_up", "Color for price increases"),
("color_down", "Color for price decreases"), ("color_down", "Color for price decreases"),
@ -46,6 +69,7 @@ class Bitcoin(IntervalModule):
) )
format = "{symbol} {status}{last_price}" format = "{symbol} {status}{last_price}"
currency = "USD" currency = "USD"
exchange = None
symbol = "฿" symbol = "฿"
wallet_addresses = "" wallet_addresses = ""
color = "#FFFFFF" color = "#FFFFFF"
@ -59,14 +83,39 @@ class Bitcoin(IntervalModule):
} }
on_leftclick = "electrum" on_leftclick = "electrum"
on_rightclick = [user_open, "https://bitcoinaverage.com/"] on_rightclick = ["open_something", "https://bitcoinaverage.com/"]
_price_prev = 0 _price_prev = 0
def _get_age(self, bitcoinaverage_timestamp):
with setlocale('C'): # Deal with locales (months name differ)
# Assume format is always utc, to avoid import pytz
utc_tstamp = datetime.strptime(
bitcoinaverage_timestamp.split(', ')[1],
u'%d %b %Y %H:%M:%S -0000')
diff = datetime.utcnow() - utc_tstamp
return int(diff.total_seconds())
def _query_api(self, api_url):
url = "{}{}".format(api_url, self.currency.upper())
response = urllib.request.urlopen(url).read().decode("utf-8")
return json.loads(response)
def _fetch_price_data(self): def _fetch_price_data(self):
api = "https://api.bitcoinaverage.com/ticker/global/" if self.exchange is None:
url = "{}{}".format(api, self.currency.upper()) api_url = "https://api.bitcoinaverage.com/ticker/global/"
return json.loads(urllib.request.urlopen(url).read().decode("utf-8")) return self._query_api(api_url)
else:
api_url = "https://api.bitcoinaverage.com/exchanges/"
ret = self._query_api(api_url)
exchange = ret[self.exchange.lower()]
# Adapt values to global ticker format
exchange['ask'] = exchange['rates']['ask']
exchange['bid'] = exchange['rates']['bid']
exchange['last'] = exchange['rates']['last']
exchange['24h_avg'] = None
exchange['timestamp'] = ret['timestamp']
return exchange
def _fetch_blockchain_data(self): def _fetch_blockchain_data(self):
api = "https://blockchain.info/multiaddr?active=" api = "https://blockchain.info/multiaddr?active="
@ -77,6 +126,7 @@ class Bitcoin(IntervalModule):
@require(internet) @require(internet)
def run(self): def run(self):
price_data = self._fetch_price_data() price_data = self._fetch_price_data()
fdict = { fdict = {
"symbol": self.symbol, "symbol": self.symbol,
"daily_average": price_data["24h_avg"], "daily_average": price_data["24h_avg"],
@ -84,6 +134,9 @@ class Bitcoin(IntervalModule):
"bid_price": price_data["bid"], "bid_price": price_data["bid"],
"last_price": price_data["last"], "last_price": price_data["last"],
"volume": price_data["volume_btc"], "volume": price_data["volume_btc"],
"volume_thousend": price_data["volume_btc"] / 1000,
"volume_percent": price_data["volume_percent"],
"age": self._get_age(price_data['timestamp'])
} }
if self._price_prev and fdict["last_price"] > self._price_prev: if self._price_prev and fdict["last_price"] > self._price_prev:
@ -125,3 +178,9 @@ class Bitcoin(IntervalModule):
"full_text": self.format.format(**fdict), "full_text": self.format.format(**fdict),
"color": color, "color": color,
} }
def open_something(self, url_or_command):
"""
Wrapper function, to pass the arguments to user_open
"""
user_open(url_or_command)