96 lines
2.5 KiB
Ruby
96 lines
2.5 KiB
Ruby
|
|
# frozen_string_literal: true
|
||
|
|
|
||
|
|
require "net/http"
|
||
|
|
require "ox"
|
||
|
|
|
||
|
|
require "provider/adapters/adapter"
|
||
|
|
|
||
|
|
class Provider
|
||
|
|
module Adapters
|
||
|
|
# Bank of Tanzania. Publishes daily exchange rates for 35+ currencies
|
||
|
|
# against the Tanzanian shilling (TZS). Uses the Mean column (midpoint
|
||
|
|
# of buy/sell). Publishes 7 days a week.
|
||
|
|
class BOTA < Adapter
|
||
|
|
BASE_URL = "https://www.bot.go.tz"
|
||
|
|
EXCLUDED_CURRENCIES = ["GOLD", "ATS", "NLG", "MZM", "ZWD", "CUC"].freeze
|
||
|
|
|
||
|
|
class << self
|
||
|
|
def backfill_range = 30
|
||
|
|
end
|
||
|
|
|
||
|
|
def fetch(after: nil, upto: nil)
|
||
|
|
uri = URI("#{BASE_URL}/ExchangeRate/previous_rates")
|
||
|
|
response = Net::HTTP.post_form(uri, {
|
||
|
|
"dateFrom" => after.strftime("%m/%d/%Y"),
|
||
|
|
"dateTo" => (upto || Date.today).strftime("%m/%d/%Y"),
|
||
|
|
})
|
||
|
|
|
||
|
|
sleep(2)
|
||
|
|
parse(response.body)
|
||
|
|
end
|
||
|
|
|
||
|
|
def parse(html)
|
||
|
|
doc = Ox.load(html, mode: :generic, effort: :tolerant, smart: true)
|
||
|
|
rows = find_table_rows(doc)
|
||
|
|
return [] unless rows
|
||
|
|
|
||
|
|
rows.filter_map { |row| parse_row(row) }
|
||
|
|
end
|
||
|
|
|
||
|
|
private
|
||
|
|
|
||
|
|
def find_table_rows(node)
|
||
|
|
return unless node.respond_to?(:nodes)
|
||
|
|
|
||
|
|
if node.respond_to?(:value) && node.value == "tbody"
|
||
|
|
return node.nodes&.select { |n| n.respond_to?(:value) && n.value == "tr" }
|
||
|
|
end
|
||
|
|
|
||
|
|
node.nodes&.each do |child|
|
||
|
|
result = find_table_rows(child)
|
||
|
|
return result if result
|
||
|
|
end
|
||
|
|
|
||
|
|
nil
|
||
|
|
end
|
||
|
|
|
||
|
|
def parse_row(row)
|
||
|
|
cells = row.nodes&.select { |n| n.respond_to?(:value) && n.value == "td" }
|
||
|
|
return unless cells && cells.size >= 6
|
||
|
|
|
||
|
|
currency = cell_text(cells[1])&.strip&.upcase
|
||
|
|
return unless currency
|
||
|
|
return if EXCLUDED_CURRENCIES.include?(currency)
|
||
|
|
|
||
|
|
mean_str = cell_text(cells[4])
|
||
|
|
date_str = cell_text(cells[5])
|
||
|
|
return unless mean_str && date_str
|
||
|
|
|
||
|
|
rate = Float(mean_str.tr(",", ""))
|
||
|
|
return if rate.zero?
|
||
|
|
|
||
|
|
date = Date.parse(date_str.strip)
|
||
|
|
|
||
|
|
{ date:, base: currency, quote: "TZS", rate: }
|
||
|
|
end
|
||
|
|
|
||
|
|
def cell_text(node)
|
||
|
|
return unless node
|
||
|
|
|
||
|
|
texts = []
|
||
|
|
collect_text(node, texts)
|
||
|
|
text = texts.join
|
||
|
|
text.empty? ? nil : text
|
||
|
|
end
|
||
|
|
|
||
|
|
def collect_text(node, texts)
|
||
|
|
if node.is_a?(String)
|
||
|
|
texts << node
|
||
|
|
elsif node.respond_to?(:nodes) && node.nodes
|
||
|
|
node.nodes.each { |child| collect_text(child, texts) }
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|