Compare commits

..
13 Commits
61 changed files with 875 additions and 235 deletions

No files matched your search

+4 -4
View File
@@ -1,8 +1,8 @@
name: CD North America Ratings Latest
name: CD Asia Ratings Latest
on:
schedule:
- cron: '0 0 * * *'
- cron: "0 0 * * *"
jobs:
host:
@@ -16,7 +16,7 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
cache: "npm"
- name: Install Yarn
run: npm install --location=global yarn
@@ -24,7 +24,7 @@ jobs:
- name: Use node_modules cache
uses: actions/cache@v3
with:
path: '**/node_modules'
path: "**/node_modules"
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- name: Install dependencies
@@ -1,8 +1,8 @@
name: CD North America Ratings Leaderboards
name: CD Asia Ratings Leaderboards
on:
schedule:
- cron: '0 17 * * *'
- cron: "0 17 * * *"
jobs:
host:
@@ -16,7 +16,7 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
cache: "npm"
- name: Install Yarn
run: npm install --location=global yarn
@@ -24,7 +24,7 @@ jobs:
- name: Use node_modules cache
uses: actions/cache@v3
with:
path: '**/node_modules'
path: "**/node_modules"
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- name: Install dependencies
+4 -4
View File
@@ -1,8 +1,8 @@
name: CD North America Ratings Latest
name: CD Europe Ratings Latest
on:
schedule:
- cron: '0 0 * * *'
- cron: "0 0 * * *"
jobs:
host:
@@ -16,7 +16,7 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
cache: "npm"
- name: Install Yarn
run: npm install --location=global yarn
@@ -24,7 +24,7 @@ jobs:
- name: Use node_modules cache
uses: actions/cache@v3
with:
path: '**/node_modules'
path: "**/node_modules"
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- name: Install dependencies
@@ -1,8 +1,8 @@
name: CD North America Ratings Leaderboards
name: CD Europe Ratings Leaderboards
on:
schedule:
- cron: '0 23 * * *'
- cron: "0 23 * * *"
jobs:
host:
@@ -16,7 +16,7 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
cache: "npm"
- name: Install Yarn
run: npm install --location=global yarn
@@ -24,7 +24,7 @@ jobs:
- name: Use node_modules cache
uses: actions/cache@v3
with:
path: '**/node_modules'
path: "**/node_modules"
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- name: Install dependencies
+2
View File
@@ -28,6 +28,7 @@
"statsfull",
"Stringifying",
"strvk",
"subnation",
"tankaverages",
"tankopedia",
"tankstats",
@@ -37,6 +38,7 @@
"vickers",
"vitepress",
"wargaming",
"webp",
"winrate"
],
"deno.enable": false,
+22
View File
@@ -0,0 +1,22 @@
# Blitzkrieg 1.11.0
I am going on vacation so I thought I'd publish this a bit prematurely without all the planned features.
## Changes
- Added `/ratings`
- This command currently only has the `leagues` subcommand group
- Displays the top players in a league
- Also shows the changes in their positions and scores since last region midnight
- Use `/help timezones` to know when Blitzkrieg records the leaderboard
- Blitzkrieg now internally generates and keeps a permanent record of all leaderboards updated every `UTC+0` midnight
- This approach will allow us to capture the absolute latest state of the leaderboard before it is wiped for the next season
## Fixed
- Removed region "Europe" in `/verify` example
## Technical Changes
- Add scripts were upgraded to TypeScript
- Secrets now only throw errors if they're used when not provided
+8 -6
View File
@@ -5,24 +5,25 @@
"main": "src/server.ts",
"type": "module",
"scripts": {
"build:bot": "node scripts/build --build=bot",
"build:server": "node scripts/build --build=server",
"build:bot": "vite-node --script scripts/build --build=bot",
"build:server": "vite-node --script scripts/build --build=server",
"build:docs": "vitepress build docs",
"build:ratingsleaderboard": "node scripts/generateRatingsLeaderboard",
"build": "node scripts/build --build=all",
"build:ratingsleaderboard": "vite-node --script scripts/buildRatingsLeaderboard",
"build": "vite-node --script scripts/build --build=all",
"dev:bot": "yarn build:bot --dev && yarn start:bot",
"dev:server": "yarn build:server --dev && yarn start:server",
"dev:docs": "vitepress dev docs",
"start:bot": "node --enable-source-maps --trace-warnings dist/bot.cjs",
"start:server": "node --enable-source-maps --trace-warnings dist/server.cjs",
"host:docs": "vitepress preview docs",
"publish:ratingsleaderboard": "node scripts/generateRatingsLeaderboard --publish",
"publish:ratingsleaderboard": "vite-node --script scripts/buildRatingsLeaderboard --publish",
"zip": "zip -r dist.zip dist",
"lint": "npx tsc",
"format": "prettier --write \"**/*.@(json|js|jsx|css|md|ts|tsx|html|xml|yml|gltf)\""
},
"license": "MIT",
"dependencies": {
"@octokit/rest": "^20.0.1",
"@radix-ui/colors": "^0.1.8",
"@resvg/resvg-js": "^2.4.1",
"bepaint": "^0.1.0-beta.3",
@@ -30,15 +31,16 @@
"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",
"react": "^18.2.0",
"satori": "^0.9.1",
"vite-node": "^0.33.0",
"vitepress-plugin-auto-sidebar": "^1.1.0"
},
"devDependencies": {
"@octokit/rest": "^20.0.1",
"@total-typescript/ts-reset": "^0.4.2",
"@types/escape-html": "^1.0.2",
"@types/express": "^4.17.17",
+3 -5
View File
@@ -1,4 +1,4 @@
import { build } from 'esbuild';
import { BuildOptions, build } from 'esbuild';
import { copyFile, mkdir, rm } from 'fs/promises';
import { argv } from 'process';
@@ -23,7 +23,7 @@ if (isProd) {
);
}
const commonOptions = {
const commonOptions: BuildOptions = {
platform: 'node',
loader: {
'.node': 'copy',
@@ -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,6 +1,11 @@
import { Octokit } from '@octokit/rest';
import { rm, writeFile } from 'fs/promises';
import { argv, env } from 'process';
import {
RatingsInfo,
RatingsNeighbors,
RatingsPlayer,
} from '../src/commands/ratings';
import commitMultipleFiles from './commitMultipleFiles.js';
/*
@@ -8,11 +13,13 @@ import commitMultipleFiles from './commitMultipleFiles.js';
* Central European Time (UTC+1): use 0 23 * * *.
* Central Asia Standard Time (UTC+7): use 0 17 * * *.
*
* :region/ratings/:season/info.json: season info like rewards, start & end time, etc.
* :region/ratings/:season/latest.json: the absolute latest leaderboard recorded at UTC+0 midnight
* :region/ratings/:season/leaderboards/:time.json: collection of all recorded leaderboards at local midnights
* na/ratings/49/info.json: season info like rewards, start & end time, etc.
* na/ratings/49/latest.json: the absolute latest leaderboard recorded at UTC+0 midnight
* na/ratings/49/midnight.json: last midnight's leaderboard relative to the region
*/
export type BlitzkriegRatingsLeaderboard = { id: number; score: number }[];
const publish = argv.includes('--publish');
const latest = argv.includes('--latest');
const server = argv.find((arg) => arg.startsWith('--region='))?.split('=')[1];
@@ -23,22 +30,22 @@ const NEIGHBORS = 1000;
const PLAYERS = await fetch(
`https://${server}.wotblitz.com/en/api/rating-leaderboards/season/`,
)
.then((response) => response.json())
.then((response) => response.json() as Promise<RatingsInfo>)
.then((data) => data.count);
const players = {};
const leaderboard = [];
const players: Record<number, RatingsPlayer> = {};
const leaderboard: BlitzkriegRatingsLeaderboard = [];
let registered = 0;
async function branchFromPlayer(id) {
async function branchFromPlayer(id: number) {
const { neighbors } = await fetch(
`https://${server}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${NEIGHBORS}`,
).then((response) => response.json());
).then((response) => response.json() as Promise<RatingsNeighbors>);
const firstPlayer = neighbors[0];
const lastPlayer = neighbors[neighbors.length - 1];
const branches = [];
const branches: Promise<void>[] = [];
if (!players[firstPlayer.number])
branches.push(branchFromPlayer(firstPlayer.spa_id));
if (!players[lastPlayer.number])
@@ -61,10 +68,10 @@ async function branchFromPlayer(id) {
await Promise.all(branches);
}
async function branchFromLeague(id) {
async function branchFromLeague(id: number) {
const { result } = await fetch(
`https://${server}.wotblitz.com/en/api/rating-leaderboards/league/${id}/top/`,
).then((response) => response.json());
).then((response) => response.json() as Promise<{ result: RatingsPlayer[] }>);
const firstPlayer = result[0];
const lastPlayer = result[result.length - 1];
@@ -75,7 +82,7 @@ async function branchFromLeague(id) {
]);
}
console.log('Branching form leagues...');
console.log('Branching from leagues...');
await Promise.all([
branchFromLeague(0),
branchFromLeague(1),
@@ -86,8 +93,13 @@ await Promise.all([
console.log('Converting to an array...');
for (let index = 0; index < PLAYERS; index++) {
leaderboard[index] = players[index + 1];
if (!leaderboard[index]) console.warn(`${index} had no player`);
const listing = players[index + 1];
if (!listing) console.warn(`Position ${index + 1} had no player`);
leaderboard[index] = {
id: listing.spa_id,
score: listing.score,
};
}
const leaderboardJSON = JSON.stringify(leaderboard);
@@ -97,14 +109,13 @@ if (publish) {
const info = await fetch(
`https://${server}.wotblitz.com/en/api/rating-leaderboards/season/`,
).then((response) => response.json());
const time = Math.round(Date.now() / 1000);
).then((response) => response.json() as Promise<RatingsInfo>);
const normalizedServer = server === 'na' ? 'com' : server;
const infoPath = `${normalizedServer}/ratings/${info.current_season}/info.json`;
const leaderboardPath = `${normalizedServer}/ratings/${info.current_season}/${
latest ? 'latest.json' : `leaderboards/${time}.json`
}`;
latest ? 'latest' : 'midnight'
}.json`;
const infoJSON = JSON.stringify(info);
console.log(`Publishing to season ${info.current_season}...`);
@@ -113,7 +124,7 @@ if (publish) {
'tresabhi',
'blitzkrieg-db',
'main',
`${info.current_season}/${time}`,
new Date().toString(),
[
{ path: infoPath, content: infoJSON },
{ path: leaderboardPath, content: leaderboardJSON },
@@ -1,10 +1,12 @@
import { Octokit } from '@octokit/rest';
export default async function commitMultipleFiles(
octokit,
owner,
repo,
branch,
message,
changes,
octokit: Octokit,
owner: string,
repo: string,
branch: string,
message: string,
changes: { path: string; content: string }[],
) {
// Get the latest commit SHA on the specified branch
const { data: refData } = await octokit.git.getRef({
@@ -31,12 +33,13 @@ export default async function commitMultipleFiles(
content: change.content,
encoding: 'utf-8',
});
return {
sha: blobData.sha,
path: change.path,
mode: '100644',
type: 'blob',
};
} as const;
}),
);
+5 -2
View File
@@ -1,5 +1,6 @@
import { Octokit } from '@octokit/rest';
import { Client, GatewayIntentBits } from 'discord.js';
import { DISCORD_TOKEN } from './core/node/arguments';
import { secrets } from './core/node/secrets';
import { registerErrorHandlers } from './events/error';
import guildMemberAdd from './events/guildMemberAdd';
import interactionCreate from './events/interactionCreate';
@@ -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(DISCORD_TOKEN);
client.login(secrets.DISCORD_TOKEN);
+3 -3
View File
@@ -12,7 +12,7 @@ import embedNegative from '../core/discord/embedNegative';
import embedPositive from '../core/discord/embedPositive';
import markdownTable, { TableInput } from '../core/discord/markdownTable';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { secrets } from '../core/node/secrets';
import { CommandRegistry } from '../events/interactionCreate';
import { AccountInfo } from '../types/accountInfo';
import { PossiblyPromise } from '../types/possiblyPromise';
@@ -49,9 +49,9 @@ export const eligibleCommand: CommandRegistry = {
async handler(interaction) {
const clan = interaction.options.getString('clan') as SkilledClan;
const { id, server } = await resolvePlayerFromCommand(interaction);
const { id, region: server } = await resolvePlayerFromCommand(interaction);
const accountInfo = await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`,
);
const problems: TableInput = [];
const title = `${accountInfo[id].nickname}'s eligibility for ${SKILLED_CLANS[clan]}`;
+5 -5
View File
@@ -12,7 +12,7 @@ import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton';
import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand';
import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { secrets } from '../core/node/secrets';
import { CommandRegistry } from '../events/interactionCreate';
import evolution from '../renderers/evolution';
import { StatType } from '../renderers/fullStats';
@@ -34,7 +34,7 @@ export const evolutionCommand: CommandRegistry = {
true,
) as StatType;
const player = await resolvePlayerFromCommand(interaction);
const period = resolvePeriodFromCommand(player.server, interaction);
const period = resolvePeriodFromCommand(player.region, interaction);
const tankIdRaw = interaction.options.getString('tank')!;
const tankId =
commandGroup === 'tank' ? await resolveTankId(tankIdRaw) : null;
@@ -42,7 +42,7 @@ export const evolutionCommand: CommandRegistry = {
const end = interaction.options.getInteger('end');
const { nickname } = (
await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${player.server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${player.id}`,
`https://api.wotblitz.${player.region}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${player.id}`,
)
)[player.id];
const path = interactionToURL(interaction, {
@@ -57,7 +57,7 @@ export const evolutionCommand: CommandRegistry = {
primaryButton(path, 'Refresh'),
// linkButton(`${CYCLIC_API}/${path}`, 'Embed'),
linkButton(
`https://www.blitzstars.com/player/${player.server}/${nickname}${
`https://www.blitzstars.com/player/${player.region}/${nickname}${
commandGroup === 'tank' ? `/tank/${tankId!}` : ''
}`,
'BlitzStars',
@@ -75,7 +75,7 @@ export const evolutionCommand: CommandRegistry = {
const path = url.pathname.split('/').filter(Boolean);
const commandGroup = path[1] as StatType;
const player = await resolvePlayerFromButton(interaction);
const period = resolvePeriodFromButton(player.server, interaction);
const period = resolvePeriodFromButton(player.region, interaction);
return await evolution(
commandGroup,
+5 -5
View File
@@ -12,7 +12,7 @@ import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton';
import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand';
import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { secrets } from '../core/node/secrets';
import { CommandRegistry } from '../events/interactionCreate';
import fullStats, { StatType } from '../renderers/fullStats';
import { AccountInfo } from '../types/accountInfo';
@@ -33,7 +33,7 @@ export const fullStatsCommand: CommandRegistry = {
true,
) as StatType;
const player = await resolvePlayerFromCommand(interaction);
const period = resolvePeriodFromCommand(player.server, interaction);
const period = resolvePeriodFromCommand(player.region, interaction);
const tankIdRaw = interaction.options.getString('tank')!;
const tankId =
commandGroup === 'tank' ? await resolveTankId(tankIdRaw) : null;
@@ -47,7 +47,7 @@ export const fullStatsCommand: CommandRegistry = {
});
const { nickname } = (
await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${player.server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${player.id}`,
`https://api.wotblitz.${player.region}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${player.id}`,
)
)[player.id];
@@ -56,7 +56,7 @@ export const fullStatsCommand: CommandRegistry = {
primaryButton(path, 'Refresh'),
// linkButton(`${CYCLIC_API}/${path}`, 'Embed'),
linkButton(
`https://www.blitzstars.com/player/${player.server}/${nickname}`,
`https://www.blitzstars.com/player/${player.region}/${nickname}`,
'BlitzStars',
),
];
@@ -72,7 +72,7 @@ export const fullStatsCommand: CommandRegistry = {
const path = url.pathname.split('/').filter(Boolean);
const commandGroup = path[1] as StatType;
const player = await resolvePlayerFromButton(interaction);
const period = resolvePeriodFromButton(player.server, interaction);
const period = resolvePeriodFromButton(player.region, interaction);
return await fullStats(
commandGroup,
+7 -7
View File
@@ -4,12 +4,12 @@ import NoData, { NoDataType } from '../components/NoData';
import PoweredBy, { PoweredByType } from '../components/PoweredBy';
import TitleBar from '../components/TitleBar';
import Wrapper from '../components/Wrapper';
import { REGION_DOMAIN_NAMES } from '../constants/regions';
import { REGION_NAMES } from '../constants/regions';
import getBlitzClan from '../core/blitz/getBlitzClan';
import getWargamingResponse from '../core/blitz/getWargamingResponse';
import addClanChoices from '../core/discord/addClanChoices';
import autocompleteClan from '../core/discord/autocompleteClan';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { secrets } from '../core/node/secrets';
import { CommandRegistry } from '../events/interactionCreate';
import { AccountInfo } from '../types/accountInfo';
import { ClanInfo } from '../types/clanInfo';
@@ -42,14 +42,14 @@ export const inactiveCommand: CommandRegistry = {
const time = new Date().getTime() / 1000;
const clanData = (
await getWargamingResponse<ClanInfo>(
`https://api.wotblitz.${server}/wotb/clans/info/?application_id=${WARGAMING_APPLICATION_ID}&clan_id=${id}`,
`https://api.wotblitz.${server}/wotb/clans/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&clan_id=${id}`,
)
)[id];
const memberIds = clanData.members_ids;
const accounts = await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${memberIds.join(
',',
)}`,
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${
secrets.WARGAMING_APPLICATION_ID
}&account_id=${memberIds.join(',')}`,
);
const inactive = memberIds
.map((memberId) => {
@@ -72,7 +72,7 @@ export const inactiveCommand: CommandRegistry = {
nameDiscriminator="(Inactivity)"
image={`https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clanData.emblem_set_id}.png`}
description={`${threshold}+ Days • ${new Date().toDateString()} • ${
REGION_DOMAIN_NAMES[server]
REGION_NAMES[server]
}`}
/>
+6 -6
View File
@@ -4,7 +4,7 @@ import PoweredBy, { PoweredByType } from '../components/PoweredBy';
import * as Tanks from '../components/Tanks';
import TitleBar from '../components/TitleBar';
import Wrapper from '../components/Wrapper';
import { REGION_DOMAIN_NAMES } from '../constants/regions';
import { REGION_NAMES } from '../constants/regions';
import getTankStats from '../core/blitz/getTankStats';
import getTreeType from '../core/blitz/getTreeType';
import getWargamingResponse from '../core/blitz/getWargamingResponse';
@@ -17,7 +17,7 @@ import {
import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { secrets } from '../core/node/secrets';
import { CommandRegistry } from '../events/interactionCreate';
import { AccountInfo } from '../types/accountInfo';
import { PlayerClanData } from '../types/playerClanData';
@@ -74,9 +74,9 @@ export const ownedTanksCommand: CommandRegistry = {
async handler(interaction) {
const tier = Number(interaction.options.getString('tier'));
const account = await resolvePlayerFromCommand(interaction);
const { id, server } = account;
const { id, region: server } = account;
const accountInfo = await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`,
);
const tankStats = await getTankStats(server, id);
const tanks = (
@@ -88,7 +88,7 @@ export const ownedTanksCommand: CommandRegistry = {
)
).filter((tank) => tank?.tier === tier);
const clanData = await getWargamingResponse<PlayerClanData>(
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`,
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`,
);
const leftColumnSize = Math.ceil(tanks.length / 2);
const leftColumn = tanks.slice(0, leftColumnSize);
@@ -105,7 +105,7 @@ export const ownedTanksCommand: CommandRegistry = {
: undefined
}
description={`Owned tanks • ${new Date().toDateString()} • ${
REGION_DOMAIN_NAMES[server]
REGION_NAMES[server]
}`}
/>
+4 -4
View File
@@ -7,7 +7,7 @@ import autocompleteUsername from '../core/discord/autocompleteUsername';
import embedInfo from '../core/discord/embedInfo';
import markdownTable, { TableInputEntry } from '../core/discord/markdownTable';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { secrets } from '../core/node/secrets';
import { CommandRegistry } from '../events/interactionCreate';
import { AccountAchievements } from '../types/accountAchievements';
import { AccountInfo } from '../types/accountInfo';
@@ -36,13 +36,13 @@ export const playerAchievementsCommand: CommandRegistry = {
async handler(interaction) {
const sortBy = (interaction.options.getString('sort') ?? 'name') as SortBy;
const account = await resolvePlayerFromCommand(interaction);
const { id, server } = account;
const { id, region: server } = account;
const accounts = await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`,
);
const accountsAchievements =
await getWargamingResponse<AccountAchievements>(
`https://api.wotblitz.${server}/wotb/account/achievements/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
`https://api.wotblitz.${server}/wotb/account/achievements/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`,
);
const accountAchievements = accountsAchievements[id];
const compound = {
+3 -3
View File
@@ -6,7 +6,7 @@ import autocompleteUsername from '../core/discord/autocompleteUsername';
import embedInfo from '../core/discord/embedInfo';
import markdownTable from '../core/discord/markdownTable';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { secrets } from '../core/node/secrets';
import { CommandRegistry } from '../events/interactionCreate';
import { AccountInfo } from '../types/accountInfo';
@@ -22,9 +22,9 @@ export const playerInfoCommand: CommandRegistry = {
async handler(interaction) {
const account = await resolvePlayerFromCommand(interaction);
const { id, server } = account;
const { id, region: server } = account;
const accounts = await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`,
);
const accountInfo = accounts[id];
+262
View File
@@ -0,0 +1,262 @@
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';
import { REGION_NAMES, Region } from '../constants/regions';
import regionToRegionSubdomain from '../core/blitz/regionToRegionSubdomain';
import addRegionChoices from '../core/discord/addRegionChoices';
import { CommandRegistry } from '../events/interactionCreate';
export interface RatingsPlayer {
spa_id: number;
mmr: number;
season_number: number;
calibrationBattlesLeft: number;
number: number;
percentile: number;
skip: boolean;
updated_at: string;
score: number;
nickname: string;
clan_tag: string;
}
export interface LeagueTop {
result: RatingsPlayer[];
}
export type RatingsReward = {
from_position: number;
to_position: number;
count: number;
} & (
| {
type: 'vehicle';
vehicle: {
id: number;
name: string;
nation: string;
subnation: string;
use_subnation_flag: boolean;
type_slug: string;
level: number;
roman_level: string;
user_string: string;
image_url: string;
preview_url: string;
is_premium: boolean;
is_collectible: boolean;
};
}
| {
type: 'stuff';
stuff: {
name: string;
count: number;
title: string;
image_url: string;
type: string;
sizes: {};
};
}
);
export interface RatingsLeague {
title: string;
small_icon: string;
big_icon: string;
background: string;
index: number;
percentile: number;
}
export interface RatingsInfo {
title: string;
icon: string | null;
start_at: string;
finish_at: string;
current_season: number;
updated_at: string;
count: number;
rewards: RatingsReward[];
leagues: RatingsLeague[];
}
export interface RatingsNeighbors {
neighbors: RatingsPlayer[];
}
console.log('Caching ratings info...');
const ratingsInfo = fetch(
'https://na.wotblitz.com/en/api/rating-leaderboards/season/',
)
.then((response) => response.json() as Promise<RatingsInfo>)
.then((info) => {
console.log('Cached ratings info');
return info;
});
function optionalParameters(option: SlashCommandSubcommandBuilder) {
return option
.addStringOption(addRegionChoices)
.addIntegerOption((option) =>
option
.setName('limit')
.setDescription('How many rows to display (default: 10)')
.setRequired(false)
.setMinValue(5)
.setMaxValue(30),
);
}
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,
inPublic: true,
inPreview: true,
command: new SlashCommandBuilder()
.setName('ratings')
.setDescription('Ratings battles statistics')
.addSubcommandGroup((option) =>
option
.setName('league')
.setDescription('Top league players')
.addSubcommand((option) =>
optionalParameters(
option
.setName('diamond')
.setDescription('Top Diamond League players'),
),
)
.addSubcommand((option) =>
optionalParameters(
option
.setName('platinum')
.setDescription('Top Platinum League players'),
),
)
.addSubcommand((option) =>
optionalParameters(
option.setName('gold').setDescription('Top Gold League players'),
),
)
.addSubcommand((option) =>
optionalParameters(
option
.setName('silver')
.setDescription('Top Silver League players'),
),
)
.addSubcommand((option) =>
optionalParameters(
option
.setName('bronze')
.setDescription('Top Bronze League players'),
),
),
),
async handler(interaction) {
const subcommandGroup = interaction.options.getSubcommandGroup(true);
if (subcommandGroup === 'league') {
const limit = interaction.options.getInteger('limit') ?? 10;
const subcommand = interaction.options.getSubcommand(true);
const leagueIndex = LEAGUE_INDEXES.indexOf(subcommand);
const region = interaction.options.getString('region') as Region;
const regionSubdomain = regionToRegionSubdomain(region);
const result = await fetch(
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/league/${leagueIndex}/top/`,
)
.then((response) => response.json() as Promise<LeagueTop>)
.then(({ result }) => result.slice(0, limit));
const awaitedInfo = await ratingsInfo;
const leagueInfo = awaitedInfo.leagues[leagueIndex];
const midnightLeaderboard = await octokit.repos
.getContent({
...DATABASE_REPO,
path: `${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 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={
leagueInfo.big_icon.startsWith('http')
? leagueInfo.big_icon
: `https:${leagueInfo.big_icon}`
}
description={`Top ${limit} players • ${new Date().toDateString()} • ${
REGION_NAMES[region]
}`}
/>
<Leaderboard.Root>
{playersBefore > 0 && <Leaderboard.Gap number={playersBefore} />}
{items}
{playersAfter > 0 && <Leaderboard.Gap number={playersAfter} />}
</Leaderboard.Root>
</Wrapper>
);
}
return [];
},
};
+7 -9
View File
@@ -1,11 +1,11 @@
import { SlashCommandBuilder } from 'discord.js';
import markdownEscape from 'markdown-escape';
import { REGION_DOMAIN_NAMES, RegionDomain } from '../constants/regions';
import { REGION_NAMES, Region } from '../constants/regions';
import getWargamingResponse from '../core/blitz/getWargamingResponse';
import addClanChoices from '../core/discord/addClanChoices';
import addServerChoices from '../core/discord/addServerChoices';
import addRegionChoices from '../core/discord/addRegionChoices';
import embedInfo from '../core/discord/embedInfo';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { secrets } from '../core/node/secrets';
import { CommandRegistry } from '../events/interactionCreate';
import { ClanList } from '../types/clanList';
@@ -17,7 +17,7 @@ export const searchClansCommand: CommandRegistry = {
command: new SlashCommandBuilder()
.setName('searchclans')
.setDescription('Search clans')
.addStringOption(addServerChoices)
.addStringOption(addRegionChoices)
.addStringOption((option) => addClanChoices(option).setAutocomplete(false))
.addIntegerOption((option) =>
option
@@ -28,17 +28,15 @@ export const searchClansCommand: CommandRegistry = {
),
async handler(interaction) {
const server = interaction.options.getString('server') as RegionDomain;
const server = interaction.options.getString('region') as Region;
const clan = interaction.options.getString('clan')!;
const limit = interaction.options.getInteger('limit') ?? 25;
const clanList = await getWargamingResponse<ClanList>(
`https://api.wotblitz.${server}/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${clan}&limit=${limit}`,
`https://api.wotblitz.${server}/wotb/clans/list/?application_id=${secrets.WARGAMING_APPLICATION_ID}&search=${clan}&limit=${limit}`,
);
return embedInfo(
`Clan search for "${markdownEscape(clan)}" in ${
REGION_DOMAIN_NAMES[server]
}`,
`Clan search for "${markdownEscape(clan)}" in ${REGION_NAMES[server]}`,
clanList.length === 0
? 'No clans found.'
: `\`\`\`\n${clanList
+9 -11
View File
@@ -1,12 +1,12 @@
import { SlashCommandBuilder } from 'discord.js';
import markdownEscape from 'markdown-escape';
import { REGION_DOMAIN_NAMES, RegionDomain } from '../constants/regions';
import { REGION_NAMES, Region } from '../constants/regions';
import getWargamingResponse from '../core/blitz/getWargamingResponse';
import { usernamePattern } from '../core/blitz/listPlayers';
import addServerChoices from '../core/discord/addServerChoices';
import addRegionChoices from '../core/discord/addRegionChoices';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import embedInfo from '../core/discord/embedInfo';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { secrets } from '../core/node/secrets';
import { CommandRegistry } from '../events/interactionCreate';
import { AccountList } from '../types/accountList';
@@ -18,7 +18,7 @@ export const searchPlayersCommand: CommandRegistry = {
command: new SlashCommandBuilder()
.setName('searchplayers')
.setDescription('Search players in a Blitz server')
.addStringOption(addServerChoices)
.addStringOption(addRegionChoices)
.addStringOption((option) =>
addUsernameChoices(option).setAutocomplete(false).setRequired(true),
)
@@ -31,22 +31,20 @@ export const searchPlayersCommand: CommandRegistry = {
),
async handler(interaction) {
const server = interaction.options.getString('server') as RegionDomain;
const server = interaction.options.getString('region') as Region;
const name = interaction.options.getString('username')!;
const limit = interaction.options.getInteger('limit') ?? 25;
const trimmedSearch = name.trim();
const players = usernamePattern.test(trimmedSearch)
? await getWargamingResponse<AccountList>(
`https://api.wotblitz.${server}/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodeURIComponent(
trimmedSearch,
)}&limit=${limit}`,
`https://api.wotblitz.${server}/wotb/account/list/?application_id=${
secrets.WARGAMING_APPLICATION_ID
}&search=${encodeURIComponent(trimmedSearch)}&limit=${limit}`,
)
: [];
return embedInfo(
`Player search for "${markdownEscape(name)}" in ${
REGION_DOMAIN_NAMES[server]
}`,
`Player search for "${markdownEscape(name)}" in ${REGION_NAMES[server]}`,
`\`\`\`${
players.length === 0
? 'No players found.'
+6 -6
View File
@@ -12,7 +12,7 @@ import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton';
import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand';
import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { secrets } from '../core/node/secrets';
import { CommandRegistry } from '../events/interactionCreate';
import { StatType } from '../renderers/fullStats';
import stats from '../renderers/stats';
@@ -26,7 +26,7 @@ export const statsCommand: CommandRegistry = {
command: addStatTypeSubCommandGroups(
new SlashCommandBuilder()
.setName('stats')
.setDescription('In-game statistics'),
.setDescription('Regular battles statistics'),
),
async handler(interaction) {
@@ -34,7 +34,7 @@ export const statsCommand: CommandRegistry = {
true,
) as StatType;
const player = await resolvePlayerFromCommand(interaction);
const period = resolvePeriodFromCommand(player.server, interaction);
const period = resolvePeriodFromCommand(player.region, interaction);
const tankIdRaw = interaction.options.getString('tank')!;
const tankId =
commandGroup === 'tank' ? await resolveTankId(tankIdRaw) : null;
@@ -48,7 +48,7 @@ export const statsCommand: CommandRegistry = {
});
const { nickname } = (
await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${player.server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${player.id}`,
`https://api.wotblitz.${player.region}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${player.id}`,
)
)[player.id];
@@ -57,7 +57,7 @@ export const statsCommand: CommandRegistry = {
primaryButton(path, 'Refresh'),
linkButton(`${CYCLIC_API}/${path}`, 'Embed'),
linkButton(
`https://www.blitzstars.com/player/${player.server}/${nickname}`,
`https://www.blitzstars.com/player/${player.region}/${nickname}`,
'BlitzStars',
),
];
@@ -73,7 +73,7 @@ export const statsCommand: CommandRegistry = {
const path = url.pathname.split('/').filter(Boolean);
const commandGroup = path[1] as StatType;
const player = await resolvePlayerFromButton(interaction);
const period = resolvePeriodFromButton(player.server, interaction);
const period = resolvePeriodFromButton(player.region, interaction);
return await stats(
commandGroup,
+3 -3
View File
@@ -8,7 +8,7 @@ import linkButton from '../core/discord/linkButton';
import primaryButton from '../core/discord/primaryButton';
import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { secrets } from '../core/node/secrets';
import { CommandRegistry } from '../events/interactionCreate';
import today from '../renderers/today';
import { AccountInfo } from '../types/accountInfo';
@@ -37,7 +37,7 @@ export const todayCommand: CommandRegistry = {
const limit = interaction.options.getInteger('limit') ?? Infinity;
const { nickname } = (
await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${player.server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${player.id}`,
`https://api.wotblitz.${player.region}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${player.id}`,
)
)[player.id];
const path = interactionToURL(interaction, player);
@@ -47,7 +47,7 @@ export const todayCommand: CommandRegistry = {
primaryButton(path, 'Refresh'),
linkButton(`${CYCLIC_API}/${path}`, 'Embed'),
linkButton(
`https://www.blitzstars.com/player/${player.server}/${nickname}`,
`https://www.blitzstars.com/player/${player.region}/${nickname}`,
'BlitzStars',
),
];
+4 -4
View File
@@ -7,7 +7,7 @@ import autocompleteUsername from '../core/discord/autocompleteUsername';
import embedNegative from '../core/discord/embedNegative';
import embedPositive from '../core/discord/embedPositive';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { secrets } from '../core/node/secrets';
import { CommandRegistry } from '../events/interactionCreate';
import { AccountInfo } from '../types/accountInfo';
import { PlayerClanData } from '../types/playerClanData';
@@ -24,12 +24,12 @@ export const verifyCommand: CommandRegistry = {
async handler(interaction) {
const blitzAccount = await resolvePlayerFromCommand(interaction);
const { id, server } = blitzAccount;
const { id, region: server } = blitzAccount;
const accountInfo = await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`,
);
const clanData = await getWargamingResponse<PlayerClanData>(
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`,
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`,
);
const clan = clanData[id]?.clan;
const clanTag = clan ? ` [${clan!.tag}]` : '';
@@ -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>
);
}
@@ -0,0 +1,170 @@
import { RatingsReward } from '../../../commands/ratings';
import { theme } from '../../../stitches.config';
export interface ItemProps {
position: number;
deltaPosition?: 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,
score,
deltaScore,
reward,
nickname,
clan,
}: ItemProps) {
return (
<div
style={{
display: 'flex',
padding: 8,
backgroundColor: theme.colors.componentInteractive,
borderRadius: 4,
alignItems: 'center',
justifyContent: 'center',
gap: 8,
}}
>
<span
style={{
color: theme.colors.textLowContrast,
fontSize: 16,
}}
>
{position.toLocaleString()}.
</span>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 4,
}}
>
<span
style={{
color: theme.colors.textHighContrast,
fontSize: 16,
}}
>
{nickname}
</span>
{clan && (
<span
style={{
color: theme.colors.textLowContrast,
fontSize: 16,
}}
>
[{clan}]
</span>
)}
</div>
{deltaPosition !== undefined && deltaPosition !== 0 && (
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 4,
}}
>
<img
style={{
width: 8,
height: 8,
}}
src={
deltaPosition > 0
? 'https://i.imgur.com/qbjiXa1.png'
: 'https://i.imgur.com/3uyNhun.png'
}
/>
<span
style={{
color: theme.colors.textLowContrast,
fontSize: 16,
}}
>
{Math.abs(deltaPosition).toLocaleString()}
</span>
</div>
)}
<div
style={{
flex: 1,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 4,
}}
>
{reward && (
<img
style={{ width: 16, height: 16, objectFit: 'cover' }}
src={normalizeImage(
reward.type === 'vehicle'
? reward.vehicle.image_url
: reward.stuff.image_url,
)}
/>
)}
{reward && reward.count > 1 && (
<span
style={{
color: theme.colors.textLowContrast,
fontSize: 16,
}}
>
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>
);
}
@@ -0,0 +1,13 @@
import { ReactNode } from 'react';
export interface RootProps {
children: ReactNode;
}
export function Root({ children }: RootProps) {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
{children}
</div>
);
}
+3
View File
@@ -0,0 +1,3 @@
export * from './components/Gap';
export * from './components/Item';
export * from './components/Root';
+2 -2
View File
@@ -1,7 +1,7 @@
export type RegionDomain = 'com' | 'eu' | 'asia';
export type Region = 'com' | 'eu' | 'asia';
export type RegionSubdomain = 'na' | 'eu' | 'asia';
export const REGION_DOMAIN_NAMES: Record<RegionDomain, string> = {
export const REGION_NAMES: Record<Region, string> = {
com: 'North America',
eu: 'Europe',
asia: 'Asia',
+2 -2
View File
@@ -1,4 +1,4 @@
import { RegionDomain } from '../../constants/regions';
import { Region } from '../../constants/regions';
import { serverAndIdPattern } from '../discord/resolvePlayerFromCommand';
import throwError from '../node/throwError';
import listClans from './listClans';
@@ -6,7 +6,7 @@ import listClans from './listClans';
export default async function getBlitzClan(clan: string) {
if (serverAndIdPattern.test(clan)) {
const [server, accountId] = clan.split('/');
return { server: server as RegionDomain, id: Number(accountId) };
return { server: server as Region, id: Number(accountId) };
} else {
const accounts = await listClans(clan);
+5 -5
View File
@@ -1,18 +1,18 @@
import { REGION_DOMAIN_NAMES, RegionDomain } from '../../constants/regions';
import { REGION_NAMES, Region } from '../../constants/regions';
import { TanksStats } from '../../types/tanksStats';
import { WARGAMING_APPLICATION_ID } from '../node/arguments';
import { secrets } from '../node/secrets';
import throwError from '../node/throwError';
import getWargamingResponse from './getWargamingResponse';
export default async function getTankStats(server: RegionDomain, id: number) {
export default async function getTankStats(server: Region, id: number) {
const tankStats = await getWargamingResponse<TanksStats>(
`https://api.wotblitz.${server}/wotb/tanks/stats/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
`https://api.wotblitz.${server}/wotb/tanks/stats/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`,
);
if (tankStats[id] === null) {
throw throwError(
'No tank stats available',
`Wargaming says there is no tank stats for this account. This account may not have any battles/tanks or exist in the ${REGION_DOMAIN_NAMES[server]} server.`,
`Wargaming says there is no tank stats for this account. This account may not have any battles/tanks or exist in the ${REGION_NAMES[server]} server.`,
);
}
+8 -8
View File
@@ -1,6 +1,6 @@
import { RegionDomain } from '../../constants/regions';
import { Region } from '../../constants/regions';
import { Clan, ClanList } from '../../types/clanList';
import { WARGAMING_APPLICATION_ID } from '../node/arguments';
import { secrets } from '../node/secrets';
import getWargamingResponse from './getWargamingResponse';
export type ClanListWithServer = (Clan & {
@@ -15,33 +15,33 @@ export default async function listClans(search: string, limit = 9) {
return (
await Promise.all([
getWargamingResponse<ClanList>(
`https://api.wotblitz.com/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${search}&limit=${normalizedLimit}`,
`https://api.wotblitz.com/wotb/clans/list/?application_id=${secrets.WARGAMING_APPLICATION_ID}&search=${search}&limit=${normalizedLimit}`,
).then(
(value) =>
value &&
value.map((account) => ({
...account,
server: 'com' as RegionDomain,
server: 'com' as Region,
})),
),
getWargamingResponse<ClanList>(
`https://api.wotblitz.eu/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${search}&limit=${normalizedLimit}`,
`https://api.wotblitz.eu/wotb/clans/list/?application_id=${secrets.WARGAMING_APPLICATION_ID}&search=${search}&limit=${normalizedLimit}`,
).then(
(value) =>
value &&
value.map((account) => ({
...account,
server: 'eu' as RegionDomain,
server: 'eu' as Region,
})),
),
getWargamingResponse<ClanList>(
`https://api.wotblitz.asia/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${search}&limit=${normalizedLimit}`,
`https://api.wotblitz.asia/wotb/clans/list/?application_id=${secrets.WARGAMING_APPLICATION_ID}&search=${search}&limit=${normalizedLimit}`,
).then(
(value) =>
value &&
value.map((account) => ({
...account,
server: 'asia' as RegionDomain,
server: 'asia' as Region,
})),
),
])
+8 -8
View File
@@ -1,6 +1,6 @@
import { RegionDomain } from '../../constants/regions';
import { Region } from '../../constants/regions';
import { Account, AccountList } from '../../types/accountList';
import { WARGAMING_APPLICATION_ID } from '../node/arguments';
import { secrets } from '../node/secrets';
import getWargamingResponse from './getWargamingResponse';
export type AccountListWithServer = (Account & {
@@ -19,33 +19,33 @@ export default async function listPlayers(search: string, limit = 9) {
return (
await Promise.all([
getWargamingResponse<AccountList>(
`https://api.wotblitz.com/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodedSearch}&limit=${normalizedLimit}`,
`https://api.wotblitz.com/wotb/account/list/?application_id=${secrets.WARGAMING_APPLICATION_ID}&search=${encodedSearch}&limit=${normalizedLimit}`,
).then(
(value) =>
value &&
value.map((account) => ({
...account,
server: 'com' as RegionDomain,
server: 'com' as Region,
})),
),
getWargamingResponse<AccountList>(
`https://api.wotblitz.eu/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodedSearch}&limit=${normalizedLimit}`,
`https://api.wotblitz.eu/wotb/account/list/?application_id=${secrets.WARGAMING_APPLICATION_ID}&search=${encodedSearch}&limit=${normalizedLimit}`,
).then(
(value) =>
value &&
value.map((account) => ({
...account,
server: 'eu' as RegionDomain,
server: 'eu' as Region,
})),
),
getWargamingResponse<AccountList>(
`https://api.wotblitz.asia/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodedSearch}&limit=${normalizedLimit}`,
`https://api.wotblitz.asia/wotb/account/list/?application_id=${secrets.WARGAMING_APPLICATION_ID}&search=${encodedSearch}&limit=${normalizedLimit}`,
).then(
(value) =>
value &&
value.map((account) => ({
...account,
server: 'asia' as RegionDomain,
server: 'asia' as Region,
})),
),
])
@@ -0,0 +1,7 @@
import { Region, RegionSubdomain } from '../../constants/regions';
export default function regionToRegionSubdomain(
regionDomain: Region,
): RegionSubdomain {
return regionDomain === 'com' ? 'na' : regionDomain;
}
+2 -2
View File
@@ -1,4 +1,4 @@
import { RegionDomain } from '../../constants/regions';
import { Region } from '../../constants/regions';
import { AllStats } from '../../types/accountInfo';
import getTankHistories, {
TankHistories,
@@ -35,7 +35,7 @@ export const emptyTankHistoryNode: TankHistoryRaw = {
};
export default async function getDiffedTankStats(
server: RegionDomain,
server: Region,
id: number,
start: number,
end: number,
+2 -2
View File
@@ -1,9 +1,9 @@
import { RegionDomain } from '../../constants/regions';
import { Region } from '../../constants/regions';
import { Period } from '../discord/addPeriodSubCommands';
import getTimeDaysAgo from './getTimeDaysAgo';
export default function getPeriodStart(
server: RegionDomain,
server: Region,
period: Exclude<Period, 'custom'>,
) {
if (period === 'career') {
+5 -5
View File
@@ -1,4 +1,4 @@
import { RegionDomain } from '../../constants/regions';
import { Region } from '../../constants/regions';
import { AccountInfo, AllStats } from '../../types/accountInfo';
import {
GetHistoriesOptions,
@@ -7,7 +7,7 @@ import {
getHistoriesDefaultOptions,
} from '../../types/histories';
import getWargamingResponse from '../blitz/getWargamingResponse';
import { WARGAMING_APPLICATION_ID } from '../node/arguments';
import { secrets } from '../node/secrets';
import { emptyAllStats } from './getDiffedTankStats';
export interface PlayerHistoryRaw {
@@ -25,13 +25,13 @@ export interface PlayerHistoryRaw {
account_id: number;
[key: string]: number;
};
region: RegionDomain;
region: Region;
}
export type PlayerHistoriesRaw = PlayerHistoryRaw[];
export default async function getPlayerHistories(
server: RegionDomain,
server: Region,
id: number,
options?: Partial<GetHistoriesOptions>,
) {
@@ -67,7 +67,7 @@ export default async function getPlayerHistories(
statistics: { all },
} = (
await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`,
)
)[id];
+2 -2
View File
@@ -1,4 +1,4 @@
import { RegionDomain } from '../../constants/regions';
import { Region } from '../../constants/regions';
import {
GetHistoriesOptions,
Histories,
@@ -32,7 +32,7 @@ export const getTankHistoriesDefaultOptions: GetTankHistoriesOptions = {
};
export default async function getTankHistories(
server: RegionDomain,
server: Region,
id: number,
options?: Partial<GetTankHistoriesOptions>,
) {
+3 -3
View File
@@ -1,12 +1,12 @@
import { RegionDomain } from '../../constants/regions';
import { Region } from '../../constants/regions';
export const TIME_ZONE_MAPPINGS: Record<RegionDomain, number> = {
export const TIME_ZONE_MAPPINGS: Record<Region, number> = {
com: -5, // Central North American Time
eu: +1, // Central European Time
asia: +7, // Central Asia Standard Time
};
export default function getTimeDaysAgo(server: RegionDomain, daysAgo: number) {
export default function getTimeDaysAgo(server: Region, daysAgo: number) {
const now = new Date();
if (daysAgo === 0) now.getTime() / 1000;
+14
View File
@@ -0,0 +1,14 @@
import { SlashCommandStringOption } from 'discord.js';
import { REGION_NAMES } from '../../constants/regions';
export default function addRegionChoices(option: SlashCommandStringOption) {
return option
.setName('region')
.setDescription('The Blitz region you are in')
.addChoices(
{ name: REGION_NAMES.com, value: 'com' },
{ name: REGION_NAMES.eu, value: 'eu' },
{ name: REGION_NAMES.asia, value: 'asia' },
)
.setRequired(true);
}
-14
View File
@@ -1,14 +0,0 @@
import { SlashCommandStringOption } from 'discord.js';
import { REGION_DOMAIN_NAMES } from '../../constants/regions';
export default function addServerChoices(option: SlashCommandStringOption) {
return option
.setName('server')
.setDescription('The Blitz server you are in')
.addChoices(
{ name: REGION_DOMAIN_NAMES.com, value: 'com' },
{ name: REGION_DOMAIN_NAMES.eu, value: 'eu' },
{ name: REGION_DOMAIN_NAMES.asia, value: 'asia' },
)
.setRequired(true);
}
+2 -2
View File
@@ -3,7 +3,7 @@ import {
AutocompleteInteraction,
CacheType,
} from 'discord.js';
import { REGION_DOMAIN_NAMES } from '../../constants/regions';
import { REGION_NAMES } from '../../constants/regions';
import listClans from '../blitz/listClans';
export default async function autocompleteClan(
@@ -18,7 +18,7 @@ export default async function autocompleteClan(
? players.map(
(player) =>
({
name: `${player.name} (${REGION_DOMAIN_NAMES[player.server]})`,
name: `${player.name} (${REGION_NAMES[player.server]})`,
value: `${player.server}/${player.clan_id}`,
}) satisfies ApplicationCommandOptionChoiceData<string>,
)
+2 -4
View File
@@ -3,7 +3,7 @@ import {
AutocompleteInteraction,
CacheType,
} from 'discord.js';
import { REGION_DOMAIN_NAMES } from '../../constants/regions';
import { REGION_NAMES } from '../../constants/regions';
import listPlayers from '../blitz/listPlayers';
export default async function autocompleteUsername(
@@ -19,9 +19,7 @@ export default async function autocompleteUsername(
? players.map(
(player) =>
({
name: `${player.nickname} (${
REGION_DOMAIN_NAMES[player.server]
})`,
name: `${player.nickname} (${REGION_NAMES[player.server]})`,
value: `${player.server}/${player.account_id}`,
}) satisfies ApplicationCommandOptionChoiceData<string>,
)
+2 -2
View File
@@ -1,10 +1,10 @@
import { ButtonInteraction, CacheType } from 'discord.js';
import { CYCLIC_API } from '../../constants/cyclic';
import { RegionDomain } from '../../constants/regions';
import { Region } from '../../constants/regions';
import resolvePeriodFromURL from '../express/resolvePeriodFromURL';
export default function resolvePeriodFromButton(
server: RegionDomain,
server: Region,
interaction: ButtonInteraction<CacheType>,
) {
return resolvePeriodFromURL(server, `${CYCLIC_API}/${interaction.customId}`);
+2 -2
View File
@@ -1,5 +1,5 @@
import { CacheType, ChatInputCommandInteraction } from 'discord.js';
import { RegionDomain } from '../../constants/regions';
import { Region } from '../../constants/regions';
import getPeriodNow from '../blitzstars/getPeriodNow';
import getPeriodStart from '../blitzstars/getPeriodStart';
import getTimeDaysAgo from '../blitzstars/getTimeDaysAgo';
@@ -31,7 +31,7 @@ export interface ResolvedPeriod {
}
export default function resolvePeriodFromCommand(
server: RegionDomain,
server: Region,
interaction: ChatInputCommandInteraction<CacheType>,
) {
let statsName: string;
+4 -4
View File
@@ -1,5 +1,5 @@
import { CacheType, ChatInputCommandInteraction } from 'discord.js';
import { RegionDomain } from '../../constants/regions';
import { Region } from '../../constants/regions';
import listPlayers, {
usernamePatternWithoutPosition,
} from '../blitz/listPlayers';
@@ -8,7 +8,7 @@ import throwError from '../node/throwError';
export const serverAndIdPattern = /(com|eu|asia)\/[0-9]+/;
export interface ResolvedPlayer {
server: RegionDomain;
region: Region;
id: number;
}
@@ -31,7 +31,7 @@ export default async function resolvePlayerFromCommand(
if (serverAndIdPattern.test(username)) {
const [server, accountId] = username.split('/');
return {
server: server as RegionDomain,
region: server as Region,
id: Number(accountId),
} satisfies ResolvedPlayer;
} else {
@@ -39,7 +39,7 @@ export default async function resolvePlayerFromCommand(
if (accounts[0]) {
return {
server: accounts[0].server,
region: accounts[0].server,
id: accounts[0].account_id,
} satisfies ResolvedPlayer;
} else {
+2 -2
View File
@@ -1,10 +1,10 @@
import { Request } from 'express';
import { CYCLIC_API } from '../../constants/cyclic';
import { RegionDomain } from '../../constants/regions';
import { Region } from '../../constants/regions';
import resolvePeriodFromURL from './resolvePeriodFromURL';
export default function resolvePeriodFromRequest(
server: RegionDomain,
server: Region,
request: Request,
) {
return resolvePeriodFromURL(server, `${CYCLIC_API}${request.path}`);
+2 -2
View File
@@ -1,4 +1,4 @@
import { RegionDomain } from '../../constants/regions';
import { Region } from '../../constants/regions';
import getPeriodNow from '../blitzstars/getPeriodNow';
import getPeriodStart from '../blitzstars/getPeriodStart';
import getTimeDaysAgo from '../blitzstars/getTimeDaysAgo';
@@ -10,7 +10,7 @@ import {
} from '../discord/resolvePeriodFromCommand';
export default function resolvePeriodFromURL(
server: RegionDomain,
server: Region,
urlString: string,
) {
let statsName: string;
+2 -2
View File
@@ -1,4 +1,4 @@
import { RegionDomain } from '../../constants/regions';
import { Region } from '../../constants/regions';
import { ResolvedPlayer } from '../discord/resolvePlayerFromCommand';
export default function resolvePlayerFromURL(urlString: string) {
@@ -6,6 +6,6 @@ export default function resolvePlayerFromURL(urlString: string) {
return {
id: parseInt(url.searchParams.get('id')!),
server: url.searchParams.get('server') as RegionDomain,
region: url.searchParams.get('region') as Region,
} satisfies ResolvedPlayer;
}
-9
View File
@@ -1,9 +0,0 @@
export const DISCORD_TOKEN = process.env.DISCORD_TOKEN!;
export const WARGAMING_APPLICATION_ID = process.env.WARGAMING_APPLICATION_ID!;
if (!DISCORD_TOKEN) {
throw new Error('No Discord token provided');
}
if (!WARGAMING_APPLICATION_ID) {
throw new Error('No Wargaming application id provided}');
}
+21
View File
@@ -0,0 +1,21 @@
export const SECRET_KEYS = [
'DISCORD_TOKEN',
'WARGAMING_APPLICATION_ID',
'GH_TOKEN',
] as const;
export type SECRET = typeof SECRET_KEYS extends readonly (infer T)[]
? T
: never;
export const secrets = SECRET_KEYS.reduce<Partial<Record<SECRET, string>>>(
(accumulator, key) => ({
...accumulator,
get [key](): string {
if (!(key in process.env)) throw new Error(`Key ${key} not provided`);
return process.env[key]!;
},
}),
{},
) as Record<SECRET, string>;
+1 -1
View File
@@ -15,7 +15,7 @@ export default async function (member: GuildMember) {
.setColor(ACCENT_COLOR)
.setTitle(`Welcome ${member.user.username}`)
.setDescription(
`Welcome to the Skilled server, **${member.user.username}**! To continue, please use the \`/verify\` command.\n\nExample: \`/verify Europe Sabina244\``,
`Welcome to the Skilled server, **${member.user.username}**! To continue, please use the \`/verify\` command.\n\nExample: \`/verify Sabina244\``,
),
],
});
@@ -6,6 +6,7 @@ import {
ChatInputCommandInteraction,
} from 'discord.js';
import { commands } from '..';
import discord from '../../../../discord.json' assert { type: 'json' };
import embedNegative from '../../../core/discord/embedNegative';
import embedWarning from '../../../core/discord/embedWarning';
import normalizeInteractionReturnable from '../../../core/discord/normalizeInteractionReturnable';
@@ -21,6 +22,19 @@ export default async function handleChatInputCommand(
await interaction.deferReply();
try {
if (registry.inPreview && interaction.guildId !== discord.tres_guild_id) {
interaction.editReply({
embeds: [
embedWarning(
`\`/${registry.command.name}\` is in Public Preview`,
'[Join the official Discord server](https://discord.gg/nDt7AjGJQH) to gain early access to this command before it is released to the public.',
),
],
});
return;
}
const returnable = await registry.handler(interaction);
if (registry.handlesInteraction) return;
+5 -2
View File
@@ -23,15 +23,16 @@ import { ownedTanksCommand } from '../../commands/ownedTanks';
import { pingCommand } from '../../commands/ping';
import { playerAchievementsCommand } from '../../commands/playerAchievements';
import { playerInfoCommand } from '../../commands/playerInfo';
import { ratingsCommand } from '../../commands/ratings';
import { searchClansCommand } from '../../commands/searchClans';
import { searchPlayersCommand } from '../../commands/searchPlayers';
import { searchTanksCommand } from '../../commands/searchTanks';
import { statsCommand } from '../../commands/stats';
import { todayCommand } from '../../commands/today';
import { verifyCommand } from '../../commands/verify';
import { DISCORD_TOKEN } from '../../core/node/arguments';
import getClientId from '../../core/node/getClientId';
import isDev from '../../core/node/isDev';
import { secrets } from '../../core/node/secrets';
import handleAutocomplete from './handlers/autocomplete';
import handleButton from './handlers/button';
import handleChatInputCommand from './handlers/chatInputCommand';
@@ -56,6 +57,7 @@ export interface Registry {
export interface CommandRegistry<HandlesInteraction extends boolean = boolean>
extends Registry {
inPublic: boolean;
inPreview?: boolean;
handlesInteraction?: HandlesInteraction;
command:
| SlashCommandBuilder
@@ -69,7 +71,7 @@ export interface CommandRegistry<HandlesInteraction extends boolean = boolean>
button?: (interaction: ButtonInteraction<CacheType>) => InteractionReturnable;
}
const rest = new REST().setToken(DISCORD_TOKEN);
const rest = new REST().setToken(secrets.DISCORD_TOKEN);
export const COMMANDS_RAW: CommandRegistry[] = [
debugCommand,
@@ -88,6 +90,7 @@ export const COMMANDS_RAW: CommandRegistry[] = [
pingCommand,
evolutionCommand,
statsCommand,
ratingsCommand,
];
export const commands: Record<string, CommandRegistry> = COMMANDS_RAW.reduce(
(accumulator, registry) => {
+6 -6
View File
@@ -3,7 +3,7 @@ import NoData, { NoDataType } from '../components/NoData';
import PoweredBy, { PoweredByType } from '../components/PoweredBy';
import TitleBar from '../components/TitleBar';
import Wrapper from '../components/Wrapper';
import { REGION_DOMAIN_NAMES } from '../constants/regions';
import { REGION_NAMES } from '../constants/regions';
import getWargamingResponse from '../core/blitz/getWargamingResponse';
import resolveTankName from '../core/blitz/resolveTankName';
import { tankopedia } from '../core/blitz/tankopedia';
@@ -11,7 +11,7 @@ import getPlayerHistories from '../core/blitzstars/getPlayerHistories';
import getTankHistories from '../core/blitzstars/getTankHistories';
import { ResolvedPeriod } from '../core/discord/resolvePeriodFromCommand';
import { ResolvedPlayer } from '../core/discord/resolvePlayerFromCommand';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { secrets } from '../core/node/secrets';
import { AccountInfo } from '../types/accountInfo';
import { Histories } from '../types/histories';
import { PlayerClanData } from '../types/playerClanData';
@@ -20,7 +20,7 @@ import { StatType } from './fullStats';
export default async function evolution<Type extends StatType>(
type: Type,
{ start, end, evolutionName }: ResolvedPeriod,
{ server, id }: ResolvedPlayer,
{ region: server, id }: ResolvedPlayer,
tankId: Type extends 'tank' ? number : null,
) {
let nameDiscriminator: string | undefined;
@@ -29,7 +29,7 @@ export default async function evolution<Type extends StatType>(
if (type === 'player') {
const clan = (
await getWargamingResponse<PlayerClanData>(
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`,
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`,
)
)[id]?.clan;
@@ -79,7 +79,7 @@ export default async function evolution<Type extends StatType>(
const minX = Math.min(...xs);
const accountInfo = await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`,
);
return (
@@ -89,7 +89,7 @@ export default async function evolution<Type extends StatType>(
nameDiscriminator={nameDiscriminator}
image={image}
description={`${evolutionName} • ${new Date().toDateString()} • ${
REGION_DOMAIN_NAMES[server]
REGION_NAMES[server]
}`}
/>
+6 -6
View File
@@ -4,7 +4,7 @@ import PoweredBy, { PoweredByType } from '../components/PoweredBy';
import TierWeights, { TierWeightsRecord } from '../components/TierWeights';
import TitleBar from '../components/TitleBar';
import Wrapper from '../components/Wrapper';
import { REGION_DOMAIN_NAMES } from '../constants/regions';
import { REGION_NAMES } from '../constants/regions';
import calculateWN8 from '../core/blitz/calculateWN8';
import getWargamingResponse from '../core/blitz/getWargamingResponse';
import resolveTankName from '../core/blitz/resolveTankName';
@@ -14,7 +14,7 @@ import getDiffedTankStats from '../core/blitzstars/getDiffedTankStats';
import { tankAverages } from '../core/blitzstars/tankAverages';
import { ResolvedPeriod } from '../core/discord/resolvePeriodFromCommand';
import { ResolvedPlayer } from '../core/discord/resolvePlayerFromCommand';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { secrets } from '../core/node/secrets';
import {
AccountInfo,
AllStats,
@@ -28,7 +28,7 @@ export type StatType = 'player' | 'tank';
export default async function fullStats<Type extends StatType>(
type: Type,
{ start, end, statsName }: ResolvedPeriod,
{ server, id }: ResolvedPlayer,
{ region: server, id }: ResolvedPlayer,
tankId: Type extends 'tank' ? number : null,
) {
let nameDiscriminator: string | undefined;
@@ -37,7 +37,7 @@ export default async function fullStats<Type extends StatType>(
if (type === 'player') {
const clan = (
await getWargamingResponse<PlayerClanData>(
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`,
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`,
)
)[id]?.clan;
@@ -133,7 +133,7 @@ export default async function fullStats<Type extends StatType>(
}
const accountInfo = await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`,
);
return (
@@ -143,7 +143,7 @@ export default async function fullStats<Type extends StatType>(
nameDiscriminator={nameDiscriminator}
image={image}
description={`${statsName} • ${new Date().toDateString()} • ${
REGION_DOMAIN_NAMES[server]
REGION_NAMES[server]
}`}
/>
+6 -6
View File
@@ -4,7 +4,7 @@ import PoweredBy, { PoweredByType } from '../components/PoweredBy';
import { TierWeightsRecord } from '../components/TierWeights';
import TitleBar from '../components/TitleBar';
import Wrapper from '../components/Wrapper';
import { REGION_DOMAIN_NAMES } from '../constants/regions';
import { REGION_NAMES } from '../constants/regions';
import calculateWN8 from '../core/blitz/calculateWN8';
import getWargamingResponse from '../core/blitz/getWargamingResponse';
import resolveTankName from '../core/blitz/resolveTankName';
@@ -14,7 +14,7 @@ import getDiffedTankStats from '../core/blitzstars/getDiffedTankStats';
import { tankAverages } from '../core/blitzstars/tankAverages';
import { ResolvedPeriod } from '../core/discord/resolvePeriodFromCommand';
import { ResolvedPlayer } from '../core/discord/resolvePlayerFromCommand';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { secrets } from '../core/node/secrets';
import { theme } from '../stitches.config';
import {
AccountInfo,
@@ -29,7 +29,7 @@ export type StatType = 'player' | 'tank';
export default async function stats<Type extends StatType>(
type: Type,
{ start, end, statsName }: ResolvedPeriod,
{ server, id }: ResolvedPlayer,
{ region: server, id }: ResolvedPlayer,
tankId: Type extends 'tank' ? number : null,
naked = false,
) {
@@ -39,7 +39,7 @@ export default async function stats<Type extends StatType>(
if (type === 'player') {
const clan = (
await getWargamingResponse<PlayerClanData>(
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`,
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`,
)
)[id]?.clan;
@@ -137,7 +137,7 @@ export default async function stats<Type extends StatType>(
}
const accountInfo = await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`,
);
const overview = (
<AllStatsOverview stats={stats} supplementaryStats={supplementaryStats} />
@@ -164,7 +164,7 @@ export default async function stats<Type extends StatType>(
nameDiscriminator={nameDiscriminator}
image={image}
description={`${statsName} • ${new Date().toDateString()} • ${
REGION_DOMAIN_NAMES[server]
REGION_NAMES[server]
}`}
/>
+6 -6
View File
@@ -3,7 +3,7 @@ import NoData, { NoDataType } from '../components/NoData';
import PoweredBy, { PoweredByType } from '../components/PoweredBy';
import TitleBar from '../components/TitleBar';
import Wrapper from '../components/Wrapper';
import { REGION_DOMAIN_NAMES } from '../constants/regions';
import { REGION_NAMES } from '../constants/regions';
import calculateWN8 from '../core/blitz/calculateWN8';
import getTankStats from '../core/blitz/getTankStats';
import getWargamingResponse from '../core/blitz/getWargamingResponse';
@@ -15,13 +15,13 @@ import getPeriodNow from '../core/blitzstars/getPeriodNow';
import getTimeDaysAgo from '../core/blitzstars/getTimeDaysAgo';
import { tankAverages } from '../core/blitzstars/tankAverages';
import { ResolvedPlayer } from '../core/discord/resolvePlayerFromCommand';
import { WARGAMING_APPLICATION_ID } from '../core/node/arguments';
import { secrets } from '../core/node/secrets';
import { AccountInfo, AllStats } from '../types/accountInfo';
import { PlayerClanData } from '../types/playerClanData';
import { PossiblyPromise } from '../types/possiblyPromise';
export default async function today(
{ server, id }: ResolvedPlayer,
{ region: server, id }: ResolvedPlayer,
limit = Infinity,
naked?: boolean,
) {
@@ -32,10 +32,10 @@ export default async function today(
getPeriodNow(),
);
const accountInfo = await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`,
);
const clanData = await getWargamingResponse<PlayerClanData>(
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`,
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`,
);
const careerTankStatsRaw = await getTankStats(server, id);
const careerStats: Record<number, AllStats> = {
@@ -162,7 +162,7 @@ export default async function today(
: undefined
}
description={`Today's breakdown • ${new Date().toDateString()} • ${
REGION_DOMAIN_NAMES[server]
REGION_NAMES[server]
}`}
/>
+1 -1
View File
@@ -11,7 +11,7 @@ export const statsRoute: RouteRegistry = {
async handler(req) {
const type = req.params.type as StatType;
const player = resolvePlayerFromRequest(req);
const period = resolvePeriodFromRequest(player.server, req);
const period = resolvePeriodFromRequest(player.region, req);
return await stats(
type,
+1 -4
View File
@@ -11,8 +11,5 @@
"resolveJsonModule": true,
"jsx": "react-jsx"
},
"include": [
"src/**/*.ts",
"src/components/AllStatsOverview/components/WN8SurroundingStat.tsx"
]
"include": ["src/**/*.ts"]
}
+95
View File
@@ -933,6 +933,11 @@ accepts@~1.3.8:
mime-types "~2.1.34"
negotiator "0.6.3"
acorn@^8.9.0:
version "8.10.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
algoliasearch@^4.0.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.18.0.tgz#1183ad0384a5b2c14f381c3a361da611acc8edb3"
@@ -1013,6 +1018,11 @@ [email protected]:
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
cac@^6.7.14:
version "6.7.14"
resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959"
integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==
call-bind@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
@@ -1089,6 +1099,13 @@ [email protected]:
dependencies:
ms "2.0.0"
debug@^4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"
[email protected]:
version "2.0.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
@@ -1252,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"
@@ -1475,11 +1497,26 @@ minisearch@^6.1.0:
resolved "https://registry.yarnpkg.com/minisearch/-/minisearch-6.1.0.tgz#6e74e743dbd0e88fa5ca52fef2391e0aa7055252"
integrity sha512-PNxA/X8pWk+TiqPbsoIYH0GQ5Di7m6326/lwU/S4mlo4wGQddIcf/V//1f9TB0V4j59b57b+HZxt8h3iMROGvg==
mlly@^1.2.0, mlly@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.4.0.tgz#830c10d63f1f97bd8785377b24dc2a15d972832b"
integrity sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==
dependencies:
acorn "^8.9.0"
pathe "^1.1.1"
pkg-types "^1.0.3"
ufo "^1.1.2"
[email protected]:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
[email protected]:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
[email protected]:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
@@ -1537,6 +1574,11 @@ [email protected]:
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
pathe@^1.1.0, pathe@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.1.tgz#1dd31d382b974ba69809adc9a7a347e65d84829a"
integrity sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==
peek-readable@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-5.0.0.tgz#7ead2aff25dc40458c60347ea76cfdfd63efdfec"
@@ -1547,6 +1589,15 @@ picocolors@^1.0.0:
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
pkg-types@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.0.3.tgz#988b42ab19254c01614d13f4f65a2cfc7880f868"
integrity sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==
dependencies:
jsonc-parser "^3.2.0"
mlly "^1.2.0"
pathe "^1.1.0"
postcss-value-parser@^4.0.2, postcss-value-parser@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
@@ -1561,6 +1612,15 @@ postcss@^8.1.10, postcss@^8.4.23:
picocolors "^1.0.0"
source-map-js "^1.0.2"
postcss@^8.4.25:
version "8.4.26"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.26.tgz#1bc62ab19f8e1e5463d98cf74af39702a00a9e94"
integrity sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw==
dependencies:
nanoid "^3.3.6"
picocolors "^1.0.0"
source-map-js "^1.0.2"
preact@^10.0.0:
version "10.15.1"
resolved "https://registry.yarnpkg.com/preact/-/preact-10.15.1.tgz#a1de60c9fc0c79a522d969c65dcaddc5d994eede"
@@ -1631,6 +1691,13 @@ rollup@^3.21.0:
optionalDependencies:
fsevents "~2.3.2"
rollup@^3.25.2:
version "3.26.2"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.26.2.tgz#2e76a37606cb523fc9fef43e6f59c93f86d95e7c"
integrity sha512-6umBIGVz93er97pMgQO08LuH3m6PUb3jlDUUGFsNJB6VgTCUaDFpupf5JfU30529m/UKOgmiX+uY6Sx8cOYpLA==
optionalDependencies:
fsevents "~2.3.2"
[email protected], safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
@@ -1790,6 +1857,11 @@ typescript@^5.0.4:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274"
integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==
ufo@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.1.2.tgz#d0d9e0fa09dece0c31ffd57bd363f030a35cfe76"
integrity sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==
undici@^5.22.0:
version "5.22.1"
resolved "https://registry.yarnpkg.com/undici/-/undici-5.22.1.tgz#877d512effef2ac8be65e695f3586922e1a57d7b"
@@ -1830,6 +1902,29 @@ vary@~1.1.2:
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
vite-node@^0.33.0:
version "0.33.0"
resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-0.33.0.tgz#c6a3a527e0b8090da7436241bc875760ae0eef28"
integrity sha512-19FpHYbwWWxDr73ruNahC+vtEdza52kA90Qb3La98yZ0xULqV8A5JLNPUff0f5zID4984tW7l3DH2przTJUZSw==
dependencies:
cac "^6.7.14"
debug "^4.3.4"
mlly "^1.4.0"
pathe "^1.1.1"
picocolors "^1.0.0"
vite "^3.0.0 || ^4.0.0"
"vite@^3.0.0 || ^4.0.0":
version "4.4.4"
resolved "https://registry.yarnpkg.com/vite/-/vite-4.4.4.tgz#b76e6049c0e080cb54e735ad2d18287753752118"
integrity sha512-4mvsTxjkveWrKDJI70QmelfVqTm+ihFAb6+xf4sjEU2TmUCTlVX87tmg/QooPEMQb/lM9qGHT99ebqPziEd3wg==
dependencies:
esbuild "^0.18.10"
postcss "^8.4.25"
rollup "^3.25.2"
optionalDependencies:
fsevents "~2.3.2"
vite@^4.3.9:
version "4.3.9"
resolved "https://registry.yarnpkg.com/vite/-/vite-4.3.9.tgz#db896200c0b1aa13b37cdc35c9e99ee2fdd5f96d"