Compare commits

...

3 Commits

Author SHA1 Message Date
bigjakk 93775cc36a v0.5.4 — Fix taskbar pin persistence across updates
Set app.setAppUserModelId() to match electron-builder appId
(com.krunkercivilian.client) so Windows associates the running process
with the installed shortcut. Add explicit shortcutName to NSIS config
for stable shortcut identity across installs. Remove stale uuid entry
from electron-builder files list.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 11:38:51 -08:00
bigjakk 467ac95b4e v0.5.3 — Fix resource swapper, add Husky, remove uuid
Fix three Electron 12→42 protocol migration bugs in the resource swapper:
register protocol on the app session instead of default, generate valid
URLs from Windows paths, and prevent non-swapped krunker.io requests from
being cancelled. Swapper now rescans on page refresh to pick up file changes.

Add Husky pre-commit hook to run ESLint. Remove unused uuid dependency.
Update README with lint script, husky, and swapper improvements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 11:19:10 -08:00
bigjakk 1eabea195a v0.5.2 — Add ESLint + typescript-eslint, fix lint errors
Add flat-config ESLint with typescript-eslint recommended rules,
fix all lint errors (unused imports/vars, empty catches, Function types,
prefer-const, useless assignments), and rename stale kpc- CSS class to kcc-.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 09:24:34 -08:00
16 changed files with 1071 additions and 114 deletions
+1
View File
@@ -0,0 +1 @@
npm run lint
+7 -4
View File
@@ -28,6 +28,7 @@ npm run dev # Builds in dev mode + launches Electron
| `npm run dist:linux` | Build + package for Linux (AppImage + deb) | | `npm run dist:linux` | Build + package for Linux (AppImage + deb) |
| `npm run dist:all` | Build + package for all platforms | | `npm run dist:all` | Build + package for all platforms |
| `npm run clean` | Remove `dist/` and `out/` directories | | `npm run clean` | Remove `dist/` and `out/` directories |
| `npm run lint` | Run ESLint (typescript-eslint) on `src/` |
## Architecture ## Architecture
@@ -138,7 +139,7 @@ src/
platform.ts OS detection, Chromium GPU/performance flags (per-platform) platform.ts OS detection, Chromium GPU/performance flags (per-platform)
config.ts electron-store schema, defaults, DEFAULT_KEYBINDS config.ts electron-store schema, defaults, DEFAULT_KEYBINDS
client-ui.ts Injected CSS for settings panel, keybind dialog, matchmaker popup client-ui.ts Injected CSS for settings panel, keybind dialog, matchmaker popup
swapper.ts Resource swapper — local asset overrides via custom protocol swapper.ts Resource swapper — local asset overrides via session-aware custom protocol
userscripts.ts Userscript manager — filesystem scanning, tracker.json, preferences userscripts.ts Userscript manager — filesystem scanning, tracker.json, preferences
discord-rpc.ts Discord Rich Presence via raw IPC socket discord-rpc.ts Discord Rich Presence via raw IPC socket
logger.ts File logging with daily rotation and 7-day retention logger.ts File logging with daily rotation and 7-day retention
@@ -159,7 +160,7 @@ Two Vite configs build independent targets:
| Config | Target | Output | Notes | | Config | Target | Output | Notes |
|--------|--------|--------|-------| |--------|--------|--------|-------|
| `vite.main.config.ts` | Main process | `dist/main/index.js` (CJS) | Externalizes `electron`, `electron-store`, and Node builtins. Targets Node 20. | | `vite.main.config.ts` | Main process | `dist/main/index.js` (CJS) | Externalizes `electron`, `electron-store`, and Node builtins. Targets Node 20. |
| `vite.preload.config.ts` | Preload script | `dist/preload/index.js` (CJS) | Externalizes `electron` and `uuid`. Targets Node 20. | | `vite.preload.config.ts` | Preload script | `dist/preload/index.js` (CJS) | Externalizes `electron`. Targets Node 20. |
### Custom Electron Binary ### Custom Electron Binary
@@ -214,7 +215,7 @@ Common flags always applied: `disable-backgrounding-occluded-windows`, `ignore-g
### Core Client ### Core Client
- Unlimited FPS via custom-patched Electron 42 build - Unlimited FPS via custom-patched Electron 42 build
- Ad blocking (network-level URL filter + CSS hiding) - Ad blocking (network-level URL filter + CSS hiding)
- Resource swapper (replace game textures, sounds, models with local files) - Resource swapper (replace game textures, sounds, models with local files — rescans on page refresh)
- Custom matchmaker (filter lobbies by region, gamemode, player count, remaining time) - Custom matchmaker (filter lobbies by region, gamemode, player count, remaining time)
- Userscript system (Tampermonkey-style metadata, custom per-script settings, instant toggle via unload) - Userscript system (Tampermonkey-style metadata, custom per-script settings, instant toggle via unload)
- Chat translator (real-time translation via Google Translate API with language tags) - Chat translator (real-time translation via Google Translate API with language tags)
@@ -267,8 +268,9 @@ At uncapped frame rates (600+ FPS), Krunker's CSS animations (e.g. death screen
- **TypeScript** 5.7 — Type-safe source code - **TypeScript** 5.7 — Type-safe source code
- **Vite** 6 — Fast bundler (2 build targets: main + preload) - **Vite** 6 — Fast bundler (2 build targets: main + preload)
- **electron-store** 8 — JSON config persistence (CJS) - **electron-store** 8 — JSON config persistence (CJS)
- **uuid** 9 — Unique ID generation
- **electron-builder** 26 — Cross-platform packaging (NSIS, portable, AppImage, deb) - **electron-builder** 26 — Cross-platform packaging (NSIS, portable, AppImage, deb)
- **ESLint** 10 + **typescript-eslint** — Linting with recommended rules
- **Husky** 9 — Git hooks (pre-commit lint)
## Project Structure ## Project Structure
@@ -284,6 +286,7 @@ Krunker-Civilian-Client/
build/ Build resources (icons, .desktop file) build/ Build resources (icons, .desktop file)
scripts/ Build scripts (Electron patched binary download) scripts/ Build scripts (Electron patched binary download)
electron-build/ Custom Electron build instructions and patches electron-build/ Custom Electron build instructions and patches
eslint.config.mjs
vite.main.config.ts vite.main.config.ts
vite.preload.config.ts vite.preload.config.ts
electron-builder.yml electron-builder.yml
+1 -1
View File
@@ -25,7 +25,6 @@ files:
- node_modules/mimic-fn/**/* - node_modules/mimic-fn/**/*
- node_modules/semver/**/* - node_modules/semver/**/*
- node_modules/onetime/**/* - node_modules/onetime/**/*
- node_modules/uuid/**/*
- "!node_modules/**/*.ts" - "!node_modules/**/*.ts"
- "!node_modules/**/*.map" - "!node_modules/**/*.map"
asar: true asar: true
@@ -43,6 +42,7 @@ nsis:
allowToChangeInstallationDirectory: true allowToChangeInstallationDirectory: true
createDesktopShortcut: true createDesktopShortcut: true
createStartMenuShortcut: true createStartMenuShortcut: true
shortcutName: Krunker Civilian Client
artifactName: "${productName}-${version}-Setup.${ext}" artifactName: "${productName}-${version}-Setup.${ext}"
portable: portable:
+19
View File
@@ -0,0 +1,19 @@
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
{
ignores: ["dist/", "out/", "scripts/"],
},
{
rules: {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
],
},
}
);
+929 -39
View File
File diff suppressed because it is too large Load Diff
+9 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "krunker-civilian-client", "name": "krunker-civilian-client",
"version": "0.5.1", "version": "0.5.4",
"description": "Cross-platform Krunker game client", "description": "Cross-platform Krunker game client",
"main": "dist/main/index.js", "main": "dist/main/index.js",
"homepage": "https://gitea.crjlab.net/bigjakk/krunker-civilian-client", "homepage": "https://gitea.crjlab.net/bigjakk/krunker-civilian-client",
@@ -17,18 +17,23 @@
"dist:win": "npm run build && electron-builder --win", "dist:win": "npm run build && electron-builder --win",
"dist:linux": "npm run build && electron-builder --linux", "dist:linux": "npm run build && electron-builder --linux",
"dist:all": "npm run build && electron-builder --win --linux", "dist:all": "npm run build && electron-builder --win --linux",
"clean": "rimraf dist out" "clean": "rimraf dist out",
"lint": "eslint src/",
"prepare": "husky"
}, },
"dependencies": { "dependencies": {
"electron-store": "^8.2.0", "electron-store": "^8.2.0"
"uuid": "^9.0.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1",
"@types/node": "^22.0.0", "@types/node": "^22.0.0",
"electron": "npm:electron-nightly@42.0.0-nightly.20260227", "electron": "npm:electron-nightly@42.0.0-nightly.20260227",
"electron-builder": "^26.0.0", "electron-builder": "^26.0.0",
"eslint": "^10.0.2",
"husky": "^9.1.7",
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
"typescript": "^5.7.0", "typescript": "^5.7.0",
"typescript-eslint": "^8.56.1",
"vite": "^6.0.0" "vite": "^6.0.0"
} }
} }
+1 -1
View File
@@ -518,7 +518,7 @@ export const MATCHMAKER_SETTINGS_CSS = `
`; `;
export const TRANSLATOR_CSS = ` export const TRANSLATOR_CSS = `
.kpc-translation { .kcc-translation {
color: #88ff88; color: #88ff88;
font-style: italic; font-style: italic;
margin-left: 8px; margin-left: 8px;
+17 -12
View File
@@ -1,4 +1,4 @@
import { app, BrowserWindow, Menu, clipboard, dialog, ipcMain, protocol, safeStorage, session, shell } from 'electron'; import { app, BrowserWindow, Menu, clipboard, dialog, ipcMain, safeStorage, session, shell } from 'electron';
import { join } from 'path'; import { join } from 'path';
import { existsSync, mkdirSync, promises as fsp } from 'fs'; import { existsSync, mkdirSync, promises as fsp } from 'fs';
import { get as httpsGet } from 'https'; import { get as httpsGet } from 'https';
@@ -14,6 +14,7 @@ import { showUpdateWindow } from './update-window';
import { DiscordRPC } from './discord-rpc'; import { DiscordRPC } from './discord-rpc';
// ── App version for API calls ── // ── App version for API calls ──
// eslint-disable-next-line @typescript-eslint/no-require-imports
const appVersion: string = require('../../package.json').version; const appVersion: string = require('../../package.json').version;
// ── Region ping cache ── // ── Region ping cache ──
@@ -53,6 +54,9 @@ const advancedConfig = { ...advancedDefaults, ...config.get('advanced') };
const perfConfig = { fpsUnlocked: true, ...config.get('performance') }; const perfConfig = { fpsUnlocked: true, ...config.get('performance') };
applyPlatformFlags(platformInfo, advancedConfig, perfConfig); applyPlatformFlags(platformInfo, advancedConfig, perfConfig);
// ── App identity (must match electron-builder appId for taskbar pin persistence) ──
app.setAppUserModelId('com.krunkercivilian.client');
// ── Resource swapper protocol (must register before app.ready) ── // ── Resource swapper protocol (must register before app.ready) ──
initSwapperProtocol(); initSwapperProtocol();
@@ -210,14 +214,14 @@ app.whenReady().then(async () => {
async function launchApp(): Promise<void> { async function launchApp(): Promise<void> {
electronLog.log('[KCC] Starting initialization'); electronLog.log('[KCC] Starting initialization');
// ── Register swapper file protocol ──
registerSwapperFileProtocol();
// ── Session: persistent partition + clean user-agent ── // ── Session: persistent partition + clean user-agent ──
const ses = session.fromPartition('persist:krunker'); const ses = session.fromPartition('persist:krunker');
const rawUA = ses.getUserAgent(); const rawUA = ses.getUserAgent();
ses.setUserAgent(rawUA.replace(/\s*krunker-civilian-client\/\S+/i, '')); ses.setUserAgent(rawUA.replace(/\s*krunker-civilian-client\/\S+/i, ''));
// ── Register swapper file protocol on this session ──
registerSwapperFileProtocol(ses);
// ── Resource swapper ── // ── Resource swapper ──
const swapperConfig = config.get('swapper'); const swapperConfig = config.get('swapper');
const swapDir = swapperConfig.path || join(app.getPath('userData'), 'Krunker Civilian Client', 'swapper'); const swapDir = swapperConfig.path || join(app.getPath('userData'), 'Krunker Civilian Client', 'swapper');
@@ -239,16 +243,17 @@ async function launchApp(): Promise<void> {
: [...BLOCKED_URL_PATTERNS]; : [...BLOCKED_URL_PATTERNS];
ses.webRequest.onBeforeRequest({ urls: requestFilterUrls }, (details, callback) => { ses.webRequest.onBeforeRequest({ urls: requestFilterUrls }, (details, callback) => {
// Check swapper first — redirect matching assets to local files
if (swapper) { if (swapper) {
const redirect = swapper.getRedirect(details.url); const redirect = swapper.getRedirect(details.url);
if (redirect) return callback({ redirectURL: redirect }); if (redirect) return callback({ redirectURL: redirect });
} }
// If we got here via the broad krunker.io pattern (not an ad), let it through // Determine if this URL is a krunker.io request (matched by the broad swapper pattern)
// vs an ad-block pattern. krunker.io requests that weren't swapped pass through normally.
try { try {
const host = new URL(details.url).hostname; if (new URL(details.url).hostname.endsWith('krunker.io')) return callback({});
if (host.endsWith('krunker.io')) return callback({}); } catch { /* invalid URL — fall through to cancel */ }
} catch {} // Matched an ad-block pattern — cancel it
// Otherwise it matched an ad-block pattern — cancel it
callback({ cancel: true }); callback({ cancel: true });
}); });
@@ -317,8 +322,6 @@ async function launchApp(): Promise<void> {
} }
// ── Common output directory (used by folder actions) ── // ── Common output directory (used by folder actions) ──
const outputDir = join(app.getPath('documents'), 'Krunker Civilian Client');
// ── Configurable keybinds via before-input-event ── // ── Configurable keybinds via before-input-event ──
win.webContents.on('before-input-event', (event, input) => { win.webContents.on('before-input-event', (event, input) => {
if (input.type !== 'keyDown') return; if (input.type !== 'keyDown') return;
@@ -333,7 +336,7 @@ async function launchApp(): Promise<void> {
event.preventDefault(); event.preventDefault();
} else if (matchesKeybind(input, binds.joinFromClipboard)) { } else if (matchesKeybind(input, binds.joinFromClipboard)) {
const text = clipboard.readText(); const text = clipboard.readText();
try { const u = new URL(text); if (u.protocol === 'https:' && u.hostname.endsWith('krunker.io')) win.loadURL(text); } catch {}; try { const u = new URL(text); if (u.protocol === 'https:' && u.hostname.endsWith('krunker.io')) win.loadURL(text); } catch { /* ignore invalid URLs */ }
event.preventDefault(); event.preventDefault();
} else if (matchesKeybind(input, binds.copyGameLink)) { } else if (matchesKeybind(input, binds.copyGameLink)) {
clipboard.writeText(win.webContents.getURL()); clipboard.writeText(win.webContents.getURL());
@@ -461,6 +464,8 @@ async function launchApp(): Promise<void> {
// ── Inject scripts after page loads ── // ── Inject scripts after page loads ──
win.webContents.on('did-finish-load', () => { win.webContents.on('did-finish-load', () => {
electronLog.log(`[KCC] Page loaded: ${win.webContents.getURL()}`); electronLog.log(`[KCC] Page loaded: ${win.webContents.getURL()}`);
// Rescan swap directory so new/changed files are picked up on refresh
if (swapper) swapper.rescan().catch(() => {});
Promise.all([ Promise.all([
win.webContents.insertCSS(HIDE_ADS_CSS), win.webContents.insertCSS(HIDE_ADS_CSS),
win.webContents.insertCSS(ALL_CLIENT_CSS), win.webContents.insertCSS(ALL_CLIENT_CSS),
+1 -1
View File
@@ -67,7 +67,7 @@ function makeLogger(getStream: () => WriteStream) {
export const electronLog = makeLogger(() => electronStream); export const electronLog = makeLogger(() => electronStream);
export function getLogPath(type: 'electron'): string { export function getLogPath(_type: 'electron'): string {
init(); init();
return electronPath; return electronPath;
} }
+42 -9
View File
@@ -1,27 +1,53 @@
import { existsSync, mkdirSync, promises as fsp } from 'fs'; import { existsSync, mkdirSync, promises as fsp } from 'fs';
import { join } from 'path'; import { join } from 'path';
import { protocol, net } from 'electron'; import { protocol, net, Session } from 'electron';
const PROTOCOL_NAME = 'kpc-swap'; const PROTOCOL_NAME = 'kpc-swap';
const TARGET_DOMAIN = 'krunker.io'; const TARGET_DOMAIN = 'krunker.io';
/**
* Convert a native file path to a proper kpc-swap:// URL.
* Windows paths like C:\foo\bar become kpc-swap://C/foo/bar
*/
function filePathToSwapURL(filePath: string): string {
const forwardSlash = filePath.replace(/\\/g, '/');
// Windows drive letter: C:/foo → kpc-swap://C/foo
const match = forwardSlash.match(/^([A-Za-z]):\/(.*)/);
if (match) {
return `${PROTOCOL_NAME}://${match[1]}/${match[2]}`;
}
// Unix absolute: /home/user/foo → kpc-swap:///home/user/foo
return `${PROTOCOL_NAME}://${forwardSlash}`;
}
/** /**
* Register the custom protocol scheme. Must be called BEFORE app.ready. * Register the custom protocol scheme. Must be called BEFORE app.ready.
*/ */
export function initSwapperProtocol(): void { export function initSwapperProtocol(): void {
protocol.registerSchemesAsPrivileged([{ protocol.registerSchemesAsPrivileged([{
scheme: PROTOCOL_NAME, scheme: PROTOCOL_NAME,
privileges: { secure: true, corsEnabled: true, bypassCSP: true }, privileges: { standard: true, secure: true, corsEnabled: true, bypassCSP: true },
}]); }]);
} }
/** /**
* Register the file protocol handler. Must be called AFTER app.ready. * Register the file protocol handler on the given session.
* Must be called AFTER app.ready.
*/ */
export function registerSwapperFileProtocol(): void { export function registerSwapperFileProtocol(ses: Session): void {
protocol.handle(PROTOCOL_NAME, (request) => { ses.protocol.handle(PROTOCOL_NAME, (request) => {
const filePath = decodeURI(request.url.replace(`${PROTOCOL_NAME}:`, '')); const url = new URL(request.url);
return net.fetch('file://' + filePath); // Reconstruct the file path from the URL
// Windows: kpc-swap://C/foo/bar → C:/foo/bar
// Unix: kpc-swap:///home/foo → /home/foo
let filePath: string;
if (url.hostname) {
// Windows drive letter is the hostname
filePath = `${url.hostname}:${url.pathname}`;
} else {
filePath = url.pathname;
}
return net.fetch(`file://${filePath}`);
}); });
} }
@@ -47,6 +73,13 @@ export class ResourceSwapper {
this.ready = true; this.ready = true;
} }
/** Rescan the swap directory to pick up added/removed/changed files */
async rescan(): Promise<void> {
this.swapFiles.clear();
await this.scanAsync('');
this.ready = true;
}
/** URL filter patterns for webRequest.onBeforeRequest — single broad pattern */ /** URL filter patterns for webRequest.onBeforeRequest — single broad pattern */
get patterns(): string[] { get patterns(): string[] {
return this.swapFiles.size > 0 ? [`*://*.${TARGET_DOMAIN}/*`] : []; return this.swapFiles.size > 0 ? [`*://*.${TARGET_DOMAIN}/*`] : [];
@@ -70,7 +103,7 @@ export class ResourceSwapper {
let pathname = queryStart === -1 ? url.substring(pathStart) : url.substring(pathStart, queryStart); let pathname = queryStart === -1 ? url.substring(pathStart) : url.substring(pathStart, queryStart);
if (pathname.startsWith('/assets/')) pathname = pathname.substring(7); if (pathname.startsWith('/assets/')) pathname = pathname.substring(7);
const localPath = this.swapFiles.get(pathname); const localPath = this.swapFiles.get(pathname);
if (localPath) return `${PROTOCOL_NAME}:/${localPath}`; if (localPath) return filePathToSwapURL(localPath);
} catch { /* malformed URL — ignore */ } } catch { /* malformed URL — ignore */ }
return null; return null;
} }
@@ -87,7 +120,7 @@ export class ResourceSwapper {
this.swapFiles.set(name, join(this.swapDir, name)); this.swapFiles.set(name, join(this.swapDir, name));
} }
} }
} catch (err) { } catch {
console.error(`Failed to scan swap directory prefix: ${prefix}`); console.error(`Failed to scan swap directory prefix: ${prefix}`);
} }
} }
+1 -1
View File
@@ -1,4 +1,4 @@
import { get as httpsGet, request as httpsRequest } from 'https'; import { get as httpsGet } from 'https';
import { createWriteStream, renameSync, unlinkSync, existsSync } from 'fs'; import { createWriteStream, renameSync, unlinkSync, existsSync } from 'fs';
import { spawn } from 'child_process'; import { spawn } from 'child_process';
import { app } from 'electron'; import { app } from 'electron';
+1 -1
View File
@@ -49,7 +49,7 @@ export class UserscriptManager {
/** Load tracker.json, add new scripts as disabled, prune deleted scripts */ /** Load tracker.json, add new scripts as disabled, prune deleted scripts */
async loadTracker(scripts: ScriptFile[]): Promise<ScriptTracker> { async loadTracker(scripts: ScriptFile[]): Promise<ScriptTracker> {
let tracker: ScriptTracker = {}; let tracker: ScriptTracker;
try { try {
tracker = JSON.parse(await fsp.readFile(this.trackerPath, 'utf-8')); tracker = JSON.parse(await fsp.readFile(this.trackerPath, 'utf-8'));
} catch { tracker = {}; } } catch { tracker = {}; }
+7 -7
View File
@@ -2,7 +2,7 @@ import { ipcRenderer } from 'electron';
import { fetchGame, MATCHMAKER_GAMEMODES, MATCHMAKER_REGIONS, MATCHMAKER_REGION_NAMES } from './matchmaker'; import { fetchGame, MATCHMAKER_GAMEMODES, MATCHMAKER_REGIONS, MATCHMAKER_REGION_NAMES } from './matchmaker';
import type { MatchmakerConfig } from './matchmaker'; import type { MatchmakerConfig } from './matchmaker';
import { initUserscripts, getInstances, setScriptEnabled } from './userscripts'; import { initUserscripts, getInstances, setScriptEnabled } from './userscripts';
import type { UserscriptInstance, UserscriptSetting } from './userscripts'; import type { UserscriptInstance } from './userscripts';
import { initTranslator, updateTranslatorConfig } from './translator'; import { initTranslator, updateTranslatorConfig } from './translator';
import { setDeathAnimBlock, escapeHtml } from './utils'; import { setDeathAnimBlock, escapeHtml } from './utils';
import type { Keybind } from '../main/config'; import type { Keybind } from '../main/config';
@@ -10,7 +10,7 @@ import type { Keybind } from '../main/config';
// ── Save console methods before Krunker overwrites them ── // ── Save console methods before Krunker overwrites them ──
// Wrapped to forward errors/warnings always, and logs when verbose is enabled // Wrapped to forward errors/warnings always, and logs when verbose is enabled
let _verboseLogging = false; const _verboseLogging = false;
const _console = { const _console = {
log: (...args: unknown[]) => { log: (...args: unknown[]) => {
@@ -75,7 +75,7 @@ function updateRefreshNotification(): void {
if (refreshPopupEl) { refreshPopupEl.remove(); refreshPopupEl = null; } if (refreshPopupEl) { refreshPopupEl.remove(); refreshPopupEl = null; }
return; return;
} }
if (refreshPopupEl) { try { refreshPopupEl.remove(); } catch (_e) { /* noop */ } } if (refreshPopupEl) { try { refreshPopupEl.remove(); } catch { /* noop */ } }
refreshPopupEl = document.createElement('div'); refreshPopupEl = document.createElement('div');
refreshPopupEl.className = 'kpc-holder-update refresh-popup'; refreshPopupEl.className = 'kpc-holder-update refresh-popup';
if (refreshLevel === RefreshLevel.restart) { if (refreshLevel === RefreshLevel.restart) {
@@ -1115,7 +1115,7 @@ function renderUserscriptsSection(body: HTMLElement): void {
scriptRow.className = 'setting settName safety-0 bool'; scriptRow.className = 'setting settName safety-0 bool';
const displayName = escapeHtml(inst.meta.name || inst.filename); const displayName = escapeHtml(inst.meta.name || inst.filename);
let metaParts: string[] = []; const metaParts: string[] = [];
if (inst.meta.author) metaParts.push('by ' + escapeHtml(inst.meta.author)); if (inst.meta.author) metaParts.push('by ' + escapeHtml(inst.meta.author));
if (inst.meta.version) metaParts.push('v' + escapeHtml(inst.meta.version)); if (inst.meta.version) metaParts.push('v' + escapeHtml(inst.meta.version));
const metaLine = metaParts.length > 0 ? '<span class="kpc-us-meta">' + metaParts.join(' &middot; ') + '</span>' : ''; const metaLine = metaParts.length > 0 ? '<span class="kpc-us-meta">' + metaParts.join(' &middot; ') + '</span>' : '';
@@ -1352,7 +1352,7 @@ ipcRenderer.on('main_did-finish-load', () => {
Promise.all([ Promise.all([
ipcRenderer.invoke('get-all-config', ['ui', 'userscripts', 'game', 'translator', 'keybinds', 'discord']), ipcRenderer.invoke('get-all-config', ['ui', 'userscripts', 'game', 'translator', 'keybinds', 'discord']),
ipcRenderer.invoke('get-platform'), ipcRenderer.invoke('get-platform'),
]).then(([allConf, platformInfo]: [any, any]) => { ]).then(([allConf, _platformInfo]: [any, any]) => {
const uiConf = allConf.ui; const uiConf = allConf.ui;
const usConf = allConf.userscripts; const usConf = allConf.userscripts;
const gameConf = allConf.game; const gameConf = allConf.game;
@@ -1417,7 +1417,7 @@ ipcRenderer.on('main_did-finish-load', () => {
let gameStartTimestamp = Math.floor(Date.now() / 1000); let gameStartTimestamp = Math.floor(Date.now() / 1000);
function pollDiscordState(): void { function pollDiscordState(): void {
let details = ''; let details: string;
let state = ''; let state = '';
let startTimestamp: number | undefined = undefined; let startTimestamp: number | undefined = undefined;
@@ -1426,7 +1426,7 @@ ipcRenderer.on('main_did-finish-load', () => {
let gameActivity: any = null; let gameActivity: any = null;
if (typeof w.getGameActivity === 'function') { if (typeof w.getGameActivity === 'function') {
try { gameActivity = w.getGameActivity(); } catch {} try { gameActivity = w.getGameActivity(); } catch { /* game API unavailable */ }
} }
if (spectating) { if (spectating) {
+1
View File
@@ -138,6 +138,7 @@ const SYSTEM_PATTERNS = [
// ── Pre-translation filtering ── // ── Pre-translation filtering ──
function isLatinOnly(text: string): boolean { function isLatinOnly(text: string): boolean {
// eslint-disable-next-line no-control-regex
return /^[\x00-\x7F\u00C0-\u024F\u1E00-\u1EFF\s\d.,!?;:'"()\-/@#$%^&*+=~`[\]{}|\\<>]+$/u.test(text); return /^[\x00-\x7F\u00C0-\u024F\u1E00-\u1EFF\s\d.,!?;:'"()\-/@#$%^&*+=~`[\]{}|\\<>]+$/u.test(text);
} }
+3 -3
View File
@@ -108,7 +108,7 @@ function toggleCSS(css: string, identifier: string, value: boolean): void {
function executeScript( function executeScript(
instance: UserscriptInstance, instance: UserscriptInstance,
_console: { log: Function; warn: Function; error: Function }, _console: { log: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; error: (...args: unknown[]) => void },
): void { ): void {
if (instance.executed) return; if (instance.executed) return;
@@ -164,7 +164,7 @@ export function getInstances(): UserscriptInstance[] {
} }
export async function initUserscripts( export async function initUserscripts(
_console: { log: Function; warn: Function; error: Function }, _console: { log: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; error: (...args: unknown[]) => void },
): Promise<void> { ): Promise<void> {
const { scripts, tracker } = await ipcRenderer.invoke('userscripts-scan'); const { scripts, tracker } = await ipcRenderer.invoke('userscripts-scan');
if (!scripts || scripts.length === 0) { if (!scripts || scripts.length === 0) {
@@ -219,7 +219,7 @@ export async function initUserscripts(
export function setScriptEnabled( export function setScriptEnabled(
filename: string, filename: string,
enabled: boolean, enabled: boolean,
_console: { log: Function; warn: Function; error: Function }, _console: { log: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; error: (...args: unknown[]) => void },
): { needsReload: boolean } { ): { needsReload: boolean } {
const inst = instances.find(i => i.filename === filename); const inst = instances.find(i => i.filename === filename);
if (!inst) return { needsReload: false }; if (!inst) return { needsReload: false };
+1 -1
View File
@@ -13,7 +13,7 @@ export default defineConfig({
outDir: 'dist/preload', outDir: 'dist/preload',
emptyDirBefore: true, emptyDirBefore: true,
rollupOptions: { rollupOptions: {
external: ['electron', 'uuid'], external: ['electron'],
}, },
target: 'node20', target: 'node20',
minify: isProd, minify: isProd,