intuned-runtime 1.2.4__py3-none-any.whl → 1.3.0rc0__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.
intuned_cli/types.py CHANGED
@@ -1,5 +1,4 @@
1
1
  from pydantic import BaseModel
2
- from pydantic import Field
3
2
  from pydantic import RootModel
4
3
 
5
4
 
@@ -20,14 +19,3 @@ class FileNode(BaseModel):
20
19
 
21
20
 
22
21
  FileSystemTree.model_rebuild()
23
-
24
-
25
- class IntunedJson(BaseModel):
26
- model_config = {"populate_by_name": True}
27
-
28
- class _AuthSessions(BaseModel):
29
- enabled: bool
30
-
31
- auth_sessions: _AuthSessions = Field(alias="authSessions")
32
- project_name: str | None = Field(alias="projectName", default=None)
33
- workspace_id: str | None = Field(alias="workspaceId", default=None)
@@ -3,8 +3,8 @@ from typing import overload
3
3
 
4
4
  from anyio import Path
5
5
 
6
- from intuned_cli.types import IntunedJson
7
6
  from intuned_cli.utils.error import CLIError
7
+ from runtime.types import IntunedJson
8
8
 
9
9
 
10
10
  @overload
@@ -1,7 +1,7 @@
1
1
  import os
2
2
 
3
- from intuned_cli.types import IntunedJson
4
3
  from intuned_cli.utils.error import CLIError
4
+ from runtime.types import IntunedJson
5
5
 
6
6
 
7
7
  def get_base_url():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: intuned-runtime
3
- Version: 1.2.4
3
+ Version: 1.3.0rc0
4
4
  Summary: Runtime commands for Intuned platform Python scrapers
5
5
  License: Elastic-2.0
6
6
  License-File: LICENSE
@@ -22,10 +22,10 @@ intuned_cli/controller/api.py,sha256=cIxfuKsMibn7XRbTQCELy1QVtWH-UMWO8KGyOCvKx2A
22
22
  intuned_cli/controller/authsession.py,sha256=KGpRYXO9W6DFbuM1ofdH5aba0Zs4A_A3nhbNj-KaCE8,14272
23
23
  intuned_cli/controller/deploy.py,sha256=krbVm0-c1XDIsiOPIf0lesFmFuVF_VfGYNWCOQ70Mmo,5452
24
24
  intuned_cli/controller/save.py,sha256=_ujsh4C9cgFGW4GTkj43JDSpIq0TuBLhZRJsH_1wgX8,9025
25
- intuned_cli/types.py,sha256=Lsykui4mbq5T1_1L7Gg5OZpJvCOmaz9EiQcZVfUEoLc,766
26
- intuned_cli/utils/api_helpers.py,sha256=57gvXVYM_9hMGIkMqSEN_jzs3mYWlXzinPjYXzzqgg8,1122
25
+ intuned_cli/types.py,sha256=A053bHBqgVUG5wdHygwlJ2F-XeJIhFRidSOij5veBOQ,394
26
+ intuned_cli/utils/api_helpers.py,sha256=q_xJMcl-RMccdTNTyDFu27T4OrXihhZtshYdZDiSDGI,1118
27
27
  intuned_cli/utils/auth_session_helpers.py,sha256=acKAPUjOfdybstLur4Lk3huaLFP2Ipl4AjPSqQPQLzY,1899
28
- intuned_cli/utils/backend.py,sha256=CtGoX6eWO6dUnND_FUurIgBXgp9Z4y89k8j3tYK9G-k,1030
28
+ intuned_cli/utils/backend.py,sha256=RvaDClFDf_Tur1E4Z_HoxM1hsHYpm_CWG0ju77ZyKZo,1026
29
29
  intuned_cli/utils/confirmation.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
30
  intuned_cli/utils/console.py,sha256=jgin2xB-0kpANPMoiDBOnWhrc8Ey5zT4IHRxQa8f5CQ,93
31
31
  intuned_cli/utils/error.py,sha256=KzqWG0e8PLerCY-AL534zygCLbIBD6uReo2WEuej-EA,990
