import smtplib
import ssl
import imaplib
import email
import threading
import time
import random
import os
import datetime
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
import socket
from tqdm import tqdm
import re
from html import unescape
import requests

init(autoreset=True)

print(Fore.CYAN + "Ai by Revo - SMTP Checker + Proxy + IMAP Verification [OPTIMIZED]")

# ----------------------- Konfiguracja -----------------------
MAX_WORKERS = 100
SEMAPHORE_LIMIT = 100  # Zwiększone dla lepszej równoległości
CONNECT_TIMEOUT = 5
SMTP_OP_TIMEOUT = 6
RETRY_COUNT = 0  # Bez retry dla prędkości
BACKOFF_BASE = 1.5
PROXY_CHECK_TIMEOUT = 2
IMAP_POLL_INTERVAL = 5

# Save original socket to be able to restore it reliably
ORIGINAL_SOCKET = socket.socket

# Thread-local storage dla proxy - każdy wątek ma swoją kopię
thread_local = threading.local()

# ----------------------- Funkcje -----------------------
def ask_proxy_mode(proxy_list):
    print("\n=== Proxy Mode ===")
    print(f"Wczytano {len(proxy_list)} proxy.")
    print("1) Najpierw sprawdź proxy (wolniej, ale pewność działania)")
    print("2) Pomijam test — od razu korzystaj z proxy do SMTP")

    while True:
        choice = input("Wybierz opcję 1 lub 2: ").strip()
        if choice == "1":
            return "check"
        elif choice == "2":
            return "skip"
        else:
            print("Niepoprawna opcja!")

def load_smtp_servers(filename):
    with open(filename, 'r', encoding='utf-8', errors='ignore') as f:
        return [line.strip().split('|') for line in f if line.strip() and 
len(line.strip().split('|')) == 4]

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

def set_proxy_thread_local(proxy_type, proxy_str):
    """Set proxy for current thread only using thread-local storage."""
    if not hasattr(thread_local, 'original_socket'):
        thread_local.original_socket = socket.socket
    
    ip, port = proxy_str.split(':')
    port = int(port)
    types = {
        'http': socks.HTTP,
        'https': socks.HTTP,
        'socks4': socks.SOCKS4,
        'socks5': socks.SOCKS5
    }
    
    # Create a new socket class for this thread
    socks.set_default_proxy(types[proxy_type], ip, port)
    socket.socket = socks.socksocket

def reset_socket_thread_local():
    """Restore original socket for current thread."""
    if hasattr(thread_local, 'original_socket'):
        socket.socket = thread_local.original_socket

def test_proxy_worker_fast(proxy_type, proxy_str):
    """Ultra fast proxy test using requests library."""
    try:
        if proxy_type in ['http', 'https']:
            proxies = {
                'http': f'http://{proxy_str}',
                'https': f'http://{proxy_str}'
            }
        elif proxy_type == 'socks4':
            proxies = {
                'http': f'socks4://{proxy_str}',
                'https': f'socks4://{proxy_str}'
            }
        elif proxy_type == 'socks5':
            proxies = {
                'http': f'socks5://{proxy_str}',
                'https': f'socks5://{proxy_str}'
            }
        else:
            return None

        response = requests.get(
            'http://www.google.com',
            proxies=proxies,
            timeout=2,
            allow_redirects=False
        )
        
        if response.status_code:
            return proxy_str
        return None
        
    except Exception:
        return None

def test_proxy_ultra_fast(proxy_type, proxy_list):
    """Ultra fast proxy testing."""
    print(Fore.YELLOW + "[i] Using ULTRA FAST mode")
    good = []
    with ThreadPoolExecutor(max_workers=300) as ex:
        futures = {ex.submit(test_proxy_worker_fast, proxy_type, p): p for p in proxy_list}
        for f in tqdm(as_completed(futures), total=len(futures), desc="Testing proxy [FAST]"):
            try:
                r = f.result()
            except Exception:
                r = None
            if r:
                good.append(r)
    return good

def create_smtp_connection(host, port, timeout):
    """Create SMTP or SMTP_SSL connection object with the configured timeout."""
    context = ssl.create_default_context()
    
    try:
        if port == 465:
            server = smtplib.SMTP_SSL(host, port, timeout=timeout, context=context)
        else:
            server = smtplib.SMTP(host, port, timeout=timeout)
            server.ehlo()
            server.starttls(context=context)
            server.ehlo()
        return server
    except Exception as e:
        raise e

