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
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from tqdm import tqdm
import re
from html import unescape

init(autoreset=True)

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

# ----------------------- Konfiguracja -----------------------
MAX_WORKERS = 100
SEMAPHORE_LIMIT = 100  # Zwiększone! Więcej równoległych połączeń SMTP
CONNECT_TIMEOUT = 4  # Krótszy timeout
SMTP_OP_TIMEOUT = 5  # Krótszy timeout dla operacji SMTP
RETRY_COUNT = 1
BACKOFF_BASE = 1.5
PROXY_CHECK_TIMEOUT = 3
IMAP_POLL_INTERVAL = 5

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

# Lock to avoid race conditions when monkeypatching the global socket for proxy use
proxy_lock = threading.Lock()

# ----------------------- 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 filter_working_proxies(proxy_list, test_function):
    """ Testuje proxy równolegle i zwraca tylko działające """
    print("\nTestowanie proxy, proszę czekać...")

    working = []
    with ThreadPoolExecutor(max_workers=20) as exe:
        futures = {exe.submit(test_function, proxy): proxy for proxy in proxy_list}
        for fut in as_completed(futures):
            proxy = futures[fut]
            try:
                ok = fut.result()
                if ok:
                    working.append(proxy)
                    print(f"[OK] {proxy}")
                else:
                    print(f"[BAD] {proxy}")
            except Exception:
                print(f"[ERR] {proxy}")

    print(f"\nGotowe. Działających proxy: {len(working)} / {len(proxy_list)}")
    return working

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(proxy_type, proxy_str):
    """Set global proxy by monkeypatching socket.socket to socks.socksocket.
    NOTE: This is a global change – callers should use proxy_lock to avoid races.
    """
    ip, port = proxy_str.split(':')
    port = int(port)
    types = {
        'http': socks.HTTP,
        'https': socks.HTTP,
        'socks4': socks.SOCKS4,
        'socks5': socks.SOCKS5
    }
    socks.setdefaultproxy(types[proxy_type], ip, port)
    socket.socket = socks.socksocket

def reset_socket():
    """Restore original socket implementation."""
    global ORIGINAL_SOCKET
    socket.socket = ORIGINAL_SOCKET

def test_proxy_worker_fast(proxy_type, proxy_str):
    """
    ULTRA FAST proxy test using requests library.
    Similar to KC-Checker approach - just test if proxy can connect.
    """
    try:
        # Format proxy for requests library
        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

        # Quick connectivity test to Google (fast and reliable)
        response = requests.get(
            'http://www.google.com',
            proxies=proxies,
            timeout=2,  # Very short timeout
            allow_redirects=False
        )
        
        # If we get any response (even error codes), proxy is alive
        if response.status_code:
            return proxy_str
        return None
        
    except Exception:
        return None

def test_proxy_worker(proxy_type, proxy_str):
    """Test proxy by attempting a simple SMTP banner read (connect to port 587).
       Uses proxy_lock to avoid concurrent global monkeypatch races.
    """
    try:
        with proxy_lock:
            set_proxy(proxy_type, proxy_str)
            try:
                sock = socket.create_connection(("142.250.195.108", 587), 
timeout=PROXY_CHECK_TIMEOUT)
                banner = sock.recv(512)
                sock.close()
            except Exception:
                banner = None
            # restore socket immediately after the network operation
            reset_socket()
        # consider proxy good only if we got an SMTP-like banner
        if banner and b'220' in banner:
            return proxy_str
        return None
    except Exception:
        try:
            reset_socket()
        except:
            pass
        return None

