DEVTOME.COM HOSTING COSTS HAVE BEGUN TO EXCEED 115$ MONTHLY. THE ADMINISTRATION IS NO LONGER ABLE TO HANDLE THE COST WITHOUT ASSISTANCE DUE TO THE RISING COST. THIS HAS BEEN OCCURRING FOR ALMOST A YEAR, BUT WE HAVE BEEN HANDLING IT FROM OUR OWN POCKETS. HOWEVER, WITH LITERALLY NO DONATIONS FOR THE PAST 2+ YEARS IT HAS DEPLETED THE BUDGET IN SHORT ORDER WITH THE INCREASE IN ACTIVITY ON THE SITE IN THE PAST 6 MONTHS. OUR CPU USAGE HAS BECOME TOO HIGH TO REMAIN ON A REASONABLE COSTING PLAN THAT WE COULD MAINTAIN. IF YOU WOULD LIKE TO SUPPORT THE DEVTOME PROJECT AND KEEP THE SITE UP/ALIVE PLEASE DONATE (EVEN IF ITS A SATOSHI) TO OUR DEVCOIN 1M4PCuMXvpWX6LHPkBEf3LJ2z1boZv4EQa OR OUR BTC WALLET 16eqEcqfw4zHUh2znvMcmRzGVwCn7CJLxR TO ALLOW US TO AFFORD THE HOSTING.

THE DEVCOIN AND DEVTOME PROJECTS ARE BOTH VERY IMPORTANT TO THE COMMUNITY. PLEASE CONTRIBUTE TO ITS FURTHER SUCCESS FOR ANOTHER 5 OR MORE YEARS!

DVC Ticker Source Code

This is the source code for the app running here: http://dvcticker.appspot.com/ DVC Ticker is a simple app based on the idea of the Bitcoin Tickers app. Given a special URL, the app will generate images representing the value of a particular cryptocoin.

The app is programmed in python, and uses Google AppEngine for hosting. The latest version of the source code can be found here: https://github.com/moozilla/dvcticker

app.yaml

This is the configuration file for Google App Engine.

application: dvcticker
version: 1
runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /favicon\.ico
  static_files: favicon.ico
  upload: favicon\.ico

- url: /img
  static_dir: ./static/img
  application_readable: true
  
- url: /font
  static_dir: ./static/font
  application_readable: true

- url: /.*
  script: main.app

libraries:
- name: webapp2
  version: "2.5.2"
- name: PIL
  version: latest
- name: jinja2
  version: latest

main.py

This is the main script.

import webapp2
from google.appengine.api import urlfetch
import json
from PIL import Image, ImageDraw, ImageFont
from google.appengine.api import memcache
import StringIO
import jinja2
import os
from decimal import * #used fixed point math for better accuracy
from google.appengine import runtime # for catching DeadlineExceededError
from google.appengine.api import urlfetch_errors # "

JINJA_ENVIRONMENT = jinja2.Environment(
    loader=jinja2.FileSystemLoader(os.path.dirname(__file__)))

def urlfetch_cache(url,exchange):
    # fetches a url, but using memcache to not hammer the exchanges server
    data = memcache.get(url)
    if data is not None:
        return process_json(data, exchange)
    else:
        try:
            result = urlfetch.fetch(url,deadline=30) #timeout after 30 sec
            if result.status_code == 200:
                value = process_json(result.content, exchange)
                memcache.add(url, result.content, 30) #cache for 30 sec
                memcache.add('longcache'+url, result.content, 3000) #also cache for 5min in case of timeouts
                return value
            else:
                return 'Error: '+exchange+' status code '+str(result.status_code) #'Error accessing Vircurex API'
        except runtime.DeadlineExceededError: #raised if the overall request times out
            data = memcache.get('longcache'+url)
            if data is not None: return process_json(data, exchange)
            else: return 'Error: '+exchange+' timeout'
        except runtime.apiproxy_errors.DeadlineExceededError: #raised if an RPC exceeded its deadline (set)
            data = memcache.get('longcache'+url)
            if data is not None: return process_json(data, exchange)
            else: return 'Error: '+exchange+' timeout'
        except urlfetch_errors.DeadlineExceededError: #raised if the URLFetch times out
            data = memcache.get('longcache'+url)
            if data is not None: return process_json(data, exchange)
            else: return 'Error: '+exchange+' timeout'
        except urlfetch.Error: #catch DownloadError
            data = memcache.get('longcache'+url)
            if data is not None: return process_json(data, exchange)
            else: return 'Error: '+exchange+' timeout'
            
