OOKNET                             [ /  search the index  ]  
──────────────────────────────────────────────────────────────────────────────────────
══════════════════════════════════════════════════════════════════════════════════════
OOKNET   [ /  search  ]  
────────────────────────────────────────────────
════════════════════════════════════════════════
 
HASH      fbe63ca4ea66
DATE      2025-12-13
SUBJECT   discord: support seasons
FILES     8 CHANGED
HASH      fbe63ca4ea66
DATE      2025-12-13
SUBJECT   discord: support seasons
FILES     8 CHANGED
 

diff --git a/nix/pkgs/ookstats/src/internal/generator/search.go b/nix/pkgs/ookstat
s/src/internal/generator/search.go
index 04dd0c1..8b931c5 100644
--- a/nix/pkgs/ookstats/src/internal/generator/search.go
+++ b/nix/pkgs/ookstats/src/internal/generator/search.go
@@ -36,6 +36,7 @@ func GenerateSearchIndex(db *sql.DB, out string, shardSize int) 
error {
         JOIN player_profiles pp ON p.id = pp.player_id
         JOIN realms r ON p.realm_id = r.id
         WHERE pp.has_complete_coverage = 1
+          AND pp.season_id = (SELECT MAX(season_number) FROM seasons)
         ORDER BY pp.global_ranking ASC, p.name ASC
     `)
 	if err != nil {
@@ -55,6 +56,7 @@ func GenerateSearchIndex(db *sql.DB, out string, shardSize int) 
error {
       JOIN player_profiles pp ON p.id = pp.player_id
       JOIN realms r ON p.realm_id = r.id
       WHERE pp.has_complete_coverage = 1
+        AND pp.season_id = (SELECT MAX(season_number) FROM seasons)
     `).Scan(&totalPlayers); err != nil {
 		return err
 	}

diff --git a/nix/pkgs/ookstats/src/internal/
generator/search.go b/nix/pkgs/ookstats/src/
internal/generator/search.go
index 04dd0c1..8b931c5 100644
--- a/nix/pkgs/ookstats/src/internal/generat
or/search.go
+++ b/nix/pkgs/ookstats/src/internal/generat
or/search.go
@@ -36,6 +36,7 @@ func GenerateSearchIndex(d
b *sql.DB, out string, shardSize int) error 
{
         JOIN player_profiles pp ON p.id = p
p.player_id
         JOIN realms r ON p.realm_id = r.id
         WHERE pp.has_complete_coverage = 1
+          AND pp.season_id = (SELECT MAX(se
ason_number) FROM seasons)
         ORDER BY pp.global_ranking ASC, p.n
ame ASC
     `)
 	if err != nil {
@@ -55,6 +56,7 @@ func GenerateSearchIndex(d
b *sql.DB, out string, shardSize int) error 
{
       JOIN player_profiles pp ON p.id = pp.
player_id
       JOIN realms r ON p.realm_id = r.id
       WHERE pp.has_complete_coverage = 1
+        AND pp.season_id = (SELECT MAX(seas
on_number) FROM seasons)
     `).Scan(&totalPlayers); err != nil {
 		return err
 	}
 