def send_email(smtp_info, to_email, subject, content, use_proxy, proxy_type, proxy_list, 
success_queue, semaphore):
    """
    Optimized email sender with thread-local proxy.
    Uses semaphore but with higher limit for better parallelism.
    """
    host, port, user, password = smtp_info
    port = int(port)

    # Tworzenie unikalnego ID SMTP
    unique_id = f"{host}|{port}|{user}|{password}"

    # Dodajemy ID do treści (fallback, gdy header zostanie usunięty przez provider)
    body = content + "\n\n[SMTP-ID] " + unique_id

    message = MIMEMultipart()
    message['From'] = user
    message['To'] = to_email
    message['Subject'] = subject
    message['X-SMTP-ID'] = unique_id
    message.attach(MIMEText(body, 'plain'))

    server = None
    chosen_proxy = None
    
    try:
        if use_proxy and proxy_list:
            chosen_proxy = random.choice(proxy_list)

        with semaphore:
            # Set proxy for this thread if needed
            if use_proxy and chosen_proxy:
                set_proxy_thread_local(proxy_type, chosen_proxy)
            
            try:
                # Create SMTP connection
                server = create_smtp_connection(host, port, SMTP_OP_TIMEOUT)
                
                # Login and send
                server.login(user, password)
                server.sendmail(user, to_email, message.as_string())
                
                # Proper quit
                try:
                    server.quit()
                except:
                    server.close()
                
                # Log success
                success_queue.put(unique_id)
                if use_proxy and chosen_proxy:
                    print(Fore.GREEN + f"[+] ✓ {user[:35]}... via {chosen_proxy}")
                else:
                    print(Fore.GREEN + f"[+] ✓ {user[:35]}...")
                    
            finally:
                # Always restore socket for this thread
                if use_proxy:
                    reset_socket_thread_local()

    except Exception as e:
        error_msg = str(e)[:50]
        print(Fore.RED + f"[-] ✗ {user[:35]}... -> {error_msg}")
    
    finally:
        # Ensure server is closed
        if server:
            try:
                server.quit()
            except:
                try:
                    server.close()
                except:
                    pass

def imap_check(server, user, password, subject, duration_minutes, success_queue):
    """
    Solidny IMAP check z poprawkami.
    """
    id_pattern = re.compile(r'\[SMTP[-_\s]?ID\]\s*([^\s]+(?:\|[^\s]+)*)', re.IGNORECASE)
    found_ids = set()
    end_time = time.time() + duration_minutes * 60

    print(Fore.CYAN + "[i] IMAP monitor running...")

    while time.time() < end_time:
        try:
            mail = imaplib.IMAP4_SSL(server, timeout=10)
            mail.login(user, password)
            mail.select("inbox")

            today = datetime.date.today().strftime("%d-%b-%Y")
            result, data = mail.search(None, f'SINCE "{today}"')

            scanned = 0
            if result == 'OK' and data and data[0]:
                ids = data[0].split()
                scanned = len(ids)
                
                print(Fore.CYAN + f"[i] Znaleziono {scanned} wiadomości z dzisiaj, sprawdzam...")
                
                for msg_id in ids:
                    try:
                        res2, msg_data = mail.fetch(msg_id, '(RFC822)')
                        if res2 != 'OK' or not msg_data or not msg_data[0]:
                            continue
                        raw = msg_data[0][1]
                        msg = email.message_from_bytes(raw)

                        # Sprawdź temat
                        msg_subject = msg.get('Subject', '')
                        if subject.lower() not in msg_subject.lower():
                            continue  # Pomijamy wiadomości z innym tematem

                        # Próbuj odczytać z nagłówka
                        smtp_id = msg.get('X-SMTP-ID')
                        if smtp_id:
                            smtp_id = smtp_id.strip()
                        else:
                            smtp_id = None

                        # Fallback - szukaj w treści
                        if not smtp_id:
                            body_text = ''
                            if msg.is_multipart():
                                for part in msg.walk():
                                    ctype = part.get_content_type()
                                    disp = str(part.get('Content-Disposition') or '')
                                    if 'attachment' in disp.lower():
                                        continue
                                    try:
                                        payload = part.get_payload(decode=True)
                                    except:
                                        payload = None
                                    if not payload:
                                        continue
                                    charset = part.get_content_charset() or part.get_charsets() or 'utf-8'
                                    if isinstance(charset, (list, tuple)):
                                        charset = charset[0] or 'utf-8'
                                    try:
                                        text = payload.decode(charset, errors='ignore')
                                    except:
                                        try:
                                            text = payload.decode('utf-8', errors='ignore')
                                        except:
                                            text = str(payload)
                                    if ctype == 'text/plain':
                                        body_text += '\n' + text
                                    elif ctype == 'text/html':
                                        txt = re.sub(r'<script.*?>.*?</script>', '', text, 
flags=re.S|re.I)
                                        txt = re.sub(r'<style.*?>.*?</style>', '', txt, 
flags=re.S|re.I)
                                        txt = re.sub(r'<[^>]+>', ' ', txt)
                                        txt = unescape(txt)
                                        body_text += '\n' + txt
                            else:
                                try:
                                    payload = msg.get_payload(decode=True)
                                    if payload:
                                        ch = msg.get_content_charset() or 'utf-8'
                                        try:
                                            body_text = payload.decode(ch, errors='ignore')
                                        except:
                                            body_text = payload.decode('utf-8', errors='ignore')
                                    else:
                                        body_text = ''
                                except:
                                    body_text = ''

                            if body_text:
                                m = id_pattern.search(body_text)
                                if m:
                                    smtp_id = m.group(1).strip()

                        # Jeśli znaleziono ID, dodaj do listy
                        if smtp_id and smtp_id not in found_ids:
                            print(Fore.YELLOW + f"[+] Delivered: {smtp_id}")
                            found_ids.add(smtp_id)

                    except Exception as e_msg:
                        print(Fore.RED + f"[IMAP FETCH ERROR] {e_msg}")
                        continue

            mail.logout()
            
            if scanned > 0:
                print(Fore.CYAN + f"[i] Sprawdzono {scanned} wiadomości, znaleziono {len(found_ids)} potwierdzonych SMTP")

        except Exception as e:
            print(Fore.RED + f"[IMAP ERROR] {e}")

        time.sleep(IMAP_POLL_INTERVAL)

    # Clear queue and add only confirmed IDs
    print(Fore.CYAN + f"[i] Czyszczenie kolejki i zapisywanie {len(found_ids)} potwierdzonych ID...")
    try:
        while True:
            success_queue.get_nowait()
    except Exception:
        pass

    for sid in found_ids:
        success_queue.put(sid)

    print(Fore.GREEN + f"[+] IMAP monitoring finished. Found {len(found_ids)} confirmed deliveries.")

