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 :  /opt/aim-bots/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /opt/aim-bots//extract_side_persona.py
#!/usr/bin/env python3
"""Extract side-chick AIM personas (casual flirty girls) from the convo archive."""

from __future__ import annotations

import argparse
import gzip
import html
import json
import random
import re
import statistics
import subprocess
from collections import Counter
from pathlib import Path

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

CONAN_ID = 10
PARTNER_KEY = "conan0fthenight"

SIDE_CHICK_META = {
    "tricycleinskirt": {
        "real_name": "Cindy",
        "display_name": "Tricycleinskirt",
        "age": 18,
        "backstory": (
            "Your name is Cindy (AIM: Tricycleinskirt). You and Caleb flirt on AIM — not boyfriend/girlfriend, "
            "just a side thing. You call him calebbbb sometimes. Lazy, hungry, into music/tattoos/piercings, "
            "myspace era. Playful, flirty, random — you'd hit him up bored like 'yo send n00dz lol' energy "
            "but you're also a real person with opinions. Almost never say his full name unless you're being cute."
        ),
        "topics": ["music", "tattoos", "piercings", "myspace", "being lazy", "flirting", "pics"],
        "profile_blurb": "music . tattoos . piercings . lazy af . msg me :]",
    },
    "curraxhomieg": {
        "real_name": "",
        "display_name": "curraxhomieg",
        "age": 17,
        "backstory": (
            "AIM flirty friend of Caleb's — myspace era, dramatic, 'omgz!!11' energy. "
            "You talk about hot guys, myspace drama, random links. Casual not serious."
        ),
        "topics": ["myspace", "flirting", "music", "random drama", "pics"],
        "profile_blurb": "myspace is life . bored . hit me up",
    },
    "kookies4katelyn": {
        "real_name": "Katelyn",
        "display_name": "Kookies4Katelyn",
        "speaker_aliases": ["Katelyn"],
        "age": 17,
        "backstory": (
            "Katelyn on AIM — casual flirt with Caleb, cookie/kawaii screenname energy. "
            "Short AIM bursts, bored, might ask for pics or talk myspace."
        ),
        "topics": ["myspace", "school", "flirting", "boredom"],
        "profile_blurb": "katelyn . bored . always on lol",
    },
    "burnoutonme": {
        "real_name": "",
        "display_name": "Burnoutonme",
        "speaker_aliases": ["Burn out on me"],
        "age": 17,
        "backstory": (
            "Emo/scene AIM girl who flirts with Caleb sometimes. Bored, music, myspace, "
            "late night chats. Not a serious relationship — casual side piece energy."
        ),
        "topics": ["music", "myspace", "boredom", "flirting", "scene/emo"],
        "profile_blurb": "burnout . music . msg me xD",
    },
    "xxxcoreblondexxx": {
        "real_name": "",
        "display_name": "XxXcoreblondeXxX",
        "age": 17,
        "backstory": (
            "Blonde myspace girl — asks about his pics, myspace layout, flirty but casual. "
            "Scene kid xXx name energy."
        ),
        "topics": ["myspace", "pics", "flirting", "hair/makeup"],
        "profile_blurb": "blonde . myspace . pics . xoxo",
    },
    "leetlegirllush": {
        "real_name": "",
        "display_name": "leetlegirllush",
        "speaker_aliases": ["leetle girl lush"],
        "age": 17,
        "backstory": (
            "AIM girl Caleb flirts with — leet speak screenname, casual side piece. "
            "Random hits, short messages, myspace era, not serious."
        ),
        "topics": ["flirting", "boredom", "myspace", "pics"],
        "profile_blurb": "leet leg girl lush . bored . msg me lol",
    },
    "ohtaylorrr": {
        "real_name": "Taylor",
        "display_name": "OhTaylorrr",
        "speaker_aliases": ["Oh Taylorrr"],
        "age": 17,
        "backstory": (
            "Taylor on AIM — emo/indie lyrics energy, casual flirt with Caleb, side thing."
        ),
        "topics": ["music", "flirting", "myspace"],
        "profile_blurb": "taylor . music . <3",
    },
    "blondegrl2390": {
        "real_name": "",
        "display_name": "blondegrl2390",
        "age": 17,
        "backstory": (
            "Blonde girl from AIM — flirty, casual, myspace era side chick."
        ),
        "topics": ["flirting", "myspace", "pics"],
        "profile_blurb": "blonde . msg me :]",
    },
    "gioiamichelle": {
        "real_name": "Michelle",
        "display_name": "GioiAMicheLLe",
        "speaker_aliases": ["GioiA MicheLLe"],
        "age": 17,
        "backstory": (
            "Michelle on AIM — casual flirt, myspace girl, not a serious relationship."
        ),
        "topics": ["myspace", "flirting", "school"],
        "profile_blurb": "michelle . bored . xoxo",
    },
    "teachanx3": {
        "real_name": "",
        "display_name": "TeaChanx3",
        "speaker_aliases": ["Tea Chan x3"],
        "age": 17,
        "backstory": (
            "Cute AIM screenname, casual flirty energy with Caleb."
        ),
        "topics": ["flirting", "boredom", "myspace"],
        "profile_blurb": "tea chan x3 . heyy . msg me",
    },
    "stephatthediscox": {
        "real_name": "Steph",
        "display_name": "StephAtTheDiscoX",
        "age": 17,
        "backstory": (
            "Scene kid AIM girl — panic at the disco lyrics, flirty casual side thing."
        ),
        "topics": ["music", "scene", "flirting", "myspace"],
        "profile_blurb": "steph . panic at the disco . <3",
    },
    "woohookatieboo": {
        "real_name": "Katie",
        "display_name": "woohookatieboo",
        "age": 17,
        "backstory": (
            "Katie — woo hoo katie boo screenname, casual AIM flirt with Caleb."
        ),
        "topics": ["flirting", "myspace", "boredom"],
        "profile_blurb": "katie . woo hoo . msg me lol",
    },
    "zandoodle": {
        "real_name": "",
        "display_name": "zandoodle",
        "age": 17,
        "backstory": (
            "Myspace-era girl — talks about sexy comments, myspace drama, casual flirt with Caleb."
        ),
        "topics": ["myspace", "flirting", "pics", "drama"],
        "profile_blurb": "myspace drama . lol . msg me",
    },
    "augustjacque": {
        "real_name": "Jacque",
        "display_name": "AugustJacque",
        "age": 17,
        "backstory": (
            "Jacque — photography/myspace pics girl, flirty, asks if Caleb saw her pictures."
        ),
        "topics": ["myspace", "pics", "photography", "flirting"],
        "profile_blurb": "pics . myspace . xoxo",
    },
    "scenebarbieex": {
        "real_name": "",
        "display_name": "scenebarbieex",
        "speaker_aliases": ["scene barbiee x"],
        "age": 17,
        "backstory": (
            "Scene barbie AIM girl — duhh, =], whats upp energy, casual side flirt."
        ),
        "topics": ["scene", "myspace", "flirting"],
        "profile_blurb": "scene barbie . xoxo . msg me",
    },
    "teasememarissa": {
        "real_name": "Marissa",
        "display_name": "TeaseMEMarissa",
        "speaker_aliases": ["Tease ME Marissa"],
        "age": 17,
        "backstory": (
            "Marissa — tease me screenname, picky/flirty casual AIM with Caleb."
        ),
        "topics": ["flirting", "myspace"],
        "profile_blurb": "marissa . tease me . ;)",
    },
    "ralexa7403": {
        "real_name": "Alexa",
        "display_name": "RAlexa7403",
        "age": 17,
        "backstory": (
            "Alexa — myspace photo drama, flirty lines, casual not serious."
        ),
        "topics": ["myspace", "pics", "flirting"],
        "profile_blurb": "alexa . pics . myspace",
    },
    "opentheharbor": {
        "real_name": "",
        "display_name": "opentheharbor",
        "age": 17,
        "backstory": (
            "Random flirty AIM girl — come hang out, basketball games, haha :P energy."
        ),
        "topics": ["flirting", "hanging out", "boredom"],
        "profile_blurb": "come hang out . msg me :P",
    },
    "peacful3": {
        "real_name": "",
        "display_name": "Peacful3",
        "age": 17,
        "backstory": (
            "Casual AIM flirt — xDDD, photo/hair jokes, myspace era side energy."
        ),
        "topics": ["flirting", "pics", "myspace"],
        "profile_blurb": "peaceful . lol . heyy",
    },
    "takethebreakdown": {
        "real_name": "",
        "display_name": "takethebreakdown",
        "age": 17,
        "backstory": (
            "Hardcore/scene AIM girl — hiii!! late night chats, casual flirt."
        ),
        "topics": ["music", "flirting", "boredom"],
        "profile_blurb": "breakdown . hiii . msg me",
    },
    "kddunn05": {
        "real_name": "",
        "display_name": "kddunn05",
        "age": 17,
        "backstory": (
            "AIM girl — kiss the gunner, myspace friend drama, casual flirt."
        ),
        "topics": ["music", "myspace", "flirting"],
        "profile_blurb": "music . myspace . heyy",
    },
    "bearychairy": {
        "real_name": "",
        "display_name": "Bearychairy",
        "age": 17,
        "backstory": (
            "Cute screenname girl — says see ya tomorrow Caleb, casual side flirt."
        ),
        "topics": ["flirting", "boredom"],
        "profile_blurb": "beary . heyy . :]",
    },
    "alllipsofredred": {
        "real_name": "",
        "display_name": "alllipsofredred",
        "speaker_aliases": ["alllipsof redred"],
        "age": 17,
        "backstory": (
            "Red lips screenname — lol/haha AIM energy, casual random chats with Caleb."
        ),
        "topics": ["flirting", "boredom"],
        "profile_blurb": "red lips . lol . msg me",
    },
    "radiofiction": {
        "real_name": "",
        "display_name": "radiofiction",
        "age": 17,
        "backstory": (
            "Random AIM girl — BOO!, homework procrastination, flirty 'what kind of hot' energy."
        ),
        "topics": ["flirting", "school", "boredom"],
        "profile_blurb": "boo . random . msg me",
    },
    "shelliharsha": {
        "real_name": "",
        "display_name": "shelliharsha",
        "age": 17,
        "backstory": (
            "Casual AIM girl — haha hey!, aw lame, short flirty chats with Caleb."
        ),
        "topics": ["flirting", "boredom"],
        "profile_blurb": "heyy . lol . msg me",
    },
}

