Eliminar tus tweets en masa, sin extensiones
Un script gratuito que pegas en la consola de Chrome para hacer limpieza en tus posts antiguos de X: primero simulación, tweets recientes protegidos, eliminación por lotes con tope. Sin extensiones, sin servicios de terceros, nada sale de tu navegador.
DRY_RUN = true): enumera lo que se eliminaría sin tocar nada. Descarga tu archivo de X de antemano, revisa la simulación y solo después pasa a la eliminación real. La automatización de la interfaz no está prevista por las condiciones de uso de X: ve con calma, bajo tu propia responsabilidad.
Cómo hacerlo
- Abre tu perfil: ve a
x.com/tu_usuario, pestaña Posts, con tu cuenta iniciada. - Abre la consola: pulsa
F12(oCmd+Option+Jen Mac), luego haz clic en la pestaña Console. - Configura el script: copia el código de abajo, reemplaza
tu_usuariopor tu identificador de X (sin la @), ajustaKEEP_RECENT(número de tweets recientes que conservar) y, si hace falta, enumera enKEEP_STATUS_IDSlos identificadores de los tweets que nunca deben eliminarse. - Simula: pega el script en la consola y valida con Intro. Con
DRY_RUN = true(el ajuste por defecto), muestra la lista de los tweets que se eliminarían, sin eliminar nada. - Elimina: si la simulación te convence, pon
DRY_RUNenfalsey vuelve a lanzarlo. El script elimina como máximo 40 tweets por ejecución, con pausas de 6 a 14 segundos. - Repite: vuelve a lanzar el script tantas veces como haga falta para tratar el resto, por lotes.
El script
(() => {
// ============ CONFIG ============
const HANDLE = "tu_usuario"; // tu identificador de X, sin la @
const KEEP_RECENT = 15; // NUNCA elimina los 15 tweets mas recientes (los de arriba)
const KEEP_STATUS_IDS = [ // proteccion ADICIONAL (opcional): identificadores que nunca eliminar
"1234567890123456789",
];
let DRY_RUN = true; // true = simulacion (solo lista). false = ELIMINACION REAL.
const MIN_DELAY_MS = 6000;
const MAX_DELAY_MS = 14000;
const MAX_DELETIONS = 40; // tope por ejecucion (vuelve a lanzar para continuar)
// ================================
const me = HANDLE.toLowerCase();
const KEEP = new Set(KEEP_STATUS_IDS.map(String));
const protectedRecent = new Set();
const RX_DELETE = /(Eliminar|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] Ve a https://x.com/${HANDLE} (pestana Posts) y vuelve a lanzarlo.`,
"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;
};
// Carga y protege los KEEP_RECENT tweets mas recientes (los de arriba).
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 de los mas recientes
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[SIMULACION] No se eliminara nada. 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[SIMULACION] ${seen.size} tweet(s) SE ELIMINARIAN (los mas recientes estan 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 ? "eliminado" : "ignorado"} ${id} (total: ${count}/${MAX_DELETIONS})`);
await sleep(rand(MIN_DELAY_MS, MAX_DELAY_MS));
}
console.log(`%c[FIN] ${count} eliminado(s) en esta ronda. Vuelve a lanzar el script si quedan.`,
"color:#00ba7c;font-weight:bold");
}
(async () => {
console.log(`Cargando y protegiendo los ${KEEP_RECENT} tweets mas recientes...`);
const recent = await protectRecent();
if (recent.length < KEEP_RECENT) {
console.warn(`%c[STOP] Solo ${recent.length} tweets cargados (X ha limitado). Recarga la pagina (F5) y reintenta.`,
"color:#e0245e;font-weight:bold");
return;
}
console.log("%cProtegidos (los mas recientes):", "color:#00ba7c;font-weight:bold", recent);
if (DRY_RUN) { await dryScan(); return; }
await run();
})();
})();
Cómo te protege el script
Hay cuatro salvaguardas integradas. Primero la simulación por defecto: mientras DRY_RUN valga true, no se elimina nada, solo obtienes la lista de los tweets afectados. Después la protección de los recientes: el script empieza por memorizar tus tweets más recientes (15 por defecto) y nunca los tocará. Luego la lista blanca: todo identificador colocado en KEEP_STATUS_IDS es intocable, práctico para tus tweets fijados o tus anuncios. Por último el ritmo prudente: pausas aleatorias de 6 a 14 segundos entre cada eliminación y parada automática tras 40, para no activar las limitaciones de X.
Antes de la gran limpieza, recuerda archivar lo que importa: el extractor de e-mails y URL recupera todos los enlaces de una exportación de tweets, el contador de palabras te dice cuánto pesaban tus años de posts, y el limpiador de texto pone en orden un copiar y pegar de tu archivo. Y para tus futuros tweets, el texto estilizado Unicode y el conversor de mayúsculas y minúsculas están ahí. Todas las herramientas de la caja de herramientas funcionan en tu navegador, como esta guía, en el espíritu de nuestra política de privacidad.
Preguntas frecuentes
¿La eliminación es reversible?
No. Un tweet eliminado lo está de forma definitiva, X no ofrece ninguna papelera. Por eso el script se entrega en modo simulación (DRY_RUN = true): ejecútalo primero tal cual para obtener la lista de lo que se eliminaría, revísala y cambia a false solo cuando estés seguro. Recuerda también descargar tu archivo de X antes (Configuración, luego Tu cuenta, luego Descarga un archivo de tus datos).
¿Puede el script eliminar los tweets de otra persona?
No. X solo muestra el botón Eliminar en tus propios posts: el script solo puede actuar sobre la cuenta con la que has iniciado sesión, en tu propia página de perfil. No toca ni las respuestas de los demás ni ningún contenido de terceros.
¿Por qué un tope de 40 eliminaciones y esperas aleatorias?
Para ser suave con la plataforma. Eliminaciones demasiado rápidas o demasiado masivas pueden activar las limitaciones de X, e incluso un bloqueo temporal de la cuenta. El script espera entre 6 y 14 segundos entre cada eliminación y se detiene tras 40: vuelve a lanzarlo para continuar por lotes.
¿Lo permite X?
Estás eliminando tu propio contenido, lo cual es tu derecho. En cambio, la automatización de la interfaz web no está prevista por las condiciones de uso de X: usa este script con moderación y bajo tu propia responsabilidad. La alternativa oficial es la eliminación manual, o los servicios dedicados que pasan por la API.
¿Cómo proteger ciertos tweets concretos?
Se acumulan dos protecciones: KEEP_RECENT conserva automáticamente tus tweets más recientes (15 por defecto), y KEEP_STATUS_IDS acepta una lista de identificadores que nunca se eliminarán. El identificador de un tweet es el número largo al final de su dirección: x.com/usuario/status/1234567890123456789.