#!/usr/bin/env python3
import os, sys, json, re, base64, requests, urllib3, concurrent.futures
from urllib.parse import urljoin, urlparse
from rich.console import Console
from rich.table import Table
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
from rich import box
from colorama import init

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
init(autoreset=True)
console = Console()
TEAL = "#2EC4B6"
session = requests.Session()
session.headers.update({"User-Agent": "Argus-JSMalwarescan/1.0"})

from argus.utils.util import clean_domain_input, ensure_directory_exists, write_to_file
from argus.config.settings import DEFAULT_TIMEOUT, RESULTS_DIR, EXPORT_SETTINGS

PAT_SCRIPT = re.compile(r"<script[^>]+src=['\"]([^'\"#]+)['\"]", re.I)
SIG_PATTERNS = {
    "driveby_iframe": r"document\.write\(['\" ]<iframe",
    "eval_base64": r"eval\(atob\(",
    "hexified": r"\\x[0-9a-fA-F]{2}",
    "long_fromcharcode": r"String\.fromCharCode\([0-9,\s]{100,}\)",
    "obf_array_join": r"\[[^\]]{100,}\]\.join\(",
    "miner_js": r"Coinhive|cryptonight|miner",
    "jquery_mal": r"\$\(document\)\.ready\(function\(\)\{var _0x",
    "red_302": r"window\.location\s*=\s*['\"]http",
    "cookie_grab": r"document\.cookie",
    "keylogger": r"keypress|onkeyup|onkeydown.*fetch"
}
BAD_DOMAINS = ("xn--", "bit.ly", "goo.gl", "tinyurl", "iplogger", "cliksaf", "adserv", "traffic", "adult", "porn", "casino")
SIG_WEIGHTS = {
    "driveby_iframe": 3, "eval_base64": 4, "hexified": 2, "long_fromcharcode": 4,
    "obf_array_join": 2, "miner_js": 3, "jquery_mal": 2, "red_302": 2,
    "cookie_grab": 2, "keylogger": 3, "suspicious_domain_ref": 1,
    "dense_escape": 1, "large_b64_blob": 2
}
MAX_SCRIPTS = 100
MAX_WORKERS = 24


def banner():
    bar = "=" * 44
    console.print(f"[{TEAL}]{bar}")
    console.print("[cyan]        Argus – JS Malware Scanner")
    console.print(f"[{TEAL}]{bar}")


def fetch(url, timeout):
    try:
        r = session.get(url, timeout=timeout, verify=False)
        return r.text if r.ok else ""
    except:
        return ""


def extract_scripts(html, base, net):
    return [u for u in dict.fromkeys(
        urljoin(base, m.group(1).strip()) for m in PAT_SCRIPT.finditer(html)
    ) if urlparse(u).netloc == net][:MAX_SCRIPTS]


def analyze_script(txt):
    hits = []
    for name, pattern in SIG_PATTERNS.items():
        if re.search(pattern, txt, re.I):
            hits.append(name)
    low = txt.lower()
    if any(dom in low for dom in BAD_DOMAINS):
        hits.append("suspicious_domain_ref")
    if len(txt) > 100000 and txt.count("\\") > 10000:
        hits.append("dense_escape")
    if re.search(r"(['\"])([A-Za-z0-9+/]{100,})\1", txt):
        hits.append("large_b64_blob")
    return list(dict.fromkeys(hits))


def score(hits):
    if not hits:
        return 0, "Clean"
    s = sum(SIG_WEIGHTS.get(h, 1) for h in hits)
    if s >= 8:
        return s, "Malicious?"
    if s >= 4:
        return s, "Suspicious"
    return s, "Benign/Minified"


def run(target, threads, opts):
    banner()
    timeout = int(opts.get("timeout", DEFAULT_TIMEOUT))
    dom = clean_domain_input(target)
    base = f"https://{dom}"
    html = fetch(base, timeout) or fetch(f"http://{dom}", timeout)
    if not html:
        console.print("[red]✖ Unable to reach domain[/red]")
        return
    net = urlparse(base).netloc
    scripts = extract_scripts(html, base, net)
    console.print(f"[white]* External scripts: {len(scripts)}[/white]")
    fetched = []
    with Progress(SpinnerColumn(), TextColumn("Fetching…"), BarColumn(), console=console, transient=True) as pg:
        task = pg.add_task("", total=len(scripts))
        with concurrent.futures.ThreadPoolExecutor(max_workers=min(MAX_WORKERS, threads)) as pool:
            for txt in pool.map(lambda u: fetch(u, timeout), scripts):
                fetched.append(txt)
                pg.advance(task)
    rows = []
    for u, txt in zip(scripts, fetched):
        hits = analyze_script(txt)
        s, label = score(hits)
        rows.append((u, str(s), label, ",".join(hits) if hits else "-"))
    table = Table(title=f"JS Malware Scan – {dom}", header_style="bold white", box=box.MINIMAL)
    table.add_column("Script", style="cyan", overflow="fold")
    table.add_column("Score", style="green")
    table.add_column("Assessment", style="yellow")
    table.add_column("Indicators", style="white", overflow="fold")
    for r in rows:
        table.add_row(*r)
    if rows:
        console.print(table)
    else:
        console.print("[yellow]No suspicious scripts detected[/yellow]")
    console.print("[green]* JS malware scan completed[/green]")
    if EXPORT_SETTINGS.get("enable_txt_export"):
        out = os.path.join(RESULTS_DIR, dom)
        ensure_directory_exists(out)
        export_console = Console(record=True, width=console.width)
        if rows:
            export_console.print(table)
        else:
            export_console.print("[yellow]No suspicious scripts detected[/yellow]")
        write_to_file(os.path.join(out, "js_malware_scan.txt"), export_console.export_text())


if __name__ == "__main__":
    if len(sys.argv) < 2:
        console.print("[red]Usage: js_malware_scan.py <domain> [threads] [options_json][/red]")
        sys.exit(1)
    tgt = sys.argv[1]
    thr = int(sys.argv[2]) if len(sys.argv) > 2 and sys.argv[2].isdigit() else MAX_WORKERS
    opts = {}
    if len(sys.argv) > 3:
        try:
            opts = json.loads(sys.argv[3])
        except:
            pass
    run(tgt, thr, opts)