def test_proxy_ultra_fast(proxy_type, proxy_list):
    """
    Ultra fast proxy testing inspired by KC-Checker.
    Tests 300+ proxies simultaneously with requests library.
    """
    print(Fore.YELLOW + "[i] Using ULTRA FAST mode (KC-Checker style)")
    good = []
    # MASSIVE parallel testing - 300 workers!
    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 test_proxy_fast(proxy_type, proxy_list):
    """Standard fast proxy testing"""
    good = []
    with ThreadPoolExecutor(max_workers=150) as ex:
        futures = {ex.submit(test_proxy_worker, proxy_type, p): p for p in proxy_list}
        for f in tqdm(as_completed(futures), total=len(futures), desc="Testing proxy"):
            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.
       This function assumes global socket is already patched if needed.
    """
    context = ssl.create_default_context()
    if port == 465:
        server = smtplib.SMTP_SSL(host, port, timeout=timeout, context=context)
    else:
        server = smtplib.SMTP(host, port, timeout=timeout)
        server.starttls(context=context)
    return server

def send_email(smtp_info, to_email, subject, content, use_proxy, proxy_type, proxy_list, 
success_queue, semaphore):
    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
    # nadal dodajemy nagłówek (nie zaszkodzi jeśli provider go zachowa)
    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:
            # If using proxy, patch socket in a critical section to avoid races.
            if use_proxy and chosen_proxy:
                with proxy_lock:
                    set_proxy(proxy_type, chosen_proxy)
                    # create server while global socket is patched
                    server = create_smtp_connection(host, port, SMTP_OP_TIMEOUT)
                    # we can restore global socket immediately after server object creation
                    reset_socket()
            else:
                server = create_smtp_connection(host, port, SMTP_OP_TIMEOUT)

            # login/send
            server.login(user, password)
            server.sendmail(user, to_email, message.as_string())
            server.quit()

            # log successful send
            success_queue.put(unique_id)
            if use_proxy and chosen_proxy:
                print(Fore.GREEN + f"[+] Email sent: {unique_id} via proxy {chosen_proxy}")
            else:
                print(Fore.GREEN + f"[+] Email sent: {unique_id}")

    except Exception as e:
        # Nie robimy retry - za wolno, po prostu logujemy błąd
        print(Fore.RED + f"[FAIL] {user} -> {str(e)[:50]}")

    finally:
        # Ensure socket restored in any case
        try:
            if use_proxy:
                reset_socket()
        except:
            pass
        # ensure server is closed if partially created
        try:
            if server:
                try:
                    server.quit()
                except:
                    server.close()
        except:
            pass

def imap_check(server, user, password, subject, duration_minutes, success_queue):
    """
    Solidny IMAP check:
    - odczytuje nagłówek X-SMTP-ID
    - fallback: wyszukuje w text/plain lub text/html wzorzec [SMTP-ID] <id>
    - obsługuje różne charsety i dekodowanie
    - działa przez duration_minutes i zbiera potwierdzone ID w found_ids
    - na końcu opróżnia success_queue i wpisuje tam tylko potwierdzone ID
    """
    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)
            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)
                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)

                        smtp_id = msg.get('X-SMTP-ID')
                        if smtp_id:
                            smtp_id = smtp_id.strip()
                        else:
                            smtp_id = None

                        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()

                        if smtp_id:
                            if 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:
                print(Fore.CYAN + f"[i] Skanowano {scanned} wiadomości z tematem '{subject}'")

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

        time.sleep(IMAP_POLL_INTERVAL)

    try:
        while True:
            success_queue.get_nowait()
    except Exception:
        pass

    for sid in found_ids:
        success_queue.put(sid)

    print(Fore.GREEN + "[+] Finished IMAP monitoring.")

# ----------------------- Main -----------------------
def main():
    smtp_file = input("Enter SMTP servers filename: ")
    threads = int(input("Enter number of threads: "))
    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): ")
        proxy_list_raw = load_proxies(proxy_file)

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

        # Zapytaj użytkownika czy testować proxy czy od razu używać listy
        mode = ask_proxy_mode(proxy_list_raw)

        if mode == "check":
            print("\n=== Wybierz tryb sprawdzania proxy ===")
            print("1) ULTRA FAST - Najszybszy (jak KC-Checker, tylko connectivity test)")
            print("2) STANDARD - Wolniejszy ale sprawdza SMTP banner")
            
            while True:
                test_mode = input("Wybierz 1 lub 2: ").strip()
                if test_mode in ["1", "2"]:
                    break
                print("Niepoprawna opcja!")
            
            print(Fore.CYAN + "[i] Testing proxy...")
            
            if test_mode == "1":
                proxy_list = test_proxy_ultra_fast(proxy_type, proxy_list_raw)
            else:
                proxy_list = test_proxy_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

    # --- kontynuacja programu ---
    smtp_servers = load_smtp_servers(smtp_file)
    success_queue = Queue()
    semaphore = threading.Semaphore(SEMAPHORE_LIMIT)

    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 + "[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"[+] Zapisano {len(good)} działających SMTP do good.txt")
    print(Fore.CYAN + "Ai by Revo - koniec programu")

if __name__ == "__main__":
    main()
