ectrol 0.0.2 → 0.0.4

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
@@ -88,9 +88,16 @@ declare class ElementHandle {
88
88
  * 填充输入框内容。
89
89
  * - 适用于 `<input>`、`<textarea>` 或 `contenteditable` 元素。
90
90
  * - 会触发 `input` 事件(冒泡)。
91
+ * - 支持自定义输入间隔, 'auto' 表示模拟自然输入节奏
92
+ * @example
93
+ * ```
94
+ * await element.fill('Hello, World!', { interval: 100 });
95
+ * ```
91
96
  */
92
97
  fill(value: string, options?: {
93
98
  timeout?: number;
99
+ interval?: number | 'auto';
100
+ append?: boolean;
94
101
  }): Promise<void>;
95
102
  /**
96
103
  * 在元素上发送按键事件。
@@ -119,6 +126,74 @@ declare class ElementHandle {
119
126
  * - 返回扩展的 DOMRect 信息并包含元素中心点坐标。
120
127
  */
121
128
  getBoundingClientRect(timeout?: number): Promise<IBoundingClientRect | null>;
129
+ /**
130
+ * 选择下拉框选项。
131
+ * @param value 选项的值
132
+ * @returns
133
+ */
134
+ selectOption(value: string): Promise<any>;
135
+ /**
136
+ * 获取元素的 HTML 内容。
137
+ */
138
+ innerHTML(): Promise<any>;
139
+ /**
140
+ * 获取元素的文本内容。
141
+ */
142
+ innerText(): Promise<any>;
143
+ /**
144
+ * 获取元素的文本内容。
145
+ * @returns 元素的文本内容
146
+ */
147
+ textContent(): Promise<any>;
148
+ /**
149
+ * 获取元素的输入值。
150
+ * @returns 元素的输入值
151
+ */
152
+ inputValue(): Promise<any>;
153
+ /**
154
+ * 获取元素的选中状态。
155
+ * @returns 元素的选中状态
156
+ */
157
+ isChecked(): Promise<any>;
158
+ /**
159
+ * 获取元素的禁用状态。
160
+ * @returns 元素的禁用状态
161
+ */
162
+ isDisabled(): Promise<any>;
163
+ /**
164
+ * 获取元素的可见状态。
165
+ * @returns 元素的可见状态
166
+ */
167
+ isVisible(): Promise<any>;
168
+ /**
169
+ * 获取元素的可用状态。
170
+ * @returns 元素的可用状态
171
+ */
172
+ isEnabled(): Promise<any>;
173
+ /**
174
+ * 获取元素的可编辑状态。
175
+ * @returns 元素的可编辑状态
176
+ */
177
+ isEditable(): Promise<any>;
178
+ /**
179
+ * 获取元素的隐藏状态。
180
+ * @returns 元素的隐藏状态
181
+ */
182
+ isHidden(): Promise<any>;
183
+ /**
184
+ * 选中元素的文本内容。
185
+ */
186
+ selectText(options: {
187
+ timeout?: number;
188
+ }): Promise<any>;
189
+ /**
190
+ * 取消选中复选框或单选框。
191
+ * @param options 选项
192
+ * @returns
193
+ */
194
+ uncheck(options?: {
195
+ timeout?: number;
196
+ }): Promise<any>;
122
197
  }
123
198
  //#endregion
124
199
  //#region src/localStorage.d.ts
@@ -169,6 +244,10 @@ declare class SessionStorage {
169
244
  removeItem(key: string): Promise<void>;
170
245
  }
171
246
  //#endregion
247
+ //#region src/utils.d.ts
248
+ declare function getHumanTypingDelay(index: number, text: string): number;
249
+ declare function sleep(ms: number): Promise<unknown>;
250
+ //#endregion
172
251
  //#region src/index.d.ts
173
252
  /**
174
253
  * Ectrol
@@ -193,4 +272,4 @@ declare class Ectrol {
193
272
  $(selector: string): ElementHandle;
194
273
  }
195
274
  //#endregion
196
- export { Ectrol, Ectrol as default, ElementHandle, IBoundingClientRect, ISize, IVector2D, LocalStorage, SessionStorage };
275
+ export { Ectrol, Ectrol as default, ElementHandle, IBoundingClientRect, ISize, IVector2D, LocalStorage, SessionStorage, getHumanTypingDelay, sleep };
package/dist/index.mjs CHANGED
@@ -1,3 +1,20 @@
1
+ //#region src/utils.ts
2
+ function getHumanTypingDelay(index, text) {
3
+ const char = text[index];
4
+ let delay = 90 + Math.random() * 60;
5
+ const progress = index / text.length;
6
+ if (progress < .15) delay *= 1.3;
7
+ 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;
11
+ return Math.max(40, Math.round(delay));
12
+ }
13
+ async function sleep(ms) {
14
+ return new Promise((resolve) => setTimeout(resolve, ms));
15
+ }
16
+
17
+ //#endregion
1
18
  //#region src/element.ts
2
19
  /**
3
20
  * ElementHandle
@@ -147,7 +164,7 @@ var ElementHandle = class {
147
164
  button: options?.button || "left",
148
165
  clickCount: 1
149
166
  });
150
- await new Promise((resolve) => setTimeout(resolve, options?.delay || 10));
167
+ await sleep(options?.delay || 10);
151
168
  this.contents.sendInputEvent({
152
169
  type: "mouseUp",
153
170
  x: rect.centerX,
@@ -198,22 +215,32 @@ var ElementHandle = class {
198
215
  * 填充输入框内容。
199
216
  * - 适用于 `<input>`、`<textarea>` 或 `contenteditable` 元素。
200
217
  * - 会触发 `input` 事件(冒泡)。
218
+ * - 支持自定义输入间隔, 'auto' 表示模拟自然输入节奏
219
+ * @example
220
+ * ```
221
+ * await element.fill('Hello, World!', { interval: 100 });
222
+ * ```
201
223
  */
