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