mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-12-01 21:42:37 +00:00
414 lines
12 KiB
Elixir
414 lines
12 KiB
Elixir
defmodule WandererApp.Character do
|
|
@moduledoc false
|
|
use Nebulex.Caching
|
|
|
|
require Logger
|
|
|
|
@read_character_wallet_scope "esi-wallet.read_character_wallet.v1"
|
|
@read_corp_wallet_scope "esi-wallet.read_corporation_wallets.v1"
|
|
|
|
@default_character_tracking_data %{
|
|
solar_system_id: nil,
|
|
structure_id: nil,
|
|
station_id: nil,
|
|
ship: nil,
|
|
ship_name: nil,
|
|
ship_item_id: nil
|
|
}
|
|
|
|
def get_by_eve_id(character_eve_id) when is_binary(character_eve_id) do
|
|
WandererApp.Api.Character.by_eve_id(character_eve_id)
|
|
end
|
|
|
|
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 ->
|
|
{: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_map_character(map_id, character_id, opts \\ []) do
|
|
case get_character(character_id) do
|
|
{:ok, character} ->
|
|
# If we are forcing the character to not be present, we merge the character state with map settings
|
|
character_is_present =
|
|
if opts |> Keyword.get(:not_present, false) do
|
|
false
|
|
else
|
|
WandererApp.Character.TrackerManager.Impl.character_is_present(map_id, character_id)
|
|
end
|
|
|
|
{:ok,
|
|
character
|
|
|> maybe_merge_map_character_settings(
|
|
map_id,
|
|
character_is_present
|
|
)}
|
|
|
|
error ->
|
|
error
|
|
end
|
|
end
|
|
|
|
def get_map_character!(map_id, character_id) do
|
|
case get_map_character(map_id, character_id) do
|
|
{:ok, character} ->
|
|
character
|
|
|
|
_ ->
|
|
Logger.error("Failed to get map character #{map_id} #{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, init_if_empty? \\ true) do
|
|
case Cachex.get(:character_state_cache, character_id) do
|
|
{:ok, nil} ->
|
|
case init_if_empty? do
|
|
true ->
|
|
character_state = WandererApp.Character.Tracker.init(character_id: character_id)
|
|
Cachex.put(:character_state_cache, character_id, character_state)
|
|
{:ok, character_state}
|
|
|
|
_ ->
|
|
{:ok, nil}
|
|
end
|
|
|
|
{: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
|
|
get_character(character_id)
|
|
|> case do
|
|
{:ok, %{access_token: access_token, eve_id: eve_id} = _character} ->
|
|
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, []}
|
|
|
|
error ->
|
|
Logger.warning("#{__MODULE__} failed search: #{inspect(error)}")
|
|
{:ok, []}
|
|
end
|
|
|
|
_error ->
|
|
{:ok, []}
|
|
end
|
|
end
|
|
|
|
def can_track_wallet?(%{scopes: scopes, id: character_id} = _character)
|
|
when is_binary(scopes) and is_binary(character_id),
|
|
do: scopes |> String.split(" ") |> Enum.member?(@read_character_wallet_scope)
|
|
|
|
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)
|
|
|
|
def can_track_corp_wallet?(_), do: false
|
|
|
|
def can_pause_tracking?(character_id) do
|
|
case get_character(character_id) do
|
|
{:ok, %{tracking_pool: tracking_pool} = character} when not is_nil(character) ->
|
|
not WandererApp.Env.character_tracking_pause_disabled?() &&
|
|
not can_track_wallet?(character) &&
|
|
(is_nil(tracking_pool) || tracking_pool == "default")
|
|
|
|
_ ->
|
|
true
|
|
end
|
|
end
|
|
|
|
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, station_id: station_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,
|
|
station_id: station_id,
|
|
solar_system_info: system_static_info
|
|
}
|
|
|
|
_ ->
|
|
%{
|
|
solar_system_id: solar_system_id,
|
|
structure_id: structure_id,
|
|
station_id: station_id,
|
|
solar_system_info: %{}
|
|
}
|
|
end
|
|
end
|
|
|
|
defp maybe_merge_map_character_settings(%{id: character_id} = character, _map_id, true) do
|
|
{:ok, tracking_paused} =
|
|
WandererApp.Cache.lookup("character:#{character_id}:tracking_paused", false)
|
|
|
|
character
|
|
|> Map.merge(%{tracking_paused: tracking_paused})
|
|
end
|
|
|
|
defp maybe_merge_map_character_settings(
|
|
%{id: character_id} = character,
|
|
map_id,
|
|
_character_is_present
|
|
) do
|
|
{:ok, tracking_paused} =
|
|
WandererApp.Cache.lookup("character:#{character_id}:tracking_paused", false)
|
|
|
|
WandererApp.MapCharacterSettingsRepo.get(map_id, character_id)
|
|
|> case do
|
|
{:ok, settings} when not is_nil(settings) ->
|
|
character
|
|
|> Map.merge(%{
|
|
solar_system_id: settings.solar_system_id,
|
|
structure_id: settings.structure_id,
|
|
station_id: settings.station_id,
|
|
ship: settings.ship,
|
|
ship_name: settings.ship_name,
|
|
ship_item_id: settings.ship_item_id
|
|
})
|
|
|
|
_ ->
|
|
character
|
|
|> Map.merge(@default_character_tracking_data)
|
|
end
|
|
|> Map.merge(%{online: false, tracking_paused: tracking_paused})
|
|
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
|
|
|
|
@doc """
|
|
Finds a character by EVE ID from a user's active characters.
|
|
|
|
## Parameters
|
|
- `current_user_id`: The current user ID
|
|
- `character_eve_id`: The EVE ID of the character to find
|
|
|
|
## Returns
|
|
- `{:ok, character}` if the character is found
|
|
- `{:error, :character_not_found}` if the character is not found
|
|
"""
|
|
def find_character_by_eve_id(current_user_id, character_eve_id) do
|
|
{:ok, all_user_characters} =
|
|
WandererApp.Api.Character.active_by_user(%{user_id: current_user_id})
|
|
|
|
case Enum.find(all_user_characters, fn char ->
|
|
"#{char.eve_id}" == "#{character_eve_id}"
|
|
end) do
|
|
nil ->
|
|
{:error, :character_not_found}
|
|
|
|
character ->
|
|
{:ok, character}
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Finds a character by character ID from a user's characters.
|
|
|
|
## Parameters
|
|
- `current_user`: The current user struct
|
|
- `char_id`: The character ID to find
|
|
|
|
## Returns
|
|
- `{:ok, character}` if the character is found
|
|
- `{:error, :character_not_found}` if the character is not found
|
|
"""
|
|
def find_user_character(current_user, char_id) do
|
|
case Enum.find(current_user.characters, &("#{&1.id}" == "#{char_id}")) do
|
|
nil ->
|
|
{:error, :character_not_found}
|
|
|
|
char ->
|
|
{:ok, char}
|
|
end
|
|
end
|
|
end
|