55import  json 
66from  configobj  import  ConfigObj 
77from  pathlib  import  Path 
8+ import  traceback 
89sys .path .insert (0 , os .path .abspath (os .path .join (os .path .dirname (__file__ ), "../../" )))
910
1011print (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 } { 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 }  )
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' , '' )} { element .attrib .get ('y' , '' )} { element .attrib .get ('width' , '' )} { element .attrib .get ('height' , '' )}  
210+             recorded_coords  =  f"self.x={ self .x } { 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 } { element_attribs } { element_coords } { area } { recorded_coords }  
216+                     element .set ('zeuz' , 'aiplugin' )
217+                 else :
218+                     label  =  f"[bold blue]{ element_tag } { 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