summaryrefslogtreecommitdiff
path: root/lib/nola_plugins/finance_plugin.ex
diff options
context:
space:
mode:
Diffstat (limited to 'lib/nola_plugins/finance_plugin.ex')
-rw-r--r--lib/nola_plugins/finance_plugin.ex190
1 files changed, 190 insertions, 0 deletions
diff --git a/lib/nola_plugins/finance_plugin.ex b/lib/nola_plugins/finance_plugin.ex
new file mode 100644
index 0000000..16d06ee
--- /dev/null
+++ b/lib/nola_plugins/finance_plugin.ex
@@ -0,0 +1,190 @@
+defmodule Nola.IRC.FinancePlugin 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