Supprimer ses tweets en masse, sans extension
Un script gratuit à coller dans la console de Chrome pour faire le ménage dans vos anciens posts X : simulation d'abord, tweets récents protégés, suppression par lots plafonnés. Aucune extension, aucun service tiers, rien ne quitte votre navigateur.
DRY_RUN = true) : il liste ce qui serait supprimé sans rien toucher. Téléchargez votre archive X au préalable, vérifiez la simulation, et seulement ensuite passez en suppression réelle. L'automatisation de l'interface n'est pas prévue par les conditions d'utilisation de X : allez-y doucement, à vos risques.
La marche à suivre
- Ouvrez votre profil : rendez-vous sur
x.com/votre_pseudo, onglet Posts, connecté à votre compte. - Ouvrez la console : appuyez sur
F12(ouCmd+Option+Jsur Mac), puis cliquez sur l'onglet Console. - Configurez le script : copiez le code ci-dessous, remplacez
votre_pseudopar votre identifiant X (sans le @), ajustezKEEP_RECENT(nombre de tweets récents à garder) et, si besoin, listez dansKEEP_STATUS_IDSles identifiants des tweets à ne jamais supprimer. - Simulez : collez le script dans la console et validez avec Entrée. Avec
DRY_RUN = true(le réglage par défaut), il affiche la liste des tweets qui seraient supprimés, sans rien supprimer. - Supprimez : si la simulation vous convient, passez
DRY_RUNàfalseet relancez. Le script supprime au maximum 40 tweets par exécution, avec des pauses de 6 à 14 secondes. - Recommencez : relancez le script autant de fois que nécessaire pour traiter le reste, par lots.
Le script
(() => {
// ============ CONFIG ============
const HANDLE = "votre_pseudo"; // votre identifiant X, sans le @
const KEEP_RECENT = 15; // ne supprime JAMAIS les 15 tweets les plus recents (en haut)
const KEEP_STATUS_IDS = [ // protection EN PLUS (optionnel) : identifiants a ne jamais supprimer
"1234567890123456789",
];
let DRY_RUN = true; // true = simulation (liste seulement). false = SUPPRESSION REELLE.
const MIN_DELAY_MS = 6000;
const MAX_DELAY_MS = 14000;
const MAX_DELETIONS = 40; // plafond par execution (relance pour continuer)
// ================================
const me = HANDLE.toLowerCase();
const KEEP = new Set(KEEP_STATUS_IDS.map(String));
const protectedRecent = new Set();
const RX_DELETE = /^(supprimer|delete)/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] Va sur https://x.com/${HANDLE} (onglet Posts) puis relance.`,
"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;
};
// Charge et protege les KEEP_RECENT tweets les plus recents (ceux du haut).
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; // un des plus recents
if (KEEP.has(id)) return null; // ID protege en plus
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[SIMULATION] Rien ne sera supprime. Comptage...", "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[SIMULATION] ${seen.size} tweet(s) SERAIENT supprimes (les plus recents sont proteges).`,
"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 ? "supprime" : "ignore"} ${id} (total: ${count}/${MAX_DELETIONS})`);
await sleep(rand(MIN_DELAY_MS, MAX_DELAY_MS));
}
console.log(`%c[FIN] ${count} supprime(s) ce tour. Relance le script s'il en reste.`,
"color:#00ba7c;font-weight:bold");
}
(async () => {
console.log(`Chargement et protection des ${KEEP_RECENT} tweets les plus recents...`);
const recent = await protectRecent();
if (recent.length < KEEP_RECENT) {
console.warn(`%c[STOP] Seulement ${recent.length} tweets charges (X a throttle). Recharge la page (F5) et reessaie.`,
"color:#e0245e;font-weight:bold");
return;
}
console.log("%cProteges (les plus recents):", "color:#00ba7c;font-weight:bold", recent);
if (DRY_RUN) { await dryScan(); return; }
await run();
})();
})();
Comment le script vous protège
Quatre garde-fous sont intégrés. D'abord la simulation par défaut : tant que DRY_RUN vaut true, rien n'est supprimé, vous obtenez seulement la liste des tweets concernés. Ensuite la protection des récents : le script commence par mémoriser vos tweets les plus récents (15 par défaut) et ne les touchera jamais. Puis la liste blanche : tout identifiant placé dans KEEP_STATUS_IDS est intouchable, pratique pour vos tweets épinglés ou vos annonces. Enfin le rythme prudent : pauses aléatoires de 6 à 14 secondes entre chaque suppression et arrêt automatique après 40, pour ne pas déclencher les limitations de X.
Avant le grand ménage, pensez à archiver ce qui compte : le extracteur d'e-mails et d'URL récupère tous les liens d'un export de tweets, le compteur de mots vous dit ce que pesaient vos années de posts, et le nettoyeur de texte remet au propre un copier-coller d'archive. Et pour vos futurs tweets, le texte stylisé Unicode et le convertisseur de casse sont là. Tous les outils de la boîte à outils fonctionnent dans votre navigateur, comme ce guide, dans l'esprit de notre politique de confidentialité.
Questions fréquentes
La suppression est-elle réversible ?
Non. Un tweet supprimé l'est définitivement, X ne propose aucune corbeille. C'est pourquoi le script est fourni en mode simulation (DRY_RUN = true) : lancez-le d'abord tel quel pour obtenir la liste de ce qui serait supprimé, vérifiez-la, et passez à false seulement quand vous êtes certain. Pensez aussi à télécharger votre archive X avant (Paramètres, puis Votre compte, puis Télécharger une archive de vos données).
Le script peut-il supprimer les tweets de quelqu'un d'autre ?
Non. X n'affiche le bouton Supprimer que sur vos propres posts : le script ne peut agir que sur le compte avec lequel vous êtes connecté, sur votre propre page de profil. Il ne touche ni aux réponses des autres ni à aucun contenu tiers.
Pourquoi un plafond de 40 suppressions et des délais aléatoires ?
Pour rester doux avec la plateforme. Des suppressions trop rapides ou trop massives peuvent déclencher les limitations de X, voire un blocage temporaire du compte. Le script attend entre 6 et 14 secondes entre chaque suppression et s'arrête après 40 : relancez-le pour continuer par lots.
Est-ce autorisé par X ?
Vous supprimez votre propre contenu, ce qui est votre droit. En revanche, l'automatisation de l'interface web n'est pas prévue par les conditions d'utilisation de X : utilisez ce script avec modération et à vos risques. L'alternative officielle est la suppression manuelle, ou les services dédiés qui passent par l'API.
Comment protéger certains tweets précis ?
Deux protections se cumulent : KEEP_RECENT garde automatiquement vos tweets les plus récents (15 par défaut), et KEEP_STATUS_IDS accepte une liste d'identifiants à ne jamais supprimer. L'identifiant d'un tweet est le grand nombre à la fin de son adresse : x.com/pseudo/status/1234567890123456789.