ectrol 0.0.4 → 0.1.2

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/dist/index.d.mts CHANGED
@@ -41,7 +41,6 @@ interface ISize {
41
41
  declare class ElementHandle {
42
42
  readonly contents: WebContents;
43
43
  readonly selector: string;
44
- isNotFound: boolean;
45
44
  constructor(contents: WebContents, selector: string);
46
45
  /**
47
46
  * 生成在 renderer 端执行的查找脚本。
@@ -53,7 +52,9 @@ declare class ElementHandle {
53
52
  /**
54
53
  * 触发元素 mouseover 事件(不移动真实鼠标)。
55
54
  */
56
- hover(): Promise<any>;
55
+ hover(options?: {
56
+ timeout?: number;
57
+ }): Promise<any>;
57
58
  /**
58
59
  * 点击
59
60
  * - 通过 WebContents.sendInputEvent 发送 mouseDown/mouseUp 事件
@@ -83,7 +84,9 @@ declare class ElementHandle {
83
84
  /**
84
85
  * 将复选框或单选按钮设为选中,并派发 change 事件。
85
86
  */
86
- check(): Promise<any>;
87
+ check(options?: {
88
+ timeout?: number;
89
+ }): Promise<any>;
87
90
  /**
88
91
  * 填充输入框内容。
89
92
  * - 适用于 `<input>`、`<textarea>` 或 `contenteditable` 元素。
@@ -131,55 +134,77 @@ declare class ElementHandle {
131
134
  * @param value 选项的值
132
135
  * @returns
133
136
  */
134
- selectOption(value: string): Promise<any>;
137
+ selectOption(value: string, options?: {
138
+ timeout?: number;
139
+ }): Promise<any>;
135
140
  /**
136
141
  * 获取元素的 HTML 内容。
137
142
  */
138
- innerHTML(): Promise<any>;
143
+ innerHTML(options?: {
144
+ timeout?: number;
145
+ }): Promise<any>;
139
146
  /**
140
147
  * 获取元素的文本内容。
141
148
  */
142
- innerText(): Promise<any>;
149
+ innerText(options?: {
150
+ timeout?: number;
151
+ }): Promise<any>;
143
152
  /**
144
153
  * 获取元素的文本内容。
145
154
  * @returns 元素的文本内容
146
155
  */
147
- textContent(): Promise<any>;
156
+ textContent(options?: {
157
+ timeout?: number;
158
+ }): Promise<any>;
148
159
  /**
149
160
  * 获取元素的输入值。
150
161
  * @returns 元素的输入值
151
162
  */
152
- inputValue(): Promise<any>;
163
+ inputValue(options?: {
164
+ timeout?: number;
165
+ }): Promise<any>;
153
166
  /**
154
167
  * 获取元素的选中状态。
155
168
  * @returns 元素的选中状态
156
169
  */
157
- isChecked(): Promise<any>;
170
+ isChecked(options?: {
171
+ timeout?: number;
172
+ }): Promise<any>;
158
173
  /**
159
174
  * 获取元素的禁用状态。
160
175
  * @returns 元素的禁用状态
161
176
  */
162
- isDisabled(): Promise<any>;
177
+ isDisabled(options?: {
178
+ timeout?: number;
179
+ }): Promise<any>;
163
180
  /**
164
181
  * 获取元素的可见状态。
165
182
  * @returns 元素的可见状态
166
183
  */
167
- isVisible(): Promise<any>;
184
+ isVisible(options?: {
185
+ timeout?: number;
186
+ }): Promise<any>;
168
187
  /**
169
188
  * 获取元素的可用状态。
170
189
  * @returns 元素的可用状态
171
190
  */
172
- isEnabled(): Promise<any>;
191
+ isEnabled(options?: {
192
+ timeout?: number;
193
+ }): Promise<any>;
173
194
  /**
174
195
  * 获取元素的可编辑状态。
175
196
  * @returns 元素的可编辑状态
176
197
  */
177
- isEditable(): Promise<any>;
198
+ isEditable(options?: {
199
+ timeout?: number;
200
+ }): Promise<any>;
178
201
  /**
179
202
  * 获取元素的隐藏状态。
180
203
  * @returns 元素的隐藏状态
181
204
  */
182
- isHidden(): Promise<any>;
205
+ isHidden(options?: {
206
+ timeout?: number;
207
+ }): Promise<any>;
183
208
  /**
184
209
  * 选中元素的文本内容。
185
210
  */
@@ -245,6 +270,7 @@ declare class SessionStorage {
245
270
  }
246
271
  //#endregion
247
272
  //#region src/utils.d.ts
273
+ declare function randomInRange(min: number, max: number, integer?: boolean): number;
248
274
  declare function getHumanTypingDelay(index: number, text: string): number;
249
275
  declare function sleep(ms: number): Promise<unknown>;
250
276
  //#endregion
@@ -272,4 +298,4 @@ declare class Ectrol {
272
298
  $(selector: string): ElementHandle;
273
299
  }
274
300
  //#endregion
275
- export { Ectrol, Ectrol as default, ElementHandle, IBoundingClientRect, ISize, IVector2D, LocalStorage, SessionStorage, getHumanTypingDelay, sleep };
301
+ export { Ectrol, Ectrol as default, ElementHandle, IBoundingClientRect, ISize, IVector2D, LocalStorage, SessionStorage, getHumanTypingDelay, randomInRange, sleep };
package/dist/index.mjs CHANGED
@@ -1,13 +1,18 @@
1
1
  //#region src/utils.ts
2
+ function randomInRange(min, max, integer = false) {
3
+ if (min > max) [min, max] = [max, min];
4
+ const value = Math.random() * (max - min) + min;
5
+ return integer ? Math.floor(value) : value;
6
+ }
2
7
  function getHumanTypingDelay(index, text) {
3
8
  const char = text[index];
4
- let delay = 90 + Math.random() * 60;
9
+ let delay = randomInRange(16, 100);
5
10
  const progress = index / text.length;
6
11
  if (progress < .15) delay *= 1.3;
7
12
  else if (progress > .85) delay *= 1.25;
8
- if (char === " ") delay += 40;
9
- if (/[,.!?;:]/.test(char)) delay += 200 + Math.random() * 150;
10
- if (Math.random() < .03) delay += 300 + Math.random() * 500;
13
+ if (char === " ") delay += 16;
14
+ if (/[,.!?;:]/.test(char)) delay += randomInRange(16, 50);
15
+ if (Math.random() < .03) delay += randomInRange(100, 200);
11
16
  return Math.max(40, Math.round(delay));
12
17
  }
13
18
  async function sleep(ms) {
@@ -24,7 +29,6 @@ async function sleep(ms) {
24
29
  * - 大多数 API 接受 `timeout`,轮询查找元素直到超时
25
30
  */
26
31
  var ElementHandle = class {
27
- isNotFound = false;
28
32
  constructor(contents, selector) {
29
33
  this.contents = contents;
30
34
  this.selector = selector;
@@ -37,104 +41,116 @@ var ElementHandle = class {
37
41
  */
38
42
  _getElement(timeout = 0) {
39
43
  return `
40
- (()=>{
41
- function getElement() {
42
- const selector = "${this.selector}";
43
-
44
- if (!selector) return null;
44
+ new Promise((resolve) => {
45
+ function getElement() {
46
+ const selector = "${this.selector}";
47
+ if (!selector) return null;
48
+
49
+ window.__ELECTROL__ = window.__ELECTROL__ || {};
50
+ if (window.__ELECTROL__[selector]) {
51
+ return window.__ELECTROL__[selector];
52
+ }
45
53
 
46
- window.__ELECTROL__ = window.__ELECTROL__ || {};
47
- if (window.__ELECTROL__[selector]) {
48
- return window.__ELECTROL__[selector];
49
- }
54
+ // 分层选择器,支持 iframe 穿越
55
+ const parts = selector.split('|>').map(s => s.trim());
56
+ if (!parts.length) return null;
50
57
 
51
- // 分层选择器,支持 iframe 穿越
52
- const parts = selector.split('|>').map(s => s.trim());
53
- if (!parts.length) return null;
58
+ let currentDocument = document;
59
+ let offsetLeft = 0;
60
+ let offsetTop = 0;
61
+ let element = null;
54
62
 
55
- let currentDocument = document;
56
- let offsetLeft = 0;
57
- let offsetTop = 0;
58
- let element = null;
63
+ for (let i = 0; i < parts.length; i++) {
64
+ if (!currentDocument) return null;
59
65
 
60
- for (let i = 0; i < parts.length; i++) {
61
- if (!currentDocument) return null;
66
+ const part = parts[i];
67
+ // 在当前文档查找该层选择器
68
+ const el = currentDocument.querySelector(part);
62
69
 
63
- const part = parts[i];
64
- // 在当前文档查找该层选择器
65
- const el = currentDocument.querySelector(part);
70
+ if (!el) {
71
+ console.warn('未找到元素:', part);
72
+ return null;
73
+ }
66
74
 
67
- if (!el) {
68
- console.warn('未找到元素:', part);
69
- return null;
70
- }
75
+ const rect = el.getBoundingClientRect();
71
76
 
72
- const rect = el.getBoundingClientRect();
77
+ // 累加当前层级偏移
78
+ offsetLeft += rect.left;
79
+ offsetTop += rect.top;
73
80
 
74
- // 累加当前层级偏移
75
- offsetLeft += rect.left;
76
- offsetTop += rect.top;
81
+ element = el;
77
82
 
78
- element = el;
83
+ const isLast = i === parts.length - 1;
84
+ if (!isLast) {
85
+ if (el.tagName !== 'IFRAME') {
86
+ console.warn('非 iframe 元素却尝试进入下一层:', part);
87
+ return null;
88
+ }
79
89
 
80
- const isLast = i === parts.length - 1;
81
- if (!isLast) {
82
- if (el.tagName !== 'IFRAME') {
83
- console.warn('非 iframe 元素却尝试进入下一层:', part);
84
- return null;
85
- }
90
+ const nextDoc = el.contentDocument || el.contentWindow?.document;
91
+ if (!nextDoc) {
92
+ console.warn('无法访问 iframe.contentDocument(可能跨域)');
93
+ return null;
94
+ }
86
95
 
87
- const nextDoc = el.contentDocument || el.contentWindow?.document;
88
- if (!nextDoc) {
89
- console.warn('无法访问 iframe.contentDocument(可能跨域)');
90
- return null;
96
+ currentDocument = nextDoc;
91
97
  }
92
-
93
- currentDocument = nextDoc;
94
98
  }
95
- }
96
99
 
97
- if (!element) return null;
100
+ if (!element) return null;
98
101
 
99
- const finalRect = element.getBoundingClientRect();
102
+ const finalRect = element.getBoundingClientRect();
100
103
 
101
- // 缓存元素
102
- // 缓存以避免重复查找
103
- window.__ELECTROL__[selector] = {
104
- element,
105
- rect: new DOMRect(
106
- offsetLeft,
107
- offsetTop,
108
- finalRect.width,
109
- finalRect.height
110
- )
111
- };
104
+ // 缓存元素
105
+ // 缓存以避免重复查找
106
+ window.__ELECTROL__[selector] = {
107
+ element,
108
+ rect: new DOMRect(
109
+ offsetLeft,
110
+ offsetTop,
111
+ finalRect.width,
112
+ finalRect.height
113
+ )
114
+ };
112
115
 
113
- return window.__ELECTROL__[selector]
114
- }
116
+ return window.__ELECTROL__[selector]
117
+ }
115
118
 
116
- const timeout = ${timeout}
119
+ const timeout = ${timeout}
117
120
 
118
- if(timeout <= 0) {
119
- return getElement();
120
- }
121
+ // 轮询查找元素
122
+ const interval = setInterval(() => {
123
+ const element = getElement();
124
+ if (element) {
125
+ clearInterval(interval);
126
+ clearTimeout(timeoutId);
127
+ resolve(element);
128
+ }
129
+ }, 100);
130
+
131
+ // 超时处理
132
+ const timeoutId = setTimeout(() => {
133
+ clearInterval(interval);
134
+ resolve(null);
135
+ }, timeout);
121
136
 
122
- const startTime = Date.now()
123
- while (Date.now() - startTime < timeout) {
124
137
  const element = getElement();
125
- if (element) return element;
126
- }
127
- return null;
128
- })()
138
+
139
+ if (element) {
140
+ clearInterval(interval);
141
+ clearTimeout(timeoutId);
142
+ resolve(element);
143
+ }
144
+ })
129
145
  `;
130
146
  }
131
147
  /**
132
148
  * 触发元素 mouseover 事件(不移动真实鼠标)。
133
149
  */
134
- hover() {
150
+ hover(options) {
135
151
  return this.contents.executeJavaScript(`
136
- (function() {
137
- const target = ${this._getElement()};
152
+ (async () => {
153
+ const target = await ${this._getElement(options?.timeout)};
138
154
  if (!target) return null;
139
155
 
140
156
  const event = new MouseEvent('mouseover', {
@@ -197,10 +213,10 @@ var ElementHandle = class {
197
213
  /**
198
214
  * 将复选框或单选按钮设为选中,并派发 change 事件。
199
215
  */
200
- check() {
216
+ check(options) {
201
217
  return this.contents.executeJavaScript(`
202
- (function() {
203
- const target = ${this._getElement()};
218
+ (async () => {
219
+ const target = await ${this._getElement(options?.timeout)};
204
220
  if (!target) return null;
205
221
 
206
222
  // 验证输入框是否是复选框或单选按钮
@@ -226,8 +242,8 @@ var ElementHandle = class {
226
242
  let interval;
227
243
  if (typeof options?.interval === "number") interval = options?.interval;
228
244
  if (!options?.append) await this.contents.executeJavaScript(`
229
- (function() {
230
- const target = ${this._getElement(options?.timeout)};
245
+ (async () => {
246
+ const target = await ${this._getElement(options?.timeout)};
231
247
  if (target && target.element) {
232
248
  if (target.element.tagName !== 'INPUT' && target.element.tagName !== 'TEXTAREA' && !target.element.isContentEditable) return null;
233
249
  target.element.value = "";
@@ -268,8 +284,8 @@ var ElementHandle = class {
268
284
  */
269
285
  async focus(options) {
270
286
  return this.contents.executeJavaScript(`
271
- (function() {
272
- const target = ${this._getElement(options?.timeout)};
287
+ (async () => {
288
+ const target = await ${this._getElement(options?.timeout)};
273
289
  if (!target) return null;
274
290
 
275
291
  // 验证输入框是否是文本类型
@@ -283,18 +299,21 @@ var ElementHandle = class {
283
299
  * 检查元素是否存在(支持超时轮询)。
284
300
  */
285
301
  async exist(timeout) {
286
- return await this.contents.executeJavaScript(`!!${this._getElement(timeout)}`);
302
+ return await this.contents.executeJavaScript(`
303
+ (async () => {
304
+ const target = await ${this._getElement(timeout)};
305
+ return !!target;
306
+ })()
307
+ `);
287
308
  }
288
309
  /**
289
310
  * 获取元素的位置信息。
290
311
  * - 返回扩展的 DOMRect 信息并包含元素中心点坐标。
291
312
  */
292
313
  getBoundingClientRect(timeout = 0) {
293
- if (this.isNotFound) return Promise.resolve(null);
294
314
  return this.contents.executeJavaScript(`
295
- (function() {
296
- const target = ${this._getElement(timeout)};
297
-
315
+ (async () => {
316
+ const target = await ${this._getElement(timeout)};
298
317
  if (!target) return null;
299
318
 
300
319
  const rect = target.rect;
@@ -318,10 +337,10 @@ var ElementHandle = class {
318
337
  * @param value 选项的值
319
338
  * @returns
320
339
  */
321
- async selectOption(value) {
340
+ async selectOption(value, options) {
322
341
  return this.contents.executeJavaScript(`
323
- (function() {
324
- const target = ${this._getElement()};
342
+ (async () => {
343
+ const target = await ${this._getElement(options?.timeout)};
325
344
  if (!target) return null;
326
345
 
327
346
  // 验证元素是否为选择框
@@ -335,10 +354,10 @@ var ElementHandle = class {
335
354
  /**
336
355
  * 获取元素的 HTML 内容。
337
356
  */
338
- async innerHTML() {
357
+ async innerHTML(options) {
339
358
  return this.contents.executeJavaScript(`
340
- (function() {
341
- const target = ${this._getElement()};
359
+ (async () => {
360
+ const target = await ${this._getElement(options?.timeout)};
342
361
  if (!target) return null;
343
362
 
344
363
  return target.element.innerHTML;
@@ -348,10 +367,10 @@ var ElementHandle = class {
348
367
  /**
349
368
  * 获取元素的文本内容。
350
369
  */
351
- async innerText() {
370
+ async innerText(options) {
352
371
  return this.contents.executeJavaScript(`
353
- (function() {
354
- const target = ${this._getElement()};
372
+ (async () => {
373
+ const target = await ${this._getElement(options?.timeout)};
355
374
  if (!target) return null;
356
375
 
357
376
  return target.element.innerText;
@@ -362,10 +381,10 @@ var ElementHandle = class {
362
381
  * 获取元素的文本内容。
363
382
  * @returns 元素的文本内容
364
383
  */
365
- async textContent() {
384
+ async textContent(options) {
366
385
  return this.contents.executeJavaScript(`
367
- (function() {
368
- const target = ${this._getElement()};
386
+ (async () => {
387
+ const target = await ${this._getElement(options?.timeout)};
369
388
  if (!target) return null;
370
389
 
371
390
  return target.element.textContent;
@@ -376,10 +395,10 @@ var ElementHandle = class {
376
395
  * 获取元素的输入值。
377
396
  * @returns 元素的输入值
378
397
  */
379
- async inputValue() {
398
+ async inputValue(options) {
380
399
  return this.contents.executeJavaScript(`
381
- (function() {
382
- const target = ${this._getElement()};
400
+ (async () => {
401
+ const target = await ${this._getElement(options?.timeout)};
383
402
  if (!target) return null;
384
403
 
385
404
  // 验证输入框是否是文本类型
@@ -393,10 +412,10 @@ var ElementHandle = class {
393
412
  * 获取元素的选中状态。
394
413
  * @returns 元素的选中状态
395
414
  */
396
- async isChecked() {
415
+ async isChecked(options) {
397
416
  return this.contents.executeJavaScript(`
398
- (function() {
399
- const target = ${this._getElement()};
417
+ (async () => {
418
+ const target = await ${this._getElement(options?.timeout)};
400
419
  if (!target) return null;
401
420
 
402
421
  // 验证输入框是否是复选框或单选框
@@ -410,10 +429,10 @@ var ElementHandle = class {
410
429
  * 获取元素的禁用状态。
411
430
  * @returns 元素的禁用状态
412
431
  */
413
- async isDisabled() {
432
+ async isDisabled(options) {
414
433
  return this.contents.executeJavaScript(`
415
- (function() {
416
- const target = ${this._getElement()};
434
+ (async () => {
435
+ const target = await ${this._getElement(options?.timeout)};
417
436
  if (!target) return null;
418
437
 
419
438
  return target.element.disabled;
@@ -424,10 +443,10 @@ var ElementHandle = class {
424
443
  * 获取元素的可见状态。
425
444
  * @returns 元素的可见状态
426
445
  */
427
- async isVisible() {
446
+ async isVisible(options) {
428
447
  return this.contents.executeJavaScript(`
429
- (function() {
430
- const target = ${this._getElement()};
448
+ (async () => {
449
+ const target = await ${this._getElement(options?.timeout)};
431
450
  if (!target) return null;
432
451
 
433
452
  return target.element.offsetParent !== null;
@@ -438,10 +457,10 @@ var ElementHandle = class {
438
457
  * 获取元素的可用状态。
439
458
  * @returns 元素的可用状态
440
459
  */
441
- async isEnabled() {
460
+ async isEnabled(options) {
442
461
  return this.contents.executeJavaScript(`
443
- (function() {
444
- const target = ${this._getElement()};
462
+ (async () => {
463
+ const target = await ${this._getElement(options?.timeout)};
445
464
  if (!target) return null;
446
465
 
447
466
  return !target.element.disabled;
@@ -452,10 +471,10 @@ var ElementHandle = class {
452
471
  * 获取元素的可编辑状态。
453
472
  * @returns 元素的可编辑状态
454
473
  */
455
- async isEditable() {
474
+ async isEditable(options) {
456
475
  return this.contents.executeJavaScript(`
457
- (function() {
458
- const target = ${this._getElement()};
476
+ (async () => {
477
+ const target = await ${this._getElement(options?.timeout)};
459
478
  if (!target) return null;
460
479
 
461
480
  return target.element.isContentEditable;
@@ -466,10 +485,10 @@ var ElementHandle = class {
466
485
  * 获取元素的隐藏状态。
467
486
  * @returns 元素的隐藏状态
468
487
  */
469
- async isHidden() {
488
+ async isHidden(options) {
470
489
  return this.contents.executeJavaScript(`
471
- (function() {
472
- const target = ${this._getElement()};
490
+ (async () => {
491
+ const target = await ${this._getElement(options?.timeout)};
473
492
  if (!target) return null;
474
493
 
475
494
  return target.element.offsetParent === null;
@@ -481,8 +500,8 @@ var ElementHandle = class {
481
500
  */
482
501
  async selectText(options) {
483
502
  return this.contents.executeJavaScript(`
484
- (function() {
485
- const target = ${this._getElement(options.timeout)};
503
+ (async () => {
504
+ const target = await ${this._getElement(options.timeout)};
486
505
  if (!target) return null;
487
506
 
488
507
  // 选中元素的文本内容
@@ -501,8 +520,8 @@ var ElementHandle = class {
501
520
  */
502
521
  async uncheck(options) {
503
522
  return this.contents.executeJavaScript(`
504
- (function() {
505
- const target = ${this._getElement(options?.timeout)};
523
+ (async () => {
524
+ const target = await ${this._getElement(options?.timeout)};
506
525
  if (!target) return null;
507
526
 
508
527
  // 验证输入框是否是复选框或单选框
@@ -614,4 +633,4 @@ var Ectrol = class {
614
633
  var src_default = Ectrol;
615
634
 
616
635
  //#endregion
617
- export { Ectrol, ElementHandle, LocalStorage, SessionStorage, src_default as default, getHumanTypingDelay, sleep };
636
+ export { Ectrol, ElementHandle, LocalStorage, SessionStorage, src_default as default, getHumanTypingDelay, randomInRange, sleep };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ectrol",
3
3
  "type": "module",
4
- "version": "0.0.4",
4
+ "version": "0.1.2",
5
5
  "description": "一个基于 Electron WebContents 的轻量级自动化助手,用于在你的应用内对网页内容进行交互(点击、输入、聚焦、键盘事件等)。",
6
6
  "author": "Cee Vee X <ceeveex@hotmail.com>",
7
7
  "license": "MIT",