202
224
  async fill(value, options) {
203
- await this.contents.executeJavaScript(`
225
+ await this.focus({ timeout: options?.timeout });
226
+ let interval;
227
+ if (typeof options?.interval === "number") interval = options?.interval;
228
+ if (!options?.append) await this.contents.executeJavaScript(`
204
229
  (function() {
205
230
  const target = ${this._getElement(options?.timeout)};
206
- if (!target) return null;
207
-
208
- // 验证输入框是否是文本类型
209
- if (target.element.tagName !== 'INPUT' && target.element.tagName !== 'TEXTAREA' && !target.element.isContentEditable) return null;
210
-
211
- target.element.focus();
212
-
213
- target.element.value = ${JSON.stringify(value)};
214
- target.element.dispatchEvent(new Event('input', { bubbles: true }));
231
+ if (target && target.element) {
232
+ if (target.element.tagName !== 'INPUT' && target.element.tagName !== 'TEXTAREA' && !target.element.isContentEditable) return null;
233
+ target.element.value = "";
234
+ }
215
235
  })()
216
236
  `);
237
+ for (let index = 0; index < value.length; index++) {
238
+ await sleep(interval || getHumanTypingDelay(index, value));
239
+ this.contents.sendInputEvent({
240
+ type: "char",
241
+ keyCode: value[index]
242
+ });
243
+ }
217
244
  }
218
245
  /**
219
246
  * 在元素上发送按键事件。
@@ -230,7 +257,7 @@ var ElementHandle = class {
230
257
  type: "keyDown",
231
258
  keyCode: key$1
232
259
  });
233
- await new Promise((resolve) => setTimeout(resolve, options?.delay || 10));
260
+ await sleep(options?.delay || 10);
234
261
  for (const key$1 of keys) this.contents.sendInputEvent({
235
262
  type: "keyUp",
236
263
  keyCode: key$1
@@ -286,6 +313,206 @@ var ElementHandle = class {
286
313
  })()
287
314
  `);
288
315
  }
