OOKNET                             [ /  search the index  ]  
──────────────────────────────────────────────────────────────────────────────────────
══════════════════════════════════════════════════════════════════════════════════════
OOKNET   [ /  search  ]  
────────────────────────────────────────────────
════════════════════════════════════════════════
 
HASH      41e3e04d377e
DATE      2025-08-04
SUBJECT   nix: initial challenge mode fetch script
FILES     3 CHANGED
HASH      41e3e04d377e
DATE      2025-08-04
SUBJECT   nix: initial challenge mode fetch
          script
FILES     3 CHANGED
 

diff --git a/nix/apps/challenge-mode-leaderboard.nix b/nix/apps/challenge-mode-lea
derboard.nix
new file mode 100644
index 0000000..659719c
--- /dev/null
+++ b/nix/apps/challenge-mode-leaderboard.nix
@@ -0,0 +1,155 @@
+{
+  writers,
+  api,
+  python3Packages,
+  ...
+}: let
+  fetcherScript =
+    writers.writePython3Bin "cm-leaderboard-fetcher" {
+      libraries = [python3Packages.requests];
+      doCheck = false;
+    }
+    ''
+      import os
+      import requests
+      import json
+      import re
+      import sys
+      import time
+
+      ALL_REALMS = ${builtins.toJSON api.realm}
+
+      # TODO: find repo root
+      OUTPUT_ROOT = "./web/public/data"
+      # TODO: fix me
+      API_TOKEN = os.getenv("BLIZZARD_API_TOKEN")
+
+      def slugify(text):
+          # converts a string to a url friendly slug
+          text = text.lower()
+          text = re.sub(r'[\s\'\W]+', '-', text)
+          return text.strip('-')
+
+      def get_current_period_and_dungeons(realm_info, session):
+          # fetch the current period ID and dungeon list
+          region, realm_id, name = realm_info['region'], realm_info['id'], realm_
info['name']
+          namespace = f"dynamic-classic-{region}"
+          url = (
+              f"https://{region}.api.blizzard.com/data/wow/connected-realm/"
+              f"{realm_id}/mythic-leaderboard/index?namespace={namespace}"
+          )
+
+          try:
+              print(f"  Fetching leaderboard index for {name}...")
+              response = session.get(url, timeout=15)
+              response.raise_for_status()
+              data = response.json()
+
+              href = data["current_leaderboards"][0]["key"]["href"]
+              match = re.search(r"/period/(\d+)", href)
+              if not match:
+                  print(f"  ERROR: Could not parse period ID for {name}.", file=s
ys.stderr)
+                  return None, None
+              period_id = match.group(1)
+
+              dungeons = []
+              for d in data["current_leaderboards"]:
+                  dungeon_name_field = d["name"]
+                  # check if the name field is a dictionary of localizations
+                  if isinstance(dungeon_name_field, dict):
+                      # if it is, pick the English name for consistency.
+                      # use .get() for safety in case en_US is missing.
+                      name_str = dungeon_name_field.get("en_US", "unknown-dungeon
-name")
+                  else:
+                      # otherwise, it's already a string.
+                      name_str = dungeon_name_field
+
+                  dungeons.append({
+                      "id": d["id"],
+                      "name": name_str, # use extracted string name
+                      "slug": slugify(name_str)
+                  })
+
+              print(f"  Found Period: {period_id}, Dungeons: {len(dungeons)}")
+              return period_id, dungeons
+
+          except requests.exceptions.RequestException as e:
+              print(f"  ERROR: API request failed for {name}: {e}", file=sys.stde
rr)
+              return None, None
+          except (KeyError, IndexError) as e:
+              print(f"  ERROR: Could not parse index for {name}. Details: {e}", f
ile=sys.stderr)
+              return None, None
+
+
+      def get_leaderboard_data(realm_info, dungeon, period_id, session):
+          # fetch a specific leaderboard.
+          region, realm_id = realm_info['region'], realm_info['id']
+          namespace = f"dynamic-classic-{region}"
+          url = (
+              f"https://{region}.api.blizzard.com/data/wow/connected-realm/"
+              f"{realm_id}/mythic-leaderboard/{dungeon['id']}/period/"
+              f"{period_id}?namespace={namespace}"
+          )
+
+          try:
+              time.sleep(0.05)
+              response = session.get(url, timeout=15)
+              response.raise_for_status()
+              return response.json()
+          except requests.exceptions.RequestException as e:
+              print(f"    ERROR fetching for {dungeon['name']}: {e}", file=sys.st
derr)
+              return None
+
+
+      def main():
+          if not API_TOKEN:
+              print(
+                  "FATAL: BLIZZARD_API_TOKEN environment variable not set.",
+                  file=sys.stderr
+              )
+              sys.exit(1)
+
+          print("Starting leaderboard fetch...")
+          print(f"Outputting data to: {os.path.abspath(OUTPUT_ROOT)}")
+
+          session = requests.Session()
+          session.headers.update({"Authorization": f"Bearer {API_TOKEN}"})
+
+          for realm_slug, realm_info in ALL_REALMS.items():
+              print(
+                  f"\nProcessing Realm: {realm_info['name']} "
+                  f"({realm_info['region'].upper()})"
+              )
+
+              period_id, dungeons = get_current_period_and_dungeons(
+                  realm_info, session
+              )
+              if not period_id or not dungeons:
+                  print(f"  Skipping realm {realm_info['name']}.")
+                  continue
+
+              for dungeon in dungeons:
+                  print(f"  - Fetching dungeon: {dungeon['name']}")
+                  leaderboard = get_leaderboard_data(
+                      realm_info, dungeon, period_id, session
+                  )
+                  if not leaderboard:
+                      continue
+
+                  output_path = os.path.join(
+                      OUTPUT_ROOT, "challenge-mode", realm_info['region'],
+                      realm_slug, dungeon['slug'],
+                      f"{realm_slug}-{dungeon['slug']}-leaderboard.json"
+                  )
+                  os.makedirs(os.path.dirname(output_path), exist_ok=True)
+                  with open(output_path, 'w', encoding='utf-8') as f:
+                      json.dump(leaderboard, f, indent=2)
+
+              print("\nDone.")
+
+
+      if __name__ == "__main__":
+          main()
+    '';
+in
+  fetcherScript

diff --git a/nix/apps/challenge-mode-leaderb
oard.nix b/nix/apps/challenge-mode-leaderboa
rd.nix
new file mode 100644
index 0000000..659719c
--- /dev/null
+++ b/nix/apps/challenge-mode-leaderboard.ni
x
@@ -0,0 +1,155 @@
+{
+  writers,
+  api,
+  python3Packages,
+  ...
+}: let
+  fetcherScript =
+    writers.writePython3Bin "cm-leaderboard
-fetcher" {
+      libraries = [python3Packages.requests
];
+      doCheck = false;
+    }
+    ''
+      import os
+      import requests
+      import json
+      import re
+      import sys
+      import time
+
+      ALL_REALMS = ${builtins.toJSON api.re
alm}
+
+      # TODO: find repo root
+      OUTPUT_ROOT = "./web/public/data"
+      # TODO: fix me
+      API_TOKEN = os.getenv("BLIZZARD_API_T
OKEN")
+
+      def slugify(text):
+          # converts a string to a url frie
ndly slug
+          text = text.lower()
+          text = re.sub(r'[\s\'\W]+', '-', 
text)
+          return text.strip('-')
+
+      def get_current_period_and_dungeons(r
ealm_info, session):
+          # fetch the current period ID and
 dungeon list
+          region, realm_id, name = realm_in
fo['region'], realm_info['id'], realm_info['
name']
+          namespace = f"dynamic-classic-{re
gion}"
+          url = (
+              f"https://{region}.api.blizza
rd.com/data/wow/connected-realm/"
+              f"{realm_id}/mythic-leaderboa
rd/index?namespace={namespace}"
+          )
+
+          try:
+              print(f"  Fetching leaderboar
d index for {name}...")
+              response = session.get(url, t
imeout=15)
+              response.raise_for_status()
+              data = response.json()
+
+              href = data["current_leaderbo
ards"][0]["key"]["href"]
+              match = re.search(r"/period/(
\d+)", href)
+              if not match:
+                  print(f"  ERROR: Could no
t parse period ID for {name}.", file=sys.std
err)
+                  return None, None
+              period_id = match.group(1)
+
+              dungeons = []
+              for d in data["current_leader
boards"]:
+                  dungeon_name_field = d["n
ame"]
+                  # check if the name field
 is a dictionary of localizations
+                  if isinstance(dungeon_nam
e_field, dict):
+                      # if it is, pick the 
English name for consistency.
+                      # use .get() for safe
ty in case en_US is missing.
+                      name_str = dungeon_na
me_field.get("en_US", "unknown-dungeon-name"
)
+                  else:
+                      # otherwise, it's alr
eady a string.
+                      name_str = dungeon_na
me_field
+
+                  dungeons.append({
+                      "id": d["id"],
+                      "name": name_str, # u
se extracted string name
+                      "slug": slugify(name_
str)
+                  })
+
+              print(f"  Found Period: {peri
od_id}, Dungeons: {len(dungeons)}")
+              return period_id, dungeons
+
+          except requests.exceptions.Reques
tException as e:
+              print(f"  ERROR: API request 
failed for {name}: {e}", file=sys.stderr)
+              return None, None
+          except (KeyError, IndexError) as 
e:
+              print(f"  ERROR: Could not pa
rse index for {name}. Details: {e}", file=sy
s.stderr)
+              return None, None
+
+
+      def get_leaderboard_data(realm_info, 
dungeon, period_id, session):
+          # fetch a specific leaderboard.
+          region, realm_id = realm_info['re
gion'], realm_info['id']
+          namespace = f"dynamic-classic-{re
gion}"
+          url = (
+              f"https://{region}.api.blizza
rd.com/data/wow/connected-realm/"
+              f"{realm_id}/mythic-leaderboa
rd/{dungeon['id']}/period/"
+              f"{period_id}?namespace={name
space}"
+          )
+
+          try:
+              time.sleep(0.05)
+              response = session.get(url, t
imeout=15)
+              response.raise_for_status()
+              return response.json()
+          except requests.exceptions.Reques
tException as e:
+              print(f"    ERROR fetching fo
r {dungeon['name']}: {e}", file=sys.stderr)
+              return None
+
+
+      def main():
+          if not API_TOKEN:
+              print(
+                  "FATAL: BLIZZARD_API_TOKE
N environment variable not set.",
+                  file=sys.stderr
+              )
+              sys.exit(1)
+
+          print("Starting leaderboard fetch
...")
+          print(f"Outputting data to: {os.p
ath.abspath(OUTPUT_ROOT)}")
+
+          session = requests.Session()
+          session.headers.update({"Authoriz
ation": f"Bearer {API_TOKEN}"})
+
+          for realm_slug, realm_info in ALL
_REALMS.items():
+              print(
+                  f"\nProcessing Realm: {re
alm_info['name']} "
+                  f"({realm_info['region'].
upper()})"
+              )
+
+              period_id, dungeons = get_cur
rent_period_and_dungeons(
+                  realm_info, session
+              )
+              if not period_id or not dunge
ons:
+                  print(f"  Skipping realm 
{realm_info['name']}.")
+                  continue
+
+              for dungeon in dungeons:
+                  print(f"  - Fetching dung
eon: {dungeon['name']}")
+                  leaderboard = get_leaderb
oard_data(
+                      realm_info, dungeon, 
period_id, session
+                  )
+                  if not leaderboard:
+                      continue
+
+                  output_path = os.path.joi
n(
+                      OUTPUT_ROOT, "challen
ge-mode", realm_info['region'],
+                      realm_slug, dungeon['
slug'],
+                      f"{realm_slug}-{dunge
on['slug']}-leaderboard.json"
+                  )
+                  os.makedirs(os.path.dirna
me(output_path), exist_ok=True)
+                  with open(output_path, 'w
', encoding='utf-8') as f:
+                      json.dump(leaderboard
, f, indent=2)
+
+              print("\nDone.")
+
+
+      if __name__ == "__main__":
+          main()
+    '';
+in
+  fetcherScript
 

diff --git a/nix/apps/default.nix b/nix/apps/default.nix
index 228534f..c908c6c 100644
--- a/nix/apps/default.nix
+++ b/nix/apps/default.nix
@@ -6,15 +6,17 @@
   debuffs,
   inputs,
   trinket,
+  api,
   ...
 }: {
   perSystem = {pkgs, ...}: let
-    inherit (pkgs) callPackage writeShellApplication;
+    inherit (pkgs) callPackage writeShellApplication writers python3Packages;
     simulation = import ./simulation {inherit lib classes encounter buffs debuffs
 inputs trinket pkgs;};
     getDB = callPackage ./getDB.nix {inherit inputs;};
     testItemLookup = callPackage ./testItemLookup.nix {inherit inputs lib;};
     testEnrichmentOutput = callPackage ./testEnrichmentOutput.nix {inherit inputs
 lib;};
     testEquipmentEnrichment = callPackage ./testEquipmentEnrichment.nix {inherit 
inputs lib;};
+    getCMLeaders = import ./challenge-mode-leaderboard.nix {inherit api writers p
ython3Packages;};
 
     # Trinket testing apps
     trinketTest = callPackage ./trinket-test.nix {inherit lib classes encounter b
uffs debuffs inputs trinket writeShellApplication;};
@@ -59,6 +61,10 @@
           type = "app";
           program = "${trinketComparisonTest}/bin/trinket-comparison-test";
         };
+        getCM = {
+          type = "app";
+          program = "${getCMLeaders}/bin/cm-leaderboard-fetcher";
+        };
       };
   };
 }

diff --git a/nix/apps/default.nix b/nix/apps
/default.nix
index 228534f..c908c6c 100644
--- a/nix/apps/default.nix
+++ b/nix/apps/default.nix
@@ -6,15 +6,17 @@
   debuffs,
   inputs,
   trinket,
+  api,
   ...
 }: {
   perSystem = {pkgs, ...}: let
-    inherit (pkgs) callPackage writeShellAp
plication;
+    inherit (pkgs) callPackage writeShellAp
plication writers python3Packages;
     simulation = import ./simulation {inher
it lib classes encounter buffs debuffs input
s trinket pkgs;};
     getDB = callPackage ./getDB.nix {inheri
t inputs;};
     testItemLookup = callPackage ./testItem
Lookup.nix {inherit inputs lib;};
     testEnrichmentOutput = callPackage ./te
stEnrichmentOutput.nix {inherit inputs lib;}
;
     testEquipmentEnrichment = callPackage .
/testEquipmentEnrichment.nix {inherit inputs
 lib;};
+    getCMLeaders = import ./challenge-mode-
leaderboard.nix {inherit api writers python3
Packages;};
 
     # Trinket testing apps
     trinketTest = callPackage ./trinket-tes
t.nix {inherit lib classes encounter buffs d
ebuffs inputs trinket writeShellApplication;
};
@@ -59,6 +61,10 @@
           type = "app";
           program = "${trinketComparisonTes
t}/bin/trinket-comparison-test";
         };
+        getCM = {
+          type = "app";
+          program = "${getCMLeaders}/bin/cm
-leaderboard-fetcher";
+        };
       };
   };
 }
 

diff --git a/nix/default.nix b/nix/default.nix
index 8fd1d3d..4cbd88d 100644
--- a/nix/default.nix
+++ b/nix/default.nix
@@ -13,5 +13,6 @@
     ./target
     ./encounter
     ./trinkets
+    ./api
   ];
 }

diff --git a/nix/default.nix b/nix/default.n
ix
index 8fd1d3d..4cbd88d 100644
--- a/nix/default.nix
+++ b/nix/default.nix
@@ -13,5 +13,6 @@
     ./target
     ./encounter
     ./trinkets
+    ./api
   ];
 }
 
[ BACK TO LOG ]
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET