mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
Compare commits
170
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d75a51660e | ||
|
|
ac1323a463 | ||
|
|
292f7aeac0 | ||
|
|
1ee232d104 | ||
|
|
1d301bf2fb | ||
|
|
2a8ca6d36f | ||
|
|
ed5d2e3ff4 | ||
|
|
b2684e3152 | ||
|
|
d078454711 | ||
|
|
6aae1c8ee0 | ||
|
|
a560a37a56 | ||
|
|
41486f84d4 | ||
|
|
29b12c7c28 | ||
|
|
2a3583a474 | ||
|
|
eb5e2a935c | ||
|
|
75f6f133ca | ||
|
|
f26711bad9 | ||
|
|
a8dc3313cf | ||
|
|
8087752172 | ||
|
|
cb03a86cce | ||
|
|
838ce7f35d | ||
|
|
e6f9b07b20 | ||
|
|
8afc334aa7 | ||
|
|
c7017a7a15 | ||
|
|
f1462162f4 | ||
|
|
da4c2ece32 | ||
|
|
80325aa3b2 | ||
|
|
3b937a6f35 | ||
|
|
7a8f88f224 | ||
|
|
5982fdde8c | ||
|
|
dcd7ce61f7 | ||
|
|
43021c4945 | ||
|
|
4b24cf313b | ||
|
|
d785588cf9 | ||
|
|
dd9b34ea34 | ||
|
|
7605e1d6f2 | ||
|
|
7f056500ca | ||
|
|
0c37550c05 | ||
|
|
1d928e8b72 | ||
|
|
9e2465a6e3 | ||
|
|
b061bfd267 | ||
|
|
a90e5e20ea | ||
|
|
5fcade3efa | ||
|
|
f1bb5736c0 | ||
|
|
9b45f21457 | ||
|
|
62fb80dddc | ||
|
|
ff0cac84e1 | ||
|
|
cbd2c2a719 | ||
|
|
5b16e22750 | ||
|
|
3a32624823 | ||
|
|
46374a7a26 | ||
|
|
b856268ee0 | ||
|
|
e60bbc03d2 | ||
|
|
30b18f60be | ||
|
|
754dff72b9 | ||
|
|
ef7655ab5d | ||
|
|
9491e7651a | ||
|
|
cccd96a021 | ||
|
|
16a54ea291 | ||
|
|
f5c00643e4 | ||
|
|
652f36ba6c | ||
|
|
e1957daa5c | ||
|
|
222fda0c91 | ||
|
|
c9467d68ae | ||
|
|
d64f04b2c8 | ||
|
|
a98f0e84d5 | ||
|
|
6319e438b1 | ||
|
|
90a64d4ccf | ||
|
|
cb339eab5f | ||
|
|
e6a92e2114 | ||
|
|
4bfc89302f | ||
|
|
6611088ccb | ||
|
|
8d4933dd60 | ||
|
|
4b5ee9db09 | ||
|
|
cc3aa976db | ||
|
|
9fb4607bba | ||
|
|
6f8790d148 | ||
|
|
57d63ea1d7 | ||
|
|
35894f39cf | ||
|
|
18d762c41b | ||
|
|
6ca74e2b83 | ||
|
|
56ca3e5ed4 | ||
|
|
2c059cdc59 | ||
|
|
b0d7a49b16 | ||
|
|
9dfa5a7693 | ||
|
|
e79ed0cf85 | ||
|
|
f52460e98b | ||
|
|
6a91e86b07 | ||
|
|
1fce9c1c88 | ||
|
|
6e8fdcfeef | ||
|
|
2fffd512ac | ||
|
|
16b3a59f88 | ||
|
|
ba36c600c4 | ||
|
|
9fbdaaf87f | ||
|
|
553c9d552b | ||
|
|
1b017aad48 | ||
|
|
74fefe6291 | ||
|
|
5d81b7ff5a | ||
|
|
789578f2b3 | ||
|
|
0864825513 | ||
|
|
6f11fb4a70 | ||
|
|
1567f8270c | ||
|
|
0b5d019dc8 | ||
|
|
932a04a398 | ||
|
|
05c0839936 | ||
|
|
95ae891212 | ||
|
|
3aea04b6cb | ||
|
|
1d28768bb7 | ||
|
|
14d9794f57 | ||
|
|
9cd38db684 | ||
|
|
6a5af32f6c | ||
|
|
af0b430927 | ||
|
|
8c7f851692 | ||
|
|
57a2daa5e4 | ||
|
|
63d27aec64 | ||
|
|
90c5c16b00 | ||
|
|
4d94205547 | ||
|
|
31ff50ffd8 | ||
|
|
22e4553720 | ||
|
|
8b268cb647 | ||
|
|
168a955380 | ||
|
|
37ec987a32 | ||
|
|
37b5d1c6e8 | ||
|
|
09abbbadc9 | ||
|
|
8b0f95c53d | ||
|
|
920ef6e5c4 | ||
|
|
6829ff921d | ||
|
|
c6f4a13abc | ||
|
|
67f312075a | ||
|
|
5881b5b533 | ||
|
|
aa4da2ac4e | ||
|
|
d6c1e8069d | ||
|
|
74a8df6842 | ||
|
|
157fa0c1dd | ||
|
|
0139dd45c9 | ||
|
|
9546bf5bb7 | ||
|
|
1714028e83 | ||
|
|
d178c458df | ||
|
|
415aebfd74 | ||
|
|
68fd8fcfc5 | ||
|
|
4c16af918d | ||
|
|
b16601704f | ||
|
|
3a2aa135a1 | ||
|
|
6a43773b55 | ||
|
|
fa5132aba5 | ||
|
|
c21a2bf58b | ||
|
|
55ef1ecbd8 | ||
|
|
3883eddd5d | ||
|
|
40dd752cb7 | ||
|
|
3c6ae546f6 | ||
|
|
51fe0fe41f | ||
|
|
1957d960aa | ||
|
|
7098395323 | ||
|
|
57287cea3a | ||
|
|
9158f0bb79 | ||
|
|
55e5298ba6 | ||
|
|
fcc34f7942 | ||
|
|
b70d65f37b | ||
|
|
693c3f42a6 | ||
|
|
eab868db05 | ||
|
|
5a9c463457 | ||
|
|
8119cdbb36 | ||
|
|
8ce7a205f1 | ||
|
|
e943a1af82 | ||
|
|
e4cb8b3116 | ||
|
|
4afc413d8b | ||
|
|
df93c986f9 | ||
|
|
6f9336d067 | ||
|
|
82af101f9c | ||
|
|
8e4f4c39bb |
No files matched your search
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"isRoot": true,
|
||||||
|
"tools": {
|
||||||
|
"csharpier": {
|
||||||
|
"version": "0.30.6",
|
||||||
|
"commands": [
|
||||||
|
"dotnet-csharpier"
|
||||||
|
],
|
||||||
|
"rollForward": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/submodules/
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"tabWidth": 2
|
||||||
|
}
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
name: Assets
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: main
|
|
||||||
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
asset_repo:
|
|
||||||
type: choice
|
|
||||||
required: false
|
|
||||||
description: What asset repo to write to
|
|
||||||
options:
|
|
||||||
- blitzkit/assets
|
|
||||||
- blitzkit/assets-preview
|
|
||||||
- blitzkit/assets-dev
|
|
||||||
default: blitzkit/assets
|
|
||||||
|
|
||||||
archive_url:
|
|
||||||
type: string
|
|
||||||
required: false
|
|
||||||
description: Archive URL to download instead of Steam client
|
|
||||||
|
|
||||||
schedule:
|
|
||||||
- cron: "0 2 * * *"
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.asset_repo }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
env:
|
|
||||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
|
||||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
|
||||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
|
||||||
VIDEOS_SHEET: ${{ secrets.VIDEOS_SHEET }}
|
|
||||||
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
|
|
||||||
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
|
|
||||||
WOTB_DLC_CDN: ${{ secrets.WOTB_DLC_CDN }}
|
|
||||||
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_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }}
|
|
||||||
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
|
|
||||||
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
|
|
||||||
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
|
|
||||||
ALLOW_ROBOTS: true
|
|
||||||
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
|
|
||||||
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
|
|
||||||
PUBLIC_PATREON_CLIENT_ID: ${{ secrets.PUBLIC_PATREON_CLIENT_ID }}
|
|
||||||
PUBLIC_PATREON_REDIRECT_URI: ${{ secrets.PUBLIC_PATREON_REDIRECT_URI }}
|
|
||||||
PATREON_CLIENT_SECRET: ${{ secrets.PATREON_CLIENT_SECRET }}
|
|
||||||
PUBLIC_NITROPAY_ID: ${{ secrets.PUBLIC_NITROPAY_ID }}
|
|
||||||
PUBLIC_DEBUG_MISSING_I18N: false
|
|
||||||
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
|
|
||||||
PUBLIC_ASSET_FAST_USER: blitzkit
|
|
||||||
PUBLIC_ASSET_FAST_REPO: assets
|
|
||||||
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
|
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
|
||||||
STEAM_USERNAME: ${{ secrets.STEAM_USERNAME }}
|
|
||||||
STEAM_PASSWORD: ${{ secrets.STEAM_PASSWORD }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build_assets:
|
|
||||||
name: Build assets
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
|
||||||
- uses: arduino/setup-protoc@v3
|
|
||||||
with:
|
|
||||||
repo-token: ${{ secrets.GH_TOKEN }}
|
|
||||||
- uses: oven-sh/setup-bun@v1
|
|
||||||
- run: bun run setup
|
|
||||||
|
|
||||||
- name: AppArmor hack
|
|
||||||
run: echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns
|
|
||||||
|
|
||||||
- name: Build assets
|
|
||||||
run: bun run build:assets --all-targets --manual
|
|
||||||
@@ -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 }}
|
||||||
@@ -12,20 +12,6 @@ concurrency:
|
|||||||
env:
|
env:
|
||||||
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 }}
|
||||||
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:
|
||||||
build:
|
build:
|
||||||
@@ -33,20 +19,16 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
|
||||||
- uses: oven-sh/setup-bun@v1
|
- uses: oven-sh/setup-bun@v1
|
||||||
- uses: arduino/setup-protoc@v3
|
|
||||||
with:
|
- name: Install dependencies
|
||||||
repo-token: ${{ secrets.GH_TOKEN }}
|
run: bun install
|
||||||
- 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 }}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: Bot
|
name: Build Bot
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -15,14 +15,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
|
||||||
- uses: oven-sh/setup-bun@v1
|
- uses: oven-sh/setup-bun@v1
|
||||||
- uses: arduino/setup-protoc@v3
|
|
||||||
with:
|
- name: Install dependencies
|
||||||
repo-token: ${{ secrets.GH_TOKEN }}
|
run: bun install
|
||||||
- run: bun run setup
|
|
||||||
|
- name: Build Prisma
|
||||||
|
run: bun run build:prisma
|
||||||
|
|
||||||
- name: Build bot
|
- name: Build bot
|
||||||
run: bun run build:bot
|
run: bun run build:bot
|
||||||
|
|||||||
@@ -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,11 +11,11 @@ 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:
|
||||||
@@ -23,14 +23,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
|
||||||
- uses: oven-sh/setup-bun@v1
|
- uses: oven-sh/setup-bun@v1
|
||||||
- uses: arduino/setup-protoc@v3
|
|
||||||
with:
|
- name: Install dependencies
|
||||||
repo-token: ${{ secrets.GH_TOKEN }}
|
run: bun install
|
||||||
- run: bun run setup
|
|
||||||
|
|
||||||
- name: Discover IDs
|
- name: Discover IDs
|
||||||
run: bun build:ids --production
|
run: bun build:ids --production
|
||||||
@@ -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]
|
||||||
@@ -20,22 +20,6 @@ env:
|
|||||||
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||||
PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }}
|
PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }}
|
||||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
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: ${{ secrets.ALLOW_ROBOTS }}
|
|
||||||
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 }}
|
|
||||||
PUBLIC_GUIDE_WEBSITE: ${{ secrets.PUBLIC_GUIDE_WEBSITE }}
|
|
||||||
SERVICING: ${{ secrets.SERVICING }}
|
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
host:
|
host:
|
||||||
@@ -43,13 +27,12 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: oven-sh/setup-bun@v1
|
- 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: |
|
||||||
DOWNLOAD_URL=$(curl -s https://api.github.com/repos/blitzkit/blitzkit/releases/latest \
|
DOWNLOAD_URL=$(curl -s https://api.github.com/repos/tresabhi/blitzkit/releases/latest \
|
||||||
| grep "browser_download_url.*bot.zip" \
|
| grep "browser_download_url.*bot.zip" \
|
||||||
| cut -d : -f 2,3 \
|
| cut -d : -f 2,3 \
|
||||||
| tr -d \")
|
| tr -d \")
|
||||||
|
|||||||
@@ -3,29 +3,7 @@ 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:
|
||||||
@@ -33,14 +11,18 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
|
||||||
- uses: oven-sh/setup-bun@v1
|
- uses: oven-sh/setup-bun@v1
|
||||||
- uses: arduino/setup-protoc@v3
|
|
||||||
with:
|
- name: Install dependencies
|
||||||
repo-token: ${{ secrets.GH_TOKEN }}
|
run: bun install
|
||||||
- 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 }}
|
||||||
run: bun build:ratingleaderboard --region=asia
|
run: bun build:ratingleaderboard --region=asia
|
||||||
@@ -3,29 +3,7 @@ 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:
|
||||||
@@ -33,14 +11,18 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
|
||||||
- uses: oven-sh/setup-bun@v1
|
- uses: oven-sh/setup-bun@v1
|
||||||
- uses: arduino/setup-protoc@v3
|
|
||||||
with:
|
- name: Install dependencies
|
||||||
repo-token: ${{ secrets.GH_TOKEN }}
|
run: bun install
|
||||||
- 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 }}
|
||||||
run: bun build:ratingleaderboard --region=eu
|
run: bun build:ratingleaderboard --region=eu
|
||||||
@@ -3,29 +3,7 @@ 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:
|
||||||
@@ -33,14 +11,18 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
|
||||||
- uses: oven-sh/setup-bun@v1
|
- uses: oven-sh/setup-bun@v1
|
||||||
- uses: arduino/setup-protoc@v3
|
|
||||||
with:
|
- name: Install dependencies
|
||||||
repo-token: ${{ secrets.GH_TOKEN }}
|
run: bun install
|
||||||
- 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 }}
|
||||||
run: bun build:ratingleaderboard --region=com
|
run: bun build:ratingleaderboard --region=com
|
||||||
@@ -11,13 +11,12 @@ 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:
|
||||||
@@ -28,11 +27,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
|
|
||||||
- uses: oven-sh/setup-bun@v1
|
- uses: oven-sh/setup-bun@v1
|
||||||
- uses: arduino/setup-protoc@v3
|
|
||||||
with:
|
- name: Install dependencies
|
||||||
repo-token: ${{ secrets.GH_TOKEN }}
|
run: bun install
|
||||||
- run: bun run setup
|
|
||||||
|
|
||||||
- name: Find reviews
|
- name: Find reviews
|
||||||
run: bun run build:reviews
|
run: bun packages/scripts/src/reviews.ts
|
||||||
+112
-17
@@ -5,7 +5,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 4 * * *"
|
- cron: '0 4 * * *'
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -14,47 +14,110 @@ permissions:
|
|||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: ${{ github.ref == 'refs/heads/main' }}
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||||
|
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||||
|
# PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }}
|
||||||
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
|
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
|
||||||
ALLOW_ROBOTS: ${{ secrets.ALLOW_ROBOTS }}
|
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 }}
|
||||||
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
|
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
|
||||||
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_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
|
DEPOT_DOWNLOADER_USERNAME: ${{ secrets.DEPOT_DOWNLOADER_USERNAME }}
|
||||||
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
|
DEPOT_DOWNLOADER_PASSWORD: ${{ secrets.DEPOT_DOWNLOADER_PASSWORD }}
|
||||||
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
|
STEAM_OS: windows
|
||||||
PUBLIC_GUIDE_WEBSITE: ${{ secrets.PUBLIC_GUIDE_WEBSITE }}
|
STEAM_APP: 3341250
|
||||||
SERVICING: ${{ secrets.SERVICING }}
|
STEAM_DEPOT: 3341251
|
||||||
|
PUBLIC_ASSET_BRANCH: ue
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
|
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
|
||||||
|
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
|
||||||
|
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
- uses: oven-sh/setup-bun@v1
|
|
||||||
- uses: arduino/setup-protoc@v3
|
|
||||||
with:
|
|
||||||
repo-token: ${{ secrets.GH_TOKEN }}
|
|
||||||
- uses: actions/configure-pages@v5
|
|
||||||
- run: bun run setup
|
|
||||||
|
|
||||||
- name: Build website
|
- name: Setup bun
|
||||||
run: bun run build:website
|
uses: oven-sh/setup-bun@v1
|
||||||
|
|
||||||
|
- name: Configure pages
|
||||||
|
uses: actions/configure-pages@v5
|
||||||
|
|
||||||
|
- name: Setup .NET
|
||||||
|
uses: actions/setup-dotnet@v4
|
||||||
|
with:
|
||||||
|
dotnet-version: 9.0
|
||||||
|
|
||||||
|
- name: Download Steam Depot
|
||||||
|
run: |
|
||||||
|
mkdir -p temp
|
||||||
|
|
||||||
|
echo "Downloading..."
|
||||||
|
DOWNLOAD_URL=$(curl -s https://api.github.com/repos/SteamRE/DepotDownloader/releases/latest | \
|
||||||
|
grep -i "linux-x64" | \
|
||||||
|
grep "browser_download_url" | \
|
||||||
|
cut -d '"' -f 4)
|
||||||
|
curl -L "$DOWNLOAD_URL" -o temp/depotDownloader.zip
|
||||||
|
|
||||||
|
echo "Unzipping..."
|
||||||
|
unzip temp/depotDownloader.zip -d temp/depotDownloader
|
||||||
|
|
||||||
|
echo "Download complete; files:"
|
||||||
|
ls temp/depotDownloader
|
||||||
|
|
||||||
|
- name: Download dependencies
|
||||||
|
run: bun install
|
||||||
|
|
||||||
|
- name: Restore dependencies
|
||||||
|
run: |
|
||||||
|
cd src/BlitzKit.CLI
|
||||||
|
dotnet restore
|
||||||
|
|
||||||
|
- name: Download client
|
||||||
|
run: |
|
||||||
|
chmod +x temp/depotDownloader/DepotDownloader
|
||||||
|
temp/depotDownloader/DepotDownloader \
|
||||||
|
-username "${{ env.DEPOT_DOWNLOADER_USERNAME }}" \
|
||||||
|
-password "${{ env.DEPOT_DOWNLOADER_PASSWORD }}" \
|
||||||
|
-os ${{ env.STEAM_OS }} \
|
||||||
|
-app ${{ env.STEAM_APP }} \
|
||||||
|
-depot ${{ env.STEAM_DEPOT }} \
|
||||||
|
-dir temp/depot \
|
||||||
|
-filelist filelist.txt
|
||||||
|
|
||||||
|
- name: Download containers
|
||||||
|
run: |
|
||||||
|
cd src/BlitzKit.CLI
|
||||||
|
dotnet run unpack ../../temp/depot
|
||||||
|
|
||||||
|
- name: Run API and build website
|
||||||
|
run: |
|
||||||
|
cd src/BlitzKit.CLI
|
||||||
|
dotnet run api --depot --verbosepa &
|
||||||
|
SERVER_PID=$!
|
||||||
|
|
||||||
|
cd ../..
|
||||||
|
bun run buildonly:website
|
||||||
|
|
||||||
|
kill $SERVER_PID || true
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-pages-artifact@v3
|
uses: actions/upload-pages-artifact@v3
|
||||||
with:
|
with:
|
||||||
|
name: build
|
||||||
path: dist/website
|
path: dist/website
|
||||||
|
|
||||||
deploy-github-pages:
|
deploy-github-pages:
|
||||||
@@ -70,3 +133,35 @@ jobs:
|
|||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
id: deployment
|
id: deployment
|
||||||
uses: actions/deploy-pages@v4
|
uses: actions/deploy-pages@v4
|
||||||
|
|
||||||
|
deploy-vercel:
|
||||||
|
if: ${{ github.ref != 'refs/heads/main' }}
|
||||||
|
name: Deploy to Vercel
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
environment:
|
||||||
|
name: Preview
|
||||||
|
url: ${{ steps.url.outputs.url }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup bun
|
||||||
|
uses: oven-sh/setup-bun@v1
|
||||||
|
|
||||||
|
- name: Download artifact
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: build
|
||||||
|
path: .vercel/output/
|
||||||
|
|
||||||
|
- name: Install Vercel CLI
|
||||||
|
run: bun install --global vercel
|
||||||
|
|
||||||
|
- name: Deploy Project Artifacts to Vercel
|
||||||
|
shell: bash
|
||||||
|
run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} | tee deploy.log
|
||||||
|
|
||||||
|
- name: Set preview url
|
||||||
|
id: url
|
||||||
|
run: echo "url=$(tail -1 deploy.log)">> $GITHUB_OUTPUT
|
||||||
+491
-27
@@ -1,7 +1,9 @@
|
|||||||
|
# src/BlitzKit.CLI/Protos
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
_node_modules
|
_node_modules
|
||||||
package-lock.json
|
package-lock.json
|
||||||
*.env
|
.env
|
||||||
/test-results/
|
/test-results/
|
||||||
/playwright-report/
|
/playwright-report/
|
||||||
/playwright/.cache/
|
/playwright/.cache/
|
||||||
@@ -22,34 +24,496 @@ depots
|
|||||||
public/sitemap*
|
public/sitemap*
|
||||||
/firebase.json
|
/firebase.json
|
||||||
target
|
target
|
||||||
# packages/core/src/protos/*.ts
|
# packages/core/src/protos/*
|
||||||
|
# !packages/core/src/protos/*.proto
|
||||||
/.vercel
|
/.vercel
|
||||||
.astro
|
/.astro
|
||||||
.vercel
|
.vercel
|
||||||
/src
|
|
||||||
/packages/bot/src/prisma
|
|
||||||
dev-dist
|
|
||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
pnpm-debug.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
|
|
||||||
node_modules
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
dist
|
## files generated by popular Visual Studio add-ons.
|
||||||
dist-ssr
|
##
|
||||||
*.local
|
## Get latest from `dotnet new gitignore`
|
||||||
|
|
||||||
# Editor directories and files
|
# dotenv files
|
||||||
.vscode/*
|
.env
|
||||||
!.vscode/extensions.json
|
|
||||||
.idea
|
# User-specific files
|
||||||
.DS_Store
|
*.rsuser
|
||||||
*.suo
|
*.suo
|
||||||
*.ntvs*
|
*.user
|
||||||
*.njsproj
|
*.userosscache
|
||||||
*.sln
|
*.sln.docstates
|
||||||
*.sw?
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Mono auto generated files
|
||||||
|
mono_crash.*
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
[Ww][Ii][Nn]32/
|
||||||
|
[Aa][Rr][Mm]/
|
||||||
|
[Aa][Rr][Mm]64/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
[Ll]ogs/
|
||||||
|
|
||||||
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# Visual Studio 2017 auto generated files
|
||||||
|
Generated\ Files/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUnit
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
nunit-*.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# Benchmark Results
|
||||||
|
BenchmarkDotNet.Artifacts/
|
||||||
|
|
||||||
|
# .NET
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# Tye
|
||||||
|
.tye/
|
||||||
|
|
||||||
|
# ASP.NET Scaffolding
|
||||||
|
ScaffoldingReadMe.txt
|
||||||
|
|
||||||
|
# StyleCop
|
||||||
|
StyleCopReport.xml
|
||||||
|
|
||||||
|
# Files built by Visual Studio
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_h.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.iobj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.ipdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*_wpftmp.csproj
|
||||||
|
*.log
|
||||||
|
*.tlog
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# Visual Studio Trace Files
|
||||||
|
*.e2e
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# AxoCover is a Code Coverage Tool
|
||||||
|
.axoCover/*
|
||||||
|
!.axoCover/settings.json
|
||||||
|
|
||||||
|
# Coverlet is a free, cross platform Code Coverage Tool
|
||||||
|
coverage*.json
|
||||||
|
coverage*.xml
|
||||||
|
coverage*.info
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# NuGet Symbol Packages
|
||||||
|
*.snupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
|
||||||
|
# REMOVING THIS BECAUSE IT IGNORES PACKAGES LOL
|
||||||
|
# **/[Pp]ackages/*
|
||||||
|
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/[Pp]ackages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
*.appx
|
||||||
|
*.appxbundle
|
||||||
|
*.appxupload
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!?*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Including strong name files can present a security risk
|
||||||
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
|
#*.snk
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
ServiceFabricBackup/
|
||||||
|
*.rptproj.bak
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
*.ndf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
*.rptproj.rsuser
|
||||||
|
*- [Bb]ackup.rdl
|
||||||
|
*- [Bb]ackup ([0-9]).rdl
|
||||||
|
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
*.vbw
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||||
|
*.vbp
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||||
|
*.dsw
|
||||||
|
*.dsp
|
||||||
|
|
||||||
|
# Visual Studio 6 technical files
|
||||||
|
*.ncb
|
||||||
|
*.aps
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
||||||
|
|
||||||
|
# CodeRush personal settings
|
||||||
|
.cr/personal
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
# tools/**
|
||||||
|
# !tools/packages.config
|
||||||
|
|
||||||
|
# Tabs Studio
|
||||||
|
*.tss
|
||||||
|
|
||||||
|
# Telerik's JustMock configuration file
|
||||||
|
*.jmconfig
|
||||||
|
|
||||||
|
# BizTalk build output
|
||||||
|
*.btp.cs
|
||||||
|
*.btm.cs
|
||||||
|
*.odx.cs
|
||||||
|
*.xsd.cs
|
||||||
|
|
||||||
|
# OpenCover UI analysis results
|
||||||
|
OpenCover/
|
||||||
|
|
||||||
|
# Azure Stream Analytics local run output
|
||||||
|
ASALocalRun/
|
||||||
|
|
||||||
|
# MSBuild Binary and Structured Log
|
||||||
|
*.binlog
|
||||||
|
|
||||||
|
# NVidia Nsight GPU debugger configuration file
|
||||||
|
*.nvuser
|
||||||
|
|
||||||
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
|
.mfractor/
|
||||||
|
|
||||||
|
# Local History for Visual Studio
|
||||||
|
.localhistory/
|
||||||
|
|
||||||
|
# Visual Studio History (VSHistory) files
|
||||||
|
.vshistory/
|
||||||
|
|
||||||
|
# BeatPulse healthcheck temp database
|
||||||
|
healthchecksdb
|
||||||
|
|
||||||
|
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||||
|
MigrationBackup/
|
||||||
|
|
||||||
|
# Ionide (cross platform F# VS Code tools) working folder
|
||||||
|
.ionide/
|
||||||
|
|
||||||
|
# Fody - auto-generated XML schema
|
||||||
|
FodyWeavers.xsd
|
||||||
|
|
||||||
|
# VS Code files for those working on multiple tools
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
*.code-workspace
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# Windows Installer files from build outputs
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
*.sln.iml
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
##
|
||||||
|
## Visual studio for Mac
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
# globs
|
||||||
|
Makefile.in
|
||||||
|
*.userprefs
|
||||||
|
*.usertasks
|
||||||
|
config.make
|
||||||
|
config.status
|
||||||
|
aclocal.m4
|
||||||
|
install-sh
|
||||||
|
autom4te.cache/
|
||||||
|
*.tar.gz
|
||||||
|
tarballs/
|
||||||
|
test-results/
|
||||||
|
|
||||||
|
# Mac bundle stuff
|
||||||
|
*.dmg
|
||||||
|
*.app
|
||||||
|
|
||||||
|
# content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore
|
||||||
|
# General
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Icon must end with two \r
|
||||||
|
Icon
|
||||||
|
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear in the root of a volume
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
||||||
|
# content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore
|
||||||
|
# Windows thumbnail cache files
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
ehthumbs_vista.db
|
||||||
|
|
||||||
|
# Dump file
|
||||||
|
*.stackdump
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
[Dd]esktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Windows Installer files
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# Windows shortcuts
|
||||||
|
*.lnk
|
||||||
|
|
||||||
|
# Vim temporary swap files
|
||||||
|
*.swp
|
||||||
+9
-3
@@ -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/UAssetAPI"]
|
||||||
|
path = submodules/UAssetAPI
|
||||||
|
url = https://github.com/atenfyr/UAssetAPI
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/submodules/
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"trailingComma": "all",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 80
|
||||||
|
}
|
||||||
Vendored
+22
-33
@@ -11,8 +11,8 @@
|
|||||||
"analyticsreporting",
|
"analyticsreporting",
|
||||||
"APAC",
|
"APAC",
|
||||||
"apcr",
|
"apcr",
|
||||||
"appinfo",
|
|
||||||
"applypatches",
|
"applypatches",
|
||||||
|
"arrayfy",
|
||||||
"artefacts",
|
"artefacts",
|
||||||
"astrojs",
|
"astrojs",
|
||||||
"astrolib",
|
"astrolib",
|
||||||
@@ -22,7 +22,6 @@
|
|||||||
"AYUV",
|
"AYUV",
|
||||||
"battleresults",
|
"battleresults",
|
||||||
"bbox",
|
"bbox",
|
||||||
"beforeinstallprompt",
|
|
||||||
"bepaint",
|
"bepaint",
|
||||||
"betakrieg",
|
"betakrieg",
|
||||||
"BINORMAL",
|
"BINORMAL",
|
||||||
@@ -35,20 +34,16 @@
|
|||||||
"blitzkit",
|
"blitzkit",
|
||||||
"blitzrinth",
|
"blitzrinth",
|
||||||
"blitzstars",
|
"blitzstars",
|
||||||
"bufbuild",
|
|
||||||
"cacheable",
|
"cacheable",
|
||||||
"Callouts",
|
"Callouts",
|
||||||
"Camo",
|
"Camo",
|
||||||
"Carro",
|
|
||||||
"CCPAOPTOUT",
|
"CCPAOPTOUT",
|
||||||
"CDON",
|
"CDON",
|
||||||
"clanid",
|
|
||||||
"classname",
|
"classname",
|
||||||
"cleandist",
|
"cleandist",
|
||||||
"closedkrieg",
|
"closedkrieg",
|
||||||
"cockroackdb",
|
"cockroackdb",
|
||||||
"Coef",
|
"Coef",
|
||||||
"Combattimento",
|
|
||||||
"cooldown",
|
"cooldown",
|
||||||
"corelations",
|
"corelations",
|
||||||
"corsproxy",
|
"corsproxy",
|
||||||
@@ -60,15 +55,12 @@
|
|||||||
"dbid",
|
"dbid",
|
||||||
"Ddpf",
|
"Ddpf",
|
||||||
"Ddsd",
|
"Ddsd",
|
||||||
"deadrail",
|
|
||||||
"debugflags",
|
"debugflags",
|
||||||
"deburr",
|
"deburr",
|
||||||
"decalmask",
|
"decalmask",
|
||||||
"decompressor",
|
"decompressor",
|
||||||
"dedup",
|
|
||||||
"defence",
|
"defence",
|
||||||
"degressive",
|
"degressive",
|
||||||
"depotdownloader",
|
|
||||||
"devkrieg",
|
"devkrieg",
|
||||||
"diacriticless",
|
"diacriticless",
|
||||||
"DIDS",
|
"DIDS",
|
||||||
@@ -79,26 +71,24 @@
|
|||||||
"DVPLCRC",
|
"DVPLCRC",
|
||||||
"dvpm",
|
"dvpm",
|
||||||
"dxgi",
|
"dxgi",
|
||||||
"easimon",
|
|
||||||
"echarts",
|
"echarts",
|
||||||
"Everytime",
|
"Everytime",
|
||||||
|
"Exergizable",
|
||||||
|
"Exergize",
|
||||||
|
"Exergizing",
|
||||||
|
"Exergy",
|
||||||
"FASTNAME",
|
"FASTNAME",
|
||||||
"favicons",
|
|
||||||
"flexdirection",
|
"flexdirection",
|
||||||
"fontsource",
|
|
||||||
"FOURCC",
|
"FOURCC",
|
||||||
"frameloop",
|
"frameloop",
|
||||||
"fsformat",
|
"fsformat",
|
||||||
"Fullscreen",
|
"Fullscreen",
|
||||||
"fullstats",
|
"fullstats",
|
||||||
"fuzzysort",
|
"fuzzysort",
|
||||||
"gamemode",
|
|
||||||
"gameplay",
|
"gameplay",
|
||||||
"glsl",
|
"glsl",
|
||||||
"GLTF",
|
"GLTF",
|
||||||
"gtag",
|
|
||||||
"haha",
|
"haha",
|
||||||
"Herdbreaker",
|
|
||||||
"highp",
|
"highp",
|
||||||
"iife",
|
"iife",
|
||||||
"imgur",
|
"imgur",
|
||||||
@@ -112,7 +102,6 @@
|
|||||||
"leaderboards",
|
"leaderboards",
|
||||||
"lerp",
|
"lerp",
|
||||||
"LINEARSIZE",
|
"LINEARSIZE",
|
||||||
"linktarget",
|
|
||||||
"lmao",
|
"lmao",
|
||||||
"loadouts",
|
"loadouts",
|
||||||
"loddist",
|
"loddist",
|
||||||
@@ -121,7 +110,6 @@
|
|||||||
"Maddoxkkm",
|
"Maddoxkkm",
|
||||||
"malum",
|
"malum",
|
||||||
"mantlets",
|
"mantlets",
|
||||||
"mediump",
|
|
||||||
"metallicness",
|
"metallicness",
|
||||||
"metalness",
|
"metalness",
|
||||||
"minitelrose",
|
"minitelrose",
|
||||||
@@ -135,45 +123,42 @@
|
|||||||
"NEGATIVEX",
|
"NEGATIVEX",
|
||||||
"NEGATIVEY",
|
"NEGATIVEY",
|
||||||
"NEGATIVEZ",
|
"NEGATIVEZ",
|
||||||
"networkidle",
|
"Newtonsoft",
|
||||||
"NITROPAY",
|
"NITROPAY",
|
||||||
"nivo",
|
"nivo",
|
||||||
"nmatname",
|
"nmatname",
|
||||||
"noout",
|
"noout",
|
||||||
"normalmap",
|
"normalmap",
|
||||||
"oneof",
|
"oneof",
|
||||||
|
"Oodle",
|
||||||
"oopsies",
|
"oopsies",
|
||||||
"Oopsy",
|
"Oopsy",
|
||||||
"opentest",
|
"opentest",
|
||||||
"OPTML",
|
"OPTML",
|
||||||
"otemp",
|
|
||||||
"outdir",
|
"outdir",
|
||||||
"outfile",
|
"outfile",
|
||||||
"ownedtanks",
|
"ownedtanks",
|
||||||
"padder",
|
"paks",
|
||||||
"partytown",
|
"partytown",
|
||||||
"patchnotes",
|
|
||||||
"PIXELFORMAT",
|
"PIXELFORMAT",
|
||||||
"playerachievements",
|
"playerachievements",
|
||||||
"playerinfo",
|
"playerinfo",
|
||||||
"playform",
|
"playform",
|
||||||
|
"Playtest",
|
||||||
"pngjs",
|
"pngjs",
|
||||||
"POSITIVEX",
|
"POSITIVEX",
|
||||||
"POSITIVEY",
|
"POSITIVEY",
|
||||||
"POSITIVEZ",
|
"POSITIVEZ",
|
||||||
"precompute",
|
"precompute",
|
||||||
|
"preforged",
|
||||||
"Promisable",
|
"Promisable",
|
||||||
"proto",
|
"proto",
|
||||||
"protobuf",
|
"protobuf",
|
||||||
"protoc",
|
|
||||||
"protos",
|
"protos",
|
||||||
"proxyclient",
|
|
||||||
"prunestrings",
|
|
||||||
"Pyogenics",
|
"Pyogenics",
|
||||||
"ratingleaderboard",
|
"ratingleaderboard",
|
||||||
"raycaster",
|
"raycaster",
|
||||||
"Redirector",
|
"regen",
|
||||||
"Relogin",
|
|
||||||
"researchable",
|
"researchable",
|
||||||
"Resvg",
|
"Resvg",
|
||||||
"rizz",
|
"rizz",
|
||||||
@@ -187,7 +172,6 @@
|
|||||||
"searchtanks",
|
"searchtanks",
|
||||||
"SHAREDEXP",
|
"SHAREDEXP",
|
||||||
"SKLLD",
|
"SKLLD",
|
||||||
"smoothstep",
|
|
||||||
"Smri",
|
"Smri",
|
||||||
"Specularity",
|
"Specularity",
|
||||||
"SSAO",
|
"SSAO",
|
||||||
@@ -207,14 +191,14 @@
|
|||||||
"tankopedia",
|
"tankopedia",
|
||||||
"tankopedias",
|
"tankopedias",
|
||||||
"tankstats",
|
"tankstats",
|
||||||
"tauri",
|
|
||||||
"texconv",
|
"texconv",
|
||||||
"TEXCOORD",
|
"TEXCOORD",
|
||||||
"tresabhi",
|
"tresabhi",
|
||||||
"tungstentip",
|
"tungstentip",
|
||||||
"Turretless",
|
|
||||||
"TYPELESS",
|
"TYPELESS",
|
||||||
|
"uasset",
|
||||||
"ubjson",
|
"ubjson",
|
||||||
|
"uexp",
|
||||||
"Unicum",
|
"Unicum",
|
||||||
"UNLOCALIZED",
|
"UNLOCALIZED",
|
||||||
"UNORM",
|
"UNORM",
|
||||||
@@ -222,14 +206,14 @@
|
|||||||
"Unregistering",
|
"Unregistering",
|
||||||
"unrotate",
|
"unrotate",
|
||||||
"unwrapper",
|
"unwrapper",
|
||||||
"userns",
|
"Usmap",
|
||||||
"vercel",
|
"vercel",
|
||||||
|
"Verts",
|
||||||
"vickers",
|
"vickers",
|
||||||
"vitepress",
|
"vitepress",
|
||||||
"Vovko",
|
"Vovko",
|
||||||
"wargaming",
|
"wargaming",
|
||||||
"webp",
|
"webp",
|
||||||
"wgpkg",
|
|
||||||
"whitespaces",
|
"whitespaces",
|
||||||
"WHSS",
|
"WHSS",
|
||||||
"winrate",
|
"winrate",
|
||||||
@@ -246,7 +230,12 @@
|
|||||||
"yolo",
|
"yolo",
|
||||||
"youtuber",
|
"youtuber",
|
||||||
"youtubers",
|
"youtubers",
|
||||||
"zoomable"
|
"zustand"
|
||||||
],
|
],
|
||||||
"typescript.tsdk": "node_modules\\typescript\\lib"
|
"deno.enable": false,
|
||||||
|
"deno.unstable": false,
|
||||||
|
"cSpell.enableFiletypes": ["glsl", "proto3"],
|
||||||
|
"typescript.tsdk": "node_modules\\typescript\\lib",
|
||||||
|
"liveServer.settings.multiRootWorkspaceName": "blitzkit",
|
||||||
|
"dotnet.defaultSolution": "blitzkit.sln"
|
||||||
}
|
}
|
||||||
Vendored
+41
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "build",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"${workspaceFolder}/blitzkit.sln",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "publish",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"publish",
|
||||||
|
"${workspaceFolder}/blitzkit.sln",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "watch",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"watch",
|
||||||
|
"run",
|
||||||
|
"--project",
|
||||||
|
"${workspaceFolder}/blitzkit.sln"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,661 @@
|
|||||||
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU Affero General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works, specifically designed to ensure
|
||||||
|
cooperation with the community in the case of network server software.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
our General Public Licenses are intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
Developers that use our General Public Licenses protect your rights
|
||||||
|
with two steps: (1) assert copyright on the software, and (2) offer
|
||||||
|
you this License which gives you legal permission to copy, distribute
|
||||||
|
and/or modify the software.
|
||||||
|
|
||||||
|
A secondary benefit of defending all users' freedom is that
|
||||||
|
improvements made in alternate versions of the program, if they
|
||||||
|
receive widespread use, become available for other developers to
|
||||||
|
incorporate. Many developers of free software are heartened and
|
||||||
|
encouraged by the resulting cooperation. However, in the case of
|
||||||
|
software used on network servers, this result may fail to come about.
|
||||||
|
The GNU General Public License permits making a modified version and
|
||||||
|
letting the public access it on a server without ever releasing its
|
||||||
|
source code to the public.
|
||||||
|
|
||||||
|
The GNU Affero General Public License is designed specifically to
|
||||||
|
ensure that, in such cases, the modified source code becomes available
|
||||||
|
to the community. It requires the operator of a network server to
|
||||||
|
provide the source code of the modified version running there to the
|
||||||
|
users of that server. Therefore, public use of a modified version, on
|
||||||
|
a publicly accessible server, gives the public access to the source
|
||||||
|
code of the modified version.
|
||||||
|
|
||||||
|
An older license, called the Affero General Public License and
|
||||||
|
published by Affero, was designed to accomplish similar goals. This is
|
||||||
|
a different license, not a version of the Affero GPL, but Affero has
|
||||||
|
released a new version of the Affero GPL which permits relicensing under
|
||||||
|
this license.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, if you modify the
|
||||||
|
Program, your modified version must prominently offer all users
|
||||||
|
interacting with it remotely through a computer network (if your version
|
||||||
|
supports such interaction) an opportunity to receive the Corresponding
|
||||||
|
Source of your version by providing access to the Corresponding Source
|
||||||
|
from a network server at no charge, through some standard or customary
|
||||||
|
means of facilitating copying of software. This Corresponding Source
|
||||||
|
shall include the Corresponding Source for any work covered by version 3
|
||||||
|
of the GNU General Public License that is incorporated pursuant to the
|
||||||
|
following paragraph.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the work with which it is combined will remain governed by version
|
||||||
|
3 of the GNU General Public License.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU Affero General Public License from time to time. Such new versions
|
||||||
|
will be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU Affero General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU Affero General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU Affero General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If your software can interact with users remotely through a computer
|
||||||
|
network, you should also make sure that it provides a way for users to
|
||||||
|
get its source. For example, if your program is a web application, its
|
||||||
|
interface could display a "Source" link that leads users to an archive
|
||||||
|
of the code. There are many ways you could offer source, and different
|
||||||
|
solutions will be better for different programs; see section 13 for the
|
||||||
|
specific requirements.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
-50
@@ -1,50 +0,0 @@
|
|||||||
# BlitzKit License
|
|
||||||
|
|
||||||
Version 1.1, August 5th, 2025. Copyright (c) TrèsAbhi.
|
|
||||||
|
|
||||||
Permission is hereby granted to anyone to:
|
|
||||||
|
|
||||||
- Fork, clone, and download the contents of this repository.
|
|
||||||
- Modify the code locally in private.
|
|
||||||
- Use and run the code **only for personal, non-public testing, or educational purposes**.
|
|
||||||
|
|
||||||
The following restrictions apply:
|
|
||||||
|
|
||||||
- You may **not** deploy, host, or make this project or any derivative publicly accessible.
|
|
||||||
- You may **not** share screenshots, recordings, or any form of media from this project unless you intend to merge your changes back into BlitzKit.
|
|
||||||
- You may **not** use this project or its contents in any public or commercial product.
|
|
||||||
- You may **not** redistribute modified or unmodified versions of this project.
|
|
||||||
|
|
||||||
Contributions:
|
|
||||||
|
|
||||||
- You are encouraged to propose changes or improvements via pull requests to the original BlitzKit repository.
|
|
||||||
- Any such contributions will be considered under the same license terms unless explicitly stated otherwise.
|
|
||||||
|
|
||||||
Violations of this license may result in revocation of usage rights and takedown requests.
|
|
||||||
|
|
||||||
For questions or licensing inquiries, contact support@blitzkit.app.
|
|
||||||
|
|
||||||
## Notes on Existing Forks
|
|
||||||
|
|
||||||
All forks fall under these categories:
|
|
||||||
|
|
||||||
- If your fork was made from commit `8462901` (Apr 17, 2023) or between commits `2958124` (Jul 3, 2023) and `85e483e` (Jul 13, 2024), your fork falls under the **MIT license**.
|
|
||||||
- If your fork was made from commits between `5646093` (Jul 13, 2024) and `e3f131c` (Aug 5, 2025), your fork falls under the **AGPL-3.0 license**.
|
|
||||||
- If your fork was made between commit `f7c356d` (Apr 17, 2023) and `2958124` (Jul 3, 2023), your fork falls under **a default exclusive copyright**.
|
|
||||||
- If your fork was made from commits between `e3f131c` (Aug 5, 2025) and today, your fork falls under **this license**.
|
|
||||||
|
|
||||||
MIT and AGPL-3.0 licensed fork owners:
|
|
||||||
|
|
||||||
- You may continue to use your fork of BlitzKit under the corresponding license.
|
|
||||||
- You may upgrade your fork up to commit `5cab394` (Aug 4, 2025) and keep the AGPL-3.0 license.
|
|
||||||
- You may upgrade to commit `e3f131c` (Aug 5, 2025) or beyond but have to **switch to this license** where all rules apply.
|
|
||||||
- If you upgrade to commit `e3f131c` (Aug 5, 2025) or beyond, you must retract all public deployments of BlitzKit.
|
|
||||||
- Existing screenshots, recordings, or any form of media can remain.
|
|
||||||
|
|
||||||
Default exclusive copyright fork owners:
|
|
||||||
|
|
||||||
- You may continue to use their forks under the AGPL-3.0 license as no license was applied to BlitzKit at the time of forking.
|
|
||||||
|
|
||||||
BlitzKit licensed fork owners:
|
|
||||||
|
|
||||||
- You may continue to upgrade to the latest commits only under the conditions of this license.
|
|
||||||
@@ -1,47 +1,13 @@
|
|||||||
# BlitzKit
|
## BlitzKit
|
||||||
|
|
||||||
Tools for everything and anything, World of Tanks Blitz.
|
Your premier source for all things Blitz.
|
||||||
|
|
||||||
This GitHub repository contains the code for BlitzKit and all its services. If you are here, looking to use BlitzKit, go to https://blitzkit.app/.
|
### https://blitzkit.app/
|
||||||
|
|
||||||
If you intend to contribute to the code, note that you may need to check out sensitive, closed-source submodules to locally develop certain aspects of BlitzKit. Communicate with `@tresabhi` on Discord to get started or discuss sensitive content, such as environment variables or submodules.
|

|
||||||
|
|
||||||
## Packages
|
## Invite the bot
|
||||||
|
|
||||||
| Package | Title | Source | Memo |
|
Go to [application directory for BlitzKit](https://discord.com/application-directory/1097673957865443370) and click "Add to Server." Voila! You're ready to go.
|
||||||
| ------------------------------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
|
|
||||||
| [packages/app](packages/app) | BlitzKit | [Play Store](https://play.google.com/store/apps/details?id=com.tresabhi.blitzkit), [App Store](https://apps.apple.com/app/4321), [Microsoft Store](https://apps.microsoft.com/detail/zyxw) | The native app of BlitzKit for all platforms. Unavailable from stores at this time. |
|
|
||||||
| [packages/bot](packages/bot) | BlitzKit Discord Bot | [Discord Application Directory](https://discord.com/discovery/applications/1097673957865443370) | The one-in-all Discord bot for statistics. |
|
|
||||||
| [packages/core](packages/core) | | | Mutual internal code shared between all other packages. |
|
|
||||||
| [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/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. |
|
|
||||||
|
|
||||||
## Submodules
|

|
||||||
|
|
||||||
| 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. |
|
|
||||||
|
|
||||||
## Environment Variables
|
|
||||||
|
|
||||||
| 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_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. |
|
|
||||||
| `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_ANALYTICS_PROPERTY_ID` | [Google Analytics](https://analytics.google.com/) | `430711767` | Google Analytics' unique property ID for [BlitzKit's apex domain](https://blitzkit.app/) and subdomains. |
|
|
||||||
| `GOOGLE_APPLICATION_CREDENTIALS` | [Google Cloud](https://cloud.google.com/) | | Google Cloud's API key, primarily used for ranking popular tanks and finding relevant tank review videos in Tankopedia. |
|
|
||||||
| `PUBLIC_DISCORD_SERVER_ID` | [BlitzKit Discord Server](https://discord.gg/nDt7AjGJQH) | `734786591205359697` | BlitzKit Discord Server's id. |
|
|
||||||
| `PUBLIC_ASSET_BASE` | [blitzkit/assets](https://github.com/blitzkit/assets) | `https://blitzkit.github.io/assets` | The root domain and path that serves the external assets like tank models and icons. |
|
|
||||||
| `PUBLIC_BRANCH` | | `main` | The branch that the website is operating on. This is completely nominal. |
|
|
||||||
| `PUBLIC_GUIDE_WEBSITE` | [GuidesBlitz](https://guidesblitz.com/) | `https://guidesblitz.com` | The website that serves the encyclopedia-like content in BlitzKit. |
|
|
||||||
| `SERVICING` | | `false` | Wether the website is down for servicing. |
|
|
||||||
| `DISCORD_CLIENT_ID` | [Discord Developer Portal](https://discord.com/developers/applications) | `1097673957865443370` | The BlitzKit Discord Bot's user ID. |
|
|
||||||
| `GH_TOKEN` | [GitHub Fine-Grained Tokens](https://github.com/settings/personal-access-tokens) | | The GitHub API key used for checking out private submodules and pushing content to the asset repository. |
|
|
||||||
| `DISCORD_TOKEN` | [Discord Developer Portal](https://discord.com/developers/applications) | | The Discord API Key for the BlitzKit Discord Bot. |
|
|
||||||
| `DATABASE_URL` | [CockroachDB](https://www.cockroachlabs.com/) | | The CockroachDB-SQL database URL used to link Wargaming and Discord users. |
|
|
||||||
| `WOTB_DLC_CDN` | | | |
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.5.2.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlitzKit.CLI", "src\BlitzKit.CLI\BlitzKit.CLI.csproj", "{65351C93-2DB4-8401-3BE1-C253571187A1}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{65351C93-2DB4-8401-3BE1-C253571187A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{65351C93-2DB4-8401-3BE1-C253571187A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{65351C93-2DB4-8401-3BE1-C253571187A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{65351C93-2DB4-8401-3BE1-C253571187A1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{65351C93-2DB4-8401-3BE1-C253571187A1} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {70DA613E-E0B8-4525-93DB-3BF91568E845}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# BlitzKit 1.27.10
|
|
||||||
|
|
||||||
This update changes the URLs across the whole website, removing the "tools" prefix and simplifying other aspects too.
|
|
||||||
|
|
||||||
## Changes
|
|
||||||
|
|
||||||
- Removed "full" tank names
|
|
||||||
- For example, tanks like the "Carro 45t" had full names like "Carro Combattimento 45t" which remained pretty much unused, so they have been removed
|
|
||||||
- Reduced the shadow below the navigation bar
|
|
||||||
- Removed the "tools" prefix from the URLs; here are the paths:
|
|
||||||
- `/tools/tankopedia` -> `/tanks`
|
|
||||||
- `/tools/tankopedia/12345` -> `/tanks/tank-name`
|
|
||||||
- `/tools/compare` -> `/compare`
|
|
||||||
- `/tools/embed` -> `/embed`
|
|
||||||
- `/tools/embed/embed-name` -> `/embed/embed-name`
|
|
||||||
- `/tools/gallery` -> `/gallery`
|
|
||||||
- `/tools/performance` -> `/performance`
|
|
||||||
- `/tools/session` -> `/session`
|
|
||||||
- `/tools/tier-list` -> `/tier-list`
|
|
||||||
|
|
||||||
## Technical Features
|
|
||||||
|
|
||||||
- The asset mangler now uses http headers and even more heuristics to minimize re-creating unchanged assets
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# BlitzKit 1.27.11
|
|
||||||
|
|
||||||
This update affects both the bot and the website.
|
|
||||||
|
|
||||||
## Technical Changes
|
|
||||||
|
|
||||||
- Added a proper CDN domain to avoid error 429s and any slowdowns
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# BlitzKit 1.27.12
|
|
||||||
|
|
||||||
This update only affects the website.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Added the ability to sort by HE shell penetration
|
|
||||||
- The Tankopedia compare bars now account for equipment and selected shells
|
|
||||||
- Made the hero section of the Tankopedia a bit shorter to show users that the page is scrollable
|
|
||||||
|
|
||||||
## Fixes
|
|
||||||
|
|
||||||
- The "Clear filters" button no longer shows up after clearing the search bar
|
|
||||||
- Guns for some tanks no longer go transparent when far away from the camera
|
|
||||||
- The dynamic armor tab no longer lags and flickers when rotating the turret of a tank
|
|
||||||
- Reimplemented the missing tool backgrounds in the navigation bar hamburger menu
|
|
||||||
- The navigation bar now properly goes transparent for pages that request like the Tankopedia pages
|
|
||||||
- Removed the tank name text outline because it doesn't work on most devices
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# BlitzKit 1.27.13
|
|
||||||
|
|
||||||
This update adds a feature for a future version of WoTB 👁️👃👁️
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Added compatibility for the new mechanic for the upcoming Polish Tank Destroyers
|
|
||||||
- This code will remain dormant until the update for WoTB is released
|
|
||||||
- Added a progress bar to the hero section of the Tankopedia to show that the model is loading
|
|
||||||
- Reordered some Tankopedia statistics to bring important statistics like Damage to the very top
|
|
||||||
|
|
||||||
## Fixes
|
|
||||||
|
|
||||||
- The second column in the footer is now aligned on mobile devices
|
|
||||||
- Fixed an issue that stopped the armor highlighting from working on the Kiwi browser and on older devices
|
|
||||||
|
|
||||||
## Technical Changes
|
|
||||||
|
|
||||||
- Removed permissions for robots to crawl non-blitzkit.app domains
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
# BlitzKit 1.27.14
|
|
||||||
|
|
||||||
This is the 14th patch for `1.27`, making it the new longest lasting version beating the previous record of `1.15.13`!
|
|
||||||
|
|
||||||
## Fixes
|
|
||||||
|
|
||||||
- Optimized the rendering of the shells in the Tankopedia search
|
|
||||||
- The Assault Shells cursor now works on iOS devices
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# BlitzKit 1.27.15
|
|
||||||
|
|
||||||
I did not see this coming
|
|
||||||
|
|
||||||
## Fixes
|
|
||||||
|
|
||||||
- The Assault Shell graph now supports damage factors of above 100% now
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# BlitzKit 1.27.16
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Added links to GuidesBlitz to Tankopedia to aid users understand tanks beyond just numbers and stats
|
|
||||||
- Added a gun depression and yaw visualizer
|
|
||||||
|
|
||||||
## Changes
|
|
||||||
|
|
||||||
- Made the colors of the advanced highlighting mode more subtle
|
|
||||||
- Made the Tankopedia stat lines more condensed
|
|
||||||
- You can now scroll the page even with your cursor on the Tankopedia sandbox
|
|
||||||
|
|
||||||
## Fixes
|
|
||||||
|
|
||||||
- Fixed a bug where the range of increased gun depression was incorrectly doubled
|
|
||||||
- Removed an unintended space before the "x" in the "Gun damaged" stat
|
|
||||||
- Fixed an issue causing the 3D renderer to render frames even when off screen
|
|
||||||
- Fixed a differential issue causing excessive rerendering of the stat lines
|
|
||||||
- Images no longer exceed the width of the device in the docs
|
|
||||||
- Assault shells no longer scale the module damage with distance
|
|
||||||
|
|
||||||
## Technical Changes
|
|
||||||
|
|
||||||
- Removed all code relating to the canceled "Shooting Range" mode
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# BlitzKit 1.27.17
|
|
||||||
|
|
||||||
This update was released early for the website. Today, it'll be officially announced and released for the bot
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Added the shell reload visualizer to aid people in getting a feel for how long shell reloads really are
|
|
||||||
- Added lines of stats for the burst shells
|
|
||||||
|
|
||||||
## Changes
|
|
||||||
|
|
||||||
- Removed the redundant word "Shell" from "Shell reload" and "Shell reloads"
|
|
||||||
- Removed the penetration over distance slider
|
|
||||||
|
|
||||||
## Fixes
|
|
||||||
|
|
||||||
- Fixed the DPM of tanks with burst shells like the ATAC
|
|
||||||
- The DPM will still be different from the game due to a bug in World of Tanks Blitz; I am trying to get Wargaming to fix it; trust BlitzKit!
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# BlitzKit 1.27.18
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Added a mini-game to BlitzKit similar to "Guess Who's That Pokemon" but with tanks: http://blitzkit.vercel.app/guess
|
|
||||||
- Switched to a new renderer for better lighting for the Tankopedia
|
|
||||||
|
|
||||||
## Fixes
|
|
||||||
|
|
||||||
- The loading bar for the Tankopedia sandbox now shows up again
|
|
||||||
- Removed a lot of dead code, improving performance
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# BlitzKit 1.27.19
|
|
||||||
|
|
||||||
Two updates in a day? I just got a little excited by the new Tankopedia lighting.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- The camera is now lower down in the Tankopedia, giving the tank a more heroic feel
|
|
||||||
- Tanks are better framed across all devices, minimizing whitespace
|
|
||||||
- Added a skip button for the reload visualizer to skip to the next event
|
|
||||||
|
|
||||||
## Fixes
|
|
||||||
|
|
||||||
- Removed the useless "show grid" setting
|
|
||||||
- Taking screenshots now no longer includes the scene props like the floor
|
|
||||||
- The subtler color contrast for the advanced highlighting has now been reverted back to the higher contrast
|
|
||||||
- The shot display card is now properly aligned as a proper table again
|
|
||||||
- Tank modules are once again considered in the penetration tester
|
|
||||||
- Tank filters in the antagonist selector in the Tankopedia sandbox are now properly aligned
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# BlitzKit 1.27.20
|
|
||||||
|
|
||||||
It's all technical updates! The HD tanks will be rolled out over the span of few days.
|
|
||||||
|
|
||||||
## Technical Changes
|
|
||||||
|
|
||||||
- Shadows are disabled if your framerate drops below 30fps
|
|
||||||
- Pages that redirect to other pages (like the old /tools/tankopedia) now load a few hundred times faster
|
|
||||||
- Replaced Zustand with a custom state manager called Varuna with the goal to rewrite a significant portion of the website to not require any React for rendering increasing performance
|
|
||||||
- The transition wireframe in the Tankopedia sandbox now uses a lower quality model for performance reasons
|
|
||||||
- Tanks with PBR now use the correct maximum resolution textures
|
|
||||||
- This is a significant update as you can now literally see individual splats of dust on tanks and scratches haha
|
|
||||||
- Baked the ambient occlusion maps into the textures, hence reducing the overall model load times even with higher resolution textures
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# BlitzKit 1.27.7
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
This update only affects the website.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Added a new foggy-wireframe transition between tabs to smooth out the jarring jumps
|
|
||||||
- Added animations pop-in and out animations on a few elements
|
|
||||||
- The tank name title now shrinks in size as soon as a disturbance is caused
|
|
||||||
- The shell selector, antagonist switcher, and thicknesses range now ease into frame
|
|
||||||
- The Tankopedia search page now loads as a skeleton by default before populating with tanks, allowing for the recently viewed tanks to load without cumulative shift
|
|
||||||
- Something similar was done to the Gallery tool
|
|
||||||
- Gave the meta section of the website some zest with nation-based backdrops
|
|
||||||
- The compare tabs in the Tankopedia are now centered
|
|
||||||
|
|
||||||
## Fixes
|
|
||||||
|
|
||||||
- The camera no longer snaps to the initial position when switching between tabs
|
|
||||||
|
|
||||||
## Technical Changes
|
|
||||||
|
|
||||||
- Backported some technologies from BlitzKit Reforged; you will not notice any differences in usage
|
|
||||||
- The popular tanks and gallery indexings have been moved to the asynchronous cron job
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# BlitzKit 1.27.8
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- The website now uses Inter as the font
|
|
||||||
- Added a cool new non-disturbed state for the Tankopedia because why not
|
|
||||||
|
|
||||||
## Changes
|
|
||||||
|
|
||||||
- Removed the Discord plug and language changer from the homepage
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
# BlitzKit 1.27.9
|
|
||||||
|
|
||||||
BlitzKit's great, but there's far too many numbers. This update adds a few visualizers to help new players understand how things relate numerically.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Added the turret and hull traverse speed visualizer
|
|
||||||
- Added the shell ricochet and normalization angles visualizer
|
|
||||||
- Added the view-range visualizer
|
|
||||||
- Added a search bar to the Tankopedia characteristics
|
|
||||||
- Bumped the decimal places for vehicle dimensions and volume
|
|
||||||
- Made tank ranks softer (the bars beneath the tank characteristics)
|
|
||||||
- Gave the navigation bar better distinction from the rest of the page
|
|
||||||
- Increased the number of characteristics rows to 3 on select devices
|
|
||||||
- Switched to a more contrasting classic style of components across the website (it's subtle, but beautiful)
|
|
||||||
|
|
||||||
## Changes
|
|
||||||
|
|
||||||
- Removed units
|
|
||||||
|
|
||||||
## Fixes
|
|
||||||
|
|
||||||
- Clarified a few confusing row labels in the Compare tool
|
|
||||||
- Made the Tankopedia hero section dynamically smaller on mobile phones to allow scrolling
|
|
||||||
- Tank ranks no longer reach 0 or go above the number of vehicles in the comparison
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# BlitzKit 1.28.0
|
|
||||||
|
|
||||||
BlitzKit, as an app and the new Mixer tool!
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Created the BlitzKit app that can be downloaded on most devices
|
|
||||||
- Re-did the BlitzKit logo to be a bit warmer
|
|
||||||
- Added the Mixer tool which lets you combine tank models into Frankensteins
|
|
||||||
- Updated all dependencies to the latest version for a decent performance boost
|
|
||||||
|
|
||||||
## Fixes
|
|
||||||
|
|
||||||
- BlitzKit no longer forgets persistent data if the app crashes without properly closing
|
|
||||||
- The tools list in the navbar no longer overflows into the neighboring elements on certain devices
|
|
||||||
- Added keying to a few places, speeding up re-rendering
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
# BlitzKit 1.28.1
|
|
||||||
|
|
||||||
## Fixes
|
|
||||||
|
|
||||||
- The lights no longer crash the Mixer tool
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
# BlitzKit 1.28.2
|
|
||||||
|
|
||||||
## Fixes
|
|
||||||
|
|
||||||
- BlitzKit no longer tries to redirect translated pages to the same page, over and over again
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# BlitzKit 1.28.3
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Turrets and guns can now be rotated in Mixer without any pitch or yaw limits
|
|
||||||
- Added nation icons to the search results for Guesser
|
|
||||||
- The table now occupies the whole screen in Compare
|
|
||||||
- Added the ability to log in with a Wargaming account in the navbar
|
|
||||||
- Added owned/unowned filters in the Tankopedia filters
|
|
||||||
- Revamped Tankopedia quick controls to be more accessible and obvious
|
|
||||||
|
|
||||||
## Fixes
|
|
||||||
|
|
||||||
- Missing tank icons across the website have been fixed
|
|
||||||
- Events of starting and stopping of the transform tanks now use an asynchronous event instead of a reactive state, making them faster
|
|
||||||
- Removed a hook call on root context, speeding up load times
|
|
||||||
- Tankopedia's pitch input now follows the same sign convention as the pitch and yaw visualizer
|
|
||||||
- Shadows are no longer disabled when the scene is unloaded when the user scrolls away from the canvas
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
# BlitzKit 1.29.1
|
|
||||||
|
|
||||||
## Technical Changes
|
|
||||||
|
|
||||||
- Recreated the full dependency tree for BlitzKit
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
# BlitzKit 1.29.2
|
|
||||||
|
|
||||||
## Technical Changes
|
|
||||||
|
|
||||||
- Implemented new workflow capabilities to deploy BlitzKit OpenTest and Preview remotely, from any device
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
# BlitzKit 1.29.3
|
|
||||||
|
|
||||||
## Technical Changes
|
|
||||||
|
|
||||||
- Added a significantly more memory and network efficient AssetUploader abstraction
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
`Blitz/Config/DefaultGameplayTags.ini` seems to have an exhaustive list of all maps, tanks, sets, game modes, legendary camouflages, consumables, skills, quick commands, UI components, etc.
|
||||||
|
|
||||||
|
`Blitz/Config/DefaultGame.ini` seems to have all locales under the name of "Cultures"
|
||||||
|
|
||||||
|
`Blitz/Config/DefaultDlc.ini` contains DLC info; essentially extra `pak` containers
|
||||||
|
|
||||||
|
`Blitz/Content/TanksStuff/Common/DT_TankClasses.uasset` seems to have all tank classes
|
||||||
|
|
||||||
|
`C:\Users\coola\AppData\Local\Blitz\Saved\PersistentDownloadDir\http\dl-wotblitz-gc.wargaming.net\dlc\p14y73c7_p5e_b10gg3RS_big_play\ue_localizations\development\general` has all localization files
|
||||||
|
|
||||||
|
`Blitz/Config/DefaultBzBattleSettings.ini` contains all modules that can take damage when a tank takes fall damage
|
||||||
|
|
||||||
|
`Blitz/Config/DefaultLocalizationFetcher.ini` has a list of all locales
|
||||||
|
|
||||||
|
`Blitz/Content/TanksStuff/Dev/DT_DAVATanks.uasset` has all DAVA-UE tank mappings
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Blitz/Content/Paks/pakchunk0-Windows.pak
|
||||||
+28
-19
@@ -1,25 +1,27 @@
|
|||||||
{
|
{
|
||||||
"name": "blitzkit",
|
"name": "blitzkit",
|
||||||
"version": "1.29.6",
|
|
||||||
"license": "BlitzKit License",
|
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"version": "1.27.6",
|
||||||
|
"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",
|
||||||
|
"buildonly:website": "bun build:prisma --no-hints && cd packages/website && bun run buildonly",
|
||||||
"build:prisma": "prisma generate",
|
"build:prisma": "prisma generate",
|
||||||
"build:proxyclient": "cd packages/scripts && bun run build:proxyclient",
|
"build:sitemap": "bun scripts/sitemap.ts",
|
||||||
"build:catalogitemaccesssor": "cd packages/scripts && bun run build:catalogitemaccesssor",
|
"build:docs": "vitepress build docs",
|
||||||
"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",
|
|
||||||
"dev:bot": "bun run build:bot --dev && bun start:bot",
|
"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",
|
||||||
"dev:app": "cd packages/app && bun run dev",
|
|
||||||
"dev": "bun run dev:website",
|
|
||||||
"dev:api": "bun --filter=@blitzkit/api run dev",
|
|
||||||
"setup:depotdownloader": "bun --filter=@blitzkit/scripts run setup:depotdownloader",
|
"setup:depotdownloader": "bun --filter=@blitzkit/scripts run setup:depotdownloader",
|
||||||
"setup:patches": "bun --filter=@blitzkit/scripts run setup:patches",
|
"setup:patches": "bun --filter=@blitzkit/scripts run setup:patches",
|
||||||
"start:website": "bun --filter=@blitzkit/website run start",
|
"start:website": "bun --filter=@blitzkit/website run start",
|
||||||
@@ -30,21 +32,28 @@
|
|||||||
"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",
|
"protos": "rm -rf src/BlitzKit.CLI/Protos && mkdir -p src/BlitzKit.CLI/Protos && protoc packages/core/src/protos/*.proto --csharp_out=src/BlitzKit.CLI/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",
|
"dev:cli:unpack": "cd src/BlitzKit.CLI && dotnet run unpack \"C:/Program Files (x86)/Steam/steamapps/common/World of Tanks Blitz Playtest\" --no-overwrite",
|
||||||
"patchnotes": "cd packages/scripts && bun run patchnotes"
|
"mangle": "cd src/BlitzKit.CLI && dotnet run mangle",
|
||||||
|
"mangle2": "cd src/BlitzKit.CLI && dotnet run mangle2",
|
||||||
|
"api": "cd src/BlitzKit.CLI && dotnet run api",
|
||||||
|
"dev:api": "cd src/BlitzKit.CLI && dotnet watch run api"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"license": "MIT",
|
||||||
"workspaces": [
|
|
||||||
"packages/*"
|
|
||||||
],
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "^6.18.0",
|
"sharp": "^0.33.5",
|
||||||
"prisma": "^6.18.0",
|
"lodash-es": "^4.17.21"
|
||||||
"typescript": "^5.9.3"
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest",
|
||||||
|
"@types/lodash-es": "^4.17.12",
|
||||||
|
"@types/node": "^22.5.4",
|
||||||
|
"prettier": "^3.3.3",
|
||||||
|
"prisma": "^6.0.0",
|
||||||
|
"typescript": "^5.5.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@blitzkit/app",
|
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite",
|
|
||||||
"build": "tsc && vite build",
|
|
||||||
"preview": "vite preview",
|
|
||||||
"tauri": "tauri"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@tauri-apps/api": "^2",
|
|
||||||
"@tauri-apps/plugin-opener": "^2",
|
|
||||||
"@tauri-apps/cli": "^2",
|
|
||||||
"vite": "^6.0.3",
|
|
||||||
"typescript": "~5.6.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# Generated by Cargo
|
|
||||||
# will have compiled files and executables
|
|
||||||
/target/
|
|
||||||
|
|
||||||
# Generated by Tauri
|
|
||||||
# will have schema files for capabilities auto-completion
|
|
||||||
/gen/schemas
|
|
||||||
Generated
-5286
File diff suppressed because it is too large.
Load diff
@@ -1,25 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "blitzkit"
|
|
||||||
version = "0.1.0"
|
|
||||||
description = "A Tauri App"
|
|
||||||
authors = ["you"]
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
# The `_lib` suffix may seem redundant but it is necessary
|
|
||||||
# to make the lib name unique and wouldn't conflict with the bin name.
|
|
||||||
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
|
|
||||||
name = "blitzkit_lib"
|
|
||||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
tauri-build = { version = "2", features = [] }
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
tauri = { version = "2", features = [] }
|
|
||||||
tauri-plugin-opener = "2"
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
|
||||||
serde_json = "1"
|
|
||||||
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
fn main() {
|
|
||||||
tauri_build::build()
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "../gen/schemas/desktop-schema.json",
|
|
||||||
"identifier": "default",
|
|
||||||
"description": "Capability for the main window",
|
|
||||||
"windows": ["main"],
|
|
||||||
"permissions": [
|
|
||||||
"core:default",
|
|
||||||
"opener:default"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
|
||||||
#[tauri::command]
|
|
||||||
fn greet(name: &str) -> String {
|
|
||||||
format!("Hello, {}! You've been greeted from Rust!", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
||||||
pub fn run() {
|
|
||||||
tauri::Builder::default()
|
|
||||||
.plugin(tauri_plugin_opener::init())
|
|
||||||
.invoke_handler(tauri::generate_handler![greet])
|
|
||||||
.run(tauri::generate_context!())
|
|
||||||
.expect("error while running tauri application");
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
|
||||||
|
|
||||||
use std::process::{Command, Stdio};
|
|
||||||
use tauri::Manager;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
tauri::Builder::default()
|
|
||||||
.setup(|app| {
|
|
||||||
let app_handle = app.handle();
|
|
||||||
|
|
||||||
let entry = app_handle
|
|
||||||
.path_resolver()
|
|
||||||
.resolve_resource("../../dist/website/server/entry.mjs")
|
|
||||||
.expect("failed to resolve server.ts");
|
|
||||||
|
|
||||||
Command::new("bun")
|
|
||||||
.arg("run")
|
|
||||||
.arg(entry)
|
|
||||||
.stderr(Stdio::null())
|
|
||||||
.spawn()
|
|
||||||
.expect("failed to start Bun server");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.run(tauri::generate_context!())
|
|
||||||
.expect("error while running Tauri app");
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
|
||||||
"productName": "blitzkit",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"identifier": "com.tresabhi.blitzkit",
|
|
||||||
"build": {
|
|
||||||
"beforeDevCommand": "cd ../website && bun run dev",
|
|
||||||
"_beforeBuildCommand": "cd ../website && bun run build",
|
|
||||||
"frontendDist": "../../../dist/website/client",
|
|
||||||
"devUrl": "http://localhost:4321"
|
|
||||||
},
|
|
||||||
"app": {
|
|
||||||
"withGlobalTauri": true,
|
|
||||||
"windows": [
|
|
||||||
{
|
|
||||||
"url": "http://localhost:4321",
|
|
||||||
"title": "BlitzKit",
|
|
||||||
"fullscreen": false,
|
|
||||||
"resizable": true,
|
|
||||||
"maximized": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"security": {
|
|
||||||
"csp": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"bundle": {
|
|
||||||
"active": true,
|
|
||||||
"targets": "all",
|
|
||||||
"icon": [
|
|
||||||
"icons/32x32.png",
|
|
||||||
"icons/128x128.png",
|
|
||||||
"icons/[email protected]",
|
|
||||||
"icons/icon.icns",
|
|
||||||
"icons/icon.ico"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": ["../../tsconfig.json"],
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ES2020",
|
|
||||||
"useDefineForClassFields": true,
|
|
||||||
"module": "ESNext",
|
|
||||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"strict": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"noFallthroughCasesInSwitch": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+13
-21
@@ -1,34 +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.10.14",
|
||||||
"@radix-ui/themes": "^3.2.1",
|
|
||||||
"@resvg/resvg-js": "^2.6.2",
|
"@resvg/resvg-js": "^2.6.2",
|
||||||
"@types/adm-zip": "^0.5.7",
|
"discord.js": "^14.16.3",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"react": "^18.3.1",
|
||||||
"@types/markdown-escape": "^1.1.3",
|
"@radix-ui/colors": "^3.0.0",
|
||||||
"@types/react": "^19.2.2",
|
"fuzzysort": "^2.0.4",
|
||||||
"adm-zip": "^0.5.16",
|
|
||||||
"bepaint": "^0.1.0-beta.3",
|
"bepaint": "^0.1.0-beta.3",
|
||||||
"discord.js": "^14.22.1",
|
"dotenv": "^16.4.5",
|
||||||
"dotenv": "^17.2.2",
|
"markdown-escape": "^2.0.0"
|
||||||
"fuzzysort": "^3.1.0",
|
|
||||||
"lodash-es": "^4.17.21",
|
|
||||||
"markdown-escape": "^2.0.0",
|
|
||||||
"pickleparser": "^0.2.1",
|
|
||||||
"prisma": "^6.17.1",
|
|
||||||
"protobufjs": "^7.5.4",
|
|
||||||
"react": "^19.2.0",
|
|
||||||
"satori": "^0.18.3",
|
|
||||||
"sharp": "^0.34.4"
|
|
||||||
},
|
},
|
||||||
"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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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';
|
||||||
COMMANDS_RAW,
|
|
||||||
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,7 +30,7 @@ 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(COMMANDS_RAW)
|
await Promise.all(COMMANDS_RAW)
|
||||||
)
|
)
|
||||||
@@ -43,7 +40,7 @@ export const aboutCommand = new Promise<CommandRegistry>(async (resolve) => {
|
|||||||
(b.command.name_localizations?.[interaction.locale] ??
|
(b.command.name_localizations?.[interaction.locale] ??
|
||||||
b.command.name)
|
b.command.name)
|
||||||
? -1
|
? -1
|
||||||
: 1
|
: 1,
|
||||||
)
|
)
|
||||||
.map(
|
.map(
|
||||||
(registry) =>
|
(registry) =>
|
||||||
@@ -54,9 +51,9 @@ export const aboutCommand = new Promise<CommandRegistry>(async (resolve) => {
|
|||||||
registry.command.description_localizations?.[
|
registry.command.description_localizations?.[
|
||||||
interaction.locale
|
interaction.locale
|
||||||
] ?? 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`;
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
import {
|
||||||
|
BlitzTankClass,
|
||||||
|
Region,
|
||||||
|
regionToRegionSubdomain,
|
||||||
|
} from '@blitzkit/core';
|
||||||
|
import { literals } from '@blitzkit/i18n';
|
||||||
|
import { tankDefinitions } from '../core/blitzkit/nonBlockingPromises';
|
||||||
|
import { chunkLines } from '../core/discord/chunkLines';
|
||||||
|
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
||||||
|
import { translator } from '../core/localization/translator';
|
||||||
|
import { CommandRegistry } from '../events/interactionCreate';
|
||||||
|
|
||||||
|
type Auction = null | {
|
||||||
|
count: number;
|
||||||
|
has_next: boolean;
|
||||||
|
results: Result[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type Result =
|
||||||
|
| {
|
||||||
|
id?: number;
|
||||||
|
type: 'vehicle';
|
||||||
|
countable?: boolean;
|
||||||
|
entity?: Entity;
|
||||||
|
initial_count: number;
|
||||||
|
current_count: number;
|
||||||
|
saleable?: boolean;
|
||||||
|
available_from?: Date;
|
||||||
|
available_before?: Date;
|
||||||
|
price?: Price;
|
||||||
|
next_price: Price | null;
|
||||||
|
available: true;
|
||||||
|
display?: boolean;
|
||||||
|
next_price_datetime: Date;
|
||||||
|
next_price_timestamp: number;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
available: false;
|
||||||
|
type: 'vehicle';
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Entity {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
nation: string;
|
||||||
|
subnation: string;
|
||||||
|
use_subnation_flag: boolean;
|
||||||
|
type_slug: BlitzTankClass;
|
||||||
|
level: number;
|
||||||
|
roman_level: string;
|
||||||
|
user_string: string;
|
||||||
|
image_url: string;
|
||||||
|
preview_image_url: string;
|
||||||
|
is_premium: boolean;
|
||||||
|
is_collectible: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Price {
|
||||||
|
currency: Currency;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Currency {
|
||||||
|
name: 'gold';
|
||||||
|
count: number;
|
||||||
|
title: 'Gold';
|
||||||
|
image_url: string;
|
||||||
|
sizes: unknown;
|
||||||
|
type: 'currency';
|
||||||
|
}
|
||||||
|
|
||||||
|
const WARNING_COUNT = 500;
|
||||||
|
|
||||||
|
export const auctionCommand = new Promise<CommandRegistry>((resolve) => {
|
||||||
|
resolve({
|
||||||
|
command: createLocalizedCommand('auction'),
|
||||||
|
|
||||||
|
async handler(interaction) {
|
||||||
|
const { strings } = translator(interaction.locale);
|
||||||
|
const awaitedTankDefinitions = await tankDefinitions;
|
||||||
|
const region: Region = 'com';
|
||||||
|
const pageSize = 80;
|
||||||
|
const saleable = true;
|
||||||
|
const data = await fetch(
|
||||||
|
`https://${regionToRegionSubdomain(
|
||||||
|
region,
|
||||||
|
)}.wotblitz.com/en/api/events/items/auction/?page_size=${
|
||||||
|
pageSize
|
||||||
|
}&type[]=vehicle&saleable=${saleable}`,
|
||||||
|
).then((response) => response.json() as Promise<Auction>);
|
||||||
|
|
||||||
|
if (data === null) {
|
||||||
|
return strings.bot.commands.auction.errors.no_auction;
|
||||||
|
}
|
||||||
|
|
||||||
|
const saleableTanks = data.results.filter((result) => result.available);
|
||||||
|
const nextPricesTimestamp = saleableTanks[0].next_price_timestamp;
|
||||||
|
|
||||||
|
const title = `# ${strings.bot.commands.auction.body.title}`;
|
||||||
|
const subtitle = `${literals(strings.bot.commands.auction.body.subtitle, [
|
||||||
|
`${saleableTanks.length} / ${data.results.length}`,
|
||||||
|
`<t:${nextPricesTimestamp}:R>`,
|
||||||
|
'<https://wotblitz.com/auction/>',
|
||||||
|
])}`;
|
||||||
|
const body = saleableTanks.map((data, index) => {
|
||||||
|
const tank = awaitedTankDefinitions.tanks[data.entity!.id];
|
||||||
|
|
||||||
|
if (!tank) return '';
|
||||||
|
|
||||||
|
const name = `[${tank.name}](<https://blitzkit.app/tools/tankopedia/${
|
||||||
|
tank.id
|
||||||
|
}>)`;
|
||||||
|
const next =
|
||||||
|
data.next_price === null
|
||||||
|
? ''
|
||||||
|
: `\n-# ${literals(strings.bot.commands.auction.body.next, [
|
||||||
|
`<:gold:1317173197082333244> ${data.next_price.value.toLocaleString(
|
||||||
|
interaction.locale,
|
||||||
|
)}`,
|
||||||
|
])}`;
|
||||||
|
const available = literals(
|
||||||
|
strings.bot.commands.auction.body.available,
|
||||||
|
[
|
||||||
|
data.current_count.toLocaleString(interaction.locale),
|
||||||
|
data.initial_count.toLocaleString(interaction.locale),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
const isOut = data.current_count === 0;
|
||||||
|
const outString = isOut ? '~~' : '';
|
||||||
|
const isLow = data.current_count <= WARNING_COUNT;
|
||||||
|
|
||||||
|
return `${index + 1}. ${
|
||||||
|
outString
|
||||||
|
}${name} <:gold:1317173197082333244> ${data.price!.value.toLocaleString(
|
||||||
|
interaction.locale,
|
||||||
|
)}${outString}\n-#${isLow ? ' ‼️ ' : ' '}${available}${isOut ? '' : next}`;
|
||||||
|
});
|
||||||
|
const header = `${title}\n${subtitle}`;
|
||||||
|
const lines = [header, ...body];
|
||||||
|
|
||||||
|
return chunkLines(lines, '\n\n');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -13,7 +13,7 @@ import { resolvePeriodFromButton } from '../../core/discord/resolvePeriodFromBut
|
|||||||
import { resolvePeriodFromCommand } from '../../core/discord/resolvePeriodFromCommand';
|
import { resolvePeriodFromCommand } from '../../core/discord/resolvePeriodFromCommand';
|
||||||
import { resolvePlayerFromButton } from '../../core/discord/resolvePlayerFromButton';
|
import { resolvePlayerFromButton } from '../../core/discord/resolvePlayerFromButton';
|
||||||
import { resolvePlayerFromCommand } from '../../core/discord/resolvePlayerFromCommand';
|
import { resolvePlayerFromCommand } from '../../core/discord/resolvePlayerFromCommand';
|
||||||
import type { CommandRegistry } from '../../events/interactionCreate';
|
import { CommandRegistry } from '../../events/interactionCreate';
|
||||||
import { renderBreakdown } from './render';
|
import { renderBreakdown } from './render';
|
||||||
|
|
||||||
export const breakdownCommand = new Promise<CommandRegistry>(
|
export const breakdownCommand = new Promise<CommandRegistry>(
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
import {
|
import {
|
||||||
type BlitzStats,
|
BlitzStats,
|
||||||
calculateWN8,
|
calculateWN8,
|
||||||
getAccountInfo,
|
getAccountInfo,
|
||||||
getClanAccountInfo,
|
getClanAccountInfo,
|
||||||
getTankStats,
|
getTankStats,
|
||||||
getWN8Percentile,
|
getWN8Percentile,
|
||||||
TankDefinition,
|
TankDefinition,
|
||||||
} from "@blitzkit/core";
|
} from '@blitzkit/core';
|
||||||
import { literals } from "@blitzkit/i18n";
|
import { literals } from '@blitzkit/i18n';
|
||||||
import { Locale } from "discord.js";
|
import { Locale } from 'discord.js';
|
||||||
import { chunk } from "lodash-es";
|
import { chunk } from 'lodash-es';
|
||||||
import * as Breakdown from "../../components/Breakdown";
|
import * as Breakdown from '../../components/Breakdown';
|
||||||
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 { tankDefinitions } from "../../core/blitzkit/nonBlockingPromises";
|
import { tankDefinitions } from '../../core/blitzkit/nonBlockingPromises';
|
||||||
import { UserError } from "../../core/blitzkit/userError";
|
import { UserError } from '../../core/blitzkit/userError';
|
||||||
import { filterStats, type StatFilters } from "../../core/blitzstars/filterStats";
|
import { filterStats, StatFilters } from '../../core/blitzstars/filterStats';
|
||||||
import { getStatsInPeriod } from "../../core/blitzstars/getStatsInPeriod";
|
import { getStatsInPeriod } from '../../core/blitzstars/getStatsInPeriod';
|
||||||
import { blitzStarsTankAverages } from "../../core/blitzstars/tankAverages";
|
import { blitzStarsTankAverages } from '../../core/blitzstars/tankAverages';
|
||||||
import type { ResolvedPeriod } from "../../core/discord/resolvePeriodFromCommand";
|
import { ResolvedPeriod } from '../../core/discord/resolvePeriodFromCommand';
|
||||||
import type { ResolvedPlayer } from "../../core/discord/resolvePlayerFromCommand";
|
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;
|
||||||
const MAX_PAGES = 9;
|
const MAX_PAGES = 9;
|
||||||
@@ -31,7 +31,7 @@ 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;
|
||||||
@@ -39,7 +39,7 @@ export async function renderBreakdown(
|
|||||||
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);
|
||||||
const clanData = await getClanAccountInfo(region, id, ["clan"]);
|
const clanData = await getClanAccountInfo(region, id, ['clan']);
|
||||||
const tankStats = await getTankStats(region, id);
|
const tankStats = await getTankStats(region, id);
|
||||||
const filterDescriptions = await filtersToDescription(filters, locale);
|
const filterDescriptions = await filtersToDescription(filters, locale);
|
||||||
const orderedCurrentStats: BlitzStats[] = [];
|
const orderedCurrentStats: BlitzStats[] = [];
|
||||||
@@ -74,63 +74,63 @@ export async function renderBreakdown(
|
|||||||
orderedCareerWN8Full.push(
|
orderedCareerWN8Full.push(
|
||||||
tankAveragesEntry
|
tankAveragesEntry
|
||||||
? calculateWN8(tankAveragesEntry.all, 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: JSX.Element[] = [];
|
||||||
|
|
||||||
if (filteredOrder.length > 0) {
|
if (filteredOrder.length > 0) {
|
||||||
children.push(
|
children.push(
|
||||||
@@ -172,7 +172,7 @@ export async function renderBreakdown(
|
|||||||
delta: currentDamage - careerDamage,
|
delta: currentDamage - careerDamage,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
filteredOrder.forEach((id, index) => {
|
filteredOrder.forEach((id, index) => {
|
||||||
@@ -193,9 +193,9 @@ export async function renderBreakdown(
|
|||||||
title={
|
title={
|
||||||
tankDefinition
|
tankDefinition
|
||||||
? unwrap(tankDefinition.name)
|
? unwrap(tankDefinition.name)
|
||||||
: literals(strings.bot.commands.breakdown.body.unknown_tank, {
|
: literals(strings.bot.commands.breakdown.body.unknown_tank, [
|
||||||
id,
|
`${id}`,
|
||||||
})
|
])
|
||||||
}
|
}
|
||||||
stats={[
|
stats={[
|
||||||
{
|
{
|
||||||
@@ -206,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:
|
||||||
@@ -230,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) {
|
||||||
|
|||||||
@@ -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')}`;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1,48 +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 { resolveTankId } from "../core/blitz/resolveTankId";
|
import { resolveTankId } from '../core/blitz/resolveTankId';
|
||||||
import { getBlitzStarsLinkButton } from "../core/blitzstars/getBlitzStarsLinkButton";
|
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
|
||||||
import { getPlayerHistories } from "../core/blitzstars/getPlayerHistories";
|
import { getPlayerHistories } from '../core/blitzstars/getPlayerHistories';
|
||||||
import { getTankHistories } from "../core/blitzstars/getTankHistories";
|
import { getTankHistories } from '../core/blitzstars/getTankHistories';
|
||||||
import { addPeriodSubCommands } from "../core/discord/addPeriodSubCommands";
|
import { addPeriodSubCommands } from '../core/discord/addPeriodSubCommands';
|
||||||
import { addTankChoices } from "../core/discord/addTankChoices";
|
import { addTankChoices } from '../core/discord/addTankChoices';
|
||||||
import { addUsernameChoices } from "../core/discord/addUsernameChoices";
|
import { addUsernameChoices } from '../core/discord/addUsernameChoices';
|
||||||
import { autocompleteTanks } from "../core/discord/autocompleteTanks";
|
import { autocompleteTanks } from '../core/discord/autocompleteTanks';
|
||||||
import { autocompleteUsername } from "../core/discord/autocompleteUsername";
|
import { autocompleteUsername } from '../core/discord/autocompleteUsername';
|
||||||
import { buttonRefresh } from "../core/discord/buttonRefresh";
|
import { buttonRefresh } from '../core/discord/buttonRefresh';
|
||||||
import { commandToURL } from "../core/discord/commandToURL";
|
import { commandToURL } from '../core/discord/commandToURL';
|
||||||
import { createLocalizedCommand } from "../core/discord/createLocalizedCommand";
|
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
||||||
import { getCustomPeriodParams } from "../core/discord/getCustomPeriodParams";
|
import { getCustomPeriodParams } from '../core/discord/getCustomPeriodParams';
|
||||||
import { resolvePeriodFromButton } from "../core/discord/resolvePeriodFromButton";
|
import { resolvePeriodFromButton } from '../core/discord/resolvePeriodFromButton';
|
||||||
import {
|
import {
|
||||||
type ResolvedPeriod,
|
ResolvedPeriod,
|
||||||
resolvePeriodFromCommand,
|
resolvePeriodFromCommand,
|
||||||
} from "../core/discord/resolvePeriodFromCommand";
|
} from '../core/discord/resolvePeriodFromCommand';
|
||||||
import { resolvePlayerFromButton } from "../core/discord/resolvePlayerFromButton";
|
import { resolvePlayerFromButton } from '../core/discord/resolvePlayerFromButton';
|
||||||
import {
|
import {
|
||||||
type ResolvedPlayer,
|
ResolvedPlayer,
|
||||||
resolvePlayerFromCommand,
|
resolvePlayerFromCommand,
|
||||||
} from "../core/discord/resolvePlayerFromCommand";
|
} from '../core/discord/resolvePlayerFromCommand';
|
||||||
import type { CommandRegistry } from "../events/interactionCreate";
|
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,
|
||||||
@@ -61,7 +61,7 @@ async function render(
|
|||||||
[
|
[
|
||||||
history.all.battles,
|
history.all.battles,
|
||||||
(history.all.wins / history.all.battles) * 100,
|
(history.all.wins / history.all.battles) * 100,
|
||||||
] as Graph.PlotItem
|
] as Graph.PlotItem,
|
||||||
);
|
);
|
||||||
const times = histories.map((history) => history.last_battle_time);
|
const times = histories.map((history) => history.last_battle_time);
|
||||||
const minTime = Math.min(...times);
|
const minTime = Math.min(...times);
|
||||||
@@ -82,7 +82,7 @@ async function render(
|
|||||||
verticalMargin={{
|
verticalMargin={{
|
||||||
min: minY,
|
min: minY,
|
||||||
max: maxY,
|
max: maxY,
|
||||||
suffix: "%",
|
suffix: '%',
|
||||||
precision: 2,
|
precision: 2,
|
||||||
}}
|
}}
|
||||||
horizontalMargin={{
|
horizontalMargin={{
|
||||||
@@ -110,22 +110,22 @@ 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),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
group: "tank",
|
group: 'tank',
|
||||||
modify(option: SlashCommandSubcommandGroupBuilder) {
|
modify(option: SlashCommandSubcommandGroupBuilder) {
|
||||||
addPeriodSubCommands(option, (option) =>
|
addPeriodSubCommands(option, (option) =>
|
||||||
option
|
option
|
||||||
.addStringOption(addTankChoices)
|
.addStringOption(addTankChoices)
|
||||||
.addStringOption(addUsernameChoices)
|
.addStringOption(addUsernameChoices),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -136,9 +136,9 @@ export const evolutionCommand = new Promise<CommandRegistry>(
|
|||||||
interaction.options.getSubcommandGroup() as EvolutionStatType;
|
interaction.options.getSubcommandGroup() as EvolutionStatType;
|
||||||
const player = await resolvePlayerFromCommand(interaction);
|
const player = await resolvePlayerFromCommand(interaction);
|
||||||
const period = resolvePeriodFromCommand(player.region, interaction);
|
const period = resolvePeriodFromCommand(player.region, interaction);
|
||||||
const tankIdRaw = interaction.options.getString("tank")!;
|
const tankIdRaw = interaction.options.getString('tank')!;
|
||||||
const tankId =
|
const tankId =
|
||||||
commandGroup === "tank"
|
commandGroup === 'tank'
|
||||||
? await resolveTankId(tankIdRaw, interaction.locale)
|
? await resolveTankId(tankIdRaw, interaction.locale)
|
||||||
: null;
|
: null;
|
||||||
const path = commandToURL(interaction, {
|
const path = commandToURL(interaction, {
|
||||||
@@ -153,13 +153,13 @@ export const evolutionCommand = new Promise<CommandRegistry>(
|
|||||||
period,
|
period,
|
||||||
commandGroup,
|
commandGroup,
|
||||||
tankId,
|
tankId,
|
||||||
interaction.locale
|
interaction.locale,
|
||||||
),
|
),
|
||||||
buttonRefresh(interaction, path),
|
buttonRefresh(interaction, path),
|
||||||
await getBlitzStarsLinkButton(
|
await getBlitzStarsLinkButton(
|
||||||
player.region,
|
player.region,
|
||||||
player.id,
|
player.id,
|
||||||
tankId ?? undefined
|
tankId ?? undefined,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
@@ -171,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);
|
||||||
@@ -180,10 +180,10 @@ 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,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
@@ -11,7 +11,7 @@ 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 { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
|
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
|
||||||
import { getStatsInPeriod } from '../core/blitzstars/getStatsInPeriod';
|
import { getStatsInPeriod } from '../core/blitzstars/getStatsInPeriod';
|
||||||
import { addPeriodicFilterOptions } from '../core/discord/addPeriodicFilterOptions';
|
import { addPeriodicFilterOptions } from '../core/discord/addPeriodicFilterOptions';
|
||||||
@@ -26,15 +26,15 @@ import { getFiltersFromButton } from '../core/discord/getFiltersFromButton';
|
|||||||
import { getFiltersFromCommand } from '../core/discord/getFiltersFromCommand';
|
import { getFiltersFromCommand } from '../core/discord/getFiltersFromCommand';
|
||||||
import { resolvePeriodFromButton } from '../core/discord/resolvePeriodFromButton';
|
import { resolvePeriodFromButton } from '../core/discord/resolvePeriodFromButton';
|
||||||
import {
|
import {
|
||||||
type ResolvedPeriod,
|
ResolvedPeriod,
|
||||||
resolvePeriodFromCommand,
|
resolvePeriodFromCommand,
|
||||||
} from '../core/discord/resolvePeriodFromCommand';
|
} from '../core/discord/resolvePeriodFromCommand';
|
||||||
import { resolvePlayerFromButton } from '../core/discord/resolvePlayerFromButton';
|
import { resolvePlayerFromButton } from '../core/discord/resolvePlayerFromButton';
|
||||||
import {
|
import {
|
||||||
type ResolvedPlayer,
|
ResolvedPlayer,
|
||||||
resolvePlayerFromCommand,
|
resolvePlayerFromCommand,
|
||||||
} from '../core/discord/resolvePlayerFromCommand';
|
} from '../core/discord/resolvePlayerFromCommand';
|
||||||
import type { CommandRegistry } from '../events/interactionCreate';
|
import { CommandRegistry } from '../events/interactionCreate';
|
||||||
|
|
||||||
async function render(
|
async function render(
|
||||||
{ region, id }: ResolvedPlayer,
|
{ region, id }: ResolvedPlayer,
|
||||||
|
|||||||
@@ -1,44 +1,44 @@
|
|||||||
import { getAccountInfo, getClanInfo } from "@blitzkit/core";
|
import { getAccountInfo, getClanInfo } from '@blitzkit/core';
|
||||||
import { literals } from "@blitzkit/i18n";
|
import { literals } from '@blitzkit/i18n';
|
||||||
import { Locale } from "discord.js";
|
import { Locale } from 'discord.js';
|
||||||
import { CommandWrapper } from "../components/CommandWrapper";
|
import { CommandWrapper } from '../components/CommandWrapper';
|
||||||
import { GenericStats } from "../components/GenericStats";
|
import { GenericStats } from '../components/GenericStats';
|
||||||
import { NoData } from "../components/NoData";
|
import { NoData } from '../components/NoData';
|
||||||
import { TitleBar } from "../components/TitleBar";
|
import { TitleBar } from '../components/TitleBar';
|
||||||
import { addClanChoices } from "../core/discord/addClanChoices";
|
import { addClanChoices } from '../core/discord/addClanChoices';
|
||||||
import { autocompleteClan } from "../core/discord/autocompleteClan";
|
import { autocompleteClan } from '../core/discord/autocompleteClan';
|
||||||
import { createLocalizedCommand } from "../core/discord/createLocalizedCommand";
|
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;
|
||||||
|
|
||||||
export const inactiveCommand = new Promise<CommandRegistry>((resolve) => {
|
export const inactiveCommand = new Promise<CommandRegistry>((resolve) => {
|
||||||
resolve({
|
resolve({
|
||||||
command: createLocalizedCommand("inactive")
|
command: createLocalizedCommand('inactive')
|
||||||
.addStringOption(addClanChoices)
|
.addStringOption(addClanChoices)
|
||||||
.addNumberOption((option) => {
|
.addNumberOption((option) => {
|
||||||
const { strings } = translator(Locale.EnglishUS);
|
const { strings } = translator(Locale.EnglishUS);
|
||||||
|
|
||||||
return option
|
return option
|
||||||
.setName("threshold")
|
.setName('threshold')
|
||||||
.setNameLocalizations(
|
.setNameLocalizations(
|
||||||
localizationObject(
|
localizationObject(
|
||||||
(strings) => strings.bot.commands.inactive.options.threshold.name,
|
(strings) => strings.bot.commands.inactive.options.threshold.name,
|
||||||
undefined,
|
undefined,
|
||||||
true
|
true,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.setDescription(
|
.setDescription(
|
||||||
strings.bot.commands.inactive.options.threshold.description
|
strings.bot.commands.inactive.options.threshold.description,
|
||||||
)
|
)
|
||||||
.setDescriptionLocalizations(
|
.setDescriptionLocalizations(
|
||||||
localizationObject(
|
localizationObject(
|
||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.inactive.options.threshold.description
|
strings.bot.commands.inactive.options.threshold.description,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.setMinValue(0);
|
.setMinValue(0);
|
||||||
}),
|
}),
|
||||||
@@ -47,7 +47,7 @@ export const inactiveCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
const { strings } = translator(interaction.locale);
|
const { strings } = translator(interaction.locale);
|
||||||
const { region, id } = await resolveClanFromCommand(interaction);
|
const { region, id } = await resolveClanFromCommand(interaction);
|
||||||
const threshold =
|
const threshold =
|
||||||
interaction.options.getNumber("threshold")! ?? DEFAULT_THRESHOLD;
|
interaction.options.getNumber('threshold')! ?? DEFAULT_THRESHOLD;
|
||||||
const time = new Date().getTime() / 1000;
|
const time = new Date().getTime() / 1000;
|
||||||
const clanInfo = await getClanInfo(region, id);
|
const clanInfo = await getClanInfo(region, id);
|
||||||
const accountInfo = await getAccountInfo(region, clanInfo.members_ids);
|
const accountInfo = await getAccountInfo(region, clanInfo.members_ids);
|
||||||
@@ -57,7 +57,7 @@ export const inactiveCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
[
|
[
|
||||||
account.nickname,
|
account.nickname,
|
||||||
(time - account.last_battle_time) / 60 / 60 / 24,
|
(time - account.last_battle_time) / 60 / 60 / 24,
|
||||||
] as [string, number]
|
] as [string, number],
|
||||||
)
|
)
|
||||||
.filter(([, inactiveDays]) => inactiveDays >= threshold)
|
.filter(([, inactiveDays]) => inactiveDays >= threshold)
|
||||||
.sort((a, b) => b[1] - a[1])
|
.sort((a, b) => b[1] - a[1])
|
||||||
@@ -65,10 +65,10 @@ export const inactiveCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
([name, days]) =>
|
([name, days]) =>
|
||||||
[
|
[
|
||||||
name,
|
name,
|
||||||
literals(strings.bot.commands.inactive.body.listing, {
|
literals(strings.bot.commands.inactive.body.listing, [
|
||||||
days: days.toFixed(0),
|
days.toFixed(0),
|
||||||
}),
|
]),
|
||||||
] as [string, string]
|
] as [string, string],
|
||||||
);
|
);
|
||||||
const hasInactiveMembers = inactive.length > 0;
|
const hasInactiveMembers = inactive.length > 0;
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ export const inactiveCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
image={`https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clanInfo.emblem_set_id}.png`}
|
image={`https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clanInfo.emblem_set_id}.png`}
|
||||||
description={`${literals(
|
description={`${literals(
|
||||||
strings.bot.commands.inactive.body.subtitle,
|
strings.bot.commands.inactive.body.subtitle,
|
||||||
{ days: threshold }
|
[`${threshold}`],
|
||||||
)} • ${new Date().toLocaleDateString(interaction.locale)}`}
|
)} • ${new Date().toLocaleDateString(interaction.locale)}`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
import { getAccountInfo, getClanAccountInfo } from '@blitzkit/core';
|
import {
|
||||||
|
getAccountInfo,
|
||||||
|
getClanAccountInfo,
|
||||||
|
linkBlitzAndDiscord,
|
||||||
|
} from '@blitzkit/core';
|
||||||
import { teal } from '@radix-ui/colors';
|
import { teal } from '@radix-ui/colors';
|
||||||
import { Glow } from '../components/AllStatsOverview/components/HeroStat/components/Glow';
|
import { Glow } from '../components/AllStatsOverview/components/HeroStat/components/Glow';
|
||||||
import { CommandWrapper } from '../components/CommandWrapper';
|
import { CommandWrapper } from '../components/CommandWrapper';
|
||||||
import { linkBlitzAndDiscord } from '../core/db/linkBlitzAndDiscord';
|
|
||||||
import { addUsernameChoices } from '../core/discord/addUsernameChoices';
|
import { addUsernameChoices } from '../core/discord/addUsernameChoices';
|
||||||
import { autocompleteUsername } from '../core/discord/autocompleteUsername';
|
import { autocompleteUsername } from '../core/discord/autocompleteUsername';
|
||||||
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
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) => {
|
||||||
|
|||||||
@@ -5,31 +5,31 @@ import {
|
|||||||
flags,
|
flags,
|
||||||
getAccountInfo,
|
getAccountInfo,
|
||||||
getTankStats,
|
getTankStats,
|
||||||
} from "@blitzkit/core";
|
} from '@blitzkit/core';
|
||||||
import { literals } from "@blitzkit/i18n";
|
import { literals } from '@blitzkit/i18n';
|
||||||
import markdownEscape from "markdown-escape";
|
import markdownEscape from 'markdown-escape';
|
||||||
import {
|
import {
|
||||||
gameDefinitions,
|
gameDefinitions,
|
||||||
tankDefinitions,
|
tankDefinitions,
|
||||||
} from "../core/blitzkit/nonBlockingPromises";
|
} from '../core/blitzkit/nonBlockingPromises';
|
||||||
import { addTierChoices } from "../core/discord/addTierChoices";
|
import { addTierChoices } from '../core/discord/addTierChoices';
|
||||||
import { addUsernameChoices } from "../core/discord/addUsernameChoices";
|
import { addUsernameChoices } from '../core/discord/addUsernameChoices';
|
||||||
import { autocompleteUsername } from "../core/discord/autocompleteUsername";
|
import { autocompleteUsername } from '../core/discord/autocompleteUsername';
|
||||||
import { chunkLines } from "../core/discord/chunkLines";
|
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({
|
||||||
command: createLocalizedCommand("owned-tanks")
|
command: createLocalizedCommand('owned-tanks')
|
||||||
.addStringOption(addTierChoices)
|
.addStringOption(addTierChoices)
|
||||||
.addStringOption(addUsernameChoices),
|
.addStringOption(addUsernameChoices),
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const { strings, unwrap } = translator(interaction.locale);
|
const { strings, unwrap } = translator(interaction.locale);
|
||||||
const tier = Number(interaction.options.getString("tier"));
|
const tier = Number(interaction.options.getString('tier'));
|
||||||
const { id, region } = await resolvePlayerFromCommand(interaction);
|
const { id, region } = await resolvePlayerFromCommand(interaction);
|
||||||
const accountInfo = await getAccountInfo(region, id);
|
const accountInfo = await getAccountInfo(region, id);
|
||||||
const tankStats = await getTankStats(region, id);
|
const tankStats = await getTankStats(region, id);
|
||||||
@@ -43,7 +43,7 @@ export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
tankStats.map(async (tankData) => ({
|
tankStats.map(async (tankData) => ({
|
||||||
tankDefinitions: (await tankDefinitions).tanks[tankData.tank_id]!,
|
tankDefinitions: (await tankDefinitions).tanks[tankData.tank_id]!,
|
||||||
id: tankData.tank_id,
|
id: tankData.tank_id,
|
||||||
}))
|
})),
|
||||||
)
|
)
|
||||||
).filter((tank) => tank.tankDefinitions?.tier === tier);
|
).filter((tank) => tank.tankDefinitions?.tier === tier);
|
||||||
const groupedTanks: Record<string, TankDefinition[]> = {};
|
const groupedTanks: Record<string, TankDefinition[]> = {};
|
||||||
@@ -60,25 +60,25 @@ export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
|
|
||||||
const awaitedTankDefinitions = await tankDefinitions;
|
const awaitedTankDefinitions = await tankDefinitions;
|
||||||
const awaitedGameDefinitions = await gameDefinitions;
|
const awaitedGameDefinitions = await gameDefinitions;
|
||||||
const title = literals(strings.bot.commands.owned_tanks.body.title, {
|
const title = literals(strings.bot.commands.owned_tanks.body.title, [
|
||||||
name: markdownEscape(accountInfo.nickname),
|
markdownEscape(accountInfo.nickname),
|
||||||
tier: TIER_ROMAN_NUMERALS[tier],
|
TIER_ROMAN_NUMERALS[tier],
|
||||||
});
|
]);
|
||||||
const tankList = tankStats
|
const tankList = tankStats
|
||||||
.map(({ tank_id }) => awaitedTankDefinitions.tanks[tank_id])
|
.map(({ tank_id }) => awaitedTankDefinitions.tanks[tank_id])
|
||||||
.filter((tank) => tank.tier === tier)
|
.filter((tank) => tank.tier === tier)
|
||||||
.sort(
|
.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
TANK_CLASSES.indexOf(a.class) - TANK_CLASSES.indexOf(b.class)
|
TANK_CLASSES.indexOf(a.class) - TANK_CLASSES.indexOf(b.class),
|
||||||
)
|
)
|
||||||
.sort(
|
.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
awaitedGameDefinitions.nations.indexOf(a.nation) -
|
awaitedGameDefinitions.nations.indexOf(a.nation) -
|
||||||
awaitedGameDefinitions.nations.indexOf(b.nation)
|
awaitedGameDefinitions.nations.indexOf(b.nation),
|
||||||
)
|
)
|
||||||
.map(
|
.map(
|
||||||
(tank) =>
|
(tank) =>
|
||||||
`[${flags[tank.nation]} ${markdownEscape(unwrap(tank.name))}](<https://blitzkit.app/tanks/${tank.slug}>)`
|
`[${flags[tank.nation]} ${markdownEscape(unwrap(tank.name))}](<https://blitzkit.app/tools/tankopedia/${tank.id}>)`,
|
||||||
);
|
);
|
||||||
const lines = [title, ...tankList];
|
const lines = [title, ...tankList];
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
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 { CommandRegistry } from "../events/interactionCreate";
|
import { CommandRegistry } from '../events/interactionCreate';
|
||||||
|
|
||||||
export const permissionsCommand = new Promise<CommandRegistry>((resolve) => {
|
export const permissionsCommand = new Promise<CommandRegistry>((resolve) => {
|
||||||
resolve({
|
resolve({
|
||||||
command: createLocalizedCommand("permissions"),
|
command: createLocalizedCommand('permissions'),
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const { strings } = translator(interaction.locale);
|
const { strings } = translator(interaction.locale);
|
||||||
@@ -14,33 +14,33 @@ export const permissionsCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
new Date().getDay() === 5 && new Date().getDate() === 13;
|
new Date().getDay() === 5 && new Date().getDate() === 13;
|
||||||
const permissions = [
|
const permissions = [
|
||||||
[
|
[
|
||||||
interaction.appPermissions?.has("ViewChannel"),
|
interaction.appPermissions?.has('ViewChannel'),
|
||||||
strings.bot.commands.permissions.body.view_channels,
|
strings.bot.commands.permissions.body.view_channels,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
interaction.appPermissions?.has("ReadMessageHistory"),
|
interaction.appPermissions?.has('ReadMessageHistory'),
|
||||||
strings.bot.commands.permissions.body.read_message_history,
|
strings.bot.commands.permissions.body.read_message_history,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
interaction.appPermissions?.has("AttachFiles"),
|
interaction.appPermissions?.has('AttachFiles'),
|
||||||
strings.bot.commands.permissions.body.attach_files,
|
strings.bot.commands.permissions.body.attach_files,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
interaction.appPermissions?.has("UseExternalEmojis"),
|
interaction.appPermissions?.has('UseExternalEmojis'),
|
||||||
strings.bot.commands.permissions.body.external_emojis,
|
strings.bot.commands.permissions.body.external_emojis,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
const body = permissions
|
const body = permissions
|
||||||
.map(([hasPermission, description]) => {
|
.map(([hasPermission, description]) => {
|
||||||
if (hasPermission) {
|
if (hasPermission) {
|
||||||
return `${isFridayThe13th ? "😇" : "✅"} ${description}`;
|
return `${isFridayThe13th ? '😇' : '✅'} ${description}`;
|
||||||
} else {
|
} else {
|
||||||
return `${isFridayThe13th ? "💀" : "❌"} ${description}`;
|
return `${isFridayThe13th ? '💀' : '❌'} ${description}`;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.join("\n");
|
.join('\n');
|
||||||
|
|
||||||
return literals(strings.bot.commands.permissions.body.title, { body });
|
return literals(strings.bot.commands.permissions.body.title, [body]);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
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({
|
||||||
handlesInteraction: true,
|
handlesInteraction: true,
|
||||||
|
|
||||||
command: createLocalizedCommand("ping", [
|
command: createLocalizedCommand('ping', [
|
||||||
{ subcommand: "blitzkit" },
|
{ subcommand: 'blitzkit' },
|
||||||
{ subcommand: "wotb" },
|
{ subcommand: 'wotb' },
|
||||||
]),
|
]),
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
@@ -17,17 +17,17 @@ export const pingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
const subcommand = interaction.options.getSubcommand();
|
const subcommand = interaction.options.getSubcommand();
|
||||||
const executionStart = Date.now();
|
const executionStart = Date.now();
|
||||||
|
|
||||||
if (subcommand === "blitzkit") {
|
if (subcommand === 'blitzkit') {
|
||||||
await interaction.editReply(strings.bot.commands.ping.body.pong);
|
await interaction.editReply(strings.bot.commands.ping.body.pong);
|
||||||
} else {
|
} else {
|
||||||
await fetch("https://api.wotblitz.com/");
|
await fetch('https://api.wotblitz.com/');
|
||||||
}
|
}
|
||||||
|
|
||||||
const executionTime = Date.now() - executionStart;
|
const executionTime = Date.now() - executionStart;
|
||||||
interaction.editReply(
|
interaction.editReply(
|
||||||
literals(strings.bot.commands.ping.body.pong_with_time, {
|
literals(strings.bot.commands.ping.body.pong_with_time, [
|
||||||
time: executionTime.toFixed(0),
|
`${Math.round(executionTime)}`,
|
||||||
})
|
]),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import { getAccountInfo } from "@blitzkit/core";
|
import { getAccountInfo } from '@blitzkit/core';
|
||||||
import { literals } from "@blitzkit/i18n";
|
import { literals } from '@blitzkit/i18n';
|
||||||
import markdownEscape from "markdown-escape";
|
import markdownEscape from 'markdown-escape';
|
||||||
import { addUsernameChoices } from "../core/discord/addUsernameChoices";
|
import { addUsernameChoices } from '../core/discord/addUsernameChoices';
|
||||||
import { autocompleteUsername } from "../core/discord/autocompleteUsername";
|
import { autocompleteUsername } from '../core/discord/autocompleteUsername';
|
||||||
import { createLocalizedCommand } from "../core/discord/createLocalizedCommand";
|
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
||||||
import { embedInfo } from "../core/discord/embedInfo";
|
import { embedInfo } from '../core/discord/embedInfo';
|
||||||
import { markdownTable } from "../core/discord/markdownTable";
|
import { markdownTable } from '../core/discord/markdownTable';
|
||||||
import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand";
|
import { resolvePlayerFromCommand } from '../core/discord/resolvePlayerFromCommand';
|
||||||
import { translator } from "../core/localization/translator";
|
import { translator } from '../core/localization/translator';
|
||||||
import { CommandRegistry } from "../events/interactionCreate";
|
import { CommandRegistry } from '../events/interactionCreate';
|
||||||
|
|
||||||
export const playerInfoCommand = new Promise<CommandRegistry>((resolve) => {
|
export const playerInfoCommand = new Promise<CommandRegistry>((resolve) => {
|
||||||
resolve({
|
resolve({
|
||||||
command:
|
command:
|
||||||
createLocalizedCommand("player-info").addStringOption(addUsernameChoices),
|
createLocalizedCommand('player-info').addStringOption(addUsernameChoices),
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const { strings } = translator(interaction.locale);
|
const { strings } = translator(interaction.locale);
|
||||||
@@ -22,9 +22,9 @@ export const playerInfoCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
const accountInfo = await getAccountInfo(region, id);
|
const accountInfo = await getAccountInfo(region, id);
|
||||||
|
|
||||||
return embedInfo(
|
return embedInfo(
|
||||||
literals(strings.bot.commands.player_info.body.title, {
|
literals(strings.bot.commands.player_info.body.title, [
|
||||||
name: markdownEscape(accountInfo.nickname),
|
markdownEscape(accountInfo.nickname),
|
||||||
}),
|
]),
|
||||||
|
|
||||||
markdownTable([
|
markdownTable([
|
||||||
[
|
[
|
||||||
@@ -51,16 +51,16 @@ export const playerInfoCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
[
|
[
|
||||||
strings.bot.commands.player_info.body.created,
|
strings.bot.commands.player_info.body.created,
|
||||||
new Date(accountInfo.created_at * 1000).toLocaleDateString(
|
new Date(accountInfo.created_at * 1000).toLocaleDateString(
|
||||||
interaction.locale
|
interaction.locale,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
strings.bot.commands.player_info.body.last_battle,
|
strings.bot.commands.player_info.body.last_battle,
|
||||||
new Date(accountInfo.last_battle_time * 1000).toLocaleDateString(
|
new Date(accountInfo.last_battle_time * 1000).toLocaleDateString(
|
||||||
interaction.locale
|
interaction.locale,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
])
|
]),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -8,42 +8,42 @@ import {
|
|||||||
getRatingInfo,
|
getRatingInfo,
|
||||||
getRatingNeighbors,
|
getRatingNeighbors,
|
||||||
imgur,
|
imgur,
|
||||||
} from "@blitzkit/core";
|
} from '@blitzkit/core';
|
||||||
import { literals } from "@blitzkit/i18n";
|
import { literals } from '@blitzkit/i18n';
|
||||||
import { SlashCommandSubcommandBuilder } from "discord.js";
|
import { SlashCommandSubcommandBuilder } from 'discord.js';
|
||||||
import markdownEscape from "markdown-escape";
|
import markdownEscape from 'markdown-escape';
|
||||||
import { Glow } from "../components/AllStatsOverview/components/HeroStat/components/Glow";
|
import { Glow } from '../components/AllStatsOverview/components/HeroStat/components/Glow';
|
||||||
import { CommandWrapper } from "../components/CommandWrapper";
|
import { CommandWrapper } from '../components/CommandWrapper';
|
||||||
import { DeltaCaret } from "../components/DeltaCaret";
|
import { DeltaCaret } from '../components/DeltaCaret';
|
||||||
import { TitleBar } from "../components/TitleBar";
|
import { TitleBar } from '../components/TitleBar';
|
||||||
import { normalizeLeagueIcon } from "../core/blitz/normalizeLeagueIcon";
|
import { normalizeLeagueIcon } from '../core/blitz/normalizeLeagueIcon';
|
||||||
import { iconPng } from "../core/blitzkit/iconPng";
|
import { iconPng } from '../core/blitzkit/iconPng';
|
||||||
import { addUsernameChoices } from "../core/discord/addUsernameChoices";
|
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 = [
|
||||||
"_purple",
|
'_purple',
|
||||||
"_sand",
|
'_sand',
|
||||||
"_amber",
|
'_amber',
|
||||||
"_mauve",
|
'_mauve',
|
||||||
"_brown",
|
'_brown',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
||||||
resolve({
|
resolve({
|
||||||
command: createLocalizedCommand("rating", [
|
command: createLocalizedCommand('rating', [
|
||||||
{
|
{
|
||||||
subcommand: "today",
|
subcommand: 'today',
|
||||||
modify(option: SlashCommandSubcommandBuilder) {
|
modify(option: SlashCommandSubcommandBuilder) {
|
||||||
option.addStringOption(addUsernameChoices);
|
option.addStringOption(addUsernameChoices);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
subcommand: "season",
|
subcommand: 'season',
|
||||||
modify(option: SlashCommandSubcommandBuilder) {
|
modify(option: SlashCommandSubcommandBuilder) {
|
||||||
option.addStringOption(addUsernameChoices);
|
option.addStringOption(addUsernameChoices);
|
||||||
},
|
},
|
||||||
@@ -52,11 +52,11 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const subcommand = interaction.options.getSubcommand(true) as
|
const subcommand = interaction.options.getSubcommand(true) as
|
||||||
| "season"
|
| 'season'
|
||||||
| "today";
|
| 'today';
|
||||||
const { strings } = translator(interaction.locale);
|
const { strings } = translator(interaction.locale);
|
||||||
const { id, region } = await resolvePlayerFromCommand(interaction);
|
const { id, region } = await resolvePlayerFromCommand(interaction);
|
||||||
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 ratingInfo = await getRatingInfo(region);
|
const ratingInfo = await getRatingInfo(region);
|
||||||
@@ -67,20 +67,20 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
|
|
||||||
const leaderboard = await getArchivedRatingLeaderboard(
|
const leaderboard = await getArchivedRatingLeaderboard(
|
||||||
region,
|
region,
|
||||||
ratingInfo.current_season - (subcommand === "today" ? 0 : 1)
|
ratingInfo.current_season - (subcommand === 'today' ? 0 : 1),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (leaderboard.version!.$case === "v1") {
|
if (leaderboard.version!.$case === 'v1') {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Encountered bkrl minimal format in latest season. Wait till next cron?"
|
'Encountered bkrl minimal format in latest season. Wait till next cron?',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const accountInfo = await getAccountInfo(region, id, [
|
const accountInfo = await getAccountInfo(region, id, [
|
||||||
"statistics.rating",
|
'statistics.rating',
|
||||||
]);
|
]);
|
||||||
const statsIndex = leaderboard.version!.value.entries.findIndex(
|
const statsIndex = leaderboard.version!.value.entries.findIndex(
|
||||||
(entry) => entry.id === id
|
(entry) => entry.id === id,
|
||||||
);
|
);
|
||||||
const statsA = leaderboard
|
const statsA = leaderboard
|
||||||
? leaderboard.version!.value.entries[statsIndex!]
|
? leaderboard.version!.value.entries[statsIndex!]
|
||||||
@@ -90,9 +90,9 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
const statsB2Array = (await getRatingNeighbors(region, id, 0)).neighbors;
|
const statsB2Array = (await getRatingNeighbors(region, id, 0)).neighbors;
|
||||||
|
|
||||||
if (statsB2Array === undefined) {
|
if (statsB2Array === undefined) {
|
||||||
return literals(strings.bot.commands.rating.errors.no_participation, {
|
return literals(strings.bot.commands.rating.errors.no_participation, [
|
||||||
name: markdownEscape(accountInfo.nickname),
|
markdownEscape(accountInfo.nickname),
|
||||||
});
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [statsB2] = statsB2Array;
|
const [statsB2] = statsB2Array;
|
||||||
@@ -102,15 +102,15 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
return strings.bot.commands.rating.errors.unrecorded_stats;
|
return strings.bot.commands.rating.errors.unrecorded_stats;
|
||||||
}
|
}
|
||||||
if (!statsB1) {
|
if (!statsB1) {
|
||||||
return literals(strings.bot.commands.rating.errors.no_participation, {
|
return literals(strings.bot.commands.rating.errors.no_participation, [
|
||||||
name: markdownEscape(accountInfo.nickname),
|
markdownEscape(accountInfo.nickname),
|
||||||
});
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const reward = ratingInfo.rewards.find(
|
const reward = ratingInfo.rewards.find(
|
||||||
(reward) =>
|
(reward) =>
|
||||||
statsB2.number >= reward.from_position &&
|
statsB2.number >= reward.from_position &&
|
||||||
statsB2.number <= reward.to_position
|
statsB2.number <= reward.to_position,
|
||||||
);
|
);
|
||||||
const positionDelta = leaderboard
|
const positionDelta = leaderboard
|
||||||
? statsB2.number - positionA!
|
? statsB2.number - positionA!
|
||||||
@@ -118,11 +118,11 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
const delta = deltaBkrlBlitzStats(
|
const delta = deltaBkrlBlitzStats(
|
||||||
statsA,
|
statsA,
|
||||||
accountInfo.statistics.rating!,
|
accountInfo.statistics.rating!,
|
||||||
statsB2
|
statsB2,
|
||||||
);
|
);
|
||||||
const leagueTheme = LEAGUE_ACCENT[league.index];
|
const leagueTheme = LEAGUE_ACCENT[league.index];
|
||||||
let rewardImage = reward
|
let rewardImage = reward
|
||||||
? reward.type === "vehicle"
|
? reward.type === 'vehicle'
|
||||||
? reward.vehicle.preview_image_url
|
? reward.vehicle.preview_image_url
|
||||||
: reward.stuff.image_url
|
: reward.stuff.image_url
|
||||||
: undefined;
|
: undefined;
|
||||||
@@ -131,7 +131,7 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
const { ok } = await fetch(rewardImage);
|
const { ok } = await fetch(rewardImage);
|
||||||
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
rewardImage = imgur("kP88vqr");
|
rewardImage = imgur('kP88vqr');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,24 +147,24 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: "relative",
|
position: 'relative',
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
overflow: "hidden",
|
overflow: 'hidden',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: "relative",
|
position: 'relative',
|
||||||
padding: 16,
|
padding: 16,
|
||||||
backgroundColor: theme.colors[`appBackground2${leagueTheme}`],
|
backgroundColor: theme.colors[`appBackground2${leagueTheme}`],
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
border: `1px solid ${
|
border: `1px solid ${
|
||||||
theme.colors[`componentInteractive${leagueTheme}`]
|
theme.colors[`componentInteractive${leagueTheme}`]
|
||||||
}`,
|
}`,
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
justifyContent: "space-between",
|
justifyContent: 'space-between',
|
||||||
width: "100%",
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{reward === undefined && (
|
{reward === undefined && (
|
||||||
@@ -172,17 +172,17 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
endOpacity={3 / 4}
|
endOpacity={3 / 4}
|
||||||
color={theme.colors[`solidBackground${leagueTheme}`]}
|
color={theme.colors[`solidBackground${leagueTheme}`]}
|
||||||
style={{
|
style={{
|
||||||
transform: "translateX(-16px) rotate(90deg)",
|
transform: 'translateX(-16px) rotate(90deg)',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
gap: 8,
|
gap: 8,
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@@ -190,24 +190,24 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
style={{
|
style={{
|
||||||
width: 64,
|
width: 64,
|
||||||
height: 64,
|
height: 64,
|
||||||
objectFit: "contain",
|
objectFit: 'contain',
|
||||||
}}
|
}}
|
||||||
src={normalizeLeagueIcon(
|
src={normalizeLeagueIcon(
|
||||||
ratingInfo.leagues[league.index].big_icon
|
ratingInfo.leagues[league.index].big_icon,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
justifyContent: "flex-start",
|
justifyContent: 'flex-start',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -232,7 +232,7 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Math.abs(delta.score).toLocaleString(
|
{Math.abs(delta.score).toLocaleString(
|
||||||
interaction.locale
|
interaction.locale,
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
@@ -241,9 +241,9 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
justifyContent: "flex-start",
|
justifyContent: 'flex-start',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -267,7 +267,7 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Math.abs(positionDelta).toLocaleString(
|
{Math.abs(positionDelta).toLocaleString(
|
||||||
interaction.locale
|
interaction.locale,
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
@@ -279,23 +279,23 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
{reward && (
|
{reward && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
gap: 8,
|
gap: 8,
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
justifyContent: "flex-end",
|
justifyContent: 'flex-end',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -303,13 +303,13 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
color: theme.colors[`textHighContrast${leagueTheme}`],
|
color: theme.colors[`textHighContrast${leagueTheme}`],
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
maxWidth: 136,
|
maxWidth: 136,
|
||||||
textAlign: "right",
|
textAlign: 'right',
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: 'nowrap',
|
||||||
textOverflow: "ellipsis",
|
textOverflow: 'ellipsis',
|
||||||
overflow: "hidden",
|
overflow: 'hidden',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{reward.type === "vehicle"
|
{reward.type === 'vehicle'
|
||||||
? reward.vehicle.user_string
|
? reward.vehicle.user_string
|
||||||
: reward.stuff.title}
|
: reward.stuff.title}
|
||||||
</span>
|
</span>
|
||||||
@@ -317,9 +317,9 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
justifyContent: "flex-end",
|
justifyContent: 'flex-end',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -329,7 +329,7 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Reward
|
Reward
|
||||||
{reward.type === "stuff" ? ` x${reward.count}` : ""}
|
{reward.type === 'stuff' ? ` x${reward.count}` : ''}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -338,7 +338,7 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
style={{
|
style={{
|
||||||
width: 64,
|
width: 64,
|
||||||
height: 64,
|
height: 64,
|
||||||
objectFit: "cover",
|
objectFit: 'cover',
|
||||||
}}
|
}}
|
||||||
src={await iconPng(rewardImage!)}
|
src={await iconPng(rewardImage!)}
|
||||||
/>
|
/>
|
||||||
@@ -350,7 +350,7 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
endOpacity={3 / 4}
|
endOpacity={3 / 4}
|
||||||
color={theme.colors[`solidBackground${leagueTheme}`]}
|
color={theme.colors[`solidBackground${leagueTheme}`]}
|
||||||
style={{
|
style={{
|
||||||
transform: "translateX(16px) rotate(-90deg)",
|
transform: 'translateX(16px) rotate(-90deg)',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -362,10 +362,10 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
direction="reverse"
|
direction="reverse"
|
||||||
color={theme.colors[`solidBackground${leagueTheme}`]}
|
color={theme.colors[`solidBackground${leagueTheme}`]}
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: 'absolute',
|
||||||
top: -40,
|
top: -40,
|
||||||
left: "50%",
|
left: '50%',
|
||||||
transform: "translateX(-50%)",
|
transform: 'translateX(-50%)',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -373,32 +373,32 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
<Glow
|
<Glow
|
||||||
color={theme.colors[`solidBackground${leagueTheme}`]}
|
color={theme.colors[`solidBackground${leagueTheme}`]}
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: 'absolute',
|
||||||
bottom: -40,
|
bottom: -40,
|
||||||
left: "50%",
|
left: '50%',
|
||||||
transform: "translateX(-50%)",
|
transform: 'translateX(-50%)',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{delta.battles > 0 && (
|
{delta.battles > 0 && (
|
||||||
<div style={{ padding: "8px 0", display: "flex" }}>
|
<div style={{ padding: '8px 0', display: 'flex' }}>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
color: theme.colors.textHighContrast,
|
color: theme.colors.textHighContrast,
|
||||||
fontWeight: "bold",
|
fontWeight: 'bold',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{delta.battles.toLocaleString(interaction.locale)}
|
{delta.battles.toLocaleString(interaction.locale)}
|
||||||
@@ -414,19 +414,19 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
color: theme.colors.textHighContrast,
|
color: theme.colors.textHighContrast,
|
||||||
fontWeight: "bold",
|
fontWeight: 'bold',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(100 * (delta.wins / delta.battles)).toFixed(0)}%
|
{(100 * (delta.wins / delta.battles)).toFixed(0)}%
|
||||||
@@ -442,23 +442,23 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
color: theme.colors.textHighContrast,
|
color: theme.colors.textHighContrast,
|
||||||
fontWeight: "bold",
|
fontWeight: 'bold',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Math.round(delta.damage / delta.battles).toLocaleString(
|
{Math.round(delta.damage / delta.battles).toLocaleString(
|
||||||
interaction.locale
|
interaction.locale,
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
@@ -472,19 +472,19 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
color: theme.colors.textHighContrast,
|
color: theme.colors.textHighContrast,
|
||||||
fontWeight: "bold",
|
fontWeight: 'bold',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(100 * (delta.survived / delta.battles)).toFixed(0)}%
|
{(100 * (delta.survived / delta.battles)).toFixed(0)}%
|
||||||
@@ -500,19 +500,19 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
color: theme.colors.textHighContrast,
|
color: theme.colors.textHighContrast,
|
||||||
fontWeight: "bold",
|
fontWeight: 'bold',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(delta.kills / delta.battles).toFixed(2)}
|
{(delta.kills / delta.battles).toFixed(2)}
|
||||||
|
|||||||
@@ -10,30 +10,30 @@ 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';
|
||||||
import { range } from "lodash-es";
|
import { range } from 'lodash-es';
|
||||||
import markdownEscape from "markdown-escape";
|
import markdownEscape from 'markdown-escape';
|
||||||
import { CommandWrapper } from "../components/CommandWrapper";
|
import { CommandWrapper } from '../components/CommandWrapper';
|
||||||
import * as Leaderboard from "../components/Leaderboard";
|
import * as Leaderboard from '../components/Leaderboard';
|
||||||
import { TitleBar } from "../components/TitleBar";
|
import { TitleBar } from '../components/TitleBar';
|
||||||
import { UserError } from "../core/blitzkit/userError";
|
import { UserError } from '../core/blitzkit/userError';
|
||||||
import { addRegionChoices } from "../core/discord/addRegionChoices";
|
import { addRegionChoices } from '../core/discord/addRegionChoices';
|
||||||
import { addUsernameChoices } from "../core/discord/addUsernameChoices";
|
import { addUsernameChoices } from '../core/discord/addUsernameChoices';
|
||||||
import { autocompleteUsername } from "../core/discord/autocompleteUsername";
|
import { autocompleteUsername } from '../core/discord/autocompleteUsername';
|
||||||
import { createLocalizedCommand } from "../core/discord/createLocalizedCommand";
|
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;
|
||||||
@@ -51,7 +51,7 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
|
|||||||
const onGoingSeason = await isOnGoingRatingSeason();
|
const onGoingSeason = await isOnGoingRatingSeason();
|
||||||
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,
|
||||||
);
|
);
|
||||||
|
|
||||||
function addOptions(option: SlashCommandSubcommandBuilder) {
|
function addOptions(option: SlashCommandSubcommandBuilder) {
|
||||||
@@ -66,50 +66,51 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
|
|||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.rating_leaderboard.options.limit.name,
|
strings.bot.commands.rating_leaderboard.options.limit.name,
|
||||||
undefined,
|
undefined,
|
||||||
true
|
true,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.setDescription(
|
.setDescription(
|
||||||
literals(
|
literals(
|
||||||
strings.bot.commands.rating_leaderboard.options.limit
|
strings.bot.commands.rating_leaderboard.options.limit
|
||||||
.description,
|
.description,
|
||||||
{ count: DEFAULT_LIMIT }
|
[`${DEFAULT_LIMIT}`],
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.setDescriptionLocalizations(
|
.setDescriptionLocalizations(
|
||||||
localizationObject(
|
localizationObject(
|
||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.rating_leaderboard.options.limit
|
strings.bot.commands.rating_leaderboard.options.limit
|
||||||
.description,
|
.description,
|
||||||
{ count: DEFAULT_LIMIT }
|
[`${DEFAULT_LIMIT}`],
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.setRequired(false)
|
.setRequired(false)
|
||||||
.setMinValue(5)
|
.setMinValue(5)
|
||||||
.setMaxValue(30)
|
.setMaxValue(30),
|
||||||
)
|
)
|
||||||
.addStringOption((option) =>
|
.addStringOption((option) =>
|
||||||
option
|
option
|
||||||
.setName(
|
.setName(
|
||||||
strings.bot.commands.rating_leaderboard.options.season.name
|
strings.bot.commands.rating_leaderboard.options.season.name,
|
||||||
)
|
)
|
||||||
.setNameLocalizations(
|
.setNameLocalizations(
|
||||||
localizationObject(
|
localizationObject(
|
||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.rating_leaderboard.options.season.name,
|
strings.bot.commands.rating_leaderboard.options.season.name,
|
||||||
undefined,
|
undefined,
|
||||||
true
|
true,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.setDescription(
|
.setDescription(
|
||||||
strings.bot.commands.rating_leaderboard.options.season.description
|
strings.bot.commands.rating_leaderboard.options.season
|
||||||
|
.description,
|
||||||
)
|
)
|
||||||
.setDescriptionLocalizations(
|
.setDescriptionLocalizations(
|
||||||
localizationObject(
|
localizationObject(
|
||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.rating_leaderboard.options.season
|
strings.bot.commands.rating_leaderboard.options.season
|
||||||
.description
|
.description,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.setRequired(false)
|
.setRequired(false)
|
||||||
.addChoices(
|
.addChoices(
|
||||||
@@ -123,7 +124,7 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
|
|||||||
? literals(
|
? literals(
|
||||||
strings.bot.commands.rating_leaderboard.options.season
|
strings.bot.commands.rating_leaderboard.options.season
|
||||||
.choices.current,
|
.choices.current,
|
||||||
{ season: number }
|
[`${number}`],
|
||||||
)
|
)
|
||||||
: `${number}`,
|
: `${number}`,
|
||||||
value: `${number}`,
|
value: `${number}`,
|
||||||
@@ -132,27 +133,27 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
|
|||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.rating_leaderboard.options
|
strings.bot.commands.rating_leaderboard.options
|
||||||
.season.choices.current,
|
.season.choices.current,
|
||||||
{ season: number }
|
[`${number}`],
|
||||||
)
|
)
|
||||||
: undefined,
|
: undefined,
|
||||||
} satisfies APIApplicationCommandOptionChoice<string>;
|
} satisfies APIApplicationCommandOptionChoice<string>;
|
||||||
})
|
})
|
||||||
.reverse()
|
.reverse(),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
command: createLocalizedCommand("rating-leaderboard", [
|
command: createLocalizedCommand('rating-leaderboard', [
|
||||||
{
|
{
|
||||||
subcommand: "neighbors",
|
subcommand: 'neighbors',
|
||||||
modify(option: SlashCommandSubcommandBuilder) {
|
modify(option: SlashCommandSubcommandBuilder) {
|
||||||
option.addStringOption(addUsernameChoices);
|
option.addStringOption(addUsernameChoices);
|
||||||
addOptions(option);
|
addOptions(option);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
subcommand: "league",
|
subcommand: 'league',
|
||||||
modify(option: SlashCommandSubcommandBuilder) {
|
modify(option: SlashCommandSubcommandBuilder) {
|
||||||
option
|
option
|
||||||
.addStringOption((option) => {
|
.addStringOption((option) => {
|
||||||
@@ -160,7 +161,7 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
|
|||||||
|
|
||||||
return option
|
return option
|
||||||
.setName(
|
.setName(
|
||||||
strings.bot.commands.rating_leaderboard.options.league.name
|
strings.bot.commands.rating_leaderboard.options.league.name,
|
||||||
)
|
)
|
||||||
.setNameLocalizations(
|
.setNameLocalizations(
|
||||||
localizationObject(
|
localizationObject(
|
||||||
@@ -168,19 +169,19 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
|
|||||||
strings.bot.commands.rating_leaderboard.options.league
|
strings.bot.commands.rating_leaderboard.options.league
|
||||||
.name,
|
.name,
|
||||||
undefined,
|
undefined,
|
||||||
true
|
true,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.setDescription(
|
.setDescription(
|
||||||
strings.bot.commands.rating_leaderboard.options.league
|
strings.bot.commands.rating_leaderboard.options.league
|
||||||
.description
|
.description,
|
||||||
)
|
)
|
||||||
.setDescriptionLocalizations(
|
.setDescriptionLocalizations(
|
||||||
localizationObject(
|
localizationObject(
|
||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.rating_leaderboard.options.league
|
strings.bot.commands.rating_leaderboard.options.league
|
||||||
.description
|
.description,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.addChoices(
|
.addChoices(
|
||||||
...LEAGUES.map(
|
...LEAGUES.map(
|
||||||
@@ -194,10 +195,10 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
|
|||||||
(strings) =>
|
(strings) =>
|
||||||
(
|
(
|
||||||
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);
|
||||||
})
|
})
|
||||||
@@ -212,10 +213,10 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
|
|||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const { strings } = translator(interaction.locale);
|
const { strings } = translator(interaction.locale);
|
||||||
const subcommand = interaction.options.getSubcommand(true) as
|
const subcommand = interaction.options.getSubcommand(true) as
|
||||||
| "league"
|
| 'league'
|
||||||
| "neighbors";
|
| 'neighbors';
|
||||||
const limit = interaction.options.getInteger("limit") ?? DEFAULT_LIMIT;
|
const limit = interaction.options.getInteger('limit') ?? DEFAULT_LIMIT;
|
||||||
const seasonOption = interaction.options.getString("season");
|
const seasonOption = interaction.options.getString('season');
|
||||||
const season = seasonOption
|
const season = seasonOption
|
||||||
? parseInt(seasonOption)
|
? parseInt(seasonOption)
|
||||||
: onGoingSeason
|
: onGoingSeason
|
||||||
@@ -231,11 +232,11 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
|
|||||||
let playerId: number | undefined;
|
let playerId: number | undefined;
|
||||||
let regionRatingInfo: RatingInfo | null;
|
let regionRatingInfo: RatingInfo | null;
|
||||||
|
|
||||||
if (subcommand === "league") {
|
if (subcommand === 'league') {
|
||||||
const leagueIndex = parseInt(
|
const leagueIndex = parseInt(
|
||||||
interaction.options.getString("league")!
|
interaction.options.getString('league')!,
|
||||||
);
|
);
|
||||||
const region = interaction.options.getString("region") as Region;
|
const region = interaction.options.getString('region') as Region;
|
||||||
regionRatingInfo = await (isCurrentSeason
|
regionRatingInfo = await (isCurrentSeason
|
||||||
? getRatingInfo(region)
|
? getRatingInfo(region)
|
||||||
: null);
|
: null);
|
||||||
@@ -258,8 +259,8 @@ 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(
|
||||||
async (players) => {
|
async (players) => {
|
||||||
@@ -268,17 +269,17 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
|
|||||||
? 0
|
? 0
|
||||||
: players.version!.value.entries.findIndex(
|
: players.version!.value.entries.findIndex(
|
||||||
(player) =>
|
(player) =>
|
||||||
player.score < LEAGUES[leagueIndex - 1].minScore
|
player.score < LEAGUES[leagueIndex - 1].minScore,
|
||||||
);
|
);
|
||||||
const lastPlayerIndex = firstPlayerIndex + limit - 1;
|
const lastPlayerIndex = firstPlayerIndex + limit - 1;
|
||||||
const trimmed = players.version!.value.entries.slice(
|
const trimmed = players.version!.value.entries.slice(
|
||||||
firstPlayerIndex,
|
firstPlayerIndex,
|
||||||
lastPlayerIndex + 1
|
lastPlayerIndex + 1,
|
||||||
);
|
);
|
||||||
const clanData = await getClanAccountInfo(
|
const clanData = await getClanAccountInfo(
|
||||||
region,
|
region,
|
||||||
trimmed.map(({ id }) => id),
|
trimmed.map(({ id }) => id),
|
||||||
["clan"]
|
['clan'],
|
||||||
);
|
);
|
||||||
|
|
||||||
return trimmed.map(
|
return trimmed.map(
|
||||||
@@ -293,16 +294,16 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
|
|||||||
literals(
|
literals(
|
||||||
strings.bot.commands.rating_leaderboard.body
|
strings.bot.commands.rating_leaderboard.body
|
||||||
.deleted_player,
|
.deleted_player,
|
||||||
{ id: player.id }
|
[`${player.id}`],
|
||||||
),
|
),
|
||||||
}) satisfies SimplifiedPlayer
|
}) satisfies SimplifiedPlayer,
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
const leagueInfo = LEAGUES[leagueIndex];
|
const leagueInfo = LEAGUES[leagueIndex];
|
||||||
archivedLeaderboard = await getArchivedRatingLeaderboard(
|
archivedLeaderboard = await getArchivedRatingLeaderboard(
|
||||||
region,
|
region,
|
||||||
season
|
season,
|
||||||
);
|
);
|
||||||
|
|
||||||
players = result;
|
players = result;
|
||||||
@@ -315,12 +316,12 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
|
|||||||
? (strings.common.leagues as Record<string, string>)[
|
? (strings.common.leagues as Record<string, string>)[
|
||||||
LEAGUES[leagueInfo.index].name
|
LEAGUES[leagueInfo.index].name
|
||||||
]
|
]
|
||||||
: ""
|
: ''
|
||||||
} - ${strings.common.regions.short[region]}`;
|
} - ${strings.common.regions.short[region]}`;
|
||||||
titleImage = imgur(leagueInfo.icon);
|
titleImage = imgur(leagueInfo.icon);
|
||||||
titleDescription = literals(
|
titleDescription = literals(
|
||||||
strings.bot.commands.rating_leaderboard.body.top_players,
|
strings.bot.commands.rating_leaderboard.body.top_players,
|
||||||
{ count: limit }
|
[`${limit}`],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const { region, id } = await resolvePlayerFromCommand(interaction);
|
const { region, id } = await resolvePlayerFromCommand(interaction);
|
||||||
@@ -337,7 +338,7 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 neighbors = isCurrentSeason
|
const neighbors = isCurrentSeason
|
||||||
? await getRatingNeighbors(region, id, Math.round(limit / 2)).then(
|
? await getRatingNeighbors(region, id, Math.round(limit / 2)).then(
|
||||||
(players) =>
|
(players) =>
|
||||||
@@ -349,13 +350,13 @@ 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(
|
||||||
async (players) => {
|
async (players) => {
|
||||||
const playerIndex = players.version!.value.entries.findIndex(
|
const playerIndex = players.version!.value.entries.findIndex(
|
||||||
(player) => player.id === id
|
(player) => player.id === id,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (playerIndex === -1) {
|
if (playerIndex === -1) {
|
||||||
@@ -363,20 +364,20 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
|
|||||||
literals(
|
literals(
|
||||||
strings.bot.commands.rating_leaderboard.body
|
strings.bot.commands.rating_leaderboard.body
|
||||||
.no_participation,
|
.no_participation,
|
||||||
{ player: markdownEscape(accountInfo.nickname), season }
|
[markdownEscape(accountInfo.nickname), `${season}`],
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const halfRange = Math.round(limit / 2);
|
const halfRange = Math.round(limit / 2);
|
||||||
const trimmed = players.version!.value.entries.slice(
|
const trimmed = players.version!.value.entries.slice(
|
||||||
playerIndex - halfRange,
|
playerIndex - halfRange,
|
||||||
playerIndex + halfRange + 1
|
playerIndex + halfRange + 1,
|
||||||
);
|
);
|
||||||
const clanData = await getClanAccountInfo(
|
const clanData = await getClanAccountInfo(
|
||||||
region,
|
region,
|
||||||
trimmed.map((player) => player.id),
|
trimmed.map((player) => player.id),
|
||||||
["clan"]
|
['clan'],
|
||||||
);
|
);
|
||||||
|
|
||||||
return trimmed.map((player, index) => ({
|
return trimmed.map((player, index) => ({
|
||||||
@@ -388,18 +389,18 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
|
|||||||
? clanData[index]!.account_name
|
? clanData[index]!.account_name
|
||||||
: `Deleted ${player.id}`,
|
: `Deleted ${player.id}`,
|
||||||
}));
|
}));
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
archivedLeaderboard = await getArchivedRatingLeaderboard(
|
archivedLeaderboard = await getArchivedRatingLeaderboard(
|
||||||
region,
|
region,
|
||||||
season
|
season,
|
||||||
);
|
);
|
||||||
|
|
||||||
players = neighbors;
|
players = neighbors;
|
||||||
titleImage = clan
|
titleImage = clan
|
||||||
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png`
|
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png`
|
||||||
: undefined;
|
: undefined;
|
||||||
titleName = neighbors ? accountInfo.nickname : "";
|
titleName = neighbors ? accountInfo.nickname : '';
|
||||||
titleDescription =
|
titleDescription =
|
||||||
strings.bot.commands.rating_leaderboard.body.neighbors;
|
strings.bot.commands.rating_leaderboard.body.neighbors;
|
||||||
playersAfter =
|
playersAfter =
|
||||||
@@ -413,7 +414,7 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
|
|||||||
const items = players.map((player) => {
|
const items = players.map((player) => {
|
||||||
const midnightIndex =
|
const midnightIndex =
|
||||||
archivedLeaderboard?.version!.value.entries.findIndex(
|
archivedLeaderboard?.version!.value.entries.findIndex(
|
||||||
(item) => item.id === player.id
|
(item) => item.id === player.id,
|
||||||
);
|
);
|
||||||
const midnightScore =
|
const midnightScore =
|
||||||
midnightIndex === undefined
|
midnightIndex === undefined
|
||||||
@@ -452,7 +453,7 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
|
|||||||
<Leaderboard.Gap
|
<Leaderboard.Gap
|
||||||
message={literals(
|
message={literals(
|
||||||
strings.bot.commands.rating_leaderboard.body.more,
|
strings.bot.commands.rating_leaderboard.body.more,
|
||||||
{ count: playersAfter.toLocaleString(interaction.locale) }
|
[`${playersAfter.toLocaleString(interaction.locale)}`],
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -464,5 +465,5 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
|
|||||||
|
|
||||||
autocomplete: autocompleteUsername,
|
autocomplete: autocompleteUsername,
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
@@ -20,7 +20,7 @@ import { blitzStarsTankAverages } from '../core/blitzstars/tankAverages';
|
|||||||
import { buttonLink } from '../core/discord/buttonLink';
|
import { buttonLink } from '../core/discord/buttonLink';
|
||||||
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';
|
||||||
import { theme } from '../stitches.config';
|
import { theme } from '../stitches.config';
|
||||||
|
|
||||||
type WotInspectorReplaySubmission =
|
type WotInspectorReplaySubmission =
|
||||||
|
|||||||
@@ -8,31 +8,31 @@ import {
|
|||||||
TANK_ICONS,
|
TANK_ICONS,
|
||||||
TankType,
|
TankType,
|
||||||
TIER_ROMAN_NUMERALS,
|
TIER_ROMAN_NUMERALS,
|
||||||
} from "@blitzkit/core";
|
} from '@blitzkit/core';
|
||||||
import { literals } from "@blitzkit/i18n";
|
import { literals } from '@blitzkit/i18n';
|
||||||
import { escapeMarkdown, Locale } from "discord.js";
|
import { escapeMarkdown, Locale } from 'discord.js';
|
||||||
import { CommandWrapper } from "../components/CommandWrapper";
|
import { CommandWrapper } from '../components/CommandWrapper';
|
||||||
import { TitleBar } from "../components/TitleBar";
|
import { TitleBar } from '../components/TitleBar';
|
||||||
import { resolveTankId } from "../core/blitz/resolveTankId";
|
import { resolveTankId } from '../core/blitz/resolveTankId';
|
||||||
import { buildTechTreeLine } from "../core/blitzkit/buildTechTreeLine";
|
import { buildTechTreeLine } from '../core/blitzkit/buildTechTreeLine';
|
||||||
import { iconPng } from "../core/blitzkit/iconPng";
|
import { iconPng } from '../core/blitzkit/iconPng';
|
||||||
import { tankDefinitions } from "../core/blitzkit/nonBlockingPromises";
|
import { tankDefinitions } from '../core/blitzkit/nonBlockingPromises';
|
||||||
import { resolveAncestry } from "../core/blitzkit/resolveAncestry";
|
import { resolveAncestry } from '../core/blitzkit/resolveAncestry';
|
||||||
import { tankIconPng } from "../core/blitzkit/tankIconPng";
|
import { tankIconPng } from '../core/blitzkit/tankIconPng';
|
||||||
import { addUsernameChoices } from "../core/discord/addUsernameChoices";
|
import { addUsernameChoices } from '../core/discord/addUsernameChoices';
|
||||||
import { autocompleteTanks } from "../core/discord/autocompleteTanks";
|
import { autocompleteTanks } from '../core/discord/autocompleteTanks';
|
||||||
import { createLocalizedCommand } from "../core/discord/createLocalizedCommand";
|
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) => {
|
||||||
const { strings } = translator(Locale.EnglishUS);
|
const { strings } = translator(Locale.EnglishUS);
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
command: createLocalizedCommand("research")
|
command: createLocalizedCommand('research')
|
||||||
.addStringOption((option) =>
|
.addStringOption((option) =>
|
||||||
option
|
option
|
||||||
.setName(strings.bot.commands.research.options.target_tank.name)
|
.setName(strings.bot.commands.research.options.target_tank.name)
|
||||||
@@ -41,20 +41,20 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.research.options.target_tank.name,
|
strings.bot.commands.research.options.target_tank.name,
|
||||||
undefined,
|
undefined,
|
||||||
true
|
true,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.setDescription(
|
.setDescription(
|
||||||
strings.bot.commands.research.options.target_tank.description
|
strings.bot.commands.research.options.target_tank.description,
|
||||||
)
|
)
|
||||||
.setDescriptionLocalizations(
|
.setDescriptionLocalizations(
|
||||||
localizationObject(
|
localizationObject(
|
||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.research.options.target_tank.description
|
strings.bot.commands.research.options.target_tank.description,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.setAutocomplete(true)
|
.setAutocomplete(true)
|
||||||
.setRequired(true)
|
.setRequired(true),
|
||||||
)
|
)
|
||||||
.addStringOption((option) =>
|
.addStringOption((option) =>
|
||||||
option
|
option
|
||||||
@@ -64,20 +64,20 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.research.options.starting_tank.name,
|
strings.bot.commands.research.options.starting_tank.name,
|
||||||
undefined,
|
undefined,
|
||||||
true
|
true,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.setDescription(
|
.setDescription(
|
||||||
strings.bot.commands.research.options.starting_tank.description
|
strings.bot.commands.research.options.starting_tank.description,
|
||||||
)
|
)
|
||||||
.setDescriptionLocalizations(
|
.setDescriptionLocalizations(
|
||||||
localizationObject(
|
localizationObject(
|
||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.research.options.starting_tank.description
|
strings.bot.commands.research.options.starting_tank.description,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.setAutocomplete(true)
|
.setAutocomplete(true)
|
||||||
.setRequired(false)
|
.setRequired(false),
|
||||||
)
|
)
|
||||||
.addStringOption(addUsernameChoices),
|
.addStringOption(addUsernameChoices),
|
||||||
|
|
||||||
@@ -85,11 +85,11 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
const { strings, unwrap } = translator(interaction.locale);
|
const { strings, unwrap } = translator(interaction.locale);
|
||||||
const awaitedTankDefinitions = await tankDefinitions;
|
const awaitedTankDefinitions = await tankDefinitions;
|
||||||
const targetTankId = await resolveTankId(
|
const targetTankId = await resolveTankId(
|
||||||
interaction.options.getString("target-tank", true),
|
interaction.options.getString('target-tank', true),
|
||||||
interaction.locale,
|
interaction.locale,
|
||||||
true
|
true,
|
||||||
);
|
);
|
||||||
const startingTankRaw = interaction.options.getString("starting-tank");
|
const startingTankRaw = interaction.options.getString('starting-tank');
|
||||||
const targetTank = awaitedTankDefinitions.tanks[targetTankId];
|
const targetTank = awaitedTankDefinitions.tanks[targetTankId];
|
||||||
const { id, region } = await resolvePlayerFromCommand(interaction);
|
const { id, region } = await resolvePlayerFromCommand(interaction);
|
||||||
let startingTankId: number;
|
let startingTankId: number;
|
||||||
@@ -99,45 +99,46 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
startingTankId = await resolveTankId(
|
startingTankId = await resolveTankId(
|
||||||
startingTankRaw,
|
startingTankRaw,
|
||||||
interaction.locale,
|
interaction.locale,
|
||||||
true
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (targetTankId === startingTankId) {
|
if (targetTankId === startingTankId) {
|
||||||
return literals(
|
return literals(
|
||||||
strings.bot.commands.research.errors.start_end_equal,
|
strings.bot.commands.research.errors.start_end_equal,
|
||||||
{ tank: escapeMarkdown(unwrap(targetTank.name)) }
|
[escapeMarkdown(unwrap(targetTank.name))],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const startingTank = awaitedTankDefinitions.tanks[startingTankId];
|
const startingTank = awaitedTankDefinitions.tanks[startingTankId];
|
||||||
|
|
||||||
if (startingTank.tier > targetTank.tier) {
|
if (startingTank.tier > targetTank.tier) {
|
||||||
return literals(strings.bot.commands.research.errors.unordered, {
|
return literals(strings.bot.commands.research.errors.unordered, [
|
||||||
tank_1: escapeMarkdown(unwrap(startingTank.name)),
|
escapeMarkdown(unwrap(startingTank.name)),
|
||||||
tank_2: escapeMarkdown(unwrap(targetTank.name)),
|
escapeMarkdown(unwrap(targetTank.name)),
|
||||||
});
|
escapeMarkdown(unwrap(targetTank.name)),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!targetTankAncestry.includes(startingTankId)) {
|
if (!targetTankAncestry.includes(startingTankId)) {
|
||||||
return literals(
|
return literals(
|
||||||
strings.bot.commands.research.errors.tanks_not_on_line,
|
strings.bot.commands.research.errors.tanks_not_on_line,
|
||||||
{
|
[
|
||||||
tank_1: escapeMarkdown(unwrap(targetTank.name)),
|
escapeMarkdown(unwrap(targetTank.name)),
|
||||||
tank_2: escapeMarkdown(unwrap(startingTank.name)),
|
escapeMarkdown(unwrap(startingTank.name)),
|
||||||
}
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (targetTank.type !== TankType.RESEARCHABLE) {
|
if (targetTank.type !== TankType.RESEARCHABLE) {
|
||||||
return literals(strings.bot.commands.research.errors.non_tech_tree, {
|
return literals(strings.bot.commands.research.errors.non_tech_tree, [
|
||||||
tank: escapeMarkdown(unwrap(targetTank.name)),
|
escapeMarkdown(unwrap(targetTank.name)),
|
||||||
});
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetTank.tier === 1) {
|
if (targetTank.tier === 1) {
|
||||||
return literals(strings.bot.commands.research.errors.tier_1, {
|
return literals(strings.bot.commands.research.errors.tier_1, [
|
||||||
tank: escapeMarkdown(unwrap(targetTank.name)),
|
escapeMarkdown(unwrap(targetTank.name)),
|
||||||
});
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tankStats = await getTankStats(region, id);
|
const tankStats = await getTankStats(region, id);
|
||||||
@@ -149,18 +150,18 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
if (tankStats.some(({ tank_id }) => tank_id === targetTankId)) {
|
if (tankStats.some(({ tank_id }) => tank_id === targetTankId)) {
|
||||||
return literals(
|
return literals(
|
||||||
strings.bot.commands.research.errors.already_researched,
|
strings.bot.commands.research.errors.already_researched,
|
||||||
{ tank: escapeMarkdown(unwrap(targetTank.name)) }
|
[escapeMarkdown(unwrap(targetTank.name))],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundAncestor = targetTankAncestry.find(
|
const foundAncestor = targetTankAncestry.find(
|
||||||
(id) =>
|
(id) =>
|
||||||
awaitedTankDefinitions.tanks[id].tier === 1 ||
|
awaitedTankDefinitions.tanks[id].tier === 1 ||
|
||||||
tankStats.some(({ tank_id }) => tank_id === id)
|
tankStats.some(({ tank_id }) => tank_id === id),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (foundAncestor === undefined) {
|
if (foundAncestor === undefined) {
|
||||||
throw new Error("No first owned tech tree found.");
|
throw new Error('No first owned tech tree found.');
|
||||||
}
|
}
|
||||||
|
|
||||||
startingTankId = foundAncestor;
|
startingTankId = foundAncestor;
|
||||||
@@ -168,12 +169,12 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
|
|
||||||
const techTreeLine = await buildTechTreeLine(
|
const techTreeLine = await buildTechTreeLine(
|
||||||
startingTankId,
|
startingTankId,
|
||||||
targetTankId
|
targetTankId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const line = [...techTreeLine, startingTankId].reverse();
|
const line = [...techTreeLine, startingTankId].reverse();
|
||||||
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 costs = await Promise.all(
|
const costs = await Promise.all(
|
||||||
line.map(async (id) => {
|
line.map(async (id) => {
|
||||||
@@ -186,20 +187,25 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
tank.turrets.forEach((turret) => {
|
tank.turrets.forEach((turret) => {
|
||||||
if (
|
if (
|
||||||
turret.research_cost !== undefined &&
|
turret.research_cost !== undefined &&
|
||||||
typeof turret.research_cost.research_cost_type!.value === "number"
|
typeof turret.research_cost.research_cost_type!.value === 'number'
|
||||||
) {
|
) {
|
||||||
turretXps.set(
|
turretXps.set(
|
||||||
turret.id,
|
turret.id,
|
||||||
turret.research_cost.research_cost_type!.value
|
turret.research_cost.research_cost_type!.value,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
turret.guns.forEach((gun) => {
|
turret.guns.forEach((gun) => {
|
||||||
if (
|
if (
|
||||||
gun.research_cost !== undefined &&
|
gun.gun_type!.value.base.research_cost !== undefined &&
|
||||||
typeof gun.research_cost.research_cost_type!.value === "number"
|
typeof gun.gun_type!.value.base.research_cost
|
||||||
|
.research_cost_type!.value === 'number'
|
||||||
) {
|
) {
|
||||||
gunXps.set(gun.id, gun.research_cost.research_cost_type!.value);
|
gunXps.set(
|
||||||
|
gun.gun_type!.value.base.id,
|
||||||
|
gun.gun_type!.value.base.research_cost.research_cost_type!
|
||||||
|
.value,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -207,11 +213,11 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
tank.engines.forEach((engine) => {
|
tank.engines.forEach((engine) => {
|
||||||
if (
|
if (
|
||||||
engine.research_cost !== undefined &&
|
engine.research_cost !== undefined &&
|
||||||
typeof engine.research_cost.research_cost_type!.value === "number"
|
typeof engine.research_cost.research_cost_type!.value === 'number'
|
||||||
) {
|
) {
|
||||||
engineXps.set(
|
engineXps.set(
|
||||||
engine.id,
|
engine.id,
|
||||||
engine.research_cost.research_cost_type!.value
|
engine.research_cost.research_cost_type!.value,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -219,11 +225,11 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
tank.tracks.forEach((track) => {
|
tank.tracks.forEach((track) => {
|
||||||
if (
|
if (
|
||||||
track.research_cost !== undefined &&
|
track.research_cost !== undefined &&
|
||||||
typeof track.research_cost.research_cost_type!.value === "number"
|
typeof track.research_cost.research_cost_type!.value === 'number'
|
||||||
) {
|
) {
|
||||||
trackXps.set(
|
trackXps.set(
|
||||||
track.id,
|
track.id,
|
||||||
track.research_cost.research_cost_type!.value
|
track.research_cost.research_cost_type!.value,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -248,10 +254,10 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
purchase: tank.price.value,
|
purchase: tank.price.value,
|
||||||
equipment: equipmentPriceMatrix[tank.tier].reduce(
|
equipment: equipmentPriceMatrix[tank.tier].reduce(
|
||||||
(a, b) => a + 3 * b,
|
(a, b) => a + 3 * b,
|
||||||
0
|
0,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const image = (
|
const image = (
|
||||||
@@ -266,14 +272,14 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
gap: 8,
|
gap: 8,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
backgroundColor: theme.colors.appBackground2,
|
backgroundColor: theme.colors.appBackground2,
|
||||||
border: theme.borderStyles.subtle,
|
border: theme.borderStyles.subtle,
|
||||||
@@ -284,10 +290,10 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
position: "relative",
|
position: 'relative',
|
||||||
right: 24,
|
right: 24,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -303,23 +309,23 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
gap: 8,
|
gap: 8,
|
||||||
padding: "16px 0",
|
padding: '16px 0',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="XP icon"
|
alt="XP icon"
|
||||||
src={await iconPng(asset("icons/currencies/xp.webp"))}
|
src={await iconPng(asset('icons/currencies/xp.webp'))}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
/>
|
/>
|
||||||
@@ -329,7 +335,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
color: theme.colors.textHighContrast,
|
color: theme.colors.textHighContrast,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Experience:{" "}
|
Experience:{' '}
|
||||||
{costs
|
{costs
|
||||||
.reduce((a, b) => a + (b.research ?? 0) + b.upgrades, 0)
|
.reduce((a, b) => a + (b.research ?? 0) + b.upgrades, 0)
|
||||||
.toLocaleString(interaction.locale)}
|
.toLocaleString(interaction.locale)}
|
||||||
@@ -338,14 +344,14 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Silver icon"
|
alt="Silver icon"
|
||||||
src={await iconPng(asset("icons/currencies/silver.webp"))}
|
src={await iconPng(asset('icons/currencies/silver.webp'))}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
/>
|
/>
|
||||||
@@ -355,7 +361,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
color: theme.colors.textHighContrast,
|
color: theme.colors.textHighContrast,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Purchase:{" "}
|
Purchase:{' '}
|
||||||
{costs
|
{costs
|
||||||
.reduce((a, b) => a + b.purchase, 0)
|
.reduce((a, b) => a + b.purchase, 0)
|
||||||
.toLocaleString(interaction.locale)}
|
.toLocaleString(interaction.locale)}
|
||||||
@@ -364,14 +370,14 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Silver icon"
|
alt="Silver icon"
|
||||||
src={await iconPng(asset("icons/currencies/silver.webp"))}
|
src={await iconPng(asset('icons/currencies/silver.webp'))}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
/>
|
/>
|
||||||
@@ -381,7 +387,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
color: theme.colors.textHighContrast,
|
color: theme.colors.textHighContrast,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Equipment:{" "}
|
Equipment:{' '}
|
||||||
{costs
|
{costs
|
||||||
.reduce((a, b) => a + b.equipment, 0)
|
.reduce((a, b) => a + b.equipment, 0)
|
||||||
.toLocaleString(interaction.locale)}
|
.toLocaleString(interaction.locale)}
|
||||||
@@ -398,7 +404,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
<div
|
<div
|
||||||
key={id}
|
key={id}
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
backgroundColor: theme.colors.appBackground2,
|
backgroundColor: theme.colors.appBackground2,
|
||||||
border: theme.borderStyles.subtle,
|
border: theme.borderStyles.subtle,
|
||||||
@@ -407,9 +413,9 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
width: 48,
|
width: 48,
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -425,9 +431,9 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
position: "relative",
|
position: 'relative',
|
||||||
right: "5%",
|
right: '5%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@@ -436,23 +442,23 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
style={{
|
style={{
|
||||||
height: 100,
|
height: 100,
|
||||||
width: 100,
|
width: 100,
|
||||||
objectFit: "contain",
|
objectFit: 'contain',
|
||||||
position: "absolute",
|
position: 'absolute',
|
||||||
left: "25%",
|
left: '25%',
|
||||||
top: 0,
|
top: 0,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
position: "absolute",
|
position: 'absolute',
|
||||||
bottom: 8,
|
bottom: 8,
|
||||||
left: "50%",
|
left: '50%',
|
||||||
transform: "translateX(-50%)",
|
transform: 'translateX(-50%)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@@ -475,23 +481,23 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
gap: 8,
|
gap: 8,
|
||||||
padding: "16px 0",
|
padding: '16px 0',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="XP icon"
|
alt="XP icon"
|
||||||
src={await iconPng(asset("icons/currencies/xp.webp"))}
|
src={await iconPng(asset('icons/currencies/xp.webp'))}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
/>
|
/>
|
||||||
@@ -503,11 +509,11 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
>
|
>
|
||||||
{literals(
|
{literals(
|
||||||
strings.bot.commands.research.body.research,
|
strings.bot.commands.research.body.research,
|
||||||
{
|
[
|
||||||
cost: costs[index].research?.toLocaleString(
|
`${costs[index].research?.toLocaleString(
|
||||||
interaction.locale
|
interaction.locale,
|
||||||
),
|
)}`,
|
||||||
}
|
],
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -515,15 +521,15 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
{costs[index].upgrades > 0 && (
|
{costs[index].upgrades > 0 && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Free XP icon"
|
alt="Free XP icon"
|
||||||
src={await iconPng(
|
src={await iconPng(
|
||||||
asset("icons/currencies/free-xp.webp")
|
asset('icons/currencies/free-xp.webp'),
|
||||||
)}
|
)}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
@@ -536,7 +542,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
>
|
>
|
||||||
{literals(
|
{literals(
|
||||||
strings.bot.commands.research.body.upgrades,
|
strings.bot.commands.research.body.upgrades,
|
||||||
{ cost: costs[index].upgrades.toLocaleString() }
|
[costs[index].upgrades.toLocaleString()],
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -544,15 +550,15 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Silver icon"
|
alt="Silver icon"
|
||||||
src={await iconPng(
|
src={await iconPng(
|
||||||
asset("icons/currencies/silver.webp")
|
asset('icons/currencies/silver.webp'),
|
||||||
)}
|
)}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
@@ -565,26 +571,26 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
>
|
>
|
||||||
{literals(
|
{literals(
|
||||||
strings.bot.commands.research.body.purchase,
|
strings.bot.commands.research.body.purchase,
|
||||||
{
|
[
|
||||||
cost: costs[index].purchase.toLocaleString(
|
costs[index].purchase.toLocaleString(
|
||||||
interaction.locale
|
interaction.locale,
|
||||||
),
|
),
|
||||||
}
|
],
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Silver icon"
|
alt="Silver icon"
|
||||||
src={await iconPng(
|
src={await iconPng(
|
||||||
asset("icons/currencies/silver.webp")
|
asset('icons/currencies/silver.webp'),
|
||||||
)}
|
)}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
@@ -597,18 +603,18 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
>
|
>
|
||||||
{literals(
|
{literals(
|
||||||
strings.bot.commands.research.body.equipment,
|
strings.bot.commands.research.body.equipment,
|
||||||
{
|
[
|
||||||
cost: costs[index].equipment.toLocaleString(
|
costs[index].equipment.toLocaleString(
|
||||||
interaction.locale
|
interaction.locale,
|
||||||
),
|
),
|
||||||
}
|
],
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})
|
}),
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CommandWrapper>
|
</CommandWrapper>
|
||||||
@@ -620,7 +626,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
autocomplete(interaction) {
|
autocomplete(interaction) {
|
||||||
autocompleteTanks(interaction, true, ["target-tank", "starting-tank"]);
|
autocompleteTanks(interaction, true, ['target-tank', 'starting-tank']);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1,23 +1,23 @@
|
|||||||
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';
|
||||||
import { addClanChoices } from "../core/discord/addClanChoices";
|
import { addClanChoices } from '../core/discord/addClanChoices';
|
||||||
import { addRegionChoices } from "../core/discord/addRegionChoices";
|
import { addRegionChoices } from '../core/discord/addRegionChoices';
|
||||||
import { createLocalizedCommand } from "../core/discord/createLocalizedCommand";
|
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;
|
||||||
|
|
||||||
export const searchClansCommand = new Promise<CommandRegistry>((resolve) => {
|
export const searchClansCommand = new Promise<CommandRegistry>((resolve) => {
|
||||||
resolve({
|
resolve({
|
||||||
command: createLocalizedCommand("search-clans")
|
command: createLocalizedCommand('search-clans')
|
||||||
.addStringOption(addRegionChoices)
|
.addStringOption(addRegionChoices)
|
||||||
.addStringOption((option) =>
|
.addStringOption((option) =>
|
||||||
addClanChoices(option).setAutocomplete(false)
|
addClanChoices(option).setAutocomplete(false),
|
||||||
)
|
)
|
||||||
.addIntegerOption((option) => {
|
.addIntegerOption((option) => {
|
||||||
const { strings } = translator(Locale.EnglishUS);
|
const { strings } = translator(Locale.EnglishUS);
|
||||||
@@ -28,21 +28,21 @@ export const searchClansCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
localizationObject(
|
localizationObject(
|
||||||
(strings) => strings.bot.commands.search_clans.options.limit.name,
|
(strings) => strings.bot.commands.search_clans.options.limit.name,
|
||||||
undefined,
|
undefined,
|
||||||
true
|
true,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.setDescription(
|
.setDescription(
|
||||||
literals(
|
literals(
|
||||||
strings.bot.commands.search_clans.options.limit.description,
|
strings.bot.commands.search_clans.options.limit.description,
|
||||||
{ limit: DEFAULT_LIMIT }
|
[`${DEFAULT_LIMIT}`],
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.setDescriptionLocalizations(
|
.setDescriptionLocalizations(
|
||||||
localizationObject(
|
localizationObject(
|
||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.search_clans.options.limit.description,
|
strings.bot.commands.search_clans.options.limit.description,
|
||||||
{ limit: DEFAULT_LIMIT }
|
[`${DEFAULT_LIMIT}`],
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.setMinValue(1)
|
.setMinValue(1)
|
||||||
.setMaxValue(100);
|
.setMaxValue(100);
|
||||||
@@ -50,24 +50,24 @@ export const searchClansCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const { strings } = translator(interaction.locale);
|
const { strings } = translator(interaction.locale);
|
||||||
const region = interaction.options.getString("region") as Region;
|
const region = interaction.options.getString('region') as Region;
|
||||||
const clan = interaction.options.getString("clan")!;
|
const clan = interaction.options.getString('clan')!;
|
||||||
const limit = interaction.options.getInteger("limit") ?? DEFAULT_LIMIT;
|
const limit = interaction.options.getInteger('limit') ?? DEFAULT_LIMIT;
|
||||||
const clanList = await fetchBlitz<ClanList>(region, "clans/list", {
|
const clanList = await fetchBlitz<ClanList>(region, 'clans/list', {
|
||||||
search: clan,
|
search: clan,
|
||||||
limit,
|
limit,
|
||||||
});
|
});
|
||||||
|
|
||||||
return embedInfo(
|
return embedInfo(
|
||||||
literals(strings.bot.commands.search_clans.body.title, {
|
literals(strings.bot.commands.search_clans.body.title, [
|
||||||
search: markdownEscape(clan),
|
markdownEscape(clan),
|
||||||
region: strings.common.regions.normal[region],
|
strings.common.regions.normal[region],
|
||||||
}),
|
]),
|
||||||
clanList.length === 0
|
clanList.length === 0
|
||||||
? strings.bot.commands.search_clans.body.no_results
|
? strings.bot.commands.search_clans.body.no_results
|
||||||
: `\`\`\`\n${clanList
|
: `\`\`\`\n${clanList
|
||||||
.map((clan) => `${clan.name} [${clan.tag}]`)
|
.map((clan) => `${clan.name} [${clan.tag}]`)
|
||||||
.join("\n")}\n\`\`\``
|
.join('\n')}\n\`\`\``,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
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';
|
||||||
import { Locale } from "discord.js";
|
import { Locale } from 'discord.js';
|
||||||
import markdownEscape from "markdown-escape";
|
import markdownEscape from 'markdown-escape';
|
||||||
import { addRegionChoices } from "../core/discord/addRegionChoices";
|
import { addRegionChoices } from '../core/discord/addRegionChoices';
|
||||||
import { addUsernameChoices } from "../core/discord/addUsernameChoices";
|
import { addUsernameChoices } from '../core/discord/addUsernameChoices';
|
||||||
import { createLocalizedCommand } from "../core/discord/createLocalizedCommand";
|
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;
|
||||||
|
|
||||||
@@ -21,10 +21,10 @@ export const searchPlayersCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
const { strings } = translator(Locale.EnglishUS);
|
const { strings } = translator(Locale.EnglishUS);
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
command: createLocalizedCommand("search-players")
|
command: createLocalizedCommand('search-players')
|
||||||
.addStringOption(addRegionChoices)
|
.addStringOption(addRegionChoices)
|
||||||
.addStringOption((option) =>
|
.addStringOption((option) =>
|
||||||
addUsernameChoices(option).setAutocomplete(false).setRequired(true)
|
addUsernameChoices(option).setAutocomplete(false).setRequired(true),
|
||||||
)
|
)
|
||||||
.addIntegerOption((option) =>
|
.addIntegerOption((option) =>
|
||||||
option
|
option
|
||||||
@@ -34,49 +34,49 @@ export const searchPlayersCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.search_players.options.limit.name,
|
strings.bot.commands.search_players.options.limit.name,
|
||||||
undefined,
|
undefined,
|
||||||
true
|
true,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.setDescription(
|
.setDescription(
|
||||||
literals(
|
literals(
|
||||||
strings.bot.commands.search_players.options.limit.description,
|
strings.bot.commands.search_players.options.limit.description,
|
||||||
{ limit: DEFAULT_LIMIT }
|
[`${DEFAULT_LIMIT}`],
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.setDescriptionLocalizations(
|
.setDescriptionLocalizations(
|
||||||
localizationObject(
|
localizationObject(
|
||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.search_players.options.limit.description,
|
strings.bot.commands.search_players.options.limit.description,
|
||||||
{ limit: DEFAULT_LIMIT }
|
[`${DEFAULT_LIMIT}`],
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.setMinValue(1)
|
.setMinValue(1)
|
||||||
.setMaxValue(100)
|
.setMaxValue(100),
|
||||||
),
|
),
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const { strings } = translator(interaction.locale);
|
const { strings } = translator(interaction.locale);
|
||||||
const region = interaction.options.getString("region") as Region;
|
const region = interaction.options.getString('region') as Region;
|
||||||
const name = interaction.options.getString("username")!;
|
const name = interaction.options.getString('username')!;
|
||||||
const limit = interaction.options.getInteger("limit") ?? 25;
|
const limit = interaction.options.getInteger('limit') ?? 25;
|
||||||
const trimmedSearch = name.trim();
|
const trimmedSearch = name.trim();
|
||||||
const players = usernamePattern.test(trimmedSearch)
|
const players = usernamePattern.test(trimmedSearch)
|
||||||
? await fetchBlitz<AccountList>(region, "account/list", {
|
? await fetchBlitz<AccountList>(region, 'account/list', {
|
||||||
search: trimmedSearch,
|
search: trimmedSearch,
|
||||||
limit,
|
limit,
|
||||||
})
|
})
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return embedInfo(
|
return embedInfo(
|
||||||
literals(strings.bot.commands.search_players.body.title, {
|
literals(strings.bot.commands.search_players.body.title, [
|
||||||
search: markdownEscape(trimmedSearch),
|
markdownEscape(trimmedSearch),
|
||||||
region: strings.common.regions.normal[region],
|
strings.common.regions.normal[region],
|
||||||
}),
|
]),
|
||||||
`\`\`\`${
|
`\`\`\`${
|
||||||
players.length === 0
|
players.length === 0
|
||||||
? strings.bot.commands.search_players.body.no_results
|
? strings.bot.commands.search_players.body.no_results
|
||||||
: players.map((player) => player.nickname).join("\n")
|
: players.map((player) => player.nickname).join('\n')
|
||||||
}\`\`\``
|
}\`\`\``,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,50 +1,50 @@
|
|||||||
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 { getBlitzStarsLinkButton } from "../core/blitzstars/getBlitzStarsLinkButton";
|
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
|
||||||
import { getStatsInPeriod } from "../core/blitzstars/getStatsInPeriod";
|
import { getStatsInPeriod } from '../core/blitzstars/getStatsInPeriod';
|
||||||
import { addPeriodicFilterOptions } from "../core/discord/addPeriodicFilterOptions";
|
import { addPeriodicFilterOptions } from '../core/discord/addPeriodicFilterOptions';
|
||||||
import { addUsernameChoices } from "../core/discord/addUsernameChoices";
|
import { addUsernameChoices } from '../core/discord/addUsernameChoices';
|
||||||
import { autocompleteTanks } from "../core/discord/autocompleteTanks";
|
import { autocompleteTanks } from '../core/discord/autocompleteTanks';
|
||||||
import { autocompleteUsername } from "../core/discord/autocompleteUsername";
|
import { autocompleteUsername } from '../core/discord/autocompleteUsername';
|
||||||
import { buttonRefresh } from "../core/discord/buttonRefresh";
|
import { buttonRefresh } from '../core/discord/buttonRefresh';
|
||||||
import { commandToURL } from "../core/discord/commandToURL";
|
import { commandToURL } from '../core/discord/commandToURL';
|
||||||
import { createLocalizedCommand } from "../core/discord/createLocalizedCommand";
|
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
||||||
import { getCustomPeriodParams } from "../core/discord/getCustomPeriodParams";
|
import { getCustomPeriodParams } from '../core/discord/getCustomPeriodParams';
|
||||||
import { getFiltersFromButton } from "../core/discord/getFiltersFromButton";
|
import { getFiltersFromButton } from '../core/discord/getFiltersFromButton';
|
||||||
import { getFiltersFromCommand } from "../core/discord/getFiltersFromCommand";
|
import { getFiltersFromCommand } from '../core/discord/getFiltersFromCommand';
|
||||||
import { resolvePeriodFromButton } from "../core/discord/resolvePeriodFromButton";
|
import { resolvePeriodFromButton } from '../core/discord/resolvePeriodFromButton';
|
||||||
import {
|
import {
|
||||||
type ResolvedPeriod,
|
ResolvedPeriod,
|
||||||
resolvePeriodFromCommand,
|
resolvePeriodFromCommand,
|
||||||
} from "../core/discord/resolvePeriodFromCommand";
|
} from '../core/discord/resolvePeriodFromCommand';
|
||||||
import { resolvePlayerFromButton } from "../core/discord/resolvePlayerFromButton";
|
import { resolvePlayerFromButton } from '../core/discord/resolvePlayerFromButton';
|
||||||
import {
|
import {
|
||||||
type ResolvedPlayer,
|
ResolvedPlayer,
|
||||||
resolvePlayerFromCommand,
|
resolvePlayerFromCommand,
|
||||||
} from "../core/discord/resolvePlayerFromCommand";
|
} from '../core/discord/resolvePlayerFromCommand';
|
||||||
import type { CommandRegistry } from "../events/interactionCreate";
|
import { CommandRegistry } from '../events/interactionCreate';
|
||||||
|
|
||||||
async function render(
|
async function render(
|
||||||
{ region, id }: ResolvedPlayer,
|
{ region, id }: ResolvedPlayer,
|
||||||
{ start, end, name }: ResolvedPeriod,
|
{ start, end, name }: ResolvedPeriod,
|
||||||
filters: StatFilters,
|
filters: StatFilters,
|
||||||
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,
|
||||||
id,
|
id,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
locale
|
locale,
|
||||||
);
|
);
|
||||||
const { stats, supplementary } = await filterStats(diffedTankStats, filters);
|
const { stats, supplementary } = await filterStats(diffedTankStats, filters);
|
||||||
const filterDescriptions = await filtersToDescription(filters, locale);
|
const filterDescriptions = await filtersToDescription(filters, locale);
|
||||||
@@ -72,8 +72,8 @@ 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) {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { resolvePeriodFromButton } from '../core/discord/resolvePeriodFromButton
|
|||||||
import { resolvePeriodFromCommand } from '../core/discord/resolvePeriodFromCommand';
|
import { resolvePeriodFromCommand } from '../core/discord/resolvePeriodFromCommand';
|
||||||
import { resolvePlayerFromButton } from '../core/discord/resolvePlayerFromButton';
|
import { resolvePlayerFromButton } from '../core/discord/resolvePlayerFromButton';
|
||||||
import { resolvePlayerFromCommand } from '../core/discord/resolvePlayerFromCommand';
|
import { resolvePlayerFromCommand } from '../core/discord/resolvePlayerFromCommand';
|
||||||
import { type CommandRegistry } from '../events/interactionCreate';
|
import { CommandRegistry } from '../events/interactionCreate';
|
||||||
import { renderBreakdown } from './breakdown/render';
|
import { renderBreakdown } from './breakdown/render';
|
||||||
|
|
||||||
export const todayCommand = new Promise<CommandRegistry>(async (resolve) => {
|
export const todayCommand = new Promise<CommandRegistry>(async (resolve) => {
|
||||||
|
|||||||
+10
-10
@@ -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;
|
||||||
|
|||||||
Loaded 100 of 762 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user