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/find_replies_left.py
#!/usr/bin/env python3
"""Find additional @datathrop posts not caught by first pass.
Focus on replies/search operators that can be omitted by default queries."""

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"
PROGRESS = Path("/workspace/datathrop_progress.json")
OUT_NEW = Path("/workspace/extra_missing.jsonl")

# Read current seen ids from original run to avoid re-processing known tweets.
if PROGRESS.exists():
    with PROGRESS.open("r", encoding="utf-8") as f:
        progress = json.load(f)
    seen = set(progress.get("seen_ids", []))
else:
    progress = {"next_day": "2011-07-01", "found": 0, "deleted": 0, "failed": 0, "seen_ids": []}
    seen = set()


def run_search(query: str, label: str):
    """Run one search query and return list of tweet dicts."""
    while True:
        r = subprocess.run(
            [
                "python3",
                WRAPPER,
                "--profile-key",
                PROFILE,
                "search",
                query,
                "--count",
                "100",
                "--all",
                "--max-pages",
                "12",
            ],
            capture_output=True,
            text=True,
            timeout=120,
        )
        if r.returncode != 0:
            if "429" in (r.stderr + r.stdout):
                print(f"{label}: 429, wait 90s")
                time.sleep(90)
                continue
            print(f"{label}: ERROR {r.returncode}")
            return []

        raw = (r.stdout or "").strip()
        if not raw:
            return []
        try:
            data = json.loads(raw)
        except Exception:
            print(f"{label}: JSON parse error")
            return []

        tweets = data.get("tweets", []) if isinstance(data, dict) else data
        return [t for t in tweets if isinstance(t, dict)]


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


start = date(2011, 7, 1)
cutoff = date(2026, 3, 13)

all_new = []

# 1) Replies first: these were not reliably surfaced in the first pass.
print("=== pass: filter:replies by month ===")
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_search(q, f"replies {cur}")
    added = 0
    for t in tweets:
        tid = t.get("id")
        if not tid or tid in seen:
            continue
        seen.add(tid)
        all_new.append(t)
        added += 1
    if added:
        print(f"  +{added} replies for {cur:%Y-%m}")
        save_new(tweets)
    cur = nxt
    time.sleep(2.2)

# 2) Non-replies explicitly, to cross-check for any operator misses.
print("=== pass: exclude:replies by month ===")
cur = start
while cur < cutoff:
    nxt = date(cur.year + (cur.month // 12), cur.month % 12 + 1, 1)
    q = f"from:datathrop exclude:replies since:{cur.isoformat()} until:{nxt.isoformat()}"
    tweets = run_search(q, f"exclude:replies {cur}")
    added = 0
    for t in tweets:
        tid = t.get("id")
        if not tid or tid in seen:
            continue
        seen.add(tid)
        all_new.append(t)
        added += 1
    if added:
        print(f"  +{added} non-replies for {cur:%Y-%m}")
        save_new(tweets)
    cur = nxt
    time.sleep(2.2)

# 3) Raw query fallback: include both replies and retweets and sort by operator.
print("=== pass: from:datathrop by month ===")
cur = start
while cur < cutoff:
    nxt = date(cur.year + (cur.month // 12), cur.month % 12 + 1, 1)
    q = f"from:datathrop since:{cur.isoformat()} until:{nxt.isoformat()}"
    tweets = run_search(q, f"from {cur}")
    added = 0
    for t in tweets:
        tid = t.get("id")
        if not tid or tid in seen:
            continue
        seen.add(tid)
        all_new.append(t)
        added += 1
    if added:
        print(f"  +{added} raw for {cur:%Y-%m}")
        save_new(tweets)
    cur = nxt
    time.sleep(2.2)

print(f"FOUND NEW={len(all_new)}")

# Persist seen ids (trim for file size)
progress["seen_ids"] = list(seen)[-5000:]
with PROGRESS.open("w", encoding="utf-8") as f:
    json.dump(progress, f)

if not all_new:
    print("NO NEW CANDIDATES")
    raise SystemExit(0)

# Delete everything found.
ok = 0
failed = []

for i, tweet in enumerate(all_new, 1):
    tid = tweet.get("id")
    if not tid:
        continue
    text = tweet.get("text", "")
    is_rt = str(text).startswith("RT @")
    url = f"https://x.com/datathrop/status/{tid}"
    cmd = "unretweet" if is_rt else "delete"

    r = subprocess.run(
        ["python3", WRAPPER, "--profile-key", PROFILE, cmd, url],
        capture_output=True,
        text=True,
        timeout=120,
    )
    stdout = r.stdout or ""
    stderr = r.stderr or ""

    success = False
    if r.returncode == 0:
        try:
            o = json.loads(stdout)
            if isinstance(o, dict) and o.get("ok"):
                success = True
        except Exception:
            if "200" in stdout:
                success = True

    if ("not found" in stderr.lower()) or ("not found" in stdout.lower()):
        success = True

    if success:
        ok += 1
        print(f"{i}/{len(all_new)} OK {tid}")
    else:
        failed.append(tid)
        print(f"{i}/{len(all_new)} FAIL {tid}")
    time.sleep(2)

print(f"=== EXTRA RUN DONE ok={ok} fail={len(failed)} total={len(all_new)} ===")
if failed:
    Path("/workspace/extra_missing_failed.txt").write_text("\n".join(failed) + "\n", encoding="utf-8")