403Webshell
Server IP : 157.230.181.24  /  Your IP : 216.73.217.11
Web Server : Apache/2.4.58 (Ubuntu)
System : Linux conductive 6.8.0-117-generic #117-Ubuntu SMP PREEMPT_DYNAMIC Tue May 5 19:26:24 UTC 2026 x86_64
User :  ( 1000)
PHP Version : 8.3.31
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /proc/self/root/opt/aim-bots/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /proc/self/root/opt/aim-bots/extract_timing.py
#!/usr/bin/env python3
"""Extract reply-delay and burst timing from AIM log timestamps into voice.json."""

from __future__ import annotations

import argparse
import json
import random
import re
import statistics
import subprocess
from datetime import datetime
from pathlib import Path

DB = {
    "user": "convo",
    "password": "b9db74bfda4d4e73",
    "database": "convo",
}

CONAN_ID = 10
PARTNER_KEY = "conan0fthenight"

SPEAKER_RE = re.compile(
    r">(?:Auto response from )?\s*([^<\n]+?)<!--\s*\(([^)]+)\)\s*-->",
    re.I,
)

TEXT_RE = re.compile(r"<FONT[^>]*>([^<]{1,220})</FONT>", re.I)

PERSONA_ALIASES = {
    "magus5311": ("magus5311", "magusishiding", "Magus5311", "MagusisHiding"),
    "soewaslike": ("soewaslike", "soEwaslike", "so E was like"),
}

SIDE_ALIASES = {
    "kookies4katelyn": ("Katelyn",),
    "burnoutonme": ("Burn out on me",),
    "leetlegirllush": ("leetle girl lush",),
    "ohtaylorrr": ("Oh Taylorrr",),
    "gioiamichelle": ("GioiA MicheLLe",),
    "teachanx3": ("Tea Chan x3",),
    "scenebarbieex": ("scene barbiee x",),
    "teasememarissa": ("Tease ME Marissa",),
    "alllipsofredred": ("alllipsof redred",),
    "dudesy7777": ("dudesy7777", "Auto response from dudesy7777"),
}


def norm(name: str) -> str:
    return re.sub(r"\s+", "", name.lower())


def mysql_rows(sql: str) -> list[str]:
    return subprocess.check_output(
        ["mysql", "-u", DB["user"], f"-p{DB['password']}", DB["database"], "-N", "-B", "-e", sql],
        text=True,
        errors="replace",
    ).splitlines()


def mysql_content(sql: str) -> str:
    """Return full query result — AIM logs embed newlines inside content rows."""
    return subprocess.check_output(
        ["mysql", "-u", DB["user"], f"-p{DB['password']}", DB["database"], "-N", "-B", "-e", sql],
        text=True,
        errors="replace",
    ).rstrip("\n")


def parse_time(raw: str) -> datetime | None:
    raw = raw.strip()
    for fmt in ("%I:%M:%S %p", "%I:%M %p", "%H:%M:%S", "%H:%M"):
        try:
            return datetime.strptime(raw, fmt)
        except ValueError:
            continue
    return None


def delta_seconds(a: datetime, b: datetime) -> float:
    d = (b - a).total_seconds()
    if d < 0:
        d += 24 * 3600
    return d


def strip_msg(chunk: str) -> str:
    texts = [t.strip() for t in TEXT_RE.findall(chunk) if t.strip() and t.strip() != ":"]
    if not texts:
        return ""
    text = texts[0]
    text = re.sub(r"\s+", " ", text).strip()
    if re.search(r"signed (on|off)|is away|is idle|direct connection", text, re.I):
        return ""
    return text


def classify_side(name: str, persona_key: str, display: str, aliases: tuple[str, ...]) -> str | None:
    n = norm(name)
    if "conan" in n:
        return PARTNER_KEY
    pk = norm(persona_key)
    disp = norm(display)
    if n == pk or n == disp:
        return persona_key
    for alias in aliases:
        if n == norm(alias):
            return persona_key
    if pk in n or n in pk:
        return persona_key
    # 1:1 archive convo — any non-Conan speaker is the persona
    return persona_key


def parse_entries(content: str, persona_key: str, display: str, aliases: tuple[str, ...]) -> list[dict]:
    entries = []
    matches = list(SPEAKER_RE.finditer(content))
    for i, match in enumerate(matches):
        side = classify_side(match.group(1).strip(), persona_key, display, aliases)
        if not side:
            continue
        ts = parse_time(match.group(2))
        if not ts:
            continue
        start = match.end()
        end = matches[i + 1].start() if i + 1 < len(matches) else len(content)
        text = strip_msg(content[start:end])
        entries.append({"side": side, "ts": ts, "text": text, "words": len(text.split()) if text else 0})
    return entries