def process_json(txt, exchange):
    #should probably add error handling in case bad json is passed
    if exchange == 'vircurex':
        if txt == '"Unknown currency"': return 'Error: bad Vircurex API result'
        obj = json.loads(txt) 
        return obj['value']
    elif exchange == 'mtgox_bid':
        obj = json.loads(txt)
        if obj['result'] == 'success':
            return obj['return']['buy']['value']
        else:
            return 'Error: bad MTGox API result'
    elif exchange == 'mtgox_ask':
        obj = json.loads(txt)
        if obj['result'] == 'success':
            return obj['return']['sell']['value']
        else:
            return 'Error: bad MTGox API result'
    elif exchange == 'btce_bid':
        obj = json.loads(txt)
        if not any('error' in s for s in obj):
            return str(obj['ticker']['buy'])
        else:
            return 'Error: bad BTC-E API result'
    elif exchange == 'btce_ask':
        obj = json.loads(txt)
        if not any('error' in s for s in obj):
            return str(obj['ticker']['sell'])
        else:
            return 'Error: bad BTC-E API result'
    else:
        return 'Error: invalid exchange'
        
def get_mtgox_value(base,alt,amount):
    cur = ['usd', 'aud', 'cad', 'chf', 'cny', 'dkk',
      'eur', 'gbp', 'hkd', 'jpy', 'nzd', 'pln', 'rub', 'sek', 'sgd', 'thb']
    reverse = False # if going from cur-> btc
    if base == 'btc': 
        if not any(alt in s for s in cur):
            return 'Error: invalid destination currency'
        url = 'http://data.mtgox.com/api/1/btc'+alt+'/ticker'
        exch = 'mtgox_bid'
    elif any(base in s for s in cur):
        if alt != 'btc':
            return 'Error: destination currency must be BTC'
        url = 'http://data.mtgox.com/api/1/btc'+base+'/ticker' #mtgox api always has btc first
        exch = 'mtgox_ask'
        reverse = True
    else:
        return 'Error: invalid base currency'
    value = urlfetch_cache(url,exch)
    if value.startswith('Error'): return value
    
    if reverse: return str((Decimal(amount) / Decimal(value)).quantize(Decimal('.00000001'), rounding=ROUND_DOWN)) # need to round to a certain number
    else: return str(Decimal(amount) * Decimal(value))
    
def get_btce_value(base,alt,amount):
    # in BTC-e currencies must be traded in pairs, we also support going in reverse (buying)
    cur_fwd = {'btc':['usd','rur','eur'], 'ltc':['btc','usd','rur'], 'nmc':['btc'], 'usd':['rur'], 'eur':['usd'], 'nvc':['btc'], 'trc':['btc'], 'ppc':['btc'], 'ftc':['btc']}
    cur_rev = {'btc':['ltc','nmc','nvc','trc','ppc','ftc'], 'usd':['btc','ltc'], 'rur':['btc','usd'], 'eur':['btc']}
    reverse = False # if going from cur-> btc
    if any(base in s for s in cur_fwd) and any(alt in s for s in cur_fwd[base]): 
        #if not any(alt in s for s in cur_fwd[base]):
            #return 'Error: invalid destination currency' # can't return here because some can be base or alt
        url = 'https://btc-e.com/api/2/'+base+'_'+alt+'/ticker' #https://btc-e.com/api/2/nmc_btc/ticker
        exch = 'btce_bid'
    else:
        if any(base in s for s in cur_rev):
            if not any(alt in s for s in cur_rev[base]):
                return 'Error: invalid currency pair'
            url = 'https://btc-e.com/api/2/'+alt+'_'+base+'/ticker'
            exch = 'btce_ask'
            reverse = True
        else:
            return 'Error: invalid currency pair'
    value = urlfetch_cache(url,exch)
    if value.startswith('Error'): return value
    
    if reverse: return str((Decimal(amount) / Decimal(value)).quantize(Decimal('.00000001'), rounding=ROUND_DOWN)) # need to round to a certain number
    else: return str(Decimal(amount) * Decimal(value))
    
def get_vircurex_value(type, base, alt, amount):
    # gets json from vircurex about bid/ask prices
    # eg. https://vircurex.com/api/get_highest_bid.json?base=BTC&alt=NMC
    if type == 'bid':
        url = 'https://vircurex.com/api/get_highest_bid.json'
    elif type == 'ask':
        url = 'https://vircurex.com/api/get_lowest_ask.json'
    else:
        return 'Error: Type must be either "bid" or "ask"'
    cur = ['btc', 'dvc', 'ixc', 'ltc', 'nmc', 'ppc', 'trc', 'usd', 'eur', 'ftc', 'frc']
    if not any(base in s for s in cur): return 'Error: invalid currency'
    if not any(alt in s for s in cur): return 'Error: invalid currency'
    
    url += '?base=' + base + '&alt=' + alt
    value = urlfetch_cache(url,'vircurex')
    if value.startswith('Error'): return value
    return str(Decimal(amount)*Decimal(value)) # return amount * value
    
    #if result.status_code == 200 and result.content != '"Unknown currency"':
    #    obj = json.loads(result.content)
    #    return obj['value']
    #else:
    #    return 'Error'#'Error accessing Vircurex API'
    
