diff --git a/lib/cobblemon_ui/evolution_api.ex b/lib/cobblemon_ui/evolution_api.ex index b0038f1..8c8144b 100644 --- a/lib/cobblemon_ui/evolution_api.ex +++ b/lib/cobblemon_ui/evolution_api.ex @@ -81,18 +81,19 @@ defmodule CobblemonUi.EvolutionApi do end defp fetch_species_chain_url(species) do - url = "https://pokeapi.co/api/v2/pokemon-species/#{species}" + api_name = CobblemonUi.PokeApiNames.normalize(species) + url = "https://pokeapi.co/api/v2/pokemon-species/#{api_name}" case Req.get(url) do {:ok, %Req.Response{status: 200, body: %{"evolution_chain" => %{"url" => url}}}} -> {:ok, url} {:ok, %Req.Response{status: status}} -> - Logger.warning("[EvolutionApi] Species lookup failed for #{species}: HTTP #{status}") + Logger.warning("[EvolutionApi] Species lookup failed for #{api_name}: HTTP #{status}") {:error, :not_found} {:error, reason} -> - Logger.warning("[EvolutionApi] Species lookup failed for #{species}: #{inspect(reason)}") + Logger.warning("[EvolutionApi] Species lookup failed for #{api_name}: #{inspect(reason)}") {:error, reason} end end diff --git a/lib/cobblemon_ui/poke_api.ex b/lib/cobblemon_ui/poke_api.ex index 11a9bd7..3cb877b 100644 --- a/lib/cobblemon_ui/poke_api.ex +++ b/lib/cobblemon_ui/poke_api.ex @@ -68,21 +68,67 @@ defmodule CobblemonUi.PokeApi do # --------------------------------------------------------------------------- defp fetch_types(species) do - url = "https://pokeapi.co/api/v2/pokemon/#{species}" + api_name = CobblemonUi.PokeApiNames.normalize(species) + + case fetch_types_direct(api_name) do + {:ok, types} -> + types + + :not_found -> + # The /pokemon/ endpoint requires the specific form name (e.g. + # "aegislash-shield"). Fall back through the /pokemon-species/ + # endpoint to resolve the default variety. + fetch_types_via_species(api_name) + end + end + + defp fetch_types_direct(api_name) do + url = "https://pokeapi.co/api/v2/pokemon/#{api_name}" case Req.get(url) do {:ok, %Req.Response{status: 200, body: %{"types" => types}}} -> - types - |> Enum.sort_by(fn t -> t["slot"] end) - |> Enum.map(fn t -> get_in(t, ["type", "name"]) end) - |> Enum.reject(&is_nil/1) + parsed = + types + |> Enum.sort_by(fn t -> t["slot"] end) + |> Enum.map(fn t -> get_in(t, ["type", "name"]) end) + |> Enum.reject(&is_nil/1) + + {:ok, parsed} + + {:ok, %Req.Response{status: 404}} -> + :not_found {:ok, %Req.Response{status: status}} -> - Logger.warning("[PokeApi] Type lookup failed for #{species}: HTTP #{status}") - [] + Logger.warning("[PokeApi] Type lookup failed for #{api_name}: HTTP #{status}") + {:ok, []} {:error, reason} -> - Logger.warning("[PokeApi] Type lookup failed for #{species}: #{inspect(reason)}") + Logger.warning("[PokeApi] Type lookup failed for #{api_name}: #{inspect(reason)}") + {:ok, []} + end + end + + defp fetch_types_via_species(api_name) do + url = "https://pokeapi.co/api/v2/pokemon-species/#{api_name}" + + with {:ok, %Req.Response{status: 200, body: body}} <- Req.get(url), + %{"varieties" => varieties} <- body, + %{"pokemon" => %{"url" => pokemon_url}} <- + Enum.find(varieties, fn v -> v["is_default"] end) do + case Req.get(pokemon_url) do + {:ok, %Req.Response{status: 200, body: %{"types" => types}}} -> + types + |> Enum.sort_by(fn t -> t["slot"] end) + |> Enum.map(fn t -> get_in(t, ["type", "name"]) end) + |> Enum.reject(&is_nil/1) + + _ -> + Logger.warning("[PokeApi] Fallback type lookup failed for #{api_name}") + [] + end + else + _ -> + Logger.warning("[PokeApi] Species fallback failed for #{api_name}") [] end end diff --git a/lib/cobblemon_ui/poke_api_names.ex b/lib/cobblemon_ui/poke_api_names.ex new file mode 100644 index 0000000..6856160 --- /dev/null +++ b/lib/cobblemon_ui/poke_api_names.ex @@ -0,0 +1,78 @@ +defmodule CobblemonUi.PokeApiNames do + @moduledoc """ + Maps Cobblemon species names to PokeAPI-compatible names. + + Cobblemon stores species names as single lowercase words (e.g. `ironthorns`), + while PokeAPI expects hyphenated forms (e.g. `iron-thorns`). This module + provides the translation layer. + """ + + # Static map of Cobblemon names that differ from PokeAPI names. + # Only entries where the name doesn't match need to be listed. + @name_map %{ + # Paradox Pokémon (Scarlet) + "greattusk" => "great-tusk", + "screamtail" => "scream-tail", + "brutebonnet" => "brute-bonnet", + "fluttermane" => "flutter-mane", + "sandyshocks" => "sandy-shocks", + "slitherwing" => "slither-wing", + "roaringmoon" => "roaring-moon", + "walkingwake" => "walking-wake", + "gougingfire" => "gouging-fire", + "ragingbolt" => "raging-bolt", + # Paradox Pokémon (Violet) + "irontreads" => "iron-treads", + "ironbundle" => "iron-bundle", + "ironhands" => "iron-hands", + "ironjugulis" => "iron-jugulis", + "ironmoth" => "iron-moth", + "ironvaliant" => "iron-valiant", + "ironthorns" => "iron-thorns", + "ironleaves" => "iron-leaves", + "ironcrown" => "iron-crown", + "ironboulder" => "iron-boulder", + # Tapu guardians + "tapukoko" => "tapu-koko", + "tapulele" => "tapu-lele", + "tapubulu" => "tapu-bulu", + "tapufini" => "tapu-fini", + # Hyphenated / special names + "hooh" => "ho-oh", + "porygonz" => "porygon-z", + "typenull" => "type-null", + "mrmime" => "mr-mime", + "mrrime" => "mr-rime", + "mimejr" => "mime-jr", + "jangmoo" => "jangmo-o", + "hakamoo" => "hakamo-o", + "kommoo" => "kommo-o", + "chiyu" => "chi-yu", + "chienpao" => "chien-pao", + "tinglu" => "ting-lu", + "wochien" => "wo-chien", + "nidoranf" => "nidoran-f", + "nidoranm" => "nidoran-m", + "farfetchd" => "farfetchd", + "sirfetchd" => "sirfetchd", + # Flabébé line + "flabebe" => "flabebe" + } + + @doc """ + Converts a Cobblemon species name to the PokeAPI-compatible name. + + ## Examples + + iex> CobblemonUi.PokeApiNames.normalize("ironthorns") + "iron-thorns" + + iex> CobblemonUi.PokeApiNames.normalize("bulbasaur") + "bulbasaur" + """ + @spec normalize(String.t()) :: String.t() + def normalize(species) when is_binary(species) do + key = species |> String.downcase() |> String.trim() + Map.get(@name_map, key, key) + end +end