import io
import smtplib
import ssl
import imaplib
import email
import threading
import time
import os
import datetime
import json
import socket
from concurrent.futures import ThreadPoolExecutor, as_completed
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from queue import Queue
from colorama import init, Fore
import socks
from tqdm import tqdm
import re
import requests
import signal
import sys
import logging
def _hard_exit(sig, frame):
    os._exit(0)


# Inicjalizacja colorama
init(autoreset=True)

# ----------------------- Konfiguracja i Stałe -----------------------
SETTINGS_FILE = "settings.json"
PROXY_SOURCES_FILE = "proxy_sources.txt"
BLOCKED_DOMAINS_FILE = "blocked-domains.txt"
RESUME_FILE = "resume_smtp_progress.txt"

# Ustawienie głównego timeoutu gniazda na starcie
socket.setdefaulttimeout(10)

# Stałe domyślne
MAX_WORKERS_DEFAULT = 100
SEMAPHORE_LIMIT_DEFAULT = 50
SMTP_OP_TIMEOUT_DEFAULT = 10
RETRY_COUNT_DEFAULT = 1
BACKOFF_BASE_DEFAULT = 3
PROXY_CHECK_TIMEOUT_DEFAULT = 5
IMAP_POLL_INTERVAL_DEFAULT = 15
IMAP_CONNECTION_TIMEOUT_DEFAULT = 35
PROXY_BLOCK_DURATION_DEFAULT = 200

# Konfiguracja loggera
class TqdmHandler(logging.Handler):
    """Handler, który używa tqdm.write, aby nie psuć paska postępu."""
    def emit(self, record):
        log_entry = self.format(record)
        tqdm.write(log_entry)

logger = logging.getLogger("SmtpChecker")
logger.setLevel(logging.INFO)
if not logger.handlers:
    formatter = logging.Formatter(f"{Fore.CYAN}[%(asctime)s] {Fore.RESET}%(levelname)s: %(message)s", datefmt="%H:%M:%S")
    tqdm_handler = TqdmHandler()
    tqdm_handler.setFormatter(formatter)
    logger.addHandler(tqdm_handler)

# ====================== FUNKCJE POMOCNICZE (Globalne) ======================

def load_settings():
    if os.path.exists(SETTINGS_FILE):
        with open(SETTINGS_FILE, 'r') as f:
            try: return json.load(f)
            except: pass
    return None

def save_settings(settings):
    try:
        with open(SETTINGS_FILE, 'w') as f:
            json.dump(settings, f, indent=4)
        logger.info(f"{Fore.GREEN}[+] Konfiguracja zapisana w {SETTINGS_FILE}")
    except Exception as e:
        logger.error(f"Blad zapisu ustawien: {e}")

def get_user_input(default_settings=None):
    s = default_settings if default_settings else {}
    print(Fore.YELLOW + "\n" + "="*40 + "\n USTAWIENIA KONFIGURACJI \n" + "="*40)
    
    # NOWA OPCJA: TRYB PRACY
    print(Fore.YELLOW + "\nTryb pracy:")
    print(" 1) SMTP + IMAP (Pełne sprawdzenie, włącznie z wysyłką)")
    print(" 2) Tylko IMAP Verify (Tylko weryfikacja skrzynki odbiorczej)")
    run_mode_input = input("Wybór (1/2): ").strip()
    run_imap_only = run_mode_input == '2'
    
    def get_input_with_default(prompt, key, default, type_func=str):
        current = s.get(key, default)
        user_input = input(f"{prompt} (aktualnie: {current}): ")
        if user_input:
            return type_func(user_input)
        return current

    # Ustawienia wspólne i dla SMTP
    smtp_file = get_input_with_default("Wprowadz plik serwerow SMTP", 'smtp_file', 'smtp.txt')
    threads = get_input_with_default("Liczba watkow (rekomendowane: 80-150)", 'threads', MAX_WORKERS_DEFAULT, int)
    to_email = get_input_with_default("E-mail odbiorcy", 'to_email', 'test@example.com')
    subject = get_input_with_default("Temat e-maila", 'subject', 'Test SMTP Verification')
    content = get_input_with_default("Tresc e-maila", 'content', 'Automatyczny test dostarczalnosci.')
    
    # Ustawienia Proxy
    use_proxy_input = input(f"Uzyc proxy? (y/n) (aktualnie: {'y' if s.get('use_proxy') else 'n'}): ").lower()
    use_proxy = use_proxy_input == 'y' if use_proxy_input else s.get('use_proxy', False)

    proxy_file = ''
    proxy_type = ''
    auto_update_proxies = False
    if use_proxy:
        proxy_file = get_input_with_default("Nazwa pliku z proxy", 'proxy_filename', 'proxies.txt')
        proxy_type = get_input_with_default("Typ proxy (http/socks4/socks5)", 'proxy_type', 'socks5').lower()
        auto_update_proxies_input = input(f"Auto-aktualizacja proxy (Scraping)? (y/n) (aktualnie: {'y' if s.get('auto_update_proxies') else 'n'}): ").lower()
        auto_update_proxies = auto_update_proxies_input == 'y' if auto_update_proxies_input else s.get('auto_update_proxies', False)

    # Ustawienia IMAP
    imap_server = get_input_with_default("Serwer IMAP", 'imap_server', 'imap.example.com')
    imap_user = get_input_with_default("Uzytkownik IMAP", 'imap_user', 'test@example.com')
    imap_pass = input("Haslo IMAP (pozostaw puste, by uzyc zapisanego): ") or s.get('imap_password', '')
    duration = get_input_with_default("Minuty monitorowania IMAP", 'duration', 5, int)

    return {
        'smtp_file': smtp_file, 'threads': threads, 'to_email': to_email, 'subject': subject,
        'content': content, 'use_proxy': use_proxy, 'proxy_filename': proxy_file if use_proxy else '',
        'proxy_type': proxy_type if use_proxy else '', 'auto_update_proxies': auto_update_proxies if use_proxy else False,
        'imap_server': imap_server, 'imap_user': imap_user, 'imap_password': imap_pass, 'duration': duration,
        'semaphore_limit': SEMAPHORE_LIMIT_DEFAULT, 'smtp_op_timeout': SMTP_OP_TIMEOUT_DEFAULT,
        'run_imap_only': run_imap_only
    }

