Skip to content

1momoh/X-Profile-Cleaner

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 

Repository files navigation

X Profile Cleaner

A browser-console helper for cleaning up your own X profile by deleting posts, removing reposts, and removing likes in slow, human-paced batches.

This script is designed for personal account cleanup. It runs in your browser while you are logged in to X and interacts with the visible page, similar to how you would click through the UI manually.

What It Can Do

  • Delete posts from your profile timeline.
  • Delete replies from your replies page.
  • Undo reposts from your profile timeline.
  • Remove likes from your likes page.
  • Run in limited batches to reduce rate-limit and account-lock risk.
  • Stop safely at any time with XCleaner.stop().

Important Warnings

Deleting posts is permanent. Make sure you have exported your X archive first if you want a backup.

X may rate-limit, challenge, or temporarily lock accounts that perform too many actions too quickly. This script intentionally uses delays and a per-run action limit, but there is still risk. Use small batches and take breaks.

Only use this on your own account.

How To Use

  1. Open X in your browser and log in.
  2. Open the page you want to clean:
    • Posts: https://x.com/YOUR_USERNAME
    • Replies: https://x.com/YOUR_USERNAME/with_replies
    • Likes: https://x.com/YOUR_USERNAME/likes
  3. Open Developer Tools.
  4. Go to the Console tab.
  5. Paste the script below and press Enter.
  6. Run one of the cleanup commands.

Commands

Delete posts or replies from the current timeline:

XCleaner.deletePosts()

Remove reposts from the current profile timeline:

XCleaner.removeReposts()

Remove likes from the likes page:

XCleaner.removeLikes()

Stop the current run:

XCleaner.stop()

Recommended Cleanup Order

  1. Go to your profile page and run XCleaner.deletePosts().
  2. Go to your replies page and run XCleaner.deletePosts().
  3. Go to your profile page and run XCleaner.removeReposts().
  4. Go to your likes page and run XCleaner.removeLikes().
  5. Repeat in batches until your profile is clean.

By default, the script stops after 80 actions per run. This is intentional.

Script

