dsh-any-background 0.1.8 → 0.2.1
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 +20 -5
- package/README.zh.md +20 -5
- package/lib/client.js +1097 -192
- package/lib/client.js.map +1 -1
- package/lib/index.js +314 -47
- package/lib/invariant.js +0 -11
- package/package.json +8 -6
package/lib/index.js
CHANGED
|
@@ -1,39 +1,63 @@
|
|
|
1
|
-
import { access, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
1
|
+
import { access, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { createReadStream, createWriteStream } from "node:fs";
|
|
2
3
|
import { dshHomePath } from "@deepseek-ai/dsh-home-paths";
|
|
3
4
|
//#region src/index.ts
|
|
4
5
|
/**
|
|
5
|
-
* Node half of
|
|
6
|
+
* Node half of dsh-any-background: file-backed theme persistence.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* the shared `/api`, so slash commands stay intact). The client reads the
|
|
11
|
-
* persisted theme on boot and writes it back on every setting change.
|
|
8
|
+
* Owns the `~/.dsh/.dsh-any-background-data/` store and exposes a small RPC
|
|
9
|
+
* surface on the dedicated `/dsh-any-background` channel (never the shared
|
|
10
|
+
* `/api`, so slash commands stay intact).
|
|
12
11
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
12
|
+
* theme-config.json settings
|
|
13
|
+
* wallpaper.jpg background image
|
|
14
|
+
* wallpaper.<ext> background video, named by MIME (mp4/webm/ogv/mov/mkv);
|
|
15
|
+
* played over HTTP route /dsh-any-background/video and
|
|
16
|
+
* uploaded to /dsh-any-background/video/upload as raw
|
|
17
|
+
* bytes — never base64 through the RPC channel.
|
|
19
18
|
*/
|
|
20
19
|
const name = "dsh-any-background";
|
|
21
|
-
const inject = ["connection"];
|
|
20
|
+
const inject = ["connection", "webServer"];
|
|
22
21
|
const DATA_DIR = ".dsh-any-background-data";
|
|
23
22
|
const CONFIG_FILE = "theme-config.json";
|
|
24
23
|
const WALLPAPER_FILE = "wallpaper.jpg";
|
|
24
|
+
const VIDEO_ROUTE = "/dsh-any-background/video";
|
|
25
|
+
const UPLOAD_ROUTE = "/dsh-any-background/video/upload";
|
|
26
|
+
const UPLOAD_TMP = "wallpaper.upload.tmp";
|
|
27
|
+
function videoFileName(mime) {
|
|
28
|
+
switch (mime) {
|
|
29
|
+
case "video/mp4": return "wallpaper.mp4";
|
|
30
|
+
case "video/webm": return "wallpaper.webm";
|
|
31
|
+
case "video/ogg": return "wallpaper.ogv";
|
|
32
|
+
case "video/quicktime": return "wallpaper.mov";
|
|
33
|
+
case "video/x-matroska": return "wallpaper.mkv";
|
|
34
|
+
default: return "wallpaper.video";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const VIDEO_CANDIDATES = [
|
|
38
|
+
"wallpaper.mp4",
|
|
39
|
+
"wallpaper.webm",
|
|
40
|
+
"wallpaper.ogv",
|
|
41
|
+
"wallpaper.mov",
|
|
42
|
+
"wallpaper.mkv",
|
|
43
|
+
"wallpaper.video"
|
|
44
|
+
];
|
|
25
45
|
const DEFAULT_CONFIG = {
|
|
26
46
|
color: null,
|
|
27
47
|
opacities: {
|
|
28
48
|
bg: .85,
|
|
29
49
|
sidebar: .93,
|
|
30
|
-
card: 1
|
|
50
|
+
card: 1,
|
|
51
|
+
input: 1
|
|
31
52
|
},
|
|
32
53
|
blurs: {
|
|
33
54
|
bg: 0,
|
|
34
55
|
sidebar: 0,
|
|
35
56
|
card: 0,
|
|
36
|
-
settings: 0
|
|
57
|
+
settings: 0,
|
|
58
|
+
chat: 0,
|
|
59
|
+
trajectory: 0,
|
|
60
|
+
input: 0
|
|
37
61
|
},
|
|
38
62
|
settingsOpacity: 1,
|
|
39
63
|
wallpaperOpacity: 1,
|
|
@@ -45,16 +69,73 @@ const DEFAULT_CONFIG = {
|
|
|
45
69
|
iw: 0,
|
|
46
70
|
ih: 0
|
|
47
71
|
},
|
|
72
|
+
videoBgState: {
|
|
73
|
+
zoom: 1,
|
|
74
|
+
x: 0,
|
|
75
|
+
y: 0,
|
|
76
|
+
iw: 0,
|
|
77
|
+
ih: 0
|
|
78
|
+
},
|
|
48
79
|
backgroundType: "image",
|
|
80
|
+
bgMode: "fit",
|
|
81
|
+
videoMime: null,
|
|
49
82
|
generatedBg: null,
|
|
50
|
-
regenerateOnReload: false
|
|
83
|
+
regenerateOnReload: false,
|
|
84
|
+
chatTextOpacity: 0,
|
|
85
|
+
trajectoryOpacity: 1
|
|
51
86
|
};
|
|
52
87
|
const dataDir = () => dshHomePath(DATA_DIR);
|
|
53
88
|
const configPath = () => dshHomePath(DATA_DIR, CONFIG_FILE);
|
|
54
89
|
const wallpaperPath = () => dshHomePath(DATA_DIR, WALLPAPER_FILE);
|
|
90
|
+
const videoPathFor = (mime) => dshHomePath(DATA_DIR, videoFileName(mime));
|
|
91
|
+
const exists = async (p) => {
|
|
92
|
+
try {
|
|
93
|
+
await access(p);
|
|
94
|
+
return true;
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
/** Locate the stored video: the recorded MIME decides the expected name; a
|
|
100
|
+
* legacy extensionless wallpaper.video is renamed on first access. */
|
|
101
|
+
async function findVideoFile() {
|
|
102
|
+
const cfg = await readConfig();
|
|
103
|
+
const expected = videoPathFor(cfg.videoMime);
|
|
104
|
+
if (await exists(expected)) return {
|
|
105
|
+
path: expected,
|
|
106
|
+
mime: cfg.videoMime
|
|
107
|
+
};
|
|
108
|
+
for (const name of VIDEO_CANDIDATES) {
|
|
109
|
+
const p = dshHomePath(DATA_DIR, name);
|
|
110
|
+
if (!await exists(p)) continue;
|
|
111
|
+
if (cfg.videoMime !== null && name !== videoFileName(cfg.videoMime)) try {
|
|
112
|
+
await rename(p, expected);
|
|
113
|
+
return {
|
|
114
|
+
path: expected,
|
|
115
|
+
mime: cfg.videoMime
|
|
116
|
+
};
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
path: p,
|
|
122
|
+
mime: cfg.videoMime
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
55
127
|
function clamp(n, lo, hi, def) {
|
|
56
128
|
return typeof n === "number" && isFinite(n) ? Math.min(hi, Math.max(lo, n)) : def;
|
|
57
129
|
}
|
|
130
|
+
function normalizeBgState(s) {
|
|
131
|
+
return {
|
|
132
|
+
zoom: clamp(s.zoom, .1, 10, 1),
|
|
133
|
+
x: typeof s.x === "number" && isFinite(s.x) ? s.x : 0,
|
|
134
|
+
y: typeof s.y === "number" && isFinite(s.y) ? s.y : 0,
|
|
135
|
+
iw: typeof s.iw === "number" && s.iw > 0 ? s.iw : 0,
|
|
136
|
+
ih: typeof s.ih === "number" && s.ih > 0 ? s.ih : 0
|
|
137
|
+
};
|
|
138
|
+
}
|
|
58
139
|
/** Coerce an unknown persisted value into a valid ThemeConfig, falling back per-field. */
|
|
59
140
|
function normalizeConfig(raw) {
|
|
60
141
|
const r = raw ?? {};
|
|
@@ -64,44 +145,56 @@ function normalizeConfig(raw) {
|
|
|
64
145
|
clamp(c[1], 0, 1, .55),
|
|
65
146
|
clamp(c[2], 0, 1, .25)
|
|
66
147
|
] : null;
|
|
67
|
-
const bg = r.bgState ?? {};
|
|
68
148
|
const bgType = [
|
|
69
149
|
"image",
|
|
150
|
+
"video",
|
|
70
151
|
"mesh",
|
|
71
152
|
"shader",
|
|
72
153
|
"pattern"
|
|
73
154
|
].includes(r.backgroundType) ? r.backgroundType : DEFAULT_CONFIG.backgroundType;
|
|
155
|
+
const bgMode = [
|
|
156
|
+
"fit",
|
|
157
|
+
"fill",
|
|
158
|
+
"stretch",
|
|
159
|
+
"tile",
|
|
160
|
+
"center"
|
|
161
|
+
].includes(r.bgMode) ? r.bgMode : DEFAULT_CONFIG.bgMode;
|
|
74
162
|
const gen = r.generatedBg && typeof r.generatedBg === "object" ? r.generatedBg : null;
|
|
75
163
|
const generatedBg = gen && gen.type === bgType ? normalizeGeneratedBg(r.generatedBg) : null;
|
|
76
164
|
const legacy = typeof r.opacity === "number" ? r.opacity : null;
|
|
77
165
|
const ops = r.opacities ?? {};
|
|
78
166
|
const bl = r.blurs ?? {};
|
|
167
|
+
const blurs = {};
|
|
168
|
+
for (const k of [
|
|
169
|
+
"bg",
|
|
170
|
+
"sidebar",
|
|
171
|
+
"card",
|
|
172
|
+
"settings",
|
|
173
|
+
"chat",
|
|
174
|
+
"trajectory",
|
|
175
|
+
"input"
|
|
176
|
+
]) blurs[k] = clamp(bl[k], 0, 60, DEFAULT_CONFIG.blurs[k]);
|
|
79
177
|
return {
|
|
80
178
|
color,
|
|
81
179
|
opacities: {
|
|
82
180
|
bg: clamp(ops.bg, 0, 1, legacy ?? DEFAULT_CONFIG.opacities.bg),
|
|
83
181
|
sidebar: clamp(ops.sidebar, 0, 1, legacy !== null ? Math.min(1, legacy + .08) : DEFAULT_CONFIG.opacities.sidebar),
|
|
84
|
-
card: clamp(ops.card, 0, 1, DEFAULT_CONFIG.opacities.card)
|
|
85
|
-
|
|
86
|
-
blurs: {
|
|
87
|
-
bg: clamp(bl.bg, 0, 60, DEFAULT_CONFIG.blurs.bg),
|
|
88
|
-
sidebar: clamp(bl.sidebar, 0, 60, DEFAULT_CONFIG.blurs.sidebar),
|
|
89
|
-
card: clamp(bl.card, 0, 60, DEFAULT_CONFIG.blurs.card),
|
|
90
|
-
settings: clamp(bl.settings, 0, 60, DEFAULT_CONFIG.blurs.settings)
|
|
182
|
+
card: clamp(ops.card, 0, 1, DEFAULT_CONFIG.opacities.card),
|
|
183
|
+
input: clamp(ops.input, 0, 1, DEFAULT_CONFIG.opacities.input)
|
|
91
184
|
},
|
|
185
|
+
blurs,
|
|
92
186
|
settingsOpacity: clamp(r.settingsOpacity, 0, 1, DEFAULT_CONFIG.settingsOpacity),
|
|
93
187
|
wallpaperOpacity: clamp(r.wallpaperOpacity, 0, 1, DEFAULT_CONFIG.wallpaperOpacity),
|
|
94
188
|
blur: clamp(r.blur, 0, 60, DEFAULT_CONFIG.blur),
|
|
95
|
-
bgState: {
|
|
96
|
-
|
|
97
|
-
x: typeof bg.x === "number" && isFinite(bg.x) ? bg.x : 0,
|
|
98
|
-
y: typeof bg.y === "number" && isFinite(bg.y) ? bg.y : 0,
|
|
99
|
-
iw: typeof bg.iw === "number" && bg.iw > 0 ? bg.iw : 0,
|
|
100
|
-
ih: typeof bg.ih === "number" && bg.ih > 0 ? bg.ih : 0
|
|
101
|
-
},
|
|
189
|
+
bgState: normalizeBgState(r.bgState ?? {}),
|
|
190
|
+
videoBgState: normalizeBgState(r.videoBgState ?? {}),
|
|
102
191
|
backgroundType: bgType,
|
|
192
|
+
bgMode,
|
|
193
|
+
videoMime: typeof r.videoMime === "string" ? r.videoMime : null,
|
|
103
194
|
generatedBg,
|
|
104
|
-
regenerateOnReload: typeof r.regenerateOnReload === "boolean" ? r.regenerateOnReload : DEFAULT_CONFIG.regenerateOnReload
|
|
195
|
+
regenerateOnReload: typeof r.regenerateOnReload === "boolean" ? r.regenerateOnReload : DEFAULT_CONFIG.regenerateOnReload,
|
|
196
|
+
chatTextOpacity: clamp(r.chatTextOpacity, 0, 1, DEFAULT_CONFIG.chatTextOpacity),
|
|
197
|
+
trajectoryOpacity: clamp(r.trajectoryOpacity, 0, 1, DEFAULT_CONFIG.trajectoryOpacity)
|
|
105
198
|
};
|
|
106
199
|
}
|
|
107
200
|
function normalizeGeneratedBg(p) {
|
|
@@ -144,16 +237,10 @@ async function ensureDir() {
|
|
|
144
237
|
}
|
|
145
238
|
async function readConfig() {
|
|
146
239
|
await ensureDir();
|
|
147
|
-
try {
|
|
148
|
-
await access(configPath());
|
|
149
|
-
} catch {
|
|
150
|
-
return { ...DEFAULT_CONFIG };
|
|
151
|
-
}
|
|
152
240
|
try {
|
|
153
241
|
const raw = await readFile(configPath(), "utf8");
|
|
154
242
|
return normalizeConfig(JSON.parse(raw));
|
|
155
|
-
} catch
|
|
156
|
-
console.warn(`dsh-any-background: cannot read "${CONFIG_FILE}", using defaults`, e);
|
|
243
|
+
} catch {
|
|
157
244
|
return { ...DEFAULT_CONFIG };
|
|
158
245
|
}
|
|
159
246
|
}
|
|
@@ -167,7 +254,6 @@ async function writeConfig(config) {
|
|
|
167
254
|
return false;
|
|
168
255
|
}
|
|
169
256
|
}
|
|
170
|
-
/** Read the wallpaper file and return it as a JPEG data URL (null when absent). */
|
|
171
257
|
async function readWallpaper() {
|
|
172
258
|
try {
|
|
173
259
|
return `data:image/jpeg;base64,${(await readFile(wallpaperPath())).toString("base64")}`;
|
|
@@ -175,11 +261,7 @@ async function readWallpaper() {
|
|
|
175
261
|
return null;
|
|
176
262
|
}
|
|
177
263
|
}
|
|
178
|
-
/**
|
|
179
|
-
* Persist a wallpaper. Passing null removes the stored file; otherwise the
|
|
180
|
-
* data URL is decoded and written to wallpaper.jpg. Returns false (and keeps
|
|
181
|
-
* the previous file) when the payload is invalid or the write fails.
|
|
182
|
-
*/
|
|
264
|
+
/** Persist a wallpaper (null removes it); false keeps the previous file. */
|
|
183
265
|
async function writeWallpaper(dataUrl) {
|
|
184
266
|
await ensureDir();
|
|
185
267
|
try {
|
|
@@ -196,6 +278,174 @@ async function writeWallpaper(dataUrl) {
|
|
|
196
278
|
return false;
|
|
197
279
|
}
|
|
198
280
|
}
|
|
281
|
+
async function videoUrl() {
|
|
282
|
+
return await findVideoFile() ? VIDEO_ROUTE : null;
|
|
283
|
+
}
|
|
284
|
+
/** Persist a video from a data URL (null removes every variant); only used
|
|
285
|
+
* for removal and small legacy/import payloads. */
|
|
286
|
+
async function writeVideo(dataUrl) {
|
|
287
|
+
await ensureDir();
|
|
288
|
+
try {
|
|
289
|
+
if (dataUrl === null) {
|
|
290
|
+
for (const name of VIDEO_CANDIDATES) await rm(dshHomePath(DATA_DIR, name), { force: true });
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
const m = /^data:(video\/[a-zA-Z0-9.+-]+);base64,([A-Za-z0-9+/=]+)$/.exec(dataUrl);
|
|
294
|
+
if (!m) return false;
|
|
295
|
+
const target = videoPathFor(m[1]);
|
|
296
|
+
for (const name of VIDEO_CANDIDATES) {
|
|
297
|
+
const p = dshHomePath(DATA_DIR, name);
|
|
298
|
+
if (p !== target) await rm(p, { force: true });
|
|
299
|
+
}
|
|
300
|
+
await writeFile(target, Buffer.from(m[2], "base64"));
|
|
301
|
+
return true;
|
|
302
|
+
} catch (e) {
|
|
303
|
+
console.error("dsh-any-background: failed to write the background video", e);
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/** Stream the stored video: correct MIME, no caching, Range answers so the
|
|
308
|
+
* browser can seek (snapshot capture does). */
|
|
309
|
+
async function serveVideo(req, res) {
|
|
310
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
311
|
+
res.writeHead(405, { "Content-Type": "application/json" });
|
|
312
|
+
res.end(JSON.stringify({
|
|
313
|
+
ok: false,
|
|
314
|
+
error: "video route only serves GET/HEAD; uploads need the plugin upload route — restart the web server to load it"
|
|
315
|
+
}));
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
const found = await findVideoFile();
|
|
320
|
+
if (found === null) {
|
|
321
|
+
res.writeHead(404);
|
|
322
|
+
res.end("no background video stored");
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const st = await stat(found.path);
|
|
326
|
+
const baseHeaders = {
|
|
327
|
+
"Content-Type": found.mime ?? "application/octet-stream",
|
|
328
|
+
"Accept-Ranges": "bytes",
|
|
329
|
+
"Cache-Control": "no-store"
|
|
330
|
+
};
|
|
331
|
+
const range = typeof req.headers.range === "string" ? req.headers.range.trim() : "";
|
|
332
|
+
const m = /^bytes=(\d*)-(\d*)$/.exec(range);
|
|
333
|
+
if (m !== null && (m[1] !== "" || m[2] !== "")) {
|
|
334
|
+
let start;
|
|
335
|
+
let end;
|
|
336
|
+
if (m[1] === "") {
|
|
337
|
+
const suffix = parseInt(m[2], 10);
|
|
338
|
+
start = Math.max(0, st.size - suffix);
|
|
339
|
+
end = st.size - 1;
|
|
340
|
+
} else {
|
|
341
|
+
start = parseInt(m[1], 10);
|
|
342
|
+
end = m[2] !== "" ? Math.min(parseInt(m[2], 10), st.size - 1) : st.size - 1;
|
|
343
|
+
}
|
|
344
|
+
if (start >= st.size || start > end) {
|
|
345
|
+
res.writeHead(416, { "Content-Range": `bytes */${st.size}` });
|
|
346
|
+
res.end();
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
res.writeHead(206, {
|
|
350
|
+
...baseHeaders,
|
|
351
|
+
"Content-Range": `bytes ${start}-${end}/${st.size}`,
|
|
352
|
+
"Content-Length": end - start + 1
|
|
353
|
+
});
|
|
354
|
+
if (req.method === "HEAD") {
|
|
355
|
+
res.end();
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
createReadStream(found.path, {
|
|
359
|
+
start,
|
|
360
|
+
end
|
|
361
|
+
}).pipe(res);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
res.writeHead(200, {
|
|
365
|
+
...baseHeaders,
|
|
366
|
+
"Content-Length": st.size
|
|
367
|
+
});
|
|
368
|
+
if (req.method === "HEAD") {
|
|
369
|
+
res.end();
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
createReadStream(found.path).pipe(res);
|
|
373
|
+
} catch (e) {
|
|
374
|
+
console.error("dsh-any-background: failed to serve the background video", e);
|
|
375
|
+
try {
|
|
376
|
+
res.writeHead(500);
|
|
377
|
+
res.end();
|
|
378
|
+
} catch {}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/** Accept a raw video upload (POST): pipe the body into a temp file, then
|
|
382
|
+
* rename it into the MIME-derived slot. Aborted transfers clean up. */
|
|
383
|
+
async function handleVideoUpload(req, res) {
|
|
384
|
+
if (req.method !== "POST") {
|
|
385
|
+
res.writeHead(405);
|
|
386
|
+
res.end();
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const mime = (typeof req.headers["content-type"] === "string" ? req.headers["content-type"] : "").split(";")[0].trim();
|
|
390
|
+
if (!mime.startsWith("video/")) {
|
|
391
|
+
req.resume();
|
|
392
|
+
res.writeHead(415, { "Content-Type": "application/json" });
|
|
393
|
+
res.end(JSON.stringify({
|
|
394
|
+
ok: false,
|
|
395
|
+
error: "unsupported media type, expected video/*"
|
|
396
|
+
}));
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
try {
|
|
400
|
+
await ensureDir();
|
|
401
|
+
const tmp = dshHomePath(DATA_DIR, UPLOAD_TMP);
|
|
402
|
+
const target = videoPathFor(mime);
|
|
403
|
+
const out = createWriteStream(tmp);
|
|
404
|
+
let failed = false;
|
|
405
|
+
const fail = () => {
|
|
406
|
+
if (failed) return;
|
|
407
|
+
failed = true;
|
|
408
|
+
out.destroy();
|
|
409
|
+
rm(tmp, { force: true });
|
|
410
|
+
};
|
|
411
|
+
req.on("aborted", fail);
|
|
412
|
+
req.on("error", fail);
|
|
413
|
+
out.on("error", () => {
|
|
414
|
+
fail();
|
|
415
|
+
try {
|
|
416
|
+
res.writeHead(500);
|
|
417
|
+
res.end();
|
|
418
|
+
} catch {}
|
|
419
|
+
});
|
|
420
|
+
req.pipe(out);
|
|
421
|
+
out.on("finish", async () => {
|
|
422
|
+
if (failed) return;
|
|
423
|
+
try {
|
|
424
|
+
for (const name of VIDEO_CANDIDATES) {
|
|
425
|
+
const p = dshHomePath(DATA_DIR, name);
|
|
426
|
+
if (p !== target) await rm(p, { force: true });
|
|
427
|
+
}
|
|
428
|
+
await rm(target, { force: true });
|
|
429
|
+
await rename(tmp, target);
|
|
430
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
431
|
+
res.end(JSON.stringify({ ok: true }));
|
|
432
|
+
} catch (e) {
|
|
433
|
+
console.error("dsh-any-background: failed to finalize the uploaded video", e);
|
|
434
|
+
rm(tmp, { force: true });
|
|
435
|
+
try {
|
|
436
|
+
res.writeHead(500);
|
|
437
|
+
res.end();
|
|
438
|
+
} catch {}
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
} catch (e) {
|
|
442
|
+
console.error("dsh-any-background: failed to accept the video upload", e);
|
|
443
|
+
try {
|
|
444
|
+
res.writeHead(500);
|
|
445
|
+
res.end();
|
|
446
|
+
} catch {}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
199
449
|
const NS = "dshAnyBackground";
|
|
200
450
|
function apply(ctx) {
|
|
201
451
|
const dispose = ctx.connection.rpc.handle("/dsh-any-background", async (ep, payload) => {
|
|
@@ -206,7 +456,8 @@ function apply(ctx) {
|
|
|
206
456
|
ok: true,
|
|
207
457
|
value: {
|
|
208
458
|
config: await readConfig(),
|
|
209
|
-
wallpaper: await readWallpaper()
|
|
459
|
+
wallpaper: await readWallpaper(),
|
|
460
|
+
videoUrl: await videoUrl()
|
|
210
461
|
}
|
|
211
462
|
};
|
|
212
463
|
case "writeConfig": return {
|
|
@@ -217,6 +468,10 @@ function apply(ctx) {
|
|
|
217
468
|
ok: true,
|
|
218
469
|
value: await writeWallpaper(payload?.dataUrl ?? null)
|
|
219
470
|
};
|
|
471
|
+
case "setVideo": return {
|
|
472
|
+
ok: true,
|
|
473
|
+
value: await writeVideo(payload?.dataUrl ?? null)
|
|
474
|
+
};
|
|
220
475
|
default: return {
|
|
221
476
|
ok: false,
|
|
222
477
|
error: {
|
|
@@ -237,7 +492,19 @@ function apply(ctx) {
|
|
|
237
492
|
};
|
|
238
493
|
}
|
|
239
494
|
}, { authority: "trusted-host" });
|
|
495
|
+
const disposeRoute = ctx.webServer.register({
|
|
496
|
+
kind: "prefix",
|
|
497
|
+
path: VIDEO_ROUTE,
|
|
498
|
+
handler: serveVideo
|
|
499
|
+
});
|
|
500
|
+
const disposeUpload = ctx.webServer.register({
|
|
501
|
+
kind: "exact",
|
|
502
|
+
path: UPLOAD_ROUTE,
|
|
503
|
+
handler: handleVideoUpload
|
|
504
|
+
});
|
|
240
505
|
ctx.on("dispose", () => {
|
|
506
|
+
disposeRoute();
|
|
507
|
+
disposeUpload();
|
|
241
508
|
dispose();
|
|
242
509
|
});
|
|
243
510
|
}
|
package/lib/invariant.js
CHANGED
|
@@ -1,19 +1,8 @@
|
|
|
1
1
|
//#region src/invariant.ts
|
|
2
2
|
const PACKAGE_NAME = "dsh-any-background";
|
|
3
|
-
/** Cordis companion plugin name. */
|
|
4
3
|
const name = "dsh-any-background-invariant";
|
|
5
|
-
/** Service required before the companion can reserve package ownership. */
|
|
6
4
|
const inject = ["invariants"];
|
|
7
|
-
/**
|
|
8
|
-
* No runtime invariant: the plugin registers a theme definition through the
|
|
9
|
-
* existing ThemeRuntime service and emits no cross-plugin mutable state.
|
|
10
|
-
*/
|
|
11
5
|
const install = () => {};
|
|
12
|
-
/**
|
|
13
|
-
* Register this package's invariant companion.
|
|
14
|
-
* @param ctx - Cordis context carrying the invariant service.
|
|
15
|
-
* @returns the installed registration's disposer after setup succeeds.
|
|
16
|
-
*/
|
|
17
6
|
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
18
7
|
//#endregion
|
|
19
8
|
export { apply, inject, name };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-any-background",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
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",
|
|
@@ -50,16 +50,17 @@
|
|
|
50
50
|
"watch": "tsdown --watch",
|
|
51
51
|
"typecheck": "tsc -p tsconfig.json"
|
|
52
52
|
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6"
|
|
55
|
+
},
|
|
53
56
|
"devDependencies": {
|
|
54
57
|
"@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6",
|
|
55
58
|
"@types/node": "^26.2.0",
|
|
56
59
|
"@types/react": "^19.2.18",
|
|
57
60
|
"@types/react-dom": "^19.2.4",
|
|
58
61
|
"tsdown": "^0.22.2",
|
|
59
|
-
"typescript": "^7.0.2"
|
|
60
|
-
|
|
61
|
-
"dependencies": {
|
|
62
|
-
"@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6"
|
|
62
|
+
"typescript": "^7.0.2",
|
|
63
|
+
"unrun": "^0.3.1"
|
|
63
64
|
},
|
|
64
65
|
"peerDependencies": {
|
|
65
66
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
@@ -102,5 +103,6 @@
|
|
|
102
103
|
],
|
|
103
104
|
"immediately": true
|
|
104
105
|
}
|
|
105
|
-
}
|
|
106
|
+
},
|
|
107
|
+
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be"
|
|
106
108
|
}
|