VOICE_TEMPLATE = {
    "persona_kind": "side_chick",
    "typing_wpm_min": 75,
    "typing_wpm_max": 95,
    "timezone": "America/New_York",
    "proactive_target": "conan0fthenight",
    "proactive_per_day": 0,
    "late_night_start": 22,
    "late_night_end": 2,
    "friend_name": "Caleb",
    "session_reset_minutes": 45,
    "fresh_topic_minutes": 8,
    "wow_openers": [
        ["hey", "u on?"],
        ["calebbbb", "whats up"],
        ["heyy", "im bored"],
        ["yo", "u there?"],
        ["sup", "nm u?"],
        ["hey", "whatcha doing"],
    ],
    "late_night_openers": [
        ["hey", "u up?", "cant sleep lol"],
        ["calebbbb", "im bored"],
        ["heyy", "whatcha doing", "its late lol"],
        ["yo", "u on?", "send me something fun lol"],
        ["hey", "im so bored", "entertain me"],
    ],
}


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


def strip_html(text: str) -> str:
    text = re.sub(r"<!--.*?-->", "", text, flags=re.S)
    text = re.sub(r"<[^>]+>", " ", text)
    text = html.unescape(text)
    text = re.sub(r"^[:\s>]+", "", text)
    text = re.sub(r"\s+", " ", text).strip()
    if re.search(r"signed (on|off)|is away|returned at|is idle at", text, re.I):
        return ""
    return text


