#!/usr/bin/env python3

# ============================================================
#   TELEGRAM BOT ENKRIPSI KODE v4.0
#   Author  : IKYY x CLAUDE
#   Version : 4.0 (Honeypot Fix + Auto Expiry + FTP)
# ============================================================

import os, re, sys, json, time, random, string, hashlib, base64
import zipfile, logging, tempfile, shutil, requests, sqlite3
from pathlib import Path
from datetime import datetime, date
from ftplib import FTP, error_perm

MAX_FILE_SIZE = 20 * 1024 * 1024
CONFIG_FILE   = os.path.expanduser("~/.enkripsi_bot_config.json")
USERS_FILE    = os.path.expanduser("~/.enkripsi_bot_users.json")
OFFSET_FILE   = os.path.expanduser("~/.enkripsi_bot_offset")
DB_FILE       = os.path.expanduser("~/.enkripsi_bot.db")

def load_or_ask_config():
    if os.path.exists(CONFIG_FILE):
        with open(CONFIG_FILE, 'r') as f:
            config = json.load(f)
        print(f"\n✅ Config ditemukan! Token: {config['token'][:20]}...")
        return config['token'], config['chat_id']
    print("\n" + "="*50)
    print("  SETUP BOT ENKRIPSI — IKYY x CLAUDE")
    print("="*50)
    token   = input("  Bot Token : ").strip()
    chat_id = input("  Chat ID   : ").strip()
    with open(CONFIG_FILE, 'w') as f:
        json.dump({'token': token, 'chat_id': chat_id}, f)
    os.chmod(CONFIG_FILE, 0o600)
    return token, chat_id

BOT_TOKEN, ALLOWED_CHAT_ID = load_or_ask_config()

# ============================================================
# DATABASE ENGINE
# ============================================================
def init_db():
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    c.execute('''CREATE TABLE IF NOT EXISTS expiry_projects (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        secret_key TEXT UNIQUE NOT NULL,
        project_name TEXT,
        exp_date TEXT NOT NULL,
        contact TEXT NOT NULL,
        source TEXT DEFAULT 'zip',
        ftp_host TEXT,
        ftp_path TEXT,
        created_at TEXT DEFAULT CURRENT_TIMESTAMP
    )''')
    c.execute('''CREATE TABLE IF NOT EXISTS ftp_config (
        id INTEGER PRIMARY KEY,
        host TEXT NOT NULL,
        username TEXT NOT NULL,
        password TEXT NOT NULL,
        port INTEGER DEFAULT 21
    )''')
    conn.commit()
    conn.close()

def generate_secret_key():
    chars = string.ascii_uppercase + string.digits
    suffix = ''.join(random.choices(chars, k=8))
    return f"PRJ-{suffix}"

def db_save_project(secret_key, exp_date, contact, project_name='', source='zip', ftp_host='', ftp_path=''):
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    c.execute('''INSERT INTO expiry_projects (secret_key, project_name, exp_date, contact, source, ftp_host, ftp_path)
                 VALUES (?,?,?,?,?,?,?)''',
              (secret_key, project_name, exp_date, contact, source, ftp_host, ftp_path))
    conn.commit()
    conn.close()

def db_get_project(secret_key):
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    c.execute('SELECT * FROM expiry_projects WHERE secret_key=?', (secret_key,))
    row = c.fetchone()
    conn.close()
    if not row: return None
    cols = ['id','secret_key','project_name','exp_date','contact','source','ftp_host','ftp_path','created_at']
    return dict(zip(cols, row))

def db_update_expiry(secret_key, new_exp_date):
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    c.execute('UPDATE expiry_projects SET exp_date=? WHERE secret_key=?', (new_exp_date, secret_key))
    conn.commit()
    conn.close()

def db_delete_project(secret_key):
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    c.execute('DELETE FROM expiry_projects WHERE secret_key=?', (secret_key,))
    conn.commit()
    conn.close()

def db_save_ftp(host, username, password, port=21):
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    c.execute('DELETE FROM ftp_config')
    c.execute('INSERT INTO ftp_config (id, host, username, password, port) VALUES (1,?,?,?,?)',
              (host, username, password, port))
    conn.commit()
    conn.close()

def db_get_ftp():
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    c.execute('SELECT host, username, password, port FROM ftp_config WHERE id=1')
    row = c.fetchone()
    conn.close()
    if not row: return None
    return {'host': row[0], 'username': row[1], 'password': row[2], 'port': row[3]}

init_db()

LOG_FILE_PATH = os.path.expanduser("~/.enkripsi_bot.log")
logging.basicConfig(level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[logging.FileHandler(LOG_FILE_PATH), logging.StreamHandler()])
log = logging.getLogger(__name__)

# ============================================================
# MULTI-USER
# ============================================================
def load_users():
    if os.path.exists(USERS_FILE):
        try:
            with open(USERS_FILE, 'r') as f:
                return json.load(f)
        except: return {"allowed": [], "blocked": []}
    return {"allowed": [], "blocked": []}

def save_users(users):
    with open(USERS_FILE, 'w') as f:
        json.dump(users, f, indent=2)

def is_allowed(chat_id):
    if str(chat_id) == str(ALLOWED_CHAT_ID): return True
    if is_blocked(chat_id): return False
    users = load_users()
    return str(chat_id) in [str(u) for u in users.get("allowed", [])]

def is_blocked(chat_id):
    users = load_users()
    return str(chat_id) in [str(u) for u in users.get("blocked", [])]

def add_user(chat_id):
    users = load_users()
    if str(chat_id) not in [str(u) for u in users["allowed"]]:
        users["allowed"].append(str(chat_id))
        save_users(users)
        return True
    return False

def remove_user(chat_id):
    users = load_users()
    users["allowed"] = [u for u in users["allowed"] if str(u) != str(chat_id)]
    save_users(users)

def block_user(chat_id):
    users = load_users()
    users["allowed"] = [u for u in users.get("allowed", []) if str(u) != str(chat_id)]
    if str(chat_id) not in [str(u) for u in users.get("blocked", [])]:
        users.setdefault("blocked", []).append(str(chat_id))
        save_users(users)
        return True
    save_users(users)
    return False

def get_users():
    users = load_users()
    return users.get("allowed", []), users.get("blocked", [])

# ============================================================
# TELEGRAM API
# ============================================================
API_URL = f"https://api.telegram.org/bot{BOT_TOKEN}"

def send_message(chat_id, text, parse_mode="HTML"):
    try:
        requests.post(f"{API_URL}/sendMessage",
            data={"chat_id": chat_id, "text": text, "parse_mode": parse_mode}, timeout=30)
    except Exception as e:
        log.error(f"Send message error: {e}")

def send_document(chat_id, file_path, caption=""):
    try:
        if not os.path.exists(file_path):
            log.error(f"File tidak ditemukan: {file_path}")
            send_message(chat_id, "❌ Gagal kirim file — file tidak ditemukan.")
            return
        with open(file_path, 'rb') as f:
            requests.post(f"{API_URL}/sendDocument",
                data={"chat_id": chat_id, "caption": caption},
                files={"document": f}, timeout=60)
    except Exception as e:
        log.error(f"Send document error: {e}")

def send_typing(chat_id):
    try:
        requests.post(f"{API_URL}/sendChatAction",
            data={"chat_id": chat_id, "action": "typing"}, timeout=10)
    except: pass

def download_file(file_id, dest_path):
    try:
        r = requests.get(f"{API_URL}/getFile?file_id={file_id}", timeout=30)
        if r.status_code != 200: return False
        result = r.json().get('result')
        if not result: return False
        file_url = f"https://api.telegram.org/file/bot{BOT_TOKEN}/{result['file_path']}"
        r2 = requests.get(file_url, timeout=60)
        if r2.status_code != 200: return False
        with open(dest_path, 'wb') as f:
            f.write(r2.content)
        return True
    except Exception as e:
        log.error(f"Download error: {e}")
        return False

# ============================================================
# ENKRIPSI ENGINE
# ============================================================
def random_var(length=8):
    return ''.join(random.choices(string.ascii_letters, k=length))

def generate_xor_key(length=16):
    return ''.join(random.choices(string.ascii_letters + string.digits, k=length))

# BUG 1 FIX: xor_encrypt handle Unicode dengan benar
def xor_encrypt_bytes(data_bytes: bytes, key: str) -> bytes:
    key_bytes = key.encode('utf-8')
    return bytes([b ^ key_bytes[i % len(key_bytes)] for i, b in enumerate(data_bytes)])

# ============================================================
# PHP OBFUSCATOR
# ============================================================
def remove_php_comments(code):
    result = []
    i = 0
    in_single = False
    in_double = False
    while i < len(code):
        c = code[i]
        if c == "'" and not in_double:
            in_single = not in_single; result.append(c); i += 1
        elif c == '"' and not in_single:
            in_double = not in_double; result.append(c); i += 1
        elif not in_single and not in_double:
            if code[i:i+2] == '//':
                while i < len(code) and code[i] != '\n': i += 1
                result.append('\n')
            elif code[i:i+2] == '/*':
                while i < len(code) and code[i:i+2] != '*/': i += 1
                i += 2
            elif c == '#':
                while i < len(code) and code[i] != '\n': i += 1
                result.append('\n')
            else:
                result.append(c); i += 1
        else:
            result.append(c); i += 1
    return ''.join(result)

def obfuscate_php(code):
    try:
        code = remove_php_comments(code)
        inner = re.sub(r'<\?php', '', code)
        inner = re.sub(r'\?>', '', inner).strip()
        # BUG 2 FIX: cek PHP kosong
        if not inner.strip():
            return "<?php // Empty file ?>"
        encoded = base64.b64encode(inner.encode('utf-8')).decode()
        v1, v2, v3, v4, v5 = [random_var() for _ in range(5)]
        return f"""<?php
${v1}=base64_decode('{base64.b64encode(b"base64_decode").decode()}');
${v2}=base64_decode('{base64.b64encode(b"str_rot13").decode()}');
${v3}=str_rot13(base64_decode('{base64.b64encode(base64.b64encode(encoded.encode()).decode().encode()).decode()}'));
${v4}=${v1}(${v3});
${v5}=base64_decode('{base64.b64encode(b"eval").decode()}');
${v5}(${v4});
?>"""
    except Exception as e:
        return f"ERROR: {e}"

def obfuscate_php_strong(code):
    try:
        code = remove_php_comments(code)
        inner = re.sub(r'<\?php', '', code)
        inner = re.sub(r'\?>', '', inner).strip()
        # BUG 2 FIX: cek PHP kosong
        if not inner.strip():
            return "<?php // Empty file ?>"
        # BUG 1 FIX: encode ke bytes dulu sebelum XOR
        xor_key = generate_xor_key(16)
        inner_bytes = inner.encode('utf-8')
        xor_encrypted = xor_encrypt_bytes(inner_bytes, xor_key)
        b64_1 = base64.b64encode(xor_encrypted).decode()
        b64_2 = base64.b64encode(b64_1.encode()).decode()
        b64_key = base64.b64encode(xor_key.encode()).decode()
        v1, v2, v3, v4, v5, v6, v7 = [random_var() for _ in range(7)]
        return f"""<?php
${v1}=base64_decode(base64_decode('{b64_2}'));
${v2}=base64_decode('{b64_key}');
${v3}='';
${v6}=strlen(${v2});
for(${v4}=0;${v4}<strlen(${v1});${v4}++){{
${v3}.=chr(ord(${v1}[${v4}])^ord(${v2}[${v4}%${v6}]));}}
${v5}=base64_decode('{base64.b64encode(b"eval").decode()}');
${v5}(${v3});
?>"""
    except Exception as e:
        return f"ERROR: {e}"

# ============================================================
# JS OBFUSCATOR
# ============================================================
def remove_js_comments(code):
    result = []
    i = 0
    in_single = False
    in_double = False
    in_template = False
    while i < len(code):
        c = code[i]
        if c == "'" and not in_double and not in_template:
            in_single = not in_single; result.append(c); i += 1
        elif c == '"' and not in_single and not in_template:
            in_double = not in_double; result.append(c); i += 1
        elif c == '`' and not in_single and not in_double:
            in_template = not in_template; result.append(c); i += 1
        elif not in_single and not in_double and not in_template:
            if code[i:i+2] == '//':
                while i < len(code) and code[i] != '\n': i += 1
                result.append('\n')
            elif code[i:i+2] == '/*':
                while i < len(code) and code[i:i+2] != '*/': i += 1
                i += 2
            else:
                result.append(c); i += 1
        else:
            result.append(c); i += 1
    return ''.join(result)

def obfuscate_js(code):
    try:
        code = remove_js_comments(code)
        b64 = base64.b64encode(code.encode('utf-8')).decode()
        v1, v2, v3 = [random_var() for _ in range(3)]
        return f"(function(){{{v1}='{b64}';{v2}=atob||function(s){{return Buffer.from(s,'base64').toString()}};{v3}=eval;{v3}({v2}({v1}))}})();"
    except Exception as e:
        return f"ERROR: {e}"

