/ tor_api_setup.py
tor_api_setup.py
  1  import sys
  2  import os
  3  import subprocess
  4  import requests
  5  import socket
  6  import time
  7  import re
  8  from PyQt6.QtCore import QThread, pyqtSignal
  9  
 10  class TORAPISetup(QThread):
 11      """Thread to handle API setup through TOR with robust error handling"""
 12      output_signal = pyqtSignal(str)
 13      finished_signal = pyqtSignal(bool)
 14      
 15      def __init__(self, tor_port=9050, control_port=9051, tor_password=None):
 16          super().__init__()
 17          self.tor_port = tor_port
 18          self.control_port = control_port
 19          self.tor_password = tor_password
 20          self.process = None
 21          self.tor_session = None
 22      
 23      def create_tor_session(self):
 24          """Create a TOR session with proper configuration"""
 25          session = requests.Session()
 26          session.proxies = {
 27              'http': f'socks5h://127.0.0.1:{self.tor_port}',
 28              'https': f'socks5h://127.0.0.1:{self.tor_port}'
 29          }
 30          
 31          # Add headers to avoid detection
 32          session.headers.update({
 33              'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
 34              'Accept': 'application/json, text/plain, */*',
 35              'Accept-Language': 'en-US,en;q=0.9',
 36              'Accept-Encoding': 'gzip, deflate, br',
 37              'Connection': 'keep-alive',
 38          })
 39          
 40          session.timeout = 15
 41          return session
 42      
 43      def check_tor_connection(self, retries=3):
 44          """Check TOR connection with multiple endpoints and retries"""
 45          if not self.tor_session:
 46              self.tor_session = self.create_tor_session()
 47          
 48          test_urls = [
 49              ("https://httpbin.org/ip", "httpbin"),
 50              ("https://api.ipify.org?format=json", "ipify"),
 51              ("https://icanhazip.com", "icanhazip"),
 52              ("https://checkip.amazonaws.com", "aws"),
 53              ("https://ipinfo.io/ip", "ipinfo"),
 54              ("http://checkip.dyndns.org", "dyndns"),
 55          ]
 56          
 57          successful_tests = 0
 58          last_ip = None
 59          
 60          for url, service_name in test_urls:
 61              for attempt in range(retries):
 62                  try:
 63                      self.output_signal.emit(f"🔍 Testing TOR via {service_name}...")
 64                      response = self.tor_session.get(url, timeout=10)
 65                      
 66                      if response.status_code == 200:
 67                          # Extract IP from response
 68                          try:
 69                              if 'application/json' in response.headers.get('Content-Type', ''):
 70                                  ip_data = response.json()
 71                                  ip = ip_data.get("origin") or ip_data.get("ip") or ip_data.get("query")
 72                              else:
 73                                  # Parse plain text
 74                                  text = response.text.strip()
 75                                  ip_match = re.search(r'\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b', text)
 76                                  ip = ip_match.group(0) if ip_match else text
 77                              
 78                              if ip:
 79                                  if last_ip is None:
 80                                      last_ip = ip
 81                                  
 82                                  # Check if IPs are consistent (should be same TOR exit node)
 83                                  if ip == last_ip:
 84                                      self.output_signal.emit(f"✅ {service_name}: IP {ip}")
 85                                      successful_tests += 1
 86                                  else:
 87                                      self.output_signal.emit(f"âš ī¸  {service_name}: Different IP {ip} (expected {last_ip})")
 88                                      # Still count as successful if we got an IP
 89                                      successful_tests += 1
 90                                      last_ip = ip
 91                                  
 92                                  # If we have at least 2 successful tests, TOR is working
 93                                  if successful_tests >= 2:
 94                                      self.output_signal.emit(f"🔒 TOR connection established - Current IP: {last_ip}")
 95                                      return True
 96                                  
 97                                  break  # Success, move to next endpoint
 98                          except Exception as e:
 99                              self.output_signal.emit(f"âš ī¸  Could not parse {service_name} response: {e}")