def get_bid(exchange, amount, base, alt):
    if exchange == 'vircurex':
        return get_vircurex_value('bid',base,alt,amount)
    elif exchange == 'mtgox':
        return get_mtgox_value(base,alt,amount)
    elif exchange == 'btc-e':
        return get_btce_value(base,alt,amount)
    else:
        return 'Error: bad exchange'
    
class MainHandler(webapp2.RequestHandler):
    def get(self):
        #base = self.request.get('base','dvc')
        #alt = self.request.get('alt','btc')
        #value = get_vircurex_value('bid',base,alt)
        
        #template_values = {
        #    'value': value
        #}
        
        template = JINJA_ENVIRONMENT.get_template('index.html')
        self.response.write(template.render())#template_values))
        
class ImageHandler(webapp2.RequestHandler):
    def get(self,exchange,amount,base,alt):
        if amount == '': amount = '1'       # default amount is 1
        exchange = exchange.lower()         # make sure everything is lowercase
        base = base.lower()
        if alt == None:
            if base == 'btc': alt = 'usd'   # btc.png just shows btc value in usd
            else: alt = 'btc'               # if no alt specified, default to BTC
        alt = alt.lower()
        value = get_bid(exchange,amount,base,alt)
        #if bid.startswith('Error'): value = bid
        #else: value = str(Decimal(amount)*Decimal(bid))
        text_pos = 19                       # 3 px after coin image (all are 16x16)
        
        if value.startswith('Error'):
            text_pos = 0
        elif alt == 'usd':
            # round down to 2 decimal places
            value = '$ '+str(Decimal(value).quantize(Decimal('.01'), rounding=ROUND_DOWN))
            text_pos = 2
        elif alt == 'eur':
            # euro symbol in unicode (only works with truetype fonts)
            value = u'\u20AC '+str(Decimal(value).quantize(Decimal('.01'), rounding=ROUND_DOWN))
            text_pos = 2                    # have to position euro symbol so it doesn't cut off
        elif any(alt in s for s in ['aud', 'cad', 'chf', 'cny', 'dkk',
          'gbp', 'hkd', 'jpy', 'nzd', 'pln', 'rub', 'sek', 'sgd', 'thb', 'rur', 'nvc']):
          value = alt.upper() + ' ' + value
          text_pos = 2
        
        img = Image.new("RGBA", (1,1))      # just used to calculate the text size, size doesn't matter
        draw = ImageDraw.Draw(img)
        #fnt = ImageFont.load('static/font/ncenB12.pil') # for testing locally, can't get truetype to work locally
        fnt = ImageFont.truetype('static/font/tahoma_bold.ttf', 14, encoding='unic')
        w, h = draw.textsize(value, fnt)    # calculate width font will take up
        
        del img
        img = Image.new("RGBA", (w+text_pos,20))
        draw = ImageDraw.Draw(img)          # set draw to new image
        #text_pos 0 = error
        if text_pos!=0 and any(alt in s for s in ['btc', 'dvc', 'ixc', 'ltc', 'nmc', 'ppc', 'trc', 'ftc','frc']):
            coinimg = Image.open('static/img/'+alt+'.png') 
            img.paste(coinimg, (0,2))       #paste the coin image into the generated image
        
        draw.text((text_pos,1), value, font=fnt, fill='#555555')
        del draw
        
        output = StringIO.StringIO()
        img.save(output, format='png')
        img_to_serve = output.getvalue()
        output.close()
        
        self.response.headers['Content-Type'] = 'image/png'
        self.response.out.write(img_to_serve)

app = webapp2.WSGIApplication([
    ('/', MainHandler),
    ('/([^/]+)/(\d*\.?\d*)([A-Za-z]{3})(?:/([A-Za-z]{3}))?(?:\.png)?', ImageHandler)
], debug=True)

index.html

The code for the main page.

