mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 02:37:05 +02:00
feat: completed /ratings league
This commit is contained in:
1 parent
eca7cbbbeb
commit
c173ece882
9 files changed
+167
-45
No files matched your search
Vendored
+1
@@ -38,6 +38,7 @@
|
||||
"vickers",
|
||||
"vitepress",
|
||||
"wargaming",
|
||||
"webp",
|
||||
"winrate"
|
||||
],
|
||||
"deno.enable": false,
|
||||
|
||||
@@ -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",
|
||||
|
||||
+2
-4
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
+69
-25
@@ -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<LeagueTop>);
|
||||
)
|
||||
.then((response) => response.json() as Promise<LeagueTop>)
|
||||
.then(({ result }) => result.slice(0, limit));
|
||||
const { nickname } = await getWargamingResponse<AccountInfo>(
|
||||
`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 (
|
||||
<Leaderboard.Item
|
||||
nickname={player.nickname}
|
||||
points={player.score}
|
||||
position={player.number}
|
||||
clan={player.clan_tag}
|
||||
reward={reward}
|
||||
deltaPoints={Math.round(Math.random() * 10 - 5)}
|
||||
deltaPosition={Math.round(Math.random() * 10 - 5)}
|
||||
key={player.spa_id}
|
||||
/>
|
||||
);
|
||||
});
|
||||
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 (
|
||||
<Leaderboard.Item
|
||||
nickname={player.nickname}
|
||||
score={player.score}
|
||||
position={player.number}
|
||||
clan={player.clan_tag}
|
||||
reward={reward}
|
||||
deltaScore={player.score - midnightScore}
|
||||
deltaPosition={midnightPosition - player.number}
|
||||
key={player.spa_id}
|
||||
/>
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
const playersBefore = result[0].number - 1;
|
||||
const playersAfter = awaitedInfo.count - result[result.length - 1].number;
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<TitleBar
|
||||
name={leagueInfo.title}
|
||||
image={`https:${leagueInfo.small_icon}`}
|
||||
image={
|
||||
leagueInfo.big_icon.startsWith('http')
|
||||
? leagueInfo.big_icon
|
||||
: `https:${leagueInfo.big_icon}`
|
||||
}
|
||||
description={`Top ${limit} players • ${new Date().toDateString()} • ${
|
||||
REGION_NAMES[player.region]
|
||||
}`}
|
||||
/>
|
||||
|
||||
<Leaderboard.Root>{items}</Leaderboard.Root>
|
||||
<Leaderboard.Root>
|
||||
{playersBefore > 0 && <Leaderboard.Gap number={playersBefore} />}
|
||||
{items}
|
||||
{playersAfter > 0 && <Leaderboard.Gap number={playersAfter} />}
|
||||
</Leaderboard.Root>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { theme } from '../../../stitches.config';
|
||||
|
||||
export interface GapProps {
|
||||
number: number;
|
||||
}
|
||||
|
||||
export function Gap({ number }: GapProps) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
padding: 8,
|
||||
backgroundColor: theme.colors.componentNonInteractive,
|
||||
borderRadius: 4,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color: theme.colors.textLowContrast,
|
||||
fontSize: 16,
|
||||
}}
|
||||
>
|
||||
+ {number.toLocaleString()} more
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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()}.
|
||||
</span>
|
||||
|
||||
<div
|
||||
@@ -95,7 +99,7 @@ export function Item({
|
||||
fontSize: 16,
|
||||
}}
|
||||
>
|
||||
{deltaPosition}
|
||||
{Math.abs(deltaPosition).toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -111,25 +115,56 @@ export function Item({
|
||||
>
|
||||
{reward && (
|
||||
<img
|
||||
style={{ width: 16, height: 16 }}
|
||||
src={
|
||||
style={{ width: 16, height: 16, objectFit: 'fill' }}
|
||||
src={normalizeImage(
|
||||
reward.type === 'vehicle'
|
||||
? reward.vehicle.image_url
|
||||
: reward.stuff.image_url
|
||||
}
|
||||
: reward.stuff.image_url,
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{reward?.type === 'stuff' && (
|
||||
{reward && reward.count > 1 && (
|
||||
<span
|
||||
style={{
|
||||
color: theme.colors.textLowContrast,
|
||||
fontSize: 16,
|
||||
}}
|
||||
>
|
||||
x {reward.count}
|
||||
x {reward.count.toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: 4,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{deltaScore !== 0 && deltaScore !== undefined && (
|
||||
<span style={{ color: theme.colors.textLowContrast, fontSize: 16 }}>
|
||||
{Math.abs(deltaScore).toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
{deltaScore !== 0 && deltaScore !== undefined && (
|
||||
<img
|
||||
style={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
}}
|
||||
src={
|
||||
deltaScore > 0
|
||||
? 'https://i.imgur.com/qbjiXa1.png'
|
||||
: 'https://i.imgur.com/3uyNhun.png'
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<span style={{ color: theme.colors.textHighContrast, fontSize: 16 }}>
|
||||
{score.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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<Partial<Record<SECRET_KEY, string>>>(
|
||||
export const secrets = SECRET_KEYS.reduce<Partial<Record<SECRET, string>>>(
|
||||
(accumulator, key) => ({
|
||||
...accumulator,
|
||||
|
||||
@@ -12,4 +18,4 @@ export const secrets = SECRET_KEYS.reduce<Partial<Record<SECRET_KEY, string>>>(
|
||||
},
|
||||
}),
|
||||
{},
|
||||
) as Record<SECRET_KEY, string>;
|
||||
) as Record<SECRET, string>;
|
||||
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user