def validate_smtp_host(host, port):
    """Sprawdza, czy DNS hosta jest resolvable. Dla proxy wymusza IPv4."""
    try:
        # Próba resolucji IPv4 (AF_INET)
        socket.getaddrinfo(host, port, socket.AF_INET, socket.SOCK_STREAM)
        return True
    except socket.gaierror:
        # Jeśli IPv4 nie działa, sprawdź czy ma tylko IPv6
        try:
            result = socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM)
            # Jeśli wszystkie wyniki to IPv6, odrzuć (PySocks nie obsługuje)
            has_ipv4 = any(addr[0] == socket.AF_INET for addr in result)
            return has_ipv4
        except:
            return False
    except:
        return False

# ====================== KLASA SMTP CHECKER (OOP) ======================

class SmtpChecker:
    def __init__(self, settings):
        self.settings = settings
        self.proxies = []
        self.proxy_lock = threading.Lock()
        self.current_proxy_index = 0
        self.blocked_proxies = {}
        self.blocked_lock = threading.Lock()
        self.blocked_domains = set()
        self.stop_scanning_flag = False
        self.active_sockets = set()
        self.socket_lock = threading.Lock()
        self.is_smtp_phase_active = True 

        self.semaphore = threading.Semaphore(settings.get('semaphore_limit', SEMAPHORE_LIMIT_DEFAULT))
        self.smtp_timeout = settings.get('smtp_op_timeout', SMTP_OP_TIMEOUT_DEFAULT)
        self.retry_count = settings.get('retry_count', RETRY_COUNT_DEFAULT)
        self.backoff_base = settings.get('backoff_base', BACKOFF_BASE_DEFAULT)

        signal.signal(signal.SIGINT, self._signal_handler)
        
        logger.info(Fore.CYAN + f"[*] Uzywam {settings['threads']} watkow, limit jednoczesnych polaczen: {self.semaphore._value}")
        if settings['use_proxy']:
             logger.info(Fore.CYAN + f"[*] Uzywam proxy: Typ={settings['proxy_type']}")


    # --- OBSŁUGA SYGNAŁÓW (Ctrl+C) ---

    def _signal_handler(self, sig, frame):
        """Obsługa sygnału Ctrl+C. Ubija wątki i zamyka gniazda natychmiast."""
        self.stop_scanning_flag = True
        self.is_smtp_phase_active = False # ZATRZYMAJ LOGOWANIE BŁĘDÓW SMTP Z UBITYCH WĄTKÓW
        logger.warning(f"{Fore.MAGENTA}[!!!] Ctrl+C - koncze skanowanie...")
        self._force_close_active_sockets() # NATYCHMIASTOWE UBICIE ZAWIESZONYCH GNIAZD
        time.sleep(0.5)

    def _force_close_active_sockets(self):
        """Silowe zamkniecie wszystkich aktywnych gniazd, aby przerwac oczekiwanie na timeouty."""
        closed_count = 0
        with self.socket_lock:
            for sock in list(self.active_sockets):
                try:
                    # Zamknięcie w trybie SHUT_RDWR (READ/WRITE) wymusza zakończenie operacji
                    sock.shutdown(socket.SHUT_RDWR) 
                    sock.close()
                    closed_count += 1
                except:
                    pass
            self.active_sockets.clear()
        if closed_count > 0:
            logger.warning(f"{Fore.RED}[!!!] Silowo zamknieto {closed_count} gniazd.")

    # --- ZARZĄDZANIE DANYMI ---

    def _load_blocked_domains(self, filename):
        try:
            with open(filename, 'r', encoding='utf-8', errors='ignore') as f:
                domains = {line.strip().lower() for line in f if line.strip()}
                logger.info(Fore.CYAN + f"[i] Wczytano {len(domains)} zablokowanych domen.")
                return domains
        except FileNotFoundError:
            logger.warning(Fore.YELLOW + f"[!] Plik {filename} nie istnieje - blokowanie wylaczone.")
            return set()

    def _load_proxy_sources(self, filename):
        try:
            with open(filename, 'r', encoding='utf-8', errors='ignore') as f:
                return [line.strip() for line in f if line.strip().startswith('http')]
        except FileNotFoundError:
            return []

    def _load_proxies(self, filename):
        try:
            with open(filename, 'r', encoding='utf-8', errors='ignore') as f:
                return [line.strip() for line in f if line.strip()]
        except FileNotFoundError:
            return []

    def _load_smtp_servers(self, filename):
        unique = set()
        servers = []
        initial_count = 0
        try:
            with open(filename, 'r', encoding='utf-8', errors='ignore') as f:
                for line in f:
                    initial_count += 1
                    parts = line.strip().split('|')
                    if len(parts) == 4:
                        t = tuple(parts)
                        if t not in unique:
                            unique.add(t)
                            servers.append(parts)
        except FileNotFoundError:
            logger.error(f"Nie znaleziono pliku serwerow SMTP: {filename}")
            pass
        
        removed_count = initial_count - len(servers)
        if removed_count > 0:
            logger.warning(Fore.YELLOW + f"[!] Usunieto {removed_count} duplikatow serwerow SMTP.")
        else:
            logger.info(Fore.CYAN + f"[i] Brak duplikatow serwerow SMTP w pliku {filename}.")
        return servers

    # --- ZARZĄDZANIE PROXY ---

    def scrape_and_update_proxies(self, proxy_file):
        logger.info(Fore.CYAN + "[i] Aktualizacja listy proxy...")
        sources = self._load_proxy_sources(PROXY_SOURCES_FILE)
        if not sources:
            logger.error(Fore.RED + "[!] Brak zrodel proxy w proxy_sources.txt")
            return []

        existing = self._load_proxies(proxy_file)
        unique = set(existing)
        added = 0
        
        for url in tqdm(sources, desc="Scraping", leave=False):
            try:
                r = requests.get(url, timeout=PROXY_CHECK_TIMEOUT_DEFAULT)
                found = re.findall(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+', r.text)
                for p in found:
                    if p not in unique:
                        unique.add(p)
                        added += 1
            except:
                pass

        final = list(unique)
        with open(proxy_file, 'w', encoding='utf-8') as f:
            f.write('\n'.join(final) + '\n')
        logger.info(Fore.GREEN + f"[+] Dodano {added} nowych proxy - lacznie {len(final)}")
        return final

    def test_proxy_worker_fast(self, proxy_type, proxy_str):
        try:
            session = requests.Session()
            if proxy_type.startswith('socks'):
                proxies = {'http': f'{proxy_type}://{proxy_str}', 'https': f'{proxy_type}://{proxy_str}'}
            else:
                proxies = {'http': f'http://{proxy_str}', 'https': f'http://{proxy_str}'}
            
            if proxy_type.startswith('socks'):
                 session.mount('http://', requests.adapters.HTTPAdapter())
                 session.mount('https://', requests.adapters.HTTPAdapter())

            r = session.get('http://www.google.com', proxies=proxies, timeout=PROXY_CHECK_TIMEOUT_DEFAULT, allow_redirects=False)
            if r.status_code in [200, 301, 302]: return proxy_str
        except Exception:
            pass
        return None

    def test_proxy_ultra_fast(self, proxy_type, proxy_list):
        logger.warning(Fore.YELLOW + "[i] Testowanie proxy (ULTRA FAST)...")
        good = []
        # Zgodnie z sugestiami optymalizacyjnymi, lepiej zredukować ten ThreadPool
        max_workers = min(300, os.cpu_count() * 10 if os.cpu_count() else 100) 
        
        with ThreadPoolExecutor(max_workers=max_workers) as ex:
            futures = {ex.submit(self.test_proxy_worker_fast, proxy_type, p): p for p in proxy_list}
            for f in tqdm(as_completed(futures), total=len(futures), desc="Test proxy", leave=False):
                r = f.result()
                if r: good.append(r)
        return good

    def get_next_proxy(self):
        now = time.time()
        with self.blocked_lock:
            for p in list(self.blocked_proxies):
                if self.blocked_proxies[p] < now:
                    del self.blocked_proxies[p]
        
        with self.proxy_lock:
            if not self.proxies: return None
            start = self.current_proxy_index
            while True:
                proxy = self.proxies[self.current_proxy_index % len(self.proxies)]
                if proxy not in self.blocked_proxies:
                    self.current_proxy_index = (self.current_proxy_index + 1) % len(self.proxies)
                    return proxy
                self.current_proxy_index = (self.current_proxy_index + 1) % len(self.proxies)
                if self.current_proxy_index == start:
                    return None

    # --- BEZPIECZNE POŁĄCZENIE SMTP Z PROXY (TRANSPLANTOWANE Z DROGA_A_CLEAN) ---

    def _create_raw_socket_with_proxy(self, host, port, proxy_str=None, proxy_type=None, timeout=SMTP_OP_TIMEOUT_DEFAULT):
        if proxy_str:
            ip, pport = proxy_str.split(':')
            pport = int(pport)
            proxy_type_num = {
                'http': socks.HTTP, 'https': socks.HTTP,
                'socks4': socks.SOCKS4, 'socks5': socks.SOCKS5
            }.get(proxy_type.lower(), socks.SOCKS5)
            
            sock = socks.socksocket()
            sock.set_proxy(proxy_type_num, ip, pport)
            sock.settimeout(timeout)
            sock.connect((host, int(port)))
            return sock
        else:
            return socket.create_connection((host, int(port)), timeout=timeout)



    def create_smtp_connection_with_proxy(self, host, port, proxy_str=None, proxy_type=None):
        if self.stop_scanning_flag:
            return None

        context = ssl.create_default_context()
        port = int(port)

        # === NO PROXY PATH (unchanged behaviour) ===
        if not proxy_str:
            if port == 465:
                server = smtplib.SMTP_SSL(host, port, timeout=self.smtp_timeout, context=context)
                try:
                    server.ehlo()
                except:
                    pass
                return server
            server = smtplib.SMTP(host, port, timeout=self.smtp_timeout)
            server.ehlo()
            if server.has_extn('starttls'):
                server.starttls(context=context)
                server.ehlo()
            return server

        # === PROXY PATH - Wymuszenie IPv4 dla PySocks ===
        # PySocks nie obsługuje IPv6 - musimy wymusić resolucję IPv4
        try:
            addr_info = socket.getaddrinfo(host, port, socket.AF_INET, socket.SOCK_STREAM)
            if not addr_info:
                raise Exception("Brak adresu IPv4 dla hosta")
            # Używamy pierwszego dostępnego IPv4
            ipv4_host = addr_info[0][4][0]
        except Exception as e:
            raise Exception(f"IPv6-only host (PySocks nie obsluguje): {host}")

        orig_socket = socket.socket

        ptype = (proxy_type or 'socks5').lower()
        if ptype.startswith('socks5'):
            p_const = socks.SOCKS5
        elif ptype.startswith('socks4'):
            p_const = socks.SOCKS4
        else:
            p_const = socks.HTTP

        ip, pport = proxy_str.split(':')
        pport = int(pport)

        try:
            try:
                socks.setdefaultproxy(p_const, ip, pport)
            except:
                socks.set_default_proxy(p_const, ip, pport)
            socket.socket = socks.socksocket

            if port == 465:
                # Używamy IPv4 zamiast hostname
                server = smtplib.SMTP_SSL(ipv4_host, port, timeout=self.smtp_timeout, context=context)
                try:
                    server.ehlo()
                except:
                    pass
                return server

            # Używamy IPv4 zamiast hostname
            server = smtplib.SMTP(ipv4_host, port, timeout=self.smtp_timeout)
            server.ehlo()
            if server.has_extn('starttls'):
                server.starttls(context=context)
                server.ehlo()
            return server
        finally:
            socket.socket = orig_socket

    def send_email(self, smtp_info, to_email, subject, content, use_proxy, proxy_type, success_queue):
        if self.stop_scanning_flag: return

        host, port, user, password = smtp_info
        try: port = int(port)
        except ValueError:
            if self.is_smtp_phase_active:
                logger.error(f"[-] Nieprawidlowy port dla {host} - pomijam.")
            return

        unique_id = f"{host}|{port}|{user}|{password}"
        body = content + "\n\n[SMTP-ID] " + unique_id
        
        msg = MIMEMultipart()
        msg['From'] = user
        msg['To'] = to_email
        msg['Subject'] = subject
        msg['X-SMTP-ID'] = unique_id
        msg.attach(MIMEText(body, 'plain'))

        attempts = 0
        while attempts <= self.retry_count and not self.stop_scanning_flag:
            attempts += 1
            proxy = None
            server = None
            try:
                if use_proxy and self.proxies:
                    proxy = self.get_next_proxy()
                    if not proxy:
                        if self.is_smtp_phase_active:
                            logger.error(f"[-] {user[:35]}... -> Brak dostepnych proxy.")
                        break
                    
                log_msg = f"[{user[:35]}...] Proba {attempts}/{self.retry_count+1} {'via '+proxy if proxy else ''}"
                
                if self.is_smtp_phase_active:
                    logger.info(log_msg)

                with self.semaphore:
                    server = self.create_smtp_connection_with_proxy(host, port, proxy, proxy_type)
                    if server is None: return

                    server.login(user, password)
                    server.sendmail(user, to_email, msg.as_string())
                    
                    # NIE DODAJEMY DO success_queue - tylko IMAP dodaje po weryfikacji doręczenia
                    if self.is_smtp_phase_active:
                        logger.info(f"{Fore.GREEN}[+] Sukces: {user} ({unique_id})")
                    return
            except Exception as e:
                if self.stop_scanning_flag: return
                
                if self.is_smtp_phase_active:
                    error_msg = str(e)[:70]
                    logger.error(f"{Fore.RED}[-] Blad dla {user[:35]}... (Proba {attempts}): {error_msg}")
                
                if attempts <= self.retry_count:
                    if self.is_smtp_phase_active:
                        logger.warning(f"[i] Ponawiam probe za {self.backoff_base}s...")
                    time.sleep(self.backoff_base)
                
                if proxy:
                    with self.blocked_lock:
                        self.blocked_proxies[proxy] = time.time() + PROXY_BLOCK_DURATION_DEFAULT
            finally:
                if server:
                    try: 
                        # Upewnienie się, że socket jest usunięty po zamknięciu połączenia
                        with self.socket_lock:
                            if server.sock:
                                self.active_sockets.discard(server.sock)
                        server.quit()
                    except: 
                        pass


    # --- IMAP CHECK ---
    
    def imap_check(self, server, user, password, subject, duration_minutes, success_queue):
        """Monitoruje IMAP w poszukiwaniu wiadomości potwierdzającej dostarczenie."""
        MAX_IMAP_RETRIES = 3
        
        # KRYTYCZNE: Upewniamy się, że IMAP NIE używa proxy
        # Przywracamy oryginalny socket przed połączeniem IMAP
        original_socket = socket.socket
        try:
            socket.socket = socket._socket.socket if hasattr(socket, '_socket') else original_socket
        except:
            pass
        
        try:
           test_conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
           test_conn.settimeout(10)
           test_conn.connect((server, 993))
           test_conn.close()
        except Exception as e:
           logger.error(f"[IMAP ERROR] Host IMAP niedostępny: {e}")
           return

        id_pattern = re.compile(r'\[SMTP[-_\s]?ID\]\s*([^\s]+(?:\|[^\s]+)*)', re.IGNORECASE)
        found_ids = set()
        end_time = time.time() + duration_minutes * 60
        logger.info(Fore.CYAN + "[i] IMAP monitor running...")

        while time.time() < end_time and not self.stop_scanning_flag:
            mail = None
            retry_count = 0
            connected = False

            while retry_count <= MAX_IMAP_RETRIES and not self.stop_scanning_flag:
                try:
                    mail = imaplib.IMAP4_SSL(server, timeout=IMAP_CONNECTION_TIMEOUT_DEFAULT)
                    mail.login(user, password)
                    connected = True
                    break
                except Exception as e:
                    if mail:
                        try: mail.logout()
                        except: pass
                    logger.error(f"[IMAP CONNECTION ERROR] {str(e)[:70]} (Proba {retry_count + 1}/{MAX_IMAP_RETRIES + 1})")
                    time.sleep(2 * retry_count + 1)
                    retry_count += 1

            if not connected:
                logger.error("[IMAP ERROR] Brak polaczenia - czekam...")
                time.sleep(IMAP_POLL_INTERVAL_DEFAULT)
                continue

            try:
                # Domyślna lista folderów do sprawdzenia
                target_folders = ['INBOX']
                spam_folders = ['JUNK', 'SPAM', 'Spam', 'Junk'] 
                
                status, folders = mail.list()
                if status == 'OK':
                    for f in folders:
                        if not f: continue
                        f_str = f.decode()
                        
                        # POZOSTAWIAM TWOJ REGEKS, ABY NIE WPROWADZAC INNYCH ZMIAN
                        match = re.search(r'\"([^\"]+)\"$', f_str)
                        if match:
                            folder_name = match.group(1).replace('"', '')
                            
                            # Dodajemy foldery, które nie są spamem/śmieciami (sprawdzamy też wielkość liter)
                            if folder_name.upper() not in [sf.upper() for sf in spam_folders]:
                                if folder_name not in target_folders:
                                    target_folders.append(folder_name)

                # Upewniamy się, że INBOX jest zawsze sprawdzany jako pierwszy
                if 'INBOX' not in target_folders:
                    target_folders.insert(0, 'INBOX')
                
                # Usuwamy duplikaty
                target_folders = list(dict.fromkeys(target_folders))

                today = datetime.date.today().strftime("%d-%b-%Y")
                scanned = 0
                
                for folder in target_folders:
                    try:
                        # Upewniamy się, że folder jest poprawnie cytowany w poleceniu SELECT
                        mail.select(f'"{folder}"', readonly=True)
                        result, data = mail.search(None, f'SINCE "{today}" NOT DELETED')
                        if result == 'OK' and data and data[0]:
                            ids = data[0].split()
                            scanned += len(ids)
                            for msg_id in ids:
                                if self.stop_scanning_flag: break
                                try:
                                    # Pobieranie tylko nagłówków (Subject, X-SMTP-ID)
                                    res2, msg_data = mail.fetch(msg_id, '(BODY.PEEK[HEADER.FIELDS (SUBJECT X-SMTP-ID)] UID)')
                                    if res2 != 'OK' or not msg_data or not msg_data[0]: continue
                                    
                                    # LINIA Z BŁĘDEM WCIŚCIA - POPRAWIONA
                                    raw_header = msg_data[0][1] 
                                    msg_header = email.message_from_bytes(raw_header)
                                    
                                    # Filtrowanie po temacie (jeśli go używamy)
                                    if subject.lower() not in msg_header.get('Subject', '').lower(): continue
                                    
                                    smtp_id = msg_header.get('X-SMTP-ID')
                                    
                                    # Pobieranie pełnej treści, jeśli X-SMTP-ID jest puste
                                    if not smtp_id:
                                        res2_full, msg_data_full = mail.fetch(msg_id, '(RFC822)')
                                        if res2_full == 'OK' and msg_data_full and msg_data_full[0]:
                                            raw_full = msg_data_full[0][1]
                                            msg_full = email.message_from_bytes(raw_full) 
                                            body_text = ''
                                            for part in msg_full.walk():
                                                if part.get_content_type() == 'text/plain' and part.get_payload():
                                                    try:
                                                        payload = part.get_payload(decode=True)
                                                        if payload:
                                                            # U-8 -> UTF-8 POPRAWIONE
                                                            body_text += payload.decode(part.get_content_charset() or 'utf-8', errors='ignore')
                                                    except: pass
                                            
                                            m = id_pattern.search(body_text)
                                            if m: smtp_id = m.group(1).strip()
                                            
                                    if smtp_id and smtp_id not in found_ids:
                                        found_ids.add(smtp_id)
                                        success_queue.put(smtp_id)
                                except Exception as e: 
                                    logger.error(f"[IMAP FETCH ERROR] {str(e)[:50]}")
                                    pass
                    except Exception as e:
                        logger.error(f"[IMAP FOLDER ERROR] ({folder}): {str(e)[:50]}")
                        pass
                
                if scanned > 0:
                    logger.info(Fore.CYAN + f"[i] Aktualnie znaleziono {len(found_ids)} potwierdzonych SMTP.")
                else:
                    logger.info(Fore.CYAN + "[i] Brak nowych wiadomosci - czekam...")

            except Exception as e:
                logger.error(f"[IMAP OPERATIONAL ERROR] {str(e)[:70]}")

            finally:
                if mail:
                    try: mail.logout()
                    except: pass

            time.sleep(IMAP_POLL_INTERVAL_DEFAULT)

        # Ostateczne dodanie wszystkich znalezionych ID do kolejki
        for sid in found_ids: 
            success_queue.put(sid) 
        logger.info(Fore.GREEN + f"[+] IMAP monitoring finished. Found {len(found_ids)} confirmed deliveries.")
        
        # Przywracamy oryginalny socket na koniec
        socket.socket = original_socket


    # --- GŁÓWNA METODA URUCHAMIAJĄCA (SMTP + IMAP) ---

    def run(self):
        """Metoda uruchamiająca cały proces sprawdzania (SMTP + IMAP lub tylko IMAP)."""
        
        if self.settings.get('run_imap_only', False):
            self.run_imap_only()
            os._exit(0)
        
        self.is_smtp_phase_active = True

        # 1. Ładowanie i filtrowanie
        self.blocked_domains = self._load_blocked_domains(BLOCKED_DOMAINS_FILE)
        
        # ... (Logika ładowania i testowania Proxy) ...
        if self.settings['use_proxy']:
            proxy_list_raw = self.scrape_and_update_proxies(self.settings['proxy_filename']) if self.settings.get('auto_update_proxies') else self._load_proxies(self.settings['proxy_filename'])
            
            print(f"\nWczytano {len(proxy_list_raw)} proxy.")
            print("1) Sprawdz proxy (wolniej, ale pewnosc)")
            print("2) Pomijam test (szybciej)")
            mode = ""
            while True:
                c = input("Wybor (1/2): ").strip()
                if c == "1": mode = "check"; break
                if c == "2": mode = "skip"; break

            self.proxies = self.test_proxy_ultra_fast(self.settings['proxy_type'], proxy_list_raw) if mode == "check" else proxy_list_raw
            with open("good_proxies.txt", "w", encoding="utf-8") as f:
                f.write("\n".join(self.proxies) + "\n")

            if not self.proxies:
                logger.error(Fore.RED + "Brak dzialajacych proxy. Koncze.")
                return
            logger.info(Fore.GREEN + f"[+] Aktywnych proxy: {len(self.proxies)}")

        # Ładowanie serwerów
        smtp_servers_raw = []
        if os.path.exists(RESUME_FILE) and input("Wznowic z resume_smtp_progress.txt? (t/n): ").lower() == 't':
            smtp_servers_raw = self._load_smtp_servers(RESUME_FILE)
        else:
            smtp_servers_raw = self._load_smtp_servers(self.settings['smtp_file'])

        # Filtrowanie zablokowanych domen
        temp_list = [s for s in smtp_servers_raw if s[2].split('@')[-1].lower() not in self.blocked_domains]
        if len(temp_list) < len(smtp_servers_raw):
            logger.warning(f"[!] Usunieto {len(smtp_servers_raw) - len(temp_list)} serwerow ze wzgledu na zablokowane domeny.")
        smtp_servers_raw = temp_list

        # Walidacja DNS
        valid_smtp_servers = []
        with ThreadPoolExecutor(max_workers=self.settings['threads']) as ex:
            futures = {ex.submit(validate_smtp_host, h, int(p)): [h,p,u,w] for h,p,u,w in smtp_servers_raw}
            for f in tqdm(as_completed(futures), total=len(futures), desc="Walidacja DNS"):
                if f.result():
                    valid_smtp_servers.append(futures[f])
        
        if not valid_smtp_servers:
            logger.error(Fore.RED + "Brak poprawnych hostow SMTP do sprawdzenia.")
            return
        
        smtp_servers_to_process = {tuple(info): info for info in valid_smtp_servers}
        logger.info(Fore.CYAN + f"[i] Rozpoczynam sprawdzanie {len(smtp_servers_to_process)} serwerow SMTP...")

        # 2. Wysyłka E-maili
        success_queue = Queue()
        executor = ThreadPoolExecutor(max_workers=self.settings['threads'])
        futures_dict = {
            executor.submit(self.send_email, info, self.settings['to_email'], self.settings['subject'], self.settings['content'], 
                            self.settings['use_proxy'], self.settings['proxy_type'], success_queue): info 
            for info in smtp_servers_to_process.values()
        }

        progress_bar = tqdm(total=len(futures_dict), desc="Checking SMTP", miniters=1)
        completed_count = 0

        # Główna pętla monitorująca
        while completed_count < len(futures_dict) and not self.stop_scanning_flag:
            newly_completed = sum(1 for f in futures_dict if f.done()) - completed_count
            if newly_completed > 0:
                progress_bar.update(newly_completed)
                completed_count += newly_completed
            time.sleep(0.1)
        
        progress_bar.close()
        
        # 3. Zapisywanie postępu i czyszczenie
        
        self.is_smtp_phase_active = False 
        
        if self.stop_scanning_flag:
            logger.warning(Fore.MAGENTA + "[!!!] Wymuszam zakonczenie puli watkow i zapis postepu...")
            executor.shutdown(wait=False) # Przerwanie oczekujących zadań
            
            # Zapis pozostałych serwerów
            remaining = [info for f, info in futures_dict.items() if not f.done()]
            
            with open(RESUME_FILE, 'w', encoding='utf-8') as f:
                f.write('\n'.join('|'.join(i) for i in remaining) + '\n')
            logger.info(Fore.MAGENTA + f"[!!!] Zapisano {len(remaining)} serwerow do wznowienia w {RESUME_FILE}.")
        
        else:
            executor.shutdown(wait=True) # Zakończenie normalne
            if os.path.exists(RESUME_FILE):
                try:
                    os.remove(RESUME_FILE)
                    logger.info(Fore.CYAN + f"[i] Skanowanie zakonczone normalnie. Usunieto plik wznowienia: {RESUME_FILE}.")
                except Exception as e:
                    logger.warning(f"[!] Ostrzezenie: Nie udalo sie usunac pliku wznowienia: {e}")

        # 4. Weryfikacja IMAP
        logger.info(Fore.CYAN + "\n[i] Wysylka zakonczona. Rozpoczynam IMAP verification.")
        self.stop_scanning_flag = False
        
        self.imap_check(self.settings['imap_server'], self.settings['imap_user'], 
                        self.settings['imap_password'], self.settings['subject'], 
                        self.settings['duration'], success_queue) 

        # 5. Zapis końcowy - TYLKO te potwierdzone przez IMAP
        good = set()
        while not success_queue.empty():
            good.add(success_queue.get())
            
        final_good = list(good)

        with open("good.txt", "w", encoding="utf-8") as f:
            f.write('\n'.join(final_good) + '\n')
        
        logger.info(Fore.GREEN + f"\n[+] Zapisano {len(final_good)} dzialajacych SMTP do good.txt")
        print(Fore.CYAN + "="*60)
        print(Fore.CYAN + "Ai by Revo - koniec programu")
        print(Fore.CYAN + "="*60)
        os._exit(0)

    # --- NOWA METODA: TYLKO IMAP ---

    def run_imap_only(self):
        """Metoda uruchamiająca tylko weryfikację IMAP."""
        logger.info(Fore.CYAN + "\n" + "="*60)
        logger.info(Fore.CYAN + "Tryb: TYLKO WERYFIKACJA IMAP (Bez Wysylki)")
        logger.info(Fore.CYAN + "="*60 + "\n")
        self.is_smtp_phase_active = False

        success_queue = Queue()
        
        # 1. Weryfikacja IMAP
        self.stop_scanning_flag = False
        
        self.imap_check(self.settings['imap_server'], self.settings['imap_user'], 
                        self.settings['imap_password'], self.settings['subject'], 
                        self.settings['duration'], success_queue) 

        # 2. Zapis końcowy
        good_ids = set()
        while not success_queue.empty():
            good_ids.add(success_queue.get())
            
        final_good = list(good_ids)

        with open("good.txt", "w", encoding="utf-8") as f:
            f.write('\n'.join(final_good) + '\n')
        
        logger.info(Fore.GREEN + f"\n[+] Zapisano {len(final_good)} potwierdzonych dostaw do good.txt")
        print(Fore.CYAN + "="*60)
        print(Fore.CYAN + "Ai by Revo - koniec programu")
        print(Fore.CYAN + "="*60)


# ====================== PUNKT WEJŚCIA ======================

def main():
    logger.info(Fore.CYAN + "Ai by Revo - SMTP Checker + Proxy + IMAP Verification [FINAL]")

    current_settings = load_settings()
    settings = {}
    
    if current_settings:
        logger.info(Fore.CYAN + f"[i] Znaleziono plik {SETTINGS_FILE}.")
        choice = input("Chcesz uzyc zapisanych danych (s) czy wprowadzic nowe (n)? (s/n): ").lower()
        if choice == 's':
            settings = current_settings
            logger.info(Fore.GREEN + "[+] Uzywam zapisanej konfiguracji.")
        else:
            settings = get_user_input(current_settings)
            save_settings(settings)
    else:
        settings = get_user_input()
        save_settings(settings)

    # Uruchomienie głównego procesu
    checker = SmtpChecker(settings)
    
    try:
        checker.run()
    except KeyboardInterrupt:
        logger.warning(Fore.MAGENTA + "[!!!] Przechwycono KeyboardInterrupt w main. Wychodzenie...")
        pass


if __name__ == "__main__":
    main()
