ectrol 0.0.4 → 0.1.0

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}";
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
+ }
43
53
 
44
- if (!selector) return null;
54
+ // 分层选择器,支持 iframe 穿越
55
+ const parts = selector.split('|>').map(s => s.trim());
56
+ if (!parts.length) return null;
45
57
 
46
- window.__ELECTROL__ = window.__ELECTROL__ || {};
47
- if (window.__ELECTROL__[selector]) {
48
- return window.__ELECTROL__[selector];
49
- }
58
+ let currentDocument = document;
59
+ let offsetLeft = 0;
60
+ let offsetTop = 0;
61
+ let element = null;
50
62
 
51
- // 分层选择器,支持 iframe 穿越
52
- const parts = selector.split('|>').map(s => s.trim());
53
- if (!parts.length) return null;
63
+ for (let i = 0; i < parts.length; i++) {
64
+ if (!currentDocument) return null;
54
65
 
55
- let currentDocument = document;
56
- let offsetLeft = 0;
57
- let offsetTop = 0;
58
- let element = null;
66
+ const part = parts[i];
67
+ // 在当前文档查找该层选择器
68
+ const el = currentDocument.querySelector(part);
59
69
 
60
- for (let i = 0; i < parts.length; i++) {
61
- if (!currentDocument) return null;
70
+ if (!el) {
71
+ console.warn('未找到元素:', part);
72
+ return null;
73
+ }
62
74
 
63
- const part = parts[i];
64
- // 在当前文档查找该层选择器
65
- const el = currentDocument.querySelector(part);
75
+ const rect = el.getBoundingClientRect();
66
76
 
67
- if (!el) {
68
- console.warn('未找到元素:', part);
69
- return null;
70
- }
77
+ // 累加当前层级偏移
78
+ offsetLeft += rect.left;
79
+ offsetTop += rect.top;
71
80
 
72
- const rect = el.getBoundingClientRect();
81
+ element = el;
73
82
 
74
- // 累加当前层级偏移
75
- offsetLeft += rect.left;
76
- offsetTop += rect.top;
83
+ const isLast = i === parts.length - 1;
84
+ if (!isLast) {
85
+ if (el.tagName !== 'IFRAME') {
86
+ console.warn('非 iframe 元素却尝试进入下一层:', part);
87
+ return null;
88
+ }
77
89
 
78
- element = el;
90
+ const nextDoc = el.contentDocument || el.contentWindow?.document;
91
+ if (!nextDoc) {
92
+ console.warn('无法访问 iframe.contentDocument(可能跨域)');
93
+ return null;
94
+ }
79
95
 
80
- const isLast = i === parts.length - 1;
81
- if (!isLast) {
82
- if (el.tagName !== 'IFRAME') {
83
- console.warn('非 iframe 元素却尝试进入下一层:', part);
84
- return null;
96
+ currentDocument = nextDoc;
85
97
  }
86
-
87
- const nextDoc = el.contentDocument || el.contentWindow?.document;
88
- if (!nextDoc) {
89
- console.warn('无法访问 iframe.contentDocument(可能跨域)');
90
- return null;
91
- }
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
  // 验证输入框是否是文本类型
@@ -290,11 +306,9 @@ var ElementHandle = class {
290
306
  * - 返回扩展的 DOMRect 信息并包含元素中心点坐标。
291
307
  */
292
308
  getBoundingClientRect(timeout = 0) {
293
- if (this.isNotFound) return Promise.resolve(null);
294
309
  return this.contents.executeJavaScript(`
295
- (function() {
296
- const target = ${this._getElement(timeout)};
297
-
310
+ (async () => {
311
+ const target = await ${this._getElement(timeout)};
298
312
  if (!target) return null;
299
313
 
300
314
  const rect = target.rect;
@@ -318,10 +332,10 @@ var ElementHandle = class {
318
332
  * @param value 选项的值
319
333
  * @returns
320
334
  */
321
- async selectOption(value) {
335
+ async selectOption(value, options) {
322
336
  return this.contents.executeJavaScript(`
323
- (function() {
324
- const target = ${this._getElement()};
337
+ (async () => {
338
+ const target = await ${this._getElement(options?.timeout)};
325
339
  if (!target) return null;
326
340
 
327
341
  // 验证元素是否为选择框
@@ -335,10 +349,10 @@ var ElementHandle = class {
335
349
  /**
336
350
  * 获取元素的 HTML 内容。
337
351
  */
338
- async innerHTML() {
352
+ async innerHTML(options) {
339
353
  return this.contents.executeJavaScript(`
340
- (function() {
341
- const target = ${this._getElement()};
354
+ (async () => {
355
+ const target = await ${this._getElement(options?.timeout)};
342
356
  if (!target) return null;
343
357
 
344
358
  return target.element.innerHTML;
@@ -348,10 +362,10 @@ var ElementHandle = class {
348
362
  /**
349
363
  * 获取元素的文本内容。
350
364
  */
351
- async innerText() {
365
+ async innerText(options) {
352
366
  return this.contents.executeJavaScript(`
353
- (function() {
354
- const target = ${this._getElement()};
367
+ (async () => {
368
+ const target = await ${this._getElement(options?.timeout)};
355
369
  if (!target) return null;
356
370
 
357
371
  return target.element.innerText;
@@ -362,10 +376,10 @@ var ElementHandle = class {
362
376
  * 获取元素的文本内容。
363
377
  * @returns 元素的文本内容
364
378
  */
365
- async textContent() {
379
+ async textContent(options) {
366
380
  return this.contents.executeJavaScript(`
367
- (function() {
368
- const target = ${this._getElement()};
381
+ (async () => {
382
+ const target = await ${this._getElement(options?.timeout)};
369
383
  if (!target) return null;
370
384
 
371
385
  return target.element.textContent;
@@ -376,10 +390,10 @@ var ElementHandle = class {
376
390
  * 获取元素的输入值。
377
391
  * @returns 元素的输入值
378
392
  */
379
- async inputValue() {
393
+ async inputValue(options) {
380
394
  return this.contents.executeJavaScript(`
381
- (function() {
382
- const target = ${this._getElement()};
395
+ (async () => {
396
+ const target = await ${this._getElement(options?.timeout)};
383
397
  if (!target) return null;
384
398
 
385
399
  // 验证输入框是否是文本类型
@@ -393,10 +407,10 @@ var ElementHandle = class {
393
407
  * 获取元素的选中状态。
394
408
  * @returns 元素的选中状态
395
409
  */
396
- async isChecked() {
410
+ async isChecked(options) {
397
411
  return this.contents.executeJavaScript(`
398
- (function() {
399
- const target = ${this._getElement()};
412
+ (async () => {
413
+ const target = await ${this._getElement(options?.timeout)};
400
414
  if (!target) return null;
401
415
 
402
416
  // 验证输入框是否是复选框或单选框
@@ -410,10 +424,10 @@ var ElementHandle = class {
410
424
  * 获取元素的禁用状态。
411
425
  * @returns 元素的禁用状态
412
426
  */
413
- async isDisabled() {
427
+ async isDisabled(options) {
414
428
  return this.contents.executeJavaScript(`
415
- (function() {
416
- const target = ${this._getElement()};
429
+ (async () => {
430
+ const target = await ${this._getElement(options?.timeout)};
417
431
  if (!target) return null;
418
432
 
419
433
  return target.element.disabled;
@@ -424,10 +438,10 @@ var ElementHandle = class {
424
438
  * 获取元素的可见状态。
425
439
  * @returns 元素的可见状态
426
440
  */
427
- async isVisible() {
441
+ async isVisible(options) {
428
442
  return this.contents.executeJavaScript(`
429
- (function() {
430
- const target = ${this._getElement()};
443
+ (async () => {
444
+ const target = await ${this._getElement(options?.timeout)};
431
445
  if (!target) return null;
432
446
 
433
447
  return target.element.offsetParent !== null;
@@ -438,10 +452,10 @@ var ElementHandle = class {
438
452
  * 获取元素的可用状态。
439
453
  * @returns 元素的可用状态
440
454
  */
441
- async isEnabled() {
455
+ async isEnabled(options) {
442
456
  return this.contents.executeJavaScript(`
443
- (function() {
444
- const target = ${this._getElement()};
457
+ (async () => {
458
+ const target = await ${this._getElement(options?.timeout)};
445
459
  if (!target) return null;
446
460
 
447
461
  return !target.element.disabled;
@@ -452,10 +466,10 @@ var ElementHandle = class {
452
466
  * 获取元素的可编辑状态。
453
467
  * @returns 元素的可编辑状态
454
468
  */
455
- async isEditable() {
469
+ async isEditable(options) {
456
470
  return this.contents.executeJavaScript(`
457
- (function() {
458
- const target = ${this._getElement()};
471
+ (async () => {
472
+ const target = await ${this._getElement(options?.timeout)};
459
473
  if (!target) return null;
460
474
 
461
475
  return target.element.isContentEditable;
@@ -466,10 +480,10 @@ var ElementHandle = class {
466
480
  * 获取元素的隐藏状态。
467
481
  * @returns 元素的隐藏状态
468
482
  */
469
- async isHidden() {
483
+ async isHidden(options) {
470
484
  return this.contents.executeJavaScript(`
471
- (function() {
472
- const target = ${this._getElement()};
485
+ (async () => {
486
+ const target = await ${this._getElement(options?.timeout)};
473
487
  if (!target) return null;
474
488
 
475
489
  return target.element.offsetParent === null;
@@ -481,8 +495,8 @@ var ElementHandle = class {
481
495
  */
482
496
  async selectText(options) {
483
497
  return this.contents.executeJavaScript(`
484
- (function() {
485
- const target = ${this._getElement(options.timeout)};
498
+ (async () => {
499
+ const target = await ${this._getElement(options.timeout)};
486
500
  if (!target) return null;
487
501
 
488
502
  // 选中元素的文本内容
@@ -501,8 +515,8 @@ var ElementHandle = class {
501
515
  */
502
516
  async uncheck(options) {
503
517
  return this.contents.executeJavaScript(`
504
- (function() {
505
- const target = ${this._getElement(options?.timeout)};
518
+ (async () => {
519
+ const target = await ${this._getElement(options?.timeout)};
506
520
  if (!target) return null;
507
521
 
508
522
  // 验证输入框是否是复选框或单选框
@@ -614,4 +628,4 @@ var Ectrol = class {
614
628
  var src_default = Ectrol;
615
629
 
616
630
  //#endregion
617
- export { Ectrol, ElementHandle, LocalStorage, SessionStorage, src_default as default, getHumanTypingDelay, sleep };
631
+ 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.0",
5
5
  "description": "一个基于 Electron WebContents 的轻量级自动化助手,用于在你的应用内对网页内容进行交互(点击、输入、聚焦、键盘事件等)。",
6
6
  "author": "Cee Vee X <ceeveex@hotmail.com>",
7
7
  "license": "MIT",