def percentile(values: list[float], pct: float) -> float:
    if not values:
        return 0.0
    values = sorted(values)
    idx = min(len(values) - 1, int(len(values) * pct / 100))
    return round(values[idx], 2)


def subsample(values: list[float], cap: int = 400) -> list[float]:
    if len(values) <= cap:
        return [round(v, 2) for v in values]
    random.seed(42)
    return [round(v, 2) for v in random.sample(values, cap)]


def analyze_conversation(entries: list[dict], persona_key: str) -> dict:
    reply_gaps: list[float] = []
    hot_gaps: list[float] = []
    warm_gaps: list[float] = []
    cold_gaps: list[float] = []
    burst_gaps: list[float] = []
    hesitation: list[float] = []
    burst_examples: list[dict] = []

    for i in range(1, len(entries)):
        prev, cur = entries[i - 1], entries[i]
        gap = delta_seconds(prev["ts"], cur["ts"])
        if not (0 < gap < 900):
            continue

        if prev["side"] == PARTNER_KEY and cur["side"] == persona_key:
            reply_gaps.append(gap)
            hot = False
            if i >= 2 and entries[i - 2]["side"] == persona_key:
                back = delta_seconds(entries[i - 2]["ts"], prev["ts"])
                hot = back < 90
            if hot:
                hot_gaps.append(gap)
            elif gap >= 90:
                cold_gaps.append(gap)
            else:
                warm_gaps.append(gap)
            if gap >= 45 and cur["words"] <= 4:
                hesitation.append(gap)

        if prev["side"] == persona_key and cur["side"] == persona_key:
            burst_gaps.append(gap)

    i = 0
    while i < len(entries):
        if entries[i]["side"] != persona_key:
            i += 1
            continue
        burst = [entries[i]]
        j = i + 1
        while j < len(entries) and entries[j]["side"] == persona_key:
            burst.append(entries[j])
            j += 1
        if len(burst) >= 2:
            msgs = [b["text"] for b in burst if b["text"]]
            if msgs:
                gaps = [delta_seconds(burst[k - 1]["ts"], burst[k]["ts"]) for k in range(1, len(burst))]
                burst_examples.append({"messages": msgs[:4], "gaps_sec": [round(g, 1) for g in gaps[:3]]})
        i = j

    return {
        "reply_gaps": reply_gaps,
        "hot_gaps": hot_gaps,
        "warm_gaps": warm_gaps,
        "cold_gaps": cold_gaps,
        "burst_gaps": burst_gaps,
        "hesitation": hesitation,
        "burst_examples": burst_examples,
    }


def lookup_account_ids(persona_key: str, display: str) -> list[int]:
    names = list(PERSONA_ALIASES.get(persona_key, (persona_key, display)))
    names.extend(SIDE_ALIASES.get(persona_key, ()))
    ids = []
    for name in names:
        q = (
            f"SELECT id FROM accounts WHERE LOWER(REPLACE(screenname,' ',''))="
            f"LOWER(REPLACE('{name.replace(chr(39), chr(39)*2)}',' ','')) LIMIT 1"
        )
        out = mysql_rows(q)
        if out:
            ids.append(int(out[0]))
    if not ids:
        q = (
            f"SELECT id FROM accounts WHERE LOWER(REPLACE(screenname,' ',''))="
            f"LOWER(REPLACE('{persona_key}',' ','')) LIMIT 1"
        )
        out = mysql_rows(q)
        if out:
            ids.append(int(out[0]))
    return list(dict.fromkeys(ids))


