kitowall 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +41 -9
- package/dist/core/workshop.js +252 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -118,6 +118,8 @@ Commands:
|
|
|
118
118
|
Show system logs (requests/downloads/errors)
|
|
119
119
|
logs clear Clear system logs
|
|
120
120
|
we config set-api-key <key> Save Steam Web API key (~/.config/kitowall/we.json)
|
|
121
|
+
we config get-steam-roots Show configured manual Steam roots
|
|
122
|
+
we config set-steam-roots <a,b,c> Save manual Steam roots (steam root or workshop/content/431960)
|
|
121
123
|
we search [--text <q>] [--tags <a,b>] [--sort <top|newest|trend|subscribed|updated>] [--page <n>] [--page-size <n>] [--days <n>] [--fixtures]
|
|
122
124
|
Search Wallpaper Engine workshop items (appid 431960)
|
|
123
125
|
we details <publishedfileid> [--fixtures]
|
|
@@ -127,6 +129,9 @@ Commands:
|
|
|
127
129
|
we job <job_id> Show one download job
|
|
128
130
|
we jobs [--limit <n>] List recent download jobs
|
|
129
131
|
we library List downloaded workshop items
|
|
132
|
+
we scan-steam Detect Steam workshop folders and list downloaded ids
|
|
133
|
+
we sync-steam Sync local Steam Workshop 431960 items into Kitsune downloads
|
|
134
|
+
we app-status Detect if Wallpaper Engine (AppID 431960) is installed
|
|
130
135
|
we coexist enter|exit|status Temporarily stop/restore wallpaper rotation services
|
|
131
136
|
check [--namespace <ns>] [--json] Quick system check (no changes)
|
|
132
137
|
|
|
@@ -303,14 +308,26 @@ async function main() {
|
|
|
303
308
|
}
|
|
304
309
|
if (action === 'config') {
|
|
305
310
|
const sub = cleanOpt(args[2] ?? null);
|
|
306
|
-
if (sub
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
311
|
+
if (sub === 'set-api-key') {
|
|
312
|
+
const key = cleanOpt(args[3] ?? null);
|
|
313
|
+
if (!key)
|
|
314
|
+
throw new Error('Usage: we config set-api-key <key>');
|
|
315
|
+
(0, workshop_1.setWorkshopApiKey)(key);
|
|
316
|
+
console.log(JSON.stringify({ ok: true, updated: 'steamWebApiKey' }, null, 2));
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (sub === 'get-steam-roots') {
|
|
320
|
+
console.log(JSON.stringify({ ok: true, ...(0, workshop_1.workshopGetSteamRoots)() }, null, 2));
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (sub === 'set-steam-roots') {
|
|
324
|
+
const raw = cleanOpt(args[3] ?? null) ?? '';
|
|
325
|
+
const roots = raw ? raw.split(',').map(v => v.trim()).filter(Boolean) : [];
|
|
326
|
+
const out = (0, workshop_1.workshopSetSteamRoots)(roots);
|
|
327
|
+
console.log(JSON.stringify(out, null, 2));
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
throw new Error('Usage: we config <set-api-key|get-steam-roots|set-steam-roots> ...');
|
|
314
331
|
}
|
|
315
332
|
if (action === 'search') {
|
|
316
333
|
const text = cleanOpt(getOptionValue(args, '--text'));
|
|
@@ -386,6 +403,21 @@ async function main() {
|
|
|
386
403
|
console.log(JSON.stringify(out, null, 2));
|
|
387
404
|
return;
|
|
388
405
|
}
|
|
406
|
+
if (action === 'scan-steam') {
|
|
407
|
+
const out = (0, workshop_1.workshopScanSteamDownloads)();
|
|
408
|
+
console.log(JSON.stringify({ ok: true, ...out }, null, 2));
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
if (action === 'sync-steam') {
|
|
412
|
+
const out = (0, workshop_1.workshopSyncSteamDownloads)();
|
|
413
|
+
console.log(JSON.stringify(out, null, 2));
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (action === 'app-status') {
|
|
417
|
+
const out = (0, workshop_1.workshopWallpaperEngineStatus)();
|
|
418
|
+
console.log(JSON.stringify(out, null, 2));
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
389
421
|
if (action === 'coexist') {
|
|
390
422
|
const sub = cleanOpt(args[2] ?? null);
|
|
391
423
|
if (sub === 'enter') {
|
|
@@ -405,7 +437,7 @@ async function main() {
|
|
|
405
437
|
}
|
|
406
438
|
throw new Error('Usage: we coexist <enter|exit|status>');
|
|
407
439
|
}
|
|
408
|
-
throw new Error('Usage: we <config|search|details|download|job|jobs|library|run-job|coexist> ...');
|
|
440
|
+
throw new Error('Usage: we <config|search|details|download|job|jobs|library|scan-steam|sync-steam|app-status|run-job|coexist> ...');
|
|
409
441
|
}
|
|
410
442
|
// Regular commands (need config/state)
|
|
411
443
|
const config = (0, config_1.loadConfig)();
|
package/dist/core/workshop.js
CHANGED
|
@@ -4,6 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.setWorkshopApiKey = setWorkshopApiKey;
|
|
7
|
+
exports.workshopGetSteamRoots = workshopGetSteamRoots;
|
|
8
|
+
exports.workshopSetSteamRoots = workshopSetSteamRoots;
|
|
9
|
+
exports.workshopWallpaperEngineStatus = workshopWallpaperEngineStatus;
|
|
10
|
+
exports.workshopScanSteamDownloads = workshopScanSteamDownloads;
|
|
11
|
+
exports.workshopSyncSteamDownloads = workshopSyncSteamDownloads;
|
|
7
12
|
exports.workshopSearch = workshopSearch;
|
|
8
13
|
exports.workshopDetails = workshopDetails;
|
|
9
14
|
exports.workshopQueueDownload = workshopQueueDownload;
|
|
@@ -74,6 +79,16 @@ function readWeConfig() {
|
|
|
74
79
|
return {};
|
|
75
80
|
}
|
|
76
81
|
}
|
|
82
|
+
function normalizePath(input) {
|
|
83
|
+
const raw = clean(input) ?? '';
|
|
84
|
+
if (!raw)
|
|
85
|
+
return '';
|
|
86
|
+
if (raw === '~')
|
|
87
|
+
return node_os_1.default.homedir();
|
|
88
|
+
if (raw.startsWith('~/'))
|
|
89
|
+
return node_path_1.default.join(node_os_1.default.homedir(), raw.slice(2));
|
|
90
|
+
return raw;
|
|
91
|
+
}
|
|
77
92
|
function setWorkshopApiKey(apiKey) {
|
|
78
93
|
const key = clean(apiKey);
|
|
79
94
|
if (!key)
|
|
@@ -84,6 +99,20 @@ function setWorkshopApiKey(apiKey) {
|
|
|
84
99
|
(0, fs_1.writeJson)(p, current);
|
|
85
100
|
return { ok: true };
|
|
86
101
|
}
|
|
102
|
+
function workshopGetSteamRoots() {
|
|
103
|
+
const cfg = readWeConfig();
|
|
104
|
+
const steamRoots = Array.isArray(cfg.steamRoots)
|
|
105
|
+
? cfg.steamRoots.map(v => normalizePath(String(v))).filter(Boolean)
|
|
106
|
+
: [];
|
|
107
|
+
return { steam_roots: Array.from(new Set(steamRoots)) };
|
|
108
|
+
}
|
|
109
|
+
function workshopSetSteamRoots(roots) {
|
|
110
|
+
const cfg = readWeConfig();
|
|
111
|
+
const normalized = roots.map(v => normalizePath(String(v))).filter(Boolean);
|
|
112
|
+
cfg.steamRoots = Array.from(new Set(normalized));
|
|
113
|
+
(0, fs_1.writeJson)(getWeConfigPath(), cfg);
|
|
114
|
+
return { ok: true, steam_roots: cfg.steamRoots };
|
|
115
|
+
}
|
|
87
116
|
function getSteamWebApiKey() {
|
|
88
117
|
const envKey = clean(process.env.STEAM_WEB_API_KEY) ?? clean(process.env.KITOWALL_STEAM_WEB_API_KEY);
|
|
89
118
|
if (envKey)
|
|
@@ -322,6 +351,229 @@ function writeMetaFile(meta) {
|
|
|
322
351
|
cached_at: now()
|
|
323
352
|
});
|
|
324
353
|
}
|
|
354
|
+
function readLibraryFoldersVdf(vdfPath) {
|
|
355
|
+
if (!node_fs_1.default.existsSync(vdfPath))
|
|
356
|
+
return [];
|
|
357
|
+
const raw = node_fs_1.default.readFileSync(vdfPath, 'utf8');
|
|
358
|
+
const out = [];
|
|
359
|
+
const lines = raw.split('\n');
|
|
360
|
+
for (const line of lines) {
|
|
361
|
+
const m = line.match(/"path"\s+"([^"]+)"/);
|
|
362
|
+
if (!m || !m[1])
|
|
363
|
+
continue;
|
|
364
|
+
const candidate = m[1].replace(/\\\\/g, '\\');
|
|
365
|
+
out.push(candidate);
|
|
366
|
+
}
|
|
367
|
+
return out;
|
|
368
|
+
}
|
|
369
|
+
function getDefaultSteamRoots() {
|
|
370
|
+
const home = node_os_1.default.homedir();
|
|
371
|
+
const roots = [
|
|
372
|
+
node_path_1.default.join(home, '.steam', 'steam'),
|
|
373
|
+
node_path_1.default.join(home, '.local', 'share', 'Steam'),
|
|
374
|
+
node_path_1.default.join(home, '.var', 'app', 'com.valvesoftware.Steam', '.local', 'share', 'Steam')
|
|
375
|
+
];
|
|
376
|
+
return Array.from(new Set(roots));
|
|
377
|
+
}
|
|
378
|
+
function getManualSteamRoots() {
|
|
379
|
+
const cfg = readWeConfig();
|
|
380
|
+
if (!Array.isArray(cfg.steamRoots))
|
|
381
|
+
return [];
|
|
382
|
+
return cfg.steamRoots.map(v => normalizePath(String(v))).filter(Boolean);
|
|
383
|
+
}
|
|
384
|
+
function detectSteamappsDirs() {
|
|
385
|
+
const steamappsSet = new Set();
|
|
386
|
+
const candidateRoots = [...getDefaultSteamRoots(), ...getManualSteamRoots()];
|
|
387
|
+
for (const root of candidateRoots) {
|
|
388
|
+
const p = normalizePath(root);
|
|
389
|
+
if (!p || !node_fs_1.default.existsSync(p))
|
|
390
|
+
continue;
|
|
391
|
+
if (node_path_1.default.basename(p) === 'steamapps' && node_fs_1.default.existsSync(p)) {
|
|
392
|
+
steamappsSet.add(p);
|
|
393
|
+
}
|
|
394
|
+
const directSteamapps = node_path_1.default.join(p, 'steamapps');
|
|
395
|
+
if (node_fs_1.default.existsSync(directSteamapps)) {
|
|
396
|
+
steamappsSet.add(directSteamapps);
|
|
397
|
+
}
|
|
398
|
+
const marker = `${node_path_1.default.sep}steamapps${node_path_1.default.sep}workshop${node_path_1.default.sep}content${node_path_1.default.sep}${WE_APP_ID}`;
|
|
399
|
+
const idx = p.indexOf(marker);
|
|
400
|
+
if (idx >= 0) {
|
|
401
|
+
const steamapps = p.slice(0, idx + `${node_path_1.default.sep}steamapps`.length);
|
|
402
|
+
if (node_fs_1.default.existsSync(steamapps))
|
|
403
|
+
steamappsSet.add(steamapps);
|
|
404
|
+
}
|
|
405
|
+
const markerNoSteamapps = `${node_path_1.default.sep}workshop${node_path_1.default.sep}content${node_path_1.default.sep}${WE_APP_ID}`;
|
|
406
|
+
const idx2 = p.indexOf(markerNoSteamapps);
|
|
407
|
+
if (idx2 >= 0) {
|
|
408
|
+
const steamappsMaybe = p.slice(0, idx2);
|
|
409
|
+
if (node_path_1.default.basename(steamappsMaybe) === 'steamapps' && node_fs_1.default.existsSync(steamappsMaybe)) {
|
|
410
|
+
steamappsSet.add(steamappsMaybe);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
const baseSteamapps = Array.from(steamappsSet);
|
|
415
|
+
for (const steamapps of baseSteamapps) {
|
|
416
|
+
const vdf = node_path_1.default.join(steamapps, 'libraryfolders.vdf');
|
|
417
|
+
for (const lib of readLibraryFoldersVdf(vdf)) {
|
|
418
|
+
const candidate = node_path_1.default.join(lib, 'steamapps');
|
|
419
|
+
if (node_fs_1.default.existsSync(candidate))
|
|
420
|
+
steamappsSet.add(candidate);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return Array.from(steamappsSet);
|
|
424
|
+
}
|
|
425
|
+
function toWorkshopAppContentDir(inputPath) {
|
|
426
|
+
const p = normalizePath(inputPath);
|
|
427
|
+
if (!p)
|
|
428
|
+
return '';
|
|
429
|
+
const direct = p;
|
|
430
|
+
const asSteamRoot = node_path_1.default.join(p, 'steamapps', 'workshop', 'content', String(WE_APP_ID));
|
|
431
|
+
const asSteamappsRoot = node_path_1.default.join(p, 'workshop', 'content', String(WE_APP_ID));
|
|
432
|
+
const asContentRoot = node_path_1.default.join(p, String(WE_APP_ID));
|
|
433
|
+
if (node_fs_1.default.existsSync(direct) && direct.endsWith(`${node_path_1.default.sep}${WE_APP_ID}`))
|
|
434
|
+
return direct;
|
|
435
|
+
if (node_fs_1.default.existsSync(direct) && direct.includes(`${node_path_1.default.sep}workshop${node_path_1.default.sep}content${node_path_1.default.sep}${WE_APP_ID}`))
|
|
436
|
+
return direct;
|
|
437
|
+
if (node_fs_1.default.existsSync(asSteamRoot))
|
|
438
|
+
return asSteamRoot;
|
|
439
|
+
if (node_fs_1.default.existsSync(asSteamappsRoot))
|
|
440
|
+
return asSteamappsRoot;
|
|
441
|
+
if (node_fs_1.default.existsSync(asContentRoot))
|
|
442
|
+
return asContentRoot;
|
|
443
|
+
return '';
|
|
444
|
+
}
|
|
445
|
+
function detectSteamWorkshopContentDirs() {
|
|
446
|
+
const discovered = new Set();
|
|
447
|
+
for (const root of getManualSteamRoots()) {
|
|
448
|
+
const fromManual = toWorkshopAppContentDir(root);
|
|
449
|
+
if (fromManual && node_fs_1.default.existsSync(fromManual))
|
|
450
|
+
discovered.add(fromManual);
|
|
451
|
+
}
|
|
452
|
+
for (const steamapps of detectSteamappsDirs()) {
|
|
453
|
+
const candidate = node_path_1.default.join(steamapps, 'workshop', 'content', String(WE_APP_ID));
|
|
454
|
+
if (node_fs_1.default.existsSync(candidate)) {
|
|
455
|
+
discovered.add(candidate);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return Array.from(discovered);
|
|
459
|
+
}
|
|
460
|
+
function workshopWallpaperEngineStatus() {
|
|
461
|
+
const manifests = [];
|
|
462
|
+
const steamapps = detectSteamappsDirs();
|
|
463
|
+
for (const dir of steamapps) {
|
|
464
|
+
const manifest = node_path_1.default.join(dir, `appmanifest_${WE_APP_ID}.acf`);
|
|
465
|
+
if (node_fs_1.default.existsSync(manifest))
|
|
466
|
+
manifests.push(manifest);
|
|
467
|
+
}
|
|
468
|
+
return {
|
|
469
|
+
ok: true,
|
|
470
|
+
installed: manifests.length > 0,
|
|
471
|
+
manifests,
|
|
472
|
+
steamapps
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
function findPreviewCandidate(dir) {
|
|
476
|
+
if (!node_fs_1.default.existsSync(dir))
|
|
477
|
+
return undefined;
|
|
478
|
+
const entries = node_fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
479
|
+
const preferred = ['preview.gif', 'preview.webm', 'preview.mp4', 'preview.jpg', 'preview.png', 'preview.webp'];
|
|
480
|
+
for (const name of preferred) {
|
|
481
|
+
const full = node_path_1.default.join(dir, name);
|
|
482
|
+
if (node_fs_1.default.existsSync(full) && node_fs_1.default.statSync(full).isFile())
|
|
483
|
+
return full;
|
|
484
|
+
}
|
|
485
|
+
for (const entry of entries) {
|
|
486
|
+
if (!entry.isFile())
|
|
487
|
+
continue;
|
|
488
|
+
const lower = entry.name.toLowerCase();
|
|
489
|
+
if (lower.startsWith('preview.') || lower.startsWith('thumbnail.')) {
|
|
490
|
+
return node_path_1.default.join(dir, entry.name);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return undefined;
|
|
494
|
+
}
|
|
495
|
+
function workshopScanSteamDownloads() {
|
|
496
|
+
const sources = detectSteamWorkshopContentDirs();
|
|
497
|
+
const idsSet = new Set();
|
|
498
|
+
for (const source of sources) {
|
|
499
|
+
if (!node_fs_1.default.existsSync(source))
|
|
500
|
+
continue;
|
|
501
|
+
const entries = node_fs_1.default.readdirSync(source, { withFileTypes: true });
|
|
502
|
+
for (const entry of entries) {
|
|
503
|
+
if (!entry.isDirectory())
|
|
504
|
+
continue;
|
|
505
|
+
const id = clean(entry.name);
|
|
506
|
+
if (!id)
|
|
507
|
+
continue;
|
|
508
|
+
idsSet.add(id);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
const ids = Array.from(idsSet).sort();
|
|
512
|
+
return { sources, count: ids.length, ids };
|
|
513
|
+
}
|
|
514
|
+
function workshopSyncSteamDownloads() {
|
|
515
|
+
const app = workshopWallpaperEngineStatus();
|
|
516
|
+
if (!app.installed) {
|
|
517
|
+
throw new Error('Wallpaper Engine (AppID 431960) is not installed in Steam. Install it first to sync downloads.');
|
|
518
|
+
}
|
|
519
|
+
const paths = getWePaths();
|
|
520
|
+
ensureWePaths(paths);
|
|
521
|
+
const sources = detectSteamWorkshopContentDirs();
|
|
522
|
+
let imported = 0;
|
|
523
|
+
let skipped = 0;
|
|
524
|
+
const seen = new Set();
|
|
525
|
+
for (const source of sources) {
|
|
526
|
+
if (!node_fs_1.default.existsSync(source))
|
|
527
|
+
continue;
|
|
528
|
+
const entries = node_fs_1.default.readdirSync(source, { withFileTypes: true });
|
|
529
|
+
for (const entry of entries) {
|
|
530
|
+
if (!entry.isDirectory())
|
|
531
|
+
continue;
|
|
532
|
+
const id = clean(entry.name);
|
|
533
|
+
if (!id || seen.has(id))
|
|
534
|
+
continue;
|
|
535
|
+
seen.add(id);
|
|
536
|
+
const sourceDir = node_path_1.default.join(source, id);
|
|
537
|
+
const targetDir = node_path_1.default.join(paths.downloads, id);
|
|
538
|
+
if (node_fs_1.default.existsSync(targetDir)) {
|
|
539
|
+
skipped += 1;
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
node_fs_1.default.cpSync(sourceDir, targetDir, { recursive: true });
|
|
543
|
+
imported += 1;
|
|
544
|
+
}
|
|
545
|
+
const metadataFile = node_path_1.default.join(paths.metadata, `${id}.json`);
|
|
546
|
+
if (!node_fs_1.default.existsSync(metadataFile)) {
|
|
547
|
+
const previewCandidate = findPreviewCandidate(sourceDir);
|
|
548
|
+
let thumbLocal;
|
|
549
|
+
if (previewCandidate && node_fs_1.default.existsSync(previewCandidate)) {
|
|
550
|
+
const ext = node_path_1.default.extname(previewCandidate) || '.jpg';
|
|
551
|
+
const previewDir = node_path_1.default.join(paths.previews, id);
|
|
552
|
+
(0, fs_1.ensureDir)(previewDir);
|
|
553
|
+
thumbLocal = node_path_1.default.join(previewDir, `thumb${ext}`);
|
|
554
|
+
if (!node_fs_1.default.existsSync(thumbLocal)) {
|
|
555
|
+
node_fs_1.default.copyFileSync(previewCandidate, thumbLocal);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
writeMetaFile({
|
|
559
|
+
id,
|
|
560
|
+
title: `Wallpaper ${id}`,
|
|
561
|
+
tags: [],
|
|
562
|
+
preview_thumb_local: thumbLocal,
|
|
563
|
+
author_name: 'Steam Workshop',
|
|
564
|
+
time_updated: Math.floor(Date.now() / 1000)
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return {
|
|
570
|
+
ok: true,
|
|
571
|
+
sources,
|
|
572
|
+
imported,
|
|
573
|
+
skipped,
|
|
574
|
+
total: imported + skipped
|
|
575
|
+
};
|
|
576
|
+
}
|
|
325
577
|
async function workshopSearch(input) {
|
|
326
578
|
const paths = getWePaths();
|
|
327
579
|
ensureWePaths(paths);
|