@@ -65,10 +65,13 @@ runtime/backend_functions/__init__.py,sha256=j2EaK4FK8bmdFtqc5FxtFwx1KhIn_7qKPCh
65
65
  runtime/backend_functions/_call_backend_function.py,sha256=NpbQmEieuRan0fgbJQ0L_skU6MgjMmR99bWsQQYuFtI,3402
66
66
  runtime/backend_functions/get_auth_session_parameters.py,sha256=pOvB7XiWpphEuBpazdKALw9EWgBU1PeY3gkzBfVLpkc,869
67
67
  runtime/browser/__init__.py,sha256=EPWfa4ZmdR8GJqh2qcsx1ZvHmCYiUYrQ-zeHYVapH9s,285
68
+ runtime/browser/extensions/__init__.py,sha256=VGUFvkYUwuYc9UlYy1TD79veXuDhnLDcmbyHiVnf6dQ,80
69
+ runtime/browser/extensions/helpers.py,sha256=W3XiOf66J_xWpa4-U7hQjW0CfAucCYz12pBcIRnzLTQ,387
70
+ runtime/browser/extensions/intuned_extension.py,sha256=_La0ikQX2isdiBgZXkZt2iQ6eyW7Fe-9yjePFzpj0rk,2804
68
71
  runtime/browser/helpers.py,sha256=CwgiBToawPgwAY9nIGkGHW544N7Db_OgKmS-SHkN2pU,1255
69
72
  runtime/browser/launch_browser.py,sha256=ym97J4RffOGUwhi9iNjAR5Ek2f8pKiAtAcDQFSqMJw0,1899
70
73
  runtime/browser/launch_camoufox.py,sha256=TBOAwwipNGlbtMdFYnGkVM0ppLU44vWNkMGZA5uPZCE,1787
71
- runtime/browser/launch_chromium.py,sha256=XW2u1Ao6MfaMzqL5hnXhR8s8EzQ0TpjyHjSQXHpuAYM,6677
74
+ runtime/browser/launch_chromium.py,sha256=uKX12nclua-tcW6_dgJvihq4MrcC0tnsgcndKSLWm_M,8002
72
75
  runtime/browser/storage_state.py,sha256=fwLg8sP-H-vgt_6AJKNl03CpgyMVCQWWcN2cqswTQMs,3603
73
76
  runtime/constants.py,sha256=YMYQgCWZdJXUpxz_IN2TvZO5rFye9k_Lk9CS8m-shLg,34
74
77
  runtime/context/__init__.py,sha256=hg8ejm4bJy4tNkwmZ9lKgYJx6bU7OgOdBS684Uv5XGg,73
@@ -93,13 +96,16 @@ runtime/run/run_api.py,sha256=AFhZerLTyHGznGCJl0fLbiTqhZ_WRmphaJK-RqrupTQ,8981
93
96
  runtime/run/setup_context_hook.py,sha256=KTX4mRmeUEmYS9zrmobR1V09GakOk6uz81Uo_xXTJZk,1156
94
97
  runtime/run/traces.py,sha256=fKzh11LqV47ujgq_9I2tdp-dgld566wffWaHwU_4gis,1123
95
98
  runtime/run/types.py,sha256=RR4RGiYVBIK6f2qXvzfLhQMZ8kmrziu265k5eqoIh98,346
96
- runtime/types/__init__.py,sha256=IJkDfqsau8F8R_j8TO6j-JwW4ElQr6aU6LNaWRehg5U,401
99
+ runtime/types/__init__.py,sha256=LWf5iOMgbve_BrpVP-LWWzDD3v2K4Y2sLxthOnVEqyY,539
97
100
  runtime/types/payload.py,sha256=sty8HgDEn3nJbZrwEOMCXyuG7_ICGDwlBIIWSON5ABY,124
98
101
  runtime/types/run_types.py,sha256=GcYLkL2BHxOjT4O3KvBP6xjBKsmJbjltMt_5bCVnfCI,4554
102
+ runtime/types/settings_types.py,sha256=KWCxrQBlvJ8cmOJ2w1qd0-R5ypKlJLFJ5aiRh3BAluU,2486
103
+ runtime/utils/__init__.py,sha256=v0qHjnc54YCkY1yPbXuihgymVZau_15xaEVyaFQj9ts,78
104
+ runtime/utils/config_loader.py,sha256=yqk2eDGbgyw0Xslgd3dJbB28NjUe02L9LyCxzCmH9r4,482
99
105
  runtime_helpers/__init__.py,sha256=1BPzEc-qC2WAYiCWDOJChpgnFyO3ZNYRKHEZqdHUGwM,322