def build_speaker_re(names: tuple[str, ...]) -> re.Pattern:
    alts = "|".join(re.escape(n) for n in names)
    return re.compile(rf"(?:Auto response from )?({alts})<!--\s*\([^)]+\)\s*-->", re.I)


# Names in archive HTML sit after a closing angle bracket: >Burn out on me<!-- (5:08:34 AM)-->
GENERIC_SPEAKER_RE = re.compile(
    r">(?:Auto response from )?\s*([^<\n]+?)<!--\s*\(\d{1,2}:\d{2}:\d{2}\s*(?:AM|PM)?\)\s*-->",
    re.I,
)


def parse_conversation_generic(content: str, persona_key: str) -> list[tuple[str, str]]:
    """Parse 1:1 convo — any non-Conan speaker is the side chick."""
    entries = []
    matches = list(GENERIC_SPEAKER_RE.finditer(content))
    for i, match in enumerate(matches):
        raw_name = match.group(1).strip()
        n = norm(raw_name)
        speaker = PARTNER_KEY if "conan" in n else persona_key
        start = match.end()
        end = matches[i + 1].start() if i + 1 < len(matches) else len(content)
        text = strip_html(content[start:end])
        if text:
            entries.append((speaker, text))
    return entries


def parse_conversation(content: str, speaker_re: re.Pattern, persona_key: str) -> list[tuple[str, str]]:
    entries = []
    matches = list(speaker_re.finditer(content))
    if not matches:
        return parse_conversation_generic(content, persona_key)
    for i, match in enumerate(matches):
        speaker_raw = match.group(1)
        speaker = norm(speaker_raw)
        if speaker == "conan0fthenight" or "conan" in speaker:
            speaker = PARTNER_KEY
        elif speaker != persona_key:
            speaker = persona_key
        start = match.end()
        end = matches[i + 1].start() if i + 1 < len(matches) else len(content)
        text = strip_html(content[start:end])
        if text:
            entries.append((speaker, text))
    return entries


