lib/ex_tdlib.ex

defmodule ExTDLib do
  @moduledoc """
  This module allow you to interact with and manage sessions.
  """

  alias ExTDLib.Method
  alias ExTDLib.Session
  alias ExTDLib.Session.Registry

  @default_config %Method.SetTdlibParameters{
    database_encryption_key: nil,
    use_test_dc: false,
    database_directory: "/tmp/tdlib",
    files_directory: "",
    use_file_database: true,
    use_chat_info_database: true,
    use_message_database: true,
    use_secret_chats: false,
    api_id: "0",
    api_hash: "0",
    system_language_code: "en",
    device_model: "Unknown",
    system_version: "Unknown",
    application_version: "Unknown",
    enable_storage_optimizer: true,
    ignore_file_names: true
  }

  @doc """
  Configuration template for TDLib, to be modified and used as parameter of
  `open/3`.

  See `ExTDLib.Object.TdlibParameters` and
  [core.telegram.org/tdlib/options](https://core.telegram.org/tdlib/options)
  for details. You can obtain an `:api_id` and an `:api_hash` on
  [my.telegram.org](https://my.telegram.org/) : they are required by TDLib,
  **don't forget to set them !**

  Be careful not to use the same `:database_directory` for two different
  sessions !
  """
  def default_config, do: @default_config

  @doc """
  Open a new session. Spawns a new instance of `tdlib-json-cli`.

  * `session_name` is the identifier of the session
  * `client_pid` is the PID of the process receiving the incoming messages
    (`{:recv, struct}`)
  * `config` is the configuration of TDLib, see `default_config/0`
  * `encryption_key` is the key used to encrypt the local database, it allows to
  store the session's cache and authorization key even if the client is offline

  Return either `{:ok, pid}` or `{:error, reason}`.
  """
  def open(session_name, client_pid, config, encryption_key \\ "") do
    Session.create(session_name, client_pid, config, encryption_key)
  end

  @doc """
  Close the session identified by `session_name`.
  """
  def close(session_name) do
    Session.destroy(session_name)
  end

  @doc """
  Transmit a message over the session identified by `session_name`.

  The parameter `msg` must be a struct (any map in reality) since it is
  directly encoded into JSON and transmitted via TDLib. You should use the
  structures generated from TDLib's documentation and provided by the
  submodules of `ExTDLib.Object` and `ExTDLib.Methods`.

  Alternatively it is also possible to directly provide an already encoded
  binary or string, althrough you should not need it.
  """
  def transmit(session_name, msg) when is_map(msg) do
    json =
      msg
      |> Map.delete(:__struct__)
      |> Map.new(fn {k, v} -> {k, transform_struct(v)} end)
      |> Jason.encode!()

    transmit(session_name, json)
  end

  def transmit(session_name, json) when is_binary(json) do
    backend_pid = Registry.get(session_name, :backend_pid)
    GenServer.call(backend_pid, {:transmit, json})
  end

  @doc """
   Set the process receiving the incoming update for session
   `session_name` to `client_pid`. The incoming messages can be handled with
   `def handle_info({:recv, msg}, state), do: ...` using GenServer.

   The client is initially set when the session is create, using `open/3`.
  """
  def update_client(session_name, client_pid) do
    handler_pid = Registry.get(session_name, :handler_pid)
    GenServer.call(handler_pid, {:set_client, client_pid})
  end

  @doc false
  def get_backend_binary do
    config = Application.get_env(:ex_tdlib, :backend_binary)

    case config do
      nil -> Path.join(Mix.Project.build_path(), "/lib/tdlib_json_cli/bin/tdlib_json_cli")
      _ -> config
    end
  end

  @doc """
  Converts the first letter of a string to uppercase, while leaving the rest of the string unchanged.
  """
  def titlecase_once(str) do
    first_letter = String.first(str)
    String.replace_prefix(str, first_letter, String.upcase(first_letter))
  end

  defp transform_struct(map) when is_map(map) do
    map
    |> Map.delete(:__struct__)
    |> Map.new(fn {k, v} -> {k, transform_struct(v)} end)
  end

  defp transform_struct(value), do: value
end