def obfuscate_js_strong(code):
    try:
        code = remove_js_comments(code)
        # BUG 1 FIX: encode ke bytes dulu
        xor_key = generate_xor_key(16)
        code_bytes = code.encode('utf-8')
        xor_encrypted = xor_encrypt_bytes(code_bytes, xor_key)
        # Encode ke hex string — lebih safe dari double atob
        hex_data = xor_encrypted.hex()
        b64_key  = base64.b64encode(xor_key.encode()).decode()
        v1, v2, v3, v4, v5, v6 = [random_var() for _ in range(6)]
        return f"""(function(){{
var {v1}='{hex_data}';
var {v2}=atob('{b64_key}');
var {v3}=[];
for(var {v4}=0;{v4}<{v1}.length;{v4}+=2){{
{v3}.push(parseInt({v1}.substr({v4},2),16));}}
var {v5}='';
for(var {v6}=0;{v6}<{v3}.length;{v6}++){{
{v5}+=String.fromCharCode({v3}[{v6}]^{v2}.charCodeAt({v6}%{v2}.length));}}
eval({v5});
}})();"""
    except Exception as e:
        return f"ERROR: {e}"

# ============================================================
# HTML, CSS, PYTHON, SQL
# ============================================================
def obfuscate_html(code):
    try:
        code = re.sub(r'<!--.*?-->', '', code, flags=re.DOTALL)
        code = re.sub(r'\s+', ' ', code)
        code = re.sub(r'> <', '><', code)
        return code.strip()
    except Exception as e:
        return f"ERROR: {e}"

# BUG 5 FIX: obfuscate_css tidak merusak URL
def obfuscate_css(code):
    try:
        code = re.sub(r'/\*.*?\*/', '', code, flags=re.DOTALL)
        # Hapus whitespace berlebih tapi jaga string di dalam quotes
        code = re.sub(r'\s+', ' ', code)
        code = re.sub(r'\s*{\s*', '{', code)
        code = re.sub(r'\s*}\s*', '}', code)
        code = re.sub(r'\s*;\s*', ';', code)
        code = re.sub(r'\s*,\s*', ',', code)
        # BUG 5 FIX: Tidak collapse spasi di sekitar ':' karena bisa merusak URL
        # Hanya hapus spasi di selector (sebelum '{'), bukan di dalam nilai property
        return code.strip()
    except Exception as e:
        return f"ERROR: {e}"

def obfuscate_python(code):
    try:
        code = re.sub(r'(?m)^[ \t]*#.*$', '', code)
        encoded = base64.b64encode(code.encode('utf-8')).decode()
        v1, v2 = random_var(), random_var()
        return f"import base64\n{v1}='{encoded}'\n{v2}=base64.b64decode({v1}).decode()\nexec({v2})"
    except Exception as e:
        return f"ERROR: {e}"

def minify_sql(code):
    try:
        code = re.sub(r'--.*?\n', '\n', code)
        code = re.sub(r'/\*.*?\*/', '', code, flags=re.DOTALL)
        code = re.sub(r'\s+', ' ', code)
        return code.strip()
    except Exception as e:
        return f"ERROR: {e}"

def minify_php(code):
    code = remove_php_comments(code)
    code = re.sub(r'\s+', ' ', code)
    return code.strip()

def minify_js(code):
    code = remove_js_comments(code)
    code = re.sub(r'\s+', ' ', code)
    return code.strip()

def minify_html(code):
    code = re.sub(r'<!--.*?-->', '', code, flags=re.DOTALL)
    code = re.sub(r'\s+', ' ', code)
    code = re.sub(r'> <', '><', code)
    return code.strip()

def remove_comments(code):
    code = remove_php_comments(code)
    code = re.sub(r'<!--.*?-->', '', code, flags=re.DOTALL)
    code = re.sub(r'--.*?\n', '\n', code)
    return code.strip()

def encode_base64(text):  return base64.b64encode(text.encode()).decode()
def decode_base64(text):
    try: return base64.b64decode(text.encode()).decode()
    except: return "ERROR: Bukan format Base64 yang valid!"
def decode_url(text):
    try:
        from urllib.parse import unquote
        return unquote(text)
    except Exception as e: return f"ERROR: {e}"
def generate_md5(text):    return hashlib.md5(text.encode()).hexdigest()
def generate_sha256(text): return hashlib.sha256(text.encode()).hexdigest()
def generate_random_key(length=32):
    return ''.join(random.choices(string.ascii_letters + string.digits + "!@#$%^&*", k=length))
def count_lines(code): return len(code.strip().split('\n'))

# ============================================================
# BCRYPT & HASH FUNCTIONS
# ============================================================
def bcrypt_hash(password):
    try:
        import bcrypt
        hashed = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt(rounds=12))
        return hashed.decode('utf-8')
    except ImportError:
        # Fallback kalau bcrypt tidak ada
        try:
            import subprocess
            result = subprocess.run(
                ['python3', '-c', f"import bcrypt; print(bcrypt.hashpw(b'{password}', bcrypt.gensalt()).decode())"],
                capture_output=True, text=True, timeout=10)
            if result.returncode == 0:
                return result.stdout.strip()
        except: pass
        return "ERROR: Library bcrypt tidak terinstall. Jalankan: pip install bcrypt"
    except Exception as e:
        return f"ERROR: {e}"

def bcrypt_verify(password, hashed):
    try:
        import bcrypt
        return bcrypt.checkpw(password.encode('utf-8'), hashed.encode('utf-8'))
    except ImportError:
        return None
    except Exception:
        return False

def hash_all(text):
    """Generate semua hash sekaligus"""
    return {
        'MD5':    hashlib.md5(text.encode()).hexdigest(),
        'SHA1':   hashlib.sha1(text.encode()).hexdigest(),
        'SHA256': hashlib.sha256(text.encode()).hexdigest(),
        'SHA512': hashlib.sha512(text.encode()).hexdigest(),
        'SHA384': hashlib.sha384(text.encode()).hexdigest(),
    }

def identify_hash(hash_str):
    """Identifikasi tipe hash berdasarkan panjang dan format"""
    hash_str = hash_str.strip()
    results = []

    # bcrypt
    if re.match(r'^\$2[aby]\$\d{2}\$.{53}$', hash_str):
        results.append(('bcrypt', 'PASTI', 'Tidak bisa di-decode, hanya bisa verify'))
        return results

    # JWT
    if re.match(r'^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$', hash_str):
        results.append(('JWT Token', 'PASTI', 'Payload bisa di-decode, signature tidak'))
        return results

    length = len(hash_str)
    is_hex = bool(re.match(r'^[a-fA-F0-9]+$', hash_str))
    is_b64 = bool(re.match(r'^[A-Za-z0-9+/=]+$', hash_str))

    if is_hex:
        if length == 32:   results.append(('MD5', 'KEMUNGKINAN BESAR', 'Tidak bisa di-decode'))
        if length == 40:   results.append(('SHA1', 'KEMUNGKINAN BESAR', 'Tidak bisa di-decode'))
        if length == 56:   results.append(('SHA224', 'KEMUNGKINAN BESAR', 'Tidak bisa di-decode'))
        if length == 64:   results.append(('SHA256', 'KEMUNGKINAN BESAR', 'Tidak bisa di-decode'))
        if length == 96:   results.append(('SHA384', 'KEMUNGKINAN BESAR', 'Tidak bisa di-decode'))
        if length == 128:  results.append(('SHA512', 'KEMUNGKINAN BESAR', 'Tidak bisa di-decode'))

    if is_b64 and not is_hex:
        results.append(('Base64', 'KEMUNGKINAN', 'Bisa di-decode dengan /base64_decode'))

    if not results:
        results.append(('Unknown', 'TIDAK DIKENAL', 'Format hash tidak dikenali'))

    return results

def decrypt_rot13(text):
    return text.translate(str.maketrans(
        'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
        'NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm'
    ))

def decrypt_caesar(text, shift=None):
    """Decrypt Caesar cipher — coba semua shift kalau tidak diketahui"""
    if shift is not None:
        result = ''
        for char in text:
            if char.isalpha():
                shifted = (ord(char.lower()) - ord('a') - shift) % 26
                result += chr(shifted + ord('A') if char.isupper() else shifted + ord('a'))
            else:
                result += char
        return result
    else:
        # Brute force semua 25 kemungkinan
        results = []
        for s in range(1, 26):
            decrypted = decrypt_caesar(text, s)
            results.append(f"Shift {s:2d}: {decrypted}")
        return '\n'.join(results)

def decode_jwt_payload(token):
    """Decode JWT payload (bukan verify signature)"""
    try:
        parts = token.split('.')
        if len(parts) != 3:
            return "ERROR: Bukan format JWT yang valid"
        # Tambah padding jika perlu
        payload = parts[1]
        payload += '=' * (4 - len(payload) % 4)
        decoded = base64.urlsafe_b64decode(payload).decode('utf-8')
        parsed = json.loads(decoded)
        return json.dumps(parsed, indent=2, ensure_ascii=False)
    except Exception as e:
        return f"ERROR: {e}"

def detect_language(code):
    code_lower = code.lower()
    if '<?php' in code_lower or '$_post' in code_lower: return 'PHP'
    elif 'function' in code_lower and ('var ' in code_lower or 'const ' in code_lower or 'let ' in code_lower): return 'JavaScript'
    elif '<html' in code_lower or '<!doctype' in code_lower: return 'HTML'
    elif '{' in code_lower and ':' in code_lower and ';' in code_lower: return 'CSS'
    elif 'def ' in code_lower or 'import ' in code_lower: return 'Python'
    elif 'select ' in code_lower or 'insert ' in code_lower: return 'SQL'
    return 'Unknown'

def process_file_by_extension(content, ext, strong=False):
    ext = ext.lower()
    if ext == 'php':   return (obfuscate_php_strong(content) if strong else obfuscate_php(content)), 'php'
    elif ext == 'js':  return (obfuscate_js_strong(content) if strong else obfuscate_js(content)), 'js'
    elif ext in ['html', 'htm']: return obfuscate_html(content), 'html'
    elif ext == 'css': return obfuscate_css(content), 'css'
    elif ext == 'py':  return obfuscate_python(content), 'py'
    elif ext == 'sql': return minify_sql(content), 'sql'
    return content, ext

# BUG 3 FIX: Tambah UnicodeEncodeError ke exception list
def process_zip(input_path, output_path, strong=False):
    processed = 0
    skipped = 0
    supported_ext = ['php', 'js', 'html', 'htm', 'css', 'py', 'sql']
    with zipfile.ZipFile(input_path, 'r') as zin:
        with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zout:
            for item in zin.infolist():
                data = zin.read(item.filename)
                ext = item.filename.rsplit('.', 1)[-1].lower() if '.' in item.filename else ''
                if ext in supported_ext:
                    try:
                        content = data.decode('utf-8')
                        encoded, _ = process_file_by_extension(content, ext, strong)
                        zout.writestr(item, encoded.encode('utf-8'))
                        processed += 1
                    except (UnicodeDecodeError, UnicodeEncodeError, ValueError, AttributeError) as e:
                        log.warning(f"Skip {item.filename}: {e}")
                        zout.writestr(item, data)
                        skipped += 1
                else:
                    zout.writestr(item, data)
                    skipped += 1
    return processed, skipped

# ============================================================
# EXPIRY ENGINE
# ============================================================

EXPIRED_PAGE_TEMPLATE = """<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lisensi Habis</title>
<style>
*{{margin:0;padding:0;box-sizing:border-box}}
body{{background:#0a0a0a;min-height:100vh;display:flex;align-items:center;justify-content:center;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;padding:1rem}}
.card{{background:#141414;border:0.5px solid #2a2a2a;border-radius:16px;padding:2.5rem 2rem;text-align:center;max-width:400px;width:100%;position:relative;overflow:hidden}}
.card::before{{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,#c0392b,#e74c3c,#c0392b)}}
.icon-wrap{{width:64px;height:64px;background:#1e0a0a;border:1px solid #3d1010;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 1.5rem;font-size:28px}}
.badge{{display:inline-block;background:#1e0a0a;border:0.5px solid #3d1010;border-radius:6px;padding:4px 12px;margin-bottom:1rem;font-size:11px;color:#e74c3c;letter-spacing:1.5px;text-transform:uppercase;font-weight:500}}
h1{{font-size:22px;font-weight:500;color:#f0f0f0;margin-bottom:.5rem}}
p{{font-size:14px;color:#666;line-height:1.6;margin-bottom:1.5rem}}
.info-box{{background:#0f0f0f;border:0.5px solid #222;border-radius:10px;padding:1rem;margin-bottom:1.5rem}}
.info-row{{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px}}
.info-row:last-child{{margin-bottom:0}}
.info-label{{font-size:12px;color:#555}}
.info-value{{font-size:12px;color:#e74c3c;font-weight:500}}
.info-badge{{background:#1e0a0a;color:#e74c3c;padding:2px 8px;border-radius:4px;font-size:12px}}
.contact-box{{background:#0f0f0f;border:0.5px solid #222;border-radius:10px;padding:1rem;margin-bottom:1.5rem;text-align:left}}
.contact-label{{font-size:12px;color:#555;margin-bottom:4px}}
.contact-value{{font-size:14px;color:#bbb}}
.divider{{height:.5px;background:#1e1e1e;margin-bottom:1.5rem}}
.footer{{font-size:11px;color:#333;letter-spacing:.5px}}
</style>
</head>
<body>
<div class="card">
  <div class="icon-wrap">🔐</div>
  <div class="badge">Akses Ditolak</div>
  <h1>Lisensi Habis</h1>
  <p>Masa aktif lisensi kamu sudah berakhir.<br>Hubungi admin untuk perpanjang akses.</p>
  <div class="info-box">
    <div class="info-row">
      <span class="info-label">Tanggal expired</span>
      <span class="info-value">{exp_date}</span>
    </div>
    <div class="info-row">
      <span class="info-label">Status</span>
      <span class="info-badge">Expired</span>
    </div>
  </div>
  <div class="contact-box">
    <div class="contact-label">Kontak admin</div>
    <div class="contact-value">{contact}</div>
  </div>
  <div class="divider"></div>
  <div class="footer">IKYY x CLAUDE</div>
</div>
</body>
</html>"""