# ----------------------- Main -----------------------
def main():
    print(Fore.CYAN + "\n" + "="*60)
    print(Fore.CYAN + "  Fast & Reliable SMTP Checker")
    print(Fore.CYAN + "  Optimized for speed while maintaining reliability")
    print(Fore.CYAN + "="*60 + "\n")
    
    smtp_file = input("Enter SMTP servers filename: ")
    threads = int(input("Enter number of threads (recommended: 80-150): "))
    to_email = input("Enter recipient email: ")
    subject = input("Enter email subject: ")
    content = input("Enter email content: ")
    use_proxy = input("Use proxy? (y/n): ").lower() == 'y'

    proxy_list = []
    proxy_type = ''

    if use_proxy:
        proxy_file = input("Enter proxy list filename: ")
        proxy_type = input("Proxy type (http/https/socks4/socks5): ").lower()
        proxy_list_raw = load_proxies(proxy_file)

        print(Fore.CYAN + f"[i] Wczytano proxy: {len(proxy_list_raw)}")

        mode = ask_proxy_mode(proxy_list_raw)

        if mode == "check":
            print(Fore.CYAN + "[i] Testing proxy (ULTRA FAST mode)...")
            proxy_list = test_proxy_ultra_fast(proxy_type, proxy_list_raw)

            print(Fore.CYAN + f"[i] Working proxy: {len(proxy_list)}")

            if not proxy_list:
                print(Fore.RED + "Brak działających proxy. Kończę.")
                return
        else:
            print(Fore.YELLOW + "[i] Pomijam test proxy — używam pełnej listy.")
            proxy_list = proxy_list_raw

    # Load SMTP servers
    smtp_servers = load_smtp_servers(smtp_file)
    success_queue = Queue()
    semaphore = threading.Semaphore(SEMAPHORE_LIMIT)

    print(Fore.CYAN + f"\n[i] Loaded {len(smtp_servers)} SMTP servers")
    print(Fore.YELLOW + f"[i] Starting with {threads} parallel threads")
    print(Fore.GREEN + f"[i] Semaphore limit: {SEMAPHORE_LIMIT} (optimized for speed)\n")

    # Send emails
    with ThreadPoolExecutor(max_workers=threads) as executor:
        futures = []
        for smtp in smtp_servers:
            if len(smtp) == 4:
                futures.append(
                    executor.submit(
                        send_email, 
                        smtp, 
                        to_email, 
                        subject, 
                        content, 
                        use_proxy, 
                        proxy_type, 
                        proxy_list, 
                        success_queue,
                        semaphore
                    )
                )

        for _ in tqdm(as_completed(futures), total=len(futures), desc="Checking SMTP"):
            pass

    print(Fore.CYAN + "\n[i] Wysyłka zakończona. Rozpoczynam IMAP verification.")

    imap_server = input("IMAP server: ")
    imap_user = input("IMAP user: ")
    imap_pass = input("IMAP password: ")
    duration = int(input("Minutes to check inbox for replies: "))

    imap_check(imap_server, imap_user, imap_pass, subject, duration, success_queue)

    good = set()
    while not success_queue.empty():
        good.add(success_queue.get())

    with open("good.txt", "w", encoding="utf-8") as f:
        for s in good:
            f.write(s + "\n")

    print(Fore.GREEN + f"\n[+] Zapisano {len(good)} działających SMTP do good.txt")
    print(Fore.CYAN + "="*60)
    print(Fore.CYAN + "Ai by Revo - koniec programu")
    print(Fore.CYAN + "="*60)

if __name__ == "__main__":
    main()
