Compare commits

...
94 Commits
Author SHA1 Message Date
TrèsAbhi 1138e1a826 Merge pull request #37 from tresabhi/1.13.0 2023-10-01 17:11:31 -05:00
TrèsAbhi e5057b8c1d fix: unknown reward icons 2023-10-01 17:10:22 -05:00
TrèsAbhi 7c04243b3d fix: old help command name reference 2023-10-01 17:02:13 -05:00
TrèsAbhi c6b54454be feat: remove gitpod 2023-10-01 16:57:05 -05:00
TrèsAbhi 876fb0fc5e feat: simplified build script 2023-10-01 16:56:59 -05:00
TrèsAbhi 38f912dc0a feat: removed server 2023-10-01 16:48:53 -05:00
TrèsAbhi eb24f49766 feat: added art credits 2023-10-01 16:43:21 -05:00
TrèsAbhi 168ab1e1a1 feat: tooltip for embed button 2023-10-01 16:38:39 -05:00
TrèsAbhi 49f601d200 feat: added reset prompt 2023-10-01 16:37:51 -05:00
TrèsAbhi 15f32a0044 feat: use if 2023-10-01 16:19:20 -05:00
TrèsAbhi 7bb3c60a61 feat: a little bit of keyboard nav 2023-10-01 16:16:32 -05:00
TrèsAbhi 4d1dfc1ae5 feat: use radix ui for dropdown 2023-10-01 16:05:51 -05:00
TrèsAbhi ec5dfd7727 feat: hide search results on unfocus 2023-10-01 15:18:50 -05:00
TrèsAbhi 8e5a588e87 fix: deltas showing up even on no diff 2023-10-01 15:15:36 -05:00
TrèsAbhi b9c3cfaee4 feat: optimized homepage images 2023-10-01 15:04:17 -05:00
TrèsAbhi 01bd3f8d68 feat: added disabled tools 2023-10-01 11:35:44 -05:00
TrèsAbhi b39f545669 feat: added deltas to totals 2023-10-01 11:05:01 -05:00
TrèsAbhi e7f517e157 feat: renamed help command to about 2023-10-01 10:56:03 -05:00
TrèsAbhi 1d7de715e7 feat: updated permissions doc 2023-10-01 10:55:45 -05:00
TrèsAbhi 76501c1aa0 feat: added dedicated tsconfig for esbuild 2023-09-30 22:36:44 -05:00
TrèsAbhi a535a07810 fix: null clan 2023-09-30 22:35:04 -05:00
TrèsAbhi 6add54e86d feat: hide navbar icons 2023-09-30 22:33:01 -05:00
TrèsAbhi a74eba9e97 feat: started migrating to radix 2023-09-30 22:30:52 -05:00
TrèsAbhi 34f782e747 feat: embeds 2023-09-30 22:12:48 -05:00
TrèsAbhi 1990322d6d fix: use effect deps 2023-09-28 10:39:32 -05:00
TrèsAbhi 0339979f9f feat: added "--" for NaN values 2023-09-28 10:28:22 -05:00
TrèsAbhi 823465fde9 feat: added reset and search click functionality 2023-09-28 10:23:46 -05:00
TrèsAbhi 1feae49490 fix: build command 2023-09-24 18:58:41 -05:00
TrèsAbhi 1a1e2169a8 bfsafjkhsadkjfhgkajsdf 2023-09-24 18:56:32 -05:00
TrèsAbhi f1b1e25d9a sad 2023-09-24 18:52:32 -05:00
TrèsAbhi ce6472be8d a 2023-09-24 18:50:38 -05:00
TrèsAbhi 79232cecb9 breath 2023-09-24 18:49:01 -05:00
TrèsAbhi 1a802da0ce (┬┬﹏┬┬) 2023-09-24 18:45:12 -05:00
TrèsAbhi b33a845c08 feat: it works?? 2023-09-24 18:43:33 -05:00
TrèsAbhi 6643b51a9b feat: undo last commit 2023-09-24 18:32:46 -05:00
TrèsAbhi 1cce00a09a feat: removed distDir 2023-09-24 18:28:44 -05:00
TrèsAbhi 1ef9a0289f fix: use pathname instead 2023-09-24 17:36:20 -05:00
TrèsAbhi 498a359f01 fix: use next/navigation instead 2023-09-24 17:35:50 -05:00
TrèsAbhi ebbcdc2089 fix: use useRouter instead of location 2023-09-24 17:33:34 -05:00
TrèsAbhi cced57b22e fix: wg id imports 2023-09-24 17:29:23 -05:00
TrèsAbhi 96d47a82bc fix: account list export 2023-09-24 17:27:04 -05:00
TrèsAbhi b4cab18ecd feat: moved tools outside page 2023-09-24 17:24:21 -05:00
TrèsAbhi d801447e5d feat: career stats 2023-09-24 17:02:09 -05:00
TrèsAbhi 8c041d4eef feat: session tracker list 2023-09-24 15:58:42 -05:00
TrèsAbhi 6ff2a23ff7 feat: removed non-proxy apis 2023-09-23 19:20:29 -05:00
TrèsAbhi 925ac96264 fix: compat for /today 2023-09-23 18:51:44 -05:00
TresAbhi 9e89198cd5 fix: a few type issues 2023-09-23 23:30:14 +00:00
TrèsAbhi a1e6643dc0 Merge pull request #36 from tresabhi/main 2023-09-23 17:30:51 -05:00
TresAbhi 63257a13f5 feat: generalized breakdown 2023-09-23 22:02:30 +00:00
TresAbhi e37e0ccf9f feat: made WARGAMING_APPLICATION_ID public 2023-09-23 21:51:00 +00:00
TresAbhi 018783bb47 feat: session persistance 2023-09-19 15:52:39 +00:00
TrèsAbhi f883670cbc Merge pull request #34 from tresabhi/1.12.9
1.12.9
2023-09-19 08:07:59 -05:00
TresAbhi 57f6e928b7 feat: search functionality 2023-09-18 23:25:24 +00:00
TresAbhi cbeb83d8d0 feat: added player list api 2023-09-18 23:05:19 +00:00
TresAbhi 33b4ea04b4 feat: session toolbar 2023-09-18 22:32:21 +00:00
TresAbhi 350434cc5a feat: added tool ground work 2023-09-18 21:55:40 +00:00
TresAbhi c0eca0c851 fix: only transition box shadow 2023-09-18 21:35:46 +00:00
TresAbhi cdac43fa5f feat: 1.12.9 2023-09-18 21:33:06 +00:00
TresAbhi 30aeabba88 feat: simplified body 2023-09-12 03:05:11 +00:00
TresAbhi 02a07f3a8a feat: homepage 2023-09-12 02:38:50 +00:00
TrèsAbhi 986a32b3ce Merge pull request #33 from tresabhi/main 2023-09-11 13:17:14 -05:00
TrèsAbhi 8c4afa73f8 Merge pull request #32 from tresabhi/1.12.8 2023-09-11 13:15:00 -05:00
TrèsAbhi 6db3ccdb40 feat: removed embed buttons 2023-09-11 13:14:05 -05:00
TresAbhi cf0fe5cbba feat: use dot env 2023-09-09 03:05:59 +00:00
TresAbhi 621398f95b feat: removed random gitpod setup command 2023-09-09 03:00:41 +00:00
TresAbhi 186a972db7 feat: added compatibility for next js 2023-09-09 02:56:34 +00:00
TresAbhi 7473a17a5e fix: added icons lib 2023-09-09 02:53:31 +00:00
TresAbhi 66e08afb0d fix: moved docs to dist/docs 2023-09-09 02:51:44 +00:00
TresAbhi 851da1d020 feat: split up CI build 2023-09-09 02:41:51 +00:00
TrèsAbhi c44840978c Merge pull request #31 from tresabhi/main 2023-09-08 21:40:22 -05:00
TrèsAbhi 90e012319a Merge pull request #30 from tresabhi/1.12.7 2023-09-08 21:35:46 -05:00
TresAbhi 18de9cbaab feat: upgraded dependencies 2023-09-09 02:35:14 +00:00
TresAbhi 42a76cbd38 feat: changelogs 2023-09-09 02:19:20 +00:00
TresAbhi eb95634801 fix: escaped markdown for clan event title 2023-09-09 02:11:32 +00:00
TresAbhi de8bfff47f feat: clearer statement about timezones 2023-09-09 02:10:54 +00:00
TresAbhi 07c18911df feat: clearer statement about public data 2023-09-09 02:06:09 +00:00
TresAbhi cc769cc330 fix: react is now defined 2023-09-09 02:04:36 +00:00
TrèsAbhi 9794f58936 Update cd-host.yml 2023-09-08 11:50:31 -05:00
TrèsAbhi a44fcff3b1 Update cd-host.yml 2023-09-08 11:48:41 -05:00
TrèsAbhi 7e4a3e88b9 Update cd-host.yml 2023-09-08 11:47:39 -05:00
TrèsAbhi 8fd45e2205 Merge pull request #29 from tresabhi/1.12.5 2023-09-08 11:45:15 -05:00
TresAbhi 7d235057e8 feat: changelogs 2023-09-08 16:41:06 +00:00
TresAbhi 63a8a67771 fix: better approximation message and players with 0 wins are omitted 2023-09-08 16:38:08 +00:00
TresAbhi bac39ca176 fix: only use players who have played within the last day 2023-09-08 16:30:44 +00:00
TresAbhi 1979047bbc feat: navbar 2023-09-07 21:25:37 +00:00
TrèsAbhi 8860c1b0f8 Merge pull request #28 from tresabhi/main 2023-09-07 15:23:26 -05:00
TrèsAbhi 626b1e8601 Merge branch '1.13.0' into main 2023-09-07 15:23:10 -05:00
TresAbhi 04086f31b9 fix: spelling 2023-09-07 03:25:07 +00:00
TresAbhi 7037aa9c3b feat: 1.12.5 2023-09-07 03:24:07 +00:00
TresAbhi f5bab8a698 faet: clan-event 2023-09-07 01:24:12 +00:00
TresAbhi ee44b1b1d8 feat: next js 2023-09-07 01:13:03 +00:00
TresAbhi d0f91df95b feat: split bot and server builds 2023-09-04 16:49:38 +00:00
TrèsAbhi dc1c49da6a Merge pull request #27 from tresabhi/main 2023-09-03 13:24:34 -05:00
TresAbhi eb4cb1fbfe feat: removed inDevelopment 2023-08-24 00:08:08 +00:00
118 changed files with 3989 additions and 1340 deletions

No files matched your search

