kitowall 1.0.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/LICENSE.md +46 -0
- package/NOTICE.md +12 -0
- package/README.md +94 -0
- package/TRADEMARKS.md +13 -0
- package/dist/adapters/genericJson.js +216 -0
- package/dist/adapters/localFolder.js +30 -0
- package/dist/adapters/reddit.js +256 -0
- package/dist/adapters/remoteBase.js +2 -0
- package/dist/adapters/selector.js +82 -0
- package/dist/adapters/staticUrl.js +126 -0
- package/dist/adapters/unsplash.js +219 -0
- package/dist/adapters/wallhaven.js +259 -0
- package/dist/cli.js +1101 -0
- package/dist/core/cache.js +283 -0
- package/dist/core/candidates.js +2 -0
- package/dist/core/config.js +367 -0
- package/dist/core/configValidator.js +144 -0
- package/dist/core/controller.js +379 -0
- package/dist/core/doctor.js +224 -0
- package/dist/core/favorites.js +35 -0
- package/dist/core/history.js +34 -0
- package/dist/core/hydrate.js +124 -0
- package/dist/core/init.js +141 -0
- package/dist/core/logs.js +135 -0
- package/dist/core/outputs.js +40 -0
- package/dist/core/remotePack.js +2 -0
- package/dist/core/scheduler.js +24 -0
- package/dist/core/state.js +181 -0
- package/dist/core/systemd.js +65 -0
- package/dist/core/watch.js +78 -0
- package/dist/managers/swww.js +65 -0
- package/dist/utils/exec.js +47 -0
- package/dist/utils/fs.js +70 -0
- package/dist/utils/hash.js +11 -0
- package/dist/utils/jsonPath.js +71 -0
- package/dist/utils/net.js +70 -0
- package/package.json +35 -0
package/dist/utils/fs.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.expandTilde = expandTilde;
|
|
7
|
+
exports.ensureDir = ensureDir;
|
|
8
|
+
exports.readJson = readJson;
|
|
9
|
+
exports.writeJson = writeJson;
|
|
10
|
+
exports.listImagesRecursive = listImagesRecursive;
|
|
11
|
+
exports.shuffle = shuffle;
|
|
12
|
+
// Filesystem helpers.
|
|
13
|
+
const fs_1 = __importDefault(require("fs"));
|
|
14
|
+
const path_1 = __importDefault(require("path"));
|
|
15
|
+
const os_1 = __importDefault(require("os"));
|
|
16
|
+
const IMAGE_EXTENSIONS = new Set([
|
|
17
|
+
'.jpg', '.jpeg', '.png', '.webp', '.bmp', '.gif'
|
|
18
|
+
]);
|
|
19
|
+
function expandTilde(inputPath) {
|
|
20
|
+
if (inputPath.startsWith('~/'))
|
|
21
|
+
return path_1.default.join(os_1.default.homedir(), inputPath.slice(2));
|
|
22
|
+
if (inputPath === '~')
|
|
23
|
+
return os_1.default.homedir();
|
|
24
|
+
return inputPath;
|
|
25
|
+
}
|
|
26
|
+
function ensureDir(dirPath) {
|
|
27
|
+
fs_1.default.mkdirSync(dirPath, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
function readJson(filePath, fallback) {
|
|
30
|
+
if (!fs_1.default.existsSync(filePath))
|
|
31
|
+
return fallback;
|
|
32
|
+
const raw = fs_1.default.readFileSync(filePath, 'utf8');
|
|
33
|
+
return JSON.parse(raw);
|
|
34
|
+
}
|
|
35
|
+
function writeJson(filePath, data) {
|
|
36
|
+
ensureDir(path_1.default.dirname(filePath));
|
|
37
|
+
fs_1.default.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8');
|
|
38
|
+
}
|
|
39
|
+
function listImagesRecursive(pathsToScan) {
|
|
40
|
+
const results = [];
|
|
41
|
+
const walk = (currentPath) => {
|
|
42
|
+
if (!fs_1.default.existsSync(currentPath))
|
|
43
|
+
return;
|
|
44
|
+
const stat = fs_1.default.statSync(currentPath);
|
|
45
|
+
if (stat.isFile()) {
|
|
46
|
+
const ext = path_1.default.extname(currentPath).toLowerCase();
|
|
47
|
+
if (IMAGE_EXTENSIONS.has(ext))
|
|
48
|
+
results.push(currentPath);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (!stat.isDirectory())
|
|
52
|
+
return;
|
|
53
|
+
const entries = fs_1.default.readdirSync(currentPath);
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
const full = path_1.default.join(currentPath, entry);
|
|
56
|
+
walk(full);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
for (const p of pathsToScan)
|
|
60
|
+
walk(expandTilde(p));
|
|
61
|
+
return results;
|
|
62
|
+
}
|
|
63
|
+
function shuffle(arr) {
|
|
64
|
+
const copy = [...arr];
|
|
65
|
+
for (let i = copy.length - 1; i > 0; i--) {
|
|
66
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
67
|
+
[copy[i], copy[j]] = [copy[j], copy[i]];
|
|
68
|
+
}
|
|
69
|
+
return copy;
|
|
70
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.sha256Hex = sha256Hex;
|
|
7
|
+
// Hash helpers.
|
|
8
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
9
|
+
function sha256Hex(input) {
|
|
10
|
+
return crypto_1.default.createHash('sha256').update(input).digest('hex');
|
|
11
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getTarget = getTarget;
|
|
4
|
+
exports.replaceRandomInPath = replaceRandomInPath;
|
|
5
|
+
// Minimal JSONPath helper with @random support.
|
|
6
|
+
const crypto_1 = require("crypto");
|
|
7
|
+
function getTarget(inputObject, inputString) {
|
|
8
|
+
if (!inputObject)
|
|
9
|
+
return [null, ''];
|
|
10
|
+
if (inputString.length === 0)
|
|
11
|
+
return [inputObject, inputString];
|
|
12
|
+
let startDot = inputString.indexOf('.');
|
|
13
|
+
if (startDot === -1)
|
|
14
|
+
startDot = inputString.length;
|
|
15
|
+
let keyString = inputString.slice(0, startDot);
|
|
16
|
+
const inputStringTail = inputString.slice(startDot + 1);
|
|
17
|
+
const startParentheses = keyString.indexOf('[');
|
|
18
|
+
if (startParentheses === -1) {
|
|
19
|
+
const targetObject = getObjectMember(inputObject, keyString);
|
|
20
|
+
if (!targetObject)
|
|
21
|
+
return [null, ''];
|
|
22
|
+
const [object, path] = getTarget(targetObject, inputStringTail);
|
|
23
|
+
return [object, inputString.slice(0, inputString.length - inputStringTail.length) + path];
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
const indexString = keyString.slice(startParentheses + 1, keyString.length - 1);
|
|
27
|
+
keyString = keyString.slice(0, startParentheses);
|
|
28
|
+
const targetObject = getObjectMember(inputObject, keyString);
|
|
29
|
+
if (!targetObject || !Array.isArray(targetObject))
|
|
30
|
+
return [null, ''];
|
|
31
|
+
switch (indexString) {
|
|
32
|
+
case '@random': {
|
|
33
|
+
const [chosenElement, chosenNumber] = randomElement(targetObject);
|
|
34
|
+
const [object, path] = getTarget(chosenElement, inputStringTail);
|
|
35
|
+
return [object, inputString
|
|
36
|
+
.slice(0, inputString.length - inputStringTail.length)
|
|
37
|
+
.replace('@random', String(chosenNumber)) + path];
|
|
38
|
+
}
|
|
39
|
+
default: {
|
|
40
|
+
const [object, path] = getTarget(targetObject[parseInt(indexString)], inputStringTail);
|
|
41
|
+
return [object, inputString.slice(0, inputString.length - inputStringTail.length) + path];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function replaceRandomInPath(randomPath, resolvedPath) {
|
|
47
|
+
if (!randomPath.includes('@random'))
|
|
48
|
+
return randomPath;
|
|
49
|
+
let newPath = randomPath;
|
|
50
|
+
while (newPath.includes('@random')) {
|
|
51
|
+
const startRandom = newPath.indexOf('@random');
|
|
52
|
+
if (newPath.substring(0, startRandom) !== resolvedPath.substring(0, startRandom))
|
|
53
|
+
break;
|
|
54
|
+
const endParenthesis = resolvedPath.indexOf(']', startRandom);
|
|
55
|
+
newPath = newPath.replace('@random', resolvedPath.substring(startRandom, endParenthesis));
|
|
56
|
+
}
|
|
57
|
+
return newPath;
|
|
58
|
+
}
|
|
59
|
+
function getObjectMember(inputObject, keyString) {
|
|
60
|
+
if (keyString === '$')
|
|
61
|
+
return inputObject;
|
|
62
|
+
for (const [key, value] of Object.entries(inputObject)) {
|
|
63
|
+
if (key === keyString)
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
function randomElement(array) {
|
|
69
|
+
const randomNumber = (0, crypto_1.randomInt)(array.length);
|
|
70
|
+
return [array[randomNumber], randomNumber];
|
|
71
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NetworkError = void 0;
|
|
4
|
+
exports.fetchWithRetry = fetchWithRetry;
|
|
5
|
+
class NetworkError extends Error {
|
|
6
|
+
constructor(message, opts) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'NetworkError';
|
|
9
|
+
this.url = opts.url;
|
|
10
|
+
this.status = opts.status;
|
|
11
|
+
this.code = opts.code;
|
|
12
|
+
this.attempt = opts.attempt;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.NetworkError = NetworkError;
|
|
16
|
+
function sleep(ms) {
|
|
17
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
18
|
+
}
|
|
19
|
+
function shouldRetryStatus(status, retryOnStatus) {
|
|
20
|
+
return retryOnStatus.includes(status);
|
|
21
|
+
}
|
|
22
|
+
async function fetchWithRetry(url, init, opts) {
|
|
23
|
+
const timeoutMs = opts?.timeoutMs ?? 15000;
|
|
24
|
+
const retries = opts?.retries ?? 2;
|
|
25
|
+
const backoffMs = opts?.backoffMs ?? 300;
|
|
26
|
+
const retryOnStatus = opts?.retryOnStatus ?? [429, 500, 502, 503, 504];
|
|
27
|
+
let lastError = null;
|
|
28
|
+
for (let attempt = 1; attempt <= retries + 1; attempt++) {
|
|
29
|
+
const controller = new AbortController();
|
|
30
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
31
|
+
try {
|
|
32
|
+
const response = await fetch(url, { ...init, signal: controller.signal });
|
|
33
|
+
if (response.ok)
|
|
34
|
+
return response;
|
|
35
|
+
if (attempt <= retries && shouldRetryStatus(response.status, retryOnStatus)) {
|
|
36
|
+
await sleep(backoffMs * attempt);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const rate = response.headers.get('x-ratelimit-remaining');
|
|
40
|
+
const reset = response.headers.get('x-ratelimit-reset');
|
|
41
|
+
const rateHint = rate !== null || reset !== null
|
|
42
|
+
? ` (rate=${rate ?? 'n/a'}, reset=${reset ?? 'n/a'})`
|
|
43
|
+
: '';
|
|
44
|
+
throw new NetworkError(`HTTP ${response.status}${rateHint}`, {
|
|
45
|
+
url,
|
|
46
|
+
status: response.status,
|
|
47
|
+
attempt
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
const e = err;
|
|
52
|
+
const cause = e?.cause;
|
|
53
|
+
const code = cause?.code;
|
|
54
|
+
const msgParts = [e?.message ?? String(err)];
|
|
55
|
+
if (code)
|
|
56
|
+
msgParts.push(`code=${code}`);
|
|
57
|
+
if (cause?.message)
|
|
58
|
+
msgParts.push(`cause=${cause.message}`);
|
|
59
|
+
lastError = new NetworkError(msgParts.join(' | '), { url, code, attempt });
|
|
60
|
+
if (attempt <= retries) {
|
|
61
|
+
await sleep(backoffMs * attempt);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
throw lastError ?? new NetworkError('fetch failed', { url, attempt: retries + 1 });
|
|
70
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kitowall",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI/daemon for Hyprland wallpapers using swww with pack-based rotation.",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md",
|
|
10
|
+
"LICENSE.md",
|
|
11
|
+
"NOTICE.md",
|
|
12
|
+
"TRADEMARKS.md"
|
|
13
|
+
],
|
|
14
|
+
"bin": {
|
|
15
|
+
"kitowall": "dist/cli.js"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"start": "node dist/cli.js",
|
|
20
|
+
"dev": "tsc --watch",
|
|
21
|
+
"lint": "node dist/cli.js --help",
|
|
22
|
+
"release:check": "npm run build && npm run test:e2e",
|
|
23
|
+
"package:cli": "npm pack",
|
|
24
|
+
"package:ui": "npm --prefix ui run tauri:build",
|
|
25
|
+
"package:all": "npm run release:check && npm run package:cli && npm run package:ui",
|
|
26
|
+
"test:smoke": "bash ./tests/smoke.e2e.sh",
|
|
27
|
+
"test:regression": "bash ./tests/regression.e2e.sh",
|
|
28
|
+
"test:e2e": "npm run test:smoke && npm run test:regression"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^22.10.0",
|
|
33
|
+
"typescript": "^5.4.0"
|
|
34
|
+
}
|
|
35
|
+
}
|