def mysql_query(sql: str) -> str:
    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 mysql_rows(sql: str) -> list[str]:
    out = subprocess.check_output(
        ["mysql", "-u", DB["user"], f"-p{DB['password']}", DB["database"], "-N", "-B", "-e", sql],
        text=True,
        errors="replace",
    )
    return [line for line in out.splitlines() if line.strip()]


def lookup_account_id(name: str) -> int:
    q = (
        f"SELECT id FROM accounts WHERE LOWER(REPLACE(screenname,' ',''))="
        f"LOWER(REPLACE('{name.replace(chr(39), chr(39)*2)}',' ','')) LIMIT 1"
    )
    out = mysql_query(q).strip()
    if not out:
        raise SystemExit(f"Account not found: {name}")
    return int(out)


def is_good_message(text: str) -> bool:
    if len(text) < 2 or len(text) > 180:
        return False
    if text.startswith("http") or "http://" in text or "www." in text:
        return False
    if re.search(r"[\x00-\x08\x0b\x0c\x0e-\x1f]", text):
        return False
    if re.search(r"\b(TIT2|TYER|WXXX|TENC|LAME)\b", text):
        return False
    return True


def voice_stats(msgs: list[str]) -> dict:
    if not msgs:
        return {}
    words = [len(m.split()) for m in msgs]
    slang = Counter()
    for m in msgs:
        for w in re.findall(r"\b[\w']+\b", m.lower()):
            slang[w] += 1
    skip = {
        "the", "a", "an", "to", "and", "or", "i", "you", "it", "is", "in", "of", "that",
        "for", "on", "with", "be", "are", "was", "at", "as", "so", "but", "not", "my", "me",
        "we", "he", "she", "they", "do", "did", "have", "has", "had", "this", "what", "just",
        "like", "get", "got", "all", "if", "can", "would", "will", "your", "u", "ur", "r",
    }
    starters = Counter(m.split()[0].lower() for m in msgs if m.split())
    return {
        "message_count": len(msgs),
        "median_words": statistics.median(words),
        "mean_words": round(statistics.mean(words), 2),
        "pct_short_3": round(sum(w <= 3 for w in words) / len(words), 3),
        "pct_short_5": round(sum(w <= 5 for w in words) / len(words), 3),
        "pct_lowercase_start": round(sum(1 for m in msgs if m and m[0].islower()) / len(msgs), 3),
        "pct_no_end_punct": round(
            sum(1 for m in msgs if not re.search(r"[.!?]$", m.rstrip())) / len(msgs), 3
        ),
        "pct_haha": round(sum(1 for m in msgs if re.search(r"haha", m, re.I)) / len(msgs), 3),
        "pct_lol": round(sum(1 for m in msgs if re.search(r"\blol\b", m, re.I)) / len(msgs), 3),
        "top_words": [w for w, _ in slang.most_common(60) if w not in skip][:25],
        "starters": [s for s, _ in starters.most_common(20) if s],
    }


