summaryrefslogblamecommitdiff
path: root/lib/nola_plugins/finance_plugin.ex
blob: 16d06ee55ca78b4f8ca3ce3cc6f113956a0a28fd (plain) (tree)
1
                                   























                                                                                        

                                                                     





















                                       
                                                          


                 



                                                                         































































































































                                                                                                                                                          
                                                 



                                    
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