Compare commits

...
23 Commits
Author SHA1 Message Date
TrèsAbhi 3c2861f956 Merge pull request #22 from tresabhi/1.11.0 2023-07-17 19:30:50 -05:00
TresAbhi 894b841769 feat: changelogs 2023-07-18 00:29:49 +00:00
TresAbhi 62627c96a3 feat: added other regions 2023-07-18 00:23:59 +00:00
TresAbhi c173ece882 feat: completed /ratings league 2023-07-18 00:17:00 +00:00
TresAbhi eca7cbbbeb feat: added more mature secrets handling 2023-07-16 18:26:33 +00:00
TresAbhi c72bfa9f71 feat: added rewards 2023-07-16 18:15:34 +00:00
TresAbhi b4ae39a509 feat: added custom lighter ratings leaderboard 2023-07-16 17:56:37 +00:00
TresAbhi 631c051895 feat: ditch histories 2023-07-16 17:51:01 +00:00
TresAbhi 9a2e11a2a4 feat: added basic rendering 2023-07-16 17:45:29 +00:00
TresAbhi aa6011b18d feat: added basic infrastructure for ratings command 2023-07-15 18:44:20 +00:00
TresAbhi 9419aec116 fix: removed "Europe" from /verify example 2023-07-15 18:07:14 +00:00
TresAbhi 638402589f feat: switched scripts to typescript 2023-07-15 01:21:59 +00:00
TrèsAbhi 6d6a6bf738 fix: workflow region names 2023-07-14 12:41:45 -05:00
TrèsAbhi 31f6123a55 Merge pull request #21 from tresabhi/1.10.2 2023-07-14 12:37:51 -05:00
TresAbhi 18a3e3092c fix: gave all regions their own cron times 2023-07-14 17:35:51 +00:00
TresAbhi 5ded0f8744 feat: changelogs 2023-07-14 17:31:05 +00:00
TresAbhi 4e0f0536fa feat: added separate latest and leaderboards for all regions 2023-07-14 17:27:57 +00:00
TresAbhi 3f081a5577 fix: install dependencies 2023-07-14 17:16:51 +00:00
TresAbhi 2447d663fa fix: use na region 2023-07-14 17:14:53 +00:00
TrèsAbhi 8a0afee97a Merge pull request #20 from tresabhi/1.10.1 2023-07-13 20:54:31 -05:00
TresAbhi 3cca68a8f0 feat: changelogs 2023-07-14 01:53:44 +00:00
TresAbhi 622cd92a13 feat: added cron job 2023-07-14 01:52:05 +00:00
TresAbhi 6d9e6973d5 feat: added ratings leaderboard generation and publication 2023-07-14 01:32:20 +00:00
70 changed files with 1412 additions and 219 deletions

No files matched your search