def burst_stats(entries: list[tuple[str, str]], persona_key: str) -> dict:
    bursts: list[list[str]] = []
    i = 0
    while i < len(entries):
        if entries[i][0] == persona_key:
            burst = [entries[i][1]]
            j = i + 1
            while j < len(entries) and entries[j][0] == persona_key:
                burst.append(entries[j][1])
                j += 1
            bursts.append(burst)
            i = j
        else:
            i += 1
    lens = [len(b) for b in bursts] or [1]
    return {
        "burst_count": len(bursts),
        "pct_multi": round(sum(l > 1 for l in lens) / len(lens), 3),
        "avg_burst_len": round(statistics.mean(lens), 2),
        "max_burst_len": min(4, max(lens)),
    }


def extract_phrases(msgs: list[str]) -> list[str]:
    phrases = Counter()
    for m in msgs:
        if not is_good_message(m):
            continue
        words = m.split()
        for n in (2, 3):
            for i in range(len(words) - n + 1):
                chunk = " ".join(words[i : i + n]).lower()
                if 4 <= len(chunk) <= 40:
                    phrases[chunk] += 1
    return [p for p, c in phrases.most_common(80) if c >= 3][:40]


def curate_examples(pairs: list[dict], limit: int = 30) -> list[dict]:
    scored = []
    flirt = re.compile(r"caleb|hot|pic|n00d|sexy|cutie|babe|myspace|:\)|xD|haha|heyy", re.I)
    for p in pairs:
        u, a = p["user"], p["assistant"]
        if not is_good_message(u) or not is_good_message(a):
            continue
        if len(a.split()) > 15:
            continue
        score = 0
        if flirt.search(a):
            score += 2
        if 1 <= len(a.split()) <= 8:
            score += 1
        scored.append((score, p))
    scored.sort(key=lambda x: (-x[0], len(x[1]["assistant"])))
    seen = set()
    out = []
    for _, p in scored:
        key = (p["user"].lower()[:40], p["assistant"].lower()[:40])
        if key in seen:
            continue
        seen.add(key)
        out.append(p)
        if len(out) >= limit:
            break
    return out


def build_pairs(entries: list[tuple[str, str]], persona_key: str, partner: str) -> list[dict]:
    pairs = []
    for i, (speaker, text) in enumerate(entries):
        if speaker == persona_key and i > 0 and entries[i - 1][0] == partner:
            pairs.append({"user": entries[i - 1][1], "assistant": text})
    return pairs


def extract_appearance(raw_lines: list[str], screen_names: tuple[str, ...]) -> dict:
    speaker_res = [re.compile(rf"(?:Auto response from )?({re.escape(sn)})<!--", re.I) for sn in screen_names if sn]
    combos: Counter = Counter()
    backgrounds: Counter = Counter()

    for line in raw_lines:
        if not any(r.search(line) for r in speaker_res):
            if not GENERIC_SPEAKER_RE.search(line):
                continue
        bg = re.search(r'BODY BGCOLOR="(#[0-9a-fA-F]+)"', line, re.I)
        if bg:
            backgrounds[bg.group(1).lower()] += 1
        for sn in screen_names:
            if not sn:
                continue
            speaker_re = re.compile(rf"(?:Auto response from )?({re.escape(sn)})<!--", re.I)
            for lm in speaker_re.finditer(line):
                chunk = line[lm.start() : lm.start() + 1200]
                rest = chunk[chunk.find("<!--") :]
                for fm in re.finditer(
                    r'<FONT\s+([^>]*(?:COLOR="#[0-9a-fA-F]+"[^>]*FACE="[^"]+"|FACE="[^"]+"[^>]*COLOR="#[0-9a-fA-F]+")[^>]*)>([^<]{1,200})',
                    rest,
                    re.I,
                ):
                    attrs, text = fm.group(1), fm.group(2).strip()
                    if not text or text == ":" or text.startswith("http"):
                        continue
                    color_m = re.search(r'COLOR="(#[0-9a-fA-F]+)"', attrs, re.I)
                    face_m = re.search(r'FACE="([^"]+)"', attrs, re.I)
                    size_m = re.search(r"SIZE=(\d+)", attrs, re.I)
                    if not face_m:
                        continue
                    color = color_m.group(1).lower() if color_m else "#ff66cc"
                    combos[(color, face_m.group(1), size_m.group(1) if size_m else "2")] += 1

    appearance = {
        "font_face": "Arial",
        "font_color": "#ff66cc",
        "font_size": "2",
        "body_bgcolor": "#000000",
        "html_wrap": "font",
    }
    if combos:
        (color, face, size), _ = combos.most_common(1)[0]
        appearance.update({"font_color": color, "font_face": face, "font_size": size})
    if backgrounds:
        appearance["body_bgcolor"] = backgrounds.most_common(1)[0][0]
    return appearance


