diff --git a/lib/archethic/oracle_chain/services/uco_price/providers/coin_marketcap.ex b/lib/archethic/oracle_chain/services/uco_price/providers/coin_marketcap.ex index 1d08eecc5..9cd461540 100644 --- a/lib/archethic/oracle_chain/services/uco_price/providers/coin_marketcap.ex +++ b/lib/archethic/oracle_chain/services/uco_price/providers/coin_marketcap.ex @@ -43,30 +43,24 @@ defmodule Archethic.OracleChain.Services.UCOPrice.Providers.CoinMarketCap do :httpc.request(:get, {query, headers}, httpc_options, []), {:ok, document} <- Floki.parse_document(body) do price = - case Floki.find(document, "div.priceTitle > div.priceValue > span") do - [] -> - regex = ~r/price today is (.+) with a/ - - Floki.find(document, "meta[name=description]") - |> Floki.attribute("content") - |> Enum.join() - |> then(&Regex.run(regex, &1, capture: :all_but_first)) - |> Enum.join() - |> String.graphemes() - |> Enum.filter(&(&1 in [".", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"])) - |> Enum.into("") - |> String.to_float() - - element -> - element - |> Floki.text() - |> String.graphemes() - |> Enum.filter(&(&1 in [".", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"])) - |> Enum.into("") - |> String.to_float() - end - - {:ok, {pair, [price]}} + extract_methods() + |> Enum.reduce_while(nil, fn extract_fn, acc -> + try do + {:halt, extract_fn.(document)} + rescue + _ -> + {:cont, acc} + catch + _ -> + {:cont, acc} + end + end) + + if is_number(price) do + {:ok, {pair, [price]}} + else + {:error, :not_a_number} + end else {:ok, {{_, _, status}, _, _}} -> {:error, status} @@ -85,4 +79,61 @@ defmodule Archethic.OracleChain.Services.UCOPrice.Providers.CoinMarketCap do {:ok, returned_prices} end + + defp extract_methods() do + [ + &extract_method1/1, + &extract_method2/1, + &extract_method3/1, + # if every other failed + &fallback_error/1 + ] + end + + defp extract_method1(document) do + Floki.find(document, "div.priceTitle > div.priceValue > span") + |> Floki.text() + |> String.graphemes() + |> Enum.filter(&(&1 in [".", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"])) + |> Enum.into("") + |> String.to_float() + end + + defp extract_method2(document) do + regex = ~r/price today is (.+) with a/ + + Floki.find(document, "meta[name=description]") + |> Floki.attribute("content") + |> Enum.join() + |> then(&Regex.run(regex, &1, capture: :all_but_first)) + |> Enum.join() + |> String.graphemes() + |> Enum.filter(&(&1 in [".", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"])) + |> Enum.into("") + |> String.to_float() + end + + defp extract_method3(document) do + document + |> Floki.find("#__NEXT_DATA__") + |> Floki.text(js: true) + |> Jason.decode!() + |> get_in(["props", "pageProps", "info", "statistics", "price"]) + end + + defp fallback_error(document) do + path = "/tmp/coinmarketcap.html" + + case File.write(path, Floki.raw_html(document, pretty: true)) do + :ok -> + Logger.warning( + "Coinmarketcap failed to be parsed, you may find the page we receive at #{path}" + ) + + {:error, _posix} -> + Logger.warning("Coinmarketcap failed to be parsed") + end + + throw("coinmarketcap failed to parse") + end end diff --git a/test/archethic/oracle_chain/services/uco_price/coin_marketcap_test.exs b/test/archethic/oracle_chain/services/uco_price/coin_marketcap_test.exs index 3ba33ba49..3911b9a8f 100644 --- a/test/archethic/oracle_chain/services/uco_price/coin_marketcap_test.exs +++ b/test/archethic/oracle_chain/services/uco_price/coin_marketcap_test.exs @@ -4,7 +4,7 @@ defmodule Archethic.OracleChain.Services.UCOPrice.Providers.CoinMarketCapTest do alias Archethic.OracleChain.Services.UCOPrice.Providers.CoinMarketCap @tag oracle_provider: true - test "fetch/1 should get the current UCO price from CoinGecko" do + test "fetch/1 should get the current UCO price from CoinMarketCap" do assert {:ok, %{"eur" => prices}} = CoinMarketCap.fetch(["eur"]) assert is_list(prices) end diff --git a/test/archethic/oracle_chain/services/uco_price/coin_paprika_test.exs b/test/archethic/oracle_chain/services/uco_price/coin_paprika_test.exs index a5a194173..392d27cf8 100644 --- a/test/archethic/oracle_chain/services/uco_price/coin_paprika_test.exs +++ b/test/archethic/oracle_chain/services/uco_price/coin_paprika_test.exs @@ -4,7 +4,7 @@ defmodule Archethic.OracleChain.Services.UCOPrice.Providers.CoinPaprikaTest do alias Archethic.OracleChain.Services.UCOPrice.Providers.CoinPaprika @tag oracle_provider: true - test "fetch/1 should get the current UCO price from CoinGecko" do + test "fetch/1 should get the current UCO price from CoinPaprika" do assert {:ok, %{"eur" => prices}} = CoinPaprika.fetch(["eur"]) assert is_list(prices) end