100                      else:
101                          self.output_signal.emit(f"âš ī¸  {service_name}: HTTP {response.status_code}")
102                          
103                  except requests.exceptions.Timeout:
104                      self.output_signal.emit(f"âš ī¸  {service_name}: Timeout (attempt {attempt + 1}/{retries})")
105                      if attempt < retries - 1:
106                          time.sleep(2)
107                  except requests.exceptions.ConnectionError:
108                      self.output_signal.emit(f"âš ī¸  {service_name}: Connection error (attempt {attempt + 1}/{retries})")
109                      if attempt < retries - 1:
110                          time.sleep(2)
111                  except Exception as e:
112                      self.output_signal.emit(f"âš ī¸  {service_name}: Error {e}")
113                      if attempt < retries - 1:
114                          time.sleep(2)
115              
116              # Small delay between endpoints
117              time.sleep(0.5)
118          
119          if successful_tests > 0:
120              self.output_signal.emit(f"âš ī¸  Partial TOR connectivity: {successful_tests}/6 endpoints")
121              return True
122          
123          self.output_signal.emit("❌ TOR connection failed across all endpoints")
124          return False
125  
126      def renew_tor_ip(self):
127          """Get fresh TOR IP with robust error handling"""
128          try:
129              self.output_signal.emit("🔄 Renewing TOR IP address...")
130              
131              # Connect to TOR control port
132              sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
133              sock.settimeout(15)
134              sock.connect(('127.0.0.1', self.control_port))
135              
136              # Authenticate
137              if self.tor_password:
138                  auth_cmd = f'AUTHENTICATE "{self.tor_password}"\r\n'
139                  sock.send(auth_cmd.encode())
140                  response = sock.recv(1024).decode()
141                  
142                  if '250 OK' in response:
143                      self.output_signal.emit("🔐 Authenticated with password")
144                  else:
145                      # Try without password
146                      auth_cmd = 'AUTHENTICATE\r\n'
147                      sock.send(auth_cmd.encode())
148                      response = sock.recv(1024).decode()
149              else:
150                  auth_cmd = 'AUTHENTICATE\r\n'
151                  sock.send(auth_cmd.encode())
152                  response = sock.recv(1024).decode()
153              
154              if '250 OK' not in response:
155                  self.output_signal.emit(f"❌ Authentication failed: {response}")
156                  sock.close()
157                  return False
158              
159              # Request new circuit
160              sock.send(b'SIGNAL NEWNYM\r\n')
161              response = sock.recv(1024).decode()
162              
163              if '250 OK' not in response:
164                  self.output_signal.emit(f"❌ NEWNYM failed: {response}")
165                  sock.close()
166                  return False
167              
168              sock.send(b'QUIT\r\n')
169              sock.close()
170              
171              self.output_signal.emit("✅ TOR circuit renewed")
172              
173              # Wait for circuit to establish and verify
174              time.sleep(5)
175              
176              # Verify new IP
177              if self.check_tor_connection(retries=2):
178                  self.output_signal.emit("✅ Fresh TOR IP ready for API registration")
179                  return True
180              else:
181                  self.output_signal.emit("âš ī¸  TOR renewal succeeded but IP verification failed")
182                  return True  # Still continue with setup
183                  
184          except socket.timeout:
185              self.output_signal.emit("❌ TOR control port timeout - is TOR running?")
186              return False
187          except ConnectionRefusedError:
188              self.output_signal.emit("❌ TOR control port refused - check TOR configuration")
189              return False
190          except Exception as e:
191              self.output_signal.emit(f"❌ TOR renewal error: {e}")
192              # Don't fail entirely, continue with current IP
193              return True
194      
195      def verify_api_connectivity(self):
196          """Verify connectivity to Blackbird AI API through TOR"""
197          test_endpoints = [
198              "https://api.blackbird.ai/status",  # If there's a status endpoint
199              "https://api.blackbird.ai/",        # Base API endpoint
200          ]
201          
202          for endpoint in test_endpoints:
203              try:
204                  response = self.tor_session.get(endpoint, timeout=10)
205                  if response.status_code < 500:  # Anything other than server errors
206                      self.output_signal.emit(f"✅ API connectivity test passed: {endpoint}")
207                      return True
208                  else:
209                      self.output_signal.emit(f"âš ī¸  API endpoint {endpoint}: HTTP {response.status_code}")
210              except Exception as e:
211                  self.output_signal.emit(f"âš ī¸  API endpoint {endpoint}: {e}")
212          
213          self.output_signal.emit("âš ī¸  API connectivity tests inconclusive - proceeding anyway")
214          return True  # Proceed even if we can't reach API
215      
216      def run_blackbird_setup(self):
217          """Run blackbird setup command with proper environment"""
218          try:
219              # Set up environment variables for TOR
220              env = os.environ.copy()
221              env['ALL_PROXY'] = f'socks5h://127.0.0.1:{self.tor_port}'
222              env['HTTP_PROXY'] = f'socks5h://127.0.0.1:{self.tor_port}'
223              env['HTTPS_PROXY'] = f'socks5h://127.0.0.1:{self.tor_port}'
224              env['BLACKBIRD_USE_TOR'] = '1'  # Signal to blackbird.py to use TOR
225              
226              # Run the setup command
227              cmd = ['python', 'blackbird.py', '--setup-ai']
228              self.output_signal.emit(f"🚀 Starting: {' '.join(cmd)}")
229              
230              self.process = subprocess.Popen(
231                  cmd,
232                  stdout=subprocess.PIPE,
233                  stderr=subprocess.STDOUT,
234                  stdin=subprocess.PIPE,
235                  text=True,
236                  env=env,
237                  bufsize=1,
238                  universal_newlines=True
239              )
240              
241              # Monitor output and handle prompts
242              confirmation_sent = False
243              for line in iter(self.process.stdout.readline, ''):
244                  if not line:
245                      break
246                      
247                  text = line.strip()
248                  self.output_signal.emit(text)
249                  
250                  # Look for registration prompt
251                  if not confirmation_sent:
252                      if any(prompt in text.lower() for prompt in ['ip is registered', '[y/n]', 'register this ip', 'confirm']):
253                          try:
254                              # Send confirmation
255                              self.process.stdin.write('Y\n')
256                              self.process.stdin.flush()
257                              confirmation_sent = True
258                              self.output_signal.emit("✅ Sent confirmation for TOR IP registration")
259                          except Exception as e:
260                              self.output_signal.emit(f"âš ī¸  Could not send confirmation: {e}")
261                  
262                  # Check for success indicators
263                  if any(success in text.lower() for success in ['success', 'api key saved', 'registered', 'setup complete']):
264                      self.output_signal.emit("✅ API setup appears successful")
265              
266              # Wait for process to complete
267              return_code = self.process.wait()
268              
269              if return_code == 0:
270                  self.output_signal.emit("✅ Blackbird AI setup completed successfully")
271                  return True
272              else:
273                  self.output_signal.emit(f"âš ī¸  Blackbird setup returned code {return_code}")
274                  # Still might be successful if API key was saved
275                  return self.check_api_key_saved()
276                  
277          except Exception as e:
278              self.output_signal.emit(f"❌ Setup process failed: {e}")
279              return False
280      
281      def check_api_key_saved(self):
282          """Check if API key was saved to file"""
283          config_paths = [
284              os.path.expanduser("~/.ai_key.json"),
285              ".ai_key.json",
286              "ai_key.json"
287          ]
288          
289          for config_path in config_paths:
290              if os.path.exists(config_path):
291                  try:
292                      with open(config_path, 'r') as f:
293                          import json
294                          config = json.load(f)
295                          if config.get("ai_api_key") or config.get("api_key"):
296                              self.output_signal.emit(f"✅ API key found in {config_path}")
297                              return True
298                  except Exception as e:
299                      self.output_signal.emit(f"âš ī¸  Could not read {config_path}: {e}")
300          
301          self.output_signal.emit("âš ī¸  No API key found in expected locations")
302          return False
303      
304      def run(self):
305          """Execute API setup through TOR with comprehensive error handling"""
306          self.output_signal.emit("=" * 60)
307          self.output_signal.emit("🚀 BLACKBIRD AI API SETUP THROUGH TOR")
308          self.output_signal.emit("=" * 60)
309          
310          # Step 1: Establish TOR connection
311          self.output_signal.emit("🔧 Step 1: Testing TOR connectivity...")
312          if not self.check_tor_connection():
313              self.output_signal.emit("❌ Cannot proceed without TOR connection")
314              self.finished_signal.emit(False)
315              return
316          
317          # Step 2: Renew IP for fresh registration
318          self.output_signal.emit("🔧 Step 2: Getting fresh TOR IP for registration...")
319          if not self.renew_tor_ip():
320              self.output_signal.emit("âš ī¸  IP renewal failed, continuing with current IP...")
321          
322          # Step 3: Verify API connectivity
323          self.output_signal.emit("🔧 Step 3: Testing API connectivity...")
324          self.verify_api_connectivity()
325          
326          # Step 4: Run the setup
327          self.output_signal.emit("🔧 Step 4: Running Blackbird AI setup...")
328          success = self.run_blackbird_setup()
329          
330          # Step 5: Final verification
331          if success:
332              self.output_signal.emit("🔧 Step 5: Verifying setup...")
333              if self.check_api_key_saved():
334                  self.output_signal.emit("=" * 60)
335                  self.output_signal.emit("✅ API SETUP COMPLETE THROUGH TOR")
336                  self.output_signal.emit("=" * 60)
337                  self.finished_signal.emit(True)
338                  return
339          
340          self.output_signal.emit("=" * 60)
341          self.output_signal.emit("âš ī¸  API SETUP MAY NOT HAVE COMPLETED SUCCESSFULLY")
342          self.output_signal.emit("=" * 60)
343          self.finished_signal.emit(False)
344      
345      def stop(self):
346          """Stop the setup process"""
347          if self.process and self.process.poll() is None:
348              self.process.terminate()
349              try:
350                  self.process.wait(timeout=5)
351              except subprocess.TimeoutExpired:
352                  self.process.kill()
353                  self.process.wait()
354              self.output_signal.emit("âšī¸  Setup process stopped")