def build_expiry_code(exp_date, contact, secret_key):
    """Generate kode expiry PHP yang akan diinjeksi"""
    expired_html = EXPIRED_PAGE_TEMPLATE.format(exp_date=exp_date, contact=contact)
    escaped_html = expired_html.replace("'", "\\'").replace("\n", "\\n")
    return f"""<?php
// === Auto Expiry — IKYY x CLAUDE | KEY:{secret_key} ===
$__exp = '{exp_date}';
if (date('Y-m-d') > $__exp) {{
    http_response_code(403);
    die('{escaped_html}');
}}
// ======================================================
"""

def inject_expiry_to_php(content, exp_date, contact, secret_key):
    """Inject kode expiry ke file PHP"""
    # Hapus expiry lama kalau ada
    content = re.sub(
        r'<\?php\s*\n// === Auto Expiry.*?// ======================================================\s*\n',
        '<?php\n', content, flags=re.DOTALL)
    expiry_code = build_expiry_code(exp_date, contact, secret_key)
    # Inject setelah <?php
    result = re.sub(r'<\?php\s*\n?', expiry_code, content, count=1)
    return result

def remove_expiry_from_php(content):
    """Hapus kode expiry dari file PHP"""
    result = re.sub(
        r'// === Auto Expiry — IKYY x CLAUDE.*?// ======================================================\s*\n',
        '', content, flags=re.DOTALL)
    return result

def process_zip_expiry(input_path, output_path, exp_date, contact, secret_key, chat_id=None):
    """Inject expiry ke semua PHP dalam ZIP"""
    injected = 0
    skipped  = 0
    try:
        with zipfile.ZipFile(input_path, 'r') as zin:
            total_php = sum(1 for i in zin.infolist() if not i.is_dir() and i.filename.rsplit('.',1)[-1].lower() == 'php')
            with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zout:
                for item in zin.infolist():
                    data = zin.read(item.filename)
                    ext  = item.filename.rsplit('.', 1)[-1].lower() if '.' in item.filename else ''
                    if ext == 'php':
                        try:
                            content = data.decode('utf-8')
                            result  = inject_expiry_to_php(content, exp_date, contact, secret_key)
                            zout.writestr(item, result.encode('utf-8'))
                            injected += 1
                            # Kirim progress setiap 10 file
                            if chat_id and total_php > 10 and injected % 10 == 0:
                                send_message(chat_id, f"⏳ Progress: {injected}/{total_php} file PHP...")
                        except Exception:
                            zout.writestr(item, data)
                            skipped += 1
                    else:
                        zout.writestr(item, data)
    except Exception as e:
        raise Exception(f"Gagal proses ZIP: {e}")
    return injected, skipped

def process_zip_remove_expiry(input_path, output_path):
    """Hapus expiry dari semua PHP dalam ZIP"""
    removed = 0
    with zipfile.ZipFile(input_path, 'r') as zin:
        with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zout:
            for item in zin.infolist():
                data = zin.read(item.filename)
                ext  = item.filename.rsplit('.', 1)[-1].lower() if '.' in item.filename else ''
                if ext == 'php':
                    try:
                        content = data.decode('utf-8')
                        result  = remove_expiry_from_php(content)
                        zout.writestr(item, result.encode('utf-8'))
                        removed += 1
                    except Exception:
                        zout.writestr(item, data)
                else:
                    zout.writestr(item, data)
    return removed

# ============================================================
# FTP ENGINE
# ============================================================

def ftp_connect():
    """Konek ke FTP pakai config yang tersimpan"""
    cfg = db_get_ftp()
    if not cfg:
        return None, "❌ FTP belum dikonfigurasi! Ketik /setup_ftp dulu."
    try:
        ftp = FTP()
        ftp.connect(cfg['host'], cfg['port'], timeout=30)
        ftp.login(cfg['username'], cfg['password'])
        return ftp, None
    except Exception as e:
        return None, f"❌ Gagal konek FTP: {e}"

def url_to_ftp_path(url):
    """Convert URL/subdomain ke path FTP"""
    url = url.strip().rstrip('/')
    url = re.sub(r'^https?://', '', url)
    # Ambil domain utama dan subdomain
    parts = url.split('.')
    # Cek apakah ada subdomain
    # Format: sub.domain.tld atau domain.tld
    ftp = db_get_ftp()
    if not ftp:
        return '/public_html'
    username = ftp['username']
    # Path base
    base = f'/home/{username}/public_html'
    # Kalau ada subdomain (lebih dari 2 bagian domain)
    # contoh: toko.xlpqr.my.id → 4 bagian, domain utama = xlpqr.my.id
    # xlpqr.my.id → 3 bagian = domain utama
    # Cek apakah TLD 2 level (my.id, co.id, web.id, etc)
    two_level_tlds = ['my.id', 'co.id', 'web.id', 'net.id', 'or.id', 'sch.id',
                      'co.uk', 'org.uk', 'me.uk', 'ac.uk', 'com.au', 'net.au']
    is_two_level = any(url.endswith(tld) for tld in two_level_tlds)
    domain_parts = 3 if is_two_level else 2
    if len(parts) > domain_parts:
        # Ada subdomain
        subdomain = '.'.join(parts[:len(parts)-domain_parts])
        return f'{base}/{subdomain}'
    return base

def ftp_list_php_files(ftp, path):
    """List semua file PHP secara rekursif di path FTP"""
    php_files = []
    try:
        items = ftp.nlst(path)
        for item in items:
            if item in [path, f'{path}/.', f'{path}/..']:
                continue
            try:
                # Coba masuk sebagai direktori
                ftp.cwd(item)
                ftp.cwd('/')
                sub = ftp_list_php_files(ftp, item)
                php_files.extend(sub)
            except:
                if item.endswith('.php'):
                    php_files.append(item)
    except Exception as e:
        pass
    return php_files

def ftp_read_file(ftp, path):
    """Baca isi file dari FTP"""
    import io
    buf = io.BytesIO()
    ftp.retrbinary(f'RETR {path}', buf.write)
    return buf.getvalue().decode('utf-8', errors='ignore')

def ftp_write_file(ftp, path, content):
    """Tulis file ke FTP"""
    import io
    buf = io.BytesIO(content.encode('utf-8'))
    ftp.storbinary(f'STOR {path}', buf)

def ftp_inject_expiry(url, exp_date, contact, secret_key):
    """Inject expiry ke semua PHP di server via FTP"""
    ftp, err = ftp_connect()
    if err:
        return False, err, 0

    path = url_to_ftp_path(url)
    php_files = ftp_list_php_files(ftp, path)

    if not php_files:
        ftp.quit()
        return False, f"❌ Tidak ada file PHP ditemukan di <code>{path}</code>", 0

    injected = 0
    for fp in php_files:
        try:
            content = ftp_read_file(ftp, fp)
            result  = inject_expiry_to_php(content, exp_date, contact, secret_key)
            ftp_write_file(ftp, fp, result)
            injected += 1
        except Exception as e:
            pass

    ftp.quit()
    return True, None, injected

# ============================================================
# HONEYPOT ENGINE
# ============================================================

def detect_form_info(html_content):
    """Deteksi info form dari HTML — tipe, ID, method, AJAX atau tidak"""
    info = {
        'form_ids': [],
        'is_ajax': False,
        'ajax_type': None,
        'has_honeypot': False,
        'honeypot_field': None,
        'honeypot_position': None,
        'honeypot_correct': False,
    }

    # Deteksi semua ID form
    form_ids = re.findall(r'<form[^>]*id=["\']([^"\']+)["\']', html_content, re.IGNORECASE)
    info['form_ids'] = form_ids

    # Deteksi AJAX
    if re.search(r'\.serialize\(\)', html_content): 
        info['is_ajax'] = True
        info['ajax_type'] = 'jquery_serialize'
    elif re.search(r'new FormData', html_content):
        info['is_ajax'] = True
        info['ajax_type'] = 'formdata'
    elif re.search(r'fetch\(', html_content):
        info['is_ajax'] = True
        info['ajax_type'] = 'fetch'
    elif re.search(r'\$\.ajax|\.ajax\(', html_content):
        info['is_ajax'] = True
        info['ajax_type'] = 'jquery_ajax'

    # Deteksi honeypot yang sudah ada
    honeypot_patterns = [
        r'name=["\']website["\']',
        r'name=["\']url["\']',
        r'name=["\']_gotcha["\']',
        r'name=["\']email_confirm["\']',
        r'<!--\s*[Hh]oneypot\s*-->',
    ]
    for pattern in honeypot_patterns:
        match = re.search(pattern, html_content)
        if match:
            info['has_honeypot'] = True
            info['honeypot_field'] = match.group(0)
            # Cek apakah posisinya benar (di dalam form, sebelum button)
            pos = match.start()
            form_start = html_content.rfind('<form', 0, pos)
            form_end = html_content.find('</form>', pos)
            button_pos = html_content.rfind('<button', 0, pos)
            if form_start != -1 and form_end != -1:
                info['honeypot_position'] = 'inside_form'
                # Cek ada display:none atau style="display:none"
                nearby = html_content[max(0, pos-50):pos+200]
                if re.search(r'display:\s*none|tabindex=["\']?-1', nearby):
                    info['honeypot_correct'] = True
            break

    return info

def detect_php_info(php_content):
    """Deteksi info dari file PHP"""
    info = {
        'has_honeypot_check': False,
        'has_antispam': False,
        'framework': 'php',
    }

    if re.search(r'\$_POST\[["\']website["\']\]|\$_POST\[["\']url["\']\]', php_content):
        info['has_honeypot_check'] = True
    if re.search(r'anti_spam\.php|AntiSpam|honeypot', php_content, re.IGNORECASE):
        info['has_antispam'] = True
    if re.search(r'Laravel|Illuminate|artisan', php_content):
        info['framework'] = 'laravel'
    elif re.search(r'CodeIgniter|CI_Controller', php_content):
        info['framework'] = 'codeigniter'

    return info

def inject_honeypot_html(html_content, field_name='website'):
    """Inject honeypot ke dalam HTML form secara otomatis"""
    honeypot_html = f'''
    <!-- Honeypot — IKYY x CLAUDE Anti Spam -->
    <input type="text" name="{field_name}" 
      style="display:none !important; visibility:hidden; position:absolute; left:-9999px;" 
      tabindex="-1" 
      autocomplete="off"
      aria-hidden="true">'''

    # Cek apakah sudah ada honeypot
    if re.search(rf'name=["\']website["\']|name=["\']_gotcha["\']', html_content):
        return None, "already_exists"

    # Cari posisi terbaik — sebelum button submit di dalam form
    # Strategy 1: sebelum </form>
    button_match = re.search(r'(<button[^>]*type=["\']submit["\'][^>]*>|<input[^>]*type=["\']submit["\'][^>]*>)', html_content, re.IGNORECASE)
    if button_match:
        pos = button_match.start()
        result = html_content[:pos] + honeypot_html + '\n    ' + html_content[pos:]
        return result, "injected_before_button"

    # Strategy 2: sebelum </form>
    form_end = html_content.rfind('</form>')
    if form_end != -1:
        result = html_content[:form_end] + honeypot_html + '\n  ' + html_content[form_end:]
        return result, "injected_before_form_end"

    return None, "no_form_found"

def inject_honeypot_php(php_content, field_name='website', framework='php'):
    """Inject validasi honeypot ke PHP file"""

    if framework == 'php':
        honeypot_check = f'''<?php
// === Anti Spam Honeypot — IKYY x CLAUDE ===
if (!empty($_POST['{field_name}'])) {{
    http_response_code(429);
    echo json_encode(['status' => 'error', 'message' => 'Bot detected']);
    exit;
}}
// Atau jika sudah pakai anti_spam.php, cukup:
// require_once './anti_spam.php';
// ==========================================
'''
        # Inject setelah <?php opening tag
        result = re.sub(r'<\?php\s*\n?', honeypot_check, php_content, count=1)
        return result

    elif framework == 'laravel':
        return f'''// Tambahkan di Controller atau Request:
if ($request->filled('{field_name}')) {{
    abort(429, 'Bot detected');
}}

// Atau di FormRequest rules():
'{field_name}' => 'size:0',
'''

    elif framework == 'codeigniter':
        return f'''// Tambahkan di Controller:
if ($this->input->post('{field_name}')) {{
    show_error('Bot detected', 429);
}}
'''

