@vacantthinker/firefox-addon-framework-easy 2026.530.1436 → 2026.602.1952

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 CHANGED
@@ -59,6 +59,16 @@ export function browserRuntimeManifestName() { }
59
59
 
60
60
  ```
61
61
 
62
+ ### 📄 File: `src/browserTab.js`
63
+ ```javascript
64
+ export async function browserTabCreateToDownload(message) { }
65
+
66
+ export async function browserTabCreateNearSendMessageToContentJs(message) { }
67
+
68
+ export function browserTabWaitReloadThenRemoveIt({ }
69
+
70
+ ```
71
+
62
72
  ### 📄 File: `src/generate.js`
63
73
  ```javascript
64
74
  export function generateHtmlByUserSettings(
@@ -130,17 +140,6 @@ export async function tabOpRemoveCssCode(tabId, code) { }
130
140
 
131
141
  ### 📄 File: `src/serviceCommon.js`
132
142
  ```javascript
133
- export async function serviceTakeScreenshot(
134
- { }
135
-
136
- export async function serviceElementPicker(message) { }
137
-
138
- export async function serviceGetFullPageRectData(message) { }
139
-
140
- export async function serviceFindAllMagnetLink(message) { }
141
-
142
- export async function serviceDealWithMagnetLink(message) { }
143
-
144
143
  ```
145
144
 
146
145
  ### 📄 File: `src/serviceFetch.js`
@@ -175,6 +174,21 @@ export function serviceRemoveIllegalWord(value) { }
175
174
 
176
175
  ```
177
176
 
177
+ ### 📄 File: `src/serviceOpJavascript.js`
178
+ ```javascript
179
+ export async function serviceTakeScreenshot(
180
+ { }
181
+
182
+ export async function serviceElementPicker(message) { }
183
+
184
+ export async function serviceGetFullPageRectData(message) { }
185
+
186
+ export async function serviceFindAllMagnetLink(message) { }
187
+
188
+ export async function serviceDealWithMagnetLink(message) { }
189
+
190
+ ```
191
+
178
192
  ### 📄 File: `src/servicePureVideolink.js`
179
193
  ```javascript
180
194
  export function servicePureVideolinkYTB(videolinkOrigin) { }
package/index.js CHANGED
@@ -2,6 +2,7 @@ export * from './src/baseORM.js'
2
2
  export * from './src/browserDownload.js'
3
3
  export * from './src/browserNotification.js'
4
4
  export * from './src/browserRuntime.js'
5
+ export * from './src/browserTab.js'
5
6
  export * from './src/generate.js'
6
7
  export * from './src/opStorage.js'
7
8
  export * from './src/opTab.js'
@@ -9,6 +10,7 @@ export * from './src/serviceCommon.js'
9
10
  export * from './src/serviceFetch.js'
10
11
  export * from './src/serviceGet.js'
11
12
  export * from './src/serviceOpContent.js'
13
+ export * from './src/serviceOpJavascript.js'
12
14
  export * from './src/servicePureVideolink.js'
13
15
  export * from './src/serviceUpdateTabStyle.js'
14
16
  export * from './src/serviceUserSettings.js'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vacantthinker/firefox-addon-framework-easy",
3
- "version": "2026.0530.1436",
3
+ "version": "2026.0602.1952",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
@@ -0,0 +1,92 @@
1
+ // @ts-check
2
+
3
+ import {browserNotificationCreate} from './browserNotification.js';
4
+ import {tabOpCreateNear, tabOpRemove} from './opTab.js';
5
+
6
+ async function browserTabSendMessage(tabId, message) {
7
+ await browser.tabs.sendMessage(tabId, message);
8
+ }
9
+
10
+ /**
11
+ * must has, url
12
+ * @param message{{
13
+ * tabId:number,
14
+ * title:string,
15
+ * url:string,
16
+ * focusNewTab:boolean,
17
+ * }}
18
+ * @returns {Promise<void>}
19
+ */
20
+ export async function browserTabCreateToDownload(message) {
21
+ let {title, url} = message;
22
+ await browserNotificationCreate(`new tab! ${title || url}`);
23
+
24
+ let {focusNewTab} = message;
25
+ let properties = {
26
+ url, tabId: message.tabId,
27
+ active: focusNewTab || false,
28
+ };
29
+
30
+ let {tabId} = await tabOpCreateNear(properties);
31
+ browser.tabs.onUpdated.addListener(
32
+ async function lis(tabId, changeInfo) {
33
+ if (changeInfo.status === 'complete') {
34
+ browser.tabs.onUpdated.removeListener(lis);
35
+ // todo code here
36
+ await tabOpRemove(tabId)
37
+ }
38
+ }
39
+ , {tabId, properties: ['status']});
40
+ }
41
+
42
+
43
+ /**
44
+ * must has, tabId, url
45
+ * @param message{{
46
+ * tabId:number,
47
+ * title:string,
48
+ * url:string,
49
+ * focusNewTab:boolean,
50
+ * }}
51
+ * @returns {Promise<void>}
52
+ */
53
+ export async function browserTabCreateNearSendMessageToContentJs(message) {
54
+ let {title, url} = message;
55
+ await browserNotificationCreate(`new tab! ${title || url}`);
56
+
57
+ let {focusNewTab} = message;
58
+ let properties = {
59
+ url, tabId: message.tabId,
60
+ active: focusNewTab || false,
61
+ };
62
+
63
+ let {tabId} = await tabOpCreateNear(properties);
64
+ browser.tabs.onUpdated.addListener(
65
+ async function lis(tabId, changeInfo) {
66
+ if (changeInfo.status === 'complete') {
67
+ browser.tabs.onUpdated.removeListener(lis);
68
+ // todo code here
69
+ await browserTabSendMessage(
70
+ tabId, Object.assign({}, message, {tabId}));
71
+ }
72
+ }
73
+ , {tabId, properties: ['status']});
74
+ }
75
+
76
+ /**
77
+ * tab exists, watch it, reload then close
78
+ * @param param0
79
+ * @param param0.tabId{number}
80
+ */
81
+ export function browserTabWaitReloadThenRemoveIt({tabId}) {
82
+ browser.tabs.onUpdated.addListener(
83
+ async function lis(tabId, changeInfo) {
84
+ if (changeInfo.status === 'complete') {
85
+ browser.tabs.onUpdated.removeListener(lis);
86
+ // todo code here
87
+ await tabOpRemove(tabId);
88
+ }
89
+ }
90
+ , {tabId, properties: ['status']});
91
+
92
+ }
@@ -1,349 +0,0 @@
1
- import {serviceGetCurrentDateYYYYMMDDHHMMSS} from './serviceGet.js';
2
- import {
3
- serviceCopyContentToClipboard,
4
- serviceRemoveIllegalWord,
5
- serviceSaveContentToLocal,
6
- } from './serviceOpContent.js';
7
- import {browserNotificationCreate} from './browserNotification.js';
8
-
9
- /**
10
- *
11
- * @param param0
12
- * @param param0.tabId {number}
13
- * @param param0.filename {string}
14
- * @param param0.rect {{
15
- * x,
16
- * y,
17
- * width,
18
- * height,
19
- * }}
20
- * @returns {Promise<void>}
21
- */
22
- export async function serviceTakeScreenshot(
23
- {
24
- tabId,
25
- filename,
26
- rect,
27
- }) {
28
-
29
- const tag = 'actTakeScreenshot()';
30
- console.info(tag, `rect=\n`, rect);
31
- let dataURI = await browser.tabs.captureTab(tabId, {
32
- rect: rect,
33
- });
34
- let assign = Object.assign(
35
- {},
36
- {dataURI, filename},
37
- );
38
- await browser.scripting.executeScript({
39
- target: {tabId},
40
- args: [assign],
41
- func: function(message) {
42
- if (message) {
43
- let {dataURI, filename} = message;
44
-
45
- imageDataToLocalFile({dataURI, filename});
46
-
47
- function imageDataToLocalFile({dataURI, filename}) {
48
- let a = document.createElement('a');
49
- a.href = dataURI;
50
- a.download = filename;
51
- a.click();
52
- }
53
-
54
- // todo end if(message)
55
- }
56
- },
57
- });
58
-
59
- }
60
-
61
- /**
62
- * middle ware, output => {rect, uniqueSelector,}
63
- * @param message{{
64
- * tabId:number,
65
- * act:string
66
- * }}
67
- * @returns {Promise<void>}
68
- */
69
- export async function serviceElementPicker(message) {
70
- let {tabId} = message;
71
- await browser.scripting.executeScript({
72
- target: {tabId},
73
- args: [message],
74
- func: async function(message) {
75
- if (!message) return;
76
- console.log('picker.js initialized', message);
77
-
78
- // 1. Create a dedicated "Always On Top" highlighter overlay
79
- const overlayId = 'extension-element-highlighter-overlay';
80
- let overlay = document.getElementById(overlayId);
81
-
82
- if (!overlay) {
83
- overlay = document.createElement('div');
84
- overlay.id = overlayId;
85
- // !important and max z-index guarantees it will never be hidden by site CSS
86
- overlay.style.cssText = `
87
- position: fixed !important;
88
- top: 0 !important;
89
- left: 0 !important;
90
- width: 0 !important;
91
- height: 0 !important;
92
- background-color: rgba(255, 0, 85, 0.2) !important;
93
- outline: 2px dashed #ff0055 !important;
94
- outline-offset: -2px !important;
95
- z-index: 2147483647 !important;
96
- pointer-events: none !important;
97
- transition: all 0.05s ease-out !important;
98
- display: none !important;
99
- `;
100
- // Appending to documentElement (<html>) avoids <body> layout restrictions
101
- document.documentElement.appendChild(overlay);
102
- }
103
-
104
- function getUniqueSelector(el) {
105
- if (!(el instanceof Element)) return '';
106
- const path = [];
107
- while (el.nodeType === Node.ELEMENT_NODE) {
108
- let selector = el.nodeName.toLowerCase();
109
- if (el.id) {
110
- selector += '#' + el.id;
111
- path.unshift(selector);
112
- break;
113
- }
114
- else {
115
- let sib = el, nth = 1;
116
- while (sib = sib.previousElementSibling) {
117
- if (sib.nodeName.toLowerCase() === selector) nth++;
118
- }
119
- if (nth !== 1) selector += `:nth-of-type(${nth})`;
120
- }
121
- path.unshift(selector);
122
- el = el.parentNode;
123
- }
124
- return path.join(' > ');
125
- }
126
-
127
- function handleMouseOver(e) {
128
- const target = e.target;
129
-
130
- // Safety check to ensure we don't try to highlight our own UI
131
- if (target.id === overlayId) return;
132
-
133
- // Get exact coordinates of the hovered element
134
- const clientRect = target.getBoundingClientRect();
135
-
136
- // Move the floating overlay exactly over the target
137
- overlay.style.setProperty('display', 'block', 'important');
138
- overlay.style.setProperty('top', `${clientRect.top}px`, 'important');
139
- overlay.style.setProperty('left', `${clientRect.left}px`, 'important');
140
- overlay.style.setProperty('width', `${clientRect.width}px`,
141
- 'important');
142
- overlay.style.setProperty('height', `${clientRect.height}px`,
143
- 'important');
144
-
145
- // Change mouse cursor to indicate picking mode
146
- document.body.style.setProperty('cursor', 'crosshair', 'important');
147
- }
148
-
149
- async function handleElementClick(e) {
150
- e.preventDefault();
151
- e.stopPropagation();
152
-
153
- const target = e.target;
154
-
155
- // Clean up the picker completely
156
- stopPickingMode();
157
-
158
- // Calculate screenshot coordinates
159
- let clientRect = target.getBoundingClientRect();
160
- let rect = {
161
- height: clientRect.height,
162
- width: clientRect.width,
163
- x: clientRect.left + window.scrollX,
164
- y: clientRect.top + window.scrollY,
165
- };
166
-
167
- // Assuming 'target' is your clicked element (e.g., from e.target)
168
- let messageTakeScreenshot = Object.assign(
169
- {}, // Start with a fresh, empty object
170
- message, // Put the original message first so it doesn't overwrite your new data
171
- {rect},
172
- {
173
- // The guaranteed unique CSS path (e.g., "div#wrap > ul > li:nth-of-type(2)")
174
- uniqueSelector: getUniqueSelector(target),
175
- },
176
- );
177
-
178
- await browser.runtime.sendMessage(messageTakeScreenshot);
179
- }
180
-
181
- function startPickingMode() {
182
- document.addEventListener('mouseover', handleMouseOver, true);
183
- document.addEventListener('click', handleElementClick, true);
184
- }
185
-
186
- function stopPickingMode() {
187
- document.removeEventListener('mouseover', handleMouseOver, true);
188
- document.removeEventListener('click', handleElementClick, true);
189
-
190
- // Restore normal mouse cursor
191
- document.body.style.removeProperty('cursor');
192
-
193
- // Remove the overlay from the DOM entirely
194
- if (overlay) {
195
- overlay.remove();
196
- }
197
- }
198
-
199
- // Initialize the picker mode
200
- startPickingMode();
201
- },
202
- });
203
- }
204
-
205
- /**
206
- * middle ware, output: {rect}
207
- *
208
- * @param message{{
209
- * tabId:number,
210
- * act:string
211
- * }}
212
- * @returns {Promise<void>}
213
- */
214
- export async function serviceGetFullPageRectData(message) {
215
- let {tabId} = message;
216
-
217
- await browser.scripting.executeScript({
218
- target: {tabId},
219
- args: [message],
220
- func: (message) => {
221
-
222
- let x = 0, y = 0;
223
- let width = document.documentElement.scrollWidth;
224
- let height = document.documentElement.scrollHeight;
225
- let rect = {
226
- x, y, width, height,
227
- };
228
- browser.runtime.sendMessage(Object.assign(
229
- {},
230
- message,
231
- {rect},
232
- ));
233
- // todo end if (message)
234
- },
235
- });
236
- }
237
-
238
- /**
239
- * middle ware, output: Object.assign({}, message, {data})
240
- *
241
- * serviceFindAllMagnetLink({
242
- * tabId, title, act, ...
243
- * })
244
- *
245
- * @param message{{
246
- * tabId:number,
247
- * title:string,
248
- * act: string,
249
- * }}
250
- */
251
- export async function serviceFindAllMagnetLink(message) {
252
- let {tabId} = message;
253
- const assign = Object.assign({}, message);
254
-
255
- await browser.scripting.executeScript({
256
- target: {tabId},
257
- args: [assign],
258
- func: async (message) => {
259
- if (message) {
260
- function findAllMagnetLinks() {
261
- const magnets = new Set(); // Prevents duplicates
262
-
263
- // --- Type 1: Find inside ANY element's attributes ---
264
- const attributeSelector = '*[href*="magnet:"], *[data-url*="magnet:"], *[data-magnet*="magnet:"], *[data-href*="magnet:"]';
265
- const attrElements = document.querySelectorAll(attributeSelector);
266
-
267
- attrElements.forEach(el => {
268
- // Check every attribute of the element to find the one holding the magnet string
269
- for (let attr of el.attributes) {
270
- if (attr.value.includes('magnet:?xt=')) {
271
- magnets.add(attr.value.trim());
272
- }
273
- }
274
- });
275
-
276
- // --- Type 2: Find inside raw text (for <div>, <span>, <td>, etc.) ---
277
- // We target elements that don't have children to avoid grabbing huge parent container blocks
278
- const allElements = document.querySelectorAll(
279
- 'div, span, td, p, a, button');
280
- allElements.forEach(el => {
281
- if (el.children.length === 0) { // Deepest element
282
- const text = el.textContent.trim();
283
- if (text.includes('magnet:?xt=')) {
284
- // Extract just the magnet link using Regex in case there is surrounding text
285
- const match = text.match(/magnet:\?xt=[^\s"'<>]+/);
286
- if (match) {
287
- magnets.add(match[0]);
288
- }
289
- }
290
- }
291
- });
292
-
293
- return Array.from(magnets);
294
- }
295
-
296
- await browser.runtime.sendMessage(Object.assign(
297
- {},
298
- message,
299
- {
300
- data: findAllMagnetLinks(),
301
- },
302
- ));
303
-
304
- // todo end if(message)
305
- }
306
- },
307
- });
308
- }
309
-
310
- /**
311
- *
312
- * @param message{{
313
- * title:string,
314
- * data: [string],
315
- * handleOption: 'clipboard'|'txt'|'clipboardAndTxt'
316
- * }}
317
- * @returns {Promise<void>}
318
- */
319
- export async function serviceDealWithMagnetLink(message) {
320
-
321
- let {title, data, handleOption} = message;
322
- let titleCleaned = serviceRemoveIllegalWord(title);
323
- console.info(`data.length=\n`, data.length);
324
-
325
- if (Array.isArray(data) && data.length >= 1) {
326
- let content = `${data.join('\n')}\n`;
327
- console.info(`content=\n`, content);
328
-
329
- let filename = [
330
- 'magnet-link',
331
- titleCleaned,
332
- serviceGetCurrentDateYYYYMMDDHHMMSS()].join(' ');
333
-
334
- if (handleOption === 'clipboard') {
335
- await serviceCopyContentToClipboard(content);
336
- }
337
- else if (handleOption === 'txt') {
338
- serviceSaveContentToLocal(content, filename);
339
- }
340
- else if (handleOption === 'clipboardAndTxt') {
341
- await serviceCopyContentToClipboard(content);
342
- serviceSaveContentToLocal(content, filename);
343
- }
344
- }
345
- else {
346
- // todo notification => magnet link not found!
347
- await browserNotificationCreate('magnet link not found!');
348
- }
349
- }
@@ -0,0 +1,349 @@
1
+ import {serviceGetCurrentDateYYYYMMDDHHMMSS} from './serviceGet.js';
2
+ import {
3
+ serviceCopyContentToClipboard,
4
+ serviceRemoveIllegalWord,
5
+ serviceSaveContentToLocal,
6
+ } from './serviceOpContent.js';
7
+ import {browserNotificationCreate} from './browserNotification.js';
8
+
9
+ /**
10
+ *
11
+ * @param param0
12
+ * @param param0.tabId {number}
13
+ * @param param0.filename {string}
14
+ * @param param0.rect {{
15
+ * x,
16
+ * y,
17
+ * width,
18
+ * height,
19
+ * }}
20
+ * @returns {Promise<void>}
21
+ */
22
+ export async function serviceTakeScreenshot(
23
+ {
24
+ tabId,
25
+ filename,
26
+ rect,
27
+ }) {
28
+
29
+ const tag = 'actTakeScreenshot()';
30
+ console.info(tag, `rect=\n`, rect);
31
+ let dataURI = await browser.tabs.captureTab(tabId, {
32
+ rect: rect,
33
+ });
34
+ let assign = Object.assign(
35
+ {},
36
+ {dataURI, filename},
37
+ );
38
+ await browser.scripting.executeScript({
39
+ target: {tabId},
40
+ args: [assign],
41
+ func: function(message) {
42
+ if (message) {
43
+ let {dataURI, filename} = message;
44
+
45
+ imageDataToLocalFile({dataURI, filename});
46
+
47
+ function imageDataToLocalFile({dataURI, filename}) {
48
+ let a = document.createElement('a');
49
+ a.href = dataURI;
50
+ a.download = filename;
51
+ a.click();
52
+ }
53
+
54
+ // todo end if(message)
55
+ }
56
+ },
57
+ });
58
+
59
+ }
60
+
61
+ /**
62
+ * middle ware, output => {rect, uniqueSelector,}
63
+ * @param message{{
64
+ * tabId:number,
65
+ * act:string
66
+ * }}
67
+ * @returns {Promise<void>}
68
+ */
69
+ export async function serviceElementPicker(message) {
70
+ let {tabId} = message;
71
+ await browser.scripting.executeScript({
72
+ target: {tabId},
73
+ args: [message],
74
+ func: async function(message) {
75
+ if (!message) return;
76
+ console.log('picker.js initialized', message);
77
+
78
+ // 1. Create a dedicated "Always On Top" highlighter overlay
79
+ const overlayId = 'extension-element-highlighter-overlay';
80
+ let overlay = document.getElementById(overlayId);
81
+
82
+ if (!overlay) {
83
+ overlay = document.createElement('div');
84
+ overlay.id = overlayId;
85
+ // !important and max z-index guarantees it will never be hidden by site CSS
86
+ overlay.style.cssText = `
87
+ position: fixed !important;
88
+ top: 0 !important;
89
+ left: 0 !important;
90
+ width: 0 !important;
91
+ height: 0 !important;
92
+ background-color: rgba(255, 0, 85, 0.2) !important;
93
+ outline: 2px dashed #ff0055 !important;
94
+ outline-offset: -2px !important;
95
+ z-index: 2147483647 !important;
96
+ pointer-events: none !important;
97
+ transition: all 0.05s ease-out !important;
98
+ display: none !important;
99
+ `;
100
+ // Appending to documentElement (<html>) avoids <body> layout restrictions
101
+ document.documentElement.appendChild(overlay);
102
+ }
103
+
104
+ function getUniqueSelector(el) {
105
+ if (!(el instanceof Element)) return '';
106
+ const path = [];
107
+ while (el.nodeType === Node.ELEMENT_NODE) {
108
+ let selector = el.nodeName.toLowerCase();
109
+ if (el.id) {
110
+ selector += '#' + el.id;
111
+ path.unshift(selector);
112
+ break;
113
+ }
114
+ else {
115
+ let sib = el, nth = 1;
116
+ while (sib = sib.previousElementSibling) {
117
+ if (sib.nodeName.toLowerCase() === selector) nth++;
118
+ }
119
+ if (nth !== 1) selector += `:nth-of-type(${nth})`;
120
+ }
121
+ path.unshift(selector);
122
+ el = el.parentNode;
123
+ }
124
+ return path.join(' > ');
125
+ }
126
+
127
+ function handleMouseOver(e) {
128
+ const target = e.target;
129
+
130
+ // Safety check to ensure we don't try to highlight our own UI
131
+ if (target.id === overlayId) return;
132
+
133
+ // Get exact coordinates of the hovered element
134
+ const clientRect = target.getBoundingClientRect();
135
+
136
+ // Move the floating overlay exactly over the target
137
+ overlay.style.setProperty('display', 'block', 'important');
138
+ overlay.style.setProperty('top', `${clientRect.top}px`, 'important');
139
+ overlay.style.setProperty('left', `${clientRect.left}px`, 'important');
140
+ overlay.style.setProperty('width', `${clientRect.width}px`,
141
+ 'important');
142
+ overlay.style.setProperty('height', `${clientRect.height}px`,
143
+ 'important');
144
+
145
+ // Change mouse cursor to indicate picking mode
146
+ document.body.style.setProperty('cursor', 'crosshair', 'important');
147
+ }
148
+
149
+ async function handleElementClick(e) {
150
+ e.preventDefault();
151
+ e.stopPropagation();
152
+
153
+ const target = e.target;
154
+
155
+ // Clean up the picker completely
156
+ stopPickingMode();
157
+
158
+ // Calculate screenshot coordinates
159
+ let clientRect = target.getBoundingClientRect();
160
+ let rect = {
161
+ height: clientRect.height,
162
+ width: clientRect.width,
163
+ x: clientRect.left + window.scrollX,
164
+ y: clientRect.top + window.scrollY,
165
+ };
166
+
167
+ // Assuming 'target' is your clicked element (e.g., from e.target)
168
+ let messageTakeScreenshot = Object.assign(
169
+ {}, // Start with a fresh, empty object
170
+ message, // Put the original message first so it doesn't overwrite your new data
171
+ {rect},
172
+ {
173
+ // The guaranteed unique CSS path (e.g., "div#wrap > ul > li:nth-of-type(2)")
174
+ uniqueSelector: getUniqueSelector(target),
175
+ },
176
+ );
177
+
178
+ await browser.runtime.sendMessage(messageTakeScreenshot);
179
+ }
180
+
181
+ function startPickingMode() {
182
+ document.addEventListener('mouseover', handleMouseOver, true);
183
+ document.addEventListener('click', handleElementClick, true);
184
+ }
185
+
186
+ function stopPickingMode() {
187
+ document.removeEventListener('mouseover', handleMouseOver, true);
188
+ document.removeEventListener('click', handleElementClick, true);
189
+
190
+ // Restore normal mouse cursor
191
+ document.body.style.removeProperty('cursor');
192
+
193
+ // Remove the overlay from the DOM entirely
194
+ if (overlay) {
195
+ overlay.remove();
196
+ }
197
+ }
198
+
199
+ // Initialize the picker mode
200
+ startPickingMode();
201
+ },
202
+ });
203
+ }
204
+
205
+ /**
206
+ * middle ware, output: {rect}
207
+ *
208
+ * @param message{{
209
+ * tabId:number,
210
+ * act:string
211
+ * }}
212
+ * @returns {Promise<void>}
213
+ */
214
+ export async function serviceGetFullPageRectData(message) {
215
+ let {tabId} = message;
216
+
217
+ await browser.scripting.executeScript({
218
+ target: {tabId},
219
+ args: [message],
220
+ func: (message) => {
221
+
222
+ let x = 0, y = 0;
223
+ let width = document.documentElement.scrollWidth;
224
+ let height = document.documentElement.scrollHeight;
225
+ let rect = {
226
+ x, y, width, height,
227
+ };
228
+ browser.runtime.sendMessage(Object.assign(
229
+ {},
230
+ message,
231
+ {rect},
232
+ ));
233
+ // todo end if (message)
234
+ },
235
+ });
236
+ }
237
+
238
+ /**
239
+ * middle ware, output: Object.assign({}, message, {data})
240
+ *
241
+ * serviceFindAllMagnetLink({
242
+ * tabId, title, act, ...
243
+ * })
244
+ *
245
+ * @param message{{
246
+ * tabId:number,
247
+ * title:string,
248
+ * act: string,
249
+ * }}
250
+ */
251
+ export async function serviceFindAllMagnetLink(message) {
252
+ let {tabId} = message;
253
+ const assign = Object.assign({}, message);
254
+
255
+ await browser.scripting.executeScript({
256
+ target: {tabId},
257
+ args: [assign],
258
+ func: async (message) => {
259
+ if (message) {
260
+ function findAllMagnetLinks() {
261
+ const magnets = new Set(); // Prevents duplicates
262
+
263
+ // --- Type 1: Find inside ANY element's attributes ---
264
+ const attributeSelector = '*[href*="magnet:"], *[data-url*="magnet:"], *[data-magnet*="magnet:"], *[data-href*="magnet:"]';
265
+ const attrElements = document.querySelectorAll(attributeSelector);
266
+
267
+ attrElements.forEach(el => {
268
+ // Check every attribute of the element to find the one holding the magnet string
269
+ for (let attr of el.attributes) {
270
+ if (attr.value.includes('magnet:?xt=')) {
271
+ magnets.add(attr.value.trim());
272
+ }
273
+ }
274
+ });
275
+
276
+ // --- Type 2: Find inside raw text (for <div>, <span>, <td>, etc.) ---
277
+ // We target elements that don't have children to avoid grabbing huge parent container blocks
278
+ const allElements = document.querySelectorAll(
279
+ 'div, span, td, p, a, button');
280
+ allElements.forEach(el => {
281
+ if (el.children.length === 0) { // Deepest element
282
+ const text = el.textContent.trim();
283
+ if (text.includes('magnet:?xt=')) {
284
+ // Extract just the magnet link using Regex in case there is surrounding text
285
+ const match = text.match(/magnet:\?xt=[^\s"'<>]+/);
286
+ if (match) {
287
+ magnets.add(match[0]);
288
+ }
289
+ }
290
+ }
291
+ });
292
+
293
+ return Array.from(magnets);
294
+ }
295
+
296
+ await browser.runtime.sendMessage(Object.assign(
297
+ {},
298
+ message,
299
+ {
300
+ data: findAllMagnetLinks(),
301
+ },
302
+ ));
303
+
304
+ // todo end if(message)
305
+ }
306
+ },
307
+ });
308
+ }
309
+
310
+ /**
311
+ *
312
+ * @param message{{
313
+ * title:string,
314
+ * data: [string],
315
+ * handleOption: 'clipboard'|'txt'|'clipboardAndTxt'
316
+ * }}
317
+ * @returns {Promise<void>}
318
+ */
319
+ export async function serviceDealWithMagnetLink(message) {
320
+
321
+ let {title, data, handleOption} = message;
322
+ let titleCleaned = serviceRemoveIllegalWord(title);
323
+ console.info(`data.length=\n`, data.length);
324
+
325
+ if (Array.isArray(data) && data.length >= 1) {
326
+ let content = `${data.join('\n')}\n`;
327
+ console.info(`content=\n`, content);
328
+
329
+ let filename = [
330
+ 'magnet-link',
331
+ titleCleaned,
332
+ serviceGetCurrentDateYYYYMMDDHHMMSS()].join(' ');
333
+
334
+ if (handleOption === 'clipboard') {
335
+ await serviceCopyContentToClipboard(content);
336
+ }
337
+ else if (handleOption === 'txt') {
338
+ serviceSaveContentToLocal(content, filename);
339
+ }
340
+ else if (handleOption === 'clipboardAndTxt') {
341
+ await serviceCopyContentToClipboard(content);
342
+ serviceSaveContentToLocal(content, filename);
343
+ }
344
+ }
345
+ else {
346
+ // todo notification => magnet link not found!
347
+ await browserNotificationCreate('magnet link not found!');
348
+ }
349
+ }