@vacantthinker/firefox-addon-framework-easy 2026.609.1038 → 2026.610.1322
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 +27 -29
- package/index.js +0 -1
- package/package.json +1 -1
- package/src/browserTab.js +44 -20
- package/src/generate.js +23 -212
- package/src/opTab.js +127 -88
- package/src/serviceUserSettings.js +0 -33
package/README.md
CHANGED
|
@@ -69,13 +69,16 @@ export function browserRuntimeOnMessageCommon(act, message, sendResponse) { }
|
|
|
69
69
|
|
|
70
70
|
### 📄 File: `src/browserTab.js`
|
|
71
71
|
```javascript
|
|
72
|
-
export async function browserTabSendMessage(
|
|
72
|
+
export async function browserTabSendMessage(
|
|
73
|
+
tabId,
|
|
74
|
+
message,
|
|
75
|
+
) { }
|
|
73
76
|
|
|
74
77
|
export function browserTabWaitReloadThenSendMessageToContentJs(message) { }
|
|
75
78
|
|
|
76
79
|
export async function browserTabCreateToDownload(message) { }
|
|
77
80
|
|
|
78
|
-
export async function browserTabCreateNearSendMessageToContentJs(message
|
|
81
|
+
export async function browserTabCreateNearSendMessageToContentJs(message = { }
|
|
79
82
|
|
|
80
83
|
export function browserTabWaitReloadThenRemoveIt({ }
|
|
81
84
|
|
|
@@ -89,15 +92,9 @@ export class DomainORM extends BaseORM { }
|
|
|
89
92
|
|
|
90
93
|
### 📄 File: `src/generate.js`
|
|
91
94
|
```javascript
|
|
92
|
-
export
|
|
93
|
-
userSettings,
|
|
94
|
-
radioItemClickCallback,
|
|
95
|
-
checkboxItemClickCallback,
|
|
96
|
-
) { }
|
|
95
|
+
export function generateMkvScriptForSystemWindows(videoInfo) { }
|
|
97
96
|
|
|
98
|
-
export function
|
|
99
|
-
|
|
100
|
-
export function generateMkvScriptForSystemFedora({ }
|
|
97
|
+
export function generateMkvScriptForSystemFedora(videoInfo) { }
|
|
101
98
|
|
|
102
99
|
```
|
|
103
100
|
|
|
@@ -121,7 +118,7 @@ export async function stoOpSetNull(k) { }
|
|
|
121
118
|
|
|
122
119
|
### 📄 File: `src/opTab.js`
|
|
123
120
|
```javascript
|
|
124
|
-
export
|
|
121
|
+
export function tabOpEnhance(tab) { }
|
|
125
122
|
|
|
126
123
|
export async function tabOpCreate(properties) { }
|
|
127
124
|
|
|
@@ -131,31 +128,40 @@ export async function tabOpCreateActiveFalse(properties) { }
|
|
|
131
128
|
|
|
132
129
|
export async function tabOpCreateByWindow(url) { }
|
|
133
130
|
|
|
134
|
-
export
|
|
131
|
+
export function tabOpGet(tabId) { }
|
|
135
132
|
|
|
136
|
-
export
|
|
133
|
+
export function tabOpQueryAll() { }
|
|
137
134
|
|
|
138
135
|
export async function tabOpQueryUrl(urlQuery) { }
|
|
139
136
|
|
|
140
137
|
export async function tabOpQueryUrlThenRemove(urlQuery) { }
|
|
141
138
|
|
|
142
|
-
export
|
|
139
|
+
export function tabOpReload(tabId) { }
|
|
143
140
|
|
|
144
|
-
export
|
|
141
|
+
export function tabOpReloadByPassCacheTrue(tabId) { }
|
|
145
142
|
|
|
146
|
-
export
|
|
143
|
+
export function tabOpRemove(tabId) { }
|
|
147
144
|
|
|
148
|
-
export
|
|
145
|
+
export function tabOpHide(tabId) { }
|
|
149
146
|
|
|
150
|
-
export async function tabOpUpdate(
|
|
147
|
+
export async function tabOpUpdate(
|
|
148
|
+
tabId,
|
|
149
|
+
updateProperties,
|
|
150
|
+
) { }
|
|
151
151
|
|
|
152
|
-
export
|
|
152
|
+
export function tabOpUpdateActiveFalse(tabId) { }
|
|
153
153
|
|
|
154
154
|
export async function tabOpFocus(tabId) { }
|
|
155
155
|
|
|
156
|
-
export
|
|
156
|
+
export function tabOpInsertCssCode(
|
|
157
|
+
tabId,
|
|
158
|
+
code,
|
|
159
|
+
) { }
|
|
157
160
|
|
|
158
|
-
export
|
|
161
|
+
export function tabOpRemoveCssCode(
|
|
162
|
+
tabId,
|
|
163
|
+
code,
|
|
164
|
+
) { }
|
|
159
165
|
|
|
160
166
|
```
|
|
161
167
|
|
|
@@ -227,11 +233,3 @@ export async function serviceUpdataALLNodeBackgroundColor(message) { }
|
|
|
227
233
|
|
|
228
234
|
```
|
|
229
235
|
|
|
230
|
-
### 📄 File: `src/serviceUserSettings.js`
|
|
231
|
-
```javascript
|
|
232
|
-
export async function serviceInitUserSettings(userSettings) { }
|
|
233
|
-
|
|
234
|
-
export async function serviceGetUserSettings(userSettings) { }
|
|
235
|
-
|
|
236
|
-
```
|
|
237
|
-
|
package/index.js
CHANGED
package/package.json
CHANGED
package/src/browserTab.js
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import {browserNotificationCreate} from './browserNotification.js';
|
|
4
3
|
import {tabOpCreateNear, tabOpGet, tabOpRemove} from './opTab.js';
|
|
5
4
|
|
|
6
|
-
export async function browserTabSendMessage(
|
|
5
|
+
export async function browserTabSendMessage(
|
|
6
|
+
tabId,
|
|
7
|
+
message,
|
|
8
|
+
) {
|
|
7
9
|
await browser.tabs.sendMessage(tabId, message);
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
export function browserTabWaitReloadThenSendMessageToContentJs(message) {
|
|
11
13
|
let tabId = message.tabId;
|
|
12
14
|
browser.tabs.onUpdated.addListener(
|
|
13
|
-
async function lis(
|
|
15
|
+
async function lis(
|
|
16
|
+
tabId,
|
|
17
|
+
changeInfo,
|
|
18
|
+
) {
|
|
14
19
|
if (changeInfo.status === 'complete') {
|
|
15
20
|
browser.tabs.onUpdated.removeListener(lis);
|
|
16
21
|
await browserTabSendMessage(tabId, message);
|
|
@@ -47,7 +52,10 @@ export async function browserTabCreateToDownload(message) {
|
|
|
47
52
|
|
|
48
53
|
let {tabId} = await tabOpCreateNear(properties);
|
|
49
54
|
browser.tabs.onUpdated.addListener(
|
|
50
|
-
async function lis(
|
|
55
|
+
async function lis(
|
|
56
|
+
tabId,
|
|
57
|
+
changeInfo,
|
|
58
|
+
) {
|
|
51
59
|
if (changeInfo.status === 'complete') {
|
|
52
60
|
browser.tabs.onUpdated.removeListener(lis);
|
|
53
61
|
// todo code here
|
|
@@ -58,18 +66,21 @@ export async function browserTabCreateToDownload(message) {
|
|
|
58
66
|
}
|
|
59
67
|
|
|
60
68
|
/**
|
|
61
|
-
*
|
|
62
|
-
* @param message
|
|
63
|
-
* @param message.tabId
|
|
64
|
-
* @param message.url
|
|
65
|
-
* @param message.options.
|
|
69
|
+
* Safely handles incoming messages to create tabs and send messages to content scripts.
|
|
70
|
+
* * @param {Object} message - The message payload.
|
|
71
|
+
* @param {number} [message.tabId] - Optional Tab ID to attach to.
|
|
72
|
+
* @param {string} [message.url] - The URL to open.
|
|
73
|
+
* @param {Object} [message.options] - Additional options.
|
|
74
|
+
* @param {boolean} [message.options.focusNewTab] - Whether the newly created tab should be active.
|
|
66
75
|
* @returns {Promise<void>}
|
|
67
76
|
*/
|
|
68
|
-
export async function browserTabCreateNearSendMessageToContentJs(message) {
|
|
77
|
+
export async function browserTabCreateNearSendMessageToContentJs(message = {}) {
|
|
69
78
|
let properties = {
|
|
70
79
|
url: message.url,
|
|
71
80
|
};
|
|
72
|
-
|
|
81
|
+
|
|
82
|
+
// Strictly check for tabId presence to prevent passing undefined.
|
|
83
|
+
if (message.tabId !== undefined) {
|
|
73
84
|
let tabId = message.tabId;
|
|
74
85
|
try {
|
|
75
86
|
await tabOpGet(tabId);
|
|
@@ -78,22 +89,32 @@ export async function browserTabCreateNearSendMessageToContentJs(message) {
|
|
|
78
89
|
delete message.tabId;
|
|
79
90
|
}
|
|
80
91
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
92
|
+
|
|
93
|
+
// Use optional chaining to safely extract nested properties.
|
|
94
|
+
// Only assign 'active' if 'focusNewTab' was explicitly provided.
|
|
95
|
+
if (message?.options?.focusNewTab !== undefined) {
|
|
96
|
+
Object.assign(properties, {active: message.options.focusNewTab});
|
|
84
97
|
}
|
|
85
98
|
|
|
86
99
|
let {tabId} = await tabOpCreateNear(properties);
|
|
100
|
+
|
|
87
101
|
browser.tabs.onUpdated.addListener(
|
|
88
|
-
async function lis(
|
|
102
|
+
async function lis(
|
|
103
|
+
updatedTabId,
|
|
104
|
+
changeInfo,
|
|
105
|
+
) {
|
|
89
106
|
if (changeInfo.status === 'complete') {
|
|
90
107
|
browser.tabs.onUpdated.removeListener(lis);
|
|
91
|
-
|
|
108
|
+
|
|
109
|
+
// Execute further logic once the tab is completely loaded.
|
|
92
110
|
await browserTabSendMessage(
|
|
93
|
-
|
|
111
|
+
updatedTabId,
|
|
112
|
+
Object.assign({}, message, {tabId: updatedTabId}),
|
|
113
|
+
);
|
|
94
114
|
}
|
|
95
|
-
}
|
|
96
|
-
|
|
115
|
+
},
|
|
116
|
+
{tabId, properties: ['status']},
|
|
117
|
+
);
|
|
97
118
|
}
|
|
98
119
|
|
|
99
120
|
/**
|
|
@@ -103,7 +124,10 @@ export async function browserTabCreateNearSendMessageToContentJs(message) {
|
|
|
103
124
|
*/
|
|
104
125
|
export function browserTabWaitReloadThenRemoveIt({tabId}) {
|
|
105
126
|
browser.tabs.onUpdated.addListener(
|
|
106
|
-
async function lis(
|
|
127
|
+
async function lis(
|
|
128
|
+
tabId,
|
|
129
|
+
changeInfo,
|
|
130
|
+
) {
|
|
107
131
|
if (changeInfo.status === 'complete') {
|
|
108
132
|
browser.tabs.onUpdated.removeListener(lis);
|
|
109
133
|
// todo code here
|
package/src/generate.js
CHANGED
|
@@ -1,207 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
// [Optimization 1] Introduce a debounce function to prevent triggering storage writes on every keystroke.
|
|
4
|
-
function debounce(func, wait) {
|
|
5
|
-
let timeout;
|
|
6
|
-
return function(...args) {
|
|
7
|
-
clearTimeout(timeout);
|
|
8
|
-
timeout = setTimeout(() => func.apply(this, args), wait);
|
|
9
|
-
};
|
|
10
|
-
}
|
|
1
|
+
// 新主题。
|
|
11
2
|
|
|
12
3
|
/**
|
|
13
|
-
* Generates HTML elements based on a user settings schema object.
|
|
14
4
|
*
|
|
15
|
-
* @param
|
|
16
|
-
* @param
|
|
17
|
-
* @
|
|
5
|
+
* @param videoInfo
|
|
6
|
+
* @param videoInfo.vid{string}
|
|
7
|
+
* @param videoInfo.videoTitle{string}
|
|
8
|
+
* @return {string}
|
|
18
9
|
*/
|
|
19
|
-
export
|
|
20
|
-
userSettings,
|
|
21
|
-
radioItemClickCallback,
|
|
22
|
-
checkboxItemClickCallback,
|
|
23
|
-
) {
|
|
24
|
-
const elementsMap = {};
|
|
25
|
-
|
|
26
|
-
// [Optimization 2] Use a DocumentFragment for off-screen DOM construction to improve rendering performance.
|
|
27
|
-
const fragment = document.createDocumentFragment();
|
|
28
|
-
|
|
29
|
-
const keys = Object.keys(userSettings);
|
|
30
|
-
|
|
31
|
-
// [Optimization 3] Batch fetch all stored values.
|
|
32
|
-
// Wait for all queries to complete to prevent async callbacks from interrupting the rendering pipeline.
|
|
33
|
-
const valuesArray = await Promise.all(keys.map((key) => stoOpGet(key)));
|
|
34
|
-
const storageData = {};
|
|
35
|
-
keys.forEach((key, index) => {
|
|
36
|
-
storageData[key] = valuesArray[index];
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// First pass: Pre-create all container wrappers.
|
|
40
|
-
// This ensures all target elements exist in the DOM references before executing triggerVisibility later.
|
|
41
|
-
keys.forEach((storageKey) => {
|
|
42
|
-
const eleWrap = document.createElement('fieldset');
|
|
43
|
-
const eleTitle = document.createElement('legend');
|
|
44
|
-
eleTitle.textContent = storageKey;
|
|
45
|
-
eleWrap.append(eleTitle);
|
|
46
|
-
|
|
47
|
-
elementsMap[storageKey] = eleWrap;
|
|
48
|
-
fragment.append(eleWrap);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
// Second pass: Synchronously populate form content and attach event listeners.
|
|
52
|
-
keys.forEach((storageKey) => {
|
|
53
|
-
const storageValue = userSettings[storageKey];
|
|
54
|
-
const type = storageValue.type || 'text';
|
|
55
|
-
const eleWrap = elementsMap[storageKey];
|
|
56
|
-
|
|
57
|
-
// Retrieve pre-loaded value; fallback to the schema's default value if undefined.
|
|
58
|
-
const storedValue = storageData[storageKey];
|
|
59
|
-
const initialValue =
|
|
60
|
-
storedValue !== undefined && storedValue !== null
|
|
61
|
-
? storedValue
|
|
62
|
-
: storageValue.selected;
|
|
63
|
-
|
|
64
|
-
// --- CONDITION 1: CHECKBOX & RADIO ---
|
|
65
|
-
if (type === 'checkbox' || type === 'radio') {
|
|
66
|
-
/** @type {string[]} */
|
|
67
|
-
const options = storageValue.options || [];
|
|
68
|
-
|
|
69
|
-
options.forEach((option) => {
|
|
70
|
-
const eleLabel = document.createElement('label');
|
|
71
|
-
eleLabel.textContent = option;
|
|
72
|
-
const eleInput = document.createElement('input');
|
|
73
|
-
eleInput.name = storageKey;
|
|
74
|
-
eleInput.type = type;
|
|
75
|
-
eleInput.value = option;
|
|
76
|
-
|
|
77
|
-
if (type === 'checkbox') {
|
|
78
|
-
const initialArray = Array.from(initialValue || []);
|
|
79
|
-
const set = new Set(initialArray);
|
|
80
|
-
eleInput.checked = set.has(option); // Apply state synchronously
|
|
81
|
-
|
|
82
|
-
eleInput.addEventListener('change', async () => {
|
|
83
|
-
// Derive state in real-time from the DOM (faster than reading from storage again)
|
|
84
|
-
const allCheckboxes = eleWrap.querySelectorAll(
|
|
85
|
-
'input[type="checkbox"]',
|
|
86
|
-
);
|
|
87
|
-
const valueNew = Array.from(allCheckboxes)
|
|
88
|
-
.filter((cb) => cb.checked)
|
|
89
|
-
.map((cb) => cb.value);
|
|
90
|
-
|
|
91
|
-
await stoOpSet(storageKey, valueNew);
|
|
92
|
-
if (typeof checkboxItemClickCallback === 'function') {
|
|
93
|
-
checkboxItemClickCallback(storageKey, option);
|
|
94
|
-
}
|
|
95
|
-
triggerVisibility(storageKey, valueNew);
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
else if (type === 'radio') {
|
|
99
|
-
if (option === initialValue) {
|
|
100
|
-
eleInput.checked = true; // Apply state synchronously
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// It is recommended to bind the 'change' event to the input rather than 'onclick' to the label
|
|
104
|
-
eleInput.addEventListener('change', () => {
|
|
105
|
-
stoOpSet(storageKey, option).then(() => {
|
|
106
|
-
if (typeof radioItemClickCallback === 'function') {
|
|
107
|
-
radioItemClickCallback(storageKey, option);
|
|
108
|
-
}
|
|
109
|
-
triggerVisibility(storageKey, option);
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Fix: Native HTML typically places the input element before the text node inside a label.
|
|
115
|
-
eleLabel.prepend(eleInput);
|
|
116
|
-
eleWrap.append(eleLabel);
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// --- CONDITION 2: TOGGLE BUTTON ---
|
|
121
|
-
else if (type === 'button') {
|
|
122
|
-
const eleButton = document.createElement('button');
|
|
123
|
-
eleButton.type = 'button';
|
|
124
|
-
|
|
125
|
-
let currentStatus = initialValue === true || initialValue === 'true';
|
|
126
|
-
eleButton.textContent = String(currentStatus); // Apply state synchronously
|
|
127
|
-
|
|
128
|
-
eleButton.addEventListener('click', async () => {
|
|
129
|
-
currentStatus = !currentStatus;
|
|
130
|
-
eleButton.textContent = String(currentStatus);
|
|
131
|
-
await stoOpSet(storageKey, currentStatus);
|
|
132
|
-
triggerVisibility(storageKey, currentStatus);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
eleWrap.append(eleButton);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// --- CONDITION 3: NUMBER & TEXT INPUTS ---
|
|
139
|
-
else if (type === 'number' || type === 'text') {
|
|
140
|
-
const eleInput = document.createElement('input');
|
|
141
|
-
eleInput.type = type;
|
|
142
|
-
eleInput.name = storageKey;
|
|
143
|
-
eleInput.value = initialValue !== undefined ? initialValue : ''; // Apply state synchronously
|
|
144
|
-
|
|
145
|
-
// [Optimization 4] Wrap the write operation with debounce, delaying storage writes by 500ms.
|
|
146
|
-
// This eliminates UI freezing regardless of typing speed.
|
|
147
|
-
const debouncedSave = debounce(async (val) => {
|
|
148
|
-
const finalizedValue = type === 'number' ? Number(val) : val;
|
|
149
|
-
await stoOpSet(storageKey, finalizedValue);
|
|
150
|
-
}, 500);
|
|
151
|
-
|
|
152
|
-
eleInput.addEventListener('input', () => {
|
|
153
|
-
const rawValue = eleInput.value;
|
|
154
|
-
debouncedSave(rawValue);
|
|
155
|
-
triggerVisibility(storageKey, rawValue); // Visual UI updates do not require a delay
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
eleWrap.append(eleInput);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// --- CONDITION 4: SPAN / READ-ONLY TEXT ---
|
|
162
|
-
else if (type === 'span') {
|
|
163
|
-
const eleSpan = document.createElement('span');
|
|
164
|
-
eleSpan.textContent = String(initialValue); // Apply state synchronously
|
|
165
|
-
eleWrap.append(eleSpan);
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
// [Optimization 5] Third pass: Globally trigger a single visibility check.
|
|
170
|
-
// At this point, all DOM elements exist and all initialValues are fully derived,
|
|
171
|
-
// preventing undefined errors or invalid target hiding.
|
|
172
|
-
keys.forEach((storageKey) => {
|
|
173
|
-
const storedValue = storageData[storageKey];
|
|
174
|
-
const initialValue =
|
|
175
|
-
storedValue !== undefined && storedValue !== null
|
|
176
|
-
? storedValue
|
|
177
|
-
: userSettings[storageKey].selected;
|
|
178
|
-
|
|
179
|
-
triggerVisibility(storageKey, initialValue);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Evaluates the visibility rules for a given source key based on its current
|
|
184
|
-
* value.
|
|
185
|
-
*/
|
|
186
|
-
function triggerVisibility(sourceKey, currentValue) {
|
|
187
|
-
const config = userSettings[sourceKey];
|
|
188
|
-
if (config && config.visibilityControl) {
|
|
189
|
-
const {targetField, expectedValue} = config.visibilityControl;
|
|
190
|
-
const targetElement = elementsMap[targetField];
|
|
191
|
-
|
|
192
|
-
if (targetElement) {
|
|
193
|
-
const shouldBeVisible = String(currentValue) === String(expectedValue);
|
|
194
|
-
targetElement.style.display = shouldBeVisible ? '' : 'none';
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return fragment;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
export function generateMkvScriptForSystemWindows({vid, title}) {
|
|
203
|
-
let args = {vid, title};
|
|
204
|
-
|
|
10
|
+
export function generateMkvScriptForSystemWindows(videoInfo) {
|
|
205
11
|
return `if (true) {
|
|
206
12
|
const path = require('path');
|
|
207
13
|
const fs = require('fs');
|
|
@@ -210,11 +16,11 @@ export function generateMkvScriptForSystemWindows({vid, title}) {
|
|
|
210
16
|
let dot = '.';
|
|
211
17
|
let extMKV = 'mkv';
|
|
212
18
|
|
|
213
|
-
let {vid,
|
|
19
|
+
let {vid, videoTitle} = ${JSON.stringify(videoInfo)};
|
|
214
20
|
let playVideoAfterMerged = true;
|
|
215
21
|
let pathToMkvmerge = 'C:\\\\Program Files\\\\MKVToolNix\\\\mkvmerge.exe';
|
|
216
22
|
|
|
217
|
-
let pathMKVOutput = path.join(pathDownload,
|
|
23
|
+
let pathMKVOutput = path.join(pathDownload, videoTitle.concat(dot, extMKV));
|
|
218
24
|
let pathOutput = path.join(pathDownload, vid.concat(dot, extMKV));
|
|
219
25
|
let pathInputAudio = path.join(pathDownload, vid.concat(dot, "mp3"));
|
|
220
26
|
let pathInputVideo = path.join(pathDownload, vid.concat(dot, "mp4"));
|
|
@@ -225,7 +31,7 @@ export function generateMkvScriptForSystemWindows({vid, title}) {
|
|
|
225
31
|
) {
|
|
226
32
|
|
|
227
33
|
console.log('');
|
|
228
|
-
console.log(['file check ok!',
|
|
34
|
+
console.log(['file check ok!', videoTitle].join(' '));
|
|
229
35
|
|
|
230
36
|
let cmd_merge = [
|
|
231
37
|
[pathToMkvmerge].map(v => '"' + v + '"').join(''),
|
|
@@ -257,7 +63,7 @@ export function generateMkvScriptForSystemWindows({vid, title}) {
|
|
|
257
63
|
console.log('error', data);
|
|
258
64
|
});
|
|
259
65
|
exec_merge.stdout.on('close', (data) => {
|
|
260
|
-
console.log(['merge finish!',
|
|
66
|
+
console.log(['merge finish!', videoTitle].join(' '));
|
|
261
67
|
|
|
262
68
|
if (true) {
|
|
263
69
|
console.log('remove inputFile');
|
|
@@ -287,9 +93,14 @@ export function generateMkvScriptForSystemWindows({vid, title}) {
|
|
|
287
93
|
`;
|
|
288
94
|
}
|
|
289
95
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
96
|
+
/**
|
|
97
|
+
*
|
|
98
|
+
* @param videoInfo
|
|
99
|
+
* @param videoInfo.vid{string}
|
|
100
|
+
* @param videoInfo.videoTitle{string}
|
|
101
|
+
* @return {string}
|
|
102
|
+
*/
|
|
103
|
+
export function generateMkvScriptForSystemFedora(videoInfo) {
|
|
293
104
|
// We wrap the Node.js script inside a Linux Shell script block
|
|
294
105
|
return `#!/usr/bin/env bash
|
|
295
106
|
# This header tells Fedora to treat this file as a runnable bash script
|
|
@@ -311,17 +122,17 @@ let pathDownload = path.join(__dirname);
|
|
|
311
122
|
let dot = '.';
|
|
312
123
|
let extMKV = 'mkv';
|
|
313
124
|
|
|
314
|
-
let {vid,
|
|
125
|
+
let {vid, videoTitle} = ${JSON.stringify(videoInfo)};
|
|
315
126
|
let playVideoAfterMerged = true;
|
|
316
127
|
let pathToMkvmerge = '/usr/bin/mkvmerge';
|
|
317
128
|
|
|
318
|
-
let pathMKVOutput = path.join(pathDownload,
|
|
129
|
+
let pathMKVOutput = path.join(pathDownload, videoTitle.concat(dot, extMKV));
|
|
319
130
|
let pathOutput = path.join(pathDownload, vid.concat(dot, extMKV));
|
|
320
131
|
let pathInputAudio = path.join(pathDownload, vid.concat(dot, "mp3"));
|
|
321
132
|
let pathInputVideo = path.join(pathDownload, vid.concat(dot, "mp4"));
|
|
322
133
|
|
|
323
134
|
if (fs.existsSync(pathToMkvmerge)) {
|
|
324
|
-
console.log('\\nfile check ok! ' +
|
|
135
|
+
console.log('\\nfile check ok! ' + videoTitle);
|
|
325
136
|
|
|
326
137
|
let cmd_merge = [
|
|
327
138
|
'"' + pathToMkvmerge + '"',
|
|
@@ -335,7 +146,7 @@ if (fs.existsSync(pathToMkvmerge)) {
|
|
|
335
146
|
try {
|
|
336
147
|
// Run merge synchronously so stdout pipes directly to the terminal
|
|
337
148
|
execSync(cmd_merge, { stdio: 'inherit' });
|
|
338
|
-
console.log('merge finish! ' +
|
|
149
|
+
console.log('merge finish! ' + videoTitle);
|
|
339
150
|
|
|
340
151
|
console.log('remove inputFile');
|
|
341
152
|
if (fs.existsSync(pathInputVideo)) fs.unlinkSync(pathInputVideo);
|
|
@@ -365,4 +176,4 @@ read unused_input
|
|
|
365
176
|
# Deletes the shell script file itself after completion
|
|
366
177
|
rm -- "$0"
|
|
367
178
|
`;
|
|
368
|
-
}
|
|
179
|
+
}
|
package/src/opTab.js
CHANGED
|
@@ -1,198 +1,237 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Enhances a tab object by explicitly attaching its ID to the 'tabId' property.
|
|
3
3
|
*
|
|
4
|
-
* @param
|
|
5
|
-
* @returns {
|
|
4
|
+
* @param {browser.tabs.Tab} tab
|
|
5
|
+
* @returns {browser.tabs.Tab & {tabId: number}|null}
|
|
6
6
|
*/
|
|
7
|
-
export
|
|
8
|
-
|
|
7
|
+
export function tabOpEnhance(tab) {
|
|
8
|
+
if (!tab || typeof tab.id !== 'number') return null;
|
|
9
|
+
return {...tab, tabId: tab.id};
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
|
-
* Creates a normal tab using
|
|
13
|
+
* Creates a normal tab using a properties object.
|
|
13
14
|
*
|
|
14
|
-
* @param {
|
|
15
|
-
* @returns {Promise<
|
|
15
|
+
* @param {Object} properties
|
|
16
|
+
* @returns {Promise<browser.tabs.Tab & {tabId: number}>}
|
|
16
17
|
*/
|
|
17
18
|
export async function tabOpCreate(properties) {
|
|
18
|
-
|
|
19
|
-
let tab = await browser.tabs.create(properties);
|
|
19
|
+
const tab = await browser.tabs.create(properties);
|
|
20
20
|
return tabOpEnhance(tab);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
+
* Creates a new tab positioned immediately after a specified existing tab.
|
|
25
|
+
* Does not mutate the original properties object.
|
|
24
26
|
*
|
|
25
|
-
* @param {
|
|
26
|
-
* @returns {Promise<
|
|
27
|
+
* @param {Object & {tabId?: number}} properties
|
|
28
|
+
* @returns {Promise<browser.tabs.Tab & {tabId: number}>}
|
|
27
29
|
*/
|
|
28
30
|
export async function tabOpCreateNear(properties) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
// Destructure tabId out to avoid mutating the original properties object
|
|
32
|
+
const {tabId, ...restProperties} = properties;
|
|
33
|
+
|
|
34
|
+
if (tabId) {
|
|
35
|
+
const tabPrev = await tabOpGet(tabId);
|
|
36
|
+
if (tabPrev) {
|
|
37
|
+
Object.assign(restProperties, {
|
|
38
|
+
index: tabPrev.index + 1,
|
|
39
|
+
openerTabId: tabPrev.id,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
36
42
|
}
|
|
37
43
|
|
|
38
|
-
|
|
44
|
+
const tab = await browser.tabs.create(restProperties);
|
|
39
45
|
return tabOpEnhance(tab);
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* @
|
|
49
|
+
* Creates a tab in the background (inactive and muted).
|
|
50
|
+
*
|
|
51
|
+
* @param {Object} properties
|
|
52
|
+
* @returns {Promise<browser.tabs.Tab & {tabId: number}>}
|
|
46
53
|
*/
|
|
47
54
|
export async function tabOpCreateActiveFalse(properties) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
const mergedProperties = {
|
|
56
|
+
...properties,
|
|
57
|
+
active: false,
|
|
58
|
+
muted: true,
|
|
59
|
+
};
|
|
60
|
+
const tab = await browser.tabs.create(mergedProperties);
|
|
54
61
|
return tabOpEnhance(tab);
|
|
55
62
|
}
|
|
56
63
|
|
|
57
64
|
/**
|
|
58
|
-
* Creates a
|
|
65
|
+
* Creates a completely new window and returns its initial tab.
|
|
59
66
|
*
|
|
60
67
|
* @param {string} url
|
|
61
|
-
* @returns {Promise<(browser.tabs.Tab & {tabId: number})>}
|
|
68
|
+
* @returns {Promise<(browser.tabs.Tab & {tabId: number})|undefined>}
|
|
62
69
|
*/
|
|
63
70
|
export async function tabOpCreateByWindow(url) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
let tab = window.tabs.shift();
|
|
70
|
-
return tabOpEnhance(tab);
|
|
71
|
+
if (typeof url !== 'string') return undefined;
|
|
72
|
+
|
|
73
|
+
const window = await browser.windows.create({url});
|
|
74
|
+
if (window && window.tabs && window.tabs.length > 0) {
|
|
75
|
+
return tabOpEnhance(window.tabs[0]);
|
|
71
76
|
}
|
|
77
|
+
return undefined;
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
/**
|
|
81
|
+
* Retrieves details about the specified tab.
|
|
75
82
|
*
|
|
76
|
-
* @param
|
|
77
|
-
* @
|
|
83
|
+
* @param {number} tabId
|
|
84
|
+
* @returns {Promise<browser.tabs.Tab>}
|
|
78
85
|
*/
|
|
79
|
-
export
|
|
80
|
-
return
|
|
86
|
+
export function tabOpGet(tabId) {
|
|
87
|
+
return browser.tabs.get(tabId);
|
|
81
88
|
}
|
|
82
89
|
|
|
83
90
|
/**
|
|
91
|
+
* Retrieves all tabs across all windows.
|
|
84
92
|
*
|
|
85
93
|
* @returns {Promise<browser.tabs.Tab[]>}
|
|
86
94
|
*/
|
|
87
|
-
export
|
|
88
|
-
return
|
|
95
|
+
export function tabOpQueryAll() {
|
|
96
|
+
return browser.tabs.query({});
|
|
89
97
|
}
|
|
90
98
|
|
|
91
99
|
/**
|
|
100
|
+
* Retrieves an array of tab IDs that match the specified URL pattern.
|
|
92
101
|
*
|
|
93
|
-
* @param
|
|
102
|
+
* @param {string} urlQuery
|
|
94
103
|
* @returns {Promise<number[]>}
|
|
95
104
|
*/
|
|
96
105
|
export async function tabOpQueryUrl(urlQuery) {
|
|
97
|
-
|
|
98
|
-
return tabs.map(
|
|
106
|
+
const tabs = await browser.tabs.query({url: urlQuery});
|
|
107
|
+
return tabs.map(tab => tab.id);
|
|
99
108
|
}
|
|
100
109
|
|
|
101
110
|
/**
|
|
111
|
+
* Finds all tabs matching the URL pattern and removes them simultaneously.
|
|
112
|
+
* [Optimization]: browser.tabs.remove accepts an array of numbers, which is
|
|
113
|
+
* significantly faster than looping and awaiting individually.
|
|
102
114
|
*
|
|
103
|
-
* @param
|
|
115
|
+
* @param {string} urlQuery
|
|
104
116
|
* @returns {Promise<void>}
|
|
105
117
|
*/
|
|
106
118
|
export async function tabOpQueryUrlThenRemove(urlQuery) {
|
|
107
|
-
|
|
108
|
-
ids.
|
|
119
|
+
const ids = await tabOpQueryUrl(urlQuery);
|
|
120
|
+
if (ids.length > 0) {
|
|
121
|
+
await browser.tabs.remove(ids);
|
|
122
|
+
}
|
|
109
123
|
}
|
|
110
124
|
|
|
111
125
|
/**
|
|
126
|
+
* Reloads the given tab.
|
|
112
127
|
*
|
|
113
|
-
* @param
|
|
128
|
+
* @param {number} tabId
|
|
114
129
|
* @returns {Promise<void>}
|
|
115
130
|
*/
|
|
116
|
-
export
|
|
117
|
-
|
|
131
|
+
export function tabOpReload(tabId) {
|
|
132
|
+
return browser.tabs.reload(tabId);
|
|
118
133
|
}
|
|
119
134
|
|
|
120
135
|
/**
|
|
136
|
+
* Reloads the given tab, bypassing the local web cache.
|
|
121
137
|
*
|
|
122
|
-
* @param
|
|
138
|
+
* @param {number} tabId
|
|
123
139
|
* @returns {Promise<void>}
|
|
124
140
|
*/
|
|
125
|
-
export
|
|
126
|
-
|
|
141
|
+
export function tabOpReloadByPassCacheTrue(tabId) {
|
|
142
|
+
return browser.tabs.reload(tabId, {bypassCache: true});
|
|
127
143
|
}
|
|
128
144
|
|
|
129
145
|
/**
|
|
146
|
+
* Closes one or more tabs.
|
|
130
147
|
*
|
|
131
|
-
* @param
|
|
148
|
+
* @param {number|number[]} tabId
|
|
132
149
|
* @returns {Promise<void>}
|
|
133
150
|
*/
|
|
134
|
-
export
|
|
135
|
-
|
|
151
|
+
export function tabOpRemove(tabId) {
|
|
152
|
+
return browser.tabs.remove(tabId);
|
|
136
153
|
}
|
|
137
154
|
|
|
138
155
|
/**
|
|
156
|
+
* Hides one or more tabs (Firefox specific API).
|
|
139
157
|
*
|
|
140
|
-
* @param
|
|
141
|
-
* @returns {Promise<
|
|
158
|
+
* @param {number|number[]} tabId
|
|
159
|
+
* @returns {Promise<number[]>}
|
|
142
160
|
*/
|
|
143
|
-
export
|
|
144
|
-
|
|
161
|
+
export function tabOpHide(tabId) {
|
|
162
|
+
return browser.tabs.hide(tabId);
|
|
145
163
|
}
|
|
146
164
|
|
|
147
165
|
/**
|
|
166
|
+
* Modifies the properties of a tab.
|
|
148
167
|
*
|
|
149
|
-
* @param {number}tabId
|
|
150
|
-
* @param {
|
|
151
|
-
* @returns {Promise<browser.tabs.Tab&{tabId: number}>}
|
|
152
|
-
*/
|
|
153
|
-
export async function tabOpUpdate(
|
|
154
|
-
|
|
168
|
+
* @param {number} tabId
|
|
169
|
+
* @param {Object} updateProperties
|
|
170
|
+
* @returns {Promise<browser.tabs.Tab & {tabId: number}>}
|
|
171
|
+
*/
|
|
172
|
+
export async function tabOpUpdate(
|
|
173
|
+
tabId,
|
|
174
|
+
updateProperties,
|
|
175
|
+
) {
|
|
176
|
+
const tab = await browser.tabs.update(tabId, updateProperties);
|
|
155
177
|
return tabOpEnhance(tab);
|
|
156
178
|
}
|
|
157
179
|
|
|
158
180
|
/**
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
* @
|
|
181
|
+
* Updates a tab to be inactive and muted.
|
|
182
|
+
*
|
|
183
|
+
* @param {number} tabId
|
|
184
|
+
* @returns {Promise<browser.tabs.Tab & {tabId: number}>}
|
|
162
185
|
*/
|
|
163
|
-
export
|
|
164
|
-
return
|
|
186
|
+
export function tabOpUpdateActiveFalse(tabId) {
|
|
187
|
+
return tabOpUpdate(tabId, {active: false, muted: true});
|
|
165
188
|
}
|
|
166
189
|
|
|
167
190
|
/**
|
|
168
|
-
*
|
|
191
|
+
* Focuses the window containing the tab, then highlights and activates the tab.
|
|
192
|
+
*
|
|
193
|
+
* @param {number} tabId
|
|
194
|
+
* @returns {Promise<browser.tabs.Tab & {tabId: number}>}
|
|
169
195
|
*/
|
|
170
196
|
export async function tabOpFocus(tabId) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
197
|
+
const tab = await tabOpGet(tabId);
|
|
198
|
+
if (!tab || !tab.windowId) throw new Error(`Tab ${tabId} not found.`);
|
|
199
|
+
|
|
200
|
+
await browser.windows.update(tab.windowId, {focused: true});
|
|
201
|
+
|
|
202
|
+
const tabUpdated = await browser.tabs.update(tabId, {
|
|
203
|
+
active: true,
|
|
204
|
+
highlighted: true,
|
|
205
|
+
});
|
|
174
206
|
|
|
175
|
-
let updateProperties = {active: true, highlighted: true};
|
|
176
|
-
let tabUpdated = await tabOpUpdate(tabId, updateProperties);
|
|
177
207
|
return tabOpEnhance(tabUpdated);
|
|
178
208
|
}
|
|
179
209
|
|
|
180
210
|
/**
|
|
211
|
+
* Injects CSS code into a page.
|
|
212
|
+
* Note: Check manifest version compatibility for 'insertCSS'.
|
|
181
213
|
*
|
|
182
|
-
* @param{number} tabId
|
|
183
|
-
* @param {string}code
|
|
214
|
+
* @param {number} tabId
|
|
215
|
+
* @param {string} code
|
|
184
216
|
* @returns {Promise<void>}
|
|
185
217
|
*/
|
|
186
|
-
export
|
|
187
|
-
|
|
218
|
+
export function tabOpInsertCssCode(
|
|
219
|
+
tabId,
|
|
220
|
+
code,
|
|
221
|
+
) {
|
|
222
|
+
return browser.tabs.insertCSS(tabId, {code});
|
|
188
223
|
}
|
|
189
224
|
|
|
190
225
|
/**
|
|
226
|
+
* Removes CSS code that was previously injected into a page.
|
|
191
227
|
*
|
|
192
|
-
* @param{number} tabId
|
|
193
|
-
* @param {string}code
|
|
228
|
+
* @param {number} tabId
|
|
229
|
+
* @param {string} code
|
|
194
230
|
* @returns {Promise<void>}
|
|
195
231
|
*/
|
|
196
|
-
export
|
|
197
|
-
|
|
232
|
+
export function tabOpRemoveCssCode(
|
|
233
|
+
tabId,
|
|
234
|
+
code,
|
|
235
|
+
) {
|
|
236
|
+
return browser.tabs.removeCSS(tabId, {code});
|
|
198
237
|
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import {stoOpGet, stoOpSet} from './opStorage.js';
|
|
2
|
-
|
|
3
|
-
export async function serviceInitUserSettings(userSettings) {
|
|
4
|
-
const initPromises = Object.entries(userSettings)
|
|
5
|
-
.map(async ([key, setting]) => {
|
|
6
|
-
const oldValue = await stoOpGet(key);
|
|
7
|
-
|
|
8
|
-
// FIX: Check strictly for null or undefined.
|
|
9
|
-
// This allows `false` and `0` to be recognized as valid saved values.
|
|
10
|
-
if (oldValue === null || oldValue === undefined) {
|
|
11
|
-
await stoOpSet(key, setting.selected);
|
|
12
|
-
}
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
await Promise.all(initPromises);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* // todo
|
|
20
|
-
* @param userSettings{{}}
|
|
21
|
-
* @returns {Promise<{Object}>}
|
|
22
|
-
*/
|
|
23
|
-
export async function serviceGetUserSettings(userSettings) {
|
|
24
|
-
const red = {};
|
|
25
|
-
for (let k of Object.keys(userSettings)) {
|
|
26
|
-
red[k] = await stoOpGet(k);
|
|
27
|
-
}
|
|
28
|
-
return red;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|