def build_profile(all_stats: list[dict]) -> dict:
    reply = [g for s in all_stats for g in s["reply_gaps"]]
    hot = [g for s in all_stats for g in s["hot_gaps"]]
    warm = [g for s in all_stats for g in s["warm_gaps"]]
    cold = [g for s in all_stats for g in s["cold_gaps"]]
    burst = [g for s in all_stats for g in s["burst_gaps"]]
    hesitate = [g for s in all_stats for g in s["hesitation"]]
    examples = [ex for s in all_stats for ex in s["burst_examples"]]

    random.seed(42)
    burst_samples = examples[:]
    random.shuffle(burst_samples)

    profile = {
        "reply_sample_count": len(reply),
        "reply_p10_sec": percentile(reply, 10),
        "reply_p25_sec": percentile(reply, 25),
        "reply_median_sec": percentile(reply, 50),
        "reply_p75_sec": percentile(reply, 75),
        "reply_p90_sec": percentile(reply, 90),
        "reply_p95_sec": percentile(reply, 95),
        "hot_median_sec": percentile(hot, 50) or percentile(reply, 25),
        "hot_p90_sec": percentile(hot, 90) or percentile(reply, 50),
        "warm_median_sec": percentile(warm, 50) or percentile(reply, 50),
        "cold_median_sec": percentile(cold, 50) or percentile(reply, 90),
        "burst_median_sec": percentile(burst, 50) or 3.0,
        "burst_p90_sec": percentile(burst, 90) or 8.0,
        "pct_instant_reply": round(sum(1 for g in reply if g < 5) / len(reply), 3) if reply else 0,
        "pct_slow_reply": round(sum(1 for g in reply if g >= 60) / len(reply), 3) if reply else 0,
        "hesitation_rate": round(len(hesitate) / len(reply), 3) if reply else 0,
        "reply_samples": subsample(reply),
        "hot_reply_samples": subsample(hot, 200),
        "burst_gap_samples": subsample(burst, 200),
        "burst_patterns": burst_samples[:25],
        "always_online_chance": 0.92,
        "reply_target_min_sec": percentile(reply, 25) or 8,
        "reply_target_max_sec": percentile(reply, 75) or 45,
        "afk_chance": 0.08,
        "keyboard_chance_cold": 0.88,
        "heat_threshold": 0.55,
    }
    if reply:
        profile["reply_mean_sec"] = round(statistics.mean(reply), 2)
    if hot:
        profile["hot_mean_sec"] = round(statistics.mean(hot), 2)
    if burst:
        profile["burst_mean_sec"] = round(statistics.mean(burst), 2)
    return profile


def extract_for_persona(persona_dir: Path) -> dict:
    persona = json.loads((persona_dir / "persona.json").read_text())
    voice_path = persona_dir / "voice.json"
    voice = json.loads(voice_path.read_text()) if voice_path.exists() else {}

    persona_key = persona["screen_name"]
    display = persona.get("display_name", persona_key)
    aliases = tuple(voice.get("speaker_aliases") or SIDE_ALIASES.get(persona_key, ()))

    account_ids = lookup_account_ids(persona_key, display)
    if not account_ids:
        raise SystemExit(f"no archive account for {persona_key}")

    ids_sql = ",".join(str(i) for i in account_ids)
    conv_ids = mysql_rows(
        f"SELECT id FROM conversations WHERE "
        f"(from_account IN ({ids_sql}) AND to_account={CONAN_ID}) OR "
        f"(from_account={CONAN_ID} AND to_account IN ({ids_sql})) ORDER BY id"
    )

    all_stats = []
    for cid in conv_ids:
        content = mysql_content(f"SELECT content FROM conversations WHERE id={cid}")
        if not content:
            continue
        entries = parse_entries(content, persona_key, display, aliases)
        if entries:
            all_stats.append(analyze_conversation(entries, persona_key))

    return build_profile(all_stats)


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("target", nargs="?", help="persona dir or 'all'")
    args = parser.parse_args()

    if args.target == "all" or not args.target:
        dirs = sorted(p for p in Path("/opt/aim-bots/personas").iterdir() if (p / "persona.json").exists())
    else:
        p = Path(args.target)
        dirs = [p if p.is_dir() else Path("/opt/aim-bots/personas") / p]

    for persona_dir in dirs:
        if persona_dir.name in {"magus5311", "soewaslike"} or (persona_dir / "persona.json").exists():
            try:
                profile = extract_for_persona(persona_dir)
            except SystemExit as exc:
                print(f"skip {persona_dir.name}: {exc}")
                continue
            voice_path = persona_dir / "voice.json"
            voice = json.loads(voice_path.read_text()) if voice_path.exists() else {}
            voice["timing_profile"] = profile
            voice_path.write_text(json.dumps(voice, indent=2) + "\n")
            print(
                f"{persona_dir.name}: n={profile['reply_sample_count']} "
                f"median={profile['reply_median_sec']}s hot={profile['hot_median_sec']}s "
                f"burst={profile['burst_median_sec']}s slow={profile['pct_slow_reply']}"
            )
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

Youez - 2016 - github.com/yon3zu
LinuXploit