+1 -1
View File
@@ -47,7 +47,7 @@ jobs:
- name: Upload pages artifact - name: Upload pages artifact
uses: actions/upload-pages-artifact@v1 uses: actions/upload-pages-artifact@v1
with: with:
path: docs/.vitepress/dist path: dist/docs
- name: Deploy - name: Deploy
id: deployment id: deployment
+4 -4
View File
@@ -22,11 +22,11 @@ jobs:
- name: Download release assets - name: Download release assets
run: | run: |
DOWNLOAD_URL=$(curl -s https://api.github.com/repos/tresabhi/blitzkrieg/releases/latest \ DOWNLOAD_URL=$(curl -s https://api.github.com/repos/tresabhi/blitzkrieg/releases/latest \
| grep "browser_download_url.*dist.zip" \ | grep "browser_download_url.*bot.zip" \
| cut -d : -f 2,3 \ | cut -d : -f 2,3 \
| tr -d \") | tr -d \")
curl -L -o dist.zip $DOWNLOAD_URL curl -L -o bot.zip $DOWNLOAD_URL
unzip dist.zip unzip bot.zip
- name: Run bot - name: Run bot
run: NODE_ENV=production GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} WARGAMING_APPLICATION_ID=${{ secrets.WARGAMING_APPLICATION_ID }} node --enable-source-maps --trace-warnings bot.cjs run: NODE_ENV=production GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} WARGAMING_APPLICATION_ID=${{ WARGAMING_APPLICATION_ID }} node --enable-source-maps --trace-warnings bot/index.cjs
@@ -34,10 +34,10 @@ jobs:
run: yarn run: yarn
- name: Build - name: Build
run: yarn build run: yarn build:bot
- name: Archive build - name: Archive build
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: dist name: bot
path: dist path: dist/bot
+43
View File
@@ -0,0 +1,43 @@
name: CI Build
on: [push, pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Use node
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- name: Install Yarn
run: npm install --location=global yarn
- name: Use node_modules cache
uses: actions/cache@v3
with:
path: '**/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- name: Install dependencies
run: yarn
- name: Build
run: yarn build:docs
- name: Archive build
uses: actions/upload-artifact@v3
with:
name: docs
path: dist/docs
+43
View File
@@ -0,0 +1,43 @@
name: CI Build
on: [push, pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Use node
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- name: Install Yarn
run: npm install --location=global yarn
- name: Use node_modules cache
uses: actions/cache@v3
with:
path: '**/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- name: Install dependencies
run: yarn
- name: Build
run: yarn build:website
- name: Archive build
uses: actions/upload-artifact@v3
with:
name: website
path: dist/website
+4 -2
View File
@@ -7,8 +7,10 @@ package-lock.json
dist/ dist/
dist.* dist.*
yarn-error.log yarn-error.log
commands.txt
/test.* /test.*
test.*
/docs/.vitepress/dist /docs/.vitepress/dist
/docs/.vitepress/cache /docs/.vitepress/cache
servers.csv servers.csv
.next/
tsconfig.tsbuildinfo
-3
View File
@@ -1,3 +0,0 @@
tasks:
- init: yarn
- command: yarn run pw
+4 -1
View File
@@ -6,6 +6,8 @@
"blitzstars", "blitzstars",
"cleandist", "cleandist",
"customperiod", "customperiod",
"deburr",
"diacriticless",
"Everytime", "Everytime",
"fullstats", "fullstats",
"fuzzysort", "fuzzysort",
@@ -39,7 +41,8 @@
"vitepress", "vitepress",
"wargaming", "wargaming",
"webp", "webp",
"winrate" "winrate",
"zustand"
], ],
"deno.enable": false, "deno.enable": false,
"deno.unstable": false "deno.unstable": false
+1
View File
@@ -8,6 +8,7 @@ export default defineConfig({
title: 'blitzkrieg', title: 'blitzkrieg',
description: '🎉 All-in-one Discord bot for everything World of Tanks Blitz', description: '🎉 All-in-one Discord bot for everything World of Tanks Blitz',
base: '/blitzkrieg/', base: '/blitzkrieg/',
outDir: '../dist/docs',
themeConfig: { themeConfig: {
nav: [ nav: [
+7
View File
@@ -0,0 +1,7 @@
# Blitzkrieg 1.12.4
Added very badly-coded temporary `/clan-event` command (without linting)
## Changes
- Added `/clan-event` command
+12
View File
@@ -0,0 +1,12 @@
# Blitzkrieg 1.12.5
Some patch up work
## Changes
- `/clan-event` now is more legible and comes with an approximation warning
## Fixes
- `/clan-event` now uses a more optimized way of fetching large data
- This should fix the issue where too many people using the command broke it
+12
View File
@@ -0,0 +1,12 @@
# Blitzkrieg 1.12.6
Stops making the bot lie!
## Changes
- The approximation warning for `/clan-event` has been made more concise
- Players with `0` wins are now omitted
## Fixes
- Players who haven't player within the current day are now omitted
+13
View File
@@ -0,0 +1,13 @@
# Blitzkrieg 1.12.7
A few fixes and clarifications
## Changes
- `/clan-event`
- Made statement about the limited publicly available data
- Added a statement about reset times
## Fixes
- Commands with images now once again work
+7
View File
@@ -0,0 +1,7 @@
# Blitzkrieg 1.12.8
Blitzkrieg is a completely free service and I earn no revenue from it. To keep it sustainable, I have to rely on free services. Unfortunately for embeds, seems like I have exceeded my limits. Before this service starts charging me, I have shut down embeds. They'll hopefully be back in 1.13 alongside the website! Only the future will tell...
## Changes
- Removed all embed buttons
+7
View File
@@ -0,0 +1,7 @@
# Blitzkrieg 1.12.9
Now that the clan event is over, we don't need `/clan-event`. This command may be reintroduced in the future as a part of a different command/feature and will definitely be re-enabled during the next clan event.
## Changes
- Disabled the `/clan-event` command
+32
View File
@@ -0,0 +1,32 @@
# Blitzkrieg 1.13.0
One of the biggest updates ever! This changelog's size does not truly represent the work (and brain cells) spent.
## Changes
- Added Blitzkrieg homepage
- Added session tracker
- Search players up and select one
- This starts tracking your games from that point
- This is displayed in a similar fashion to the `/today` command
- Updated the permissions doc
- This now includes all 3 of the needed permissions for full operation
- Also renamed the permissions to better match Discord
- Renamed `/help` to `/about`
- Renamed `/help about` to `/about bot`
## Fixes
- Added `/ratings` error handling for `null` clans, players, and rewards
## Technical Changes
- Removed all server assets
- Simplified build script to only compile the bot
- Upgraded the build script to TypeScript
- Moved docs build to `dist/docs`
- Split `tsconfig.json` for Next.js and esbuild
- Removed Gitpod config
- Switched to `.env`
- Made Wargaming application ID public
- Started migration to Radix UI
@@ -1,4 +1,4 @@
# About # Introduction
Blitzkrieg is a multipurpose bot for anything and everything World of Tanks Blitz. Blitzkrieg is a multipurpose bot for anything and everything World of Tanks Blitz.
+2 -1
View File
@@ -2,5 +2,6 @@
The following Discord permissions are required by the bot to ensure full functionality: The following Discord permissions are required by the bot to ensure full functionality:
- Manage nicknames
- Read messages / view channels
- Attach files - Attach files
- View channels
+5
View File
@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
+12
View File
@@ -0,0 +1,12 @@
import { createVanillaExtractPlugin } from '@vanilla-extract/next-plugin';
const withVanillaExtract = createVanillaExtractPlugin();
/** @type {import('next').NextConfig} */
const nextConfig = {
...withVanillaExtract(),
distDir: 'dist/website',
};
export default nextConfig;
+30 -21
View File
@@ -1,24 +1,24 @@
{ {
"name": "blitzkrieg", "name": "blitzkrieg",
"version": "1.12.1", "version": "1.13.0",
"description": "🎉 All-in-one Discord bot for everything World of Tanks Blitz", "description": "🎉 All-in-one Discord bot for everything World of Tanks Blitz",
"main": "src/server.ts", "main": "src/server.ts",
"type": "module", "type": "module",
"scripts": { "scripts": {
"build:bot": "node scripts/build --build=bot", "build:bot": "vite-node --script scripts/build",
"build:server": "node scripts/build --build=server", "build:website": "next build",
"build:docs": "vitepress build docs", "build:docs": "vitepress build docs",
"build:ratingsleaderboard": "vite-node --script scripts/buildRatingsLeaderboard", "build:ratingsleaderboard": "vite-node --script scripts/buildRatingsLeaderboard",
"build": "node scripts/build --build=all", "dev:bot": "yarn build:bot && yarn start:bot",
"dev:bot": "yarn build:bot --dev && yarn start:bot",
"dev:server": "yarn build:server --dev && yarn start:server",
"dev:docs": "vitepress dev docs", "dev:docs": "vitepress dev docs",
"start:bot": "node --enable-source-maps --trace-warnings dist/bot.cjs", "dev:website": "next dev",
"start:server": "node --enable-source-maps --trace-warnings dist/server.cjs", "start:bot": "node --enable-source-maps --trace-warnings dist/bot/index.cjs",
"start:website": "next start",
"host:docs": "vitepress preview docs", "host:docs": "vitepress preview docs",
"publish:ratingsleaderboard": "vite-node --script scripts/buildRatingsLeaderboard --publish", "publish:ratingsleaderboard": "vite-node --script scripts/buildRatingsLeaderboard --publish",
"zip": "zip -r dist.zip dist", "zip": "zip -r dist.zip dist",
"lint": "npx tsc", "lint": "npx tsc",
"lint:website": "next lint",
"format": "prettier --write \"**/*.@(json|js|jsx|css|md|ts|tsx|html|xml|yml|gltf)\"", "format": "prettier --write \"**/*.@(json|js|jsx|css|md|ts|tsx|html|xml|yml|gltf)\"",
"knip": "knip", "knip": "knip",
"update": "yarn upgrade --latest" "update": "yarn upgrade --latest"
@@ -26,32 +26,41 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@octokit/rest": "^20.0.1", "@octokit/rest": "^20.0.1",
"@radix-ui/colors": "^2.1.0", "@radix-ui/colors": "^3.0.0-rc.4",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/themes": "^1.1.2",
"@resvg/resvg-js": "^2.4.1", "@resvg/resvg-js": "^2.4.1",
"@vanilla-extract/css": "^1.13.0",
"bepaint": "^0.1.0-beta.3", "bepaint": "^0.1.0-beta.3",
"discord.js": "^14.13.0", "discord.js": "^14.13.0",
"esbuild": "^0.19.2", "dotenv": "^16.3.1",
"esbuild": "^0.19.3",
"express": "^4.18.2", "express": "^4.18.2",
"fuzzysort": "^2.0.4", "fuzzysort": "^2.0.4",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"markdown-escape": "^2.0.0", "markdown-escape": "^2.0.0",
"next": "^13.5.2",
"react": "^18.2.0", "react": "^18.2.0",
"satori": "^0.10.3", "react-dom": "^18.2.0",
"vite-node": "^0.34.2", "react-fuzzysort": "^1.0.1",
"vitepress-plugin-auto-sidebar": "^1.1.0" "satori": "^0.10.8",
"vite-node": "^0.34.5",
"vitepress": "^1.0.0-rc.20",
"vitepress-plugin-auto-sidebar": "^1.1.0",
"zustand": "^4.4.1"
}, },
"devDependencies": { "devDependencies": {
"@total-typescript/ts-reset": "^0.5.1", "@total-typescript/ts-reset": "^0.5.1",
"@types/escape-html": "^1.0.2", "@types/escape-html": "^1.0.2",
"@types/express": "^4.17.17", "@types/express": "^4.17.18",
"@types/lodash": "^4.14.197", "@types/lodash": "^4.14.199",
"@types/markdown-escape": "^1.1.0", "@types/markdown-escape": "^1.1.0",
"@types/node": "^20.5.3", "@types/node": "^20.6.5",
"@types/react": "^18.2.21", "@types/react": "^18.2.22",
"@vanilla-extract/next-plugin": "^2.3.1",
"compare-versions": "^6.1.0", "compare-versions": "^6.1.0",
"knip": "^2.19.9", "knip": "^2.27.1",
"prettier": "^3.0.2", "prettier": "^3.0.3",
"typescript": "^5.0.4", "typescript": "^5.2.2"
"vitepress": "^1.0.0-rc.4"
} }
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

-63
View File
@@ -1,63 +0,0 @@
import { build } from 'esbuild';
import { copyFile, mkdirSync, rmSync } from 'fs';
import { argv } from 'process';
const isProd = !argv.includes('--dev');
const buildAll = argv.includes('--build=all');
const buildBot = argv.includes('--build=bot') || buildAll;
const buildServer = argv.includes('--build=server') || buildAll;
if (isProd) {
// only remove in production to ensure no time is wasted in dev
console.log('Removing dist...');
rmSync('dist', { recursive: true, force: true });
console.log('Dist removed');
console.log('Creating dist...');
mkdirSync('dist');
console.log('Dist created');
console.log('Copying package.json...');
copyFile('package.dist.json', 'dist/package.json', () =>
console.log('package.json copied'),
);
}
const commonOptions = {
platform: 'node',
loader: {
'.node': 'copy',
'.ttf': 'file',
},
format: 'cjs',
bundle: true,
sourcemap: true,
minifyIdentifiers: false, // causes errors
minifySyntax: isProd,
minifyWhitespace: isProd,
logLevel: isProd ? 'info' : 'silent',
};
if (buildBot) {
console.log('Building bot...');
build({
...commonOptions,
entryPoints: ['src/bot.ts'],
outfile: 'dist/bot.cjs',
}).then(() => console.log('Bot built'));
}
if (buildServer) {
console.log('Building server...');
build({
...commonOptions,
entryPoints: ['src/server.ts'],
outfile: 'dist/server.cjs',
}).then(() => {
console.log('Server built');
});
}
+27
View File
@@ -0,0 +1,27 @@
import { build } from 'esbuild';
console.log('Building bot...');
await build({
entryPoints: ['src/bot.ts'],
outfile: 'dist/bot/index.cjs',
platform: 'node',
tsconfig: 'tsconfig.esbuild.json',
loader: {
'.node': 'copy',
'.ttf': 'file',
},
format: 'cjs',
jsx: 'transform',
bundle: true,
sourcemap: true,
minifyIdentifiers: false, // cause goofy errors
minifySyntax: true,
minifyWhitespace: true,
logLevel: 'info',
});
console.log('Built bot');
+10 -3
View File
@@ -31,7 +31,11 @@ const PLAYERS = await fetch(
`https://${server}.wotblitz.com/en/api/rating-leaderboards/season/`, `https://${server}.wotblitz.com/en/api/rating-leaderboards/season/`,
) )
.then((response) => response.json() as Promise<RatingsInfo>) .then((response) => response.json() as Promise<RatingsInfo>)
.then((data) => data.count); .then((data) => (data.detail === undefined ? data.count : undefined));
if (PLAYERS === undefined) {
throw new Error('No current ratings season running');
}
const players: Record<number, RatingsPlayer> = {}; const players: Record<number, RatingsPlayer> = {};
const leaderboard: BlitzkriegRatingsLeaderboard = []; const leaderboard: BlitzkriegRatingsLeaderboard = [];
@@ -60,7 +64,7 @@ async function branchFromPlayer(id: number) {
console.log( console.log(
`Registered ${registered} / ${PLAYERS} (${( `Registered ${registered} / ${PLAYERS} (${(
(registered / PLAYERS) * (registered / PLAYERS!) *
100 100
).toFixed(2)}%)`, ).toFixed(2)}%)`,
); );
@@ -109,7 +113,10 @@ if (publish) {
const info = await fetch( const info = await fetch(
`https://${server}.wotblitz.com/en/api/rating-leaderboards/season/`, `https://${server}.wotblitz.com/en/api/rating-leaderboards/season/`,
).then((response) => response.json() as Promise<RatingsInfo>); ).then(
(response) =>
response.json() as Promise<RatingsInfo & { detail: undefined }>,
);
const normalizedServer = server === 'na' ? 'com' : server; const normalizedServer = server === 'na' ? 'com' : server;
const infoPath = `${normalizedServer}/ratings/${info.current_season}/info.json`; const infoPath = `${normalizedServer}/ratings/${info.current_season}/info.json`;
+6
View File
@@ -0,0 +1,6 @@
import { NextResponse } from 'next/server';
import { tankAverages } from '../../../core/blitzstars/tankAverages';
export async function GET() {
return NextResponse.json(await tankAverages);
}
+6
View File
@@ -0,0 +1,6 @@
import { NextResponse } from 'next/server';
import { tankopedia } from '../../../core/blitz/tankopedia';
export async function GET() {
return NextResponse.json(await tankopedia);
}
+274
View File
@@ -0,0 +1,274 @@
'use client';
import { useEffect, useState } from 'react';
import * as Breakdown from '../../../components/Breakdown';
import { TreeTypeEnum } from '../../../components/Tanks';
import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID';
import calculateWN8 from '../../../core/blitz/calculateWN8';
import { diffNormalizedTankStats } from '../../../core/blitz/diffNormalizedTankStats';
import getWN8Percentile from '../../../core/blitz/getWN8Percentile';
import getWargamingResponse from '../../../core/blitz/getWargamingResponse';
import { TankopediaEntry, tankopedia } from '../../../core/blitz/tankopedia';
import { tankAverages } from '../../../core/blitzstars/tankAverages';
import { useSession } from '../../../stores/session';
import { IndividualTankStats, TanksStats } from '../../../types/tanksStats';
const REFRESH_RATE = 1 / 5;
export default function SessionPage() {
const session = useSession();
const since = session.isTracking ? new Date(session.time) : undefined;
const [diff, setDiff] = useState<
| {
careerWN8: number;
list: {
stats: IndividualTankStats;
tankopedia?: TankopediaEntry;
career: IndividualTankStats;
currentWN8?: number;
careerWN8?: number;
}[];
}
| undefined
>(undefined);
const sessionBattles = diff?.list.reduce(
(accumulator, { stats }) => accumulator + stats.all.battles,
0,
);
const tankStatsArray = session.isTracking
? (Object.values(session.tankStats) as IndividualTankStats[])
: [];
const sessionWinrate = diff
? diff.list.reduce(
(accumulator, { stats }) => accumulator + stats.all.wins,
0,
) / sessionBattles!
: 0;
const careerBattles = tankStatsArray.reduce(
(accumulator, stats) => accumulator + stats.all.battles,
0,
);
const careerWinrate =
tankStatsArray.reduce(
(accumulator, stats) => accumulator + stats.all.wins,
0,
) / careerBattles;
const currentDamage = diff
? diff.list.reduce(
(accumulator, { stats }) => accumulator + stats.all.damage_dealt,
0,
) / sessionBattles!
: 0;
const careerDamage =
tankStatsArray.reduce(
(accumulator, stats) => accumulator + stats.all.damage_dealt,
0,
) / careerBattles;
useEffect(() => {
async function recalculateDiff() {
if (session.isTracking) {
const { id, region } = session;
const careerRaw = await getWargamingResponse<TanksStats>(
`https://api.wotblitz.${region}/wotb/tanks/stats/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
);
const career = careerRaw[id].reduce<
Record<number, IndividualTankStats>
>(
(accumulator, curr) => ({
...accumulator,
[curr.tank_id]: curr,
}),
{},
);
const awaitedTankopedia = await tankopedia;
const awaitedTankAverages = await tankAverages;
const careerWN8 =
careerRaw[id].reduce(
(accumulator, { tank_id, all }) =>
accumulator +
(awaitedTankAverages[tank_id] && all.battles > 0
? all.battles *
calculateWN8(awaitedTankAverages[tank_id].all, all)
: 0),
0,
) /
careerRaw[id].reduce(
(accumulator, { tank_id, all }) =>
accumulator +
(awaitedTankAverages[tank_id] && all.battles > 0
? all.battles
: 0),
0,
);
setDiff({
careerWN8,
list: (
Object.values(
diffNormalizedTankStats(session.tankStats, career),
) as IndividualTankStats[]
).map((stats) => {
return {
stats,
tankopedia: awaitedTankopedia[stats.tank_id],
career: career[stats.tank_id],
currentWN8: awaitedTankAverages[stats.tank_id]
? calculateWN8(
awaitedTankAverages[stats.tank_id].all,
stats.all,
)
: undefined,
careerWN8: awaitedTankAverages[stats.tank_id]
? calculateWN8(
awaitedTankAverages[stats.tank_id].all,
career[stats.tank_id].all,
)
: undefined,
};
}),
});
}
}
recalculateDiff();
const interval = setInterval(recalculateDiff, 1000 / REFRESH_RATE);
return () => clearInterval(interval);
}, [session]);
return (
<Breakdown.Root>
{session.isTracking && since !== undefined && diff !== undefined && (
<Breakdown.Row
title={`Since ${since.toLocaleString(undefined, {
hour: 'numeric',
minute: 'numeric',
hour12: true,
month: 'short',
year: 'numeric',
day: 'numeric',
})}`}
stats={[
{
title: 'Battles',
current: sessionBattles!.toLocaleString(),
career: tankStatsArray
.reduce(
(accumulator, stats) => accumulator + stats.all.battles,
0,
)
.toLocaleString(),
},
{
title: 'Winrate',
current: sessionBattles
? `${(100 * sessionWinrate).toFixed(2)}%`
: '--',
career: `${(100 * careerWinrate).toFixed(2)}%`,
delta: sessionBattles === 0 ? 0 : sessionWinrate - careerWinrate,
},
(() => {
const battlesWithWN8 = diff.list.reduce(
(accumulator, { currentWN8, stats }) =>
(currentWN8 === undefined ? 0 : stats.all.battles) +
accumulator,
0,
);
const currentWN8 =
diff.list.reduce(
(accumulator, { currentWN8, stats }) =>
stats.all.battles * (currentWN8 ?? 0) + accumulator,
0,
) / battlesWithWN8;
return {
title: 'WN8',
current: battlesWithWN8
? Math.round(currentWN8).toLocaleString()
: '--',
percentile: battlesWithWN8
? getWN8Percentile(currentWN8)
: undefined,
career: Math.round(diff.careerWN8).toLocaleString(),
};
})(),
{
title: 'Damage',
delta: sessionBattles === 0 ? 0 : currentDamage - careerDamage,
current: sessionBattles
? Math.round(currentDamage).toLocaleString()
: '--',
career: Math.round(careerDamage).toLocaleString(),
},
]}
/>
)}
{diff?.list
.sort((a, b) => b.stats.last_battle_time - a.stats.last_battle_time)
.map(({ stats, tankopedia, career, careerWN8, currentWN8 }) => (
<Breakdown.Row
key={stats.tank_id}
title={tankopedia?.name ?? `Unknown tank ${stats.tank_id}`}
type="tank"
tankType={tankopedia?.type}
treeType={(() => {
if (tankopedia?.is_collectible) return TreeTypeEnum.Collector;
if (tankopedia?.is_premium) return TreeTypeEnum.Premium;
})()}
stats={[
{
title: 'Battles',
current: stats.all.battles.toLocaleString(),
career: career.all.battles.toLocaleString(),
},
{
title: 'Winrate',
current: `${(
100 *
(stats.all.wins / stats.all.battles)
).toFixed(2)}%`,
career: `${(
100 *
(career.all.wins / career.all.battles)
).toFixed(2)}%`,
delta:
stats.all.wins / stats.all.battles -
career.all.wins / career.all.battles,
},
{
title: 'WN8',
current:
currentWN8 === undefined
? undefined
: Math.round(currentWN8).toLocaleString(),
career:
careerWN8 === undefined
? undefined
: Math.round(careerWN8).toLocaleString(),
percentile:
currentWN8 === undefined
? undefined
: getWN8Percentile(currentWN8),
},
{
title: 'Damage',
current: Math.round(
stats.all.damage_dealt / stats.all.battles,
).toLocaleString(),
career: Math.round(
career.all.damage_dealt / career.all.battles,
).toLocaleString(),
delta:
stats.all.damage_dealt / stats.all.battles -
career.all.damage_dealt / career.all.battles,
},
]}
/>
))}
</Breakdown.Root>
);
}
+49
View File
@@ -0,0 +1,49 @@
'use client';
import { Flex, Theme } from '@radix-ui/themes';
import '@radix-ui/themes/styles.css';
import { config } from 'dotenv';
import { Roboto_Flex } from 'next/font/google';
import { usePathname } from 'next/navigation';
import { ReactNode } from 'react';
import Navbar from '../components/Navbar';
config();
interface RootLayoutProps {
children: ReactNode;
}
const robotoFlex = Roboto_Flex({
subsets: ['latin'],
display: 'swap',
});
export default function RootLayout({ children }: RootLayoutProps) {
const pathname = usePathname();
const isEmbed = pathname.split('/')[1] === 'embeds';
return (
<html lang="en" className={robotoFlex.className}>
<body
style={{
margin: 0,
background: isEmbed
? 'transparent'
: 'url(https://i.imgur.com/PhS06NJ.png)',
}}
>
<Theme
appearance="dark"
panelBackground="translucent"
radius="full"
accentColor="blue"
>
{!isEmbed && <Navbar />}
<Flex direction="column">{children}</Flex>
</Theme>
</body>
</html>
);
}
+15
View File
@@ -0,0 +1,15 @@
import { styleVariants } from '@vanilla-extract/css';
import { theme } from '../stitches.config';
export const tool = styleVariants({
enabled: {
boxShadow: `inset 0 -192px 128px -128px ${theme.colors.appBackground1}`,
':hover': {
boxShadow: `inset 0 -160px 128px -128px ${theme.colors.appBackground1}`,
},
},
disabled: {
boxShadow: `inset 0 -192px 128px -128px ${theme.colors.appBackground1}`,
},
});
+94
View File
@@ -0,0 +1,94 @@
'use client';
import { CaretRightIcon, MagnifyingGlassIcon } from '@radix-ui/react-icons';
import { Flex, Text, TextField } from '@radix-ui/themes';
import Link from 'next/link';
import { useRef } from 'react';
import { Search, SearchItem } from 'react-fuzzysort';
import PageWrapper from '../components/PageWrapper';
import { TOOLS } from '../constants/tools';
import { theme } from '../stitches.config';
import * as styles from './page.css';
export default function Page() {
const input = useRef<HTMLInputElement>(null);
return (
<PageWrapper>
<TextField.Root>
<TextField.Slot>
<MagnifyingGlassIcon height="16" width="16" />
</TextField.Slot>
<TextField.Input ref={input} placeholder="Search tools..." />
</TextField.Root>
<Flex gap="3" wrap="wrap" align="center" justify="center">
<Search
input={input}
list={TOOLS.map(
(tool) =>
({
node: (
<Link
href={`/tools/${tool.id}`}
className={
tool.disabled ? styles.tool.disabled : styles.tool.enabled
}
style={{
backgroundImage: `url(/assets/banners/${tool.id}.webp)`,
cursor: tool.disabled ? 'default' : 'pointer',
opacity: tool.disabled ? 0.25 : 1,
height: 128,
minWidth: 256,
flex: 1,
borderRadius: 8,
textDecoration: 'none',
backgroundSize: 'cover',
backgroundPosition: 'center',
overflow: 'hidden',
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
justifyContent: 'flex-end',
padding: 16,
gap: 4,
transition: `box-shadow ${theme.durations.regular}`,
}}
onClick={(event) => {
if (tool.disabled) event.preventDefault();
}}
>
<span
style={{
fontSize: 32,
color: theme.colors.textHighContrast,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
{tool.title}
<CaretRightIcon style={{ width: '1em', height: '1em' }} />
</span>
<span
style={{
color: theme.colors.textLowContrast,
fontSize: 16,
}}
>
{tool.disabled ? 'Coming soon!' : tool.description}
</span>
</Link>
),
query: `${tool.title} ${tool.description}`,
}) satisfies SearchItem,
)}
/>
</Flex>
<Text size="1" color="gray" align="center">
Images courtesy of Wargaming Art Department
</Text>
</PageWrapper>
);
}
+54
View File
@@ -0,0 +1,54 @@
'use client';
import { usePathname } from 'next/navigation';
import { ReactNode } from 'react';
import { TOOLS } from '../../constants/tools';
import { theme } from '../../stitches.config';
interface ToolsLayoutProps {
children: ReactNode;
}
export default function ToolsLayout({ children }: ToolsLayoutProps) {
const pathname = usePathname();
const toolId = pathname.split('/').at(-1);
const tool = TOOLS.find((tool) => tool.id === toolId);
return (
<>
<div
style={{
height: 128,
backgroundImage: `url(/assets/banners/${toolId}.png)`,
backgroundSize: 'cover',
backgroundPosition: 'center',
boxShadow: `inset 0 -192px 128px -128px ${theme.colors.appBackground1}`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 4,
flexDirection: 'column',
}}
>
<span
style={{
color: theme.colors.textHighContrast,
fontSize: 32,
}}
>
{tool?.title}
</span>
<span
style={{
color: theme.colors.textLowContrast,
fontSize: 16,
}}
>
{tool?.description}
</span>
</div>
{children}
</>
);
}
+40
View File
@@ -0,0 +1,40 @@
import { style } from '@vanilla-extract/css';
import { theme } from '../../../stitches.config';
export const toolBar = style({
'@media': {
'screen and (min-width: 640px)': {
flexDirection: 'row',
},
},
flexDirection: 'column',
display: 'flex',
gap: 8,
});
export const toolbarButton = style({
'@media': {
'screen and (min-width: 640px)': {
flex: 0,
},
},
flex: 1,
});
export const searchButton = style({
border: 'none',
background: 'none',
fontSize: 16,
display: 'flex',
padding: 8,
alignItems: 'center',
justifyContent: 'space-between',
cursor: 'pointer',
':hover': {
backgroundColor: theme.colors.componentInteractiveHover,
borderRadius: 4,
},
});
+225
View File
@@ -0,0 +1,225 @@
'use client';
import { CopyIcon, PersonIcon, ReloadIcon } from '@radix-ui/react-icons';
import {
AlertDialog,
Button,
Checkbox,
DropdownMenu,
Flex,
Text,
TextField,
Tooltip,
} from '@radix-ui/themes';
import { debounce } from 'lodash';
import { ChangeEvent, useRef, useState } from 'react';
import PageWrapper from '../../../components/PageWrapper';
import { REGION_NAMES, Region } from '../../../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID';
import getWargamingResponse from '../../../core/blitz/getWargamingResponse';
import listPlayers, {
AccountListWithServer,
} from '../../../core/blitz/listPlayers';
import { useSession } from '../../../stores/session';
import { NormalizedTankStats, TanksStats } from '../../../types/tanksStats';
import SessionPage from '../../embeds/session/page';
import * as styles from './page.css';
// 1 once per 5 seconds
export default function Page() {
const input = useRef<HTMLInputElement>(null);
const [searchResults, setSearchResults] = useState<
AccountListWithServer | undefined
>(undefined);
const [showSearchResults, setShowSearchResults] = useState(false);
const handleChange = debounce(
async (event: ChangeEvent<HTMLInputElement>) => {
if (event.target.value) {
setSearchResults(await listPlayers(event.target.value));
} else {
setSearchResults(undefined);
setShowSearchResults(false);
}
},
500,
);
const session = useSession();
async function setSession(region: Region, id: number, nickname: string) {
input.current!.value = nickname;
const rawTankStats = (
await getWargamingResponse<TanksStats>(
`https://api.wotblitz.${region}/wotb/tanks/stats/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
)
)[id];
const tankStats = rawTankStats.reduce<NormalizedTankStats>(
(accumulator, tank) => ({
...accumulator,
[tank.tank_id]: tank,
}),
{},
);
useSession.setState({
isTracking: true,
id,
region,
nickname,
tankStats,
time: Date.now(),
});
}
function reset() {
const session = useSession.getState();
if (!session.isTracking) return;
setSession(session.region, session.id, session.nickname);
}
return (
<PageWrapper>
<div className={styles.toolBar}>
<div style={{ flex: 1, boxSizing: 'border-box', position: 'relative' }}>
<TextField.Root>
<TextField.Slot>
<PersonIcon height="16" width="16" />
</TextField.Slot>
<TextField.Input
defaultValue={session.isTracking ? session.nickname : undefined}
ref={input}
onChange={(event) => {
event.stopPropagation();
if (event.target.value) {
setShowSearchResults(true);
setSearchResults(undefined);
} else {
setShowSearchResults(false);
}
handleChange(event);
}}
onBlur={(event) => {
// TODO: remove this hack when https://github.com/radix-ui/primitives/issues/2193 is fixed
if (showSearchResults) event.target.focus();
}}
onKeyDown={(event) => {
if (event.key === 'Escape') {
setShowSearchResults(false);
input.current?.blur();
}
}}
placeholder="Search for a player..."
/>
</TextField.Root>
<DropdownMenu.Root open={showSearchResults} modal={false}>
<DropdownMenu.Trigger>
<div />
</DropdownMenu.Trigger>
<DropdownMenu.Content>
{searchResults === undefined ? (
<DropdownMenu.Item disabled>Searching...</DropdownMenu.Item>
) : searchResults.length === 0 ? (
<DropdownMenu.Item disabled>No results</DropdownMenu.Item>
) : (
searchResults?.map(
({ account_id: id, nickname, region }, index) => (
<DropdownMenu.Item
key={id}
onClick={() => {
setShowSearchResults(false);
setSession(region, id, nickname);
}}
shortcut={REGION_NAMES[region]}
>
{nickname}
</DropdownMenu.Item>
),
)
)}
</DropdownMenu.Content>
</DropdownMenu.Root>
</div>
<div style={{ display: 'flex', gap: 8 }}>
<AlertDialog.Root>
<AlertDialog.Trigger>
<Button
onClick={(event) => {
if (!session.promptBeforeReset) {
event.preventDefault();
reset();
}
}}
className={styles.toolbarButton}
color="red"
>
<ReloadIcon width="16" height="16" /> Reset
</Button>
</AlertDialog.Trigger>
<AlertDialog.Content>
<Flex direction="column" gap="4">
<Flex direction="column" gap="0">
<AlertDialog.Title>
Are you sure you want to reset?
</AlertDialog.Title>
<AlertDialog.Description>
This'll discard your current session and start tracking from
scratch again.
</AlertDialog.Description>
</Flex>
<Text size="2">
<Checkbox
mr="1"
onCheckedChange={(checked) =>
useSession.setState({
promptBeforeReset: !checked,
})
}
/>
Don't ask me again
</Text>
</Flex>
<Flex gap="3" mt="4">
<AlertDialog.Cancel>
<Button variant="soft" color="gray">
Cancel
</Button>
</AlertDialog.Cancel>
<AlertDialog.Action>
<Button variant="solid" color="red" onClick={reset}>
Reset
</Button>
</AlertDialog.Action>
</Flex>
</AlertDialog.Content>
</AlertDialog.Root>
<Tooltip content="Copy to clipboard">
<Button
className={styles.toolbarButton}
onClick={() =>
navigator.clipboard.writeText(
`${location.origin}/embeds/session`,
)
}
>
<CopyIcon width="16" height="16" /> Embed
</Button>
</Tooltip>
</div>
</div>
<SessionPage />
</PageWrapper>
);
}
@@ -8,14 +8,14 @@ const RAW_PATH = `https://raw.githubusercontent.com/tresabhi/blitzkrieg/main/doc
const DOCS = { const DOCS = {
permissions: 'guide/permissions', permissions: 'guide/permissions',
embeds: 'guide/embeds', embeds: 'guide/embeds',
about: 'guide/about', introduction: 'guide/introduction',
invite: 'guide/invite', invite: 'guide/invite',
timezones: 'guide/timezones', timezones: 'guide/timezones',
}; };
const DOC_DESCRIPTIONS: Record<keyof typeof DOCS, string> = { const DOC_DESCRIPTIONS: Record<keyof typeof DOCS, string> = {
permissions: 'The permissions needed for the bot to function', permissions: 'The permissions needed for the bot to function',
embeds: 'How embeds work', embeds: 'How embeds work',
about: 'About the bot', introduction: 'About the bot',
invite: 'How to invite the bot', invite: 'How to invite the bot',
timezones: 'How Blitzkrieg infers and uses your timezone', timezones: 'How Blitzkrieg infers and uses your timezone',
}; };
@@ -28,15 +28,14 @@ function addDocsSubcommands(option: SlashCommandSubcommandsOnlyBuilder) {
return option; return option;
} }
export const helpCommand: CommandRegistry = { export const aboutCommand: CommandRegistry = {
inProduction: true, inProduction: true,
inDevelopment: true,
inPublic: true, inPublic: true,
command: addDocsSubcommands( command: addDocsSubcommands(
new SlashCommandBuilder() new SlashCommandBuilder()
.setName('help') .setName('about')
.setDescription('All the help you need about the bot') .setDescription('All the info you need about the bot')
.addSubcommand((option) => .addSubcommand((option) =>
option option
.setName('commands') .setName('commands')
+97
View File
@@ -0,0 +1,97 @@
import { SlashCommandBuilder, escapeMarkdown } from 'discord.js';
import markdownEscape from 'markdown-escape';
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
import getWargamingResponse from '../core/blitz/getWargamingResponse';
import { PlayerHistoriesRaw } from '../core/blitzstars/getPlayerHistories';
import getTimeDaysAgo from '../core/blitzstars/getTimeDaysAgo';
import addClanChoices from '../core/discord/addClanChoices';
import autocompleteClan from '../core/discord/autocompleteClan';
import embedWarning from '../core/discord/embedWarning';
import resolveClanFromCommand from '../core/discord/resolveClanFromCommand';
import { CommandRegistry } from '../events/interactionCreate';
import { AccountAchievements } from '../types/accountAchievements';
import { AccountInfo } from '../types/accountInfo';
import { ClanInfo } from '../types/clanInfo';
export const clanEventCommand: CommandRegistry = {
inProduction: false,
inPublic: true,
command: new SlashCommandBuilder()
.setName('clan-event')
.setDescription('Lists all inactive players')
.addStringOption(addClanChoices),
async handler(interaction) {
const { region, id } = await resolveClanFromCommand(interaction);
const clan = (
await getWargamingResponse<ClanInfo>(
`https://api.wotblitz.${region}/wotb/clans/info/?application_id=${WARGAMING_APPLICATION_ID}&clan_id=${id}`,
)
)[id];
const memberIds = clan.members_ids.join(',');
const [players, achievements, previousJointVictories] = await Promise.all([
getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${region}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${memberIds}`,
),
getWargamingResponse<AccountAchievements>(
`https://api.wotblitz.${region}/wotb/account/achievements/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${memberIds}`,
),
Promise.all(
clan.members_ids.map((id) =>
fetch(`https://www.blitzstars.com/api/playerstats/${id}`)
.then((response) => response.json() as Promise<PlayerHistoriesRaw>)
.then((data) => ({
id,
jointVictory: data[0]?.achievements.jointVictoryCount as
| number
| undefined,
})),
),
).then((data) =>
data.reduce<Record<number, number | undefined>>(
(accumulator, value) => ({
...accumulator,
[value.id]: value.jointVictory,
}),
{},
),
),
]);
const jointVictories = clan.members_ids.map((id) => ({
id,
joinVictory:
players[id].last_battle_time > getTimeDaysAgo(region, 1)
? previousJointVictories[id] === undefined
? 0
: achievements[id].max_series.jointVictory -
previousJointVictories[id]!
: 0,
}));
return [
`# ${escapeMarkdown(clan.name)} [${escapeMarkdown(
clan.tag,
)}]'s platoon wins today: ${(
jointVictories.reduce(
(accumulator, { joinVictory }) => accumulator + joinVictory,
0,
) / 2
).toFixed(0)}\n${jointVictories
.filter(({ joinVictory }) => joinVictory)
.sort((a, b) => b.joinVictory - a.joinVictory)
.map(
({ id, joinVictory }) =>
`- ${markdownEscape(players[id].nickname)}: ${joinVictory}`,
)
.join('\n')}`,
embedWarning(
'This is an approximation!',
"Wargaming provides very little information about platoons publicly. Caveats:\n- Players in two different clans platooning artificially inflates the total count\n- Game-mode battles aren't counted since Wargaming doesn't publicly share that data\n- This command is also subject to Blitzkrieg's reset times (use `/about timezones` for more info)",
),
];
},
autocomplete: autocompleteClan,
};
-1
View File
@@ -12,7 +12,6 @@ const executionStart = new Date().getTime();
export const debugCommand: CommandRegistry = { export const debugCommand: CommandRegistry = {
inProduction: true, inProduction: true,
inDevelopment: true,
inPublic: true, inPublic: true,
command: new SlashCommandBuilder() command: new SlashCommandBuilder()
+2 -3
View File
@@ -1,4 +1,5 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from 'discord.js';
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
import calculateWN8 from '../core/blitz/calculateWN8'; import calculateWN8 from '../core/blitz/calculateWN8';
import getWargamingResponse from '../core/blitz/getWargamingResponse'; import getWargamingResponse from '../core/blitz/getWargamingResponse';
import sumStats from '../core/blitz/sumStats'; import sumStats from '../core/blitz/sumStats';
@@ -12,7 +13,6 @@ import embedNegative from '../core/discord/embedNegative';
import embedPositive from '../core/discord/embedPositive'; import embedPositive from '../core/discord/embedPositive';
import markdownTable, { TableInput } from '../core/discord/markdownTable'; import markdownTable, { TableInput } from '../core/discord/markdownTable';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { secrets } from '../core/node/secrets';
import { CommandRegistry } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
import { AccountInfo } from '../types/accountInfo'; import { AccountInfo } from '../types/accountInfo';
import { PossiblyPromise } from '../types/possiblyPromise'; import { PossiblyPromise } from '../types/possiblyPromise';
@@ -26,7 +26,6 @@ export const SKILLED_CLANS: Record<SkilledClan, string> = {
export const eligibleCommand: CommandRegistry = { export const eligibleCommand: CommandRegistry = {
inProduction: true, inProduction: true,
inDevelopment: true,
inPublic: false, inPublic: false,
command: new SlashCommandBuilder() command: new SlashCommandBuilder()
@@ -51,7 +50,7 @@ export const eligibleCommand: CommandRegistry = {
const clan = interaction.options.getString('clan') as SkilledClan; const clan = interaction.options.getString('clan') as SkilledClan;
const { id, region: server } = await resolvePlayerFromCommand(interaction); const { id, region: server } = await resolvePlayerFromCommand(interaction);
const accountInfo = await getWargamingResponse<AccountInfo>( const accountInfo = await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`, `https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
); );
const problems: TableInput = []; const problems: TableInput = [];
const title = `${accountInfo[id].nickname}'s eligibility for ${SKILLED_CLANS[clan]}`; const title = `${accountInfo[id].nickname}'s eligibility for ${SKILLED_CLANS[clan]}`;
+2 -3
View File
@@ -1,5 +1,6 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from 'discord.js';
import { CYCLIC_API } from '../constants/cyclic'; import { CYCLIC_API } from '../constants/cyclic';
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
import getWargamingResponse from '../core/blitz/getWargamingResponse'; import getWargamingResponse from '../core/blitz/getWargamingResponse';
import resolveTankId from '../core/blitz/resolveTankId'; import resolveTankId from '../core/blitz/resolveTankId';
import addStatTypeSubCommandGroups from '../core/discord/addStatTypeSubCommandGroups'; import addStatTypeSubCommandGroups from '../core/discord/addStatTypeSubCommandGroups';
@@ -12,7 +13,6 @@ import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton';
import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand'; import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand';
import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton'; import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { secrets } from '../core/node/secrets';
import { CommandRegistryRaw } from '../events/interactionCreate'; import { CommandRegistryRaw } from '../events/interactionCreate';
import evolution from '../renderers/evolution'; import evolution from '../renderers/evolution';
import { StatType } from '../renderers/stats'; import { StatType } from '../renderers/stats';
@@ -29,7 +29,6 @@ export const evolutionCommand = new Promise<CommandRegistryRaw>(
resolve({ resolve({
inProduction: true, inProduction: true,
inDevelopment: true,
inPublic: true, inPublic: true,
command, command,
@@ -47,7 +46,7 @@ export const evolutionCommand = new Promise<CommandRegistryRaw>(
const end = interaction.options.getInteger('end'); const end = interaction.options.getInteger('end');
const { nickname } = ( const { nickname } = (
await getWargamingResponse<AccountInfo>( await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${player.region}/wotb/account/info/?application_id=${secrets.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]; )[player.id];
const path = interactionToURL(interaction, { const path = interactionToURL(interaction, {
+2 -3
View File
@@ -1,6 +1,7 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from 'discord.js';
import { TreeTypeString } from '../components/Tanks'; import { TreeTypeString } from '../components/Tanks';
import { CYCLIC_API } from '../constants/cyclic'; import { CYCLIC_API } from '../constants/cyclic';
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
import getWargamingResponse from '../core/blitz/getWargamingResponse'; import getWargamingResponse from '../core/blitz/getWargamingResponse';
import resolveTankId from '../core/blitz/resolveTankId'; import resolveTankId from '../core/blitz/resolveTankId';
import addStatTypeSubCommandGroups from '../core/discord/addStatTypeSubCommandGroups'; import addStatTypeSubCommandGroups from '../core/discord/addStatTypeSubCommandGroups';
@@ -13,7 +14,6 @@ import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton';
import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand'; import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand';
import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton'; import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { secrets } from '../core/node/secrets';
import { CommandRegistryRaw } from '../events/interactionCreate'; import { CommandRegistryRaw } from '../events/interactionCreate';
import fullStats from '../renderers/fullStats'; import fullStats from '../renderers/fullStats';
import { MultiTankFilters, StatType } from '../renderers/stats'; import { MultiTankFilters, StatType } from '../renderers/stats';
@@ -29,7 +29,6 @@ export const fullStatsCommand = new Promise<CommandRegistryRaw>(
resolve({ resolve({
inProduction: true, inProduction: true,
inDevelopment: true,
inPublic: true, inPublic: true,
command, command,
@@ -58,7 +57,7 @@ export const fullStatsCommand = new Promise<CommandRegistryRaw>(
}); });
const { nickname } = ( const { nickname } = (
await getWargamingResponse<AccountInfo>( await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${player.region}/wotb/account/info/?application_id=${secrets.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]; )[player.id];
+7 -9
View File
@@ -3,11 +3,11 @@ import GenericStats from '../components/GenericStats';
import NoData, { NoDataType } from '../components/NoData'; import NoData, { NoDataType } from '../components/NoData';
import TitleBar from '../components/TitleBar'; import TitleBar from '../components/TitleBar';
import Wrapper from '../components/Wrapper'; import Wrapper from '../components/Wrapper';
import getBlitzClan from '../core/blitz/getBlitzClan'; import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
import getWargamingResponse from '../core/blitz/getWargamingResponse'; import getWargamingResponse from '../core/blitz/getWargamingResponse';
import addClanChoices from '../core/discord/addClanChoices'; import addClanChoices from '../core/discord/addClanChoices';
import autocompleteClan from '../core/discord/autocompleteClan'; import autocompleteClan from '../core/discord/autocompleteClan';
import { secrets } from '../core/node/secrets'; import resolveClanFromCommand from '../core/discord/resolveClanFromCommand';
import { CommandRegistry } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
import { AccountInfo } from '../types/accountInfo'; import { AccountInfo } from '../types/accountInfo';
import { ClanInfo } from '../types/clanInfo'; import { ClanInfo } from '../types/clanInfo';
@@ -16,7 +16,6 @@ const DEFAULT_THRESHOLD = 7;
export const inactiveCommand: CommandRegistry = { export const inactiveCommand: CommandRegistry = {
inProduction: true, inProduction: true,
inDevelopment: true,
inPublic: true, inPublic: true,
command: new SlashCommandBuilder() command: new SlashCommandBuilder()
@@ -33,21 +32,20 @@ export const inactiveCommand: CommandRegistry = {
), ),
async handler(interaction) { async handler(interaction) {
const clanName = interaction.options.getString('clan')!; const { region, id } = await resolveClanFromCommand(interaction);
const { server, id } = await getBlitzClan(clanName);
const threshold = const threshold =
interaction.options.getNumber('threshold')! ?? DEFAULT_THRESHOLD; interaction.options.getNumber('threshold')! ?? DEFAULT_THRESHOLD;
const time = new Date().getTime() / 1000; const time = new Date().getTime() / 1000;
const clanData = ( const clanData = (
await getWargamingResponse<ClanInfo>( await getWargamingResponse<ClanInfo>(
`https://api.wotblitz.${server}/wotb/clans/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&clan_id=${id}`, `https://api.wotblitz.${region}/wotb/clans/info/?application_id=${WARGAMING_APPLICATION_ID}&clan_id=${id}`,
) )
)[id]; )[id];
const memberIds = clanData.members_ids; const memberIds = clanData.members_ids;
const accounts = await getWargamingResponse<AccountInfo>( const accounts = await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${ `https://api.wotblitz.${region}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${memberIds.join(
secrets.WARGAMING_APPLICATION_ID ',',
}&account_id=${memberIds.join(',')}`, )}`,
); );
const inactive = memberIds const inactive = memberIds
.map((memberId) => { .map((memberId) => {
+3 -4
View File
@@ -3,6 +3,7 @@ import NoData, { NoDataType } from '../components/NoData';
import * as Tanks from '../components/Tanks'; import * as Tanks from '../components/Tanks';
import TitleBar from '../components/TitleBar'; import TitleBar from '../components/TitleBar';
import Wrapper from '../components/Wrapper'; import Wrapper from '../components/Wrapper';
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
import getTankStats from '../core/blitz/getTankStats'; import getTankStats from '../core/blitz/getTankStats';
import getTreeType from '../core/blitz/getTreeType'; import getTreeType from '../core/blitz/getTreeType';
import getWargamingResponse from '../core/blitz/getWargamingResponse'; import getWargamingResponse from '../core/blitz/getWargamingResponse';
@@ -18,7 +19,6 @@ import addTierChoices from '../core/discord/addTierChoices';
import addUsernameChoices from '../core/discord/addUsernameChoices'; import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername'; import autocompleteUsername from '../core/discord/autocompleteUsername';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { secrets } from '../core/node/secrets';
import { CommandRegistry } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
import { AccountInfo } from '../types/accountInfo'; import { AccountInfo } from '../types/accountInfo';
import { PlayerClanData } from '../types/playerClanData'; import { PlayerClanData } from '../types/playerClanData';
@@ -46,7 +46,6 @@ const COMP_TANKS = [
export const ownedTanksCommand: CommandRegistry = { export const ownedTanksCommand: CommandRegistry = {
inProduction: true, inProduction: true,
inDevelopment: true,
inPublic: true, inPublic: true,
command: new SlashCommandBuilder() command: new SlashCommandBuilder()
@@ -60,7 +59,7 @@ export const ownedTanksCommand: CommandRegistry = {
const account = await resolvePlayerFromCommand(interaction); const account = await resolvePlayerFromCommand(interaction);
const { id, region: server } = account; const { id, region: server } = account;
const accountInfo = await getWargamingResponse<AccountInfo>( const accountInfo = await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`, `https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
); );
const tankStats = await getTankStats(server, id); const tankStats = await getTankStats(server, id);
const filteredTanks = ( const filteredTanks = (
@@ -72,7 +71,7 @@ export const ownedTanksCommand: CommandRegistry = {
) )
).filter((tank) => tank.tankopedia?.tier === tier); ).filter((tank) => tank.tankopedia?.tier === tier);
const clanData = await getWargamingResponse<PlayerClanData>( const clanData = await getWargamingResponse<PlayerClanData>(
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`, `https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`,
); );
const groupedTanks: Record<string, TankopediaEntry[]> = {}; const groupedTanks: Record<string, TankopediaEntry[]> = {};
const nations: string[] = []; const nations: string[] = [];
-1
View File
@@ -3,7 +3,6 @@ import { CommandRegistry } from '../events/interactionCreate';
export const pingCommand: CommandRegistry = { export const pingCommand: CommandRegistry = {
inProduction: true, inProduction: true,
inDevelopment: true,
inPublic: true, inPublic: true,
handlesInteraction: true, handlesInteraction: true,
+3 -4
View File
@@ -1,13 +1,13 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from 'discord.js';
import { startCase } from 'lodash'; import { startCase } from 'lodash';
import markdownEscape from 'markdown-escape'; import markdownEscape from 'markdown-escape';
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
import getWargamingResponse from '../core/blitz/getWargamingResponse'; import getWargamingResponse from '../core/blitz/getWargamingResponse';
import addUsernameChoices from '../core/discord/addUsernameChoices'; import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername'; import autocompleteUsername from '../core/discord/autocompleteUsername';
import embedInfo from '../core/discord/embedInfo'; import embedInfo from '../core/discord/embedInfo';
import markdownTable, { TableInputEntry } from '../core/discord/markdownTable'; import markdownTable, { TableInputEntry } from '../core/discord/markdownTable';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { secrets } from '../core/node/secrets';
import { CommandRegistry } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
import { AccountAchievements } from '../types/accountAchievements'; import { AccountAchievements } from '../types/accountAchievements';
import { AccountInfo } from '../types/accountInfo'; import { AccountInfo } from '../types/accountInfo';
@@ -16,7 +16,6 @@ type SortBy = 'name' | 'count';
export const playerAchievementsCommand: CommandRegistry = { export const playerAchievementsCommand: CommandRegistry = {
inProduction: true, inProduction: true,
inDevelopment: true,
inPublic: true, inPublic: true,
command: new SlashCommandBuilder() command: new SlashCommandBuilder()
@@ -38,11 +37,11 @@ export const playerAchievementsCommand: CommandRegistry = {
const account = await resolvePlayerFromCommand(interaction); const account = await resolvePlayerFromCommand(interaction);
const { id, region: server } = account; const { id, region: server } = account;
const accounts = await getWargamingResponse<AccountInfo>( const accounts = await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`, `https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
); );
const accountsAchievements = const accountsAchievements =
await getWargamingResponse<AccountAchievements>( await getWargamingResponse<AccountAchievements>(
`https://api.wotblitz.${server}/wotb/account/achievements/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`, `https://api.wotblitz.${server}/wotb/account/achievements/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
); );
const accountAchievements = accountsAchievements[id]; const accountAchievements = accountsAchievements[id];
const compound = { const compound = {
+2 -3
View File
@@ -1,18 +1,17 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from 'discord.js';
import markdownEscape from 'markdown-escape'; import markdownEscape from 'markdown-escape';
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
import getWargamingResponse from '../core/blitz/getWargamingResponse'; import getWargamingResponse from '../core/blitz/getWargamingResponse';
import addUsernameChoices from '../core/discord/addUsernameChoices'; import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername'; import autocompleteUsername from '../core/discord/autocompleteUsername';
import embedInfo from '../core/discord/embedInfo'; import embedInfo from '../core/discord/embedInfo';
import markdownTable from '../core/discord/markdownTable'; import markdownTable from '../core/discord/markdownTable';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { secrets } from '../core/node/secrets';
import { CommandRegistry } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
import { AccountInfo } from '../types/accountInfo'; import { AccountInfo } from '../types/accountInfo';
export const playerInfoCommand: CommandRegistry = { export const playerInfoCommand: CommandRegistry = {
inProduction: true, inProduction: true,
inDevelopment: true,
inPublic: true, inPublic: true,
command: new SlashCommandBuilder() command: new SlashCommandBuilder()
@@ -24,7 +23,7 @@ export const playerInfoCommand: CommandRegistry = {
const account = await resolvePlayerFromCommand(interaction); const account = await resolvePlayerFromCommand(interaction);
const { id, region: server } = account; const { id, region: server } = account;
const accounts = await getWargamingResponse<AccountInfo>( const accounts = await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`, `https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
); );
const accountInfo = accounts[id]; const accountInfo = accounts[id];
+10 -9
View File
@@ -9,6 +9,7 @@ import * as Leaderboard from '../components/Leaderboard';
import TitleBar from '../components/TitleBar'; import TitleBar from '../components/TitleBar';
import Wrapper from '../components/Wrapper'; import Wrapper from '../components/Wrapper';
import { REGION_NAMES_SHORT, Region } from '../constants/regions'; import { REGION_NAMES_SHORT, Region } from '../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
import getArchivedRatingsInfo from '../core/blitz/getArchivedRatingsInfo'; import getArchivedRatingsInfo from '../core/blitz/getArchivedRatingsInfo';
import getRatingsInfo from '../core/blitz/getRatingsInfo'; import getRatingsInfo from '../core/blitz/getRatingsInfo';
import getWargamingResponse from '../core/blitz/getWargamingResponse'; import getWargamingResponse from '../core/blitz/getWargamingResponse';
@@ -20,8 +21,7 @@ import addRegionChoices from '../core/discord/addRegionChoices';
import addUsernameChoices from '../core/discord/addUsernameChoices'; import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername'; import autocompleteUsername from '../core/discord/autocompleteUsername';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { octokit } from '../core/express/octokit'; import { octokit } from '../core/github/octokit';
import { secrets } from '../core/node/secrets';
import throwError from '../core/node/throwError'; import throwError from '../core/node/throwError';
import { CommandRegistryRaw } from '../events/interactionCreate'; import { CommandRegistryRaw } from '../events/interactionCreate';
import { AccountInfo } from '../types/accountInfo'; import { AccountInfo } from '../types/accountInfo';
@@ -218,7 +218,6 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
resolve({ resolve({
inProduction: true, inProduction: true,
inDevelopment: true,
inPublic: true, inPublic: true,
command, command,
@@ -308,7 +307,7 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
); );
const ids = trimmed.map((player) => player.id).join(','); const ids = trimmed.map((player) => player.id).join(',');
const clanData = await getWargamingResponse<PlayerClanData>( const clanData = await getWargamingResponse<PlayerClanData>(
`https://api.wotblitz.${region}/wotb/clans/accountinfo/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${ids}&extra=clan`, `https://api.wotblitz.${region}/wotb/clans/accountinfo/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${ids}&extra=clan`,
); );
return trimmed.map( return trimmed.map(
@@ -317,8 +316,10 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
id: player.id, id: player.id,
score: player.score, score: player.score,
position: firstPlayerIndex + index + 1, position: firstPlayerIndex + index + 1,
clan: clanData[player.id]!.clan?.tag, clan: clanData[player.id]?.clan?.tag,
nickname: clanData[player.id]!.account_name, nickname:
clanData[player.id]?.account_name ??
`Unknown Player ${player.id}`,
}) satisfies SimplifiedPlayer, }) satisfies SimplifiedPlayer,
); );
}); });
@@ -372,11 +373,11 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
)) as RatingsInfo; )) as RatingsInfo;
}); });
const accountInfo = await getWargamingResponse<AccountInfo>( const accountInfo = await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${region}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`, `https://api.wotblitz.${region}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
); );
const clan = ( const clan = (
await getWargamingResponse<PlayerClanData>( await getWargamingResponse<PlayerClanData>(
`https://api.wotblitz.${region}/wotb/clans/accountinfo/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`, `https://api.wotblitz.${region}/wotb/clans/accountinfo/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`,
) )
)[id]?.clan; )[id]?.clan;
const regionSubdomain = regionToRegionSubdomain(region); const regionSubdomain = regionToRegionSubdomain(region);
@@ -439,7 +440,7 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
); );
const ids = trimmed.map((player) => player.id).join(','); const ids = trimmed.map((player) => player.id).join(',');
const clanData = await getWargamingResponse<PlayerClanData>( const clanData = await getWargamingResponse<PlayerClanData>(
`https://api.wotblitz.${region}/wotb/clans/accountinfo/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${ids}&extra=clan`, `https://api.wotblitz.${region}/wotb/clans/accountinfo/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${ids}&extra=clan`,
); );
return trimmed.map((player, index) => ({ return trimmed.map((player, index) => ({
+2 -3
View File
@@ -1,17 +1,16 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from 'discord.js';
import markdownEscape from 'markdown-escape'; import markdownEscape from 'markdown-escape';
import { REGION_NAMES, Region } from '../constants/regions'; import { REGION_NAMES, Region } from '../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
import getWargamingResponse from '../core/blitz/getWargamingResponse'; import getWargamingResponse from '../core/blitz/getWargamingResponse';
import addClanChoices from '../core/discord/addClanChoices'; import addClanChoices from '../core/discord/addClanChoices';
import addRegionChoices from '../core/discord/addRegionChoices'; import addRegionChoices from '../core/discord/addRegionChoices';
import embedInfo from '../core/discord/embedInfo'; import embedInfo from '../core/discord/embedInfo';
import { secrets } from '../core/node/secrets';
import { CommandRegistry } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
import { ClanList } from '../types/clanList'; import { ClanList } from '../types/clanList';
export const searchClansCommand: CommandRegistry = { export const searchClansCommand: CommandRegistry = {
inProduction: true, inProduction: true,
inDevelopment: true,
inPublic: true, inPublic: true,
command: new SlashCommandBuilder() command: new SlashCommandBuilder()
@@ -32,7 +31,7 @@ export const searchClansCommand: CommandRegistry = {
const clan = interaction.options.getString('clan')!; const clan = interaction.options.getString('clan')!;
const limit = interaction.options.getInteger('limit') ?? 25; const limit = interaction.options.getInteger('limit') ?? 25;
const clanList = await getWargamingResponse<ClanList>( const clanList = await getWargamingResponse<ClanList>(
`https://api.wotblitz.${server}/wotb/clans/list/?application_id=${secrets.WARGAMING_APPLICATION_ID}&search=${clan}&limit=${limit}`, `https://api.wotblitz.${server}/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${clan}&limit=${limit}`,
); );
return embedInfo( return embedInfo(
+5 -7
View File
@@ -1,18 +1,16 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from 'discord.js';
import markdownEscape from 'markdown-escape'; import markdownEscape from 'markdown-escape';
import { REGION_NAMES, Region } from '../constants/regions'; import { REGION_NAMES, Region } from '../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
import getWargamingResponse from '../core/blitz/getWargamingResponse'; import getWargamingResponse from '../core/blitz/getWargamingResponse';
import { usernamePattern } from '../core/blitz/listPlayers'; import { AccountList, usernamePattern } from '../core/blitz/listPlayers';
import addRegionChoices from '../core/discord/addRegionChoices'; import addRegionChoices from '../core/discord/addRegionChoices';
import addUsernameChoices from '../core/discord/addUsernameChoices'; import addUsernameChoices from '../core/discord/addUsernameChoices';
import embedInfo from '../core/discord/embedInfo'; import embedInfo from '../core/discord/embedInfo';
import { secrets } from '../core/node/secrets';
import { CommandRegistry } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
import { AccountList } from '../types/accountList';
export const searchPlayersCommand: CommandRegistry = { export const searchPlayersCommand: CommandRegistry = {
inProduction: true, inProduction: true,
inDevelopment: true,
inPublic: true, inPublic: true,
command: new SlashCommandBuilder() command: new SlashCommandBuilder()
@@ -37,9 +35,9 @@ export const searchPlayersCommand: CommandRegistry = {
const trimmedSearch = name.trim(); const trimmedSearch = name.trim();
const players = usernamePattern.test(trimmedSearch) const players = usernamePattern.test(trimmedSearch)
? await getWargamingResponse<AccountList>( ? await getWargamingResponse<AccountList>(
`https://api.wotblitz.${server}/wotb/account/list/?application_id=${ `https://api.wotblitz.${server}/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodeURIComponent(
secrets.WARGAMING_APPLICATION_ID trimmedSearch,
}&search=${encodeURIComponent(trimmedSearch)}&limit=${limit}`, )}&limit=${limit}`,
) )
: []; : [];
-1
View File
@@ -8,7 +8,6 @@ import { CommandRegistry } from '../events/interactionCreate';
export const searchTanksCommand: CommandRegistry = { export const searchTanksCommand: CommandRegistry = {
inProduction: true, inProduction: true,
inDevelopment: true,
inPublic: true, inPublic: true,
command: new SlashCommandBuilder() command: new SlashCommandBuilder()
+3 -4
View File
@@ -1,6 +1,7 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from 'discord.js';
import { TreeTypeString } from '../components/Tanks'; import { TreeTypeString } from '../components/Tanks';
import { CYCLIC_API } from '../constants/cyclic'; import { CYCLIC_API } from '../constants/cyclic';
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
import getWargamingResponse from '../core/blitz/getWargamingResponse'; import getWargamingResponse from '../core/blitz/getWargamingResponse';
import resolveTankId from '../core/blitz/resolveTankId'; import resolveTankId from '../core/blitz/resolveTankId';
import addStatTypeSubCommandGroups from '../core/discord/addStatTypeSubCommandGroups'; import addStatTypeSubCommandGroups from '../core/discord/addStatTypeSubCommandGroups';
@@ -13,7 +14,6 @@ import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton';
import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand'; import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand';
import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton'; import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { secrets } from '../core/node/secrets';
import { CommandRegistryRaw } from '../events/interactionCreate'; import { CommandRegistryRaw } from '../events/interactionCreate';
import stats, { MultiTankFilters, StatType } from '../renderers/stats'; import stats, { MultiTankFilters, StatType } from '../renderers/stats';
import { AccountInfo } from '../types/accountInfo'; import { AccountInfo } from '../types/accountInfo';
@@ -27,7 +27,6 @@ export const statsCommand = new Promise<CommandRegistryRaw>(async (resolve) => {
resolve({ resolve({
inProduction: true, inProduction: true,
inDevelopment: true,
inPublic: true, inPublic: true,
command, command,
@@ -56,7 +55,7 @@ export const statsCommand = new Promise<CommandRegistryRaw>(async (resolve) => {
}); });
const { nickname } = ( const { nickname } = (
await getWargamingResponse<AccountInfo>( await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${player.region}/wotb/account/info/?application_id=${secrets.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]; )[player.id];
@@ -81,7 +80,7 @@ export const statsCommand = new Promise<CommandRegistryRaw>(async (resolve) => {
: null, : null,
), ),
primaryButton(path, 'Refresh'), primaryButton(path, 'Refresh'),
linkButton(`${CYCLIC_API}/${path}`, 'Embed'), // linkButton(`${CYCLIC_API}/${path}`, 'Embed'),
linkButton( linkButton(
`https://www.blitzstars.com/player/${player.region}/${nickname}`, `https://www.blitzstars.com/player/${player.region}/${nickname}`,
'BlitzStars', 'BlitzStars',
+3 -5
View File
@@ -1,5 +1,5 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from 'discord.js';
import { CYCLIC_API } from '../constants/cyclic'; import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
import getWargamingResponse from '../core/blitz/getWargamingResponse'; import getWargamingResponse from '../core/blitz/getWargamingResponse';
import addUsernameChoices from '../core/discord/addUsernameChoices'; import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername'; import autocompleteUsername from '../core/discord/autocompleteUsername';
@@ -8,14 +8,12 @@ import linkButton from '../core/discord/linkButton';
import primaryButton from '../core/discord/primaryButton'; import primaryButton from '../core/discord/primaryButton';
import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton'; import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { secrets } from '../core/node/secrets';
import { CommandRegistry } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
import today from '../renderers/today'; import today from '../renderers/today';
import { AccountInfo } from '../types/accountInfo'; import { AccountInfo } from '../types/accountInfo';
export const todayCommand: CommandRegistry = { export const todayCommand: CommandRegistry = {
inProduction: true, inProduction: true,
inDevelopment: true,
inPublic: true, inPublic: true,
command: new SlashCommandBuilder() command: new SlashCommandBuilder()
@@ -54,7 +52,7 @@ export const todayCommand: CommandRegistry = {
const showTotal = interaction.options.getBoolean('show-total') ?? undefined; const showTotal = interaction.options.getBoolean('show-total') ?? undefined;
const { nickname } = ( const { nickname } = (
await getWargamingResponse<AccountInfo>( await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${player.region}/wotb/account/info/?application_id=${secrets.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]; )[player.id];
const path = interactionToURL(interaction, { const path = interactionToURL(interaction, {
@@ -67,7 +65,7 @@ export const todayCommand: CommandRegistry = {
return [ return [
await today(player, cutoff, maximized, showTotal, false), await today(player, cutoff, maximized, showTotal, false),
primaryButton(path, 'Refresh'), primaryButton(path, 'Refresh'),
linkButton(`${CYCLIC_API}/${path}`, 'Embed'), // linkButton(`${CYCLIC_API}/${path}`, 'Embed'),
linkButton( linkButton(
`https://www.blitzstars.com/player/${player.region}/${nickname}`, `https://www.blitzstars.com/player/${player.region}/${nickname}`,
'BlitzStars', 'BlitzStars',
+3 -4
View File
@@ -1,20 +1,19 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from 'discord.js';
import markdownEscape from 'markdown-escape'; import markdownEscape from 'markdown-escape';
import discord from '../../discord.json' assert { type: 'json' }; import discord from '../../discord.json' assert { type: 'json' };
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
import getWargamingResponse from '../core/blitz/getWargamingResponse'; import getWargamingResponse from '../core/blitz/getWargamingResponse';
import addUsernameChoices from '../core/discord/addUsernameChoices'; import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername'; import autocompleteUsername from '../core/discord/autocompleteUsername';
import embedNegative from '../core/discord/embedNegative'; import embedNegative from '../core/discord/embedNegative';
import embedPositive from '../core/discord/embedPositive'; import embedPositive from '../core/discord/embedPositive';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { secrets } from '../core/node/secrets';
import { CommandRegistry } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
import { AccountInfo } from '../types/accountInfo'; import { AccountInfo } from '../types/accountInfo';
import { PlayerClanData } from '../types/playerClanData'; import { PlayerClanData } from '../types/playerClanData';
export const verifyCommand: CommandRegistry = { export const verifyCommand: CommandRegistry = {
inProduction: true, inProduction: true,
inDevelopment: true,
inPublic: true, inPublic: true,
command: new SlashCommandBuilder() command: new SlashCommandBuilder()
@@ -26,10 +25,10 @@ export const verifyCommand: CommandRegistry = {
const blitzAccount = await resolvePlayerFromCommand(interaction); const blitzAccount = await resolvePlayerFromCommand(interaction);
const { id, region: server } = blitzAccount; const { id, region: server } = blitzAccount;
const accountInfo = await getWargamingResponse<AccountInfo>( const accountInfo = await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`, `https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
); );
const clanData = await getWargamingResponse<PlayerClanData>( const clanData = await getWargamingResponse<PlayerClanData>(
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`, `https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`,
); );
const clan = clanData[id]?.clan; const clan = clanData[id]?.clan;
const clanTag = clan ? ` [${clan!.tag}]` : ''; const clanTag = clan ? ` [${clan!.tag}]` : '';
+51
View File
@@ -0,0 +1,51 @@
export function BlitzkriegWormWide() {
return (
<svg
width="74"
height="17"
viewBox="0 0 74 17"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M33.945 12.9858V0H36.875V7.50095L39.4608 4.63967H42.7879L36.875 10.7408V12.9858H33.945ZM39.7873 12.9858L37.0515 9.04164L38.8695 7.04314L43.2292 12.9858H39.7873Z"
fill="currentColor"
/>
<path
d="M43.8823 12.9858V4.63967H46.8123L46.8299 5.74016H46.8476C47.0712 5.3704 47.3624 5.06813 47.7213 4.83335C48.0861 4.59271 48.4979 4.47239 48.9568 4.47239C49.1216 4.47239 49.2775 4.48413 49.4246 4.50761C49.5775 4.53109 49.6835 4.55457 49.7423 4.57804V6.78783C49.6364 6.74674 49.5069 6.7174 49.354 6.69979C49.2069 6.67631 49.0421 6.66457 48.8598 6.66457C48.4773 6.66457 48.1037 6.78783 47.7389 7.03434C47.3742 7.27498 47.0888 7.59192 46.8829 7.98517V12.9858H43.8823Z"
fill="currentColor"
/>
<path
d="M50.7042 12.9858V4.63967H53.6872V12.9858H50.7042ZM50.5366 1.84883C50.5366 1.42624 50.6807 1.07115 50.969 0.78355C51.2573 0.495955 51.6691 0.35216 52.2045 0.35216C52.7399 0.35216 53.1488 0.493019 53.4313 0.774745C53.7137 1.05647 53.8549 1.41156 53.8549 1.84002C53.8549 2.26261 53.7078 2.62357 53.4136 2.9229C53.1253 3.21637 52.7164 3.3631 52.1869 3.3631C51.6574 3.3631 51.2485 3.21637 50.9602 2.9229C50.6778 2.62357 50.5366 2.26555 50.5366 1.84883Z"
fill="currentColor"
/>
<path
d="M54.9492 8.92719V8.64546C54.9492 7.34835 55.3611 6.31535 56.1848 5.54648C57.0143 4.77173 58.1175 4.38436 59.4942 4.38436C60.8475 4.38436 61.8918 4.75412 62.6272 5.49365C63.3685 6.23318 63.7392 7.24857 63.7392 8.53981V9.36739H56.5731V7.86191H60.9063V7.77387C60.9063 7.38063 60.7857 7.05488 60.5445 6.79663C60.3091 6.53838 59.9502 6.40926 59.4678 6.40926C58.9441 6.40926 58.5441 6.59708 58.2675 6.97271C57.9969 7.34835 57.8616 7.81202 57.8616 8.36374V8.97121C57.8616 9.66966 58.0086 10.2096 58.3028 10.5911C58.6029 10.9726 59.0324 11.1634 59.5913 11.1634C59.9973 11.1634 60.3385 11.0783 60.6151 10.9081C60.8975 10.732 61.1152 10.506 61.2681 10.2302L63.6686 10.9873C63.3921 11.674 62.9243 12.2169 62.2654 12.616C61.6064 13.0093 60.7239 13.2059 59.6178 13.2059C58.141 13.2059 56.9937 12.8244 56.1759 12.0614C55.3581 11.2925 54.9492 10.2478 54.9492 8.92719Z"
fill="currentColor"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M68.399 14.8787C68.9756 14.8787 69.4874 14.7114 69.9346 14.3768C70.3876 14.0423 70.6141 13.47 70.6141 12.6601V11.9029C70.3611 12.261 70.0287 12.5515 69.6169 12.7745C69.205 12.9975 68.6608 13.1091 67.9842 13.1091C66.9369 13.1091 66.0956 12.7246 65.4601 11.9557C64.8306 11.181 64.5158 10.1773 64.5158 8.94479V8.66307C64.5158 7.43052 64.8188 6.42393 65.4248 5.64332C66.0367 4.85683 66.8898 4.46359 67.9842 4.46359C68.549 4.46359 69.0668 4.58098 69.5374 4.81575C70.0081 5.05052 70.3582 5.34692 70.5876 5.70495H70.6141L70.6318 4.63967H73.5V12.2023C73.5 13.7165 73.0852 14.8728 72.2556 15.671C71.4261 16.4751 70.3082 16.8771 68.902 16.8771H0V14.8787H68.399ZM67.8342 10.5207C67.5694 10.1157 67.437 9.59042 67.437 8.94479V8.67187C67.437 8.03212 67.5694 7.50682 67.8342 7.09597C68.0989 6.68512 68.5078 6.47969 69.0609 6.47969C69.4374 6.47969 69.7551 6.58534 70.014 6.79663C70.2788 7.00793 70.47 7.26031 70.5876 7.55377V10.0541C70.4759 10.3299 70.2876 10.5765 70.0228 10.7936C69.7581 11.0108 69.4404 11.1194 69.0697 11.1194C68.5108 11.1194 68.0989 10.9198 67.8342 10.5207Z"
fill="currentColor"
/>
<path
d="M0 12.977V0H2.92118V5.61692C3.20359 5.24715 3.5566 4.95662 3.98021 4.74533C4.40383 4.52816 4.90687 4.41958 5.48934 4.41958C6.54837 4.41958 7.40443 4.80989 8.0575 5.59051C8.71057 6.36525 9.03711 7.36303 9.03711 8.58385V8.86558C9.03711 10.0923 8.70469 11.1135 8.03985 11.9293C7.37501 12.7452 6.51307 13.1531 5.45404 13.1531C4.87157 13.1531 4.35676 13.0533 3.90961 12.8538C3.46246 12.6542 3.12122 12.3813 2.88587 12.035H2.86822L2.8594 12.977H0ZM2.92118 10.0717C3.04473 10.3534 3.23595 10.6 3.49482 10.8112C3.7537 11.0225 4.092 11.1282 4.50973 11.1282C5.02748 11.1282 5.41579 10.9169 5.67467 10.4943C5.93943 10.0658 6.07181 9.52881 6.07181 8.88318V8.61907C6.07181 7.97344 5.93943 7.45694 5.67467 7.06957C5.41579 6.67633 5.00983 6.47971 4.45678 6.47971C4.07435 6.47971 3.75075 6.58535 3.48599 6.79665C3.22712 7.00794 3.03885 7.25445 2.92118 7.53618V10.0717Z"
fill="currentColor"
/>
<path d="M10.2815 12.977V0H13.238V12.977H10.2815Z" fill="currentColor" />
<path
d="M14.9236 12.977V4.63088H17.9065V12.977H14.9236ZM14.7559 1.84002C14.7559 1.41744 14.9001 1.06234 15.1883 0.774748C15.4766 0.487152 15.8885 0.343354 16.4239 0.343354C16.9593 0.343354 17.3682 0.484217 17.6506 0.765944C17.933 1.04767 18.0742 1.40276 18.0742 1.83122C18.0742 2.25381 17.9271 2.61477 17.633 2.91411C17.3447 3.20757 16.9358 3.3543 16.4062 3.3543C15.8767 3.3543 15.4678 3.20757 15.1795 2.91411C14.8971 2.61477 14.7559 2.25675 14.7559 1.84002Z"
fill="currentColor"
/>
<path
d="M18.8332 6.73502V4.63088H24.7726V6.73502H18.8332ZM20.1217 10.3887V5.15031L20.1923 5.00945V2.04252H23.0076V9.78119C23.0076 10.3329 23.0811 10.6909 23.2282 10.8553C23.3812 11.0137 23.6195 11.093 23.943 11.093C24.096 11.093 24.2402 11.0842 24.3755 11.0666C24.5108 11.0431 24.6432 11.0049 24.7726 10.9521V12.9418C24.6138 12.9946 24.3725 13.0445 24.0489 13.0915C23.7254 13.1443 23.3253 13.1707 22.8487 13.1707C21.9779 13.1707 21.3043 12.9535 20.8277 12.5192C20.357 12.079 20.1217 11.3688 20.1217 10.3887Z"
fill="currentColor"
/>
<path
d="M25.3551 12.977V11.3307L29.1323 6.74382H25.461V4.63088H32.8125V6.33004L29.0264 10.9257H33.0684V12.977H25.3551Z"
fill="currentColor"
/>
</svg>
);
}
+40 -63
View File
@@ -1,40 +1,26 @@
import getWN8Percentile from '../../../core/blitz/getWN8Percentile'; import { Percentile } from '../../../constants/percentiles';
import { TankType } from '../../../core/blitz/tankopedia'; import { TankType } from '../../../core/blitz/tankopedia';
import { theme } from '../../../stitches.config'; import { theme } from '../../../stitches.config';
import { TREE_TYPE_ICONS, TreeTypeEnum } from '../../Tanks'; import { TREE_TYPE_ICONS, TreeTypeEnum } from '../../Tanks';
import { RowStat } from './RowStat'; import { RowStat } from './RowStat';
export interface RowProps { interface RowProps {
name: string; type?: 'tank' | 'summary';
winrate: number; title: string;
careerWinrate: number; minimized?: boolean;
WN8?: number;
careerWN8?: number;
damage: number;
careerDamage: number;
battles: number;
careerBattles: number;
minimized: boolean;
isTank: boolean;
treeType?: TreeTypeEnum; treeType?: TreeTypeEnum;
tankType?: TankType; tankType?: TankType;
stats: {
title: string;
current?: number | string;
career?: number | string;
delta?: number;
percentile?: Percentile;
}[];
} }
export function Row({ export function Row(props: RowProps) {
isTank,
minimized,
name,
winrate,
careerWinrate,
WN8,
careerWN8,
damage,
careerDamage,
battles,
careerBattles,
treeType,
tankType,
}: RowProps) {
return ( return (
<div <div
style={{ style={{
@@ -55,26 +41,32 @@ export function Row({
backgroundColor: theme.colors.componentInteractive, backgroundColor: theme.colors.componentInteractive,
}} }}
> >
{isTank && treeType !== undefined && tankType !== undefined && ( {props.type === 'tank' && props.tankType !== undefined && (
<img <img
src={TREE_TYPE_ICONS[treeType][tankType]} src={
TREE_TYPE_ICONS[props.treeType ?? TreeTypeEnum.TechTree][
props.tankType
]
}
style={{ width: 16, height: 16 }} style={{ width: 16, height: 16 }}
/> />
)} )}
<span <span
style={{ style={{
color: isTank color:
? treeType === TreeTypeEnum.Collector props.type === 'tank'
? theme.colors.textLowContrast_blue ? props.treeType === TreeTypeEnum.Collector
: treeType === TreeTypeEnum.Premium ? theme.colors.textLowContrast_blue
? theme.colors.textLowContrast_amber : props.treeType === TreeTypeEnum.Premium
: theme.colors.textHighContrast ? theme.colors.textLowContrast_amber
: theme.colors.textLowContrast, : theme.colors.textHighContrast
: theme.colors.textLowContrast,
fontWeight: 900, fontWeight: 900,
fontSize: 16, fontSize: 16,
}} }}
> >
{name} {props.title}
</span> </span>
</div> </div>
@@ -85,31 +77,16 @@ export function Row({
paddingBottom: 8, paddingBottom: 8,
}} }}
> >
<RowStat {props.stats.map((row, index) => (
minimized={minimized} <RowStat
name={`Battles • ${careerBattles.toFixed(0)}`} minimized={props.minimized}
value={battles.toFixed(0)} key={index}
/> name={`${row.title} • ${row.career ?? '--'}`}
<RowStat value={row.current ?? '--'}
minimized={minimized} delta={row.delta}
name={`Winrate • ${(careerWinrate * 100).toFixed(2)}%`} percentile={row.percentile}
value={`${(winrate * 100).toFixed(2)}%`} />
delta={winrate - careerWinrate} ))}
/>
<RowStat
minimized={minimized}
name={`WN8 • ${
careerWN8 === undefined ? '--' : careerWN8.toFixed(0)
}`}
value={WN8 === undefined ? '--' : WN8.toFixed(0)}
percentile={WN8 === undefined ? undefined : getWN8Percentile(WN8)}
/>
<RowStat
minimized={minimized}
name={`Damage • ${careerDamage.toFixed(0)}`}
value={damage.toFixed(0)}
delta={damage - careerDamage}
/>
</div> </div>
</div> </div>
); );
@@ -4,7 +4,7 @@ import PercentileIndicator from '../../PercentileIndicator';
export interface RowStatProps { export interface RowStatProps {
name: string; name: string;
value: string | number; value?: string | number;
delta?: number; delta?: number;
percentile?: Percentile; percentile?: Percentile;
minimized?: boolean; minimized?: boolean;
-1
View File
@@ -1,4 +1,3 @@
export * from './components/Root'; export * from './components/Root';
export * from './components/Row'; export * from './components/Row';
export * from './components/RowStat'; export * from './components/RowStat';
@@ -76,7 +76,10 @@ export function Card({ items, title }: CardProps) {
<PercentileIndicator percentile={item.percentile} /> <PercentileIndicator percentile={item.percentile} />
)} )}
<span <span
style={{ fontSize: 16, color: theme.colors.textHighContrast }} style={{
fontSize: 16,
color: theme.colors.textHighContrast,
}}
> >
{item.value === undefined || Number.isNaN(item.value) {item.value === undefined || Number.isNaN(item.value)
? '--' ? '--'
@@ -133,6 +133,8 @@ export function Item({
? reward.vehicle.image_url ? reward.vehicle.image_url
: reward.stuff.image_url, : reward.stuff.image_url,
)} )}
width={16}
height={16}
/> />
)} )}
{reward && reward.count > 1 && ( {reward && reward.count > 1 && (
@@ -0,0 +1,21 @@
import { ReactNode } from 'react';
interface ButtonProps {
children: ReactNode;
}
export function Button({ children }: ButtonProps) {
return (
<button
style={{
padding: 16,
border: 'none',
cursor: 'pointer',
background: 'none',
color: 'inherit',
}}
>
{children}
</button>
);
}
+30
View File
@@ -0,0 +1,30 @@
import Link from 'next/link';
import { theme } from '../../stitches.config';
import { BlitzkriegWormWide } from '../BlitzkriegWormWide';
export default function Navbar() {
return (
<div
style={{
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: theme.colors.textHighContrast,
backgroundColor: theme.colors.appBackground2,
borderBottom: theme.borderStyles.nonInteractive,
height: 48, // TODO: remove this when the icons are implemented
}}
>
{/* <Button>
<HamburgerMenuIcon />
</Button> */}
<Link href="/" style={{ color: 'inherit' }}>
<BlitzkriegWormWide />
</Link>
{/* <Button>
<PersonIcon />
</Button> */}
</div>
);
}
+24
View File
@@ -0,0 +1,24 @@
import { ComponentProps } from 'react';
export default function PageWrapper({
style,
...props
}: ComponentProps<'div'>) {
return (
<div
style={{
width: '100%',
maxWidth: 800,
margin: 'auto',
display: 'flex',
padding: 16,
flexDirection: 'column',
gap: 16,
boxSizing: 'border-box',
...style,
}}
{...props}
/>
);
}
+44
View File
@@ -0,0 +1,44 @@
interface Tool {
id: string;
title: string;
description: string;
disabled?: boolean;
}
export const TOOLS: Tool[] = [
{
id: 'session',
title: 'Session tracker',
description: 'Track your performance in a session',
},
{
id: 'ratings',
title: 'Ratings',
description: 'Current and old season leader boards',
disabled: true,
},
{
id: 'tankopedia',
title: 'Tankopedia',
description: 'Full Blitz tank encyclopedia',
disabled: true,
},
{
id: 'inactive',
title: 'Inactivity tracker',
description: 'Find inactive members of a clan',
disabled: true,
},
{
id: 'profile',
title: 'Player profile',
description: "A player's basic non-statistical info",
disabled: true,
},
// {
// title: 'More coming soon',
// description: 'New tools are added often',
// banner: '/assets/banners/more.png',
// id: 'https://discord.gg/nDt7AjGJQH',
// },
];
+2
View File
@@ -0,0 +1,2 @@
// if you forked this repository, please replace this with your own id
export const WARGAMING_APPLICATION_ID = 'd03364bdc9114fc61f1d5e4e168d5f3c';
+66
View File
@@ -0,0 +1,66 @@
import {
IndividualTankStats,
NormalizedTankStats,
emptyIndividualTankStats,
} from '../../types/tanksStats';
export function diffNormalizedTankStats(
a: NormalizedTankStats,
b: NormalizedTankStats,
) {
const bTanks = Object.keys(b);
const changedTanks = bTanks.filter((id) => {
return (
a[id as unknown as number]?.all.battles !==
b[id as unknown as number].all.battles
);
});
const diff = changedTanks.map((id) => {
const aTank = a[id as unknown as number] ?? emptyIndividualTankStats;
const bTank = b[id as unknown as number];
const tankDiff = {
account_id: bTank.account_id,
battle_life_time: bTank.battle_life_time,
frags: (bTank.frags ?? aTank.frags ?? 0) - (aTank.frags ?? 0),
max_frags: Math.max(aTank.max_frags, bTank.max_frags),
in_garage: bTank.in_garage,
max_xp: Math.max(aTank.max_xp, bTank.max_xp),
in_garage_updated: bTank.in_garage_updated,
last_battle_time: bTank.last_battle_time - aTank.last_battle_time,
mark_of_mastery: bTank.mark_of_mastery,
tank_id: bTank.tank_id,
all: {
battles: bTank.all.battles - aTank.all.battles,
wins: bTank.all.wins - aTank.all.wins,
capture_points: bTank.all.capture_points - aTank.all.capture_points,
damage_dealt: bTank.all.damage_dealt - aTank.all.damage_dealt,
damage_received: bTank.all.damage_received - aTank.all.damage_received,
dropped_capture_points:
bTank.all.dropped_capture_points - aTank.all.dropped_capture_points,
frags: bTank.all.frags - aTank.all.frags,
frags8p: bTank.all.frags8p - aTank.all.frags8p,
hits: bTank.all.hits - aTank.all.hits,
losses: bTank.all.losses - aTank.all.losses,
max_frags: Math.max(aTank.max_frags, bTank.max_frags),
max_xp: Math.max(aTank.max_xp, bTank.max_xp),
shots: bTank.all.shots - aTank.all.shots,
spotted: bTank.all.spotted - aTank.all.spotted,
survived_battles:
bTank.all.survived_battles - aTank.all.survived_battles,
win_and_survived:
bTank.all.win_and_survived - aTank.all.win_and_survived,
xp: bTank.all.xp - aTank.all.xp,
},
} satisfies IndividualTankStats;
return tankDiff;
});
const normalized = diff.reduce<NormalizedTankStats>(
(accumulator, diff) => ({ ...accumulator, [diff.tank_id]: diff }),
{},
);
return normalized;
}
+1 -1
View File
@@ -1,7 +1,7 @@
import { RatingsInfo } from '../../commands/ratings'; import { RatingsInfo } from '../../commands/ratings';
import { Region } from '../../constants/regions'; import { Region } from '../../constants/regions';
import { DATABASE_REPO } from '../database/getMidnightLeaderboard'; import { DATABASE_REPO } from '../database/getMidnightLeaderboard';
import { octokit } from '../express/octokit'; import { octokit } from '../github/octokit';
import throwError from '../node/throwError'; import throwError from '../node/throwError';
export default async function getArchivedRatingsInfo( export default async function getArchivedRatingsInfo(
-22
View File
@@ -1,22 +0,0 @@
import { Region } from '../../constants/regions';
import { serverAndIdPattern } from '../discord/resolvePlayerFromCommand';
import throwError from '../node/throwError';
import listClans from './listClans';
export default async function getBlitzClan(clan: string) {
if (serverAndIdPattern.test(clan)) {
const [server, accountId] = clan.split('/');
return { server: server as Region, id: Number(accountId) };
} else {
const accounts = await listClans(clan);
if (accounts[0]) {
return { server: accounts[0].server, id: accounts[0].clan_id };
} else {
throw throwError(
'Could not find clan',
`I couldn't find clan \`${clan}\`. Try selecting a username from the search result.`,
);
}
}
}
+4 -4
View File
@@ -1,18 +1,18 @@
import { REGION_NAMES, Region } from '../../constants/regions'; import { REGION_NAMES, Region } from '../../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
import { TanksStats } from '../../types/tanksStats'; import { TanksStats } from '../../types/tanksStats';
import { secrets } from '../node/secrets';
import throwError from '../node/throwError'; import throwError from '../node/throwError';
import getWargamingResponse from './getWargamingResponse'; import getWargamingResponse from './getWargamingResponse';
export default async function getTankStats(server: Region, id: number) { export default async function getTankStats(region: Region, id: number) {
const tankStats = await getWargamingResponse<TanksStats>( const tankStats = await getWargamingResponse<TanksStats>(
`https://api.wotblitz.${server}/wotb/tanks/stats/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`, `https://api.wotblitz.${region}/wotb/tanks/stats/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
); );
if (tankStats[id] === null) { if (tankStats[id] === null) {
throw throwError( throw throwError(
'No tank stats available', '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_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[region]} server.`,
); );
} }
+7 -8
View File
@@ -13,17 +13,16 @@ export type WargamingResponse<Data extends object> =
export default async function getWargamingResponse<Data extends object>( export default async function getWargamingResponse<Data extends object>(
url: string, url: string,
) { ) {
let parsed: WargamingResponse<Data>; const data = (await fetch(url).then((response) =>
response.json(),
)) as WargamingResponse<Data>;
const response = await fetch(url); if (data.status === 'ok') {
parsed = (await response.json()) as WargamingResponse<Data>; return data.data;
if (parsed.status === 'ok') {
return parsed.data;
} else { } else {
throw throwError( throw throwError(
`Wargaming response error status:"${parsed.status}"`, `Wargaming response error status:"${data.status}"`,
`Message: "${parsed.error.message}"\nURL: "${url}"`, `Message: "${data.error.message}"\nURL: "${url}"`,
); );
} }
} }
+9 -9
View File
@@ -1,10 +1,10 @@
import { Region } from '../../constants/regions'; import { Region } from '../../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
import { Clan, ClanList } from '../../types/clanList'; import { Clan, ClanList } from '../../types/clanList';
import { secrets } from '../node/secrets';
import getWargamingResponse from './getWargamingResponse'; import getWargamingResponse from './getWargamingResponse';
export type ClanListWithServer = (Clan & { export type ClanListWithRegion = (Clan & {
server: 'com' | 'eu' | 'asia'; region: 'com' | 'eu' | 'asia';
})[]; })[];
export default async function listClans(search: string, limit = 9) { export default async function listClans(search: string, limit = 9) {
@@ -15,33 +15,33 @@ export default async function listClans(search: string, limit = 9) {
return ( return (
await Promise.all([ await Promise.all([
getWargamingResponse<ClanList>( getWargamingResponse<ClanList>(
`https://api.wotblitz.com/wotb/clans/list/?application_id=${secrets.WARGAMING_APPLICATION_ID}&search=${search}&limit=${normalizedLimit}`, `https://api.wotblitz.com/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${search}&limit=${normalizedLimit}`,
).then( ).then(
(value) => (value) =>
value && value &&
value.map((account) => ({ value.map((account) => ({
...account, ...account,
server: 'com' as Region, region: 'com' as Region,
})), })),
), ),
getWargamingResponse<ClanList>( getWargamingResponse<ClanList>(
`https://api.wotblitz.eu/wotb/clans/list/?application_id=${secrets.WARGAMING_APPLICATION_ID}&search=${search}&limit=${normalizedLimit}`, `https://api.wotblitz.eu/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${search}&limit=${normalizedLimit}`,
).then( ).then(
(value) => (value) =>
value && value &&
value.map((account) => ({ value.map((account) => ({
...account, ...account,
server: 'eu' as Region, region: 'eu' as Region,
})), })),
), ),
getWargamingResponse<ClanList>( getWargamingResponse<ClanList>(
`https://api.wotblitz.asia/wotb/clans/list/?application_id=${secrets.WARGAMING_APPLICATION_ID}&search=${search}&limit=${normalizedLimit}`, `https://api.wotblitz.asia/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${search}&limit=${normalizedLimit}`,
).then( ).then(
(value) => (value) =>
value && value &&
value.map((account) => ({ value.map((account) => ({
...account, ...account,
server: 'asia' as Region, region: 'asia' as Region,
})), })),
), ),
]) ])
+15 -11
View File
@@ -1,10 +1,14 @@
import { Region } from '../../constants/regions'; import { Region } from '../../constants/regions';
import { Account, AccountList } from '../../types/accountList'; import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
import { secrets } from '../node/secrets';
import getWargamingResponse from './getWargamingResponse'; import getWargamingResponse from './getWargamingResponse';
interface Account {
nickname: string;
account_id: number;
}
export type AccountList = Account[];
export type AccountListWithServer = (Account & { export type AccountListWithServer = (Account & {
server: 'com' | 'eu' | 'asia'; region: Region;
})[]; })[];
export const usernamePattern = /^[a-zA-Z0-9_]{3,24}$/; export const usernamePattern = /^[a-zA-Z0-9_]{3,24}$/;
@@ -19,40 +23,40 @@ export default async function listPlayers(search: string, limit = 9) {
return ( return (
await Promise.all([ await Promise.all([
getWargamingResponse<AccountList>( getWargamingResponse<AccountList>(
`https://api.wotblitz.com/wotb/account/list/?application_id=${secrets.WARGAMING_APPLICATION_ID}&search=${encodedSearch}&limit=${normalizedLimit}`, `https://api.wotblitz.com/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodedSearch}&limit=${normalizedLimit}`,
).then( ).then(
(value) => (value) =>
value && value &&
value.map((account) => ({ value.map((account) => ({
...account, ...account,
server: 'com' as Region, region: 'com' as Region,
})), })),
), ),
getWargamingResponse<AccountList>( getWargamingResponse<AccountList>(
`https://api.wotblitz.eu/wotb/account/list/?application_id=${secrets.WARGAMING_APPLICATION_ID}&search=${encodedSearch}&limit=${normalizedLimit}`, `https://api.wotblitz.eu/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodedSearch}&limit=${normalizedLimit}`,
).then( ).then(
(value) => (value) =>
value && value &&
value.map((account) => ({ value.map((account) => ({
...account, ...account,
server: 'eu' as Region, region: 'eu' as Region,
})), })),
), ),
getWargamingResponse<AccountList>( getWargamingResponse<AccountList>(
`https://api.wotblitz.asia/wotb/account/list/?application_id=${secrets.WARGAMING_APPLICATION_ID}&search=${encodedSearch}&limit=${normalizedLimit}`, `https://api.wotblitz.asia/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodedSearch}&limit=${normalizedLimit}`,
).then( ).then(
(value) => (value) =>
value && value &&
value.map((account) => ({ value.map((account) => ({
...account, ...account,
server: 'asia' as Region, region: 'asia' as Region,
})), })),
), ),
]) ])
) )
.filter(Boolean) .filter(Boolean)
.flat(); .flat() as AccountListWithServer;
} else { } else {
return []; return [] as AccountListWithServer;
} }
} }
+12 -5
View File
@@ -1,5 +1,6 @@
import { deburr } from 'lodash'; import { deburr } from 'lodash';
import { secrets } from '../node/secrets'; import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
import { context } from '../blitzkrieg/context';
import getWargamingResponse from './getWargamingResponse'; import getWargamingResponse from './getWargamingResponse';
export type TankType = 'AT-SPG' | 'lightTank' | 'mediumTank' | 'heavyTank'; export type TankType = 'AT-SPG' | 'lightTank' | 'mediumTank' | 'heavyTank';
@@ -22,11 +23,17 @@ export interface Tankopedia {
} }
console.log('Caching tankopedia...'); console.log('Caching tankopedia...');
export const tankopedia = fetch('https://www.blitzstars.com/bs-tankopedia.json') export const tankopedia = fetch(
.then((response) => response.json()) context === 'bot'
? 'https://www.blitzstars.com/bs-tankopedia.json'
: '/api/tankopedia',
)
.then(async (response) => response.json())
.then((wrapperTankopedia) => { .then((wrapperTankopedia) => {
console.log('Tankopedia cached'); console.log('Tankopedia cached');
return (wrapperTankopedia as { data: Tankopedia }).data; return context === 'bot'
? (wrapperTankopedia as { data: Tankopedia }).data
: (wrapperTankopedia as Tankopedia);
}); });
const entries = new Promise<TankopediaEntry[]>(async (resolve) => { const entries = new Promise<TankopediaEntry[]>(async (resolve) => {
resolve(Object.entries(await tankopedia).map(([, entry]) => entry)); resolve(Object.entries(await tankopedia).map(([, entry]) => entry));
@@ -66,7 +73,7 @@ export interface TankopediaInfo {
// this is blocking because info is needed for command creation // this is blocking because info is needed for command creation
console.log('Caching tankopedia info...'); console.log('Caching tankopedia info...');
export const tankopediaInfo = getWargamingResponse<TankopediaInfo>( export const tankopediaInfo = getWargamingResponse<TankopediaInfo>(
`https://api.wotblitz.com/wotb/encyclopedia/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}`, `https://api.wotblitz.com/wotb/encyclopedia/info/?application_id=${WARGAMING_APPLICATION_ID}`,
); );
console.log('Cached tankopedia info'); console.log('Cached tankopedia info');
+4
View File
@@ -0,0 +1,4 @@
type Context = 'website' | 'bot';
export const context: Context =
typeof window === 'undefined' ? 'bot' : 'website';
@@ -34,6 +34,7 @@ export const emptyTankHistoryNode: TankHistoryRaw = {
all: emptyAllStats, all: emptyAllStats,
}; };
// BIG TODO: merge this neatly with diffNormalizedTankStats
export default async function getDiffedTankStats( export default async function getDiffedTankStats(
server: Region, server: Region,
id: number, id: number,
+2 -2
View File
@@ -1,4 +1,5 @@
import { Region } from '../../constants/regions'; import { Region } from '../../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
import { AccountInfo, AllStats } from '../../types/accountInfo'; import { AccountInfo, AllStats } from '../../types/accountInfo';
import { import {
GetHistoriesOptions, GetHistoriesOptions,
@@ -7,7 +8,6 @@ import {
getHistoriesDefaultOptions, getHistoriesDefaultOptions,
} from '../../types/histories'; } from '../../types/histories';
import getWargamingResponse from '../blitz/getWargamingResponse'; import getWargamingResponse from '../blitz/getWargamingResponse';
import { secrets } from '../node/secrets';
import { emptyAllStats } from './getDiffedTankStats'; import { emptyAllStats } from './getDiffedTankStats';
export interface PlayerHistoryRaw { export interface PlayerHistoryRaw {
@@ -67,7 +67,7 @@ export default async function getPlayerHistories(
statistics: { all }, statistics: { all },
} = ( } = (
await getWargamingResponse<AccountInfo>( await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`, `https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
) )
)[id]; )[id];
+14 -9
View File
@@ -1,4 +1,5 @@
import { AllStats } from '../../types/accountInfo'; import { AllStats } from '../../types/accountInfo';
import { context } from '../blitzkrieg/context';
export interface SpecialStats { export interface SpecialStats {
winrate: number; winrate: number;
@@ -82,20 +83,24 @@ export type TankAverages = Record<number, IndividualTankAverage>;
console.log('Caching tank averages...'); console.log('Caching tank averages...');
export const tankAverages = fetch( export const tankAverages = fetch(
'https://www.blitzstars.com/api/tankaverages.json', context === 'bot'
? 'https://www.blitzstars.com/api/tankaverages.json'
: '/api/tank-averages',
) )
.then((response) => response.json()) .then((response) => response.json())
.then((individualTankAverages) => { .then((json) => {
const partialTankAverages: TankAverages = {}; if (context === 'bot') {
const partialTankAverages: TankAverages = {};
(individualTankAverages as IndividualTankAverage[]).forEach( (json as IndividualTankAverage[]).forEach((individualTankAverage) => {
(individualTankAverage) => {
partialTankAverages[individualTankAverage.tank_id] = partialTankAverages[individualTankAverage.tank_id] =
individualTankAverage; individualTankAverage;
}, });
);
console.log('Tank averages cached'); console.log('Tank averages cached');
return partialTankAverages; return partialTankAverages;
} else {
return json as TankAverages;
}
}); });
+1 -1
View File
@@ -1,6 +1,6 @@
import { BlitzkriegRatingsLeaderboard } from '../../../scripts/buildRatingsLeaderboard'; import { BlitzkriegRatingsLeaderboard } from '../../../scripts/buildRatingsLeaderboard';
import { Region } from '../../constants/regions'; import { Region } from '../../constants/regions';
import { octokit } from '../express/octokit'; import { octokit } from '../github/octokit';
export const DATABASE_REPO = { owner: 'tresabhi', repo: 'blitzkrieg-db' }; export const DATABASE_REPO = { owner: 'tresabhi', repo: 'blitzkrieg-db' };
+2 -2
View File
@@ -19,8 +19,8 @@ export default async function autocompleteClan(
? players.map( ? players.map(
(player) => (player) =>
({ ({
name: `${player.name} (${REGION_NAMES[player.server]})`, name: `${player.name} (${REGION_NAMES[player.region]})`,
value: `${player.server}/${player.clan_id}`, value: `${player.region}/${player.clan_id}`,
}) satisfies ApplicationCommandOptionChoiceData<string>, }) satisfies ApplicationCommandOptionChoiceData<string>,
) )
: [], : [],
+2 -2
View File
@@ -19,8 +19,8 @@ export default async function autocompleteUsername(
? players.map( ? players.map(
(player) => (player) =>
({ ({
name: `${player.nickname} (${REGION_NAMES[player.server]})`, name: `${player.nickname} (${REGION_NAMES[player.region]})`,
value: `${player.server}/${player.account_id}`, value: `${player.region}/${player.account_id}`,
}) satisfies ApplicationCommandOptionChoiceData<string>, }) satisfies ApplicationCommandOptionChoiceData<string>,
) )
: [], : [],
@@ -0,0 +1,27 @@
import { ChatInputCommandInteraction } from 'discord.js';
import { Region } from '../../constants/regions';
import listClans from '../blitz/listClans';
import throwError from '../node/throwError';
import { serverAndIdPattern } from './resolvePlayerFromCommand';
export default async function resolveClanFromCommand(
interaction: ChatInputCommandInteraction,
) {
const clan = interaction.options.getString('clan', true);
if (serverAndIdPattern.test(clan)) {
const [server, accountId] = clan.split('/');
return { region: server as Region, id: Number(accountId) };
} else {
const accounts = await listClans(clan);
if (accounts[0]) {
return { region: accounts[0].region, id: accounts[0].clan_id };
} else {
throw throwError(
'Could not find clan',
`I couldn't find clan \`${clan}\`. Try selecting a username from the search result.`,
);
}
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
import { ButtonInteraction, CacheType } from 'discord.js'; import { ButtonInteraction, CacheType } from 'discord.js';
import { CYCLIC_API } from '../../constants/cyclic'; import { CYCLIC_API } from '../../constants/cyclic';
import { Region } from '../../constants/regions'; import { Region } from '../../constants/regions';
import resolvePeriodFromURL from '../express/resolvePeriodFromURL'; import resolvePeriodFromURL from './resolvePeriodFromURL';
export default function resolvePeriodFromButton( export default function resolvePeriodFromButton(
server: Region, server: Region,
@@ -2,11 +2,11 @@ import { Region } from '../../constants/regions';
import getPeriodNow from '../blitzstars/getPeriodNow'; import getPeriodNow from '../blitzstars/getPeriodNow';
import getPeriodStart from '../blitzstars/getPeriodStart'; import getPeriodStart from '../blitzstars/getPeriodStart';
import getTimeDaysAgo from '../blitzstars/getTimeDaysAgo'; import getTimeDaysAgo from '../blitzstars/getTimeDaysAgo';
import { PeriodSize, PeriodType } from '../discord/addPeriodSubCommands'; import { PeriodSize, PeriodType } from './addPeriodSubCommands';
import { import {
ResolvedPeriod, ResolvedPeriod,
getPeriodOptionName, getPeriodOptionName,
} from '../discord/resolvePeriodFromCommand'; } from './resolvePeriodFromCommand';
export default function resolvePeriodFromURL( export default function resolvePeriodFromURL(
server: Region, server: Region,
+1 -1
View File
@@ -1,6 +1,6 @@
import { ButtonInteraction, CacheType } from 'discord.js'; import { ButtonInteraction, CacheType } from 'discord.js';
import { CYCLIC_API } from '../../constants/cyclic'; import { CYCLIC_API } from '../../constants/cyclic';
import resolvePlayerFromURL from '../express/resolvePlayerFromURL'; import resolvePlayerFromURL from './resolvePlayerFromURL';
export default async function resolvePlayerFromButton( export default async function resolvePlayerFromButton(
interaction: ButtonInteraction<CacheType>, interaction: ButtonInteraction<CacheType>,
+1 -1
View File
@@ -39,7 +39,7 @@ export default async function resolvePlayerFromCommand(
if (accounts[0]) { if (accounts[0]) {
return { return {
region: accounts[0].server, region: accounts[0].region,
id: accounts[0].account_id, id: accounts[0].account_id,
} satisfies ResolvedPlayer; } satisfies ResolvedPlayer;
} else { } else {
@@ -1,11 +0,0 @@
import { RouteReturnable } from '../../server';
import jsxToSvg from '../node/jsxToSvg';
export default async function normalizeRouteReturnable(
returnable: RouteReturnable,
) {
const awaitedReturnable = await returnable;
const svg = await jsxToSvg(awaitedReturnable);
return svg;
}
@@ -1,11 +0,0 @@
import { Request } from 'express';
import { CYCLIC_API } from '../../constants/cyclic';
import { Region } from '../../constants/regions';
import resolvePeriodFromURL from './resolvePeriodFromURL';
export default function resolvePeriodFromRequest(
server: Region,
request: Request,
) {
return resolvePeriodFromURL(server, `${CYCLIC_API}${request.url}`);
}
@@ -1,7 +0,0 @@
import { Request } from 'express';
import { CYCLIC_API } from '../../constants/cyclic';
import resolvePlayerFromURL from './resolvePlayerFromURL';
export default function resolvePlayerFromRequest(request: Request) {
return resolvePlayerFromURL(`${CYCLIC_API}${request.originalUrl}`);
}
-23
View File
@@ -1,23 +0,0 @@
export default function withAutoRefresh(svg: string, refreshAfter: number) {
return `
<html>
<head>
<script>
setTimeout(() => {
window.location.reload();
}, ${refreshAfter});
</script>
<style>
body {
margin: 0;
}
</style>
</head>
<body>
${svg}
</body>
</html>
`;
}
File renamed without changes.
Loaded 100 of 118 files, more files were not shown because too many files have changed in this diff. Show more