Apagar seus tweets em massa, sem extensão

Um script gratuito para colar no console do Chrome e fazer a faxina nas suas publicações antigas no X: simulação primeiro, tweets recentes protegidos, exclusão em lotes limitados. Nenhuma extensão, nenhum serviço de terceiros, nada sai do seu navegador.

Antes de começar: a exclusão de um tweet é definitiva. O script vem em modo simulação (DRY_RUN = true): ele lista o que seria apagado sem mexer em nada. Baixe o seu arquivo do X previamente, confira a simulação e só então passe para a exclusão real. A automação da interface não é prevista pelos termos de uso do X: vá com calma, por sua conta e risco.

O passo a passo

  1. Abra o seu perfil: acesse x.com/seu_usuario, aba Posts, conectado à sua conta.
  2. Abra o console: pressione F12 (ou Cmd+Option+J no Mac), depois clique na aba Console.
  3. Configure o script: copie o código abaixo, substitua seu_usuario pelo seu identificador do X (sem o @), ajuste KEEP_RECENT (número de tweets recentes a manter) e, se precisar, liste em KEEP_STATUS_IDS os identificadores dos tweets que nunca devem ser apagados.
  4. Simule: cole o script no console e confirme com Enter. Com DRY_RUN = true (a configuração padrão), ele exibe a lista dos tweets que seriam apagados, sem apagar nada.
  5. Apague: se a simulação estiver a seu gosto, mude DRY_RUN para false e execute de novo. O script apaga no máximo 40 tweets por execução, com pausas de 6 a 14 segundos.
  6. Repita: execute o script quantas vezes forem necessárias para tratar o restante, em lotes.

O script

Console do Chrome · x.com
(() => {
  // ============ CONFIG ============
  const HANDLE          = "seu_usuario"; // seu identificador do X, sem o @
  const KEEP_RECENT     = 15;           // NUNCA apaga os 15 tweets mais recentes (no topo)
  const KEEP_STATUS_IDS = [             // protecao ADICIONAL (opcional): identificadores a nunca apagar
    "1234567890123456789",
  ];
  let   DRY_RUN         = true;         // true = simulacao (so lista). false = EXCLUSAO REAL.
  const MIN_DELAY_MS    = 6000;
  const MAX_DELAY_MS    = 14000;
  const MAX_DELETIONS   = 40;           // limite por execucao (execute de novo para continuar)
  // ================================

  const me = HANDLE.toLowerCase();
  const KEEP = new Set(KEEP_STATUS_IDS.map(String));
  const protectedRecent = new Set();
  const RX_DELETE = /(Excluir|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[PARE] Va para https://x.com/${HANDLE} (aba Posts) e execute de novo.`,
      "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;
  };

  // Carrega e protege os KEEP_RECENT tweets mais recentes (os do topo).
  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;   // um dos mais recentes
    if (KEEP.has(id)) return null;              // ID protegido adicional
    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[SIMULACAO] Nada sera apagado. Contando...", "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[SIMULACAO] ${seen.size} tweet(s) SERIAM apagados (os mais recentes estao protegidos).`,
      "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 ? "apagado" : "ignorado"} ${id}  (total: ${count}/${MAX_DELETIONS})`);
      await sleep(rand(MIN_DELAY_MS, MAX_DELAY_MS));
    }
    console.log(`%c[FIM] ${count} apagado(s) nesta rodada. Execute o script de novo se ainda restarem.`,
      "color:#00ba7c;font-weight:bold");
  }

  (async () => {
    console.log(`Carregando e protegendo os ${KEEP_RECENT} tweets mais recentes...`);
    const recent = await protectRecent();
    if (recent.length < KEEP_RECENT) {
      console.warn(`%c[PARE] Apenas ${recent.length} tweets carregados (o X limitou a taxa). Recarregue a pagina (F5) e tente de novo.`,
        "color:#e0245e;font-weight:bold");
      return;
    }
    console.log("%cProtegidos (os mais recentes):", "color:#00ba7c;font-weight:bold", recent);
    if (DRY_RUN) { await dryScan(); return; }
    await run();
  })();
})();

Como o script protege você

Há quatro barreiras de segurança integradas. Primeiro, a simulação por padrão: enquanto DRY_RUN vale true, nada é apagado, você só obtém a lista dos tweets envolvidos. Depois, a proteção dos recentes: o script começa memorizando os seus tweets mais recentes (15 por padrão) e nunca vai mexer neles. Em seguida, a lista de exceções: qualquer identificador colocado em KEEP_STATUS_IDS fica intocável, prático para os seus tweets fixados ou os seus anúncios. Por fim, o ritmo prudente: pausas aleatórias de 6 a 14 segundos entre cada exclusão e parada automática após 40, para não acionar as limitações do X.

Antes da grande faxina, lembre-se de arquivar o que importa: o extrator de e-mails e URLs recupera todos os links de uma exportação de tweets, o contador de palavras revela quanto pesavam seus anos de publicações, e o limpador de texto deixa em ordem um copia e cola de arquivo. E para os seus próximos tweets, o texto estilizado Unicode e o conversor de maiúsculas e minúsculas estão à disposição. Todas as ferramentas da caixa de ferramentas funcionam no seu navegador, como este guia, no espírito da nossa política de privacidade.

Perguntas frequentes

A exclusão pode ser desfeita?

Não. Um tweet apagado é apagado definitivamente, o X não oferece nenhuma lixeira. É por isso que o script vem em modo simulação (DRY_RUN = true): execute-o primeiro assim mesmo para obter a lista do que seria apagado, confira-a e mude para false somente quando tiver certeza. Lembre-se também de baixar o seu arquivo do X antes (Configurações, depois Sua conta, depois Baixar um arquivo dos seus dados).

O script pode apagar os tweets de outra pessoa?

Não. O X só exibe o botão Excluir nas suas próprias publicações: o script só pode agir na conta com a qual você está conectado, na sua própria página de perfil. Ele não mexe nas respostas de outras pessoas nem em nenhum conteúdo de terceiros.

Por que um limite de 40 exclusões e intervalos aleatórios?

Para ir com calma com a plataforma. Exclusões rápidas ou massivas demais podem acionar as limitações do X, ou até um bloqueio temporário da conta. O script espera entre 6 e 14 segundos entre cada exclusão e para depois de 40: execute-o de novo para continuar em lotes.

Isso é permitido pelo X?

Você apaga o seu próprio conteúdo, o que é um direito seu. Por outro lado, a automação da interface web não é prevista pelos termos de uso do X: use este script com moderação e por sua conta e risco. A alternativa oficial é a exclusão manual, ou os serviços dedicados que passam pela API.

Como proteger tweets específicos?

Duas proteções se somam: KEEP_RECENT mantém automaticamente os seus tweets mais recentes (15 por padrão), e KEEP_STATUS_IDS aceita uma lista de identificadores que nunca devem ser apagados. O identificador de um tweet é o número grande no final do seu endereço: x.com/usuario/status/1234567890123456789.