Skip to content

Commit 31e45ed

Browse files
inspect, render tree, send to server
1 parent 3fac925 commit 31e45ed

File tree

1 file changed

+138
-9
lines changed

1 file changed

+138
-9
lines changed

Apps/Mac/inspector.py

Lines changed: 138 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import json
66
from configobj import ConfigObj
77
from pathlib import Path
8+
import traceback
89
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")))
910

1011
print(f"Python Version: {sys.version}")
@@ -86,7 +87,8 @@ def __init__(self):
8687
self.x: int = -1
8788
self.y: int = -1
8889
self.app: App = App(name="", bundle_id="", pid=-1, window_title="")
89-
self.xml_path: str = ""
90+
self.xml_str: str = ""
91+
self.xml_tree: ET.ElementTree = None
9092

9193
self.server_address: str = "http://127.0.0.1"
9294
self.server_path: str = "/api/v1/mac/dump/driver"
@@ -98,8 +100,11 @@ def wait_for_control_press(self):
98100
flags = CGEventSourceFlagsState(kCGEventSourceStateHIDSystemState)
99101
if flags & kCGEventFlagMaskControl:
100102
point = NSEvent.mouseLocation()
101-
rich_print(f"Captured at x={point.x}, y={point.y}")
102-
self.x, self.y = round(point.x), round(point.y)
103+
height = NSScreen.mainScreen().frame().size.height
104+
x = round(point.x)
105+
y = round(height - point.y)
106+
rich_print(f"Captured at x={x}, y={y}")
107+
self.x, self.y = x, y
103108
return
104109
time.sleep(0.1)
105110

@@ -122,11 +127,13 @@ def get_server_port(self):
122127

