@tocha688/browser 0.0.1 → 1.0.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.
@@ -1,308 +1,308 @@
1
- // const { Bezier } = require('bezier-js');
2
- // const { sleep } = require('timers/promises');
3
- import { sleep } from '@tocha688/utils';
4
- import { rInt } from '@tocha688/utils/random';
5
- import { Bezier } from 'bezier-js';
6
- import type { Locator, Page } from 'patchright-core';
7
-
8
- /** 坐标点定义 */
9
- type Point = {
10
- /** X坐标 */
11
- x: number;
12
- /** Y坐标 */
13
- y: number;
14
- };
15
-
16
- /** 范围定义,用于指定元素内的随机点范围 */
17
- type Range = {
18
- /** 最小边界比例 (0-1) */
19
- min?: number;
20
- /** 最大边界比例 (0-1) */
21
- max?: number;
22
- };
23
-
24
- /** 鼠标移动配置 */
25
- type Config = {
26
- /** 最小移动步数 */
27
- minSteps: number;
28
- /** 最大移动步数 */
29
- maxSteps: number;
30
- };
31
-
32
- /** 延迟和移动选项 */
33
- type DelayOptions = {
34
- /** 移动步数,覆盖默认配置 */
35
- steps?: number;
36
- /** 移动速度 (0-1),越大越快 */
37
- speed?: number;
38
- /** 随机性 (0-1),越大路径越随机 */
39
- randomness?: number;
40
- };
41
-
42
- /** 移动到元素的选项,结合了延迟选项和范围选项 */
43
- type MoveOptions = DelayOptions & Range;
44
-
45
- /** 鼠标按键类型 */
46
- type MouseButton = 'left' | 'right' | 'middle';
47
-
48
- /** 点击选项 */
49
- export type ClickOptions = DelayOptions & {
50
- /** 是否执行双击 */
51
- doubleClick?: boolean;
52
- /** 鼠标按键 */
53
- button?: MouseButton;
54
- };
55
-
56
- /** 点击元素的选项,结合了点击选项和范围选项 */
57
- type ClickElementOptions = ClickOptions & Range;
58
-
59
- /**
60
- * 模拟真人鼠标移动和点击的类
61
- * 使用贝塞尔曲线生成自然的鼠标轨迹,避免被检测为机器人
62
- */
63
- export class HumanMouse {
64
- /** Playwright页面实例 */
65
- page: Page;
66
- /** 当前鼠标位置 */
67
- currentPosition: Point = { x: 0, y: 0 };
68
- /** 移动配置 */
69
- config: Config = { minSteps: 15, maxSteps: 50 };
70
-
71
- /**
72
- * 构造函数
73
- * @param page Playwright页面实例
74
- * @param config 可选的配置参数
75
- */
76
- constructor(page: Page, config: Partial<Config> = {}) {
77
- this.page = page;
78
- this.currentPosition = { x: 0, y: 0 };
79
- this.config = {
80
- minSteps: 15,
81
- maxSteps: 50,
82
- ...config
83
- };
84
- }
85
-
86
- /**
87
- * 生成随机步数
88
- * @returns 在配置范围内的随机步数
89
- */
90
- private _randomSteps(): number {
91
- return Math.floor(Math.random() *
92
- (this.config.maxSteps - this.config.minSteps)) + this.config.minSteps;
93
- }
94
-
95
- /**
96
- * 计算元素内的随机点
97
- * @param el 目标元素
98
- * @param options 范围选项
99
- * @returns 元素内的随机坐标点
100
- */
101
- private async _toPoint(el: Locator, options?: Range): Promise<Point> {
102
- const { min = 0.2, max = 0.8 } = options || {};
103
- const box = await el.boundingBox();
104
- if (!box) {
105
- throw new Error('Element has no bounding box');
106
- }
107
- return {
108
- x: box.x + box.width * (min + (Math.random() * (max - min))),
109
- y: box.y + box.height * (min + (Math.random() * (max - min)))
110
- };
111
- }
112
-
113
- /**
114
- * 模拟真人鼠标绝对移动
115
- * @param x 目标X坐标
116
- * @param y 目标Y坐标
117
- * @param options 移动选项
118
- */
119
- async moveTo(x: number, y: number, options: DelayOptions = {}): Promise<void> {
120
- const { speed = 0.5, randomness = 0.3 } = options;
121
- const start = this.currentPosition;
122
- const end = { x, y };
123
- // 生成贝塞尔曲线控制点
124
- const controlPoints = this.generateControlPoints(start, end, randomness);
125
- //@ts-ignore
126
- const curve = new Bezier(...controlPoints);
127
- const steps = options.steps || this._randomSteps();
128
- const points = curve.getLUT(steps);
129
-
130
- for (let i = 0; i < points.length; i++) {
131
- const point = points[i] as Point;
132
- const delay = this.getDynamicDelay(i, points.length, speed);
133
-
134
- await this.page.mouse.move(point.x, point.y);
135
- await sleep(delay);
136
- }
137
- this.currentPosition = end;
138
- }
139
-
140
- /**
141
- * 模拟真人鼠标移动到指定元素
142
- * @param el 目标元素
143
- * @param options 移动和范围选项
144
- */
145
- async moveToEl(el: Locator, options: MoveOptions = {}): Promise<void> {
146
- const point = await this._toPoint(el, options);
147
- await this.moveTo(point.x, point.y, options);
148
- }
149
-
150
- /**
151
- * 模拟真人鼠标相对移动
152
- * @param deltaX X轴偏移量
153
- * @param deltaY Y轴偏移量
154
- * @param options 移动和范围选项
155
- */
156
- async moveBy(deltaX: number, deltaY: number, options: MoveOptions = {}): Promise<void> {
157
- const targetX = this.currentPosition.x + deltaX;
158
- const targetY = this.currentPosition.y + deltaY;
159
- return this.moveTo(targetX, targetY, options);
160
- }
161
-
162
- async scrollBy(deltaY: number, options: DelayOptions = {}): Promise<void> {
163
- const { speed = 0.5, randomness = 0.3 } = options;
164
- const steps = options.steps || this._randomSteps();
165
- const baseStep = deltaY / steps;
166
- for (let i = 0; i < steps; i++) {
167
- const jitter = baseStep * (Math.random() - 0.5) * randomness;
168
- const stepY = baseStep + jitter;
169
- //@ts-ignore
170
- await this.page.evaluate(`(y) => { window.scrollBy(0, y); }`, stepY);
171
- const delay = this.getDynamicDelay(i, steps, speed);
172
- await sleep(delay);
173
- }
174
- }
175
-
176
- async scrollTo(y: number, options: DelayOptions = {}): Promise<void> {
177
- const current = await this.page.evaluate(`() => window.scrollY || document.documentElement.scrollTop || 0`) as number;
178
- const delta = y - current;
179
- if (Math.abs(delta) < 1) return;
180
- await this.scrollBy(delta, options);
181
- }
182
-
183
- async scrollToEl(el: Locator, options: DelayOptions = {}): Promise<void> {
184
- const viewport = this.page.viewportSize();
185
- //@ts-ignore
186
- const viewportHeight = viewport?.height ?? await this.page.evaluate(`() => window.innerHeight`) as any;
187
- const metrics = await el.evaluate(`(node) => {
188
- const rect = node.getBoundingClientRect();
189
- return { top: rect.top + window.scrollY, height: rect.height };
190
- }`) as any;
191
- const target = Math.max(0, metrics.top - viewportHeight * 0.3);
192
- await this.scrollTo(target, options);
193
- }
194
-
195
- /**
196
- * 模拟真人鼠标单击
197
- * @param x 目标X坐标
198
- * @param y 目标Y坐标
199
- * @param options 点击选项
200
- */
201
- async click(x: number, y: number, options: ClickOptions = {}): Promise<void> {
202
- const { doubleClick = false, button = 'left' } = options;
203
- await this.moveTo(x, y, {
204
- steps: options.steps,
205
- speed: options.speed,
206
- randomness: options.randomness
207
- });
208
- await this.page.mouse.down({ button });
209
- await sleep(rInt(50, 150));
210
- await this.page.mouse.up({ button });
211
-
212
- if (doubleClick) {
213
- await sleep(rInt(100, 300));
214
- await this.page.mouse.down({ button });
215
- await sleep(rInt(50, 150));
216
- await this.page.mouse.up({ button });
217
- }
218
- }
219
-
220
- /**
221
- * 模拟真人鼠标单击指定元素
222
- * @param el 目标元素
223
- * @param options 点击和范围选项
224
- */
225
- async clickEl(el: Locator, options: ClickElementOptions = {}): Promise<void> {
226
- try {
227
- const point = await this._toPoint(el, options);
228
- await this.click(point.x, point.y, options);
229
- } catch (err) {
230
- console.log('clickEl err: ', err, 'el: ', el);
231
- }
232
- }
233
-
234
- /**
235
- * 生成贝塞尔曲线控制点
236
- * @param start 起始点
237
- * @param end 终点
238
- * @param randomness 随机性程度
239
- * @returns 四个控制点组成的数组
240
- */
241
- generateControlPoints(start: Point, end: Point, randomness: number): Point[] {
242
- const distance = Math.hypot(end.x - start.x, end.y - start.y);
243
- const cp1 = {
244
- x: start.x + (end.x - start.x) * 0.3 + (Math.random() - 0.5) * distance * randomness,
245
- y: start.y + (end.y - start.y) * 0.3 + (Math.random() - 0.5) * distance * randomness
246
- };
247
- const cp2 = {
248
- x: end.x - (end.x - start.x) * 0.3 + (Math.random() - 0.5) * distance * randomness,
249
- y: end.y - (end.y - start.y) * 0.3 + (Math.random() - 0.5) * distance * randomness
250
- };
251
- return [start, cp1, cp2, end];
252
- }
253
-
254
- /**
255
- * 计算动态延迟,模拟加速减速效果
256
- * @param index 当前步骤索引
257
- * @param totalPoints 总步数
258
- * @param speed 速度参数
259
- * @returns 延迟时间(毫秒)
260
- */
261
- getDynamicDelay(index: number, totalPoints: number, speed: number): number {
262
- // 模拟加速减速效果
263
- const progress = index / totalPoints;
264
- const baseDelay = 10 + (1 - speed) * 50;
265
- return Math.max(10, baseDelay * (1 - Math.sin(progress * Math.PI)) * 0.5);
266
- }
267
-
268
- /**
269
- * 生成指定范围内的随机延迟
270
- * @param min 最小延迟(毫秒)
271
- * @param max 最大延迟(毫秒)
272
- * @returns 随机延迟时间(毫秒)
273
- */
274
- getRandomDelay(min = 20, max = 100): number {
275
- return Math.floor(Math.random() * (max - min + 1)) + min;
276
- }
277
-
278
- /**
279
- * 在页面上显示鼠标轨迹,用于调试
280
- * 会在页面上添加一个红色小点跟随鼠标移动
281
- */
282
- async showCursor(): Promise<void> {
283
- await this.page.addInitScript({
284
- content: `
285
- let dot;
286
- document.addEventListener('mousemove', (e) => {
287
- if (!dot) {
288
- dot = document.createElement('div');
289
- Object.assign(dot.style, {
290
- position: 'fixed',
291
- width: '5px',
292
- height: '5px',
293
- borderRadius: '50%',
294
- backgroundColor: 'red',
295
- pointerEvents: 'none',
296
- zIndex: 999999
297
- });
298
- document.body.appendChild(dot);
299
- }
300
- dot.style.left = e.clientX + 'px';
301
- dot.style.top = e.clientY + 'px';
302
- });
303
- `
304
- });
305
- }
306
- }
307
-
308
- export default HumanMouse;
1
+ // const { Bezier } = require('bezier-js');
2
+ // const { sleep } = require('timers/promises');
3
+ import { sleep } from '@tocha688/utils';
4
+ import { rInt } from '@tocha688/utils/random';
5
+ import { Bezier } from 'bezier-js';
6
+ import type { Locator, Page } from 'patchright-core';
7
+
8
+ /** 坐标点定义 */
9
+ type Point = {
10
+ /** X坐标 */
11
+ x: number;
12
+ /** Y坐标 */
13
+ y: number;
14
+ };
15
+
16
+ /** 范围定义,用于指定元素内的随机点范围 */
17
+ type Range = {
18
+ /** 最小边界比例 (0-1) */
19
+ min?: number;
20
+ /** 最大边界比例 (0-1) */
21
+ max?: number;
22
+ };
23
+
24
+ /** 鼠标移动配置 */
25
+ type Config = {
26
+ /** 最小移动步数 */
27
+ minSteps: number;
28
+ /** 最大移动步数 */
29
+ maxSteps: number;
30
+ };
31
+
32
+ /** 延迟和移动选项 */
33
+ type DelayOptions = {
34
+ /** 移动步数,覆盖默认配置 */
35
+ steps?: number;
36
+ /** 移动速度 (0-1),越大越快 */
37
+ speed?: number;
38
+ /** 随机性 (0-1),越大路径越随机 */
39
+ randomness?: number;
40
+ };
41
+
42
+ /** 移动到元素的选项,结合了延迟选项和范围选项 */
43
+ type MoveOptions = DelayOptions & Range;
44
+
45
+ /** 鼠标按键类型 */
46
+ type MouseButton = 'left' | 'right' | 'middle';
47
+
48
+ /** 点击选项 */
49
+ export type ClickOptions = DelayOptions & {
50
+ /** 是否执行双击 */
51
+ doubleClick?: boolean;
52
+ /** 鼠标按键 */
53
+ button?: MouseButton;
54
+ };
55
+
56
+ /** 点击元素的选项,结合了点击选项和范围选项 */
57
+ type ClickElementOptions = ClickOptions & Range;
58
+
59
+ /**
60
+ * 模拟真人鼠标移动和点击的类
61
+ * 使用贝塞尔曲线生成自然的鼠标轨迹,避免被检测为机器人
62
+ */
63
+ export class HumanMouse {
64
+ /** Playwright页面实例 */
65
+ page: Page;
66
+ /** 当前鼠标位置 */
67
+ currentPosition: Point = { x: 0, y: 0 };
68
+ /** 移动配置 */
69
+ config: Config = { minSteps: 15, maxSteps: 50 };
70
+
71
+ /**
72
+ * 构造函数
73
+ * @param page Playwright页面实例
74
+ * @param config 可选的配置参数
75
+ */
76
+ constructor(page: Page, config: Partial<Config> = {}) {
77
+ this.page = page;
78
+ this.currentPosition = { x: 0, y: 0 };
79
+ this.config = {
80
+ minSteps: 15,
81
+ maxSteps: 50,
82
+ ...config
83
+ };
84
+ }
85
+
86
+ /**
87
+ * 生成随机步数
88
+ * @returns 在配置范围内的随机步数
89
+ */
90
+ private _randomSteps(): number {
91
+ return Math.floor(Math.random() *
92
+ (this.config.maxSteps - this.config.minSteps)) + this.config.minSteps;
93
+ }
94
+
95
+ /**
96
+ * 计算元素内的随机点
97
+ * @param el 目标元素
98
+ * @param options 范围选项
99
+ * @returns 元素内的随机坐标点
100
+ */
101
+ private async _toPoint(el: Locator, options?: Range): Promise<Point> {
102
+ const { min = 0.2, max = 0.8 } = options || {};
103
+ const box = await el.boundingBox();
104
+ if (!box) {
105
+ throw new Error('Element has no bounding box');
106
+ }
107
+ return {
108
+ x: box.x + box.width * (min + (Math.random() * (max - min))),
109
+ y: box.y + box.height * (min + (Math.random() * (max - min)))
110
+ };
111
+ }
112
+
113
+ /**
114
+ * 模拟真人鼠标绝对移动
115
+ * @param x 目标X坐标
116
+ * @param y 目标Y坐标
117
+ * @param options 移动选项
118
+ */
119
+ async moveTo(x: number, y: number, options: DelayOptions = {}): Promise<void> {
120
+ const { speed = 0.5, randomness = 0.3 } = options;
121
+ const start = this.currentPosition;
122
+ const end = { x, y };
123
+ // 生成贝塞尔曲线控制点
124
+ const controlPoints = this.generateControlPoints(start, end, randomness);
125
+ //@ts-ignore
126
+ const curve = new Bezier(...controlPoints);
127
+ const steps = options.steps || this._randomSteps();
128
+ const points = curve.getLUT(steps);
129
+
130
+ for (let i = 0; i < points.length; i++) {
131
+ const point = points[i] as Point;
132
+ const delay = this.getDynamicDelay(i, points.length, speed);
133
+
134
+ await this.page.mouse.move(point.x, point.y);
135
+ await sleep(delay);
136
+ }
137
+ this.currentPosition = end;
138
+ }
139
+
140
+ /**
141
+ * 模拟真人鼠标移动到指定元素
142
+ * @param el 目标元素
143
+ * @param options 移动和范围选项
144
+ */
145
+ async moveToEl(el: Locator, options: MoveOptions = {}): Promise<void> {
146
+ const point = await this._toPoint(el, options);
147
+ await this.moveTo(point.x, point.y, options);
148
+ }
149
+
150
+ /**
151
+ * 模拟真人鼠标相对移动
152
+ * @param deltaX X轴偏移量
153
+ * @param deltaY Y轴偏移量
154
+ * @param options 移动和范围选项
155
+ */
156
+ async moveBy(deltaX: number, deltaY: number, options: MoveOptions = {}): Promise<void> {
157
+ const targetX = this.currentPosition.x + deltaX;
158
+ const targetY = this.currentPosition.y + deltaY;
159
+ return this.moveTo(targetX, targetY, options);
160
+ }
161
+
162
+ async scrollBy(deltaY: number, options: DelayOptions = {}): Promise<void> {
163
+ const { speed = 0.5, randomness = 0.3 } = options;
164
+ const steps = options.steps || this._randomSteps();
165
+ const baseStep = deltaY / steps;
166
+ for (let i = 0; i < steps; i++) {
167
+ const jitter = baseStep * (Math.random() - 0.5) * randomness;
168
+ const stepY = baseStep + jitter;
169
+ //@ts-ignore
170
+ await this.page.evaluate(`(y) => { window.scrollBy(0, y); }`, stepY);
171
+ const delay = this.getDynamicDelay(i, steps, speed);
172
+ await sleep(delay);
173
+ }
174
+ }
175
+
176
+ async scrollTo(y: number, options: DelayOptions = {}): Promise<void> {
177
+ const current = await this.page.evaluate(`() => window.scrollY || document.documentElement.scrollTop || 0`) as number;
178
+ const delta = y - current;
179
+ if (Math.abs(delta) < 1) return;
180
+ await this.scrollBy(delta, options);
181
+ }
182
+
183
+ async scrollToEl(el: Locator, options: DelayOptions = {}): Promise<void> {
184
+ const viewport = this.page.viewportSize();
185
+ //@ts-ignore
186
+ const viewportHeight = viewport?.height ?? await this.page.evaluate(`() => window.innerHeight`) as any;
187
+ const metrics = await el.evaluate(`(node) => {
188
+ const rect = node.getBoundingClientRect();
189
+ return { top: rect.top + window.scrollY, height: rect.height };
190
+ }`) as any;
191
+ const target = Math.max(0, metrics.top - viewportHeight * 0.3);
192
+ await this.scrollTo(target, options);
193
+ }
194
+
195
+ /**
196
+ * 模拟真人鼠标单击
197
+ * @param x 目标X坐标
198
+ * @param y 目标Y坐标
199
+ * @param options 点击选项
200
+ */
201
+ async click(x: number, y: number, options: ClickOptions = {}): Promise<void> {
202
+ const { doubleClick = false, button = 'left' } = options;
203
+ await this.moveTo(x, y, {
204
+ steps: options.steps,
205
+ speed: options.speed,
206
+ randomness: options.randomness
207
+ });
208
+ await this.page.mouse.down({ button });
209
+ await sleep(rInt(50, 150));
210
+ await this.page.mouse.up({ button });
211
+
212
+ if (doubleClick) {
213
+ await sleep(rInt(100, 300));
214
+ await this.page.mouse.down({ button });
215
+ await sleep(rInt(50, 150));
216
+ await this.page.mouse.up({ button });
217
+ }
218
+ }
219
+
220
+ /**
221
+ * 模拟真人鼠标单击指定元素
222
+ * @param el 目标元素
223
+ * @param options 点击和范围选项
224
+ */
225
+ async clickEl(el: Locator, options: ClickElementOptions = {}): Promise<void> {
226
+ try {
227
+ const point = await this._toPoint(el, options);
228
+ await this.click(point.x, point.y, options);
229
+ } catch (err) {
230
+ console.log('clickEl err: ', err, 'el: ', el);
231
+ }
232
+ }
233
+
234
+ /**
235
+ * 生成贝塞尔曲线控制点
236
+ * @param start 起始点
237
+ * @param end 终点
238
+ * @param randomness 随机性程度
239
+ * @returns 四个控制点组成的数组
240
+ */
241
+ generateControlPoints(start: Point, end: Point, randomness: number): Point[] {
242
+ const distance = Math.hypot(end.x - start.x, end.y - start.y);
243
+ const cp1 = {
244
+ x: start.x + (end.x - start.x) * 0.3 + (Math.random() - 0.5) * distance * randomness,
245
+ y: start.y + (end.y - start.y) * 0.3 + (Math.random() - 0.5) * distance * randomness
246
+ };
247
+ const cp2 = {
248
+ x: end.x - (end.x - start.x) * 0.3 + (Math.random() - 0.5) * distance * randomness,
249
+ y: end.y - (end.y - start.y) * 0.3 + (Math.random() - 0.5) * distance * randomness
250
+ };
251
+ return [start, cp1, cp2, end];
252
+ }
253
+
254
+ /**
255
+ * 计算动态延迟,模拟加速减速效果
256
+ * @param index 当前步骤索引
257
+ * @param totalPoints 总步数
258
+ * @param speed 速度参数
259
+ * @returns 延迟时间(毫秒)
260
+ */
261
+ getDynamicDelay(index: number, totalPoints: number, speed: number): number {
262
+ // 模拟加速减速效果
263
+ const progress = index / totalPoints;
264
+ const baseDelay = 10 + (1 - speed) * 50;
265
+ return Math.max(10, baseDelay * (1 - Math.sin(progress * Math.PI)) * 0.5);
266
+ }
267
+
268
+ /**
269
+ * 生成指定范围内的随机延迟
270
+ * @param min 最小延迟(毫秒)
271
+ * @param max 最大延迟(毫秒)
272
+ * @returns 随机延迟时间(毫秒)
273
+ */
274
+ getRandomDelay(min = 20, max = 100): number {
275
+ return Math.floor(Math.random() * (max - min + 1)) + min;
276
+ }
277
+
278
+ /**
279
+ * 在页面上显示鼠标轨迹,用于调试
280
+ * 会在页面上添加一个红色小点跟随鼠标移动
281
+ */
282
+ async showCursor(): Promise<void> {
283
+ await this.page.addInitScript({
284
+ content: `
285
+ let dot;
286
+ document.addEventListener('mousemove', (e) => {
287
+ if (!dot) {
288
+ dot = document.createElement('div');
289
+ Object.assign(dot.style, {
290
+ position: 'fixed',
291
+ width: '5px',
292
+ height: '5px',
293
+ borderRadius: '50%',
294
+ backgroundColor: 'red',
295
+ pointerEvents: 'none',
296
+ zIndex: 999999
297
+ });
298
+ document.body.appendChild(dot);
299
+ }
300
+ dot.style.left = e.clientX + 'px';
301
+ dot.style.top = e.clientY + 'px';
302
+ });
303
+ `
304
+ });
305
+ }
306
+ }
307
+
308
+ export default HumanMouse;
package/src/index.ts CHANGED
@@ -1,13 +1,13 @@
1
- export * from "./actions";
2
- export * from "./core/config";
3
- export * from "./core/launcher";
4
- export * from "./core/pool";
5
- export * from "./human/mouse";
6
- export * from "./network/cache";
7
- export * from "./network/resource";
8
- export * from "./providers/local";
9
- export * from "./providers/adspower";
10
- export * from "./providers/ok";
11
- export * from "./providers/base";
12
- export * from "./types";
1
+ export * from "./actions";
2
+ export * from "./core/config";
3
+ export * from "./core/launcher";
4
+ export * from "./core/pool";
5
+ export * from "./human/mouse";
6
+ export * from "./network/cache";
7
+ export * from "./network/resource";
8
+ export * from "./providers/local";
9
+ export * from "./providers/adspower";
10
+ export * from "./providers/ok";
11
+ export * from "./providers/base";
12
+ export * from "./types";
13
13
  export * from "./const/browser";