100
106
  runtime_helpers/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
101
- intuned_runtime-1.2.4.dist-info/METADATA,sha256=dHCG5TB2mH07dwJNrxrhaclfvjJuH5juy7L2auOhdYY,5372
102
- intuned_runtime-1.2.4.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
103
- intuned_runtime-1.2.4.dist-info/entry_points.txt,sha256=ToMS2cqDeRmF1FGkflwoeD-Xz6jJV5p1zIbw9G7IxMg,85
104
- intuned_runtime-1.2.4.dist-info/licenses/LICENSE,sha256=9LIjQdgyU_ptzNIfItNCR7VmEHqYnrY1f1XwOreKFI0,3714
105
- intuned_runtime-1.2.4.dist-info/RECORD,,
107
+ intuned_runtime-1.3.0rc0.dist-info/METADATA,sha256=zmH_fbP08dee7FsL2b-eWC_BiPM3o48fEI862hmwZgk,5375
108
+ intuned_runtime-1.3.0rc0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
109
+ intuned_runtime-1.3.0rc0.dist-info/entry_points.txt,sha256=ToMS2cqDeRmF1FGkflwoeD-Xz6jJV5p1zIbw9G7IxMg,85
110
+ intuned_runtime-1.3.0rc0.dist-info/licenses/LICENSE,sha256=9LIjQdgyU_ptzNIfItNCR7VmEHqYnrY1f1XwOreKFI0,3714
111
+ intuned_runtime-1.3.0rc0.dist-info/RECORD,,
@@ -0,0 +1,3 @@
1
+ from .helpers import build_extensions_list
2
+
3
+ __all__ = ["build_extensions_list"]
@@ -0,0 +1,11 @@
1
+ from .intuned_extension import get_intuned_extension_path
2
+ from .intuned_extension import is_intuned_extension_enabled
3
+
4
+
5
+ def build_extensions_list() -> list[str]:
6
+ extensions_list: list[str] = []
7
+
8
+ if is_intuned_extension_enabled():
9
+ intuned_extension_path = get_intuned_extension_path()
10
+ extensions_list.append(str(intuned_extension_path))
11
+ return extensions_list
@@ -0,0 +1,87 @@
1
+ import logging
2
+ import os
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ from playwright.async_api import BrowserContext
7
+
8
+ from runtime.context.context import IntunedContext
9
+ from runtime.env import get_functions_domain
10
+ from runtime.env import get_project_id
11
+ from runtime.env import get_workspace_id
12
+ from runtime.types import CaptchaSolverSettings
13
+ from runtime.utils.config_loader import load_intuned_json
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ def get_intuned_extension_path() -> Path | None:
19
+ if "INTUNED_EXTENSION_PATH" not in os.environ:
20
+ return None
21
+ intuned_extension_path = Path(os.environ["INTUNED_EXTENSION_PATH"])
22
+ if not intuned_extension_path.exists():
23
+ return None
24
+ return intuned_extension_path
25
+
26
+
27
+ def is_intuned_extension_enabled() -> bool:
28
+ intuned_extension_path = get_intuned_extension_path()
29
+ if intuned_extension_path is None:
30
+ return False
31
+ else:
32
+ return True
33
+
34
+
35
+ async def get_intuned_worker(context: BrowserContext):
36
+ if not is_intuned_extension_enabled():
37
+ return None
38
+
39
+ for attempt in range(5):
40
+ for service_worker in context.service_workers:
41
+ if "intunedWorker.js" in service_worker.url:
42
+ return service_worker
43
+ try:
44
+ if attempt < 4:
45
+ await context.wait_for_event("serviceworker", timeout=3000)
46
+ except Exception as e:
47
+ logger.warning(f"Error accessing service workers (attempt {attempt + 1}): {e}")
48
+
49
+ logger.warning("Failed to get intuned worker after 5 attempts")
50
+ return None
51
+
52
+
53
+ async def get_intuned_extension_settings() -> dict[str, Any]:
54
+ intuned_json = await load_intuned_json()
55
+ captcha_settings: CaptchaSolverSettings = (
56
+ intuned_json.captcha_solver
57
+ if intuned_json and intuned_json.captcha_solver is not None
58
+ else CaptchaSolverSettings()
59
+ )
60
+ context = IntunedContext.current()
61
+ return {
62
+ **captcha_settings.model_dump(mode="json"),
63
+ "workspaceId": get_workspace_id(),
64
+ "projectId": get_project_id(),
65
+ "token": context.functions_token,
66
+ "baseUrl": get_functions_domain(),
67
+ }
68
+
69
+
70
+ async def setup_intuned_extension():
71
+ if not is_intuned_extension_enabled():
72
+ return
73
+ intuned_extension_path = get_intuned_extension_path()
74
+ if intuned_extension_path is None:
75
+ logger.warning("Intuned extension path not found, intuned extension might not work properly")
76
+ return
77
+
78
+ settings_path = intuned_extension_path / "intunedSettings.json"
79
+ settings_data = await get_intuned_extension_settings()
80
+
81
+ try:
82
+ with open(settings_path, "w") as f:
83
+ import json
84
+
85
+ json.dump(settings_data, f)
86
+ except Exception as e:
87
+ logger.warning(f"Failed to write intuned settings to {settings_path}: {e}")
@@ -8,6 +8,11 @@ from typing import TYPE_CHECKING
8
8
 