123128
def get_dump(self):
124129
url = f"{self.server_address}:{self.server_port}{self.server_path}"
125-
response = requests.get(url).json()
126-
print('url', url)
127-
print('response', response)
130+
try:
131+
response = requests.get(url).json()
132+
except requests.exceptions.ConnectionError:
133+
print(Fore.RED + "Failed to connect to the server. Please launch the Zeuz Node first and launch an app")
134+
return
128135
if response["status"] == "ok":
129-
self.response = response["ui_xml"]
136+
self.page_src = response["ui_xml"]
130137
print(Fore.GREEN + f"Successfully got dump from appium driver")
131138
elif response["status"] == "not_found":
132139
print(Fore.GREEN + f"You have not launched any app yet. Launch app with the following action:")
@@ -145,16 +152,138 @@ def get_dump(self):
145152
else:
146153
print(Fore.RED + f"Error: {response['error']}")
147154
self.page_src = ""
148-
155+
156+
def render_tree(self):
157+
print('rendertree')
158+
if not self.page_src:
159+
return
160+
161+
root = ET.fromstring(self.page_src)
162+
tree = Tree(f"[bold green]{self.app.name} ({self.app.bundle_id})")
163+
self.xml_tree = tree
164+
165+
def check_bounding_box(element):
166+
if element.attrib.get('x') and element.attrib.get('y') and element.attrib.get('width') and element.attrib.get('height'):
167+
x = int(element.attrib.get('x'))
168+
y = int(element.attrib.get('y'))
169+
width = int(element.attrib.get('width'))
170+
height = int(element.attrib.get('height'))
171+
if (self.x >= x and
172+
self.x <= x + width and
173+
self.y >= y and
174+
self.y <= y + height
175+
):
176+
return True
177+
return False
178+
179+
def get_attribute_string(element):
180+
ignore = ['x', 'y', 'width', 'height']
181+
return " ".join([f'{k}="{v}"' for k, v in element.attrib.items() if k not in ignore])
182+
183+
def set_single_zeuz_apiplugin(root):
184+
elements = root.findall(".//*[@zeuz='aiplugin']")
185+
if len(elements) > 1:
186+
element_areas = []
187+
for element in elements:
188+
width = int(element.attrib.get('width', 0))
189+
height = int(element.attrib.get('height', 0))
190+
area = width * height
191+
element_areas.append((element, area))
192+
193+
element_areas.sort(key=lambda x: x[1])
194+
for element, _ in element_areas[1:]:
195+
del element.attrib['zeuz']
196+
197+
def remove_coordinates(node):
198+
remove = ['x', 'y', 'width', 'height']
199+
for child in node:
200+
for attrib in list(child.attrib):
201+
if attrib in remove:
202+
del child.attrib[attrib]
203+
remove_coordinates(child)
204+
205+
def build_tree(element, parent_tree):
206+
element_tag = element.tag
207+
ignore = ['x', 'y', 'width', 'height']
208+
element_attribs = get_attribute_string(element)
209+
element_coords = f"x={element.attrib.get('x', '')}, y={element.attrib.get('y', '')}, w={element.attrib.get('width', '')}, h={element.attrib.get('height', '')}"
210+
recorded_coords = f"self.x={self.x}, self.y={self.y}"
211+
212+
if check_bounding_box(element):
213+
if not any(check_bounding_box(child) for child in element):
214+
area = int(element.attrib.get('width', '1')) * int(element.attrib.get('height', '1'))
215+
label = f"[bold blue]{element_tag}: [green]{element_attribs} [dim]({element_coords} Area: {area} {recorded_coords})"
216+
element.set('zeuz', 'aiplugin')
217+
else:
218+
label = f"[bold blue]{element_tag}: [yellow]{element_attribs}"
219+
220+
else:
221+
label = f"[bold]{element_tag}: {element_attribs}"
222+
node = parent_tree.add(label)
223+
224+
for child in element:
225+
if check_bounding_box(child):
226+
build_tree(child, node)
227+
else:
228+
node.add(f"[bold]{child.tag}: {get_attribute_string(child)}")
229+
230+
build_tree(root, tree)
231+
set_single_zeuz_apiplugin(root)
232+
rich_print(tree)
233+
remove_coordinates(root)
234+
self.xml_str = ET.tostring(root).decode().encode('ascii', 'ignore').decode()
235+
236+
237+
''' Comment out the below code to check if tree contains single zeuz apiplugin '''
238+
# tree2 = Tree(f"[bold green]{self.app.name} ({self.app.bundle_id})")
239+
# build_tree(root, tree2)
240+
# rich_print(tree2)
241+
def send_to_server(self):
242+
config = ConfigObj(settings_conf_path)
243+
api_key = config["Authentication"]["api-key"].strip()
244+
server = config["Authentication"]["server_address"].strip()
245+
246+
if not api_key or not server:
247+
print(Fore.RED + "API key or server address is not set. Please launch the Zeuz Node first and login")
248+
return
249+
url = f"{self.server_address}:{self.server_port}{self.server_path}"
250+
try:
251+
url = server + "/" if server[-1] != "/" else server
252+
url += "ai_record_single_action/"
253+
print(url)
254+
content = json.dumps({
255+
'page_src': self.xml_str,
256+
"action_type": "android",
257+
})
258+
headers = {
259+
"X-Api-Key": api_key,
260+
}
261+
262+
r = requests.request("POST", url, headers=headers, data=content, verify=False)
263+
response = r.json()
264+
if response["info"] == "success":
265+
r.ok and print("Element sent. You can " + Fore.GREEN + "'Add by AI' " + Fore.RESET + "from server")
266+
else:
267+
print(Fore.RED + response["info"])
268+
except:
269+
traceback.print_exc()
270+
print(Fore.RED + "Failed to send content to AI Engine")
271+
return
272+
149273
def run(self):
150274
while True:
151-
# input("Press any key to start capturing...")
275+
input("Press any key to start capturing...")
152276
self.wait_for_control_press()
153277
self.get_frontmost_app()
154278
self.get_server_port()
279+
if self.server_port == 0:
280+
print(Fore.RED + "Server port is not set. Please launch the Zeuz Node first and launch an app")
281+
continue
155282
self.get_dump()
156283
if not self.page_src:
157284
continue
285+
self.render_tree()
286+
self.send_to_server()
158287

159288
time.sleep(0.2)
160289

0 commit comments

Comments
 (0)