HEX
Server: LiteSpeed
System: Linux houston.panomity.com 6.8.0-100-generic #100-Ubuntu SMP PREEMPT_DYNAMIC Tue Jan 13 16:40:06 UTC 2026 x86_64
User: nudepix (1011)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: //proc/thread-self/root/workspace/karateka_nuke.py
#!/usr/bin/env python3
"""Delete tweets from @Karateka using search window strategy."""
from __future__ import annotations

import json
import subprocess
import time
from datetime import date
from pathlib import Path

WRAPPER = "/root/clawd/skills/x-twitter/scripts/x_twitter.py"
PROFILE = "Karateka"
USER = "Karateka"
START = date(2006, 1, 1)
CUTOFF = date(2026, 3, 13)
DELAY = 1.0
DELAY_429 = 90.0
DELAY_DELETE = 1.5

PROGRESS_PATH = Path("/workspace/karateka_progress.json")
LOG_PATH = Path("/workspace/karateka_nuke.log")


def log(msg: str) -> None:
    from datetime import datetime

    ts = datetime.utcnow().isoformat(timespec="seconds")
    line = f"[{ts}] {msg}"
    print(line, flush=True)
    with LOG_PATH.open("a", encoding="utf-8") as f:
        f.write(line + "\n")


def load_progress() -> dict:
    if PROGRESS_PATH.exists():
        try:
            with PROGRESS_PATH.open("r", encoding="utf-8") as f:
                return json.load(f)
        except Exception:
            pass
    return {
        "next_day": START.isoformat(),
        "found": 0,
        "deleted": 0,
        "failed": 0,
        "seen_ids": [],
    }


def save_progress(state: dict, seen: set[int | str]) -> None:
    state["seen_ids"] = list(seen)[-5000:]
    with PROGRESS_PATH.open("w", encoding="utf-8") as f:
        json.dump(state, f)


def next_month(d: date) -> date:
    return date(d.year + (d.month // 12), d.month % 12 + 1, 1)


def run_search(query: str):
    attempts = 0
    while True:
        attempts += 1
        proc = subprocess.run(
            [
                "python3",
                WRAPPER,
                "--profile-key",
                PROFILE,
                "search",
                query,
                "--count",
                "100",
                "--all",
                "--max-pages",
                "12",
            ],
            capture_output=True,
            text=True,
            timeout=120,
        )
        if proc.returncode == 0:
            raw = (proc.stdout or "").strip()
            if not raw:
                return []
            try:
                data = json.loads(raw)
            except Exception:
                log(f"WARN json parse failed for query={query}")
                return []
            return [t for t in (data.get("tweets", []) if isinstance(data, dict) else data) if isinstance(t, dict)]

        err = (proc.stderr or "") + (proc.stdout or "")
        if "429" in err and attempts <= 6:
            log(f"429 on {query}, attempt {attempts}, wait {DELAY_429}s")
            time.sleep(DELAY_429)
            continue

        log(f"QUERY ERROR rc={proc.returncode} query={query}: {err[:240]}")
        return []


def delete_tweet(tweet: dict) -> bool:
    tid = tweet.get("id")
    if not tid:
        return False
    text = tweet.get("text", "")
    cmd = "unretweet" if str(text).startswith("RT @") else "delete"
    url = f"https://x.com/{USER}/status/{tid}"

    proc = subprocess.run(
        ["python3", WRAPPER, "--profile-key", PROFILE, cmd, url],
        capture_output=True,
        text=True,
        timeout=120,
    )
    if proc.returncode != 0:
        low = (proc.stderr + proc.stdout).lower()
        if "not found" in low or "does not exist" in low:
            return True
        if "429" in low:
            time.sleep(DELAY_429)
            return delete_tweet(tweet)
        return False

    try:
        data = json.loads(proc.stdout)
        if isinstance(data, dict) and data.get("ok"):
            return True
    except Exception:
        pass

    return '"status_code": 200' in proc.stdout


def main() -> None:
    state = load_progress()
    cur = date.fromisoformat(state.get("next_day", START.isoformat()))
    if cur < START:
        cur = START

    found = state.get("found", 0)
    deleted = state.get("deleted", 0)
    failed = state.get("failed", 0)
    seen = set(state.get("seen_ids", []))

    log(f"=== START Karateka | from {cur} ===")

    while cur < CUTOFF:
        nxt = next_month(cur)
        q = f"from:{USER} since:{cur.isoformat()} until:{nxt.isoformat()}"
        tweets = run_search(q)

        added = 0
        for t in tweets:
            tid = t.get("id")
            if not tid or tid in seen:
                continue
            seen.add(tid)
            added += 1
            found += 1

            if delete_tweet(t):
                deleted += 1
                log(f"DELETE OK {tid}")
            else:
                failed += 1
                log(f"DELETE FAIL {tid}")

            time.sleep(DELAY_DELETE)

        if added:
            log(f"{cur:%Y-%m}: +{added} new")

        state["next_day"] = nxt.isoformat()
        state["found"] = found
        state["deleted"] = deleted
        state["failed"] = failed
        save_progress(state, seen)

        cur = nxt
        time.sleep(DELAY)

    log(f"=== DONE Karateka | found={found} deleted={deleted} failed={failed} ===")


if __name__ == "__main__":
    main()