osn-selenium 0.0.0__py3-none-any.whl
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.
- osn_selenium/__init__.py +1 -0
- osn_selenium/browsers_handler/__init__.py +70 -0
- osn_selenium/browsers_handler/_windows.py +130 -0
- osn_selenium/browsers_handler/types.py +20 -0
- osn_selenium/captcha_workers/__init__.py +26 -0
- osn_selenium/dev_tools/__init__.py +1 -0
- osn_selenium/dev_tools/_types.py +22 -0
- osn_selenium/dev_tools/domains/__init__.py +63 -0
- osn_selenium/dev_tools/domains/abstract.py +378 -0
- osn_selenium/dev_tools/domains/fetch.py +1295 -0
- osn_selenium/dev_tools/domains_default/__init__.py +1 -0
- osn_selenium/dev_tools/domains_default/fetch.py +155 -0
- osn_selenium/dev_tools/errors.py +89 -0
- osn_selenium/dev_tools/logger.py +558 -0
- osn_selenium/dev_tools/manager.py +1551 -0
- osn_selenium/dev_tools/utils.py +509 -0
- osn_selenium/errors.py +16 -0
- osn_selenium/types.py +118 -0
- osn_selenium/webdrivers/BaseDriver/__init__.py +1 -0
- osn_selenium/webdrivers/BaseDriver/_utils.py +37 -0
- osn_selenium/webdrivers/BaseDriver/flags.py +644 -0
- osn_selenium/webdrivers/BaseDriver/protocols.py +2135 -0
- osn_selenium/webdrivers/BaseDriver/trio_wrapper.py +71 -0
- osn_selenium/webdrivers/BaseDriver/webdriver.py +2626 -0
- osn_selenium/webdrivers/Blink/__init__.py +1 -0
- osn_selenium/webdrivers/Blink/flags.py +1349 -0
- osn_selenium/webdrivers/Blink/protocols.py +330 -0
- osn_selenium/webdrivers/Blink/webdriver.py +637 -0
- osn_selenium/webdrivers/Chrome/__init__.py +1 -0
- osn_selenium/webdrivers/Chrome/flags.py +192 -0
- osn_selenium/webdrivers/Chrome/protocols.py +228 -0
- osn_selenium/webdrivers/Chrome/webdriver.py +394 -0
- osn_selenium/webdrivers/Edge/__init__.py +1 -0
- osn_selenium/webdrivers/Edge/flags.py +192 -0
- osn_selenium/webdrivers/Edge/protocols.py +228 -0
- osn_selenium/webdrivers/Edge/webdriver.py +394 -0
- osn_selenium/webdrivers/Yandex/__init__.py +1 -0
- osn_selenium/webdrivers/Yandex/flags.py +192 -0
- osn_selenium/webdrivers/Yandex/protocols.py +211 -0
- osn_selenium/webdrivers/Yandex/webdriver.py +350 -0
- osn_selenium/webdrivers/__init__.py +1 -0
- osn_selenium/webdrivers/_functions.py +504 -0
- osn_selenium/webdrivers/js_scripts/check_element_in_viewport.js +18 -0
- osn_selenium/webdrivers/js_scripts/get_document_scroll_size.js +4 -0
- osn_selenium/webdrivers/js_scripts/get_element_css.js +6 -0
- osn_selenium/webdrivers/js_scripts/get_element_rect_in_viewport.js +9 -0
- osn_selenium/webdrivers/js_scripts/get_random_element_point_in_viewport.js +59 -0
- osn_selenium/webdrivers/js_scripts/get_viewport_position.js +4 -0
- osn_selenium/webdrivers/js_scripts/get_viewport_rect.js +6 -0
- osn_selenium/webdrivers/js_scripts/get_viewport_size.js +4 -0
- osn_selenium/webdrivers/js_scripts/open_new_tab.js +1 -0
- osn_selenium/webdrivers/js_scripts/stop_window_loading.js +1 -0
- osn_selenium/webdrivers/types.py +390 -0
- osn_selenium-0.0.0.dist-info/METADATA +710 -0
- osn_selenium-0.0.0.dist-info/RECORD +57 -0
- osn_selenium-0.0.0.dist-info/WHEEL +5 -0
- osn_selenium-0.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import sys
|
|
3
|
+
import math
|
|
4
|
+
import pathlib
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
from random import randint
|
|
7
|
+
from subprocess import PIPE, Popen
|
|
8
|
+
from typing import Optional, Union
|
|
9
|
+
from pandas import DataFrame, Series
|
|
10
|
+
from osn_selenium.errors import (
|
|
11
|
+
PlatformNotSupportedError
|
|
12
|
+
)
|
|
13
|
+
from osn_windows_cmd.netstat import (
|
|
14
|
+
get_netstat_connections_data as windows_netstat_connections_data
|
|
15
|
+
)
|
|
16
|
+
from osn_selenium.webdrivers.types import (
|
|
17
|
+
ActionPoint,
|
|
18
|
+
JS_Scripts,
|
|
19
|
+
MoveOffset,
|
|
20
|
+
MovePart,
|
|
21
|
+
ScrollDelta,
|
|
22
|
+
ScrollPart,
|
|
23
|
+
TextInputPart,
|
|
24
|
+
_MoveStep
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def text_input_to_parts(text: str) -> list[TextInputPart]:
|
|
29
|
+
"""
|
|
30
|
+
Breaks down a text string into smaller parts for simulating human-like typing.
|
|
31
|
+
|
|
32
|
+
Generates a list of `TextInputPart` objects, where each part contains a character
|
|
33
|
+
or sequence of characters and a calculated duration. The duration simulates pauses
|
|
34
|
+
between key presses, with potentially longer pauses between different consecutive
|
|
35
|
+
characters.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
text (str): The input string to be broken down.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
list[TextInputPart]: A list of `TextInputPart` objects representing the sequence
|
|
42
|
+
of text segments and their associated durations for simulated typing.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
parts = []
|
|
46
|
+
previous_sign = None
|
|
47
|
+
|
|
48
|
+
for sign in text:
|
|
49
|
+
if previous_sign is None or sign == previous_sign:
|
|
50
|
+
parts.append(TextInputPart(text=sign, duration=randint(50, 70)))
|
|
51
|
+
else:
|
|
52
|
+
parts.append(TextInputPart(text=sign, duration=randint(100, 200)))
|
|
53
|
+
|
|
54
|
+
previous_sign = sign
|
|
55
|
+
|
|
56
|
+
return parts
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def str_adding_validation_function(value: Optional[str]) -> bool:
|
|
60
|
+
"""
|
|
61
|
+
Validation function that checks if a value is a non-empty string.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
value (Optional[str]): The value to validate.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
bool: `True` if the value is a non-empty string, `False` otherwise.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
if value is not None and not isinstance(value, str):
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
return bool(value)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def scroll_to_parts(start_position: ActionPoint, end_position: ActionPoint) -> list[ScrollPart]:
|
|
77
|
+
"""
|
|
78
|
+
Calculates a sequence of smaller scroll steps with durations for human-like scrolling.
|
|
79
|
+
|
|
80
|
+
Simulates scrolling by generating a series of vertical steps followed by horizontal steps
|
|
81
|
+
until the target coordinates are reached. Each step is represented by a `ScrollPart`
|
|
82
|
+
containing the scroll delta for that step and a random duration.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
start_position (ActionPoint): The starting conceptual scroll position.
|
|
86
|
+
end_position (ActionPoint): The target conceptual scroll position.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
list[ScrollPart]: A list of `ScrollPart` objects representing the sequence of scroll
|
|
90
|
+
movements and their associated durations. The `delta` in each part
|
|
91
|
+
represents the scroll amount for that step, and the `point` represents
|
|
92
|
+
the conceptual position *after* applying that delta.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
parts = []
|
|
96
|
+
|
|
97
|
+
current_position = deepcopy(start_position)
|
|
98
|
+
|
|
99
|
+
if start_position.y != end_position.y:
|
|
100
|
+
while current_position.y != end_position.y:
|
|
101
|
+
offset = randint(5, 20)
|
|
102
|
+
|
|
103
|
+
if start_position.y > end_position.y:
|
|
104
|
+
offset *= -1
|
|
105
|
+
|
|
106
|
+
if start_position.y < end_position.y:
|
|
107
|
+
if current_position.y + offset > end_position.y:
|
|
108
|
+
offset = end_position.y - current_position.y
|
|
109
|
+
elif start_position.y > end_position.y:
|
|
110
|
+
if current_position.y + offset < end_position.y:
|
|
111
|
+
offset = end_position.y - current_position.y
|
|
112
|
+
|
|
113
|
+
current_position.y += offset
|
|
114
|
+
parts.append(
|
|
115
|
+
ScrollPart(
|
|
116
|
+
point=deepcopy(current_position),
|
|
117
|
+
delta=ScrollDelta(x=0, y=offset),
|
|
118
|
+
duration=randint(1, 4)
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if start_position.x != end_position.x:
|
|
123
|
+
while current_position.x != end_position.x:
|
|
124
|
+
offset = randint(5, 20)
|
|
125
|
+
|
|
126
|
+
if start_position.x > end_position.x:
|
|
127
|
+
offset *= -1
|
|
128
|
+
|
|
129
|
+
if start_position.x <= end_position.x:
|
|
130
|
+
if current_position.x + offset > end_position.x:
|
|
131
|
+
offset = end_position.x - current_position.x
|
|
132
|
+
elif start_position.x > end_position.x:
|
|
133
|
+
if current_position.x + offset < end_position.x:
|
|
134
|
+
offset = current_position.x - end_position.x
|
|
135
|
+
|
|
136
|
+
current_position.x += offset
|
|
137
|
+
parts.append(
|
|
138
|
+
ScrollPart(
|
|
139
|
+
point=deepcopy(current_position),
|
|
140
|
+
delta=ScrollDelta(x=offset, y=0),
|
|
141
|
+
duration=randint(1, 4)
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
return parts
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def read_js_scripts() -> JS_Scripts:
|
|
149
|
+
"""
|
|
150
|
+
Reads JavaScript scripts from files and returns them in a _JS_Scripts object.
|
|
151
|
+
|
|
152
|
+
This function locates all `.js` files within the 'js_scripts' directory, which is expected to be located two levels above the current file's directory.
|
|
153
|
+
It reads the content of each JavaScript file, using UTF-8 encoding, and stores these scripts in a dictionary-like `_JS_Scripts` object.
|
|
154
|
+
The filenames (without the `.js` extension) are used as keys in the `_JS_Scripts` object to access the script content.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
JS_Scripts: An object of type _JS_Scripts, containing the content of each JavaScript file as attributes.
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
scripts = {}
|
|
161
|
+
|
|
162
|
+
for script_file in (pathlib.Path(__file__).parent / "js_scripts").iterdir():
|
|
163
|
+
scripts[re.sub(r"\.js$", "", script_file.name)] = open(script_file, "r", encoding="utf-8").read()
|
|
164
|
+
|
|
165
|
+
return JS_Scripts(
|
|
166
|
+
check_element_in_viewport=scripts["check_element_in_viewport"],
|
|
167
|
+
get_document_scroll_size=scripts["get_document_scroll_size"],
|
|
168
|
+
get_element_css=scripts["get_element_css"],
|
|
169
|
+
get_element_rect_in_viewport=scripts["get_element_rect_in_viewport"],
|
|
170
|
+
get_random_element_point_in_viewport=scripts["get_random_element_point_in_viewport"],
|
|
171
|
+
get_viewport_position=scripts["get_viewport_position"],
|
|
172
|
+
get_viewport_rect=scripts["get_viewport_rect"],
|
|
173
|
+
get_viewport_size=scripts["get_viewport_size"],
|
|
174
|
+
open_new_tab=scripts["open_new_tab"],
|
|
175
|
+
stop_window_loading=scripts["stop_window_loading"],
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def path_adding_validation_function(value: Optional[Union[str, pathlib.Path]]) -> bool:
|
|
180
|
+
"""
|
|
181
|
+
Validation function that checks if a value is a non-empty string or a pathlib.Path object.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
value (Optional[Union[str, pathlib.Path]]): The value to validate.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
bool: `True` if the value is a valid path-like object, `False` otherwise.
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
if value is not None and not isinstance(value, (str, pathlib.Path)):
|
|
191
|
+
return False
|
|
192
|
+
|
|
193
|
+
return bool(value)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def optional_bool_adding_validation_function(value: Optional[bool]) -> bool:
|
|
197
|
+
"""
|
|
198
|
+
Validation function that checks if a value is a boolean or None.
|
|
199
|
+
|
|
200
|
+
The function returns `True` if the value is not None, allowing the flag
|
|
201
|
+
to be added.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
value (Optional[bool]): The value to validate.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
bool: `True` if the value is not None, `False` if the value is not a boolean.
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
if value is not None and not isinstance(value, bool):
|
|
211
|
+
return False
|
|
212
|
+
|
|
213
|
+
return value is not None
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def move_to_parts(start_position: ActionPoint, end_position: ActionPoint) -> list[MovePart]:
|
|
217
|
+
"""
|
|
218
|
+
Calculates a sequence of smaller move steps with durations for human-like mouse movement.
|
|
219
|
+
|
|
220
|
+
Generates a path between a start and end point that deviates slightly from a
|
|
221
|
+
straight line using a sinusoidal function, simulating more natural mouse movement.
|
|
222
|
+
The path is broken into segments, each represented by a `MovePart` object specifying
|
|
223
|
+
the target point for that segment, the offset from the previous point, and a random duration.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
start_position (ActionPoint): The starting coordinates for the movement.
|
|
227
|
+
end_position (ActionPoint): The target coordinates for the movement.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
list[MovePart]: A list of `MovePart` objects representing the sequence of mouse
|
|
231
|
+
movements and their associated durations. Each `MovePart` indicates
|
|
232
|
+
the `point` to move to, the `offset` from the previous point,
|
|
233
|
+
and the `duration` for that movement segment.
|
|
234
|
+
"""
|
|
235
|
+
|
|
236
|
+
def get_new_position():
|
|
237
|
+
"""Calculates random horizontal and vertical amplitudes for deviation."""
|
|
238
|
+
|
|
239
|
+
linear_progress = step.index / len(steps)
|
|
240
|
+
|
|
241
|
+
deviation_x = step.amplitude_x * math.sin(linear_progress * 2 * math.pi)
|
|
242
|
+
current_x_linear = start_position.x + (end_position.x - start_position.x) * linear_progress
|
|
243
|
+
|
|
244
|
+
deviation_y = step.amplitude_y * math.cos(linear_progress * 2 * math.pi)
|
|
245
|
+
current_y_linear = start_position.y + (end_position.y - start_position.y) * linear_progress
|
|
246
|
+
|
|
247
|
+
return ActionPoint(
|
|
248
|
+
x=int(current_x_linear + deviation_x),
|
|
249
|
+
y=int(current_y_linear + deviation_y)
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
def get_amplitude():
|
|
253
|
+
"""Generates a sequence of internal _MoveStep objects."""
|
|
254
|
+
|
|
255
|
+
amplitude_x = randint(10, 20)
|
|
256
|
+
amplitude_y = randint(10, 20)
|
|
257
|
+
|
|
258
|
+
if start_position.x > end_position.x:
|
|
259
|
+
amplitude_x *= -1
|
|
260
|
+
|
|
261
|
+
if start_position.y > end_position.y:
|
|
262
|
+
amplitude_y *= -1
|
|
263
|
+
|
|
264
|
+
return amplitude_x, amplitude_y
|
|
265
|
+
|
|
266
|
+
def calculate_steps():
|
|
267
|
+
"""Calculates the target point for a move step, including sinusoidal deviation."""
|
|
268
|
+
|
|
269
|
+
steps_ = []
|
|
270
|
+
current_point = deepcopy(start_position)
|
|
271
|
+
index = 0
|
|
272
|
+
|
|
273
|
+
while current_point != end_position:
|
|
274
|
+
amplitude_x, amplitude_y = get_amplitude()
|
|
275
|
+
steps_.append(_MoveStep(amplitude_x=amplitude_x, amplitude_y=amplitude_y, index=index))
|
|
276
|
+
|
|
277
|
+
index += 1
|
|
278
|
+
|
|
279
|
+
if start_position.x <= end_position.x:
|
|
280
|
+
current_point.x += amplitude_x if current_point.x + amplitude_x <= end_position.x else end_position.x - current_point.x
|
|
281
|
+
else:
|
|
282
|
+
current_point.x += amplitude_x if current_point.x + amplitude_x >= end_position.x else end_position.x - current_point.x
|
|
283
|
+
|
|
284
|
+
if start_position.y <= end_position.y:
|
|
285
|
+
current_point.y += amplitude_y if current_point.y + amplitude_y <= end_position.y else end_position.y - current_point.y
|
|
286
|
+
else:
|
|
287
|
+
current_point.y += amplitude_y if current_point.y + amplitude_y >= end_position.y else end_position.y - current_point.y
|
|
288
|
+
|
|
289
|
+
return steps_
|
|
290
|
+
|
|
291
|
+
parts = []
|
|
292
|
+
steps = calculate_steps()
|
|
293
|
+
previous_position = deepcopy(start_position)
|
|
294
|
+
|
|
295
|
+
for step in steps:
|
|
296
|
+
new_position = get_new_position()
|
|
297
|
+
|
|
298
|
+
if start_position.x <= end_position.x:
|
|
299
|
+
new_position.x = int(new_position.x if new_position.x < end_position.x else end_position.x)
|
|
300
|
+
else:
|
|
301
|
+
new_position.x = int(new_position.x if new_position.x > end_position.x else end_position.x)
|
|
302
|
+
|
|
303
|
+
if start_position.y <= end_position.y:
|
|
304
|
+
new_position.y = int(new_position.y if new_position.y < end_position.y else end_position.y)
|
|
305
|
+
else:
|
|
306
|
+
new_position.y = int(new_position.y if new_position.y > end_position.y else end_position.y)
|
|
307
|
+
|
|
308
|
+
parts.append(
|
|
309
|
+
MovePart(
|
|
310
|
+
point=new_position,
|
|
311
|
+
offset=MoveOffset(
|
|
312
|
+
x=new_position.x - previous_position.x,
|
|
313
|
+
y=new_position.y - previous_position.y
|
|
314
|
+
),
|
|
315
|
+
duration=randint(1, 4)
|
|
316
|
+
)
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
previous_position = deepcopy(new_position)
|
|
320
|
+
|
|
321
|
+
if parts[-1].point == end_position:
|
|
322
|
+
break
|
|
323
|
+
|
|
324
|
+
if parts[-1].point != end_position:
|
|
325
|
+
parts.append(
|
|
326
|
+
MovePart(
|
|
327
|
+
point=end_position,
|
|
328
|
+
offset=MoveOffset(
|
|
329
|
+
x=end_position.x - previous_position.x,
|
|
330
|
+
y=end_position.y - previous_position.y
|
|
331
|
+
),
|
|
332
|
+
duration=randint(1, 4)
|
|
333
|
+
)
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
return parts
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def int_adding_validation_function(value: Optional[int]) -> bool:
|
|
340
|
+
"""
|
|
341
|
+
Validation function that checks if a value is an integer.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
value (Optional[int]): The value to validate.
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
bool: `True` if the value is an integer or None, `False` otherwise.
|
|
348
|
+
"""
|
|
349
|
+
|
|
350
|
+
if value is not None and not isinstance(value, int):
|
|
351
|
+
return False
|
|
352
|
+
|
|
353
|
+
return True
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def get_found_profile_dir(data: Series, profile_dir_command: str) -> Optional[str]:
|
|
357
|
+
"""
|
|
358
|
+
Extracts the browser profile directory path from a process's command line arguments.
|
|
359
|
+
|
|
360
|
+
This function executes a platform-specific command to retrieve the command line
|
|
361
|
+
of a process given its PID. It then searches for a profile directory path within
|
|
362
|
+
the command line using a provided command pattern. Currently, only Windows platform is supported.
|
|
363
|
+
|
|
364
|
+
Args:
|
|
365
|
+
data (Series): A Pandas Series containing process information, which must include a 'PID' column
|
|
366
|
+
representing the process ID.
|
|
367
|
+
profile_dir_command (str): A string representing the command line pattern to search for the profile directory.
|
|
368
|
+
This string should contain '{value}' as a placeholder where the profile directory path is expected.
|
|
369
|
+
For example: "--user-data-dir='{value}'".
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
Optional[str]: The profile directory path as a string if found in the command line, otherwise None.
|
|
373
|
+
|
|
374
|
+
Raises:
|
|
375
|
+
PlatformNotSupportedError: If the platform is not supported.
|
|
376
|
+
"""
|
|
377
|
+
|
|
378
|
+
if sys.platform == "win32":
|
|
379
|
+
stdout = Popen(
|
|
380
|
+
f"wmic process where processid={int(data['PID'])} get CommandLine /FORMAT:LIST",
|
|
381
|
+
stdout=PIPE,
|
|
382
|
+
shell=True
|
|
383
|
+
).communicate()[0].decode("866", errors="ignore").strip()
|
|
384
|
+
found_command_line = re.sub(r"^CommandLine=", "", stdout).strip()
|
|
385
|
+
|
|
386
|
+
found_profile_dir = re.search(profile_dir_command.format(value="(.*?)"), found_command_line)
|
|
387
|
+
if found_profile_dir is not None:
|
|
388
|
+
found_profile_dir = found_profile_dir.group(1)
|
|
389
|
+
|
|
390
|
+
return found_profile_dir
|
|
391
|
+
|
|
392
|
+
raise PlatformNotSupportedError(f"Unsupported platform: {sys.platform}.")
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def get_active_executables_table(browser_exe: Union[str, pathlib.Path]) -> DataFrame:
|
|
396
|
+
"""
|
|
397
|
+
Retrieves a table of active executables related to a specified browser, listening on localhost.
|
|
398
|
+
|
|
399
|
+
This function uses platform-specific methods to fetch network connection information
|
|
400
|
+
and filters it to find entries associated with the provided browser executable
|
|
401
|
+
that are in a "LISTENING" state on localhost. Currently, only Windows platform is supported.
|
|
402
|
+
|
|
403
|
+
Args:
|
|
404
|
+
browser_exe (Union[str, pathlib.Path]): The path to the browser executable.
|
|
405
|
+
It can be a string or a pathlib.Path object.
|
|
406
|
+
|
|
407
|
+
Returns:
|
|
408
|
+
DataFrame: A Pandas DataFrame containing rows of active executable connections
|
|
409
|
+
that match the browser executable and listening criteria.
|
|
410
|
+
Returns an empty DataFrame if no matching executables are found.
|
|
411
|
+
|
|
412
|
+
Raises:
|
|
413
|
+
PlatformNotSupportedError: If the platform is not supported.
|
|
414
|
+
"""
|
|
415
|
+
|
|
416
|
+
if sys.platform == "win32":
|
|
417
|
+
connections_data = windows_netstat_connections_data(
|
|
418
|
+
show_all_ports=True,
|
|
419
|
+
show_connections_exe=True,
|
|
420
|
+
show_connection_pid=True
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
return connections_data.loc[
|
|
424
|
+
(
|
|
425
|
+
connections_data["Executable"] == (browser_exe if isinstance(browser_exe, str) else browser_exe.name)
|
|
426
|
+
) &
|
|
427
|
+
connections_data["Local Address"].str.contains(r"127\.0\.0\.1:\d+", regex=True, na=False) &
|
|
428
|
+
(connections_data["State"] == "LISTENING")
|
|
429
|
+
]
|
|
430
|
+
|
|
431
|
+
raise PlatformNotSupportedError(f"Unsupported platform: {sys.platform}.")
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def find_browser_previous_session(
|
|
435
|
+
browser_exe: Union[str, pathlib.Path],
|
|
436
|
+
profile_dir_command: str,
|
|
437
|
+
profile_dir: Optional[str]
|
|
438
|
+
) -> Optional[int]:
|
|
439
|
+
"""
|
|
440
|
+
Finds the port number of a previously opened browser session, if it exists.
|
|
441
|
+
|
|
442
|
+
This function checks for an existing browser session by examining network connections.
|
|
443
|
+
It searches for listening connections associated with the given browser executable and profile directory.
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
browser_exe (Union[str, pathlib.Path]): Path to the browser executable or just the executable name.
|
|
447
|
+
profile_dir_command (str): Command line pattern to find the profile directory argument in the process command line. Should use `{value}` as a placeholder for the directory path.
|
|
448
|
+
profile_dir (Optional[str]): The expected profile directory path to match against.
|
|
449
|
+
|
|
450
|
+
Returns:
|
|
451
|
+
Optional[int]: The port number of the previous session if found and matched, otherwise None.
|
|
452
|
+
"""
|
|
453
|
+
|
|
454
|
+
executables_table = get_active_executables_table(browser_exe)
|
|
455
|
+
|
|
456
|
+
for index, row in executables_table.iterrows():
|
|
457
|
+
found_profile_dir = get_found_profile_dir(row, profile_dir_command)
|
|
458
|
+
|
|
459
|
+
if found_profile_dir == profile_dir:
|
|
460
|
+
return int(re.search(r"127\.0\.0\.1:(\d+)", row["Local Address"]).group(1))
|
|
461
|
+
|
|
462
|
+
return None
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def build_first_start_argument(browser_exe: Union[str, pathlib.Path]) -> str:
|
|
466
|
+
"""
|
|
467
|
+
Builds the first command line argument to start a browser executable.
|
|
468
|
+
|
|
469
|
+
This function constructs the initial command line argument needed to execute a browser,
|
|
470
|
+
handling different operating systems and executable path formats.
|
|
471
|
+
|
|
472
|
+
Args:
|
|
473
|
+
browser_exe (Union[str, pathlib.Path]): Path to the browser executable or just the executable name.
|
|
474
|
+
|
|
475
|
+
Returns:
|
|
476
|
+
str: The constructed command line argument string.
|
|
477
|
+
|
|
478
|
+
Raises:
|
|
479
|
+
TypeError: If `browser_exe` is not of type str or pathlib.Path.
|
|
480
|
+
"""
|
|
481
|
+
|
|
482
|
+
if isinstance(browser_exe, str):
|
|
483
|
+
return browser_exe
|
|
484
|
+
elif isinstance(browser_exe, pathlib.Path):
|
|
485
|
+
return f"\"{str(browser_exe.resolve())}\""
|
|
486
|
+
else:
|
|
487
|
+
raise TypeError(f"browser_exe must be str or pathlib.Path, not {type(browser_exe)}.")
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def bool_adding_validation_function(value: Optional[bool]) -> bool:
|
|
491
|
+
"""
|
|
492
|
+
Validation function that checks if a value is a boolean and `True`.
|
|
493
|
+
|
|
494
|
+
Args:
|
|
495
|
+
value (Optional[bool]): The value to validate.
|
|
496
|
+
|
|
497
|
+
Returns:
|
|
498
|
+
bool: `True` if the value is `True`, `False` otherwise.
|
|
499
|
+
"""
|
|
500
|
+
|
|
501
|
+
if not isinstance(value, bool):
|
|
502
|
+
return False
|
|
503
|
+
|
|
504
|
+
return value
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
var element = arguments[0];
|
|
2
|
+
|
|
3
|
+
if (!element) {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
var rect = element.getBoundingClientRect();
|
|
8
|
+
var viewportWidth = window.innerWidth || document.documentElement.clientWidth;
|
|
9
|
+
var viewportHeight = window.innerHeight || document.documentElement.clientHeight;
|
|
10
|
+
|
|
11
|
+
var isVisible = (
|
|
12
|
+
rect.top >= 0 &&
|
|
13
|
+
rect.left >= 0 &&
|
|
14
|
+
rect.bottom <= viewportHeight &&
|
|
15
|
+
rect.right <= viewportWidth
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
return isVisible;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
function get_random_point_in_element_shape_grid(targetElement, step) {
|
|
2
|
+
try {
|
|
3
|
+
const rect = targetElement.getBoundingClientRect();
|
|
4
|
+
|
|
5
|
+
if (rect.width <= 0 || rect.height <= 0) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const points = [];
|
|
10
|
+
|
|
11
|
+
for (let xOffset = 0; xOffset < rect.width; xOffset += step) {
|
|
12
|
+
for (let yOffset = 0; yOffset < rect.height; yOffset += step) {
|
|
13
|
+
points.push(
|
|
14
|
+
{
|
|
15
|
+
viewportX: Math.floor(rect.left + xOffset),
|
|
16
|
+
viewportY: Math.floor(rect.top + yOffset),
|
|
17
|
+
elementOffsetX: xOffset,
|
|
18
|
+
elementOffsetY: yOffset
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (points.length === 0) {
|
|
25
|
+
const centerX = rect.left + rect.width / 2;
|
|
26
|
+
const centerY = rect.top + rect.height / 2;
|
|
27
|
+
|
|
28
|
+
const hitElement = document.elementFromPoint(Math.floor(centerX), Math.floor(centerY));
|
|
29
|
+
|
|
30
|
+
if (hitElement && (hitElement === targetElement || targetElement.contains(hitElement))) {
|
|
31
|
+
return { x: rect.width / 2, y: rect.height / 2 };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (let i = points.length - 1; i > 0; i--) {
|
|
38
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
39
|
+
[points[i], points[j]] = [points[j], points[i]];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const point of points) {
|
|
43
|
+
const hitElement = document.elementFromPoint(point.viewportX, point.viewportY);
|
|
44
|
+
|
|
45
|
+
if (hitElement && (hitElement === targetElement || targetElement.contains(hitElement))) {
|
|
46
|
+
return {
|
|
47
|
+
x: point.elementOffsetX,
|
|
48
|
+
y: point.elementOffsetY
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return null;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return get_random_point_in_element_shape_grid(arguments[0], arguments[1]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
window.open(arguments[0]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
window.stop();
|