@vacantthinker/firefox-addon-framework-easy 2026.526.1707 → 2026.527.1212
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 +12 -0
- package/index.js +1 -0
- package/package.json +1 -1
- package/src/browserDownload.js +11 -0
- package/src/generate.js +236 -9
- package/src/serviceFetch.js +3 -2
- package/src/serviceOpContent.js +28 -2
package/README.md
CHANGED
|
@@ -23,6 +23,12 @@ export class BaseORM { }
|
|
|
23
23
|
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
### 📄 File: `src/browserDownload.js`
|
|
27
|
+
```javascript
|
|
28
|
+
export async function browserDownloadByDownlink({ }
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
|
|
26
32
|
### 📄 File: `src/browserNotification.js`
|
|
27
33
|
```javascript
|
|
28
34
|
export async function browserNotificationCreate(
|
|
@@ -59,6 +65,10 @@ export function generateHtmlByUserSettings(
|
|
|
59
65
|
radioItemClickCallback,
|
|
60
66
|
) { }
|
|
61
67
|
|
|
68
|
+
export function generateMkvScriptForSystemWindows({ }
|
|
69
|
+
|
|
70
|
+
export function generateMkvScriptForSystemFedora({ }
|
|
71
|
+
|
|
62
72
|
```
|
|
63
73
|
|
|
64
74
|
### 📄 File: `src/opStorage.js`
|
|
@@ -149,6 +159,8 @@ export async function serviceCopyContentToClipboard(data) { }
|
|
|
149
159
|
|
|
150
160
|
export function serviceSaveContentToLocal(content, filename, ext = 'txt') { }
|
|
151
161
|
|
|
162
|
+
export async function serviceGenerateMkvToolNixScript({ }
|
|
163
|
+
|
|
152
164
|
export function serviceRemoveIllegalWord(value) { }
|
|
153
165
|
|
|
154
166
|
```
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @param param0
|
|
4
|
+
* @param param0.downlink{string}
|
|
5
|
+
* @param param0.filename{string}
|
|
6
|
+
* @returns {Promise<void>}
|
|
7
|
+
*/
|
|
8
|
+
export async function browserDownloadByDownlink({ downlink, filename }) {
|
|
9
|
+
let url = downlink;
|
|
10
|
+
await browser.downloads.download({url, filename});
|
|
11
|
+
}
|
package/src/generate.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {stoOpGet, stoOpSet} from './opStorage.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Generates HTML elements based on a user settings schema object.
|
|
@@ -11,7 +11,10 @@ export function generateHtmlByUserSettings(
|
|
|
11
11
|
userSettings,
|
|
12
12
|
radioItemClickCallback,
|
|
13
13
|
) {
|
|
14
|
-
|
|
14
|
+
// Keeps track of all generated fieldsets by their storageKey
|
|
15
|
+
const elementsMap = {};
|
|
16
|
+
|
|
17
|
+
const fieldsets = Object.keys(userSettings).map((storageKey) => {
|
|
15
18
|
const storageValue = userSettings[storageKey];
|
|
16
19
|
const type = storageValue.type || 'text'; // Default to text if type is not specified
|
|
17
20
|
|
|
@@ -21,6 +24,9 @@ export function generateHtmlByUserSettings(
|
|
|
21
24
|
eleTitle.textContent = storageKey;
|
|
22
25
|
eleWrap.append(eleTitle);
|
|
23
26
|
|
|
27
|
+
// Save a reference to the wrapper element for visibility switching
|
|
28
|
+
elementsMap[storageKey] = eleWrap;
|
|
29
|
+
|
|
24
30
|
// --- CONDITION 1: CHECKBOX & RADIO ---
|
|
25
31
|
if (type === 'checkbox' || type === 'radio') {
|
|
26
32
|
const options = storageValue.options || [];
|
|
@@ -39,37 +45,54 @@ export function generateHtmlByUserSettings(
|
|
|
39
45
|
const initialArray = Array.from(v || storageValue.selected || []);
|
|
40
46
|
const set = new Set(initialArray);
|
|
41
47
|
eleInput.checked = set.has(option);
|
|
48
|
+
|
|
49
|
+
// Initial visibility evaluation
|
|
50
|
+
triggerVisibility(storageKey, initialArray);
|
|
42
51
|
});
|
|
43
52
|
|
|
44
53
|
eleInput.addEventListener('change', async () => {
|
|
45
|
-
const optionsCurrent = await stoOpGet(storageKey) ||
|
|
54
|
+
const optionsCurrent = await stoOpGet(storageKey) ||
|
|
55
|
+
storageValue.selected || [];
|
|
46
56
|
const set = new Set(Array.from(optionsCurrent));
|
|
47
57
|
|
|
48
58
|
if (eleInput.checked) {
|
|
49
59
|
set.add(option);
|
|
50
|
-
}
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
51
62
|
set.delete(option);
|
|
52
63
|
}
|
|
53
64
|
|
|
54
65
|
const valueNew = Array.from(set);
|
|
55
|
-
console.info(
|
|
66
|
+
console.info(
|
|
67
|
+
`k=${storageKey} option=${option} eleInput.checked=${eleInput.checked} valueNew=${valueNew}`);
|
|
56
68
|
await stoOpSet(storageKey, valueNew);
|
|
69
|
+
|
|
70
|
+
// Dynamic visibility update
|
|
71
|
+
triggerVisibility(storageKey, valueNew);
|
|
57
72
|
});
|
|
58
73
|
}
|
|
59
74
|
else if (type === 'radio') {
|
|
60
75
|
stoOpGet(storageKey).then((v) => {
|
|
61
|
-
const currentSelected = (v !== undefined && v !== null) ?
|
|
76
|
+
const currentSelected = (v !== undefined && v !== null) ?
|
|
77
|
+
v :
|
|
78
|
+
storageValue.selected;
|
|
62
79
|
if (option === currentSelected) {
|
|
63
80
|
eleInput.checked = true;
|
|
64
81
|
}
|
|
82
|
+
|
|
83
|
+
// Initial visibility evaluation
|
|
84
|
+
triggerVisibility(storageKey, currentSelected);
|
|
65
85
|
});
|
|
66
86
|
|
|
67
|
-
eleLabel.onclick = function
|
|
87
|
+
eleLabel.onclick = function() {
|
|
68
88
|
stoOpSet(storageKey, option).then(() => {
|
|
69
89
|
console.info(`k=${storageKey} option=${option}`);
|
|
70
90
|
if (typeof radioItemClickCallback === 'function') {
|
|
71
91
|
radioItemClickCallback(storageKey, option);
|
|
72
92
|
}
|
|
93
|
+
|
|
94
|
+
// Dynamic visibility update
|
|
95
|
+
triggerVisibility(storageKey, option);
|
|
73
96
|
});
|
|
74
97
|
};
|
|
75
98
|
}
|
|
@@ -86,14 +109,22 @@ export function generateHtmlByUserSettings(
|
|
|
86
109
|
|
|
87
110
|
stoOpGet(storageKey).then((v) => {
|
|
88
111
|
// Fallback to default schema configuration if no value is stored yet
|
|
89
|
-
let currentStatus = (v !== undefined && v !== null) ?
|
|
112
|
+
let currentStatus = (v !== undefined && v !== null) ?
|
|
113
|
+
(v === true || v === 'true') :
|
|
114
|
+
storageValue.selected;
|
|
90
115
|
eleButton.textContent = String(currentStatus);
|
|
91
116
|
|
|
117
|
+
// Initial visibility evaluation
|
|
118
|
+
triggerVisibility(storageKey, currentStatus);
|
|
119
|
+
|
|
92
120
|
eleButton.addEventListener('click', async () => {
|
|
93
121
|
currentStatus = !currentStatus; // Toggle state
|
|
94
122
|
eleButton.textContent = String(currentStatus);
|
|
95
123
|
console.info(`k=${storageKey} toggled to=${currentStatus}`);
|
|
96
124
|
await stoOpSet(storageKey, currentStatus);
|
|
125
|
+
|
|
126
|
+
// Dynamic visibility update
|
|
127
|
+
triggerVisibility(storageKey, currentStatus);
|
|
97
128
|
});
|
|
98
129
|
});
|
|
99
130
|
|
|
@@ -107,8 +138,13 @@ export function generateHtmlByUserSettings(
|
|
|
107
138
|
eleInput.name = storageKey;
|
|
108
139
|
|
|
109
140
|
stoOpGet(storageKey).then((v) => {
|
|
110
|
-
const currentVal = (v !== undefined && v !== null) ?
|
|
141
|
+
const currentVal = (v !== undefined && v !== null) ?
|
|
142
|
+
v :
|
|
143
|
+
storageValue.selected;
|
|
111
144
|
eleInput.value = currentVal;
|
|
145
|
+
|
|
146
|
+
// Initial visibility evaluation
|
|
147
|
+
triggerVisibility(storageKey, currentVal);
|
|
112
148
|
});
|
|
113
149
|
|
|
114
150
|
// Updates storage on every keystroke/change execution
|
|
@@ -118,6 +154,9 @@ export function generateHtmlByUserSettings(
|
|
|
118
154
|
|
|
119
155
|
console.info(`k=${storageKey} value changed to=${finalizedValue}`);
|
|
120
156
|
await stoOpSet(storageKey, finalizedValue);
|
|
157
|
+
|
|
158
|
+
// Dynamic visibility update
|
|
159
|
+
triggerVisibility(storageKey, finalizedValue);
|
|
121
160
|
});
|
|
122
161
|
|
|
123
162
|
eleWrap.append(eleInput);
|
|
@@ -125,4 +164,192 @@ export function generateHtmlByUserSettings(
|
|
|
125
164
|
|
|
126
165
|
return eleWrap;
|
|
127
166
|
});
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Evaluates the visibility rules for a given source key based on its current value.
|
|
170
|
+
*/
|
|
171
|
+
function triggerVisibility(sourceKey, currentValue) {
|
|
172
|
+
const config = userSettings[sourceKey];
|
|
173
|
+
if (config && config.visibilityControl) {
|
|
174
|
+
const {targetField, expectedValue} = config.visibilityControl;
|
|
175
|
+
const targetElement = elementsMap[targetField];
|
|
176
|
+
|
|
177
|
+
if (targetElement) {
|
|
178
|
+
// String conversion guarantees type safety (e.g., matching boolean true against string "true")
|
|
179
|
+
const shouldBeVisible = String(currentValue) === String(expectedValue);
|
|
180
|
+
targetElement.style.display = shouldBeVisible ? '' : 'none';
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return fieldsets;
|
|
128
186
|
}
|
|
187
|
+
|
|
188
|
+
export function generateMkvScriptForSystemWindows({vid, title}) {
|
|
189
|
+
let args = {vid, title};
|
|
190
|
+
|
|
191
|
+
return `if (true) {
|
|
192
|
+
const path = require('path');
|
|
193
|
+
const fs = require('fs');
|
|
194
|
+
const {execSync, exec} = require('node:child_process');
|
|
195
|
+
let pathDownload = path.join(__dirname);
|
|
196
|
+
let dot = '.';
|
|
197
|
+
let extMKV = 'mkv';
|
|
198
|
+
|
|
199
|
+
let {vid, title} = ${JSON.stringify(args)};
|
|
200
|
+
let playVideoAfterMerged = true;
|
|
201
|
+
let pathToMkvmerge = 'C:\\\\Program Files\\\\MKVToolNix\\\\mkvmerge.exe';
|
|
202
|
+
|
|
203
|
+
let pathMKVOutput = path.join(pathDownload, title.concat(dot, extMKV));
|
|
204
|
+
let pathOutput = path.join(pathDownload, vid.concat(dot, extMKV));
|
|
205
|
+
let pathInputAudio = path.join(pathDownload, vid.concat(dot, "mp3"));
|
|
206
|
+
let pathInputVideo = path.join(pathDownload, vid.concat(dot, "mp4"));
|
|
207
|
+
let pathJsFile = path.join(__filename);
|
|
208
|
+
|
|
209
|
+
if (
|
|
210
|
+
fs.existsSync(pathToMkvmerge)
|
|
211
|
+
) {
|
|
212
|
+
|
|
213
|
+
console.log('');
|
|
214
|
+
console.log(['file check ok!', title].join(' '));
|
|
215
|
+
|
|
216
|
+
let cmd_merge = [
|
|
217
|
+
[pathToMkvmerge].map(v => '"' + v + '"').join(''),
|
|
218
|
+
|
|
219
|
+
'-o',
|
|
220
|
+
[pathOutput]
|
|
221
|
+
.map(value => '"' + value + '"')
|
|
222
|
+
.join(' '),
|
|
223
|
+
|
|
224
|
+
'--no-video',
|
|
225
|
+
[pathInputAudio]
|
|
226
|
+
.map(value => '"' + value + '"')
|
|
227
|
+
.join(' '),
|
|
228
|
+
|
|
229
|
+
'--no-audio',
|
|
230
|
+
[pathInputVideo]
|
|
231
|
+
.map(value => '"' + value + '"')
|
|
232
|
+
.join(' '),
|
|
233
|
+
].join(' ');
|
|
234
|
+
|
|
235
|
+
console.log('execute script merge ...')
|
|
236
|
+
console.log(cmd_merge);
|
|
237
|
+
|
|
238
|
+
let exec_merge = exec(cmd_merge);
|
|
239
|
+
exec_merge.stdout.on('data', (data) => {
|
|
240
|
+
console.log(data);
|
|
241
|
+
});
|
|
242
|
+
exec_merge.stderr.on('data', (data) => {
|
|
243
|
+
console.log('error', data);
|
|
244
|
+
});
|
|
245
|
+
exec_merge.stdout.on('close', (data) => {
|
|
246
|
+
console.log(['merge finish!', title].join(' '));
|
|
247
|
+
|
|
248
|
+
if (true) {
|
|
249
|
+
console.log('remove inputFile');
|
|
250
|
+
fs.unlinkSync(pathInputVideo);
|
|
251
|
+
fs.unlinkSync(pathInputAudio);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
console.log('rename output mkv video...');
|
|
255
|
+
fs.renameSync(pathOutput, pathMKVOutput);
|
|
256
|
+
|
|
257
|
+
if (playVideoAfterMerged) {
|
|
258
|
+
console.log('play mkv video...');
|
|
259
|
+
execSync('"' + pathMKVOutput + '"');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (true) {
|
|
263
|
+
console.log('remove script file');
|
|
264
|
+
fs.unlinkSync(pathJsFile);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
} else {
|
|
269
|
+
console.log('pathToMkvmerge error')
|
|
270
|
+
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
`;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function generateMkvScriptForSystemFedora({vid, title}) {
|
|
277
|
+
let args = {vid, title};
|
|
278
|
+
|
|
279
|
+
// We wrap the Node.js script inside a Linux Shell script block
|
|
280
|
+
return `#!/usr/bin/env bash
|
|
281
|
+
# This header tells Fedora to treat this file as a runnable bash script
|
|
282
|
+
|
|
283
|
+
# Open a terminal window if not already running inside one
|
|
284
|
+
if [ -z "$VTE_VERSION" ] && [ -z "$ALACRITTY_WINDOW_ID" ] && [ -z "$KITTY_WINDOW_ID" ] && [ "$1" != "--child" ]; then
|
|
285
|
+
# Re-run this same script inside a visible terminal window so the user sees progress
|
|
286
|
+
gnome-terminal -- "$0" --child
|
|
287
|
+
exit 0
|
|
288
|
+
fi
|
|
289
|
+
|
|
290
|
+
# Run the embedded Node.js code below
|
|
291
|
+
node << 'EOF'
|
|
292
|
+
const path = require('path');
|
|
293
|
+
const fs = require('fs');
|
|
294
|
+
const { execSync } = require('node:child_process');
|
|
295
|
+
|
|
296
|
+
let pathDownload = path.join(__dirname);
|
|
297
|
+
let dot = '.';
|
|
298
|
+
let extMKV = 'mkv';
|
|
299
|
+
|
|
300
|
+
let {vid, title} = ${JSON.stringify(args)};
|
|
301
|
+
let playVideoAfterMerged = true;
|
|
302
|
+
let pathToMkvmerge = '/usr/bin/mkvmerge';
|
|
303
|
+
|
|
304
|
+
let pathMKVOutput = path.join(pathDownload, title.concat(dot, extMKV));
|
|
305
|
+
let pathOutput = path.join(pathDownload, vid.concat(dot, extMKV));
|
|
306
|
+
let pathInputAudio = path.join(pathDownload, vid.concat(dot, "mp3"));
|
|
307
|
+
let pathInputVideo = path.join(pathDownload, vid.concat(dot, "mp4"));
|
|
308
|
+
|
|
309
|
+
if (fs.existsSync(pathToMkvmerge)) {
|
|
310
|
+
console.log('\\nfile check ok! ' + title);
|
|
311
|
+
|
|
312
|
+
let cmd_merge = [
|
|
313
|
+
'"' + pathToMkvmerge + '"',
|
|
314
|
+
'-o', '"' + pathOutput + '"',
|
|
315
|
+
'--no-video', '"' + pathInputAudio + '"',
|
|
316
|
+
'--no-audio', '"' + pathInputVideo + '"',
|
|
317
|
+
].join(' ');
|
|
318
|
+
|
|
319
|
+
console.log('execute script merge ...\\n' + cmd_merge);
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
// Run merge synchronously so stdout pipes directly to the terminal
|
|
323
|
+
execSync(cmd_merge, { stdio: 'inherit' });
|
|
324
|
+
console.log('merge finish! ' + title);
|
|
325
|
+
|
|
326
|
+
console.log('remove inputFile');
|
|
327
|
+
if (fs.existsSync(pathInputVideo)) fs.unlinkSync(pathInputVideo);
|
|
328
|
+
if (fs.existsSync(pathInputAudio)) fs.unlinkSync(pathInputAudio);
|
|
329
|
+
|
|
330
|
+
console.log('rename output mkv video...');
|
|
331
|
+
fs.renameSync(pathOutput, pathMKVOutput);
|
|
332
|
+
|
|
333
|
+
if (playVideoAfterMerged) {
|
|
334
|
+
console.log('play mkv video...');
|
|
335
|
+
// xdg-open usually hands off to the player and exits immediately
|
|
336
|
+
execSync('xdg-open "' + pathMKVOutput + '"');
|
|
337
|
+
}
|
|
338
|
+
} catch (err) {
|
|
339
|
+
console.error('An error occurred during processing:', err.message);
|
|
340
|
+
}
|
|
341
|
+
} else {
|
|
342
|
+
console.log('pathToMkvmerge error. run: sudo dnf install mkvtoolnix')
|
|
343
|
+
}
|
|
344
|
+
EOF
|
|
345
|
+
|
|
346
|
+
# Prevent the terminal window from closing instantly so the user can read messages
|
|
347
|
+
echo ""
|
|
348
|
+
echo "Press Enter to exit and automatically delete this script..."
|
|
349
|
+
read unused_input
|
|
350
|
+
|
|
351
|
+
# Deletes the shell script file itself after completion
|
|
352
|
+
rm -- "$0"
|
|
353
|
+
`;
|
|
354
|
+
}
|
|
355
|
+
|
package/src/serviceFetch.js
CHANGED
|
@@ -40,7 +40,7 @@ export async function servicePostJson(
|
|
|
40
40
|
* rpcsecret:string,
|
|
41
41
|
* rpcport:string
|
|
42
42
|
* }}
|
|
43
|
-
* @returns {Promise<Response>}
|
|
43
|
+
* @returns {Promise<Response|null>}
|
|
44
44
|
*/
|
|
45
45
|
export async function serviceSendDataToLocalAria2(message) {
|
|
46
46
|
let {downlink, filename, rpcsecret, rpcport} = message;
|
|
@@ -76,8 +76,9 @@ export async function serviceSendDataToLocalAria2(message) {
|
|
|
76
76
|
|
|
77
77
|
return response;
|
|
78
78
|
} catch (e) {
|
|
79
|
+
await browserNotificationCreate(e)
|
|
79
80
|
console.error(e);
|
|
80
|
-
|
|
81
|
+
return null;
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
}
|
package/src/serviceOpContent.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {browserRuntimePlatformInfo} from './browserRuntime.js';
|
|
2
|
+
import {
|
|
3
|
+
generateMkvScriptForSystemFedora,
|
|
4
|
+
generateMkvScriptForSystemWindows,
|
|
5
|
+
} from './generate.js';
|
|
6
|
+
|
|
1
7
|
/**
|
|
2
8
|
*
|
|
3
9
|
* @param data
|
|
@@ -22,8 +28,8 @@ export function serviceSaveContentToLocal(content, filename, ext = 'txt') {
|
|
|
22
28
|
|
|
23
29
|
const extObj = {
|
|
24
30
|
txt: 'text/plain',
|
|
25
|
-
json: 'application/json'
|
|
26
|
-
}
|
|
31
|
+
json: 'application/json',
|
|
32
|
+
};
|
|
27
33
|
const type = extObj[ext];
|
|
28
34
|
|
|
29
35
|
const file = new Blob([content], {type});
|
|
@@ -38,6 +44,26 @@ export function serviceSaveContentToLocal(content, filename, ext = 'txt') {
|
|
|
38
44
|
// eleBtn.previousElementSibling
|
|
39
45
|
}
|
|
40
46
|
|
|
47
|
+
/**
|
|
48
|
+
* need install nodejs mkvtoolnix
|
|
49
|
+
* @param message{{
|
|
50
|
+
* vid, title,
|
|
51
|
+
* }}
|
|
52
|
+
* @returns {Promise<void>}
|
|
53
|
+
*/
|
|
54
|
+
export async function serviceGenerateMkvToolNixScript({vid, title}) {
|
|
55
|
+
let message = {vid, title};
|
|
56
|
+
const platformInfo = await browserRuntimePlatformInfo();
|
|
57
|
+
if (platformInfo.os === 'win') {
|
|
58
|
+
let content = generateMkvScriptForSystemWindows(message);
|
|
59
|
+
serviceSaveContentToLocal(content, title, 'js');
|
|
60
|
+
}
|
|
61
|
+
else if (platformInfo.os === 'linux') {
|
|
62
|
+
let content = generateMkvScriptForSystemFedora(message);
|
|
63
|
+
serviceSaveContentToLocal(content, title, 'sh');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
41
67
|
/**
|
|
42
68
|
*
|
|
43
69
|
* @param {string} value
|