/ crow.py
crow.py
1 import sys 2 import subprocess 3 import os 4 import json 5 import requests 6 import re 7 import time # Add this import 8 from datetime import datetime 9 from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, 10 QLabel, QLineEdit, QPushButton, QTextEdit, QFileDialog, 11 QCheckBox, QGroupBox, QFormLayout, QSpinBox, QMessageBox, QInputDialog) 12 from PyQt6.QtCore import Qt, QThread, pyqtSignal 13 # Import the separate save and load functions 14 from pathlib import Path 15 from save_settings import save_settings 16 from load_settings import load_settings 17 from build_blackbird_command import build_blackbird_command 18 from tor_spoofing import TORSpoofer 19 from breach_vip import BreachVIP 20 from tor_api_setup import TORAPISetup 21 22 # Worker class that handles executing the Blackbird command in a separate thread 23 class BlackbirdWorker(QThread): 24 output_signal = pyqtSignal(str) 25 26 def __init__(self, command, needs_ai_confirmation=False, is_setup_ai=False, tor_spoofer=None): 27 super().__init__() 28 self.command = command 29 self.process = None 30 self.needs_ai_confirmation = needs_ai_confirmation 31 self.is_setup_ai = is_setup_ai 32 self.tor_spoofer = tor_spoofer 33 34 def run(self): 35 # If TOR is enabled for AI, set environment variable to signal the blackbird.py 36 # to use TOR for AI requests 37 if self.tor_spoofer and self.tor_spoofer.tor_enabled: 38 # Set environment variable that blackbird.py can check 39 import os 40 os.environ["BLACKBIRD_USE_TOR"] = "1" 41 os.environ["TOR_PORT"] = str(self.tor_spoofer.tor_port) 42 self.process = subprocess.Popen( 43 self.command, 44 stdout=subprocess.PIPE, 45 stderr=subprocess.STDOUT, 46 stdin=subprocess.PIPE, 47 text=True, 48 shell=True, 49 bufsize=1 50 ) 51 52 # For setup-ai, send confirmation immediately 53 if self.is_setup_ai: 54 try: 55 # Wait a bit for the prompt to appear 56 import time 57 time.sleep(2) 58 # Send 'Y' and newline 59 self.process.stdin.write('Y\n') 60 self.process.stdin.flush() 61 self.output_signal.emit("✓ Sent confirmation for API key setup") 62 except Exception as e: 63 self.output_signal.emit(f"Setup confirmation error: {e}") 64 65 # For regular AI analysis, wait for the specific prompt 66 elif self.needs_ai_confirmation: 67 confirmation_sent = False 68 for line in self.process.stdout: 69 text = line.strip() 70 self.output_signal.emit(text) 71 72 # Check for AI analysis prompt 73 if not confirmation_sent and ('analyzing with ai' in text.lower() or 'consent' in text.lower()): 74 try: 75 self.process.stdin.write('Y\n') 76 self.process.stdin.flush() 77 confirmation_sent = True 78 self.output_signal.emit("✓ Automatically confirmed AI analysis") 79 except Exception as e: 80 self.output_signal.emit(f"AI confirmation error: {e}") 81 return 82 83 # Read output line by line for non-AI or after confirmation 84 for line in self.process.stdout: 85 self.output_signal.emit(line.strip()) 86 87 self.process.stdout.close() 88 self.process.wait() 89 90 def terminate(self): 91 # Terminate the process if it's running 92 if self.process: 93 self.process.terminate() 94 self.process.wait() 95 96 # Main GUI class for the Blackbird OSINT tool 97 class BlackbirdGUI(QMainWindow): 98 def __init__(self): 99 super().__init__() 100 # Set window properties 101 self.setWindowTitle("Crow") 102 self.setGeometry(100, 100, 1000, 800) 103 self.worker = None 104 105 # Create the central widget and layout for the main window 106 central_widget = QWidget() 107 self.setCentralWidget(central_widget) 108 109 layout = QVBoxLayout() 110 central_widget.setLayout(layout) 111 112 self.breach = BreachVIP(self) 113 layout.addWidget(self.breach) 114 115 116 # Input Group: For entering usernames, emails, and file selection 117 input_group = QGroupBox("Blackbird Search") 118 input_layout = QFormLayout() 119 120 self.username_input = QLineEdit() 121 input_layout.addRow("Username(s):", self.username_input) 122 123 self.email_input = QLineEdit() 124 input_layout.addRow("Email(s):", self.email_input) 125 126 # File inputs for usernames and emails 127 self.username_file_input = QLineEdit() 128 username_file_button = QPushButton("Select Username File") 129 username_file_button.clicked.connect(self.select_username_file) 130 username_file_layout = QHBoxLayout() 131 username_file_layout.addWidget(self.username_file_input) 132 username_file_layout.addWidget(username_file_button) 133 input_layout.addRow("Username File:", username_file_layout) 134 135 self.email_file_input = QLineEdit() 136 email_file_button = QPushButton("Select Email File") 137 email_file_button.clicked.connect(self.select_email_file) 138 email_file_layout = QHBoxLayout() 139 email_file_layout.addWidget(self.email_file_input) 140 email_file_layout.addWidget(email_file_button) 141 input_layout.addRow("Email File:", email_file_layout) 142 143 input_group.setLayout(input_layout) 144 layout.addWidget(input_group) 145 146 # Options Group: Various checkboxes for additional configuration 147 options_group = QGroupBox("Options") 148 options_layout = QVBoxLayout() 149 150 AI_layout = QHBoxLayout() 151 self.AI_checkbox = QCheckBox("Extract metadata AI") 152 AI_layout.addWidget(self.AI_checkbox) 153 154 # Add setup button for AI API key 155 AI_setup_button = QPushButton("Setup API Key") 156 AI_setup_button.clicked.connect(self.setup_ai_api_key) 157 AI_layout.addWidget(AI_setup_button) 158 159 AI_help_button = QPushButton("?") 160 AI_help_button.setFixedSize(30, 30) 161 AI_help_button.clicked.connect(self.show_AI_help) 162 AI_layout.addWidget(AI_help_button) 163 options_layout.addLayout(AI_layout) 164 165 # TOR Spoofing setup - ADD THIS RIGHT AFTER AI LAYOUT 166 self.tor_spoofer = TORSpoofer(self) 167 self.setup_tor_ui(options_layout) # Pass the options_layout to the method 168 169 # Permute username checkbox with help button on the right 170 permute_layout = QHBoxLayout() 171 self.permute_checkbox = QCheckBox("Permute username") 172 permute_layout.addWidget(self.permute_checkbox) 173 permute_help_button = QPushButton("?") 174 permute_help_button.setFixedSize(30, 30) 175 permute_help_button.clicked.connect(self.show_permute_help) 176 permute_layout.addWidget(permute_help_button) 177 options_layout.addLayout(permute_layout) 178 179 # Permute all elements checkbox with help button on the right 180 permuteall_layout = QHBoxLayout() 181 self.permuteall_checkbox = QCheckBox("Permute all") 182 permuteall_layout.addWidget(self.permuteall_checkbox) 183 permuteall_help_button = QPushButton("?") 184 permuteall_help_button.setFixedSize(30, 30) 185 permuteall_help_button.clicked.connect(self.show_permuteall_help) 186 permuteall_layout.addWidget(permuteall_help_button) 187 options_layout.addLayout(permuteall_layout) 188 189 190 self.no_nsfw_checkbox = QCheckBox("Exclude NSFW sites") 191 options_layout.addWidget(self.no_nsfw_checkbox) 192 193 # Proxy input 194 proxy_layout = QHBoxLayout() 195 proxy_layout.addWidget(QLabel("Proxy:")) 196 self.proxy_input = QLineEdit() 197 proxy_layout.addWidget(self.proxy_input) 198 options_layout.addLayout(proxy_layout) 199 200 # Timeout input with spinner for seconds 201 timeout_layout = QHBoxLayout() 202 timeout_layout.addWidget(QLabel("Timeout (seconds):")) 203 self.timeout_spinbox = QSpinBox() 204 self.timeout_spinbox.setRange(1, 300) # Limit timeout to 1-300 seconds 205 self.timeout_spinbox.setValue(30) # Default timeout is 30 seconds 206 timeout_layout.addWidget(self.timeout_spinbox) 207 options_layout.addLayout(timeout_layout) 208 209 # Checkbox to disable update checks 210 self.no_update_checkbox = QCheckBox("Don't check for updates") 211 options_layout.addWidget(self.no_update_checkbox) 212 213 # Filter input field with a help button 214 filter_layout = QHBoxLayout() 215 filter_layout.addWidget(QLabel("Filter:")) 216 self.filter_input = QLineEdit() 217 filter_layout.addWidget(self.filter_input) 218 219 # Create a help button and connect it to the help function 220 filter_help_button = QPushButton("?") 221 filter_help_button.clicked.connect(self.show_filter_help) # Connect the button to the help function 222 filter_layout.addWidget(filter_help_button) 223 224 options_layout.addLayout(filter_layout) 225 226 options_group.setLayout(options_layout) 227 layout.addWidget(options_group) 228 229 230 # Output Options Group: Allows user to specify output types 231 output_group = QGroupBox("Output Options") 232 output_layout = QHBoxLayout() 233 self.csv_checkbox = QCheckBox("CSV (Results)") 234 self.pdf_checkbox = QCheckBox("PDF (Results)") 235 self.json_checkbox = QCheckBox("JSON (Results)") 236 self.verbose_checkbox = QCheckBox("Verbose (LOGS)") 237 self.dump_checkbox = QCheckBox("Dump HTML (Results)") 238 output_layout.addWidget(self.csv_checkbox) 239 output_layout.addWidget(self.pdf_checkbox) 240 output_layout.addWidget(self.json_checkbox) 241 output_layout.addWidget(self.verbose_checkbox) 242 output_layout.addWidget(self.dump_checkbox) 243 output_group.setLayout(output_layout) 244 layout.addWidget(output_group) 245 246 # Instagram session ID input for enhanced metadata extraction 247 instagram_group = QGroupBox("Instagram Enhanced Metadata") 248 instagram_layout = QHBoxLayout() 249 self.instagram_session_id = QLineEdit() 250 instagram_layout.addWidget(QLabel("Instagram Session ID:")) 251 instagram_layout.addWidget(self.instagram_session_id) 252 instagram_help_button = QPushButton("?") 253 instagram_help_button.clicked.connect(self.show_instagram_help) # Show help on click 254 instagram_layout.addWidget(instagram_help_button) 255 instagram_group.setLayout(instagram_layout) 256 layout.addWidget(instagram_group) 257 258 button_layout = QHBoxLayout() 259 # Save and Load buttons connected to the functions 260 save_button = QPushButton("Save Settings") 261 save_button.clicked.connect(self.save_settings) 262 button_layout.addWidget(save_button) 263 264 load_button = QPushButton("Load Settings") 265 load_button.clicked.connect(self.load_settings) 266 button_layout.addWidget(load_button) 267 268 # Add the button layout to the main layout 269 layout.addLayout(button_layout) 270 271 # Run and Stop buttons 272 button_layout = QHBoxLayout() 273 self.run_button = QPushButton("Run Blackbird") 274 self.run_button.clicked.connect(self.run_blackbird) # Start Blackbird on click 275 button_layout.addWidget(self.run_button) 276 277 self.stop_button = QPushButton("Stop Blackbird") 278 self.stop_button.clicked.connect(self.stop_blackbird) # Stop Blackbird on click 279 self.stop_button.setEnabled(False) # Disable initially 280 button_layout.addWidget(self.stop_button) 281 282 layout.addLayout(button_layout) 283 284 # Output area for displaying logs and results 285 self.output_area = QTextEdit() 286 self.output_area.setReadOnly(True) 287 layout.addWidget(self.output_area) 288 289 # Easter egg setup 290 self.key_sequence = "" 291 self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) 292 293 def setup_ai_api_key(self): 294 """Configure AI API key through TOR for anonymous registration ONLY when TOR is enabled""" 295 self.output_area.clear() 296 self.output_area.append("🔧 Starting API Key setup...") 297 298 # Check if TOR is explicitly enabled by the user 299 tor_enabled = hasattr(self, 'tor_checkbox') and self.tor_checkbox.isChecked() 300 301 if tor_enabled: 302 self.output_area.append("🕶️ TOR enabled - setting up anonymous registration") 303 self.output_area.append("This will register your TOR IP instead of your real IP") 304 305 # Use existing TOR connection 306 tor_port = self.tor_spoofer.tor_port 307 control_port = self.tor_spoofer.control_port 308 tor_password = self.tor_spoofer.tor_password 309 310 # ONLY delete existing API key when TOR is explicitly enabled 311 self.delete_existing_api_key() 312 313 # Start TOR setup 314 self.tor_setup_worker = TORAPISetup(tor_port, control_port, tor_password) 315 self.tor_setup_worker.output_signal.connect(self.update_output) 316 self.tor_setup_worker.finished_signal.connect(self.on_tor_setup_finished) 317 self.tor_setup_worker.start() 318 319 else: 320 # TOR NOT enabled - use direct connection and PRESERVE existing API key 321 self.output_area.append("🔗 Setting up direct connection (TOR not enabled)") 322 self.output_area.append("Your real IP will be registered with Blackbird AI") 323 self.setup_ai_api_key_direct() 324 325 return # Return early since direct setup handles its own flow 326 327 self.run_button.setEnabled(False) 328 self.stop_button.setEnabled(True) 329 330 def delete_existing_api_key(self): 331 """Delete existing API key file ONLY when using TOR for fresh registration""" 332 import os 333 import json 334 335 # Only proceed if TOR is explicitly enabled 336 if not (hasattr(self, 'tor_checkbox') and self.tor_checkbox.isChecked()): 337 return # Don't delete anything if TOR is not enabled 338 339 config_paths = [ 340 os.path.expanduser("~/.ai_key.json"), 341 ".ai_key.json" 342 ] 343 344 for config_path in config_paths: 345 if os.path.exists(config_path): 346 try: 347 os.remove(config_path) 348 self.output_area.append(f"🗑️ Deleted existing API key for TOR registration: {config_path}") 349 break 350 except Exception as e: 351 self.output_area.append(f"⚠️ Could not delete {config_path}: {e}") 352 353 def setup_ai_api_key_direct(self): 354 """Fallback direct setup without TOR""" 355 self.output_area.append("🔄 Starting direct API setup (without TOR)...") 356 357 # Build the setup command 358 command = ["python", "blackbird.py", "--setup-ai"] 359 360 # Create and start the worker for setup 361 self.worker = BlackbirdWorker(" ".join(command), is_setup_ai=True) 362 self.worker.output_signal.connect(self.update_output) 363 self.worker.finished.connect(lambda: self.on_tor_setup_finished(True)) 364 self.worker.start() 365 366 self.run_button.setEnabled(False) 367 self.stop_button.setEnabled(True) 368 369 def on_tor_setup_finished(self, success): 370 """Handle completion of TOR API setup""" 371 self.run_button.setEnabled(True) 372 self.stop_button.setEnabled(False) 373 374 if success: 375 self.output_area.append("✅ API setup completed successfully!") 376 self.check_api_key_config() 377 else: 378 self.output_area.append("❌ API setup failed") 379 380 reply = QMessageBox.question( 381 self, 382 "Setup Failed", 383 "API setup failed.\n\n" 384 "Do you want to try again?", 385 QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No 386 ) 387 388 if reply == QMessageBox.StandardButton.Yes: 389 self.setup_ai_api_key() 390 391 def check_api_key_config(self): 392 """Check if API key was successfully configured""" 393 import os 394 import json 395 396 api_key_found = False 397 config_paths = [ 398 os.path.expanduser("~/.ai_key.json"), 399 ".ai_key.json" 400 ] 401 402 for config_path in config_paths: 403 if os.path.exists(config_path): 404 try: 405 with open(config_path, 'r') as f: 406 config = json.load(f) 407 if config.get("ai_api_key"): 408 api_key_found = True 409 self.ai_api_key = config["ai_api_key"] 410 os.environ["BLACKBIRD_AI_API_KEY"] = config["ai_api_key"] 411 self.output_area.append("✅ AI API Key configured and loaded!") 412 break 413 elif config.get("api_key"): 414 api_key_found = True 415 self.ai_api_key = config["api_key"] 416 os.environ["BLACKBIRD_AI_API_KEY"] = config["api_key"] 417 self.output_area.append("✅ AI API Key configured and loaded!") 418 break 419 except (json.JSONDecodeError, KeyError): 420 continue 421 422 if not api_key_found: 423 self.output_area.append("⚠️ API key setup completed, but couldn't automatically detect the key.") 424 self.output_area.append("You may need to manually check the configuration.") 425 426 def on_setup_finished(self): 427 """Handle completion of AI setup""" 428 self.run_button.setEnabled(True) 429 self.stop_button.setEnabled(False) 430 431 # Check if setup was successful by looking for the API key 432 import os 433 import json 434 import os.path 435 436 api_key_found = False 437 config_paths = [ 438 os.path.expanduser("~/.ai_key.json"), 439 ".ai_key.json" 440 ] 441 442 for config_path in config_paths: 443 if os.path.exists(config_path): 444 try: 445 with open(config_path, 'r') as f: 446 config = json.load(f) 447 if config.get("ai_api_key"): 448 api_key_found = True 449 self.ai_api_key = config["ai_api_key"] 450 os.environ["BLACKBIRD_AI_API_KEY"] = config["ai_api_key"] 451 self.output_area.append("✅ AI API Key setup completed and loaded!") 452 break 453 elif config.get("api_key"): 454 api_key_found = True 455 self.ai_api_key = config["api_key"] 456 os.environ["BLACKBIRD_AI_API_KEY"] = config["api_key"] 457 self.output_area.append("✅ AI API Key setup completed and loaded!") 458 break 459 except (json.JSONDecodeError, KeyError): 460 continue 461 462 if not api_key_found: 463 self.output_area.append("⚠️ API key setup completed, but couldn't automatically detect the key.") 464 self.output_area.append("You may need to manually enter the API key using the 'Setup API Key' button.") 465 466 def keyPressEvent(self, event): 467 super().keyPressEvent(event) 468 self.key_sequence += event.text() 469 470 if 'iddqd' in self.key_sequence.lower(): 471 self.trigger_easter_egg() 472 self.key_sequence = '' 473 474 if len(self.key_sequence) > 10: 475 self.key_sequence = self.key_sequence[-10:] 476 477 def trigger_easter_egg(self): 478 easter_egg = """ 479 Crows mimic, crows are intelligent! 480 """ 481 self.output_area.append("Easter egg activated!") 482 self.output_area.append(easter_egg) 483 484 def select_username_file(self): 485 # Open a file dialog to select a username file and set its path in the input field 486 file_name, _ = QFileDialog.getOpenFileName(self, "Select Username File") 487 if file_name: 488 self.username_file_input.setText(file_name) 489 490 def select_email_file(self): 491 # Open a file dialog to select an email file and set its path in the input field 492 file_name, _ = QFileDialog.getOpenFileName(self, "Select Email File") 493 if file_name: 494 self.email_file_input.setText(file_name) 495 496 def save_settings(self): 497 # Use the modular save_settings function 498 save_settings(self) 499 500 def load_settings(self): 501 # Use the modular load_settings function 502 load_settings(self) 503 504 def show_instagram_help(self): 505 QMessageBox.information(self, "Instagram Session ID Help", 506 "To use enhanced Instagram metadata extraction:\n\n" 507 "1. Log in to Instagram in your browser\n" 508 "2. Open developer tools (F12)\n" 509 "3. Go to Application > Cookies\n" 510 "4. Find the 'sessionid' cookie\n" 511 "5. Copy its value and paste it here") 512 513 def show_filter_help(self): 514 QMessageBox.information(self, "Filter Help", 515 "Create custom search filters using specific properties and operators.\n\n" 516 "Properties: name, cat, uri_check, e_code, e_string, m_string, m_code\n" 517 "Operators: =, ~, >, <, >=, <=, !=\n\n" 518 "Examples:\n" 519 "1. name~Mastodon\n" 520 "2. e_code>200\n" 521 "3. cat=social and uri_check~101010\n" 522 "4. e_string=@101010.pl or m_code<=404\n\n" 523 "Concatenate commands with \"\" for multiple filters.\n\n" 524 "Visit https://p1ngul1n0.gitbook.io/blackbird/advanced-usage") 525 526 def show_permute_help(self): 527 QMessageBox.information(self, "Permute Username Help", 528 "The '--permute' option generates variations of a username.\n\n" 529 "For 'balestek86', permutations include:\n" 530 "balestek86, _balestek86, balestek86_, balestek_86, balestek-86, balestek.86\n" 531 "86balestek, _86balestek, 86balestek_, 86_balestek, 86-balestek, 86.balestek") 532 533 def show_permuteall_help(self): 534 QMessageBox.information(self, "Permute All Elements Help", 535 "The '--permuteall' option generates a broader set of permutations.\n\n" 536 "For 'balestek86', permutations include:\n" 537 "balestek, _balestek, balestek_, 86, _86, 86_,\n" 538 "balestek86, _balestek86, balestek86_, balestek_86, balestek-86, balestek.86,\n" 539 "86balestek, _86balestek, 86balestek_, 86_balestek, 86-balestek, 86.balestek") 540 541 def show_AI_help(self): 542 QMessageBox.information(self, "AI Metadata Help", 543 "The '--ai' option performs AI analysis on found results.\n\n" 544 "Features:\n" 545 "• Generates comprehensive profile summaries\n" 546 "• Identifies profile types and interests\n" 547 "• Provides risk assessment flags\n" 548 "• Adds relevant tags for categorization\n" 549 "• Shows remaining daily AI query quota\n\n" 550 "Results are marked with a robot emoji (🤖) and include:\n" 551 "- Summary of online presence\n" 552 "- Profile type classification\n" 553 "- Key insights and interests\n" 554 "- Risk flags and warnings\n" 555 "- Relevant tags\n\n" 556 "Note: Uses Blackbird AI API with daily query limits.") 557 558 def setup_tor_ui(self, options_layout): 559 """Add TOR configuration to the options group""" 560 # Add TOR section to options group 561 tor_group = QGroupBox("TOR IP Spoofing (for AI)") 562 tor_layout = QHBoxLayout() 563 564 self.tor_checkbox = QCheckBox("Enable TOR for AI requests") 565 self.tor_checkbox.stateChanged.connect(self.toggle_tor_spoofing) 566 tor_layout.addWidget(self.tor_checkbox) 567 568 tor_setup_button = QPushButton("TOR Settings") 569 tor_setup_button.clicked.connect(self.configure_tor_settings) 570 tor_layout.addWidget(tor_setup_button) 571 572 tor_help_button = QPushButton("?") 573 tor_help_button.setFixedSize(30, 30) 574 tor_help_button.clicked.connect(self.show_tor_help) 575 tor_layout.addWidget(tor_help_button) 576 577 tor_group.setLayout(tor_layout) 578 579 # Insert TOR group after AI options but before permute options 580 # We need to find the correct position in the layout 581 # Since we're calling this during setup, we can add it directly 582 options_layout.addWidget(tor_group) 583 584 def toggle_tor_spoofing(self, state): 585 """Enable/disable TOR spoofing""" 586 if state == Qt.CheckState.Checked.value: 587 # Try to enable TOR with default settings 588 if not self.tor_spoofer.enable_tor_for_ai(): 589 QMessageBox.warning(self, "TOR Not Available", 590 "TOR connection failed. Please ensure TOR is running and configured.") 591 self.tor_checkbox.setChecked(False) 592 else: 593 self.tor_spoofer.disable_tor() 594 595 def configure_tor_settings(self): 596 """Open dialog to configure TOR settings""" 597 from PyQt6.QtWidgets import QDialog, QVBoxLayout, QFormLayout, QLineEdit, QDialogButtonBox, QLabel 598 599 dialog = QDialog(self) 600 dialog.setWindowTitle("TOR Configuration") 601 layout = QVBoxLayout() 602 603 form_layout = QFormLayout() 604 605 tor_port_input = QLineEdit(str(self.tor_spoofer.tor_port)) 606 control_port_input = QLineEdit(str(self.tor_spoofer.control_port)) 607 608 # Show current password (masked) but don't allow changing in this version 609 password_info = QLabel(f"Current password: {'*' * 20} (hardcoded for testing)") 610 password_info.setWordWrap(True) 611 612 form_layout.addRow("TOR Port:", tor_port_input) 613 form_layout.addRow("Control Port:", control_port_input) 614 form_layout.addRow("Password:", password_info) 615 616 help_label = QLabel("Note: Using hardcoded password for testing. Port changes will be applied.") 617 help_label.setWordWrap(True) 618 form_layout.addRow("", help_label) 619 620 layout.addLayout(form_layout) 621 622 buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | 623 QDialogButtonBox.StandardButton.Cancel) 624 buttons.accepted.connect(dialog.accept) 625 buttons.rejected.connect(dialog.reject) 626 layout.addWidget(buttons) 627 628 dialog.setLayout(layout) 629 630 if dialog.exec() == QDialog.DialogCode.Accepted: 631 try: 632 self.tor_spoofer.tor_port = int(tor_port_input.text()) 633 self.tor_spoofer.control_port = int(control_port_input.text()) 634 635 # Test new configuration 636 if self.tor_checkbox.isChecked(): 637 if not self.tor_spoofer.enable_tor_for_ai(): 638 QMessageBox.warning(self, "TOR Configuration Failed", 639 "New TOR settings are invalid.") 640 self.tor_checkbox.setChecked(False) 641 else: 642 QMessageBox.information(self, "Success", "TOR configuration updated successfully!") 643 644 except ValueError: 645 QMessageBox.warning(self, "Invalid Input", "Port numbers must be integers.") 646 647 def show_tor_help(self): 648 """Show TOR help information""" 649 QMessageBox.information(self, "TOR IP Spoofing Help", 650 "TOR IP Spoofing for AI Requests:\n\n" 651 "• Routes AI API calls through TOR network\n" 652 "• Automatically renews IP for each request\n" 653 "• Enhances privacy and anonymity\n" 654 "• Bypasses IP-based rate limits\n\n" 655 "Requirements:\n" 656 "• TOR service must be running locally\n" 657 "• Default ports: 9050 (TOR), 9051 (control)\n" 658 "• TOR control protocol enabled\n\n" 659 "Note: This only affects AI metadata extraction requests.") 660 661 662 def run_blackbird(self): 663 # Check if AI is enabled but no API key is set 664 if self.AI_checkbox.isChecked(): 665 # If TOR is also enabled, set it up 666 if hasattr(self, 'tor_checkbox') and self.tor_checkbox.isChecked(): 667 if not self.tor_spoofer.tor_enabled: 668 if not self.tor_spoofer.enable_tor_for_ai(): 669 self.output_area.append("⚠️ TOR spoofing failed, continuing with direct connection") 670 671 import os 672 import json 673 import os.path 674 675 # Check multiple locations for API key 676 api_key_found = False 677 678 # 1. Check environment variable 679 if os.environ.get("BLACKBIRD_AI_API_KEY"): 680 api_key_found = True 681 682 # 2. Check Blackbird config file 683 if not api_key_found: 684 config_paths = [ 685 os.path.expanduser(".ai_key.json"), 686 ".ai_key.json", # Current directory 687 ] 688 689 for config_path in config_paths: 690 if os.path.exists(config_path): 691 try: 692 with open(config_path, 'r') as f: 693 config = json.load(f) 694 if config.get("ai_api_key") or config.get("api_key"): 695 api_key_found = True 696 break 697 except (json.JSONDecodeError, KeyError): 698 continue 699 700 # 3. Check if we have a stored API key in the GUI instance 701 if not api_key_found and hasattr(self, 'ai_api_key') and self.ai_api_key: 702 api_key_found = True 703 704 if not api_key_found: 705 reply = QMessageBox.question( 706 self, 707 "AI API Key Required", 708 "AI analysis requires an API key. Would you like to configure it now?", 709 QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No 710 ) 711 if reply == QMessageBox.StandardButton.Yes: 712 self.setup_ai_api_key() 713 return # Don't proceed with regular search until setup is complete 714 else: 715 QMessageBox.warning(self, "Warning", "AI analysis disabled - no API key configured.") 716 self.AI_checkbox.setChecked(False) 717 718 # Rest of the existing run_blackbird method... 719 if self.worker and self.worker.isRunning(): 720 self.worker.terminate() 721 self.worker.wait() 722 723 # Get all input values 724 username_input = self.username_input.text() 725 email_input = self.email_input.text() 726 username_file_input = self.username_file_input.text() 727 email_file_input = self.email_file_input.text() 728 permute_checkbox = self.permute_checkbox.isChecked() 729 permuteall_checkbox = self.permuteall_checkbox.isChecked() 730 AI_checkbox = self.AI_checkbox.isChecked() 731 no_nsfw_checkbox = self.no_nsfw_checkbox.isChecked() 732 no_update_checkbox = self.no_update_checkbox.isChecked() 733 csv_checkbox = self.csv_checkbox.isChecked() 734 pdf_checkbox = self.pdf_checkbox.isChecked() 735 json_checkbox = self.json_checkbox.isChecked() 736 verbose_checkbox = self.verbose_checkbox.isChecked() 737 dump_checkbox = self.dump_checkbox.isChecked() 738 proxy_input = self.proxy_input.text() 739 timeout_spinbox = self.timeout_spinbox.value() 740 filter_input = self.filter_input.text() 741 instagram_session_id = self.instagram_session_id.text() 742 743 # Show AI info if enabled 744 if AI_checkbox: 745 self.output_area.append("🤖 AI Analysis Enabled") 746 self.output_area.append("Note: This will analyze results using Blackbird AI") 747 self.output_area.append("") 748 749 command = build_blackbird_command(username_input, email_input, username_file_input, 750 email_file_input, permute_checkbox, permuteall_checkbox, 751 AI_checkbox, no_nsfw_checkbox, no_update_checkbox, 752 csv_checkbox, pdf_checkbox, json_checkbox, verbose_checkbox, 753 dump_checkbox, proxy_input, timeout_spinbox, 754 filter_input, instagram_session_id) 755 756 self.output_area.clear() 757 # Pass AI_checkbox to determine if we need to auto-confirm 758 self.worker = BlackbirdWorker(" ".join(command), needs_ai_confirmation=AI_checkbox) 759 self.worker.output_signal.connect(self.update_output) 760 self.worker.finished.connect(self.on_worker_finished) 761 self.worker.start() 762 763 self.run_button.setEnabled(False) 764 self.stop_button.setEnabled(True) 765 766 def stop_blackbird(self): 767 # Stop the Blackbird process if running 768 if self.worker and self.worker.isRunning(): 769 self.worker.terminate() 770 self.worker.wait() 771 self.run_button.setEnabled(True) # Re-enable Run button 772 self.stop_button.setEnabled(False) # Disable Stop button 773 774 def update_output(self, text): 775 # Initialize AI results buffer if it doesn't exist 776 if not hasattr(self, 'ai_results_buffer'): 777 self.ai_results_buffer = [] 778 self.ai_results_started = False 779 780 # Check if AI analysis is starting 781 if 'analyzing with ai' in text.lower() or '✨ analyzing with ai' in text.lower(): 782 self.ai_results_started = True 783 self.ai_results_buffer = [ 784 "🤖 BLACKBIRD AI ANALYSIS REPORT", 785 "=" * 60, 786 f"Generated: {self.get_current_timestamp()}", 787 "=" * 60, 788 "" 789 ] 790 791 # Add search context 792 username = self.username_input.text().strip() 793 email = self.email_input.text().strip() 794 if username: 795 self.ai_results_buffer.append(f"Target Username: {username}") 796 if email: 797 self.ai_results_buffer.append(f"Target Email: {email}") 798 if username or email: 799 self.ai_results_buffer.append("") 800 801 # Buffer AI results 802 if self.ai_results_started: 803 # Clean and format the text for file output 804 clean_text = text.replace('🤖', '').replace('📊', '').strip() 805 self.ai_results_buffer.append(clean_text) 806 807 # Check if AI analysis is complete 808 if 'ai queries left' in text.lower(): 809 self.ai_results_buffer.extend([ 810 "", 811 "=" * 60, 812 f"Analysis complete - {self.get_current_timestamp()}", 813 "=" * 60 814 ]) 815 self.auto_save_ai_results() # Auto-save instead of dialog 816 self.ai_results_started = False 817 818 # Format for GUI display 819 formatted_text = self.format_ai_text_for_gui(text) 820 821 # Update GUI 822 self.append_to_output_area(formatted_text) 823 824 def format_ai_text_for_gui(self, text): 825 """Format AI text for GUI display with emojis""" 826 if any(keyword in text.lower() for keyword in ['analyzing with ai', 'ai queries left', '✨']): 827 return f"🤖 {text}" 828 elif text.startswith('[Summary]'): 829 return f"📋 {text}" 830 elif text.startswith('[Profile Type]'): 831 return f"🎯 {text}" 832 elif text.startswith('[Insights]'): 833 return f"💡 {text}" 834 elif text.startswith('[Risk Flags]'): 835 return f"⚠️ {text}" 836 elif text.startswith('[Tags]'): 837 return f"🏷️ {text}" 838 else: 839 return text 840 841 def get_current_timestamp(self): 842 """Get current timestamp for file naming and reports""" 843 from datetime import datetime 844 return datetime.now().strftime("%Y-%m-%d %H:%M:%S") 845 846 def auto_save_ai_results(self): 847 """Automatically save AI results to file""" 848 if not self.ai_results_buffer: 849 return 850 851 try: 852 # Generate filename with timestamp and target info 853 from datetime import datetime 854 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") 855 856 username = self.username_input.text().strip() 857 email = self.email_input.text().strip() 858 859 # Create descriptive filename 860 if username: 861 base_name = username 862 elif email: 863 base_name = email.split('@')[0] 864 else: 865 base_name = "analysis" 866 867 # Clean filename 868 safe_name = re.sub(r'[^\w\-_.]', '_', base_name) 869 filename = f"blackbird_ai_{safe_name}_{timestamp}.txt" 870 871 # Directory path where you want to save the file (e.g., "output_files") 872 directory_path = "results" 873 874 # Create directory if it doesn't exist 875 if not os.path.exists(directory_path): 876 os.makedirs(directory_path) 877 878 # Full file path with directory 879 full_path = os.path.join(directory_path, filename) 880 881 # Save to file 882 with open(full_path, 'w', encoding='utf-8') as f: 883 f.write('\n'.join(self.ai_results_buffer)) 884 885 # Notify user 886 self.append_to_output_area(f"💾 AI results auto-saved to: {filename}") 887 888 except Exception as e: 889 self.append_to_output_area(f"❌ Error auto-saving AI results: {e}") 890 891 def append_to_output_area(self, text): 892 """Helper method to append text to output area with auto-scroll""" 893 scrollbar = self.output_area.verticalScrollBar() 894 was_at_bottom = scrollbar.value() == scrollbar.maximum() 895 896 self.output_area.append(text) 897 898 if was_at_bottom: 899 scrollbar.setValue(scrollbar.maximum()) 900 901 def on_worker_finished(self): 902 # Re-enable the Run button and disable the Stop button when worker finishes 903 self.run_button.setEnabled(True) 904 self.stop_button.setEnabled(False) 905 906 if __name__ == "__main__": 907 # Create and run the application 908 app = QApplication(sys.argv) 909 window = BlackbirdGUI() 910 window.show() 911 sys.exit(app.exec())