diff --git a/web/api/discord.ts b/web/api/discord.ts
index db6037b..1fb28de 100644
--- a/web/api/discord.ts
+++ b/web/api/discord.ts
@@ -258,21 +258,24 @@ async function handleButton(interaction: DiscordInteraction)
 {
 
   // handle refresh player profile
   if (customId.startsWith("refresh_player:")) {
-    const [, region, realm, name] = customId.split(":");
+    const [, region, realm, name, season] = customId.split(":");
 
     const { handlePlayerCommand } = await import(
       "../src/discord/commands/player.js"
     );
 
+    const options: any[] = [
+      { name: "name", type: 3, value: name }, // STRING
+      { name: "region", type: 3, value: region }, // STRING
+      { name: "realm", type: 3, value: realm }, // STRING
+    ];
+    if (season) options.push({ name: "season", type: 3, value: season }); // STRI
NG
+
     const fakeInteraction: DiscordInteraction = {
       ...interaction,
       data: {
         name: "player",
-        options: [
-          { name: "name", type: 3, value: name }, // STRING
-          { name: "region", type: 3, value: region }, // STRING
-          { name: "realm", type: 3, value: realm }, // STRING
-        ],
+        options,
       },
     };
 

diff --git a/web/api/discord.ts b/web/api/di
scord.ts
index db6037b..1fb28de 100644
--- a/web/api/discord.ts
+++ b/web/api/discord.ts
@@ -258,21 +258,24 @@ async function handleB
utton(interaction: DiscordInteraction) {
 
   // handle refresh player profile
   if (customId.startsWith("refresh_player:"
)) {
-    const [, region, realm, name] = customI
d.split(":");
+    const [, region, realm, name, season] =
 customId.split(":");
 
     const { handlePlayerCommand } = await i
mport(
       "../src/discord/commands/player.js"
     );
 
+    const options: any[] = [
+      { name: "name", type: 3, value: name 
}, // STRING
+      { name: "region", type: 3, value: reg
ion }, // STRING
+      { name: "realm", type: 3, value: real
m }, // STRING
+    ];
+    if (season) options.push({ name: "seaso
n", type: 3, value: season }); // STRING
+
     const fakeInteraction: DiscordInteracti
on = {
       ...interaction,
       data: {
         name: "player",
-        options: [
-          { name: "name", type: 3, value: n
ame }, // STRING
-          { name: "region", type: 3, value:
 region }, // STRING
-          { name: "realm", type: 3, value: 
realm }, // STRING
-        ],
+        options,
       },
     };
 
 

diff --git a/web/src/discord/commands/player.ts b/web/src/discord/commands/player.
ts
index 2bb16f3..1e3e43e 100644
--- a/web/src/discord/commands/player.ts
+++ b/web/src/discord/commands/player.ts
@@ -6,7 +6,7 @@ import type {
   PlayerCommandOptions,
 } from "../types.js";
 import { fetchPlayerProfile } from "../../lib/api.js";
-import { API_BASE_URL } from "../constants.js";
+import { API_BASE_URL, DEFAULT_SEASON_ID } from "../constants.js";
 import {
   createPlayerProfileEmbed,
   createViewProfileButton,
@@ -43,6 +43,7 @@ export async function handlePlayerCommand(
   const name = options.name.trim();
   const region = options.region.toLowerCase();
   const realm = options.realm.toLowerCase();
+  const season = options.season || DEFAULT_SEASON_ID;
 
   // validate region
   if (!["us", "eu", "kr", "tw"].includes(region)) {
@@ -64,8 +65,8 @@ export async function handlePlayerCommand(
     }
 
     // create embed and button
-    const embed = createPlayerProfileEmbed(profile);
-    const buttons = createViewProfileButton(name, realm, region);
+    const embed = createPlayerProfileEmbed(profile, season);
+    const buttons = createViewProfileButton(name, realm, region, season);
 
     return {
       embeds: [embed],
@@ -101,6 +102,7 @@ function parsePlayerOptions(
     name: getOptionValue(options, "name") as string,
     region: getOptionValue(options, "region") as "us" | "eu" | "kr" | "tw",
     realm: getOptionValue(options, "realm") as string,
+    season: getOptionValue(options, "season") as string | undefined,
   };
 }
 

diff --git a/web/src/discord/commands/player
.ts b/web/src/discord/commands/player.ts
index 2bb16f3..1e3e43e 100644
--- a/web/src/discord/commands/player.ts
+++ b/web/src/discord/commands/player.ts
@@ -6,7 +6,7 @@ import type {
   PlayerCommandOptions,
 } from "../types.js";
 import { fetchPlayerProfile } from "../../l
ib/api.js";
-import { API_BASE_URL } from "../constants.
js";
+import { API_BASE_URL, DEFAULT_SEASON_ID } 
from "../constants.js";
 import {
   createPlayerProfileEmbed,
   createViewProfileButton,
@@ -43,6 +43,7 @@ export async function hand
lePlayerCommand(
   const name = options.name.trim();
   const region = options.region.toLowerCase
();
   const realm = options.realm.toLowerCase()
;
+  const season = options.season || DEFAULT_
SEASON_ID;
 
   // validate region
   if (!["us", "eu", "kr", "tw"].includes(re
gion)) {
@@ -64,8 +65,8 @@ export async function hand
lePlayerCommand(
     }
 
     // create embed and button
-    const embed = createPlayerProfileEmbed(
profile);
-    const buttons = createViewProfileButton
(name, realm, region);
+    const embed = createPlayerProfileEmbed(
profile, season);
+    const buttons = createViewProfileButton
(name, realm, region, season);
 
     return {
       embeds: [embed],
@@ -101,6 +102,7 @@ function parsePlayerOpti
ons(
     name: getOptionValue(options, "name") a
s string,
     region: getOptionValue(options, "region
") as "us" | "eu" | "kr" | "tw",
     realm: getOptionValue(options, "realm")
 as string,
+    season: getOptionValue(options, "season
") as string | undefined,
   };
 }
 
 

diff --git a/web/src/discord/constants.ts b/web/src/discord/constants.ts
index 60a9e7c..66944ad 100644
--- a/web/src/discord/constants.ts
+++ b/web/src/discord/constants.ts
@@ -137,4 +137,4 @@ export const CACHE = {
 } as const;
 
 // default season (current)
-export const DEFAULT_SEASON_ID = "1";
+export const DEFAULT_SEASON_ID = "2";

diff --git a/web/src/discord/constants.ts b/
web/src/discord/constants.ts
index 60a9e7c..66944ad 100644
--- a/web/src/discord/constants.ts
+++ b/web/src/discord/constants.ts
@@ -137,4 +137,4 @@ export const CACHE = {
 } as const;
 
 // default season (current)
-export const DEFAULT_SEASON_ID = "1";
+export const DEFAULT_SEASON_ID = "2";
 

diff --git a/web/src/discord/embeds/player-profile.ts b/web/src/discord/embeds/pla
yer-profile.ts
index 0e9536e..cf426d1 100644
--- a/web/src/discord/embeds/player-profile.ts
+++ b/web/src/discord/embeds/player-profile.ts
@@ -23,14 +23,29 @@ const BRACKET_PERCENTILES: Record<string, string> = {
 };
 
 // creates a player profile embed
-export function createPlayerProfileEmbed(profile: PlayerProfileData): Embed {
+export function createPlayerProfileEmbed(
+  profile: PlayerProfileData,
+  season: string = "2",
+): Embed {
   const player = profile.player;
   if (!player) {
     throw new Error("Player data not found");
   }
 
-  // get season 1 data (current season)
-  const seasonData = player.seasons?.["1"];
+  // get specified season data, fallback to latest season if requested season doe
sn't exist
+  let seasonData = player.seasons?.[season];
+  let actualSeason = season;
+
+  if (!seasonData && player.seasons) {
+    // find the highest season number available
+    const availableSeasons = Object.keys(player.seasons)
+      .map(Number)
+      .sort((a, b) => b - a);
+    if (availableSeasons.length > 0) {
+      actualSeason = String(availableSeasons[0]);
+      seasonData = player.seasons[actualSeason];
+    }
+  }
 
   // determine ranking bracket
   const bracket = seasonData?.global_ranking_bracket || "common";
@@ -134,13 +149,19 @@ export function createPlayerProfileEmbed(profile: PlayerProf
ileData): Embed {
     .replace(/\s+/g, "_") as keyof typeof CLASS_COLORS;
   const color = CLASS_COLORS[classKey] || 0x5865f2; // fallback to discord blurpl
e
 
+  // add footer with season info
+  const seasonNote =
+    actualSeason !== season
+      ? ` - Showing Season ${actualSeason} (Season ${season} data not available)`
+      : ` - Season ${actualSeason}`;
+
   return {
     title,
     description,
     fields,
     color,
     footer: {
-      text: "wowsimstats.com",
+      text: `wowsimstats.com${seasonNote}`,
     },
     timestamp: new Date().toISOString(),
   };
@@ -151,8 +172,9 @@ export function createViewProfileButton(
   name: string,
   realm: string,
   region: string,
+  season: string = "2",
 ): MessageComponent[] {
-  const profileUrl = `${API_BASE_URL}/player/${region}/${realm}/${name}`;
+  const profileUrl = `${API_BASE_URL}/player/${region}/${realm}/${name}?season=${
season}`;
   const armoryUrl = `https://classic-armory.org/character/${region}/mop/${realm}/
${name}`;
 
   return [
@@ -163,7 +185,7 @@ export function createViewProfileButton(
           type: ComponentType.BUTTON,
           style: ButtonStyle.PRIMARY,
           label: "? Refresh",
-          custom_id: `refresh_player:${region}:${realm}:${name}`,
+          custom_id: `refresh_player:${region}:${realm}:${name}:${season}`,
         },
         {
           type: ComponentType.BUTTON,

diff --git a/web/src/discord/embeds/player-p
rofile.ts b/web/src/discord/embeds/player-pr
ofile.ts
index 0e9536e..cf426d1 100644
--- a/web/src/discord/embeds/player-profile.
ts
+++ b/web/src/discord/embeds/player-profile.
ts
@@ -23,14 +23,29 @@ const BRACKET_PERCENTILE
S: Record<string, string> = {
 };
 
 // creates a player profile embed
-export function createPlayerProfileEmbed(pr
ofile: PlayerProfileData): Embed {
+export function createPlayerProfileEmbed(
+  profile: PlayerProfileData,
+  season: string = "2",
+): Embed {
   const player = profile.player;
   if (!player) {
     throw new Error("Player data not found"
);
   }
 
-  // get season 1 data (current season)
-  const seasonData = player.seasons?.["1"];
+  // get specified season data, fallback to
 latest season if requested season doesn't e
xist
+  let seasonData = player.seasons?.[season]
;
+  let actualSeason = season;
+
+  if (!seasonData && player.seasons) {
+    // find the highest season number avail
able
+    const availableSeasons = Object.keys(pl
ayer.seasons)
+      .map(Number)
+      .sort((a, b) => b - a);
+    if (availableSeasons.length > 0) {
+      actualSeason = String(availableSeason
s[0]);
+      seasonData = player.seasons[actualSea
son];
+    }
+  }
 
   // determine ranking bracket
   const bracket = seasonData?.global_rankin
g_bracket || "common";
@@ -134,13 +149,19 @@ export function create
PlayerProfileEmbed(profile: PlayerProfileDat
a): Embed {
     .replace(/\s+/g, "_") as keyof typeof C
LASS_COLORS;
   const color = CLASS_COLORS[classKey] || 0
x5865f2; // fallback to discord blurple
 
+  // add footer with season info
+  const seasonNote =
+    actualSeason !== season
+      ? ` - Showing Season ${actualSeason} 
(Season ${season} data not available)`
+      : ` - Season ${actualSeason}`;
+
   return {
     title,
     description,
     fields,
     color,
     footer: {
-      text: "wowsimstats.com",
+      text: `wowsimstats.com${seasonNote}`,
     },
     timestamp: new Date().toISOString(),
   };
@@ -151,8 +172,9 @@ export function createVi
ewProfileButton(
   name: string,
   realm: string,
   region: string,
+  season: string = "2",
 ): MessageComponent[] {
-  const profileUrl = `${API_BASE_URL}/playe
r/${region}/${realm}/${name}`;
+  const profileUrl = `${API_BASE_URL}/playe
r/${region}/${realm}/${name}?season=${season
}`;
   const armoryUrl = `https://classic-armory
.org/character/${region}/mop/${realm}/${name
}`;
 
   return [
@@ -163,7 +185,7 @@ export function createVi
ewProfileButton(
           type: ComponentType.BUTTON,
           style: ButtonStyle.PRIMARY,
           label: "? Refresh",
-          custom_id: `refresh_player:${regi
on}:${realm}:${name}`,
+          custom_id: `refresh_player:${regi
on}:${realm}:${name}:${season}`,
         },
         {
           type: ComponentType.BUTTON,
 

diff --git a/web/src/discord/indexes/cache.ts b/web/src/discord/indexes/cache.ts
index 19a12b9..4b0594d 100644
--- a/web/src/discord/indexes/cache.ts
+++ b/web/src/discord/indexes/cache.ts
@@ -97,7 +97,7 @@ async function loadDungeons(): Promise<CachedDungeon[]> {
   }
 
   try {
-    const url = `${API_BASE_URL}/api/leaderboard/season/1/global/index.json`;
+    const url = `${API_BASE_URL}/api/leaderboard/season/2/global/index.json`;
     const response = await fetch(url);
     if (!response.ok)
       throw new Error(`Failed to fetch dungeons: ${response.statusText}`);
@@ -124,7 +124,7 @@ async function loadClasses(): Promise<CachedClass[]> {
   }
 
   try {
-    const url = `${API_BASE_URL}/api/leaderboard/season/1/players/class/index.jso
n`;
+    const url = `${API_BASE_URL}/api/leaderboard/season/2/players/class/index.jso
n`;
     const response = await fetch(url);
     if (!response.ok)
       throw new Error(`Failed to fetch classes: ${response.statusText}`);
@@ -151,8 +151,8 @@ async function loadPlayers(): Promise<CachedPlayer[]> {
   }
 
   try {
-    // load both shards
-    const shards = ["000", "001"];
+    // load all shards (000, 001, 002, ...)
+    const shards = ["000", "001", "002"];
     const allPlayers: CachedPlayer[] = [];
 
     for (const shard of shards) {
@@ -196,7 +196,7 @@ async function loadRealms(
   }
 
   try {
-    const url = `${API_BASE_URL}/api/leaderboard/season/1/${region}/index.json`;
+    const url = `${API_BASE_URL}/api/leaderboard/season/2/${region}/index.json`;
     const response = await fetch(url);
     if (!response.ok)
       throw new Error(

diff --git a/web/src/discord/indexes/cache.t
s b/web/src/discord/indexes/cache.ts
index 19a12b9..4b0594d 100644
--- a/web/src/discord/indexes/cache.ts
+++ b/web/src/discord/indexes/cache.ts
@@ -97,7 +97,7 @@ async function loadDungeon
s(): Promise<CachedDungeon[]> {
   }
 
   try {
-    const url = `${API_BASE_URL}/api/leader
board/season/1/global/index.json`;
+    const url = `${API_BASE_URL}/api/leader
board/season/2/global/index.json`;
     const response = await fetch(url);
     if (!response.ok)
       throw new Error(`Failed to fetch dung
eons: ${response.statusText}`);
@@ -124,7 +124,7 @@ async function loadClass
es(): Promise<CachedClass[]> {
   }
 
   try {
-    const url = `${API_BASE_URL}/api/leader
board/season/1/players/class/index.json`;
+    const url = `${API_BASE_URL}/api/leader
board/season/2/players/class/index.json`;
     const response = await fetch(url);
     if (!response.ok)
       throw new Error(`Failed to fetch clas
ses: ${response.statusText}`);
@@ -151,8 +151,8 @@ async function loadPlaye
rs(): Promise<CachedPlayer[]> {
   }
 
   try {
-    // load both shards
-    const shards = ["000", "001"];
+    // load all shards (000, 001, 002, ...)
+    const shards = ["000", "001", "002"];
     const allPlayers: CachedPlayer[] = [];
 
     for (const shard of shards) {
@@ -196,7 +196,7 @@ async function loadRealm
s(
   }
 
   try {
-    const url = `${API_BASE_URL}/api/leader
board/season/1/${region}/index.json`;
+    const url = `${API_BASE_URL}/api/leader
board/season/2/${region}/index.json`;
     const response = await fetch(url);
     if (!response.ok)
       throw new Error(
 

diff --git a/web/src/discord/types.ts b/web/src/discord/types.ts
index 4d1a214..dc19672 100644
--- a/web/src/discord/types.ts
+++ b/web/src/discord/types.ts
@@ -200,6 +200,7 @@ export interface PlayerCommandOptions {
   name: string;
   region: "us" | "eu" | "kr" | "tw";
   realm: string;
+  season?: string;
 }
 
 export interface PaginationData {

diff --git a/web/src/discord/types.ts b/web/
src/discord/types.ts
index 4d1a214..dc19672 100644
--- a/web/src/discord/types.ts
+++ b/web/src/discord/types.ts
@@ -200,6 +200,7 @@ export interface PlayerC
ommandOptions {
   name: string;
   region: "us" | "eu" | "kr" | "tw";
   realm: string;
+  season?: string;
 }
 
 export interface PaginationData {
 

diff --git a/web/src/scripts/deploy-commands.ts b/web/src/scripts/deploy-commands.
ts
index 274fdfc..24e4506 100644
--- a/web/src/scripts/deploy-commands.ts
+++ b/web/src/scripts/deploy-commands.ts
@@ -43,6 +43,16 @@ const commands = [
         .setDescription("Realm")
         .setRequired(true)
         .setAutocomplete(true),
+    )
+    .addStringOption((option) =>
+      option
+        .setName("season")
+        .setDescription("Season (default: current season)")
+        .setRequired(false)
+        .addChoices(
+          { name: "Season 1", value: "1" },
+          { name: "Season 2", value: "2" },
+        ),
     ),
 
   // /leaderboard command
@@ -102,7 +112,11 @@ const commands = [
           option
             .setName("season")
             .setDescription("Season (default: current season)")
-            .setRequired(false),
+            .setRequired(false)
+            .addChoices(
+              { name: "Season 1", value: "1" },
+              { name: "Season 2", value: "2" },
+            ),
         ),
     )
     .addSubcommand((subcommand) =>
@@ -160,7 +174,11 @@ const commands = [
           option
             .setName("season")
             .setDescription("Season (default: current season)")
-            .setRequired(false),
+            .setRequired(false)
+            .addChoices(
+              { name: "Season 1", value: "1" },
+              { name: "Season 2", value: "2" },
+            ),
         ),
     ),
 ].map((command) => command.toJSON());

diff --git a/web/src/scripts/deploy-commands
.ts b/web/src/scripts/deploy-commands.ts
index 274fdfc..24e4506 100644
--- a/web/src/scripts/deploy-commands.ts
+++ b/web/src/scripts/deploy-commands.ts
@@ -43,6 +43,16 @@ const commands = [
         .setDescription("Realm")
         .setRequired(true)
         .setAutocomplete(true),
+    )
+    .addStringOption((option) =>
+      option
+        .setName("season")
+        .setDescription("Season (default: c
urrent season)")
+        .setRequired(false)
+        .addChoices(
+          { name: "Season 1", value: "1" },
+          { name: "Season 2", value: "2" },
+        ),
     ),
 
   // /leaderboard command
@@ -102,7 +112,11 @@ const commands = [
           option
             .setName("season")
             .setDescription("Season (defaul
t: current season)")
-            .setRequired(false),
+            .setRequired(false)
+            .addChoices(
+              { name: "Season 1", value: "1
" },
+              { name: "Season 2", value: "2
" },
+            ),
         ),
     )
     .addSubcommand((subcommand) =>
@@ -160,7 +174,11 @@ const commands = [
           option
             .setName("season")
             .setDescription("Season (defaul
t: current season)")
-            .setRequired(false),
+            .setRequired(false)
+            .addChoices(
+              { name: "Season 1", value: "1
" },
+              { name: "Season 2", value: "2
" },
+            ),
         ),
     ),
 ].map((command) => command.toJSON());
 
[ BACK TO LOG ]
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET