aura-browser 0.1.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.
aura_browser/__init__.py
ADDED
aura_browser/core.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from DrissionPage import ChromiumPage, ChromiumOptions
|
|
2
|
+
from typing import List, Dict, Any
|
|
3
|
+
|
|
4
|
+
class AIBrowser:
|
|
5
|
+
def __init__(self, headless: bool = False, mute: bool = True):
|
|
6
|
+
co = ChromiumOptions()
|
|
7
|
+
|
|
8
|
+
if headless:
|
|
9
|
+
co.headless()
|
|
10
|
+
|
|
11
|
+
if mute:
|
|
12
|
+
co.mute(True)
|
|
13
|
+
|
|
14
|
+
# Advanced stealth settings to bypass bot protection
|
|
15
|
+
co.set_argument('--no-sandbox')
|
|
16
|
+
co.set_argument('--disable-gpu')
|
|
17
|
+
co.set_argument('--disable-blink-features=AutomationControlled')
|
|
18
|
+
|
|
19
|
+
self.page = ChromiumPage(addr_or_opts=co)
|
|
20
|
+
self.interactive_elements = {}
|
|
21
|
+
|
|
22
|
+
def go_to(self, url: str):
|
|
23
|
+
"""Navigate to a URL and wait for it to load completely."""
|
|
24
|
+
self.page.get(url)
|
|
25
|
+
self.page.wait.load_start()
|
|
26
|
+
|
|
27
|
+
def get_interactive_elements(self) -> List[Dict[str, str]]:
|
|
28
|
+
"""
|
|
29
|
+
Scans the page for interactive elements (buttons, links, inputs).
|
|
30
|
+
Returns them as a list of dictionaries with unique IDs for AI to control.
|
|
31
|
+
"""
|
|
32
|
+
self.interactive_elements.clear()
|
|
33
|
+
elements_data = []
|
|
34
|
+
|
|
35
|
+
# Selectors that an AI might want to interact with
|
|
36
|
+
selectors = ['a', 'button', 'input', 'textarea', 'select', '[role="button"]']
|
|
37
|
+
|
|
38
|
+
idx = 1
|
|
39
|
+
for selector in selectors:
|
|
40
|
+
eles = self.page.eles(selector)
|
|
41
|
+
for ele in eles:
|
|
42
|
+
try:
|
|
43
|
+
# Ignore elements that are not visible
|
|
44
|
+
if not ele.states.is_displayed or ele.states.is_hidden:
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
tag = ele.tag
|
|
48
|
+
text = ele.text or ele.attr('aria-label') or ele.attr('placeholder') or ele.attr('value') or ele.attr('name') or ''
|
|
49
|
+
text = text.strip()
|
|
50
|
+
|
|
51
|
+
if not text and tag not in ['input', 'textarea']:
|
|
52
|
+
continue # Ignore empty, non-input elements
|
|
53
|
+
|
|
54
|
+
eid = f"ID_{idx}"
|
|
55
|
+
self.interactive_elements[eid] = ele
|
|
56
|
+
|
|
57
|
+
elements_data.append({
|
|
58
|
+
"id": eid,
|
|
59
|
+
"tag": tag,
|
|
60
|
+
"text": text,
|
|
61
|
+
"type": ele.attr('type') if tag == 'input' else None
|
|
62
|
+
})
|
|
63
|
+
idx += 1
|
|
64
|
+
except Exception:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
return elements_data
|
|
68
|
+
|
|
69
|
+
def click(self, element_id: str) -> bool:
|
|
70
|
+
"""Click an element by its ID."""
|
|
71
|
+
ele = self.interactive_elements.get(element_id)
|
|
72
|
+
if ele:
|
|
73
|
+
try:
|
|
74
|
+
# by_js=True helps bypass some tricky interceptors
|
|
75
|
+
ele.click(by_js=True)
|
|
76
|
+
self.page.wait.load_start()
|
|
77
|
+
return True
|
|
78
|
+
except Exception as e:
|
|
79
|
+
print(f"Failed to click {element_id}: {e}")
|
|
80
|
+
return False
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
def type_text(self, element_id: str, text: str, press_enter: bool = False) -> bool:
|
|
84
|
+
"""Type text into an input element by its ID."""
|
|
85
|
+
ele = self.interactive_elements.get(element_id)
|
|
86
|
+
if ele:
|
|
87
|
+
try:
|
|
88
|
+
ele.input(text, clear=True)
|
|
89
|
+
if press_enter:
|
|
90
|
+
# Press enter key
|
|
91
|
+
self.page.actions.type('\n')
|
|
92
|
+
self.page.wait.load_start()
|
|
93
|
+
return True
|
|
94
|
+
except Exception as e:
|
|
95
|
+
print(f"Failed to type in {element_id}: {e}")
|
|
96
|
+
return False
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
def take_screenshot(self, save_path: str = "screenshot.jpg"):
|
|
100
|
+
"""Take a screenshot of the current page and save it locally."""
|
|
101
|
+
try:
|
|
102
|
+
self.page.get_screenshot(path=save_path)
|
|
103
|
+
return True
|
|
104
|
+
except Exception as e:
|
|
105
|
+
print(f"Failed to take screenshot: {e}")
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
def get_page_text(self) -> str:
|
|
109
|
+
"""Extract clean, readable text from the current page."""
|
|
110
|
+
return self.page.body.text
|
|
111
|
+
|
|
112
|
+
def close(self):
|
|
113
|
+
"""Quit the browser."""
|
|
114
|
+
self.page.quit()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aura-browser
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An AI-driven, lightning-fast stealth browser controller.
|
|
5
|
+
Author: Your Name
|
|
6
|
+
Author-email: your.email@example.com
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.7
|
|
11
|
+
Requires-Dist: DrissionPage>=4.0.0
|
|
12
|
+
Dynamic: author
|
|
13
|
+
Dynamic: author-email
|
|
14
|
+
Dynamic: classifier
|
|
15
|
+
Dynamic: requires-dist
|
|
16
|
+
Dynamic: requires-python
|
|
17
|
+
Dynamic: summary
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
aura_browser/__init__.py,sha256=MZqlvbr1kRsAl3-4e1kN_utfWjDm9_4l8odrSHswOKo,53
|
|
2
|
+
aura_browser/core.py,sha256=eXjCTxleHm2_DtNz_SA7kCTH3quo0fHrVA23beBqvFI,4252
|
|
3
|
+
aura_browser-0.1.0.dist-info/METADATA,sha256=tnt-oCU3vllK2WpsR8l3ZEAKJVsmxqqrGSW5olATA5w,521
|
|
4
|
+
aura_browser-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
5
|
+
aura_browser-0.1.0.dist-info/top_level.txt,sha256=sBDqSs624WBmjE6vaWPUl_3if2kcqbVOS1VeOVIvihI,13
|
|
6
|
+
aura_browser-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
aura_browser
|