summaryrefslogblamecommitdiff
path: root/lib/lsg_irc/sms_plugin.ex
blob: d8f7387a30d2b95780e70dfeb98278db8bc4b58b (plain) (tree)
1
                               


































                                                                                                                              
                          








                                                             
                                                                  
                                                       




                    
                                                                              


                     
                                                          


               
                                                                     




























                                                                               
                                                                                 

                                 
           
































                                                                                                                               
                                                                                   
                         
                                                                                    


























                                                                              
                                    





                           
defmodule Nola.IRC.SmsPlugin do
  @moduledoc """
  ## sms

  * **!sms `<nick>` `<message>`** envoie un SMS.
  """
  def short_irc_doc, do: false
  def irc_doc, do: @moduledoc
  require Logger

  def incoming(from, "enable "<>key) do
    key = String.trim(key)
    account = IRC.Account.find_meta_account("sms-validation-code", String.downcase(key))
    if account do
      net = IRC.Account.get_meta(account, "sms-validation-target")
      IRC.Account.put_meta(account, "sms-number", from)
      IRC.Account.delete_meta(account, "sms-validation-code")
      IRC.Account.delete_meta(account, "sms-validation-number")
      IRC.Account.delete_meta(account, "sms-validation-target")
      IRC.Connection.broadcast_message(net, account, "SMS Number #{from} added!")
      send_sms(from, "Yay! Number linked to account #{account.name}")
    end
  end

  def incoming(from, message) do
    account = IRC.Account.find_meta_account("sms-number", from)
    if account do
      reply_fun = fn(text) ->
        send_sms(from, text)
      end
      trigger_text = if Enum.any?(IRC.Connection.triggers(), fn({trigger, _}) -> String.starts_with?(message, trigger) end) do
        message
      else
        "!"<>message
      end
      message = %IRC.Message{
        id: FlakeId.get(),
        transport: :sms,
        network: "sms",
        channel: nil,
        text: message,
        account: account,
        sender: %ExIRC.SenderInfo{nick: account.name},
        replyfun: reply_fun,
        trigger: IRC.Connection.extract_trigger(trigger_text)
      }
      Logger.debug("converted sms to message: #{inspect message}")
      IRC.Connection.publish(message, ["messages:sms"])
      message
    end
  end

  def my_number() do
    Keyword.get(Application.get_env(:nola, :sms, []), :number, "+33000000000")
  end

  def start_link() do
    GenServer.start_link(__MODULE__, [], name: __MODULE__)
  end

  def path() do
    account = Keyword.get(Application.get_env(:nola, :sms), :account)
    "https://eu.api.ovh.com/1.0/sms/#{account}"
  end

  def path(rest) do
    Path.join(path(), rest)
  end

  def send_sms(number, text) do
    url = path("/virtualNumbers/#{my_number()}/jobs")
    body = %{
      "message" => text,
      "receivers" => [number],
      #"senderForResponse" => true,
      #"noStopClause" => true,
      "charset" => "UTF-8",
      "coding" => "8bit"
    } |> Poison.encode!()
    headers = [{"content-type", "application/json"}] ++ sign("POST", url, body)
    options = []
    case HTTPoison.post(url, body, headers, options) do
      {:ok, %HTTPoison.Response{status_code: 200}} -> :ok
      {:ok, %HTTPoison.Response{status_code: code} = resp} ->
        Logger.error("SMS Error: #{inspect resp}")
        {:error, code}
      {:error, error} -> {:error, error}
    end
  end

  def init([]) do
    {:ok, _} = Registry.register(IRC.PubSub, "trigger:sms", [plugin: __MODULE__])
    :ok = register_ovh_callback()
    {:ok, %{}}
    :ignore
  end

  def handle_info({:irc, :trigger, "sms", m = %IRC.Message{trigger: %IRC.Trigger{type: :bang, args: [nick | text]}}}, state) do
    with \
        {:tree, false} <- {:tree, m.sender.nick == "Tree"},
        {_, %IRC.Account{} = account} <- {:account, IRC.Account.find_always_by_nick(m.network, m.channel, nick)},
        {_, number} when not is_nil(number) <- {:number, IRC.Account.get_meta(account, "sms-number")}
    do
      text = Enum.join(text, " ")
      sender = if m.channel do
        "#{m.channel} <#{m.sender.nick}> "
      else
        "<#{m.sender.nick}> "
      end
      case send_sms(number, sender<>text) do
        :ok -> m.replyfun.("sent!")
        {:error, error} -> m.replyfun.("not sent, error: #{inspect error}")
      end
    else
      {:tree, _} -> m.replyfun.("Tree: va en enfer")
      {:account, _} -> m.replyfun.("#{nick} not known")
      {:number, _} -> m.replyfun.("#{nick} have not enabled sms")
    end
    {:noreply, state}
  end

  def handle_info(msg, state) do
    {:noreply, state}
  end

  defp register_ovh_callback() do
    url = path()
    body = %{
      "callBack" =>NolaWeb.Router.Helpers.sms_url(NolaWeb.Endpoint, :ovh_callback),
      "smsResponse" => %{
        "cgiUrl" => NolaWeb.Router.Helpers.sms_url(NolaWeb.Endpoint, :ovh_callback),
        "responseType" => "cgi"
      }
    } |> Poison.encode!()
    headers = [{"content-type", "application/json"}] ++ sign("PUT", url, body)
    options = []
    case HTTPoison.put(url, body, headers, options) do
      {:ok, %HTTPoison.Response{status_code: 200}} ->
        :ok
      error -> error
    end
  end

  defp sign(method, url, body) do
    ts = DateTime.utc_now() |> DateTime.to_unix()
    as = env(:app_secret)
    ck = env(:consumer_key)
    sign = Enum.join([as, ck, String.upcase(method), url, body, ts], "+")
    sign_hex = :crypto.hash(:sha, sign) |> Base.encode16(case: :lower)
    headers = [{"X-OVH-Application", env(:app_key)}, {"X-OVH-Timestamp", ts},
      {"X-OVH-Signature", "$1$"<>sign_hex}, {"X-Ovh-Consumer", ck}]
  end

  def parse_number(num) do
    {:error, :todo}
  end

  defp env() do
    Application.get_env(:nola, :sms)
  end

  defp env(key) do
    Keyword.get(env(), key)
  end
end