def scan_file_security(content, filename):
    """Scan file untuk backdoor, malware, dan vulnerability"""
    issues = []
    ext = filename.rsplit('.', 1)[-1].lower() if '.' in filename else ''
    lines = content.split('\n')

    # === SCAN SQL / DATABASE DUMP ===
    if ext in ['sql']:
        sql_patterns = [
            # Payload SQLi tersisa
            (r"UNION\s+SELECT", 'HIGH', 'SQLi payload: UNION SELECT ditemukan di dump'),
            (r"OR\s+1\s*=\s*1", 'HIGH', 'SQLi payload: OR 1=1 ditemukan di dump'),
            (r"'\s*--\s*$", 'MEDIUM', 'SQLi pattern: komentar SQL mencurigakan'),
            (r"DROP\s+TABLE", 'HIGH', 'Perintah DROP TABLE ditemukan — berbahaya!'),
            (r"xp_cmdshell", 'CRITICAL', 'MSSQL xp_cmdshell ditemukan — RCE vulnerability'),
            # Konten mencurigakan di field HTML
            (r"<script[\s>]", 'HIGH', 'Tag <script> ditemukan di dalam data DB — kemungkinan XSS tersimpan'),
            (r"<iframe[\s>]", 'HIGH', 'Tag <iframe> ditemukan di dalam data DB — mencurigakan'),
            (r"javascript:", 'MEDIUM', 'javascript: protocol ditemukan di data DB'),
            # Konten asing (rekening, nomor HP)
            (r"(?:BCA|BRI|BNI|Mandiri|DANA|OVO|GoPay)\s*[:\-]?\s*\d{6,}", 'HIGH', 'Kemungkinan injeksi rekening bank di data DB'),
            (r"(?:08[0-9]{8,11})", 'MEDIUM', 'Nomor HP ditemukan di data DB — cek apakah milik lo'),
            (r"https?://(?!(?:localhost|\d{1,3}\.\d{1,3}))[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}/[^\s'\"]{10,}", 'MEDIUM', 'URL eksternal ditemukan di data DB — verifikasi'),
            # htaccess di SQL (rare tapi ada)
            (r"RewriteRule.*http[s]?://", 'HIGH', 'Redirect rule mencurigakan di data'),
        ]
        for i, line in enumerate(lines, 1):
            for pattern, severity, description in sql_patterns:
                if re.search(pattern, line, re.IGNORECASE):
                    issues.append({
                        'line': i, 'severity': severity,
                        'description': description,
                        'code': line.strip()[:100]
                    })
        return issues

    # === SCAN HTACCESS ===
    if filename == '.htaccess' or ext == 'htaccess':
        htaccess_patterns = [
            (r"RewriteRule.*https?://(?!.*(?:localhost))", 'HIGH', '.htaccess: Redirect ke domain luar — kemungkinan hijack'),
            (r"php_value\s+auto_prepend_file", 'CRITICAL', '.htaccess: auto_prepend_file — inject PHP ke semua halaman'),
            (r"php_value\s+auto_append_file", 'CRITICAL', '.htaccess: auto_append_file — inject PHP ke semua halaman'),
            (r"AddType\s+application/x-httpd-php\s+\.", 'HIGH', '.htaccess: eksekusi PHP dari ekstensi lain'),
            (r"Options\s+.*Indexes", 'MEDIUM', '.htaccess: directory listing aktif — info disclosure'),
        ]
        for i, line in enumerate(lines, 1):
            for pattern, severity, description in htaccess_patterns:
                if re.search(pattern, line, re.IGNORECASE):
                    issues.append({
                        'line': i, 'severity': severity,
                        'description': description,
                        'code': line.strip()[:100]
                    })
        return issues

    # === BACKDOOR PATTERNS ===
    backdoor_patterns = [
        # PHP backdoor
        (r'eval\s*\(\s*base64_decode\s*\(', 'CRITICAL', 'PHP Backdoor: eval(base64_decode()) — teknik classic webshell'),
        (r'eval\s*\(\s*\$_(GET|POST|REQUEST|COOKIE)', 'CRITICAL', 'PHP Backdoor: eval() dari user input langsung'),
        (r'system\s*\(\s*\$_(GET|POST|REQUEST|COOKIE)', 'CRITICAL', 'PHP Backdoor: system() dari user input — RCE vulnerability'),
        (r'shell_exec\s*\(\s*\$_(GET|POST|REQUEST|COOKIE)', 'CRITICAL', 'PHP Backdoor: shell_exec() dari user input — RCE vulnerability'),
        (r'passthru\s*\(\s*\$_(GET|POST|REQUEST|COOKIE)', 'CRITICAL', 'PHP Backdoor: passthru() dari user input — RCE vulnerability'),
        (r'exec\s*\(\s*\$_(GET|POST|REQUEST|COOKIE)', 'CRITICAL', 'PHP Backdoor: exec() dari user input — RCE vulnerability'),
        (r'preg_replace\s*\([^,]*\/e["\'\s]', 'CRITICAL', 'PHP Backdoor: preg_replace /e flag — code execution'),
        (r'assert\s*\(\s*\$_(GET|POST|REQUEST|COOKIE)', 'CRITICAL', 'PHP Backdoor: assert() dari user input'),
        (r'\$\{?\$_(GET|POST|REQUEST|COOKIE)\b', 'CRITICAL', 'PHP Backdoor: variable variable dari user input'),

        # Obfuscation mencurigakan (bukan dari bot enkripsi kita)
        (r'str_rot13\s*\(\s*base64_decode\s*\(\s*str_rot13', 'HIGH', 'Obfuscasi mencurigakan: multi-layer rot13+base64'),
        (r'gzinflate\s*\(\s*base64_decode', 'HIGH', 'Obfuscasi mencurigakan: gzinflate+base64 — sering dipakai malware'),
        (r'str_replace\s*\(.{0,20}base64_decode', 'HIGH', 'Obfuscasi mencurigakan: str_replace+base64'),

        # Webshell signature
        (r'FilesMan|c99shell|r57shell|WSO\s*Shell|b374k', 'CRITICAL', 'Webshell terdeteksi — hapus segera!'),
        (r'uname\s*-a|/etc/passwd|/proc/version', 'HIGH', 'Command sensitif Linux — kemungkinan backdoor'),

        # SQL Injection
        (r'\$_(GET|POST|REQUEST)\[.{0,30}\]\s*(?:\.|\s)*(?:WHERE|UNION|SELECT|INSERT|UPDATE|DELETE)', 'HIGH', 'SQL Injection: user input langsung masuk ke query tanpa sanitasi'),
        (r'mysql_query\s*\(\s*["\'].*\$_(GET|POST)', 'HIGH', 'SQL Injection: query dengan user input tanpa prepared statement'),

        # XSS
        (r'echo\s+\$_(GET|POST|REQUEST|COOKIE)', 'MEDIUM', 'XSS: echo user input langsung tanpa escape — reflected XSS'),
        (r'print\s+\$_(GET|POST|REQUEST|COOKIE)', 'MEDIUM', 'XSS: print user input langsung tanpa escape'),
        (r'innerHTML\s*=\s*location\.(search|hash|href)', 'MEDIUM', 'XSS: innerHTML dari URL — DOM XSS vulnerability'),
        (r'document\.write\s*\(\s*(?:location|document\.URL)', 'MEDIUM', 'XSS: document.write dari URL — DOM XSS vulnerability'),

        # File inclusion
        (r'(?:include|require)(?:_once)?\s*\(\s*\$_(GET|POST|REQUEST)', 'CRITICAL', 'LFI/RFI: file inclusion dari user input — sangat berbahaya'),

        # Info disclosure
        (r'phpinfo\s*\(\s*\)', 'MEDIUM', 'Info disclosure: phpinfo() — hapus dari production'),
        (r'var_dump\s*\(\s*\$_(GET|POST|SESSION|SERVER)', 'LOW', 'Info disclosure: var_dump user data — jangan di production'),

        # Suspicious encoded
        (r'\\x[0-9a-fA-F]{2}\\x[0-9a-fA-F]{2}\\x[0-9a-fA-F]{2}\\x[0-9a-fA-F]{2}\\x[0-9a-fA-F]{2}', 'HIGH', 'Kode hex encoding mencurigakan — kemungkinan obfuscasi malware'),
        (r'chr\(\d+\)\.chr\(\d+\)\.chr\(\d+\)\.chr\(\d+\)', 'HIGH', 'chr() chaining mencurigakan — teknik obfuscasi malware'),

        # Suspicious JS
        (r'document\[.{0,20}\]\[.{0,20}\]\(atob\(', 'HIGH', 'JS Backdoor: dynamic method call dengan atob — mencurigakan'),
        (r'eval\(atob\(atob\(', 'HIGH', 'JS Backdoor: eval(atob(atob())) — double encoding mencurigakan'),
        (r'String\.fromCharCode\(\d+,\d+,\d+,\d+,\d+,\d+,\d+,\d+,\d+,\d+', 'HIGH', 'JS Obfuscasi: String.fromCharCode panjang — kemungkinan malware'),
    ]

    for i, line in enumerate(lines, 1):
        for pattern, severity, description in backdoor_patterns:
            if re.search(pattern, line, re.IGNORECASE):
                # Skip kalau ini file hasil enkripsi bot kita sendiri
                if 'IKYY x CLAUDE' in content[:500]:
                    continue
                issues.append({
                    'line': i,
                    'severity': severity,
                    'description': description,
                    'code': line.strip()[:100]
                })

    return issues

def format_scan_report(issues, filename):
    """Format laporan scan untuk dikirim ke Telegram"""
    if not issues:
        return f"✅ <b>SCAN BERSIH!</b>\n\nFile <code>{filename}</code> tidak ditemukan ancaman keamanan.\n\n🛡 Aman untuk diupload ke hosting."

    # Group by severity
    critical = [i for i in issues if i['severity'] == 'CRITICAL']
    high     = [i for i in issues if i['severity'] == 'HIGH']
    medium   = [i for i in issues if i['severity'] == 'MEDIUM']
    low      = [i for i in issues if i['severity'] == 'LOW']

    msg = f"🚨 <b>HASIL SCAN KEAMANAN</b>\n"
    msg += f"📄 File: <code>{filename}</code>\n\n"
    msg += f"📊 Temuan: {len(issues)} masalah\n"

    if critical: msg += f"🔴 CRITICAL : {len(critical)}\n"
    if high:     msg += f"🟠 HIGH     : {len(high)}\n"
    if medium:   msg += f"🟡 MEDIUM   : {len(medium)}\n"
    if low:      msg += f"🟢 LOW      : {len(low)}\n"

    msg += "\n"

    all_issues = critical + high + medium + low
    for issue in all_issues[:10]:  # Max 10 tampil
        emoji = {'CRITICAL': '🔴', 'HIGH': '🟠', 'MEDIUM': '🟡', 'LOW': '🟢'}.get(issue['severity'], '⚪')
        msg += f"{emoji} <b>Line {issue['line']}</b> — {issue['description']}\n"
        msg += f"   <code>{issue['code']}</code>\n\n"

    if len(all_issues) > 10:
        msg += f"... dan {len(all_issues) - 10} temuan lainnya\n\n"

    if critical:
        msg += "⚠️ <b>JANGAN UPLOAD ke hosting sebelum diperbaiki!</b>"
    else:
        msg += "💡 Perbaiki temuan di atas sebelum upload ke production."

    return msg

# ============================================================
# STATE
# ============================================================
user_state = {}

def set_state(chat_id, state, data=None):
    user_state[str(chat_id)] = {'state': state, 'data': data or {}}

def get_state(chat_id):
    return user_state.get(str(chat_id), {})

def clear_state(chat_id):
    user_state.pop(str(chat_id), None)

def load_offset():
    try:
        if os.path.exists(OFFSET_FILE):
            with open(OFFSET_FILE, 'r') as f:
                return int(f.read().strip())
    except: pass
    return 0

def save_offset(offset):
    try:
        with open(OFFSET_FILE, 'w') as f:
            f.write(str(offset))
    except Exception as e:
        log.error(f"Gagal simpan offset: {e}")

