Compare commits

..
10 Commits
364 changed files with 6016 additions and 9076 deletions

No files matched your search

+101 -30
View File
@@ -1,4 +1,4 @@
name: Assets name: Build Assets
on: on:
push: push:
@@ -6,31 +6,30 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
asset_repo: cdn_branch:
type: choice
required: false
description: What asset repo to write to
options:
- blitzkit/assets
- blitzkit/assets-preview
- blitzkit/assets-dev
default: blitzkit/assets
asset_source:
type: string type: string
required: false required: false
description: "Archive URL or Steam to download (example: steam:APP_ID/DEPOT_ID or zip:URL_TO_ZIP)" default: main
default: "steam:444200/444202" description: What CDN branch to write to
zip_url:
type: string
required: false
description: ZIP URL to download instead of Steam client
zip_root:
type: string
required: false
description: Root dir of ZIP which houses the executable and Data dir
schedule: schedule:
- cron: "0 2 * * *" - cron: '0 2 * * *'
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.asset_repo }} group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.cdn_branch }}
cancel-in-progress: true cancel-in-progress: true
env: env:
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }} DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DATABASE_URL: ${{ secrets.DATABASE_URL }} DATABASE_URL: ${{ secrets.DATABASE_URL }}
VIDEOS_SHEET: ${{ secrets.VIDEOS_SHEET }} VIDEOS_SHEET: ${{ secrets.VIDEOS_SHEET }}
@@ -38,8 +37,8 @@ env:
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }} WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
WOTB_DLC_CDN: ${{ secrets.WOTB_DLC_CDN }} WOTB_DLC_CDN: ${{ secrets.WOTB_DLC_CDN }}
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }} PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
PUBLIC_ASSET_REPO: ${{ github.event.inputs.asset_repo != '' && github.event.inputs.asset_repo || secrets.PUBLIC_ASSET_REPO }} PUBLIC_ASSET_REPO: blitzkit/assets
PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }} PUBLIC_ASSET_BRANCH: main
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }} WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }} WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }} PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
@@ -55,28 +54,100 @@ env:
PUBLIC_ASSET_FAST_USER: blitzkit PUBLIC_ASSET_FAST_USER: blitzkit
PUBLIC_ASSET_FAST_REPO: assets PUBLIC_ASSET_FAST_REPO: assets
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }} PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
STEAM_USERNAME: ${{ secrets.STEAM_USERNAME }}
STEAM_PASSWORD: ${{ secrets.STEAM_PASSWORD }}
jobs: jobs:
build_assets: download_client:
name: Build assets name: Download client
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - name: (ZIP) Maximize build space
if: github.event.inputs.zip_url != ''
uses: easimon/maximize-build-space@master
with:
root-reserve-mb: 512
swap-size-mb: 1024
remove-dotnet: 'true'
- uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
token: ${{ secrets.GH_TOKEN }} token: ${{ secrets.GH_TOKEN }}
- uses: arduino/setup-protoc@v3
- name: (Steam) Setup bun
uses: oven-sh/setup-bun@v1
if: github.event.inputs.zip_url == ''
- name: (Steam) Install dependencies
if: github.event.inputs.zip_url == ''
run: bun install
- name: (Steam) Setup DepotDownloader
if: github.event.inputs.zip_url == ''
run: bun run setup:depotdownloader
- name: (Steam) Download client
if: github.event.inputs.zip_url == ''
run: |
chmod +x ./temp/depotDownloader/DepotDownloader
./temp/depotDownloader/DepotDownloader -q -os windows -app 444200 -username ${{ secrets.DEPOT_DOWNLOADER_USERNAME }} -password "${{ secrets.DEPOT_DOWNLOADER_PASSWORD }}"
- name: (Steam) Apply patches
if: github.event.inputs.zip_url == ''
run: bun setup:patches --depot
- name: (ZIP) Download client
if: github.event.inputs.zip_url != ''
env:
ZIP_URL: ${{ github.event.inputs.zip_url }}
ZIP_ROOT: ${{ github.event.inputs.zip_root }}
run: |
wget -q -O client.zip $ZIP_URL
unzip -q client.zip -d client
rm client.zip
mkdir -p depots/444202/0
mv client/$ZIP_ROOT/* depots/444202/0
- name: Archive depots
uses: actions/upload-artifact@v4
with: with:
repo-token: ${{ secrets.GH_TOKEN }} name: depots
- uses: oven-sh/setup-bun@v2 path: depots
- run: bun run setup
build_assets:
name: Build assets
runs-on: ubuntu-latest
needs: download_client
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
token: ${{ secrets.GH_TOKEN }}
- name: Setup bun
uses: oven-sh/setup-bun@v1
- name: Install dependencies
run: bun install
- name: Lint
run: bun run lint:website
- name: Download depots
uses: actions/download-artifact@v4
with:
name: depots
path: depots
- name: AppArmor hack - name: AppArmor hack
run: echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns run: echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns
- name: Build assets - name: Start server and build assets
run: bun run build:assets --all-targets --source=${{ github.event.inputs.asset_source || 'steam:444200/444202' }} run: |
bun dev:website --verbosepa &
SERVER_PID=$!
bun run build:assets --all-targets --depot --production
kill $SERVER_PID || true
+8 -13
View File
@@ -3,7 +3,7 @@ name: Averages
on: on:
workflow_dispatch: workflow_dispatch:
schedule: schedule:
- cron: "0 8 * * *" - cron: '0 8 * * *'
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
@@ -15,7 +15,7 @@ env:
PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }} PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }}
PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }} PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }}
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }} PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
ALLOW_ROBOTS: "true" ALLOW_ROBOTS: 'true'
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }} WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }} PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }} PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
@@ -25,28 +25,23 @@ env:
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }} PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }} PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }} PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: - uses: oven-sh/setup-bun@v1
submodules: recursive
token: ${{ secrets.GH_TOKEN }} - name: Install dependencies
- uses: oven-sh/setup-bun@v2 run: bun install
- uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GH_TOKEN }}
- run: bun run setup
- name: Build averages - name: Build averages
env: env:
PUBLIC_ASSET_BRANCH: main PUBLIC_ASSET_BRANCH: main
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }} DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DATABASE_URL: ${{ secrets.DATABASE_URL }} DATABASE_URL: ${{ secrets.DATABASE_URL }}
VIDEOS_SHEET: ${{ secrets.VIDEOS_SHEET }} VIDEOS_SHEET: ${{ secrets.VIDEOS_SHEET }}
+11 -12
View File
@@ -1,4 +1,4 @@
name: Bot name: Build Bot
on: on:
push: push:
@@ -10,25 +10,24 @@ concurrency:
jobs: jobs:
build: build:
name: Build bot name: Build
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: - uses: oven-sh/setup-bun@v1
submodules: recursive
token: ${{ secrets.GH_TOKEN }} - name: Install dependencies
- uses: oven-sh/setup-bun@v2 run: bun install
- uses: arduino/setup-protoc@v3
with: - name: Build Prisma
repo-token: ${{ secrets.GH_TOKEN }} run: bun run build:prisma
- run: bun run setup
- name: Build bot - name: Build bot
run: bun run build:bot run: bun run build:bot
- name: Archive build - name: Archive build
uses: actions/upload-artifact@v5 uses: actions/upload-artifact@v4
with: with:
name: bot name: bot
path: dist/bot path: dist/bot
+8 -12
View File
@@ -1,9 +1,9 @@
name: IDs name: Discover IDs
on: on:
workflow_dispatch: workflow_dispatch:
schedule: schedule:
- cron: "0 0 * * 1" - cron: '0 0 * * 1'
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
@@ -11,26 +11,22 @@ concurrency:
env: env:
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
DATABASE_URL: ${{ secrets.DATABASE_URL }} DATABASE_URL: ${{ secrets.DATABASE_URL }}
PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }} PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }}
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }} PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }} PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: - uses: oven-sh/setup-bun@v1
submodules: recursive
token: ${{ secrets.GH_TOKEN }} - name: Install dependencies
- uses: oven-sh/setup-bun@v2 run: bun install
- uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GH_TOKEN }}
- run: bun run setup
- name: Discover IDs - name: Discover IDs
run: bun build:ids --production run: bun build:ids --production
+5 -7
View File
@@ -1,10 +1,10 @@
name: Host name: Host Bot
on: on:
push: push:
branches: main branches: main
schedule: schedule:
- cron: "0 */5 * * *" - cron: '0 */5 * * *'
workflow_dispatch: workflow_dispatch:
release: release:
types: [created, published, released, edited] types: [created, published, released, edited]
@@ -35,17 +35,15 @@ env:
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }} PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
PUBLIC_GUIDE_WEBSITE: ${{ secrets.PUBLIC_GUIDE_WEBSITE }} PUBLIC_GUIDE_WEBSITE: ${{ secrets.PUBLIC_GUIDE_WEBSITE }}
SERVICING: ${{ secrets.SERVICING }} SERVICING: ${{ secrets.SERVICING }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
jobs: jobs:
host: host:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: oven-sh/setup-bun@v2 - uses: oven-sh/setup-bun@v1
- uses: arduino/setup-protoc@v3 # with:
with: # bun-version: 1.1.33
repo-token: ${{ secrets.GH_TOKEN }}
- name: Download release assets - name: Download release assets
run: | run: |
+27 -32
View File
@@ -3,44 +3,39 @@ name: Rating Asia
on: on:
workflow_dispatch: workflow_dispatch:
schedule: schedule:
- cron: "0 17 * * *" - cron: '0 17 * * *'
env:
PUBLIC_ASSET_BRANCH: main
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }}
PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }}
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
ALLOW_ROBOTS: "true"
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
jobs: jobs:
host: host:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: - uses: oven-sh/setup-bun@v1
submodules: recursive
token: ${{ secrets.GH_TOKEN }} - name: Install dependencies
- uses: oven-sh/setup-bun@v2 run: bun install
- uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GH_TOKEN }}
- run: bun run setup
- name: Generate leaderboard - name: Generate leaderboard
env:
PUBLIC_ASSET_BRANCH: main
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }}
PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }}
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
ALLOW_ROBOTS: 'true'
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
run: bun build:ratingleaderboard --region=asia run: bun build:ratingleaderboard --region=asia
+27 -32
View File
@@ -3,44 +3,39 @@ name: Rating Europe
on: on:
workflow_dispatch: workflow_dispatch:
schedule: schedule:
- cron: "0 23 * * *" - cron: '0 23 * * *'
env:
PUBLIC_ASSET_BRANCH: main
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }}
PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }}
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
ALLOW_ROBOTS: "true"
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
jobs: jobs:
host: host:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: - uses: oven-sh/setup-bun@v1
submodules: recursive
token: ${{ secrets.GH_TOKEN }} - name: Install dependencies
- uses: oven-sh/setup-bun@v2 run: bun install
- uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GH_TOKEN }}
- run: bun run setup
- name: Generate leaderboard - name: Generate leaderboard
env:
PUBLIC_ASSET_BRANCH: main
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }}
PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }}
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
ALLOW_ROBOTS: 'true'
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
run: bun build:ratingleaderboard --region=eu run: bun build:ratingleaderboard --region=eu
+27 -32
View File
@@ -3,44 +3,39 @@ name: Rating North America
on: on:
workflow_dispatch: workflow_dispatch:
schedule: schedule:
- cron: "0 5 * * *" - cron: '0 5 * * *'
env:
PUBLIC_ASSET_BRANCH: main
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }}
PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }}
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
ALLOW_ROBOTS: "true"
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
jobs: jobs:
host: host:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: - uses: oven-sh/setup-bun@v1
submodules: recursive
token: ${{ secrets.GH_TOKEN }} - name: Install dependencies
- uses: oven-sh/setup-bun@v2 run: bun install
- uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GH_TOKEN }}
- run: bun run setup
- name: Generate leaderboard - name: Generate leaderboard
env:
PUBLIC_ASSET_BRANCH: main
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }}
PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }}
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
ALLOW_ROBOTS: 'true'
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
run: bun build:ratingleaderboard --region=com run: bun build:ratingleaderboard --region=com
+8 -9
View File
@@ -11,28 +11,27 @@ concurrency:
env: env:
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
DATABASE_URL: ${{ secrets.DATABASE_URL }} DATABASE_URL: ${{ secrets.DATABASE_URL }}
PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }} PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }}
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }} PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }} PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
token: ${{ secrets.GH_TOKEN }} token: ${{ secrets.GH_TOKEN }}
- uses: oven-sh/setup-bun@v2
- uses: arduino/setup-protoc@v3 - uses: oven-sh/setup-bun@v1
with:
repo-token: ${{ secrets.GH_TOKEN }} - name: Install dependencies
- run: bun run setup run: bun install
- name: Find reviews - name: Find reviews
run: bun run build:reviews run: bun packages/scripts/src/reviews.ts
+9 -10
View File
@@ -5,7 +5,7 @@ on:
workflow_dispatch: workflow_dispatch:
schedule: schedule:
- cron: "0 4 * * *" - cron: '0 4 * * *'
permissions: permissions:
contents: read contents: read
@@ -31,29 +31,28 @@ env:
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }} PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
PUBLIC_GUIDE_WEBSITE: ${{ secrets.PUBLIC_GUIDE_WEBSITE }} PUBLIC_GUIDE_WEBSITE: ${{ secrets.PUBLIC_GUIDE_WEBSITE }}
SERVICING: ${{ secrets.SERVICING }} SERVICING: ${{ secrets.SERVICING }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
jobs: jobs:
build: build:
name: Build website name: Build
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
token: ${{ secrets.GH_TOKEN }} token: ${{ secrets.GH_TOKEN }}
- uses: oven-sh/setup-bun@v2
- uses: arduino/setup-protoc@v3 - uses: oven-sh/setup-bun@v1
with:
repo-token: ${{ secrets.GH_TOKEN }}
- uses: actions/configure-pages@v5 - uses: actions/configure-pages@v5
- run: bun run setup
- name: Install dependencies
run: bun install
- name: Build website - name: Build website
run: bun run build:website run: bun run build:website
- name: Upload artifact - name: Upload artifact
uses: actions/upload-pages-artifact@v4 uses: actions/upload-pages-artifact@v3
with: with:
path: dist/website path: dist/website
+9 -3
View File
@@ -1,3 +1,9 @@
[submodule "packages/closed"] [submodule "submodules/blitzkit-closed"]
path = packages/closed path = submodules/blitzkit-closed
url = https://github.com/blitzkit/closed.git url = https://github.com/tresabhi/blitzkit-closed
[submodule "submodules/CUE4Parse"]
path = submodules/CUE4Parse
url = https://github.com/FabianFG/CUE4Parse
[submodule "submodules/blitzkit-internal"]
path = submodules/blitzkit-internal
url = https://github.com/blitzkit/internal
-22
View File
@@ -11,13 +11,11 @@
"analyticsreporting", "analyticsreporting",
"APAC", "APAC",
"apcr", "apcr",
"appinfo",
"applypatches", "applypatches",
"artefacts", "artefacts",
"astrojs", "astrojs",
"astrolib", "astrolib",
"atgm", "atgm",
"autocrop",
"Autoloader", "Autoloader",
"Autoreloader", "Autoreloader",
"AYUV", "AYUV",
@@ -36,14 +34,12 @@
"blitzkit", "blitzkit",
"blitzrinth", "blitzrinth",
"blitzstars", "blitzstars",
"bufbuild",
"cacheable", "cacheable",
"Callouts", "Callouts",
"Camo", "Camo",
"Carro", "Carro",
"CCPAOPTOUT", "CCPAOPTOUT",
"CDON", "CDON",
"clanid",
"classname", "classname",
"cleandist", "cleandist",
"closedkrieg", "closedkrieg",
@@ -66,23 +62,18 @@
"deburr", "deburr",
"decalmask", "decalmask",
"decompressor", "decompressor",
"décor",
"dedup",
"defence", "defence",
"degressive", "degressive",
"depotdownloader",
"devkrieg", "devkrieg",
"diacriticless", "diacriticless",
"DIDS", "DIDS",
"donotremove", "donotremove",
"drei", "drei",
"Droodles",
"dvpd", "dvpd",
"dvpl", "dvpl",
"DVPLCRC", "DVPLCRC",
"dvpm", "dvpm",
"dxgi", "dxgi",
"easimon",
"echarts", "echarts",
"Everytime", "Everytime",
"FASTNAME", "FASTNAME",
@@ -99,11 +90,8 @@
"gameplay", "gameplay",
"glsl", "glsl",
"GLTF", "GLTF",
"GSOR",
"gtag", "gtag",
"haha", "haha",
"Herdbreaker",
"Hetz",
"highp", "highp",
"iife", "iife",
"imgur", "imgur",
@@ -117,7 +105,6 @@
"leaderboards", "leaderboards",
"lerp", "lerp",
"LINEARSIZE", "LINEARSIZE",
"linktarget",
"lmao", "lmao",
"loadouts", "loadouts",
"loddist", "loddist",
@@ -140,7 +127,6 @@
"NEGATIVEX", "NEGATIVEX",
"NEGATIVEY", "NEGATIVEY",
"NEGATIVEZ", "NEGATIVEZ",
"networkidle",
"NITROPAY", "NITROPAY",
"nivo", "nivo",
"nmatname", "nmatname",
@@ -151,13 +137,11 @@
"Oopsy", "Oopsy",
"opentest", "opentest",
"OPTML", "OPTML",
"otemp",
"outdir", "outdir",
"outfile", "outfile",
"ownedtanks", "ownedtanks",
"padder", "padder",
"partytown", "partytown",
"patchnotes",
"PIXELFORMAT", "PIXELFORMAT",
"playerachievements", "playerachievements",
"playerinfo", "playerinfo",
@@ -170,15 +154,11 @@
"Promisable", "Promisable",
"proto", "proto",
"protobuf", "protobuf",
"protoc",
"protos", "protos",
"proxyclient",
"prunestrings",
"Pyogenics", "Pyogenics",
"ratingleaderboard", "ratingleaderboard",
"raycaster", "raycaster",
"Redirector", "Redirector",
"Relogin",
"researchable", "researchable",
"Resvg", "Resvg",
"rizz", "rizz",
@@ -227,14 +207,12 @@
"Unregistering", "Unregistering",
"unrotate", "unrotate",
"unwrapper", "unwrapper",
"userns",
"vercel", "vercel",
"vickers", "vickers",
"vitepress", "vitepress",
"Vovko", "Vovko",
"wargaming", "wargaming",
"webp", "webp",
"wgpkg",
"whitespaces", "whitespaces",
"WHSS", "WHSS",
"winrate", "winrate",
+7 -5
View File
@@ -16,20 +16,22 @@ If you intend to contribute to the code, note that you may need to check out sen
| [packages/i18n](packages/i18n) | | [Crowdin](https://crowdin.com/project/blitzkrieg) | Translations served as JSON shared across all other packages, including some utility code. | | [packages/i18n](packages/i18n) | | [Crowdin](https://crowdin.com/project/blitzkrieg) | Translations served as JSON shared across all other packages, including some utility code. |
| [packages/scripts](packages/scripts) | | | CLI utilities for the development and deployment of BlitzKit services. | | [packages/scripts](packages/scripts) | | | CLI utilities for the development and deployment of BlitzKit services. |
| [packages/varuna](packages/varuna) | Varuna | [NPM](https://www.npmjs.com/package/varuna) | React and Astro-oriented state management library, awaiting separation from BlitzKit. Unavailable from NPM at this time. | | [packages/varuna](packages/varuna) | Varuna | [NPM](https://www.npmjs.com/package/varuna) | React and Astro-oriented state management library, awaiting separation from BlitzKit. Unavailable from NPM at this time. |
| [packages/website](packages/website) | BlitzKit | https://blitzkit.app | The BlitzKit website provides a wide range of tools, APIs, and interconnects all other services. | | [packages/website](packages/website) | BlitzKit | https://blitzkit.app | The BlitzKit website provides a wide range of tools, APIs, and interconnects all other services. |
## Submodules ## Submodules
| Submodule | Title | Source | Memo | | Submodule | Title | Source | Memo |
| ------------------------------------------------------ | --------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------------------------------------------------ | ----------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [submodules/blitzkit-closed](packages/blitzkit-closed) | ClosedKit | [blitzkit/closed](https://github.com/blitzkit/closed) | Closed source, sensitive code hidden from the public to prevent bad actors from abusing the game. Discuss with `@tresabhi` on Discord for **special permission** to access. | | [submodules/blitzkit-closed](submodules/blitzkit-closed) | ClosedKit | [blitzkit/closed](https://github.com/blitzkit/closed) | Closed source, sensitive code hidden from the public to prevent bad actors from abusing the game. Discuss with `@tresabhi` on Discord for **special permission** to access. |
| [submodules/blitzkit-internal](submodules/blitzkit-internal) | InternalKit | [blitzkit/internal](https://github.com/blitzkit/internal) | Closed source, sensitive internal documentation hidden from the public to prevent bad actors from abusing the game. Access to this submodule is **not negotiable**. |
| [submodules/CUE4Parse](submodules/CUE4Parse) | CUE4Parse | [FabianFG/CUE4Parse](https://github.com/FabianFG/CUE4Parse) | An Unreal Engine archives and packages parsing library in C#. |
## Environment Variables ## Environment Variables
| Name | Source | BlitzKit Default | Memo | | Name | Source | BlitzKit Default | Memo |
| ------------------------------------------------------------- | -------------------------------------------------------------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | ------------------------------------------------------------- | -------------------------------------------------------------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `PUBLIC_WARGAMING_APPLICATION_ID` | [Wargaming Developer's Room](https://developers.wargaming.net/) | | Application ID needed to communicate with Wargaming APIs. | | `PUBLIC_WARGAMING_APPLICATION_ID` | [Wargaming Developer's Room](https://developers.wargaming.net/) | | Application ID needed to communicate with Wargaming APIs. |
| `PUBLIC_PROMOTE_OPENTEST` | | `false` | Optional boolean dictating whether the website advertises [the opentest subdomain of BlitzKit](https://opentest.blitzkit.app/). | | `PUBLIC_PROMOTE_OPENTEST` | | `false` | Optional boolean dictating whether the website advertises [the opentest subdomain of BlitzKit](https://opentest.blitzkit.app/). |
| `ALLOW_ROBOTS` | | `true` | Toggles between `Allow: /` and `Disallow: /` in the website's `/robots.txt`. This is usually `false` for all BlitzKit's subdomains. | | `ALLOW_ROBOTS` | | `true` | Toggles between `Allow: /` and `Disallow: /` in the website's `/robots.txt`. This is usually `false` for all BlitzKit's subdomains. |
| `WOTB_GLOSSARY`, `WOTB_GLOSSARY_DOMAIN`, `WOTB_GLOSSARY_PATH` | | | | | `WOTB_GLOSSARY`, `WOTB_GLOSSARY_DOMAIN`, `WOTB_GLOSSARY_PATH` | | | |
| `PUBLIC_GOOGLE_TAG_MANAGER_ID` | [Google Tag Manager](https://tagmanager.google.com/) | `G-GL2JVHCGPQ` | Google Tag Manager's unique identifier for analytical purposes. | | `PUBLIC_GOOGLE_TAG_MANAGER_ID` | [Google Tag Manager](https://tagmanager.google.com/) | `G-GL2JVHCGPQ` | Google Tag Manager's unique identifier for analytical purposes. |
+356 -517
View File
File diff suppressed because it is too large. Load diff
+16 -44
View File
@@ -1,47 +1,19 @@
# BlitzKit Credits # BlitzKit Credits
BlitzKit is a one man band **TrèsAbhi** but I am far from solo. Helpers, in the order of contribution over time: BlitzKit is a one man band **TrèsAbhi** but I am far from solo.
### S0AP | Role | People |
| ------------------------------ | ------------------------------ |
BlitzKit Discord bot verification, testing, advertisement, and the initial encouragement to create BlitzKit. | Bot verification | S0AP |
| General testing | Prince_NA |
### Prince_NA | Bot advertisement platform | S0AP |
| Bot rendering pipeline | Vovko |
BlitzKit Discord bot and website testing and general advise. | Game asset decompression | Maddox |
| Game 3D model parsing | Pyogenics |
### Vovko | Game file layout | Aeson |
| 3-caliber rule edge cases | Minitelrose, ChickenMan7777 |
BlitzKit Discord bot and website rendering pipeline. | Embed UX | \_34, HisRoyalFatness, Synx |
| Tankopedia pre-release testing | [OPTML], Aeson, \_34, xkdkw |
### Maddox | Website advertisement platform | Aeson |
| Compare pre-release testing | [OPTML] |
Game file decompression. | Redacted | GonnaHetzMe |
### Pyogenics
Game 3D model parsing.
### Aeson
Game file layout, general advise, advertisement, and Tankopedia pre-release testing.
### Minitelrose, ChickenMan7777
3-caliber rule edge cases.
### HisRoyalFatness, Synx
Embed UX.
### [OPTML] clan, \_34, xkdkw, Droodles Blitz
Tankopedia pre-release testing.
### GonnaHetzMe
Redacted by request.
### Mark Greene
GitHub contributor on bug fixes and feature additions.
-10
View File
@@ -1,10 +0,0 @@
# BlitzKit 1.28.4
## Fixes
- The tank names in the Tankopedia sandbox now have less margin from the navbar
- Controls in the Tankopedia sandbox are now shown in all modes, except for the advanced highlighting button which only appears in the dynamic armor mode
- The settings page now reacts to input again
- The settings page now has a tab title and description for search engines
- Added support for transparent textures, fixing tanks like the Herdbreaker and the WZ-132-1
- The tank filters in the Performance and Tier-List tools work once again
-12
View File
@@ -1,12 +0,0 @@
# BlitzKit 1.29.0
## Features
- Added the Playlist tool
- Don't know what to play today? Let BlitzKit do the thinking!
- Navigate to [the tool on BlitzKit](https://blitzkit.app/playlist)
- Make sure you login with your Wargaming account to unlock all features like live tracking and filtering by owned tanks
- Use filters to pick tiers, nations, etc. then hit Generate playlist
- BlitzKit will generate a checklist for you to go through as you play
- Don't like the order of tanks? Hit Shuffle, Sort by Last played and Least played, or straight up remove tanks from the list by using the (-) button
- If you're logged in, BlitzKit will automatically check off tanks as you play them
-5
View File
@@ -1,5 +0,0 @@
# BlitzKit 1.29.1
## Technical Changes
- Recreated the full dependency tree for BlitzKit
-14
View File
@@ -1,14 +0,0 @@
# BlitzKit 1.29.10
Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
## Features
- Combined game mode passive and active abilities into a single dropdown
- [Mark Greene] Added tier filters to the Guess game
- Changing tiers will break your streak
## Fixes
- [Mark Greene] module damage is now properly shown when sorting with it in the tank search
- Duplicate abilities in the tank filters are now removed
-5
View File
@@ -1,5 +0,0 @@
# BlitzKit 1.29.11
## Fixes
- The Guesser game's toolbar now scales properly with screen size
-14
View File
@@ -1,14 +0,0 @@
# BlitzKit 1.29.12
## Fixes
- BlitzKit no longer uses BlitzStars for tank averages for some commands
- BlitzKit Performance was created to completely replace BlitzStars but BlitzStars was still used for some older commands
- But since BlitzStars keeps having downtime, it has been completely removed
- Upgraded all dependencies to the latest versions
- The replay command is now properly localized across all languages
- Special Chinese characters are now properly handled
- Commands are now registered synchronously, allowing errors to reveal their exact origins
- Error handling now properly exists the process after logging the error
- The bot now uses Discord's new `clientReady` instead of the ambiguous `ready` event
- Both the armor and model extractors now use the new BlitzKit `SteamVFS`
-20
View File
@@ -1,20 +0,0 @@
# BlitzKit 1.29.13
## Features
- The screenshot button now automatically crops your image so you don't have excessive blank space all around
- Increased the max zoom out distance in Tankopedia to 50m
## Fixes
- Turrets in Tankopedia now stay within a range of -180 and +180 degrees
- Tanks with transparency like the T34 and Herdbreaker are now properly rendered
- This will take a few days to roll out for all tanks (see screenshots below for the fixed models)
- High End Consumables now give a 33% boost instead of 70%
- Static Armor now properly deselects all irrelevant armor plates
- Removed a few debug loggers left behind
- Static Armor now properly rerenders armor plates when their thicknesses are changed
- The Ricochet Visualizer now stays in place when dragging around on mobile
- Reduced the text in the Tankopedia Compare segmented display to fit on smaller screens
- Sorting by tertiary shells no longer crashes for some tanks
- The Tank Filters are now left aligned on smaller screens for readability
-11
View File
@@ -1,11 +0,0 @@
# BlitzKit 1.29.14
## Changes
- BlitzStars has enabled aggressive Cloudflare protections; hence, disabling BlitzKit from using BlitzStar's API
- Disabled the following commands:
- `/stats`
- `/today`
- `/breakdown`
- `/evolution`
- `/full-stats`
-5
View File
@@ -1,5 +0,0 @@
# BlitzKit 1.29.2
## Technical Changes
- Implemented new workflow capabilities to deploy BlitzKit OpenTest and Preview remotely, from any device
-5
View File
@@ -1,5 +0,0 @@
# BlitzKit 1.29.3
## Technical Changes
- Added a significantly more memory and network efficient AssetUploader abstraction
-5
View File
@@ -1,5 +0,0 @@
# BlitzKit 1.29.4
## Technical Changes
- BlitzKit now partially downloads game files instead of the entire game, allowing for faster updates
-11
View File
@@ -1,11 +0,0 @@
# BlitzKit 1.29.5
## Features
- Added new lighting to be a bit softer on tanks
- Added a button with the sun icon to toggle high graphics
- Added a new reveal and transition animation between tabs
## Fixes
- Track wheels on some tanks no longer look translucent
-18
View File
@@ -1,18 +0,0 @@
# BlitzKit 1.29.6
## Features
- Simplified the ownership and test tank filters in the tank search
- Renamed "Guess the tank" to simply "Guess"
- The player count value in the Performance tool is now set to Total by default
- Added Compare-like red/green highlighting to the Performance tool
## Changes
- Removed the last updated info bar in Performance for some more screen real estate
## Fixes
- The remove played tanks in the Playlist tool now shows even when logged out
- The Guess game now has a page title
- The compare table now occupies the minimum width possible instead of expanding all columns excessively
-5
View File
@@ -1,5 +0,0 @@
# BlitzKit 1.29.7
## Features
- Re-introduced the ability to deploy BlitzKit OpenTests
-19
View File
@@ -1,19 +0,0 @@
# BlitzKit 1.29.8
Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
## Features
- Added an option to clear provisions and consumables from all tanks in Compare
- Added the ability to choose stock turrets and guns in Mixer if a tank has any
- Added streaks to Guesser
- Introduced an option to partially reveal tanks in Guesser
- This removes the darkness effect but keeps the name hidden
- This will reset your streak to 0
- Regardless of getting the right guess on the tank's name, your response will be counted as incorrect if partially revealed
## Fixes
- The Tankopedia now smoothly resizes when enlarging on interaction
- [Mark Greene] Stock turrets now correctly mount the corresponding stock gun
- The consumable and provision buttons in Compare are no longer tiny
-14
View File
@@ -1,14 +0,0 @@
# BlitzKit 1.29.9
Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
## Features
- Reworked tank search filters to be far more compact to make way for new filters
- [Mark Greene] Added the ability to filter tanks by consumables
- Added the ability to filter tanks by provisions and game-mode abilities and powers
## Fixes
- Consumable and provision icons in Compare are now properly sized
- [Mark Greene] Provisions now properly affect terrain resistances
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
-57
View File
@@ -1,57 +0,0 @@
# Christmas Garage Guide 2025
This content was pre-recorded on a test server and the version you may see may not be the same. Please see [the official WoTB portal](https://wotblitz.com/) for the most up-to-date information.
The holiday season has arrived, and this year the celebrations spill directly into your garage, transforming the familiar space into a festive hub filled with activities, rewards, and seasonal flair. Instead of just offering decorations or a visual makeover, this event brings a full set of mechanics that turn everyday battles into a steady stream of progress. Whether you’re aiming for bonuses, collectibles, or new tanks, the garage is designed to make every match feel like part of a larger holiday journey.
![](https://i.imgur.com/HmbDw0x.jpeg)
## The Music Box
The familiar Snow Globes are absent this year, replaced entirely by **Music Boxes**, which act as the new way to try your luck for seasonal rewards. They behave similarly to the Snow Globes and give you a chance to obtain various items, including a shot at the brand-new **[GSOR the TANK](https://blitzkit.app/tanks/gsor-the-tank/)**. But this time around, **they do not guarantee a tank every single time.** But as a bonus, the Music Box guarantees you will not receive duplicate tanks. Once you have a tank, the Music Box will try its best to give you a different one each time.
![](https://i.imgur.com/sxYV77L.jpeg)
## Win Battles, Earn Rewards
Each victory during the event rewards you with **3 Hot Coffees**, a seasonal currency that serves as the backbone of the progression system. These coffees can be exchanged for a variety of Festive Town rewards, giving you reasons to keep pushing through battles even if you’re just casually playing.
![](https://i.imgur.com/MXGxejz.jpeg)
With the coffees, you’ll also be able to claim **2 tank certificates**, one for **tier VII** vehicles and another covering **tier IX–X** options.
The tier VII pool includes the [E 25](https://blitzkit.app/tanks/e-25), [CS-52 LIS](https://blitzkit.app/tanks/cs-52-lis), [AMX 13 57](https://blitzkit.app/tanks/amx-13-57), [Steyr WT](https://blitzkit.app/tanks/steyr-wt), [Kunze Panzer](https://blitzkit.app/tanks/kunze-panzer), and [Super Hellcat](https://blitzkit.app/tanks/super-hellcat). Meanwhile, the tier IX–X certificate gives access to higher-tier picks like the [121B](https://blitzkit.app/tanks/121b), [AMX 30 B](https://blitzkit.app/tanks/amx-30-b), [Obj. 260](https://blitzkit.app/tanks/obj-260), [Char Futur 4](https://blitzkit.app/tanks/char-futur-4), [TL-7-120](https://blitzkit.app/tanks/tl-7-120), and [Object 452K](https://blitzkit.app/tanks/object-452k).
![](https://i.imgur.com/lxr7PE9.png)
## Upgrade Your Christmas Tree
Golden tokens allow you to upgrade your Christmas Tree through **four distinct stages**, each bringing your garage closer to its fully decorated holiday form.
![](https://i.imgur.com/bcmQE8T.jpeg)
As the tree grows, additional ornament slots open up, eight in total, which appear on the left side of your screen. These slots give you more room to customize the look of your garage and make the tree feel like something you’ve built up piece by piece over the course of the event. Each new ornament adds to the atmosphere, making the progression feel both cosmetic and rewarding.
A fully decorated tree grants up to **+25% silver** per battle, turning your regular gameplay into a more profitable experience without requiring extra effort. Ensure all your ornaments come from the same set to unlock the full potential of the tree (all left, all middle, or all right).
![](https://i.imgur.com/MMbyYLn.jpeg)
## Upgrade Your Yuletide Stall
Your golden tokens can also be invested into upgrading the **Yuletide Stall**, another element of the garage that evolves as you level it up. Each upgrade unlocks new rewards, giving you incentives to distribute your tokens across different attractions instead of focusing on only one. The stall begins in a simple state, but after upgrades it transforms visually and functionally, becoming a more vibrant part of your garage.
![](https://i.imgur.com/AONZN5O.jpeg)
Each upgrade unlocks a tier worth of rewards that you can claim in exchange for Free XP, Gold, Silver, and more Hot Coffees!
![](https://i.imgur.com/8fNy0Yj.png)
## Upgrade Your Shooting Gallery
Another upgradeable area is the **Shooting Gallery**, which contributes its own set of rewards as it levels up. Enhancing the gallery expands your options for exchanging mastery badges, making those badges more useful during the event than they usually are.
![](https://i.imgur.com/Rj1WNCE.jpeg)
![](https://i.imgur.com/GAUd2uR.png)
Consider yourselves debriefed. Happy holidays!
+31 -17
View File
@@ -1,19 +1,25 @@
{ {
"name": "blitzkit", "name": "blitzkit",
"version": "1.29.14",
"license": "BlitzKit License",
"private": true, "private": true,
"version": "1.28.3",
"description": "🎉 Tools for everything World of Tanks Blitz",
"type": "module",
"types": "./types.d.ts",
"workspaces": [
"packages/*"
],
"scripts": { "scripts": {
"build:assets": "cd packages/scripts && bun run build:assets", "build:assets": "cd packages/scripts && bun run build:assets",
"build:averages": "bun --filter=@blitzkit/scripts run build:averages", "build:averages": "bun --filter=@blitzkit/scripts run build:averages",
"build:bot": "cd packages/scripts && bun run build:bot", "build:bot": "cd packages/scripts && bun run build:bot",
"build:website": "cd packages/website && bun run build", "build:website": "bun build:prisma --no-hints && cd packages/website && bun run build",
"build:prisma": "prisma generate", "build:prisma": "prisma generate",
"build:sitemap": "bun scripts/sitemap.ts",
"build:docs": "vitepress build docs",
"build:proxyclient": "cd packages/scripts && bun run build:proxyclient", "build:proxyclient": "cd packages/scripts && bun run build:proxyclient",
"build:catalogitemaccesssor": "cd packages/scripts && bun run build:catalogitemaccesssor",
"build:ratingleaderboard": "cd packages/scripts && bun run build:ratingleaderboard", "build:ratingleaderboard": "cd packages/scripts && bun run build:ratingleaderboard",
"build:app": "cd packages/app && bun run build", "build:app": "cd packages/app && bun run build",
"dev:bot": "bun run build:bot --dev && bun start:bot --dev", "dev:bot": "bun run build:bot --dev && bun start:bot",
"start:bot": "bun dist/bot/main.js", "start:bot": "bun dist/bot/main.js",
"dev:docs": "vitepress dev docs", "dev:docs": "vitepress dev docs",
"dev:website": "cd packages/website && bun run dev", "dev:website": "cd packages/website && bun run dev",
@@ -30,22 +36,30 @@
"update": "yarn upgrade --latest", "update": "yarn upgrade --latest",
"tunnel": "cloudflared tunnel --url http://localhost:3000", "tunnel": "cloudflared tunnel --url http://localhost:3000",
"discoverids": "scripts/discoverIds.ts", "discoverids": "scripts/discoverIds.ts",
"build:protos": "cd packages/scripts && bun run build:protos", "build:protos": "bun packages/scripts/src/protos.ts",
"preview:website": "cd packages/website && bun run preview", "preview:website": "cd packages/website && bun run preview",
"build:reviews": "cd packages/scripts && bun build:reviews", "build:reviews": "cd packages/scripts && bun build:reviews",
"build:ids": "cd packages/scripts && bun build:ids", "build:ids": "cd packages/scripts && bun build:ids",
"prunestrings": "cd packages/scripts && bun prunestrings", "prunestrings": "cd packages/scripts && bun prunestrings"
"setup": "bun install && bun run build:prisma && bun run build:protos && bun run build:proxyclient",
"patchnotes": "cd packages/scripts && bun run patchnotes"
}, },
"type": "module", "license": "MIT",
"workspaces": [
"packages/*"
],
"dependencies": { "dependencies": {
"@prisma/client": "^6.18.0", "@types/xmpp__client": "^0.13.3",
"prisma": "^6.18.0", "@xmpp/client": "^0.13.6",
"sharp": "^0.34.5", "astro-favicons": "^3.1.5",
"typescript": "^5.9.3" "cheerio": "^1.1.2",
"googleapis": "^160.0.0",
"lodash-es": "^4.17.21",
"protobufjs": "^7.5.4",
"quicklime": "^1.2.0",
"sharp": "^0.34.4"
},
"devDependencies": {
"@types/bun": "^1.2.22",
"@types/lodash-es": "^4.17.12",
"@types/node": "^24.5.2",
"prettier": "^3.6.2",
"prisma": "^6.16.2",
"typescript": "^5.9.2"
} }
} }
+11 -11
View File
@@ -356,9 +356,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.11.1" version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@@ -2105,9 +2105,9 @@ checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
[[package]] [[package]]
name = "num-conv" name = "num-conv"
version = "0.2.0" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]] [[package]]
name = "num-traits" name = "num-traits"
@@ -3907,30 +3907,30 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.47" version = "0.3.44"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
dependencies = [ dependencies = [
"deranged", "deranged",
"itoa", "itoa",
"num-conv", "num-conv",
"powerfmt", "powerfmt",
"serde_core", "serde",
"time-core", "time-core",
"time-macros", "time-macros",
] ]
[[package]] [[package]]
name = "time-core" name = "time-core"
version = "0.1.8" version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
[[package]] [[package]]
name = "time-macros" name = "time-macros"
version = "0.2.27" version = "0.2.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
dependencies = [ dependencies = [
"num-conv", "num-conv",
"time-core", "time-core",
+13 -22
View File
@@ -1,35 +1,26 @@
{ {
"name": "@blitzkit/bot", "name": "@blitzkit/bot",
"private": true,
"type": "module",
"types": "src/types.d.ts",
"dependencies": { "dependencies": {
"@blitzkit/core": "workspace:*", "@blitzkit/core": "workspace:*",
"@blitzkit/i18n": "workspace:*", "@blitzkit/i18n": "workspace:*",
"@radix-ui/colors": "^3.0.0", "satori": "^0.18.3",
"@radix-ui/themes": "^3.3.0",
"@resvg/resvg-js": "^2.6.2", "@resvg/resvg-js": "^2.6.2",
"@types/adm-zip": "^0.5.7", "discord.js": "^14.22.1",
"@types/bun": "^1.3.9", "react": "^19.1.1",
"@types/lodash-es": "^4.17.12", "@radix-ui/colors": "^3.0.0",
"@types/markdown-escape": "^1.1.3",
"@types/react": "^19.2.14",
"adm-zip": "^0.5.16",
"bepaint": "^0.1.0-beta.3",
"discord.js": "^14.25.1",
"dotenv": "^17.3.1",
"fuzzysort": "^3.1.0", "fuzzysort": "^3.1.0",
"lodash-es": "^4.17.23", "bepaint": "^0.1.0-beta.3",
"markdown-escape": "^2.0.0", "dotenv": "^17.2.2",
"pickleparser": "^0.2.1", "markdown-escape": "^2.0.0"
"prisma": "^6.19.2",
"protobufjs": "^7.5.4",
"react": "^19.2.4",
"satori": "^0.18.4",
"sharp": "^0.34.5"
}, },
"private": true,
"scripts": { "scripts": {
"dev": "bun --watch src/main.ts", "dev": "bun --watch src/main.ts",
"start": "bun src/main.ts" "start": "bun src/main.ts"
}, },
"type": "module", "devDependencies": {
"types": "src/types.d.ts" "@types/markdown-escape": "^1.1.3"
}
} }
+18 -21
View File
@@ -1,31 +1,28 @@
import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
import { translator } from "../core/localization/translator"; import { translator } from '../core/localization/translator';
import { import { COMMANDS_RAW, CommandRegistry } from '../events/interactionCreate';
commandsArray,
type CommandRegistry,
} from "../events/interactionCreate";
const subcommands = [ const subcommands = [
"embeds", 'embeds',
"introduction", 'introduction',
"invite", 'invite',
"timezones", 'timezones',
"credits", 'credits',
] as const; ] as const;
type Subcommand = (typeof subcommands)[number]; type Subcommand = (typeof subcommands)[number];
const DOCS: Record<Subcommand, string> = { const DOCS: Record<Subcommand, string> = {
embeds: "guide/embeds", embeds: 'guide/embeds',
introduction: "guide/introduction", introduction: 'guide/introduction',
invite: "guide/invite", invite: 'guide/invite',
timezones: "guide/timezones", timezones: 'guide/timezones',
credits: "about/credits", credits: 'about/credits',
}; };
export const aboutCommand = new Promise<CommandRegistry>(async (resolve) => { export const aboutCommand = new Promise<CommandRegistry>(async (resolve) => {
resolve({ resolve({
command: createLocalizedCommand("about", [ command: createLocalizedCommand('about', [
{ subcommand: "commands" }, { subcommand: 'commands' },
...subcommands.map((subcommand) => ({ subcommand })), ...subcommands.map((subcommand) => ({ subcommand })),
]), ]),
@@ -33,9 +30,9 @@ export const aboutCommand = new Promise<CommandRegistry>(async (resolve) => {
const subcommand = interaction.options.getSubcommand(); const subcommand = interaction.options.getSubcommand();
const { strings } = translator(interaction.locale); const { strings } = translator(interaction.locale);
if (subcommand === "commands") { if (subcommand === 'commands') {
return `${strings.bot.commands.about.subcommands.commands.body}${( return `${strings.bot.commands.about.subcommands.commands.body}${(
await Promise.all(commandsArray) await Promise.all(COMMANDS_RAW)
) )
.sort((a, b) => .sort((a, b) =>
(a.command.name_localizations?.[interaction.locale] ?? (a.command.name_localizations?.[interaction.locale] ??
@@ -56,7 +53,7 @@ export const aboutCommand = new Promise<CommandRegistry>(async (resolve) => {
] ?? registry.command.description ] ?? registry.command.description
}`, }`,
) )
.join("\n")}`; .join('\n')}`;
} }
const url = `https://raw.githubusercontent.com/tresabhi/blitzkit/main/docs/${DOCS[subcommand as Subcommand]}.md`; const url = `https://raw.githubusercontent.com/tresabhi/blitzkit/main/docs/${DOCS[subcommand as Subcommand]}.md`;
+36 -33
View File
@@ -1,46 +1,49 @@
import { addPeriodicFilterOptions } from "../../core/discord/addPeriodicFilterOptions"; import { getBlitzStarsLinkButton } from '../../core/blitzstars/getBlitzStarsLinkButton';
import { addUsernameChoices } from "../../core/discord/addUsernameChoices"; import { addPeriodicFilterOptions } from '../../core/discord/addPeriodicFilterOptions';
import { autocompleteTanks } from "../../core/discord/autocompleteTanks"; import { addUsernameChoices } from '../../core/discord/addUsernameChoices';
import { autocompleteUsername } from "../../core/discord/autocompleteUsername"; import { autocompleteTanks } from '../../core/discord/autocompleteTanks';
import { createLocalizedCommand } from "../../core/discord/createLocalizedCommand"; import { autocompleteUsername } from '../../core/discord/autocompleteUsername';
import { getFiltersFromButton } from "../../core/discord/getFiltersFromButton"; import { buttonRefresh } from '../../core/discord/buttonRefresh';
import { resolvePeriodFromButton } from "../../core/discord/resolvePeriodFromButton"; import { commandToURL } from '../../core/discord/commandToURL';
import { resolvePlayerFromButton } from "../../core/discord/resolvePlayerFromButton"; import { createLocalizedCommand } from '../../core/discord/createLocalizedCommand';
import { translator } from "../../core/localization/translator"; import { getCustomPeriodParams } from '../../core/discord/getCustomPeriodParams';
import type { CommandRegistry } from "../../events/interactionCreate"; import { getFiltersFromButton } from '../../core/discord/getFiltersFromButton';
import { renderBreakdown } from "./render"; import { getFiltersFromCommand } from '../../core/discord/getFiltersFromCommand';
import { resolvePeriodFromButton } from '../../core/discord/resolvePeriodFromButton';
import { resolvePeriodFromCommand } from '../../core/discord/resolvePeriodFromCommand';
import { resolvePlayerFromButton } from '../../core/discord/resolvePlayerFromButton';
import { resolvePlayerFromCommand } from '../../core/discord/resolvePlayerFromCommand';
import { CommandRegistry } from '../../events/interactionCreate';
import { renderBreakdown } from './render';
export const breakdownCommand = new Promise<CommandRegistry>( export const breakdownCommand = new Promise<CommandRegistry>(
async (resolve) => { async (resolve) => {
resolve({ resolve({
command: await addPeriodicFilterOptions( command: await addPeriodicFilterOptions(
createLocalizedCommand("breakdown"), createLocalizedCommand('breakdown'),
(option) => option.addStringOption(addUsernameChoices), (option) => option.addStringOption(addUsernameChoices),
), ),
async handler(interaction) { async handler(interaction) {
const { strings } = translator(interaction.locale); const player = await resolvePlayerFromCommand(interaction);
return strings.bot.commands.dead; const period = resolvePeriodFromCommand(player.region, interaction);
const filters = await getFiltersFromCommand(interaction);
const path = commandToURL(interaction, {
...player,
...getCustomPeriodParams(interaction),
...filters,
});
// const player = await resolvePlayerFromCommand(interaction); return [
// const period = resolvePeriodFromCommand(player.region, interaction); ...(await renderBreakdown(
// const filters = await getFiltersFromCommand(interaction); player,
// const path = commandToURL(interaction, { period,
// ...player, filters,
// ...getCustomPeriodParams(interaction), interaction.locale,
// ...filters, )),
// }); buttonRefresh(interaction, path),
await getBlitzStarsLinkButton(player.region, player.id),
// return [ ];
// ...(await renderBreakdown(
// player,
// period,
// filters,
// interaction.locale,
// )),
// buttonRefresh(interaction, path),
// await getBlitzStarsLinkButton(player.region, player.id),
// ];
}, },
autocomplete: (interaction) => { autocomplete: (interaction) => {
+30 -35
View File
@@ -1,5 +1,5 @@
import { import {
type BlitzStats, BlitzStats,
calculateWN8, calculateWN8,
getAccountInfo, getAccountInfo,
getClanAccountInfo, getClanAccountInfo,
@@ -15,18 +15,13 @@ import { CommandWrapper } from "../../components/CommandWrapper";
import { NoData } from "../../components/NoData"; import { NoData } from "../../components/NoData";
import { TitleBar } from "../../components/TitleBar"; import { TitleBar } from "../../components/TitleBar";
import { filtersToDescription } from "../../core/blitzkit/filtersToDescription"; import { filtersToDescription } from "../../core/blitzkit/filtersToDescription";
import { import { tankDefinitions } from "../../core/blitzkit/nonBlockingPromises";
averageDefinitions,
tankDefinitions,
} from "../../core/blitzkit/nonBlockingPromises";
import { UserError } from "../../core/blitzkit/userError"; import { UserError } from "../../core/blitzkit/userError";
import { import { filterStats, StatFilters } from "../../core/blitzstars/filterStats";
filterStats,
type StatFilters,
} from "../../core/blitzstars/filterStats";
import { getStatsInPeriod } from "../../core/blitzstars/getStatsInPeriod"; import { getStatsInPeriod } from "../../core/blitzstars/getStatsInPeriod";
import type { ResolvedPeriod } from "../../core/discord/resolvePeriodFromCommand"; import { blitzStarsTankAverages } from "../../core/blitzstars/tankAverages";
import type { ResolvedPlayer } from "../../core/discord/resolvePlayerFromCommand"; import { ResolvedPeriod } from "../../core/discord/resolvePeriodFromCommand";
import { ResolvedPlayer } from "../../core/discord/resolvePlayerFromCommand";
import { translator } from "../../core/localization/translator"; import { translator } from "../../core/localization/translator";
const ROWS_PER_PAGE = 8; const ROWS_PER_PAGE = 8;
@@ -36,11 +31,11 @@ export async function renderBreakdown(
{ region, id }: ResolvedPlayer, { region, id }: ResolvedPlayer,
{ start, end, name }: ResolvedPeriod, { start, end, name }: ResolvedPeriod,
filters: StatFilters, filters: StatFilters,
locale: Locale, locale: Locale
) { ) {
const { strings, unwrap } = translator(locale); const { strings, unwrap } = translator(locale);
const awaitedTankDefinitions = await tankDefinitions; const awaitedTankDefinitions = await tankDefinitions;
const awaitedTankAverages = await averageDefinitions; const awaitedTankAverages = await blitzStarsTankAverages;
const statsInPeriod = await getStatsInPeriod(region, id, start, end, locale); const statsInPeriod = await getStatsInPeriod(region, id, start, end, locale);
const { filteredOrder } = await filterStats(statsInPeriod, filters); const { filteredOrder } = await filterStats(statsInPeriod, filters);
const accountInfo = await getAccountInfo(region, id); const accountInfo = await getAccountInfo(region, id);
@@ -58,14 +53,14 @@ export async function renderBreakdown(
} }
filteredOrder.forEach((id) => { filteredOrder.forEach((id) => {
const tankAveragesEntry = awaitedTankAverages.averages[id]; const tankAveragesEntry = awaitedTankAverages[id];
const current = statsInPeriod.diff[id]; const current = statsInPeriod.diff[id];
const career = tankStats.find((tank) => tank.tank_id === id)!.all; const career = tankStats.find((tank) => tank.tank_id === id)!.all;
const currentWN8 = tankAveragesEntry const currentWN8 = tankAveragesEntry
? calculateWN8(tankAveragesEntry.mu, current) ? calculateWN8(tankAveragesEntry.all, current)
: undefined; : undefined;
const careerWN8 = tankAveragesEntry const careerWN8 = tankAveragesEntry
? calculateWN8(tankAveragesEntry.mu, career) ? calculateWN8(tankAveragesEntry.all, career)
: undefined; : undefined;
orderedCurrentStats.push(current); orderedCurrentStats.push(current);
@@ -74,65 +69,65 @@ export async function renderBreakdown(
orderedCareerWN8.push(careerWN8); orderedCareerWN8.push(careerWN8);
}); });
tankStats.forEach((tank) => { tankStats.forEach((tank) => {
const tankAveragesEntry = awaitedTankAverages.averages[tank.tank_id]; const tankAveragesEntry = awaitedTankAverages[tank.tank_id];
orderedCareerWN8Full.push( orderedCareerWN8Full.push(
tankAveragesEntry tankAveragesEntry
? calculateWN8(tankAveragesEntry.mu, tank.all) ? calculateWN8(tankAveragesEntry.all, tank.all)
: undefined, : undefined
); );
}); });
const currentBattles = orderedCurrentStats.reduce( const currentBattles = orderedCurrentStats.reduce(
(accumulator, current) => accumulator + current.battles, (accumulator, current) => accumulator + current.battles,
0, 0
); );
const careerBattles = tankStats.reduce( const careerBattles = tankStats.reduce(
(accumulator, current) => accumulator + current.all.battles, (accumulator, current) => accumulator + current.all.battles,
0, 0
); );
const currentWinrate = const currentWinrate =
orderedCurrentStats.reduce( orderedCurrentStats.reduce(
(accumulator, current) => accumulator + current.wins, (accumulator, current) => accumulator + current.wins,
0, 0
) / currentBattles; ) / currentBattles;
const careerWinrate = const careerWinrate =
tankStats.reduce( tankStats.reduce(
(accumulator, current) => accumulator + current.all.wins, (accumulator, current) => accumulator + current.all.wins,
0, 0
) / careerBattles; ) / careerBattles;
const currentWN8 = const currentWN8 =
orderedCurrentWN8.reduce<number>( orderedCurrentWN8.reduce<number>(
(accumulator, current, index) => (accumulator, current, index) =>
accumulator + (current ?? 0) * orderedCurrentStats[index].battles, accumulator + (current ?? 0) * orderedCurrentStats[index].battles,
0, 0
) / ) /
orderedCurrentStats.reduce( orderedCurrentStats.reduce(
(accumulator, current, index) => (accumulator, current, index) =>
accumulator + accumulator +
(orderedCurrentWN8[index] === undefined ? 0 : current.battles), (orderedCurrentWN8[index] === undefined ? 0 : current.battles),
0, 0
); );
const careerWN8 = const careerWN8 =
orderedCareerWN8Full.reduce<number>( orderedCareerWN8Full.reduce<number>(
(accumulator, current) => accumulator + (current ?? 0), (accumulator, current) => accumulator + (current ?? 0),
0, 0
) / ) /
tankStats.reduce( tankStats.reduce(
(accumulator, current, index) => (accumulator, current, index) =>
accumulator + accumulator +
(orderedCareerWN8Full[index] === undefined ? 0 : current.all.battles), (orderedCareerWN8Full[index] === undefined ? 0 : current.all.battles),
0, 0
); );
const currentDamage = const currentDamage =
orderedCurrentStats.reduce( orderedCurrentStats.reduce(
(accumulator, current) => accumulator + current.damage_dealt, (accumulator, current) => accumulator + current.damage_dealt,
0, 0
) / currentBattles; ) / currentBattles;
const careerDamage = const careerDamage =
tankStats.reduce( tankStats.reduce(
(accumulator, current) => accumulator + current.all.damage_dealt, (accumulator, current) => accumulator + current.all.damage_dealt,
0, 0
) / careerBattles; ) / careerBattles;
const children: React.JSX.Element[] = []; const children: React.JSX.Element[] = [];
@@ -177,7 +172,7 @@ export async function renderBreakdown(
delta: currentDamage - careerDamage, delta: currentDamage - careerDamage,
}, },
]} ]}
/>, />
); );
filteredOrder.forEach((id, index) => { filteredOrder.forEach((id, index) => {
@@ -211,7 +206,7 @@ export async function renderBreakdown(
{ {
title: strings.bot.commands.breakdown.body.winrate, title: strings.bot.commands.breakdown.body.winrate,
current: `${(100 * (current.wins / current.battles)).toFixed( current: `${(100 * (current.wins / current.battles)).toFixed(
2, 2
)}%`, )}%`,
career: `${(100 * (career.wins / career.battles)).toFixed(2)}%`, career: `${(100 * (career.wins / career.battles)).toFixed(2)}%`,
delta: delta:
@@ -235,24 +230,24 @@ export async function renderBreakdown(
{ {
title: strings.bot.commands.breakdown.body.damage, title: strings.bot.commands.breakdown.body.damage,
current: Math.round( current: Math.round(
current.damage_dealt / current.battles, current.damage_dealt / current.battles
).toLocaleString(), ).toLocaleString(),
career: Math.round( career: Math.round(
career.damage_dealt / career.battles, career.damage_dealt / career.battles
).toLocaleString(), ).toLocaleString(),
delta: delta:
current.damage_dealt / current.battles - current.damage_dealt / current.battles -
career.damage_dealt / career.battles, career.damage_dealt / career.battles,
}, },
]} ]}
/>, />
); );
}); });
} }
const pages = chunk( const pages = chunk(
children, children,
Math.max(Math.ceil(children.length / MAX_PAGES), ROWS_PER_PAGE), Math.max(Math.ceil(children.length / MAX_PAGES), ROWS_PER_PAGE)
); );
if (filteredOrder.length > 0) { if (filteredOrder.length > 0) {
+9 -9
View File
@@ -1,14 +1,14 @@
import packageJSON from "../../../../package.json"; import packageJSON from '../../../../package.json';
import { client } from "../core/discord/client"; import { client } from '../core/discord/client';
import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
import { translator } from "../core/localization/translator"; import { translator } from '../core/localization/translator';
import type { CommandRegistry } from "../events/interactionCreate"; import { CommandRegistry } from '../events/interactionCreate';
const executionStart = new Date().getTime(); const executionStart = new Date().getTime();
export const debugCommand = new Promise<CommandRegistry>((resolve) => { export const debugCommand = new Promise<CommandRegistry>((resolve) => {
resolve({ resolve({
command: createLocalizedCommand("debug"), command: createLocalizedCommand('debug'),
async handler(interaction) { async handler(interaction) {
const { strings } = translator(interaction.locale); const { strings } = translator(interaction.locale);
@@ -23,15 +23,15 @@ export const debugCommand = new Promise<CommandRegistry>((resolve) => {
[ [
strings.bot.commands.debug.body.uptime, strings.bot.commands.debug.body.uptime,
`${Math.floor((uptime / 1000 / 60 / 60) % 24)}h ${Math.floor( `${Math.floor((uptime / 1000 / 60 / 60) % 24)}h ${Math.floor(
(uptime / 1000 / 60) % 60 (uptime / 1000 / 60) % 60,
)}m ${Math.floor((uptime / 1000) % 60)}s ${Math.floor( )}m ${Math.floor((uptime / 1000) % 60)}s ${Math.floor(
uptime % 1000 uptime % 1000,
)}ms`, )}ms`,
], ],
]; ];
return `${strings.bot.commands.debug.body.title}${list return `${strings.bot.commands.debug.body.title}${list
.map(([key, value]) => `- ${key}: ${value}`) .map(([key, value]) => `- ${key}: ${value}`)
.join("\n")}`; .join('\n')}`;
}, },
}); });
}); });
+70 -63
View File
@@ -1,38 +1,48 @@
import { emblemURL, getAccountInfo, getClanAccountInfo } from "@blitzkit/core"; import { emblemURL, getAccountInfo, getClanAccountInfo } from '@blitzkit/core';
import { Locale, SlashCommandSubcommandGroupBuilder } from "discord.js"; import { Locale, SlashCommandSubcommandGroupBuilder } from 'discord.js';
import { CommandWrapper } from "../components/CommandWrapper"; import { CommandWrapper } from '../components/CommandWrapper';
import * as Graph from "../components/Graph"; import * as Graph from '../components/Graph';
import { LineColor } from "../components/Graph/components/Line/constants"; import { LineColor } from '../components/Graph/components/Line/constants';
import { NoData } from "../components/NoData"; import { NoData } from '../components/NoData';
import { TitleBar } from "../components/TitleBar"; import { TitleBar } from '../components/TitleBar';
import { getPlayerHistories } from "../core/blitzstars/getPlayerHistories"; import { resolveTankId } from '../core/blitz/resolveTankId';
import { getTankHistories } from "../core/blitzstars/getTankHistories"; import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
import { addPeriodSubCommands } from "../core/discord/addPeriodSubCommands"; import { getPlayerHistories } from '../core/blitzstars/getPlayerHistories';
import { addTankChoices } from "../core/discord/addTankChoices"; import { getTankHistories } from '../core/blitzstars/getTankHistories';
import { addUsernameChoices } from "../core/discord/addUsernameChoices"; import { addPeriodSubCommands } from '../core/discord/addPeriodSubCommands';
import { autocompleteTanks } from "../core/discord/autocompleteTanks"; import { addTankChoices } from '../core/discord/addTankChoices';
import { autocompleteUsername } from "../core/discord/autocompleteUsername"; import { addUsernameChoices } from '../core/discord/addUsernameChoices';
import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; import { autocompleteTanks } from '../core/discord/autocompleteTanks';
import { resolvePeriodFromButton } from "../core/discord/resolvePeriodFromButton"; import { autocompleteUsername } from '../core/discord/autocompleteUsername';
import { type ResolvedPeriod } from "../core/discord/resolvePeriodFromCommand"; import { buttonRefresh } from '../core/discord/buttonRefresh';
import { resolvePlayerFromButton } from "../core/discord/resolvePlayerFromButton"; import { commandToURL } from '../core/discord/commandToURL';
import { type ResolvedPlayer } from "../core/discord/resolvePlayerFromCommand"; import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
import { translator } from "../core/localization/translator"; import { getCustomPeriodParams } from '../core/discord/getCustomPeriodParams';
import type { CommandRegistry } from "../events/interactionCreate"; import { resolvePeriodFromButton } from '../core/discord/resolvePeriodFromButton';
import {
ResolvedPeriod,
resolvePeriodFromCommand,
} from '../core/discord/resolvePeriodFromCommand';
import { resolvePlayerFromButton } from '../core/discord/resolvePlayerFromButton';
import {
ResolvedPlayer,
resolvePlayerFromCommand,
} from '../core/discord/resolvePlayerFromCommand';
import { CommandRegistry } from '../events/interactionCreate';
type EvolutionStatType = "player" | "tank"; type EvolutionStatType = 'player' | 'tank';
async function render( async function render(
{ region, id }: ResolvedPlayer, { region, id }: ResolvedPlayer,
{ start, end, name }: ResolvedPeriod, { start, end, name }: ResolvedPeriod,
type: "player" | "tank", type: 'player' | 'tank',
tankId: number | null, tankId: number | null,
locale: Locale, locale: Locale,
) { ) {
const accountInfo = await getAccountInfo(region, id); const accountInfo = await getAccountInfo(region, id);
const clan = (await getClanAccountInfo(region, id, ["clan"]))?.clan; const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
const logo = clan ? emblemURL(clan.emblem_set_id) : undefined; const logo = clan ? emblemURL(clan.emblem_set_id) : undefined;
const histories = await (type === "player" const histories = await (type === 'player'
? getPlayerHistories(region, id, { ? getPlayerHistories(region, id, {
start, start,
end, end,
@@ -72,7 +82,7 @@ async function render(
verticalMargin={{ verticalMargin={{
min: minY, min: minY,
max: maxY, max: maxY,
suffix: "%", suffix: '%',
precision: 2, precision: 2,
}} }}
horizontalMargin={{ horizontalMargin={{
@@ -100,9 +110,9 @@ async function render(
export const evolutionCommand = new Promise<CommandRegistry>( export const evolutionCommand = new Promise<CommandRegistry>(
async (resolve) => { async (resolve) => {
resolve({ resolve({
command: createLocalizedCommand("evolution", [ command: createLocalizedCommand('evolution', [
{ {
group: "player", group: 'player',
modify(option: SlashCommandSubcommandGroupBuilder) { modify(option: SlashCommandSubcommandGroupBuilder) {
addPeriodSubCommands(option, (option) => addPeriodSubCommands(option, (option) =>
option.addStringOption(addUsernameChoices), option.addStringOption(addUsernameChoices),
@@ -110,7 +120,7 @@ export const evolutionCommand = new Promise<CommandRegistry>(
}, },
}, },
{ {
group: "tank", group: 'tank',
modify(option: SlashCommandSubcommandGroupBuilder) { modify(option: SlashCommandSubcommandGroupBuilder) {
addPeriodSubCommands(option, (option) => addPeriodSubCommands(option, (option) =>
option option
@@ -122,39 +132,36 @@ export const evolutionCommand = new Promise<CommandRegistry>(
]), ]),
async handler(interaction) { async handler(interaction) {
const { strings } = translator(interaction.locale); const commandGroup =
return strings.bot.commands.dead; interaction.options.getSubcommandGroup() as EvolutionStatType;
const player = await resolvePlayerFromCommand(interaction);
const period = resolvePeriodFromCommand(player.region, interaction);
const tankIdRaw = interaction.options.getString('tank')!;
const tankId =
commandGroup === 'tank'
? await resolveTankId(tankIdRaw, interaction.locale)
: null;
const path = commandToURL(interaction, {
...player,
...getCustomPeriodParams(interaction),
tankId,
});
// const commandGroup = return [
// interaction.options.getSubcommandGroup() as EvolutionStatType; await render(
// const player = await resolvePlayerFromCommand(interaction); player,
// const period = resolvePeriodFromCommand(player.region, interaction); period,
// const tankIdRaw = interaction.options.getString("tank")!; commandGroup,
// const tankId = tankId,
// commandGroup === "tank" interaction.locale,
// ? await resolveTankId(tankIdRaw, interaction.locale) ),
// : null; buttonRefresh(interaction, path),
// const path = commandToURL(interaction, { await getBlitzStarsLinkButton(
// ...player, player.region,
// ...getCustomPeriodParams(interaction), player.id,
// tankId, tankId ?? undefined,
// }); ),
];
// return [
// await render(
// player,
// period,
// commandGroup,
// tankId,
// interaction.locale
// ),
// buttonRefresh(interaction, path),
// await getBlitzStarsLinkButton(
// player.region,
// player.id,
// tankId ?? undefined
// ),
// ];
}, },
autocomplete: (interaction) => { autocomplete: (interaction) => {
@@ -164,7 +171,7 @@ export const evolutionCommand = new Promise<CommandRegistry>(
async button(interaction) { async button(interaction) {
const url = new URL(`https://example.com/${interaction.customId}`); const url = new URL(`https://example.com/${interaction.customId}`);
const path = url.pathname.split("/"); const path = url.pathname.split('/');
const commandGroup = path[2] as EvolutionStatType; const commandGroup = path[2] as EvolutionStatType;
const player = await resolvePlayerFromButton(interaction); const player = await resolvePlayerFromButton(interaction);
const period = resolvePeriodFromButton(player.region, interaction); const period = resolvePeriodFromButton(player.region, interaction);
@@ -173,7 +180,7 @@ export const evolutionCommand = new Promise<CommandRegistry>(
player, player,
period, period,
commandGroup, commandGroup,
parseInt(url.searchParams.get("tankId") ?? "0") || null, parseInt(url.searchParams.get('tankId') ?? '0') || null,
interaction.locale, interaction.locale,
); );
}, },
+47 -40
View File
@@ -3,28 +3,38 @@ import {
getAccountInfo, getAccountInfo,
getClanAccountInfo, getClanAccountInfo,
getTierWeights, getTierWeights,
} from "@blitzkit/core"; } from '@blitzkit/core';
import { Locale } from "discord.js"; import { Locale } from 'discord.js';
import { CommandWrapper } from "../components/CommandWrapper"; import { CommandWrapper } from '../components/CommandWrapper';
import { GenericAllStats } from "../components/GenericAllStats"; import { GenericAllStats } from '../components/GenericAllStats';
import { NoData } from "../components/NoData"; import { NoData } from '../components/NoData';
import { TierWeights } from "../components/TierWeights"; import { TierWeights } from '../components/TierWeights';
import { TitleBar } from "../components/TitleBar"; import { TitleBar } from '../components/TitleBar';
import { filtersToDescription } from "../core/blitzkit/filtersToDescription"; import { filtersToDescription } from '../core/blitzkit/filtersToDescription';
import { filterStats, type StatFilters } from "../core/blitzstars/filterStats"; import { filterStats, StatFilters } from '../core/blitzstars/filterStats';
import { getStatsInPeriod } from "../core/blitzstars/getStatsInPeriod"; import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
import { addPeriodicFilterOptions } from "../core/discord/addPeriodicFilterOptions"; import { getStatsInPeriod } from '../core/blitzstars/getStatsInPeriod';
import { addUsernameChoices } from "../core/discord/addUsernameChoices"; import { addPeriodicFilterOptions } from '../core/discord/addPeriodicFilterOptions';
import { autocompleteTanks } from "../core/discord/autocompleteTanks"; import { addUsernameChoices } from '../core/discord/addUsernameChoices';
import { autocompleteUsername } from "../core/discord/autocompleteUsername"; import { autocompleteTanks } from '../core/discord/autocompleteTanks';
import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; import { autocompleteUsername } from '../core/discord/autocompleteUsername';
import { getFiltersFromButton } from "../core/discord/getFiltersFromButton"; import { buttonRefresh } from '../core/discord/buttonRefresh';
import { resolvePeriodFromButton } from "../core/discord/resolvePeriodFromButton"; import { commandToURL } from '../core/discord/commandToURL';
import { type ResolvedPeriod } from "../core/discord/resolvePeriodFromCommand"; import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
import { resolvePlayerFromButton } from "../core/discord/resolvePlayerFromButton"; import { getCustomPeriodParams } from '../core/discord/getCustomPeriodParams';
import { type ResolvedPlayer } from "../core/discord/resolvePlayerFromCommand"; import { getFiltersFromButton } from '../core/discord/getFiltersFromButton';
import { translator } from "../core/localization/translator"; import { getFiltersFromCommand } from '../core/discord/getFiltersFromCommand';
import type { CommandRegistry } from "../events/interactionCreate"; import { resolvePeriodFromButton } from '../core/discord/resolvePeriodFromButton';
import {
ResolvedPeriod,
resolvePeriodFromCommand,
} from '../core/discord/resolvePeriodFromCommand';
import { resolvePlayerFromButton } from '../core/discord/resolvePlayerFromButton';
import {
ResolvedPlayer,
resolvePlayerFromCommand,
} from '../core/discord/resolvePlayerFromCommand';
import { CommandRegistry } from '../events/interactionCreate';
async function render( async function render(
{ region, id }: ResolvedPlayer, { region, id }: ResolvedPlayer,
@@ -33,7 +43,7 @@ async function render(
locale: Locale, locale: Locale,
) { ) {
const { nickname } = await getAccountInfo(region, id); const { nickname } = await getAccountInfo(region, id);
const clan = (await getClanAccountInfo(region, id, ["clan"]))?.clan; const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined; const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined;
const diffedTankStats = await getStatsInPeriod( const diffedTankStats = await getStatsInPeriod(
region, region,
@@ -76,28 +86,25 @@ export const fullStatsCommand = new Promise<CommandRegistry>(
async (resolve) => { async (resolve) => {
resolve({ resolve({
command: await addPeriodicFilterOptions( command: await addPeriodicFilterOptions(
createLocalizedCommand("full-stats"), createLocalizedCommand('full-stats'),
(option) => option.addStringOption(addUsernameChoices), (option) => option.addStringOption(addUsernameChoices),
), ),
async handler(interaction) { async handler(interaction) {
const { strings } = translator(interaction.locale); const player = await resolvePlayerFromCommand(interaction);
return strings.bot.commands.dead; const period = resolvePeriodFromCommand(player.region, interaction);
const filters = await getFiltersFromCommand(interaction);
const path = commandToURL(interaction, {
...player,
...getCustomPeriodParams(interaction),
...filters,
});
// const player = await resolvePlayerFromCommand(interaction); return Promise.all([
// const period = resolvePeriodFromCommand(player.region, interaction); render(player, period, filters, interaction.locale),
// const filters = await getFiltersFromCommand(interaction); buttonRefresh(interaction, path),
// const path = commandToURL(interaction, { getBlitzStarsLinkButton(player.region, player.id),
// ...player, ]);
// ...getCustomPeriodParams(interaction),
// ...filters,
// });
// return Promise.all([
// render(player, period, filters, interaction.locale),
// buttonRefresh(interaction, path),
// getBlitzStarsLinkButton(player.region, player.id),
// ]);
}, },
autocomplete: (interaction) => { autocomplete: (interaction) => {
+1 -1
View File
@@ -11,7 +11,7 @@ import { createLocalizedCommand } from "../core/discord/createLocalizedCommand";
import { localizationObject } from "../core/discord/localizationObject"; import { localizationObject } from "../core/discord/localizationObject";
import { resolveClanFromCommand } from "../core/discord/resolveClanFromCommand"; import { resolveClanFromCommand } from "../core/discord/resolveClanFromCommand";
import { translator } from "../core/localization/translator"; import { translator } from "../core/localization/translator";
import type { CommandRegistry } from "../events/interactionCreate"; import { CommandRegistry } from "../events/interactionCreate";
const DEFAULT_THRESHOLD = 7; const DEFAULT_THRESHOLD = 7;
+1 -1
View File
@@ -9,7 +9,7 @@ import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
import { embedInfo } from '../core/discord/embedInfo'; import { embedInfo } from '../core/discord/embedInfo';
import { resolvePlayerFromCommand } from '../core/discord/resolvePlayerFromCommand'; import { resolvePlayerFromCommand } from '../core/discord/resolvePlayerFromCommand';
import { translator } from '../core/localization/translator'; import { translator } from '../core/localization/translator';
import type { CommandRegistry } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
import { theme } from '../stitches.config'; import { theme } from '../stitches.config';
export const verifyCommand = new Promise<CommandRegistry>((resolve) => { export const verifyCommand = new Promise<CommandRegistry>((resolve) => {
+1 -1
View File
@@ -19,7 +19,7 @@ import { chunkLines } from "../core/discord/chunkLines";
import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; import { createLocalizedCommand } from "../core/discord/createLocalizedCommand";
import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand"; import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand";
import { translator } from "../core/localization/translator"; import { translator } from "../core/localization/translator";
import { type CommandRegistry } from "../events/interactionCreate"; import { CommandRegistry } from "../events/interactionCreate";
export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => { export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
resolve({ resolve({
+1 -1
View File
@@ -1,7 +1,7 @@
import { literals } from "@blitzkit/i18n"; import { literals } from "@blitzkit/i18n";
import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; import { createLocalizedCommand } from "../core/discord/createLocalizedCommand";
import { translator } from "../core/localization/translator"; import { translator } from "../core/localization/translator";
import type { CommandRegistry } from "../events/interactionCreate"; import { CommandRegistry } from "../events/interactionCreate";
export const pingCommand = new Promise<CommandRegistry>((resolve) => { export const pingCommand = new Promise<CommandRegistry>((resolve) => {
resolve({ resolve({
+1 -1
View File
@@ -22,7 +22,7 @@ import { addUsernameChoices } from "../core/discord/addUsernameChoices";
import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; import { createLocalizedCommand } from "../core/discord/createLocalizedCommand";
import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand"; import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand";
import { translator } from "../core/localization/translator"; import { translator } from "../core/localization/translator";
import type { CommandRegistry } from "../events/interactionCreate"; import { CommandRegistry } from "../events/interactionCreate";
import { theme } from "../stitches.config"; import { theme } from "../stitches.config";
const LEAGUE_ACCENT = [ const LEAGUE_ACCENT = [
+11 -11
View File
@@ -10,13 +10,13 @@ import {
imgur, imgur,
isOnGoingRatingSeason, isOnGoingRatingSeason,
LEAGUES, LEAGUES,
type RatingInfo, RatingInfo,
RatingLeaderboard, RatingLeaderboard,
type Region, Region,
} from "@blitzkit/core"; } from "@blitzkit/core";
import { literals } from "@blitzkit/i18n"; import { literals } from "@blitzkit/i18n";
import { import {
type APIApplicationCommandOptionChoice, APIApplicationCommandOptionChoice,
Locale, Locale,
SlashCommandSubcommandBuilder, SlashCommandSubcommandBuilder,
} from "discord.js"; } from "discord.js";
@@ -33,7 +33,7 @@ import { createLocalizedCommand } from "../core/discord/createLocalizedCommand";
import { localizationObject } from "../core/discord/localizationObject"; import { localizationObject } from "../core/discord/localizationObject";
import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand"; import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand";
import { translator } from "../core/localization/translator"; import { translator } from "../core/localization/translator";
import type { CommandRegistry } from "../events/interactionCreate"; import { CommandRegistry } from "../events/interactionCreate";
interface SimplifiedPlayer { interface SimplifiedPlayer {
id: number; id: number;
@@ -52,7 +52,7 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
const seasonNumbers = range( const seasonNumbers = range(
FIRST_MINIMAL_ARCHIVED_RATING_SEASON, FIRST_MINIMAL_ARCHIVED_RATING_SEASON,
latestArchivedSeasonNumber + (onGoingSeason ? 1 : 0) + 1 latestArchivedSeasonNumber + (onGoingSeason ? 1 : 0) + 1
).slice(-25); );
function addOptions(option: SlashCommandSubcommandBuilder) { function addOptions(option: SlashCommandSubcommandBuilder) {
const { strings } = translator(Locale.EnglishUS); const { strings } = translator(Locale.EnglishUS);
@@ -196,7 +196,7 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
strings.common.leagues as Record<string, string> strings.common.leagues as Record<string, string>
)[name] )[name]
), ),
} satisfies APIApplicationCommandOptionChoice<string>) }) satisfies APIApplicationCommandOptionChoice<string>
) )
) )
.setRequired(true); .setRequired(true);
@@ -219,8 +219,8 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
const season = seasonOption const season = seasonOption
? parseInt(seasonOption) ? parseInt(seasonOption)
: onGoingSeason : onGoingSeason
? latestArchivedSeasonNumber + 1 ? latestArchivedSeasonNumber + 1
: latestArchivedSeasonNumber; : latestArchivedSeasonNumber;
const isCurrentSeason = season === latestArchivedSeasonNumber + 1; const isCurrentSeason = season === latestArchivedSeasonNumber + 1;
let titleName: string; let titleName: string;
let titleImage: string | undefined; let titleImage: string | undefined;
@@ -258,7 +258,7 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
position: player.number, position: player.number,
clan: player.clan_tag, clan: player.clan_tag,
nickname: player.nickname, nickname: player.nickname,
} satisfies SimplifiedPlayer) }) satisfies SimplifiedPlayer
) )
) )
: await getArchivedRatingLeaderboard(region, season!).then( : await getArchivedRatingLeaderboard(region, season!).then(
@@ -295,7 +295,7 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
.deleted_player, .deleted_player,
{ id: player.id } { id: player.id }
), ),
} satisfies SimplifiedPlayer) }) satisfies SimplifiedPlayer
); );
} }
); );
@@ -349,7 +349,7 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
score: player.score, score: player.score,
clan: player.clan_tag, clan: player.clan_tag,
nickname: player.nickname, nickname: player.nickname,
} satisfies SimplifiedPlayer) }) satisfies SimplifiedPlayer
) )
) )
: await getArchivedRatingLeaderboard(region, season!).then( : await getArchivedRatingLeaderboard(region, season!).then(
+77 -92
View File
@@ -5,29 +5,28 @@ import {
idToRegion, idToRegion,
tankIcon, tankIcon,
TIER_ROMAN_NUMERALS, TIER_ROMAN_NUMERALS,
} from "@blitzkit/core"; } from '@blitzkit/core';
import { Locale } from "discord.js"; import { Locale } from 'discord.js';
import { clamp } from "lodash-es"; import { clamp } from 'lodash-es';
import { CommandWrapper } from "../components/CommandWrapper"; import { CommandWrapper } from '../components/CommandWrapper';
import { TitleBar } from "../components/TitleBar"; import { TitleBar } from '../components/TitleBar';
import { iconPng } from "../core/blitzkit/iconPng"; import { iconPng } from '../core/blitzkit/iconPng';
import { import {
averageDefinitions,
mapDefinitions, mapDefinitions,
tankDefinitions, tankDefinitions,
} from "../core/blitzkit/nonBlockingPromises"; } from '../core/blitzkit/nonBlockingPromises';
import { RenderConfiguration } from "../core/blitzkit/renderConfiguration"; import { RenderConfiguration } from '../core/blitzkit/renderConfiguration';
import { buttonLink } from "../core/discord/buttonLink"; import { blitzStarsTankAverages } from '../core/blitzstars/tankAverages';
import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; import { buttonLink } from '../core/discord/buttonLink';
import { localizationObject } from "../core/discord/localizationObject"; import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
import { translator } from "../core/localization/translator"; import { translator } from '../core/localization/translator';
import { type CommandRegistry } from "../events/interactionCreate"; import { CommandRegistry } from '../events/interactionCreate';
import { theme } from "../stitches.config"; import { theme } from '../stitches.config';
type WotInspectorReplaySubmission = type WotInspectorReplaySubmission =
| { status: "error"; data: unknown; error: { message: string } } | { status: 'error'; data: unknown; error: { message: string } }
| { | {
status: "ok"; status: 'ok';
data: { data: {
download_url: string; download_url: string;
view_url: string; view_url: string;
@@ -210,9 +209,9 @@ function stat(value: string | number, flex = 2) {
<div <div
style={{ style={{
flex, flex,
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
justifyContent: "center", justifyContent: 'center',
}} }}
> >
<span style={{ fontSize: 16 }}>{value}</span> <span style={{ fontSize: 16 }}>{value}</span>
@@ -228,15 +227,15 @@ async function playerListing(
recordingPlayer = false, recordingPlayer = false,
) { ) {
const { unwrap } = translator(locale); const { unwrap } = translator(locale);
const awaitedTankAverages = await averageDefinitions; const awaitedTankAverages = await blitzStarsTankAverages;
const awaitedTankDefinitions = await tankDefinitions; const awaitedTankDefinitions = await tankDefinitions;
const tank = awaitedTankDefinitions.tanks[player.vehicle_descr]; const tank = awaitedTankDefinitions.tanks[player.vehicle_descr];
const blockAccent = recordingPlayer ? "_amber" : ""; const blockAccent = recordingPlayer ? '_amber' : '';
const healthAccent = protagonistTeam const healthAccent = protagonistTeam
? recordingPlayer ? recordingPlayer
? "_amber" ? '_amber'
: "_green" : '_green'
: "_red"; : '_red';
// our turret ids are always off by 1 or 2 lol so imma just grab the closest // our turret ids are always off by 1 or 2 lol so imma just grab the closest
// TODO: 💀 // TODO: 💀
@@ -246,11 +245,11 @@ async function playerListing(
)[0]; )[0];
const totalHealth = turret.health + tank.health; const totalHealth = turret.health + tank.health;
const healthLeft = clamp(player.hitpoints_left / totalHealth, 0, 1); const healthLeft = clamp(player.hitpoints_left / totalHealth, 0, 1);
const tankAveragesEntry = awaitedTankAverages.averages[player.vehicle_descr]; const tankAveragesEntry = awaitedTankAverages[player.vehicle_descr];
const wn8 = const wn8 =
tankAveragesEntry === undefined tankAveragesEntry === undefined
? -1 ? -1
: calculateWN8(tankAveragesEntry.mu, { : calculateWN8(tankAveragesEntry.all, {
battles: 1, battles: 1,
damage_dealt: player.damage_made, damage_dealt: player.damage_made,
dropped_capture_points: player.base_defend_points, dropped_capture_points: player.base_defend_points,
@@ -263,8 +262,8 @@ async function playerListing(
<div <div
key={player.entity_id} key={player.entity_id}
style={{ style={{
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
gap: 8, gap: 8,
backgroundColor: theme.colors[`appBackground2${blockAccent}`], backgroundColor: theme.colors[`appBackground2${blockAccent}`],
padding: 8, padding: 8,
@@ -272,11 +271,11 @@ async function playerListing(
> >
<div <div
style={{ style={{
display: "flex", display: 'flex',
gap: 8, gap: 8,
flex: 1, flex: 1,
overflow: "hidden", overflow: 'hidden',
alignItems: "center", alignItems: 'center',
}} }}
> >
<img <img
@@ -285,22 +284,22 @@ async function playerListing(
style={{ style={{
width: 32, width: 32,
height: 32, height: 32,
objectFit: "contain", objectFit: 'contain',
}} }}
/> />
<div <div
style={{ style={{
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
// align // align
}} }}
> >
<div <div
style={{ style={{
display: "flex", display: 'flex',
gap: 4, gap: 4,
alignItems: "center", alignItems: 'center',
}} }}
> >
<span <span
@@ -325,23 +324,23 @@ async function playerListing(
style={{ style={{
fontSize: 16, fontSize: 16,
color: theme.colors[`textLowContrast${blockAccent}`], color: theme.colors[`textLowContrast${blockAccent}`],
whiteSpace: "nowrap", whiteSpace: 'nowrap',
}} }}
> >
{player.name} {player.name}
{player.clan_tag ? ` [${player.clan_tag}]` : ""} {player.clan_tag ? ` [${player.clan_tag}]` : ''}
</span> </span>
</div> </div>
</div> </div>
<div <div
style={{ style={{
display: "flex", display: 'flex',
flex: 1, flex: 1,
}} }}
> >
{stat(player.damage_made.toLocaleString())} {stat(player.damage_made.toLocaleString())}
{stat(wn8 === -1 ? "--" : Math.round(wn8).toLocaleString())} {stat(wn8 === -1 ? '--' : Math.round(wn8).toLocaleString())}
{stat(player.exp.toLocaleString())} {stat(player.exp.toLocaleString())}
{stat(player.enemies_destroyed, 1)} {stat(player.enemies_destroyed, 1)}
</div> </div>
@@ -350,15 +349,15 @@ async function playerListing(
style={{ style={{
borderRadius: 2, borderRadius: 2,
backgroundColor: theme.colors[`componentInteractive${healthAccent}`], backgroundColor: theme.colors[`componentInteractive${healthAccent}`],
display: "flex", display: 'flex',
width: 4, width: 4,
height: "100%", height: '100%',
transform: "scaleY(-1)", transform: 'scaleY(-1)',
}} }}
> >
<div <div
style={{ style={{
width: "100%", width: '100%',
height: `${healthLeft * 100}%`, height: `${healthLeft * 100}%`,
borderRadius: 2, borderRadius: 2,
backgroundColor: theme.colors[`solidBackground${healthAccent}`], backgroundColor: theme.colors[`solidBackground${healthAccent}`],
@@ -374,8 +373,8 @@ function Header() {
<div <div
style={{ style={{
padding: 8, padding: 8,
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
gap: 8, gap: 8,
}} }}
> >
@@ -383,16 +382,16 @@ function Header() {
<div <div
style={{ style={{
display: "flex", display: 'flex',
flex: 1, flex: 1,
}} }}
> >
<div <div
style={{ style={{
flex: 2, flex: 2,
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
justifyContent: "center", justifyContent: 'center',
}} }}
> >
<span <span
@@ -407,9 +406,9 @@ function Header() {
<div <div
style={{ style={{
flex: 2, flex: 2,
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
justifyContent: "center", justifyContent: 'center',
}} }}
> >
<span <span
@@ -424,9 +423,9 @@ function Header() {
<div <div
style={{ style={{
flex: 2, flex: 2,
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
justifyContent: "center", justifyContent: 'center',
}} }}
> >
<span <span
@@ -441,9 +440,9 @@ function Header() {
<div <div
style={{ style={{
flex: 1, flex: 1,
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
justifyContent: "center", justifyContent: 'center',
}} }}
> >
<span <span
@@ -461,42 +460,28 @@ function Header() {
} }
export const replayCommand = new Promise<CommandRegistry>((resolve) => { export const replayCommand = new Promise<CommandRegistry>((resolve) => {
const { strings } = translator(Locale.EnglishUS);
resolve({ resolve({
command: createLocalizedCommand("replay").addAttachmentOption((option) => command: createLocalizedCommand('replay').addAttachmentOption((option) =>
option option.setName('file').setDescription('Replay file').setRequired(true),
.setName(strings.bot.commands.replay.options.file.name)
.setNameLocalizations(
localizationObject(
(strings) => strings.bot.commands.replay.options.file.name,
undefined,
true,
),
)
.setDescription(strings.bot.commands.replay.options.file.description)
.setDescriptionLocalizations(
localizationObject(
(strings) => strings.bot.commands.replay.options.file.description,
),
)
.setRequired(true),
), ),
async handler(interaction) { async handler(interaction) {
const awaitedMapDefinitions = await mapDefinitions; const awaitedMapDefinitions = await mapDefinitions;
const file = interaction.options.getAttachment("file")!; const file = interaction.options.getAttachment('file')!;
// const file = {
// url: 'https://cdn.discordapp.com/ephemeral-attachments/1232434891652861972/1232781496016834581/20240328_1530__TresAbhi_M60_1157057448155406708.wotbreplay?ex=662ab4bc&is=6629633c&hm=c784cd10e80232350ddaf291fa987b879c4d0a8083b78298c9eee4fa0d507f6c&',
// };
const submissionData = (await fetch( const submissionData = (await fetch(
`https://wotinspector.com/api/replay/upload/?url=${encodeURIComponent( `https://wotinspector.com/api/replay/upload/?url=${encodeURIComponent(
file.url, file.url,
)}`, )}`,
).then((response) => response.json())) as WotInspectorReplaySubmission; ).then((response) => response.json())) as WotInspectorReplaySubmission;
if (submissionData.status === "error") { if (submissionData.status === 'error') {
return `# Uh oh! WoT Inspector didn't like that.\n\nThere was an error processing your replay. Get help on [the official Discord server](https://discord.gg/nDt7AjGJQH).`; return `# Uh oh! WoT Inspector didn't like that.\n\nThere was an error processing your replay. Get help on [the official Discord server](https://discord.gg/nDt7AjGJQH).`;
} }
const replayId = submissionData.data.download_url.split("/").at(-1)!; const replayId = submissionData.data.download_url.split('/').at(-1)!;
const replayData = (await fetch( const replayData = (await fetch(
`https://api.wotinspector.com/v2/blitz/replays/${replayId}/`, `https://api.wotinspector.com/v2/blitz/replays/${replayId}/`,
).then((response) => response.json())) as WotInspectorReplay; ).then((response) => response.json())) as WotInspectorReplay;
@@ -508,7 +493,7 @@ export const replayCommand = new Promise<CommandRegistry>((resolve) => {
await getClanAccountInfo( await getClanAccountInfo(
idToRegion(replayData.protagonist), idToRegion(replayData.protagonist),
replayData.protagonist, replayData.protagonist,
["clan"], ['clan'],
) )
)?.clan; )?.clan;
const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined; const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined;
@@ -521,22 +506,22 @@ export const replayCommand = new Promise<CommandRegistry>((resolve) => {
image={clanImage} image={clanImage}
description={`${ description={`${
replayData.protagonist_team === replayData.winner_team replayData.protagonist_team === replayData.winner_team
? "Victory" ? 'Victory'
: "Defeat" : 'Defeat'
} • ${awaitedMapDefinitions.maps[replayData.map_id].name}`} } • ${awaitedMapDefinitions.maps[replayData.map_id].name}`}
/> />
<div <div
style={{ style={{
display: "flex", display: 'flex',
gap: 8, gap: 8,
}} }}
> >
<div <div
style={{ style={{
flex: 1, flex: 1,
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
gap: 4, gap: 4,
}} }}
> >
@@ -562,8 +547,8 @@ export const replayCommand = new Promise<CommandRegistry>((resolve) => {
<div <div
style={{ style={{
flex: 1, flex: 1,
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
gap: 4, gap: 4,
}} }}
> >
@@ -584,8 +569,8 @@ export const replayCommand = new Promise<CommandRegistry>((resolve) => {
</div> </div>
</div> </div>
</CommandWrapper>, </CommandWrapper>,
buttonLink(submissionData.data.view_url, "WoT Inspector"), buttonLink(submissionData.data.view_url, 'WoT Inspector'),
buttonLink(submissionData.data.download_url, "Download"), buttonLink(submissionData.data.download_url, 'Download'),
]; ];
}, },
}); });
+1 -1
View File
@@ -25,7 +25,7 @@ import { createLocalizedCommand } from "../core/discord/createLocalizedCommand";
import { localizationObject } from "../core/discord/localizationObject"; import { localizationObject } from "../core/discord/localizationObject";
import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand"; import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand";
import { translator } from "../core/localization/translator"; import { translator } from "../core/localization/translator";
import { type CommandRegistry } from "../events/interactionCreate"; import { CommandRegistry } from "../events/interactionCreate";
import { theme } from "../stitches.config"; import { theme } from "../stitches.config";
export const researchCommand = new Promise<CommandRegistry>((resolve) => { export const researchCommand = new Promise<CommandRegistry>((resolve) => {
+2 -2
View File
@@ -1,4 +1,4 @@
import { type ClanList, fetchBlitz, type Region } from "@blitzkit/core"; import { ClanList, fetchBlitz, Region } from "@blitzkit/core";
import { literals } from "@blitzkit/i18n"; import { literals } from "@blitzkit/i18n";
import { Locale } from "discord.js"; import { Locale } from "discord.js";
import markdownEscape from "markdown-escape"; import markdownEscape from "markdown-escape";
@@ -8,7 +8,7 @@ import { createLocalizedCommand } from "../core/discord/createLocalizedCommand";
import { embedInfo } from "../core/discord/embedInfo"; import { embedInfo } from "../core/discord/embedInfo";
import { localizationObject } from "../core/discord/localizationObject"; import { localizationObject } from "../core/discord/localizationObject";
import { translator } from "../core/localization/translator"; import { translator } from "../core/localization/translator";
import type { CommandRegistry } from "../events/interactionCreate"; import { CommandRegistry } from "../events/interactionCreate";
const DEFAULT_LIMIT = 25; const DEFAULT_LIMIT = 25;
+3 -3
View File
@@ -1,7 +1,7 @@
import { import {
type AccountList, AccountList,
fetchBlitz, fetchBlitz,
type Region, Region,
usernamePattern, usernamePattern,
} from "@blitzkit/core"; } from "@blitzkit/core";
import { literals } from "@blitzkit/i18n"; import { literals } from "@blitzkit/i18n";
@@ -13,7 +13,7 @@ import { createLocalizedCommand } from "../core/discord/createLocalizedCommand";
import { embedInfo } from "../core/discord/embedInfo"; import { embedInfo } from "../core/discord/embedInfo";
import { localizationObject } from "../core/discord/localizationObject"; import { localizationObject } from "../core/discord/localizationObject";
import { translator } from "../core/localization/translator"; import { translator } from "../core/localization/translator";
import type { CommandRegistry } from "../events/interactionCreate"; import { CommandRegistry } from "../events/interactionCreate";
const DEFAULT_LIMIT = 25; const DEFAULT_LIMIT = 25;
+46 -39
View File
@@ -1,24 +1,34 @@
import { emblemURL, getAccountInfo, getClanAccountInfo } from "@blitzkit/core"; import { emblemURL, getAccountInfo, getClanAccountInfo } from '@blitzkit/core';
import { Locale } from "discord.js"; import { Locale } from 'discord.js';
import { AllStatsOverview } from "../components/AllStatsOverview"; import { AllStatsOverview } from '../components/AllStatsOverview';
import { CommandWrapper } from "../components/CommandWrapper"; import { CommandWrapper } from '../components/CommandWrapper';
import { NoData } from "../components/NoData"; import { NoData } from '../components/NoData';
import { TitleBar } from "../components/TitleBar"; import { TitleBar } from '../components/TitleBar';
import { filtersToDescription } from "../core/blitzkit/filtersToDescription"; import { filtersToDescription } from '../core/blitzkit/filtersToDescription';
import { filterStats, type StatFilters } from "../core/blitzstars/filterStats"; import { filterStats, StatFilters } from '../core/blitzstars/filterStats';
import { getStatsInPeriod } from "../core/blitzstars/getStatsInPeriod"; import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
import { addPeriodicFilterOptions } from "../core/discord/addPeriodicFilterOptions"; import { getStatsInPeriod } from '../core/blitzstars/getStatsInPeriod';
import { addUsernameChoices } from "../core/discord/addUsernameChoices"; import { addPeriodicFilterOptions } from '../core/discord/addPeriodicFilterOptions';
import { autocompleteTanks } from "../core/discord/autocompleteTanks"; import { addUsernameChoices } from '../core/discord/addUsernameChoices';
import { autocompleteUsername } from "../core/discord/autocompleteUsername"; import { autocompleteTanks } from '../core/discord/autocompleteTanks';
import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; import { autocompleteUsername } from '../core/discord/autocompleteUsername';
import { getFiltersFromButton } from "../core/discord/getFiltersFromButton"; import { buttonRefresh } from '../core/discord/buttonRefresh';
import { resolvePeriodFromButton } from "../core/discord/resolvePeriodFromButton"; import { commandToURL } from '../core/discord/commandToURL';
import { type ResolvedPeriod } from "../core/discord/resolvePeriodFromCommand"; import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
import { resolvePlayerFromButton } from "../core/discord/resolvePlayerFromButton"; import { getCustomPeriodParams } from '../core/discord/getCustomPeriodParams';
import { type ResolvedPlayer } from "../core/discord/resolvePlayerFromCommand"; import { getFiltersFromButton } from '../core/discord/getFiltersFromButton';
import { translator } from "../core/localization/translator"; import { getFiltersFromCommand } from '../core/discord/getFiltersFromCommand';
import type { CommandRegistry } from "../events/interactionCreate"; import { resolvePeriodFromButton } from '../core/discord/resolvePeriodFromButton';
import {
ResolvedPeriod,
resolvePeriodFromCommand,
} from '../core/discord/resolvePeriodFromCommand';
import { resolvePlayerFromButton } from '../core/discord/resolvePlayerFromButton';
import {
ResolvedPlayer,
resolvePlayerFromCommand,
} from '../core/discord/resolvePlayerFromCommand';
import { CommandRegistry } from '../events/interactionCreate';
async function render( async function render(
{ region, id }: ResolvedPlayer, { region, id }: ResolvedPlayer,
@@ -27,7 +37,7 @@ async function render(
locale: Locale, locale: Locale,
) { ) {
const { nickname } = await getAccountInfo(region, id); const { nickname } = await getAccountInfo(region, id);
const clan = (await getClanAccountInfo(region, id, ["clan"]))?.clan; const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined; const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined;
const diffedTankStats = await getStatsInPeriod( const diffedTankStats = await getStatsInPeriod(
region, region,
@@ -62,28 +72,25 @@ async function render(
export const statsCommand = new Promise<CommandRegistry>(async (resolve) => { export const statsCommand = new Promise<CommandRegistry>(async (resolve) => {
resolve({ resolve({
command: await addPeriodicFilterOptions( command: await addPeriodicFilterOptions(
createLocalizedCommand("stats"), createLocalizedCommand('stats'),
(option) => option.addStringOption(addUsernameChoices), (option) => option.addStringOption(addUsernameChoices),
), ),
async handler(interaction) { async handler(interaction) {
const { strings } = translator(interaction.locale); const player = await resolvePlayerFromCommand(interaction);
return strings.bot.commands.dead; const period = resolvePeriodFromCommand(player.region, interaction);
const filters = await getFiltersFromCommand(interaction);
const path = commandToURL(interaction, {
...player,
...getCustomPeriodParams(interaction),
...filters,
});
// const player = await resolvePlayerFromCommand(interaction); return Promise.all([
// const period = resolvePeriodFromCommand(player.region, interaction); render(player, period, filters, interaction.locale),
// const filters = await getFiltersFromCommand(interaction); buttonRefresh(interaction, path),
// const path = commandToURL(interaction, { getBlitzStarsLinkButton(player.region, player.id),
// ...player, ]);
// ...getCustomPeriodParams(interaction),
// ...filters,
// });
// return Promise.all([
// render(player, period, filters, interaction.locale),
// buttonRefresh(interaction, path),
// getBlitzStarsLinkButton(player.region, player.id),
// ]);
}, },
autocomplete: (interaction) => { autocomplete: (interaction) => {
+36 -33
View File
@@ -1,47 +1,50 @@
import { gameDefinitions } from "../core/blitzkit/nonBlockingPromises"; import { gameDefinitions } from '../core/blitzkit/nonBlockingPromises';
import { addFilterOptions } from "../core/discord/addFilterOptions"; import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
import { addUsernameChoices } from "../core/discord/addUsernameChoices"; import { addFilterOptions } from '../core/discord/addFilterOptions';
import { autocompleteTanks } from "../core/discord/autocompleteTanks"; import { addUsernameChoices } from '../core/discord/addUsernameChoices';
import { autocompleteUsername } from "../core/discord/autocompleteUsername"; import { autocompleteTanks } from '../core/discord/autocompleteTanks';
import { createLocalizedCommand } from "../core/discord/createLocalizedCommand"; import { autocompleteUsername } from '../core/discord/autocompleteUsername';
import { getFiltersFromButton } from "../core/discord/getFiltersFromButton"; import { buttonRefresh } from '../core/discord/buttonRefresh';
import { resolvePeriodFromButton } from "../core/discord/resolvePeriodFromButton"; import { commandToURL } from '../core/discord/commandToURL';
import { resolvePlayerFromButton } from "../core/discord/resolvePlayerFromButton"; import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
import { translator } from "../core/localization/translator"; import { getCustomPeriodParams } from '../core/discord/getCustomPeriodParams';
import { type CommandRegistry } from "../events/interactionCreate"; import { getFiltersFromButton } from '../core/discord/getFiltersFromButton';
import { renderBreakdown } from "./breakdown/render"; import { getFiltersFromCommand } from '../core/discord/getFiltersFromCommand';
import { resolvePeriodFromButton } from '../core/discord/resolvePeriodFromButton';
import { resolvePeriodFromCommand } from '../core/discord/resolvePeriodFromCommand';
import { resolvePlayerFromButton } from '../core/discord/resolvePlayerFromButton';
import { resolvePlayerFromCommand } from '../core/discord/resolvePlayerFromCommand';
import { CommandRegistry } from '../events/interactionCreate';
import { renderBreakdown } from './breakdown/render';
export const todayCommand = new Promise<CommandRegistry>(async (resolve) => { export const todayCommand = new Promise<CommandRegistry>(async (resolve) => {
const awaitedGameDefinitions = await gameDefinitions; const awaitedGameDefinitions = await gameDefinitions;
resolve({ resolve({
command: addFilterOptions( command: addFilterOptions(
createLocalizedCommand("today"), createLocalizedCommand('today'),
awaitedGameDefinitions.nations, awaitedGameDefinitions.nations,
).addStringOption(addUsernameChoices), ).addStringOption(addUsernameChoices),
async handler(interaction) { async handler(interaction) {
const { strings } = translator(interaction.locale); const player = await resolvePlayerFromCommand(interaction);
return strings.bot.commands.dead; const period = resolvePeriodFromCommand(
player.region,
interaction,
'today',
);
const filters = await getFiltersFromCommand(interaction);
const path = commandToURL(interaction, {
...player,
...getCustomPeriodParams(interaction, true),
...filters,
});
// const player = await resolvePlayerFromCommand(interaction); return [
// const period = resolvePeriodFromCommand( ...(await renderBreakdown(player, period, filters, interaction.locale)),
// player.region, buttonRefresh(interaction, path),
// interaction, await getBlitzStarsLinkButton(player.region, player.id),
// 'today', ];
// );
// const filters = await getFiltersFromCommand(interaction);
// const path = commandToURL(interaction, {
// ...player,
// ...getCustomPeriodParams(interaction, true),
// ...filters,
// });
// return [
// ...(await renderBreakdown(player, period, filters, interaction.locale)),
// buttonRefresh(interaction, path),
// await getBlitzStarsLinkButton(player.region, player.id),
// ];
}, },
autocomplete: (interaction) => { autocomplete: (interaction) => {
@@ -1,8 +1,8 @@
import { type ComponentProps } from "react"; import { ComponentProps } from 'react';
export interface GlowProps extends ComponentProps<"div"> { export interface GlowProps extends ComponentProps<'div'> {
color: string; color: string;
direction?: "default" | "reverse"; direction?: 'default' | 'reverse';
rotation?: number; rotation?: number;
startOpacity?: number; startOpacity?: number;
endOpacity?: number; endOpacity?: number;
@@ -12,7 +12,7 @@ const width = 16;
export function Glow({ export function Glow({
color, color,
direction = "default", direction = 'default',
rotation = 0, rotation = 0,
style, style,
startOpacity = 0, startOpacity = 0,
@@ -21,20 +21,20 @@ export function Glow({
}: GlowProps) { }: GlowProps) {
const color1 = `${color}${Math.round(endOpacity * 255) const color1 = `${color}${Math.round(endOpacity * 255)
.toString(16) .toString(16)
.padStart(2, "0")}`; .padStart(2, '0')}`;
const color2 = `${color}${Math.round(startOpacity * 255) const color2 = `${color}${Math.round(startOpacity * 255)
.toString(16) .toString(16)
.padStart(2, "0")}`; .padStart(2, '0')}`;
const background = `linear-gradient(${ const background = `linear-gradient(${
direction === "default" ? 0 : 180 direction === 'default' ? 0 : 180
}deg, ${color1} 0%, ${color2} 100%)`; }deg, ${color1} 0%, ${color2} 100%)`;
return ( return (
<div <div
style={{ style={{
display: "flex", display: 'flex',
alignItems: direction === "default" ? "flex-end" : "flex-start", alignItems: direction === 'default' ? 'flex-end' : 'flex-start',
justifyContent: "center", justifyContent: 'center',
transform: `rotate(${rotation}deg)`, transform: `rotate(${rotation}deg)`,
...style, ...style,
}} }}
@@ -1,7 +1,7 @@
import { import {
getWN8Percentile, getWN8Percentile,
Percentile, Percentile,
type SupplementaryStats, SupplementaryStats,
} from '@blitzkit/core'; } from '@blitzkit/core';
import { Locale } from 'discord.js'; import { Locale } from 'discord.js';
import { translator } from '../../core/localization/translator'; import { translator } from '../../core/localization/translator';
@@ -1,4 +1,4 @@
import { type ReactNode } from "react"; import { ReactNode } from 'react';
interface RootProps { interface RootProps {
children: ReactNode; children: ReactNode;
@@ -8,7 +8,7 @@ export function Root({ children }: RootProps) {
return ( return (
<div <div
className="session-tracker-root" className="session-tracker-root"
style={{ display: "flex", flexDirection: "column", gap: 8 }} style={{ display: 'flex', flexDirection: 'column', gap: 8 }}
> >
{children} {children}
</div> </div>
@@ -6,8 +6,8 @@ import {
} from '@blitzkit/core'; } from '@blitzkit/core';
import { createColors } from 'bepaint'; import { createColors } from 'bepaint';
import { import {
type AccentColor, AccentColor,
type GrayColor, GrayColor,
PALETTES, PALETTES,
} from '../../../core/radix/radixColors'; } from '../../../core/radix/radixColors';
import { RowStat } from './RowStat'; import { RowStat } from './RowStat';
@@ -1,11 +1,11 @@
import { Percentile } from "@blitzkit/core"; import { Percentile } from '@blitzkit/core';
import { createColors } from "bepaint"; import { createColors } from 'bepaint';
import { import {
type AccentColor, AccentColor,
type GrayColor, GrayColor,
PALETTES, PALETTES,
} from "../../../core/radix/radixColors"; } from '../../../core/radix/radixColors';
import { PercentileIndicator } from "../../PercentileIndicator"; import { PercentileIndicator } from '../../PercentileIndicator';
interface RowStatProps { interface RowStatProps {
name: string; name: string;
@@ -22,7 +22,7 @@ export function RowStat({
delta, delta,
percentile, percentile,
minimized = false, minimized = false,
color = "slate", color = 'slate',
}: RowStatProps) { }: RowStatProps) {
const theme = createColors(PALETTES[`${color}Dark`]); const theme = createColors(PALETTES[`${color}Dark`]);
@@ -30,29 +30,29 @@ export function RowStat({
<div <div
className="session-tracker-row-stat" className="session-tracker-row-stat"
style={{ style={{
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
gap: 4, gap: 4,
alignItems: "center", alignItems: 'center',
justifyContent: "center", justifyContent: 'center',
flex: 1, flex: 1,
}} }}
> >
<div <div
style={{ style={{
display: "flex", display: 'flex',
gap: 4, gap: 4,
alignItems: "center", alignItems: 'center',
justifyContent: "center", justifyContent: 'center',
}} }}
> >
{percentile === undefined && delta !== undefined && delta !== 0 && ( {percentile === undefined && delta !== undefined && delta !== 0 && (
<img <img
alt={delta > 0 ? "Increase" : "Decrease"} alt={delta > 0 ? 'Increase' : 'Decrease'}
src={ src={
(delta ?? 0) > 0 (delta ?? 0) > 0
? "https://i.imgur.com/qbjiXa1.png" ? 'https://i.imgur.com/qbjiXa1.png'
: "https://i.imgur.com/3uyNhun.png" : 'https://i.imgur.com/3uyNhun.png'
} }
style={{ width: 12, height: 12 }} style={{ width: 12, height: 12 }}
/> />
@@ -66,7 +66,7 @@ export function RowStat({
color: theme.textHighContrast, color: theme.textHighContrast,
fontWeight: 700, fontWeight: 700,
fontSize: 16, fontSize: 16,
textAlign: "center", textAlign: 'center',
}} }}
> >
{value} {value}
@@ -77,7 +77,7 @@ export function RowStat({
<span <span
style={{ style={{
color: theme.textLowContrast, color: theme.textLowContrast,
textAlign: "center", textAlign: 'center',
fontSize: 12, fontSize: 12,
}} }}
> >
@@ -1,5 +1,5 @@
import { type ReactNode } from "react"; import { ReactNode } from 'react';
import { theme } from "../stitches.config"; import { theme } from '../stitches.config';
interface CommandWrapperProps { interface CommandWrapperProps {
children: ReactNode; children: ReactNode;
@@ -10,14 +10,14 @@ export function CommandWrapper({ children, fat }: CommandWrapperProps) {
return ( return (
<div <div
style={{ style={{
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
padding: `16px ${fat ? 0 : 16}px`, padding: `16px ${fat ? 0 : 16}px`,
gap: 16, gap: 16,
width: "100%", width: '100%',
color: theme.colors.textHighContrast, color: theme.colors.textHighContrast,
backgroundColor: theme.colors.appBackground1, backgroundColor: theme.colors.appBackground1,
position: "relative", position: 'relative',
}} }}
> >
{children} {children}
@@ -1,8 +1,8 @@
import { import {
type BlitzStats, BlitzStats,
getWN8Percentile, getWN8Percentile,
isNumber, isNumber,
type SupplementaryStats, SupplementaryStats,
} from '@blitzkit/core'; } from '@blitzkit/core';
import { Locale } from 'discord.js'; import { Locale } from 'discord.js';
import { translator } from '../../core/localization/translator'; import { translator } from '../../core/localization/translator';
@@ -1,5 +1,5 @@
import { type GenericStat } from ".."; import { GenericStat } from '..';
import { GenericStatsRow } from "./GenericStatsRow"; import { GenericStatsRow } from './GenericStatsRow';
interface GenericStatsColumnProps { interface GenericStatsColumnProps {
stats: GenericStat[]; stats: GenericStat[];
@@ -7,7 +7,7 @@ interface GenericStatsColumnProps {
export function GenericStatsColumn({ stats }: GenericStatsColumnProps) { export function GenericStatsColumn({ stats }: GenericStatsColumnProps) {
return ( return (
<div style={{ flex: 1, display: "flex", flexDirection: "column", gap: 8 }}> <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 8 }}>
{stats.map((stat) => ( {stats.map((stat) => (
<GenericStatsRow key={stat[0]} stat={stat} /> <GenericStatsRow key={stat[0]} stat={stat} />
))} ))}
@@ -1,6 +1,6 @@
import { type GenericStat } from ".."; import { GenericStat } from '..';
import { theme } from "../../../stitches.config"; import { theme } from '../../../stitches.config';
import { PercentileIndicator } from "../../PercentileIndicator"; import { PercentileIndicator } from '../../PercentileIndicator';
interface GenericStatsRowProps { interface GenericStatsRowProps {
stat: GenericStat; stat: GenericStat;
@@ -10,18 +10,18 @@ export function GenericStatsRow({ stat }: GenericStatsRowProps) {
return ( return (
<div <div
style={{ style={{
display: "flex", display: 'flex',
justifyContent: "space-between", justifyContent: 'space-between',
alignItems: "center", alignItems: 'center',
}} }}
> >
<span <span
style={{ style={{
color: theme.colors.textLowContrast, color: theme.colors.textLowContrast,
fontSize: 16, fontSize: 16,
whiteSpace: "nowrap", whiteSpace: 'nowrap',
maxWidth: 128, maxWidth: 128,
overflow: "hidden", overflow: 'hidden',
}} }}
> >
{stat[0]} {stat[0]}
@@ -32,9 +32,9 @@ export function GenericStatsRow({ stat }: GenericStatsRowProps) {
color: theme.colors.textHighContrast, color: theme.colors.textHighContrast,
fontWeight: 700, fontWeight: 700,
fontSize: 16, fontSize: 16,
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
justifyContent: "center", justifyContent: 'center',
gap: 4, gap: 4,
}} }}
> >
@@ -1,6 +1,6 @@
import { times } from 'lodash-es'; import { times } from 'lodash-es';
import { theme } from '../../../../stitches.config'; import { theme } from '../../../../stitches.config';
import { type MarginInputProps } from '../Root'; import { MarginInputProps } from '../Root';
import { MarginOrientation } from './constants'; import { MarginOrientation } from './constants';
interface MarginProps extends MarginInputProps { interface MarginProps extends MarginInputProps {
@@ -1,9 +1,9 @@
import { times } from "lodash-es"; import { times } from 'lodash-es';
import { type ReactNode } from "react"; import { ReactNode } from 'react';
import { theme } from "../../../../stitches.config"; import { theme } from '../../../../stitches.config';
import { Margin } from "../Margin"; import { Margin } from '../Margin';
import { MarginOrientation } from "../Margin/constants"; import { MarginOrientation } from '../Margin/constants';
import { GRAPH_HEIGHT, GRAPH_WIDTH } from "./constants"; import { GRAPH_HEIGHT, GRAPH_WIDTH } from './constants';
export interface MarginInputProps { export interface MarginInputProps {
min: number; min: number;
@@ -32,12 +32,12 @@ export function Root({
xTitle, xTitle,
}: RootProps) { }: RootProps) {
return ( return (
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<div <div
style={{ style={{
paddingLeft: 64, paddingLeft: 64,
display: "flex", display: 'flex',
justifyContent: "space-between", justifyContent: 'space-between',
}} }}
> >
<span style={{ fontSize: 16, color: theme.colors.textHighContrast }}> <span style={{ fontSize: 16, color: theme.colors.textHighContrast }}>
@@ -48,7 +48,7 @@ export function Root({
</span> </span>
</div> </div>
<div style={{ display: "flex", gap: 16 }}> <div style={{ display: 'flex', gap: 16 }}>
{verticalMargin && ( {verticalMargin && (
<Margin {...verticalMargin} verticalSeparations={separations} /> <Margin {...verticalMargin} verticalSeparations={separations} />
)} )}
@@ -94,9 +94,9 @@ export function Root({
<div <div
style={{ style={{
paddingLeft: 64, paddingLeft: 64,
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
justifyContent: "center", justifyContent: 'center',
}} }}
> >
<span style={{ fontSize: 16, color: theme.colors.textLowContrast }}> <span style={{ fontSize: 16, color: theme.colors.textLowContrast }}>
@@ -1,7 +1,7 @@
import { type ComponentProps } from "react"; import { ComponentProps } from 'react';
import { theme } from "../../../stitches.config"; import { theme } from '../../../stitches.config';
interface ItemProps extends ComponentProps<"div"> { interface ItemProps extends ComponentProps<'div'> {
position: number; position: number;
deltaPosition?: number; deltaPosition?: number;
score?: number; score?: number;
@@ -25,15 +25,15 @@ export function Item({
<div <div
{...props} {...props}
style={{ style={{
cursor: "pointer", cursor: 'pointer',
display: "flex", display: 'flex',
padding: 8, padding: 8,
backgroundColor: highlight backgroundColor: highlight
? theme.colors.componentInteractive_blue ? theme.colors.componentInteractive_blue
: theme.colors.componentInteractive, : theme.colors.componentInteractive,
borderRadius: 4, borderRadius: 4,
alignItems: "center", alignItems: 'center',
justifyContent: "center", justifyContent: 'center',
gap: 8, gap: 8,
}} }}
> >
@@ -50,9 +50,9 @@ export function Item({
<div <div
style={{ style={{
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
justifyContent: "center", justifyContent: 'center',
gap: 4, gap: 4,
}} }}
> >
@@ -83,22 +83,22 @@ export function Item({
{deltaPosition !== undefined && deltaPosition !== 0 && ( {deltaPosition !== undefined && deltaPosition !== 0 && (
<div <div
style={{ style={{
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
justifyContent: "center", justifyContent: 'center',
gap: 4, gap: 4,
}} }}
> >
<img <img
alt={deltaPosition > 0 ? "Increase" : "Decrease"} alt={deltaPosition > 0 ? 'Increase' : 'Decrease'}
style={{ style={{
width: 8, width: 8,
height: 8, height: 8,
}} }}
src={ src={
deltaPosition > 0 deltaPosition > 0
? "https://i.imgur.com/qbjiXa1.png" ? 'https://i.imgur.com/qbjiXa1.png'
: "https://i.imgur.com/3uyNhun.png" : 'https://i.imgur.com/3uyNhun.png'
} }
/> />
<span <span
@@ -119,11 +119,11 @@ export function Item({
<div <div
style={{ style={{
display: "flex", display: 'flex',
gap: 4, gap: 4,
alignItems: "center", alignItems: 'center',
width: 96, width: 96,
justifyContent: "flex-end", justifyContent: 'flex-end',
}} }}
> >
{deltaScore !== 0 && deltaScore !== undefined && ( {deltaScore !== 0 && deltaScore !== undefined && (
@@ -140,15 +140,15 @@ export function Item({
)} )}
{deltaScore !== 0 && deltaScore !== undefined && ( {deltaScore !== 0 && deltaScore !== undefined && (
<img <img
alt={deltaScore > 0 ? "Increase" : "Decrease"} alt={deltaScore > 0 ? 'Increase' : 'Decrease'}
style={{ style={{
width: 8, width: 8,
height: 8, height: 8,
}} }}
src={ src={
deltaScore > 0 deltaScore > 0
? "https://i.imgur.com/qbjiXa1.png" ? 'https://i.imgur.com/qbjiXa1.png'
: "https://i.imgur.com/3uyNhun.png" : 'https://i.imgur.com/3uyNhun.png'
} }
/> />
)} )}
@@ -1,4 +1,4 @@
import { type ReactNode } from 'react'; import { ReactNode } from 'react';
interface RootProps { interface RootProps {
children: ReactNode; children: ReactNode;
@@ -1,4 +1,4 @@
import { type ReactNode } from 'react'; import { ReactNode } from 'react';
interface ColumnProps { interface ColumnProps {
children: ReactNode; children: ReactNode;
@@ -1,4 +1,4 @@
import { type ReactNode } from "react"; import { ReactNode } from 'react';
interface RootProps { interface RootProps {
children: ReactNode; children: ReactNode;
@@ -8,8 +8,8 @@ export function Root({ children }: RootProps) {
return ( return (
<div <div
style={{ style={{
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
gap: 16, gap: 16,
}} }}
> >
@@ -1,4 +1,4 @@
import { type ReactNode } from "react"; import { ReactNode } from 'react';
interface RowProps { interface RowProps {
children: ReactNode; children: ReactNode;
@@ -8,7 +8,7 @@ export function Row({ children }: RowProps) {
return ( return (
<div <div
style={{ style={{
display: "flex", display: 'flex',
gap: 8, gap: 8,
}} }}
> >
+7 -7
View File
@@ -1,5 +1,5 @@
import type { TierWeightsRecord } from "@blitzkit/core"; import { TierWeightsRecord } from '@blitzkit/core';
import { theme } from "../stitches.config"; import { theme } from '../stitches.config';
interface TierWeightsProps { interface TierWeightsProps {
weights: TierWeightsRecord; weights: TierWeightsRecord;
@@ -7,7 +7,7 @@ interface TierWeightsProps {
export function TierWeights({ weights }: TierWeightsProps) { export function TierWeights({ weights }: TierWeightsProps) {
return ( return (
<div style={{ display: "flex", gap: 8 }}> <div style={{ display: 'flex', gap: 8 }}>
{Object.entries(weights).map(([tier, weight]) => ( {Object.entries(weights).map(([tier, weight]) => (
<div <div
key={tier} key={tier}
@@ -15,11 +15,11 @@ export function TierWeights({ weights }: TierWeightsProps) {
backgroundColor: theme.colors.appBackground1_light, backgroundColor: theme.colors.appBackground1_light,
flex: weight, flex: weight,
color: theme.colors.textHighContrast_light, color: theme.colors.textHighContrast_light,
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
justifyContent: "center", justifyContent: 'center',
fontSize: 16, fontSize: 16,
overflow: "hidden", overflow: 'hidden',
borderRadius: 8, borderRadius: 8,
minWidth: 16, minWidth: 16,
}} }}
@@ -1,7 +1,7 @@
import { fetchTankDefinitions, TIER_ROMAN_NUMERALS } from "@blitzkit/core"; import { fetchTankDefinitions, TIER_ROMAN_NUMERALS } from "@blitzkit/core";
import { literals } from "@blitzkit/i18n"; import { literals } from "@blitzkit/i18n";
import { Locale } from "discord.js"; import { Locale } from "discord.js";
import { type StatFilters } from "../blitzstars/filterStats"; import { StatFilters } from "../blitzstars/filterStats";
import { translator } from "../localization/translator"; import { translator } from "../localization/translator";
export async function filtersToDescription( export async function filtersToDescription(
@@ -1,5 +1,5 @@
import { getTimeDaysAgo, type Region } from '@blitzkit/core'; import { getTimeDaysAgo, Region } from '@blitzkit/core';
import type { PeriodSize } from '../discord/addPeriodSubCommands'; import { PeriodSize } from '../discord/addPeriodSubCommands';
export function getPeriodStart(region: Region, period: PeriodSize) { export function getPeriodStart(region: Region, period: PeriodSize) {
if (period === 'career') return 0; if (period === 'career') return 0;
+3 -3
View File
@@ -1,9 +1,9 @@
import sharp from "sharp"; import sharp from 'sharp';
const cache: Record<string, Buffer> = {}; const cache: Record<string, Buffer> = {};
export async function iconPng(url: string) { export async function iconPng(url: string) {
if (!url.endsWith(".webp")) return url; if (!url.endsWith('.webp')) return url;
if (!cache[url]) { if (!cache[url]) {
const imageResponse = await fetch(url); const imageResponse = await fetch(url);
@@ -12,5 +12,5 @@ export async function iconPng(url: string) {
cache[url] = imageBufferPNG; cache[url] = imageBufferPNG;
} }
return `data:image/png;base64,${cache[url].toString("base64")}`; return `data:image/png;base64,${cache[url].toString('base64')}`;
} }
@@ -1,13 +1,11 @@
import { import {
fetchAverageDefinitions,
fetchDiscoveredIdsDefinitions, fetchDiscoveredIdsDefinitions,
fetchGameDefinitions, fetchGameDefinitions,
fetchMapDefinitions, fetchMapDefinitions,
fetchTankDefinitions, fetchTankDefinitions,
fetchTankNames, fetchTankNames,
} from "@blitzkit/core"; } from '@blitzkit/core';
export const averageDefinitions = fetchAverageDefinitions();
export const tankDefinitions = fetchTankDefinitions(); export const tankDefinitions = fetchTankDefinitions();
export const gameDefinitions = fetchGameDefinitions(); export const gameDefinitions = fetchGameDefinitions();
export const tankNames = fetchTankNames(); export const tankNames = fetchTankNames();
+18 -26
View File
@@ -1,37 +1,29 @@
import { usersDatabase } from "../db/users"; import { usersDatabase } from '../db/users';
import { client } from "../discord/client"; import { client } from '../discord/client';
const EXIT_EVENTS = ["SIGINT", "SIGTERM", "SIGUSR1", "SIGUSR2"]; const EXIT_EVENTS = [
'beforeExit',
'exit',
'SIGINT',
'SIGTERM',
'SIGUSR1',
'SIGUSR2',
];
let cleaningUp = false; async function cleanup() {
console.log('Cleaning up...');
async function cleanup(signal: string) { await Promise.all([usersDatabase.$disconnect(), client.destroy()]);
if (cleaningUp) return;
cleaningUp = true;
console.log(`Cleaning up... (${signal})`); console.log('Gracefully exiting...');
try {
await Promise.all([usersDatabase.$disconnect(), client.destroy()]);
} catch (err) {
console.error("Cleanup error:", err);
}
console.log("Gracefully exiting...");
EXIT_EVENTS.forEach((event) => process.off(event, cleanup));
process.exit(0); process.exit(0);
} }
export function registerProcesses() { export function registerProcesses() {
process.on("uncaughtException", (err) => { process.on('uncaughtException', console.error);
console.error("uncaughtException:", err); client.on('error', console.error);
});
process.on("unhandledRejection", (err) => { EXIT_EVENTS.forEach((event) => process.on(event, cleanup));
console.error("unhandledRejection:", err);
});
client.on("error", console.error);
EXIT_EVENTS.forEach((event) => process.on(event, () => cleanup(event)));
} }
+11 -15
View File
@@ -1,12 +1,10 @@
import { TankClass, TankType } from "@blitzkit/core"; import { TankClass, TankType } from '@blitzkit/core';
import type { SupplementaryStats } from "@blitzkit/core/src/blitz/getAccountInfo"; import { SupplementaryStats } from '@blitzkit/core/src/blitz/getAccountInfo';
import { calculateWN8 } from "@blitzkit/core/src/statistics/calculateWN8"; import { calculateWN8 } from '@blitzkit/core/src/statistics/calculateWN8';
import type { BlitzStats } from "@blitzkit/core/src/statistics/compositeStats"; import { BlitzStats } from '@blitzkit/core/src/statistics/compositeStats';
import { sumAllStats } from "@blitzkit/core/src/statistics/sumAllStats"; import { sumAllStats } from '@blitzkit/core/src/statistics/sumAllStats';
import { import { tankDefinitions } from '../blitzkit/nonBlockingPromises';
averageDefinitions, import { blitzStarsTankAverages } from './tankAverages';
tankDefinitions,
} from "../blitzkit/nonBlockingPromises";
export interface StatFilters { export interface StatFilters {
nation?: string; nation?: string;
@@ -24,7 +22,7 @@ export async function filterStats(
if (filters.tank) filters = { tank: filters.tank }; if (filters.tank) filters = { tank: filters.tank };
const awaitedTankDefinitions = await tankDefinitions; const awaitedTankDefinitions = await tankDefinitions;
const awaitedTankAverages = await averageDefinitions; const awaitedTankAverages = await blitzStarsTankAverages;
const filteredOrder = order.filter((id) => { const filteredOrder = order.filter((id) => {
const entry = awaitedTankDefinitions.tanks[id]; const entry = awaitedTankDefinitions.tanks[id];
@@ -46,9 +44,7 @@ export async function filterStats(
const stats = sumAllStats(filteredOrder.map((id) => diff[id])); const stats = sumAllStats(filteredOrder.map((id) => diff[id]));
const battlesOfTanksWithAverages = filteredOrder.reduce<number>( const battlesOfTanksWithAverages = filteredOrder.reduce<number>(
(accumulator, id) => (accumulator, id) =>
awaitedTankAverages.averages[id] awaitedTankAverages[id] ? accumulator + diff[id].battles : accumulator,
? accumulator + diff[id].battles
: accumulator,
0, 0,
); );
const battlesOfTanksWithTankDefinition = filteredOrder.reduce<number>( const battlesOfTanksWithTankDefinition = filteredOrder.reduce<number>(
@@ -62,9 +58,9 @@ export async function filterStats(
WN8: WN8:
filteredOrder.reduce<number>( filteredOrder.reduce<number>(
(accumulator, id) => (accumulator, id) =>
awaitedTankAverages.averages[id] awaitedTankAverages[id]
? accumulator + ? accumulator +
calculateWN8(awaitedTankAverages.averages[id].mu, diff[id]) * calculateWN8(awaitedTankAverages[id].all, diff[id]) *
diff[id].battles diff[id].battles
: accumulator, : accumulator,
0, 0,
@@ -1,17 +1,17 @@
import { getAccountInfo, type Region } from "@blitzkit/core"; import { getAccountInfo, Region } from '@blitzkit/core';
import { buttonLink } from "../discord/buttonLink"; import { buttonLink } from '../discord/buttonLink';
export async function getBlitzStarsLinkButton( export async function getBlitzStarsLinkButton(
region: Region, region: Region,
id: number, id: number,
tankId?: number tankId?: number,
) { ) {
const { nickname } = await getAccountInfo(region, id); const { nickname } = await getAccountInfo(region, id);
return buttonLink( return buttonLink(
`https://www.blitzstars.com/player/${region}/${nickname}${ `https://www.blitzstars.com/player/${region}/${nickname}${
tankId ? `/tank/${tankId!}` : "" tankId ? `/tank/${tankId!}` : ''
}`, }`,
"BlitzStars" 'BlitzStars',
); );
} }
@@ -1,11 +1,11 @@
import { type Region, emptyAllStats, getAccountInfo } from '@blitzkit/core'; import { Region, emptyAllStats, getAccountInfo } from '@blitzkit/core';
import { import {
type GetHistoriesOptions, GetHistoriesOptions,
type Histories, Histories,
type History, History,
getHistoriesDefaultOptions, getHistoriesDefaultOptions,
} from '../../types/histories'; } from '../../types/histories';
import type { PlayerStats } from './getPlayerStats'; import { PlayerStats } from './getPlayerStats';
export async function getPlayerHistories( export async function getPlayerHistories(
server: Region, server: Region,
@@ -1,4 +1,4 @@
import type { BlitzStats, Region } from "@blitzkit/core"; import { BlitzStats, Region } from '@blitzkit/core';
interface IndividualPlayerStats { interface IndividualPlayerStats {
clan: { clan_id: number; name: string; tag: string }; clan: { clan_id: number; name: string; tag: string };
@@ -25,13 +25,13 @@ export async function getPlayerStats<
ReturnType = Ids extends number ? PlayerStats : PlayerStats[], ReturnType = Ids extends number ? PlayerStats : PlayerStats[],
>(region: Region, ids: Ids) { >(region: Region, ids: Ids) {
const stats = await Promise.all( const stats = await Promise.all(
((typeof ids === "number" ? [ids] : ids) as number[]).map((id) => ((typeof ids === 'number' ? [ids] : ids) as number[]).map((id) =>
fetch(`https://www.blitzstars.com/api/playerstats/${id}`).then( fetch(`https://www.blitzstars.com/api/playerstats/${id}`).then(
(response) => response.json() as Promise<PlayerStats> (response) => response.json() as Promise<PlayerStats>,
) ),
) ),
); );
if (typeof ids === "number") return stats[0] as ReturnType; if (typeof ids === 'number') return stats[0] as ReturnType;
return stats as ReturnType; return stats as ReturnType;
} }
@@ -1,11 +1,11 @@
import { type BlitzStats, emptyAllStats, type Region } from "@blitzkit/core"; import { BlitzStats, emptyAllStats, Region } from '@blitzkit/core';
import { Locale } from "discord.js"; import { Locale } from 'discord.js';
import type { DiffedTankStats } from "./filterStats"; import { DiffedTankStats } from './filterStats';
import { import {
getTankHistories, getTankHistories,
type TankHistories, TankHistories,
type TankHistoryRaw, TankHistoryRaw,
} from "./getTankHistories"; } from './getTankHistories';
const emptyTankHistoryNode: TankHistoryRaw = { const emptyTankHistoryNode: TankHistoryRaw = {
account_id: 0, account_id: 0,
@@ -22,7 +22,7 @@ export async function getStatsInPeriod(
id: number, id: number,
start: number, start: number,
end: number, end: number,
locale: Locale locale: Locale,
) { ) {
const history = await getTankHistories(server, id, locale, { const history = await getTankHistories(server, id, locale, {
includeLatestHistories: true, includeLatestHistories: true,
@@ -43,8 +43,8 @@ export async function getStatsInPeriod(
// find the ones played within the range // find the ones played within the range
const inRangeTanks = tanks.filter((tankId) => const inRangeTanks = tanks.filter((tankId) =>
tankSortedHistory[tankId].some( tankSortedHistory[tankId].some(
(node) => node.last_battle_time > start && node.last_battle_time < end (node) => node.last_battle_time > start && node.last_battle_time < end,
) ),
); );
const playedTanks: number[] = []; const playedTanks: number[] = [];
@@ -1,13 +1,13 @@
import { type Region, emptyAllStats, getTankStats } from "@blitzkit/core"; import { Region, emptyAllStats, getTankStats } from '@blitzkit/core';
import { Locale } from "discord.js"; import { Locale } from 'discord.js';
import { import {
type GetHistoriesOptions, GetHistoriesOptions,
type Histories, Histories,
type History, History,
getHistoriesDefaultOptions, getHistoriesDefaultOptions,
} from "../../types/histories"; } from '../../types/histories';
import { UserError } from "../blitzkit/userError"; import { UserError } from '../blitzkit/userError';
import { translator } from "../localization/translator"; import { translator } from '../localization/translator';
export interface TankHistory extends History { export interface TankHistory extends History {
tank_id: number; tank_id: number;
@@ -36,12 +36,12 @@ export async function getTankHistories(
server: Region, server: Region,
id: number, id: number,
locale: Locale, locale: Locale,
options?: Partial<GetTankHistoriesOptions> options?: Partial<GetTankHistoriesOptions>,
) { ) {
const { strings } = translator(locale); const { strings } = translator(locale);
const mergedOptions = { ...getTankHistoriesDefaultOptions, ...options }; const mergedOptions = { ...getTankHistoriesDefaultOptions, ...options };
const tankHistoriesResponse = await fetch( const tankHistoriesResponse = await fetch(
`https://www.blitzstars.com/api/tankhistories/for/${id}/` `https://www.blitzstars.com/api/tankhistories/for/${id}/`,
); );
const tankHistories = ( const tankHistories = (
(await tankHistoriesResponse.json()) as TankHistoriesRaw (await tankHistoriesResponse.json()) as TankHistoriesRaw
@@ -51,7 +51,7 @@ export async function getTankHistories(
all: history.all, all: history.all,
last_battle_time: history.last_battle_time, last_battle_time: history.last_battle_time,
tank_id: history.tank_id, tank_id: history.tank_id,
}) satisfies TankHistory }) satisfies TankHistory,
); );
const compliantTanks: number[] = []; const compliantTanks: number[] = [];
const lastBattles: Record<number, number> = {}; const lastBattles: Record<number, number> = {};
@@ -73,7 +73,7 @@ export async function getTankHistories(
lastBattles[tank_id] = battles; lastBattles[tank_id] = battles;
return complies; return complies;
} },
); );
const latestNodes: TankHistories = []; const latestNodes: TankHistories = [];
@@ -112,7 +112,7 @@ export async function getTankHistories(
all: emptyAllStats, all: emptyAllStats,
last_battle_time: -Infinity, last_battle_time: -Infinity,
tank_id: tankId, tank_id: tankId,
} satisfies TankHistory) } satisfies TankHistory),
); );
} }
}); });
@@ -0,0 +1,101 @@
import { BlitzStats } from '@blitzkit/core';
import { context } from '@blitzkit/core/src/blitzkit/context';
interface SpecialStats {
winrate: number;
damageRatio: number;
kdr: number;
damagePerBattle: number;
killsPerBattle: number;
hitsPerBattle: number;
spotsPerBattle: number;
wpm: number;
dpm: number;
kpm: number;
hitRate: number;
survivalRate: number;
}
export type Percentiles = [
number,
number,
number,
number,
number,
number,
number,
number,
number,
];
export interface SpecialPercentiles {
winRate: Percentiles;
damageRatio: Percentiles;
kdr: Percentiles;
damagePerBattle: Percentiles;
killsPerBattle: Percentiles;
hitsPerBattle: Percentiles;
spotsPerBattle: Percentiles;
wpm: Percentiles;
dpm: Percentiles;
kpm: Percentiles;
hitRate: Percentiles;
survivalRate: Percentiles;
}
export interface MeanSd {
winrateMean: number;
winrateSd: number;
hitRateMean: number;
hitRateSd: number;
survivalRateMean: number;
survivalRateSd: number;
damageRatioMean: number;
damageRatioSd: number;
kdrMean: number;
kdrSd: number;
dpbMean: number;
dpbSd: number;
kpbMean: number;
kpbSd: number;
hpbMean: number;
hpbSd: number;
spbMean: number;
spbSd: number;
}
export interface IndividualTankAverage {
_id: number;
battle_life_time: number;
total_battle_life_time: number;
number_of_players: number;
tank_id: number;
all: BlitzStats;
special: SpecialStats;
percentiles: SpecialPercentiles;
percentiles_player_count: number;
meanSd: MeanSd;
shotEff: number;
last_update: number;
}
export type BlitzStarsTankAverages = Record<number, IndividualTankAverage>;
export const blitzStarsTankAverages = fetch(
'https://www.blitzstars.com/api/tankaverages.json',
)
.then((response) => response.json())
.then((json) => {
if (context === 'server') {
const partialTankAverages: BlitzStarsTankAverages = {};
(json as IndividualTankAverage[]).forEach((individualTankAverage) => {
partialTankAverages[individualTankAverage.tank_id] =
individualTankAverage;
});
return partialTankAverages;
} else {
return json as BlitzStarsTankAverages;
}
});
@@ -4,7 +4,7 @@ import {
TANK_TYPES, TANK_TYPES,
} from '@blitzkit/core'; } from '@blitzkit/core';
import { import {
type APIApplicationCommandOptionChoice, APIApplicationCommandOptionChoice,
Locale, Locale,
SlashCommandBuilder, SlashCommandBuilder,
SlashCommandSubcommandBuilder, SlashCommandSubcommandBuilder,
@@ -1,11 +1,11 @@
import { REGIONS } from "@blitzkit/core"; import { REGIONS } from '@blitzkit/core';
import { import {
type APIApplicationCommandOptionChoice, APIApplicationCommandOptionChoice,
Locale, Locale,
SlashCommandStringOption, SlashCommandStringOption,
} from "discord.js"; } from 'discord.js';
import { translator } from "../localization/translator"; import { translator } from '../localization/translator';
import { localizationObject } from "./localizationObject"; import { localizationObject } from './localizationObject';
export function addRegionChoices(option: SlashCommandStringOption) { export function addRegionChoices(option: SlashCommandStringOption) {
const { strings } = translator(Locale.EnglishUS); const { strings } = translator(Locale.EnglishUS);
@@ -16,14 +16,14 @@ export function addRegionChoices(option: SlashCommandStringOption) {
localizationObject( localizationObject(
(strings) => strings.bot.common.options.region.name, (strings) => strings.bot.common.options.region.name,
undefined, undefined,
true true,
) ),
) )
.setDescription(strings.bot.common.options.region.description) .setDescription(strings.bot.common.options.region.description)
.setDescriptionLocalizations( .setDescriptionLocalizations(
localizationObject( localizationObject(
(strings) => strings.bot.common.options.region.description (strings) => strings.bot.common.options.region.description,
) ),
) )
.addChoices( .addChoices(
...REGIONS.map( ...REGIONS.map(
@@ -32,10 +32,10 @@ export function addRegionChoices(option: SlashCommandStringOption) {
value: region, value: region,
name: strings.common.regions.normal[region], name: strings.common.regions.normal[region],
name_localizations: localizationObject( name_localizations: localizationObject(
(strings) => strings.common.regions.normal[region] (strings) => strings.common.regions.normal[region],
), ),
}) satisfies APIApplicationCommandOptionChoice }) satisfies APIApplicationCommandOptionChoice,
) ),
) )
.setRequired(true); .setRequired(true);
} }
@@ -1,25 +1,25 @@
import { fetchTankNames, SEARCH_KEYS, TankType } from "@blitzkit/core"; import { fetchTankNames, SEARCH_KEYS, TankType } from '@blitzkit/core';
import { import {
type ApplicationCommandOptionChoiceData, ApplicationCommandOptionChoiceData,
AutocompleteInteraction, AutocompleteInteraction,
type CacheType, CacheType,
} from "discord.js"; } from 'discord.js';
import { go } from "fuzzysort"; import { go } from 'fuzzysort';
import { tankNames } from "../blitzkit/nonBlockingPromises"; import { tankNames } from '../blitzkit/nonBlockingPromises';
import { translator } from "../localization/translator"; import { translator } from '../localization/translator';
import { import {
DISCORD_CHOICES_MAX_NAME_SIZE, DISCORD_CHOICES_MAX_NAME_SIZE,
OVERFLOW_SUFFIX, OVERFLOW_SUFFIX,
} from "./autocompleteClan/constants"; } from './autocompleteClan/constants';
export const tankNamesTechTreeOnly = fetchTankNames().then((names) => export const tankNamesTechTreeOnly = fetchTankNames().then((names) =>
names.filter((tank) => tank.treeType === TankType.RESEARCHABLE) names.filter((tank) => tank.treeType === TankType.RESEARCHABLE),
); );
export async function autocompleteTanks( export async function autocompleteTanks(
interaction: AutocompleteInteraction<CacheType>, interaction: AutocompleteInteraction<CacheType>,
techTreeOnly = false, techTreeOnly = false,
fields = ["tank"] fields = ['tank'],
) { ) {
const { unwrap } = translator(interaction.locale); const { unwrap } = translator(interaction.locale);
const focusedOption = interaction.options.getFocused(true); const focusedOption = interaction.options.getFocused(true);
@@ -31,7 +31,7 @@ export async function autocompleteTanks(
go( go(
focusedOption.value, focusedOption.value,
await (techTreeOnly ? tankNamesTechTreeOnly : tankNames), await (techTreeOnly ? tankNamesTechTreeOnly : tankNames),
{ keys: SEARCH_KEYS, limit: 10 } { keys: SEARCH_KEYS, limit: 10 },
).map(async (item) => { ).map(async (item) => {
let name = unwrap(item.obj.searchableName); let name = unwrap(item.obj.searchableName);
@@ -42,7 +42,7 @@ export async function autocompleteTanks(
0, 0,
unwrap(item.obj.searchableName).length - unwrap(item.obj.searchableName).length -
overSize - overSize -
OVERFLOW_SUFFIX.length OVERFLOW_SUFFIX.length,
)}${OVERFLOW_SUFFIX}`; )}${OVERFLOW_SUFFIX}`;
} }
@@ -50,8 +50,8 @@ export async function autocompleteTanks(
name, name,
value: `${item.obj.id}`, value: `${item.obj.id}`,
} satisfies ApplicationCommandOptionChoiceData<string>; } satisfies ApplicationCommandOptionChoiceData<string>;
}) }),
) )
: [] : [],
); );
} }
+3 -3
View File
@@ -1,13 +1,13 @@
import { import {
ButtonBuilder, ButtonBuilder,
ButtonStyle, ButtonStyle,
type ComponentEmojiResolvable, ComponentEmojiResolvable,
} from "discord.js"; } from 'discord.js';
export function buttonLink( export function buttonLink(
url: string, url: string,
label: string, label: string,
emoji?: ComponentEmojiResolvable emoji?: ComponentEmojiResolvable,
) { ) {
const button = new ButtonBuilder() const button = new ButtonBuilder()
.setStyle(ButtonStyle.Link) .setStyle(ButtonStyle.Link)
@@ -1,13 +1,13 @@
import { import {
ButtonBuilder, ButtonBuilder,
ButtonStyle, ButtonStyle,
type ComponentEmojiResolvable, ComponentEmojiResolvable,
} from "discord.js"; } from 'discord.js';
export function buttonPrimary( export function buttonPrimary(
id: string, id: string,
label: string, label: string,
emoji?: ComponentEmojiResolvable emoji?: ComponentEmojiResolvable,
) { ) {
const button = new ButtonBuilder() const button = new ButtonBuilder()
.setCustomId(id) .setCustomId(id)
+5 -5
View File
@@ -1,9 +1,9 @@
import { Client, GatewayIntentBits } from "discord.js"; import { Client, GatewayIntentBits } from 'discord.js';
import { interactionCreate } from "../../events/interactionCreate"; import { interactionCreate } from '../../events/interactionCreate';
import { ready } from "../../events/ready"; import { ready } from '../../events/ready';
export const client = new Client({ export const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers], intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers],
}) })
.on("clientReady", ready) .on('ready', ready)
.on("interactionCreate", interactionCreate); .on('interactionCreate', interactionCreate);
@@ -1,4 +1,4 @@
import { type CacheType, ChatInputCommandInteraction } from "discord.js"; import { CacheType, ChatInputCommandInteraction } from 'discord.js';
export interface Parameters { export interface Parameters {
[key: string]: string | number | boolean | undefined | null; [key: string]: string | number | boolean | undefined | null;
@@ -9,7 +9,7 @@ export interface Parameters {
export function commandToURL( export function commandToURL(
interaction: ChatInputCommandInteraction<CacheType>, interaction: ChatInputCommandInteraction<CacheType>,
parameters?: Parameters parameters?: Parameters,
) { ) {
const pathname = [ const pathname = [
interaction.commandName, interaction.commandName,
@@ -17,11 +17,11 @@ export function commandToURL(
interaction.options.getSubcommand(false), interaction.options.getSubcommand(false),
] ]
.filter(Boolean) .filter(Boolean)
.join("/"); .join('/');
const params = (parameters ? Object.entries(parameters) : []) const params = (parameters ? Object.entries(parameters) : [])
.filter(([, value]) => value !== undefined && value !== null) .filter(([, value]) => value !== undefined && value !== null)
.map(([key, value]) => `${key}=${value}`) .map(([key, value]) => `${key}=${value}`)
.join("&"); .join('&');
return `${pathname}${params.length > 0 ? `?${params}` : ""}`; return `${pathname}${params.length > 0 ? `?${params}` : ''}`;
} }
@@ -1,12 +1,12 @@
import { type BlitzKitStrings } from "@blitzkit/i18n"; import { BlitzKitStrings } from '@blitzkit/i18n';
import { import {
Locale, Locale,
SlashCommandBuilder, SlashCommandBuilder,
SlashCommandSubcommandBuilder, SlashCommandSubcommandBuilder,
SlashCommandSubcommandGroupBuilder, SlashCommandSubcommandGroupBuilder,
} from "discord.js"; } from 'discord.js';
import { translator } from "../localization/translator"; import { translator } from '../localization/translator';
import { localizationObject } from "./localizationObject"; import { localizationObject } from './localizationObject';
interface CreateLocalizedCommandSubcommand { interface CreateLocalizedCommandSubcommand {
subcommand: string; subcommand: string;
@@ -25,9 +25,9 @@ export function createLocalizedCommand(
| CreateLocalizedCommandSubcommandGroup[], | CreateLocalizedCommandSubcommandGroup[],
) { ) {
const commandPathItem = command.replaceAll( const commandPathItem = command.replaceAll(
"-", '-',
"_", '_',
) as keyof BlitzKitStrings["bot"]["commands"]; ) as keyof BlitzKitStrings['bot']['commands'];
const { strings } = translator(Locale.EnglishUS); const { strings } = translator(Locale.EnglishUS);
const slashCommand = new SlashCommandBuilder() const slashCommand = new SlashCommandBuilder()
.setName(command) .setName(command)
@@ -47,7 +47,7 @@ export function createLocalizedCommand(
if (sub) { if (sub) {
sub.map((subItem) => { sub.map((subItem) => {
if ("subcommand" in subItem) { if ('subcommand' in subItem) {
const subcommandNameLocalizations = localizationObject( const subcommandNameLocalizations = localizationObject(
(strings) => (strings) =>
( (
@@ -1,17 +1,17 @@
import { ChatInputCommandInteraction } from "discord.js"; import { ChatInputCommandInteraction } from 'discord.js';
import type { PeriodType } from "./addPeriodSubCommands"; import { PeriodType } from './addPeriodSubCommands';
export function getCustomPeriodParams( export function getCustomPeriodParams(
interaction: ChatInputCommandInteraction, interaction: ChatInputCommandInteraction,
forcedNonCustomPeriod = false forcedNonCustomPeriod = false,
) { ) {
if ( if (
!forcedNonCustomPeriod && !forcedNonCustomPeriod &&
(interaction.options.getSubcommand() as PeriodType) === "custom" (interaction.options.getSubcommand() as PeriodType) === 'custom'
) { ) {
return { return {
start: interaction.options.getInteger("start", true), start: interaction.options.getInteger('start', true),
end: interaction.options.getInteger("end", true), end: interaction.options.getInteger('end', true),
}; };
} }
@@ -1,19 +1,19 @@
import { TankClass, TankType } from "@blitzkit/core"; import { TankClass, TankType } from '@blitzkit/core';
import { ButtonInteraction } from "discord.js"; import { ButtonInteraction } from 'discord.js';
import type { StatFilters } from "../blitzstars/filterStats"; import { StatFilters } from '../blitzstars/filterStats';
export function getFiltersFromButton(interaction: ButtonInteraction) { export function getFiltersFromButton(interaction: ButtonInteraction) {
const url = new URL(`https://example.com/${interaction.customId}`); const url = new URL(`https://example.com/${interaction.customId}`);
return { return {
nation: url.searchParams.get("nation") ?? undefined, nation: url.searchParams.get('nation') ?? undefined,
tank: parseInt(url.searchParams.get("tank") ?? "0") || undefined, tank: parseInt(url.searchParams.get('tank') ?? '0') || undefined,
class: class:
(Number(url.searchParams.get("class")) as TankClass | undefined) ?? (Number(url.searchParams.get('class')) as TankClass | undefined) ??
undefined, undefined,
type: type:
(Number(url.searchParams.get("type")) as TankType | undefined) ?? (Number(url.searchParams.get('type')) as TankType | undefined) ??
undefined, undefined,
tier: parseInt(url.searchParams.get("tier") ?? "0") || undefined, tier: parseInt(url.searchParams.get('tier') ?? '0') || undefined,
} satisfies StatFilters; } satisfies StatFilters;
} }
@@ -1,6 +1,6 @@
import { ChatInputCommandInteraction } from 'discord.js'; import { ChatInputCommandInteraction } from 'discord.js';
import { resolveTankId } from '../blitz/resolveTankId'; import { resolveTankId } from '../blitz/resolveTankId';
import type { StatFilters } from '../blitzstars/filterStats'; import { StatFilters } from '../blitzstars/filterStats';
export async function getFiltersFromCommand( export async function getFiltersFromCommand(
interaction: ChatInputCommandInteraction, interaction: ChatInputCommandInteraction,
@@ -1,7 +1,7 @@
import { literals } from "@blitzkit/i18n"; import { literals } from "@blitzkit/i18n";
import { Locale } from "discord.js"; import { Locale } from "discord.js";
import { translator } from "../localization/translator"; import { translator } from "../localization/translator";
import type { PeriodSize } from "./addPeriodSubCommands"; import { PeriodSize } from "./addPeriodSubCommands";
export function getPeriodOptionName(period: PeriodSize, locale: Locale) { export function getPeriodOptionName(period: PeriodSize, locale: Locale) {
const { strings } = translator(locale); const { strings } = translator(locale);
@@ -1,4 +1,4 @@
import { type BlitzKitStrings, literals as literalsFunc } from "@blitzkit/i18n"; import { BlitzKitStrings, literals as literalsFunc } from "@blitzkit/i18n";
import { Locale } from "discord.js"; import { Locale } from "discord.js";
import { SUPPORTED_LOCALES_DISCORD } from "../localization/strings/constants"; import { SUPPORTED_LOCALES_DISCORD } from "../localization/strings/constants";
import { translator } from "../localization/translator"; import { translator } from "../localization/translator";
@@ -19,9 +19,9 @@ export function localizationObject(
(!strict || validNameRegex.test(translation)) && translation.length > 0; (!strict || validNameRegex.test(translation)) && translation.length > 0;
if (!isValid) { if (!isValid) {
// console.warn( console.warn(
// `Invalid localization for ${locale} (${path}): ${translation}; skipping...` `Invalid localization for ${locale} (${path}): ${translation}; skipping...`
// ); );
return localizations; return localizations;
} }
Loaded 100 of 364 files, more files were not shown because too many files have changed in this diff. Show more