beauticode-dsh 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/README.zh-CN.md +48 -0
- package/agent.mjs +595 -0
- package/atmosphere.js +306 -0
- package/bin/beauticode-dsh +219 -0
- package/cli.js +219 -0
- package/client.js +370 -0
- package/console.js +303 -0
- package/control-client.mjs +445 -0
- package/cordis.patch.yml +4 -0
- package/host-apply.mjs +163 -0
- package/index.mjs +570 -0
- package/package.json +56 -0
- package/presets.mjs +73 -0
- package/themes/internal-beyond/NOTICE.md +9 -0
- package/themes/internal-beyond/bg-canvas-4k.png +0 -0
- package/themes/internal-beyond/bg-canvas.png +0 -0
- package/themes/internal-beyond/bg-infernal.jpg +0 -0
- package/themes/internal-beyond/bg-internal.jpg +0 -0
- package/ui-host.mjs +292 -0
- package/vendor/adapter-dsh/bridge.js +215 -0
- package/vendor/adapter-dsh/host-descriptor.js +15 -0
- package/vendor/adapter-dsh/index.js +6 -0
- package/vendor/adapter-dsh/injector-lock.js +27 -0
- package/vendor/adapter-dsh/package.json +5 -0
- package/vendor/adapter-dsh/session.js +527 -0
- package/vendor/adapter-dsh/token.js +46 -0
- package/vendor/adapter-dsh/tray-handoff.js +54 -0
- package/vendor/core/apply-transaction.js +287 -0
- package/vendor/core/background-store.js +1116 -0
- package/vendor/core/bundled-gallery.js +53 -0
- package/vendor/core/constants.js +47 -0
- package/vendor/core/error-message.js +208 -0
- package/vendor/core/file-lock.js +151 -0
- package/vendor/core/host-session.js +2 -0
- package/vendor/core/index.js +12 -0
- package/vendor/core/media-server.js +544 -0
- package/vendor/core/media-validation.js +244 -0
- package/vendor/core/package.json +5 -0
- package/vendor/core/paths.js +168 -0
- package/vendor/core/types.js +15 -0
package/index.mjs
ADDED
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { registerAgentSurfaces } from "./agent.mjs";
|
|
7
|
+
import { resolvePluginBaseUrl } from "./host-apply.mjs";
|
|
8
|
+
import { createBeauticodeUi } from "./ui-host.mjs";
|
|
9
|
+
import { canvasImagePath, iceFrostImagePath, normalizeAtmosphere } from "./presets.mjs";
|
|
10
|
+
|
|
11
|
+
export const name = "beauticode-bridge";
|
|
12
|
+
export const inject = ["webServer"];
|
|
13
|
+
export const bridgeProtocol = 4;
|
|
14
|
+
|
|
15
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const TOKEN_PATTERN = /^[a-f0-9]{64}$/;
|
|
17
|
+
const REVISION_PATTERN = /^[a-f0-9]{64}$/;
|
|
18
|
+
const MAX_BODY_BYTES = 64 * 1024;
|
|
19
|
+
|
|
20
|
+
async function readBridgeIdentity() {
|
|
21
|
+
try {
|
|
22
|
+
const manifest = JSON.parse(
|
|
23
|
+
await fs.readFile(path.join(here, "bridge-manifest.json"), "utf8"),
|
|
24
|
+
);
|
|
25
|
+
if (
|
|
26
|
+
manifest.schema === "beauticode.dsh-bridge/v1" &&
|
|
27
|
+
manifest.protocol === bridgeProtocol &&
|
|
28
|
+
REVISION_PATTERN.test(manifest.revision)
|
|
29
|
+
) {
|
|
30
|
+
return { protocol: bridgeProtocol, revision: manifest.revision };
|
|
31
|
+
}
|
|
32
|
+
} catch {}
|
|
33
|
+
return { protocol: bridgeProtocol, revision: "source" };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function defaultTokenFile() {
|
|
37
|
+
const base =
|
|
38
|
+
process.env.BEAUTICODE_DATA_ROOT ||
|
|
39
|
+
(process.env.LOCALAPPDATA
|
|
40
|
+
? path.join(process.env.LOCALAPPDATA, "beautiCode")
|
|
41
|
+
: path.join(os.homedir(), ".beauticode"));
|
|
42
|
+
return path.join(base, "dsh-bridge.token");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function sendJson(res, status, body) {
|
|
46
|
+
const encoded = JSON.stringify(body);
|
|
47
|
+
res.writeHead(status, {
|
|
48
|
+
"content-type": "application/json; charset=utf-8",
|
|
49
|
+
"cache-control": "no-store",
|
|
50
|
+
"content-length": Buffer.byteLength(encoded),
|
|
51
|
+
});
|
|
52
|
+
res.end(encoded);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function readJson(req) {
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const chunks = [];
|
|
58
|
+
let size = 0;
|
|
59
|
+
req.on("data", (chunk) => {
|
|
60
|
+
size += chunk.length;
|
|
61
|
+
if (size > MAX_BODY_BYTES) {
|
|
62
|
+
const error = new Error("请求内容过大。");
|
|
63
|
+
error.statusCode = 413;
|
|
64
|
+
reject(error);
|
|
65
|
+
req.removeAllListeners("data");
|
|
66
|
+
req.resume();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
chunks.push(chunk);
|
|
70
|
+
});
|
|
71
|
+
req.on("end", () => {
|
|
72
|
+
try {
|
|
73
|
+
const parsed = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
|
|
74
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
75
|
+
const error = new Error("请求内容必须是 JSON 对象。");
|
|
76
|
+
error.statusCode = 400;
|
|
77
|
+
reject(error);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
resolve(parsed);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
error.statusCode = 400;
|
|
83
|
+
reject(error);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
req.on("error", reject);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function authorized(req, tokenFile) {
|
|
91
|
+
const match = /^Bearer\s+(.+)$/i.exec(String(req.headers.authorization || "").trim());
|
|
92
|
+
if (!match) return false;
|
|
93
|
+
let expected;
|
|
94
|
+
try {
|
|
95
|
+
expected = (await fs.readFile(tokenFile, "utf8")).trim();
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
if (!TOKEN_PATTERN.test(expected)) return false;
|
|
100
|
+
const actualBuffer = Buffer.from(match[1], "utf8");
|
|
101
|
+
const expectedBuffer = Buffer.from(expected, "utf8");
|
|
102
|
+
return (
|
|
103
|
+
actualBuffer.length === expectedBuffer.length &&
|
|
104
|
+
crypto.timingSafeEqual(actualBuffer, expectedBuffer)
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isSameOrigin(req) {
|
|
109
|
+
const origin = req.headers.origin;
|
|
110
|
+
if (typeof origin === "string") return origin === `http://${req.headers.host}`;
|
|
111
|
+
return req.headers["sec-fetch-site"] === "same-origin";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function validLoopbackMediaUrl(value) {
|
|
115
|
+
if (typeof value !== "string") return false;
|
|
116
|
+
try {
|
|
117
|
+
const url = new URL(value);
|
|
118
|
+
return (
|
|
119
|
+
url.protocol === "http:" &&
|
|
120
|
+
["127.0.0.1", "localhost", "[::1]"].includes(url.hostname.toLowerCase()) &&
|
|
121
|
+
url.searchParams.has("t")
|
|
122
|
+
);
|
|
123
|
+
} catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function validApplyPayload(body) {
|
|
129
|
+
if (!Number.isSafeInteger(body.generation) || body.generation < 0) return false;
|
|
130
|
+
if (!["image", "video", "clear"].includes(body.media)) return false;
|
|
131
|
+
if (body.atmosphere != null && !normalizeAtmosphere(body.atmosphere)) return false;
|
|
132
|
+
if (body.media === "clear") {
|
|
133
|
+
return body.imageUrl == null && body.videoUrl == null && body.startAt == null && body.atmosphere == null;
|
|
134
|
+
}
|
|
135
|
+
if (!validLoopbackMediaUrl(body.imageUrl)) return false;
|
|
136
|
+
if (body.media === "image") return body.videoUrl == null && body.startAt == null;
|
|
137
|
+
return (
|
|
138
|
+
validLoopbackMediaUrl(body.videoUrl) &&
|
|
139
|
+
body.atmosphere == null &&
|
|
140
|
+
(body.startAt == null || (Number.isFinite(body.startAt) && body.startAt >= 0))
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function validTone(value) {
|
|
145
|
+
return value === "dark" || value === "light" || value === "auto";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function publicStatus(current, modes, clients, clientStates) {
|
|
149
|
+
const states = [...clientStates.values()];
|
|
150
|
+
const activeAcks = states
|
|
151
|
+
.map((state) => state.render)
|
|
152
|
+
.filter(
|
|
153
|
+
(ack) =>
|
|
154
|
+
ack &&
|
|
155
|
+
current &&
|
|
156
|
+
ack.generation === current.generation &&
|
|
157
|
+
ack.media === current.media,
|
|
158
|
+
);
|
|
159
|
+
const modeAcks = states.map((state) => state.mode).filter(Boolean);
|
|
160
|
+
const modeReady = modeAcks.filter(
|
|
161
|
+
(ack) =>
|
|
162
|
+
ack.fish === modes.fish &&
|
|
163
|
+
ack.tone === modes.tone &&
|
|
164
|
+
ack.themeSynced === true &&
|
|
165
|
+
(modes.tone === "auto" || ack.resolvedTone === modes.tone) &&
|
|
166
|
+
(ack.muted === modes.muted || (modes.muted === false && ack.blocked === true)),
|
|
167
|
+
);
|
|
168
|
+
const playback =
|
|
169
|
+
activeAcks.find((ack) => ack.ok === true && ack.playback?.hasVideo === true)
|
|
170
|
+
?.playback ?? null;
|
|
171
|
+
return {
|
|
172
|
+
ok: true,
|
|
173
|
+
connectedClients: clients.size,
|
|
174
|
+
current,
|
|
175
|
+
readyClients: activeAcks.filter((ack) => ack.ok === true).length,
|
|
176
|
+
failedClients: activeAcks.filter((ack) => ack.ok !== true).length,
|
|
177
|
+
visibleClients: activeAcks.filter((ack) => ack.ok === true && ack.visible === true).length,
|
|
178
|
+
modeReadyClients: modeReady.length,
|
|
179
|
+
blockedClients: modeReady.filter((ack) => ack.blocked === true).length,
|
|
180
|
+
resolvedTone: modeReady[0]?.resolvedTone ?? null,
|
|
181
|
+
modes: { ...modes },
|
|
182
|
+
playback,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function apply(ctx, config = {}) {
|
|
187
|
+
const tokenFile = path.resolve(config.tokenFile || defaultTokenFile());
|
|
188
|
+
const clients = new Map();
|
|
189
|
+
const clientStates = new Map();
|
|
190
|
+
let current = null;
|
|
191
|
+
const modes = { fish: false, muted: true, tone: "auto" };
|
|
192
|
+
const dataRoot = path.dirname(tokenFile);
|
|
193
|
+
const baseUrl = resolvePluginBaseUrl(ctx);
|
|
194
|
+
registerAgentSurfaces(ctx, { dataRoot, baseUrl });
|
|
195
|
+
const ui = createBeauticodeUi({
|
|
196
|
+
dataRoot,
|
|
197
|
+
getBaseUrl: () => resolvePluginBaseUrl(ctx),
|
|
198
|
+
sendJson,
|
|
199
|
+
isSameOrigin,
|
|
200
|
+
readJson,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const broadcast = (payload) => {
|
|
204
|
+
const frame = `data: ${JSON.stringify(payload)}\n\n`;
|
|
205
|
+
for (const response of clients.values()) response.write(frame);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
ctx.effect(() => {
|
|
209
|
+
const disposeTap = ctx.webServer.tapIndex((html) => {
|
|
210
|
+
if (html.includes("data-beauticode-bridge")) return html;
|
|
211
|
+
const script =
|
|
212
|
+
'<script defer src="/__beauticode/atmosphere.js"></script>' +
|
|
213
|
+
'<script defer src="/__beauticode/client.js" data-beauticode-bridge></script>' +
|
|
214
|
+
'<script defer src="/__beauticode/console.js"></script>';
|
|
215
|
+
return html.includes("</body>")
|
|
216
|
+
? html.replace("</body>", `${script}</body>`)
|
|
217
|
+
: `${html}${script}`;
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const disposers = [
|
|
221
|
+
ctx.webServer.register({
|
|
222
|
+
kind: "exact",
|
|
223
|
+
path: "/__beauticode/version",
|
|
224
|
+
handler: async (req, res) => {
|
|
225
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
226
|
+
res.writeHead(405).end();
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const identity = await readBridgeIdentity();
|
|
230
|
+
if (req.method === "HEAD") {
|
|
231
|
+
res.writeHead(200, { "cache-control": "no-store" }).end();
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
sendJson(res, 200, { ok: true, ...identity });
|
|
235
|
+
},
|
|
236
|
+
}),
|
|
237
|
+
ctx.webServer.register({
|
|
238
|
+
kind: "exact",
|
|
239
|
+
path: "/__beauticode/atmosphere.js",
|
|
240
|
+
handler: async (req, res) => {
|
|
241
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
242
|
+
res.writeHead(405).end();
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const source = await fs.readFile(path.join(here, "atmosphere.js"));
|
|
246
|
+
res.writeHead(200, {
|
|
247
|
+
"content-type": "text/javascript; charset=utf-8",
|
|
248
|
+
"cache-control": "no-store",
|
|
249
|
+
"content-length": source.length,
|
|
250
|
+
});
|
|
251
|
+
res.end(req.method === "HEAD" ? undefined : source);
|
|
252
|
+
},
|
|
253
|
+
}),
|
|
254
|
+
ctx.webServer.register({
|
|
255
|
+
kind: "exact",
|
|
256
|
+
path: "/__beauticode/themes/bg-canvas.png",
|
|
257
|
+
handler: async (req, res) => {
|
|
258
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
259
|
+
res.writeHead(405).end();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const filePath = canvasImagePath();
|
|
263
|
+
if (!filePath) {
|
|
264
|
+
res.writeHead(404).end();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const source = await fs.readFile(filePath);
|
|
268
|
+
res.writeHead(200, {
|
|
269
|
+
"content-type": "image/png",
|
|
270
|
+
"cache-control": "public, max-age=86400",
|
|
271
|
+
"content-length": source.length,
|
|
272
|
+
});
|
|
273
|
+
res.end(req.method === "HEAD" ? undefined : source);
|
|
274
|
+
},
|
|
275
|
+
}),
|
|
276
|
+
ctx.webServer.register({
|
|
277
|
+
kind: "exact",
|
|
278
|
+
path: "/__beauticode/themes/ice-frost.png",
|
|
279
|
+
handler: async (req, res) => {
|
|
280
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
281
|
+
res.writeHead(405).end();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const filePath = iceFrostImagePath();
|
|
285
|
+
if (!filePath) {
|
|
286
|
+
res.writeHead(404).end();
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const source = await fs.readFile(filePath);
|
|
290
|
+
res.writeHead(200, {
|
|
291
|
+
"content-type": "image/png",
|
|
292
|
+
"cache-control": "public, max-age=86400",
|
|
293
|
+
"content-length": source.length,
|
|
294
|
+
});
|
|
295
|
+
res.end(req.method === "HEAD" ? undefined : source);
|
|
296
|
+
},
|
|
297
|
+
}),
|
|
298
|
+
ctx.webServer.register({
|
|
299
|
+
kind: "exact",
|
|
300
|
+
path: "/__beauticode/client.js",
|
|
301
|
+
handler: async (req, res) => {
|
|
302
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
303
|
+
res.writeHead(405).end();
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const source = await fs.readFile(path.join(here, "client.js"));
|
|
307
|
+
res.writeHead(200, {
|
|
308
|
+
"content-type": "text/javascript; charset=utf-8",
|
|
309
|
+
"cache-control": "no-store",
|
|
310
|
+
"content-length": source.length,
|
|
311
|
+
});
|
|
312
|
+
res.end(req.method === "HEAD" ? undefined : source);
|
|
313
|
+
},
|
|
314
|
+
}),
|
|
315
|
+
ctx.webServer.register({
|
|
316
|
+
kind: "exact",
|
|
317
|
+
path: "/__beauticode/console.js",
|
|
318
|
+
handler: async (req, res) => {
|
|
319
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
320
|
+
res.writeHead(405).end();
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const source = await fs.readFile(path.join(here, "console.js"));
|
|
324
|
+
res.writeHead(200, {
|
|
325
|
+
"content-type": "text/javascript; charset=utf-8",
|
|
326
|
+
"cache-control": "no-store",
|
|
327
|
+
"content-length": source.length,
|
|
328
|
+
});
|
|
329
|
+
res.end(req.method === "HEAD" ? undefined : source);
|
|
330
|
+
},
|
|
331
|
+
}),
|
|
332
|
+
ctx.webServer.register({
|
|
333
|
+
kind: "exact",
|
|
334
|
+
path: "/__beauticode/ui/status",
|
|
335
|
+
handler: (req, res) => ui.status(req, res),
|
|
336
|
+
}),
|
|
337
|
+
ctx.webServer.register({
|
|
338
|
+
kind: "exact",
|
|
339
|
+
path: "/__beauticode/ui/import",
|
|
340
|
+
handler: (req, res) => ui.importFile(req, res),
|
|
341
|
+
}),
|
|
342
|
+
ctx.webServer.register({
|
|
343
|
+
kind: "exact",
|
|
344
|
+
path: "/__beauticode/ui/clear",
|
|
345
|
+
handler: (req, res) => ui.clear(req, res),
|
|
346
|
+
}),
|
|
347
|
+
ctx.webServer.register({
|
|
348
|
+
kind: "exact",
|
|
349
|
+
path: "/__beauticode/ui/mode",
|
|
350
|
+
handler: (req, res) => ui.mode(req, res),
|
|
351
|
+
}),
|
|
352
|
+
ctx.webServer.register({
|
|
353
|
+
kind: "exact",
|
|
354
|
+
path: "/__beauticode/ui/theme/use",
|
|
355
|
+
handler: (req, res) => ui.useTheme(req, res),
|
|
356
|
+
}),
|
|
357
|
+
ctx.webServer.register({
|
|
358
|
+
kind: "exact",
|
|
359
|
+
path: "/__beauticode/ui/preset",
|
|
360
|
+
handler: (req, res) => ui.applyPreset(req, res),
|
|
361
|
+
}),
|
|
362
|
+
ctx.webServer.register({
|
|
363
|
+
kind: "exact",
|
|
364
|
+
path: "/__beauticode/events",
|
|
365
|
+
handler: (req, res) => {
|
|
366
|
+
if (req.method !== "GET" || !isSameOrigin(req)) {
|
|
367
|
+
res.writeHead(req.method === "GET" ? 403 : 405).end();
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const url = new URL(req.url || "/", `http://${req.headers.host}`);
|
|
371
|
+
const clientId = url.searchParams.get("clientId");
|
|
372
|
+
if (!clientId || !/^[A-Za-z0-9._-]{8,80}$/.test(clientId)) {
|
|
373
|
+
res.writeHead(400).end();
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
res.writeHead(200, {
|
|
377
|
+
"content-type": "text/event-stream",
|
|
378
|
+
"cache-control": "no-cache",
|
|
379
|
+
connection: "keep-alive",
|
|
380
|
+
});
|
|
381
|
+
res.write(": connected\n\n");
|
|
382
|
+
clients.get(clientId)?.destroy();
|
|
383
|
+
clients.set(clientId, res);
|
|
384
|
+
clientStates.set(clientId, { render: null, mode: null });
|
|
385
|
+
if (current) {
|
|
386
|
+
res.write(`data: ${JSON.stringify({ type: "apply", ...current })}\n\n`);
|
|
387
|
+
}
|
|
388
|
+
res.write(`data: ${JSON.stringify({ type: "mode", ...modes })}\n\n`);
|
|
389
|
+
ui.scheduleRestore();
|
|
390
|
+
res.on("close", () => {
|
|
391
|
+
if (clients.get(clientId) === res) {
|
|
392
|
+
clients.delete(clientId);
|
|
393
|
+
clientStates.delete(clientId);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
},
|
|
397
|
+
}),
|
|
398
|
+
ctx.webServer.register({
|
|
399
|
+
kind: "exact",
|
|
400
|
+
path: "/__beauticode/apply",
|
|
401
|
+
handler: async (req, res) => {
|
|
402
|
+
if (req.method !== "POST") {
|
|
403
|
+
res.writeHead(405).end();
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (!(await authorized(req, tokenFile))) {
|
|
407
|
+
sendJson(res, 401, { ok: false, error: "未授权的请求。" });
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
const body = await readJson(req);
|
|
411
|
+
if (!validApplyPayload(body)) {
|
|
412
|
+
sendJson(res, 400, { ok: false, error: "背景载荷无效。" });
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
current = {
|
|
416
|
+
generation: body.generation,
|
|
417
|
+
media: body.media,
|
|
418
|
+
imageUrl: body.media === "clear" ? null : body.imageUrl,
|
|
419
|
+
videoUrl: body.media === "video" ? body.videoUrl : null,
|
|
420
|
+
startAt: body.media === "video" ? body.startAt ?? null : null,
|
|
421
|
+
};
|
|
422
|
+
const atmosphere = normalizeAtmosphere(body.atmosphere);
|
|
423
|
+
if (atmosphere) current.atmosphere = atmosphere;
|
|
424
|
+
if (body.media === "clear") modes.fish = false;
|
|
425
|
+
for (const state of clientStates.values()) state.render = null;
|
|
426
|
+
broadcast({ type: "apply", ...current });
|
|
427
|
+
if (body.media === "clear") broadcast({ type: "mode", ...modes });
|
|
428
|
+
sendJson(res, 200, { ok: true });
|
|
429
|
+
},
|
|
430
|
+
}),
|
|
431
|
+
ctx.webServer.register({
|
|
432
|
+
kind: "exact",
|
|
433
|
+
path: "/__beauticode/mode",
|
|
434
|
+
handler: async (req, res) => {
|
|
435
|
+
if (req.method !== "POST") {
|
|
436
|
+
res.writeHead(405).end();
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (!(await authorized(req, tokenFile))) {
|
|
440
|
+
sendJson(res, 401, { ok: false, error: "未授权的请求。" });
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
const body = await readJson(req);
|
|
444
|
+
const keys = Object.keys(body);
|
|
445
|
+
if (
|
|
446
|
+
keys.length === 0 ||
|
|
447
|
+
keys.some((key) => !["fish", "muted", "tone"].includes(key)) ||
|
|
448
|
+
("fish" in body && typeof body.fish !== "boolean") ||
|
|
449
|
+
("muted" in body && typeof body.muted !== "boolean") ||
|
|
450
|
+
("tone" in body && !validTone(body.tone))
|
|
451
|
+
) {
|
|
452
|
+
sendJson(res, 400, { ok: false, error: "显示模式载荷无效。" });
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
if (typeof body.fish === "boolean") modes.fish = body.fish;
|
|
456
|
+
if (typeof body.muted === "boolean") modes.muted = body.muted;
|
|
457
|
+
if (validTone(body.tone)) modes.tone = body.tone;
|
|
458
|
+
for (const state of clientStates.values()) state.mode = null;
|
|
459
|
+
broadcast({ type: "mode", ...modes });
|
|
460
|
+
sendJson(res, 200, { ok: true, modes: { ...modes } });
|
|
461
|
+
},
|
|
462
|
+
}),
|
|
463
|
+
ctx.webServer.register({
|
|
464
|
+
kind: "exact",
|
|
465
|
+
path: "/__beauticode/status",
|
|
466
|
+
handler: async (req, res) => {
|
|
467
|
+
if (req.method !== "GET") {
|
|
468
|
+
res.writeHead(405).end();
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (!(await authorized(req, tokenFile))) {
|
|
472
|
+
sendJson(res, 401, { ok: false, error: "未授权的请求。" });
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
sendJson(res, 200, publicStatus(current, modes, clients, clientStates));
|
|
476
|
+
},
|
|
477
|
+
}),
|
|
478
|
+
ctx.webServer.register({
|
|
479
|
+
kind: "exact",
|
|
480
|
+
path: "/__beauticode/ack",
|
|
481
|
+
handler: async (req, res) => {
|
|
482
|
+
if (req.method !== "POST" || !isSameOrigin(req)) {
|
|
483
|
+
res.writeHead(req.method === "POST" ? 403 : 405).end();
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
const body = await readJson(req);
|
|
487
|
+
const state = clientStates.get(body.clientId);
|
|
488
|
+
if (!clients.has(body.clientId) || !state) {
|
|
489
|
+
sendJson(res, 400, { ok: false, error: "渲染回执无效。" });
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
if (body.kind === "render") {
|
|
493
|
+
if (
|
|
494
|
+
!current ||
|
|
495
|
+
body.generation !== current.generation ||
|
|
496
|
+
body.media !== current.media ||
|
|
497
|
+
typeof body.ok !== "boolean" ||
|
|
498
|
+
typeof body.visible !== "boolean"
|
|
499
|
+
) {
|
|
500
|
+
sendJson(res, 400, { ok: false, error: "渲染回执无效。" });
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
let playback = null;
|
|
504
|
+
if (body.playback?.hasVideo === true) {
|
|
505
|
+
if (
|
|
506
|
+
!Number.isFinite(body.playback.currentTime) ||
|
|
507
|
+
!Number.isFinite(body.playback.duration) ||
|
|
508
|
+
typeof body.playback.muted !== "boolean" ||
|
|
509
|
+
typeof body.playback.paused !== "boolean" ||
|
|
510
|
+
typeof body.playback.blocked !== "boolean"
|
|
511
|
+
) {
|
|
512
|
+
sendJson(res, 400, { ok: false, error: "播放状态回执无效。" });
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
playback = {
|
|
516
|
+
currentTime: Math.max(0, body.playback.currentTime),
|
|
517
|
+
duration: Math.max(0, body.playback.duration),
|
|
518
|
+
hasVideo: true,
|
|
519
|
+
muted: body.playback.muted,
|
|
520
|
+
paused: body.playback.paused,
|
|
521
|
+
blocked: body.playback.blocked,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
state.render = {
|
|
525
|
+
generation: body.generation,
|
|
526
|
+
media: body.media,
|
|
527
|
+
ok: body.ok,
|
|
528
|
+
visible: body.visible,
|
|
529
|
+
error: typeof body.error === "string" ? body.error.slice(0, 300) : null,
|
|
530
|
+
playback,
|
|
531
|
+
};
|
|
532
|
+
} else if (body.kind === "mode") {
|
|
533
|
+
if (
|
|
534
|
+
typeof body.fish !== "boolean" ||
|
|
535
|
+
typeof body.muted !== "boolean" ||
|
|
536
|
+
!validTone(body.tone) ||
|
|
537
|
+
!["dark", "light"].includes(body.resolvedTone) ||
|
|
538
|
+
typeof body.themeSynced !== "boolean" ||
|
|
539
|
+
typeof body.blocked !== "boolean"
|
|
540
|
+
) {
|
|
541
|
+
sendJson(res, 400, { ok: false, error: "显示模式回执无效。" });
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
state.mode = {
|
|
545
|
+
fish: body.fish,
|
|
546
|
+
muted: body.muted,
|
|
547
|
+
tone: body.tone,
|
|
548
|
+
resolvedTone: body.resolvedTone,
|
|
549
|
+
themeSynced: body.themeSynced,
|
|
550
|
+
blocked: body.blocked,
|
|
551
|
+
};
|
|
552
|
+
} else {
|
|
553
|
+
sendJson(res, 400, { ok: false, error: "回执类型无效。" });
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
sendJson(res, 200, { ok: true });
|
|
557
|
+
},
|
|
558
|
+
}),
|
|
559
|
+
];
|
|
560
|
+
|
|
561
|
+
return () => {
|
|
562
|
+
disposeTap();
|
|
563
|
+
for (const dispose of disposers) dispose();
|
|
564
|
+
for (const response of clients.values()) response.destroy();
|
|
565
|
+
clients.clear();
|
|
566
|
+
clientStates.clear();
|
|
567
|
+
void ui.dispose();
|
|
568
|
+
};
|
|
569
|
+
}, "beauticode-bridge: routes and browser injection");
|
|
570
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "beauticode-dsh",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Cordis plugin: image/video backgrounds for DeepSeek Harness web.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.mjs",
|
|
7
|
+
"bin": {
|
|
8
|
+
"beauticode-dsh": "bin/beauticode-dsh"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./index.mjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"index.mjs",
|
|
17
|
+
"client.js",
|
|
18
|
+
"console.js",
|
|
19
|
+
"atmosphere.js",
|
|
20
|
+
"presets.mjs",
|
|
21
|
+
"agent.mjs",
|
|
22
|
+
"control-client.mjs",
|
|
23
|
+
"host-apply.mjs",
|
|
24
|
+
"ui-host.mjs",
|
|
25
|
+
"cli.js",
|
|
26
|
+
"bin/beauticode-dsh",
|
|
27
|
+
"cordis.patch.yml",
|
|
28
|
+
"vendor",
|
|
29
|
+
"themes"
|
|
30
|
+
],
|
|
31
|
+
"dsh": {
|
|
32
|
+
"bundle": {
|
|
33
|
+
"patch": "./cordis.patch.yml"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"dsh",
|
|
41
|
+
"dsh-plugin",
|
|
42
|
+
"deepseek",
|
|
43
|
+
"deepseek-harness",
|
|
44
|
+
"background",
|
|
45
|
+
"wallpaper"
|
|
46
|
+
],
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/starsstreaming/beautiCode.git",
|
|
50
|
+
"directory": "integrations/deepseek-harness"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=22"
|
|
54
|
+
},
|
|
55
|
+
"license": "MIT"
|
|
56
|
+
}
|
package/presets.mjs
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
|
|
7
|
+
export const ATMOSPHERE_PRESETS = Object.freeze({
|
|
8
|
+
internal: Object.freeze({
|
|
9
|
+
id: "internal",
|
|
10
|
+
name: "Internal",
|
|
11
|
+
file: "bg-internal.jpg",
|
|
12
|
+
tone: "light",
|
|
13
|
+
}),
|
|
14
|
+
infernal: Object.freeze({
|
|
15
|
+
id: "infernal",
|
|
16
|
+
name: "Infernal",
|
|
17
|
+
file: "bg-infernal.jpg",
|
|
18
|
+
tone: "dark",
|
|
19
|
+
}),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export function normalizeAtmosphere(value) {
|
|
23
|
+
if (!value || typeof value !== "object") return null;
|
|
24
|
+
if (
|
|
25
|
+
value.preset !== "internal" &&
|
|
26
|
+
value.preset !== "infernal" &&
|
|
27
|
+
value.preset !== "gallery"
|
|
28
|
+
) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return { preset: value.preset, rain: true, overlay: true, water: true };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function effectsForPreset(id) {
|
|
35
|
+
return normalizeAtmosphere({ preset: id });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function themeAssetsDir() {
|
|
39
|
+
const candidates = [
|
|
40
|
+
path.join(here, "themes", "internal-beyond"),
|
|
41
|
+
path.join(here, "..", "..", "assets", "themes", "internal-beyond"),
|
|
42
|
+
];
|
|
43
|
+
for (const dir of candidates) {
|
|
44
|
+
if (
|
|
45
|
+
fs.existsSync(path.join(dir, "bg-canvas-4k.png")) ||
|
|
46
|
+
fs.existsSync(path.join(dir, "bg-canvas.png")) ||
|
|
47
|
+
fs.existsSync(path.join(dir, "bg-internal.jpg"))
|
|
48
|
+
) {
|
|
49
|
+
return dir;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return candidates[candidates.length - 1];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function presetImagePath(id) {
|
|
56
|
+
const preset = ATMOSPHERE_PRESETS[id];
|
|
57
|
+
if (!preset) return null;
|
|
58
|
+
const filePath = path.join(themeAssetsDir(), preset.file);
|
|
59
|
+
return fs.existsSync(filePath) ? filePath : null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function canvasImagePath() {
|
|
63
|
+
const dir = themeAssetsDir();
|
|
64
|
+
const hi = path.join(dir, "bg-canvas-4k.png");
|
|
65
|
+
if (fs.existsSync(hi)) return hi;
|
|
66
|
+
const filePath = path.join(dir, "bg-canvas.png");
|
|
67
|
+
return fs.existsSync(filePath) ? filePath : null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function iceFrostImagePath() {
|
|
71
|
+
const filePath = path.join(themeAssetsDir(), "ice-frost.png");
|
|
72
|
+
return fs.existsSync(filePath) ? filePath : null;
|
|
73
|
+
}
|