diff options
Diffstat (limited to 'lib/plugins/finance.ex')
-rw-r--r-- | lib/plugins/finance.ex | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/lib/plugins/finance.ex b/lib/plugins/finance.ex new file mode 100644 index 0000000..d6f890a --- /dev/null +++ b/lib/plugins/finance.ex @@ -0,0 +1,190 @@ +defmodule Nola.Plugins.Finance do + require Logger + + @moduledoc """ + # finance + + Données de [alphavantage.co](https://alphavantage.co). + + ## forex / monnaies / crypto-monnaies + + * **`!forex <MONNAIE1> [MONNAIE2]`**: taux de change entre deux monnaies. + * **`!forex <MONTANT> <MONNAIE1> <MONNAIE2>`**: converti `montant` entre deux monnaies + * **`?currency <recherche>`**: recherche une monnaie + + Utiliser le symbole des monnaies (EUR, USD, ...). + + ## bourses + + * **`!stocks <SYMBOLE>`** + * **`?stocks <recherche>`** cherche un symbole + + Pour les symboles non-US, ajouter le suffixe (RNO Paris: RNO.PAR). + + """ + + @currency_list "http://www.alphavantage.co/physical_currency_list/" + @crypto_list "http://www.alphavantage.co/digital_currency_list/" + + HTTPoison.start() + load_currency = fn(url) -> + resp = HTTPoison.get!(url) + resp.body + |> String.strip() + |> String.split("\n") + |> Enum.drop(1) + |> Enum.map(fn(line) -> + [symbol, name] = line + |> String.strip() + |> String.split(",", parts: 2) + {symbol, name} + end) + |> Enum.into(Map.new) + end + fiat = load_currency.(@currency_list) + crypto = load_currency.(@crypto_list) + @currencies Map.merge(fiat, crypto) + + def irc_doc, do: @moduledoc + def start_link() do + GenServer.start_link(__MODULE__, [], name: __MODULE__) + end + + def init([]) do + regopts = [plugin: __MODULE__] + {:ok, _} = Registry.register(IRC.PubSub, "trigger:forex", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:currency", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:stocks", regopts) + {:ok, nil} + end + + + def handle_info({:irc, :trigger, "stocks", message = %{trigger: %{type: :query, args: args = search}}}, state) do + search = Enum.join(search, "%20") + url = "https://www.alphavantage.co/query?function=SYMBOL_SEARCH&keywords=#{search}&apikey=#{api_key()}" + case HTTPoison.get(url) do + {:ok, %HTTPoison.Response{status_code: 200, body: data}} -> + data = Poison.decode!(data) + if error = Map.get(data, "Error Message") do + Logger.error("AlphaVantage API invalid request #{url} - #{inspect error}") + message.replyfun.("stocks: requête invalide") + else + items = for item <- Map.get(data, "bestMatches") do + symbol = Map.get(item, "1. symbol") + name = Map.get(item, "2. name") + type = Map.get(item, "3. type") + region = Map.get(item, "4. region") + currency = Map.get(item, "8. currency") + "#{symbol}: #{name} (#{region}; #{currency}; #{type})" + end + |> Enum.join(", ") + items = if items == "" do + "no results!" + else + items + end + message.replyfun.(items) + end + {:ok, resp = %HTTPoison.Response{status_code: code}} -> + Logger.error "AlphaVantage API error: #{code} #{url} - #{inspect resp}" + message.replyfun.("forex: erreur (api #{code})") + {:error, %HTTPoison.Error{reason: error}} -> + Logger.error "AlphaVantage HTTP error: #{inspect error}" + message.replyfun.("forex: erreur (http #{inspect error})") + end + {:noreply, state} + end + + def handle_info({:irc, :trigger, "stocks", message = %{trigger: %{type: :bang, args: args = [symbol]}}}, state) do + url = "https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=#{symbol}&apikey=#{api_key()}" + case HTTPoison.get(url) do + {:ok, %HTTPoison.Response{status_code: 200, body: data}} -> + data = Poison.decode!(data) + if error = Map.get(data, "Error Message") do + Logger.error("AlphaVantage API invalid request #{url} - #{inspect error}") + message.replyfun.("stocks: requête invalide") + else + data = Map.get(data, "Global Quote") + open = Map.get(data, "02. open") + high = Map.get(data, "03. high") + low = Map.get(data, "04. low") + price = Map.get(data, "05. price") + volume = Map.get(data, "06. volume") + prev_close = Map.get(data, "08. previous close") + change = Map.get(data, "09. change") + change_pct = Map.get(data, "10. change percent") + + msg = "#{symbol}: #{price} #{change} [#{change_pct}] (high: #{high}, low: #{low}, open: #{open}, prev close: #{prev_close}) (volume: #{volume})" + message.replyfun.(msg) + end + {:ok, resp = %HTTPoison.Response{status_code: code}} -> + Logger.error "AlphaVantage API error: #{code} #{url} - #{inspect resp}" + message.replyfun.("stocks: erreur (api #{code})") + {:error, %HTTPoison.Error{reason: error}} -> + Logger.error "AlphaVantage HTTP error: #{inspect error}" + message.replyfun.("stocks: erreur (http #{inspect error})") + end + {:noreply, state} + end + + + def handle_info({:irc, :trigger, "forex", message = %{trigger: %{type: :bang, args: args = [_ | _]}}}, state) do + {amount, from, to} = case args do + [amount, from, to] -> + {amount, _} = Float.parse(amount) + {amount, from, to} + [from, to] -> + {1, from, to} + [from] -> + {1, from, "EUR"} + end + url = "https://www.alphavantage.co/query?function=CURRENCY_EXCHANGE_RATE&from_currency=#{from}&to_currency=#{to}&apikey=#{api_key()}" + case HTTPoison.get(url) do + {:ok, %HTTPoison.Response{status_code: 200, body: data}} -> + data = Poison.decode!(data) + if error = Map.get(data, "Error Message") do + Logger.error("AlphaVantage API invalid request #{url} - #{inspect error}") + message.replyfun.("forex: requête invalide") + else + data = Map.get(data, "Realtime Currency Exchange Rate") + from_name = Map.get(data, "2. From_Currency Name") + to_name = Map.get(data, "4. To_Currency Name") + rate = Map.get(data, "5. Exchange Rate") + {rate, _} = Float.parse(rate) + value = amount*rate + message.replyfun.("#{amount} #{from} (#{from_name}) -> #{value} #{to} (#{to_name}) (#{rate})") + end + {:ok, resp = %HTTPoison.Response{status_code: code}} -> + Logger.error "AlphaVantage API error: #{code} #{url} - #{inspect resp}" + message.replyfun.("forex: erreur (api #{code})") + {:error, %HTTPoison.Error{reason: error}} -> + Logger.error "AlphaVantage HTTP error: #{inspect error}" + message.replyfun.("forex: erreur (http #{inspect error})") + end + {:noreply, state} + end + + def handle_info({:irc, :trigger, "currency", message = %{trigger: %{type: :query, args: args = search}}}, state) do + search = Enum.join(search, " ") + results = Enum.filter(@currencies, fn({symbol, name}) -> + String.contains?(String.downcase(name), String.downcase(search)) || String.contains?(String.downcase(symbol), String.downcase(search)) + end) + |> Enum.map(fn({symbol, name}) -> + "#{symbol}: #{name}" + end) + |> Enum.join(", ") + + if results == "" do + message.replyfun.("no results!") + else + message.replyfun.(results) + end + {:noreply, state} + end + + defp api_key() do + Application.get_env(:nola, :alphavantage, []) + |> Keyword.get(:api_key, "demo") + end + +end |