beauticode-dsh 1.0.2 → 1.0.3
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 +1 -1
- package/agent.mjs +33 -0
- package/bin/beauticode-dsh +7 -252
- package/cli.js +37 -18
- package/console.js +15 -0
- package/control-client.mjs +75 -6
- package/cordis.patch.yml +1 -1
- package/gallery-host.mjs +253 -0
- package/gallery.js +178 -0
- package/index.mjs +34 -1
- package/package.json +4 -1
- package/skin-center.json +3 -0
- package/ui-host.mjs +17 -0
- package/vendor/adapter-dsh/session.js +3 -0
- package/vendor/adapter-dsh/tray-handoff.js +11 -13
- package/vendor/core/background-store.js +104 -1
- package/vendor/core/error-message.js +6 -0
- package/vendor/core/file-lock.js +34 -13
- package/vendor/core/index.js +2 -0
- package/vendor/core/process-liveness.js +205 -0
- package/vendor/core/skin-pack.js +64 -0
package/README.zh-CN.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
该 Cordis 插件向 DSH Web 注入 beautiCode 浏览器客户端,并提供本机鉴权接口。支持图片、MP4、播放位置、静音、摸鱼模式以及清除背景。
|
|
4
4
|
|
|
5
|
-
装好插件并运行 `dsh web` 后,侧栏「设置」上方会出现「背景」:可从文件夹选图片或 MP4
|
|
5
|
+
装好插件并运行 `dsh web` 后,侧栏「设置」上方会出现「背景」:可从文件夹选图片或 MP4、清除、开关声音、切换已保存主题,以及打开「皮肤中心」安装已审核的社区皮肤。网页控制台没有摸鱼;浅色/深色跟 DSH 自己的外观走。下次打开会恢复上次背景。设置 `BEAUTICODE_SKIN_CENTER` 或填写 `skin-center.json` 后,皮肤中心才会显示远程目录。
|
|
6
6
|
|
|
7
7
|
插件也会注册对话工具(`beauticode_*`)和斜杠命令(`/bg`、`/bg-theme`、`/bg-clear`)。不需要托盘;托盘若已在跑则复用它。
|
|
8
8
|
|
package/agent.mjs
CHANGED
|
@@ -240,6 +240,39 @@ export function createBeauticodeActions(dataRootOrOptions) {
|
|
|
240
240
|
});
|
|
241
241
|
},
|
|
242
242
|
|
|
243
|
+
async importTheme(input, signal) {
|
|
244
|
+
const name = String(input?.name ?? "").trim();
|
|
245
|
+
const imagePath = String(input?.imagePath ?? "").trim();
|
|
246
|
+
if (!name || !imagePath) fail("导入皮肤必须提供名称和图片。");
|
|
247
|
+
const body = {
|
|
248
|
+
name,
|
|
249
|
+
imagePath,
|
|
250
|
+
};
|
|
251
|
+
if (typeof input.videoPath === "string" && input.videoPath.trim()) {
|
|
252
|
+
body.videoPath = input.videoPath.trim();
|
|
253
|
+
}
|
|
254
|
+
if (input.effects) body.effects = input.effects;
|
|
255
|
+
if (input.source) body.source = input.source;
|
|
256
|
+
const resolved = await backend();
|
|
257
|
+
if (resolved.kind === "tray") {
|
|
258
|
+
const result = await request({
|
|
259
|
+
method: "POST",
|
|
260
|
+
path: "/theme/import",
|
|
261
|
+
body,
|
|
262
|
+
signal,
|
|
263
|
+
timeoutMs: 30 * 60 * 1000,
|
|
264
|
+
});
|
|
265
|
+
if (!result || result.ok === false) fail(result?.error || "导入皮肤失败。");
|
|
266
|
+
return {
|
|
267
|
+
ok: true,
|
|
268
|
+
theme: result.theme,
|
|
269
|
+
message: `已保存皮肤「${result.theme.name}」。`,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
const theme = await resolved.session.importSavedTheme(body);
|
|
273
|
+
return { ok: true, theme, message: `已保存皮肤「${theme.name}」。` };
|
|
274
|
+
},
|
|
275
|
+
|
|
243
276
|
async listThemes(signal) {
|
|
244
277
|
const resolved = await backend();
|
|
245
278
|
if (resolved.kind === "tray") {
|
package/bin/beauticode-dsh
CHANGED
|
@@ -1,252 +1,7 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import fs from "node:fs";
|
|
9
|
-
import fsp from "node:fs/promises";
|
|
10
|
-
import os from "node:os";
|
|
11
|
-
import path from "node:path";
|
|
12
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
13
|
-
|
|
14
|
-
function findPackageRoot(startDir) {
|
|
15
|
-
let dir = startDir;
|
|
16
|
-
for (let i = 0; i < 5; i += 1) {
|
|
17
|
-
if (
|
|
18
|
-
fs.existsSync(path.join(dir, "package.json")) &&
|
|
19
|
-
fs.existsSync(path.join(dir, "index.mjs"))
|
|
20
|
-
) {
|
|
21
|
-
return dir;
|
|
22
|
-
}
|
|
23
|
-
const parent = path.dirname(dir);
|
|
24
|
-
if (parent === dir) break;
|
|
25
|
-
dir = parent;
|
|
26
|
-
}
|
|
27
|
-
return startDir;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const here = findPackageRoot(path.dirname(fileURLToPath(import.meta.url)));
|
|
31
|
-
const pluginName = "beauticode-dsh";
|
|
32
|
-
const bridgeId = "beauticode-bridge";
|
|
33
|
-
|
|
34
|
-
function argValue(argv, name) {
|
|
35
|
-
const idx = argv.indexOf(name);
|
|
36
|
-
if (idx === -1) return null;
|
|
37
|
-
const value = argv[idx + 1];
|
|
38
|
-
return value && !value.startsWith("--") ? value : null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function defaultDataRoot() {
|
|
42
|
-
if (process.env.BEAUTICODE_DATA_ROOT) return process.env.BEAUTICODE_DATA_ROOT;
|
|
43
|
-
if (process.env.LOCALAPPDATA) {
|
|
44
|
-
return path.join(process.env.LOCALAPPDATA, "beautiCode");
|
|
45
|
-
}
|
|
46
|
-
return path.join(os.homedir(), ".beauticode");
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function defaultDshHome() {
|
|
50
|
-
return process.env.DSH_HOME || path.join(os.homedir(), ".dsh");
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function toFileUri(filePath) {
|
|
54
|
-
const full = path.resolve(filePath).replaceAll("\\", "/");
|
|
55
|
-
if (/^[A-Za-z]:/.test(full)) return `file:///${full}`;
|
|
56
|
-
return pathToFileURL(full).href;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function fileUriInsert(uri) {
|
|
60
|
-
return [
|
|
61
|
-
"# beauticode-bridge (installer)",
|
|
62
|
-
"- insert:",
|
|
63
|
-
" - id: beauticode-bridge",
|
|
64
|
-
` name: '${uri}'`,
|
|
65
|
-
" inject: [webServer]",
|
|
66
|
-
"",
|
|
67
|
-
].join("\n");
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function packageInsert() {
|
|
71
|
-
return [
|
|
72
|
-
"# beauticode-bridge (installer)",
|
|
73
|
-
"- insert:",
|
|
74
|
-
" - id: beauticode-bridge",
|
|
75
|
-
` name: '${pluginName}'`,
|
|
76
|
-
" inject: [webServer]",
|
|
77
|
-
"",
|
|
78
|
-
].join("\n");
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function hasBridge(text) {
|
|
82
|
-
return new RegExp(`^\\s*-\\s*id:\\s*${bridgeId}\\s*$`, "m").test(text);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function stripBridge(text) {
|
|
86
|
-
const patterns = [
|
|
87
|
-
/(?:^|\r?\n)# beauticode-bridge \(installer\)\r?\n- insert:\r?\n(?:[ \t]+.*\r?\n)*/g,
|
|
88
|
-
/(?:^|\r?\n)- insert:\r?\n(?:[ \t]+.*\r?\n)*?[ \t]+-\s*id:\s*beauticode-bridge\r?\n(?:[ \t]+.*\r?\n)*/g,
|
|
89
|
-
];
|
|
90
|
-
let next = text;
|
|
91
|
-
for (const pattern of patterns) next = next.replace(pattern, "\n");
|
|
92
|
-
return next;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async function writePatch(filePath, body) {
|
|
96
|
-
await fsp.mkdir(path.dirname(filePath), { recursive: true });
|
|
97
|
-
if (!fs.existsSync(filePath)) {
|
|
98
|
-
await fsp.writeFile(filePath, body, "utf8");
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
const raw = await fsp.readFile(filePath, "utf8");
|
|
102
|
-
if (hasBridge(raw)) {
|
|
103
|
-
const replaced = raw.replace(
|
|
104
|
-
/(?:# beauticode-bridge \(installer\)\r?\n)?- insert:\r?\n(?:[ \t]+.*\r?\n)*?[ \t]+-\s*id:\s*beauticode-bridge\r?\n(?:[ \t]+.*\r?\n)*/,
|
|
105
|
-
body,
|
|
106
|
-
);
|
|
107
|
-
await fsp.writeFile(filePath, replaced.endsWith("\n") ? replaced : `${replaced}\n`, "utf8");
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
const stripped = raw.trim();
|
|
111
|
-
if (stripped === "" || stripped === "[]") {
|
|
112
|
-
await fsp.writeFile(filePath, body, "utf8");
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
await fsp.writeFile(filePath, `${stripped}\n\n${body}`, "utf8");
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async function removePatch(filePath) {
|
|
119
|
-
if (!fs.existsSync(filePath)) return false;
|
|
120
|
-
const raw = await fsp.readFile(filePath, "utf8");
|
|
121
|
-
if (!hasBridge(raw)) return false;
|
|
122
|
-
const cleaned = stripBridge(raw).trim();
|
|
123
|
-
if (cleaned === "" || cleaned === "[]") {
|
|
124
|
-
await fsp.writeFile(
|
|
125
|
-
filePath,
|
|
126
|
-
"# Your patch layer for this dsh profile.\n[]\n",
|
|
127
|
-
"utf8",
|
|
128
|
-
);
|
|
129
|
-
return true;
|
|
130
|
-
}
|
|
131
|
-
await fsp.writeFile(filePath, `${cleaned}\n`, "utf8");
|
|
132
|
-
return true;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function shouldCopy(source) {
|
|
136
|
-
const relative = path.relative(here, source);
|
|
137
|
-
if (relative.startsWith("test") || relative.includes(`${path.sep}test${path.sep}`)) {
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
if (relative.endsWith(".test.mjs")) return false;
|
|
141
|
-
return true;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
async function sameInstalledVersion(dest) {
|
|
145
|
-
try {
|
|
146
|
-
const incoming = JSON.parse(await fsp.readFile(path.join(here, "package.json"), "utf8"));
|
|
147
|
-
const installed = JSON.parse(await fsp.readFile(path.join(dest, "package.json"), "utf8"));
|
|
148
|
-
return (
|
|
149
|
-
incoming.name === installed.name &&
|
|
150
|
-
incoming.version === installed.version &&
|
|
151
|
-
fs.existsSync(path.join(dest, "index.mjs")) &&
|
|
152
|
-
fs.existsSync(path.join(dest, "vendor", "adapter-dsh", "index.js"))
|
|
153
|
-
);
|
|
154
|
-
} catch {
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async function copyPackage(dest) {
|
|
160
|
-
if (await sameInstalledVersion(dest)) return false;
|
|
161
|
-
await fsp.rm(dest, { recursive: true, force: true });
|
|
162
|
-
await fsp.cp(here, dest, {
|
|
163
|
-
recursive: true,
|
|
164
|
-
filter: (source) => shouldCopy(source),
|
|
165
|
-
});
|
|
166
|
-
return true;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
async function ensureEngine(dest) {
|
|
170
|
-
const vendor = path.join(dest, "vendor", "adapter-dsh", "index.js");
|
|
171
|
-
if (fs.existsSync(vendor)) return;
|
|
172
|
-
const packPath = path.resolve(here, "../../scripts/pack-dsh-plugin.mjs");
|
|
173
|
-
if (!fs.existsSync(packPath)) {
|
|
174
|
-
throw new Error("插件包不完整:缺少本机导入引擎。请重新执行 npx beauticode-dsh。");
|
|
175
|
-
}
|
|
176
|
-
const { stageEngineInto } = await import(pathToFileURL(packPath).href);
|
|
177
|
-
await stageEngineInto(dest);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
async function install(opts) {
|
|
181
|
-
const dest = path.resolve(opts.pluginHome);
|
|
182
|
-
const dshHome = path.resolve(opts.dshHome);
|
|
183
|
-
const webProfile = path.join(dshHome, "profiles", "web");
|
|
184
|
-
const webPatch = path.join(webProfile, "cordis.patch.yml");
|
|
185
|
-
const webPackage = path.join(webProfile, "package.json");
|
|
186
|
-
const homePatch = path.join(dshHome, "cordis.patch.yml");
|
|
187
|
-
|
|
188
|
-
await fsp.mkdir(dest, { recursive: true });
|
|
189
|
-
const copied = await copyPackage(dest);
|
|
190
|
-
await ensureEngine(dest);
|
|
191
|
-
const indexFile = path.join(dest, "index.mjs");
|
|
192
|
-
if (!fs.existsSync(indexFile)) {
|
|
193
|
-
throw new Error(`缺少插件入口:${indexFile}`);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (fs.existsSync(webPackage)) {
|
|
197
|
-
await writePatch(webPatch, fileUriInsert(toFileUri(indexFile)));
|
|
198
|
-
if (fs.existsSync(homePatch)) {
|
|
199
|
-
const homeRaw = await fsp.readFile(homePatch, "utf8");
|
|
200
|
-
if (hasBridge(homeRaw)) await removePatch(homePatch);
|
|
201
|
-
}
|
|
202
|
-
console.log(`已写入 ${webPatch}`);
|
|
203
|
-
console.log(copied ? `插件已安装到 ${dest}` : `已复用已安装的插件 ${dest}`);
|
|
204
|
-
console.log("请自己运行:npx @deepseek-ai/dsh web");
|
|
205
|
-
return { dest, patch: webPatch };
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
await fsp.mkdir(dshHome, { recursive: true });
|
|
209
|
-
await writePatch(homePatch, fileUriInsert(toFileUri(indexFile)));
|
|
210
|
-
console.log(`DSH web profile 还不存在,已写入 ${homePatch}`);
|
|
211
|
-
console.log(copied ? `插件已安装到 ${dest}` : `已复用已安装的插件 ${dest}`);
|
|
212
|
-
console.log("请自己运行:npx @deepseek-ai/dsh web");
|
|
213
|
-
return { dest, patch: homePatch };
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
async function uninstall(opts) {
|
|
217
|
-
const dshHome = path.resolve(opts.dshHome);
|
|
218
|
-
const removed = [];
|
|
219
|
-
if (await removePatch(path.join(dshHome, "profiles", "web", "cordis.patch.yml"))) {
|
|
220
|
-
removed.push("web patch");
|
|
221
|
-
}
|
|
222
|
-
if (await removePatch(path.join(dshHome, "cordis.patch.yml"))) {
|
|
223
|
-
removed.push("home patch");
|
|
224
|
-
}
|
|
225
|
-
const dest = path.resolve(opts.pluginHome);
|
|
226
|
-
if (fs.existsSync(dest)) {
|
|
227
|
-
await fsp.rm(dest, { recursive: true, force: true });
|
|
228
|
-
removed.push(dest);
|
|
229
|
-
}
|
|
230
|
-
console.log(removed.length ? `已移除:${removed.join("、")}` : "没有可移除的 beautiCode 插件接线。");
|
|
231
|
-
return { removed };
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
export async function runCli(argv = process.argv.slice(2)) {
|
|
235
|
-
const dshHome = argValue(argv, "--dsh-home") || defaultDshHome();
|
|
236
|
-
const pluginHome =
|
|
237
|
-
argValue(argv, "--plugin-home") || path.join(defaultDataRoot(), "plugin");
|
|
238
|
-
const opts = { dshHome, pluginHome };
|
|
239
|
-
if (argv.includes("--remove")) return uninstall(opts);
|
|
240
|
-
return install(opts);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const launchedDirectly =
|
|
244
|
-
Boolean(process.argv[1]) &&
|
|
245
|
-
pathToFileURL(path.resolve(process.argv[1])).href.toLowerCase() ===
|
|
246
|
-
import.meta.url.toLowerCase();
|
|
247
|
-
if (launchedDirectly) {
|
|
248
|
-
runCli().catch((error) => {
|
|
249
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
250
|
-
process.exit(1);
|
|
251
|
-
});
|
|
252
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runCli } from "../cli.js";
|
|
3
|
+
|
|
4
|
+
runCli().catch((error) => {
|
|
5
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
6
|
+
process.exit(1);
|
|
7
|
+
});
|
package/cli.js
CHANGED
|
@@ -92,35 +92,54 @@ function stripBridge(text) {
|
|
|
92
92
|
return next;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
function overlayPayload(text) {
|
|
96
|
+
return text
|
|
97
|
+
.split(/\r?\n/)
|
|
98
|
+
.map((line) => line.trim())
|
|
99
|
+
.filter((line) => line && !line.startsWith("#"))
|
|
100
|
+
.join("\n");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function isEmptyOverlay(text) {
|
|
104
|
+
const payload = overlayPayload(text);
|
|
105
|
+
return payload === "" || payload === "[]";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// DSH seeds overlays as `# comment\n[]`. `[]` is already a complete YAML
|
|
109
|
+
// document, so appending `- insert:` makes js-yaml throw.
|
|
110
|
+
function keptOverlay(text) {
|
|
111
|
+
const withoutFlowEmpty = text
|
|
112
|
+
.split(/\r?\n/)
|
|
113
|
+
.filter((line) => line.trim() !== "[]")
|
|
114
|
+
.join("\n");
|
|
115
|
+
if (isEmptyOverlay(withoutFlowEmpty)) return "";
|
|
116
|
+
return withoutFlowEmpty.trim();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function withTrailingNewline(text) {
|
|
120
|
+
return text.endsWith("\n") ? text : `${text}\n`;
|
|
121
|
+
}
|
|
122
|
+
|
|
95
123
|
async function writePatch(filePath, body) {
|
|
96
124
|
await fsp.mkdir(path.dirname(filePath), { recursive: true });
|
|
125
|
+
const insert = withTrailingNewline(body);
|
|
97
126
|
if (!fs.existsSync(filePath)) {
|
|
98
|
-
await fsp.writeFile(filePath,
|
|
127
|
+
await fsp.writeFile(filePath, insert, "utf8");
|
|
99
128
|
return;
|
|
100
129
|
}
|
|
101
130
|
const raw = await fsp.readFile(filePath, "utf8");
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
);
|
|
107
|
-
await fsp.writeFile(filePath, replaced.endsWith("\n") ? replaced : `${replaced}\n`, "utf8");
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
const stripped = raw.trim();
|
|
111
|
-
if (stripped === "" || stripped === "[]") {
|
|
112
|
-
await fsp.writeFile(filePath, body, "utf8");
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
await fsp.writeFile(filePath, `${stripped}\n\n${body}`, "utf8");
|
|
131
|
+
const remainder = hasBridge(raw) ? stripBridge(raw) : raw;
|
|
132
|
+
const kept = keptOverlay(remainder);
|
|
133
|
+
const next = kept ? `${kept}\n\n${insert}` : insert;
|
|
134
|
+
await fsp.writeFile(filePath, withTrailingNewline(next), "utf8");
|
|
116
135
|
}
|
|
117
136
|
|
|
118
137
|
async function removePatch(filePath) {
|
|
119
138
|
if (!fs.existsSync(filePath)) return false;
|
|
120
139
|
const raw = await fsp.readFile(filePath, "utf8");
|
|
121
140
|
if (!hasBridge(raw)) return false;
|
|
122
|
-
const
|
|
123
|
-
if (
|
|
141
|
+
const kept = keptOverlay(stripBridge(raw));
|
|
142
|
+
if (!kept) {
|
|
124
143
|
await fsp.writeFile(
|
|
125
144
|
filePath,
|
|
126
145
|
"# Your patch layer for this dsh profile.\n[]\n",
|
|
@@ -128,7 +147,7 @@ async function removePatch(filePath) {
|
|
|
128
147
|
);
|
|
129
148
|
return true;
|
|
130
149
|
}
|
|
131
|
-
await fsp.writeFile(filePath,
|
|
150
|
+
await fsp.writeFile(filePath, withTrailingNewline(kept), "utf8");
|
|
132
151
|
return true;
|
|
133
152
|
}
|
|
134
153
|
|
package/console.js
CHANGED
|
@@ -64,6 +64,9 @@ body:not([data-ds-dark-theme]) #beauticode-console-pop .bc-theme-item:hover{back
|
|
|
64
64
|
'<button type="button" class="bc-btn" data-act="clear">清除</button>' +
|
|
65
65
|
'<button type="button" class="bc-btn" data-act="sound">声音</button>' +
|
|
66
66
|
"</div>" +
|
|
67
|
+
'<div class="bc-row">' +
|
|
68
|
+
'<button type="button" class="bc-btn" data-act="gallery">皮肤中心</button>' +
|
|
69
|
+
"</div>" +
|
|
67
70
|
'<div class="bc-themes" hidden>' +
|
|
68
71
|
'<button type="button" class="bc-theme-toggle" aria-expanded="false">已保存主题</button>' +
|
|
69
72
|
'<div class="bc-theme-list" hidden></div>' +
|
|
@@ -236,6 +239,15 @@ body:not([data-ds-dark-theme]) #beauticode-console-pop .bc-theme-item:hover{back
|
|
|
236
239
|
fileInput.accept = VIDEO_ACCEPT;
|
|
237
240
|
fileInput.click();
|
|
238
241
|
});
|
|
242
|
+
pop.querySelector('[data-act="gallery"]').addEventListener("click", (event) => {
|
|
243
|
+
event.stopPropagation();
|
|
244
|
+
setOpen(false);
|
|
245
|
+
if (window.BeauticodeGallery) {
|
|
246
|
+
void window.BeauticodeGallery.open();
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
showMessage("皮肤中心脚本尚未加载。");
|
|
250
|
+
});
|
|
239
251
|
pop.querySelector('[data-act="clear"]').addEventListener("click", () => {
|
|
240
252
|
currentThemeId = "";
|
|
241
253
|
void run(() => request("/__beauticode/ui/clear", { method: "POST" }));
|
|
@@ -294,6 +306,9 @@ body:not([data-ds-dark-theme]) #beauticode-console-pop .bc-theme-item:hover{back
|
|
|
294
306
|
document.addEventListener("keydown", (event) => {
|
|
295
307
|
if (event.key === "Escape" && !pop.hidden) setOpen(false);
|
|
296
308
|
});
|
|
309
|
+
document.addEventListener("beauticode-gallery-installed", () => {
|
|
310
|
+
void refresh();
|
|
311
|
+
});
|
|
297
312
|
|
|
298
313
|
const observer = new MutationObserver(() => place());
|
|
299
314
|
observer.observe(document.documentElement, { childList: true, subtree: true });
|
package/control-client.mjs
CHANGED
|
@@ -90,6 +90,47 @@ export function isPidAlive(pid) {
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
let livenessModulePromise;
|
|
94
|
+
|
|
95
|
+
function loadCoreLiveness() {
|
|
96
|
+
if (!livenessModulePromise) {
|
|
97
|
+
livenessModulePromise = (async () => {
|
|
98
|
+
const candidates = [
|
|
99
|
+
"@beauticode/core",
|
|
100
|
+
new URL("./vendor/core/index.js", import.meta.url).href,
|
|
101
|
+
new URL("../../packages/core/dist/index.js", import.meta.url).href,
|
|
102
|
+
];
|
|
103
|
+
for (const specifier of candidates) {
|
|
104
|
+
try {
|
|
105
|
+
const mod = await import(specifier);
|
|
106
|
+
if (typeof mod.isRecordedPidLive === "function") return mod;
|
|
107
|
+
} catch {
|
|
108
|
+
/* try the next resolution path */
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
})();
|
|
113
|
+
}
|
|
114
|
+
return livenessModulePromise;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function isLiveRecordedPid(pid, startedAt, mtimeMs) {
|
|
118
|
+
if (!isPidAlive(pid)) return false;
|
|
119
|
+
const recorded =
|
|
120
|
+
typeof startedAt === "string" && startedAt
|
|
121
|
+
? startedAt
|
|
122
|
+
: Number.isFinite(mtimeMs)
|
|
123
|
+
? new Date(mtimeMs).toISOString()
|
|
124
|
+
: null;
|
|
125
|
+
try {
|
|
126
|
+
const core = await loadCoreLiveness();
|
|
127
|
+
if (core) return await core.isRecordedPidLive(pid, recorded);
|
|
128
|
+
} catch {
|
|
129
|
+
/* fall back to the cheap PID check */
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
|
|
93
134
|
export function stripPathQuotes(value) {
|
|
94
135
|
const text = String(value ?? "").trim();
|
|
95
136
|
if (text.length >= 2) {
|
|
@@ -195,6 +236,7 @@ export async function writeDshControlFile(opts) {
|
|
|
195
236
|
pid,
|
|
196
237
|
url: new URL(url).origin,
|
|
197
238
|
token,
|
|
239
|
+
startedAt: new Date().toISOString(),
|
|
198
240
|
});
|
|
199
241
|
}
|
|
200
242
|
|
|
@@ -219,6 +261,7 @@ export async function writeSessionHostFile(opts) {
|
|
|
219
261
|
pid,
|
|
220
262
|
url: new URL(url).origin,
|
|
221
263
|
token,
|
|
264
|
+
startedAt: new Date().toISOString(),
|
|
222
265
|
});
|
|
223
266
|
}
|
|
224
267
|
|
|
@@ -253,8 +296,11 @@ export async function removeDshControlFile(opts) {
|
|
|
253
296
|
export async function readDshControlFile(dataRoot, opts = {}) {
|
|
254
297
|
const file = controlFilePath(dataRoot);
|
|
255
298
|
let raw;
|
|
299
|
+
let mtimeMs = 0;
|
|
256
300
|
try {
|
|
257
|
-
|
|
301
|
+
const [text, stat] = await Promise.all([fs.readFile(file, "utf8"), fs.stat(file)]);
|
|
302
|
+
raw = text;
|
|
303
|
+
mtimeMs = stat.mtimeMs;
|
|
258
304
|
} catch (error) {
|
|
259
305
|
if (error && typeof error === "object" && error.code === "ENOENT") return null;
|
|
260
306
|
throw error;
|
|
@@ -277,13 +323,19 @@ export async function readDshControlFile(dataRoot, opts = {}) {
|
|
|
277
323
|
) {
|
|
278
324
|
return null;
|
|
279
325
|
}
|
|
280
|
-
if (
|
|
326
|
+
if (
|
|
327
|
+
!opts.allowDead &&
|
|
328
|
+
!(await isLiveRecordedPid(parsed.pid, parsed.startedAt, mtimeMs))
|
|
329
|
+
) {
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
281
332
|
return {
|
|
282
333
|
schema: CONTROL_SCHEMA,
|
|
283
334
|
host: "dsh",
|
|
284
335
|
pid: parsed.pid,
|
|
285
336
|
url: new URL(parsed.url).origin,
|
|
286
337
|
token: parsed.token,
|
|
338
|
+
startedAt: typeof parsed.startedAt === "string" ? parsed.startedAt : null,
|
|
287
339
|
};
|
|
288
340
|
}
|
|
289
341
|
|
|
@@ -305,8 +357,11 @@ export async function removeSessionHostFile(opts) {
|
|
|
305
357
|
export async function readSessionHostFile(dataRoot, opts = {}) {
|
|
306
358
|
const file = sessionHostFilePath(dataRoot);
|
|
307
359
|
let raw;
|
|
360
|
+
let mtimeMs = 0;
|
|
308
361
|
try {
|
|
309
|
-
|
|
362
|
+
const [text, stat] = await Promise.all([fs.readFile(file, "utf8"), fs.stat(file)]);
|
|
363
|
+
raw = text;
|
|
364
|
+
mtimeMs = stat.mtimeMs;
|
|
310
365
|
} catch (error) {
|
|
311
366
|
if (error && typeof error === "object" && error.code === "ENOENT") return null;
|
|
312
367
|
throw error;
|
|
@@ -329,21 +384,30 @@ export async function readSessionHostFile(dataRoot, opts = {}) {
|
|
|
329
384
|
) {
|
|
330
385
|
return null;
|
|
331
386
|
}
|
|
332
|
-
if (
|
|
387
|
+
if (
|
|
388
|
+
!opts.allowDead &&
|
|
389
|
+
!(await isLiveRecordedPid(parsed.pid, parsed.startedAt, mtimeMs))
|
|
390
|
+
) {
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
333
393
|
return {
|
|
334
394
|
schema: SESSION_HOST_SCHEMA,
|
|
335
395
|
host: parsed.host,
|
|
336
396
|
pid: parsed.pid,
|
|
337
397
|
url: new URL(parsed.url).origin,
|
|
338
398
|
token: parsed.token,
|
|
399
|
+
startedAt: typeof parsed.startedAt === "string" ? parsed.startedAt : null,
|
|
339
400
|
};
|
|
340
401
|
}
|
|
341
402
|
|
|
342
403
|
export async function readTrayClaim(dataRoot, opts = {}) {
|
|
343
404
|
const file = trayClaimFilePath(dataRoot);
|
|
344
405
|
let raw;
|
|
406
|
+
let mtimeMs = 0;
|
|
345
407
|
try {
|
|
346
|
-
|
|
408
|
+
const [text, stat] = await Promise.all([fs.readFile(file, "utf8"), fs.stat(file)]);
|
|
409
|
+
raw = text;
|
|
410
|
+
mtimeMs = stat.mtimeMs;
|
|
347
411
|
} catch (error) {
|
|
348
412
|
if (error && typeof error === "object" && error.code === "ENOENT") return null;
|
|
349
413
|
throw error;
|
|
@@ -362,7 +426,12 @@ export async function readTrayClaim(dataRoot, opts = {}) {
|
|
|
362
426
|
) {
|
|
363
427
|
return null;
|
|
364
428
|
}
|
|
365
|
-
if (
|
|
429
|
+
if (
|
|
430
|
+
!opts.allowDead &&
|
|
431
|
+
!(await isLiveRecordedPid(parsed.pid, parsed.startedAt, mtimeMs))
|
|
432
|
+
) {
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
366
435
|
return {
|
|
367
436
|
schema: TRAY_CLAIM_SCHEMA,
|
|
368
437
|
pid: parsed.pid,
|
package/cordis.patch.yml
CHANGED