From 829d69d81f60d0a222bf5d1a38f78e07ae69dfeb Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Tue, 20 Apr 2021 10:34:58 -0500 Subject: [PATCH] Add helper to fetch current BTC sell prices Scrapes the sell price for Bitcoin from canadianbitcoins.com USD price is done by converting this CAD sell price to USD via openexchangerates --- Gemfile | 2 ++ lib/btc_sell_prices.rb | 57 ++++++++++++++++++++++++++++++++++++ test/test_btc_sell_prices.rb | 31 ++++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 lib/btc_sell_prices.rb create mode 100644 test/test_btc_sell_prices.rb diff --git a/Gemfile b/Gemfile index e777509..c32ccea 100644 --- a/Gemfile +++ b/Gemfile @@ -6,9 +6,11 @@ gem "blather", git: "https://github.com/singpolyma/blather.git", branch: "ergono gem "braintree" gem "dhall" gem "em-hiredis" +gem "em-http-request" gem "em-pg-client", git: "https://github.com/royaltm/ruby-em-pg-client" gem "em_promise.rb" gem "eventmachine" +gem "money-open-exchange-rates" group(:development) do gem "pry-reload" diff --git a/lib/btc_sell_prices.rb b/lib/btc_sell_prices.rb new file mode 100644 index 0000000..3262110 --- /dev/null +++ b/lib/btc_sell_prices.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require "em-http" +require "money/bank/open_exchange_rates_bank" +require "nokogiri" + +require_relative "em" + +class BTCSellPrices + def initialize(redis, oxr_app_id) + @redis = redis + @oxr = Money::Bank::OpenExchangeRatesBank.new( + Money::RatesStore::Memory.new + ) + @oxr.app_id = oxr_app_id + end + + def cad + fetch_canadianbitcoins.then do |http| + canadianbitcoins = Nokogiri::HTML.parse(http.response) + + bitcoin_row = canadianbitcoins.at("#ticker > table > tbody > tr") + raise "Bitcoin row has moved" unless bitcoin_row.at("td").text == "Bitcoin" + + BigDecimal.new( + bitcoin_row.at("td:nth-of-type(3)").text.match(/^\$(\d+\.\d+)/)[1] + ) + end + end + + def usd + EMPromise.all([cad, cad_to_usd]).then { |(a, b)| a * b } + end + +protected + + def fetch_canadianbitcoins + EM::HttpRequest.new( + "https://www.canadianbitcoins.com", + tls: { verify_peer: true } + ).get + end + + def cad_to_usd + @redis.get("cad_to_usd").then do |rate| + next rate.to_f if rate + + EM.promise_defer { + # OXR gem is not async, so defer to threadpool + oxr.update_rates + oxr.get_rate("CAD", "USD") + }.then do |orate| + @redis.set("cad_to_usd", orate, ex: 60 * 60).then { orate } + end + end + end +end diff --git a/test/test_btc_sell_prices.rb b/test/test_btc_sell_prices.rb new file mode 100644 index 0000000..dc53a1d --- /dev/null +++ b/test/test_btc_sell_prices.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "em-hiredis" +require "test_helper" +require "btc_sell_prices" + +class BTCSellPricesTest < Minitest::Test + def setup + @redis = Minitest::Mock.new + @subject = BTCSellPrices.new(@redis, "") + end + + def test_cad + stub_request(:get, "https://www.canadianbitcoins.com").to_return( + body: "
" \ + "" + ) + assert_equal BigDecimal.new(123), @subject.cad.sync + end + em :test_cad + + def test_usd + stub_request(:get, "https://www.canadianbitcoins.com").to_return( + body: "
Bitcoin$123.00
" \ + "" + ) + @redis.expect(:get, EMPromise.resolve("0.5"), ["cad_to_usd"]) + assert_equal BigDecimal.new(123) / 2, @subject.usd.sync + end + em :test_usd +end -- 2.38.5
Bitcoin$123.00