# ============================================================
# HANDLE MESSAGE
# ============================================================
def handle_message(message):
    chat_id  = str(message.get('chat', {}).get('id', ''))
    text     = message.get('text', '')
    document = message.get('document')

    if is_blocked(chat_id):
        send_message(chat_id, "❌ Akses lo diblokir oleh admin."); return

    if not is_allowed(chat_id):
        send_message(chat_id, f"❌ Akses ditolak! Bot ini private.\n\nChat ID lo: <code>{chat_id}</code>")
        send_message(ALLOWED_CHAT_ID, f"⚠️ Ada yang coba akses!\nChat ID: <code>{chat_id}</code>\n\nGunakan /add {chat_id} untuk berikan akses.")
        return

    send_typing(chat_id)
    state    = get_state(chat_id)
    is_admin = str(chat_id) == str(ALLOWED_CHAT_ID)

    # ---- HANDLE FILE ----
    if document:
        file_name = document.get('file_name', 'file.txt')
        file_size = document.get('file_size', 0)
        file_id   = document.get('file_id')
        ext = file_name.rsplit('.', 1)[-1].lower() if '.' in file_name else ''
        strong = state.get('data', {}).get('strong', False) or \
                 state.get('state', '') in ['encode_php_strong', 'encode_js_strong']
        current_mode = state.get('data', {}).get('mode', 'encode')
        current_state_name = state.get('state', '')

        if file_size > MAX_FILE_SIZE:
            send_message(chat_id, "❌ File terlalu besar! Maksimal 20MB."); return

        mode_label = {
            'encode': '⏳ Mengenkripsi',
            'honeypot_inject': '🍯 Menginjeksi honeypot ke',
            'honeypot_check': '🔍 Mengecek honeypot di',
            'scan': '🔍 Scanning keamanan',
            'base64_encode': '🔐 Encoding Base64',
            'base64_decode': '🔓 Decoding Base64',
            'remove_comment': '🧹 Menghapus komentar dari',
            'count_lines': '📊 Menghitung baris',
            'detect_lang': '💻 Mendeteksi bahasa',
        }.get(current_mode if current_mode != 'encode' else current_state_name, '⏳ Memproses')

        send_message(chat_id, f"{mode_label} <b>{file_name}</b>...")

        tmpdir = tempfile.mkdtemp()
        try:
            input_path = os.path.join(tmpdir, file_name)
            if not download_file(file_id, input_path):
                send_message(chat_id, "❌ Gagal download file!"); return

            if ext == 'zip':
                if current_mode == 'scan':
                    # Scan semua file dalam ZIP
                    try:
                        all_issues = []
                        file_count = 0
                        with zipfile.ZipFile(input_path, 'r') as zin:
                            for item in zin.infolist():
                                if item.is_dir(): continue
                                fname    = item.filename
                                basename = os.path.basename(fname)
                                item_ext = fname.rsplit('.', 1)[-1].lower() if '.' in fname else ''
                                scan_exts = ['php', 'js', 'html', 'htm', 'sql', 'htaccess']
                                is_htaccess = basename == '.htaccess'
                                if item_ext in scan_exts or is_htaccess:
                                    try:
                                        content = zin.read(fname).decode('utf-8', errors='ignore')
                                        issues = scan_file_security(content, basename if is_htaccess else fname)
                                        for iss in issues:
                                            iss['file'] = fname
                                        all_issues.extend(issues)
                                        file_count += 1
                                    except: pass

                        if not all_issues:
                            send_message(chat_id, f"✅ <b>SCAN ZIP BERSIH!</b>\n\n📦 <code>{file_name}</code>\n📂 {file_count} file discan\nTidak ditemukan ancaman keamanan. Aman diupload!")
                        else:
                            critical = len([i for i in all_issues if i['severity'] == 'CRITICAL'])
                            high     = len([i for i in all_issues if i['severity'] == 'HIGH'])
                            medium   = len([i for i in all_issues if i['severity'] == 'MEDIUM'])
                            low      = len([i for i in all_issues if i['severity'] == 'LOW'])
                            msg = f"🚨 <b>HASIL SCAN ZIP</b>\n📦 <code>{file_name}</code>\n📂 {file_count} file discan\n\n"
                            msg += f"📊 Total: {len(all_issues)} masalah\n"
                            if critical: msg += f"🔴 CRITICAL: {critical}\n"
                            if high:     msg += f"🟠 HIGH    : {high}\n"
                            if medium:   msg += f"🟡 MEDIUM  : {medium}\n"
                            if low:      msg += f"🟢 LOW     : {low}\n\n"
                            for issue in all_issues[:10]:
                                emoji = {'CRITICAL':'🔴','HIGH':'🟠','MEDIUM':'🟡','LOW':'🟢'}.get(issue['severity'],'⚪')
                                fname_display = issue.get('file', '')
                                msg += f"{emoji} <b>{fname_display}</b> — Line {issue['line']}\n"
                                msg += f"   {issue['description']}\n"
                                if issue.get('code'):
                                    msg += f"   <code>{issue['code'][:80]}</code>\n\n"
                                else:
                                    msg += "\n"
                            if len(all_issues) > 10:
                                msg += f"... dan {len(all_issues)-10} temuan lainnya\n\n"
                            if critical:
                                msg += "⚠️ <b>JANGAN UPLOAD sebelum diperbaiki!</b>"
                            send_message(chat_id, msg)
                    except Exception as e:
                        send_message(chat_id, f"❌ Error scan ZIP: {e}")
                elif current_mode == 'honeypot_inject':
                    # FIX BUG: honeypot inject ZIP
                    inj_html = 0; inj_php = 0; skip = 0
                    output_name = file_name.replace('.zip', '_honeypot.zip')
                    output_path = os.path.join(tmpdir, output_name)
                    with zipfile.ZipFile(input_path, 'r') as zin:
                        with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zout:
                            for item in zin.infolist():
                                data    = zin.read(item.filename)
                                i_ext   = item.filename.rsplit('.', 1)[-1].lower() if '.' in item.filename else ''
                                try:
                                    content = data.decode('utf-8')
                                    if i_ext in ['html', 'htm']:
                                        result, status = inject_honeypot_html(content)
                                        if result:
                                            zout.writestr(item, result.encode('utf-8'))
                                            inj_html += 1
                                        else:
                                            zout.writestr(item, data)
                                    elif i_ext == 'php':
                                        info = detect_php_info(content)
                                        if not info['has_honeypot_check']:
                                            result = inject_honeypot_php(content, framework=info['framework'])
                                            zout.writestr(item, result.encode('utf-8'))
                                            inj_php += 1
                                        else:
                                            zout.writestr(item, data)
                                    else:
                                        zout.writestr(item, data)
                                except Exception:
                                    zout.writestr(item, data)
                                    skip += 1
                    send_document(chat_id, output_path,
                        f"✅ <b>Honeypot diinjeksi ke ZIP!</b>\n\n🌐 HTML: {inj_html} file\n🐘 PHP: {inj_php} file\n⏭ Skip: {skip} file")

                elif current_mode == 'honeypot_check':
                    # Honeypot check untuk ZIP
                    html_ok = 0; html_missing = 0; php_ok = 0; php_missing = 0
                    with zipfile.ZipFile(input_path, 'r') as zin:
                        for item in zin.infolist():
                            i_ext = item.filename.rsplit('.', 1)[-1].lower() if '.' in item.filename else ''
                            try:
                                content = zin.read(item.filename).decode('utf-8', errors='ignore')
                                if i_ext in ['html', 'htm']:
                                    info = detect_form_info(content)
                                    if info['has_honeypot']: html_ok += 1
                                    elif info['form_ids'] or '<form' in content.lower(): html_missing += 1
                                elif i_ext == 'php':
                                    info = detect_php_info(content)
                                    if info['has_honeypot_check']: php_ok += 1
                                    else: php_missing += 1
                            except: pass
                    msg = f"🔍 <b>HONEYPOT CHECK ZIP</b>\n📦 <code>{file_name}</code>\n\n"
                    msg += f"🌐 HTML — ✅ Ada: {html_ok} | ❌ Kurang: {html_missing}\n"
                    msg += f"🐘 PHP  — ✅ Ada: {php_ok} | ❌ Kurang: {php_missing}\n"
                    if html_missing or php_missing:
                        msg += f"\n💡 Kirim ZIP ke /honeypot untuk inject otomatis."
                    send_message(chat_id, msg)

                elif current_mode == 'expiry_ask_date':
                    # Terima ZIP untuk add_expiry, simpan path, tanya tanggal
                    if ext != 'zip':
                        send_message(chat_id, "❌ Kirim file ZIP ya!"); return
                    secret_key = generate_secret_key()
                    set_state(chat_id, 'expiry_ask_expdate', {
                        'input_path': input_path,
                        'file_name':  file_name,
                        'tmpdir':     tmpdir,
                        'secret_key': secret_key,
                        'source':     'zip',
                    })
                    send_message(chat_id, f"✅ ZIP diterima: <b>{file_name}</b>\n\n⏳ <b>Langkah 2/3</b>\n\nKirim <b>tanggal expired</b> (format: <code>YYYY-MM-DD</code>):\nContoh: <code>2026-12-31</code>")
                    return  # jangan cleanup tmpdir, masih dipakai

                elif current_mode == 'expiry_remove_confirm':
                    # Terima ZIP untuk remove expiry
                    if ext != 'zip':
                        send_message(chat_id, "❌ Kirim file ZIP ya!"); return
                    output_name = file_name.replace('.zip', '_clean.zip')
                    output_path = os.path.join(tmpdir, output_name)
                    removed = process_zip_remove_expiry(input_path, output_path)
                    data       = state.get('data', {})
                    secret_key = data.get('secret_key')
                    if secret_key:
                        db_delete_project(secret_key)
                    send_document(chat_id, output_path,
                        f"✅ <b>Expiry dihapus!</b>\n\n📁 {removed} file PHP dibersihkan")
                    clear_state(chat_id)

                else:
                    suffix = '_strong_encoded.zip' if strong else '_encoded.zip'
                    output_name = file_name.replace('.zip', suffix)
                    output_path = os.path.join(tmpdir, output_name)
                    processed, skipped = process_zip(input_path, output_path, strong)
                    label = "🔐 STRONG" if strong else "🔒 STANDARD"
                    send_document(chat_id, output_path,
                        f"✅ ZIP diproses! {label}\n📁 {processed} file dienkripsi\n⏭ {skipped} file diskip")

            elif ext in ['php', 'js', 'html', 'htm', 'css', 'py', 'sql']:
                try:
                    with open(input_path, 'r', encoding='utf-8', errors='ignore') as f:
                        content = f.read()

                    if current_mode == 'honeypot_inject':
                        if ext in ['html', 'htm']:
                            result, status = inject_honeypot_html(content)
                            if status == 'already_exists':
                                send_message(chat_id, "ℹ️ <b>Honeypot sudah ada!</b>\nGunakan /honeypot_check untuk verifikasi.")
                            elif status == 'no_form_found':
                                send_message(chat_id, "❌ Tidak ada <code>&lt;form&gt;</code> di file ini.")
                            else:
                                output_name = file_name.replace(f'.{ext}', f'_honeypot.{ext}')
                                output_path = os.path.join(tmpdir, output_name)
                                with open(output_path, 'w', encoding='utf-8') as f:
                                    f.write(result)
                                info = detect_form_info(content)
                                ajax_note = ""
                                if info['is_ajax'] and info['ajax_type'] == 'jquery_serialize':
                                    ajax_note = "\n\n✅ Pakai <code>.serialize()</code> — honeypot otomatis ikut!"
                                elif info['is_ajax']:
                                    ajax_note = "\n\n⚠️ Pakai AJAX — pastikan honeypot field ikut di request."
                                send_document(chat_id, output_path,
                                    f"✅ <b>Honeypot diinjeksi!</b>{ajax_note}\n\n💡 Tambahkan di PHP:\n<code>require_once './anti_spam.php';</code>")
                        elif ext == 'php':
                            info = detect_php_info(content)
                            if info['has_honeypot_check']:
                                send_message(chat_id, "ℹ️ <b>Validasi honeypot sudah ada</b> di file ini!")
                            else:
                                result = inject_honeypot_php(content, framework=info['framework'])
                                output_name = file_name.replace('.php', '_honeypot.php')
                                output_path = os.path.join(tmpdir, output_name)
                                with open(output_path, 'w', encoding='utf-8') as f:
                                    f.write(result)
                                send_document(chat_id, output_path,
                                    f"✅ <b>Validasi honeypot diinjeksi ke PHP!</b>")
                        else:
                            send_message(chat_id, f"❌ Honeypot hanya support .html dan .php")

                    elif current_mode == 'honeypot_check':
                        if ext in ['html', 'htm']:
                            info = detect_form_info(content)
                            msg = f"🔍 <b>HONEYPOT CHECK</b>\n📄 <code>{file_name}</code>\n\n"
                            if info['form_ids']:
                                msg += f"📋 Form: {', '.join([f'<code>#{fid}</code>' for fid in info['form_ids']])}\n"
                            msg += f"⚡ Tipe: {'AJAX (' + info['ajax_type'] + ')' if info['is_ajax'] else 'Form POST biasa'}\n\n"
                            if not info['has_honeypot']:
                                msg += "❌ <b>Honeypot TIDAK ditemukan!</b>\n\n💡 Kirim file dengan /honeypot untuk inject otomatis."
                            elif info['honeypot_correct']:
                                msg += "✅ <b>Honeypot ADA dan BENAR!</b>\n"
                                if info['is_ajax'] and info['ajax_type'] == 'jquery_serialize':
                                    msg += "\n✅ .serialize() — honeypot otomatis ikut terkirim!"
                            else:
                                msg += "⚠️ <b>Honeypot ADA tapi kurang lengkap!</b>\n"
                                msg += "Pastikan ada: <code>style=\"display:none\"</code> dan <code>tabindex=\"-1\"</code>"
                            send_message(chat_id, msg)
                        elif ext == 'php':
                            info = detect_php_info(content)
                            msg = f"🔍 <b>HONEYPOT CHECK PHP</b>\n📄 <code>{file_name}</code>\n\n"
                            msg += "✅ Validasi honeypot ADA\n" if info['has_honeypot_check'] else "❌ Validasi honeypot TIDAK ADA\n"
                            msg += "✅ anti_spam.php sudah di-include\n" if info['has_antispam'] else "⚠️ anti_spam.php belum di-include\n"
                            if not info['has_antispam']:
                                msg += "\n💡 Tambahkan:\n<code>require_once './anti_spam.php';</code>"
                            send_message(chat_id, msg)
                        else:
                            send_message(chat_id, "❌ Honeypot check hanya support .html dan .php")

                    elif current_mode == 'scan':
                        issues = scan_file_security(content, file_name)
                        report = format_scan_report(issues, file_name)
                        send_message(chat_id, report)

                    else:
                        # Mode encode normal
                        encoded, out_ext = process_file_by_extension(content, ext, strong)
                        suffix = f'_strong_encoded.{out_ext}' if strong else f'_encoded.{out_ext}'
                        output_name = file_name.replace(f'.{ext}', suffix)
                        output_path = os.path.join(tmpdir, output_name)
                        with open(output_path, 'w', encoding='utf-8') as f:
                            f.write(encoded)
                        label = "🔐 STRONG" if strong else "🔒 STANDARD"
                        size_before = len(content)
                        size_after  = len(encoded)
                        reduction   = round((1 - size_after/size_before) * 100, 1) if size_before > 0 else 0
                        send_document(chat_id, output_path,
                            f"✅ <b>{file_name}</b> dienkripsi! {label}\n📊 {count_lines(content)} baris\n📦 {size_before}→{size_after} bytes ({reduction}%)")

                except Exception as e:
                    send_message(chat_id, f"❌ Error: {e}")
            else:
                send_message(chat_id, f"❌ Format .{ext} tidak didukung!\nSupport: .php .js .html .css .py .sql .zip")

            # Handle tools yang support file
            tools_handled = False
            if current_state_name in ['base64_encode', 'base64_decode', 'remove_comment', 'count_lines', 'detect_lang']:
                tools_handled = True
                try:
                    with open(input_path, 'r', encoding='utf-8', errors='ignore') as f:
                        content = f.read()

                    if current_state_name == 'base64_encode':
                        result = base64.b64encode(content.encode('utf-8')).decode()
                        output_name = file_name + '_base64.txt'
                        output_path = os.path.join(tmpdir, output_name)
                        with open(output_path, 'w', encoding='utf-8') as f:
                            f.write(result)
                        send_document(chat_id, output_path, f"✅ <b>{file_name}</b> di-encode Base64!")

                    elif current_state_name == 'base64_decode':
                        try:
                            decoded = base64.b64decode(content.strip().encode()).decode('utf-8')
                            output_name = file_name.replace('_base64', '').replace('.txt', '_decoded.txt')
                            output_path = os.path.join(tmpdir, output_name)
                            with open(output_path, 'w', encoding='utf-8') as f:
                                f.write(decoded)
                            send_document(chat_id, output_path, f"✅ <b>{file_name}</b> berhasil di-decode Base64!")
                        except Exception:
                            send_message(chat_id, "❌ Isi file bukan format Base64 yang valid!")

                    elif current_state_name == 'remove_comment':
                        if ext in ['php', 'js', 'html', 'htm', 'css']:
                            cleaned = remove_comments(content)
                            output_name = file_name.replace(f'.{ext}', f'_clean.{ext}')
                            output_path = os.path.join(tmpdir, output_name)
                            with open(output_path, 'w', encoding='utf-8') as f:
                                f.write(cleaned)
                            before = len(content)
                            after  = len(cleaned)
                            saved  = round((1 - after/before) * 100, 1) if before > 0 else 0
                            send_document(chat_id, output_path,
                                f"✅ Komentar dihapus dari <b>{file_name}</b>\n📦 {before}→{after} bytes ({saved}% lebih kecil)")
                        else:
                            send_message(chat_id, f"❌ Remove comment hanya support .php .js .html .css\nFile lo: .{ext}")

                    elif current_state_name == 'count_lines':
                        lines = count_lines(content)
                        lang  = detect_language(content)
                        chars = len(content)
                        send_message(chat_id, f"📊 <b>INFO FILE</b>\n\n📄 File: <code>{file_name}</code>\n📝 Baris: <code>{lines}</code>\n💻 Bahasa: <code>{lang}</code>\n📦 Karakter: <code>{chars}</code>")

                    elif current_state_name == 'detect_lang':
                        lang = detect_language(content)
                        send_message(chat_id, f"💻 <b>BAHASA TERDETEKSI</b>\n\n📄 File: <code>{file_name}</code>\n💻 Bahasa: <code>{lang}</code>")

                except Exception as e:
                    send_message(chat_id, f"❌ Error: {e}")

            if not tools_handled and ext not in ['php', 'js', 'html', 'htm', 'css', 'py', 'sql', 'zip'] and current_mode == 'encode' and current_state_name not in ['base64_encode', 'base64_decode', 'remove_comment', 'count_lines', 'detect_lang']:
                send_message(chat_id, f"❌ Format .{ext} tidak didukung!\nSupport: .php .js .html .css .py .sql .zip")
        finally:
            # Cleanup tmpdir KECUALI kalau state masih butuh file (expiry multi-step)
            skip_cleanup_states = ['expiry_ask_expdate', 'expiry_ask_contact']
            current_st = get_state(chat_id).get('state', '')
            if current_st not in skip_cleanup_states:
                try: shutil.rmtree(tmpdir)
                except: pass

        # Clear state KECUALI state multi-step yang masih aktif
        skip_clear_states = ['expiry_ask_expdate', 'expiry_ask_contact']
        if get_state(chat_id).get('state', '') not in skip_clear_states:
            clear_state(chat_id)
        return

    # ---- HANDLE STATE ----
    if state.get('state') == 'waiting_file' and text and not text.startswith('/'):
        send_message(chat_id, "📎 Kirim file dulu ya, bukan teks.\nKetik /cancel untuk batalkan.")
        return

    if state.get('state') and state.get('state') != 'waiting_file' and text and not text.startswith('/'):
        current_state = state['state']
        result = None
        label  = ""

        state_map = {
            'encode_php':        (obfuscate_php,       "PHP Obfuscated 🔒"),
            'encode_php_strong': (obfuscate_php_strong,"PHP STRONG 🔐"),
            'encode_js':         (obfuscate_js,        "JS Obfuscated 🔒"),
            'encode_js_strong':  (obfuscate_js_strong, "JS STRONG 🔐"),
            'encode_html':       (obfuscate_html,      "HTML Minified"),
            'encode_css':        (obfuscate_css,       "CSS Minified"),
            'encode_python':     (obfuscate_python,    "Python Obfuscated"),
            'encode_sql':        (minify_sql,          "SQL Minified"),
            'minify_php':        (minify_php,          "PHP Minified"),
            'minify_js':         (minify_js,           "JS Minified"),
            'minify_css':        (obfuscate_css,       "CSS Minified"),
            'minify_html':       (minify_html,         "HTML Minified"),
            'remove_comment':    (remove_comments,     "Komentar Dihapus"),
            'base64_encode':     (encode_base64,       "Base64 Encoded"),
            'base64_decode':     (decode_base64,       "Base64 Decoded"),
            'decode_url':        (decode_url,          "URL Decoded"),
            'md5':               (generate_md5,        "MD5 Hash"),
            'sha256':            (generate_sha256,     "SHA256 Hash"),
        }

        if current_state == 'count_lines':
            lines = count_lines(text)
            lang  = detect_language(text)
            send_message(chat_id, f"📊 <b>Info Kode:</b>\n\n📝 Baris: <code>{lines}</code>\n💻 Bahasa: <code>{lang}</code>")
            clear_state(chat_id); return

        elif current_state == 'detect_lang':
            send_message(chat_id, f"💻 <b>Bahasa:</b> <code>{detect_language(text)}</code>")
            clear_state(chat_id); return

        # === BCRYPT ===
        elif current_state == 'bcrypt_hash':
            send_typing(chat_id)
            hashed = bcrypt_hash(text)
            if hashed.startswith('ERROR'):
                send_message(chat_id, f"❌ {hashed}")
            else:
                send_message(chat_id, f"🔐 <b>BCRYPT HASH</b>\n\n🔑 Password: <code>{text}</code>\n\n🔒 Hash:\n<code>{hashed}</code>\n\n💡 Gunakan untuk:\n• CyberPanel: adminPass\n• Laravel: Hash::make()\n• PHP: password_hash()\n• Node.js: bcryptjs.hash()")
            clear_state(chat_id); return

        elif current_state == 'bcrypt_check_pass':
            set_state(chat_id, 'bcrypt_check_hash', {'password': text})
            send_message(chat_id, f"✅ Password disimpan.\n\nLangkah 2: Kirim hash bcrypt yang mau dicek:")
            return

        elif current_state == 'bcrypt_check_hash':
            password = state.get('data', {}).get('password', '')
            result_verify = bcrypt_verify(password, text)
            if result_verify is None:
                send_message(chat_id, "❌ Library bcrypt tidak terinstall. Jalankan: pip install bcrypt")
            elif result_verify:
                send_message(chat_id, f"✅ <b>PASSWORD COCOK!</b>\n\n🔑 Password: <code>{password}</code>\n🔒 Hash: <code>{text[:30]}...</code>")
            else:
                send_message(chat_id, f"❌ <b>PASSWORD TIDAK COCOK!</b>\n\n🔑 Password: <code>{password}</code>\n🔒 Hash: <code>{text[:30]}...</code>")
            clear_state(chat_id); return

        # === HASH ALL ===
        elif current_state == 'hash_all':
            hashes = hash_all(text)
            msg = f"🔐 <b>HASH GENERATOR</b>\n\n🔑 Input: <code>{text[:50]}</code>\n\n"
            for algo, h in hashes.items():
                msg += f"<b>{algo}:</b>\n<code>{h}</code>\n\n"
            send_message(chat_id, msg)
            clear_state(chat_id); return

        # === HASH IDENTIFY ===
        elif current_state == 'hash_identify':
            results = identify_hash(text)
            msg = f"🔍 <b>HASH IDENTIFIER</b>\n\n🔒 Input: <code>{text[:50]}</code>\n\n"
            for hash_type, confidence, note in results:
                emoji = '✅' if confidence == 'PASTI' else '🟡' if 'KEMUNGKINAN' in confidence else '❓'
                msg += f"{emoji} <b>{hash_type}</b> — {confidence}\n   {note}\n\n"
            send_message(chat_id, msg)
            clear_state(chat_id); return

        # === DECRYPT — FIX BUG: tidak kirim 2x, clear_state dipanggil ===
        elif current_state == 'decrypt_rot13':
            result_text = decrypt_rot13(text)
            send_message(chat_id, f"🔓 <b>ROT13 DECODED</b>\n\n<code>{result_text}</code>")
            clear_state(chat_id); return

        elif current_state == 'decrypt_caesar':
            results_text = decrypt_caesar(text)
            if len(results_text) <= 4000:
                send_message(chat_id, f"🔓 <b>CAESAR BRUTE FORCE</b>\n\n<code>{results_text}</code>")
            else:
                tmp = tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False, encoding='utf-8')
                tmp.write(results_text); tmp_path = tmp.name; tmp.close()
                send_document(chat_id, tmp_path, "🔓 Caesar Cipher — semua 25 kemungkinan shift")
                try: os.unlink(tmp_path)
                except: pass
            clear_state(chat_id); return

        elif current_state == 'decrypt_jwt':
            result_text = decode_jwt_payload(text)
            send_message(chat_id, f"🔓 <b>JWT PAYLOAD DECODED</b>\n\n⚠️ Ini hanya payload, bukan verify!\n\n<code>{result_text}</code>")
            clear_state(chat_id); return

        # ============================================================
        # EXPIRY STATE HANDLERS
        # ============================================================
        elif current_state == 'expiry_ask_date':
            # Setelah terima file ZIP, bot tanya tanggal
            # (ini di-set oleh file handler, state ini harusnya tidak dipanggil via text)
            send_message(chat_id, "📎 Kirim file ZIP dulu ya!")
            return

        elif current_state == 'expiry_ask_expdate':
            # Validasi format tanggal
            try:
                datetime.strptime(text.strip(), '%Y-%m-%d')
                exp_date = text.strip()
                data = state.get('data', {})
                data['exp_date'] = exp_date
                set_state(chat_id, 'expiry_ask_contact', data)
                send_message(chat_id, f"✅ Tanggal: <b>{exp_date}</b>\n\n⏳ <b>Langkah 3/3</b>\n\nKirim <b>pesan kontak</b> yang tampil saat expired:\nContoh: <code>Hubungi @ikyy</code> atau bebas isi apa aja")
            except ValueError:
                send_message(chat_id, "❌ Format salah! Gunakan: <code>YYYY-MM-DD</code>\nContoh: <code>2026-12-31</code>")
            return

        elif current_state == 'expiry_ask_contact':
            data       = state.get('data', {})
            contact    = text.strip()
            exp_date   = data.get('exp_date')
            secret_key = data.get('secret_key')
            source     = data.get('source', 'zip')

            if source == 'zip':
                # Proses ZIP yang sudah didownload
                input_path  = data.get('input_path')
                file_name   = data.get('file_name')
                tmpdir      = data.get('tmpdir')
                output_name = file_name.replace('.zip', '_expiry.zip')
                output_path = os.path.join(tmpdir, output_name)
                send_typing(chat_id)
                send_message(chat_id, "⏳ Menginjeksi expiry ke semua PHP...")
                injected, skipped = process_zip_expiry(input_path, output_path, exp_date, contact, secret_key, chat_id=chat_id)
                db_save_project(secret_key, exp_date, contact, project_name=file_name, source='zip')
                send_document(chat_id, output_path,
                    f"✅ <b>Expiry diinjeksi!</b>\n\n📁 {injected} file PHP\n⏭ {skipped} diskip\n\n🔑 Secret Key:\n<code>{secret_key}</code>\n\n📅 Expired: <b>{exp_date}</b>\n\n⚠️ Simpan key ini untuk extend/check/remove!")
                try: shutil.rmtree(tmpdir)
                except: pass
            clear_state(chat_id); return

        elif current_state == 'expiry_extend_key':
            proj = db_get_project(text.strip())
            if not proj:
                send_message(chat_id, "❌ Secret key tidak ditemukan!")
                clear_state(chat_id); return
            set_state(chat_id, 'expiry_extend_date', {'secret_key': text.strip(), 'project': proj})
            send_message(chat_id, f"✅ Project ditemukan!\n📅 Expired sekarang: <b>{proj['exp_date']}</b>\n\nKirim tanggal baru (format: <code>YYYY-MM-DD</code>):")
            return

        elif current_state == 'expiry_extend_date':
            try:
                datetime.strptime(text.strip(), '%Y-%m-%d')
                data       = state.get('data', {})
                secret_key = data.get('secret_key')
                proj       = data.get('project')
                new_date   = text.strip()
                db_update_expiry(secret_key, new_date)
                send_message(chat_id, f"✅ <b>Expiry di-extend!</b>\n\n🔑 Key: <code>{secret_key}</code>\n📅 Expired lama: <b>{proj['exp_date']}</b>\n📅 Expired baru: <b>{new_date}</b>\n\n⚠️ Update file PHP di server/ZIP lo dengan expiry baru ya!")
            except ValueError:
                send_message(chat_id, "❌ Format salah! Gunakan: <code>YYYY-MM-DD</code>")
            clear_state(chat_id); return

        elif current_state == 'expiry_check_key':
            proj = db_get_project(text.strip())
            if not proj:
                send_message(chat_id, "❌ Secret key tidak ditemukan!")
                clear_state(chat_id); return
            exp = datetime.strptime(proj['exp_date'], '%Y-%m-%d').date()
            today = date.today()
            sisa  = (exp - today).days
            if sisa > 0:
                status = f"✅ Aktif ({sisa} hari lagi)"
            elif sisa == 0:
                status = "⚠️ Expired hari ini!"
            else:
                status = f"❌ Sudah expired ({abs(sisa)} hari lalu)"
            msg = f"📊 <b>STATUS EXPIRY</b>\n\n"
            msg += f"🔑 Key: <code>{proj['secret_key']}</code>\n"
            msg += f"📁 Project: {proj['project_name'] or '-'}\n"
            msg += f"📅 Expired: <b>{proj['exp_date']}</b>\n"
            msg += f"📡 Status: {status}\n"
            msg += f"🌐 Source: {proj['source']}"
            if proj.get('ftp_host'):
                msg += f"\n🖥 Host: {proj['ftp_host']}"
            send_message(chat_id, msg)
            clear_state(chat_id); return

        elif current_state == 'expiry_remove_key':
            proj = db_get_project(text.strip())
            if not proj:
                send_message(chat_id, "❌ Secret key tidak ditemukan!")
                clear_state(chat_id); return
            set_state(chat_id, 'waiting_file', {'mode': 'expiry_remove_confirm', 'secret_key': text.strip(), 'project': proj})
            send_message(chat_id, f"✅ Key valid!\n📁 Project: {proj['project_name'] or '-'}\n📅 Expired: {proj['exp_date']}\n\nSekarang kirim <b>ZIP project</b> lo untuk dihapus expiry-nya:")
            return

        # ============================================================
        # FTP STATE HANDLERS
        # ============================================================
        elif current_state == 'ftp_ask_host':
            set_state(chat_id, 'ftp_ask_user', {'host': text.strip()})
            send_message(chat_id, f"✅ Host: <code>{text.strip()}</code>\n\n⚙️ <b>SETUP FTP — Langkah 2/4</b>\n\nKirim <b>FTP Username</b>:")
            return

        elif current_state == 'ftp_ask_user':
            data = state.get('data', {})
            data['username'] = text.strip()
            set_state(chat_id, 'ftp_ask_pass', data)
            send_message(chat_id, f"✅ Username: <code>{text.strip()}</code>\n\n⚙️ <b>SETUP FTP — Langkah 3/4</b>\n\nKirim <b>FTP Password</b>:")
            return

        elif current_state == 'ftp_ask_pass':
            data = state.get('data', {})
            data['password'] = text.strip()
            set_state(chat_id, 'ftp_ask_port', data)
            send_message(chat_id, "⚙️ <b>SETUP FTP — Langkah 4/4</b>\n\nKirim <b>FTP Port</b> (default: <code>21</code>):")
            return

        elif current_state == 'ftp_ask_port':
            data = state.get('data', {})
            try:
                port = int(text.strip())
            except:
                port = 21
            send_typing(chat_id)
            send_message(chat_id, "⏳ Tes koneksi FTP...")
            try:
                ftp = FTP()
                ftp.connect(data['host'], port, timeout=15)
                ftp.login(data['username'], data['password'])
                ftp.quit()
                db_save_ftp(data['host'], data['username'], data['password'], port)
                send_message(chat_id, f"✅ <b>FTP Berhasil!</b>\n\n🖥 Host: <code>{data['host']}</code>\n👤 User: <code>{data['username']}</code>\n🔌 Port: <code>{port}</code>\n\nSekarang bisa pakai /add_expiry_url")
            except Exception as e:
                send_message(chat_id, f"❌ <b>Koneksi FTP gagal!</b>\n\nError: {e}\n\nCek kembali kredensial lo dan coba /setup_ftp lagi.")
            clear_state(chat_id); return

        elif current_state == 'expiry_url_ask_url':
            set_state(chat_id, 'expiry_url_ask_date', {'url': text.strip()})
            send_message(chat_id, f"✅ URL: <code>{text.strip()}</code>\n\n⏳ <b>Langkah 2/3</b>\n\nKirim <b>tanggal expired</b> (format: <code>YYYY-MM-DD</code>):\nContoh: <code>2026-12-31</code>")
            return

        elif current_state == 'expiry_url_ask_date':
            try:
                datetime.strptime(text.strip(), '%Y-%m-%d')
                data = state.get('data', {})
                data['exp_date'] = text.strip()
                set_state(chat_id, 'expiry_url_ask_contact', data)
                send_message(chat_id, f"✅ Expired: <b>{text.strip()}</b>\n\n⏳ <b>Langkah 3/3</b>\n\nKirim <b>pesan kontak</b> yang tampil saat expired:\nContoh: <code>Hubungi @ikyy</code>")
            except ValueError:
                send_message(chat_id, "❌ Format salah! Gunakan: <code>YYYY-MM-DD</code>")
            return

        elif current_state == 'expiry_url_ask_contact':
            data       = state.get('data', {})
            contact    = text.strip()
            exp_date   = data.get('exp_date')
            url        = data.get('url')
            secret_key = generate_secret_key()
            send_typing(chat_id)
            send_message(chat_id, f"⏳ Konek ke FTP dan inject expiry ke <code>{url}</code>...")
            ok, err, injected = ftp_inject_expiry(url, exp_date, contact, secret_key)
            if not ok:
                send_message(chat_id, err)
            else:
                ftp_cfg = db_get_ftp()
                ftp_path = url_to_ftp_path(url)
                db_save_project(secret_key, exp_date, contact, project_name=url, source='ftp',
                                ftp_host=ftp_cfg['host'], ftp_path=ftp_path)
                send_message(chat_id,
                    f"✅ <b>Expiry diinjeksi via FTP!</b>\n\n🌐 URL: <code>{url}</code>\n📁 {injected} file PHP\n📅 Expired: <b>{exp_date}</b>\n\n🔑 Secret Key:\n<code>{secret_key}</code>\n\n⚠️ Simpan key ini untuk extend/check!")
            clear_state(chat_id); return

        elif current_state == 'expiry_url_check_key':
            proj = db_get_project(text.strip())
            if not proj:
                send_message(chat_id, "❌ Secret key tidak ditemukan!")
                clear_state(chat_id); return
            exp   = datetime.strptime(proj['exp_date'], '%Y-%m-%d').date()
            today = date.today()
            sisa  = (exp - today).days
            status = f"✅ Aktif ({sisa} hari lagi)" if sisa > 0 else ("⚠️ Expired hari ini!" if sisa == 0 else f"❌ Sudah expired ({abs(sisa)} hari lalu)")
            msg  = f"📊 <b>STATUS EXPIRY URL</b>\n\n"
            msg += f"🔑 Key: <code>{proj['secret_key']}</code>\n"
            msg += f"🌐 URL: {proj['project_name']}\n"
            msg += f"📅 Expired: <b>{proj['exp_date']}</b>\n"
            msg += f"📡 Status: {status}"
            send_message(chat_id, msg)
            clear_state(chat_id); return

        elif current_state == 'expiry_url_extend_key':
            proj = db_get_project(text.strip())
            if not proj:
                send_message(chat_id, "❌ Secret key tidak ditemukan!")
                clear_state(chat_id); return
            set_state(chat_id, 'expiry_url_extend_date', {'secret_key': text.strip(), 'project': proj})
            send_message(chat_id, f"✅ Project: <code>{proj['project_name']}</code>\n📅 Expired sekarang: <b>{proj['exp_date']}</b>\n\nKirim tanggal baru (<code>YYYY-MM-DD</code>):")
            return

        elif current_state == 'expiry_url_extend_date':
            try:
                datetime.strptime(text.strip(), '%Y-%m-%d')
                data       = state.get('data', {})
                secret_key = data.get('secret_key')
                proj       = data.get('project')
                new_date   = text.strip()
                # Update DB
                db_update_expiry(secret_key, new_date)
                # Inject ulang via FTP
                send_typing(chat_id)
                send_message(chat_id, "⏳ Update expiry di server via FTP...")
                ok, err, injected = ftp_inject_expiry(proj['project_name'], new_date, proj['contact'], secret_key)
                if not ok:
                    send_message(chat_id, f"⚠️ DB sudah diupdate tapi FTP gagal: {err}")
                else:
                    send_message(chat_id, f"✅ <b>Expiry di-extend!</b>\n\n🌐 URL: {proj['project_name']}\n📅 Expired lama: <b>{proj['exp_date']}</b>\n📅 Expired baru: <b>{new_date}</b>\n📁 {injected} file PHP diupdate")
            except ValueError:
                send_message(chat_id, "❌ Format salah! Gunakan: <code>YYYY-MM-DD</code>")
            clear_state(chat_id); return

        elif current_state in state_map:
            func, label = state_map[current_state]
            result = func(text)

        if result:
            if len(result) <= 4000:
                send_message(chat_id, f"✅ <b>{label}:</b>\n\n<code>{result}</code>")
            else:
                tmp = tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False, encoding='utf-8')
                tmp.write(result); tmp_path = tmp.name; tmp.close()
                send_document(chat_id, tmp_path, f"✅ {label}")
                try: os.unlink(tmp_path)
                except: pass

        clear_state(chat_id)
        return

    # ---- HANDLE COMMANDS ----
    if not text: return
    cmd  = text.split()[0].lower().split('@')[0]
    args = text.split()[1:] if len(text.split()) > 1 else []

    if cmd == '/start':
        send_message(chat_id, """🔐 <b>IKYY x CLAUDE — Bot Enkripsi v4.0</b>

Ketik /help untuk semua command.

⚠️ <b>PERHATIAN!</b>
Sebelum menggunakan fitur ini, pastikan kamu sudah melakukan backup file original kamu terlebih dahulu.

Bot ini akan memodifikasi file PHP kamu secara permanen. Kami tidak bertanggung jawab atas kehilangan data jika tidak ada backup.

<i>AUTHOR IKYY X CLAUDE</i>""")

    elif cmd == '/help':
        send_message(chat_id, """📋 <b>SEMUA COMMAND</b>

🔒 <b>Standard:</b>
/encode_php /encode_js /encode_html /encode_css /encode_python /encode_sql

🔐 <b>Strong (VPS recommended):</b>
/encode_php_strong /encode_js_strong

🔧 <b>Minify:</b>
/minify_php /minify_js /minify_css /minify_html /remove_comment

🛠 <b>Tools:</b>
/base64_encode /base64_decode /decode_url
/md5 /sha256 /random_key /count_lines /detect_lang

🔐 <b>Hash & Crypto:</b>
/bcrypt — Generate bcrypt hash
/bcrypt_check — Verifikasi password vs hash
/hash — MD5/SHA1/SHA256/SHA512 sekaligus
/hash_identify — Identifikasi tipe hash

🔓 <b>Decrypt/Decode:</b>
/decrypt — Menu decode
/decrypt_rot13 — Decode ROT13
/decrypt_caesar — Brute force Caesar
/decrypt_jwt — Decode JWT payload

🍯 <b>Honeypot:</b>
/honeypot — Inject honeypot ke file HTML/PHP
/honeypot_check — Cek honeypot sudah benar

🔍 <b>Security Scan:</b>
/scan — Scan backdoor/malware/vulnerability (PHP, JS, SQL, htaccess)

⏳ <b>Auto Expiry (ZIP):</b>
/add_expiry — Inject expiry ke ZIP project
/extend_expiry — Extend tanggal expired
/check_expiry — Cek status expired
/remove_expiry — Hapus expiry dari ZIP

🌐 <b>Auto Expiry (FTP/URL):</b>
/setup_ftp — Simpan kredensial FTP (sekali aja)
/add_expiry_url — Inject expiry langsung ke server
/check_expiry_url — Cek status expired URL
/extend_expiry_url — Extend expired di server via FTP

📁 Kirim file → auto enkripsi/scan/honeypot
/cancel — Batalkan mode aktif""" + ("""

👥 <b>Admin:</b>
/add /remove /block /users""" if is_admin else ""))

    elif cmd == '/random_key':
        send_message(chat_id, f"🔑 <b>RANDOM KEY</b>\n\n16: <code>{generate_random_key(16)}</code>\n32: <code>{generate_random_key(32)}</code>\n64: <code>{generate_random_key(64)}</code>")

    elif cmd == '/bcrypt':
        set_state(chat_id, 'bcrypt_hash')
        send_message(chat_id, "🔐 <b>BCRYPT HASH</b>\n\nKirim password yang mau di-hash:")

    elif cmd == '/bcrypt_check':
        set_state(chat_id, 'bcrypt_check_pass')
        send_message(chat_id, "🔍 <b>BCRYPT VERIFY</b>\n\nLangkah 1: Kirim password yang mau dicek:")

    elif cmd == '/hash':
        set_state(chat_id, 'hash_all')
        send_message(chat_id, "🔐 <b>HASH GENERATOR</b>\n\nKirim teks yang mau di-hash (MD5, SHA1, SHA256, SHA512 sekaligus):")

    elif cmd == '/hash_identify':
        set_state(chat_id, 'hash_identify')
        send_message(chat_id, "🔍 <b>HASH IDENTIFIER</b>\n\nKirim hash yang mau diidentifikasi tipenya:")

    elif cmd == '/decrypt':
        send_message(chat_id, """🔓 <b>DECRYPT / DECODE</b>

Pilih tipe:
/decrypt_rot13 — Decode ROT13
/decrypt_caesar — Brute force Caesar cipher
/decrypt_jwt — Decode JWT payload
/base64_decode — Decode Base64 (sudah ada)""")

    elif cmd == '/decrypt_rot13':
        set_state(chat_id, 'decrypt_rot13')
        send_message(chat_id, "🔓 <b>ROT13 DECODE</b>\n\nKirim teks yang mau di-decode:")

    elif cmd == '/decrypt_caesar':
        set_state(chat_id, 'decrypt_caesar')
        send_message(chat_id, "🔓 <b>CAESAR CIPHER DECODE</b>\n\nKirim teks yang mau di-decode (bot akan coba semua 25 kemungkinan shift):")

    elif cmd == '/decrypt_jwt':
        set_state(chat_id, 'decrypt_jwt')
        send_message(chat_id, "🔓 <b>JWT PAYLOAD DECODE</b>\n\n⚠️ Hanya decode payload, TIDAK verify signature!\n\nKirim JWT token:")

    elif cmd == '/add' and is_admin:
        if not args: send_message(chat_id, "❌ Format: /add [chat_id]")
        elif add_user(args[0]):
            send_message(chat_id, f"✅ User <code>{args[0]}</code> ditambahkan!")
            send_message(args[0], "✅ Akses diberikan! Ketik /help.")
        else: send_message(chat_id, f"ℹ User <code>{args[0]}</code> sudah punya akses.")

    elif cmd == '/remove' and is_admin:
        if not args: send_message(chat_id, "❌ Format: /remove [chat_id]")
        else:
            remove_user(args[0])
            send_message(chat_id, f"✅ Akses <code>{args[0]}</code> dicabut!")

    elif cmd == '/block' and is_admin:
        if not args: send_message(chat_id, "❌ Format: /block [chat_id]")
        elif block_user(args[0]):
            send_message(chat_id, f"🚫 User <code>{args[0]}</code> diblokir!")
        else: send_message(chat_id, f"ℹ Sudah diblokir.")

    elif cmd == '/users' and is_admin:
        allowed, blocked = get_users()
        msg = "👥 <b>USERS</b>\n\n✅ Diizinkan:\n"
        msg += "\n".join([f"  • <code>{u}</code>" for u in allowed]) or "  (kosong)"
        msg += "\n\n🚫 Diblokir:\n"
        msg += "\n".join([f"  • <code>{u}</code>" for u in blocked]) or "  (kosong)"
        send_message(chat_id, msg)

    elif cmd in ['/add', '/remove', '/block', '/users'] and not is_admin:
        send_message(chat_id, "❌ Hanya admin!")

    elif cmd == '/honeypot':
        set_state(chat_id, 'waiting_file', {'mode': 'honeypot_inject'})
        send_message(chat_id, """🍯 <b>HONEYPOT INJECT</b>

Kirim file HTML atau PHP lo sekarang.

Bot akan otomatis:
📄 <b>HTML</b> → inject input honeypot di dalam form
🐘 <b>PHP</b> → inject validasi honeypot di awal file

✅ Support form biasa & AJAX jQuery .serialize()
✅ Deteksi posisi form otomatis
✅ Kirim balik file yang sudah siap pakai""")

    elif cmd == '/honeypot_check':
        set_state(chat_id, 'waiting_file', {'mode': 'honeypot_check'})
        send_message(chat_id, """🔍 <b>HONEYPOT CHECK</b>

Kirim file HTML atau PHP lo sekarang.

Bot akan cek:
✅ Apakah honeypot sudah ada
✅ Apakah posisinya benar (di dalam form)
✅ Apakah attribute lengkap (display:none, tabindex=-1)
✅ Apakah PHP sudah validasi honeypot
✅ Apakah anti_spam.php sudah di-include""")

    elif cmd == '/scan':
        set_state(chat_id, 'waiting_file', {'mode': 'scan'})
        send_message(chat_id, """🔍 <b>SECURITY SCAN</b>

Kirim file PHP/JS/HTML atau ZIP lo sekarang.

Bot akan scan untuk:
🔴 Backdoor & webshell
🔴 RCE (Remote Code Execution)
🟠 SQL Injection vulnerability
🟠 XSS vulnerability
🟠 File inclusion vulnerability
🟡 Info disclosure
🟡 Kode obfuscasi mencurigakan
🟢 Debug code di production

Max file: 20MB per file
ZIP: semua file di dalam akan discan""")

    elif cmd == '/add_expiry':
        set_state(chat_id, 'waiting_file', {'mode': 'expiry_ask_date'})
        send_message(chat_id, """⏳ <b>ADD EXPIRY — Langkah 1/3</b>

Kirim file ZIP project lo sekarang.

Bot akan inject kode expiry ke semua file PHP di dalam ZIP.""")

    elif cmd == '/extend_expiry':
        set_state(chat_id, 'expiry_extend_key')
        send_message(chat_id, "🔑 <b>EXTEND EXPIRY</b>\n\nKirim secret key project yang mau di-extend:")

    elif cmd == '/check_expiry':
        set_state(chat_id, 'expiry_check_key')
        send_message(chat_id, "🔑 <b>CHECK EXPIRY</b>\n\nKirim secret key project yang mau dicek:")

    elif cmd == '/remove_expiry':
        set_state(chat_id, 'expiry_remove_key')
        send_message(chat_id, "🔑 <b>REMOVE EXPIRY</b>\n\nKirim secret key project yang mau dihapus expiry-nya:")

    elif cmd == '/setup_ftp':
        set_state(chat_id, 'ftp_ask_host')
        send_message(chat_id, """⚙️ <b>SETUP FTP — Langkah 1/4</b>

Kirim <b>FTP Host</b> lo:
Contoh: <code>ftp.namadomain.com</code>""")

    elif cmd == '/add_expiry_url':
        cfg = db_get_ftp()
        if not cfg:
            send_message(chat_id, "❌ FTP belum dikonfigurasi!\n\nKetik /setup_ftp dulu untuk simpan kredensial FTP lo.")
        else:
            set_state(chat_id, 'expiry_url_ask_url')
            send_message(chat_id, """⏳ <b>ADD EXPIRY URL — Langkah 1/3</b>

Kirim <b>URL/subdomain</b> web lo:
Contoh: <code>toko.xlpqr.my.id</code> atau <code>xlpqr.my.id</code>""")

    elif cmd == '/check_expiry_url':
        set_state(chat_id, 'expiry_url_check_key')
        send_message(chat_id, "🔑 <b>CHECK EXPIRY URL</b>\n\nKirim secret key project yang mau dicek:")

    elif cmd == '/extend_expiry_url':
        set_state(chat_id, 'expiry_url_extend_key')
        send_message(chat_id, "🔑 <b>EXTEND EXPIRY URL — Langkah 1/2</b>\n\nKirim secret key project yang mau di-extend:")

    elif cmd == '/cancel':
        clear_state(chat_id)
        send_message(chat_id, "✅ Mode dibatalkan. Ketik /help untuk melihat command.")

    else:
        cmd_state_map = {
            '/encode_php': 'encode_php', '/encode_php_strong': 'encode_php_strong',
            '/encode_js': 'encode_js', '/encode_js_strong': 'encode_js_strong',
            '/encode_html': 'encode_html', '/encode_css': 'encode_css',
            '/encode_python': 'encode_python', '/encode_sql': 'encode_sql',
            '/minify_php': 'minify_php', '/minify_js': 'minify_js',
            '/minify_css': 'minify_css', '/minify_html': 'minify_html',
            '/remove_comment': 'remove_comment', '/base64_encode': 'base64_encode',
            '/base64_decode': 'base64_decode', '/decode_url': 'decode_url',
            '/md5': 'md5', '/sha256': 'sha256',
            '/count_lines': 'count_lines', '/detect_lang': 'detect_lang',
            '/decrypt_rot13': 'decrypt_rot13',
            '/decrypt_caesar': 'decrypt_caesar',
            '/decrypt_jwt': 'decrypt_jwt',
            '/hash': 'hash_all',
            '/hash_identify': 'hash_identify',
        }
        prompt_map = {
            '/encode_php': '📝 Kirim kode PHP:', '/encode_php_strong': '📝 Kirim kode PHP (STRONG):',
            '/encode_js': '📝 Kirim kode JS:', '/encode_js_strong': '📝 Kirim kode JS (STRONG):',
            '/encode_html': '📝 Kirim kode HTML:', '/encode_css': '📝 Kirim kode CSS:',
            '/encode_python': '📝 Kirim kode Python:', '/encode_sql': '📝 Kirim query SQL:',
            '/minify_php': '📝 Kirim kode PHP:', '/minify_js': '📝 Kirim kode JS:',
            '/minify_css': '📝 Kirim kode CSS:', '/minify_html': '📝 Kirim kode HTML:',
            '/remove_comment': '📝 Kirim kode:', '/base64_encode': '📝 Kirim teks:',
            '/base64_decode': '📝 Kirim Base64:', '/decode_url': '📝 Kirim URL:',
            '/md5': '📝 Kirim teks:', '/sha256': '📝 Kirim teks:',
            '/count_lines': '📝 Kirim kode:', '/detect_lang': '📝 Kirim kode:',
            '/decrypt_rot13': '📝 Kirim teks ROT13:',
            '/decrypt_caesar': '📝 Kirim teks Caesar cipher:',
            '/decrypt_jwt': '📝 Kirim JWT token:',
            '/hash': '📝 Kirim teks yang mau di-hash:',
            '/hash_identify': '📝 Kirim hash yang mau diidentifikasi:',
        }
        if cmd in cmd_state_map:
            set_state(chat_id, cmd_state_map[cmd])
            send_message(chat_id, prompt_map[cmd])
        elif text.startswith('/'):
            send_message(chat_id, "❓ Command tidak dikenal. Ketik /help")

# ============================================================
# MAIN
# ============================================================
def main():
    log.info("=== Bot Enkripsi IKYY x CLAUDE v4.0 dimulai ===")
    offset = load_offset()
    log.info(f"Offset: {offset}")

    while True:
        try:
            r = requests.get(f"{API_URL}/getUpdates",
                params={"offset": offset, "timeout": 30}, timeout=35)
            if r.status_code != 200:
                time.sleep(5); continue
            for update in r.json().get('result', []):
                offset = update['update_id'] + 1
                save_offset(offset)
                message = update.get('message', {})
                if message:
                    try: handle_message(message)
                    except Exception as e: log.error(f"Handle error: {e}")
        except requests.exceptions.Timeout:
            continue
        except Exception as e:
            log.error(f"Loop error: {e}")
            time.sleep(5)

if __name__ == '__main__':
    main()
