HASH 2708fe5e9981
DATE 2025-09-25
SUBJECT web: add new realms (for testing), fix class color resolution for players
that connot be found
FILES 8 CHANGED HASH 2708fe5e9981
DATE 2025-09-25
SUBJECT web: add new realms (for testing), fix
class color resolution for players
that connot be found
FILES 8 CHANGED ┌─ nix/apps/utils/multi-period-analyzer.nix ──────────────────────────────── [ COPY ] ─┐
│ diff --git a/nix/apps/utils/multi-period-analyzer.nix b/nix/apps/utils/multi-perio │
│ d-analyzer.nix │
│ index eb5986a..af23d9b 100644 │
│ --- a/nix/apps/utils/multi-period-analyzer.nix │
│ +++ b/nix/apps/utils/multi-period-analyzer.nix │
│ @@ -97,7 +97,7 @@ │
│ ] │
│ │
│ API_TOKEN = os.getenv("BLIZZARD_API_TOKEN") │
│ - PERIOD_RANGE = range(1026, 1030) # Test periods 1026-1028 │
│ + PERIOD_RANGE = range(1026, 1031) # Test periods 1026-1028 │
│ MAX_CONCURRENT = 50 # Reduced concurrency for comprehensive testing │
│ BATCH_SIZE = 50 │
│ │
│ @@ -362,4 +362,3 @@ │
│ ''; │
│ in │
│ multiPeriodAnalyzerScript │
│ - │
└────────────────────────────────────────────────────────────────────────────────────┘ ┌─ ...ils/multi-period-analyzer.nix ── [ COPY ] ─┐
│ diff --git a/nix/apps/utils/multi-period-ana │
│ lyzer.nix b/nix/apps/utils/multi-period-anal │
│ yzer.nix │
│ index eb5986a..af23d9b 100644 │
│ --- a/nix/apps/utils/multi-period-analyzer.n │
│ ix │
│ +++ b/nix/apps/utils/multi-period-analyzer.n │
│ ix │
│ @@ -97,7 +97,7 @@ │
│ ] │
│ │
│ API_TOKEN = os.getenv("BLIZZARD_API_T │
│ OKEN") │
│ - PERIOD_RANGE = range(1026, 1030) # T │
│ est periods 1026-1028 │
│ + PERIOD_RANGE = range(1026, 1031) # T │
│ est periods 1026-1028 │
│ MAX_CONCURRENT = 50 # Reduced concur │
│ rency for comprehensive testing │
│ BATCH_SIZE = 50 │
│ │
│ @@ -362,4 +362,3 @@ │
│ ''; │
│ in │
│ multiPeriodAnalyzerScript │
│ - │
└──────────────────────────────────────────────┘ ┌─ nix/pkgs/ookstats/src/cmd/generate.go ─────────────────────────────────── [ COPY ] ─┐
│ diff --git a/nix/pkgs/ookstats/src/cmd/generate.go b/nix/pkgs/ookstats/src/cmd/gen │
│ erate.go │
│ index 25fadc8..7cd348c 100644 │
│ --- a/nix/pkgs/ookstats/src/cmd/generate.go │
│ +++ b/nix/pkgs/ookstats/src/cmd/generate.go │
│ @@ -75,6 +75,85 @@ type PlayerPageJSON struct { │
│ Version string `json:"version"` │
│ } │
│ │
│ +// --- Fallback helpers for class/spec resolution when Profile API is unavailable │
│ --- │
│ +func specIdToClassSpec(id int) (className string, specName string, ok bool) { │
│ + switch id { │
│ + // Tanks │
│ + case 73: │
│ + return "Warrior", "Protection", true │
│ + case 104: │
│ + return "Druid", "Guardian", true │
│ + case 250: │
│ + return "Death Knight", "Blood", true │
│ + case 268: │
│ + return "Monk", "Brewmaster", true │
│ + case 66: │
│ + return "Paladin", "Protection", true │
│ + // Healers │
│ + case 105: │
│ + return "Druid", "Restoration", true │
│ + case 270: │
│ + return "Monk", "Mistweaver", true │
│ + case 65: │
│ + return "Paladin", "Holy", true │
│ + case 256: │
│ + return "Priest", "Discipline", true │
│ + case 257: │
│ + return "Priest", "Holy", true │
│ + case 264: │
│ + return "Shaman", "Restoration", true │
│ + // DPS │
│ + case 71: │
│ + return "Warrior", "Arms", true │
│ + case 72: │
│ + return "Warrior", "Fury", true │
│ + case 70: │
│ + return "Paladin", "Retribution", true │
│ + case 253: │
│ + return "Hunter", "Beast Mastery", true │
│ + case 254: │
│ + return "Hunter", "Marksmanship", true │
│ + case 255: │
│ + return "Hunter", "Survival", true │
│ + case 259: │
│ + return "Rogue", "Assassination", true │
│ + case 260: │
│ + return "Rogue", "Outlaw", true │
│ + case 261: │
│ + return "Rogue", "Subtlety", true │
│ + case 258: │
│ + return "Priest", "Shadow", true │
│ + case 251: │
│ + return "Death Knight", "Frost", true │
│ + case 252: │
│ + return "Death Knight", "Unholy", true │
│ + case 262: │
│ + return "Shaman", "Elemental", true │
│ + case 263: │
│ + return "Shaman", "Enhancement", true │
│ + case 62: │
│ + return "Mage", "Arcane", true │
│ + case 63: │
│ + return "Mage", "Fire", true │
│ + case 64: │
│ + return "Mage", "Frost", true │
│ + case 265: │
│ + return "Warlock", "Affliction", true │
│ + case 266: │
│ + return "Warlock", "Demonology", true │
│ + case 267: │
│ + return "Warlock", "Destruction", true │
│ + case 269: │
│ + return "Monk", "Windwalker", true │
│ + case 102: │
│ + return "Druid", "Balance", true │
│ + case 103: │
│ + return "Druid", "Feral", true │
│ + default: │
│ + return "", "", false │
│ + } │
│ +} │
│ + │
│ // Optimized data structures for batch loading │
│ type PlayerData struct { │
│ ID int64 │
│ @@ -686,6 +765,14 @@ func generateSinglePlayerJSON(player PlayerData, bestRunsMap │
│ map[int64][]BestRun │
│ if player.RealmRanking.Valid { v := int(player.RealmRanking.Int64); pj.RealmR │
│ anking = &v } │
│ if player.AverageItemLevel.Valid { v := int(player.AverageItemLevel.Int64); p │
│ j.AverageItemLevel = &v } │
│ if player.EquippedItemLevel.Valid { v := int(player.EquippedItemLevel.Int64); │
│ pj.EquippedItemLevel = &v } │
│ + │
│ + // Fallback: if class/spec missing (Profile API issues), derive from main_spe │
│ c_id │
│ + if (pj.ClassName == "" || pj.ActiveSpecName == "") && pj.MainSpecID != nil { │
│ + if cls, spec, ok := specIdToClassSpec(*pj.MainSpecID); ok { │
│ + if pj.ClassName == "" { pj.ClassName = cls } │
│ + if pj.ActiveSpecName == "" { pj.ActiveSpecName = spec } │
│ + } │
│ + } │
│ if player.LastUpdated.Valid { v := player.LastUpdated.Int64; pj.LastUpdated = │
│ &v } │
│ │
│ // Build best runs │
│ @@ -1187,6 +1274,27 @@ func generatePlayerLeaderboards(db *sql.DB, out string, pag │
│ eSize int, regions [] │
│ "dungeons_completed": r.DungeonsCompleted, │
│ "total_runs": r.TotalRuns, │
│ } │
│ + // Fallback class/spec if missing │
│ + if (r.ClassName == "" || r.ActiveSpecName == "") && r.MainSpecID. │
│ Valid { │
│ + if cls, spec, ok := specIdToClassSpec(int(r.MainSpecID.Int64) │
│ ); ok { │
│ + if r.ClassName == "" { obj["class_name"] = cls } │
│ + if r.ActiveSpecName == "" { obj["active_spec_name"] = spe │
│ c } │
│ + } │
│ + } │
│ + // Fallback class/spec if missing │
│ + if (r.ClassName == "" || r.ActiveSpecName == "") && r.MainSpecID. │
│ Valid { │
│ + if cls, spec, ok := specIdToClassSpec(int(r.MainSpecID.Int64) │
│ ); ok { │
│ + if r.ClassName == "" { obj["class_name"] = cls } │
│ + if r.ActiveSpecName == "" { obj["active_spec_name"] = spe │
│ c } │
│ + } │
│ + } │
│ + // Fallback class/spec if missing │
│ + if (r.ClassName == "" || r.ActiveSpecName == "") && r.MainSpecID. │
│ Valid { │
│ + if cls, spec, ok := specIdToClassSpec(int(r.MainSpecID.Int64) │
│ ); ok { │
│ + if r.ClassName == "" { obj["class_name"] = cls } │
│ + if r.ActiveSpecName == "" { obj["active_spec_name"] = spe │
│ c } │
│ + } │
│ + } │
│ if r.MainSpecID.Valid { obj["main_spec_id"] = int(r.MainSpecID.In │
│ t64) } │
│ if r.CombinedBestTime.Valid { obj["combined_best_time"] = r.Combi │
│ nedBestTime.Int64 } │
│ list = append(list, obj) │
└────────────────────────────────────────────────────────────────────────────────────┘ ┌─ .../ookstats/src/cmd/generate.go ── [ COPY ] ─┐
│ diff --git a/nix/pkgs/ookstats/src/cmd/gener │
│ ate.go b/nix/pkgs/ookstats/src/cmd/generate. │
│ go │
│ index 25fadc8..7cd348c 100644 │
│ --- a/nix/pkgs/ookstats/src/cmd/generate.go │
│ +++ b/nix/pkgs/ookstats/src/cmd/generate.go │
│ @@ -75,6 +75,85 @@ type PlayerPageJSON struc │
│ t { │
│ Version string `json: │
│ "version"` │
│ } │
│ │
│ +// --- Fallback helpers for class/spec reso │
│ lution when Profile API is unavailable --- │
│ +func specIdToClassSpec(id int) (className s │
│ tring, specName string, ok bool) { │
│ + switch id { │
│ + // Tanks │
│ + case 73: │
│ + return "Warrior", "Protection", tru │
│ e │
│ + case 104: │
│ + return "Druid", "Guardian", true │
│ + case 250: │
│ + return "Death Knight", "Blood", tru │
│ e │
│ + case 268: │
│ + return "Monk", "Brewmaster", true │
│ + case 66: │
│ + return "Paladin", "Protection", tru │
│ e │
│ + // Healers │
│ + case 105: │
│ + return "Druid", "Restoration", true │
│ + case 270: │
│ + return "Monk", "Mistweaver", true │
│ + case 65: │
│ + return "Paladin", "Holy", true │
│ + case 256: │
│ + return "Priest", "Discipline", true │
│ + case 257: │
│ + return "Priest", "Holy", true │
│ + case 264: │
│ + return "Shaman", "Restoration", tru │
│ e │
│ + // DPS │
│ + case 71: │
│ + return "Warrior", "Arms", true │
│ + case 72: │
│ + return "Warrior", "Fury", true │
│ + case 70: │
│ + return "Paladin", "Retribution", tr │
│ ue │
│ + case 253: │
│ + return "Hunter", "Beast Mastery", t │
│ rue │
│ + case 254: │
│ + return "Hunter", "Marksmanship", tr │
│ ue │
│ + case 255: │
│ + return "Hunter", "Survival", true │
│ + case 259: │
│ + return "Rogue", "Assassination", tr │
│ ue │
│ + case 260: │
│ + return "Rogue", "Outlaw", true │
│ + case 261: │
│ + return "Rogue", "Subtlety", true │
│ + case 258: │
│ + return "Priest", "Shadow", true │
│ + case 251: │
│ + return "Death Knight", "Frost", tru │
│ e │
│ + case 252: │
│ + return "Death Knight", "Unholy", tr │
│ ue │
│ + case 262: │
│ + return "Shaman", "Elemental", true │
│ + case 263: │
│ + return "Shaman", "Enhancement", tru │
│ e │
│ + case 62: │
│ + return "Mage", "Arcane", true │
│ + case 63: │
│ + return "Mage", "Fire", true │
│ + case 64: │
│ + return "Mage", "Frost", true │
│ + case 265: │
│ + return "Warlock", "Affliction", tru │
│ e │
│ + case 266: │
│ + return "Warlock", "Demonology", tru │
│ e │
│ + case 267: │
│ + return "Warlock", "Destruction", tr │
│ ue │
│ + case 269: │
│ + return "Monk", "Windwalker", true │
│ + case 102: │
│ + return "Druid", "Balance", true │
│ + case 103: │
│ + return "Druid", "Feral", true │
│ + default: │
│ + return "", "", false │
│ + } │
│ +} │
│ + │
│ // Optimized data structures for batch load │
│ ing │
│ type PlayerData struct { │
│ ID int64 │
│ @@ -686,6 +765,14 @@ func generateSinglePlay │
│ erJSON(player PlayerData, bestRunsMap map[in │
│ t64][]BestRun │
│ if player.RealmRanking.Valid { v := int │
│ (player.RealmRanking.Int64); pj.RealmRanking │
│ = &v } │
│ if player.AverageItemLevel.Valid { v := │
│ int(player.AverageItemLevel.Int64); pj.Aver │
│ ageItemLevel = &v } │
│ if player.EquippedItemLevel.Valid { v : │
│ = int(player.EquippedItemLevel.Int64); pj.Eq │
│ uippedItemLevel = &v } │
│ + │
│ + // Fallback: if class/spec missing (Pro │
│ file API issues), derive from main_spec_id │
│ + if (pj.ClassName == "" || pj.ActiveSpec │
│ Name == "") && pj.MainSpecID != nil { │
│ + if cls, spec, ok := specIdToClassSp │
│ ec(*pj.MainSpecID); ok { │
│ + if pj.ClassName == "" { pj.Clas │
│ sName = cls } │
│ + if pj.ActiveSpecName == "" { pj │
│ .ActiveSpecName = spec } │
│ + } │
│ + } │
│ if player.LastUpdated.Valid { v := play │
│ er.LastUpdated.Int64; pj.LastUpdated = &v } │
│ │
│ // Build best runs │
│ @@ -1187,6 +1274,27 @@ func generatePlayerLe │
│ aderboards(db *sql.DB, out string, pageSize │
│ int, regions [] │
│ "dungeons_completed": r │
│ .DungeonsCompleted, │
│ "total_runs": r.TotalRu │
│ ns, │
│ } │
│ + // Fallback class/spec if m │
│ issing │
│ + if (r.ClassName == "" || r. │
│ ActiveSpecName == "") && r.MainSpecID.Valid │
│ { │
│ + if cls, spec, ok := spe │
│ cIdToClassSpec(int(r.MainSpecID.Int64)); ok │
│ { │
│ + if r.ClassName == " │
│ " { obj["class_name"] = cls } │
│ + if r.ActiveSpecName │
│ == "" { obj["active_spec_name"] = spec } │
│ + } │
│ + } │
│ + // Fallback class/spec if m │
│ issing │
│ + if (r.ClassName == "" || r. │
│ ActiveSpecName == "") && r.MainSpecID.Valid │
│ { │
│ + if cls, spec, ok := spe │
│ cIdToClassSpec(int(r.MainSpecID.Int64)); ok │
│ { │
│ + if r.ClassName == " │
│ " { obj["class_name"] = cls } │
│ + if r.ActiveSpecName │
│ == "" { obj["active_spec_name"] = spec } │
│ + } │
│ + } │
│ + // Fallback class/spec if m │
│ issing │
│ + if (r.ClassName == "" || r. │
│ ActiveSpecName == "") && r.MainSpecID.Valid │
│ { │
│ + if cls, spec, ok := spe │
│ cIdToClassSpec(int(r.MainSpecID.Int64)); ok │
│ { │
│ + if r.ClassName == " │
│ " { obj["class_name"] = cls } │
│ + if r.ActiveSpecName │
│ == "" { obj["active_spec_name"] = spec } │
│ + } │
│ + } │
│ if r.MainSpecID.Valid { obj │
│ ["main_spec_id"] = int(r.MainSpecID.Int64) } │
│ if r.CombinedBestTime.Valid │
│ { obj["combined_best_time"] = r.CombinedBes │
│ tTime.Int64 } │
│ list = append(list, obj) │
└──────────────────────────────────────────────┘ ┌─ nix/pkgs/ookstats/src/internal/blizzard/constants.go ──────────────────── [ COPY ] ─┐
│ diff --git a/nix/pkgs/ookstats/src/internal/blizzard/constants.go b/nix/pkgs/ookst │
│ ats/src/internal/blizzard/constants.go │
│ index c656fe0..eb5c620 100644 │
│ --- a/nix/pkgs/ookstats/src/internal/blizzard/constants.go │
│ +++ b/nix/pkgs/ookstats/src/internal/blizzard/constants.go │
│ @@ -69,9 +69,10 @@ func GetAllRealms() map[string]RealmInfo { │
│ "faerlina": {ID: 4408, Region: "us", Name: "Faerlina", Slug: "faerl │
│ ina"}, │
│ "grobbulus": {ID: 4647, Region: "us", Name: "Grobbulus", Slug: "grob │
│ bulus"}, │
│ "bloodsail-buccaneers": {ID: 4648, Region: "us", Name: "Bloodsail Buccaneers", │
│ Slug: "bloodsail-buccaneers"}, │
│ - "remulos": {ID: 4667, Region: "us", Name: "Remulos", Slug: "remulo │
│ s"}, │
│ - "arugal": {ID: 4669, Region: "us", Name: "Arugal", Slug: "arugal" │
│ }, │
│ - "yojamba": {ID: 4670, Region: "us", Name: "Yojamba", Slug: "yojamb │
│ a"}, │
│ + // OCE realms are served under US region with -au slug suffix │
│ + "remulos-au": {ID: 4667, Region: "us", Name: "Remulos (AU)", Sl │
│ ug: "remulos-au"}, │
│ + "arugal-au": {ID: 4669, Region: "us", Name: "Arugal (AU)", Slu │
│ g: "arugal-au"}, │
│ + "yojamba-au": {ID: 4670, Region: "us", Name: "Yojamba (AU)", Sl │
│ ug: "yojamba-au"}, │
│ "skyfury": {ID: 4725, Region: "us", Name: "Skyfury", Slug: "skyfur │
│ y"}, │
│ "sulfuras": {ID: 4726, Region: "us", Name: "Sulfuras", Slug: "sulfu │
│ ras"}, │
│ "windseeker": {ID: 4727, Region: "us", Name: "Windseeker", Slug: "win │
│ dseeker"}, │
│ @@ -80,6 +81,12 @@ func GetAllRealms() map[string]RealmInfo { │
│ "maladath": {ID: 4738, Region: "us", Name: "Maladath", Slug: "malad │
│ ath"}, │
│ "angerforge": {ID: 4795, Region: "us", Name: "Angerforge", Slug: "ang │
│ erforge"}, │
│ "eranikus": {ID: 4800, Region: "us", Name: "Eranikus", Slug: "erani │
│ kus"}, │
│ + // New US realms (MoP Classic launch wave) │
│ + "nazgrim": {ID: 6359, Region: "us", Name: "Nazgrim", Slug: "n │
│ azgrim"}, │
│ + "galakras": {ID: 6360, Region: "us", Name: "Galakras", Slug: " │
│ galakras"}, │
│ + "raden": {ID: 6361, Region: "us", Name: "Ra-den", Slug: "ra │
│ den"}, │
│ + "lei-shen": {ID: 6362, Region: "us", Name: "Lei Shen", Slug: " │
│ lei-shen"}, │
│ + "immerseus": {ID: 6363, Region: "us", Name: "Immerseus", Slug: │
│ "immerseus"}, │
│ │
│ // eu realms │
│ "everlook": {ID: 4440, Region: "eu", Name: "Everlook", Slug: "everl │
│ ook"}, │
│ @@ -107,6 +114,12 @@ func GetAllRealms() map[string]RealmInfo { │
│ "mandokir": {ID: 4813, Region: "eu", Name: "Mandokir", Slug: "mando │
│ kir"}, │
│ "thekal": {ID: 4815, Region: "eu", Name: "Thekal", Slug: "thekal" │
│ }, │
│ "jindo": {ID: 4816, Region: "eu", Name: "Jin'do", Slug: "jindo"} │
│ , │
│ + // New EU realms (MoP Classic launch wave) │
│ + "shekzeer": {ID: 6364, Region: "eu", Name: "Shek'zeer", Slug: │
│ "shekzeer"}, │
│ + "garalon": {ID: 6365, Region: "eu", Name: "Garalon", Slug: "g │
│ aralon"}, │
│ + "norushen": {ID: 6366, Region: "eu", Name: "Norushen", Slug: " │
│ norushen"}, │
│ + "hoptallus": {ID: 6367, Region: "eu", Name: "Hoptallus", Slug: │
│ "hoptallus"}, │
│ + "ook-ook": {ID: 6368, Region: "eu", Name: "Ook Ook", Slug: "o │
│ ok-ook"}, │
│ │
│ // kr realms │
│ "shimmering-flats": {ID: 4417, Region: "kr", Name: "Shimmering Flats", Sl │
│ ug: "shimmering-flats"}, │
└────────────────────────────────────────────────────────────────────────────────────┘ ┌─ ...nternal/blizzard/constants.go ── [ COPY ] ─┐
│ diff --git a/nix/pkgs/ookstats/src/internal/ │
│ blizzard/constants.go b/nix/pkgs/ookstats/sr │
│ c/internal/blizzard/constants.go │
│ index c656fe0..eb5c620 100644 │
│ --- a/nix/pkgs/ookstats/src/internal/blizzar │
│ d/constants.go │
│ +++ b/nix/pkgs/ookstats/src/internal/blizzar │
│ d/constants.go │
│ @@ -69,9 +69,10 @@ func GetAllRealms() map[s │
│ tring]RealmInfo { │
│ "faerlina": {ID: 4408, Region │
│ : "us", Name: "Faerlina", Slug: "faerlina"}, │
│ "grobbulus": {ID: 4647, Region │
│ : "us", Name: "Grobbulus", Slug: "grobbulus" │
│ }, │
│ "bloodsail-buccaneers": {ID: 4648, Region │
│ : "us", Name: "Bloodsail Buccaneers", Slug: │
│ "bloodsail-buccaneers"}, │
│ - "remulos": {ID: 4667, Region │
│ : "us", Name: "Remulos", Slug: "remulos"}, │
│ - "arugal": {ID: 4669, Region │
│ : "us", Name: "Arugal", Slug: "arugal"}, │
│ - "yojamba": {ID: 4670, Region │
│ : "us", Name: "Yojamba", Slug: "yojamba"}, │
│ + // OCE realms are served under US r │
│ egion with -au slug suffix │
│ + "remulos-au": {ID: 4667, │
│ Region: "us", Name: "Remulos (AU)", Slug: "r │
│ emulos-au"}, │
│ + "arugal-au": {ID: 4669, │
│ Region: "us", Name: "Arugal (AU)", Slug: "ar │
│ ugal-au"}, │
│ + "yojamba-au": {ID: 4670, │
│ Region: "us", Name: "Yojamba (AU)", Slug: "y │
│ ojamba-au"}, │
│ "skyfury": {ID: 4725, Region │
│ : "us", Name: "Skyfury", Slug: "skyfury"}, │
│ "sulfuras": {ID: 4726, Region │
│ : "us", Name: "Sulfuras", Slug: "sulfuras"}, │
│ "windseeker": {ID: 4727, Region │
│ : "us", Name: "Windseeker", Slug: "windseeke │
│ r"}, │
│ @@ -80,6 +81,12 @@ func GetAllRealms() map[s │
│ tring]RealmInfo { │
│ "maladath": {ID: 4738, Region │
│ : "us", Name: "Maladath", Slug: "maladath"}, │
│ "angerforge": {ID: 4795, Region │
│ : "us", Name: "Angerforge", Slug: "angerforg │
│ e"}, │
│ "eranikus": {ID: 4800, Region │
│ : "us", Name: "Eranikus", Slug: "eranikus"}, │
│ + // New US realms (MoP Classic launc │
│ h wave) │
│ + "nazgrim": {ID: 6359, R │
│ egion: "us", Name: "Nazgrim", Slug: "nazgrim │
│ "}, │
│ + "galakras": {ID: 6360, R │
│ egion: "us", Name: "Galakras", Slug: "galakr │
│ as"}, │
│ + "raden": {ID: 6361, R │
│ egion: "us", Name: "Ra-den", Slug: "raden"}, │
│ + "lei-shen": {ID: 6362, R │
│ egion: "us", Name: "Lei Shen", Slug: "lei-sh │
│ en"}, │
│ + "immerseus": {ID: 6363, R │
│ egion: "us", Name: "Immerseus", Slug: "immer │
│ seus"}, │
│ │
│ // eu realms │
│ "everlook": {ID: 4440, Region │
│ : "eu", Name: "Everlook", Slug: "everlook"}, │
│ @@ -107,6 +114,12 @@ func GetAllRealms() map │
│ [string]RealmInfo { │
│ "mandokir": {ID: 4813, Region │
│ : "eu", Name: "Mandokir", Slug: "mandokir"}, │
│ "thekal": {ID: 4815, Region │
│ : "eu", Name: "Thekal", Slug: "thekal"}, │
│ "jindo": {ID: 4816, Region │
│ : "eu", Name: "Jin'do", Slug: "jindo"}, │
│ + // New EU realms (MoP Classic launc │
│ h wave) │
│ + "shekzeer": {ID: 6364, R │
│ egion: "eu", Name: "Shek'zeer", Slug: "shekz │
│ eer"}, │
│ + "garalon": {ID: 6365, R │
│ egion: "eu", Name: "Garalon", Slug: "garalon │
│ "}, │
│ + "norushen": {ID: 6366, R │
│ egion: "eu", Name: "Norushen", Slug: "norush │
│ en"}, │
│ + "hoptallus": {ID: 6367, R │
│ egion: "eu", Name: "Hoptallus", Slug: "hopta │
│ llus"}, │
│ + "ook-ook": {ID: 6368, R │
│ egion: "eu", Name: "Ook Ook", Slug: "ook-ook │
│ "}, │
│ │
│ // kr realms │
│ "shimmering-flats": {ID: 4417, Regi │
│ on: "kr", Name: "Shimmering Flats", Slug: "s │
│ himmering-flats"}, │
└──────────────────────────────────────────────┘ ┌─ nix/pkgs/ookstats/src/internal/database/operations.go ─────────────────── [ COPY ] ─┐
│ diff --git a/nix/pkgs/ookstats/src/internal/database/operations.go b/nix/pkgs/ooks │
│ tats/src/internal/database/operations.go │
│ index 1444492..5f0e187 100644 │
│ --- a/nix/pkgs/ookstats/src/internal/database/operations.go │
│ +++ b/nix/pkgs/ookstats/src/internal/database/operations.go │
│ @@ -765,7 +765,8 @@ func (ds *DatabaseService) getRealmIDTx(tx *sql.Tx, slug strin │
│ g, region string) │
│ var realmID int │
│ err := tx.QueryRow("SELECT id FROM realms WHERE slug = ? AND region = ?", slu │
│ g, region).Scan(&realmID) │
│ if err == sql.ErrNoRows { │
│ - return 0, fmt.Errorf("realm not found: %s/%s", region, slug) │
│ + // Not found is not an error for callers that may insert placeholders/ali │
│ ases │
│ + return 0, nil │
│ } │
│ return realmID, err │
│ } │
└────────────────────────────────────────────────────────────────────────────────────┘ ┌─ ...ternal/database/operations.go ── [ COPY ] ─┐
│ diff --git a/nix/pkgs/ookstats/src/internal/ │
│ database/operations.go b/nix/pkgs/ookstats/s │
│ rc/internal/database/operations.go │
│ index 1444492..5f0e187 100644 │
│ --- a/nix/pkgs/ookstats/src/internal/databas │
│ e/operations.go │
│ +++ b/nix/pkgs/ookstats/src/internal/databas │
│ e/operations.go │
│ @@ -765,7 +765,8 @@ func (ds *DatabaseServic │
│ e) getRealmIDTx(tx *sql.Tx, slug string, reg │
│ ion string) │
│ var realmID int │
│ err := tx.QueryRow("SELECT id FROM real │
│ ms WHERE slug = ? AND region = ?", slug, reg │
│ ion).Scan(&realmID) │
│ if err == sql.ErrNoRows { │
│ - return 0, fmt.Errorf("realm not fou │
│ nd: %s/%s", region, slug) │
│ + // Not found is not an error for ca │
│ llers that may insert placeholders/aliases │
│ + return 0, nil │
│ } │
│ return realmID, err │
│ } │
└──────────────────────────────────────────────┘ ┌─ web/.astro/data-store.json ────────────────────────────────────────────── [ COPY ] ─┐
│ diff --git a/web/.astro/data-store.json b/web/.astro/data-store.json │
│ index 03b5f7a..2f54464 100644 │
│ --- a/web/.astro/data-store.json │
│ +++ b/web/.astro/data-store.json │
│ @@ -1 +1 @@ │
│ -[["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.12.0","astro-config- │
│ digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{} │
│ ,\"site\":\"https://wowsimstats.com\",\"compressHTML\":true,\"base\":\"/\",\"trail │
│ ingSlash\":\"ignore\",\"output\":\"server\",\"scopedStyleStrategy\":\"attribute\", │
│ \"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astr │
│ o\",\"serverEntry\":\"entry.mjs\",\"redirects\":false,\"inlineStylesheets\":\"auto │
│ \",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":26601,\"s │
│ treaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"r │
│ oute\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\" │
│ config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"de │
│ vToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\ │
│ ",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"th │
│ eme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPl │
│ ugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":t │
│ rue},\"security\":{\"checkOrigin\":true},\"env\":{\"schema\":{},\"validateSecrets\ │
│ ":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false │
│ ,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\ │
│ ":false,\"csp\":false,\"rawEnvValues\":false},\"legacy\":{\"collections\":false}}" │
│ ] │
│ \ No newline at end of file │
│ +[["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.12.0","astro-config- │
│ digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{} │
│ ,\"site\":\"https://wowsimstats.com\",\"compressHTML\":true,\"base\":\"/\",\"trail │
│ ingSlash\":\"ignore\",\"output\":\"server\",\"scopedStyleStrategy\":\"attribute\", │
│ \"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astr │
│ o\",\"serverEntry\":\"entry.mjs\",\"redirects\":false,\"inlineStylesheets\":\"auto │
│ \",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":20679,\"s │
│ treaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"r │
│ oute\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\" │
│ config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"de │
│ vToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\ │
│ ",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"th │
│ eme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPl │
│ ugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":t │
│ rue},\"security\":{\"checkOrigin\":true},\"env\":{\"schema\":{},\"validateSecrets\ │
│ ":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false │
│ ,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\ │
│ ":false,\"csp\":false,\"rawEnvValues\":false},\"legacy\":{\"collections\":false}}" │
│ ] │
│ \ No newline at end of file │
└────────────────────────────────────────────────────────────────────────────────────┘ ┌─ web/.astro/data-store.json ──────── [ COPY ] ─┐
│ diff --git a/web/.astro/data-store.json b/we │
│ b/.astro/data-store.json │
│ index 03b5f7a..2f54464 100644 │
│ --- a/web/.astro/data-store.json │
│ +++ b/web/.astro/data-store.json │
│ @@ -1 +1 @@ │
│ -[["Map",1,2],"meta::meta",["Map",3,4,5,6]," │
│ astro-version","5.12.0","astro-config-digest │
│ ","{\"root\":{},\"srcDir\":{},\"publicDir\": │
│ {},\"outDir\":{},\"cacheDir\":{},\"site\":\" │
│ https://wowsimstats.com\",\"compressHTML\":t │
│ rue,\"base\":\"/\",\"trailingSlash\":\"ignor │
│ e\",\"output\":\"server\",\"scopedStyleStrat │
│ egy\":\"attribute\",\"build\":{\"format\":\" │
│ directory\",\"client\":{},\"server\":{},\"as │
│ sets\":\"_astro\",\"serverEntry\":\"entry.mj │
│ s\",\"redirects\":false,\"inlineStylesheets\ │
│ ":\"auto\",\"concurrency\":1},\"server\":{\" │
│ open\":false,\"host\":false,\"port\":26601,\ │
│ "streaming\":true,\"allowedHosts\":[]},\"red │
│ irects\":{},\"image\":{\"endpoint\":{\"route │
│ \":\"/_image\"},\"service\":{\"entrypoint\": │
│ \"astro/assets/services/sharp\",\"config\":{ │
│ }},\"domains\":[],\"remotePatterns\":[],\"re │
│ sponsiveStyles\":false},\"devToolbar\":{\"en │
│ abled\":true},\"markdown\":{\"syntaxHighligh │
│ t\":{\"type\":\"shiki\",\"excludeLangs\":[\" │
│ math\"]},\"shikiConfig\":{\"langs\":[],\"lan │
│ gAlias\":{},\"theme\":\"github-dark\",\"them │
│ es\":{},\"wrap\":false,\"transformers\":[]}, │
│ \"remarkPlugins\":[],\"rehypePlugins\":[],\" │
│ remarkRehype\":{},\"gfm\":true,\"smartypants │
│ \":true},\"security\":{\"checkOrigin\":true} │
│ ,\"env\":{\"schema\":{},\"validateSecrets\": │
│ false},\"experimental\":{\"clientPrerender\" │
│ :false,\"contentIntellisense\":false,\"headi │
│ ngIdCompat\":false,\"preserveScriptOrder\":f │
│ alse,\"liveContentCollections\":false,\"csp\ │
│ ":false,\"rawEnvValues\":false},\"legacy\":{ │
│ \"collections\":false}}"] │
│ \ No newline at end of file │
│ +[["Map",1,2],"meta::meta",["Map",3,4,5,6]," │
│ astro-version","5.12.0","astro-config-digest │
│ ","{\"root\":{},\"srcDir\":{},\"publicDir\": │
│ {},\"outDir\":{},\"cacheDir\":{},\"site\":\" │
│ https://wowsimstats.com\",\"compressHTML\":t │
│ rue,\"base\":\"/\",\"trailingSlash\":\"ignor │
│ e\",\"output\":\"server\",\"scopedStyleStrat │
│ egy\":\"attribute\",\"build\":{\"format\":\" │
│ directory\",\"client\":{},\"server\":{},\"as │
│ sets\":\"_astro\",\"serverEntry\":\"entry.mj │
│ s\",\"redirects\":false,\"inlineStylesheets\ │
│ ":\"auto\",\"concurrency\":1},\"server\":{\" │
│ open\":false,\"host\":false,\"port\":20679,\ │
│ "streaming\":true,\"allowedHosts\":[]},\"red │
│ irects\":{},\"image\":{\"endpoint\":{\"route │
│ \":\"/_image\"},\"service\":{\"entrypoint\": │
│ \"astro/assets/services/sharp\",\"config\":{ │
│ }},\"domains\":[],\"remotePatterns\":[],\"re │
│ sponsiveStyles\":false},\"devToolbar\":{\"en │
│ abled\":true},\"markdown\":{\"syntaxHighligh │
│ t\":{\"type\":\"shiki\",\"excludeLangs\":[\" │
│ math\"]},\"shikiConfig\":{\"langs\":[],\"lan │
│ gAlias\":{},\"theme\":\"github-dark\",\"them │
│ es\":{},\"wrap\":false,\"transformers\":[]}, │
│ \"remarkPlugins\":[],\"rehypePlugins\":[],\" │
│ remarkRehype\":{},\"gfm\":true,\"smartypants │
│ \":true},\"security\":{\"checkOrigin\":true} │
│ ,\"env\":{\"schema\":{},\"validateSecrets\": │
│ false},\"experimental\":{\"clientPrerender\" │
│ :false,\"contentIntellisense\":false,\"headi │
│ ngIdCompat\":false,\"preserveScriptOrder\":f │
│ alse,\"liveContentCollections\":false,\"csp\ │
│ ":false,\"rawEnvValues\":false},\"legacy\":{ │
│ \"collections\":false}}"] │
│ \ No newline at end of file │
└──────────────────────────────────────────────┘ ┌─ web/src/client/player-leaderboard.ts ──────────────────────────────────── [ COPY ] ─┐
│ diff --git a/web/src/client/player-leaderboard.ts b/web/src/client/player-leaderbo │
│ ard.ts │
│ index 62e00c3..33ce99b 100644 │
│ --- a/web/src/client/player-leaderboard.ts │
│ +++ b/web/src/client/player-leaderboard.ts │
│ @@ -226,9 +226,15 @@ class PlayerLeaderboardClient { │
│ ? this.formatDuration(player.combined_best_time) │
│ : "-"; │
│ const row = document.createElement("div"); │
│ - const classColor = player.class_name │
│ - ? getClassColor(player.class_name) │
│ - : "#FFFFFF"; │
│ + let classColor = "#FFFFFF"; │
│ + if (player.class_name) { │
│ + classColor = getClassColor(player.class_name); │
│ + } else if (player.main_spec_id) { │
│ + const si = getSpecInfo(Number(player.main_spec_id)); │
│ + if (si && si.class) { │
│ + classColor = getClassColor(si.class); │
│ + } │
│ + } │
│ const profileUrl = buildPlayerProfileURL( │
│ player.region || "us", │
│ player.realm_slug, │
└────────────────────────────────────────────────────────────────────────────────────┘ ┌─ .../client/player-leaderboard.ts ── [ COPY ] ─┐
│ diff --git a/web/src/client/player-leaderboa │
│ rd.ts b/web/src/client/player-leaderboard.ts │
│ index 62e00c3..33ce99b 100644 │
│ --- a/web/src/client/player-leaderboard.ts │
│ +++ b/web/src/client/player-leaderboard.ts │
│ @@ -226,9 +226,15 @@ class PlayerLeaderboard │
│ Client { │
│ ? this.formatDuration(player.combin │
│ ed_best_time) │
│ : "-"; │
│ const row = document.createElement("d │
│ iv"); │
│ - const classColor = player.class_name │
│ - ? getClassColor(player.class_name) │
│ - : "#FFFFFF"; │
│ + let classColor = "#FFFFFF"; │
│ + if (player.class_name) { │
│ + classColor = getClassColor(player.c │
│ lass_name); │
│ + } else if (player.main_spec_id) { │
│ + const si = getSpecInfo(Number(playe │
│ r.main_spec_id)); │
│ + if (si && si.class) { │
│ + classColor = getClassColor(si.cla │
│ ss); │
│ + } │
│ + } │
│ const profileUrl = buildPlayerProfile │
│ URL( │
│ player.region || "us", │
│ player.realm_slug, │
└──────────────────────────────────────────────┘ ┌─ web/src/client/player-profile.ts ──────────────────────────────────────── [ COPY ] ─┐
│ diff --git a/web/src/client/player-profile.ts b/web/src/client/player-profile.ts │
│ index 73675fc..371de53 100644 │
│ --- a/web/src/client/player-profile.ts │
│ +++ b/web/src/client/player-profile.ts │
│ @@ -4,6 +4,7 @@ import { │
│ formatDurationMMSS, │
│ } from "../lib/utils"; │
│ import { getClassTextClass } from "../lib/wow-constants"; │
│ +import { getSpecInfo } from "../lib/client-utils.ts"; │
│ import { formatRankingWithBracket as formatRank } from "../lib/client-utils.ts"; │
│ import { renderBestRunsWithWrapper } from "../lib/bestRunsRenderer"; │
│ import { │
│ @@ -149,7 +150,13 @@ class PlayerProfileManager { │
│ const regBracket = player.regional_ranking_bracket || ""; │
│ const realmBracket = player.realm_ranking_bracket || ""; │
│ │
│ - const classText = getClassTextClass(player.class_name || "common"); │
│ + // Fallback class color when class_name missing: derive from main_spec_id if │
│ available │
│ + let classNameForColor = player.class_name || ""; │
│ + if (!classNameForColor && (player.main_spec_id || player.main_spec_id === 0)) │
│ { │
│ + const si = getSpecInfo(Number(player.main_spec_id)); │
│ + if (si && si.class) classNameForColor = si.class; │
│ + } │
│ + const classText = getClassTextClass(classNameForColor || "common"); │
│ const headerHTML = ` │
│ <div class="player-header-horizontal"> │
│ <div class="player-avatar"> │
└────────────────────────────────────────────────────────────────────────────────────┘ ┌─ web/src/client/player-profile.ts ── [ COPY ] ─┐
│ diff --git a/web/src/client/player-profile.t │
│ s b/web/src/client/player-profile.ts │
│ index 73675fc..371de53 100644 │
│ --- a/web/src/client/player-profile.ts │
│ +++ b/web/src/client/player-profile.ts │
│ @@ -4,6 +4,7 @@ import { │
│ formatDurationMMSS, │
│ } from "../lib/utils"; │
│ import { getClassTextClass } from "../lib/w │
│ ow-constants"; │
│ +import { getSpecInfo } from "../lib/client- │
│ utils.ts"; │
│ import { formatRankingWithBracket as format │
│ Rank } from "../lib/client-utils.ts"; │
│ import { renderBestRunsWithWrapper } from " │
│ ../lib/bestRunsRenderer"; │
│ import { │
│ @@ -149,7 +150,13 @@ class PlayerProfileMana │
│ ger { │
│ const regBracket = player.regional_rank │
│ ing_bracket || ""; │
│ const realmBracket = player.realm_ranki │
│ ng_bracket || ""; │
│ │
│ - const classText = getClassTextClass(pla │
│ yer.class_name || "common"); │
│ + // Fallback class color when class_name │
│ missing: derive from main_spec_id if availa │
│ ble │
│ + let classNameForColor = player.class_na │
│ me || ""; │
│ + if (!classNameForColor && (player.main_ │
│ spec_id || player.main_spec_id === 0)) { │
│ + const si = getSpecInfo(Number(player. │
│ main_spec_id)); │
│ + if (si && si.class) classNameForColor │
│ = si.class; │
│ + } │
│ + const classText = getClassTextClass(cla │
│ ssNameForColor || "common"); │
│ const headerHTML = ` │
│ <div class="player-header-horizontal" │
│ > │
│ <div class="player-avatar"> │
└──────────────────────────────────────────────┘ ┌─ web/src/lib/realms.ts ─────────────────────────────────────────────────── [ COPY ] ─┐
│ diff --git a/web/src/lib/realms.ts b/web/src/lib/realms.ts │
│ index fd2e987..ce3c94f 100644 │
│ --- a/web/src/lib/realms.ts │
│ +++ b/web/src/lib/realms.ts │
│ @@ -14,9 +14,9 @@ export const REALM_DATA = { │
│ faerlina: "Faerlina", │
│ grobbulus: "Grobbulus", │
│ "bloodsail-buccaneers": "Bloodsail Buccaneers", │
│ - remulos: "Remulos", │
│ - arugal: "Arugal", │
│ - yojamba: "Yojamba", │
│ + "remulos-au": "Remulos (AU)", │
│ + "arugal-au": "Arugal (AU)", │
│ + "yojamba-au": "Yojamba (AU)", │
│ skyfury: "Skyfury", │
│ sulfuras: "Sulfuras", │
│ windseeker: "Windseeker", │
│ @@ -25,6 +25,11 @@ export const REALM_DATA = { │
│ maladath: "Maladath", │
│ angerforge: "Angerforge", │
│ eranikus: "Eranikus", │
│ + nazgrim: "Nazgrim", │
│ + galakras: "Galakras", │
│ + raden: "Ra-den", │
│ + "lei-shen": "Lei Shen", │
│ + immerseus: "Immerseus", │
│ }, │
│ eu: { │
│ everlook: "Everlook", │
│ @@ -52,6 +57,11 @@ export const REALM_DATA = { │
│ mandokir: "Mandokir", │
│ thekal: "Thekal", │
│ jindo: "Jin'do", │
│ + shekzeer: "Shek'zeer", │
│ + garalon: "Garalon", │
│ + norushen: "Norushen", │
│ + hoptallus: "Hoptallus", │
│ + "ook-ook": "Ook Ook", │
│ }, │
│ kr: { │
│ "shimmering-flats": "Shimmering Flats", │
└────────────────────────────────────────────────────────────────────────────────────┘ ┌─ web/src/lib/realms.ts ───────────── [ COPY ] ─┐
│ diff --git a/web/src/lib/realms.ts b/web/src │
│ /lib/realms.ts │
│ index fd2e987..ce3c94f 100644 │
│ --- a/web/src/lib/realms.ts │
│ +++ b/web/src/lib/realms.ts │
│ @@ -14,9 +14,9 @@ export const REALM_DATA = │
│ { │
│ faerlina: "Faerlina", │
│ grobbulus: "Grobbulus", │
│ "bloodsail-buccaneers": "Bloodsail Bucc │
│ aneers", │
│ - remulos: "Remulos", │
│ - arugal: "Arugal", │
│ - yojamba: "Yojamba", │
│ + "remulos-au": "Remulos (AU)", │
│ + "arugal-au": "Arugal (AU)", │
│ + "yojamba-au": "Yojamba (AU)", │
│ skyfury: "Skyfury", │
│ sulfuras: "Sulfuras", │
│ windseeker: "Windseeker", │
│ @@ -25,6 +25,11 @@ export const REALM_DATA = │
│ { │
│ maladath: "Maladath", │
│ angerforge: "Angerforge", │
│ eranikus: "Eranikus", │
│ + nazgrim: "Nazgrim", │
│ + galakras: "Galakras", │
│ + raden: "Ra-den", │
│ + "lei-shen": "Lei Shen", │
│ + immerseus: "Immerseus", │
│ }, │
│ eu: { │
│ everlook: "Everlook", │
│ @@ -52,6 +57,11 @@ export const REALM_DATA = │
│ { │
│ mandokir: "Mandokir", │
│ thekal: "Thekal", │
│ jindo: "Jin'do", │
│ + shekzeer: "Shek'zeer", │
│ + garalon: "Garalon", │
│ + norushen: "Norushen", │
│ + hoptallus: "Hoptallus", │
│ + "ook-ook": "Ook Ook", │
│ }, │
│ kr: { │
│ "shimmering-flats": "Shimmering Flats", │
└──────────────────────────────────────────────┘ [ BACK TO LOG ] ──────────────────────────────────────────────────────────────────────────────────────
OOKNET ────────────────────────────────────────────────
OOKNET