dsh-any-background 0.2.3 → 0.2.5
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 +150 -142
- package/README.zh.md +150 -142
- package/lib/client.js +1508 -76
- package/lib/client.js.map +1 -1
- package/lib/index.js +455 -54
- package/package.json +10 -12
package/lib/index.js
CHANGED
|
@@ -21,6 +21,7 @@ const inject = ["connection", "webServer"];
|
|
|
21
21
|
const DATA_DIR = ".dsh-any-background-data";
|
|
22
22
|
const CONFIG_FILE = "theme-config.json";
|
|
23
23
|
const WALLPAPER_FILE = "wallpaper.jpg";
|
|
24
|
+
const ROTATION_DIR = "rotation";
|
|
24
25
|
const VIDEO_ROUTE = "/dsh-any-background/video";
|
|
25
26
|
const UPLOAD_ROUTE = "/dsh-any-background/video/upload";
|
|
26
27
|
const UPLOAD_TMP = "wallpaper.upload.tmp";
|
|
@@ -84,7 +85,26 @@ const DEFAULT_CONFIG = {
|
|
|
84
85
|
generatedBg: null,
|
|
85
86
|
regenerateOnReload: false,
|
|
86
87
|
chatTextOpacity: 0,
|
|
87
|
-
trajectoryOpacity: 1
|
|
88
|
+
trajectoryOpacity: 1,
|
|
89
|
+
profiles: [],
|
|
90
|
+
rotation: {
|
|
91
|
+
enabled: false,
|
|
92
|
+
mode: "shuffle",
|
|
93
|
+
interval: "daily",
|
|
94
|
+
current: 0,
|
|
95
|
+
items: [],
|
|
96
|
+
lastRotate: null
|
|
97
|
+
},
|
|
98
|
+
schedule: {
|
|
99
|
+
enabled: false,
|
|
100
|
+
mode: "time",
|
|
101
|
+
dayProfile: null,
|
|
102
|
+
nightProfile: null,
|
|
103
|
+
dayStart: "07:00",
|
|
104
|
+
nightStart: "19:00"
|
|
105
|
+
},
|
|
106
|
+
schemeOverride: "auto",
|
|
107
|
+
activeProfile: null
|
|
88
108
|
};
|
|
89
109
|
const dataDir = () => dshHomePath(DATA_DIR);
|
|
90
110
|
const configPath = () => dshHomePath(DATA_DIR, CONFIG_FILE);
|
|
@@ -196,7 +216,12 @@ function normalizeConfig(raw) {
|
|
|
196
216
|
generatedBg,
|
|
197
217
|
regenerateOnReload: typeof r.regenerateOnReload === "boolean" ? r.regenerateOnReload : DEFAULT_CONFIG.regenerateOnReload,
|
|
198
218
|
chatTextOpacity: clamp(r.chatTextOpacity, 0, 1, DEFAULT_CONFIG.chatTextOpacity),
|
|
199
|
-
trajectoryOpacity: clamp(r.trajectoryOpacity, 0, 1, DEFAULT_CONFIG.trajectoryOpacity)
|
|
219
|
+
trajectoryOpacity: clamp(r.trajectoryOpacity, 0, 1, DEFAULT_CONFIG.trajectoryOpacity),
|
|
220
|
+
profiles: normalizeProfiles(r.profiles),
|
|
221
|
+
rotation: normalizeRotation(r.rotation),
|
|
222
|
+
schedule: normalizeSchedule(r.schedule),
|
|
223
|
+
schemeOverride: r.schemeOverride === "light" || r.schemeOverride === "dark" ? r.schemeOverride : "auto",
|
|
224
|
+
activeProfile: typeof r.activeProfile === "string" ? r.activeProfile : null
|
|
200
225
|
};
|
|
201
226
|
}
|
|
202
227
|
function normalizeGeneratedBg(p) {
|
|
@@ -230,6 +255,101 @@ function normalizeGeneratedBg(p) {
|
|
|
230
255
|
};
|
|
231
256
|
return null;
|
|
232
257
|
}
|
|
258
|
+
const MAX_PROFILES = 20;
|
|
259
|
+
const MAX_ROTATION_ITEMS = 30;
|
|
260
|
+
const MAX_THUMB_BYTES = 65536;
|
|
261
|
+
const HHMM_RE = /^([01]\d|2[0-3]):[0-5]\d$/;
|
|
262
|
+
/** Coerce an unknown value into a ProfileAppearance (appearance subset only). */
|
|
263
|
+
function normalizeProfileAppearance(raw) {
|
|
264
|
+
const a = raw ?? {};
|
|
265
|
+
const c = a.color;
|
|
266
|
+
const ops = a.opacities ?? {};
|
|
267
|
+
const bl = a.blurs ?? {};
|
|
268
|
+
const blurs = {};
|
|
269
|
+
for (const k of [
|
|
270
|
+
"bg",
|
|
271
|
+
"sidebar",
|
|
272
|
+
"card",
|
|
273
|
+
"settings",
|
|
274
|
+
"chat",
|
|
275
|
+
"trajectory",
|
|
276
|
+
"input"
|
|
277
|
+
]) blurs[k] = clamp(bl[k], 0, 60, DEFAULT_CONFIG.blurs[k]);
|
|
278
|
+
return {
|
|
279
|
+
color: Array.isArray(c) && c.length === 3 && c.every((x) => typeof x === "number" && isFinite(x)) ? [
|
|
280
|
+
clamp(c[0], 0, 360, 220),
|
|
281
|
+
clamp(c[1], 0, 1, .55),
|
|
282
|
+
clamp(c[2], 0, 1, .25)
|
|
283
|
+
] : null,
|
|
284
|
+
opacities: {
|
|
285
|
+
bg: clamp(ops.bg, 0, 1, DEFAULT_CONFIG.opacities.bg),
|
|
286
|
+
sidebar: clamp(ops.sidebar, 0, 1, DEFAULT_CONFIG.opacities.sidebar),
|
|
287
|
+
card: clamp(ops.card, 0, 1, DEFAULT_CONFIG.opacities.card),
|
|
288
|
+
input: clamp(ops.input, 0, 1, DEFAULT_CONFIG.opacities.input)
|
|
289
|
+
},
|
|
290
|
+
blurs,
|
|
291
|
+
settingsOpacity: clamp(a.settingsOpacity, 0, 1, DEFAULT_CONFIG.settingsOpacity),
|
|
292
|
+
wallpaperOpacity: clamp(a.wallpaperOpacity, 0, 1, DEFAULT_CONFIG.wallpaperOpacity),
|
|
293
|
+
blur: clamp(a.blur, 0, 60, DEFAULT_CONFIG.blur),
|
|
294
|
+
chatTextOpacity: clamp(a.chatTextOpacity, 0, 1, DEFAULT_CONFIG.chatTextOpacity),
|
|
295
|
+
trajectoryOpacity: clamp(a.trajectoryOpacity, 0, 1, DEFAULT_CONFIG.trajectoryOpacity)
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function normalizeProfiles(raw) {
|
|
299
|
+
if (!Array.isArray(raw)) return [];
|
|
300
|
+
const out = [];
|
|
301
|
+
for (const item of raw.slice(0, MAX_PROFILES)) {
|
|
302
|
+
const p = item ?? {};
|
|
303
|
+
if (typeof p.id !== "string" || p.id.length === 0 || p.id.length > 64) continue;
|
|
304
|
+
if (out.some((e) => e.id === p.id)) continue;
|
|
305
|
+
out.push({
|
|
306
|
+
id: p.id,
|
|
307
|
+
name: typeof p.name === "string" && p.name.trim() ? p.name.slice(0, 60) : "Profile",
|
|
308
|
+
createdAt: typeof p.createdAt === "string" ? p.createdAt : "",
|
|
309
|
+
config: normalizeProfileAppearance(p.config)
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
return out;
|
|
313
|
+
}
|
|
314
|
+
/** Rotation items live as files under the rotation dir; only the server
|
|
315
|
+
* creates those names, so a stored `file` is accepted only when it is a bare
|
|
316
|
+
* filename with a known image extension (no path traversal). */
|
|
317
|
+
function safeRotationFile(name) {
|
|
318
|
+
if (typeof name !== "string" || !/^[\w-]+\.(jpg|jpeg|png|gif|webp)$/i.test(name)) return null;
|
|
319
|
+
return name;
|
|
320
|
+
}
|
|
321
|
+
function normalizeRotation(raw) {
|
|
322
|
+
const r = raw ?? {};
|
|
323
|
+
const items = [];
|
|
324
|
+
if (Array.isArray(r.items)) for (const item of r.items.slice(0, MAX_ROTATION_ITEMS)) {
|
|
325
|
+
const it = item ?? {};
|
|
326
|
+
const file = safeRotationFile(it.file);
|
|
327
|
+
if (file === null) continue;
|
|
328
|
+
items.push({
|
|
329
|
+
file,
|
|
330
|
+
thumb: typeof it.thumb === "string" && it.thumb.startsWith("data:image/") && it.thumb.length <= MAX_THUMB_BYTES ? it.thumb : ""
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
enabled: r.enabled === true,
|
|
335
|
+
mode: r.mode === "order" ? "order" : "shuffle",
|
|
336
|
+
interval: r.interval === "reload" || r.interval === "weekly" ? r.interval : "daily",
|
|
337
|
+
current: typeof r.current === "number" && isFinite(r.current) && r.current >= 0 ? Math.floor(r.current) : 0,
|
|
338
|
+
items,
|
|
339
|
+
lastRotate: typeof r.lastRotate === "string" ? r.lastRotate : null
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
function normalizeSchedule(raw) {
|
|
343
|
+
const r = raw ?? {};
|
|
344
|
+
return {
|
|
345
|
+
enabled: r.enabled === true,
|
|
346
|
+
mode: r.mode === "system" ? "system" : "time",
|
|
347
|
+
dayProfile: typeof r.dayProfile === "string" ? r.dayProfile : null,
|
|
348
|
+
nightProfile: typeof r.nightProfile === "string" ? r.nightProfile : null,
|
|
349
|
+
dayStart: typeof r.dayStart === "string" && HHMM_RE.test(r.dayStart) ? r.dayStart : DEFAULT_CONFIG.schedule.dayStart,
|
|
350
|
+
nightStart: typeof r.nightStart === "string" && HHMM_RE.test(r.nightStart) ? r.nightStart : DEFAULT_CONFIG.schedule.nightStart
|
|
351
|
+
};
|
|
352
|
+
}
|
|
233
353
|
async function ensureDir() {
|
|
234
354
|
try {
|
|
235
355
|
await mkdir(dataDir(), { recursive: true });
|
|
@@ -258,7 +378,8 @@ async function writeConfig(config) {
|
|
|
258
378
|
}
|
|
259
379
|
async function readWallpaper() {
|
|
260
380
|
try {
|
|
261
|
-
|
|
381
|
+
const buf = await readFile(wallpaperPath());
|
|
382
|
+
return `data:${sniffImageMime(buf)};base64,${buf.toString("base64")}`;
|
|
262
383
|
} catch {
|
|
263
384
|
return null;
|
|
264
385
|
}
|
|
@@ -368,6 +489,132 @@ async function writeWallpaperFromUrl(url) {
|
|
|
368
489
|
error: "write failed"
|
|
369
490
|
};
|
|
370
491
|
}
|
|
492
|
+
const rotationDir = () => dshHomePath(DATA_DIR, ROTATION_DIR);
|
|
493
|
+
async function ensureRotationDir() {
|
|
494
|
+
try {
|
|
495
|
+
await mkdir(rotationDir(), { recursive: true });
|
|
496
|
+
} catch {}
|
|
497
|
+
}
|
|
498
|
+
function imageExtFor(mime) {
|
|
499
|
+
if (mime === "image/png") return "png";
|
|
500
|
+
if (mime === "image/gif") return "gif";
|
|
501
|
+
if (mime === "image/webp") return "webp";
|
|
502
|
+
return "jpg";
|
|
503
|
+
}
|
|
504
|
+
/** Accept only an inline base64 image data URL (same fence as writeWallpaper). */
|
|
505
|
+
function decodeImageDataUrl(dataUrl) {
|
|
506
|
+
if (typeof dataUrl !== "string") return null;
|
|
507
|
+
const m = /^data:image\/[a-zA-Z0-9.+-]+;base64,([A-Za-z0-9+/=]+)$/.exec(dataUrl);
|
|
508
|
+
if (!m) return null;
|
|
509
|
+
const buf = Buffer.from(m[1], "base64");
|
|
510
|
+
return buf.length > 0 ? buf : null;
|
|
511
|
+
}
|
|
512
|
+
/** Persist a thumbnail string only when it is a small inline image data URL. */
|
|
513
|
+
function sanitizeThumb(thumb) {
|
|
514
|
+
return typeof thumb === "string" && thumb.startsWith("data:image/") && thumb.length <= MAX_THUMB_BYTES ? thumb : "";
|
|
515
|
+
}
|
|
516
|
+
async function handleRotationAdd(payload) {
|
|
517
|
+
const buf = decodeImageDataUrl(payload?.dataUrl);
|
|
518
|
+
if (buf === null) return {
|
|
519
|
+
ok: false,
|
|
520
|
+
error: "invalid image"
|
|
521
|
+
};
|
|
522
|
+
const thumb = sanitizeThumb(payload?.thumb);
|
|
523
|
+
await ensureDir();
|
|
524
|
+
await ensureRotationDir();
|
|
525
|
+
if ((await readConfig()).rotation.items.length >= MAX_ROTATION_ITEMS) return {
|
|
526
|
+
ok: false,
|
|
527
|
+
error: "too many items"
|
|
528
|
+
};
|
|
529
|
+
const file = `wp-${Date.now().toString(36)}.${imageExtFor(sniffImageMime(buf))}`;
|
|
530
|
+
try {
|
|
531
|
+
await writeFile(dshHomePath(DATA_DIR, ROTATION_DIR, file), buf);
|
|
532
|
+
} catch (e) {
|
|
533
|
+
console.error("dsh-any-background: failed to write a rotation wallpaper", e);
|
|
534
|
+
return {
|
|
535
|
+
ok: false,
|
|
536
|
+
error: "write failed"
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
const fresh = await readConfig();
|
|
540
|
+
fresh.rotation.items.push({
|
|
541
|
+
file,
|
|
542
|
+
thumb
|
|
543
|
+
});
|
|
544
|
+
if (!await writeConfig(fresh)) return {
|
|
545
|
+
ok: false,
|
|
546
|
+
error: "config write failed"
|
|
547
|
+
};
|
|
548
|
+
return {
|
|
549
|
+
ok: true,
|
|
550
|
+
index: fresh.rotation.items.length - 1,
|
|
551
|
+
items: fresh.rotation.items
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
async function handleRotationRemove(payload) {
|
|
555
|
+
const idx = payload?.index;
|
|
556
|
+
if (typeof idx !== "number" || !isFinite(idx)) return {
|
|
557
|
+
ok: false,
|
|
558
|
+
error: "invalid index"
|
|
559
|
+
};
|
|
560
|
+
const cfg = await readConfig();
|
|
561
|
+
const i = Math.floor(idx);
|
|
562
|
+
if (i < 0 || i >= cfg.rotation.items.length) return {
|
|
563
|
+
ok: false,
|
|
564
|
+
error: "not found"
|
|
565
|
+
};
|
|
566
|
+
const [removed] = cfg.rotation.items.splice(i, 1);
|
|
567
|
+
cfg.rotation.current = Math.max(0, Math.min(cfg.rotation.current >= i ? cfg.rotation.current - 1 : cfg.rotation.current, Math.max(0, cfg.rotation.items.length - 1)));
|
|
568
|
+
if (removed !== void 0) try {
|
|
569
|
+
await rm(dshHomePath(DATA_DIR, ROTATION_DIR, removed.file), { force: true });
|
|
570
|
+
} catch {}
|
|
571
|
+
if (!await writeConfig(cfg)) return {
|
|
572
|
+
ok: false,
|
|
573
|
+
error: "config write failed"
|
|
574
|
+
};
|
|
575
|
+
return {
|
|
576
|
+
ok: true,
|
|
577
|
+
items: cfg.rotation.items
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
/** Activate a rotation item: copy its bytes over the active wallpaper slot and
|
|
581
|
+
* return the freshly sniffed data URL so the client can apply it live. */
|
|
582
|
+
async function handleRotationSet(payload) {
|
|
583
|
+
const idx = payload?.index;
|
|
584
|
+
if (typeof idx !== "number" || !isFinite(idx)) return {
|
|
585
|
+
ok: false,
|
|
586
|
+
error: "invalid index"
|
|
587
|
+
};
|
|
588
|
+
const cfg = await readConfig();
|
|
589
|
+
const i = Math.floor(idx);
|
|
590
|
+
const item = cfg.rotation.items[i];
|
|
591
|
+
if (item === void 0) return {
|
|
592
|
+
ok: false,
|
|
593
|
+
error: "not found"
|
|
594
|
+
};
|
|
595
|
+
let buf;
|
|
596
|
+
try {
|
|
597
|
+
buf = await readFile(dshHomePath(DATA_DIR, ROTATION_DIR, item.file));
|
|
598
|
+
} catch {
|
|
599
|
+
return {
|
|
600
|
+
ok: false,
|
|
601
|
+
error: "file missing"
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
try {
|
|
605
|
+
await writeFile(wallpaperPath(), buf);
|
|
606
|
+
} catch (e) {
|
|
607
|
+
console.error("dsh-any-background: failed to activate a rotation wallpaper", e);
|
|
608
|
+
return {
|
|
609
|
+
ok: false,
|
|
610
|
+
error: "write failed"
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
return {
|
|
614
|
+
ok: true,
|
|
615
|
+
dataUrl: `data:${sniffImageMime(buf)};base64,${buf.toString("base64")}`
|
|
616
|
+
};
|
|
617
|
+
}
|
|
371
618
|
async function videoUrl() {
|
|
372
619
|
return await findVideoFile() ? VIDEO_ROUTE : null;
|
|
373
620
|
}
|
|
@@ -537,69 +784,223 @@ async function handleVideoUpload(req, res) {
|
|
|
537
784
|
}
|
|
538
785
|
}
|
|
539
786
|
const NS = "dshAnyBackground";
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
787
|
+
const RPC_CHANNEL = "/dsh-any-background";
|
|
788
|
+
const RPC_BODY_MAX = 314572800;
|
|
789
|
+
/** ISO week key — mirrors the client's rotationDue so daily/weekly cadence
|
|
790
|
+
* decisions agree across both halves. */
|
|
791
|
+
function isoWeekKey(d) {
|
|
792
|
+
const t = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
|
|
793
|
+
const day = t.getUTCDay() || 7;
|
|
794
|
+
t.setUTCDate(t.getUTCDate() + 4 - day);
|
|
795
|
+
const yearStart = new Date(Date.UTC(t.getUTCFullYear(), 0, 1));
|
|
796
|
+
const week = Math.ceil(((t.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
|
|
797
|
+
return `${t.getUTCFullYear()}-W${week}`;
|
|
798
|
+
}
|
|
799
|
+
/** Advance the rotation pool when its cadence is due: pick the next item,
|
|
800
|
+
* copy it over the active wallpaper slot, and persist the rotation state.
|
|
801
|
+
* Runs inside `read` so a reload restores the NEW wallpaper directly — the
|
|
802
|
+
* old one never reaches the screen. The client's maybeRotate stays as a
|
|
803
|
+
* fallback and skips when this already advanced (the `rotated` flag). */
|
|
804
|
+
async function advanceRotationIfDue() {
|
|
805
|
+
const cfg = await readConfig();
|
|
806
|
+
const rot = cfg.rotation;
|
|
807
|
+
if (!rot.enabled || rot.items.length === 0) return false;
|
|
808
|
+
const now = /* @__PURE__ */ new Date();
|
|
809
|
+
const last = rot.lastRotate !== null ? new Date(rot.lastRotate) : null;
|
|
810
|
+
if (!(rot.interval === "reload" || last === null || isNaN(last.getTime()) || (rot.interval === "daily" ? last.toDateString() !== now.toDateString() : isoWeekKey(last) !== isoWeekKey(now)))) return false;
|
|
811
|
+
const n = rot.items.length;
|
|
812
|
+
let idx = rot.current;
|
|
813
|
+
if (rot.mode === "shuffle" && n > 1) while (idx === rot.current) idx = Math.floor(Math.random() * n);
|
|
814
|
+
else idx = (rot.current + 1) % n;
|
|
815
|
+
const item = rot.items[idx];
|
|
816
|
+
if (item === void 0) return false;
|
|
817
|
+
try {
|
|
818
|
+
const buf = await readFile(dshHomePath(DATA_DIR, ROTATION_DIR, item.file));
|
|
819
|
+
await writeFile(wallpaperPath(), buf);
|
|
820
|
+
} catch (e) {
|
|
821
|
+
console.error("dsh-any-background: failed to advance the rotation pool", e);
|
|
822
|
+
return false;
|
|
823
|
+
}
|
|
824
|
+
cfg.rotation = {
|
|
825
|
+
...rot,
|
|
826
|
+
current: idx,
|
|
827
|
+
lastRotate: now.toISOString()
|
|
828
|
+
};
|
|
829
|
+
if (!await writeConfig(cfg)) return false;
|
|
830
|
+
return true;
|
|
831
|
+
}
|
|
832
|
+
/** Dispatch one decoded RPC method to the matching persistence routine and
|
|
833
|
+
* return the wire `result` half of the server-response envelope. */
|
|
834
|
+
async function handleRpcMethod(endpoint, payload) {
|
|
835
|
+
const method = endpoint.slice(`${NS}/`.length);
|
|
836
|
+
try {
|
|
837
|
+
switch (method) {
|
|
838
|
+
case "read": {
|
|
839
|
+
const rotated = await advanceRotationIfDue();
|
|
840
|
+
return {
|
|
546
841
|
ok: true,
|
|
547
842
|
value: {
|
|
548
843
|
config: await readConfig(),
|
|
549
844
|
wallpaper: await readWallpaper(),
|
|
550
|
-
videoUrl: await videoUrl()
|
|
551
|
-
|
|
552
|
-
};
|
|
553
|
-
case "writeConfig": return {
|
|
554
|
-
ok: true,
|
|
555
|
-
value: await writeConfig(payload?.config ?? {})
|
|
556
|
-
};
|
|
557
|
-
case "setWallpaper": return {
|
|
558
|
-
ok: true,
|
|
559
|
-
value: await writeWallpaper(payload?.dataUrl ?? null)
|
|
560
|
-
};
|
|
561
|
-
case "setVideo": return {
|
|
562
|
-
ok: true,
|
|
563
|
-
value: await writeVideo(payload?.dataUrl ?? null)
|
|
564
|
-
};
|
|
565
|
-
case "setWallpaperUrl": return {
|
|
566
|
-
ok: true,
|
|
567
|
-
value: await writeWallpaperFromUrl(payload?.url ?? null)
|
|
568
|
-
};
|
|
569
|
-
default: return {
|
|
570
|
-
ok: false,
|
|
571
|
-
error: {
|
|
572
|
-
code: "dsh-any-background/bad-request",
|
|
573
|
-
message: `unknown endpoint ${ep}`,
|
|
574
|
-
details: { issues: [] }
|
|
845
|
+
videoUrl: await videoUrl(),
|
|
846
|
+
rotated
|
|
575
847
|
}
|
|
576
848
|
};
|
|
577
849
|
}
|
|
578
|
-
|
|
579
|
-
|
|
850
|
+
case "writeConfig": return {
|
|
851
|
+
ok: true,
|
|
852
|
+
value: await writeConfig(payload?.config ?? {})
|
|
853
|
+
};
|
|
854
|
+
case "setWallpaper": return {
|
|
855
|
+
ok: true,
|
|
856
|
+
value: await writeWallpaper(payload?.dataUrl ?? null)
|
|
857
|
+
};
|
|
858
|
+
case "setVideo": return {
|
|
859
|
+
ok: true,
|
|
860
|
+
value: await writeVideo(payload?.dataUrl ?? null)
|
|
861
|
+
};
|
|
862
|
+
case "setWallpaperUrl": return {
|
|
863
|
+
ok: true,
|
|
864
|
+
value: await writeWallpaperFromUrl(payload?.url ?? null)
|
|
865
|
+
};
|
|
866
|
+
case "rotationAdd": return {
|
|
867
|
+
ok: true,
|
|
868
|
+
value: await handleRotationAdd(payload)
|
|
869
|
+
};
|
|
870
|
+
case "rotationRemove": return {
|
|
871
|
+
ok: true,
|
|
872
|
+
value: await handleRotationRemove(payload)
|
|
873
|
+
};
|
|
874
|
+
case "rotationSet": return {
|
|
875
|
+
ok: true,
|
|
876
|
+
value: await handleRotationSet(payload)
|
|
877
|
+
};
|
|
878
|
+
default: return {
|
|
580
879
|
ok: false,
|
|
581
880
|
error: {
|
|
582
|
-
code: "dsh-any-background/
|
|
583
|
-
message:
|
|
584
|
-
details: {}
|
|
881
|
+
code: "dsh-any-background/bad-request",
|
|
882
|
+
message: `unknown endpoint ${endpoint}`,
|
|
883
|
+
details: { issues: [] }
|
|
585
884
|
}
|
|
586
885
|
};
|
|
587
886
|
}
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
887
|
+
} catch (e) {
|
|
888
|
+
return {
|
|
889
|
+
ok: false,
|
|
890
|
+
error: {
|
|
891
|
+
code: "dsh-any-background/internal",
|
|
892
|
+
message: e instanceof Error ? e.message : String(e),
|
|
893
|
+
details: {}
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
function apply(ctx) {
|
|
899
|
+
ctx.inject(["connection", "webServer"], (webCtx) => {
|
|
900
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
901
|
+
kind: "prefix",
|
|
902
|
+
path: RPC_CHANNEL,
|
|
903
|
+
handler: async (req, res) => {
|
|
904
|
+
const rejection = webCtx.connection.requestRejection(req);
|
|
905
|
+
if (rejection !== void 0) {
|
|
906
|
+
res.writeHead(rejection);
|
|
907
|
+
res.end(rejection === 401 ? "unauthorized" : "forbidden");
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
if (req.method !== "POST") {
|
|
911
|
+
res.writeHead(405, { "Content-Type": "application/json" });
|
|
912
|
+
res.end(JSON.stringify({
|
|
913
|
+
ok: false,
|
|
914
|
+
error: {
|
|
915
|
+
code: "dsh-any-background/bad-request",
|
|
916
|
+
message: "expected POST",
|
|
917
|
+
details: {}
|
|
918
|
+
}
|
|
919
|
+
}));
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
const pathname = new URL(req.url ?? "/", "http://dsh.internal").pathname;
|
|
923
|
+
const endpoint = pathname.startsWith(`${RPC_CHANNEL}/`) ? pathname.slice(20) : void 0;
|
|
924
|
+
if (endpoint === void 0 || endpoint.length === 0) {
|
|
925
|
+
res.writeHead(404);
|
|
926
|
+
res.end();
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
const chunks = [];
|
|
930
|
+
let received = 0;
|
|
931
|
+
for await (const chunk of req) {
|
|
932
|
+
const buf = chunk;
|
|
933
|
+
received += buf.byteLength;
|
|
934
|
+
if (received > RPC_BODY_MAX) {
|
|
935
|
+
res.writeHead(413, { connection: "close" });
|
|
936
|
+
res.end();
|
|
937
|
+
req.destroy();
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
chunks.push(buf);
|
|
941
|
+
}
|
|
942
|
+
let env;
|
|
943
|
+
try {
|
|
944
|
+
env = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
|
|
945
|
+
} catch {
|
|
946
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
947
|
+
res.end(JSON.stringify({
|
|
948
|
+
ok: false,
|
|
949
|
+
error: {
|
|
950
|
+
code: "dsh-any-background/bad-request",
|
|
951
|
+
message: "body is not JSON",
|
|
952
|
+
details: {}
|
|
953
|
+
}
|
|
954
|
+
}));
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
if (env === null || typeof env !== "object" || env.type !== "client-request" || typeof env.rpcId !== "string" || typeof env.method !== "string") {
|
|
958
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
959
|
+
res.end(JSON.stringify({
|
|
960
|
+
ok: false,
|
|
961
|
+
error: {
|
|
962
|
+
code: "dsh-any-background/bad-request",
|
|
963
|
+
message: "invalid client-request envelope",
|
|
964
|
+
details: {}
|
|
965
|
+
}
|
|
966
|
+
}));
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
if (env.method !== endpoint) {
|
|
970
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
971
|
+
res.end(JSON.stringify({
|
|
972
|
+
type: "server-response",
|
|
973
|
+
rpcId: env.rpcId,
|
|
974
|
+
result: {
|
|
975
|
+
ok: false,
|
|
976
|
+
error: {
|
|
977
|
+
code: "dsh-any-background/bad-request",
|
|
978
|
+
message: `method ${env.method} does not match endpoint ${endpoint}`,
|
|
979
|
+
details: { issues: [] }
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
}));
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
const result = await handleRpcMethod(endpoint, env.payload);
|
|
986
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
987
|
+
res.end(JSON.stringify({
|
|
988
|
+
type: "server-response",
|
|
989
|
+
rpcId: env.rpcId,
|
|
990
|
+
result
|
|
991
|
+
}));
|
|
992
|
+
}
|
|
993
|
+
}), "dsh-any-background: rpc channel");
|
|
994
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
995
|
+
kind: "prefix",
|
|
996
|
+
path: VIDEO_ROUTE,
|
|
997
|
+
handler: serveVideo
|
|
998
|
+
}), "dsh-any-background: video route");
|
|
999
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
1000
|
+
kind: "exact",
|
|
1001
|
+
path: UPLOAD_ROUTE,
|
|
1002
|
+
handler: handleVideoUpload
|
|
1003
|
+
}), "dsh-any-background: upload route");
|
|
603
1004
|
});
|
|
604
1005
|
}
|
|
605
1006
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-any-background",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
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",
|
|
@@ -22,6 +22,9 @@
|
|
|
22
22
|
"license": "MIT",
|
|
23
23
|
"author": "Tkingxiao",
|
|
24
24
|
"type": "module",
|
|
25
|
+
"engines": {
|
|
26
|
+
"dsh": ">=0.1.2-rc.1"
|
|
27
|
+
},
|
|
25
28
|
"exports": {
|
|
26
29
|
".": {
|
|
27
30
|
"default": "./lib/index.js"
|
|
@@ -63,14 +66,13 @@
|
|
|
63
66
|
"@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6"
|
|
64
67
|
},
|
|
65
68
|
"peerDependencies": {
|
|
66
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
67
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.
|
|
68
|
-
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
|
|
69
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
70
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.2-alpha.2",
|
|
69
71
|
"@deepseek-ai/dsh-client-store": "^0.1.2-alpha.2",
|
|
70
|
-
"@deepseek-ai/dsh-client-connection": "^0.1.
|
|
71
|
-
"@deepseek-ai/dsh-client-ui-settings": "^0.1.
|
|
72
|
-
"@deepseek-ai/dsh-client-ui-theme": "^0.1.
|
|
73
|
-
"@deepseek-ai/dsh-invariants": "^0.1.
|
|
72
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.2-alpha.2",
|
|
73
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.2-alpha.2",
|
|
74
|
+
"@deepseek-ai/dsh-client-ui-theme": "^0.1.2-alpha.2",
|
|
75
|
+
"@deepseek-ai/dsh-invariants": "^0.1.2-alpha.2",
|
|
74
76
|
"react": "^18.2.0"
|
|
75
77
|
},
|
|
76
78
|
"peerDependenciesMeta": {
|
|
@@ -89,9 +91,6 @@
|
|
|
89
91
|
"@deepseek-ai/dsh-client-ui-settings": {
|
|
90
92
|
"optional": true
|
|
91
93
|
},
|
|
92
|
-
"@deepseek-ai/dsh-client-runtime": {
|
|
93
|
-
"optional": true
|
|
94
|
-
},
|
|
95
94
|
"@deepseek-ai/dsh-client-ui-theme": {
|
|
96
95
|
"optional": true
|
|
97
96
|
},
|
|
@@ -109,7 +108,6 @@
|
|
|
109
108
|
"client": {
|
|
110
109
|
"platform": "web",
|
|
111
110
|
"inject": [
|
|
112
|
-
"@deepseek-ai/dsh-client-runtime",
|
|
113
111
|
"@deepseek-ai/dsh-client-store",
|
|
114
112
|
"@deepseek-ai/dsh-client-locale",
|
|
115
113
|
"@deepseek-ai/dsh-client-connection",
|