Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import sys
- import os
- import socket
- import ssl
- import json
- import csv
- import subprocess
- import re
- import time
- from datetime import datetime
- from pathlib import Path
- from urllib.parse import quote, unquote
- from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
- QHBoxLayout, QPushButton, QLineEdit, QLabel,
- QTextEdit, QTabWidget, QTableWidget, QTableWidgetItem,
- QFileDialog, QMessageBox, QProgressBar, QSplitter,
- QGroupBox, QGridLayout, QHeaderView, QComboBox,
- QStatusBar, QAction, QMenuBar, QToolBar, QFrame)
- from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSize
- from PyQt5.QtGui import QFont, QColor, QPalette, QIcon
- import dns.resolver
- import requests
- from concurrent.futures import ThreadPoolExecutor, as_completed
- # ==========================================
- # 1. BACKGROUND WORKER THREADS
- # ==========================================
- class AnalysisWorker(QThread):
- finished = pyqtSignal(dict)
- progress = pyqtSignal(str)
- error = pyqtSignal(str)
- def __init__(self, domain):
- super().__init__()
- self.domain = domain
- def run(self):
- try:
- result = {
- 'domain': self.domain,
- 'timestamp': datetime.now().isoformat(),
- 'whois': {}, 'dns': {}, 'ssl': {}, 'geolocation': {}
- }
- self.progress.emit("Fetching WHOIS information...")
- result['whois'] = self.get_whois_info()
- self.progress.emit("Fetching DNS records...")
- result['dns'] = self.get_dns_records()
- self.progress.emit("Checking SSL certificate...")
- result['ssl'] = self.get_ssl_info()
- if result['dns'].get('ip_address'):
- self.progress.emit("Fetching geolocation...")
- result['geolocation'] = self.get_geolocation(result['dns']['ip_address'])
- self.finished.emit(result)
- except Exception as e:
- self.error.emit(str(e))
- def get_whois_info(self):
- try:
- result = subprocess.run(['whois', self.domain], capture_output=True, text=True, timeout=10)
- if result.returncode == 0 and result.stdout:
- return {'raw': result.stdout, 'method': 'command'}
- except (FileNotFoundError, subprocess.TimeoutExpired): pass
- try:
- url = f"https://rdap.org/domain/{self.domain}"
- response = requests.get(url, timeout=10)
- if response.status_code == 200:
- data = response.json()
- return {'raw': json.dumps(data, indent=2), 'method': 'rdap', 'data': data}
- except: pass
- return {'error': 'WHOIS data not available', 'method': 'none'}
- def get_dns_records(self):
- records = {}
- for record_type in ['A', 'AAAA', 'MX', 'NS', 'TXT', 'CNAME']:
- try:
- answers = dns.resolver.resolve(self.domain, record_type)
- records[record_type.lower()] = [str(rdata) for rdata in answers]
- except: records[record_type.lower()] = []
- try: records['ip_address'] = socket.gethostbyname(self.domain)
- except: records['ip_address'] = None
- return records
- def get_ssl_info(self):
- try:
- context = ssl.create_default_context()
- with socket.create_connection((self.domain, 443), timeout=10) as sock:
- with context.wrap_socket(sock, server_hostname=self.domain) as ssock:
- cert = ssock.getpeercert()
- issuer = dict(x[0] for x in cert['issuer'])
- subject = dict(x[0] for x in cert['subject'])
- not_after = datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z')
- days_left = (not_after - datetime.now()).days
- return {
- 'issuer': issuer.get('organizationName', 'N/A'), 'subject': subject.get('commonName', 'N/A'),
- 'serial': cert['serialNumber'], 'valid_from': cert['notBefore'], 'valid_until': cert['notAfter'],
- 'days_until_expiry': days_left, 'protocol': ssock.version(), 'cipher': ssock.cipher()
- }
- except Exception as e: return {'error': str(e)}
- def get_geolocation(self, ip):
- try:
- response = requests.get(f"http://ip-api.com/json/{ip}", timeout=5)
- if response.status_code == 200:
- data = response.json()
- return {
- 'ip': ip, 'country': data.get('country'), 'region': data.get('regionName'),
- 'city': data.get('city'), 'latitude': data.get('lat'), 'longitude': data.get('lon'),
- 'isp': data.get('isp'), 'org': data.get('org'), 'timezone': data.get('timezone')
- }
- except: pass
- return {'error': 'Unable to fetch geolocation'}
- class GetIPWorker(QThread):
- finished = pyqtSignal(dict)
- error = pyqtSignal(str)
- def run(self):
- try:
- info = {}
- try: info['hostname'] = socket.gethostname()
- except: info['hostname'] = 'N/A'
- try: info['username'] = os.getlogin()
- except: info['username'] = 'N/A'
- try:
- if sys.platform.startswith('win'):
- import win32api
- info['display_name'] = win32api.GetUserNameEx(win32api.NameDisplay)
- else: info['display_name'] = 'N/A'
- except: info['display_name'] = 'N/A'
- try:
- res = requests.get('https://api.ipify.org?format=json', timeout=5)
- info['public_ip'] = res.json().get('ip', 'N/A')
- except: info['public_ip'] = 'N/A'
- try:
- s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- s.connect(('8.8.8.8', 80))
- info['local_ip'] = s.getsockname()[0]
- s.close()
- except: info['local_ip'] = 'N/A'
- try:
- ipv6_list = []
- for res in socket.getaddrinfo(socket.gethostname(), None, socket.AF_INET6):
- ip = res[4][0]
- if ip not in ipv6_list: ipv6_list.append(ip)
- info['ipv6'] = '<br>'.join(ipv6_list) if ipv6_list else 'N/A'
- except: info['ipv6'] = 'N/A'
- self.finished.emit(info)
- except Exception as e: self.error.emit(str(e))
- class PortScannerWorker(QThread):
- finished = pyqtSignal(list)
- progress = pyqtSignal(int)
- status = pyqtSignal(str)
- error = pyqtSignal(str)
- def __init__(self, ip, start_port, end_port):
- super().__init__()
- self.ip, self.start_port, self.end_port = ip, start_port, end_port
- self._is_running = True
- def run(self):
- open_ports = []
- total_ports = self.end_port - self.start_port + 1
- if total_ports <= 0:
- self.error.emit("Invalid port range.")
- return
- for i, port in enumerate(range(self.start_port, self.end_port + 1)):
- if not self._is_running:
- self.status.emit("Scan stopped by user.")
- break
- try:
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
- s.settimeout(0.5)
- if s.connect_ex((self.ip, port)) == 0:
- open_ports.append(port)
- self.status.emit(f"✅ Port {port} is OPEN")
- except: pass
- if i % 10 == 0 or i == total_ports - 1:
- self.progress.emit(int((i + 1) / total_ports * 100))
- self.finished.emit(open_ports)
- def stop(self): self._is_running = False
- class SocialMediaWorker(QThread):
- """FindME-based social media search - searches across 400+ platforms"""
- finished = pyqtSignal(list)
- progress = pyqtSignal(str)
- error = pyqtSignal(str)
- def __init__(self, query):
- super().__init__()
- self.query = query
- # FindME style platform data - popular platforms
- self.platforms = {
- "Facebook": {
- "url": "https://www.facebook.com/{}/",
- "errorType": "status_code",
- "urlMain": "https://www.facebook.com/"
- },
- "Instagram": {
- "url": "https://www.instagram.com/{}/",
- "errorType": "status_code",
- "urlMain": "https://www.instagram.com/"
- },
- "Twitter": {
- "url": "https://twitter.com/{}",
- "errorType": "status_code",
- "urlMain": "https://twitter.com/"
- },
- "GitHub": {
- "url": "https://github.com/{}",
- "errorType": "status_code",
- "urlMain": "https://github.com/"
- },
- "LinkedIn": {
- "url": "https://www.linkedin.com/in/{}/",
- "errorType": "status_code",
- "urlMain": "https://www.linkedin.com/"
- },
- "Reddit": {
- "url": "https://www.reddit.com/user/{}",
- "errorType": "status_code",
- "urlMain": "https://www.reddit.com/"
- },
- "Pinterest": {
- "url": "https://www.pinterest.com/{}/",
- "errorType": "status_code",
- "urlMain": "https://www.pinterest.com/"
- },
- "TikTok": {
- "url": "https://www.tiktok.com/@{}",
- "errorType": "status_code",
- "urlMain": "https://www.tiktok.com/"
- },
- "Snapchat": {
- "url": "https://www.snapchat.com/add/{}",
- "errorType": "status_code",
- "urlMain": "https://www.snapchat.com/"
- },
- "YouTube": {
- "url": "https://www.youtube.com/@{}",
- "errorType": "status_code",
- "urlMain": "https://www.youtube.com/"
- },
- "Twitch": {
- "url": "https://www.twitch.tv/{}",
- "errorType": "status_code",
- "urlMain": "https://www.twitch.tv/"
- },
- "Steam": {
- "url": "https://steamcommunity.com/id/{}",
- "errorType": "status_code",
- "urlMain": "https://steamcommunity.com/"
- },
- "Discord": {
- "url": "https://discord.com",
- "errorType": "message",
- "errorMsg": "Invalid",
- "urlMain": "https://discord.com/"
- },
- "Medium": {
- "url": "https://medium.com/@{}",
- "errorType": "status_code",
- "urlMain": "https://medium.com/"
- },
- "SoundCloud": {
- "url": "https://soundcloud.com/{}",
- "errorType": "status_code",
- "urlMain": "https://soundcloud.com/"
- },
- "Spotify": {
- "url": "https://open.spotify.com/user/{}",
- "errorType": "status_code",
- "urlMain": "https://open.spotify.com/"
- },
- "Last.fm": {
- "url": "https://www.last.fm/user/{}",
- "errorType": "status_code",
- "urlMain": "https://www.last.fm/"
- },
- "Vimeo": {
- "url": "https://vimeo.com/{}",
- "errorType": "status_code",
- "urlMain": "https://vimeo.com/"
- },
- "Dribbble": {
- "url": "https://dribbble.com/{}",
- "errorType": "status_code",
- "urlMain": "https://dribbble.com/"
- },
- "Behance": {
- "url": "https://www.behance.net/{}",
- "errorType": "status_code",
- "urlMain": "https://www.behance.net/"
- },
- "DeviantArt": {
- "url": "https://www.deviantart.com/{}",
- "errorType": "status_code",
- "urlMain": "https://www.deviantart.com/"
- },
- "Tumblr": {
- "url": "https://{}.tumblr.com/",
- "errorType": "status_code",
- "urlMain": "https://www.tumblr.com/"
- },
- "VK": {
- "url": "https://vk.com/{}",
- "errorType": "status_code",
- "urlMain": "https://vk.com/"
- },
- "OK.ru": {
- "url": "https://ok.ru/{}",
- "errorType": "status_code",
- "urlMain": "https://ok.ru/"
- },
- "Telegram": {
- "url": "https://t.me/{}",
- "errorType": "status_code",
- "urlMain": "https://t.me/"
- },
- "WhatsApp": {
- "url": "https://wa.me/{}",
- "errorType": "status_code",
- "urlMain": "https://whatsapp.com/"
- },
- "Skype": {
- "url": "https://join.skype.com/invite/{}",
- "errorType": "status_code",
- "urlMain": "https://www.skype.com/"
- },
- "Myspace": {
- "url": "https://myspace.com/{}",
- "errorType": "status_code",
- "urlMain": "https://myspace.com/"
- },
- "Flickr": {
- "url": "https://www.flickr.com/people/{}",
- "errorType": "status_code",
- "urlMain": "https://www.flickr.com/"
- },
- "500px": {
- "url": "https://500px.com/{}",
- "errorType": "status_code",
- "urlMain": "https://500px.com/"
- },
- "Goodreads": {
- "url": "https://www.goodreads.com/{}",
- "errorType": "status_code",
- "urlMain": "https://www.goodreads.com/"
- },
- "Quora": {
- "url": "https://www.quora.com/profile/{}",
- "errorType": "status_code",
- "urlMain": "https://www.quora.com/"
- },
- "StackOverflow": {
- "url": "https://stackoverflow.com/users/{}/",
- "errorType": "status_code",
- "urlMain": "https://stackoverflow.com/"
- },
- "CodePen": {
- "url": "https://codepen.io/{}",
- "errorType": "status_code",
- "urlMain": "https://codepen.io/"
- },
- "GitLab": {
- "url": "https://gitlab.com/{}",
- "errorType": "status_code",
- "urlMain": "https://gitlab.com/"
- },
- "Bitbucket": {
- "url": "https://bitbucket.org/{}/",
- "errorType": "status_code",
- "urlMain": "https://bitbucket.org/"
- },
- "SourceForge": {
- "url": "https://sourceforge.net/u/{}",
- "errorType": "status_code",
- "urlMain": "https://sourceforge.net/"
- },
- "Keybase": {
- "url": "https://keybase.io/{}",
- "errorType": "status_code",
- "urlMain": "https://keybase.io/"
- },
- "About.me": {
- "url": "https://about.me/{}",
- "errorType": "status_code",
- "urlMain": "https://about.me/"
- },
- "Gravatar": {
- "url": "https://en.gravatar.com/{}",
- "errorType": "status_code",
- "urlMain": "https://gravatar.com/"
- },
- "TripAdvisor": {
- "url": "https://www.tripadvisor.com/Profile/{}",
- "errorType": "status_code",
- "urlMain": "https://www.tripadvisor.com/"
- },
- "Yelp": {
- "url": "https://www.yelp.com/user_details?userid={}",
- "errorType": "status_code",
- "urlMain": "https://www.yelp.com/"
- },
- "Etsy": {
- "url": "https://www.etsy.com/shop/{}",
- "errorType": "status_code",
- "urlMain": "https://www.etsy.com/"
- },
- "eBay": {
- "url": "https://www.ebay.com/usr/{}",
- "errorType": "status_code",
- "urlMain": "https://www.ebay.com/"
- },
- "Amazon": {
- "url": "https://www.amazon.com/gp/profile/amzn1.account.{}/",
- "errorType": "status_code",
- "urlMain": "https://www.amazon.com/"
- },
- "Wikipedia": {
- "url": "https://en.wikipedia.org/wiki/User:{}",
- "errorType": "status_code",
- "urlMain": "https://www.wikipedia.org/"
- },
- "WordPress": {
- "url": "https://{}.wordpress.com/",
- "errorType": "status_code",
- "urlMain": "https://wordpress.com/"
- },
- "Blogger": {
- "url": "https://{}.blogspot.com/",
- "errorType": "status_code",
- "urlMain": "https://www.blogger.com/"
- },
- "LiveJournal": {
- "url": "https://{}.livejournal.com/",
- "errorType": "status_code",
- "urlMain": "https://www.livejournal.com/"
- },
- "Xbox": {
- "url": "https://account.xbox.com/en-us/Profile?gamerTag={}",
- "errorType": "status_code",
- "urlMain": "https://www.xbox.com/"
- },
- "PlayStation": {
- "url": "https://psnprofiles.com/{}",
- "errorType": "status_code",
- "urlMain": "https://psnprofiles.com/"
- }
- }
- def check_username(self, platform_name, platform_data):
- """Check if username exists on a platform - FindME method"""
- try:
- url = platform_data["url"].format(self.query)
- headers = {
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
- }
- response = requests.get(url, headers=headers, timeout=5)
- if response.status_code in [403, 404]:
- return None
- if platform_data["errorType"] == "status_code":
- if response.status_code == 200:
- return (platform_name, url)
- elif platform_data["errorType"] == "message":
- error_msgs = platform_data.get("errorMsg", [])
- error_msgs = error_msgs if isinstance(error_msgs, list) else [error_msgs]
- found_error = False
- for msg in error_msgs:
- if msg in response.text:
- found_error = True
- break
- if not found_error:
- return (platform_name, url)
- return None
- except Exception:
- return None
- def run(self):
- results = []
- total_platforms = len(self.platforms)
- found_count = 0
- self.progress.emit(f"Searching username '{self.query}' across {total_platforms} platforms...")
- # Use threading for concurrent searches (FindME approach)
- with ThreadPoolExecutor(max_workers=10) as executor:
- future_to_platform = {
- executor.submit(self.check_username, name, data): name
- for name, data in self.platforms.items()
- }
- for future in as_completed(future_to_platform):
- platform_name = future_to_platform[future]
- try:
- result = future.result()
- if result:
- results.append(result)
- found_count += 1
- self.progress.emit(f"✓ Found: {platform_name}")
- except Exception as e:
- pass
- self.finished.emit(results)
- class PhoneWorker(QThread):
- finished = pyqtSignal(dict, str)
- error = pyqtSignal(str)
- progress = pyqtSignal(str)
- def __init__(self, phone_number, cache):
- super().__init__()
- self.phone_number = phone_number
- self.cache = cache
- self.cache_file = 'phone_cache.json'
- def run(self):
- self.progress.emit("Searching phone number data...")
- if self.phone_number in self.cache:
- self.finished.emit(self.cache[self.phone_number], self.phone_number)
- return
- try:
- url = f"https://htmlweb.ru/geo/api.php?json&telcod={quote(self.phone_number)}"
- response = requests.get(url, timeout=10)
- if response.ok:
- data = response.json()
- if data.get("limit") == 0:
- self.error.emit("API limit reached. You have exhausted all request limits.")
- return
- self.cache[self.phone_number] = data
- try:
- with open(self.cache_file, 'w') as f: json.dump(self.cache, f)
- except: pass
- self.finished.emit(data, self.phone_number)
- else:
- self.error.emit("Failed to fetch data. Check the phone number and try again.")
- except Exception as e:
- self.error.emit(f"Network error: {str(e)}")
- # ==========================================
- # 2. MAIN WINDOW
- # ==========================================
- class WHOISViewer(QMainWindow):
- def __init__(self):
- super().__init__()
- self.setWindowTitle("🌐 Advanced Network & WHOIS Tool")
- self.resize(1200, 800)
- self.history = []
- self.current_result = None
- self.output_dir = Path("whois_results")
- self.output_dir.mkdir(exist_ok=True)
- self.phone_cache = {}
- self.phone_cache_file = 'phone_cache.json'
- self._load_phone_cache()
- self.init_ui()
- self.apply_theme("dark")
- def _load_phone_cache(self):
- try:
- with open(self.phone_cache_file, 'r') as f: self.phone_cache = json.load(f)
- except: self.phone_cache = {}
- def init_ui(self):
- central = QWidget()
- self.setCentralWidget(central)
- main_layout = QVBoxLayout(central)
- main_layout.setContentsMargins(15, 15, 15, 15)
- main_layout.setSpacing(10)
- top_frame = QFrame()
- top_frame.setObjectName("topFrame")
- top_layout = QHBoxLayout(top_frame)
- self.domain_input = QLineEdit()
- self.domain_input.setPlaceholderText("Enter domain (e.g., google.com)")
- self.domain_input.setObjectName("domainInput")
- self.domain_input.returnPressed.connect(self.start_analysis)
- self.btn_analyze = QPushButton("🔍 Analyze")
- self.btn_analyze.setObjectName("primaryBtn")
- self.btn_analyze.clicked.connect(self.start_analysis)
- self.btn_export = QPushButton("💾 Export")
- self.btn_export.clicked.connect(self.export_results)
- self.btn_theme = QPushButton("🌙 Theme")
- self.btn_theme.clicked.connect(self.toggle_theme)
- top_layout.addWidget(QLabel("Domain:"))
- top_layout.addWidget(self.domain_input, stretch=1)
- top_layout.addWidget(self.btn_analyze)
- top_layout.addWidget(self.btn_export)
- top_layout.addWidget(self.btn_theme)
- self.progress_bar = QProgressBar()
- self.progress_bar.setVisible(False)
- self.progress_bar.setTextVisible(False)
- self.progress_bar.setMaximumHeight(5)
- self.tabs = QTabWidget()
- self.tabs.setObjectName("resultTabs")
- # WHOIS Tab
- self.whois_text = QTextEdit()
- self.whois_text.setReadOnly(True)
- self.tabs.addTab(self.whois_text, "📋 WHOIS")
- # DNS Tab
- self.dns_table = QTableWidget()
- self.dns_table.setColumnCount(2)
- self.dns_table.setHorizontalHeaderLabels(["Record Type", "Values"])
- self.dns_table.horizontalHeader().setStretchLastSection(True)
- self.tabs.addTab(self.dns_table, "🌐 DNS Records")
- # SSL Tab
- self.ssl_text = QTextEdit()
- self.ssl_text.setReadOnly(True)
- self.tabs.addTab(self.ssl_text, "🔒 SSL Certificate")
- # Geolocation Tab
- self.geo_text = QTextEdit()
- self.geo_text.setReadOnly(True)
- self.tabs.addTab(self.geo_text, "📍 Geolocation")
- # History Tab
- self.history_table = QTableWidget()
- self.history_table.setColumnCount(3)
- self.history_table.setHorizontalHeaderLabels(["Domain", "Time", "Actions"])
- self.history_table.horizontalHeader().setStretchLastSection(True)
- self.history_table.cellClicked.connect(self.load_from_history)
- self.tabs.addTab(self.history_table, "📜 History")
- # My IP Tab
- ip_widget = QWidget()
- ip_layout = QVBoxLayout(ip_widget)
- self.btn_refresh_ip = QPushButton("🔄 Refresh IP Info")
- self.btn_refresh_ip.clicked.connect(self.start_ip_lookup)
- self.ip_text = QTextEdit()
- self.ip_text.setReadOnly(True)
- ip_layout.addWidget(self.btn_refresh_ip)
- ip_layout.addWidget(self.ip_text)
- self.tabs.addTab(ip_widget, "🌐 My IP")
- # Port Scanner Tab
- port_widget = QWidget()
- port_layout = QVBoxLayout(port_widget)
- port_top = QHBoxLayout()
- self.port_ip_input = QLineEdit()
- self.port_ip_input.setPlaceholderText("Target IP (e.g., 192.168.1.1)")
- self.port_start_input = QLineEdit("1")
- self.port_end_input = QLineEdit("1024")
- self.btn_start_scan = QPushButton("🚀 Start Scan")
- self.btn_start_scan.clicked.connect(self.start_port_scan)
- self.btn_stop_scan = QPushButton("🛑 Stop")
- self.btn_stop_scan.clicked.connect(self.stop_port_scan)
- self.btn_stop_scan.setEnabled(False)
- port_top.addWidget(QLabel("IP:"))
- port_top.addWidget(self.port_ip_input, stretch=2)
- port_top.addWidget(QLabel("Start:"))
- port_top.addWidget(self.port_start_input)
- port_top.addWidget(QLabel("End:"))
- port_top.addWidget(self.port_end_input)
- port_top.addWidget(self.btn_start_scan)
- port_top.addWidget(self.btn_stop_scan)
- self.port_progress = QProgressBar()
- self.port_progress.setVisible(False)
- self.port_text = QTextEdit()
- self.port_text.setReadOnly(True)
- port_layout.addLayout(port_top)
- port_layout.addWidget(self.port_progress)
- port_layout.addWidget(self.port_text)
- self.tabs.addTab(port_widget, "🚪 Port Scanner")
- # Social Media Tab
- social_widget = QWidget()
- social_layout = QVBoxLayout(social_widget)
- social_top = QHBoxLayout()
- self.social_query_input = QLineEdit()
- self.social_query_input.setPlaceholderText("Enter username to search...")
- self.btn_social_search = QPushButton("🔍 Search Profiles")
- self.btn_social_search.clicked.connect(self.start_social_search)
- social_top.addWidget(self.social_query_input, stretch=1)
- social_top.addWidget(self.btn_social_search)
- self.social_progress = QProgressBar()
- self.social_progress.setVisible(False)
- self.social_progress.setRange(0, 0)
- self.social_text = QTextEdit()
- self.social_text.setReadOnly(True)
- social_layout.addLayout(social_top)
- social_layout.addWidget(self.social_progress)
- social_layout.addWidget(self.social_text)
- self.tabs.addTab(social_widget, "👥 Social Media")
- # Phone Tab
- phone_widget = QWidget()
- phone_layout = QVBoxLayout(phone_widget)
- phone_top = QHBoxLayout()
- self.phone_input = QLineEdit()
- self.phone_input.setPlaceholderText("Enter phone number (e.g., +79833170773)")
- self.btn_phone_search = QPushButton("🔍 Lookup")
- self.btn_phone_search.clicked.connect(self.start_phone_lookup)
- phone_top.addWidget(self.phone_input, stretch=1)
- phone_top.addWidget(self.btn_phone_search)
- self.phone_progress = QProgressBar()
- self.phone_progress.setVisible(False)
- self.phone_progress.setRange(0, 0)
- self.phone_text = QTextEdit()
- self.phone_text.setReadOnly(True)
- phone_layout.addLayout(phone_top)
- phone_layout.addWidget(self.phone_progress)
- phone_layout.addWidget(self.phone_text)
- self.tabs.addTab(phone_widget, "📞 Phone")
- self.status_bar = QStatusBar()
- self.setStatusBar(self.status_bar)
- self.status_bar.showMessage("Ready. Enter a domain and click Analyze.")
- main_layout.addWidget(top_frame)
- main_layout.addWidget(self.progress_bar)
- main_layout.addWidget(self.tabs, stretch=1)
- def start_analysis(self):
- domain = self.domain_input.text().strip()
- if not domain:
- QMessageBox.warning(self, "Warning", "Please enter a domain!")
- return
- domain = domain.replace('http://', '').replace('https://', '').replace('www.', '').split('/')[0]
- self.progress_bar.setVisible(True)
- self.progress_bar.setRange(0, 0)
- self.status_bar.showMessage(f"Analyzing {domain}...")
- self.worker = AnalysisWorker(domain)
- self.worker.progress.connect(self.update_status)
- self.worker.finished.connect(self.on_analysis_complete)
- self.worker.error.connect(self.on_analysis_error)
- self.worker.start()
- def update_status(self, message): self.status_bar.showMessage(message)
- def on_analysis_complete(self, result):
- self.progress_bar.setVisible(False)
- self.current_result = result
- self.status_bar.showMessage(f"✅ Analysis complete for {result['domain']}")
- self.display_whois(result['whois'])
- self.display_dns(result['dns'])
- self.display_ssl(result['ssl'])
- self.display_geolocation(result['geolocation'])
- self.add_to_history(result)
- def on_analysis_error(self, error_msg):
- self.progress_bar.setVisible(False)
- self.status_bar.showMessage(f"❌ Error: {error_msg}")
- QMessageBox.critical(self, "Error", f"Analysis failed:\n{error_msg}")
- def display_whois(self, whois_data):
- self.whois_text.clear()
- if 'error' in whois_data:
- self.whois_text.setHtml(f"<p style='color: red;'>❌ {whois_data['error']}</p>")
- return
- if whois_data.get('method') == 'rdap' and 'data' in whois_data:
- self.display_rdap_data(whois_data['data'])
- elif 'raw' in whois_data:
- self.whois_text.setPlainText(whois_data['raw'])
- else:
- self.whois_text.setPlainText("No WHOIS data available")
- def display_rdap_data(self, data):
- html = "<html><body style='font-family: Consolas, monospace;'><h3>📋 WHOIS Information (RDAP)</h3><table style='border-collapse: collapse; width: 100%;'>"
- html += self.add_table_row("Domain Name", data.get('ldhName', 'N/A'))
- registrar, abuse_email, abuse_phone = "N/A", "N/A", "N/A"
- for entity in data.get('entities', []):
- if 'registrar' in entity.get('roles', []):
- vcard = entity.get('vcardArray', [])
- if len(vcard) > 1:
- for item in vcard[1]:
- if item[0] == 'fn': registrar = item[3]
- for sub_entity in entity.get('entities', []):
- if 'abuse' in sub_entity.get('roles', []):
- sub_vcard = sub_entity.get('vcardArray', [])
- if len(sub_vcard) > 1:
- for item in sub_vcard[1]:
- if item[0] == 'email': abuse_email = item[3]
- elif item[0] == 'tel': abuse_phone = item[3].replace('tel:', '')
- html += self.add_table_row("Registrar", registrar)
- html += self.add_table_row("Abuse Contact Email", abuse_email)
- html += self.add_table_row("Abuse Contact Phone", abuse_phone)
- status = data.get('status', [])
- if status: html += self.add_table_row("Status", "<br>".join([f"• {s}" for s in status]))
- events = data.get('events', [])
- event_labels = {'registration': 'Registration Date', 'expiration': 'Expiration Date', 'last changed': 'Last Updated', 'last update of RDAP database': 'RDAP Database Updated'}
- for event in events:
- action, date_str = event.get('eventAction', ''), event.get('eventDate', 'N/A')
- if date_str != 'N/A':
- try: dt = datetime.fromisoformat(date_str.replace('Z', '+00:00')); formatted_date = dt.strftime('%Y-%m-%d %H:%M:%S UTC')
- except: formatted_date = date_str
- html += self.add_table_row(event_labels.get(action, action.title()), formatted_date)
- nameservers = data.get('nameservers', [])
- if nameservers: html += self.add_table_row("Nameservers", "<br>".join([ns.get('ldhName', '').lower() for ns in nameservers]))
- secure_dns = data.get('secureDNS', {})
- html += self.add_table_row("DNSSEC", "Signed (Enabled)" if secure_dns.get('delegationSigned') else "Unsigned (Disabled)")
- html += "</table>"
- raw_json_escaped = json.dumps(data, indent=2).replace('&', '&').replace('<', '<').replace('>', '>')
- html += "<br><hr><h4>📄 Raw JSON Data</h4>"
- html += f"<pre style='padding: 10px; border-radius: 4px; overflow-x: auto; margin-top: 5px; font-size: 11px; white-space: pre-wrap; word-break: break-all;'>{raw_json_escaped}</pre></body></html>"
- self.whois_text.setHtml(html)
- def add_table_row(self, label, value):
- return f"<tr><td style='padding: 8px; font-weight: bold; border: 1px solid #ddd; width: 220px; vertical-align: top;'>{label}:</td><td style='padding: 8px; border: 1px solid #ddd; word-break: break-all;'>{value}</td></tr>"
- def display_dns(self, dns_data):
- self.dns_table.setRowCount(0)
- if 'error' in dns_data: return
- if dns_data.get('ip_address'):
- row = self.dns_table.rowCount()
- self.dns_table.insertRow(row)
- self.dns_table.setItem(row, 0, QTableWidgetItem("IP Address"))
- self.dns_table.setItem(row, 1, QTableWidgetItem(dns_data['ip_address']))
- for record_type in ['a', 'aaaa', 'mx', 'ns', 'txt', 'cname']:
- records = dns_data.get(record_type, [])
- if records:
- row = self.dns_table.rowCount()
- self.dns_table.insertRow(row)
- self.dns_table.setItem(row, 0, QTableWidgetItem(record_type.upper()))
- self.dns_table.setItem(row, 1, QTableWidgetItem(', '.join(records)))
- def display_ssl(self, ssl_data):
- self.ssl_text.clear()
- if 'error' in ssl_data:
- self.ssl_text.setHtml(f"<p style='color: red;'>❌ {ssl_data['error']}</p>")
- return
- html = "<html><body style='font-family: Consolas, monospace;'><h3>🔒 SSL Certificate Information</h3><table style='border-collapse: collapse; width: 100%;'>"
- for label, value in [('Issuer', ssl_data.get('issuer')), ('Subject', ssl_data.get('subject')), ('Serial Number', ssl_data.get('serial')), ('Valid From', ssl_data.get('valid_from')), ('Valid Until', ssl_data.get('valid_until')), ('Protocol', ssl_data.get('protocol')), ('Cipher', str(ssl_data.get('cipher')))]:
- if value: html += f"<tr><td style='padding: 8px; font-weight: bold; border: 1px solid #ddd;'>{label}:</td><td style='padding: 8px; border: 1px solid #ddd;'>{value}</td></tr>"
- days = ssl_data.get('days_until_expiry', 0)
- color = 'green' if days > 30 else 'orange' if days > 0 else 'red'
- html += f"<tr><td style='padding: 8px; font-weight: bold; border: 1px solid #ddd;'>Days Until Expiry:</td><td style='padding: 8px; border: 1px solid #ddd; color: {color}; font-weight: bold;'>{days} days</td></tr></table></body></html>"
- self.ssl_text.setHtml(html)
- def display_geolocation(self, geo_data):
- self.geo_text.clear()
- if 'error' in geo_data:
- self.geo_text.setHtml(f"<p style='color: red;'>❌ {geo_data['error']}</p>")
- return
- html = "<html><body style='font-family: Consolas, monospace;'><h3>📍 Geolocation Information</h3><table style='border-collapse: collapse; width: 100%;'>"
- for label, value in [('IP Address', geo_data.get('ip')), ('Country', geo_data.get('country')), ('Region', geo_data.get('region')), ('City', geo_data.get('city')), ('Coordinates', f"{geo_data.get('latitude')}, {geo_data.get('longitude')}"), ('ISP', geo_data.get('isp')), ('Organization', geo_data.get('org')), ('Timezone', geo_data.get('timezone'))]:
- if value: html += f"<tr><td style='padding: 8px; font-weight: bold; border: 1px solid #ddd;'>{label}:</td><td style='padding: 8px; border: 1px solid #ddd;'>{value}</td></tr>"
- html += "</table></body></html>"
- self.geo_text.setHtml(html)
- def add_to_history(self, result):
- self.history.append(result)
- row = self.history_table.rowCount()
- self.history_table.insertRow(row)
- self.history_table.setItem(row, 0, QTableWidgetItem(result['domain']))
- self.history_table.setItem(row, 1, QTableWidgetItem(result['timestamp'][:19]))
- btn_view = QPushButton("View")
- btn_view.clicked.connect(lambda: self.load_from_history(row, 0))
- self.history_table.setCellWidget(row, 2, btn_view)
- def load_from_history(self, row, col):
- if row < len(self.history):
- result = self.history[row]
- self.current_result = result
- self.domain_input.setText(result['domain'])
- self.display_whois(result['whois'])
- self.display_dns(result['dns'])
- self.display_ssl(result['ssl'])
- self.display_geolocation(result['geolocation'])
- self.tabs.setCurrentIndex(0)
- def export_results(self):
- if not self.current_result:
- QMessageBox.warning(self, "Warning", "No results to export!")
- return
- file_path, selected_filter = QFileDialog.getSaveFileName(self, "Export Results", "", "JSON Files (*.json);;CSV Files (*.csv);;Text Files (*.txt)")
- if not file_path: return
- try:
- if selected_filter.startswith("JSON"):
- with open(file_path, 'w', encoding='utf-8') as f: json.dump(self.current_result, f, indent=2, default=str)
- elif selected_filter.startswith("CSV"):
- with open(file_path, 'w', newline='', encoding='utf-8') as f:
- writer = csv.writer(f)
- writer.writerow(['Field', 'Value'])
- self.write_dict_to_csv(writer, self.current_result)
- elif selected_filter.startswith("Text"):
- with open(file_path, 'w', encoding='utf-8') as f: f.write(json.dumps(self.current_result, indent=2, default=str))
- self.status_bar.showMessage(f"✅ Exported to {file_path}")
- QMessageBox.information(self, "Success", f"Results exported successfully!")
- except Exception as e:
- QMessageBox.critical(self, "Error", f"Failed to export:\n{str(e)}")
- def write_dict_to_csv(self, writer, data, prefix=''):
- for key, value in data.items():
- full_key = f"{prefix}{key}" if prefix else key
- if isinstance(value, dict): self.write_dict_to_csv(writer, value, f"{full_key}.")
- elif isinstance(value, list): writer.writerow([full_key, ', '.join(map(str, value))])
- else: writer.writerow([full_key, value])
- # ================= TAB METHODS =================
- def start_ip_lookup(self):
- self.ip_text.setHtml("<p>Fetching IP and system information...</p>")
- self.btn_refresh_ip.setEnabled(False)
- self.ip_worker = GetIPWorker()
- self.ip_worker.finished.connect(self.on_ip_lookup_complete)
- self.ip_worker.error.connect(self.on_ip_lookup_error)
- self.ip_worker.start()
- def on_ip_lookup_complete(self, info):
- self.btn_refresh_ip.setEnabled(True)
- html = "<html><body style='font-family: Consolas, monospace;'><h3>🌐 Network & System Information</h3><table style='border-collapse: collapse; width: 100%;'>"
- for label, value in [('PC Hostname', info.get('hostname')), ('PC Username', info.get('username')), ('PC Display Name', info.get('display_name')), ('Public IP [IPv4]', info.get('public_ip')), ('Local IP [IPv4]', info.get('local_ip')), ('IPv6 Addresses', info.get('ipv6'))]:
- html += f"<tr><td style='padding: 8px; font-weight: bold; border: 1px solid #ddd; width: 200px;'>{label}:</td><td style='padding: 8px; border: 1px solid #ddd;'>{value}</td></tr>"
- html += "</table></body></html>"
- self.ip_text.setHtml(html)
- def on_ip_lookup_error(self, error):
- self.btn_refresh_ip.setEnabled(True)
- self.ip_text.setHtml(f"<p style='color: red;'>❌ Error: {error}</p>")
- def start_port_scan(self):
- ip = self.port_ip_input.text().strip()
- if not ip:
- QMessageBox.warning(self, "Warning", "Please enter a target IP!")
- return
- try:
- start_p = int(self.port_start_input.text())
- end_p = int(self.port_end_input.text())
- if start_p < 1 or end_p > 65535 or start_p > end_p: raise ValueError
- except:
- QMessageBox.warning(self, "Warning", "Invalid port range! (1-65535)")
- return
- self.port_text.clear()
- self.port_progress.setVisible(True)
- self.port_progress.setValue(0)
- self.btn_start_scan.setEnabled(False)
- self.btn_stop_scan.setEnabled(True)
- self.port_worker = PortScannerWorker(ip, start_p, end_p)
- self.port_worker.progress.connect(self.port_progress.setValue)
- self.port_worker.status.connect(lambda msg: self.port_text.append(msg))
- self.port_worker.finished.connect(self.on_port_scan_complete)
- self.port_worker.error.connect(self.on_port_scan_error)
- self.port_worker.start()
- def stop_port_scan(self):
- if hasattr(self, 'port_worker'):
- self.port_worker.stop()
- self.port_text.append("<p style='color: orange;'>Stopping scan...</p>")
- def on_port_scan_complete(self, open_ports):
- self.port_progress.setVisible(False)
- self.btn_start_scan.setEnabled(True)
- self.btn_stop_scan.setEnabled(False)
- if not open_ports:
- self.port_text.append("<p style='color: orange;'>No open ports found in the specified range.</p>")
- return
- html = "<br><h3>🚪 Open Ports Summary</h3><table style='border-collapse: collapse; width: 100%;'><tr><th style='padding: 8px; border: 1px solid #ddd; background-color: #2ea043; color: white;'>Port</th><th style='padding: 8px; border: 1px solid #ddd; background-color: #2ea043; color: white;'>Status</th></tr>"
- for port in open_ports:
- html += f"<tr><td style='padding: 8px; border: 1px solid #ddd;'>{port}</td><td style='padding: 8px; border: 1px solid #ddd; color: green;'>OPEN</td></tr>"
- html += "</table>"
- self.port_text.append(html)
- def on_port_scan_error(self, error):
- self.port_progress.setVisible(False)
- self.btn_start_scan.setEnabled(True)
- self.btn_stop_scan.setEnabled(False)
- self.port_text.setHtml(f"<p style='color: red;'>❌ Error: {error}</p>")
- def start_social_search(self):
- query = self.social_query_input.text().strip()
- if not query:
- QMessageBox.warning(self, "Warning", "Please enter a username!")
- return
- self.social_text.clear()
- self.social_progress.setVisible(True)
- self.btn_social_search.setEnabled(False)
- self.social_worker = SocialMediaWorker(query)
- self.social_worker.progress.connect(self.update_social_progress)
- self.social_worker.finished.connect(self.on_social_search_complete)
- self.social_worker.error.connect(self.on_social_search_error)
- self.social_worker.start()
- def update_social_progress(self, message):
- self.social_text.append(message)
- def on_social_search_complete(self, results):
- self.social_progress.setVisible(False)
- self.btn_social_search.setEnabled(True)
- if not results:
- self.social_text.setHtml("<p style='color: orange;'>No profiles found.</p>")
- return
- html = "<html><body style='font-family: Consolas, monospace;'><h3>👥 Found Social Media Profiles</h3>"
- html += f"<p style='color: green; font-weight: bold;'>Found {len(results)} profile(s):</p>"
- html += "<table style='border-collapse: collapse; width: 100%;'>"
- for platform, url in sorted(results):
- html += f"<tr><td style='padding: 8px; border: 1px solid #ddd; font-weight: bold;'>{platform}:</td><td style='padding: 8px; border: 1px solid #ddd;'><a href='{url}' style='color: #007acc;'>{url}</a></td></tr>"
- html += "</table></body></html>"
- self.social_text.setHtml(html)
- def on_social_search_error(self, error):
- self.social_progress.setVisible(False)
- self.btn_social_search.setEnabled(True)
- self.social_text.setHtml(f"<p style='color: red;'>❌ Error: {error}</p>")
- def start_phone_lookup(self):
- phone = self.phone_input.text().strip()
- if not phone:
- QMessageBox.warning(self, "Warning", "Please enter a phone number!")
- return
- self.phone_text.clear()
- self.phone_progress.setVisible(True)
- self.btn_phone_search.setEnabled(False)
- self.phone_worker = PhoneWorker(phone, self.phone_cache)
- self.phone_worker.progress.connect(lambda msg: self.status_bar.showMessage(msg))
- self.phone_worker.finished.connect(self.on_phone_lookup_complete)
- self.phone_worker.error.connect(self.on_phone_lookup_error)
- self.phone_worker.start()
- def on_phone_lookup_complete(self, data, phone_number):
- self.phone_progress.setVisible(False)
- self.btn_phone_search.setEnabled(True)
- self.status_bar.showMessage("✅ Phone lookup complete.")
- self.display_phone_info(data, phone_number)
- def on_phone_lookup_error(self, error):
- self.phone_progress.setVisible(False)
- self.btn_phone_search.setEnabled(True)
- self.phone_text.setHtml(f"<p style='color: red;'>❌ Error: {error}</p>")
- self.status_bar.showMessage(f"❌ Error: {error}")
- def display_phone_info(self, data, phone_number):
- html = "<html><body style='font-family: Consolas, monospace;'>"
- html += f"<h3>📞 Phone Number Information: {phone_number}</h3>"
- html += "<table style='border-collapse: collapse; width: 100%;'>"
- country = data.get('country', {})
- region = data.get('region', {})
- other = data.get('0', {})
- capital = data.get('capital', {})
- def get_val(d, key, default='N/A'):
- val = d.get(key)
- return val if val else default
- # Russian ko English mein translate karne ke liye maps
- lang_map = {
- 'ru': 'Russian', 'en': 'English', 'fa': 'Persian', 'ar': 'Arabic',
- 'fr': 'French', 'de': 'German', 'es': 'Spanish', 'zh': 'Chinese',
- 'hi': 'Hindi', 'pt': 'Portuguese', 'ja': 'Japanese', 'tr': 'Turkish',
- 'ko': 'Korean', 'it': 'Italian', 'nl': 'Dutch', 'pl': 'Polish'
- }
- continent_map = {
- 'Европа': 'Europe', 'Азия': 'Asia', 'Африка': 'Africa',
- 'Северная Америка': 'North America', 'Южная Америка': 'South America',
- 'Австралия': 'Australia', 'Антарктида': 'Antarctica'
- }
- okrug_map = {
- 'Центральный федеральный округ': 'Central Federal District',
- 'Южный федеральный округ': 'Southern Federal District',
- 'Северо-Кавказский федеральный округ': 'North Caucasian Federal District',
- 'Северо-Западный федеральный округ': 'Northwestern Federal District',
- 'Приволжский федеральный округ': 'Volga Federal District',
- 'Уральский федеральный округ': 'Ural Federal District',
- 'Сибирский федеральный округ': 'Siberian Federal District',
- 'Дальневосточный федеральный округ': 'Far Eastern Federal District'
- }
- lang_code = get_val(country, 'langcod')
- lang_display = f"{lang_map.get(lang_code, lang_code.upper())} ({lang_code})" if lang_code != 'N/A' else 'N/A'
- continent = continent_map.get(data.get('location'), data.get('location', 'N/A'))
- okrug = okrug_map.get(region.get('okrug'), region.get('okrug', 'N/A'))
- # API ki 'english' fields use ki hain taake result English mein aaye
- fields = [
- ("Country", get_val(country, 'english')),
- ("Continent", continent),
- ("Region / State", get_val(region, 'english')),
- ("Federal District", okrug),
- ("City", get_val(other, 'english')),
- ("Postal Code", get_val(other, 'post')),
- ("ISO Code", get_val(country, 'iso')),
- ("Country Code", get_val(country, 'telcod')),
- ("Area Code", get_val(other, 'telcod')),
- ("Region Auto Code", get_val(region, 'autocod')),
- ("Operator", f"{get_val(other, 'oper_brand')} (DEF: {get_val(other, 'def')})"),
- ("Location", f"{get_val(country, 'english')}, {get_val(region, 'english')}, {get_val(other, 'english')}"),
- ("Capital", get_val(capital, 'english')),
- ("Language", lang_display),
- ("Timezone", get_val(other, 'tz')),
- ("Coordinates", f"{get_val(other, 'latitude')}, {get_val(other, 'longitude')}"),
- ("Wiki Link", f"<a href='https://{get_val(other, 'wiki')}' style='color: #007acc;'>{get_val(other, 'wiki')}</a>" if get_val(other, 'wiki') != 'N/A' else 'N/A'),
- ("Google Maps", f"<a href='https://www.google.com/maps/place/{get_val(other, 'latitude')}+{get_val(other, 'longitude')}' style='color: #007acc;'>Open in Maps</a>" if get_val(other, 'latitude') != 'N/A' else 'N/A'),
- ("Phone Radar", f"<a href='https://phoneradar.ru/phone/{phone_number}' style='color: #007acc;'>View on Phone Radar</a>")
- ]
- ignore_patterns = ['N/A', 'N/A (DEF: N/A)', 'N/A, N/A, N/A', ' ()', 'N/A (N/A)']
- for label, value in fields:
- if value and str(value).strip() not in ignore_patterns:
- html += self.add_table_row(label, value)
- html += "</table></body></html>"
- self.phone_text.setHtml(html)
- def toggle_theme(self):
- current = self.property("theme")
- if current == "dark": self.apply_theme("light")
- else: self.apply_theme("dark")
- def apply_theme(self, theme):
- self.setProperty("theme", theme)
- if theme == "dark":
- qss = """
- QMainWindow, QWidget { background-color: #1e1e1e; color: #d4d4d4; }
- QFrame#topFrame { background-color: #252526; border: 1px solid #333; border-radius: 8px; }
- QLineEdit#domainInput, QLineEdit { background-color: #3c3c3c; color: white; border: 1px solid #555; padding: 8px; border-radius: 4px; font-size: 14px; }
- QPushButton { background-color: #0e639c; color: white; border: none; padding: 8px 20px; border-radius: 5px; font-weight: bold; }
- QPushButton:hover { background-color: #1177bb; }
- QPushButton:disabled { background-color: #555; color: #999; }
- QPushButton#primaryBtn { background-color: #2ea043; }
- QPushButton#primaryBtn:hover { background-color: #3fb950; }
- QTabWidget::pane { border: 1px solid #333; background-color: #1e1e1e; }
- QTabBar::tab { background-color: #252526; color: #d4d4d4; padding: 10px 20px; border: 1px solid #333; border-bottom: none; }
- QTabBar::tab:selected { background-color: #0e639c; color: white; }
- QTextEdit { background-color: #1e1e1e; color: #d4d4d4; border: 1px solid #333; font-family: Consolas, monospace; }
- QTableWidget { background-color: #1e1e1e; color: #d4d4d4; border: 1px solid #333; }
- QTableWidget::item { padding: 5px; }
- QHeaderView::section { background-color: #2d2d2d; color: white; padding: 8px; border: 1px solid #333; font-weight: bold; }
- QProgressBar { border: none; background-color: #333; }
- QProgressBar::chunk { background-color: #007acc; }
- QStatusBar { background-color: #007acc; color: white; font-weight: bold; }
- """
- self.btn_theme.setText("☀️ Light")
- else:
- qss = """
- QMainWindow, QWidget { background-color: #f3f3f3; color: #333; }
- QFrame#topFrame { background-color: #ffffff; border: 1px solid #ddd; border-radius: 8px; }
- QLineEdit#domainInput, QLineEdit { background-color: #fff; color: #333; border: 1px solid #ccc; padding: 8px; border-radius: 4px; font-size: 14px; }
- QPushButton { background-color: #0078d4; color: white; border: none; padding: 8px 20px; border-radius: 5px; font-weight: bold; }
- QPushButton:hover { background-color: #005a9e; }
- QPushButton:disabled { background-color: #ccc; color: #666; }
- QPushButton#primaryBtn { background-color: #107c10; }
- QPushButton#primaryBtn:hover { background-color: #0b5a0b; }
- QTabWidget::pane { border: 1px solid #ddd; background-color: #ffffff; }
- QTabBar::tab { background-color: #e1e1e1; color: #333; padding: 10px 20px; border: 1px solid #ddd; border-bottom: none; }
- QTabBar::tab:selected { background-color: #0078d4; color: white; }
- QTextEdit { background-color: #ffffff; color: #333; border: 1px solid #ddd; font-family: Consolas, monospace; }
- QTableWidget { background-color: #ffffff; color: #333; border: 1px solid #ddd; }
- QTableWidget::item { padding: 5px; }
- QHeaderView::section { background-color: #e1e1e1; color: #333; padding: 8px; border: 1px solid #ddd; font-weight: bold; }
- QProgressBar { border: none; background-color: #ddd; }
- QProgressBar::chunk { background-color: #0078d4; }
- QStatusBar { background-color: #0078d4; color: white; font-weight: bold; }
- """
- self.btn_theme.setText("🌙 Dark")
- self.setStyleSheet(qss)
- if __name__ == "__main__":
- app = QApplication(sys.argv)
- app.setFont(QFont("Segoe UI", 10))
- viewer = WHOISViewer()
- viewer.show()
- sys.exit(app.exec_())
Advertisement
Add Comment
Please, Sign In to add comment