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/self/root/workspace/scan_and_delete_replies.py
#!/usr/bin/env python3
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 = "datathrop"
START = date(2011, 7, 1)
CUTOFF = date(2026, 3, 13)
MAX_RETRIES = 4
RETRY_DELAY = 90
DELAY_BETWEEN_QUERIES = 2
DELAY_BETWEEN_DELETES = 1

PROGRESS = Path("/workspace/datathrop_progress.json")
OUT_JSONL = Path("/workspace/extra_missing.jsonl")
FAILED_TXT = Path("/workspace/extra_missing_failed.txt")

if PROGRESS.exists():
    progress = json.loads(PROGRESS.read_text(encoding="utf-8"))
    seen = set(progress.get("seen_ids", []))
else:
    progress = {"next_day": "2011-07-01", "found": 0, "deleted": 0, "failed": 0, "seen_ids": []}
    seen = set()

# Load already found ids
if OUT_JSONL.exists():
    for line in OUT_JSONL.read_text(encoding="utf-8").splitlines():
        try:
            t = json.loads(line)
            tid = t.get("id")
            if tid:
                seen.add(tid)
        except Exception:
            pass


def run_query(query: str):
    for attempt in range(1, MAX_RETRIES + 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 as e:
                print(f"JSON parse fail for '{query}': {e}")
                return []
            tweets = data.get("tweets", []) if isinstance(data, dict) else data
            return [t for t in tweets if isinstance(t, dict)]

        err = (proc.stderr or "") + (proc.stdout or "")
        if "429" in err:
            print(f"429 on {query}, attempt {attempt}/{MAX_RETRIES}, wait {RETRY_DELAY}s", flush=True)
            if attempt < MAX_RETRIES:
                time.sleep(RETRY_DELAY)
                continue
            print(f"SKIP after retries: {query}", flush=True)
            return []
        print(f"ERROR {proc.returncode} on {query}: {err.strip()[:240]}", flush=True)
        return []

    return []


def save_jsonl(rows):
    if not rows:
        return
    mode = "a" if OUT_JSONL.exists() else "w"
    with OUT_JSONL.open(mode, encoding="utf-8") as f:
        for r in rows:
            f.write(json.dumps(r) + "\n")


def delete_tweet(tid: str, text: str) -> bool:
    op = "unretweet" if str(text).startswith("RT @") else "delete"
    url = f"https://x.com/datathrop/status/{tid}"
    p = subprocess.run(["python3", WRAPPER, "--profile-key", PROFILE, op, url], capture_output=True, text=True, timeout=120)
    if p.returncode != 0:
        err = (p.stderr or "") + (p.stdout or "")
        if "not found" in err.lower() or "does not exist" in err.lower():
            return True
        return False
    try:
        d = json.loads(p.stdout)
        return isinstance(d, dict) and d.get("ok")
    except Exception:
        return '"status_code": 200' in p.stdout or "200" in p.stdout


new_found = []

print("=== scanning by month: filter:replies ===", flush=True)
cur = START
while cur < CUTOFF:
    nxt = date(cur.year + (cur.month // 12), cur.month % 12 + 1, 1)
    q = f"from:datathrop filter:replies since:{cur.isoformat()} until:{nxt.isoformat()}"
    tweets = run_query(q)
    added = []
    for t in tweets:
        tid = t.get("id")
        if tid and tid not in seen:
            seen.add(tid)
            new_found.append(t)
            added.append(t)
    if added:
        save_jsonl(added)
        print(f"{cur:%Y-%m}: +{len(added)} new replies", flush=True)
    cur = nxt
    time.sleep(DELAY_BETWEEN_QUERIES)

print(f"FOUND replies total={len(new_found)}", flush=True)

if not new_found:
    print("No new replies found.")
    progress["seen_ids"] = list(seen)[-8000:]
    PROGRESS.write_text(json.dumps(progress), encoding="utf-8")
    raise SystemExit(0)

ok = fail = 0
fails = []

for i, t in enumerate(new_found, 1):
    tid = t.get("id")
    text = t.get("text", "")
    if not tid:
        fail += 1
        fails.append("<missing-id>")
        continue
    if delete_tweet(tid, text):
        ok += 1
        print(f"DEL {i}/{len(new_found)} OK {tid}", flush=True)
    else:
        fail += 1
        fails.append(tid)
        print(f"DEL {i}/{len(new_found)} FAIL {tid}", flush=True)
    time.sleep(DELAY_BETWEEN_DELETES)

print(f"=== DONE replies scan/delete: found={len(new_found)} ok={ok} fail={len(fails)} ===", flush=True)
if fails:
    FAILED_TXT.write_text("\n".join(fails) + "\n", encoding="utf-8")

# update main progress counters roughly
progress["found"] = progress.get("found", 0) + len(new_found)
progress["deleted"] = progress.get("deleted", 0) + ok
progress["failed"] = progress.get("failed", 0) + fail
progress["seen_ids"] = list(seen)[-8000:]
PROGRESS.write_text(json.dumps(progress), encoding="utf-8")