From 638402589f739d3503dcf8e64c5746758d80d8fb Mon Sep 17 00:00:00 2001 From: tresabhi Date: Sat, 15 Jul 2023 01:21:59 +0000 Subject: [PATCH 01/11] feat: switched scripts to typescript --- package.json | 13 +-- scripts/{build.js => build.ts} | 6 +- ...derboard.js => buildRatingsLeaderboard.ts} | 29 ++++-- ...ultipleFiles.js => commitMultipleFiles.ts} | 17 ++-- yarn.lock | 90 +++++++++++++++++++ 5 files changed, 132 insertions(+), 23 deletions(-) rename scripts/{build.js => build.ts} (91%) rename scripts/{generateRatingsLeaderboard.js => buildRatingsLeaderboard.ts} (86%) rename scripts/{commitMultipleFiles.js => commitMultipleFiles.ts} (87%) diff --git a/package.json b/package.json index 510aef5a9..e91f5f688 100644 --- a/package.json +++ b/package.json @@ -5,24 +5,25 @@ "main": "src/server.ts", "type": "module", "scripts": { - "build:bot": "node scripts/build --build=bot", - "build:server": "node scripts/build --build=server", + "build:bot": "vite-node --script scripts/build --build=bot", + "build:server": "vite-node --script scripts/build --build=server", "build:docs": "vitepress build docs", - "build:ratingsleaderboard": "node scripts/generateRatingsLeaderboard", - "build": "node scripts/build --build=all", + "build:ratingsleaderboard": "vite-node --script scripts/buildRatingsLeaderboard", + "build": "vite-node --script scripts/build --build=all", "dev:bot": "yarn build:bot --dev && yarn start:bot", "dev:server": "yarn build:server --dev && yarn start:server", "dev:docs": "vitepress dev docs", "start:bot": "node --enable-source-maps --trace-warnings dist/bot.cjs", "start:server": "node --enable-source-maps --trace-warnings dist/server.cjs", "host:docs": "vitepress preview docs", - "publish:ratingsleaderboard": "node scripts/generateRatingsLeaderboard --publish", + "publish:ratingsleaderboard": "vite-node --script scripts/buildRatingsLeaderboard --publish", "zip": "zip -r dist.zip dist", "lint": "npx tsc", "format": "prettier --write \"**/*.@(json|js|jsx|css|md|ts|tsx|html|xml|yml|gltf)\"" }, "license": "MIT", "dependencies": { + "@octokit/rest": "^20.0.1", "@radix-ui/colors": "^0.1.8", "@resvg/resvg-js": "^2.4.1", "bepaint": "^0.1.0-beta.3", @@ -35,10 +36,10 @@ "markdown-escape": "^2.0.0", "react": "^18.2.0", "satori": "^0.9.1", + "vite-node": "^0.33.0", "vitepress-plugin-auto-sidebar": "^1.1.0" }, "devDependencies": { - "@octokit/rest": "^20.0.1", "@total-typescript/ts-reset": "^0.4.2", "@types/escape-html": "^1.0.2", "@types/express": "^4.17.17", diff --git a/scripts/build.js b/scripts/build.ts similarity index 91% rename from scripts/build.js rename to scripts/build.ts index 59e812ddb..18c450987 100644 --- a/scripts/build.js +++ b/scripts/build.ts @@ -1,4 +1,4 @@ -import { build } from 'esbuild'; +import { BuildOptions, build } from 'esbuild'; import { copyFile, mkdir, rm } from 'fs/promises'; import { argv } from 'process'; @@ -7,6 +7,8 @@ const buildAll = argv.includes('--build=all'); const buildBot = argv.includes('--build=bot') || buildAll; const buildServer = argv.includes('--build=server') || buildAll; +console.log(buildAll, buildBot, buildServer); + if (isProd) { // only remove in production to ensure no time is wasted in dev console.log('Removing dist...'); @@ -23,7 +25,7 @@ if (isProd) { ); } -const commonOptions = { +const commonOptions: BuildOptions = { platform: 'node', loader: { '.node': 'copy', diff --git a/scripts/generateRatingsLeaderboard.js b/scripts/buildRatingsLeaderboard.ts similarity index 86% rename from scripts/generateRatingsLeaderboard.js rename to scripts/buildRatingsLeaderboard.ts index b39f732dd..835c52f65 100644 --- a/scripts/generateRatingsLeaderboard.js +++ b/scripts/buildRatingsLeaderboard.ts @@ -3,6 +3,19 @@ import { rm, writeFile } from 'fs/promises'; import { argv, env } from 'process'; import commitMultipleFiles from './commitMultipleFiles.js'; +interface RatingsSeasonInfo { + count: number; +} + +interface RatingsPlayer { + number: number; + spa_id: number; +} + +interface RatingsNeighbors { + neighbors: RatingsPlayer[]; +} + /* * Central North American Time (UTC-5): use 0 5 * * *. * Central European Time (UTC+1): use 0 23 * * *. @@ -23,22 +36,22 @@ const NEIGHBORS = 1000; const PLAYERS = await fetch( `https://${server}.wotblitz.com/en/api/rating-leaderboards/season/`, ) - .then((response) => response.json()) + .then((response) => response.json() as Promise) .then((data) => data.count); -const players = {}; -const leaderboard = []; +const players: Record = {}; +const leaderboard: RatingsPlayer[] = []; let registered = 0; -async function branchFromPlayer(id) { +async function branchFromPlayer(id: number) { const { neighbors } = await fetch( `https://${server}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${NEIGHBORS}`, - ).then((response) => response.json()); + ).then((response) => response.json() as Promise); const firstPlayer = neighbors[0]; const lastPlayer = neighbors[neighbors.length - 1]; - const branches = []; + const branches: Promise[] = []; if (!players[firstPlayer.number]) branches.push(branchFromPlayer(firstPlayer.spa_id)); if (!players[lastPlayer.number]) @@ -61,7 +74,7 @@ async function branchFromPlayer(id) { await Promise.all(branches); } -async function branchFromLeague(id) { +async function branchFromLeague(id: number) { const { result } = await fetch( `https://${server}.wotblitz.com/en/api/rating-leaderboards/league/${id}/top/`, ).then((response) => response.json()); @@ -75,7 +88,7 @@ async function branchFromLeague(id) { ]); } -console.log('Branching form leagues...'); +console.log('Branching from leagues...'); await Promise.all([ branchFromLeague(0), branchFromLeague(1), diff --git a/scripts/commitMultipleFiles.js b/scripts/commitMultipleFiles.ts similarity index 87% rename from scripts/commitMultipleFiles.js rename to scripts/commitMultipleFiles.ts index 58d6a6cdd..f00083554 100644 --- a/scripts/commitMultipleFiles.js +++ b/scripts/commitMultipleFiles.ts @@ -1,10 +1,12 @@ +import { Octokit } from '@octokit/rest'; + export default async function commitMultipleFiles( - octokit, - owner, - repo, - branch, - message, - changes, + octokit: Octokit, + owner: string, + repo: string, + branch: string, + message: string, + changes: { path: string; content: string }[], ) { // Get the latest commit SHA on the specified branch const { data: refData } = await octokit.git.getRef({ @@ -31,12 +33,13 @@ export default async function commitMultipleFiles( content: change.content, encoding: 'utf-8', }); + return { sha: blobData.sha, path: change.path, mode: '100644', type: 'blob', - }; + } as const; }), ); diff --git a/yarn.lock b/yarn.lock index 9199e75d1..69a544844 100644 --- a/yarn.lock +++ b/yarn.lock @@ -933,6 +933,11 @@ accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" +acorn@^8.9.0: + version "8.10.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" + integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== + algoliasearch@^4.0.0: version "4.18.0" resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.18.0.tgz#1183ad0384a5b2c14f381c3a361da611acc8edb3" @@ -1013,6 +1018,11 @@ bytes@3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== +cac@^6.7.14: + version "6.7.14" + resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + call-bind@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -1089,6 +1099,13 @@ debug@2.6.9: 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" + depd@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -1475,11 +1492,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" + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + ms@2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" @@ -1537,6 +1569,11 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== +pathe@^1.1.0, pathe@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.1.tgz#1dd31d382b974ba69809adc9a7a347e65d84829a" + integrity sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q== + peek-readable@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-5.0.0.tgz#7ead2aff25dc40458c60347ea76cfdfd63efdfec" @@ -1547,6 +1584,15 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +pkg-types@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.0.3.tgz#988b42ab19254c01614d13f4f65a2cfc7880f868" + integrity sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A== + dependencies: + jsonc-parser "^3.2.0" + mlly "^1.2.0" + pathe "^1.1.0" + postcss-value-parser@^4.0.2, postcss-value-parser@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" @@ -1561,6 +1607,15 @@ postcss@^8.1.10, postcss@^8.4.23: picocolors "^1.0.0" source-map-js "^1.0.2" +postcss@^8.4.25: + version "8.4.26" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.26.tgz#1bc62ab19f8e1e5463d98cf74af39702a00a9e94" + integrity sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw== + dependencies: + nanoid "^3.3.6" + picocolors "^1.0.0" + source-map-js "^1.0.2" + preact@^10.0.0: version "10.15.1" resolved "https://registry.yarnpkg.com/preact/-/preact-10.15.1.tgz#a1de60c9fc0c79a522d969c65dcaddc5d994eede" @@ -1631,6 +1686,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" + safe-buffer@5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -1790,6 +1852,11 @@ typescript@^5.0.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== +ufo@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.1.2.tgz#d0d9e0fa09dece0c31ffd57bd363f030a35cfe76" + integrity sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ== + undici@^5.22.0: version "5.22.1" resolved "https://registry.yarnpkg.com/undici/-/undici-5.22.1.tgz#877d512effef2ac8be65e695f3586922e1a57d7b" @@ -1830,6 +1897,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" From 9419aec1161ebe45120440bc9fb86001b96c1e8c Mon Sep 17 00:00:00 2001 From: tresabhi Date: Sat, 15 Jul 2023 18:07:14 +0000 Subject: [PATCH 02/11] fix: removed "Europe" from /verify example --- src/events/guildMemberAdd.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/events/guildMemberAdd.ts b/src/events/guildMemberAdd.ts index 4e57ef9d9..a8182b3a4 100644 --- a/src/events/guildMemberAdd.ts +++ b/src/events/guildMemberAdd.ts @@ -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\``, ), ], }); From aa6011b18d1955d2ca0fc1a9d245362df73f44ae Mon Sep 17 00:00:00 2001 From: tresabhi Date: Sat, 15 Jul 2023 18:44:20 +0000 Subject: [PATCH 03/11] feat: added basic infrastructure for ratings command --- scripts/build.ts | 2 - src/commands/eligible.ts | 2 +- src/commands/evolution.tsx | 6 +- src/commands/fullStats.tsx | 6 +- src/commands/inactive.tsx | 4 +- src/commands/ownedTanks.tsx | 6 +- src/commands/playerAchievements.ts | 2 +- src/commands/playerInfo.ts | 2 +- src/commands/ratings.ts | 115 ++++++++++++++++++ src/commands/searchClans.ts | 8 +- src/commands/searchPlayers.ts | 8 +- src/commands/stats.tsx | 8 +- src/commands/today.tsx | 4 +- src/commands/verify.ts | 2 +- src/constants/regions.ts | 4 +- src/core/blitz/getBlitzClan.ts | 4 +- src/core/blitz/getTankStats.ts | 6 +- src/core/blitz/listClans.ts | 8 +- src/core/blitz/listPlayers.ts | 8 +- src/core/blitz/regionToRegionSubdomain.ts | 7 ++ src/core/blitzstars/getDiffedTankStats.ts | 4 +- src/core/blitzstars/getPeriodStart.ts | 4 +- src/core/blitzstars/getPlayerHistories.ts | 6 +- src/core/blitzstars/getTankHistories.ts | 4 +- src/core/blitzstars/getTimeDaysAgo.ts | 6 +- src/core/discord/addServerChoices.ts | 8 +- src/core/discord/autocompleteClan.ts | 4 +- src/core/discord/autocompleteUsername.ts | 6 +- src/core/discord/resolvePeriodFromButton.ts | 4 +- src/core/discord/resolvePeriodFromCommand.ts | 4 +- src/core/discord/resolvePlayerFromCommand.ts | 8 +- src/core/express/resolvePeriodFromRequest.ts | 4 +- src/core/express/resolvePeriodFromURL.ts | 4 +- src/core/express/resolvePlayerFromURL.ts | 4 +- .../handlers/chatInputCommand.ts | 14 +++ src/events/interactionCreate/index.ts | 3 + src/renderers/evolution.tsx | 6 +- src/renderers/fullStats.tsx | 6 +- src/renderers/stats.tsx | 6 +- src/renderers/today.tsx | 6 +- 40 files changed, 227 insertions(+), 96 deletions(-) create mode 100644 src/commands/ratings.ts create mode 100644 src/core/blitz/regionToRegionSubdomain.ts diff --git a/scripts/build.ts b/scripts/build.ts index 18c450987..522841e3f 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -7,8 +7,6 @@ const buildAll = argv.includes('--build=all'); const buildBot = argv.includes('--build=bot') || buildAll; const buildServer = argv.includes('--build=server') || buildAll; -console.log(buildAll, buildBot, buildServer); - if (isProd) { // only remove in production to ensure no time is wasted in dev console.log('Removing dist...'); diff --git a/src/commands/eligible.ts b/src/commands/eligible.ts index 43c6991d6..2ded85c56 100644 --- a/src/commands/eligible.ts +++ b/src/commands/eligible.ts @@ -49,7 +49,7 @@ 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( `https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`, ); diff --git a/src/commands/evolution.tsx b/src/commands/evolution.tsx index e3b62fbe4..4754f635a 100644 --- a/src/commands/evolution.tsx +++ b/src/commands/evolution.tsx @@ -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( - `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=${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', diff --git a/src/commands/fullStats.tsx b/src/commands/fullStats.tsx index adbd15159..d5c9a7e67 100644 --- a/src/commands/fullStats.tsx +++ b/src/commands/fullStats.tsx @@ -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( - `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=${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', ), ]; diff --git a/src/commands/inactive.tsx b/src/commands/inactive.tsx index 33a6ef8b6..e059125fe 100644 --- a/src/commands/inactive.tsx +++ b/src/commands/inactive.tsx @@ -4,7 +4,7 @@ import NoData, { NoDataType } from '../components/NoData'; import PoweredBy, { PoweredByType } from '../components/PoweredBy'; import TitleBar from '../components/TitleBar'; import Wrapper from '../components/Wrapper'; -import { REGION_DOMAIN_NAMES } from '../constants/regions'; +import { REGION_NAMES } from '../constants/regions'; import getBlitzClan from '../core/blitz/getBlitzClan'; import getWargamingResponse from '../core/blitz/getWargamingResponse'; import addClanChoices from '../core/discord/addClanChoices'; @@ -72,7 +72,7 @@ export const inactiveCommand: CommandRegistry = { nameDiscriminator="(Inactivity)" image={`https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clanData.emblem_set_id}.png`} description={`${threshold}+ Days • ${new Date().toDateString()} • ${ - REGION_DOMAIN_NAMES[server] + REGION_NAMES[server] }`} /> diff --git a/src/commands/ownedTanks.tsx b/src/commands/ownedTanks.tsx index 0a4cbcfde..637739d6b 100644 --- a/src/commands/ownedTanks.tsx +++ b/src/commands/ownedTanks.tsx @@ -4,7 +4,7 @@ import PoweredBy, { PoweredByType } from '../components/PoweredBy'; import * as Tanks from '../components/Tanks'; import TitleBar from '../components/TitleBar'; import Wrapper from '../components/Wrapper'; -import { REGION_DOMAIN_NAMES } from '../constants/regions'; +import { REGION_NAMES } from '../constants/regions'; import getTankStats from '../core/blitz/getTankStats'; import getTreeType from '../core/blitz/getTreeType'; import getWargamingResponse from '../core/blitz/getWargamingResponse'; @@ -74,7 +74,7 @@ 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( `https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`, ); @@ -105,7 +105,7 @@ export const ownedTanksCommand: CommandRegistry = { : undefined } description={`Owned tanks • ${new Date().toDateString()} • ${ - REGION_DOMAIN_NAMES[server] + REGION_NAMES[server] }`} /> diff --git a/src/commands/playerAchievements.ts b/src/commands/playerAchievements.ts index 3301e5542..505cf18de 100644 --- a/src/commands/playerAchievements.ts +++ b/src/commands/playerAchievements.ts @@ -36,7 +36,7 @@ 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( `https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`, ); diff --git a/src/commands/playerInfo.ts b/src/commands/playerInfo.ts index d6661373f..ad7876c66 100644 --- a/src/commands/playerInfo.ts +++ b/src/commands/playerInfo.ts @@ -22,7 +22,7 @@ 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( `https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`, ); diff --git a/src/commands/ratings.ts b/src/commands/ratings.ts new file mode 100644 index 000000000..de0702faa --- /dev/null +++ b/src/commands/ratings.ts @@ -0,0 +1,115 @@ +import { SlashCommandBuilder, SlashCommandSubcommandBuilder } from 'discord.js'; +import regionToRegionSubdomain from '../core/blitz/regionToRegionSubdomain'; +import addUsernameChoices from '../core/discord/addUsernameChoices'; +import autocompleteUsername from '../core/discord/autocompleteUsername'; +import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; +import { CommandRegistry } from '../events/interactionCreate'; + +export interface LeaguePlayer { + 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: LeaguePlayer[]; +} + +const LEAGUE_INDEXES = ['diamond', 'platinum', 'gold', 'silver', 'bronze']; + +function optionalParameters(option: SlashCommandSubcommandBuilder) { + return option + .addIntegerOption((option) => + option + .setName('limit') + .setDescription('How many rows to display (default: 16)') + .setRequired(true) + .setMinValue(5) + .setMaxValue(30), + ) + .addStringOption(addUsernameChoices); +} + +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') ?? 16; + const subcommand = interaction.options.getSubcommand(true); + const leagueIndex = LEAGUE_INDEXES.indexOf(subcommand); + const player = await resolvePlayerFromCommand(interaction); + const regionSubdomain = regionToRegionSubdomain(player.region); + const { result } = await fetch( + `https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/league/${leagueIndex}/top/`, + ).then((response) => response.json() as Promise); + + return `\`\`\`${result + .slice(0, limit) + .map( + (player) => + `${player.number}: ${player.nickname} [${player.clan_tag}]`, + ) + .join('\n')}\`\`\``; + } + + return []; + }, + + autocomplete: autocompleteUsername, +}; diff --git a/src/commands/searchClans.ts b/src/commands/searchClans.ts index f89ea8f78..a483d4510 100644 --- a/src/commands/searchClans.ts +++ b/src/commands/searchClans.ts @@ -1,6 +1,6 @@ import { SlashCommandBuilder } from 'discord.js'; import markdownEscape from 'markdown-escape'; -import { REGION_DOMAIN_NAMES, RegionDomain } from '../constants/regions'; +import { REGION_NAMES, Region } from '../constants/regions'; import getWargamingResponse from '../core/blitz/getWargamingResponse'; import addClanChoices from '../core/discord/addClanChoices'; import addServerChoices from '../core/discord/addServerChoices'; @@ -28,7 +28,7 @@ export const searchClansCommand: CommandRegistry = { ), async handler(interaction) { - const server = interaction.options.getString('server') as RegionDomain; + const server = interaction.options.getString('server') as Region; const clan = interaction.options.getString('clan')!; const limit = interaction.options.getInteger('limit') ?? 25; const clanList = await getWargamingResponse( @@ -36,9 +36,7 @@ export const searchClansCommand: CommandRegistry = { ); return embedInfo( - `Clan search for "${markdownEscape(clan)}" in ${ - REGION_DOMAIN_NAMES[server] - }`, + `Clan search for "${markdownEscape(clan)}" in ${REGION_NAMES[server]}`, clanList.length === 0 ? 'No clans found.' : `\`\`\`\n${clanList diff --git a/src/commands/searchPlayers.ts b/src/commands/searchPlayers.ts index 4b430aea4..7de26bc69 100644 --- a/src/commands/searchPlayers.ts +++ b/src/commands/searchPlayers.ts @@ -1,6 +1,6 @@ import { SlashCommandBuilder } from 'discord.js'; import markdownEscape from 'markdown-escape'; -import { REGION_DOMAIN_NAMES, RegionDomain } from '../constants/regions'; +import { REGION_NAMES, Region } from '../constants/regions'; import getWargamingResponse from '../core/blitz/getWargamingResponse'; import { usernamePattern } from '../core/blitz/listPlayers'; import addServerChoices from '../core/discord/addServerChoices'; @@ -31,7 +31,7 @@ export const searchPlayersCommand: CommandRegistry = { ), async handler(interaction) { - const server = interaction.options.getString('server') as RegionDomain; + const server = interaction.options.getString('server') as Region; const name = interaction.options.getString('username')!; const limit = interaction.options.getInteger('limit') ?? 25; const trimmedSearch = name.trim(); @@ -44,9 +44,7 @@ export const searchPlayersCommand: CommandRegistry = { : []; return embedInfo( - `Player search for "${markdownEscape(name)}" in ${ - REGION_DOMAIN_NAMES[server] - }`, + `Player search for "${markdownEscape(name)}" in ${REGION_NAMES[server]}`, `\`\`\`${ players.length === 0 ? 'No players found.' diff --git a/src/commands/stats.tsx b/src/commands/stats.tsx index 4f5f5721f..2b392fb7f 100644 --- a/src/commands/stats.tsx +++ b/src/commands/stats.tsx @@ -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( - `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=${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', ), ]; diff --git a/src/commands/today.tsx b/src/commands/today.tsx index 7026723fd..48c01c68c 100644 --- a/src/commands/today.tsx +++ b/src/commands/today.tsx @@ -37,7 +37,7 @@ export const todayCommand: CommandRegistry = { const limit = interaction.options.getInteger('limit') ?? Infinity; const { nickname } = ( await getWargamingResponse( - `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=${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', ), ]; diff --git a/src/commands/verify.ts b/src/commands/verify.ts index 66c78d35e..de53dc2a7 100644 --- a/src/commands/verify.ts +++ b/src/commands/verify.ts @@ -24,7 +24,7 @@ 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( `https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`, ); diff --git a/src/constants/regions.ts b/src/constants/regions.ts index 59602911c..43118a767 100644 --- a/src/constants/regions.ts +++ b/src/constants/regions.ts @@ -1,7 +1,7 @@ -export type RegionDomain = 'com' | 'eu' | 'asia'; +export type Region = 'com' | 'eu' | 'asia'; export type RegionSubdomain = 'na' | 'eu' | 'asia'; -export const REGION_DOMAIN_NAMES: Record = { +export const REGION_NAMES: Record = { com: 'North America', eu: 'Europe', asia: 'Asia', diff --git a/src/core/blitz/getBlitzClan.ts b/src/core/blitz/getBlitzClan.ts index be7281baa..68f32222b 100644 --- a/src/core/blitz/getBlitzClan.ts +++ b/src/core/blitz/getBlitzClan.ts @@ -1,4 +1,4 @@ -import { RegionDomain } from '../../constants/regions'; +import { Region } from '../../constants/regions'; import { serverAndIdPattern } from '../discord/resolvePlayerFromCommand'; import throwError from '../node/throwError'; import listClans from './listClans'; @@ -6,7 +6,7 @@ import listClans from './listClans'; export default async function getBlitzClan(clan: string) { if (serverAndIdPattern.test(clan)) { const [server, accountId] = clan.split('/'); - return { server: server as RegionDomain, id: Number(accountId) }; + return { server: server as Region, id: Number(accountId) }; } else { const accounts = await listClans(clan); diff --git a/src/core/blitz/getTankStats.ts b/src/core/blitz/getTankStats.ts index 09b2b58c4..d12be6448 100644 --- a/src/core/blitz/getTankStats.ts +++ b/src/core/blitz/getTankStats.ts @@ -1,10 +1,10 @@ -import { REGION_DOMAIN_NAMES, RegionDomain } from '../../constants/regions'; +import { REGION_NAMES, Region } from '../../constants/regions'; import { TanksStats } from '../../types/tanksStats'; import { WARGAMING_APPLICATION_ID } from '../node/arguments'; import throwError from '../node/throwError'; import getWargamingResponse from './getWargamingResponse'; -export default async function getTankStats(server: RegionDomain, id: number) { +export default async function getTankStats(server: Region, id: number) { const tankStats = await getWargamingResponse( `https://api.wotblitz.${server}/wotb/tanks/stats/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`, ); @@ -12,7 +12,7 @@ export default async function getTankStats(server: RegionDomain, id: number) { if (tankStats[id] === null) { throw throwError( 'No tank stats available', - `Wargaming says there is no tank stats for this account. This account may not have any battles/tanks or exist in the ${REGION_DOMAIN_NAMES[server]} server.`, + `Wargaming says there is no tank stats for this account. This account may not have any battles/tanks or exist in the ${REGION_NAMES[server]} server.`, ); } diff --git a/src/core/blitz/listClans.ts b/src/core/blitz/listClans.ts index 5ae7c3f25..69e84ec29 100644 --- a/src/core/blitz/listClans.ts +++ b/src/core/blitz/listClans.ts @@ -1,4 +1,4 @@ -import { RegionDomain } from '../../constants/regions'; +import { Region } from '../../constants/regions'; import { Clan, ClanList } from '../../types/clanList'; import { WARGAMING_APPLICATION_ID } from '../node/arguments'; import getWargamingResponse from './getWargamingResponse'; @@ -21,7 +21,7 @@ export default async function listClans(search: string, limit = 9) { value && value.map((account) => ({ ...account, - server: 'com' as RegionDomain, + server: 'com' as Region, })), ), getWargamingResponse( @@ -31,7 +31,7 @@ export default async function listClans(search: string, limit = 9) { value && value.map((account) => ({ ...account, - server: 'eu' as RegionDomain, + server: 'eu' as Region, })), ), getWargamingResponse( @@ -41,7 +41,7 @@ export default async function listClans(search: string, limit = 9) { value && value.map((account) => ({ ...account, - server: 'asia' as RegionDomain, + server: 'asia' as Region, })), ), ]) diff --git a/src/core/blitz/listPlayers.ts b/src/core/blitz/listPlayers.ts index 2571c7f14..6ecc9a3fb 100644 --- a/src/core/blitz/listPlayers.ts +++ b/src/core/blitz/listPlayers.ts @@ -1,4 +1,4 @@ -import { RegionDomain } from '../../constants/regions'; +import { Region } from '../../constants/regions'; import { Account, AccountList } from '../../types/accountList'; import { WARGAMING_APPLICATION_ID } from '../node/arguments'; import getWargamingResponse from './getWargamingResponse'; @@ -25,7 +25,7 @@ export default async function listPlayers(search: string, limit = 9) { value && value.map((account) => ({ ...account, - server: 'com' as RegionDomain, + server: 'com' as Region, })), ), getWargamingResponse( @@ -35,7 +35,7 @@ export default async function listPlayers(search: string, limit = 9) { value && value.map((account) => ({ ...account, - server: 'eu' as RegionDomain, + server: 'eu' as Region, })), ), getWargamingResponse( @@ -45,7 +45,7 @@ export default async function listPlayers(search: string, limit = 9) { value && value.map((account) => ({ ...account, - server: 'asia' as RegionDomain, + server: 'asia' as Region, })), ), ]) diff --git a/src/core/blitz/regionToRegionSubdomain.ts b/src/core/blitz/regionToRegionSubdomain.ts new file mode 100644 index 000000000..fe44d9dcd --- /dev/null +++ b/src/core/blitz/regionToRegionSubdomain.ts @@ -0,0 +1,7 @@ +import { Region, RegionSubdomain } from '../../constants/regions'; + +export default function regionToRegionSubdomain( + regionDomain: Region, +): RegionSubdomain { + return regionDomain === 'com' ? 'na' : regionDomain; +} diff --git a/src/core/blitzstars/getDiffedTankStats.ts b/src/core/blitzstars/getDiffedTankStats.ts index 2b1e3f269..68bb54618 100644 --- a/src/core/blitzstars/getDiffedTankStats.ts +++ b/src/core/blitzstars/getDiffedTankStats.ts @@ -1,4 +1,4 @@ -import { RegionDomain } from '../../constants/regions'; +import { Region } from '../../constants/regions'; import { AllStats } from '../../types/accountInfo'; import getTankHistories, { TankHistories, @@ -35,7 +35,7 @@ export const emptyTankHistoryNode: TankHistoryRaw = { }; export default async function getDiffedTankStats( - server: RegionDomain, + server: Region, id: number, start: number, end: number, diff --git a/src/core/blitzstars/getPeriodStart.ts b/src/core/blitzstars/getPeriodStart.ts index 0faae2fba..1ad1ff478 100644 --- a/src/core/blitzstars/getPeriodStart.ts +++ b/src/core/blitzstars/getPeriodStart.ts @@ -1,9 +1,9 @@ -import { RegionDomain } from '../../constants/regions'; +import { Region } from '../../constants/regions'; import { Period } from '../discord/addPeriodSubCommands'; import getTimeDaysAgo from './getTimeDaysAgo'; export default function getPeriodStart( - server: RegionDomain, + server: Region, period: Exclude, ) { if (period === 'career') { diff --git a/src/core/blitzstars/getPlayerHistories.ts b/src/core/blitzstars/getPlayerHistories.ts index 895ba7edf..ad9cf6e46 100644 --- a/src/core/blitzstars/getPlayerHistories.ts +++ b/src/core/blitzstars/getPlayerHistories.ts @@ -1,4 +1,4 @@ -import { RegionDomain } from '../../constants/regions'; +import { Region } from '../../constants/regions'; import { AccountInfo, AllStats } from '../../types/accountInfo'; import { GetHistoriesOptions, @@ -25,13 +25,13 @@ export interface PlayerHistoryRaw { account_id: number; [key: string]: number; }; - region: RegionDomain; + region: Region; } export type PlayerHistoriesRaw = PlayerHistoryRaw[]; export default async function getPlayerHistories( - server: RegionDomain, + server: Region, id: number, options?: Partial, ) { diff --git a/src/core/blitzstars/getTankHistories.ts b/src/core/blitzstars/getTankHistories.ts index 96f9b8e03..3b5ed9d14 100644 --- a/src/core/blitzstars/getTankHistories.ts +++ b/src/core/blitzstars/getTankHistories.ts @@ -1,4 +1,4 @@ -import { RegionDomain } from '../../constants/regions'; +import { Region } from '../../constants/regions'; import { GetHistoriesOptions, Histories, @@ -32,7 +32,7 @@ export const getTankHistoriesDefaultOptions: GetTankHistoriesOptions = { }; export default async function getTankHistories( - server: RegionDomain, + server: Region, id: number, options?: Partial, ) { diff --git a/src/core/blitzstars/getTimeDaysAgo.ts b/src/core/blitzstars/getTimeDaysAgo.ts index 421fec09f..652b28fea 100644 --- a/src/core/blitzstars/getTimeDaysAgo.ts +++ b/src/core/blitzstars/getTimeDaysAgo.ts @@ -1,12 +1,12 @@ -import { RegionDomain } from '../../constants/regions'; +import { Region } from '../../constants/regions'; -export const TIME_ZONE_MAPPINGS: Record = { +export const TIME_ZONE_MAPPINGS: Record = { com: -5, // Central North American Time eu: +1, // Central European Time asia: +7, // Central Asia Standard Time }; -export default function getTimeDaysAgo(server: RegionDomain, daysAgo: number) { +export default function getTimeDaysAgo(server: Region, daysAgo: number) { const now = new Date(); if (daysAgo === 0) now.getTime() / 1000; diff --git a/src/core/discord/addServerChoices.ts b/src/core/discord/addServerChoices.ts index e702c85d7..83e35c4f7 100644 --- a/src/core/discord/addServerChoices.ts +++ b/src/core/discord/addServerChoices.ts @@ -1,14 +1,14 @@ import { SlashCommandStringOption } from 'discord.js'; -import { REGION_DOMAIN_NAMES } from '../../constants/regions'; +import { REGION_NAMES } from '../../constants/regions'; export default function addServerChoices(option: SlashCommandStringOption) { return option .setName('server') .setDescription('The Blitz server you are in') .addChoices( - { name: REGION_DOMAIN_NAMES.com, value: 'com' }, - { name: REGION_DOMAIN_NAMES.eu, value: 'eu' }, - { name: REGION_DOMAIN_NAMES.asia, value: 'asia' }, + { name: REGION_NAMES.com, value: 'com' }, + { name: REGION_NAMES.eu, value: 'eu' }, + { name: REGION_NAMES.asia, value: 'asia' }, ) .setRequired(true); } diff --git a/src/core/discord/autocompleteClan.ts b/src/core/discord/autocompleteClan.ts index a06497e6a..6f0efb31e 100644 --- a/src/core/discord/autocompleteClan.ts +++ b/src/core/discord/autocompleteClan.ts @@ -3,7 +3,7 @@ import { AutocompleteInteraction, CacheType, } from 'discord.js'; -import { REGION_DOMAIN_NAMES } from '../../constants/regions'; +import { REGION_NAMES } from '../../constants/regions'; import listClans from '../blitz/listClans'; export default async function autocompleteClan( @@ -18,7 +18,7 @@ export default async function autocompleteClan( ? players.map( (player) => ({ - name: `${player.name} (${REGION_DOMAIN_NAMES[player.server]})`, + name: `${player.name} (${REGION_NAMES[player.server]})`, value: `${player.server}/${player.clan_id}`, }) satisfies ApplicationCommandOptionChoiceData, ) diff --git a/src/core/discord/autocompleteUsername.ts b/src/core/discord/autocompleteUsername.ts index 4664baf10..f52557095 100644 --- a/src/core/discord/autocompleteUsername.ts +++ b/src/core/discord/autocompleteUsername.ts @@ -3,7 +3,7 @@ import { AutocompleteInteraction, CacheType, } from 'discord.js'; -import { REGION_DOMAIN_NAMES } from '../../constants/regions'; +import { REGION_NAMES } from '../../constants/regions'; import listPlayers from '../blitz/listPlayers'; export default async function autocompleteUsername( @@ -19,9 +19,7 @@ export default async function autocompleteUsername( ? players.map( (player) => ({ - name: `${player.nickname} (${ - REGION_DOMAIN_NAMES[player.server] - })`, + name: `${player.nickname} (${REGION_NAMES[player.server]})`, value: `${player.server}/${player.account_id}`, }) satisfies ApplicationCommandOptionChoiceData, ) diff --git a/src/core/discord/resolvePeriodFromButton.ts b/src/core/discord/resolvePeriodFromButton.ts index 1e7236857..3bdd6d103 100644 --- a/src/core/discord/resolvePeriodFromButton.ts +++ b/src/core/discord/resolvePeriodFromButton.ts @@ -1,10 +1,10 @@ import { ButtonInteraction, CacheType } from 'discord.js'; import { CYCLIC_API } from '../../constants/cyclic'; -import { RegionDomain } from '../../constants/regions'; +import { Region } from '../../constants/regions'; import resolvePeriodFromURL from '../express/resolvePeriodFromURL'; export default function resolvePeriodFromButton( - server: RegionDomain, + server: Region, interaction: ButtonInteraction, ) { return resolvePeriodFromURL(server, `${CYCLIC_API}/${interaction.customId}`); diff --git a/src/core/discord/resolvePeriodFromCommand.ts b/src/core/discord/resolvePeriodFromCommand.ts index c5fe1259c..95210715d 100644 --- a/src/core/discord/resolvePeriodFromCommand.ts +++ b/src/core/discord/resolvePeriodFromCommand.ts @@ -1,5 +1,5 @@ import { CacheType, ChatInputCommandInteraction } from 'discord.js'; -import { RegionDomain } from '../../constants/regions'; +import { Region } from '../../constants/regions'; import getPeriodNow from '../blitzstars/getPeriodNow'; import getPeriodStart from '../blitzstars/getPeriodStart'; import getTimeDaysAgo from '../blitzstars/getTimeDaysAgo'; @@ -31,7 +31,7 @@ export interface ResolvedPeriod { } export default function resolvePeriodFromCommand( - server: RegionDomain, + server: Region, interaction: ChatInputCommandInteraction, ) { let statsName: string; diff --git a/src/core/discord/resolvePlayerFromCommand.ts b/src/core/discord/resolvePlayerFromCommand.ts index e9762d01a..1452e6bc8 100644 --- a/src/core/discord/resolvePlayerFromCommand.ts +++ b/src/core/discord/resolvePlayerFromCommand.ts @@ -1,5 +1,5 @@ import { CacheType, ChatInputCommandInteraction } from 'discord.js'; -import { RegionDomain } from '../../constants/regions'; +import { Region } from '../../constants/regions'; import listPlayers, { usernamePatternWithoutPosition, } from '../blitz/listPlayers'; @@ -8,7 +8,7 @@ import throwError from '../node/throwError'; export const serverAndIdPattern = /(com|eu|asia)\/[0-9]+/; export interface ResolvedPlayer { - server: RegionDomain; + region: Region; id: number; } @@ -31,7 +31,7 @@ export default async function resolvePlayerFromCommand( if (serverAndIdPattern.test(username)) { const [server, accountId] = username.split('/'); return { - server: server as RegionDomain, + region: server as Region, id: Number(accountId), } satisfies ResolvedPlayer; } else { @@ -39,7 +39,7 @@ export default async function resolvePlayerFromCommand( if (accounts[0]) { return { - server: accounts[0].server, + region: accounts[0].server, id: accounts[0].account_id, } satisfies ResolvedPlayer; } else { diff --git a/src/core/express/resolvePeriodFromRequest.ts b/src/core/express/resolvePeriodFromRequest.ts index 7ff160402..bf88d3ea7 100644 --- a/src/core/express/resolvePeriodFromRequest.ts +++ b/src/core/express/resolvePeriodFromRequest.ts @@ -1,10 +1,10 @@ import { Request } from 'express'; import { CYCLIC_API } from '../../constants/cyclic'; -import { RegionDomain } from '../../constants/regions'; +import { Region } from '../../constants/regions'; import resolvePeriodFromURL from './resolvePeriodFromURL'; export default function resolvePeriodFromRequest( - server: RegionDomain, + server: Region, request: Request, ) { return resolvePeriodFromURL(server, `${CYCLIC_API}${request.path}`); diff --git a/src/core/express/resolvePeriodFromURL.ts b/src/core/express/resolvePeriodFromURL.ts index 464e1c904..035f68916 100644 --- a/src/core/express/resolvePeriodFromURL.ts +++ b/src/core/express/resolvePeriodFromURL.ts @@ -1,4 +1,4 @@ -import { RegionDomain } from '../../constants/regions'; +import { Region } from '../../constants/regions'; import getPeriodNow from '../blitzstars/getPeriodNow'; import getPeriodStart from '../blitzstars/getPeriodStart'; import getTimeDaysAgo from '../blitzstars/getTimeDaysAgo'; @@ -10,7 +10,7 @@ import { } from '../discord/resolvePeriodFromCommand'; export default function resolvePeriodFromURL( - server: RegionDomain, + server: Region, urlString: string, ) { let statsName: string; diff --git a/src/core/express/resolvePlayerFromURL.ts b/src/core/express/resolvePlayerFromURL.ts index 112e2abb5..421b05cdc 100644 --- a/src/core/express/resolvePlayerFromURL.ts +++ b/src/core/express/resolvePlayerFromURL.ts @@ -1,4 +1,4 @@ -import { RegionDomain } from '../../constants/regions'; +import { Region } from '../../constants/regions'; import { ResolvedPlayer } from '../discord/resolvePlayerFromCommand'; export default function resolvePlayerFromURL(urlString: string) { @@ -6,6 +6,6 @@ export default function resolvePlayerFromURL(urlString: string) { return { id: parseInt(url.searchParams.get('id')!), - server: url.searchParams.get('server') as RegionDomain, + region: url.searchParams.get('server') as Region, } satisfies ResolvedPlayer; } diff --git a/src/events/interactionCreate/handlers/chatInputCommand.ts b/src/events/interactionCreate/handlers/chatInputCommand.ts index f7b1a855d..18b81dcb6 100644 --- a/src/events/interactionCreate/handlers/chatInputCommand.ts +++ b/src/events/interactionCreate/handlers/chatInputCommand.ts @@ -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; diff --git a/src/events/interactionCreate/index.ts b/src/events/interactionCreate/index.ts index af2ba8d27..ef951d1d9 100644 --- a/src/events/interactionCreate/index.ts +++ b/src/events/interactionCreate/index.ts @@ -23,6 +23,7 @@ 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'; @@ -56,6 +57,7 @@ export interface Registry { export interface CommandRegistry extends Registry { inPublic: boolean; + inPreview?: boolean; handlesInteraction?: HandlesInteraction; command: | SlashCommandBuilder @@ -88,6 +90,7 @@ export const COMMANDS_RAW: CommandRegistry[] = [ pingCommand, evolutionCommand, statsCommand, + ratingsCommand, ]; export const commands: Record = COMMANDS_RAW.reduce( (accumulator, registry) => { diff --git a/src/renderers/evolution.tsx b/src/renderers/evolution.tsx index 5300a2619..c6996cbcb 100644 --- a/src/renderers/evolution.tsx +++ b/src/renderers/evolution.tsx @@ -3,7 +3,7 @@ import NoData, { NoDataType } from '../components/NoData'; import PoweredBy, { PoweredByType } from '../components/PoweredBy'; import TitleBar from '../components/TitleBar'; import Wrapper from '../components/Wrapper'; -import { REGION_DOMAIN_NAMES } from '../constants/regions'; +import { REGION_NAMES } from '../constants/regions'; import getWargamingResponse from '../core/blitz/getWargamingResponse'; import resolveTankName from '../core/blitz/resolveTankName'; import { tankopedia } from '../core/blitz/tankopedia'; @@ -20,7 +20,7 @@ import { StatType } from './fullStats'; export default async function evolution( type: Type, { start, end, evolutionName }: ResolvedPeriod, - { server, id }: ResolvedPlayer, + { region: server, id }: ResolvedPlayer, tankId: Type extends 'tank' ? number : null, ) { let nameDiscriminator: string | undefined; @@ -89,7 +89,7 @@ export default async function evolution( nameDiscriminator={nameDiscriminator} image={image} description={`${evolutionName} • ${new Date().toDateString()} • ${ - REGION_DOMAIN_NAMES[server] + REGION_NAMES[server] }`} /> diff --git a/src/renderers/fullStats.tsx b/src/renderers/fullStats.tsx index 16957b2bd..21e798190 100644 --- a/src/renderers/fullStats.tsx +++ b/src/renderers/fullStats.tsx @@ -4,7 +4,7 @@ import PoweredBy, { PoweredByType } from '../components/PoweredBy'; import TierWeights, { TierWeightsRecord } from '../components/TierWeights'; import TitleBar from '../components/TitleBar'; import Wrapper from '../components/Wrapper'; -import { REGION_DOMAIN_NAMES } from '../constants/regions'; +import { REGION_NAMES } from '../constants/regions'; import calculateWN8 from '../core/blitz/calculateWN8'; import getWargamingResponse from '../core/blitz/getWargamingResponse'; import resolveTankName from '../core/blitz/resolveTankName'; @@ -28,7 +28,7 @@ export type StatType = 'player' | 'tank'; export default async function fullStats( type: Type, { start, end, statsName }: ResolvedPeriod, - { server, id }: ResolvedPlayer, + { region: server, id }: ResolvedPlayer, tankId: Type extends 'tank' ? number : null, ) { let nameDiscriminator: string | undefined; @@ -143,7 +143,7 @@ export default async function fullStats( nameDiscriminator={nameDiscriminator} image={image} description={`${statsName} • ${new Date().toDateString()} • ${ - REGION_DOMAIN_NAMES[server] + REGION_NAMES[server] }`} /> diff --git a/src/renderers/stats.tsx b/src/renderers/stats.tsx index ed8755d0d..b95db2528 100644 --- a/src/renderers/stats.tsx +++ b/src/renderers/stats.tsx @@ -4,7 +4,7 @@ import PoweredBy, { PoweredByType } from '../components/PoweredBy'; import { TierWeightsRecord } from '../components/TierWeights'; import TitleBar from '../components/TitleBar'; import Wrapper from '../components/Wrapper'; -import { REGION_DOMAIN_NAMES } from '../constants/regions'; +import { REGION_NAMES } from '../constants/regions'; import calculateWN8 from '../core/blitz/calculateWN8'; import getWargamingResponse from '../core/blitz/getWargamingResponse'; import resolveTankName from '../core/blitz/resolveTankName'; @@ -29,7 +29,7 @@ export type StatType = 'player' | 'tank'; export default async function stats( type: Type, { start, end, statsName }: ResolvedPeriod, - { server, id }: ResolvedPlayer, + { region: server, id }: ResolvedPlayer, tankId: Type extends 'tank' ? number : null, naked = false, ) { @@ -164,7 +164,7 @@ export default async function stats( nameDiscriminator={nameDiscriminator} image={image} description={`${statsName} • ${new Date().toDateString()} • ${ - REGION_DOMAIN_NAMES[server] + REGION_NAMES[server] }`} /> diff --git a/src/renderers/today.tsx b/src/renderers/today.tsx index 9928062c3..dd0716979 100644 --- a/src/renderers/today.tsx +++ b/src/renderers/today.tsx @@ -3,7 +3,7 @@ import NoData, { NoDataType } from '../components/NoData'; import PoweredBy, { PoweredByType } from '../components/PoweredBy'; import TitleBar from '../components/TitleBar'; import Wrapper from '../components/Wrapper'; -import { REGION_DOMAIN_NAMES } from '../constants/regions'; +import { REGION_NAMES } from '../constants/regions'; import calculateWN8 from '../core/blitz/calculateWN8'; import getTankStats from '../core/blitz/getTankStats'; import getWargamingResponse from '../core/blitz/getWargamingResponse'; @@ -21,7 +21,7 @@ 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, ) { @@ -162,7 +162,7 @@ export default async function today( : undefined } description={`Today's breakdown • ${new Date().toDateString()} • ${ - REGION_DOMAIN_NAMES[server] + REGION_NAMES[server] }`} /> From 9a2e11a2a40abef143e13606347ad55fe1ec4e66 Mon Sep 17 00:00:00 2001 From: tresabhi Date: Sun, 16 Jul 2023 17:45:29 +0000 Subject: [PATCH 04/11] feat: added basic rendering --- .vscode/settings.json | 1 + src/commands/{ratings.ts => ratings.tsx} | 120 ++++++++++++++++-- src/components/Leaderboard/components/Gap.tsx | 0 .../Leaderboard/components/Item.tsx | 104 +++++++++++++++ .../Leaderboard/components/Root.tsx | 13 ++ src/components/Leaderboard/index.ts | 3 + tsconfig.json | 5 +- 7 files changed, 233 insertions(+), 13 deletions(-) rename src/commands/{ratings.ts => ratings.tsx} (52%) create mode 100644 src/components/Leaderboard/components/Gap.tsx create mode 100644 src/components/Leaderboard/components/Item.tsx create mode 100644 src/components/Leaderboard/components/Root.tsx create mode 100644 src/components/Leaderboard/index.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index dee1574f6..4cc04f9af 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -28,6 +28,7 @@ "statsfull", "Stringifying", "strvk", + "subnation", "tankaverages", "tankopedia", "tankstats", diff --git a/src/commands/ratings.ts b/src/commands/ratings.tsx similarity index 52% rename from src/commands/ratings.ts rename to src/commands/ratings.tsx index de0702faa..51e8cc1e1 100644 --- a/src/commands/ratings.ts +++ b/src/commands/ratings.tsx @@ -1,9 +1,16 @@ import { SlashCommandBuilder, SlashCommandSubcommandBuilder } from 'discord.js'; +import * as Leaderboard from '../components/Leaderboard'; +import TitleBar from '../components/TitleBar'; +import Wrapper from '../components/Wrapper'; +import { REGION_NAMES } from '../constants/regions'; +import getWargamingResponse from '../core/blitz/getWargamingResponse'; import regionToRegionSubdomain from '../core/blitz/regionToRegionSubdomain'; 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 { CommandRegistry } from '../events/interactionCreate'; +import { AccountInfo } from '../types/accountInfo'; export interface LeaguePlayer { spa_id: number; @@ -23,7 +30,76 @@ export interface LeagueTop { result: LeaguePlayer[]; } -const LEAGUE_INDEXES = ['diamond', 'platinum', 'gold', 'silver', 'bronze']; +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[]; +} + +console.log('Caching ratings info...'); +const ratingsLeagueInfo = fetch( + 'https://na.wotblitz.com/en/api/rating-leaderboards/season/', +) + .then((response) => response.json() as Promise) + .then((info) => { + console.log('Cached ratings info'); + return info; + }); function optionalParameters(option: SlashCommandSubcommandBuilder) { return option @@ -31,13 +107,15 @@ function optionalParameters(option: SlashCommandSubcommandBuilder) { option .setName('limit') .setDescription('How many rows to display (default: 16)') - .setRequired(true) + .setRequired(false) .setMinValue(5) .setMaxValue(30), ) .addStringOption(addUsernameChoices); } +const LEAGUE_INDEXES = ['diamond', 'platinum', 'gold', 'silver', 'bronze']; + export const ratingsCommand: CommandRegistry = { inProduction: true, inDevelopment: true, @@ -98,14 +176,38 @@ export const ratingsCommand: CommandRegistry = { const { result } = await fetch( `https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/league/${leagueIndex}/top/`, ).then((response) => response.json() as Promise); - - return `\`\`\`${result + const { nickname } = await getWargamingResponse( + `https://api.wotblitz.${player.region}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${player.id}`, + ).then((accounts) => accounts[player.id]); + const leagueInfo = (await ratingsLeagueInfo).leagues[leagueIndex]; + const items = result .slice(0, limit) - .map( - (player) => - `${player.number}: ${player.nickname} [${player.clan_tag}]`, - ) - .join('\n')}\`\`\``; + .map((player) => ( + + )); + + return ( + + + + {items} + + ); } return []; diff --git a/src/components/Leaderboard/components/Gap.tsx b/src/components/Leaderboard/components/Gap.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/src/components/Leaderboard/components/Item.tsx b/src/components/Leaderboard/components/Item.tsx new file mode 100644 index 000000000..dbc3ac48e --- /dev/null +++ b/src/components/Leaderboard/components/Item.tsx @@ -0,0 +1,104 @@ +import { RatingsReward } from '../../../commands/ratings'; +import { theme } from '../../../stitches.config'; + +export interface ItemProps { + position: number; + deltaPosition?: number; + points: number; + deltaPoints?: number; + reward: RatingsReward; + nickname: string; + clan?: string; +} + +export function Item({ + position, + deltaPosition, + points, + deltaPoints, + reward, + nickname, + clan, +}: ItemProps) { + return ( +
+ + {position}. + + +
+ + {nickname} + + {clan && ( + + [{clan}] + + )} +
+ + {deltaPosition !== undefined && deltaPosition !== 0 && ( +
+ 0 + ? 'https://i.imgur.com/qbjiXa1.png' + : 'https://i.imgur.com/3uyNhun.png' + } + /> + + {deltaPosition} + +
+ )} +
+ ); +} diff --git a/src/components/Leaderboard/components/Root.tsx b/src/components/Leaderboard/components/Root.tsx new file mode 100644 index 000000000..9f40d82f9 --- /dev/null +++ b/src/components/Leaderboard/components/Root.tsx @@ -0,0 +1,13 @@ +import { ReactNode } from 'react'; + +export interface RootProps { + children: ReactNode; +} + +export function Root({ children }: RootProps) { + return ( +
+ {children} +
+ ); +} diff --git a/src/components/Leaderboard/index.ts b/src/components/Leaderboard/index.ts new file mode 100644 index 000000000..4334194b1 --- /dev/null +++ b/src/components/Leaderboard/index.ts @@ -0,0 +1,3 @@ +export * from './components/Gap'; +export * from './components/Item'; +export * from './components/Root'; diff --git a/tsconfig.json b/tsconfig.json index 5f2ace03b..e6d1941ec 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,8 +11,5 @@ "resolveJsonModule": true, "jsx": "react-jsx" }, - "include": [ - "src/**/*.ts", - "src/components/AllStatsOverview/components/WN8SurroundingStat.tsx" - ] + "include": ["src/**/*.ts"] } From 631c051895c13b4f96d36148628b670aa5148845 Mon Sep 17 00:00:00 2001 From: tresabhi Date: Sun, 16 Jul 2023 17:51:01 +0000 Subject: [PATCH 05/11] feat: ditch histories --- scripts/buildRatingsLeaderboard.ts | 33 +++++++++++------------------- src/commands/ratings.tsx | 8 ++++++-- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/scripts/buildRatingsLeaderboard.ts b/scripts/buildRatingsLeaderboard.ts index 835c52f65..91d4cca9d 100644 --- a/scripts/buildRatingsLeaderboard.ts +++ b/scripts/buildRatingsLeaderboard.ts @@ -1,29 +1,21 @@ 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'; -interface RatingsSeasonInfo { - count: number; -} - -interface RatingsPlayer { - number: number; - spa_id: number; -} - -interface RatingsNeighbors { - neighbors: RatingsPlayer[]; -} - /* * 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 * * *. * - * :region/ratings/:season/info.json: season info like rewards, start & end time, etc. - * :region/ratings/:season/latest.json: the absolute latest leaderboard recorded at UTC+0 midnight - * :region/ratings/:season/leaderboards/:time.json: collection of all recorded leaderboards at local midnights + * na/ratings/49/info.json: season info like rewards, start & end time, etc. + * na/ratings/49/latest.json: the absolute latest leaderboard recorded at UTC+0 midnight + * na/ratings/49/midnight.json: last midnight's leaderboard relative to the region */ const publish = argv.includes('--publish'); @@ -36,7 +28,7 @@ const NEIGHBORS = 1000; const PLAYERS = await fetch( `https://${server}.wotblitz.com/en/api/rating-leaderboards/season/`, ) - .then((response) => response.json() as Promise) + .then((response) => response.json() as Promise) .then((data) => data.count); const players: Record = {}; @@ -111,13 +103,12 @@ if (publish) { const info = await fetch( `https://${server}.wotblitz.com/en/api/rating-leaderboards/season/`, ).then((response) => response.json()); - const time = Math.round(Date.now() / 1000); const normalizedServer = server === 'na' ? 'com' : server; const infoPath = `${normalizedServer}/ratings/${info.current_season}/info.json`; const leaderboardPath = `${normalizedServer}/ratings/${info.current_season}/${ - latest ? 'latest.json' : `leaderboards/${time}.json` - }`; + latest ? 'latest' : 'midnight' + }.json`; const infoJSON = JSON.stringify(info); console.log(`Publishing to season ${info.current_season}...`); @@ -126,7 +117,7 @@ if (publish) { 'tresabhi', 'blitzkrieg-db', 'main', - `${info.current_season}/${time}`, + new Date().toString(), [ { path: infoPath, content: infoJSON }, { path: leaderboardPath, content: leaderboardJSON }, diff --git a/src/commands/ratings.tsx b/src/commands/ratings.tsx index 51e8cc1e1..bd0f7d673 100644 --- a/src/commands/ratings.tsx +++ b/src/commands/ratings.tsx @@ -12,7 +12,7 @@ import { WARGAMING_APPLICATION_ID } from '../core/node/arguments'; import { CommandRegistry } from '../events/interactionCreate'; import { AccountInfo } from '../types/accountInfo'; -export interface LeaguePlayer { +export interface RatingsPlayer { spa_id: number; mmr: number; season_number: number; @@ -27,7 +27,7 @@ export interface LeaguePlayer { } export interface LeagueTop { - result: LeaguePlayer[]; + result: RatingsPlayer[]; } export type RatingsReward = { @@ -91,6 +91,10 @@ export interface RatingsInfo { leagues: RatingsLeague[]; } +export interface RatingsNeighbors { + neighbors: RatingsPlayer[]; +} + console.log('Caching ratings info...'); const ratingsLeagueInfo = fetch( 'https://na.wotblitz.com/en/api/rating-leaderboards/season/', From b4ae39a509adda18e8d4ec5fc35a25899b117fc4 Mon Sep 17 00:00:00 2001 From: tresabhi Date: Sun, 16 Jul 2023 17:56:37 +0000 Subject: [PATCH 06/11] feat: added custom lighter ratings leaderboard --- scripts/buildRatingsLeaderboard.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/scripts/buildRatingsLeaderboard.ts b/scripts/buildRatingsLeaderboard.ts index 91d4cca9d..3e8ee6c26 100644 --- a/scripts/buildRatingsLeaderboard.ts +++ b/scripts/buildRatingsLeaderboard.ts @@ -18,6 +18,8 @@ import commitMultipleFiles from './commitMultipleFiles.js'; * 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]; @@ -32,7 +34,7 @@ const PLAYERS = await fetch( .then((data) => data.count); const players: Record = {}; -const leaderboard: RatingsPlayer[] = []; +const leaderboard: BlitzkriegRatingsLeaderboard = []; let registered = 0; async function branchFromPlayer(id: number) { @@ -69,7 +71,7 @@ async function branchFromPlayer(id: number) { async function branchFromLeague(id: number) { const { result } = await fetch( `https://${server}.wotblitz.com/en/api/rating-leaderboards/league/${id}/top/`, - ).then((response) => response.json()); + ).then((response) => response.json() as Promise<{ result: RatingsPlayer[] }>); const firstPlayer = result[0]; const lastPlayer = result[result.length - 1]; @@ -91,8 +93,13 @@ await Promise.all([ console.log('Converting to an array...'); for (let index = 0; index < PLAYERS; index++) { - leaderboard[index] = players[index + 1]; - if (!leaderboard[index]) console.warn(`${index} had no player`); + const listing = players[index + 1]; + if (!listing) console.warn(`Position ${index + 1} had no player`); + + leaderboard[index] = { + id: listing.spa_id, + score: listing.score, + }; } const leaderboardJSON = JSON.stringify(leaderboard); @@ -102,7 +109,7 @@ if (publish) { const info = await fetch( `https://${server}.wotblitz.com/en/api/rating-leaderboards/season/`, - ).then((response) => response.json()); + ).then((response) => response.json() as Promise); const normalizedServer = server === 'na' ? 'com' : server; const infoPath = `${normalizedServer}/ratings/${info.current_season}/info.json`; From c72bfa9f7175a91e67cdc39adb30b60075eda325 Mon Sep 17 00:00:00 2001 From: tresabhi Date: Sun, 16 Jul 2023 18:15:34 +0000 Subject: [PATCH 07/11] feat: added rewards --- src/commands/ratings.tsx | 21 ++++++++---- .../Leaderboard/components/Item.tsx | 33 ++++++++++++++++++- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/commands/ratings.tsx b/src/commands/ratings.tsx index bd0f7d673..1b9959c63 100644 --- a/src/commands/ratings.tsx +++ b/src/commands/ratings.tsx @@ -96,7 +96,7 @@ export interface RatingsNeighbors { } console.log('Caching ratings info...'); -const ratingsLeagueInfo = fetch( +const ratingsInfo = fetch( 'https://na.wotblitz.com/en/api/rating-leaderboards/season/', ) .then((response) => response.json() as Promise) @@ -183,21 +183,28 @@ export const ratingsCommand: CommandRegistry = { const { nickname } = await getWargamingResponse( `https://api.wotblitz.${player.region}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${player.id}`, ).then((accounts) => accounts[player.id]); - const leagueInfo = (await ratingsLeagueInfo).leagues[leagueIndex]; - const items = result - .slice(0, limit) - .map((player) => ( + const awaitedInfo = await ratingsInfo; + const leagueInfo = awaitedInfo.leagues[leagueIndex]; + const items = result.slice(0, limit).map((player) => { + const reward = awaitedInfo.rewards.find( + (reward) => + player.number >= reward.from_position && + player.number <= reward.to_position, + ); + + return ( - )); + ); + }); return ( diff --git a/src/components/Leaderboard/components/Item.tsx b/src/components/Leaderboard/components/Item.tsx index dbc3ac48e..1dc9df991 100644 --- a/src/components/Leaderboard/components/Item.tsx +++ b/src/components/Leaderboard/components/Item.tsx @@ -6,7 +6,7 @@ export interface ItemProps { deltaPosition?: number; points: number; deltaPoints?: number; - reward: RatingsReward; + reward?: RatingsReward; nickname: string; clan?: string; } @@ -99,6 +99,37 @@ export function Item({ )} + +
+ {reward && ( + + )} + {reward?.type === 'stuff' && ( + + x {reward.count} + + )} +
); } From eca7cbbbebee0429ea9e1855851350caf21db3c9 Mon Sep 17 00:00:00 2001 From: tresabhi Date: Sun, 16 Jul 2023 18:26:33 +0000 Subject: [PATCH 08/11] feat: added more mature secrets handling --- src/bot.ts | 4 ++-- src/commands/eligible.ts | 4 ++-- src/commands/evolution.tsx | 6 +++--- src/commands/fullStats.tsx | 6 +++--- src/commands/inactive.tsx | 10 +++++----- src/commands/ownedTanks.tsx | 6 +++--- src/commands/playerAchievements.ts | 6 +++--- src/commands/playerInfo.ts | 4 ++-- src/commands/ratings.tsx | 4 ++-- src/commands/searchClans.ts | 4 ++-- src/commands/searchPlayers.ts | 8 ++++---- src/commands/stats.tsx | 6 +++--- src/commands/today.tsx | 4 ++-- src/commands/verify.ts | 6 +++--- src/core/blitz/getTankStats.ts | 4 ++-- src/core/blitz/listClans.ts | 8 ++++---- src/core/blitz/listPlayers.ts | 8 ++++---- src/core/blitzstars/getPlayerHistories.ts | 4 ++-- src/core/node/arguments.ts | 9 --------- src/core/node/secrets.ts | 15 +++++++++++++++ src/events/interactionCreate/index.ts | 4 ++-- src/renderers/evolution.tsx | 6 +++--- src/renderers/fullStats.tsx | 6 +++--- src/renderers/stats.tsx | 6 +++--- src/renderers/today.tsx | 6 +++--- src/routes/stats.tsx | 2 +- 26 files changed, 81 insertions(+), 75 deletions(-) delete mode 100644 src/core/node/arguments.ts create mode 100644 src/core/node/secrets.ts diff --git a/src/bot.ts b/src/bot.ts index 8297d739e..cbf0561b9 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -1,5 +1,5 @@ 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'; @@ -13,4 +13,4 @@ export const client = new Client({ .on('interactionCreate', interactionCreate); registerErrorHandlers(); -client.login(DISCORD_TOKEN); +client.login(secrets.DISCORD_TOKEN); diff --git a/src/commands/eligible.ts b/src/commands/eligible.ts index 2ded85c56..06ad066fd 100644 --- a/src/commands/eligible.ts +++ b/src/commands/eligible.ts @@ -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'; @@ -51,7 +51,7 @@ export const eligibleCommand: CommandRegistry = { const clan = interaction.options.getString('clan') as SkilledClan; const { id, region: server } = await resolvePlayerFromCommand(interaction); const accountInfo = await getWargamingResponse( - `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]}`; diff --git a/src/commands/evolution.tsx b/src/commands/evolution.tsx index 4754f635a..9354f2689 100644 --- a/src/commands/evolution.tsx +++ b/src/commands/evolution.tsx @@ -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'; @@ -42,7 +42,7 @@ export const evolutionCommand: CommandRegistry = { const end = interaction.options.getInteger('end'); const { nickname } = ( await getWargamingResponse( - `https://api.wotblitz.${player.region}/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, { @@ -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, diff --git a/src/commands/fullStats.tsx b/src/commands/fullStats.tsx index d5c9a7e67..8b30f58d9 100644 --- a/src/commands/fullStats.tsx +++ b/src/commands/fullStats.tsx @@ -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'; @@ -47,7 +47,7 @@ export const fullStatsCommand: CommandRegistry = { }); const { nickname } = ( await getWargamingResponse( - `https://api.wotblitz.${player.region}/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]; @@ -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, diff --git a/src/commands/inactive.tsx b/src/commands/inactive.tsx index e059125fe..8784e03d7 100644 --- a/src/commands/inactive.tsx +++ b/src/commands/inactive.tsx @@ -9,7 +9,7 @@ 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( - `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( - `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) => { diff --git a/src/commands/ownedTanks.tsx b/src/commands/ownedTanks.tsx index 637739d6b..f5bddbb84 100644 --- a/src/commands/ownedTanks.tsx +++ b/src/commands/ownedTanks.tsx @@ -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'; @@ -76,7 +76,7 @@ export const ownedTanksCommand: CommandRegistry = { const account = await resolvePlayerFromCommand(interaction); const { id, region: server } = account; const accountInfo = await getWargamingResponse( - `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( - `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); diff --git a/src/commands/playerAchievements.ts b/src/commands/playerAchievements.ts index 505cf18de..904b8534a 100644 --- a/src/commands/playerAchievements.ts +++ b/src/commands/playerAchievements.ts @@ -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'; @@ -38,11 +38,11 @@ export const playerAchievementsCommand: CommandRegistry = { const account = await resolvePlayerFromCommand(interaction); const { id, region: server } = account; const accounts = await getWargamingResponse( - `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( - `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 = { diff --git a/src/commands/playerInfo.ts b/src/commands/playerInfo.ts index ad7876c66..e11a66ebe 100644 --- a/src/commands/playerInfo.ts +++ b/src/commands/playerInfo.ts @@ -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'; @@ -24,7 +24,7 @@ export const playerInfoCommand: CommandRegistry = { const account = await resolvePlayerFromCommand(interaction); const { id, region: server } = account; const accounts = await getWargamingResponse( - `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]; diff --git a/src/commands/ratings.tsx b/src/commands/ratings.tsx index 1b9959c63..db20006c0 100644 --- a/src/commands/ratings.tsx +++ b/src/commands/ratings.tsx @@ -8,7 +8,7 @@ import regionToRegionSubdomain from '../core/blitz/regionToRegionSubdomain'; 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'; @@ -181,7 +181,7 @@ export const ratingsCommand: CommandRegistry = { `https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/league/${leagueIndex}/top/`, ).then((response) => response.json() as Promise); const { nickname } = await getWargamingResponse( - `https://api.wotblitz.${player.region}/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}`, ).then((accounts) => accounts[player.id]); const awaitedInfo = await ratingsInfo; const leagueInfo = awaitedInfo.leagues[leagueIndex]; diff --git a/src/commands/searchClans.ts b/src/commands/searchClans.ts index a483d4510..15d44a919 100644 --- a/src/commands/searchClans.ts +++ b/src/commands/searchClans.ts @@ -5,7 +5,7 @@ import getWargamingResponse from '../core/blitz/getWargamingResponse'; import addClanChoices from '../core/discord/addClanChoices'; import addServerChoices from '../core/discord/addServerChoices'; 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'; @@ -32,7 +32,7 @@ export const searchClansCommand: CommandRegistry = { const clan = interaction.options.getString('clan')!; const limit = interaction.options.getInteger('limit') ?? 25; const clanList = await getWargamingResponse( - `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( diff --git a/src/commands/searchPlayers.ts b/src/commands/searchPlayers.ts index 7de26bc69..e01e01f25 100644 --- a/src/commands/searchPlayers.ts +++ b/src/commands/searchPlayers.ts @@ -6,7 +6,7 @@ import { usernamePattern } from '../core/blitz/listPlayers'; import addServerChoices from '../core/discord/addServerChoices'; 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'; @@ -37,9 +37,9 @@ export const searchPlayersCommand: CommandRegistry = { const trimmedSearch = name.trim(); const players = usernamePattern.test(trimmedSearch) ? await getWargamingResponse( - `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}`, ) : []; diff --git a/src/commands/stats.tsx b/src/commands/stats.tsx index 2b392fb7f..a366eeb93 100644 --- a/src/commands/stats.tsx +++ b/src/commands/stats.tsx @@ -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'; @@ -48,7 +48,7 @@ export const statsCommand: CommandRegistry = { }); const { nickname } = ( await getWargamingResponse( - `https://api.wotblitz.${player.region}/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]; @@ -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, diff --git a/src/commands/today.tsx b/src/commands/today.tsx index 48c01c68c..9a5a7a037 100644 --- a/src/commands/today.tsx +++ b/src/commands/today.tsx @@ -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( - `https://api.wotblitz.${player.region}/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); diff --git a/src/commands/verify.ts b/src/commands/verify.ts index de53dc2a7..1dea0918f 100644 --- a/src/commands/verify.ts +++ b/src/commands/verify.ts @@ -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'; @@ -26,10 +26,10 @@ export const verifyCommand: CommandRegistry = { const blitzAccount = await resolvePlayerFromCommand(interaction); const { id, region: server } = blitzAccount; const accountInfo = await getWargamingResponse( - `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( - `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}]` : ''; diff --git a/src/core/blitz/getTankStats.ts b/src/core/blitz/getTankStats.ts index d12be6448..444b544fa 100644 --- a/src/core/blitz/getTankStats.ts +++ b/src/core/blitz/getTankStats.ts @@ -1,12 +1,12 @@ 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: Region, id: number) { const tankStats = await getWargamingResponse( - `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) { diff --git a/src/core/blitz/listClans.ts b/src/core/blitz/listClans.ts index 69e84ec29..3c7e3fab8 100644 --- a/src/core/blitz/listClans.ts +++ b/src/core/blitz/listClans.ts @@ -1,6 +1,6 @@ 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,7 +15,7 @@ export default async function listClans(search: string, limit = 9) { return ( await Promise.all([ getWargamingResponse( - `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 && @@ -25,7 +25,7 @@ export default async function listClans(search: string, limit = 9) { })), ), getWargamingResponse( - `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 && @@ -35,7 +35,7 @@ export default async function listClans(search: string, limit = 9) { })), ), getWargamingResponse( - `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 && diff --git a/src/core/blitz/listPlayers.ts b/src/core/blitz/listPlayers.ts index 6ecc9a3fb..15df2cb1e 100644 --- a/src/core/blitz/listPlayers.ts +++ b/src/core/blitz/listPlayers.ts @@ -1,6 +1,6 @@ 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,7 +19,7 @@ export default async function listPlayers(search: string, limit = 9) { return ( await Promise.all([ getWargamingResponse( - `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 && @@ -29,7 +29,7 @@ export default async function listPlayers(search: string, limit = 9) { })), ), getWargamingResponse( - `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 && @@ -39,7 +39,7 @@ export default async function listPlayers(search: string, limit = 9) { })), ), getWargamingResponse( - `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 && diff --git a/src/core/blitzstars/getPlayerHistories.ts b/src/core/blitzstars/getPlayerHistories.ts index ad9cf6e46..0c04357c0 100644 --- a/src/core/blitzstars/getPlayerHistories.ts +++ b/src/core/blitzstars/getPlayerHistories.ts @@ -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 { @@ -67,7 +67,7 @@ export default async function getPlayerHistories( statistics: { all }, } = ( await getWargamingResponse( - `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]; diff --git a/src/core/node/arguments.ts b/src/core/node/arguments.ts deleted file mode 100644 index dc8797b8c..000000000 --- a/src/core/node/arguments.ts +++ /dev/null @@ -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}'); -} diff --git a/src/core/node/secrets.ts b/src/core/node/secrets.ts new file mode 100644 index 000000000..096b5d382 --- /dev/null +++ b/src/core/node/secrets.ts @@ -0,0 +1,15 @@ +const SECRET_KEYS = ['DISCORD_TOKEN', 'WARGAMING_APPLICATION_ID'] as const; + +type SECRET_KEY = typeof SECRET_KEYS extends readonly (infer T)[] ? T : never; + +export const secrets = SECRET_KEYS.reduce>>( + (accumulator, key) => ({ + ...accumulator, + + get [key](): string { + if (!(key in process.env)) throw new Error(`Key ${key} not provided`); + return process.env[key]!; + }, + }), + {}, +) as Record; diff --git a/src/events/interactionCreate/index.ts b/src/events/interactionCreate/index.ts index ef951d1d9..b1d0ab354 100644 --- a/src/events/interactionCreate/index.ts +++ b/src/events/interactionCreate/index.ts @@ -30,9 +30,9 @@ 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'; @@ -71,7 +71,7 @@ export interface CommandRegistry button?: (interaction: ButtonInteraction) => InteractionReturnable; } -const rest = new REST().setToken(DISCORD_TOKEN); +const rest = new REST().setToken(secrets.DISCORD_TOKEN); export const COMMANDS_RAW: CommandRegistry[] = [ debugCommand, diff --git a/src/renderers/evolution.tsx b/src/renderers/evolution.tsx index c6996cbcb..1267cf92c 100644 --- a/src/renderers/evolution.tsx +++ b/src/renderers/evolution.tsx @@ -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'; @@ -29,7 +29,7 @@ export default async function evolution( if (type === 'player') { const clan = ( await getWargamingResponse( - `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( const minX = Math.min(...xs); const accountInfo = await getWargamingResponse( - `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 ( diff --git a/src/renderers/fullStats.tsx b/src/renderers/fullStats.tsx index 21e798190..c6fc3a6d2 100644 --- a/src/renderers/fullStats.tsx +++ b/src/renderers/fullStats.tsx @@ -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, @@ -37,7 +37,7 @@ export default async function fullStats( if (type === 'player') { const clan = ( await getWargamingResponse( - `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( } const accountInfo = await getWargamingResponse( - `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 ( diff --git a/src/renderers/stats.tsx b/src/renderers/stats.tsx index b95db2528..8c0287cc4 100644 --- a/src/renderers/stats.tsx +++ b/src/renderers/stats.tsx @@ -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, @@ -39,7 +39,7 @@ export default async function stats( if (type === 'player') { const clan = ( await getWargamingResponse( - `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( } const accountInfo = await getWargamingResponse( - `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 = ( diff --git a/src/renderers/today.tsx b/src/renderers/today.tsx index dd0716979..d3afd0ba1 100644 --- a/src/renderers/today.tsx +++ b/src/renderers/today.tsx @@ -15,7 +15,7 @@ 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'; @@ -32,10 +32,10 @@ export default async function today( getPeriodNow(), ); const accountInfo = await getWargamingResponse( - `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( - `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 = { diff --git a/src/routes/stats.tsx b/src/routes/stats.tsx index b9f79752a..b06a87a7a 100644 --- a/src/routes/stats.tsx +++ b/src/routes/stats.tsx @@ -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, From c173ece882788eb3acc2eeec5ad3d76ab0d0b996 Mon Sep 17 00:00:00 2001 From: tresabhi Date: Tue, 18 Jul 2023 00:17:00 +0000 Subject: [PATCH 09/11] feat: completed /ratings league --- .vscode/settings.json | 1 + package.json | 1 + scripts/build.ts | 6 +- src/bot.ts | 3 + src/commands/ratings.tsx | 94 ++++++++++++++----- src/components/Leaderboard/components/Gap.tsx | 29 ++++++ .../Leaderboard/components/Item.tsx | 59 +++++++++--- src/core/node/secrets.ts | 14 ++- yarn.lock | 5 + 9 files changed, 167 insertions(+), 45 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 4cc04f9af..5f186195b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -38,6 +38,7 @@ "vickers", "vitepress", "wargaming", + "webp", "winrate" ], "deno.enable": false, diff --git a/package.json b/package.json index e91f5f688..0430bdf53 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "esbuild": "^0.18.10", "escape-html": "^1.0.3", "express": "^4.18.2", + "false": "^0.0.4", "fuzzysort": "^2.0.4", "lodash": "^4.17.21", "markdown-escape": "^2.0.0", diff --git a/scripts/build.ts b/scripts/build.ts index 522841e3f..28cc244f4 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -1,5 +1,5 @@ import { BuildOptions, build } from 'esbuild'; -import { copyFile, mkdir, rm } from 'fs/promises'; +import { mkdir, rm } from 'fs/promises'; import { argv } from 'process'; const isProd = !argv.includes('--dev'); @@ -46,9 +46,7 @@ if (buildBot) { entryPoints: ['src/bot.ts'], outfile: 'dist/bot.cjs', - }).then(() => { - console.log('Bot built'); - }); + }).then(() => console.log('Bot built')); } if (buildServer) { diff --git a/src/bot.ts b/src/bot.ts index cbf0561b9..f099ef031 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -1,3 +1,4 @@ +import { Octokit } from '@octokit/rest'; import { Client, GatewayIntentBits } from 'discord.js'; import { secrets } from './core/node/secrets'; import { registerErrorHandlers } from './events/error'; @@ -12,5 +13,7 @@ export const client = new Client({ .on('guildMemberAdd', guildMemberAdd) .on('interactionCreate', interactionCreate); +export const octokit = new Octokit({ auth: secrets.GH_TOKEN }); + registerErrorHandlers(); client.login(secrets.DISCORD_TOKEN); diff --git a/src/commands/ratings.tsx b/src/commands/ratings.tsx index db20006c0..c88753844 100644 --- a/src/commands/ratings.tsx +++ b/src/commands/ratings.tsx @@ -1,4 +1,6 @@ import { SlashCommandBuilder, SlashCommandSubcommandBuilder } from 'discord.js'; +import { BlitzkriegRatingsLeaderboard } from '../../scripts/buildRatingsLeaderboard'; +import { octokit } from '../bot'; import * as Leaderboard from '../components/Leaderboard'; import TitleBar from '../components/TitleBar'; import Wrapper from '../components/Wrapper'; @@ -110,7 +112,7 @@ function optionalParameters(option: SlashCommandSubcommandBuilder) { .addIntegerOption((option) => option .setName('limit') - .setDescription('How many rows to display (default: 16)') + .setDescription('How many rows to display (default: 10)') .setRequired(false) .setMinValue(5) .setMaxValue(30), @@ -120,6 +122,8 @@ function optionalParameters(option: SlashCommandSubcommandBuilder) { const LEAGUE_INDEXES = ['diamond', 'platinum', 'gold', 'silver', 'bronze']; +export const DATABASE_REPO = { owner: 'tresabhi', repo: 'blitzkrieg-db' }; + export const ratingsCommand: CommandRegistry = { inProduction: true, inDevelopment: true, @@ -172,51 +176,91 @@ export const ratingsCommand: CommandRegistry = { const subcommandGroup = interaction.options.getSubcommandGroup(true); if (subcommandGroup === 'league') { - const limit = interaction.options.getInteger('limit') ?? 16; + const limit = interaction.options.getInteger('limit') ?? 10; const subcommand = interaction.options.getSubcommand(true); const leagueIndex = LEAGUE_INDEXES.indexOf(subcommand); const player = await resolvePlayerFromCommand(interaction); const regionSubdomain = regionToRegionSubdomain(player.region); - const { result } = await fetch( + const result = await fetch( `https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/league/${leagueIndex}/top/`, - ).then((response) => response.json() as Promise); + ) + .then((response) => response.json() as Promise) + .then(({ result }) => result.slice(0, limit)); const { nickname } = await getWargamingResponse( `https://api.wotblitz.${player.region}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${player.id}`, ).then((accounts) => accounts[player.id]); const awaitedInfo = await ratingsInfo; const leagueInfo = awaitedInfo.leagues[leagueIndex]; - const items = result.slice(0, limit).map((player) => { - const reward = awaitedInfo.rewards.find( - (reward) => - player.number >= reward.from_position && - player.number <= reward.to_position, - ); + const midnightLeaderboard = await octokit.repos + .getContent({ + ...DATABASE_REPO, + path: `${player.region}/ratings/${awaitedInfo.current_season}/midnight.json`, + }) + .then(({ data }) => { + if (!Array.isArray(data) && data.type === 'file') { + const content = Buffer.from(data.content, 'base64').toString(); + const jsonContent = JSON.parse( + content, + ) as BlitzkriegRatingsLeaderboard; - return ( - - ); - }); + return jsonContent; + } + }) + .catch(() => console.warn('No midnight leaderboard found')); + const items = await Promise.all( + result.map(async (player) => { + const reward = awaitedInfo.rewards.find( + (reward) => + player.number >= reward.from_position && + player.number <= reward.to_position, + ); + const midnightIndex = midnightLeaderboard?.findIndex( + (item) => item.id === player.spa_id, + ); + const midnightScore = + midnightIndex === undefined + ? player.score + : midnightLeaderboard![midnightIndex].score; + const midnightPosition = + midnightIndex === undefined ? player.number : midnightIndex + 1; + + return ( + + ); + }), + ); + + const playersBefore = result[0].number - 1; + const playersAfter = awaitedInfo.count - result[result.length - 1].number; return ( - {items} + + {playersBefore > 0 && } + {items} + {playersAfter > 0 && } + ); } diff --git a/src/components/Leaderboard/components/Gap.tsx b/src/components/Leaderboard/components/Gap.tsx index e69de29bb..fab9f6aba 100644 --- a/src/components/Leaderboard/components/Gap.tsx +++ b/src/components/Leaderboard/components/Gap.tsx @@ -0,0 +1,29 @@ +import { theme } from '../../../stitches.config'; + +export interface GapProps { + number: number; +} + +export function Gap({ number }: GapProps) { + return ( +
+ + + {number.toLocaleString()} more + +
+ ); +} diff --git a/src/components/Leaderboard/components/Item.tsx b/src/components/Leaderboard/components/Item.tsx index 1dc9df991..5a3d3d384 100644 --- a/src/components/Leaderboard/components/Item.tsx +++ b/src/components/Leaderboard/components/Item.tsx @@ -4,18 +4,22 @@ import { theme } from '../../../stitches.config'; export interface ItemProps { position: number; deltaPosition?: number; - points: number; - deltaPoints?: number; + score: number; + deltaScore?: number; reward?: RatingsReward; nickname: string; clan?: string; } +function normalizeImage(url: string) { + return url.endsWith('.webp') ? 'https://i.imgur.com/ZniCzbO.png' : url; +} + export function Item({ position, deltaPosition, - points, - deltaPoints, + score, + deltaScore, reward, nickname, clan, @@ -38,7 +42,7 @@ export function Item({ fontSize: 16, }} > - {position}. + {position.toLocaleString()}.
- {deltaPosition} + {Math.abs(deltaPosition).toLocaleString()}
)} @@ -111,25 +115,56 @@ export function Item({ > {reward && ( )} - {reward?.type === 'stuff' && ( + {reward && reward.count > 1 && ( - x {reward.count} + x {reward.count.toLocaleString()} )} + +
+ {deltaScore !== 0 && deltaScore !== undefined && ( + + {Math.abs(deltaScore).toLocaleString()} + + )} + {deltaScore !== 0 && deltaScore !== undefined && ( + 0 + ? 'https://i.imgur.com/qbjiXa1.png' + : 'https://i.imgur.com/3uyNhun.png' + } + /> + )} + + {score.toLocaleString()} + +
); } diff --git a/src/core/node/secrets.ts b/src/core/node/secrets.ts index 096b5d382..f3bea2763 100644 --- a/src/core/node/secrets.ts +++ b/src/core/node/secrets.ts @@ -1,8 +1,14 @@ -const SECRET_KEYS = ['DISCORD_TOKEN', 'WARGAMING_APPLICATION_ID'] as const; +export const SECRET_KEYS = [ + 'DISCORD_TOKEN', + 'WARGAMING_APPLICATION_ID', + 'GH_TOKEN', +] as const; -type SECRET_KEY = typeof SECRET_KEYS extends readonly (infer T)[] ? T : never; +export type SECRET = typeof SECRET_KEYS extends readonly (infer T)[] + ? T + : never; -export const secrets = SECRET_KEYS.reduce>>( +export const secrets = SECRET_KEYS.reduce>>( (accumulator, key) => ({ ...accumulator, @@ -12,4 +18,4 @@ export const secrets = SECRET_KEYS.reduce>>( }, }), {}, -) as Record; +) as Record; diff --git a/yarn.lock b/yarn.lock index 69a544844..0ebee1b61 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1269,6 +1269,11 @@ express@^4.18.2: utils-merge "1.0.1" vary "~1.1.2" +"false@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/false/-/false-0.0.4.tgz#8f55de5af5c03f50bf68aed57ed80e3d2176a517" + integrity sha512-CPAG3ud9Cz8NvLKlsTLr/F1aoJL6tPyhNTzaDFzS5CX6id21hVuS5sBK8wlTLuvFklpvH1idvPbW8Lbri3G7qA== + fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" From 62627c96a34e0f5980fa36765899805c7559490d Mon Sep 17 00:00:00 2001 From: tresabhi Date: Tue, 18 Jul 2023 00:23:59 +0000 Subject: [PATCH 10/11] feat: added other regions --- src/commands/ratings.tsx | 26 ++++++------------- src/commands/searchClans.ts | 6 ++--- src/commands/searchPlayers.ts | 6 ++--- .../Leaderboard/components/Item.tsx | 2 +- ...ddServerChoices.ts => addRegionChoices.ts} | 6 ++--- src/core/express/resolvePlayerFromURL.ts | 2 +- 6 files changed, 19 insertions(+), 29 deletions(-) rename src/core/discord/{addServerChoices.ts => addRegionChoices.ts} (70%) diff --git a/src/commands/ratings.tsx b/src/commands/ratings.tsx index c88753844..948440aec 100644 --- a/src/commands/ratings.tsx +++ b/src/commands/ratings.tsx @@ -4,15 +4,10 @@ import { octokit } from '../bot'; import * as Leaderboard from '../components/Leaderboard'; import TitleBar from '../components/TitleBar'; import Wrapper from '../components/Wrapper'; -import { REGION_NAMES } from '../constants/regions'; -import getWargamingResponse from '../core/blitz/getWargamingResponse'; +import { REGION_NAMES, Region } from '../constants/regions'; import regionToRegionSubdomain from '../core/blitz/regionToRegionSubdomain'; -import addUsernameChoices from '../core/discord/addUsernameChoices'; -import autocompleteUsername from '../core/discord/autocompleteUsername'; -import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; -import { secrets } from '../core/node/secrets'; +import addRegionChoices from '../core/discord/addRegionChoices'; import { CommandRegistry } from '../events/interactionCreate'; -import { AccountInfo } from '../types/accountInfo'; export interface RatingsPlayer { spa_id: number; @@ -109,6 +104,7 @@ const ratingsInfo = fetch( function optionalParameters(option: SlashCommandSubcommandBuilder) { return option + .addStringOption(addRegionChoices) .addIntegerOption((option) => option .setName('limit') @@ -116,8 +112,7 @@ function optionalParameters(option: SlashCommandSubcommandBuilder) { .setRequired(false) .setMinValue(5) .setMaxValue(30), - ) - .addStringOption(addUsernameChoices); + ); } const LEAGUE_INDEXES = ['diamond', 'platinum', 'gold', 'silver', 'bronze']; @@ -179,22 +174,19 @@ export const ratingsCommand: CommandRegistry = { const limit = interaction.options.getInteger('limit') ?? 10; const subcommand = interaction.options.getSubcommand(true); const leagueIndex = LEAGUE_INDEXES.indexOf(subcommand); - const player = await resolvePlayerFromCommand(interaction); - const regionSubdomain = regionToRegionSubdomain(player.region); + const 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) .then(({ result }) => result.slice(0, limit)); - const { nickname } = await getWargamingResponse( - `https://api.wotblitz.${player.region}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${player.id}`, - ).then((accounts) => accounts[player.id]); const awaitedInfo = await ratingsInfo; const leagueInfo = awaitedInfo.leagues[leagueIndex]; const midnightLeaderboard = await octokit.repos .getContent({ ...DATABASE_REPO, - path: `${player.region}/ratings/${awaitedInfo.current_season}/midnight.json`, + path: `${region}/ratings/${awaitedInfo.current_season}/midnight.json`, }) .then(({ data }) => { if (!Array.isArray(data) && data.type === 'file') { @@ -252,7 +244,7 @@ export const ratingsCommand: CommandRegistry = { : `https:${leagueInfo.big_icon}` } description={`Top ${limit} players • ${new Date().toDateString()} • ${ - REGION_NAMES[player.region] + REGION_NAMES[region] }`} /> @@ -267,6 +259,4 @@ export const ratingsCommand: CommandRegistry = { return []; }, - - autocomplete: autocompleteUsername, }; diff --git a/src/commands/searchClans.ts b/src/commands/searchClans.ts index 15d44a919..7d7436d3b 100644 --- a/src/commands/searchClans.ts +++ b/src/commands/searchClans.ts @@ -3,7 +3,7 @@ import markdownEscape from 'markdown-escape'; 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 { secrets } from '../core/node/secrets'; import { CommandRegistry } from '../events/interactionCreate'; @@ -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,7 +28,7 @@ export const searchClansCommand: CommandRegistry = { ), async handler(interaction) { - const server = interaction.options.getString('server') as Region; + 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( diff --git a/src/commands/searchPlayers.ts b/src/commands/searchPlayers.ts index e01e01f25..c68d8a0e6 100644 --- a/src/commands/searchPlayers.ts +++ b/src/commands/searchPlayers.ts @@ -3,7 +3,7 @@ import markdownEscape from 'markdown-escape'; 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 { secrets } from '../core/node/secrets'; @@ -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,7 +31,7 @@ export const searchPlayersCommand: CommandRegistry = { ), async handler(interaction) { - const server = interaction.options.getString('server') as Region; + 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(); diff --git a/src/components/Leaderboard/components/Item.tsx b/src/components/Leaderboard/components/Item.tsx index 5a3d3d384..77fc18097 100644 --- a/src/components/Leaderboard/components/Item.tsx +++ b/src/components/Leaderboard/components/Item.tsx @@ -115,7 +115,7 @@ export function Item({ > {reward && ( Date: Tue, 18 Jul 2023 00:29:49 +0000 Subject: [PATCH 11/11] feat: changelogs --- docs/changelogs/1.11.0.md | 22 ++++++++++++++++++++++ scripts/build.ts | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 docs/changelogs/1.11.0.md diff --git a/docs/changelogs/1.11.0.md b/docs/changelogs/1.11.0.md new file mode 100644 index 000000000..8cbf7e077 --- /dev/null +++ b/docs/changelogs/1.11.0.md @@ -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 diff --git a/scripts/build.ts b/scripts/build.ts index 28cc244f4..db54175b2 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -1,5 +1,5 @@ import { BuildOptions, build } from 'esbuild'; -import { mkdir, rm } from 'fs/promises'; +import { copyFile, mkdir, rm } from 'fs/promises'; import { argv } from 'process'; const isProd = !argv.includes('--dev');