Title: Bitcoin monitor script for Conky [Updated: v92704]
Post by: Rena on April 19, 2011, 03:33:32 AM
Here's a quick Lua script I wrote for the system monitor Conky to display Bitcoin network/mining status, account balance, transaction history and MtGox ticker data. It's a little messy but it works. :P Requires Json4Lua (http://json.luaforge.net) and LuaSocket (http://luaforge.net/projects/luasocket), talks to an instance of bitcoin[d] on the local system. --[[ Bitcoin Lua script for Conky, by Rena. Build #92704. Usage: Add a "lua_load" line to your .conkyrc to load this script, then add variables to your config:
${lua bitcoin_balance [account name]} (You can also use lua_bar, lua_graph, etc. A graph might be interesting for those who perform a lot of transactions!)
${lua bitcoin_info format string} (or lua_parse) Format string is any string (can contain line breaks), in which any instance of %foo% is replaced with the value of the variable foo output by "bitcoin getinfo". Cannot contain multiple spaces; use ${tab} for alignment. (This is a limitation of how Conky passes strings to Lua.)
${lua bitcoin_transactions account num format} (or lua_parse) List up to 'num' recent transactions (most recent first) on an account. Account name must be quoted if it contains a space, can be * for all accounts. Format works same as bitcoin_info.
${if_empty ${lua bitcoin_running}}...${else}...${endif} To display text only if Bitcoin is/isn't running. bitcoin_running returns an empty string when Bitcoin running and '0' when it isn't, so that if_empty can be used like a normal 'if'.
${lua mtgox_ticker format string} (or lua_parse) Works same as bitcoin_info. Field names are those under 'ticker', so e.g. write %high%, not %ticker.high%.
Send bug reports, suggestions, flames, cat photos and whatever else to ('moc.liamg\064rekcahrepyh'):reverse(). Send spare change to 15R7dpxfWuDrQY9CtUzMsg6A7sKMdWbTd3 why not.
Version history: -92704: -Added mtgox_ticker() and ability to fetch JSON URLs. (Adds dependency on LuaSocket.) -Changed keypoololdest to a formatted date. Raw timestamp is keypoololdest_raw. -92432: Initial release. ]]
local json = require('json') --http://json.luaforge.net/ local http = require('socket.http') --http://luaforge.net/projects/luasocket/
--options local UpdateFreq = 5*60 --only update once every this many seconds local SignPlus = '${color3}+' --prefix for positive numbers local SignMinus = '${color2}-' --prefix for negative numbers local DateFmt = '%H:%M:%S %y/%m/%d' --strftime date format local FeeEmpty = '-.--' --string to display for %fee% when empty
--state local Account = {} --indexed by account name local LastQuery = 0 --last time bitcoin was called local QueryCache = {} local URLCache = {}
--[[ Send a query to bitcoin. This function caches queries so that bitcoin is only called once per UpdateFreq to avoid creating a lot of needless overhead by spawning processes over and over. ]] local function bitcoin_query(query) local now = os.time() LastQuery = now local cache = QueryCache[query] if cache then if os.difftime(now, cache.Time) < UpdateFreq then return cache.Result end else QueryCache[query] = {} cache = QueryCache[query] end --hack: redirect stderr to stdout and look for either an error message or --what looks like JSON data. local data, found = '', false local app = io.popen('bitcoin ' .. query .. ' 2>&1') for line in app:lines() do if line:sub(1,6) == 'error:' then if line == "error: no response from server" or line == "error: couldn't connect to server" then Running = false return nil, "Bitcoin not running" else Running = false --presumably... io.stderr:write("Bitcoin: " .. line .. "\n") return nil, line end elseif found then data = data .. line elseif line:sub(1,1) == '{' or line:sub(1,1) == '[' then found = true data = data .. line end end app:close() Running = true --well it must be, we just talked to it. cache.Time, cache.Result = now, json.decode(data) return cache.Result end
--[[ Fetch JSON data from a HTTP URL. This function caches queries in URLCache so that the same URL isn't accessed more than once per UpdateFreq. This is a simple naive cache that only works if the URL is exactly the same each time. On success, returns JSON data as table. On failure, returns nil and error message. ]] local function get_json_url(url) if not URLCache[url] then URLCache[url] = {LastQuery=0} end local now = os.time() if (now - URLCache[url].LastQuery) < UpdateFreq then return URLCache[url].LastResult end URLCache[url].LastQuery = now local text, status = http.request(url) if (not text) or (status < 200) or (status >= 300) then URLCache[url].LastResult = 'Error: ' .. tostring(status) return nil, URLCache[url].LastResult end local err URLCache[url].LastResult, err = json.decode(text) if URLCache[url].LastResult then return URLCache[url].LastResult else return nil, err end end
--[[ Conky splits args at space, regardless of quotes. So if arg begins with a quote, look for end quote and piece the string together. note that this fails if there are multiple spaces in a row since Conky compresses them into one. ]] local function readquotedarg(args, idx) idx = idx or 1 local str = '' if args[idx] and args[idx]:sub(1,1) == '"' then if args[idx]:sub(-1) == '"' then return table.remove(args, idx):sub(1, -2) end str = table.remove(args, idx):sub(2) while args[idx] and args[idx]:sub(-1) ~= '"' do str = str .. ' ' .. table.remove(args, idx) end return str .. ' ' .. table.remove(args, idx):sub(1, -2) elseif args[idx] then return table.remove(args, idx) else return nil end end
--[[ Check if Bitcoin is running. ]] function conky_bitcoin_running(...) local now = os.time() if Running == nil --don't know or os.difftime(now, LastQuery) >= UpdateFreq then bitcoin_query('getinfo') --make some random query to find out end return Running and '' or '0' --for Conky if_empty end
--[[ Balance for particular account. ]] function conky_bitcoin_balance(...) local args = {...} local account = readquotedarg(args) or "Your Address" --default account name --create status table for this account if not done already. if not Account[account] then Account[account] = { LastUpdate = 0, LastBalance = 0 } end --Query bitcoin to find account balance. local info = bitcoin_query('listaccounts') --display negative balance to signal error, since some Conky functions --expect a number. if not info then Account[account].LastBalance = -1 end Account[account].LastUpdate = now return tonumber(Account[account].LastBalance) end
--[[ Recent transaction list. ]] function conky_bitcoin_transactions(...) local args = {...} local account = readquotedarg(args) or "Your Address" local numshow = tonumber(args[1]) or 5 local fmt = table.concat(args, ' ', 2) --format string local info = bitcoin_query(('listtransactions "%s" %d'): format(account, numshow)) --add some convenient fields to info for i = 1,#info do if not info[i].sign then --cache means these may already be set info[i].sign = (info[i].amount > 0) and SignPlus or SignMinus info[i].absamount = math.abs(info[i].amount) info[i].absfee = info[i].fee and math.abs(info[i].fee) or FeeEmpty info[i].rawtime = info[i].time info[i].time = os.date(DateFmt, tonumber(info[i].rawtime)) info[i].fee = info[i].fee or FeeEmpty end end --transactions seem to be listed oldest first, so reverse iterate. local res = '' for i = math.min(numshow,#info), 1, -1 do res = res .. fmt:gsub('%%([%w_]+)%%', info[i]) .. '\n' end return res end
--[[ General info. ]] function conky_bitcoin_info(...) local args, info = {...}, {} local fmt = table.concat(args, ' ') --format string --query Bitcoin and parse results. local info = bitcoin_query('getinfo') if not info.keypoololdest_raw then info.keypoololdest_raw = info.keypoololdest info.keypoololdest = os.date(DateFmt, tonumber(info.keypoololdest)) end --generate and return output string. LastInfoStr = fmt:gsub('%%([%w_]+)%%', info) LastInfoUpdate = now return LastInfoStr end
--[[ MtGox info. ]] function conky_mtgox_ticker(...) local args, info = {...}, {} local fmt = table.concat(args, ' ') --format string local data, err = get_json_url('http://mtgox.com/code/data/ticker.php') if data then MtGoxLastResult = fmt:gsub('%%([%w_]+)%%', data.ticker) else MtGoxLastResult = tostring(err) end return MtGoxLastResult end Example config: ${color1}BTC${hr} ${if_empty ${lua bitcoin_running}}${lua_parse bitcoin_info Balance:${tab 40}${color0}%balance%${color1} Tx Fee:${tab 40}${color0}%paytxfee%${color1} Connections:${tab 10}${color0}%connections%${color1} Blocks:${tab 40}${color0}%blocks%${color1} Oldest:${tab 40}${color0}%keypoololdest%${color1} Difficulty:${tab 20}${color0}%difficulty%${color1} Version:${tab 40}${color0}%version%${color1}} ${lua_parse bitcoin_transactions * 5 %sign%%absamount% %confirmations%${tab 16}%absfee%${tab 16}%time%} ${else}${color0}NO DATA${endif} (Ugly ain't it? :P But the result looks quite nice! Screenshot here (http://imgh.us/conkybitcoin.png) as it's too big to attach to the post. :p)
Title: Re: Bitcoin monitor script for Conky [Updated: v92704]
Post by: Rena on May 02, 2011, 08:07:43 AM
Updated to add MtGox ticker data. Adds a dependency on LuaSocket. It's structured to be fairly simple to add support for any JSON HTTP URL, so feel free to ask.
Title: Re: Bitcoin monitor script for Conky [Updated: v92704]
Post by: mrlithium on March 23, 2013, 03:40:24 PM
You didnt have anything in your conky rc example file on the mtgox_ticker. I spent a few hours learning conky. This is the best I came up with. I don't know what format "format" is looking for. Also, the ticker url has changed so that code doesnt work anymore, replace the old ticker URL with http://data.mtgox.com/code/data/ticker.php?Currency=USD . Note that this URL likely to change soon too (says its deprecated). (Put this in your .conkyrc file once youve lua load'ed the script that does the heavy lifting by grabbing the tickerdata) ${lua_parse mtgox_ticker Buy:%buy% Sell:%sell% Last:%last%} ${lua_parse mtgox_ticker High:%high% Low:%low% Vol:%vol%} ${lua_parse mtgox_ticker Avg:%avg% VWAP:%vwap% } But it's working, fully, just theres a heck of a lot of decimal places which I don't know how to round off :) https://i.imgur.com/7EN610E.png
|