mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5bab8a698 | ||
|
|
331040bbdf | ||
|
|
70e1d4b30d | ||
|
|
03dbd797fb | ||
|
|
28172f7f8f | ||
|
|
cb5347cbb8 | ||
|
|
1ce0781d2d | ||
|
|
1257aa76d7 | ||
|
|
6ec3835896 | ||
|
|
506fe7d9bd | ||
|
|
14ef9f6295 | ||
|
|
0d76e1a29e | ||
|
|
9e42d7e8f5 | ||
|
|
364f97332b | ||
|
|
caa7bb8a65 | ||
|
|
3e956b34c1 | ||
|
|
038374e5aa | ||
|
|
dafd54f830 | ||
|
|
cb65e6b0af | ||
|
|
4ca8590c83 | ||
|
|
c5ef02d7e6 |
No files matched your search
@@ -1,4 +1,7 @@
|
|||||||
import { DefaultTheme, defineConfig } from 'vitepress';
|
import { compareVersions } from 'compare-versions';
|
||||||
|
import { readdirSync } from 'fs';
|
||||||
|
import { parse } from 'path';
|
||||||
|
import { defineConfig } from 'vitepress';
|
||||||
import { getSidebar } from 'vitepress-plugin-auto-sidebar';
|
import { getSidebar } from 'vitepress-plugin-auto-sidebar';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
@@ -9,17 +12,27 @@ export default defineConfig({
|
|||||||
themeConfig: {
|
themeConfig: {
|
||||||
nav: [
|
nav: [
|
||||||
{ text: 'Home', link: '/' },
|
{ text: 'Home', link: '/' },
|
||||||
{ text: 'Examples', link: '/markdown-examples' },
|
{ text: 'Guide', link: '/guide/about' },
|
||||||
],
|
],
|
||||||
|
|
||||||
sidebar: getSidebar({
|
sidebar: [
|
||||||
collapsed: false,
|
...getSidebar({
|
||||||
contentDirs: ['guide', 'legal', 'changelogs'],
|
collapsed: false,
|
||||||
contentRoot: '/docs/',
|
contentDirs: ['guide', 'legal'],
|
||||||
}) as DefaultTheme.Sidebar,
|
contentRoot: '/docs/',
|
||||||
|
}),
|
||||||
|
|
||||||
|
{
|
||||||
|
text: 'Changelogs',
|
||||||
|
items: readdirSync('./docs/changelogs')
|
||||||
|
.map((file) => parse(file).name)
|
||||||
|
.sort(compareVersions)
|
||||||
|
.map((name) => ({ text: name, link: `/changelogs/${name}.html` })),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
socialLinks: [
|
socialLinks: [
|
||||||
{ icon: 'github', link: 'https://github.com/vuejs/vitepress' },
|
{ icon: 'github', link: 'https://github.com/tresabhi/blitzkrieg' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Blitzkrieg 1.12.2
|
||||||
|
|
||||||
|
A much needed room service is here!
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- Tier selection is now easier
|
||||||
|
- Tier options across all commands have been unified to a single style
|
||||||
|
- Tiers are now sorted from `10` to `1`
|
||||||
|
- Tiers now show up in the format `Tier {tier_number} - {tier_roman_numeral}` (example: `Tier 8 - VIII`)
|
||||||
|
- Removed powered by footer as links are provided regardless
|
||||||
|
- Changed invite instructions to instead use application directory
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
- Guide link on the website is now fixed
|
||||||
|
- Versions now show up in the correct order in the navigation bar
|
||||||
|
- Added Blitzkrieg's GitHub link instead of the default Vitepress
|
||||||
|
- Changed examples to guide in the navigation bar
|
||||||
|
- Clan name and icon now show up with multi-tank filters
|
||||||
|
- Removed a random `console.log`
|
||||||
|
|
||||||
|
## Technical Changes
|
||||||
|
|
||||||
|
- Added `knip`
|
||||||
|
- Removed a lot of unused files and dependencies
|
||||||
|
- Updated all dependencies to their latest
|
||||||
|
- Code split `bot.ts`
|
||||||
|
- `client` and `octokit` now reside separately
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Blitzkrieg 1.12.3
|
||||||
|
|
||||||
|
3-day downtime fixed!
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- Split `/ratings` into `/ratings current` and `/ratings archive`
|
||||||
|
- `current` now properly handles when there is no ongoing ratings season
|
||||||
|
- `archive` displays older archived seasons
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
- The bot is now no ratings seasons tolerant
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Blitzkrieg 1.12.4
|
||||||
|
|
||||||
|
Added very badly-coded temporary `/clan-event` command (without linting)
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- Added `/clan-event` command
|
||||||
@@ -1,10 +1,3 @@
|
|||||||
# Inviting the Bot
|
# Inviting the Bot
|
||||||
|
|
||||||
It's quite simple really. Just follow the steps below:
|
Go to [application directory for Blitzkrieg](https://discord.com/application-directory/1097673957865443370) and click "Add to Server." Voila! You're ready to go.
|
||||||
|
|
||||||
1. Join a Discord server that already has Blitzkrieg installed or [the official Discord Server](https://discord.gg/nDt7AjGJQH).
|
|
||||||
2. Click on the bot's profile.
|
|
||||||
3. Click the "Add to Server" button under the bot's name and ID.
|
|
||||||
4. Select what server you want it in and see what permissions you seem fit for the bot.
|
|
||||||
|
|
||||||
Voila! You're ready to go.
|
|
||||||
+1
-1
@@ -15,7 +15,7 @@ hero:
|
|||||||
link: guide/install
|
link: guide/install
|
||||||
- theme: alt
|
- theme: alt
|
||||||
text: Guide
|
text: Guide
|
||||||
link: /guide
|
link: /guide/about
|
||||||
|
|
||||||
features:
|
features:
|
||||||
- title: Clean and Efficient UI
|
- title: Clean and Efficient UI
|
||||||
|
|||||||
+16
-14
@@ -19,37 +19,39 @@
|
|||||||
"publish:ratingsleaderboard": "vite-node --script scripts/buildRatingsLeaderboard --publish",
|
"publish:ratingsleaderboard": "vite-node --script scripts/buildRatingsLeaderboard --publish",
|
||||||
"zip": "zip -r dist.zip dist",
|
"zip": "zip -r dist.zip dist",
|
||||||
"lint": "npx tsc",
|
"lint": "npx tsc",
|
||||||
"format": "prettier --write \"**/*.@(json|js|jsx|css|md|ts|tsx|html|xml|yml|gltf)\""
|
"format": "prettier --write \"**/*.@(json|js|jsx|css|md|ts|tsx|html|xml|yml|gltf)\"",
|
||||||
|
"knip": "knip",
|
||||||
|
"update": "yarn upgrade --latest"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/rest": "^20.0.1",
|
"@octokit/rest": "^20.0.1",
|
||||||
"@radix-ui/colors": "^0.1.8",
|
"@radix-ui/colors": "^2.1.0",
|
||||||
"@resvg/resvg-js": "^2.4.1",
|
"@resvg/resvg-js": "^2.4.1",
|
||||||
"bepaint": "^0.1.0-beta.3",
|
"bepaint": "^0.1.0-beta.3",
|
||||||
"discord.js": "^14.12.0",
|
"discord.js": "^14.13.0",
|
||||||
"esbuild": "^0.18.10",
|
"esbuild": "^0.19.2",
|
||||||
"escape-html": "^1.0.3",
|
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"false": "^0.0.4",
|
|
||||||
"fuzzysort": "^2.0.4",
|
"fuzzysort": "^2.0.4",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"markdown-escape": "^2.0.0",
|
"markdown-escape": "^2.0.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"satori": "^0.9.1",
|
"satori": "^0.10.3",
|
||||||
"vite-node": "^0.33.0",
|
"vite-node": "^0.34.2",
|
||||||
"vitepress-plugin-auto-sidebar": "^1.1.0"
|
"vitepress-plugin-auto-sidebar": "^1.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@total-typescript/ts-reset": "^0.4.2",
|
"@total-typescript/ts-reset": "^0.5.1",
|
||||||
"@types/escape-html": "^1.0.2",
|
"@types/escape-html": "^1.0.2",
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
"@types/lodash": "^4.14.195",
|
"@types/lodash": "^4.14.197",
|
||||||
"@types/markdown-escape": "^1.1.0",
|
"@types/markdown-escape": "^1.1.0",
|
||||||
"@types/node": "^20.2.1",
|
"@types/node": "^20.5.3",
|
||||||
"@types/react": "^18.2.7",
|
"@types/react": "^18.2.21",
|
||||||
"prettier": "^3.0.0",
|
"compare-versions": "^6.1.0",
|
||||||
|
"knip": "^2.19.9",
|
||||||
|
"prettier": "^3.0.2",
|
||||||
"typescript": "^5.0.4",
|
"typescript": "^5.0.4",
|
||||||
"vitepress": "^1.0.0-beta.3"
|
"vitepress": "^1.0.0-rc.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-14
@@ -1,19 +1,6 @@
|
|||||||
import { Octokit } from '@octokit/rest';
|
import { client } from './core/discord/client';
|
||||||
import { Client, GatewayIntentBits } from 'discord.js';
|
|
||||||
import { secrets } from './core/node/secrets';
|
import { secrets } from './core/node/secrets';
|
||||||
import { registerErrorHandlers } from './events/error';
|
import { registerErrorHandlers } from './events/error';
|
||||||
import guildMemberAdd from './events/guildMemberAdd';
|
|
||||||
import interactionCreate from './events/interactionCreate';
|
|
||||||
import ready from './events/ready';
|
|
||||||
|
|
||||||
export const client = new Client({
|
|
||||||
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers],
|
|
||||||
})
|
|
||||||
.on('ready', ready)
|
|
||||||
.on('guildMemberAdd', guildMemberAdd)
|
|
||||||
.on('interactionCreate', interactionCreate);
|
|
||||||
|
|
||||||
export const octokit = new Octokit({ auth: secrets.GH_TOKEN });
|
|
||||||
|
|
||||||
registerErrorHandlers();
|
registerErrorHandlers();
|
||||||
client.login(secrets.DISCORD_TOKEN);
|
client.login(secrets.DISCORD_TOKEN);
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
|
import markdownEscape from 'markdown-escape';
|
||||||
|
import autocompleteClan from '../core/discord/autocompleteClan';
|
||||||
|
import embedInfo from '../core/discord/embedInfo';
|
||||||
|
import { secrets } from '../core/node/secrets';
|
||||||
|
import { CommandRegistry } from '../events/interactionCreate';
|
||||||
|
|
||||||
|
const DEFAULT_THRESHOLD = 7;
|
||||||
|
|
||||||
|
export const clanEventCommand: CommandRegistry = {
|
||||||
|
inProduction: true,
|
||||||
|
inDevelopment: true,
|
||||||
|
inPublic: true,
|
||||||
|
|
||||||
|
command: new SlashCommandBuilder()
|
||||||
|
.setName('clan-event')
|
||||||
|
.setDescription('Lists all inactive players')
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName('clan')
|
||||||
|
.setDescription('The clan name or tag you are checking')
|
||||||
|
.setRequired(true),
|
||||||
|
),
|
||||||
|
|
||||||
|
async handler(interaction) {
|
||||||
|
const REGION = 'com';
|
||||||
|
const CLAN_SEARCH = interaction.options.getString('clan', true);
|
||||||
|
|
||||||
|
const {
|
||||||
|
clan_id,
|
||||||
|
name: clan_name,
|
||||||
|
tag: clan_tag,
|
||||||
|
} = await fetch(
|
||||||
|
`https://api.wotblitz.${REGION}/wotb/clans/list/?application_id=${secrets.WARGAMING_APPLICATION_ID}&search=${CLAN_SEARCH}`,
|
||||||
|
)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then(({ data }) => data[0]);
|
||||||
|
const memebers = await fetch(
|
||||||
|
`https://api.wotblitz.${REGION}/wotb/clans/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&clan_id=${clan_id}`,
|
||||||
|
)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then(({ data }) => data[clan_id].members_ids);
|
||||||
|
const stats = await Promise.all(
|
||||||
|
memebers.map(async (id, index) => {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, index * 200));
|
||||||
|
|
||||||
|
const [yesterdayPlatoonWins, name, todayPlatoonWins] =
|
||||||
|
await Promise.all([
|
||||||
|
fetch(`https://www.blitzstars.com/api/playerstats/${id}`)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => data[0]?.achievements.jointVictoryCount),
|
||||||
|
fetch(
|
||||||
|
`https://api.wotblitz.${REGION}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`,
|
||||||
|
)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then(({ data }) => data[id].nickname),
|
||||||
|
fetch(
|
||||||
|
`https://api.wotblitz.${REGION}/wotb/account/achievements/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`,
|
||||||
|
)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then(({ data }) => data[id].max_series.jointVictory),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
yesterdayPlatoonWins,
|
||||||
|
todayPlatoonWins,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return embedInfo(
|
||||||
|
`Estimation of ${clan_name} [${clan_tag}]'s total platoon wins today: ${(
|
||||||
|
stats.reduce(
|
||||||
|
(accumulator, player) =>
|
||||||
|
accumulator +
|
||||||
|
(player.yesterdayPlatoonWins === undefined
|
||||||
|
? 0
|
||||||
|
: player.todayPlatoonWins - player.yesterdayPlatoonWins),
|
||||||
|
0,
|
||||||
|
) / 2
|
||||||
|
).toFixed(0)}`,
|
||||||
|
stats
|
||||||
|
.sort(
|
||||||
|
(playerA, playerB) =>
|
||||||
|
(playerB.yesterdayPlatoonWins === undefined
|
||||||
|
? 0
|
||||||
|
: playerB.todayPlatoonWins - playerB.yesterdayPlatoonWins) -
|
||||||
|
(playerA.yesterdayPlatoonWins === undefined
|
||||||
|
? 0
|
||||||
|
: playerA.todayPlatoonWins - playerA.yesterdayPlatoonWins),
|
||||||
|
)
|
||||||
|
.map(
|
||||||
|
(player) =>
|
||||||
|
`${markdownEscape(player.name)}: ${
|
||||||
|
player.yesterdayPlatoonWins === undefined
|
||||||
|
? 0
|
||||||
|
: player.todayPlatoonWins - player.yesterdayPlatoonWins
|
||||||
|
}`,
|
||||||
|
)
|
||||||
|
.join('\n'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
autocomplete: autocompleteClan,
|
||||||
|
};
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { SlashCommandBuilder } from 'discord.js';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
import packageJSON from '../../package.json' assert { type: 'json' };
|
import packageJSON from '../../package.json' assert { type: 'json' };
|
||||||
import { client } from '../bot';
|
|
||||||
import { tankopedia } from '../core/blitz/tankopedia';
|
import { tankopedia } from '../core/blitz/tankopedia';
|
||||||
import { tankAverages } from '../core/blitzstars/tankAverages';
|
import { tankAverages } from '../core/blitzstars/tankAverages';
|
||||||
|
import { client } from '../core/discord/client';
|
||||||
import embedInfo from '../core/discord/embedInfo';
|
import embedInfo from '../core/discord/embedInfo';
|
||||||
import markdownTable from '../core/discord/markdownTable';
|
import markdownTable from '../core/discord/markdownTable';
|
||||||
import getClientId from '../core/node/getClientId';
|
import getClientId from '../core/node/getClientId';
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ export const fullStatsCommand = new Promise<CommandRegistryRaw>(
|
|||||||
)
|
)
|
||||||
)[player.id];
|
)[player.id];
|
||||||
|
|
||||||
|
const tierOption = interaction.options.getString('tier');
|
||||||
|
|
||||||
return [
|
return [
|
||||||
await fullStats(
|
await fullStats(
|
||||||
commandGroup,
|
commandGroup,
|
||||||
@@ -74,7 +76,7 @@ export const fullStatsCommand = new Promise<CommandRegistryRaw>(
|
|||||||
'tank-type':
|
'tank-type':
|
||||||
interaction.options.getString('tank-type') ?? undefined,
|
interaction.options.getString('tank-type') ?? undefined,
|
||||||
nation: interaction.options.getString('nation') ?? undefined,
|
nation: interaction.options.getString('nation') ?? undefined,
|
||||||
tier: interaction.options.getInteger('tier') ?? undefined,
|
tier: tierOption ? parseInt(tierOption) : undefined,
|
||||||
'tree-type': (interaction.options.getString('tree-type') ??
|
'tree-type': (interaction.options.getString('tree-type') ??
|
||||||
undefined) as TreeTypeString | undefined,
|
undefined) as TreeTypeString | undefined,
|
||||||
} satisfies Partial<MultiTankFilters>)
|
} satisfies Partial<MultiTankFilters>)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { SlashCommandBuilder } from 'discord.js';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
import GenericStats from '../components/GenericStats';
|
import GenericStats from '../components/GenericStats';
|
||||||
import NoData, { NoDataType } from '../components/NoData';
|
import NoData, { NoDataType } from '../components/NoData';
|
||||||
import PoweredBy, { PoweredByType } from '../components/PoweredBy';
|
|
||||||
import TitleBar from '../components/TitleBar';
|
import TitleBar from '../components/TitleBar';
|
||||||
import Wrapper from '../components/Wrapper';
|
import Wrapper from '../components/Wrapper';
|
||||||
import getBlitzClan from '../core/blitz/getBlitzClan';
|
import getBlitzClan from '../core/blitz/getBlitzClan';
|
||||||
@@ -74,8 +73,6 @@ export const inactiveCommand: CommandRegistry = {
|
|||||||
|
|
||||||
{!hasInactiveMembers && <NoData type={NoDataType.PlayersInPeriod} />}
|
{!hasInactiveMembers && <NoData type={NoDataType.PlayersInPeriod} />}
|
||||||
{hasInactiveMembers && <GenericStats stats={inactive} />}
|
{hasInactiveMembers && <GenericStats stats={inactive} />}
|
||||||
|
|
||||||
<PoweredBy type={PoweredByType.Wargaming} />
|
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { SlashCommandBuilder } from 'discord.js';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
import NoData, { NoDataType } from '../components/NoData';
|
import NoData, { NoDataType } from '../components/NoData';
|
||||||
import PoweredBy, { PoweredByType } from '../components/PoweredBy';
|
|
||||||
import * as Tanks from '../components/Tanks';
|
import * as Tanks from '../components/Tanks';
|
||||||
import TitleBar from '../components/TitleBar';
|
import TitleBar from '../components/TitleBar';
|
||||||
import Wrapper from '../components/Wrapper';
|
import Wrapper from '../components/Wrapper';
|
||||||
@@ -15,6 +14,7 @@ import {
|
|||||||
tankopedia,
|
tankopedia,
|
||||||
tankopediaInfo,
|
tankopediaInfo,
|
||||||
} from '../core/blitz/tankopedia';
|
} from '../core/blitz/tankopedia';
|
||||||
|
import addTierChoices from '../core/discord/addTierChoices';
|
||||||
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
||||||
import autocompleteUsername from '../core/discord/autocompleteUsername';
|
import autocompleteUsername from '../core/discord/autocompleteUsername';
|
||||||
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
|
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
|
||||||
@@ -52,24 +52,7 @@ export const ownedTanksCommand: CommandRegistry = {
|
|||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
.setName('owned-tanks')
|
.setName('owned-tanks')
|
||||||
.setDescription("Shows a player's owned tanks")
|
.setDescription("Shows a player's owned tanks")
|
||||||
.addStringOption((option) =>
|
.addStringOption(addTierChoices)
|
||||||
option
|
|
||||||
.setName('tier')
|
|
||||||
.setDescription('The tier you want to see')
|
|
||||||
.setChoices(
|
|
||||||
{ name: 'Tier I (1)', value: '1' },
|
|
||||||
{ name: 'Tier II (2)', value: '2' },
|
|
||||||
{ name: 'Tier III (3)', value: '3' },
|
|
||||||
{ name: 'Tier IV (4)', value: '4' },
|
|
||||||
{ name: 'Tier V (5)', value: '5' },
|
|
||||||
{ name: 'Tier VI (6)', value: '6' },
|
|
||||||
{ name: 'Tier VII (7)', value: '7' },
|
|
||||||
{ name: 'Tier VIII (8)', value: '8' },
|
|
||||||
{ name: 'Tier IX (9)', value: '9' },
|
|
||||||
{ name: 'Tier X (10)', value: '10' },
|
|
||||||
)
|
|
||||||
.setRequired(true),
|
|
||||||
)
|
|
||||||
.addStringOption(addUsernameChoices),
|
.addStringOption(addUsernameChoices),
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
@@ -170,8 +153,6 @@ export const ownedTanksCommand: CommandRegistry = {
|
|||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<PoweredBy type={PoweredByType.Wargaming} />
|
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
+358
-112
@@ -1,21 +1,28 @@
|
|||||||
import {
|
import {
|
||||||
APIApplicationCommandOptionChoice,
|
APIApplicationCommandOptionChoice,
|
||||||
SlashCommandBuilder,
|
SlashCommandBuilder,
|
||||||
|
SlashCommandSubcommandBuilder,
|
||||||
|
SlashCommandSubcommandGroupBuilder,
|
||||||
} from 'discord.js';
|
} from 'discord.js';
|
||||||
import { BlitzkriegRatingsLeaderboard } from '../../scripts/buildRatingsLeaderboard';
|
import { BlitzkriegRatingsLeaderboard } from '../../scripts/buildRatingsLeaderboard';
|
||||||
import * as Leaderboard from '../components/Leaderboard';
|
import * as Leaderboard from '../components/Leaderboard';
|
||||||
import TitleBar from '../components/TitleBar';
|
import TitleBar from '../components/TitleBar';
|
||||||
import Wrapper from '../components/Wrapper';
|
import Wrapper from '../components/Wrapper';
|
||||||
import { REGION_NAMES_SHORT, Region } from '../constants/regions';
|
import { REGION_NAMES_SHORT, Region } from '../constants/regions';
|
||||||
|
import getArchivedRatingsInfo from '../core/blitz/getArchivedRatingsInfo';
|
||||||
import getRatingsInfo from '../core/blitz/getRatingsInfo';
|
import getRatingsInfo from '../core/blitz/getRatingsInfo';
|
||||||
import getWargamingResponse from '../core/blitz/getWargamingResponse';
|
import getWargamingResponse from '../core/blitz/getWargamingResponse';
|
||||||
import regionToRegionSubdomain from '../core/blitz/regionToRegionSubdomain';
|
import regionToRegionSubdomain from '../core/blitz/regionToRegionSubdomain';
|
||||||
import getMidnightLeaderboard from '../core/database/getMidnightLeaderboard';
|
import getMidnightLeaderboard, {
|
||||||
|
DATABASE_REPO,
|
||||||
|
} from '../core/database/getMidnightLeaderboard';
|
||||||
import addRegionChoices from '../core/discord/addRegionChoices';
|
import addRegionChoices from '../core/discord/addRegionChoices';
|
||||||
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
||||||
import autocompleteUsername from '../core/discord/autocompleteUsername';
|
import autocompleteUsername from '../core/discord/autocompleteUsername';
|
||||||
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
|
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
|
||||||
|
import { octokit } from '../core/express/octokit';
|
||||||
import { secrets } from '../core/node/secrets';
|
import { secrets } from '../core/node/secrets';
|
||||||
|
import throwError from '../core/node/throwError';
|
||||||
import { CommandRegistryRaw } from '../events/interactionCreate';
|
import { CommandRegistryRaw } from '../events/interactionCreate';
|
||||||
import { AccountInfo } from '../types/accountInfo';
|
import { AccountInfo } from '../types/accountInfo';
|
||||||
import { PlayerClanData } from '../types/playerClanData';
|
import { PlayerClanData } from '../types/playerClanData';
|
||||||
@@ -86,78 +93,127 @@ export interface RatingsLeague {
|
|||||||
percentile: number;
|
percentile: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RatingsInfo {
|
export type RatingsInfo =
|
||||||
title: string;
|
| {
|
||||||
icon: string | null;
|
detail: undefined;
|
||||||
start_at: string;
|
|
||||||
finish_at: string;
|
|
||||||
current_season: number;
|
|
||||||
updated_at: string;
|
|
||||||
count: number;
|
|
||||||
|
|
||||||
rewards: RatingsReward[];
|
title: string;
|
||||||
leagues: RatingsLeague[];
|
icon: string | null;
|
||||||
}
|
start_at: string;
|
||||||
|
finish_at: string;
|
||||||
|
current_season: number;
|
||||||
|
updated_at: string;
|
||||||
|
count: number;
|
||||||
|
|
||||||
|
rewards: RatingsReward[];
|
||||||
|
leagues: RatingsLeague[];
|
||||||
|
}
|
||||||
|
| { detail: { error: string } };
|
||||||
|
|
||||||
export interface RatingsNeighbors {
|
export interface RatingsNeighbors {
|
||||||
neighbors: RatingsPlayer[];
|
neighbors: RatingsPlayer[];
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Caching ratings info...');
|
const LEAGUES = [
|
||||||
const ratingsInfo = getRatingsInfo('com').then((info) => {
|
{ name: 'Diamond', minScore: 5000 },
|
||||||
console.log('Cached ratings info');
|
{ name: 'Platinum', minScore: 4000 },
|
||||||
return info;
|
{ name: 'Gold', minScore: 3000 },
|
||||||
});
|
{ name: 'Silver', minScore: 2000 },
|
||||||
|
{ name: 'Bronze', minScore: 0 },
|
||||||
|
];
|
||||||
|
|
||||||
|
function addSubcommands(
|
||||||
|
option: SlashCommandSubcommandGroupBuilder,
|
||||||
|
addOptions = (option: SlashCommandSubcommandBuilder) => option,
|
||||||
|
) {
|
||||||
|
return option
|
||||||
|
.addSubcommand((option) =>
|
||||||
|
addOptions(
|
||||||
|
option
|
||||||
|
.setName('neighbours')
|
||||||
|
.setDescription('Your position and neighbours'),
|
||||||
|
)
|
||||||
|
.addStringOption(addUsernameChoices)
|
||||||
|
.addIntegerOption((option) =>
|
||||||
|
option
|
||||||
|
.setName('limit')
|
||||||
|
.setDescription('How many neighbours to display (default: 10)')
|
||||||
|
.setRequired(false)
|
||||||
|
.setMinValue(5)
|
||||||
|
.setMaxValue(30),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.addSubcommand((option) =>
|
||||||
|
addOptions(option.setName('league').setDescription('Top league players'))
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName('league')
|
||||||
|
.setDescription('The league to display')
|
||||||
|
.addChoices(
|
||||||
|
...LEAGUES.map(
|
||||||
|
({ name }, value) =>
|
||||||
|
({
|
||||||
|
name,
|
||||||
|
value: `${value}`,
|
||||||
|
}) satisfies APIApplicationCommandOptionChoice<string>,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.setRequired(true),
|
||||||
|
)
|
||||||
|
.addStringOption(addRegionChoices)
|
||||||
|
.addIntegerOption((option) =>
|
||||||
|
option
|
||||||
|
.setName('limit')
|
||||||
|
.setDescription('How many rows to display (default: 10)')
|
||||||
|
.setRequired(false)
|
||||||
|
.setMinValue(5)
|
||||||
|
.setMaxValue(30),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const ratingsCommand = new Promise<CommandRegistryRaw>(
|
export const ratingsCommand = new Promise<CommandRegistryRaw>(
|
||||||
async (resolve) => {
|
async (resolve) => {
|
||||||
const awaitedRatingsInfo = await ratingsInfo;
|
const seasonChoices = await octokit.repos
|
||||||
|
.getContent({
|
||||||
|
...DATABASE_REPO,
|
||||||
|
path: 'com/ratings',
|
||||||
|
})
|
||||||
|
.then(({ data }) => {
|
||||||
|
if (!Array.isArray(data)) {
|
||||||
|
throw throwError('Archived ratings data is malformed');
|
||||||
|
}
|
||||||
|
return data.map(
|
||||||
|
(folder) =>
|
||||||
|
({
|
||||||
|
name: `Season ${folder.name}`,
|
||||||
|
value: folder.name,
|
||||||
|
}) satisfies APIApplicationCommandOptionChoice<string>,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const command = new SlashCommandBuilder()
|
const command = new SlashCommandBuilder()
|
||||||
.setName('ratings')
|
.setName('ratings')
|
||||||
.setDescription('Ratings battles statistics')
|
.setDescription('Ratings battles statistics')
|
||||||
.addSubcommand((option) =>
|
.addSubcommandGroup((option) =>
|
||||||
option
|
addSubcommands(
|
||||||
.setName('league')
|
option.setName('archive').setDescription('Any previous season'),
|
||||||
.setDescription('Top league players')
|
(option) =>
|
||||||
.addStringOption((option) =>
|
option.addStringOption((option) =>
|
||||||
option
|
option
|
||||||
.setName('league')
|
.setName('season')
|
||||||
.setDescription('The league to display')
|
.setDescription('The season number')
|
||||||
.addChoices(
|
.setChoices(...seasonChoices)
|
||||||
...awaitedRatingsInfo.leagues.map(
|
.setRequired(true),
|
||||||
(league) =>
|
),
|
||||||
({
|
),
|
||||||
name: league.title.split(' ')[0].toLowerCase(),
|
|
||||||
value: `${league.index}`,
|
|
||||||
}) satisfies APIApplicationCommandOptionChoice<string>,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.setRequired(true),
|
|
||||||
)
|
|
||||||
.addStringOption(addRegionChoices)
|
|
||||||
.addIntegerOption((option) =>
|
|
||||||
option
|
|
||||||
.setName('limit')
|
|
||||||
.setDescription('How many rows to display (default: 10)')
|
|
||||||
.setRequired(false)
|
|
||||||
.setMinValue(5)
|
|
||||||
.setMaxValue(30),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.addSubcommand((option) =>
|
.addSubcommandGroup((option) =>
|
||||||
option
|
addSubcommands(
|
||||||
.setName('neighbours')
|
option
|
||||||
.setDescription('Your position and neighbours')
|
.setName('current')
|
||||||
.addStringOption(addUsernameChoices)
|
.setDescription('The current running Ratings season'),
|
||||||
.addIntegerOption((option) =>
|
),
|
||||||
option
|
|
||||||
.setName('limit')
|
|
||||||
.setDescription('How many neighbours to display (default: 10)')
|
|
||||||
.setRequired(false)
|
|
||||||
.setMinValue(5)
|
|
||||||
.setMaxValue(30),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
@@ -168,15 +224,30 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
|
|||||||
command,
|
command,
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const subcommand = interaction.options.getSubcommand(true);
|
interface SimplifiedPlayer {
|
||||||
|
id: number;
|
||||||
|
score: number;
|
||||||
|
position: number;
|
||||||
|
clan: string | undefined;
|
||||||
|
nickname: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subcommandGroup = interaction.options.getSubcommandGroup(true) as
|
||||||
|
| 'archive'
|
||||||
|
| 'current';
|
||||||
|
const subcommand = interaction.options.getSubcommand(true) as
|
||||||
|
| 'league'
|
||||||
|
| 'neighbours';
|
||||||
const limit = interaction.options.getInteger('limit') ?? 10;
|
const limit = interaction.options.getInteger('limit') ?? 10;
|
||||||
|
const seasonOption = interaction.options.getString('season');
|
||||||
|
const season = seasonOption ? parseInt(seasonOption) : undefined;
|
||||||
let titleName: string;
|
let titleName: string;
|
||||||
let titleImage: string | undefined;
|
let titleImage: string | undefined;
|
||||||
let titleNameDiscriminator: string | undefined;
|
let titleNameDiscriminator: string | undefined;
|
||||||
let titleDescription: string;
|
let titleDescription: string;
|
||||||
let playersBefore: number;
|
let playersBefore: number;
|
||||||
let playersAfter: number;
|
let playersAfter: number;
|
||||||
let players: RatingsPlayer[];
|
let players: SimplifiedPlayer[] | undefined;
|
||||||
let midnightLeaderboard: BlitzkriegRatingsLeaderboard | undefined;
|
let midnightLeaderboard: BlitzkriegRatingsLeaderboard | undefined;
|
||||||
let playerId: number | undefined;
|
let playerId: number | undefined;
|
||||||
let regionRatingsInfo: RatingsInfo;
|
let regionRatingsInfo: RatingsInfo;
|
||||||
@@ -186,31 +257,120 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
|
|||||||
interaction.options.getString('league')!,
|
interaction.options.getString('league')!,
|
||||||
);
|
);
|
||||||
const region = interaction.options.getString('region') as Region;
|
const region = interaction.options.getString('region') as Region;
|
||||||
regionRatingsInfo = await getRatingsInfo(region);
|
regionRatingsInfo = await (subcommandGroup === 'current'
|
||||||
|
? getRatingsInfo(region)
|
||||||
|
: getArchivedRatingsInfo(season!, region));
|
||||||
const regionSubdomain = regionToRegionSubdomain(region);
|
const regionSubdomain = regionToRegionSubdomain(region);
|
||||||
const result = await fetch(
|
const result =
|
||||||
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/league/${leagueIndex}/top/`,
|
subcommandGroup === 'current'
|
||||||
)
|
? regionRatingsInfo.detail === undefined
|
||||||
.then((response) => response.json() as Promise<LeagueTop>)
|
? await fetch(
|
||||||
.then(({ result }) => result.slice(0, limit));
|
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/league/${leagueIndex}/top/`,
|
||||||
const leagueInfo = regionRatingsInfo.leagues[leagueIndex];
|
)
|
||||||
midnightLeaderboard = await getMidnightLeaderboard(
|
.then((response) => response.json() as Promise<LeagueTop>)
|
||||||
region,
|
.then(async ({ result }) =>
|
||||||
regionRatingsInfo.current_season,
|
result.slice(0, limit).map(
|
||||||
);
|
(player) =>
|
||||||
|
({
|
||||||
|
id: player.spa_id,
|
||||||
|
score: player.score,
|
||||||
|
position: player.number,
|
||||||
|
clan: player.clan_tag,
|
||||||
|
nickname: player.nickname,
|
||||||
|
}) satisfies SimplifiedPlayer,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: undefined
|
||||||
|
: await octokit.repos
|
||||||
|
.getContent({
|
||||||
|
...DATABASE_REPO,
|
||||||
|
path: `${region}/ratings/${season}/latest.json`,
|
||||||
|
})
|
||||||
|
.then(async ({ data }) => {
|
||||||
|
if (Array.isArray(data) || data.type !== 'file') {
|
||||||
|
throw throwError('Archived ratings data is malformed');
|
||||||
|
}
|
||||||
|
|
||||||
|
const players = (await fetch(data.download_url!).then(
|
||||||
|
(response) => response.json(),
|
||||||
|
)) as BlitzkriegRatingsLeaderboard;
|
||||||
|
const firstPlayerIndex =
|
||||||
|
leagueIndex === 0
|
||||||
|
? 0
|
||||||
|
: players.findIndex(
|
||||||
|
(player) =>
|
||||||
|
player.score < LEAGUES[leagueIndex - 1].minScore,
|
||||||
|
);
|
||||||
|
const lastPlayerIndex = firstPlayerIndex + limit - 1;
|
||||||
|
const trimmed = players.slice(
|
||||||
|
firstPlayerIndex,
|
||||||
|
lastPlayerIndex + 1,
|
||||||
|
);
|
||||||
|
const ids = trimmed.map((player) => player.id).join(',');
|
||||||
|
const clanData = await getWargamingResponse<PlayerClanData>(
|
||||||
|
`https://api.wotblitz.${region}/wotb/clans/accountinfo/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${ids}&extra=clan`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return trimmed.map(
|
||||||
|
(player, index) =>
|
||||||
|
({
|
||||||
|
id: player.id,
|
||||||
|
score: player.score,
|
||||||
|
position: firstPlayerIndex + index + 1,
|
||||||
|
clan: clanData[player.id]!.clan?.tag,
|
||||||
|
nickname: clanData[player.id]!.account_name,
|
||||||
|
}) satisfies SimplifiedPlayer,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const leagueInfo =
|
||||||
|
regionRatingsInfo.detail === undefined
|
||||||
|
? regionRatingsInfo.leagues[leagueIndex]
|
||||||
|
: undefined;
|
||||||
|
midnightLeaderboard =
|
||||||
|
subcommandGroup === 'current'
|
||||||
|
? regionRatingsInfo.detail === undefined
|
||||||
|
? await getMidnightLeaderboard(
|
||||||
|
region,
|
||||||
|
regionRatingsInfo.current_season,
|
||||||
|
)
|
||||||
|
: undefined
|
||||||
|
: await getMidnightLeaderboard(region, season!);
|
||||||
|
|
||||||
players = result;
|
players = result;
|
||||||
playersBefore = result[0].number - 1;
|
playersBefore = result ? result[0].position - 1 : 0;
|
||||||
playersAfter =
|
playersAfter =
|
||||||
regionRatingsInfo.count - result[result.length - 1].number;
|
regionRatingsInfo.detail === undefined && result !== undefined
|
||||||
titleName = `${leagueInfo.title} - ${REGION_NAMES_SHORT[region]}`;
|
? regionRatingsInfo.count - result[result.length - 1].position
|
||||||
titleImage = leagueInfo.big_icon.startsWith('http')
|
: 0;
|
||||||
? leagueInfo.big_icon
|
titleName = `${
|
||||||
: `https:${leagueInfo.big_icon}`;
|
leagueInfo ? leagueInfo.title : 'No Ongoing Season'
|
||||||
|
} - ${REGION_NAMES_SHORT[region]}`;
|
||||||
|
titleImage = leagueInfo
|
||||||
|
? leagueInfo.big_icon.startsWith('http')
|
||||||
|
? leagueInfo.big_icon
|
||||||
|
: `https:${leagueInfo.big_icon}`
|
||||||
|
: undefined;
|
||||||
titleDescription = `Top ${limit} players`;
|
titleDescription = `Top ${limit} players`;
|
||||||
} else {
|
} else {
|
||||||
const { region, id } = await resolvePlayerFromCommand(interaction);
|
const { region, id } = await resolvePlayerFromCommand(interaction);
|
||||||
regionRatingsInfo = await getRatingsInfo(region);
|
regionRatingsInfo =
|
||||||
|
subcommandGroup === 'current'
|
||||||
|
? await getRatingsInfo(region)
|
||||||
|
: await octokit.repos
|
||||||
|
.getContent({
|
||||||
|
...DATABASE_REPO,
|
||||||
|
path: `${region}/ratings/${season}/info.json`,
|
||||||
|
})
|
||||||
|
.then(async ({ data }) => {
|
||||||
|
if (Array.isArray(data) || data.type !== 'file') {
|
||||||
|
throw throwError('Archived ratings info is malformed');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = fetch(data.download_url!);
|
||||||
|
return (await response.then((response) =>
|
||||||
|
response.json(),
|
||||||
|
)) as RatingsInfo;
|
||||||
|
});
|
||||||
const accountInfo = await getWargamingResponse<AccountInfo>(
|
const accountInfo = await getWargamingResponse<AccountInfo>(
|
||||||
`https://api.wotblitz.${region}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`,
|
`https://api.wotblitz.${region}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`,
|
||||||
);
|
);
|
||||||
@@ -220,57 +380,139 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
|
|||||||
)
|
)
|
||||||
)[id]?.clan;
|
)[id]?.clan;
|
||||||
const regionSubdomain = regionToRegionSubdomain(region);
|
const regionSubdomain = regionToRegionSubdomain(region);
|
||||||
const { neighbors } = await fetch(
|
const neighbors =
|
||||||
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${Math.round(
|
subcommandGroup === 'current'
|
||||||
limit / 2,
|
? regionRatingsInfo.detail === undefined
|
||||||
)}`,
|
? await fetch(
|
||||||
).then((response) => response.json() as Promise<RatingsNeighbors>);
|
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${Math.round(
|
||||||
midnightLeaderboard = await getMidnightLeaderboard(
|
limit / 2,
|
||||||
region,
|
)}`,
|
||||||
regionRatingsInfo.current_season,
|
)
|
||||||
);
|
.then(
|
||||||
|
(response) =>
|
||||||
|
response.json() as Promise<RatingsNeighbors>,
|
||||||
|
)
|
||||||
|
.then((players) =>
|
||||||
|
players.neighbors.map(
|
||||||
|
(player) =>
|
||||||
|
({
|
||||||
|
id: player.spa_id,
|
||||||
|
position: player.number,
|
||||||
|
score: player.score,
|
||||||
|
clan: player.clan_tag,
|
||||||
|
nickname: player.nickname,
|
||||||
|
}) satisfies SimplifiedPlayer,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: undefined
|
||||||
|
: await octokit.repos
|
||||||
|
.getContent({
|
||||||
|
...DATABASE_REPO,
|
||||||
|
path: `${region}/ratings/${season}/latest.json`,
|
||||||
|
})
|
||||||
|
.then(async ({ data }) => {
|
||||||
|
if (Array.isArray(data) || data.type !== 'file') {
|
||||||
|
throw throwError(
|
||||||
|
'Archived ratings latest data is malformed',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (clan) titleNameDiscriminator = `[${clan.tag}]`;
|
const response = fetch(data.download_url!);
|
||||||
|
const players = (await response.then((response) =>
|
||||||
|
response.json(),
|
||||||
|
)) as BlitzkriegRatingsLeaderboard;
|
||||||
|
const playerIndex = players.findIndex(
|
||||||
|
(player) => player.id === id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (playerIndex === -1) {
|
||||||
|
throw throwError(
|
||||||
|
`${accountInfo[id].nickname} didn't player ratings in season ${season}`,
|
||||||
|
'This player did not participate in this season or did not get past calibration.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const halfRange = Math.round(limit / 2);
|
||||||
|
const trimmed = players.slice(
|
||||||
|
playerIndex - halfRange,
|
||||||
|
playerIndex + halfRange + 1,
|
||||||
|
);
|
||||||
|
const ids = trimmed.map((player) => player.id).join(',');
|
||||||
|
const clanData = await getWargamingResponse<PlayerClanData>(
|
||||||
|
`https://api.wotblitz.${region}/wotb/clans/accountinfo/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${ids}&extra=clan`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return trimmed.map((player, index) => ({
|
||||||
|
id: player.id,
|
||||||
|
score: player.score,
|
||||||
|
position: playerIndex + index + 1,
|
||||||
|
clan: clanData[player.id]!.clan?.tag,
|
||||||
|
nickname: clanData[player.id]!.account_name,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
midnightLeaderboard =
|
||||||
|
subcommandGroup === 'current'
|
||||||
|
? regionRatingsInfo.detail === undefined
|
||||||
|
? await getMidnightLeaderboard(
|
||||||
|
region,
|
||||||
|
regionRatingsInfo.current_season,
|
||||||
|
)
|
||||||
|
: undefined
|
||||||
|
: await getMidnightLeaderboard(region, season!);
|
||||||
|
|
||||||
|
if (clan && neighbors) titleNameDiscriminator = `[${clan.tag}]`;
|
||||||
|
|
||||||
players = neighbors;
|
players = neighbors;
|
||||||
titleImage = clan
|
titleImage = neighbors
|
||||||
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png`
|
? clan
|
||||||
|
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png`
|
||||||
|
: undefined
|
||||||
: undefined;
|
: undefined;
|
||||||
titleName = accountInfo[id].nickname;
|
titleName = neighbors
|
||||||
|
? accountInfo[id].nickname
|
||||||
|
: `No Ongoing Season - ${REGION_NAMES_SHORT[region]}`;
|
||||||
titleDescription = 'Ratings neighbours';
|
titleDescription = 'Ratings neighbours';
|
||||||
playersBefore = neighbors[0].number - 1;
|
playersBefore = neighbors ? neighbors[0].position - 1 : 0;
|
||||||
playersAfter =
|
playersAfter =
|
||||||
regionRatingsInfo.count - neighbors[neighbors.length - 1].number;
|
regionRatingsInfo.detail === undefined
|
||||||
|
? neighbors
|
||||||
|
? regionRatingsInfo.count -
|
||||||
|
neighbors[neighbors.length - 1].position
|
||||||
|
: 0
|
||||||
|
: 0;
|
||||||
playerId = id;
|
playerId = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = players.map((player) => {
|
const items = players?.map((player) => {
|
||||||
const reward = regionRatingsInfo.rewards.find(
|
const reward =
|
||||||
(reward) =>
|
regionRatingsInfo.detail === undefined
|
||||||
player.number >= reward.from_position &&
|
? regionRatingsInfo.rewards.find(
|
||||||
player.number <= reward.to_position,
|
(reward) =>
|
||||||
);
|
player.position >= reward.from_position &&
|
||||||
|
player.position <= reward.to_position,
|
||||||
|
)
|
||||||
|
: undefined;
|
||||||
const midnightIndex = midnightLeaderboard?.findIndex(
|
const midnightIndex = midnightLeaderboard?.findIndex(
|
||||||
(item) => item.id === player.spa_id,
|
(item) => item.id === player.id,
|
||||||
);
|
);
|
||||||
const midnightScore =
|
const midnightScore =
|
||||||
midnightIndex === undefined
|
midnightIndex === undefined
|
||||||
? player.score
|
? player.score
|
||||||
: midnightLeaderboard![midnightIndex].score;
|
: midnightLeaderboard![midnightIndex].score;
|
||||||
const midnightPosition =
|
const midnightPosition =
|
||||||
midnightIndex === undefined ? player.number : midnightIndex + 1;
|
midnightIndex === undefined ? player.position : midnightIndex + 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Leaderboard.Item
|
<Leaderboard.Item
|
||||||
nickname={player.nickname}
|
nickname={player.nickname}
|
||||||
score={player.score}
|
score={player.score}
|
||||||
position={player.number}
|
position={player.position}
|
||||||
clan={player.clan_tag}
|
clan={player.clan}
|
||||||
reward={reward}
|
reward={reward}
|
||||||
deltaScore={player.score - midnightScore}
|
deltaScore={player.score - midnightScore}
|
||||||
deltaPosition={midnightPosition - player.number}
|
deltaPosition={midnightPosition - player.position}
|
||||||
key={player.spa_id}
|
key={player.id}
|
||||||
highlight={player.spa_id === playerId}
|
highlight={player.id === playerId}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -284,11 +526,15 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
|
|||||||
description={`${titleDescription} • ${new Date().toDateString()}`}
|
description={`${titleDescription} • ${new Date().toDateString()}`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Leaderboard.Root>
|
{items && (
|
||||||
{playersBefore > 0 && <Leaderboard.Gap number={playersBefore} />}
|
<Leaderboard.Root>
|
||||||
{items}
|
{playersBefore > 0 && (
|
||||||
{playersAfter > 0 && <Leaderboard.Gap number={playersAfter} />}
|
<Leaderboard.Gap number={playersBefore} />
|
||||||
</Leaderboard.Root>
|
)}
|
||||||
|
{items}
|
||||||
|
{playersAfter > 0 && <Leaderboard.Gap number={playersAfter} />}
|
||||||
|
</Leaderboard.Root>
|
||||||
|
)}
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ export const statsCommand = new Promise<CommandRegistryRaw>(async (resolve) => {
|
|||||||
)
|
)
|
||||||
)[player.id];
|
)[player.id];
|
||||||
|
|
||||||
|
const tierOption = interaction.options.getString('tier');
|
||||||
|
|
||||||
return [
|
return [
|
||||||
await stats(
|
await stats(
|
||||||
commandGroup,
|
commandGroup,
|
||||||
@@ -72,7 +74,7 @@ export const statsCommand = new Promise<CommandRegistryRaw>(async (resolve) => {
|
|||||||
'tank-type':
|
'tank-type':
|
||||||
interaction.options.getString('tank-type') ?? undefined,
|
interaction.options.getString('tank-type') ?? undefined,
|
||||||
nation: interaction.options.getString('nation') ?? undefined,
|
nation: interaction.options.getString('nation') ?? undefined,
|
||||||
tier: interaction.options.getInteger('tier') ?? undefined,
|
tier: tierOption ? parseInt(tierOption) : undefined,
|
||||||
'tree-type': (interaction.options.getString('tree-type') ??
|
'tree-type': (interaction.options.getString('tree-type') ??
|
||||||
undefined) as TreeTypeString | undefined,
|
undefined) as TreeTypeString | undefined,
|
||||||
} satisfies Partial<MultiTankFilters>)
|
} satisfies Partial<MultiTankFilters>)
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
import { ReactNode } from 'react';
|
|
||||||
import { theme } from '../stitches.config';
|
|
||||||
|
|
||||||
export enum PoweredByType {
|
|
||||||
BlitzStars,
|
|
||||||
Wargaming,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PoweredByProps {
|
|
||||||
type: PoweredByType;
|
|
||||||
footer?: ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
const POWERED_BY_IMAGES: Record<PoweredByType, string> = {
|
|
||||||
[PoweredByType.BlitzStars]:
|
|
||||||
'https://www.blitzstars.com/assets/images/4b4cb591.TankyMcPewpew.png',
|
|
||||||
[PoweredByType.Wargaming]: 'https://i.imgur.com/jADGWm0.png',
|
|
||||||
};
|
|
||||||
|
|
||||||
const POWERED_BY_NAMES: Record<PoweredByType, string> = {
|
|
||||||
[PoweredByType.BlitzStars]: 'BlitzStars',
|
|
||||||
[PoweredByType.Wargaming]: 'Wargaming',
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function PoweredBy({ type, footer }: PoweredByProps) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: footer === undefined ? 'space-around' : 'space-between',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{footer !== undefined && <div style={{ display: 'flex' }}>{footer}</div>}
|
|
||||||
|
|
||||||
<div style={{ display: 'flex', gap: 4 }}>
|
|
||||||
<img src={POWERED_BY_IMAGES[type]} style={{ height: 16, width: 16 }} />
|
|
||||||
<span style={{ color: theme.colors.textLowContrast, fontSize: 16 }}>
|
|
||||||
Powered by {POWERED_BY_NAMES[type]}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { AllStats } from '../../types/accountInfo';
|
|
||||||
|
|
||||||
export default function diffStats(stats1: AllStats, stats2: AllStats) {
|
|
||||||
function diff(value: (allStats: AllStats) => number) {
|
|
||||||
return value(stats2) - value(stats1);
|
|
||||||
}
|
|
||||||
function max(value: (allStats: AllStats) => number) {
|
|
||||||
return Math.max(value(stats1), value(stats2));
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
battles: diff((a) => a.battles),
|
|
||||||
wins: diff((a) => a.wins),
|
|
||||||
capture_points: diff((a) => a.capture_points),
|
|
||||||
damage_dealt: diff((a) => a.damage_dealt),
|
|
||||||
damage_received: diff((a) => a.damage_received),
|
|
||||||
dropped_capture_points: diff((a) => a.dropped_capture_points),
|
|
||||||
frags: diff((a) => a.frags),
|
|
||||||
frags8p: diff((a) => a.frags8p),
|
|
||||||
hits: diff((a) => a.hits),
|
|
||||||
losses: diff((a) => a.losses),
|
|
||||||
max_frags: max((a) => a.max_frags),
|
|
||||||
max_xp: max((a) => a.max_xp),
|
|
||||||
shots: diff((a) => a.shots),
|
|
||||||
spotted: diff((a) => a.spotted),
|
|
||||||
survived_battles: diff((a) => a.survived_battles),
|
|
||||||
win_and_survived: diff((a) => a.win_and_survived),
|
|
||||||
xp: diff((a) => a.xp),
|
|
||||||
} satisfies AllStats;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { RatingsInfo } from '../../commands/ratings';
|
||||||
|
import { Region } from '../../constants/regions';
|
||||||
|
import { DATABASE_REPO } from '../database/getMidnightLeaderboard';
|
||||||
|
import { octokit } from '../express/octokit';
|
||||||
|
import throwError from '../node/throwError';
|
||||||
|
|
||||||
|
export default async function getArchivedRatingsInfo(
|
||||||
|
season: number,
|
||||||
|
region: Region,
|
||||||
|
) {
|
||||||
|
const { data } = await octokit.repos.getContent({
|
||||||
|
...DATABASE_REPO,
|
||||||
|
path: `${region}/ratings/${season}/info.json`,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Array.isArray(data) || data.type !== 'file')
|
||||||
|
throw throwError('Archived ratings info is malformed');
|
||||||
|
|
||||||
|
const content = Buffer.from(data.content, 'base64').toString();
|
||||||
|
const jsonContent = JSON.parse(content) as RatingsInfo & {
|
||||||
|
detail: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
return jsonContent;
|
||||||
|
}
|
||||||
@@ -8,5 +8,6 @@ export default async function getRatingsInfo(region: Region) {
|
|||||||
region,
|
region,
|
||||||
)}.wotblitz.com/en/api/rating-leaderboards/season/`,
|
)}.wotblitz.com/en/api/rating-leaderboards/season/`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (await response.json()) as Promise<RatingsInfo>;
|
return (await response.json()) as Promise<RatingsInfo>;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Thanks, August!
|
||||||
|
* https://stackoverflow.com/a/41358305/12294756
|
||||||
|
*/
|
||||||
|
const ROMAN = {
|
||||||
|
M: 1000,
|
||||||
|
CM: 900,
|
||||||
|
D: 500,
|
||||||
|
CD: 400,
|
||||||
|
C: 100,
|
||||||
|
XC: 90,
|
||||||
|
L: 50,
|
||||||
|
XL: 40,
|
||||||
|
X: 10,
|
||||||
|
IX: 9,
|
||||||
|
V: 5,
|
||||||
|
IV: 4,
|
||||||
|
I: 1,
|
||||||
|
};
|
||||||
|
const ROMAN_KEYS = Object.keys(ROMAN) as (keyof typeof ROMAN)[];
|
||||||
|
|
||||||
|
export default function numberToRomanNumeral(value: number) {
|
||||||
|
let string = '';
|
||||||
|
|
||||||
|
ROMAN_KEYS.forEach((index) => {
|
||||||
|
const part = Math.floor(value / ROMAN[index]);
|
||||||
|
value -= part * ROMAN[index];
|
||||||
|
string += index.repeat(part);
|
||||||
|
});
|
||||||
|
|
||||||
|
return string;
|
||||||
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import sumStats from '../blitz/sumStats';
|
|
||||||
import { emptyAllStats } from './getDiffedTankStats';
|
|
||||||
import { tankAverages } from './tankAverages';
|
|
||||||
|
|
||||||
console.log('Calculating player average...');
|
|
||||||
export const playerAverage = Object.entries(await tankAverages).reduce(
|
|
||||||
(accumulator, [, tankAverage]) => {
|
|
||||||
return sumStats([accumulator, tankAverage.all]);
|
|
||||||
},
|
|
||||||
emptyAllStats,
|
|
||||||
);
|
|
||||||
console.log('Calculated player average');
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { BlitzkriegRatingsLeaderboard } from '../../../scripts/buildRatingsLeaderboard';
|
import { BlitzkriegRatingsLeaderboard } from '../../../scripts/buildRatingsLeaderboard';
|
||||||
import { octokit } from '../../bot';
|
|
||||||
import { Region } from '../../constants/regions';
|
import { Region } from '../../constants/regions';
|
||||||
|
import { octokit } from '../express/octokit';
|
||||||
|
|
||||||
export const DATABASE_REPO = { owner: 'tresabhi', repo: 'blitzkrieg-db' };
|
export const DATABASE_REPO = { owner: 'tresabhi', repo: 'blitzkrieg-db' };
|
||||||
|
|
||||||
@@ -15,12 +15,13 @@ export default async function getMidnightLeaderboard(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!Array.isArray(data) && data.type === 'file') {
|
if (!Array.isArray(data) && data.type === 'file') {
|
||||||
const content = Buffer.from(data.content, 'base64').toString();
|
const response = await fetch(data.download_url!);
|
||||||
const jsonContent = JSON.parse(content) as BlitzkriegRatingsLeaderboard;
|
|
||||||
|
|
||||||
return jsonContent;
|
return (await response.json()) as BlitzkriegRatingsLeaderboard;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('No midnight leaderboard found');
|
console.warn(
|
||||||
|
`No midnight leaderboard found for season ${season}, region ${region}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import { StatType } from '../../renderers/stats';
|
|||||||
import { tankopediaInfo } from '../blitz/tankopedia';
|
import { tankopediaInfo } from '../blitz/tankopedia';
|
||||||
import addPeriodSubCommands from './addPeriodSubCommands';
|
import addPeriodSubCommands from './addPeriodSubCommands';
|
||||||
import addTankChoices from './addTankChoices';
|
import addTankChoices from './addTankChoices';
|
||||||
|
import addTierChoices from './addTierChoices';
|
||||||
import addUsernameChoices from './addUsernameChoices';
|
import addUsernameChoices from './addUsernameChoices';
|
||||||
|
|
||||||
export default async function addStatTypeSubCommandGroups(
|
export default async function addStatTypeSubCommandGroups(
|
||||||
@@ -61,13 +62,8 @@ export default async function addStatTypeSubCommandGroups(
|
|||||||
)
|
)
|
||||||
.setRequired(false),
|
.setRequired(false),
|
||||||
)
|
)
|
||||||
.addIntegerOption((option) =>
|
.addStringOption((option) =>
|
||||||
option
|
addTierChoices(option).setRequired(false),
|
||||||
.setName('tier')
|
|
||||||
.setDescription('Tier')
|
|
||||||
.setMinValue(1)
|
|
||||||
.setMaxValue(10)
|
|
||||||
.setRequired(false),
|
|
||||||
)
|
)
|
||||||
.addStringOption((option) =>
|
.addStringOption((option) =>
|
||||||
option
|
option
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { SlashCommandStringOption } from 'discord.js';
|
||||||
|
import { range } from 'lodash';
|
||||||
|
import numberToRomanNumeral from '../blitz/numberToRomanNumeral';
|
||||||
|
|
||||||
|
export default function addTierChoices(option: SlashCommandStringOption) {
|
||||||
|
return option
|
||||||
|
.setName('tier')
|
||||||
|
.setDescription('The tier you want to see')
|
||||||
|
.setChoices(
|
||||||
|
...range(10, 0).map((tier) => ({
|
||||||
|
name: `Tier ${tier} - ${numberToRomanNumeral(tier)}`,
|
||||||
|
value: `${tier}`,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.setRequired(true);
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { Client, GatewayIntentBits } from 'discord.js';
|
||||||
|
import guildMemberAdd from '../../events/guildMemberAdd';
|
||||||
|
import interactionCreate from '../../events/interactionCreate';
|
||||||
|
import ready from '../../events/ready';
|
||||||
|
|
||||||
|
export const client = new Client({
|
||||||
|
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers],
|
||||||
|
})
|
||||||
|
.on('ready', ready)
|
||||||
|
.on('guildMemberAdd', guildMemberAdd)
|
||||||
|
.on('interactionCreate', interactionCreate);
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import { ButtonBuilder, ButtonStyle } from 'discord.js';
|
|
||||||
|
|
||||||
// TODO: ADD THIS AGAIN IMMEDIATELY
|
|
||||||
export const supportBlitzStars = new ButtonBuilder()
|
|
||||||
.setLabel('Support BlitzStars')
|
|
||||||
.setURL('https://www.blitzstars.com/supporters')
|
|
||||||
.setStyle(ButtonStyle.Link);
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { Octokit } from '@octokit/rest';
|
||||||
|
import { secrets } from '../node/secrets';
|
||||||
|
|
||||||
|
export const octokit = new Octokit({ auth: secrets.GH_TOKEN });
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
import { EmbedBuilder, TextChannel } from 'discord.js';
|
import { EmbedBuilder, TextChannel } from 'discord.js';
|
||||||
import discord from '../../discord.json' assert { type: 'json' };
|
import discord from '../../discord.json' assert { type: 'json' };
|
||||||
import { client } from '../bot';
|
|
||||||
import { NEGATIVE_COLOR } from '../constants/colors';
|
import { NEGATIVE_COLOR } from '../constants/colors';
|
||||||
|
import { client } from '../core/discord/client';
|
||||||
import isDev from '../core/node/isDev';
|
import isDev from '../core/node/isDev';
|
||||||
|
|
||||||
const PROCESS_ERROR_EVENTS = ['uncaughtException'];
|
const PROCESS_ERROR_EVENTS = ['uncaughtException'];
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
SlashCommandSubcommandsOnlyBuilder,
|
SlashCommandSubcommandsOnlyBuilder,
|
||||||
} from 'discord.js';
|
} from 'discord.js';
|
||||||
import discord from '../../../discord.json' assert { type: 'json' };
|
import discord from '../../../discord.json' assert { type: 'json' };
|
||||||
|
import { clanEventCommand } from '../../commands/clanEvent';
|
||||||
import { debugCommand } from '../../commands/debug';
|
import { debugCommand } from '../../commands/debug';
|
||||||
import { eligibleCommand } from '../../commands/eligible';
|
import { eligibleCommand } from '../../commands/eligible';
|
||||||
import { evolutionCommand } from '../../commands/evolution';
|
import { evolutionCommand } from '../../commands/evolution';
|
||||||
@@ -99,6 +100,7 @@ export const COMMANDS_RAW: CommandRegistry[] = [
|
|||||||
evolutionCommand,
|
evolutionCommand,
|
||||||
statsCommand,
|
statsCommand,
|
||||||
ratingsCommand,
|
ratingsCommand,
|
||||||
|
clanEventCommand,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const commands = Promise.all(COMMANDS_RAW).then((rawCommands) =>
|
export const commands = Promise.all(COMMANDS_RAW).then((rawCommands) =>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import * as Graph from '../components/Graph';
|
import * as Graph from '../components/Graph';
|
||||||
import NoData, { NoDataType } from '../components/NoData';
|
import NoData, { NoDataType } from '../components/NoData';
|
||||||
import PoweredBy, { PoweredByType } from '../components/PoweredBy';
|
|
||||||
import TitleBar from '../components/TitleBar';
|
import TitleBar from '../components/TitleBar';
|
||||||
import Wrapper from '../components/Wrapper';
|
import Wrapper from '../components/Wrapper';
|
||||||
import getWargamingResponse from '../core/blitz/getWargamingResponse';
|
import getWargamingResponse from '../core/blitz/getWargamingResponse';
|
||||||
@@ -118,8 +117,6 @@ export default async function evolution<Type extends StatType>(
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{plot.length === 0 && <NoData type={NoDataType.BattlesInPeriod} />}
|
{plot.length === 0 && <NoData type={NoDataType.BattlesInPeriod} />}
|
||||||
|
|
||||||
<PoweredBy type={PoweredByType.BlitzStars} />
|
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import GenericAllStats from '../components/GenericAllStats';
|
import GenericAllStats from '../components/GenericAllStats';
|
||||||
import NoData, { NoDataType } from '../components/NoData';
|
import NoData, { NoDataType } from '../components/NoData';
|
||||||
import PoweredBy, { PoweredByType } from '../components/PoweredBy';
|
|
||||||
import TierWeights, { TierWeightsRecord } from '../components/TierWeights';
|
import TierWeights, { TierWeightsRecord } from '../components/TierWeights';
|
||||||
import TitleBar from '../components/TitleBar';
|
import TitleBar from '../components/TitleBar';
|
||||||
import Wrapper from '../components/Wrapper';
|
import Wrapper from '../components/Wrapper';
|
||||||
@@ -35,7 +34,7 @@ export default async function fullStats<Type extends StatType>(
|
|||||||
let nameDiscriminator: string | undefined;
|
let nameDiscriminator: string | undefined;
|
||||||
let image: string | undefined;
|
let image: string | undefined;
|
||||||
|
|
||||||
if (type === 'player') {
|
if (type === 'player' || type === 'multi-tank') {
|
||||||
const clan = (
|
const clan = (
|
||||||
await getWargamingResponse<PlayerClanData>(
|
await getWargamingResponse<PlayerClanData>(
|
||||||
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${secrets.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`,
|
||||||
@@ -49,7 +48,6 @@ export default async function fullStats<Type extends StatType>(
|
|||||||
} else if (type === 'tank') {
|
} else if (type === 'tank') {
|
||||||
nameDiscriminator = `(${await resolveTankName(filters as number)})`;
|
nameDiscriminator = `(${await resolveTankName(filters as number)})`;
|
||||||
image = (await tankopedia)[filters as number]?.images.normal;
|
image = (await tankopedia)[filters as number]?.images.normal;
|
||||||
} else {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { diffed, order } = await getDiffedTankStats(server, id, start, end);
|
const { diffed, order } = await getDiffedTankStats(server, id, start, end);
|
||||||
@@ -165,8 +163,6 @@ export default async function fullStats<Type extends StatType>(
|
|||||||
supplementaryStats={supplementaryStats}
|
supplementaryStats={supplementaryStats}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<PoweredBy type={PoweredByType.BlitzStars} />
|
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import AllStatsOverview from '../components/AllStatsOverview';
|
import AllStatsOverview from '../components/AllStatsOverview';
|
||||||
import NoData, { NoDataType } from '../components/NoData';
|
import NoData, { NoDataType } from '../components/NoData';
|
||||||
import PoweredBy, { PoweredByType } from '../components/PoweredBy';
|
|
||||||
import { TreeTypeString } from '../components/Tanks';
|
import { TreeTypeString } from '../components/Tanks';
|
||||||
import TitleBar from '../components/TitleBar';
|
import TitleBar from '../components/TitleBar';
|
||||||
import Wrapper from '../components/Wrapper';
|
import Wrapper from '../components/Wrapper';
|
||||||
@@ -45,7 +44,7 @@ export default async function stats<Type extends StatType>(
|
|||||||
let nameDiscriminator: string | undefined;
|
let nameDiscriminator: string | undefined;
|
||||||
let image: string | undefined;
|
let image: string | undefined;
|
||||||
|
|
||||||
if (type === 'player') {
|
if (type === 'player' || type === 'multi-tank') {
|
||||||
const clan = (
|
const clan = (
|
||||||
await getWargamingResponse<PlayerClanData>(
|
await getWargamingResponse<PlayerClanData>(
|
||||||
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${secrets.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`,
|
||||||
@@ -168,8 +167,6 @@ export default async function stats<Type extends StatType>(
|
|||||||
|
|
||||||
{!stats?.battles && <NoData type={NoDataType.BattlesInPeriod} />}
|
{!stats?.battles && <NoData type={NoDataType.BattlesInPeriod} />}
|
||||||
{stats?.battles > 0 && overview}
|
{stats?.battles > 0 && overview}
|
||||||
|
|
||||||
<PoweredBy type={PoweredByType.BlitzStars} footer={footer} />
|
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import * as Breakdown from '../components/Breakdown';
|
import * as Breakdown from '../components/Breakdown';
|
||||||
import NoData, { NoDataType } from '../components/NoData';
|
import NoData, { NoDataType } from '../components/NoData';
|
||||||
import PoweredBy, { PoweredByType } from '../components/PoweredBy';
|
|
||||||
import TitleBar from '../components/TitleBar';
|
import TitleBar from '../components/TitleBar';
|
||||||
import Wrapper from '../components/Wrapper';
|
import Wrapper from '../components/Wrapper';
|
||||||
import calculateWN8 from '../core/blitz/calculateWN8';
|
import calculateWN8 from '../core/blitz/calculateWN8';
|
||||||
@@ -27,8 +26,6 @@ export default async function today(
|
|||||||
showTotal = true,
|
showTotal = true,
|
||||||
naked?: boolean,
|
naked?: boolean,
|
||||||
) {
|
) {
|
||||||
console.log(maximized);
|
|
||||||
|
|
||||||
const { diffed, order } = await getDiffedTankStats(
|
const { diffed, order } = await getDiffedTankStats(
|
||||||
server,
|
server,
|
||||||
id,
|
id,
|
||||||
@@ -174,8 +171,6 @@ export default async function today(
|
|||||||
|
|
||||||
{rows.length === 0 && <NoData type={NoDataType.BattlesInPeriod} />}
|
{rows.length === 0 && <NoData type={NoDataType.BattlesInPeriod} />}
|
||||||
{rows.length > 0 && <Breakdown.Root>{rows}</Breakdown.Root>}
|
{rows.length > 0 && <Breakdown.Root>{rows}</Breakdown.Root>}
|
||||||
|
|
||||||
<PoweredBy type={PoweredByType.BlitzStars} />
|
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
+1
-1
@@ -11,5 +11,5 @@
|
|||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"jsx": "react-jsx"
|
"jsx": "react-jsx"
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "src/components/Tanks/components/Row.tsx"]
|
"include": ["src/**/*.ts", "src/components/Tanks/components/Row.tsx", "src/commands/clanEvent.ts"]
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user