diff --git a/.vscode/settings.json b/.vscode/settings.json index 4cc04f9af..5f186195b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -38,6 +38,7 @@ "vickers", "vitepress", "wargaming", + "webp", "winrate" ], "deno.enable": false, diff --git a/package.json b/package.json index e91f5f688..0430bdf53 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "esbuild": "^0.18.10", "escape-html": "^1.0.3", "express": "^4.18.2", + "false": "^0.0.4", "fuzzysort": "^2.0.4", "lodash": "^4.17.21", "markdown-escape": "^2.0.0", diff --git a/scripts/build.ts b/scripts/build.ts index 522841e3f..28cc244f4 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -1,5 +1,5 @@ import { BuildOptions, build } from 'esbuild'; -import { copyFile, mkdir, rm } from 'fs/promises'; +import { mkdir, rm } from 'fs/promises'; import { argv } from 'process'; const isProd = !argv.includes('--dev'); @@ -46,9 +46,7 @@ if (buildBot) { entryPoints: ['src/bot.ts'], outfile: 'dist/bot.cjs', - }).then(() => { - console.log('Bot built'); - }); + }).then(() => console.log('Bot built')); } if (buildServer) { diff --git a/src/bot.ts b/src/bot.ts index cbf0561b9..f099ef031 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -1,3 +1,4 @@ +import { Octokit } from '@octokit/rest'; import { Client, GatewayIntentBits } from 'discord.js'; import { secrets } from './core/node/secrets'; import { registerErrorHandlers } from './events/error'; @@ -12,5 +13,7 @@ export const client = new Client({ .on('guildMemberAdd', guildMemberAdd) .on('interactionCreate', interactionCreate); +export const octokit = new Octokit({ auth: secrets.GH_TOKEN }); + registerErrorHandlers(); client.login(secrets.DISCORD_TOKEN); diff --git a/src/commands/ratings.tsx b/src/commands/ratings.tsx index db20006c0..c88753844 100644 --- a/src/commands/ratings.tsx +++ b/src/commands/ratings.tsx @@ -1,4 +1,6 @@ import { SlashCommandBuilder, SlashCommandSubcommandBuilder } from 'discord.js'; +import { BlitzkriegRatingsLeaderboard } from '../../scripts/buildRatingsLeaderboard'; +import { octokit } from '../bot'; import * as Leaderboard from '../components/Leaderboard'; import TitleBar from '../components/TitleBar'; import Wrapper from '../components/Wrapper'; @@ -110,7 +112,7 @@ function optionalParameters(option: SlashCommandSubcommandBuilder) { .addIntegerOption((option) => option .setName('limit') - .setDescription('How many rows to display (default: 16)') + .setDescription('How many rows to display (default: 10)') .setRequired(false) .setMinValue(5) .setMaxValue(30), @@ -120,6 +122,8 @@ function optionalParameters(option: SlashCommandSubcommandBuilder) { const LEAGUE_INDEXES = ['diamond', 'platinum', 'gold', 'silver', 'bronze']; +export const DATABASE_REPO = { owner: 'tresabhi', repo: 'blitzkrieg-db' }; + export const ratingsCommand: CommandRegistry = { inProduction: true, inDevelopment: true, @@ -172,51 +176,91 @@ export const ratingsCommand: CommandRegistry = { const subcommandGroup = interaction.options.getSubcommandGroup(true); if (subcommandGroup === 'league') { - const limit = interaction.options.getInteger('limit') ?? 16; + const limit = interaction.options.getInteger('limit') ?? 10; const subcommand = interaction.options.getSubcommand(true); const leagueIndex = LEAGUE_INDEXES.indexOf(subcommand); const player = await resolvePlayerFromCommand(interaction); const regionSubdomain = regionToRegionSubdomain(player.region); - const { result } = await fetch( + const result = await fetch( `https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/league/${leagueIndex}/top/`, - ).then((response) => response.json() as Promise); + ) + .then((response) => response.json() as Promise) + .then(({ result }) => result.slice(0, limit)); const { nickname } = await getWargamingResponse( `https://api.wotblitz.${player.region}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${player.id}`, ).then((accounts) => accounts[player.id]); const awaitedInfo = await ratingsInfo; const leagueInfo = awaitedInfo.leagues[leagueIndex]; - const items = result.slice(0, limit).map((player) => { - const reward = awaitedInfo.rewards.find( - (reward) => - player.number >= reward.from_position && - player.number <= reward.to_position, - ); + const midnightLeaderboard = await octokit.repos + .getContent({ + ...DATABASE_REPO, + path: `${player.region}/ratings/${awaitedInfo.current_season}/midnight.json`, + }) + .then(({ data }) => { + if (!Array.isArray(data) && data.type === 'file') { + const content = Buffer.from(data.content, 'base64').toString(); + const jsonContent = JSON.parse( + content, + ) as BlitzkriegRatingsLeaderboard; - return ( - - ); - }); + return jsonContent; + } + }) + .catch(() => console.warn('No midnight leaderboard found')); + const items = await Promise.all( + result.map(async (player) => { + const reward = awaitedInfo.rewards.find( + (reward) => + player.number >= reward.from_position && + player.number <= reward.to_position, + ); + const midnightIndex = midnightLeaderboard?.findIndex( + (item) => item.id === player.spa_id, + ); + const midnightScore = + midnightIndex === undefined + ? player.score + : midnightLeaderboard![midnightIndex].score; + const midnightPosition = + midnightIndex === undefined ? player.number : midnightIndex + 1; + + return ( + + ); + }), + ); + + const playersBefore = result[0].number - 1; + const playersAfter = awaitedInfo.count - result[result.length - 1].number; return ( - {items} + + {playersBefore > 0 && } + {items} + {playersAfter > 0 && } + ); } diff --git a/src/components/Leaderboard/components/Gap.tsx b/src/components/Leaderboard/components/Gap.tsx index e69de29bb..fab9f6aba 100644 --- a/src/components/Leaderboard/components/Gap.tsx +++ b/src/components/Leaderboard/components/Gap.tsx @@ -0,0 +1,29 @@ +import { theme } from '../../../stitches.config'; + +export interface GapProps { + number: number; +} + +export function Gap({ number }: GapProps) { + return ( +
+ + + {number.toLocaleString()} more + +
+ ); +} diff --git a/src/components/Leaderboard/components/Item.tsx b/src/components/Leaderboard/components/Item.tsx index 1dc9df991..5a3d3d384 100644 --- a/src/components/Leaderboard/components/Item.tsx +++ b/src/components/Leaderboard/components/Item.tsx @@ -4,18 +4,22 @@ import { theme } from '../../../stitches.config'; export interface ItemProps { position: number; deltaPosition?: number; - points: number; - deltaPoints?: number; + score: number; + deltaScore?: number; reward?: RatingsReward; nickname: string; clan?: string; } +function normalizeImage(url: string) { + return url.endsWith('.webp') ? 'https://i.imgur.com/ZniCzbO.png' : url; +} + export function Item({ position, deltaPosition, - points, - deltaPoints, + score, + deltaScore, reward, nickname, clan, @@ -38,7 +42,7 @@ export function Item({ fontSize: 16, }} > - {position}. + {position.toLocaleString()}.
- {deltaPosition} + {Math.abs(deltaPosition).toLocaleString()}
)} @@ -111,25 +115,56 @@ export function Item({ > {reward && ( )} - {reward?.type === 'stuff' && ( + {reward && reward.count > 1 && ( - x {reward.count} + x {reward.count.toLocaleString()} )} + +
+ {deltaScore !== 0 && deltaScore !== undefined && ( + + {Math.abs(deltaScore).toLocaleString()} + + )} + {deltaScore !== 0 && deltaScore !== undefined && ( + 0 + ? 'https://i.imgur.com/qbjiXa1.png' + : 'https://i.imgur.com/3uyNhun.png' + } + /> + )} + + {score.toLocaleString()} + +
); } diff --git a/src/core/node/secrets.ts b/src/core/node/secrets.ts index 096b5d382..f3bea2763 100644 --- a/src/core/node/secrets.ts +++ b/src/core/node/secrets.ts @@ -1,8 +1,14 @@ -const SECRET_KEYS = ['DISCORD_TOKEN', 'WARGAMING_APPLICATION_ID'] as const; +export const SECRET_KEYS = [ + 'DISCORD_TOKEN', + 'WARGAMING_APPLICATION_ID', + 'GH_TOKEN', +] as const; -type SECRET_KEY = typeof SECRET_KEYS extends readonly (infer T)[] ? T : never; +export type SECRET = typeof SECRET_KEYS extends readonly (infer T)[] + ? T + : never; -export const secrets = SECRET_KEYS.reduce>>( +export const secrets = SECRET_KEYS.reduce>>( (accumulator, key) => ({ ...accumulator, @@ -12,4 +18,4 @@ export const secrets = SECRET_KEYS.reduce>>( }, }), {}, -) as Record; +) as Record; diff --git a/yarn.lock b/yarn.lock index 69a544844..0ebee1b61 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1269,6 +1269,11 @@ express@^4.18.2: utils-merge "1.0.1" vary "~1.1.2" +"false@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/false/-/false-0.0.4.tgz#8f55de5af5c03f50bf68aed57ed80e3d2176a517" + integrity sha512-CPAG3ud9Cz8NvLKlsTLr/F1aoJL6tPyhNTzaDFzS5CX6id21hVuS5sBK8wlTLuvFklpvH1idvPbW8Lbri3G7qA== + fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"