text: # coding=utf-8
  # Copyright 2013 Elsie Powell, embolalia.com
  # Licensed under the Eiffel Forum License 2
  from __future__ import unicode_literals, absolute_import, print_function, division
  
  import json
  import xmltodict
  import re
  
  from sopel import web
  from sopel.module import commands, example, NOLIMIT
  
  # The Canadian central bank has better exchange rate data than the Fed, the
  # Bank of England, or the European Central Bank. Who knew?
  base_url = 'http://www.bankofcanada.ca/stats/assets/rates_rss/noon/en_{}.xml'
  regex = re.compile(r'''
      (\d+(?:\.\d+)?)        # Decimal number
      \s*([a-zA-Z]{3})       # 3-letter currency code
      \s+(?:in|as|of|to)\s+  # preposition
      ([a-zA-Z]{3})          # 3-letter currency code
      ''', re.VERBOSE)
  
  
  def get_rate(code):
      code = code.upper()
      if code == 'CAD':
          return 1, 'Canadian Dollar'
      elif code == 'BTC':
          rates = json.loads(web.get('https://api.bitcoinaverage.com/ticker/all'))
          return 1 / rates['CAD']['24h_avg'], 'Bitcoin—24hr average'
  
      data, headers = web.get(base_url.format(code), dont_decode=True, return_headers=True)
      if headers['_http_status'] == 404:
          return False, False
      namespaces = {
          'http://www.cbwiki.net/wiki/index.php/Specification_1.1': 'cb',
          'http://purl.org/rss/1.0/': None,
          'http://www.w3.org/1999/02/22-rdf-syntax-ns#': 'rdf'
      }
      xml = xmltodict.parse(data, process_namespaces=True, namespaces=namespaces).get('rdf:RDF')
      namestring = xml.get('channel').get('title').get('#text')
      name = namestring[len('Bank of Canada noon rate: '):]
      name = re.sub(r'\s*\(noon\)\s*', '', name)
      rate = xml.get('item').get('cb:statistics').get('cb:exchangeRate').get('cb:value').get('#text')
      return float(rate), name
  
  
  @commands('cur', 'currency', 'exchange')
  @example('.cur 20 EUR in USD')
  def exchange(bot, trigger):
      """Show the exchange rate between two currencies"""
      if not trigger.group(2):
          return bot.reply("No search term. An example: .cur 20 EUR in USD")
      match = regex.match(trigger.group(2))
      if not match:
          # It's apologetic, because it's using Canadian data.
          bot.reply("Sorry, I didn't understand the input.")
          return NOLIMIT
  
      amount, of, to = match.groups()
      try:
          amount = float(amount)
      except:
          bot.reply("Sorry, I didn't understand the input.")
      display(bot, amount, of, to)
  
  
  def display(bot, amount, of, to):
      if not amount:
          bot.reply("Zero is zero, no matter what country you're in.")
      try:
          of_rate, of_name = get_rate(of)
          if not of_name:
              bot.reply("Unknown currency: %s" % of)
              return
          to_rate, to_name = get_rate(to)
          if not to_name:
              bot.reply("Unknown currency: %s" % to)
              return
      except Exception:
          bot.reply("Something went wrong while I was getting the exchange rate.")
          return NOLIMIT
  
      result = amount / of_rate * to_rate
      bot.say("{} {} ({}) = {} {} ({})".format(amount, of.upper(), of_name,
                                               result, to.upper(), to_name))
  
  
  @commands('btc', 'bitcoin')
  @example('.btc 20 EUR')
  def bitcoin(bot, trigger):
      #if 2 args, 1st is number and 2nd is currency. If 1 arg, it's either the number or the currency.
      to = trigger.group(4)
      amount = trigger.group(3)
      if not to:
          to = trigger.group(3) or 'USD'
          amount = 1
  
      try:
          amount = float(amount)
      except:
          bot.reply("Sorry, I didn't understand the input.")
          return NOLIMIT
  
      display(bot, amount, 'BTC', to)
  