Compare commits

..
4 Commits
Author SHA1 Message Date
TrèsAbhi b3a0edb9e0 New translations en.json (Ukrainian)
[ci skip]
2026-09-19 04:50:21 -07:00
TrèsAbhi 2a3d1f13b0 New translations en.json (Ukrainian)
[ci skip]
2026-09-15 11:42:29 -07:00
TrèsAbhi 7cb87a5f0f New translations en.json (Ukrainian)
[ci skip]
2026-09-15 10:35:44 -07:00
TrèsAbhi d665c414b0 New translations en.json (Ukrainian)
[ci skip]
2026-09-10 08:13:06 -07:00
13 changed files with 151 additions and 266 deletions

No files matched your search

+44 -36
View File
@@ -2,6 +2,7 @@ name: Website
on: on:
push: push:
branches: production
schedule: schedule:
- cron: "0 4 * * *" - cron: "0 4 * * *"
@@ -33,40 +34,14 @@ on:
- preview - preview
- opentest - opentest
client_provider:
description: Client provider
type: choice
required: true
default: steam
options:
- steam
- zip
- wgpkg
client_source:
description: Client source
type: string
required: true
default: 444200/windows
permissions: permissions:
contents: read contents: read
concurrency: concurrency:
group: ${{ github.workflow }}-${{ inputs.environment || github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
env: env:
# client
STEAM_USERNAME: ${{ secrets.STEAM_USERNAME }}
STEAM_PASSWORD: ${{ secrets.STEAM_PASSWORD }}
CLIENT_PROVIDER: ${{ inputs.client_provider || 'steam' }}
CLIENT_SOURCE: ${{ inputs.client_source || '444200/windows' }}
CLIENT_DIR: ../../temp/game
# blitzkit # blitzkit
PUBLIC_ENVIRONMENT: ${{ inputs.environment || 'production' }} PUBLIC_ENVIRONMENT: ${{ inputs.environment || 'production' }}
PUBLIC_PROMOTE_OPENTEST: false PUBLIC_PROMOTE_OPENTEST: false
@@ -77,6 +52,7 @@ env:
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }} WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
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 }}
GAME_DIR: ../../temp/game
# google # google
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }} PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
@@ -84,6 +60,12 @@ env:
# wargaming # wargaming
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }} PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
# steam
STEAM_USERNAME: ${{ secrets.STEAM_USERNAME }}
STEAM_PASSWORD: ${{ secrets.STEAM_PASSWORD }}
STEAM_APP: 444200
STEAM_OS: windows
jobs: jobs:
build: build:
name: Build website name: Build website
@@ -91,7 +73,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: true fail-fast: false
matrix: matrix:
chunk: [default, api] chunk: [default, api]
@@ -114,16 +96,44 @@ jobs:
with: with:
repo-token: ${{ secrets.GH_TOKEN }} repo-token: ${{ secrets.GH_TOKEN }}
- name: Setup CLIs - name: Configure pages
run: | uses: actions/configure-pages@v6
sudo apt-get update
sudo apt-get install -y aria2 libarchive-tools
- name: Install dependencies - name: Install dependencies
run: bun install run: bun install
- name: Download Client - name: Setup DepotDownloader
run: bun run download-client run: |
REPO_USER="SteamRE"
REPO_NAME="DepotDownloader"
TEMP="temp"
OUT="$TEMP/$REPO_NAME"
ZIP="$TEMP/$REPO_NAME.zip"
URL=$(
curl -s "https://api.github.com/repos/$REPO_USER/$REPO_NAME/releases/latest" \
| jq -r '.assets[]
| select(.name | contains("linux-x64"))
| .browser_download_url' \
| head -n 1
)
mkdir -p "$TEMP"
curl -L "$URL" -o "$ZIP"
unzip -p "$ZIP" "$REPO_NAME" > "$OUT"
rm "$ZIP"
chmod +x "$OUT"
- name: Download game
run: |
TEMP="temp"
DEPOT_DOWNLOADER="$TEMP/DepotDownloader"
GAME="$TEMP/game"
$DEPOT_DOWNLOADER -os $STEAM_OS -app $STEAM_APP -username $STEAM_USERNAME -password $STEAM_PASSWORD -dir $GAME -max-downloads 256
- name: Build - name: Build
run: bun run --bun build run: bun run --bun build
@@ -139,8 +149,6 @@ jobs:
- name: Trigger deployment - name: Trigger deployment
if: ${{ github.ref == 'refs/heads/production' }}
env: env:
TOKEN: ${{ secrets.GH_NODE_DEPLOY_TOKEN }} TOKEN: ${{ secrets.GH_NODE_DEPLOY_TOKEN }}
CHUNK: ${{ matrix.chunk }} CHUNK: ${{ matrix.chunk }}
-3
View File
@@ -1,3 +0,0 @@
# BlitzKit 1.31.1
This update adds capabilities to release BlitzKit OpenTest and Preview.
+2 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "blitzkit", "name": "blitzkit",
"version": "1.31.1", "version": "1.31.0",
"license": "BlitzKit License", "license": "BlitzKit License",
"private": true, "private": true,
"scripts": { "scripts": {
@@ -8,8 +8,7 @@
"build": "cd packages/website && bun run build", "build": "cd packages/website && bun run build",
"discover-ids": "cd packages/scripts && bun run discover-ids", "discover-ids": "cd packages/scripts && bun run discover-ids",
"build-protos": "cd packages/scripts && bun run build-protos", "build-protos": "cd packages/scripts && bun run build-protos",
"record-performance": "cd packages/scripts && bun run record-performance", "record-performance": "cd packages/scripts && bun run record-performance"
"download-client": "cd packages/scripts && bun run download-client"
}, },
"type": "module", "type": "module",
"workspaces": [ "workspaces": [
+10 -10
View File
@@ -13,7 +13,7 @@
"hp": "{value}од", "hp": "{value}од",
"x": "x{value}", "x": "x{value}",
"percentage": "{value}%", "percentage": "{value}%",
"deg_s": "{value}град/c", "deg_s": "{value}°/c",
"plus": "+{value}" "plus": "+{value}"
}, },
"gun_types": { "gun_types": {
@@ -37,8 +37,8 @@
"d_1": "Вчора" "d_1": "Вчора"
}, },
"branches": { "branches": {
"beta": "Beta", "beta": "Бета",
"development": "Development", "development": "Розробка",
"opentest": "Відкритий тест", "opentest": "Відкритий тест",
"preview": "Preview", "preview": "Preview",
"production": "Production" "production": "Production"
@@ -340,8 +340,8 @@
}, },
"mixer": { "mixer": {
"name": "Mixer", "name": "Mixer",
"button": "Mix and match", "button": "Змішувати та поєднувати",
"description": "Mix tank modules", "description": "Змішані танкові модулі",
"seo_description": "Mix and match hulls, turrets, and guns of tanks in World of Tanks Blitz", "seo_description": "Mix and match hulls, turrets, and guns of tanks in World of Tanks Blitz",
"screenshot": "Знімок екрану", "screenshot": "Знімок екрану",
"randomize": "Рандомізувати" "randomize": "Рандомізувати"
@@ -501,11 +501,11 @@
"tech_tree": { "tech_tree": {
"title": "Звичайний", "title": "Звичайний",
"battle": "1 бій", "battle": "1 бій",
"battles": "{count} battles", "battles": "{count} битви",
"route": "Route {index} of {count}" "route": "Route {index} of {count}"
}, },
"game_modes": { "game_modes": {
"title": "Game mode abilities" "title": "Здібності ігрового режиму"
}, },
"review": { "review": {
"title": "Review videos", "title": "Review videos",
@@ -626,7 +626,7 @@
"camouflageShootingStill": "Нерухомо", "camouflageShootingStill": "Нерухомо",
"camouflageShootingMoving": "У русі", "camouflageShootingMoving": "У русі",
"camouflageCaughtOnFire": "Caught on fire", "camouflageCaughtOnFire": "Caught on fire",
"width": "Width", "width": "Ширина",
"height": "Height", "height": "Height",
"length": "Length", "length": "Length",
"volume": "Volume", "volume": "Volume",
@@ -740,7 +740,7 @@
} }
}, },
"gallery": { "gallery": {
"name": "Gallery", "name": "Галерея",
"button": "Browse all", "button": "Browse all",
"description": "Blitz player avatars", "description": "Blitz player avatars",
"seo_description": "Усі World of Tanks Blitz аватари гравців", "seo_description": "Усі World of Tanks Blitz аватари гравців",
@@ -843,7 +843,7 @@
"breakdown": { "breakdown": {
"name": "Breakdown", "name": "Breakdown",
"options": { "options": {
"width": "Width", "width": "Ширина",
"height": "Height", "height": "Height",
"showTotal": "Show total", "showTotal": "Show total",
"listGap": "List gap", "listGap": "List gap",
+1 -2
View File
@@ -4,8 +4,7 @@
"scripts": { "scripts": {
"discover-ids": "bun src/discoverIds/index.ts", "discover-ids": "bun src/discoverIds/index.ts",
"build-protos": "bun src/protos.ts", "build-protos": "bun src/protos.ts",
"record-performance": "bun src/recordPerformance/index.ts", "record-performance": "bun src/recordPerformance/index.ts"
"download-client": "bun src/downloadClient/index.ts"
}, },
"dependencies": { "dependencies": {
"@blitzkit/closed": "workspace:*", "@blitzkit/closed": "workspace:*",
+91 -59
View File
@@ -4,7 +4,7 @@ import { exit } from "node:process";
import { GitObjectStorage } from "../core/blitzkit/gitObjectStorage"; import { GitObjectStorage } from "../core/blitzkit/gitObjectStorage";
import { IdArray } from "../core/blitzkit/idArray"; import { IdArray } from "../core/blitzkit/idArray";
import { CHUNK_COUNT, MAX_DRY_STREAK, MAX_IDS_PER_CALL } from "./constants"; import { CHUNK_COUNT, MAX_DRY_STREAK, MAX_IDS_PER_CALL } from "./constants";
import { atMaxRate } from "./time"; import { sleep, withinTimeLimit } from "./time";
import { IdsManifest, QuickInfo, RegionDescriptor } from "./types"; import { IdsManifest, QuickInfo, RegionDescriptor } from "./types";
const storage = await new GitObjectStorage( const storage = await new GitObjectStorage(
@@ -30,16 +30,18 @@ for (let i = 0; i < manifest.chunks; i++) {
console.log(`Found ${manifest.chunks} pre-existing chunks`); console.log(`Found ${manifest.chunks} pre-existing chunks`);
manifest.chunks = CHUNK_COUNT;
let chunks: IdArray[] = []; let chunks: IdArray[] = [];
if (manifest.chunks === CHUNK_COUNT) { if (discoveredChunks.length === manifest.chunks) {
console.log("Pre-existing chunking is consistent, passing as-is..."); console.log("Pre-existing chunking is consistent, passing as-is...");
chunks = discoveredChunks as IdArray[]; chunks = discoveredChunks as IdArray[];
} else { } else {
console.log("Pre-existing chunking is not consistent, re-chunking..."); console.log("Pre-existing chunking is not consistent, re-chunking...");
chunks = Array.from({ length: CHUNK_COUNT }).map(() => new IdArray()); chunks = Array.from({ length: manifest.chunks }).map(() => new IdArray());
for (let i = 0; i < discoveredChunks.length; i++) { for (let i = 0; i < discoveredChunks.length; i++) {
const ids = discoveredChunks[i]; const ids = discoveredChunks[i];
@@ -47,7 +49,7 @@ if (manifest.chunks === CHUNK_COUNT) {
for (let j = 0; j < size; j++) { for (let j = 0; j < size; j++) {
const id = ids!.get(j); const id = ids!.get(j);
const chunk = chunks[id % CHUNK_COUNT]; const chunk = chunks[id % manifest.chunks];
chunk.push(id); chunk.push(id);
} }
@@ -58,9 +60,6 @@ if (manifest.chunks === CHUNK_COUNT) {
console.log("Sorting chunks..."); console.log("Sorting chunks...");
for (let i = 0; i < chunks.length; i++) chunks[i].sort(); for (let i = 0; i < chunks.length; i++) chunks[i].sort();
manifest.chunks = CHUNK_COUNT;
manifest.last_verified = manifest.last_verified % CHUNK_COUNT;
} }
const regions: RegionDescriptor[] = [ const regions: RegionDescriptor[] = [
@@ -117,6 +116,7 @@ async function save() {
manifest.total_count = totalFound; manifest.total_count = totalFound;
await storage.write("manifest.json", JSON.stringify(manifest, null, 2)); await storage.write("manifest.json", JSON.stringify(manifest, null, 2));
await storage.commit(); await storage.commit();
console.log( console.log(
@@ -129,44 +129,41 @@ async function save() {
process.on("SIGINT", save); process.on("SIGINT", save);
process.on("SIGTERM", save); process.on("SIGTERM", save);
const queue: { region: RegionDescriptor; url: string; size: number }[] = [];
const applicationId = import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID; const applicationId = import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID;
const fields = "account_id%2C-account_id"; const fields = "account_id%2C-account_id";
let regionI = 0; let regionI = 0;
console.log("Starting discovery loop..."); function fillQueue() {
if (queue.length >= 1) return;
atMaxRate(async ({ _break: break1 }) => { while (regions.length > 0) {
if (regions.length === 0) { regionI = (regionI + 1) % regions.length;
console.log("All regions exhausted, exiting discovery loop..."); const region = regions[regionI];
return break1();
}
regionI = (regionI + 1) % regions.length; if (region.dry_streak >= MAX_DRY_STREAK) {
const region = regions[regionI]; console.log(
`Removing region ${region.domain} removed due to ${region.dry_streak} dry streak...`,
);
regions.splice(regionI, 1);
continue;
}
if (region.dry_streak >= MAX_DRY_STREAK) { const id0 = region.seed;
console.log( const id1 = Math.min(id0 + MAX_IDS_PER_CALL - 1, region.max);
`Removing region ${region.domain} removed due to ${region.dry_streak} dry streak...`, const idRange = range(id0, id1 + 1);
);
regions.splice(regionI, 1);
return; region.seed += idRange.length;
}
const id0 = region.seed; const accountIds = idRange.join(",");
const id1 = Math.min(id0 + MAX_IDS_PER_CALL - 1, region.max); const url = `https://api.wotblitz.${
const idRange = range(id0, id1 + 1); region.domain
}/wotb/account/info/?application_id=${applicationId}&fields=${
fields
}&account_id=${accountIds}`;
region.seed += idRange.length; queue.push({ region, url, size: idRange.length });
const accountIds = idRange.join(",");
const url = `https://api.wotblitz.${
region.domain
}/wotb/account/info/?application_id=${applicationId}&fields=${
fields
}&account_id=${accountIds}`;
atMaxRate(async ({ _break: break2 }) => {
if (idRange.length < MAX_IDS_PER_CALL) { if (idRange.length < MAX_IDS_PER_CALL) {
console.log( console.log(
`Removing region ${region.domain} removed due to ID range exhaustion...`, `Removing region ${region.domain} removed due to ID range exhaustion...`,
@@ -174,39 +171,74 @@ atMaxRate(async ({ _break: break1 }) => {
regions.splice(regionI, 1); regions.splice(regionI, 1);
} }
const response = await fetch(url); return;
const data = (await response.json()) as QuickInfo; }
}
if (data.status !== "ok") { console.log("Starting discovery loop...");
console.warn("Failed API call; trying again...");
return;
}
let foundIds = 0; while (withinTimeLimit() && regions.length > 0) {
await sleep();
for (const key in data.data) { fillQueue();
const value = data.data[key];
if (value === null) continue; if (queue.length === 0) {
console.log("Queue empty, ending discovery loop...");
break;
}
foundIds++; const request = queue.shift()!;
const response = await fetch(request.url);
const data = (await response.json()) as QuickInfo;
const id = Number(key); if (data.status !== "ok") {
const chunkIndex = id % manifest.chunks; console.warn("Failed API call; pushing back into queue...");
const chunk = chunks[chunkIndex]; queue.push(request);
chunk.push(id); continue;
} }
if (foundIds === 0) { let foundIds = 0;
region.dry_streak += idRange.length;
} else {
region.dry_streak = 0;
// console.log(`Found ${foundIds} ids in ${request.region.domain}`);
}
break2(); for (const key in data.data) {
}); const value = data.data[key];
});
if (value === null) continue;
foundIds++;
const id = Number(key);
const chunkIndex = id % manifest.chunks;
const chunk = chunks[chunkIndex];
chunk.push(id);
}
if (foundIds === 0) {
request.region.dry_streak += request.size;
} else {
request.region.dry_streak = 0;
// console.log(`Found ${foundIds} ids in ${request.region.domain}`);
}
}
// console.log("Starting verification loop...");
// while (withinTimeLimit()) {
// const verificationIndex = (manifest.last_verified + 1) % manifest.chunks;
// manifest.last_verified = verificationIndex;
// const chunk = chunks[verificationIndex];
// const size = chunk.size();
// let i0 = 0;
// while (withinTimeLimit() && i0 < size) {
// await sleep();
// const i1 = Math.min(i0 + MAX_IDS_PER_CALL - 1, size - 1);
// const indices = range(i0, i1 + 1);
// }
// console.log(`Verification complete for chunk ${verificationIndex}`);
// }
await save(); await save();
+2 -33
View File
@@ -9,37 +9,6 @@ export function withinTimeLimit() {
return performance.now() < t1; return performance.now() < t1;
} }
export function sleep(t: number) { export function sleep() {
return new Promise((resolve) => setTimeout(resolve, t)); return new Promise((resolve) => setTimeout(resolve, 1000 / API_RATE));
}
interface MethodContext {
_break(): void;
}
let lastT = 0;
const maxDelta = 1000 / API_RATE;
const DELTA_BUFFER = 0;
export async function atMaxRate(method: (context: MethodContext) => void) {
let isBroken = false;
const context: MethodContext = {
_break() {
isBroken = true;
},
};
while (!isBroken) {
if (!withinTimeLimit()) break;
const t = performance.now();
const delta = t - lastT;
if (delta < maxDelta) {
await sleep(maxDelta - delta + DELTA_BUFFER);
}
method(context);
lastT = t;
}
} }
@@ -1,9 +0,0 @@
// dava engine has a lot of tiny files, this is faster
export const DEPOT_DOWNLOADER_MAX_DOWNLOADS = 256;
export const DEPOT_DOWNLOADER_OUTPUT = "../../temp/depotdownloader";
export const DEPOT_DOWNLOADER_EXECUTABLE = `${DEPOT_DOWNLOADER_OUTPUT}/DepotDownloader`;
export const ARIA2_OUTPUT = "../../temp/aria2";
export const ARIA2_EXECUTABLE = `${ARIA2_OUTPUT}/aria2c`;
export const WGPKG_DIR = "../../temp/wgpkgs";
@@ -1,98 +0,0 @@
import { assertSecret } from "@blitzkit/core";
import { $ } from "bun";
import { mkdir, rm } from "node:fs/promises";
import {
DEPOT_DOWNLOADER_EXECUTABLE,
DEPOT_DOWNLOADER_MAX_DOWNLOADS,
DEPOT_DOWNLOADER_OUTPUT,
WGPKG_DIR,
} from "./constants";
import { QuickGitHubRelease } from "./types";
const provider = assertSecret(import.meta.env.CLIENT_PROVIDER);
const source = assertSecret(import.meta.env.CLIENT_SOURCE);
const dir = assertSecret(import.meta.env.CLIENT_DIR);
await rm(dir, { recursive: true, force: true });
await mkdir(dir, { recursive: true });
if (provider === "steam") {
const [id, platform] = source.split("/");
console.log("Downloading Depot Downloader...");
const releases = await fetch(
"https://api.github.com/repos/SteamRE/DepotDownloader/releases/latest",
).then((response) => response.json() as Promise<QuickGitHubRelease>);
const asset = releases.assets.find((asset) =>
asset.name.includes("linux-x64"),
);
if (!asset) {
throw new Error("No linux-x64 asset found for Steam DepotDownloader");
}
await rm(DEPOT_DOWNLOADER_OUTPUT, { recursive: true, force: true });
await $`curl -L "${asset.browser_download_url}" -o "${DEPOT_DOWNLOADER_OUTPUT}.zip"`;
await $`unzip "${DEPOT_DOWNLOADER_OUTPUT}.zip" -d "${DEPOT_DOWNLOADER_OUTPUT}"`;
await $`chmod +x "${DEPOT_DOWNLOADER_EXECUTABLE}"`;
console.log("Downloading client...");
const username = assertSecret(import.meta.env.STEAM_USERNAME);
const password = assertSecret(import.meta.env.STEAM_PASSWORD);
await $`${DEPOT_DOWNLOADER_EXECUTABLE} -os ${
platform
} -app ${id} -username ${username} -password ${
password
} -dir ${dir} -max-downloads ${DEPOT_DOWNLOADER_MAX_DOWNLOADS}`;
} else if (provider === "zip") {
const url = assertSecret(import.meta.env.CLIENT_SOURCE);
console.log("Downloading and extracting zipped client...");
await mkdir(dir, { recursive: true });
await $`curl -L "${url}" | bsdtar -xf- --strip-components=1 -C ${dir}`;
} else if (provider === "wgpkg") {
let index = 1;
const urls: string[] = [];
console.log("Discovering packages...");
while (true) {
const url = `${source}.${index.toString().padStart(3, "0")}`;
const response = await fetch(url);
if (response.status === 200) {
urls.push(url);
} else if (response.status === 404) {
break;
} else {
throw new Error(`Wargaming response error status: ${response.status}`);
}
index++;
}
console.log(`Downloading ${urls.length} packages...`);
await rm(WGPKG_DIR, { recursive: true });
const inputContent = urls
.map((url) => {
const name = url.split("/").pop()!;
return `${url}\n out=${name}\n dir=${WGPKG_DIR}`;
})
.join("\n");
await $`echo ${inputContent} | aria2c -j ${urls.length} -x 2 -s 2 -i -`;
const firstChunk = `${WGPKG_DIR}/${urls[0].split("/").pop()!}`;
await $`7z x "${firstChunk}" -o${dir} -y`;
await rm(WGPKG_DIR, { recursive: true });
} else {
throw new Error(`Unknown provider: ${provider}`);
}
@@ -1,8 +0,0 @@
export interface QuickGitHubRelease {
assets: Asset[];
}
interface Asset {
name: string;
browser_download_url: string;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

-4
View File
@@ -74,8 +74,4 @@ export const tools: Record<string, Tool> = {
href: "https://discord.gg/nDt7AjGJQH", href: "https://discord.gg/nDt7AjGJQH",
button: "plum", button: "plum",
}, },
studio: {
id: "studio",
button: "indigo",
},
}; };
+1 -1
View File
@@ -1,4 +1,4 @@
import { assertSecret } from "@blitzkit/core"; import { assertSecret } from "@blitzkit/core";
import { LocalVFS } from "@blitzkit/core/src/blitzkit/vfs/local"; import { LocalVFS } from "@blitzkit/core/src/blitzkit/vfs/local";
export const vfs = new LocalVFS(assertSecret(import.meta.env.CLIENT_DIR)); export const vfs = new LocalVFS(assertSecret(import.meta.env.GAME_DIR));