(() => {
  window.XCleaner?.stop?.();

  const CFG = {
    delayMin: 1300,
    delayMax: 2800,
    afterActionDelay: 2500,
    scrollDelay: 1800,
    maxActionsPerRun: 80,
  };

  let stopped = false;
  let actions = 0;

  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
  const rand = (min, max) => Math.floor(min + Math.random() * (max - min));

  const log = (...a) =>
    console.log("%c[XCleaner]", "color:#1d9bf0;font-weight:bold", ...a);

  const txt = (el) => (el?.innerText || el?.textContent || "").trim();

  function q(sel, root = document) {
    return root.querySelector(sel);
  }

  function qa(sel, root = document) {
    return [...root.querySelectorAll(sel)];
  }

  function visible(el) {
    if (!el) return false;
    const r = el.getBoundingClientRect();
    const s = getComputedStyle(el);
    return r.width > 0 && r.height > 0 && s.display !== "none" && s.visibility !== "hidden";
  }

  async function waitFor(selector, timeout = 5000) {
    const start = Date.now();
    while (!stopped && Date.now() - start < timeout) {
      const el = q(selector);
      if (visible(el)) return el;
      await sleep(150);
    }
    return null;
  }

  async function click(el, label = "element") {
    if (!el || stopped) return false;
    el.scrollIntoView({ block: "center", inline: "center" });
    await sleep(rand(250, 600));
    el.click();
    log("clicked", label);
    await sleep(rand(CFG.delayMin, CFG.delayMax));
    return true;
  }

  async function confirmSheet() {
    const confirm =
      q('[data-testid="confirmationSheetConfirm"]') ||
      qa('[role="button"]').find((b) =>
        /delete|unlike|undo repost|repost|remove/i.test(txt(b))
      );

    if (!confirm) return false;
    return click(confirm, "confirm");
  }

  function getTweets() {
    return qa('article[data-testid="tweet"]').filter(visible);
  }

  async function deleteOnePost(tweet) {
    const menu =
      q('[data-testid="caret"]', tweet) ||
      qa('[role="button"]', tweet).find((b) => txt(b) === "" && visible(b));

    if (!menu) return false;

    await click(menu, "post menu");

    const deleteItem = await waitFor('[role="menuitem"]', 3000);
    const deleteButton = qa('[role="menuitem"], [role="button"]').find((b) =>
      /^delete$/i.test(txt(b))
    );

    if (!deleteItem && !deleteButton) {
      document.body.click();
      return false;
    }

    await click(deleteButton || deleteItem, "delete menu item");

    const ok = await confirmSheet();
    if (ok) {
      actions++;
      log(`deleted post ${actions}/${CFG.maxActionsPerRun}`);
      await sleep(CFG.afterActionDelay);
    }

    return ok;
  }

  async function undoOneRepost(tweet) {
    const unRepost = q('[data-testid="unretweet"]', tweet);
    if (!unRepost) return false;

    await click(unRepost, "undo repost button");

    const confirm =
      await waitFor('[data-testid="unretweetConfirm"]', 3000) ||
      qa('[role="menuitem"], [role="button"]').find((b) =>
        /undo repost/i.test(txt(b))
      );

    if (!confirm) {
      document.body.click();
      return false;
    }

    await click(confirm, "confirm undo repost");
    actions++;
    log(`removed repost ${actions}/${CFG.maxActionsPerRun}`);
    await sleep(CFG.afterActionDelay);
    return true;
  }

  async function unlikeOnePost(tweet) {
    const unlike = q('[data-testid="unlike"]', tweet);
    if (!unlike) return false;

    await click(unlike, "unlike");
    actions++;
    log(`removed like ${actions}/${CFG.maxActionsPerRun}`);
    await sleep(CFG.afterActionDelay);
    return true;
  }

  async function runMode(mode) {
    stopped = false;
    actions = 0;

    log(`started mode: ${mode}`);
    log("type XCleaner.stop() to stop");

    while (!stopped && actions < CFG.maxActionsPerRun) {
      let didAction = false;
      const tweets = getTweets();

      for (const tweet of tweets) {
        if (stopped || actions >= CFG.maxActionsPerRun) break;

        if (mode === "delete-posts") {
          didAction = await deleteOnePost(tweet);
        }

        if (mode === "remove-reposts") {
          didAction = await undoOneRepost(tweet);
        }

        if (mode === "remove-likes") {
          didAction = await unlikeOnePost(tweet);
        }

        if (didAction) break;
      }

      if (!didAction) {
        window.scrollBy(0, Math.floor(window.innerHeight * 0.85));
        await sleep(CFG.scrollDelay);
      }
    }

    log(`finished or paused. actions this run: ${actions}`);
  }

  window.XCleaner = {
    deletePosts: () => runMode("delete-posts"),
    removeReposts: () => runMode("remove-reposts"),
    removeLikes: () => runMode("remove-likes"),
    stop: () => {
      stopped = true;
      log("stopping...");
    },
  };

  log("loaded.");
  log("Use:");
  log("XCleaner.deletePosts()    // run on your profile / with_replies pages");
  log("XCleaner.removeReposts()  // run on your profile page");
  log("XCleaner.removeLikes()    // run on your /likes page");
  log("XCleaner.stop()           // stop anytime");
})();

Configuration

You can edit these values near the top of the script:

const CFG = {
  delayMin: 1300,
  delayMax: 2800,
  afterActionDelay: 2500,
  scrollDelay: 1800,
  maxActionsPerRun: 80,
};

Lower delays may be faster, but they increase the risk of rate limits or account challenges. Larger delays are safer for long cleanup sessions.

Troubleshooting

If nothing happens, make sure you are on the correct page and that the timeline has loaded.

If the script stops after scrolling, X may not have loaded more posts yet. Scroll manually, wait a few seconds, and run the command again.

If X changes its interface, selectors such as data-testid="tweet" or data-testid="unlike" may need updating.

Disclaimer

This project is provided as-is for personal account management. You are responsible for any actions taken on your account, including permanent deletion of posts and possible X rate limits or account restrictions.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors