This commit is contained in:
@@ -30,6 +30,8 @@ spec:
|
|||||||
value: "4000"
|
value: "4000"
|
||||||
- name: MINECRAFT_SERVER_URL
|
- name: MINECRAFT_SERVER_URL
|
||||||
value: "http://minecraft-svc:8085"
|
value: "http://minecraft-svc:8085"
|
||||||
|
- name: CACHE_DIR
|
||||||
|
value: "/app/cache"
|
||||||
- name: SECRET_KEY_BASE
|
- name: SECRET_KEY_BASE
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
@@ -39,6 +41,8 @@ spec:
|
|||||||
- name: cobblemon-data
|
- name: cobblemon-data
|
||||||
mountPath: /cobblemon-data
|
mountPath: /cobblemon-data
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
- name: cache
|
||||||
|
mountPath: /app/cache
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 100m
|
cpu: 100m
|
||||||
@@ -65,3 +69,7 @@ spec:
|
|||||||
hostPath:
|
hostPath:
|
||||||
path: /data/minecraft/cobblemon-data
|
path: /data/minecraft/cobblemon-data
|
||||||
type: Directory
|
type: Directory
|
||||||
|
- name: cache
|
||||||
|
hostPath:
|
||||||
|
path: /data/minecraft/cobblemon-ui-cache
|
||||||
|
type: DirectoryOrCreate
|
||||||
|
|||||||
65
lib/cobblemon_ui/tier_list_scraper.ex
Normal file
65
lib/cobblemon_ui/tier_list_scraper.ex
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
defmodule CobblemonUi.TierListScraper do
|
||||||
|
@url "https://rankedboost.com/pokemon/tier-list/"
|
||||||
|
@filename "pokemon_tier_list.json"
|
||||||
|
|
||||||
|
def output_file do
|
||||||
|
dir = System.get_env("CACHE_DIR", ".")
|
||||||
|
Path.join(dir, @filename)
|
||||||
|
end
|
||||||
|
|
||||||
|
def run do
|
||||||
|
with {:ok, html} <- fetch_page(),
|
||||||
|
pokemon <- parse(html),
|
||||||
|
:ok <- write_json(pokemon) do
|
||||||
|
{:ok, pokemon}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_page do
|
||||||
|
case Req.get(@url) do
|
||||||
|
{:ok, %Req.Response{status: 200, body: body}} -> {:ok, body}
|
||||||
|
{:ok, %Req.Response{status: status}} -> {:error, {:http_error, status}}
|
||||||
|
{:error, err} -> {:error, err}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse(html) do
|
||||||
|
html
|
||||||
|
|> Floki.parse_document!()
|
||||||
|
|> Floki.find(".pokemon-tier")
|
||||||
|
|> Enum.map(&extract_pokemon/1)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp extract_pokemon(node) do
|
||||||
|
name =
|
||||||
|
node
|
||||||
|
|> Floki.find(".name")
|
||||||
|
|> Floki.text()
|
||||||
|
|> String.trim()
|
||||||
|
|
||||||
|
tier =
|
||||||
|
node
|
||||||
|
|> Floki.find(".tier")
|
||||||
|
|> Floki.text()
|
||||||
|
|> String.trim()
|
||||||
|
|
||||||
|
%{name: name, tier: tier}
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_json(data) do
|
||||||
|
json = Jason.encode!(data, pretty: true)
|
||||||
|
File.write(output_file(), json)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns a map of %{lowercase_name => tier} for fast lookup, or an empty map if unavailable.
|
||||||
|
def load_tier_list do
|
||||||
|
with {:ok, contents} <- File.read(output_file()),
|
||||||
|
{:ok, entries} <- Jason.decode(contents) do
|
||||||
|
Map.new(entries, fn %{"name" => name, "tier" => tier} ->
|
||||||
|
{String.downcase(name), tier}
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
_ -> %{}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -3,7 +3,10 @@ defmodule CobblemonUiWeb.DashboardLive do
|
|||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def mount(_params, _session, socket) do
|
def mount(_params, _session, socket) do
|
||||||
if connected?(socket), do: :timer.send_interval(1000, self(), :tick)
|
if connected?(socket) do
|
||||||
|
:timer.send_interval(1000, self(), :tick)
|
||||||
|
unless File.exists?(CobblemonUi.TierListScraper.output_file()), do: send(self(), :scrape_tier_list)
|
||||||
|
end
|
||||||
|
|
||||||
players =
|
players =
|
||||||
case CobblemonUi.CobblemonFS.list_players() do
|
case CobblemonUi.CobblemonFS.list_players() do
|
||||||
@@ -18,6 +21,7 @@ defmodule CobblemonUiWeb.DashboardLive do
|
|||||||
player_data: nil,
|
player_data: nil,
|
||||||
battle: nil,
|
battle: nil,
|
||||||
selected_pokemon: nil,
|
selected_pokemon: nil,
|
||||||
|
tier_list: CobblemonUi.TierListScraper.load_tier_list(),
|
||||||
view_mode: :party,
|
view_mode: :party,
|
||||||
loading: false,
|
loading: false,
|
||||||
error: nil
|
error: nil
|
||||||
@@ -96,6 +100,19 @@ defmodule CobblemonUiWeb.DashboardLive do
|
|||||||
{:noreply, do_refresh(socket)}
|
{:noreply, do_refresh(socket)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_info(:scrape_tier_list, socket) do
|
||||||
|
lv = self()
|
||||||
|
Task.start(fn ->
|
||||||
|
CobblemonUi.TierListScraper.run()
|
||||||
|
send(lv, :reload_tier_list)
|
||||||
|
end)
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info(:reload_tier_list, socket) do
|
||||||
|
{:noreply, assign(socket, tier_list: CobblemonUi.TierListScraper.load_tier_list())}
|
||||||
|
end
|
||||||
|
|
||||||
defp do_refresh(socket) do
|
defp do_refresh(socket) do
|
||||||
players =
|
players =
|
||||||
case CobblemonUi.CobblemonFS.list_players() do
|
case CobblemonUi.CobblemonFS.list_players() do
|
||||||
@@ -262,7 +279,11 @@ defmodule CobblemonUiWeb.DashboardLive do
|
|||||||
<div :if={@view_mode == :party}>
|
<div :if={@view_mode == :party}>
|
||||||
<div class="grid grid-cols-2 sm:grid-cols-3 gap-3">
|
<div class="grid grid-cols-2 sm:grid-cols-3 gap-3">
|
||||||
<%= for {pokemon, idx} <- Enum.with_index(@player_data.party) do %>
|
<%= for {pokemon, idx} <- Enum.with_index(@player_data.party) do %>
|
||||||
<.pokemon_card pokemon={pokemon} index={idx} />
|
<.pokemon_card
|
||||||
|
pokemon={pokemon}
|
||||||
|
index={idx}
|
||||||
|
tier={Map.get(@tier_list, String.downcase(pokemon.species || ""), nil)}
|
||||||
|
/>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -278,6 +299,7 @@ defmodule CobblemonUiWeb.DashboardLive do
|
|||||||
<.pokemon_card
|
<.pokemon_card
|
||||||
pokemon={pokemon}
|
pokemon={pokemon}
|
||||||
index={pc_global_index(@player_data.pc, box.box, idx)}
|
index={pc_global_index(@player_data.pc, box.box, idx)}
|
||||||
|
tier={Map.get(@tier_list, String.downcase(pokemon.species || ""), nil)}
|
||||||
compact
|
compact
|
||||||
/>
|
/>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -292,7 +314,11 @@ defmodule CobblemonUiWeb.DashboardLive do
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%!-- Pokémon detail panel --%>
|
<%!-- Pokémon detail panel --%>
|
||||||
<.pokemon_detail :if={@selected_pokemon} pokemon={@selected_pokemon} />
|
<.pokemon_detail
|
||||||
|
:if={@selected_pokemon}
|
||||||
|
pokemon={@selected_pokemon}
|
||||||
|
tier={Map.get(@tier_list, String.downcase(@selected_pokemon.species || ""), nil)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -307,6 +333,7 @@ defmodule CobblemonUiWeb.DashboardLive do
|
|||||||
attr :pokemon, :map, required: true
|
attr :pokemon, :map, required: true
|
||||||
attr :index, :integer, required: true
|
attr :index, :integer, required: true
|
||||||
attr :compact, :boolean, default: false
|
attr :compact, :boolean, default: false
|
||||||
|
attr :tier, :string, default: nil
|
||||||
|
|
||||||
defp pokemon_card(%{pokemon: nil} = assigns) do
|
defp pokemon_card(%{pokemon: nil} = assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
@@ -321,7 +348,7 @@ defmodule CobblemonUiWeb.DashboardLive do
|
|||||||
|
|
||||||
defp pokemon_card(assigns) do
|
defp pokemon_card(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<button
|
<div
|
||||||
phx-click="select_pokemon"
|
phx-click="select_pokemon"
|
||||||
phx-value-index={@index}
|
phx-value-index={@index}
|
||||||
class={[
|
class={[
|
||||||
@@ -346,7 +373,9 @@ defmodule CobblemonUiWeb.DashboardLive do
|
|||||||
Lv. {@pokemon.level || "?"}
|
Lv. {@pokemon.level || "?"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div :if={@pokemon.shiny} class="shrink-0" title="Shiny">
|
<div class="flex items-center gap-1 shrink-0">
|
||||||
|
<.tier_badge :if={@tier} tier={@tier} species={@pokemon.species} compact={@compact} />
|
||||||
|
<div :if={@pokemon.shiny} title="Shiny">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-sparkles"
|
name="hero-sparkles"
|
||||||
class={[
|
class={[
|
||||||
@@ -356,6 +385,7 @@ defmodule CobblemonUiWeb.DashboardLive do
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div :if={!@compact} class="flex items-center gap-2 mt-2">
|
<div :if={!@compact} class="flex items-center gap-2 mt-2">
|
||||||
<span
|
<span
|
||||||
:if={@pokemon.nature}
|
:if={@pokemon.nature}
|
||||||
@@ -373,11 +403,40 @@ defmodule CobblemonUiWeb.DashboardLive do
|
|||||||
{gender_symbol(@pokemon.gender)}
|
{gender_symbol(@pokemon.gender)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
attr :tier, :string, required: true
|
||||||
|
attr :species, :string, required: true
|
||||||
|
attr :compact, :boolean, default: false
|
||||||
|
|
||||||
|
defp tier_badge(assigns) do
|
||||||
|
~H"""
|
||||||
|
<a
|
||||||
|
href={"https://rankedboost.com/pokemon/#{String.downcase(@species || "")}/"}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class={[
|
||||||
|
"inline-flex items-center justify-center font-black rounded leading-none shrink-0 hover:scale-110 transition-transform",
|
||||||
|
if(@compact, do: "text-[9px] w-4 h-4", else: "text-[10px] w-5 h-5"),
|
||||||
|
case @tier do
|
||||||
|
"S" -> "bg-red-500/20 text-red-400 ring-1 ring-red-500/30 hover:bg-red-500/30"
|
||||||
|
"A" -> "bg-orange-500/20 text-orange-400 ring-1 ring-orange-500/30 hover:bg-orange-500/30"
|
||||||
|
"B" -> "bg-yellow-500/20 text-yellow-400 ring-1 ring-yellow-500/30 hover:bg-yellow-500/30"
|
||||||
|
"C" -> "bg-green-500/20 text-green-400 ring-1 ring-green-500/30 hover:bg-green-500/30"
|
||||||
|
"D" -> "bg-blue-500/20 text-blue-400 ring-1 ring-blue-500/30 hover:bg-blue-500/30"
|
||||||
|
_ -> "bg-base-300/30 text-base-content/40 ring-1 ring-base-300/40"
|
||||||
|
end
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{@tier}
|
||||||
|
</a>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
attr :pokemon, :map, required: true
|
attr :pokemon, :map, required: true
|
||||||
|
attr :tier, :string, default: nil
|
||||||
|
|
||||||
defp pokemon_detail(assigns) do
|
defp pokemon_detail(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
@@ -389,11 +448,12 @@ defmodule CobblemonUiWeb.DashboardLive do
|
|||||||
<.icon name="hero-bolt" class="size-4 text-primary/70" />
|
<.icon name="hero-bolt" class="size-4 text-primary/70" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-bold text-base-content capitalize text-lg">
|
<h3 class="font-bold text-base-content capitalize text-lg flex items-center gap-2">
|
||||||
{@pokemon.species || "Unknown"}
|
{@pokemon.species || "Unknown"}
|
||||||
|
<.tier_badge :if={@tier} tier={@tier} species={@pokemon.species} />
|
||||||
<span
|
<span
|
||||||
:if={@pokemon.shiny}
|
:if={@pokemon.shiny}
|
||||||
class="text-warning text-sm ml-1"
|
class="text-warning text-sm"
|
||||||
title="Shiny"
|
title="Shiny"
|
||||||
>
|
>
|
||||||
★
|
★
|
||||||
|
|||||||
1
mix.exs
1
mix.exs
@@ -60,6 +60,7 @@ defmodule CobblemonUi.MixProject do
|
|||||||
{:telemetry_poller, "~> 1.0"},
|
{:telemetry_poller, "~> 1.0"},
|
||||||
{:gettext, "~> 1.0"},
|
{:gettext, "~> 1.0"},
|
||||||
{:jason, "~> 1.2"},
|
{:jason, "~> 1.2"},
|
||||||
|
{:floki, "~> 0.35"},
|
||||||
{:dns_cluster, "~> 0.2.0"},
|
{:dns_cluster, "~> 0.2.0"},
|
||||||
{:bandit, "~> 1.5"}
|
{:bandit, "~> 1.5"}
|
||||||
]
|
]
|
||||||
|
|||||||
1
mix.lock
1
mix.lock
@@ -8,6 +8,7 @@
|
|||||||
"file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
|
"file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
|
||||||
"finch": {:hex, :finch, "0.21.0", "b1c3b2d48af02d0c66d2a9ebfb5622be5c5ecd62937cf79a88a7f98d48a8290c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "87dc6e169794cb2570f75841a19da99cfde834249568f2a5b121b809588a4377"},
|
"finch": {:hex, :finch, "0.21.0", "b1c3b2d48af02d0c66d2a9ebfb5622be5c5ecd62937cf79a88a7f98d48a8290c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "87dc6e169794cb2570f75841a19da99cfde834249568f2a5b121b809588a4377"},
|
||||||
"fine": {:hex, :fine, "0.1.4", "b19a89c1476c7c57afb5f9314aed5960b5bc95d5277de4cb5ee8e1d1616ce379", [:mix], [], "hexpm", "be3324cc454a42d80951cf6023b9954e9ff27c6daa255483b3e8d608670303f5"},
|
"fine": {:hex, :fine, "0.1.4", "b19a89c1476c7c57afb5f9314aed5960b5bc95d5277de4cb5ee8e1d1616ce379", [:mix], [], "hexpm", "be3324cc454a42d80951cf6023b9954e9ff27c6daa255483b3e8d608670303f5"},
|
||||||
|
"floki": {:hex, :floki, "0.38.0", "62b642386fa3f2f90713f6e231da0fa3256e41ef1089f83b6ceac7a3fd3abf33", [:mix], [], "hexpm", "a5943ee91e93fb2d635b612caf5508e36d37548e84928463ef9dd986f0d1abd9"},
|
||||||
"gettext": {:hex, :gettext, "1.0.2", "5457e1fd3f4abe47b0e13ff85086aabae760497a3497909b8473e0acee57673b", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "eab805501886802071ad290714515c8c4a17196ea76e5afc9d06ca85fb1bfeb3"},
|
"gettext": {:hex, :gettext, "1.0.2", "5457e1fd3f4abe47b0e13ff85086aabae760497a3497909b8473e0acee57673b", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "eab805501886802071ad290714515c8c4a17196ea76e5afc9d06ca85fb1bfeb3"},
|
||||||
"heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "0435d4ca364a608cc75e2f8683d374e55abbae26", [tag: "v2.2.0", sparse: "optimized", depth: 1]},
|
"heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "0435d4ca364a608cc75e2f8683d374e55abbae26", [tag: "v2.2.0", sparse: "optimized", depth: 1]},
|
||||||
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
|
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
|
||||||
|
|||||||
Reference in New Issue
Block a user