@@ -0,0 +1,34 @@
name: CD Asia Ratings Latest
on:
schedule:
- cron: "0 0 * * *"
jobs:
host:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Use node
uses: actions/setup-node@v3
with:
node-version: 18
cache: "npm"
- name: Install Yarn
run: npm install --location=global yarn
- name: Use node_modules cache
uses: actions/cache@v3
with:
path: "**/node_modules"
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- name: Install dependencies
run: yarn
- name: Generate leaderboard
run: GH_TOKEN=${{ secrets.GH_TOKEN }} yarn publish:ratingsleaderboard --region=asia --latest
@@ -0,0 +1,34 @@
name: CD Asia Ratings Leaderboards
on:
schedule:
- cron: "0 17 * * *"
jobs:
host:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Use node
uses: actions/setup-node@v3
with:
node-version: 18
cache: "npm"
- name: Install Yarn
run: npm install --location=global yarn
- name: Use node_modules cache
uses: actions/cache@v3
with:
path: "**/node_modules"
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- name: Install dependencies
run: yarn
- name: Generate leaderboard
run: GH_TOKEN=${{ secrets.GH_TOKEN }} yarn publish:ratingsleaderboard --region=asia
+2 -6
View File
@@ -16,18 +16,14 @@ jobs:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
strategy:
matrix:
node-version: [18.x]
steps:
- name: Get code
- name: Checkout
uses: actions/checkout@v3
- name: Use node
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
node-version: 18
cache: 'npm'
- name: Install Yarn
@@ -0,0 +1,34 @@
name: CD Europe Ratings Latest
on:
schedule:
- cron: "0 0 * * *"
jobs:
host:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Use node
uses: actions/setup-node@v3
with:
node-version: 18
cache: "npm"
- name: Install Yarn
run: npm install --location=global yarn
- name: Use node_modules cache
uses: actions/cache@v3
with:
path: "**/node_modules"
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- name: Install dependencies
run: yarn
- name: Generate leaderboard
run: GH_TOKEN=${{ secrets.GH_TOKEN }} yarn publish:ratingsleaderboard --region=eu --latest
@@ -0,0 +1,34 @@
name: CD Europe Ratings Leaderboards
on:
schedule:
- cron: "0 23 * * *"
jobs:
host:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Use node
uses: actions/setup-node@v3
with:
node-version: 18
cache: "npm"
- name: Install Yarn
run: npm install --location=global yarn
- name: Use node_modules cache
uses: actions/cache@v3
with:
path: "**/node_modules"
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- name: Install dependencies
run: yarn
- name: Generate leaderboard
run: GH_TOKEN=${{ secrets.GH_TOKEN }} yarn publish:ratingsleaderboard --region=eu
@@ -1,4 +1,4 @@
name: Host
name: CD Host
on:
schedule:
@@ -12,15 +12,12 @@ concurrency:
jobs:
host:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x]
steps:
- name: Use node
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
node-version: 18
- name: Download release assets
run: |
@@ -0,0 +1,34 @@
name: CD North America Ratings Latest
on:
schedule:
- cron: '0 0 * * *'
jobs:
host:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Use node
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- name: Install Yarn
run: npm install --location=global yarn
- name: Use node_modules cache
uses: actions/cache@v3
with:
path: '**/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- name: Install dependencies
run: yarn
- name: Generate leaderboard
run: GH_TOKEN=${{ secrets.GH_TOKEN }} yarn publish:ratingsleaderboard --region=na --latest
@@ -0,0 +1,34 @@
name: CD North America Ratings Leaderboards
on:
schedule:
- cron: '0 5 * * *'
jobs:
host:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Use node
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- name: Install Yarn
run: npm install --location=global yarn
- name: Use node_modules cache
uses: actions/cache@v3
with:
path: '**/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- name: Install dependencies
run: yarn
- name: Generate leaderboard
run: GH_TOKEN=${{ secrets.GH_TOKEN }} yarn publish:ratingsleaderboard --region=na
+2 -6
View File
@@ -11,18 +11,14 @@ jobs:
name: Build
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x]
steps:
- name: Get code
- name: Checkout
uses: actions/checkout@v3
- name: Use node
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
node-version: 18
cache: 'npm'
- name: Install Yarn
+1 -1
View File
@@ -5,8 +5,8 @@ package-lock.json
/playwright-report/
/playwright/.cache/
dist/
dist.*
yarn-error.log
dist.zip
commands.txt
/test.*
/docs/.vitepress/dist
+7
View File
@@ -10,10 +10,13 @@
"fullstats",
"fuzzysort",
"kranvagn",
"Leaderboard",
"leaderboards",
"outfile",
"ownedtanks",
"playerachievements",
"playerinfo",
"ratingsleaderboard",
"Resvg",
"roboto",
"searchclans",
@@ -23,15 +26,19 @@
"Smri",
"startcase",
"statsfull",
"Stringifying",
"strvk",
"subnation",
"tankaverages",
"tankopedia",
"tankstats",
"tresabhi",
"Unicum",
"Unregistering",
"vickers",
"vitepress",
"wargaming",
"webp",
"winrate"
],
"deno.enable": false,
+7
View File
@@ -0,0 +1,7 @@
# Blitzkrieg 1.10.1
This is a bit of an experiment.
## Technical Changes
- Every midnight in the Central North American Time, a permanent record of the entire ratings leaderboard will be stored for North America
+10
View File
@@ -0,0 +1,10 @@
# Blitzkrieg 1.10.2
The experiment in Blitzkrieg `1.10.1` was a bit of a failure so here are a few changes.
## Fixes
- North American leaderboards are now saved in the `na` directory instead of `asia`
- Dependencies are now installed before the script is ran
- Added leaderboards for all regions
- All regions now have their own separate cron timings
+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
+9 -4
View File
@@ -1,26 +1,29 @@
{
"name": "blitzkrieg",
"version": "1.10.0",
"version": "1.10.2",
"description": "",
"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": "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": "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",
@@ -28,11 +31,13 @@
"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": {
+14 -16
View File
@@ -1,29 +1,29 @@
import { build } from 'esbuild';
import { BuildOptions, build } from 'esbuild';
import { copyFile, mkdir, rm } from 'fs/promises';
import { argv } from 'process';
const isProd = !argv.includes('--dev');
const buildAll = argv.includes('--build=all');
const buildBot = argv.includes('--build=bot') | buildAll;
const buildServer = argv.includes('--build=server') | buildAll;
const buildBot = argv.includes('--build=bot') || buildAll;
const buildServer = argv.includes('--build=server') || buildAll;
if (isProd) {
// only remove in production to ensure no time is wasted in dev
console.log('🟡 Removing dist...');
console.log('Removing dist...');
await rm('dist', { recursive: true, force: true });
console.log('✅ Dist removed');
console.log('Dist removed');
console.log('🟡 Creating dist...');
console.log('Creating dist...');
await mkdir('dist');
console.log('✅ Dist created');
console.log('Dist created');
console.log('🟡 Copying package.json...');
console.log('Copying package.json...');
copyFile('package.dist.json', 'dist/package.json').then(() =>
console.log('✅ package.json copied'),
console.log('package.json copied'),
);
}
const commonOptions = {
const commonOptions: BuildOptions = {
platform: 'node',
loader: {
'.node': 'copy',
@@ -40,25 +40,23 @@ const commonOptions = {
};
if (buildBot) {
console.log('🟡 Building bot...');
console.log('Building bot...');
build({
...commonOptions,
entryPoints: ['src/bot.ts'],
outfile: 'dist/bot.cjs',
}).then(() => {
console.log('✅ Bot built');
});
}).then(() => console.log('Bot built'));
}
if (buildServer) {
console.log('🟡 Building server...');
console.log('Building server...');
build({
...commonOptions,
entryPoints: ['src/server.ts'],
outfile: 'dist/server.cjs',
}).then(() => {
console.log('✅ Server built');
console.log('Server built');
});
}
+137
View File
@@ -0,0 +1,137 @@
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';
/*
* Central North American Time (UTC-5): use 0 5 * * *.
* Central European Time (UTC+1): use 0 23 * * *.
* Central Asia Standard Time (UTC+7): use 0 17 * * *.
*
* 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];
if (!server) throw new Error('Region parameter not specified');
const NEIGHBORS = 1000;
const PLAYERS = await fetch(
`https://${server}.wotblitz.com/en/api/rating-leaderboards/season/`,
)
.then((response) => response.json() as Promise<RatingsInfo>)
.then((data) => data.count);
const players: Record<number, RatingsPlayer> = {};
const leaderboard: BlitzkriegRatingsLeaderboard = [];
let registered = 0;
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() as Promise<RatingsNeighbors>);
const firstPlayer = neighbors[0];
const lastPlayer = neighbors[neighbors.length - 1];
const branches: Promise<void>[] = [];
if (!players[firstPlayer.number])
branches.push(branchFromPlayer(firstPlayer.spa_id));
if (!players[lastPlayer.number])
branches.push(branchFromPlayer(lastPlayer.spa_id));
neighbors.forEach((neighbor) => {
if (players[neighbor.number]) return;
players[neighbor.number] = neighbor;
registered++;
});
console.log(
`Registered ${registered} / ${PLAYERS} (${(
(registered / PLAYERS) *
100
).toFixed(2)}%)`,
);
await Promise.all(branches);
}
async function branchFromLeague(id: number) {
const { result } = await fetch(
`https://${server}.wotblitz.com/en/api/rating-leaderboards/league/${id}/top/`,
).then((response) => response.json() as Promise<{ result: RatingsPlayer[] }>);
const firstPlayer = result[0];
const lastPlayer = result[result.length - 1];
await Promise.all([
branchFromPlayer(firstPlayer.spa_id),
branchFromPlayer(lastPlayer.spa_id),
]);
}
console.log('Branching from leagues...');
await Promise.all([
branchFromLeague(0),
branchFromLeague(1),
branchFromLeague(2),
branchFromLeague(3),
branchFromLeague(4),
]);
console.log('Converting to an array...');
for (let index = 0; index < PLAYERS; index++) {
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);
if (publish) {
const octokit = new Octokit({ auth: env.GH_TOKEN });
const info = await fetch(
`https://${server}.wotblitz.com/en/api/rating-leaderboards/season/`,
).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' : 'midnight'
}.json`;
const infoJSON = JSON.stringify(info);
console.log(`Publishing to season ${info.current_season}...`);
commitMultipleFiles(
octokit,
'tresabhi',
'blitzkrieg-db',
'main',
new Date().toString(),
[
{ path: infoPath, content: infoJSON },
{ path: leaderboardPath, content: leaderboardJSON },
],
);
} else {
console.log('Writing to disk...');
await rm('dist.json', { force: true });
await writeFile('dist.json', leaderboardJSON);
}
+70
View File
@@ -0,0 +1,70 @@
import { Octokit } from '@octokit/rest';
export default async function commitMultipleFiles(
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({
owner,
repo,
ref: `heads/${branch}`,
});
const latestCommitSha = refData.object.sha;
// Get the tree SHA for the latest commit
const { data: commitData } = await octokit.git.getCommit({
owner,
repo,
commit_sha: latestCommitSha,
});
const treeSha = commitData.tree.sha;
// Create blobs for each file
const blobs = await Promise.all(
changes.map(async (change) => {
const { data: blobData } = await octokit.git.createBlob({
owner,
repo,
content: change.content,
encoding: 'utf-8',
});
return {
sha: blobData.sha,
path: change.path,
mode: '100644',
type: 'blob',
} as const;
}),
);
// Create a new tree with the new blobs
const { data: treeData } = await octokit.git.createTree({
owner,
repo,
base_tree: treeSha,
tree: blobs,
});
// Create a new commit with the new tree
const { data: newCommitData } = await octokit.git.createCommit({
owner,
repo,
message,
tree: treeData.sha,
parents: [latestCommitSha],
});
// Update the branch reference to point to the new commit
await octokit.git.updateRef({
owner,
repo,
ref: `heads/${branch}`,
sha: newCommitData.sha,
});
}
+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 { BLITZ_SERVERS } from '../constants/servers';
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()} • ${
BLITZ_SERVERS[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 { BLITZ_SERVERS } from '../constants/servers';
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()} • ${
BLITZ_SERVERS[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 -7
View File
@@ -1,11 +1,11 @@
import { SlashCommandBuilder } from 'discord.js';
import markdownEscape from 'markdown-escape';
import { BLITZ_SERVERS, BlitzServer } from '../constants/servers';
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,15 +28,15 @@ export const searchClansCommand: CommandRegistry = {
),
async handler(interaction) {
const server = interaction.options.getString('server') as BlitzServer;
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 ${BLITZ_SERVERS[server]}`,
`Clan search for "${markdownEscape(clan)}" in ${REGION_NAMES[server]}`,
clanList.length === 0
? 'No clans found.'
: `\`\`\`\n${clanList
+9 -9
View File
@@ -1,12 +1,12 @@
import { SlashCommandBuilder } from 'discord.js';
import markdownEscape from 'markdown-escape';
import { BLITZ_SERVERS, BlitzServer } from '../constants/servers';
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,20 +31,20 @@ export const searchPlayersCommand: CommandRegistry = {
),
async handler(interaction) {
const server = interaction.options.getString('server') as BlitzServer;
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 ${BLITZ_SERVERS[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';
+14
View File
@@ -0,0 +1,14 @@
export type Region = 'com' | 'eu' | 'asia';
export type RegionSubdomain = 'na' | 'eu' | 'asia';
export const REGION_NAMES: Record<Region, string> = {
com: 'North America',
eu: 'Europe',
asia: 'Asia',
};
export const REGION_SUBDOMAIN_NAMES: Record<RegionSubdomain, string> = {
na: 'North America',
eu: 'Europe',
asia: 'Asia',
};
-7
View File
@@ -1,7 +0,0 @@
export type BlitzServer = 'com' | 'eu' | 'asia';
export const BLITZ_SERVERS: Record<BlitzServer, string> = {
com: 'North America',
eu: 'Europe',
asia: 'Asia',
};
+2 -2
View File
@@ -1,4 +1,4 @@
import { BlitzServer } from '../../constants/servers';
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 BlitzServer, 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 { BLITZ_SERVERS, BlitzServer } from '../../constants/servers';
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: BlitzServer, 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 ${BLITZ_SERVERS[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 { BlitzServer } from '../../constants/servers';
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 BlitzServer,
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 BlitzServer,
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 BlitzServer,
server: 'asia' as Region,
})),
),
])
+8 -8
View File
@@ -1,6 +1,6 @@
import { BlitzServer } from '../../constants/servers';
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 BlitzServer,
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 BlitzServer,
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 BlitzServer,
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 { BlitzServer } from '../../constants/servers';
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: BlitzServer,
server: Region,
id: number,
start: number,
end: number,
+2 -2
View File
@@ -1,9 +1,9 @@
import { BlitzServer } from '../../constants/servers';
import { Region } from '../../constants/regions';
import { Period } from '../discord/addPeriodSubCommands';
import getTimeDaysAgo from './getTimeDaysAgo';
export default function getPeriodStart(
server: BlitzServer,
server: Region,
period: Exclude<Period, 'custom'>,
) {
if (period === 'career') {
+5 -5
View File
@@ -1,4 +1,4 @@
import { BlitzServer } from '../../constants/servers';
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: BlitzServer;
region: Region;
}
export type PlayerHistoriesRaw = PlayerHistoryRaw[];
export default async function getPlayerHistories(
server: BlitzServer,
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 { BlitzServer } from '../../constants/servers';
import { Region } from '../../constants/regions';
import {
GetHistoriesOptions,
Histories,
@@ -32,7 +32,7 @@ export const getTankHistoriesDefaultOptions: GetTankHistoriesOptions = {
};
export default async function getTankHistories(
server: BlitzServer,
server: Region,
id: number,
options?: Partial<GetTankHistoriesOptions>,
) {
+3 -3
View File
@@ -1,12 +1,12 @@
import { BlitzServer } from '../../constants/servers';
import { Region } from '../../constants/regions';
export const TIME_ZONE_MAPPINGS: Record<BlitzServer, 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: BlitzServer, 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 { BLITZ_SERVERS } from '../../constants/servers';
export default function addServerChoices(option: SlashCommandStringOption) {
return option
.setName('server')
.setDescription('The Blitz server you are in')
.addChoices(
{ name: BLITZ_SERVERS.com, value: 'com' },
{ name: BLITZ_SERVERS.eu, value: 'eu' },
{ name: BLITZ_SERVERS.asia, value: 'asia' },
)
.setRequired(true);
}
+2 -2
View File
@@ -3,7 +3,7 @@ import {
AutocompleteInteraction,
CacheType,
} from 'discord.js';
import { BLITZ_SERVERS } from '../../constants/servers';
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} (${BLITZ_SERVERS[player.server]})`,
name: `${player.name} (${REGION_NAMES[player.server]})`,
value: `${player.server}/${player.clan_id}`,
}) satisfies ApplicationCommandOptionChoiceData<string>,
)
+2 -2
View File
@@ -3,7 +3,7 @@ import {
AutocompleteInteraction,
CacheType,
} from 'discord.js';
import { BLITZ_SERVERS } from '../../constants/servers';
import { REGION_NAMES } from '../../constants/regions';
import listPlayers from '../blitz/listPlayers';
export default async function autocompleteUsername(
@@ -19,7 +19,7 @@ export default async function autocompleteUsername(
? players.map(
(player) =>
({
name: `${player.nickname} (${BLITZ_SERVERS[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 { BlitzServer } from '../../constants/servers';
import { Region } from '../../constants/regions';
import resolvePeriodFromURL from '../express/resolvePeriodFromURL';
export default function resolvePeriodFromButton(
server: BlitzServer,
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 { BlitzServer } from '../../constants/servers';
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: BlitzServer,
server: Region,
interaction: ChatInputCommandInteraction<CacheType>,
) {
let statsName: string;
+4 -4
View File
@@ -1,5 +1,5 @@
import { CacheType, ChatInputCommandInteraction } from 'discord.js';
import { BlitzServer } from '../../constants/servers';
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: BlitzServer;
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 BlitzServer,
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 { BlitzServer } from '../../constants/servers';
import { Region } from '../../constants/regions';
import resolvePeriodFromURL from './resolvePeriodFromURL';
export default function resolvePeriodFromRequest(
server: BlitzServer,
server: Region,
request: Request,
) {
return resolvePeriodFromURL(server, `${CYCLIC_API}${request.path}`);
+2 -2
View File
@@ -1,4 +1,4 @@
import { BlitzServer } from '../../constants/servers';
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: BlitzServer,
server: Region,
urlString: string,
) {
let statsName: string;
+2 -2
View File
@@ -1,4 +1,4 @@
import { BlitzServer } from '../../constants/servers';
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 BlitzServer,
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 { BLITZ_SERVERS } from '../constants/servers';
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()} • ${
BLITZ_SERVERS[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 { BLITZ_SERVERS } from '../constants/servers';
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()} • ${
BLITZ_SERVERS[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 { BLITZ_SERVERS } from '../constants/servers';
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()} • ${
BLITZ_SERVERS[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 { BLITZ_SERVERS } from '../constants/servers';
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()} • ${
BLITZ_SERVERS[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"]
}
+224
View File
@@ -445,6 +445,103 @@
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
"@octokit/auth-token@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-4.0.0.tgz#40d203ea827b9f17f42a29c6afb93b7745ef80c7"
integrity sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==
"@octokit/core@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@octokit/core/-/core-5.0.0.tgz#0fc2b6eb88437e5c1d69f756a5dcee7472d2b2dd"
integrity sha512-YbAtMWIrbZ9FCXbLwT9wWB8TyLjq9mxpKdgB3dUNxQcIVTf9hJ70gRPwAcqGZdY6WdJPZ0I7jLaaNDCiloGN2A==
dependencies:
"@octokit/auth-token" "^4.0.0"
"@octokit/graphql" "^7.0.0"
"@octokit/request" "^8.0.2"
"@octokit/request-error" "^5.0.0"
"@octokit/types" "^11.0.0"
before-after-hook "^2.2.0"
universal-user-agent "^6.0.0"
"@octokit/endpoint@^9.0.0":
version "9.0.0"
resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-9.0.0.tgz#c5ce19c74b999b85af9a8a189275c80faa3e90fd"
integrity sha512-szrQhiqJ88gghWY2Htt8MqUDO6++E/EIXqJ2ZEp5ma3uGS46o7LZAzSLt49myB7rT+Hfw5Y6gO3LmOxGzHijAQ==
dependencies:
"@octokit/types" "^11.0.0"
is-plain-object "^5.0.0"
universal-user-agent "^6.0.0"
"@octokit/graphql@^7.0.0":
version "7.0.1"
resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-7.0.1.tgz#f2291620e17cdaa8115f8d0cdfc0644789ec2db2"
integrity sha512-T5S3oZ1JOE58gom6MIcrgwZXzTaxRnxBso58xhozxHpOqSTgDS6YNeEUvZ/kRvXgPrRz/KHnZhtb7jUMRi9E6w==
dependencies:
"@octokit/request" "^8.0.1"
"@octokit/types" "^11.0.0"
universal-user-agent "^6.0.0"
"@octokit/openapi-types@^18.0.0":
version "18.0.0"
resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-18.0.0.tgz#f43d765b3c7533fd6fb88f3f25df079c24fccf69"
integrity sha512-V8GImKs3TeQRxRtXFpG2wl19V7444NIOTDF24AWuIbmNaNYOQMWRbjcGDXV5B+0n887fgDcuMNOmlul+k+oJtw==
"@octokit/plugin-paginate-rest@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-8.0.0.tgz#417b5367da2ba3c2d255a59b87c1cc608228ec38"
integrity sha512-2xZ+baZWUg+qudVXnnvXz7qfrTmDeYPCzangBVq/1gXxii/OiS//4shJp9dnCCvj1x+JAm9ji1Egwm1BA47lPQ==
dependencies:
"@octokit/types" "^11.0.0"
"@octokit/plugin-request-log@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-4.0.0.tgz#260fa6970aa97bbcbd91f99f3cd812e2b285c9f1"
integrity sha512-2uJI1COtYCq8Z4yNSnM231TgH50bRkheQ9+aH8TnZanB6QilOnx8RMD2qsnamSOXtDj0ilxvevf5fGsBhBBzKA==
"@octokit/plugin-rest-endpoint-methods@^9.0.0":
version "9.0.0"
resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-9.0.0.tgz#e15d54540893202da107305ded2bfd21ce6f769d"
integrity sha512-KquMF/VB1IkKNiVnzJKspY5mFgGyLd7HzdJfVEGTJFzqu9BRFNWt+nwTCMuUiWc72gLQhRWYubTwOkQj+w/1PA==
dependencies:
"@octokit/types" "^11.0.0"
"@octokit/request-error@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-5.0.0.tgz#060c5770833f9d563ad9a49fec6650c41584bc40"
integrity sha512-1ue0DH0Lif5iEqT52+Rf/hf0RmGO9NWFjrzmrkArpG9trFfDM/efx00BJHdLGuro4BR/gECxCU2Twf5OKrRFsQ==
dependencies:
"@octokit/types" "^11.0.0"
deprecation "^2.0.0"
once "^1.4.0"
"@octokit/request@^8.0.1", "@octokit/request@^8.0.2":
version "8.1.0"
resolved "https://registry.yarnpkg.com/@octokit/request/-/request-8.1.0.tgz#3b0fd15faeda1545024af09c31ac2689f4d65e7d"
integrity sha512-0gg/NwewU0iXctYBale0VVcCPqOtoW5lsog8cNBJgzV/CyTHa2gicUBOlNnzOk6pJkuwXI34qkq+uRm40PmD4A==
dependencies:
"@octokit/endpoint" "^9.0.0"
"@octokit/request-error" "^5.0.0"
"@octokit/types" "^11.1.0"
is-plain-object "^5.0.0"
universal-user-agent "^6.0.0"
"@octokit/rest@^20.0.1":
version "20.0.1"
resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-20.0.1.tgz#b8ee194b7cf89772d1e3fea3209f741c76a5efd3"
integrity sha512-wROV21RwHQIMNb2Dgd4+pY+dVy1Dwmp85pBrgr6YRRDYRBu9Gb+D73f4Bl2EukZSj5hInq2Tui9o7gAQpc2k2Q==
dependencies:
"@octokit/core" "^5.0.0"
"@octokit/plugin-paginate-rest" "^8.0.0"
"@octokit/plugin-request-log" "^4.0.0"
"@octokit/plugin-rest-endpoint-methods" "^9.0.0"
"@octokit/types@^11.0.0", "@octokit/types@^11.1.0":
version "11.1.0"
resolved "https://registry.yarnpkg.com/@octokit/types/-/types-11.1.0.tgz#9e5db741d582b05718a4d91bac8cc987def235ea"
integrity sha512-Fz0+7GyLm/bHt8fwEqgvRBWwIV1S6wRRyq+V6exRKLVWaKGsuy6H9QFYeBVDV7rK6fO3XwHgQOPxv+cLj2zpXQ==
dependencies:
"@octokit/openapi-types" "^18.0.0"
"@radix-ui/colors@^0.1.8":
version "0.1.9"
resolved "https://registry.yarnpkg.com/@radix-ui/colors/-/colors-0.1.9.tgz#aad732ecc4ce1018adcb3aedd3ce3c573c2256cc"
@@ -836,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"
@@ -871,6 +973,11 @@ [email protected]:
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978"
integrity sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==
before-after-hook@^2.2.0:
version "2.2.3"
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c"
integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==
bepaint@^0.1.0-beta.3:
version "0.1.0-beta.3"
resolved "https://registry.yarnpkg.com/bepaint/-/bepaint-0.1.0-beta.3.tgz#fe8f0d20e35da075a6ed69f42d61156d985da584"
@@ -911,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"
@@ -987,11 +1099,23 @@ [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"
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
deprecation@^2.0.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919"
integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==
[email protected]:
version "1.2.0"
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
@@ -1145,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"
@@ -1274,6 +1403,11 @@ [email protected]:
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
is-plain-object@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
"js-tokens@^3.0.0 || ^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -1363,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"
@@ -1395,6 +1544,13 @@ [email protected]:
dependencies:
ee-first "1.1.1"
once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
dependencies:
wrappy "1"
pako@^0.2.5:
version "0.2.9"
resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
@@ -1418,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"
@@ -1428,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"
@@ -1442,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"
@@ -1512,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"
@@ -1671,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"
@@ -1686,6 +1877,11 @@ unicode-trie@^2.0.0:
pako "^0.2.5"
tiny-inflate "^1.0.0"
universal-user-agent@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee"
integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==
[email protected], unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
@@ -1706,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"
@@ -1767,6 +1986,11 @@ vue@^3.3.4:
"@vue/server-renderer" "3.3.4"
"@vue/shared" "3.3.4"
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
ws@^8.13.0:
version "8.13.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0"