defmodule WandererApp.Character do @moduledoc false require Logger @read_character_wallet_scope "esi-wallet.read_character_wallet.v1" @read_corp_wallet_scope "esi-wallet.read_corporation_wallets.v1" def get_character(character_id) when not is_nil(character_id) do case Cachex.get(:character_cache, character_id) do {:ok, nil} -> case WandererApp.Api.Character.by_id(character_id) do {:ok, character} -> Cachex.put(:character_cache, character_id, character) {:ok, character} _ -> {:error, :not_found} end {:ok, character} -> {:ok, character} end end def get_character(_character_id), do: {:ok, nil} def get_character!(character_id) do case get_character(character_id) do {:ok, character} -> character _ -> Logger.error("Failed to get character #{character_id}") nil end end def get_character_eve_ids!(character_ids), do: character_ids |> Enum.map(fn character_id -> character_id |> get_character!() |> Map.get(:eve_id) end) def update_character(character_id, character_update) do Cachex.get_and_update(:character_cache, character_id, fn character -> case character do nil -> case WandererApp.Api.Character.by_id(character_id) do {:ok, character} -> {:commit, Map.merge(character, character_update)} _ -> {:ignore, nil} end _ -> {:commit, Map.merge(character, character_update)} end end) end def get_character_state(character_id) do case Cachex.get(:character_state_cache, character_id) do {:ok, nil} -> character_state = WandererApp.Character.Tracker.init(character_id: character_id) Cachex.put(:character_state_cache, character_id, character_state) {:ok, character_state} {:ok, character_state} -> {:ok, character_state} end end def get_character_state!(character_id) do case get_character_state(character_id) do {:ok, character_state} -> character_state _ -> Logger.error("Failed to get character_state #{character_id}") throw("Failed to get character_state #{character_id}") end end def update_character_state(character_id, character_state_update) do Cachex.get_and_update(:character_state_cache, character_id, fn character_state -> case character_state do nil -> new_state = WandererApp.Character.Tracker.init(character_id: character_id) :telemetry.execute([:wanderer_app, :character, :tracker, :started], %{count: 1}) {:commit, Map.merge(new_state, character_state_update)} _ -> {:commit, Map.merge(character_state, character_state_update)} end end) end def delete_character_state(character_id) do Cachex.del(:character_state_cache, character_id) end def set_autopilot_waypoint( character_id, destination_id, opts ) do {:ok, %{access_token: access_token}} = WandererApp.Character.get_character(character_id) WandererApp.Esi.set_autopilot_waypoint( opts[:add_to_beginning], opts[:clear_other_waypoints], destination_id, access_token: access_token ) :ok end def search(character_id, opts \\ []) do {:ok, %{access_token: access_token, eve_id: eve_id} = _character} = get_character(character_id) case WandererApp.Esi.search(eve_id |> String.to_integer(), access_token: access_token, character_id: character_id, refresh_token?: true, params: opts[:params] ) do {:ok, result} -> {:ok, result |> _prepare_search_results()} {:error, error} -> Logger.warning("#{__MODULE__} failed search: #{inspect(error)}") {:ok, []} end end def can_track_wallet?(%{scopes: scopes} = _character) when not is_nil(scopes) do scopes |> String.split(" ") |> Enum.member?(@read_character_wallet_scope) end def can_track_wallet?(_), do: false def can_track_corp_wallet?(%{scopes: scopes} = _character) when not is_nil(scopes) do scopes |> String.split(" ") |> Enum.member?(@read_corp_wallet_scope) end def can_track_corp_wallet?(_), do: false def get_ship(%{ship: ship_type_id, ship_name: ship_name} = _character) when not is_nil(ship_type_id) and is_integer(ship_type_id) do ship_type_id |> WandererApp.CachedInfo.get_ship_type() |> case do {:ok, ship_type_info} when not is_nil(ship_type_info) -> %{ship_type_id: ship_type_id, ship_name: ship_name, ship_type_info: ship_type_info} _ -> %{ship_type_id: ship_type_id, ship_name: ship_name, ship_type_info: %{}} end end def get_ship(%{ship_name: ship_name} = _character) when is_binary(ship_name), do: %{ship_name: ship_name, ship_type_info: %{}} def get_ship(_), do: %{ship_name: nil, ship_type_info: %{}} def get_location( %{solar_system_id: solar_system_id, structure_id: structure_id} = _character ) do case WandererApp.CachedInfo.get_system_static_info(solar_system_id) do {:ok, system_static_info} when not is_nil(system_static_info) -> %{ solar_system_id: solar_system_id, structure_id: structure_id, solar_system_info: system_static_info } _ -> %{ solar_system_id: solar_system_id, structure_id: structure_id, solar_system_info: %{} } end end defp _prepare_search_results(result) do {:ok, characters} = _load_eve_info(Map.get(result, "character"), :get_character_info, &_map_character_info/1) {:ok, corporations} = _load_eve_info( Map.get(result, "corporation"), :get_corporation_info, &_map_corporation_info/1 ) {:ok, alliances} = _load_eve_info(Map.get(result, "alliance"), :get_alliance_info, &_map_alliance_info/1) [[characters | corporations] | alliances] |> List.flatten() end defp _load_eve_info(nil, _, _), do: {:ok, []} defp _load_eve_info([], _, _), do: {:ok, []} defp _load_eve_info(eve_ids, method, map_function), do: {:ok, Enum.map(eve_ids, fn eve_id -> Task.async(fn -> apply(WandererApp.Esi.ApiClient, method, [eve_id]) end) end) # 145000 == Timeout in milliseconds |> Enum.map(fn task -> Task.await(task, 145_000) end) |> Enum.map(fn result -> case result do {:ok, result} -> map_function.(result) _ -> nil end end) |> Enum.filter(fn result -> not is_nil(result) end)} defp _map_alliance_info(info) do %{ label: info["name"], value: info["eve_id"] |> to_string(), alliance: true } end defp _map_character_info(info) do %{ label: info["name"], value: info["eve_id"] |> to_string(), character: true } end defp _map_corporation_info(info) do %{ label: info["name"], value: info["eve_id"] |> to_string(), corporation: true } end end