def build_profile_html(meta: dict, appearance: dict) -> str:
    name = meta.get("display_name", "AIM")
    blurb = meta.get("profile_blurb", "bored . msg me")
    color = appearance.get("font_color", "#ff66cc")
    face = appearance.get("font_face", "Arial")
    size = appearance.get("font_size", "2")
    bg = appearance.get("body_bgcolor", "#000000")
    return (
        f'<BODY BGCOLOR="{bg}"><FONT COLOR="{color}" FACE="{face}" SIZE="{size}">'
        f"<B>{html.escape(name)}</B><BR><BR>"
        f"{html.escape(blurb)}<BR><BR>"
        f"<I>probably on lol</I><BR><BR>msg me :]<BR></FONT></BODY>"
    )


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("screenname")
    parser.add_argument("--out", default="/opt/aim-bots/personas")
    args = parser.parse_args()

    persona_key = norm(args.screenname)
    meta = SIDE_CHICK_META.get(persona_key, {})
    display = meta.get("display_name", args.screenname)
    account_id = lookup_account_id(args.screenname)

    conan_names = ("Conan0fTheNight", "conan0fthenight")
    alias_names = tuple(meta.get("speaker_aliases", ()))
    all_names = (display, args.screenname) + alias_names + conan_names
    speaker_re = build_speaker_re(all_names)

    conv_ids = mysql_rows(
        f"SELECT id FROM conversations WHERE "
        f"(from_account={account_id} AND to_account={CONAN_ID}) OR "
        f"(from_account={CONAN_ID} AND to_account={account_id}) ORDER BY id"
    )
    raw_lines: list[str] = []
    all_entries: list[tuple[str, str]] = []
    for cid in conv_ids:
        content = mysql_query(f"SELECT content FROM conversations WHERE id={cid}")
        if not content:
            continue
        raw_lines.append(content)
        all_entries.extend(parse_conversation(content, speaker_re, persona_key))

    persona_msgs = [text for speaker, text in all_entries if speaker == persona_key]
    pairs = build_pairs(all_entries, persona_key, PARTNER_KEY)
    good_samples = list(dict.fromkeys(m for m in persona_msgs if is_good_message(m)))
    appearance_names = (display, args.screenname) + alias_names
    appearance = extract_appearance(raw_lines, appearance_names)

    random.seed(42)
    persona = {
        "screen_name": persona_key,
        "display_name": display,
        "persona_kind": "side_chick",
        "message_count": len(persona_msgs),
        "pair_count": len(pairs),
        "examples": curate_examples(pairs, 30),
        "sample_messages": random.sample(good_samples, min(60, len(good_samples))),
        "phrases": extract_phrases(persona_msgs),
        **appearance,
    }

    out_dir = Path(args.out) / persona_key
    out_dir.mkdir(parents=True, exist_ok=True)

    voice = VOICE_TEMPLATE.copy()
    voice.update(meta)
    voice.update(voice_stats(persona_msgs))
    voice.update(burst_stats(all_entries, persona_key))
    voice_path = out_dir / "voice.json"
    if voice_path.exists():
        saved = json.loads(voice_path.read_text())
        if saved.get("timing_profile"):
            voice["timing_profile"] = saved["timing_profile"]
    voice["profile_html"] = build_profile_html(meta or {"display_name": display}, appearance)

    (out_dir / "persona.json").write_text(json.dumps(persona, indent=2))
    (out_dir / "voice.json").write_text(json.dumps(voice, indent=2))
    (out_dir / "bot.env").write_text(
        f"OSCAR_PASSWORD={persona_key}\nPERSONA_DIR={out_dir}\nPROACTIVE_TARGET=conan0fthenight\n"
    )
    with gzip.open(out_dir / "pairs.json.gz", "wt", encoding="utf-8") as fh:
        json.dump(pairs, fh)

    print(
        f"Extracted side chick {persona_key}: {len(persona_msgs)} msgs, {len(pairs)} pairs, "
        f"style {appearance.get('font_color')} on {appearance.get('body_bgcolor')}"
    )


if __name__ == "__main__":
    main()

Youez - 2016 - github.com/yon3zu
LinuXploit