Eliminare i tuoi tweet in massa, senza estensione

Uno script gratuito da incollare nella console di Chrome per fare pulizia tra i tuoi vecchi post X: prima la simulazione, i tweet recenti protetti, eliminazione a lotti con un tetto massimo. Nessuna estensione, nessun servizio di terzi, niente lascia il tuo browser.

Prima di iniziare: l'eliminazione di un tweet è definitiva. Lo script viene fornito in modalità simulazione (DRY_RUN = true): elenca ciò che verrebbe eliminato senza toccare nulla. Scarica prima il tuo archivio X, verifica la simulazione e solo dopo passa all'eliminazione reale. L'automazione dell'interfaccia non è prevista dalle condizioni d'uso di X: procedi con calma, a tuo rischio.

Come procedere

  1. Apri il tuo profilo: vai su x.com/tuo_username, scheda Post, connesso al tuo account.
  2. Apri la console: premi F12 (oppure Cmd+Option+J su Mac), poi clicca sulla scheda Console.
  3. Configura lo script: copia il codice qui sotto, sostituisci tuo_username con il tuo identificatore X (senza la @), regola KEEP_RECENT (numero di tweet recenti da conservare) e, se serve, elenca in KEEP_STATUS_IDS gli identificatori dei tweet da non eliminare mai.
  4. Simula: incolla lo script nella console e conferma con Invio. Con DRY_RUN = true (l'impostazione predefinita), mostra l'elenco dei tweet che verrebbero eliminati, senza eliminare nulla.
  5. Elimina: se la simulazione ti convince, imposta DRY_RUN su false e rilancia. Lo script elimina al massimo 40 tweet per esecuzione, con pause da 6 a 14 secondi.
  6. Ripeti: rilancia lo script tutte le volte che serve per trattare il resto, a lotti.

Lo script

Console Chrome · x.com
(() => {
  // ============ CONFIG ============
  const HANDLE          = "tuo_username"; // il tuo identificatore X, senza la @
  const KEEP_RECENT     = 15;           // non elimina MAI i 15 tweet piu recenti (in alto)
  const KEEP_STATUS_IDS = [             // protezione IN PIU (opzionale): identificatori da non eliminare mai
    "1234567890123456789",
  ];
  let   DRY_RUN         = true;         // true = simulazione (solo elenco). false = ELIMINAZIONE REALE.
  const MIN_DELAY_MS    = 6000;
  const MAX_DELAY_MS    = 14000;
  const MAX_DELETIONS   = 40;           // tetto per esecuzione (rilancia per continuare)
  // ================================

  const me = HANDLE.toLowerCase();
  const KEEP = new Set(KEEP_STATUS_IDS.map(String));
  const protectedRecent = new Set();
  const RX_DELETE = /(Elimina|delete|supprimer)/i;
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
  const rand  = (a, b) => Math.floor(a + Math.random() * (b - a));
  const skipped = new Set();

  if (!location.pathname.toLowerCase().startsWith("/" + me)) {
    console.warn(`%c[STOP] Vai su https://x.com/${HANDLE} (scheda Post) e rilancia.`,
      "color:#e0245e;font-weight:bold");
    return;
  }

  const articles = () => [...document.querySelectorAll('article[data-testid="tweet"]')];

  const idOf = (art) => {
    const href = [...art.querySelectorAll('a[href*="/status/"]')]
      .map((x) => x.getAttribute("href"))
      .find((h) => new RegExp(`^/${HANDLE}/status/\\d+`, "i").test(h));
    if (!href) return null;
    const m = href.match(/status\/(\d+)/);
    return m ? m[1] : null;
  };

  // Carica e protegge i KEEP_RECENT tweet piu recenti (quelli in alto).
  async function protectRecent() {
    const order = [], seen = new Set();
    let noNew = 0;
    window.scrollTo(0, 0);
    await sleep(1200);
    while (order.length < KEEP_RECENT && noNew < 6) {
      let added = 0;
      for (const art of articles()) {
        const id = idOf(art);
        if (id && !seen.has(id)) { seen.add(id); order.push(id); added++; }
      }
      noNew = added ? 0 : noNew + 1;
      if (order.length >= KEEP_RECENT) break;
      const cs = articles();
      (cs[cs.length - 1] || document.body).scrollIntoView({ block: "end" });
      await sleep(1400);
    }
    for (const id of order.slice(0, KEEP_RECENT)) protectedRecent.add(id);
    return order.slice(0, KEEP_RECENT);
  }

  const eligible = (art) => {
    const id = idOf(art);
    if (!id) return null;
    if (protectedRecent.has(id)) return null;   // uno dei piu recenti
    if (KEEP.has(id)) return null;              // ID protetto in piu
    if (skipped.has(id)) return null;
    return id;
  };

  async function deleteOne(art, id) {
    const caret = art.querySelector('[data-testid="caret"]');
    if (!caret) { skipped.add(id); return false; }
    caret.click();
    await sleep(700);
    const del = [...document.querySelectorAll('[role="menuitem"]')]
      .find((i) => RX_DELETE.test((i.innerText || "").trim()));
    if (!del) { document.body.click(); skipped.add(id); return false; }
    del.click();
    await sleep(700);
    const confirmBtn = document.querySelector('[data-testid="confirmationSheetConfirm"]');
    if (!confirmBtn) { skipped.add(id); return false; }
    confirmBtn.click();
    await sleep(1500);
    return true;
  }

  async function dryScan() {
    const seen = new Set();
    let noNew = 0;
    console.log("%c[SIMULAZIONE] Non verra eliminato nulla. Conteggio...", "color:#1d9bf0;font-weight:bold");
    while (noNew < 4) {
      let added = 0;
      for (const art of articles()) {
        const id = eligible(art);
        if (id && !seen.has(id)) { seen.add(id); added++; }
      }
      noNew = added ? 0 : noNew + 1;
      const cs = articles();
      (cs[cs.length - 1] || document.body).scrollIntoView({ block: "end" });
      await sleep(1200);
    }
    console.log(`%c[SIMULAZIONE] ${seen.size} tweet VERREBBERO eliminati (i piu recenti sono protetti).`,
      "color:#1d9bf0;font-weight:bold");
    console.log([...seen].slice(0, 60));
  }

  async function run() {
    let count = 0, guard = 0, noHit = 0, started = false;
    while (count < MAX_DELETIONS && guard++ < 1500) {
      const pair = articles().map((a) => [a, eligible(a)]).find(([, id]) => id);
      if (!pair) {
        if ((started && noHit >= 6) || noHit >= 40) break;
        const cs = articles();
        (cs[cs.length - 1] || document.body).scrollIntoView({ block: "end" });
        await sleep(1500);
        noHit++;
        continue;
      }
      started = true; noHit = 0;
      const [node, id] = pair;
      node.scrollIntoView({ block: "center" });
      await sleep(600);
      const ok = await deleteOne(node, id);
      if (ok) count++;
      console.log(`${ok ? "eliminato" : "ignorato"} ${id}  (totale: ${count}/${MAX_DELETIONS})`);
      await sleep(rand(MIN_DELAY_MS, MAX_DELAY_MS));
    }
    console.log(`%c[FINE] ${count} eliminato/i in questo giro. Rilancia lo script se ne restano.`,
      "color:#00ba7c;font-weight:bold");
  }

  (async () => {
    console.log(`Caricamento e protezione dei ${KEEP_RECENT} tweet piu recenti...`);
    const recent = await protectRecent();
    if (recent.length < KEEP_RECENT) {
      console.warn(`%c[STOP] Solo ${recent.length} tweet caricati (X ha applicato un throttle). Ricarica la pagina (F5) e riprova.`,
        "color:#e0245e;font-weight:bold");
      return;
    }
    console.log("%cProtetti (i piu recenti):", "color:#00ba7c;font-weight:bold", recent);
    if (DRY_RUN) { await dryScan(); return; }
    await run();
  })();
})();

Come lo script ti protegge

Sono integrate quattro protezioni. Innanzitutto la simulazione predefinita: finché DRY_RUN vale true, non viene eliminato nulla, ottieni solo l'elenco dei tweet interessati. Poi la protezione dei recenti: lo script inizia memorizzando i tuoi tweet più recenti (15 di default) e non li toccherà mai. Quindi la lista bianca: qualunque identificatore inserito in KEEP_STATUS_IDS è intoccabile, comodo per i tuoi tweet fissati o i tuoi annunci. Infine il ritmo prudente: pause casuali da 6 a 14 secondi tra un'eliminazione e l'altra e arresto automatico dopo 40, per non attivare le limitazioni di X.

Prima delle grandi pulizie, ricorda di archiviare ciò che conta: l'estrattore di e-mail e URL recupera tutti i link di un export di tweet, il contatore di parole ti dice quanto pesavano i tuoi anni di post, e la pulizia testo rimette in ordine un copia-incolla di archivio. E per i tuoi futuri tweet, il testo stilizzato Unicode e il convertitore di maiuscole e minuscole sono a tua disposizione. Tutti gli strumenti della cassetta degli attrezzi funzionano nel tuo browser, come questa guida, nello spirito della nostra informativa sulla privacy.

Domande frequenti

L'eliminazione è reversibile?

No. Un tweet eliminato lo è in modo definitivo, X non offre alcun cestino. Ecco perché lo script viene fornito in modalità simulazione (DRY_RUN = true): eseguilo prima così com'è per ottenere l'elenco di ciò che verrebbe eliminato, verificalo e passa a false solo quando sei sicuro. Ricordati anche di scaricare il tuo archivio X prima (Impostazioni, poi Il tuo account, poi Scarica un archivio dei tuoi dati).

Lo script può eliminare i tweet di qualcun altro?

No. X mostra il pulsante Elimina solo sui tuoi post: lo script può agire solo sull'account con cui sei connesso, sulla tua pagina di profilo. Non tocca né le risposte degli altri né alcun contenuto di terzi.

Perché un tetto di 40 eliminazioni e ritardi casuali?

Per restare gentili con la piattaforma. Eliminazioni troppo rapide o troppo massicce possono attivare le limitazioni di X, o addirittura un blocco temporaneo dell'account. Lo script attende tra 6 e 14 secondi tra un'eliminazione e l'altra e si ferma dopo 40: rilancialo per continuare a lotti.

È consentito da X?

Elimini i tuoi contenuti, il che è un tuo diritto. L'automazione dell'interfaccia web, invece, non è prevista dalle condizioni d'uso di X: usa questo script con moderazione e a tuo rischio. L'alternativa ufficiale è l'eliminazione manuale, oppure i servizi dedicati che passano dall'API.

Come proteggere alcuni tweet specifici?

Due protezioni si sommano: KEEP_RECENT conserva automaticamente i tuoi tweet più recenti (15 di default), e KEEP_STATUS_IDS accetta un elenco di identificatori da non eliminare mai. L'identificatore di un tweet è il grande numero alla fine del suo indirizzo: x.com/username/status/1234567890123456789.