<html>
    <head>
        <title>Cryptocoin Tickers</title>
        <style type="text/css">
            p { line-height: 2em }
            code { border: 1px dotted #999; padding: 1px; background-color: #eee }
            .small { font-size: 10pt }
            .ind { margin-left: 2em }
        </style>
    </head>
    <body>
        <h1>Cryptocoin Ticker Images</h1>
        <p>
        This is a simple app based on the idea of the <a href="http://btcticker.appspot.com/">Bitcoin Tickers</a> app.
        Given a special URL, the app will generate images representing the value of a particular coin.
        </p>
        
        <p>
        For example, to generate an image for the current highest bid price for DVC in BTC on Vircurex, use the following URL: <br />
        <code>http://dvcticker.appspot.com/vircurex/dvc/btc.png</code><br />
        The resulting image: <img src="vircurex/dvc/btc.png" />
        </p>
        
        <p>
        To see how much 100.00BTC is worth in USD: <br />
        <code>http://dvcticker.appspot.com/vircurex/100.00btc/usd.png</code><br />
        The resulting image: <img src="vircurex/100.00btc/usd.png" />
        </p>
        
        <p>Supported exchanges: <br/>
        Vircurex. Supported currencies are:
        <br /><span class="small ind">BTC, USD, EUR, DVC, IXC, LTC, NMC, PPC, TRC, FTC, FRC.</span><br />
        MTGox. Supported currencies (one must be BTC):
        <br /><span class="small ind">BTC, USD, AUD, CAD, CHF, CNY, DKK, EUR, GBP, HKD, JPY, NZD, PLN, RUB, SEK, SGD, THB.</span><br />
        BTC-E. Supported currency pairs (can be forward or reverse):
        <br /><span class="small ind">BTC/USD, BTC/RUR, BTC/EUR, LTC/BTC, LTC/USD, LTC/RUR, NMC/BTC, USD/RUR, EUR/USD, NVC/BTC, TRC/BTC, PPC/BTC, FTC/BTC</span>
        </p>
        
        <p>
        Some more examples:<br />
        <table>
        <tr><td><code>http://dvcticker.appspot.com/vircurex/180000000dvc.png&nbsp;&nbsp;</code></td><td><img src="vircurex/180000000dvc.png" /></td></tr>
        <tr><td><code>http://dvcticker.appspot.com/vircurex/btc/dvc.png&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</code></td><td><img src="vircurex/btc/dvc.png" /></td></tr>
        <tr><td><code>http://dvcticker.appspot.com/vircurex/ltc/btc.png&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</code></td><td><img src="vircurex/ltc/btc.png" /></td></tr>
        <tr><td><code>http://dvcticker.appspot.com/vircurex/10trc/ppc.png&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</code></td><td><img src="vircurex/10trc/ppc.png" /></td></tr>
        <tr><td><code>http://dvcticker.appspot.com/vircurex/155.72nmc/usd.png&nbsp;</code></td><td><img src="vircurex/155.7nmc/usd.png" /></td></tr>
        <tr><td><code>http://dvcticker.appspot.com/vircurex/1000ftc/eur.png&nbsp;&nbsp;&nbsp;</code></td><td><img src="vircurex/1000ixc/eur.png" /></td></tr>
        <tr><td><code>http://dvcticker.appspot.com/mtgox/btc.png&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</code></td><td><img src="mtgox/btc.png" /></td></tr>
        <tr><td><code>http://dvcticker.appspot.com/mtgox/btc/jpy.png&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</code></td><td><img src="mtgox/btc/jpy.png" /></td></tr>
        <tr><td><code>http://dvcticker.appspot.com/mtgox/500dkk/btc.png&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</code></td><td><img src="mtgox/500dkk/btc.png" /></td></tr>
        <tr><td><code>http://dvcticker.appspot.com/btc-e/10btc/nvc.png&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</code></td><td><img src="btc-e/10btc/nvc.png" /></td></tr>
        </table>
        <span class="small">
        (The .png extension and the second currency are optional. USD and EUR automatically round to two decimal places.<br />
        All images are based on the highest bid price in the forward direction and lowest ask price in the reverse.)
        </span>
        </p>
        
        <p>
        Source code: <a href="https://github.com/moozilla/dvcticker">https://github.com/moozilla/dvcticker</a> &mdash;
        Contact: <a href="https://bitcointalk.org/index.php?action=pm;sa=send;u=91796">metazilla on Bitcoin Talk</a>
        </p>
        
        <p style="font-size: 10pt; color: #333">
        <b>DVC:</b> 1FTpugE41hE46NwJndmpWMedYDQHBtMEKB ::
        <b>BTC:</b> 1L1qtxesL5uk2xRNmxGqKC8KNEgYXmWzNC ::
        <b>LTC:</b> LYNGdNpa6K6tiJVFLmXdBChcji2BMoLvQ8
        </p>
    </body>
</html>

The images and font

There are several images and a font file that the script uses. You can find them here: https://github.com/moozilla/dvcticker/tree/master/dvcticker/static. They should be placed in a folder called “static”. If this is confusing, look at the structure of the github repo. Any TrueType font will work, but I'd recommend staying around 14pt, since the coin images are only 16×16 (and images are hardcoded to be 20px tall.)


QR Code
QR Code dvcticker (generated for current page)
 

Advertise with Anonymous Ads