dsh-any-background 0.2.8 → 0.2.9
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/README.md +17 -10
- package/README.zh.md +16 -10
- package/lib/client.js +946 -37
- package/lib/client.js.map +1 -1
- package/lib/index.js +358 -23
- package/package.json +2 -1
package/lib/index.js
CHANGED
|
@@ -27,10 +27,14 @@ const VIDEO_ROUTE = "/dsh-any-background/video";
|
|
|
27
27
|
const UPLOAD_ROUTE = "/dsh-any-background/video/upload";
|
|
28
28
|
const WALLPAPER_ROUTE = "/dsh-any-background/wallpaper";
|
|
29
29
|
const WALLPAPER_UPLOAD_ROUTE = "/dsh-any-background/wallpaper/upload";
|
|
30
|
+
const FONT_ROUTE = "/dsh-any-background/font";
|
|
31
|
+
const FONT_UPLOAD_ROUTE = "/dsh-any-background/font/upload";
|
|
30
32
|
const UPLOAD_TMP = "wallpaper.upload.tmp";
|
|
31
33
|
const VIDEO_UPLOAD_TMP = "video.upload.tmp";
|
|
34
|
+
const FONT_UPLOAD_TMP = "font.upload.tmp";
|
|
32
35
|
const WALLPAPER_UPLOAD_MAX = 104857600;
|
|
33
36
|
const VIDEO_UPLOAD_MAX = 2147483648;
|
|
37
|
+
const FONT_UPLOAD_MAX = 104857600;
|
|
34
38
|
const WALLPAPER_FETCH_MAX = 26214400;
|
|
35
39
|
const WALLPAPER_FETCH_TIMEOUT = 2e4;
|
|
36
40
|
const VIDEO_FETCH_MAX = 2147483648;
|
|
@@ -54,28 +58,100 @@ const VIDEO_CANDIDATES = [
|
|
|
54
58
|
"wallpaper.mkv",
|
|
55
59
|
"wallpaper.video"
|
|
56
60
|
];
|
|
61
|
+
/** Font slot: one font owns the slot, named by format like the video slot. */
|
|
62
|
+
function fontFileName(mime) {
|
|
63
|
+
switch (mime) {
|
|
64
|
+
case "font/woff2": return "font.woff2";
|
|
65
|
+
case "font/woff": return "font.woff";
|
|
66
|
+
case "font/otf": return "font.otf";
|
|
67
|
+
case "font/ttf": return "font.ttf";
|
|
68
|
+
default: return "font.ttf";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const FONT_CANDIDATES = [
|
|
72
|
+
"font.woff2",
|
|
73
|
+
"font.woff",
|
|
74
|
+
"font.otf",
|
|
75
|
+
"font.ttf"
|
|
76
|
+
];
|
|
77
|
+
/** Sniff a font's container format from its leading magic bytes (null when the
|
|
78
|
+
* bytes are not a recognized font — uploads are rejected rather than stored). */
|
|
79
|
+
function sniffFontMime(buf) {
|
|
80
|
+
if (buf.length >= 4 && buf[0] === 119 && buf[1] === 79 && buf[2] === 70 && buf[3] === 50) return "font/woff2";
|
|
81
|
+
if (buf.length >= 4 && buf[0] === 119 && buf[1] === 79 && buf[2] === 70 && buf[3] === 70) return "font/woff";
|
|
82
|
+
if (buf.length >= 4 && buf[0] === 79 && buf[1] === 84 && buf[2] === 84 && buf[3] === 79) return "font/otf";
|
|
83
|
+
if (buf.length >= 4 && buf[0] === 0 && buf[1] === 1 && buf[2] === 0 && buf[3] === 0) return "font/ttf";
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
57
86
|
const DEFAULT_CONFIG = {
|
|
58
87
|
color: null,
|
|
59
88
|
opacities: {
|
|
60
|
-
bg: .
|
|
61
|
-
sidebar: .
|
|
62
|
-
card:
|
|
63
|
-
input:
|
|
89
|
+
bg: .5,
|
|
90
|
+
sidebar: .5,
|
|
91
|
+
card: .5,
|
|
92
|
+
input: .5
|
|
64
93
|
},
|
|
65
94
|
blurs: {
|
|
66
|
-
bg:
|
|
67
|
-
sidebar:
|
|
68
|
-
card:
|
|
69
|
-
settings:
|
|
70
|
-
chat:
|
|
71
|
-
trajectory:
|
|
72
|
-
input:
|
|
73
|
-
panel:
|
|
74
|
-
produced:
|
|
95
|
+
bg: 30,
|
|
96
|
+
sidebar: 30,
|
|
97
|
+
card: 30,
|
|
98
|
+
settings: 30,
|
|
99
|
+
chat: 30,
|
|
100
|
+
trajectory: 30,
|
|
101
|
+
input: 30,
|
|
102
|
+
panel: 30,
|
|
103
|
+
produced: 30
|
|
75
104
|
},
|
|
76
|
-
|
|
105
|
+
strokes: {
|
|
106
|
+
bg: {
|
|
107
|
+
width: 0,
|
|
108
|
+
color: "auto",
|
|
109
|
+
customColor: "#808080"
|
|
110
|
+
},
|
|
111
|
+
sidebar: {
|
|
112
|
+
width: 0,
|
|
113
|
+
color: "auto",
|
|
114
|
+
customColor: "#808080"
|
|
115
|
+
},
|
|
116
|
+
card: {
|
|
117
|
+
width: 0,
|
|
118
|
+
color: "auto",
|
|
119
|
+
customColor: "#808080"
|
|
120
|
+
},
|
|
121
|
+
settings: {
|
|
122
|
+
width: 0,
|
|
123
|
+
color: "auto",
|
|
124
|
+
customColor: "#808080"
|
|
125
|
+
},
|
|
126
|
+
chat: {
|
|
127
|
+
width: 0,
|
|
128
|
+
color: "auto",
|
|
129
|
+
customColor: "#808080"
|
|
130
|
+
},
|
|
131
|
+
trajectory: {
|
|
132
|
+
width: 0,
|
|
133
|
+
color: "auto",
|
|
134
|
+
customColor: "#808080"
|
|
135
|
+
},
|
|
136
|
+
input: {
|
|
137
|
+
width: 0,
|
|
138
|
+
color: "auto",
|
|
139
|
+
customColor: "#808080"
|
|
140
|
+
},
|
|
141
|
+
panel: {
|
|
142
|
+
width: 0,
|
|
143
|
+
color: "auto",
|
|
144
|
+
customColor: "#808080"
|
|
145
|
+
},
|
|
146
|
+
produced: {
|
|
147
|
+
width: 0,
|
|
148
|
+
color: "auto",
|
|
149
|
+
customColor: "#808080"
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
settingsOpacity: .5,
|
|
77
153
|
wallpaperOpacity: 1,
|
|
78
|
-
blur:
|
|
154
|
+
blur: 30,
|
|
79
155
|
bgState: {
|
|
80
156
|
zoom: 1,
|
|
81
157
|
x: 0,
|
|
@@ -93,12 +169,14 @@ const DEFAULT_CONFIG = {
|
|
|
93
169
|
backgroundType: "image",
|
|
94
170
|
bgMode: "fit",
|
|
95
171
|
videoMime: null,
|
|
172
|
+
fontMime: null,
|
|
173
|
+
fontEnabled: true,
|
|
96
174
|
generatedBg: null,
|
|
97
175
|
regenerateOnReload: false,
|
|
98
|
-
chatTextOpacity:
|
|
99
|
-
trajectoryOpacity:
|
|
100
|
-
panelOpacity:
|
|
101
|
-
producedOpacity:
|
|
176
|
+
chatTextOpacity: .5,
|
|
177
|
+
trajectoryOpacity: .5,
|
|
178
|
+
panelOpacity: .5,
|
|
179
|
+
producedOpacity: .5,
|
|
102
180
|
profiles: [],
|
|
103
181
|
rotation: {
|
|
104
182
|
enabled: false,
|
|
@@ -123,6 +201,7 @@ const dataDir = () => dshHomePath(DATA_DIR);
|
|
|
123
201
|
const configPath = () => dshHomePath(DATA_DIR, CONFIG_FILE);
|
|
124
202
|
const wallpaperPath = () => dshHomePath(DATA_DIR, WALLPAPER_FILE);
|
|
125
203
|
const videoPathFor = (mime) => dshHomePath(DATA_DIR, videoFileName(mime));
|
|
204
|
+
const fontPathFor = (mime) => dshHomePath(DATA_DIR, fontFileName(mime));
|
|
126
205
|
const exists = async (p) => {
|
|
127
206
|
try {
|
|
128
207
|
await access(p);
|
|
@@ -162,6 +241,43 @@ async function findVideoFile() {
|
|
|
162
241
|
function clamp(n, lo, hi, def) {
|
|
163
242
|
return typeof n === "number" && isFinite(n) ? Math.min(hi, Math.max(lo, n)) : def;
|
|
164
243
|
}
|
|
244
|
+
/** Locate the stored font: the recorded MIME decides the expected name; stray
|
|
245
|
+
* files from a lost config write are adopted via rename (mirrors the video
|
|
246
|
+
* slot's recovery). */
|
|
247
|
+
async function findFontFile() {
|
|
248
|
+
const mime = (await readConfig()).fontMime ?? "font/ttf";
|
|
249
|
+
const expected = fontPathFor(mime);
|
|
250
|
+
if (await exists(expected)) return {
|
|
251
|
+
path: expected,
|
|
252
|
+
mime
|
|
253
|
+
};
|
|
254
|
+
for (const name of FONT_CANDIDATES) {
|
|
255
|
+
const p = dshHomePath(DATA_DIR, name);
|
|
256
|
+
if (!await exists(p)) continue;
|
|
257
|
+
const foundMime = mimeForFontFile(name);
|
|
258
|
+
try {
|
|
259
|
+
await rename(p, expected);
|
|
260
|
+
return {
|
|
261
|
+
path: expected,
|
|
262
|
+
mime: foundMime
|
|
263
|
+
};
|
|
264
|
+
} catch {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
function mimeForFontFile(name) {
|
|
271
|
+
switch (name) {
|
|
272
|
+
case "font.woff2": return "font/woff2";
|
|
273
|
+
case "font.woff": return "font/woff";
|
|
274
|
+
case "font.otf": return "font/otf";
|
|
275
|
+
default: return "font/ttf";
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
async function fontUrl() {
|
|
279
|
+
return await findFontFile() ? FONT_ROUTE : null;
|
|
280
|
+
}
|
|
165
281
|
function normalizeBgState(s) {
|
|
166
282
|
return {
|
|
167
283
|
zoom: clamp(s.zoom, .1, 10, 1),
|
|
@@ -172,6 +288,40 @@ function normalizeBgState(s) {
|
|
|
172
288
|
};
|
|
173
289
|
}
|
|
174
290
|
/** Coerce an unknown persisted value into a valid ThemeConfig, falling back per-field. */
|
|
291
|
+
const STROKE_GROUPS = [
|
|
292
|
+
"bg",
|
|
293
|
+
"sidebar",
|
|
294
|
+
"card",
|
|
295
|
+
"settings",
|
|
296
|
+
"chat",
|
|
297
|
+
"trajectory",
|
|
298
|
+
"input",
|
|
299
|
+
"panel",
|
|
300
|
+
"produced"
|
|
301
|
+
];
|
|
302
|
+
const STROKE_COLOR_KEYS = [
|
|
303
|
+
"auto",
|
|
304
|
+
"gray",
|
|
305
|
+
"black",
|
|
306
|
+
"white",
|
|
307
|
+
"theme",
|
|
308
|
+
"custom"
|
|
309
|
+
];
|
|
310
|
+
const HEX_RE = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
311
|
+
function normalizeStroke(raw) {
|
|
312
|
+
const s = raw ?? {};
|
|
313
|
+
return {
|
|
314
|
+
width: clamp(s.width, 0, 4, 0),
|
|
315
|
+
color: STROKE_COLOR_KEYS.includes(s.color) ? s.color : "auto",
|
|
316
|
+
customColor: typeof s.customColor === "string" && HEX_RE.test(s.customColor) ? s.customColor : "#808080"
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function normalizeStrokes(raw) {
|
|
320
|
+
const s = raw ?? {};
|
|
321
|
+
const out = {};
|
|
322
|
+
for (const k of STROKE_GROUPS) out[k] = normalizeStroke(s[k]);
|
|
323
|
+
return out;
|
|
324
|
+
}
|
|
175
325
|
function normalizeConfig(raw) {
|
|
176
326
|
const r = raw ?? {};
|
|
177
327
|
const c = r.color;
|
|
@@ -220,6 +370,7 @@ function normalizeConfig(raw) {
|
|
|
220
370
|
input: clamp(ops.input, 0, 1, DEFAULT_CONFIG.opacities.input)
|
|
221
371
|
},
|
|
222
372
|
blurs,
|
|
373
|
+
strokes: normalizeStrokes(r.strokes),
|
|
223
374
|
settingsOpacity: clamp(r.settingsOpacity, 0, 1, DEFAULT_CONFIG.settingsOpacity),
|
|
224
375
|
wallpaperOpacity: clamp(r.wallpaperOpacity, 0, 1, DEFAULT_CONFIG.wallpaperOpacity),
|
|
225
376
|
blur: clamp(r.blur, 0, 60, DEFAULT_CONFIG.blur),
|
|
@@ -228,6 +379,8 @@ function normalizeConfig(raw) {
|
|
|
228
379
|
backgroundType: bgType,
|
|
229
380
|
bgMode,
|
|
230
381
|
videoMime: typeof r.videoMime === "string" ? r.videoMime : null,
|
|
382
|
+
fontMime: typeof r.fontMime === "string" ? r.fontMime : null,
|
|
383
|
+
fontEnabled: typeof r.fontEnabled === "boolean" ? r.fontEnabled : DEFAULT_CONFIG.fontEnabled,
|
|
231
384
|
generatedBg,
|
|
232
385
|
regenerateOnReload: typeof r.regenerateOnReload === "boolean" ? r.regenerateOnReload : DEFAULT_CONFIG.regenerateOnReload,
|
|
233
386
|
chatTextOpacity: clamp(r.chatTextOpacity, 0, 1, DEFAULT_CONFIG.chatTextOpacity),
|
|
@@ -311,6 +464,7 @@ function normalizeProfileAppearance(raw) {
|
|
|
311
464
|
input: clamp(ops.input, 0, 1, DEFAULT_CONFIG.opacities.input)
|
|
312
465
|
},
|
|
313
466
|
blurs,
|
|
467
|
+
strokes: normalizeStrokes(a.strokes),
|
|
314
468
|
settingsOpacity: clamp(a.settingsOpacity, 0, 1, DEFAULT_CONFIG.settingsOpacity),
|
|
315
469
|
wallpaperOpacity: clamp(a.wallpaperOpacity, 0, 1, DEFAULT_CONFIG.wallpaperOpacity),
|
|
316
470
|
blur: clamp(a.blur, 0, 60, DEFAULT_CONFIG.blur),
|
|
@@ -406,7 +560,11 @@ function warnUnknownConfigKeys(raw, normalized) {
|
|
|
406
560
|
if (known.has(key) || LEGACY_CONFIG_KEYS.has(key)) continue;
|
|
407
561
|
warn(key);
|
|
408
562
|
}
|
|
409
|
-
for (const group of [
|
|
563
|
+
for (const group of [
|
|
564
|
+
"blurs",
|
|
565
|
+
"opacities",
|
|
566
|
+
"strokes"
|
|
567
|
+
]) {
|
|
410
568
|
const got = r[group];
|
|
411
569
|
if (got === null || typeof got !== "object") continue;
|
|
412
570
|
const have = new Set(Object.keys(normalized[group]));
|
|
@@ -1123,6 +1281,164 @@ async function handleWallpaperUpload(req, res) {
|
|
|
1123
1281
|
} catch {}
|
|
1124
1282
|
}
|
|
1125
1283
|
}
|
|
1284
|
+
/** Stream the stored custom font: sniffed MIME, no caching (uploads replace
|
|
1285
|
+
* the file in place). Fonts need no Range support — the browser fetches once. */
|
|
1286
|
+
async function serveFont(req, res) {
|
|
1287
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
1288
|
+
res.writeHead(405, { "Content-Type": "application/json" });
|
|
1289
|
+
res.end(JSON.stringify({
|
|
1290
|
+
ok: false,
|
|
1291
|
+
error: "font route only serves GET/HEAD"
|
|
1292
|
+
}));
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
try {
|
|
1296
|
+
const found = await findFontFile();
|
|
1297
|
+
if (found === null) {
|
|
1298
|
+
res.writeHead(404);
|
|
1299
|
+
res.end("no custom font stored");
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
const st = await stat(found.path);
|
|
1303
|
+
res.writeHead(200, {
|
|
1304
|
+
"Content-Type": found.mime,
|
|
1305
|
+
"Content-Length": st.size,
|
|
1306
|
+
"Cache-Control": "no-store"
|
|
1307
|
+
});
|
|
1308
|
+
if (req.method === "HEAD") {
|
|
1309
|
+
res.end();
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
createReadStream(found.path).pipe(res);
|
|
1313
|
+
} catch (e) {
|
|
1314
|
+
console.error("dsh-any-background: failed to serve the custom font", e);
|
|
1315
|
+
try {
|
|
1316
|
+
res.writeHead(500);
|
|
1317
|
+
res.end();
|
|
1318
|
+
} catch {}
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
/** Accept a raw font upload (POST): pipe the body into a temp file, sniff the
|
|
1322
|
+
* container format from its magic bytes (rejecting anything that is not a
|
|
1323
|
+
* recognizable font), then rename it into the format-derived slot and record
|
|
1324
|
+
* the MIME in the config so serve/find resolve immediately. */
|
|
1325
|
+
async function handleFontUpload(req, res) {
|
|
1326
|
+
if (req.method !== "POST") {
|
|
1327
|
+
res.writeHead(405);
|
|
1328
|
+
res.end();
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
try {
|
|
1332
|
+
await ensureDir();
|
|
1333
|
+
const tmp = dshHomePath(DATA_DIR, FONT_UPLOAD_TMP);
|
|
1334
|
+
const out = createWriteStream(tmp);
|
|
1335
|
+
let received = 0;
|
|
1336
|
+
let failed = false;
|
|
1337
|
+
const fail = () => {
|
|
1338
|
+
if (failed) return;
|
|
1339
|
+
failed = true;
|
|
1340
|
+
out.destroy();
|
|
1341
|
+
rm(tmp, { force: true });
|
|
1342
|
+
};
|
|
1343
|
+
req.on("aborted", fail);
|
|
1344
|
+
req.on("error", fail);
|
|
1345
|
+
out.on("error", () => {
|
|
1346
|
+
fail();
|
|
1347
|
+
try {
|
|
1348
|
+
res.writeHead(500);
|
|
1349
|
+
res.end();
|
|
1350
|
+
} catch {}
|
|
1351
|
+
});
|
|
1352
|
+
req.on("data", (chunk) => {
|
|
1353
|
+
received += chunk.byteLength;
|
|
1354
|
+
if (received > FONT_UPLOAD_MAX) {
|
|
1355
|
+
req.destroy();
|
|
1356
|
+
fail();
|
|
1357
|
+
}
|
|
1358
|
+
});
|
|
1359
|
+
req.pipe(out);
|
|
1360
|
+
out.on("finish", async () => {
|
|
1361
|
+
if (failed) return;
|
|
1362
|
+
try {
|
|
1363
|
+
if (received === 0) {
|
|
1364
|
+
fail();
|
|
1365
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1366
|
+
res.end(JSON.stringify({
|
|
1367
|
+
ok: false,
|
|
1368
|
+
error: "empty upload"
|
|
1369
|
+
}));
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
const mime = sniffFontMime(await new Promise((resolve, reject) => {
|
|
1373
|
+
const chunks = [];
|
|
1374
|
+
const s = createReadStream(tmp, {
|
|
1375
|
+
start: 0,
|
|
1376
|
+
end: 15
|
|
1377
|
+
});
|
|
1378
|
+
s.on("data", (c) => chunks.push(c));
|
|
1379
|
+
s.on("end", () => resolve(Buffer.concat(chunks)));
|
|
1380
|
+
s.on("error", reject);
|
|
1381
|
+
}));
|
|
1382
|
+
if (mime === null) {
|
|
1383
|
+
fail();
|
|
1384
|
+
res.writeHead(415, { "Content-Type": "application/json" });
|
|
1385
|
+
res.end(JSON.stringify({
|
|
1386
|
+
ok: false,
|
|
1387
|
+
error: "not a font (expected ttf/otf/woff/woff2)"
|
|
1388
|
+
}));
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
const target = fontPathFor(mime);
|
|
1392
|
+
for (const name of FONT_CANDIDATES) {
|
|
1393
|
+
const p = dshHomePath(DATA_DIR, name);
|
|
1394
|
+
if (p !== target) await rm(p, { force: true });
|
|
1395
|
+
}
|
|
1396
|
+
await rm(target, { force: true });
|
|
1397
|
+
await rename(tmp, target);
|
|
1398
|
+
const cfg = await readConfig();
|
|
1399
|
+
if (cfg.fontMime !== mime) {
|
|
1400
|
+
cfg.fontMime = mime;
|
|
1401
|
+
await writeConfig(cfg);
|
|
1402
|
+
}
|
|
1403
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1404
|
+
res.end(JSON.stringify({
|
|
1405
|
+
ok: true,
|
|
1406
|
+
fontUrl: FONT_ROUTE,
|
|
1407
|
+
mime
|
|
1408
|
+
}));
|
|
1409
|
+
} catch (e) {
|
|
1410
|
+
console.error("dsh-any-background: failed to finalize the font upload", e);
|
|
1411
|
+
rm(tmp, { force: true });
|
|
1412
|
+
try {
|
|
1413
|
+
res.writeHead(500);
|
|
1414
|
+
res.end();
|
|
1415
|
+
} catch {}
|
|
1416
|
+
}
|
|
1417
|
+
});
|
|
1418
|
+
} catch (e) {
|
|
1419
|
+
console.error("dsh-any-background: failed to accept the font upload", e);
|
|
1420
|
+
try {
|
|
1421
|
+
res.writeHead(500);
|
|
1422
|
+
res.end();
|
|
1423
|
+
} catch {}
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
/** Remove every stored font variant and clear the recorded MIME. */
|
|
1427
|
+
async function removeFontFile() {
|
|
1428
|
+
try {
|
|
1429
|
+
for (const name of FONT_CANDIDATES) await rm(dshHomePath(DATA_DIR, name), { force: true });
|
|
1430
|
+
await rm(dshHomePath(DATA_DIR, FONT_UPLOAD_TMP), { force: true });
|
|
1431
|
+
const cfg = await readConfig();
|
|
1432
|
+
if (cfg.fontMime !== null) {
|
|
1433
|
+
cfg.fontMime = null;
|
|
1434
|
+
await writeConfig(cfg);
|
|
1435
|
+
}
|
|
1436
|
+
return true;
|
|
1437
|
+
} catch (e) {
|
|
1438
|
+
console.error("dsh-any-background: failed to remove the custom font", e);
|
|
1439
|
+
return false;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1126
1442
|
const NS = "dshAnyBackground";
|
|
1127
1443
|
const RPC_CHANNEL = "/dsh-any-background";
|
|
1128
1444
|
const RPC_BODY_MAX = 314572800;
|
|
@@ -1177,13 +1493,18 @@ async function handleRpcMethod(endpoint, payload) {
|
|
|
1177
1493
|
switch (method) {
|
|
1178
1494
|
case "read": {
|
|
1179
1495
|
const rotated = await advanceRotationIfDue();
|
|
1496
|
+
const config = await readConfig();
|
|
1497
|
+
const firstRun = !await exists(configPath());
|
|
1498
|
+
if (firstRun) await writeConfig(config);
|
|
1180
1499
|
return {
|
|
1181
1500
|
ok: true,
|
|
1182
1501
|
value: {
|
|
1183
|
-
config
|
|
1502
|
+
config,
|
|
1184
1503
|
wallpaperUrl: await wallpaperServeUrl(),
|
|
1185
1504
|
videoUrl: await videoUrl(),
|
|
1186
|
-
|
|
1505
|
+
fontUrl: await fontUrl(),
|
|
1506
|
+
rotated,
|
|
1507
|
+
firstRun
|
|
1187
1508
|
}
|
|
1188
1509
|
};
|
|
1189
1510
|
}
|
|
@@ -1219,6 +1540,10 @@ async function handleRpcMethod(endpoint, payload) {
|
|
|
1219
1540
|
ok: true,
|
|
1220
1541
|
value: await handleRotationSet(payload)
|
|
1221
1542
|
};
|
|
1543
|
+
case "removeFont": return {
|
|
1544
|
+
ok: true,
|
|
1545
|
+
value: await removeFontFile()
|
|
1546
|
+
};
|
|
1222
1547
|
default: return {
|
|
1223
1548
|
ok: false,
|
|
1224
1549
|
error: {
|
|
@@ -1364,7 +1689,17 @@ function apply(ctx) {
|
|
|
1364
1689
|
path: WALLPAPER_UPLOAD_ROUTE,
|
|
1365
1690
|
handler: fenceUpload(handleWallpaperUpload)
|
|
1366
1691
|
}), "dsh-any-background: wallpaper upload route");
|
|
1692
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
1693
|
+
kind: "prefix",
|
|
1694
|
+
path: FONT_ROUTE,
|
|
1695
|
+
handler: serveFont
|
|
1696
|
+
}), "dsh-any-background: font route");
|
|
1697
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
1698
|
+
kind: "exact",
|
|
1699
|
+
path: FONT_UPLOAD_ROUTE,
|
|
1700
|
+
handler: fenceUpload(handleFontUpload)
|
|
1701
|
+
}), "dsh-any-background: font upload route");
|
|
1367
1702
|
});
|
|
1368
1703
|
}
|
|
1369
1704
|
//#endregion
|
|
1370
|
-
export { apply, inject, name };
|
|
1705
|
+
export { DEFAULT_CONFIG, apply, inject, name };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-any-background",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "Appearance plugin for DeepSeek Harness: custom theme color (PS-style color wheel), background wallpaper with opacity/blur controls, and separate main/settings interface opacity.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -103,6 +103,7 @@
|
|
|
103
103
|
},
|
|
104
104
|
"dsh": {
|
|
105
105
|
"compatibility": {
|
|
106
|
+
"dsh": "0.1.5-rc.2",
|
|
106
107
|
"dshReleases": {
|
|
107
108
|
"0.1.5-rc.2": "compatible"
|
|
108
109
|
}
|