beauticode-dsh 1.0.2 → 1.0.4
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 +185 -30
- 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 +8 -1
- package/vendor/adapter-dsh/tray-handoff.js +11 -13
- package/vendor/core/apply-transaction.js +19 -7
- package/vendor/core/background-store.js +186 -47
- 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/media-server.js +23 -9
- 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
|
@@ -78,49 +78,115 @@ function packageInsert() {
|
|
|
78
78
|
].join("\n");
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
function hasBridge(text) {
|
|
82
|
-
return new RegExp(`^\\s*-\\s*id:\\s*${bridgeId}\\s*$`, "m").test(text);
|
|
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 lines = text.split(/\r?\n/);
|
|
87
|
+
const removed = new Array(lines.length).fill(false);
|
|
88
|
+
const indentOf = (line) => line.match(/^[ \t]*/)?.[0].length ?? 0;
|
|
89
|
+
|
|
90
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
91
|
+
const insert = lines[i].match(/^([ \t]*)-\s*insert:\s*(?:#.*)?$/);
|
|
92
|
+
if (!insert) continue;
|
|
93
|
+
const insertIndent = insert[1].length;
|
|
94
|
+
let blockEnd = i + 1;
|
|
95
|
+
while (blockEnd < lines.length) {
|
|
96
|
+
const line = lines[blockEnd];
|
|
97
|
+
if (line.trim() && indentOf(line) <= insertIndent) break;
|
|
98
|
+
blockEnd += 1;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let foundBridge = false;
|
|
102
|
+
for (let j = i + 1; j < blockEnd; ) {
|
|
103
|
+
const bridge = lines[j].match(
|
|
104
|
+
/^([ \t]*)-\s*id:\s*beauticode-bridge\s*(?:#.*)?$/,
|
|
105
|
+
);
|
|
106
|
+
if (!bridge || bridge[1].length <= insertIndent) {
|
|
107
|
+
j += 1;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
foundBridge = true;
|
|
111
|
+
const itemIndent = bridge[1].length;
|
|
112
|
+
let itemEnd = j + 1;
|
|
113
|
+
while (itemEnd < blockEnd) {
|
|
114
|
+
const line = lines[itemEnd];
|
|
115
|
+
if (line.trim() && indentOf(line) <= itemIndent) break;
|
|
116
|
+
itemEnd += 1;
|
|
117
|
+
}
|
|
118
|
+
for (let k = j; k < itemEnd; k += 1) removed[k] = true;
|
|
119
|
+
j = itemEnd;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!foundBridge) continue;
|
|
123
|
+
const marker = `${insert[1]}# beauticode-bridge (installer)`;
|
|
124
|
+
if (i > 0 && lines[i - 1].trimEnd() === marker) removed[i - 1] = true;
|
|
125
|
+
const hasSibling = lines
|
|
126
|
+
.slice(i + 1, blockEnd)
|
|
127
|
+
.some(
|
|
128
|
+
(line, offset) =>
|
|
129
|
+
!removed[i + 1 + offset] &&
|
|
130
|
+
line.trim() !== "" &&
|
|
131
|
+
!line.trimStart().startsWith("#"),
|
|
132
|
+
);
|
|
133
|
+
if (!hasSibling) {
|
|
134
|
+
for (let k = i; k < blockEnd; k += 1) removed[k] = true;
|
|
135
|
+
}
|
|
136
|
+
i = blockEnd - 1;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return lines.filter((_, index) => !removed[index]).join("\n");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function overlayPayload(text) {
|
|
143
|
+
return text
|
|
144
|
+
.split(/\r?\n/)
|
|
145
|
+
.map((line) => line.trim())
|
|
146
|
+
.filter((line) => line && !line.startsWith("#"))
|
|
147
|
+
.join("\n");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function isEmptyOverlay(text) {
|
|
151
|
+
const payload = overlayPayload(text);
|
|
152
|
+
return payload === "" || payload === "[]";
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// DSH seeds overlays as `# comment\n[]`. `[]` is already a complete YAML
|
|
156
|
+
// document, so appending `- insert:` makes js-yaml throw.
|
|
157
|
+
function keptOverlay(text) {
|
|
158
|
+
const withoutFlowEmpty = text
|
|
159
|
+
.split(/\r?\n/)
|
|
160
|
+
.filter((line) => line.trim() !== "[]")
|
|
161
|
+
.join("\n");
|
|
162
|
+
if (isEmptyOverlay(withoutFlowEmpty)) return "";
|
|
163
|
+
return withoutFlowEmpty.trim();
|
|
83
164
|
}
|
|
84
165
|
|
|
85
|
-
function
|
|
86
|
-
|
|
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;
|
|
166
|
+
function withTrailingNewline(text) {
|
|
167
|
+
return text.endsWith("\n") ? text : `${text}\n`;
|
|
93
168
|
}
|
|
94
169
|
|
|
95
170
|
async function writePatch(filePath, body) {
|
|
96
171
|
await fsp.mkdir(path.dirname(filePath), { recursive: true });
|
|
172
|
+
const insert = withTrailingNewline(body);
|
|
97
173
|
if (!fs.existsSync(filePath)) {
|
|
98
|
-
await fsp.writeFile(filePath,
|
|
174
|
+
await fsp.writeFile(filePath, insert, "utf8");
|
|
99
175
|
return;
|
|
100
176
|
}
|
|
101
177
|
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");
|
|
178
|
+
const remainder = hasBridge(raw) ? stripBridge(raw) : raw;
|
|
179
|
+
const kept = keptOverlay(remainder);
|
|
180
|
+
const next = kept ? `${kept}\n\n${insert}` : insert;
|
|
181
|
+
await fsp.writeFile(filePath, withTrailingNewline(next), "utf8");
|
|
116
182
|
}
|
|
117
183
|
|
|
118
184
|
async function removePatch(filePath) {
|
|
119
185
|
if (!fs.existsSync(filePath)) return false;
|
|
120
186
|
const raw = await fsp.readFile(filePath, "utf8");
|
|
121
187
|
if (!hasBridge(raw)) return false;
|
|
122
|
-
const
|
|
123
|
-
if (
|
|
188
|
+
const kept = keptOverlay(stripBridge(raw));
|
|
189
|
+
if (!kept) {
|
|
124
190
|
await fsp.writeFile(
|
|
125
191
|
filePath,
|
|
126
192
|
"# Your patch layer for this dsh profile.\n[]\n",
|
|
@@ -128,7 +194,7 @@ async function removePatch(filePath) {
|
|
|
128
194
|
);
|
|
129
195
|
return true;
|
|
130
196
|
}
|
|
131
|
-
await fsp.writeFile(filePath,
|
|
197
|
+
await fsp.writeFile(filePath, withTrailingNewline(kept), "utf8");
|
|
132
198
|
return true;
|
|
133
199
|
}
|
|
134
200
|
|
|
@@ -177,6 +243,83 @@ async function ensureEngine(dest) {
|
|
|
177
243
|
await stageEngineInto(dest);
|
|
178
244
|
}
|
|
179
245
|
|
|
246
|
+
function pluginLinkPath(webProfile) {
|
|
247
|
+
return path.join(webProfile, "node_modules", pluginName);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function legacyPluginLinkPath(webProfile) {
|
|
251
|
+
return path.join(webProfile, "node_modules", "@beauticode", "dsh-plugin");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function linkSpecFor(pluginHome) {
|
|
255
|
+
return `link:${path.resolve(pluginHome).replaceAll("\\", "/")}`;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function sameLinkTarget(link, dest) {
|
|
259
|
+
try {
|
|
260
|
+
const stat = await fsp.lstat(link);
|
|
261
|
+
if (stat.isSymbolicLink()) {
|
|
262
|
+
const target = await fsp.readlink(link);
|
|
263
|
+
return path.resolve(path.dirname(link), target) === path.resolve(dest);
|
|
264
|
+
}
|
|
265
|
+
if (stat.isDirectory()) {
|
|
266
|
+
return path.resolve(link) === path.resolve(dest);
|
|
267
|
+
}
|
|
268
|
+
} catch {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function linkPluginIntoProfile(webProfile, dest) {
|
|
275
|
+
const link = pluginLinkPath(webProfile);
|
|
276
|
+
const legacy = legacyPluginLinkPath(webProfile);
|
|
277
|
+
if (fs.existsSync(legacy)) {
|
|
278
|
+
await fsp.rm(legacy, { recursive: true, force: true });
|
|
279
|
+
}
|
|
280
|
+
await fsp.mkdir(path.dirname(link), { recursive: true });
|
|
281
|
+
if (fs.existsSync(link)) {
|
|
282
|
+
if (await sameLinkTarget(link, dest)) return;
|
|
283
|
+
await fsp.rm(link, { recursive: true, force: true });
|
|
284
|
+
}
|
|
285
|
+
const type = process.platform === "win32" ? "junction" : "dir";
|
|
286
|
+
await fsp.symlink(path.resolve(dest), link, type);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function ensureWebPackageDep(webPackage, pluginHome) {
|
|
290
|
+
const raw = await fsp.readFile(webPackage, "utf8");
|
|
291
|
+
const json = JSON.parse(raw);
|
|
292
|
+
if (!json.dependencies || typeof json.dependencies !== "object" || Array.isArray(json.dependencies)) {
|
|
293
|
+
json.dependencies = {};
|
|
294
|
+
}
|
|
295
|
+
const spec = linkSpecFor(pluginHome);
|
|
296
|
+
const hadLegacy = Object.prototype.hasOwnProperty.call(
|
|
297
|
+
json.dependencies,
|
|
298
|
+
"@beauticode/dsh-plugin",
|
|
299
|
+
);
|
|
300
|
+
delete json.dependencies["@beauticode/dsh-plugin"];
|
|
301
|
+
const current = json.dependencies[pluginName];
|
|
302
|
+
if (current === spec && !hadLegacy) return;
|
|
303
|
+
json.dependencies[pluginName] = spec;
|
|
304
|
+
await fsp.writeFile(webPackage, `${JSON.stringify(json, null, 2)}\n`, "utf8");
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async function removeWebPackageDep(webPackage) {
|
|
308
|
+
if (!fs.existsSync(webPackage)) return false;
|
|
309
|
+
const json = JSON.parse(await fsp.readFile(webPackage, "utf8"));
|
|
310
|
+
if (!json.dependencies || typeof json.dependencies !== "object") return false;
|
|
311
|
+
let changed = false;
|
|
312
|
+
for (const name of [pluginName, "@beauticode/dsh-plugin"]) {
|
|
313
|
+
if (Object.prototype.hasOwnProperty.call(json.dependencies, name)) {
|
|
314
|
+
delete json.dependencies[name];
|
|
315
|
+
changed = true;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (!changed) return false;
|
|
319
|
+
await fsp.writeFile(webPackage, `${JSON.stringify(json, null, 2)}\n`, "utf8");
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
|
|
180
323
|
async function install(opts) {
|
|
181
324
|
const dest = path.resolve(opts.pluginHome);
|
|
182
325
|
const dshHome = path.resolve(opts.dshHome);
|
|
@@ -194,7 +337,9 @@ async function install(opts) {
|
|
|
194
337
|
}
|
|
195
338
|
|
|
196
339
|
if (fs.existsSync(webPackage)) {
|
|
197
|
-
await
|
|
340
|
+
await linkPluginIntoProfile(webProfile, dest);
|
|
341
|
+
await ensureWebPackageDep(webPackage, dest);
|
|
342
|
+
await writePatch(webPatch, packageInsert());
|
|
198
343
|
if (fs.existsSync(homePatch)) {
|
|
199
344
|
const homeRaw = await fsp.readFile(homePatch, "utf8");
|
|
200
345
|
if (hasBridge(homeRaw)) await removePatch(homePatch);
|
|
@@ -215,13 +360,23 @@ async function install(opts) {
|
|
|
215
360
|
|
|
216
361
|
async function uninstall(opts) {
|
|
217
362
|
const dshHome = path.resolve(opts.dshHome);
|
|
363
|
+
const webProfile = path.join(dshHome, "profiles", "web");
|
|
218
364
|
const removed = [];
|
|
219
|
-
if (await removePatch(path.join(
|
|
365
|
+
if (await removePatch(path.join(webProfile, "cordis.patch.yml"))) {
|
|
220
366
|
removed.push("web patch");
|
|
221
367
|
}
|
|
222
368
|
if (await removePatch(path.join(dshHome, "cordis.patch.yml"))) {
|
|
223
369
|
removed.push("home patch");
|
|
224
370
|
}
|
|
371
|
+
if (await removeWebPackageDep(path.join(webProfile, "package.json"))) {
|
|
372
|
+
removed.push("web package.json");
|
|
373
|
+
}
|
|
374
|
+
for (const link of [pluginLinkPath(webProfile), legacyPluginLinkPath(webProfile)]) {
|
|
375
|
+
if (fs.existsSync(link)) {
|
|
376
|
+
await fsp.rm(link, { recursive: true, force: true });
|
|
377
|
+
removed.push(link);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
225
380
|
const dest = path.resolve(opts.pluginHome);
|
|
226
381
|
if (fs.existsSync(dest)) {
|
|
227
382
|
await fsp.rm(dest, { recursive: true, force: true });
|
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 });
|