summaryrefslogblamecommitdiff
path: root/lib/telegram/room.ex
blob: cc10e90a1a16ed1e5593c5bd017e94ff531afa8a (plain) (tree)
1
2
3
4
5
                              



                             









                                                                              

     











                                                                           
                



                      
                                                                                                                                   

     
                        
                                                
                                                                        
                                                            












                                                                                                 

                                                                                          
                                   


                                                                                                



                                                                                
       





                                                                                                           

     
                                                                       
                                                                          










                                                      


                                                      







                                                                                                             
                                                      
                                                                   


                

                                                                                                                                                            










                                                                                    



                                            



                                                  



                                                                                                 
                                                
       


                
                                 
                                            


                

                                                                                                                               

























                                                                                          
                                                                                    
                                                                               




                                                                                                              
                                                                                   













                                                                                                            
   
defmodule Nola.TelegramRoom do
  require Logger
  @behaviour Telegram.ChatBot
  alias Telegram.Api

  @couch "bot-telegram-rooms"

  def rooms(), do: rooms(:with_docs)

  @spec rooms(:with_docs | :ids) :: [Map.t | integer( )]
  def rooms(:with_docs) do
    case Couch.get(@couch, :all_docs, include_docs: true) do
      {:ok, %{"rows" => rows}} -> {:ok, for(%{"doc" => doc} <- rows, do: doc)}
      error = {:error, _} -> error
    end
  end

  def rooms(:ids) do
    case Couch.get(@couch, :all_docs) do
      {:ok, %{"rows" => rows}} -> {:ok, for(%{"id" => id} <- rows, do: id)}
      error = {:error, _} -> error
    end
  end

  def room(id, opts \\ []) do
    Couch.get(@couch, id, opts)
  end

  # TODO: Create couch
  def setup() do
    :ok
  end

  def after_start() do
    for id <- room(:ids), do: Telegram.Bot.ChatBot.Chat.Session.Supervisor.start_child(Nola.Telegram, Integer.parse(id) |> elem(0))
  end

  @impl Telegram.ChatBot
  def init(id) when is_integer(id) and id < 0 do
    token = Keyword.get(Application.get_env(:nola, :telegram, []), :key)
    {:ok, chat} = Api.request(token, "getChat", chat_id: id)
    Logger.metadata(transport: :telegram, id: id, telegram_room_id: id)
    tg_room = case room(id) do
      {:ok, tg_room = %{"network" => _net, "channel" => _chan}} -> tg_room
      {:error, :not_found} ->
        [net, chan] = String.split(chat["title"], "/", parts: 2)
        {net, chan} = case IRC.Connection.get_network(net, chan) do
          %IRC.Connection{} -> {net, chan}
          _ -> {nil, nil}
        end
        {:ok, _id, _rev} = Couch.post(@couch, %{"_id" => id, "network" => net, "channel" => nil})
        {:ok, tg_room} = room(id)
        tg_room
    end
    %{"network" => net, "channel" => chan} = tg_room
    Logger.info("Starting ChatBot for room #{id} \"#{chat["title"]}\" #{inspect tg_room}")
    irc_plumbed = if net && chan do
        {:ok, _} = Registry.register(Nola.PubSub, "#{net}/#{chan}:messages", plugin: __MODULE__)
        {:ok, _} = Registry.register(Nola.PubSub, "#{net}/#{chan}:triggers", plugin: __MODULE__)
        {:ok, _} = Registry.register(Nola.PubSub, "#{net}/#{chan}:outputs", plugin: __MODULE__)
        true
    else
      Logger.warn("Did not found telegram match for #{id} \"#{chat["title"]}\"")
      false
    end
    {:ok, %{id: id, net: net, chan: chan, irc: irc_plumbed}}
  end

  def init(id) do
    Logger.error("telegram_room: bad id (not room id)", transport: :telegram, id: id, telegram_room_id: id)
    :ignoree
  end

  defp find_or_create_meta_account(from = %{"id" => user_id}, state) do
    if account = Nola.Account.find_meta_account("telegram-id", user_id) do
      account
    else
      first_name = Map.get(from, "first_name")
      last_name = Map.get(from, "last_name")
      name = [first_name, last_name]
      |> Enum.filter(& &1)
      |> Enum.join(" ")

      username = Map.get(from, "username", first_name)

      account = username
      |> Nola.Account.new_account()
      |> Nola.Account.update_account_name(name)
      |> Nola.Account.put_meta("telegram-id", user_id)

      Logger.info("telegram_room: created account #{account.id} for telegram user #{user_id}")
      account
    end
  end

  def handle_update(%{"message" => %{"from" => from = %{"id" => user_id}, "text" => text}}, _token, state) do
    account = find_or_create_meta_account(from, state)
    connection = IRC.Connection.get_network(state.net)
    IRC.send_message_as(account, state.net, state.chan, text, true)
    {:ok, state}
  end

  def handle_update(data = %{"message" => %{"from" => from = %{"id" => user_id}, "location" => %{"latitude" => lat, "longitude" => lon}}}, _token, state) do
    account = find_or_create_meta_account(from, state)
    connection = IRC.Connection.get_network(state.net)
    IRC.send_message_as(account, state.net, state.chan, "@ #{lat}, #{lon}", true)
    {:ok, state}
  end

  for type <- ~w(photo voice video document animation) do
    def handle_update(data = %{"message" => %{unquote(type) => _}}, token, state) do
      upload(unquote(type), data, token, state)
    end
  end

  def handle_update(update, token, state) do
    {:ok, state}
  end

  def handle_info({:irc, _, _, message}, state) do
    handle_info({:irc, nil, message}, state)
  end

  def handle_info({:irc, _, message = %IRC.Message{sender: %{nick: nick}, text: text}}, state) do
    if Map.get(message.meta, :from) == self() do
    else
      body = if Map.get(message.meta, :self), do: text, else: "<#{nick}> #{text}"
      Nola.Telegram.send_message(state.id, body)
    end
    {:ok, state}
  end

  def handle_info(info, state) do
    Logger.info("UNhandled #{inspect info}")
    {:ok, state}
  end

  defp upload(_type, %{"message" => m = %{"chat" => %{"id" => chat_id}, "from" => from = %{"id" => user_id}}}, token, state) do
    account = find_or_create_meta_account(from, state)
    if account do
      {content, type} = cond do
        m["photo"] -> {m["photo"], "photo"}
        m["voice"] -> {m["voice"], "voice message"}
        m["video"] -> {m["video"], "video"}
        m["document"] -> {m["document"], "file"}
        m["animation"] -> {m["animation"], "gif"}
      end

      file = if is_list(content) && Enum.count(content) > 1 do
        Enum.sort_by(content, fn(p) -> p["file_size"] end, &>=/2)
        |> List.first()
      else
        content
      end

      file_id = file["file_id"]
      file_unique_id = file["file_unique_id"]
      text = if(m["caption"], do: m["caption"] <> " ", else: "")

      spawn(fn() ->
        with \
          {:ok, file} <- Telegram.Api.request(token, "getFile", file_id: file_id),
          path = "https://api.telegram.org/file/bot#{token}/#{file["file_path"]}",
          {:ok, %HTTPoison.Response{status_code: 200, body: body}} <- HTTPoison.get(path),
          <<smol_body::binary-size(20), _::binary>> = body,
          {:ok, magic} <- GenMagic.Pool.perform(Nola.GenMagic, {:bytes, smol_body}),
          bucket = Application.get_env(:nola, :s3, []) |> Keyword.get(:bucket),
          ext = Path.extname(file["file_path"]),
          s3path = "#{account.id}/#{file_unique_id}#{ext}",
          s3req = ExAws.S3.put_object(bucket, s3path, body, acl: :public_read, content_type: magic.mime_type),
          {:ok, _} <- ExAws.request(s3req)
        do
          path = NolaWeb.Router.Helpers.url(NolaWeb.Endpoint) <> "/files/#{s3path}"
          txt = "#{type}: #{text}#{path}"
          connection = IRC.Connection.get_network(state.net)
          IRC.send_message_as(account, state.net, state.chan, txt, true)
        else
          error ->
            Telegram.Api.request(token, "sendMessage", chat_id: chat_id, text: "File upload failed, sorry.")
            Logger.error("Failed upload from Telegram: #{inspect error}")
        end
      end)

      {:ok, state}
    end
  end

end