316
+ /**
317
+ * 选择下拉框选项。
318
+ * @param value 选项的值
319
+ * @returns
320
+ */
321
+ async selectOption(value) {
322
+ return this.contents.executeJavaScript(`
323
+ (function() {
324
+ const target = ${this._getElement()};
325
+ if (!target) return null;
326
+
327
+ // 验证元素是否为选择框
328
+ if (target.element.tagName !== 'SELECT') return null;
329
+
330
+ target.element.value = ${JSON.stringify(value)};
331
+ target.element.dispatchEvent(new Event('change', { bubbles: true }));
332
+ })()
333
+ `);
334
+ }
335
+ /**
336
+ * 获取元素的 HTML 内容。
337
+ */
338
+ async innerHTML() {
339
+ return this.contents.executeJavaScript(`
340
+ (function() {
341
+ const target = ${this._getElement()};
342
+ if (!target) return null;
343
+
344
+ return target.element.innerHTML;
345
+ })()
346
+ `);
347
+ }
348
+ /**
349
+ * 获取元素的文本内容。
350
+ */
351
+ async innerText() {
352
+ return this.contents.executeJavaScript(`
353
+ (function() {
354
+ const target = ${this._getElement()};
355
+ if (!target) return null;
356
+
357
+ return target.element.innerText;
358
+ })()
359
+ `);
360
+ }
361
+ /**
362
+ * 获取元素的文本内容。
363
+ * @returns 元素的文本内容
364
+ */
365
+ async textContent() {
366
+ return this.contents.executeJavaScript(`
367
+ (function() {
368
+ const target = ${this._getElement()};
369
+ if (!target) return null;
370
+
371
+ return target.element.textContent;
372
+ })()
373
+ `);
374
+ }
375
+ /**
376
+ * 获取元素的输入值。
377
+ * @returns 元素的输入值
378
+ */
379
+ async inputValue() {
380
+ return this.contents.executeJavaScript(`
381
+ (function() {
382
+ const target = ${this._getElement()};
383
+ if (!target) return null;
384
+
385
+ // 验证输入框是否是文本类型
386
+ if (target.element.tagName !== 'INPUT' && target.element.tagName !== 'TEXTAREA' && !target.element.isContentEditable) return null;
387
+
388
+ return target.element.value;
389
+ })()
390
+ `);
391
+ }
392
+ /**
393
+ * 获取元素的选中状态。
394
+ * @returns 元素的选中状态
395
+ */
396
+ async isChecked() {
397
+ return this.contents.executeJavaScript(`
398
+ (function() {
399
+ const target = ${this._getElement()};
400
+ if (!target) return null;
401
+
402
+ // 验证输入框是否是复选框或单选框
403
+ if (target.element.tagName !== 'INPUT' || target.element.type !== 'checkbox' && target.element.type !== 'radio') return null;
404
+
405
+ return target.element.checked;
406
+ })()
407
+ `);
408
+ }
409
+ /**
410
+ * 获取元素的禁用状态。
411
+ * @returns 元素的禁用状态
412
+ */
413
+ async isDisabled() {
414
+ return this.contents.executeJavaScript(`
415
+ (function() {
416
+ const target = ${this._getElement()};
417
+ if (!target) return null;
418
+
419
+ return target.element.disabled;
420
+ })()
421
+ `);
422
+ }
423
+ /**
424
+ * 获取元素的可见状态。
425
+ * @returns 元素的可见状态
426
+ */
427
+ async isVisible() {
428
+ return this.contents.executeJavaScript(`
429
+ (function() {
430
+ const target = ${this._getElement()};
431
+ if (!target) return null;
432
+
433
+ return target.element.offsetParent !== null;
434
+ })()
435
+ `);
436
+ }
437
+ /**
438
+ * 获取元素的可用状态。
439
+ * @returns 元素的可用状态
440
+ */
441
+ async isEnabled() {
442
+ return this.contents.executeJavaScript(`
443
+ (function() {
444
+ const target = ${this._getElement()};
445
+ if (!target) return null;
446
+
447
+ return !target.element.disabled;
448
+ })()
449
+ `);
450
+ }
451
+ /**
452
+ * 获取元素的可编辑状态。
453
+ * @returns 元素的可编辑状态
454
+ */
455
+ async isEditable() {
456
+ return this.contents.executeJavaScript(`
457
+ (function() {
458
+ const target = ${this._getElement()};
459
+ if (!target) return null;
460
+
461
+ return target.element.isContentEditable;
462
+ })()
463
+ `);
464
+ }
465
+ /**
466
+ * 获取元素的隐藏状态。
467
+ * @returns 元素的隐藏状态
468
+ */
469
+ async isHidden() {
470
+ return this.contents.executeJavaScript(`
471
+ (function() {
472
+ const target = ${this._getElement()};
473
+ if (!target) return null;
474
+
475
+ return target.element.offsetParent === null;
476
+ })()
477
+ `);
478
+ }
479
+ /**
480
+ * 选中元素的文本内容。
481
+ */
482
+ async selectText(options) {
483
+ return this.contents.executeJavaScript(`
484
+ (function() {
485
+ const target = ${this._getElement(options.timeout)};
486
+ if (!target) return null;
487
+
488
+ // 选中元素的文本内容
489
+ const selection = window.getSelection();
490
+ const range = document.createRange();
491
+ range.selectNodeContents(target.element);
492
+ selection.removeAllRanges();
493
+ selection.addRange(range);
494
+ })()
495
+ `);
496
+ }
497
+ /**
498
+ * 取消选中复选框或单选框。
499
+ * @param options 选项
500
+ * @returns
501
+ */
502
+ async uncheck(options) {
503
+ return this.contents.executeJavaScript(`
504
+ (function() {
505
+ const target = ${this._getElement(options?.timeout)};
506
+ if (!target) return null;
507
+
508
+ // 验证输入框是否是复选框或单选框
509
+ if (target.element.tagName !== 'INPUT' || target.element.type !== 'checkbox' && target.element.type !== 'radio') return null;
510
+
511
+ target.element.checked = false;
512
+ target.element.dispatchEvent(new Event('change', { bubbles: true }));
513
+ })()
514
+ `);
515
+ }
289
516
  };
290
517
  var element_default = ElementHandle;
291
518
 
@@ -387,4 +614,4 @@ var Ectrol = class {
387
614
  var src_default = Ectrol;
388
615
 
389
616
  //#endregion
390
- export { Ectrol, ElementHandle, LocalStorage, SessionStorage, src_default as default };
617
+ export { Ectrol, ElementHandle, LocalStorage, SessionStorage, src_default as default, getHumanTypingDelay, sleep };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ectrol",
3
3
  "type": "module",
4
- "version": "0.0.2",
4
+ "version": "0.0.4",
5
5
  "description": "一个基于 Electron WebContents 的轻量级自动化助手,用于在你的应用内对网页内容进行交互(点击、输入、聚焦、键盘事件等)。",
6
6
  "author": "Cee Vee X <ceeveex@hotmail.com>",
7
7
  "license": "MIT",