diff --git a/src/app/api/ratings/archives/[region]/[season]/route.ts b/src/app/api/ratings/archives/[region]/[season]/route.ts new file mode 100644 index 000000000..01dd9cb31 --- /dev/null +++ b/src/app/api/ratings/archives/[region]/[season]/route.ts @@ -0,0 +1,12 @@ +import { NextResponse } from 'next/server'; +import { Region } from '../../../../../../constants/regions'; +import { getArchivedRatingsLeaderboard } from '../../../../../../core/blitzkrieg/getArchivedRatingsLeaderboard'; + +export async function GET( + request: Request, + { params }: { params: { region: Region; season: number } }, +) { + return NextResponse.json( + await getArchivedRatingsLeaderboard(params.region, params.season), + ); +} diff --git a/src/app/api/ratings/latest-archived-season-number/route.ts b/src/app/api/ratings/latest-archived-season-number/route.ts new file mode 100644 index 000000000..241f9777c --- /dev/null +++ b/src/app/api/ratings/latest-archived-season-number/route.ts @@ -0,0 +1,6 @@ +import { NextResponse } from 'next/server'; +import { getArchivedLatestSeasonNumber } from '../../../../core/blitzkrieg/getArchivedLatestSeasonNumber'; + +export async function GET() { + return NextResponse.json(await getArchivedLatestSeasonNumber()); +} diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 349db2d4a..7b222fcdb 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -7,11 +7,11 @@ import { useEffect, useState } from 'react'; import { RatingsInfo } from '../../../commands/ratings'; import PageWrapper from '../../../components/PageWrapper'; import { REGIONS, REGION_NAMES, Region } from '../../../constants/regions'; +import getRatingsInfo from '../../../core/blitz/getRatingsInfo'; import { FIRST_ARCHIVED_RATINGS_SEASON, - getLatestArchivedSeasonNumber, -} from '../../../core/blitz/getLatestArchivedSeasonNumber'; -import getRatingsInfo from '../../../core/blitz/getRatingsInfo'; + getArchivedLatestSeasonNumberAPI, +} from '../../../core/blitzkrieg/getArchivedLatestSeasonNumberAPI'; import { useRatings } from '../../../stores/ratings'; export default function Page() { @@ -27,8 +27,8 @@ export default function Page() { useEffect(() => { (async () => { const [newRatingsInfo, newLatestArchivedSeasonNumber] = await Promise.all( - [getRatingsInfo(ratings.region), getLatestArchivedSeasonNumber()], - ); + [getRatingsInfo(ratings.region), getArchivedLatestSeasonNumberAPI()], + ); setRatingsInfo(newRatingsInfo); setLatestArchivedSeasonNumber(newLatestArchivedSeasonNumber); diff --git a/src/commands/ratings.tsx b/src/commands/ratings.tsx index 18c4f4504..ac3bb9d25 100644 --- a/src/commands/ratings.tsx +++ b/src/commands/ratings.tsx @@ -4,6 +4,7 @@ import { SlashCommandSubcommandBuilder, SlashCommandSubcommandGroupBuilder, } from 'discord.js'; +import { range } from 'lodash'; import { BlitzkriegRatingsLeaderboard } from '../../scripts/buildRatingsLeaderboard'; import * as Leaderboard from '../components/Leaderboard'; import TitleBar from '../components/TitleBar'; @@ -13,11 +14,11 @@ import { getAccountInfo } from '../core/blitz/getAccountInfo'; import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo'; import getRatingsInfo from '../core/blitz/getRatingsInfo'; import regionToRegionSubdomain from '../core/blitz/regionToRegionSubdomain'; +import { getArchivedLatestSeasonNumber } from '../core/blitzkrieg/getArchivedLatestSeasonNumber'; +import { FIRST_ARCHIVED_RATINGS_SEASON } from '../core/blitzkrieg/getArchivedLatestSeasonNumberAPI'; import getArchivedRatingsInfo from '../core/blitzkrieg/getArchivedRatingsInfo'; -import getMidnightLeaderboard, { - DATABASE_REPO, -} from '../core/blitzkrieg/getMidnightLeaderboard'; -import { octokit } from '../core/blitzkrieg/octokit'; +import { getArchivedRatingsLeaderboard } from '../core/blitzkrieg/getArchivedRatingsLeaderboard'; +import getArchivedRatingsMidnightLeaderboard from '../core/blitzkrieg/getArchivedRatingsMidnightLeaderboard'; import { UserError } from '../core/blitzkrieg/userError'; import addRegionChoices from '../core/discord/addRegionChoices'; import addUsernameChoices from '../core/discord/addUsernameChoices'; @@ -178,23 +179,17 @@ const noOngoingSeason = embedNegative( export const ratingsCommand = new Promise( async (resolve) => { - const seasonChoices = await octokit.repos - .getContent({ - ...DATABASE_REPO, - path: 'com/ratings', - }) - .then(({ data }) => { - if (!Array.isArray(data)) { - throw new Error('Archived ratings data is malformed'); - } - return data.map( - (folder) => - ({ - name: `Season ${folder.name}`, - value: folder.name, - }) satisfies APIApplicationCommandOptionChoice, - ); - }); + const latestArchivedSeasonNumber = await getArchivedLatestSeasonNumber(); + const seasonChoices = range( + FIRST_ARCHIVED_RATINGS_SEASON, + latestArchivedSeasonNumber + 1, + ).map( + (number) => + ({ + name: `Season ${number}`, + value: `${number}`, + }) satisfies APIApplicationCommandOptionChoice, + ); const command = new SlashCommandBuilder() .setName('ratings') @@ -262,7 +257,7 @@ export const ratingsCommand = new Promise( const region = interaction.options.getString('region') as Region; regionRatingsInfo = await (subcommandGroup === 'current' ? getRatingsInfo(region) - : getArchivedRatingsInfo(season!, region)); + : getArchivedRatingsInfo(region, season!)); if (regionRatingsInfo.detail !== undefined) return noOngoingSeason; @@ -285,19 +280,8 @@ export const ratingsCommand = new Promise( }) satisfies SimplifiedPlayer, ), ) - : await octokit.repos - .getContent({ - ...DATABASE_REPO, - path: `${region}/ratings/${season}/latest.json`, - }) - .then(async ({ data }) => { - if (Array.isArray(data) || data.type !== 'file') { - throw new Error('Archived ratings data is malformed'); - } - - const players = (await fetch(data.download_url!).then( - (response) => response.json(), - )) as BlitzkriegRatingsLeaderboard; + : await getArchivedRatingsLeaderboard(region, season!).then( + async (players) => { const firstPlayerIndex = leagueIndex === 0 ? 0 @@ -328,15 +312,16 @@ export const ratingsCommand = new Promise( `Unknown Player ${player.id}`, }) satisfies SimplifiedPlayer, ); - }); + }, + ); const leagueInfo = regionRatingsInfo.leagues[leagueIndex]; midnightLeaderboard = subcommandGroup === 'current' - ? await getMidnightLeaderboard( + ? await getArchivedRatingsMidnightLeaderboard( region, regionRatingsInfo.current_season, ) - : await getMidnightLeaderboard(region, season!); + : await getArchivedRatingsMidnightLeaderboard(region, season!); players = result; playersBefore = result ? result[0].position - 1 : 0; @@ -354,21 +339,7 @@ export const ratingsCommand = new Promise( regionRatingsInfo = subcommandGroup === 'current' ? await getRatingsInfo(region) - : await octokit.repos - .getContent({ - ...DATABASE_REPO, - path: `${region}/ratings/${season}/info.json`, - }) - .then(async ({ data }) => { - if (Array.isArray(data) || data.type !== 'file') { - throw new Error('Archived ratings info is malformed'); - } - - const response = fetch(data.download_url!); - return (await response.then((response) => - response.json(), - )) as RatingsInfo; - }); + : await getArchivedRatingsInfo(region, season!); if (regionRatingsInfo.detail !== undefined) return noOngoingSeason; @@ -397,22 +368,8 @@ export const ratingsCommand = new Promise( }) satisfies SimplifiedPlayer, ), ) - : await octokit.repos - .getContent({ - ...DATABASE_REPO, - path: `${region}/ratings/${season}/latest.json`, - }) - .then(async ({ data }) => { - if (Array.isArray(data) || data.type !== 'file') { - throw new Error( - 'Archived ratings latest data is malformed', - ); - } - - const response = fetch(data.download_url!); - const players = (await response.then((response) => - response.json(), - )) as BlitzkriegRatingsLeaderboard; + : await getArchivedRatingsLeaderboard(region, season!).then( + async (players) => { const playerIndex = players.findIndex( (player) => player.id === id, ); @@ -445,14 +402,15 @@ export const ratingsCommand = new Promise( clan: clanData[player.id]!.clan?.tag, nickname: clanData[player.id]!.account_name, })); - }); + }, + ); midnightLeaderboard = subcommandGroup === 'current' - ? await getMidnightLeaderboard( + ? await getArchivedRatingsMidnightLeaderboard( region, regionRatingsInfo.current_season, ) - : await getMidnightLeaderboard(region, season!); + : await getArchivedRatingsMidnightLeaderboard(region, season!); if (clan && neighbors) titleNameDiscriminator = `[${clan.tag}]`; diff --git a/src/core/blitz/getLatestArchivedSeasonNumber.ts b/src/core/blitz/getLatestArchivedSeasonNumber.ts deleted file mode 100644 index 40cf7deac..000000000 --- a/src/core/blitz/getLatestArchivedSeasonNumber.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * The first every season that Blitzkrieg tracked :) - */ -export const FIRST_ARCHIVED_RATINGS_SEASON = 49; - -export async function getLatestArchivedSeasonNumber() { - return 51; -} diff --git a/src/core/blitzkrieg/context.ts b/src/core/blitzkrieg/context.ts index 07ba4b209..7d57bd9d7 100644 --- a/src/core/blitzkrieg/context.ts +++ b/src/core/blitzkrieg/context.ts @@ -1,4 +1,4 @@ -type Context = 'website' | 'bot'; +type Context = 'website' | 'server'; export const context: Context = - typeof window === 'undefined' ? 'bot' : 'website'; + typeof window === 'undefined' ? 'server' : 'website'; diff --git a/src/core/blitzkrieg/getArchivedLatestSeasonNumber.ts b/src/core/blitzkrieg/getArchivedLatestSeasonNumber.ts new file mode 100644 index 000000000..e3ba0cf51 --- /dev/null +++ b/src/core/blitzkrieg/getArchivedLatestSeasonNumber.ts @@ -0,0 +1,25 @@ +import { DATABASE_REPO } from './getArchivedRatingsMidnightLeaderboard'; +import { octokit } from './octokit'; + +let cachedLatestArchivedSeasonNumber: number | null = null; + +export async function getArchivedLatestSeasonNumber() { + if (cachedLatestArchivedSeasonNumber) { + return cachedLatestArchivedSeasonNumber; + } + + const { data } = await octokit.repos.getContent({ + ...DATABASE_REPO, + path: 'com/ratings', + }); + + if (!Array.isArray(data)) { + throw new Error('Archived ratings data is malformed'); + } + + cachedLatestArchivedSeasonNumber = Math.max( + ...data.map(({ name }) => parseInt(name)), + ); + + return cachedLatestArchivedSeasonNumber; +} diff --git a/src/core/blitzkrieg/getArchivedLatestSeasonNumberAPI.ts b/src/core/blitzkrieg/getArchivedLatestSeasonNumberAPI.ts new file mode 100644 index 000000000..45671cf13 --- /dev/null +++ b/src/core/blitzkrieg/getArchivedLatestSeasonNumberAPI.ts @@ -0,0 +1,18 @@ +let cachedLatestArchivedSeasonNumber: number | null = null; + +/** + * The first every season that Blitzkrieg tracked :) + */ +export const FIRST_ARCHIVED_RATINGS_SEASON = 49; + +export async function getArchivedLatestSeasonNumberAPI() { + if (cachedLatestArchivedSeasonNumber) { + return cachedLatestArchivedSeasonNumber; + } + + const response = await fetch(`/api/ratings/latest-archived-season-number`); + const number = parseInt(await response.text()); + cachedLatestArchivedSeasonNumber = number; + + return cachedLatestArchivedSeasonNumber; +} diff --git a/src/core/blitzkrieg/getArchivedRatingsInfo.ts b/src/core/blitzkrieg/getArchivedRatingsInfo.ts index 3a1e3157a..76f85908b 100644 --- a/src/core/blitzkrieg/getArchivedRatingsInfo.ts +++ b/src/core/blitzkrieg/getArchivedRatingsInfo.ts @@ -1,9 +1,9 @@ import { RatingsInfo } from '../../commands/ratings'; import { Region } from '../../constants/regions'; -import { DATABASE_REPO } from './getMidnightLeaderboard'; +import { DATABASE_REPO } from './getArchivedRatingsMidnightLeaderboard'; import { octokit } from './octokit'; -const ARCHIVED_RATINGS_CACHE: Record< +const INFO_CACHE: Record< Region, Record< number, @@ -18,11 +18,11 @@ const ARCHIVED_RATINGS_CACHE: Record< }; export default async function getArchivedRatingsInfo( - season: number, region: Region, + season: number, ) { - if (ARCHIVED_RATINGS_CACHE[region][season]) { - return ARCHIVED_RATINGS_CACHE[region][season]; + if (INFO_CACHE[region][season]) { + return INFO_CACHE[region][season]; } const { data } = await octokit.repos.getContent({ @@ -38,7 +38,7 @@ export default async function getArchivedRatingsInfo( detail: undefined; }; - ARCHIVED_RATINGS_CACHE[region][season] = jsonContent; + INFO_CACHE[region][season] = jsonContent; return jsonContent; } diff --git a/src/core/blitzkrieg/getArchivedRatingsLeaderboard.ts b/src/core/blitzkrieg/getArchivedRatingsLeaderboard.ts new file mode 100644 index 000000000..c253ea094 --- /dev/null +++ b/src/core/blitzkrieg/getArchivedRatingsLeaderboard.ts @@ -0,0 +1,38 @@ +import { BlitzkriegRatingsLeaderboard } from '../../../scripts/buildRatingsLeaderboard'; +import { Region } from '../../constants/regions'; +import { DATABASE_REPO } from './getArchivedRatingsMidnightLeaderboard'; +import { octokit } from './octokit'; + +const LEADERBOARD_CACHE: Record< + Region, + Record +> = { + asia: {}, + eu: {}, + com: {}, +}; + +export async function getArchivedRatingsLeaderboard( + region: Region, + season: number, +) { + if (LEADERBOARD_CACHE[region][season]) { + return LEADERBOARD_CACHE[region][season]; + } + + const { data } = await octokit.repos.getContent({ + ...DATABASE_REPO, + path: `${region}/ratings/${season}/latest.json`, + }); + + if (Array.isArray(data) || data.type !== 'file') { + throw new Error('Archived ratings data is malformed'); + } + + const response = await fetch(data.download_url!); + const json = (await response.json()) as BlitzkriegRatingsLeaderboard; + + LEADERBOARD_CACHE[region][season] = json; + + return json; +} diff --git a/src/core/blitzkrieg/getMidnightLeaderboard.ts b/src/core/blitzkrieg/getArchivedRatingsMidnightLeaderboard.ts similarity index 94% rename from src/core/blitzkrieg/getMidnightLeaderboard.ts rename to src/core/blitzkrieg/getArchivedRatingsMidnightLeaderboard.ts index 17794025d..392169365 100644 --- a/src/core/blitzkrieg/getMidnightLeaderboard.ts +++ b/src/core/blitzkrieg/getArchivedRatingsMidnightLeaderboard.ts @@ -13,7 +13,7 @@ const MIDNIGHT_LEADERBOARD_CACHE: Record< asia: {}, }; -export default async function getMidnightLeaderboard( +export default async function getArchivedRatingsMidnightLeaderboard( region: Region, season: number, ) { diff --git a/src/core/blitzstars/tankAverages.ts b/src/core/blitzstars/tankAverages.ts index 336bb5bcf..a2d1bf8ae 100644 --- a/src/core/blitzstars/tankAverages.ts +++ b/src/core/blitzstars/tankAverages.ts @@ -90,7 +90,7 @@ export const tankAverages = fetch( ) .then((response) => response.json()) .then((json) => { - if (context === 'bot') { + if (context === 'server') { const partialTankAverages: TankAverages = {}; (json as IndividualTankAverage[]).forEach((individualTankAverage) => { diff --git a/src/core/blitzstars/tankopedia.ts b/src/core/blitzstars/tankopedia.ts index f918ff7d4..3d12d61cb 100644 --- a/src/core/blitzstars/tankopedia.ts +++ b/src/core/blitzstars/tankopedia.ts @@ -23,7 +23,7 @@ export const tankopedia = fetch( ) .then(async (response) => response.json()) .then((wrapperTankopedia) => - context === 'bot' + context === 'server' ? (wrapperTankopedia as { data: Tankopedia }).data : (wrapperTankopedia as Tankopedia), );