9
9
  import anyio
10
10
 
11
+ from runtime.browser.extensions import build_extensions_list
12
+ from runtime.browser.extensions.intuned_extension import get_intuned_worker
13
+ from runtime.browser.extensions.intuned_extension import is_intuned_extension_enabled
14
+ from runtime.browser.extensions.intuned_extension import setup_intuned_extension
15
+
11
16
  from .helpers import get_local_cdp_address
12
17
  from .helpers import get_proxy_env
13
18
  from .helpers import wait_on_cdp_address
@@ -80,15 +85,36 @@ async def launch_chromium(
80
85
  if cdp_port:
81
86
  extra_args.append(f"--remote-debugging-port={cdp_port}")
82
87
 
88
+ args_to_ignore = [
89
+ "--disable-extensions",
90
+ "--disable-component-extensions-with-background-pages",
91
+ "--disable-background-networking",
92
+ "--disable-backgrounding-occluded-windows",
93
+ "--disable-background-timer-throttling",
94
+ ]
95
+ if is_intuned_extension_enabled():
96
+ extensions_list = build_extensions_list()
97
+ extensions = ",".join(extensions_list)
98
+ extra_args.append("--disable-extensions-except=" + extensions)
99
+ extra_args.append(f"--load-extension={extensions}")
100
+ await setup_intuned_extension()
101
+
102
+ if headless:
103
+ args_to_ignore.append("--headless=old")
104
+ extra_args.append("--headless=new")
83
105
  context = await playwright.chromium.launch_persistent_context(
84
106
  os.fspath(user_preferences_dir),
85
107
  headless=headless,
86
108
  viewport=viewport,
87
109
  proxy=proxy,
110
+ ignore_default_args=args_to_ignore,
88
111
  user_agent=os.environ.get("USER_AGENT", default_user_agent),
89
112
  args=extra_args,
90
113
  **kwargs,
91
114
  )
115
+ if is_intuned_extension_enabled():
116
+ # wait for intuned extension to be ready
117
+ await get_intuned_worker(context)
92
118
  if cdp_port:
93
119
  await wait_on_cdp_address(get_local_cdp_address(cdp_port))
94
120
 
runtime/types/__init__.py CHANGED
@@ -4,6 +4,8 @@ from .run_types import RunAutomationErrorResult
4
4
  from .run_types import RunAutomationResult
5
5
  from .run_types import RunAutomationSuccessResult
6
6
  from .run_types import RunBody
7
+ from .settings_types import CaptchaSolverSettings
8
+ from .settings_types import IntunedJson
7
9
 
8
10
  __all__ = [
9
11
  "Payload",
@@ -12,4 +14,6 @@ __all__ = [
12
14
  "RunAutomationErrorResult",
13
15
  "RunAutomationSuccessResult",
14
16
  "PayloadToAppend",
17
+ "IntunedJson",
18
+ "CaptchaSolverSettings",
15
19
  ]
@@ -0,0 +1,63 @@
1
+ from typing import List
2
+
3
+ from pydantic import BaseModel
4
+ from pydantic import Field
5
+
6
+
7
+ class CaptchaSettings(BaseModel):
8
+ enabled: bool = Field(default=False)
9
+
10
+
11
+ class CustomCaptchaSettings(CaptchaSettings):
12
+ model_config = {
13
+ "populate_by_name": True,
14
+ "serialize_by_alias": True,
15
+ }
16
+
17
+ image_locators: List[str] = Field(alias="imageLocators", default=[])
18
+ submit_locators: List[str] = Field(alias="submitLocators", default=[])
19
+ input_locators: List[str] = Field(alias="inputLocators", default=[])
20
+
21
+
22
+ class TextCaptchaSettings(CaptchaSettings):
23
+ model_config = {
24
+ "populate_by_name": True,
25
+ "serialize_by_alias": True,
26
+ }
27
+ label_locators: List[str] = Field(alias="labelLocators", default=[])
28
+ submit_locators: List[str] = Field(alias="submitLocators", default=[])
29
+ input_locators: List[str] = Field(alias="inputLocators", default=[])
30
+
31
+
32
+ class CaptchaSolverSettings(BaseModel):
33
+ model_config = {
34
+ "populate_by_name": True,
35
+ "serialize_by_alias": True,
36
+ }
37
+
38
+ enabled: bool = Field(default=False)
39
+ cloudflare: CaptchaSettings = Field(default_factory=CaptchaSettings)
40
+ google_recaptcha_v2: CaptchaSettings = Field(alias="googleRecaptchaV2", default_factory=CaptchaSettings)
41
+ google_recaptcha_v3: CaptchaSettings = Field(alias="googleRecaptchaV3", default_factory=CaptchaSettings)
42
+ awscaptcha: CaptchaSettings = Field(default_factory=CaptchaSettings)
43
+ hcaptcha: CaptchaSettings = Field(default_factory=CaptchaSettings)
44
+ funcaptcha: CaptchaSettings = Field(default_factory=CaptchaSettings)
45
+ geetest: CaptchaSettings = Field(default_factory=CaptchaSettings)
46
+ lemin: CaptchaSettings = Field(default_factory=CaptchaSettings)
47
+ custom_captcha: CustomCaptchaSettings = Field(alias="customCaptcha", default_factory=CustomCaptchaSettings)
48
+ text: TextCaptchaSettings = Field(default_factory=TextCaptchaSettings)
49
+ settings: dict[str, int | bool] = Field(
50
+ default={"autoSolve": True, "solveDelay": 2000, "maxRetries": 3, "timeout": 30000}
51
+ )
52
+
53
+
54
+ class IntunedJson(BaseModel):
55
+ model_config = {"populate_by_name": True}
56
+
57
+ class _AuthSessions(BaseModel):
58
+ enabled: bool
59
+
60
+ auth_sessions: _AuthSessions = Field(alias="authSessions")
61
+ project_name: str | None = Field(alias="projectName", default=None)
62
+ workspace_id: str | None = Field(alias="workspaceId", default=None)
63
+ captcha_solver: CaptchaSolverSettings | None = Field(alias="captchaSolver", default=None)
@@ -0,0 +1,3 @@
1
+ from .config_loader import load_intuned_json
2
+
3
+ __all__ = ["load_intuned_json"]
@@ -0,0 +1,17 @@
1
+ from anyio import Path
2
+
3
+ from runtime.types import IntunedJson
4
+
5
+
6
+ async def load_intuned_json() -> IntunedJson | None:
7
+ """
8
+ Load the Intuned.json configuration file.
9
+ Returns None if file doesn't exist or fails to parse.
10
+ """
11
+ intuned_json_path = Path("Intuned.json")
12
+ if not await intuned_json_path.exists():
13
+ return None
14
+ try:
15
+ return IntunedJson.model_validate_json(await intuned_json_path.read_text())
16
+ except Exception:
17
+ return None